修复 ccweb MCP 启动与重载并重新打包

This commit is contained in:
shiyue
2026-09-12 21:13:30 +08:00
parent 965aefe9a4
commit d9db66ca8f
10 changed files with 217 additions and 28 deletions

View File

@@ -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');