fix: 收敛公共 trace result 契约

This commit is contained in:
Codex Agent
2026-06-06 14:20:57 +08:00
parent 494d3ed3bb
commit c58a7fff6c
5 changed files with 26 additions and 2 deletions
@@ -46,6 +46,8 @@
5. 写回内存缓存和 session 派生证据时,只能写刚由 command result 生成的 payload;旧 `finalResponse`、旧 `traceSummary` 和旧 `traceResults` 不得参与 merge 覆盖。
6. 如果 command registry 找不到该 `traceId` 对应 command,返回结构化错误,例如 `agentrun_trace_command_not_found`;不得回退到旧缓存、top-level snapshot、latest trace 或其他 command result。
Completed result 响应必须把 AgentRun command result 的终态证据公开为 `terminalEvidence`,并与 `agentRun.commandId``finalResponse.traceId` 保持一致。`/result``/trace` 响应不得导出 `fallback` 字段;降级、缺失或过期只能用 `status``traceStatus``retention``error``terminalEvidence` 表达,避免旧 fallback contract 重新成为并行 final response 路径。
Running 轮询可以返回 `202` 和当前 runnerTrace;这只是“尚未 terminal”的轮询语义,不是 final fallback。Completed / failed / canceled / blocked 等 terminal 状态必须经过同一 command registry/result 路径确认。
## Trace 读取与渲染流程
-1
View File
@@ -455,7 +455,6 @@ function decorateChatSessionLifecycle(payload) {
sessionSummary: summary,
sessionReused: summary.reused,
isNewSession: summary.newSession,
fallbackUsed: summary.fallback,
degraded: summary.degraded,
degradationReason: summary.reason,
degradationReasonCode: summary.reasonCode
@@ -112,7 +112,6 @@ export function codeAgentSessionLifecycleSummary(input = {}) {
busy: status === "busy",
requiresNewSession,
degraded,
fallback: fallbackUsed,
unsupported: input.unsupported === true,
reasonCode: reasonCode ?? null,
reason: reason ?? null,
+8
View File
@@ -1990,9 +1990,13 @@ test("cloud api repairs historical same-session AgentRun trace after lastTraceId
assert.equal(secondResult.status, "completed");
assert.equal(secondResult.agentRun.commandId, secondCommandId);
assert.equal(secondResult.agentRun.traceId, secondTraceId);
assert.equal(secondResult.terminalEvidence.available, true);
assert.equal(secondResult.terminalEvidence.agentRun.commandId, secondCommandId);
assert.equal(secondResult.terminalEvidence.finalResponse.traceId, secondTraceId);
assert.equal(secondResult.finalResponse.traceId, secondTraceId);
assert.match(secondText, //u);
assert.doesNotMatch(secondText, / HWPOD /u);
assert.equal(secondText.includes('"fallback"'), false);
assert.ok(calls.some((call) => call.path === `/api/v1/runs/${runId}/commands/${secondCommandId}/result`));
assert.equal(ownerSessions.get("ses_issue955_historical").session.traceResults[secondTraceId].agentRun.commandId, secondCommandId);
assert.equal(ownerSessions.get("ses_issue955_historical").session.traceResults[secondTraceId].finalResponse.text, secondFinalText);
@@ -2109,10 +2113,14 @@ test("cloud api result polling repairs polluted completed AgentRun memory cache
assert.equal(body.status, "completed");
assert.equal(body.agentRun.commandId, secondCommandId);
assert.equal(body.agentRun.traceId, secondTraceId);
assert.equal(body.terminalEvidence.available, true);
assert.equal(body.terminalEvidence.agentRun.commandId, secondCommandId);
assert.equal(body.terminalEvidence.finalResponse.traceId, secondTraceId);
assert.equal(body.reply.content, secondFinalText);
assert.match(text, //u);
assert.match(text, /Keil MDK/u);
assert.doesNotMatch(text, / HWPOD /u);
assert.equal(text.includes('"fallback"'), false);
assert.ok(calls.some((call) => call.path === `/api/v1/runs/${runId}/commands`));
assert.ok(calls.some((call) => call.path === `/api/v1/runs/${runId}/commands/${secondCommandId}/result`));
assert.equal(calls.some((call) => call.path === `/api/v1/runs/${runId}/commands/${firstCommandId}/result`), false);
+16
View File
@@ -2088,14 +2088,30 @@ export function createCodeAgentChatResultStore({ maxResults = 256 } = {}) {
function compactCodeAgentChatResultPayload(payload, options = {}) {
if (!payload || typeof payload !== "object") return payload;
const limit = resultTraceEventLimit(options);
const terminalEvidence = agentRunTerminalTraceEvidence(payload, payload.traceId);
return {
...payload,
...(terminalEvidence ? { terminalEvidence: terminalEvidencePayload(terminalEvidence) } : {}),
...(payload.runnerTrace && typeof payload.runnerTrace === "object"
? { runnerTrace: compactRunnerTraceForResult(payload.runnerTrace, limit) }
: {})
};
}
function terminalEvidencePayload(evidence) {
return {
available: true,
source: evidence.source,
conversationId: evidence.conversationId,
sessionId: evidence.sessionId,
threadId: evidence.threadId,
agentRun: evidence.agentRun,
traceSummary: evidence.traceSummary,
finalResponse: evidence.finalResponse,
valuesPrinted: false
};
}
function compactRunnerTraceForResult(runnerTrace, limit = DEFAULT_CODE_AGENT_RESULT_TRACE_EVENT_LIMIT) {
if (!runnerTrace || typeof runnerTrace !== "object") return runnerTrace;
const events = Array.isArray(runnerTrace.events) ? runnerTrace.events : [];