fix: classify code agent timeout failures

This commit is contained in:
Code Queue Review
2026-05-23 11:12:31 +00:00
parent 4325d0a09b
commit ff99b30118
10 changed files with 449 additions and 29 deletions
+100 -1
View File
@@ -2,7 +2,17 @@ const trustedLiveProviders = new Set(["openai-responses", "codex-cli", "codex-re
const trustedRunnerProviders = new Set(["codex-readonly-runner"]);
const readonlySessionCapabilityLevels = new Set(["read-only-tools", "read-only-session-tools"]);
const untrustedProviderPattern = /\b(?:echo|mock|fixture|stub|sample)\b/iu;
const blockedCodeAgentUiLabels = new Set(["发送失败", "服务受阻", "BLOCKED 凭证缺口"]);
const blockedCodeAgentUiLabels = new Set([
"发送失败",
"服务受阻",
"等待超时",
"Provider 不可用",
"Runner 忙碌",
"Session 受阻",
"Runner 受阻",
"API 错误",
"BLOCKED 凭证缺口"
]);
const timeoutPattern = /\b(?:timeout|timed out|abort|aborted|超时|请求超过)\b/iu;
export function classifyCodeAgentChatReadiness(payload, { realDevLive = false, httpStatus = null } = {}) {
@@ -345,6 +355,7 @@ export function summarizeCodeAgentPayload(payload, { httpStatus = null, traceId
export function classifyCodeAgentBrowserJourney({ responseSummary, httpOk = false, httpStatus = null, ui = null } = {}) {
const providerBlock = classifyCodeAgentProviderBlock(responseSummary, { httpStatus });
const runtimeBlock = classifyCodeAgentRuntimeBlock(responseSummary, { httpStatus });
const uiCompleted = ui?.agentChatStatus === "DEV-LIVE 回复" || ui?.agentChatStatus === "已回复" || ui?.completedMessageVisible === true;
const uiFailed = ui?.failedMessageVisible === true || blockedCodeAgentUiLabels.has(ui?.agentChatStatus);
const evidence = inspectSanitizedCompletionEvidence(responseSummary);
@@ -363,6 +374,10 @@ export function classifyCodeAgentBrowserJourney({ responseSummary, httpOk = fals
};
}
if (runtimeBlock.blocked) {
return runtimeBlock;
}
if (providerBlock.blocked) {
return {
status: "blocked",
@@ -387,6 +402,63 @@ export function classifyCodeAgentBrowserJourney({ responseSummary, httpOk = fals
};
}
export function classifyCodeAgentRuntimeBlock(payload, { httpStatus = null } = {}) {
const errorCode = stringOrNull(payload?.error?.code);
const payloadStatus = stringOrNull(payload?.status);
if (errorCode === "session_busy") {
return {
blocked: true,
status: "blocked",
blocker: "runner-busy",
category: "runner_busy",
reason: "Code Agent read-only runner session is already busy; the UI must keep the input retryable."
};
}
if (["session_expired", "session_reuse_conflict"].includes(errorCode)) {
return {
blocked: true,
status: "blocked",
blocker: "session-blocked",
category: "session_blocked",
reason: `Code Agent session is blocked by ${errorCode}; fallback must not be reported as a Codex session.`
};
}
if (["runner_unavailable", "tool_unavailable", "skills_unavailable", "security_blocked"].includes(errorCode)) {
return {
blocked: true,
status: "blocked",
blocker: "runner-blocked",
category: "runner_blocked",
reason: `Code Agent read-only runner returned ${errorCode}; this is blocked runner evidence, not a completed fallback.`
};
}
if (["invalid_params", "parse_error", "request_failed", "cloud_api_proxy_failed", "upstream_unavailable"].includes(errorCode)) {
return {
blocked: true,
status: "blocked",
blocker: "api-error",
category: "api_error",
reason: `Code Agent API returned ${errorCode}; the request is retryable and must remain distinguishable from provider failure.`
};
}
if (payloadStatus === "failed" && errorCode && errorCode !== "provider_unavailable") {
return {
blocked: true,
status: "blocked",
blocker: "api-error",
category: "api_error",
reason: `Code Agent returned failed status with ${errorCode}.`
};
}
return { blocked: false };
}
export function classifyCodeAgentBrowserFailure(error, { traceId = null } = {}) {
const message = error instanceof Error ? error.message : String(error ?? "");
const observedTraceId = traceId ?? error?.traceId ?? null;
@@ -399,6 +471,24 @@ export function classifyCodeAgentBrowserFailure(error, { traceId = null } = {})
reason: message
};
}
if (/session[_ -]?busy|runner busy|already busy/iu.test(message)) {
return {
status: "blocked",
blocker: "runner-busy",
category: "runner_busy",
chineseSummary: `Runner 忙碌:Code Agent 正在处理上一轮请求,可稍后重试${observedTraceId ? `traceId=${observedTraceId}` : ""}`,
reason: message
};
}
if (/session[_ -]?(expired|blocked|reuse_conflict)|conversation.*session/iu.test(message)) {
return {
status: "blocked",
blocker: "session-blocked",
category: "session_blocked",
chineseSummary: `Session 受阻:Code Agent 会话无法复用或已过期${observedTraceId ? `traceId=${observedTraceId}` : ""}`,
reason: message
};
}
if (/provider|upstream|openai|codex|502|503|504|429|provider_unavailable/iu.test(message)) {
return {
status: "blocked",
@@ -408,6 +498,15 @@ export function classifyCodeAgentBrowserFailure(error, { traceId = null } = {})
reason: message
};
}
if (/api|http \d{3}|invalid_params|parse_error|request_failed|cloud_api_proxy_failed/iu.test(message)) {
return {
status: "blocked",
blocker: "api-error",
category: "api_error",
chineseSummary: `API 错误:Code Agent 接口返回失败,输入应保留可重试${observedTraceId ? `traceId=${observedTraceId}` : ""}`,
reason: message
};
}
return {
status: "blocked",
blocker: "browser-runtime",
+14 -5
View File
@@ -223,7 +223,16 @@ const requiredCodeAgentEvidenceTerms = Object.freeze([
"message-evidence-chip"
]);
const blockedCodeAgentUiLabels = Object.freeze(["服务受阻", "BLOCKED 凭证缺口"]);
const blockedCodeAgentUiLabels = Object.freeze([
"服务受阻",
"等待超时",
"Provider 不可用",
"Runner 忙碌",
"Session 受阻",
"Runner 受阻",
"API 错误",
"BLOCKED 凭证缺口"
]);
const forbiddenWritePatterns = Object.freeze([
/callRpc\(\s*["']hardware\./u,
@@ -507,7 +516,7 @@ function runStaticSmoke() {
evidence: [
"DEV-LIVE 回复",
"SOURCE 回复",
"发送失败",
"等待超时/API 错误/发送失败",
"服务受阻 or legacy BLOCKED 凭证缺口",
"message-evidence/details/chips"
]
@@ -2047,7 +2056,7 @@ function trustedRecordGroups({ html, app, styles }) {
/state\.liveSurface/u.test(app) &&
/renderRecords\(state\.liveSurface\)/u.test(app) &&
/traceId: error\.traceId \|\| traceId/u.test(app) &&
/error\.traceId = traceId/u.test(app) &&
/error\.traceId = (?:traceId|response\.data\?\.traceId \|\| traceId)/u.test(app) &&
requiredTrustedRecordTerms.every((term) => source.includes(term)) &&
/\.record-group\s*\{/u.test(styles) &&
/\.record-group-title\s*\{/u.test(styles) &&
@@ -3623,7 +3632,7 @@ async function runLocalAgentFixtureSmoke({
await page.locator("#command-input").fill(codeAgentE2ePrompts[0].text);
await page.locator("#command-send").click();
await page.waitForFunction(
() => document.querySelector("#agent-chat-status")?.textContent?.trim() === "发送失败",
() => ["等待超时", "发送失败"].includes(document.querySelector("#agent-chat-status")?.textContent?.trim() ?? ""),
null,
{ timeout: 8000 }
);
@@ -3641,7 +3650,7 @@ async function runLocalAgentFixtureSmoke({
responseSummary = response ? sanitizeAgentChatBody(responseBody, { httpStatus: response.status() }) : null;
const pass = expectTimeout
? (
ui.agentChatStatus === "发送失败" &&
["等待超时", "发送失败"].includes(ui.agentChatStatus) &&
ui.failedMessageVisible &&
ui.retryInputPreserved &&
ui.failedMessageHasTrace &&