修复 ccweb MCP 启动与重载并重新打包
This commit is contained in:
@@ -1133,24 +1133,29 @@ function handleRequest(message) {
|
||||
}
|
||||
if (method === 'config/mcpServer/reload') {
|
||||
mcpReloadCount += 1;
|
||||
send({
|
||||
method: 'mcpServer/startupStatus/updated',
|
||||
params: {
|
||||
server: 'ccweb',
|
||||
state: 'starting',
|
||||
message: 'ccweb MCP starting',
|
||||
threadId: null,
|
||||
},
|
||||
});
|
||||
send({
|
||||
method: 'mcpServer/startupStatus/updated',
|
||||
params: {
|
||||
name: 'ccweb',
|
||||
status: 'ready',
|
||||
message: 'ccweb MCP ready CC_WEB_MCP_TOKEN=mock-secret-token',
|
||||
threadId: null,
|
||||
},
|
||||
});
|
||||
const suppressReloadStatus = process.env.MOCK_CODEX_APP_MCP_RELOAD_TIMEOUT_ON_SECOND === '1'
|
||||
&& mcpReloadCount >= 2;
|
||||
if (!suppressReloadStatus) {
|
||||
send({
|
||||
method: 'mcpServer/startupStatus/updated',
|
||||
params: {
|
||||
server: 'ccweb',
|
||||
state: 'starting',
|
||||
message: 'ccweb MCP starting',
|
||||
// 模拟 app-server 的全局 reload 通知可能携带其他线程 ID。
|
||||
threadId: 'mock-reload-unrelated-thread',
|
||||
},
|
||||
});
|
||||
send({
|
||||
method: 'mcpServer/startupStatus/updated',
|
||||
params: {
|
||||
name: 'ccweb',
|
||||
status: 'ready',
|
||||
message: 'ccweb MCP ready CC_WEB_MCP_TOKEN=mock-secret-token',
|
||||
threadId: 'mock-reload-unrelated-thread',
|
||||
},
|
||||
});
|
||||
}
|
||||
send({ id, result: { reloaded: true, reloadCount: mcpReloadCount } });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1613,6 +1613,32 @@ function assertFrontendMcpReloadContract() {
|
||||
assert(source.includes('MCP 启动失败'), 'Frontend should expose a failed startup toast');
|
||||
}
|
||||
|
||||
function assertCcwebMcpRecoveryContract() {
|
||||
const serverSource = fs.readFileSync(SERVER_PATH, 'utf8');
|
||||
const runtimeSource = fs.readFileSync(path.join(REPO_DIR, 'lib', 'agent-runtime.js'), 'utf8');
|
||||
const mockSource = fs.readFileSync(MOCK_CODEX_APP_SERVER, 'utf8');
|
||||
assert(
|
||||
serverSource.includes('CC_WEB_CODEX_APP_MCP_STARTUP_TIMEOUT_SEC')
|
||||
&& serverSource.includes('CODEX_APP_MCP_STARTUP_TIMEOUT_SEC,')
|
||||
&& serverSource.includes('CC_WEB_CODEX_APP_MCP_RELOAD_STATUS_WAIT_MS')
|
||||
&& serverSource.includes('CODEX_APP_MCP_RELOAD_STATUS_WAIT_MS,')
|
||||
&& serverSource.includes('allowThreadMismatch: true'),
|
||||
'ccweb MCP recovery should expose configurable startup/reload windows and tolerate global reload thread IDs'
|
||||
);
|
||||
assert(
|
||||
serverSource.includes('codex_app_mcp_reload_timeout')
|
||||
&& serverSource.includes("creationStatus: 'created_but_initial_message_failed'")
|
||||
&& serverSource.includes('mcpStatus: buildCodexAppMcpStatusSummary'),
|
||||
'ccweb MCP recovery should persist timeout failure and expose creation/MCP status'
|
||||
);
|
||||
assert(
|
||||
runtimeSource.includes('CC_WEB_CODEX_APP_MCP_STARTUP_TIMEOUT_SEC')
|
||||
&& runtimeSource.includes('mcp_servers.ccweb.startup_timeout_sec=${CCWEB_MCP_STARTUP_TIMEOUT_SEC}'),
|
||||
'Legacy Codex runtime should use the same configurable ccweb MCP startup timeout'
|
||||
);
|
||||
assert(mockSource.includes('mock-reload-unrelated-thread'), 'MCP reload regression fixture should cover unrelated notification thread ids');
|
||||
}
|
||||
|
||||
function assertFrontendSubagentCardMetadataContract() {
|
||||
const source = fs.readFileSync(PUBLIC_APP_PATH, 'utf8');
|
||||
const styleSource = fs.readFileSync(PUBLIC_STYLE_PATH, 'utf8');
|
||||
@@ -6637,6 +6663,7 @@ async function main() {
|
||||
assertFrontendMarkdownLinkContract();
|
||||
assertMockCodexAppPromptUserNotTextTriggered();
|
||||
assertFrontendMcpReloadContract();
|
||||
assertCcwebMcpRecoveryContract();
|
||||
assertPlanListProgressContract();
|
||||
assertFrontendSubagentCardMetadataContract();
|
||||
assertCodexAppRuntimeSubAgentActivityContract();
|
||||
@@ -6793,6 +6820,8 @@ async function main() {
|
||||
CLAUDE_PATH: MOCK_CLAUDE,
|
||||
CODEX_PATH: MOCK_CODEX_APP_SERVER,
|
||||
CC_WEB_CODEX_TRANSIENT_RETRY_BASE_DELAY_MS: '100',
|
||||
CC_WEB_CODEX_APP_MCP_RELOAD_STATUS_WAIT_MS: '1000',
|
||||
MOCK_CODEX_APP_MCP_RELOAD_TIMEOUT_ON_SECOND: '1',
|
||||
}, async () => {
|
||||
await assertWsUpgradeRejected(port, '/not-ws');
|
||||
|
||||
@@ -8212,6 +8241,11 @@ async function main() {
|
||||
Number(reloadAfterTracking.result?.reloadCount || 0) === baselineMcpReloadCount + 1,
|
||||
'Task tracking changes must not trigger an implicit MCP reload; only the explicit reload request should count'
|
||||
);
|
||||
assert(reloadAfterTracking.reloadTimedOut === true, 'MCP reload without a final startup notification should report a timeout');
|
||||
assert(reloadAfterTracking.mcpStatus?.status === 'failed', 'MCP reload timeout should converge to failed instead of pending');
|
||||
storedCodexApp = JSON.parse(fs.readFileSync(path.join(sessionsDir, `${codexAppSession.sessionId}.json`), 'utf8'));
|
||||
assert(storedCodexApp.codexAppMcpStartupStatus?.servers?.ccweb?.status === 'failed', 'MCP reload timeout should persist failed status for retry');
|
||||
assert(/可稍后再次重试/.test(storedCodexApp.codexAppMcpStartupStatus?.servers?.ccweb?.message || ''), 'MCP reload timeout should explain that retry is available');
|
||||
|
||||
ws.send(JSON.stringify({ type: 'composer_suggestions', requestId: 'reg-codexapp-prompt-user-mcp', trigger: '/', query: 'prompt_user', sessionId: codexAppSession.sessionId, agent: 'codexapp' }));
|
||||
const codexAppPromptUserMcpComposer = await nextMessage(messages, ws, (msg) => msg.type === 'composer_suggestions' && msg.requestId === 'reg-codexapp-prompt-user-mcp');
|
||||
|
||||
Reference in New Issue
Block a user