fix: classify code agent timeout failures
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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 &&
|
||||
|
||||
Reference in New Issue
Block a user