fix: 压制 agentMessage lifecycle 噪声 (#72)

Co-authored-by: Codex <codex@pikas.tech>
This commit is contained in:
Lyon
2026-06-02 10:59:59 +08:00
committed by GitHub
parent e1c0fb5245
commit aa0bd64714
3 changed files with 4 additions and 2 deletions
+1 -1
View File
@@ -38,7 +38,7 @@ Backend adapter 的第一阶段实现应吸收 HWLAB v0.2 已验证的 Codex std
| --- | --- | --- |
| Codex app-server JSON-RPC stdio | `internal/cloud/codex-stdio-session.ts``internal/cloud/codex-stdio-session-turn-state.ts` | 支持 `initialize``thread/start``thread/resume``turn/start`,并处理 app-server client request;未知请求要记录 unsupported error,不能静默等待。 |
| completed 判定 | `docs/reference/code-agent-chat-readiness.md` | 只有 Codex turn terminal completed 且 assistant reply 可聚合时才输出 completedassistant delta、item completed、stdout 或 transport close 不能单独完成。 |
| assistant stream 和 trace | `internal/cloud/code-agent-trace-store.ts``internal/cloud/codex-stdio-session-turn-state.ts` | assistant delta 只能作为 stream/progress 证据;每个非空 completed `agentMessage` item 必须输出一个 `assistant_message` event,保留 `itemId` 和顺序;最终 result reply 必须优先来自最后一个 completed `agentMessage` item,不能把 commentary/progress delta 与 final response 直接串接。event 必须保留 `threadId``turnId`、session 摘要和 redacted backend metadata。 |
| assistant stream 和 trace | `internal/cloud/code-agent-trace-store.ts``internal/cloud/codex-stdio-session-turn-state.ts` | assistant delta 只能作为 stream/progress 证据;每个非空 completed `agentMessage` item 必须输出一个 `assistant_message` event,保留 `itemId` 和顺序;`item/agentMessage:started``item/agentMessage:completed` 这类 lifecycle 不得额外持久化为 `backend_status`,避免同一消息在 Web/CLI trace 中重复渲染;最终 result reply 必须优先来自最后一个 completed `agentMessage` item,不能把 commentary/progress delta 与 final response 直接串接。event 必须保留 `threadId``turnId`、session 摘要和 redacted backend metadata。 |
| command/tool output bounded | `docs/reference/code-agent-chat-readiness.md``web/hwlab-cloud-web/app-trace.ts` | `tool_call``command_output` 必须记录状态、摘要、字节数、截断标记;完整大输出只能通过后续 log/artifact 引用。 |
| provider/profile 隔离 | `internal/cloud/code-agent-contract.ts` | `codex``deepseek``minimax-m3` 共享同一 backend kind,但必须使用 profile-scoped SecretRef、model/base-url/config 和 writable runtime home。 |
| Secret redaction | `internal/cloud/code-agent-trace-store.ts` | `OPENAI_API_KEY`、auth/config、token、password、kubeconfig、URL credential 不得进入 event、result、log 或 health。 |
+1 -1
View File
@@ -600,7 +600,7 @@ function normalizeCodexNotification(message: JsonRecord, suppressed: SuppressedN
const text = method === "item/completed" ? agentMessageText(item) : "";
const completedAssistantMessage = text.trim().length > 0 ? { itemId: itemId ?? null, text } : undefined;
return {
events: [{ type: "backend_status", payload: { phase: method === "item/completed" ? "item/agentMessage:completed" : "item/agentMessage:started", itemId, textBytes: Buffer.byteLength(text, "utf8") } }],
events: [],
...(completedAssistantMessage ? { completedAssistantMessage } : {}),
};
}
+2
View File
@@ -92,6 +92,7 @@ const selfTest: SelfTestCase = async (context) => {
const turnCompletedIndex = finalMessageItems.findIndex((event) => event.type === "backend_status" && eventPayload(event).phase === "turn/completed");
assert.ok(progressMessageIndex >= 0 && progressMessageIndex < turnCompletedIndex, "progress agentMessage should be emitted before turn/completed instead of being delayed to final response");
assert.ok(finalMessageIndex >= 0 && finalMessageIndex < turnCompletedIndex, "final agentMessage should be emitted before turn/completed instead of being delayed to final response");
assert.equal(finalMessageItems.some((event) => event.type === "backend_status" && String(eventPayload(event).phase ?? "").startsWith("item/agentMessage:")), false, "agentMessage lifecycle must not be persisted as backend_status noise");
const staleThread = await createStaleThreadRun(client, context);
const staleThreadResult = await runOnce({
@@ -144,6 +145,7 @@ const selfTest: SelfTestCase = async (context) => {
assert.equal(noisyItems.some((event) => event.type === "tool_call" && eventPayload(event).type === "reasoning"), false, "reasoning items must not be persisted as tool_call");
assert.ok(noisyItems.some((event) => event.type === "tool_call" && eventPayload(event).method === "item/started" && eventPayload(event).type === "commandExecution"), "real commandExecution tool call should remain visible");
assert.equal(noisyItems.some((event) => event.type === "tool_call" && eventPayload(event).type !== "commandExecution"), false, "non-commandExecution item lifecycle must not be persisted as tool_call");
assert.equal(noisyItems.some((event) => event.type === "backend_status" && String(eventPayload(event).phase ?? "").startsWith("item/agentMessage:")), false, "agentMessage lifecycle must not be persisted as backend_status noise");
assert.equal(noisyPhases.includes("backend-turn-running"), false, "backend progress ticks must be summarized instead of persisted as durable trace events");
const noisyFinished = noisyItems.find((event) => event.type === "backend_status" && eventPayload(event).phase === "backend-turn-finished");
assert.equal(eventPayload(noisyFinished ?? { payload: {} }).progressEventsPrinted, false, "backend-turn-finished must declare progress ticks were not printed as events");