diff --git a/docs/BUG_HISTORY.md b/docs/BUG_HISTORY.md index 61ac2ab2..e01c634b 100644 --- a/docs/BUG_HISTORY.md +++ b/docs/BUG_HISTORY.md @@ -5643,6 +5643,22 @@ - 复发自:BUG-351(一件带日期事件后冲突探针插队,早于采用门) - 修复版本:待发布 +## BUG-387 | staging publish 的 next build 被口语绑定 chunk 类型挡住 + +- 状态:resolved +- 首次发现:2026-08-26 +- 最近更新:2026-08-26 +- 影响面:`openQuestionPromptFromToolResult`、`runV9AgentTurn`、`deploy/railway-web.Dockerfile` 的 `RUN npm run build` +- 用户现象:向 `staging` 推送 `97b02aef` 后 quality gate run `2080` 的 validate 通过,publish 在 web 镜像 `next build` 失败。没有 dispatch `Deploy staging`。公网仍为上一成功 SHA。 +- 触发条件:staging push 的 publish 镜像构建跑 `next build`。validate 对 staging push 跳过 production build。 +- 根因:运行器把流式 chunk 传给 `openQuestionPromptFromToolResult`,函数把 `payload` 收成 `{ result?: unknown; output?: unknown }`。流式 chunk 的 payload 是 `toolName` / `text` / `args`,TypeScript 判为 TS2345。日志末尾 Import traces 只是动态文件访问警告。 +- 修复:函数改收 `payload?: unknown`,再从 result/output/object 读已盖戳题干。不改 Skill `10.0.11`。 +- 验证:`npx tsc --noEmit` 不得再报 `agent-run.ts` 的 TS2345;`frontend/tests/rectification-v9-stream.test.ts`。 +- 防复发:改 `runV9AgentTurn` 流式 chunk 形状后必须跑 `npx tsc --noEmit` 或等价的 `next build`。staging push 的类型回归仍只在 publish Docker 暴露。 +- 相关记录:BUG-309、BUG-365、BUG-371、BUG-384 +- 复发自:BUG-309(validate 跳过 `next build`,publish 才暴露类型错误);BUG-384 口语绑定收窄了 payload +- 修复版本:待发布 + ## BUG-379 | 生时纠正已记入学后仍编造高考年并再问入学 - 状态:resolved diff --git a/frontend/src/lib/rectification-agentic/v9/turn-narration.ts b/frontend/src/lib/rectification-agentic/v9/turn-narration.ts index 71a1a113..2db2f29a 100644 --- a/frontend/src/lib/rectification-agentic/v9/turn-narration.ts +++ b/frontend/src/lib/rectification-agentic/v9/turn-narration.ts @@ -48,12 +48,15 @@ export function readOpenQuestionPrompt(value: unknown): string | null { export function openQuestionPromptFromToolResult(chunk: { type?: string; - payload?: { result?: unknown; output?: unknown }; + payload?: unknown; object?: unknown; }): string | null { if (chunk.type !== "tool-result") return null; - return readOpenQuestionPrompt(chunk.payload?.result) - ?? readOpenQuestionPrompt(chunk.payload?.output) + const payload = chunk.payload && typeof chunk.payload === "object" && !Array.isArray(chunk.payload) + ? chunk.payload as Record + : null; + return readOpenQuestionPrompt(payload?.result) + ?? readOpenQuestionPrompt(payload?.output) ?? readOpenQuestionPrompt(chunk.object) ?? readOpenQuestionPrompt(chunk.payload); } diff --git a/frontend/tests/rectification-agentic-entry.test.ts b/frontend/tests/rectification-agentic-entry.test.ts index 9a3d8ee4..fa46882f 100644 --- a/frontend/tests/rectification-agentic-entry.test.ts +++ b/frontend/tests/rectification-agentic-entry.test.ts @@ -245,6 +245,11 @@ test("usage completes or releases without hiding settlement failures", () => { assert.match(run, /if \(!answerText\.trim\(\)\) \{[\s\S]*composeRectificationTurnNarration/); assert.match(run, /bindSpokenToOpenQuestion/); assert.match(run, /openQuestionPromptFromToolResult/); + const narration = readFileSync( + new URL("../src/lib/rectification-agentic/v9/turn-narration.ts", import.meta.url), + "utf8", + ); + assert.match(narration, /payload\?: unknown/); assert.doesNotMatch(run, /splitRectificationSpokenAndThinking/); assert.match(run, /emit\(event: PublicStreamEvent\): Promise \| void;/); assert.match(run, /agentGenerationSettings\(options\.generationModel, \{[\s\S]*thinking: "enabled"/); diff --git a/frontend/tests/rectification-v9-stream.test.ts b/frontend/tests/rectification-v9-stream.test.ts index 56b354e4..9146a173 100644 --- a/frontend/tests/rectification-v9-stream.test.ts +++ b/frontend/tests/rectification-v9-stream.test.ts @@ -1371,7 +1371,7 @@ test("persisted choice prompt replaces a competing model follow-up without a top ); assert.equal(openQuestionPromptFromToolResult({ type: "tool-result", - payload: { result: { open_question: { prompt } } }, + payload: { toolName: "rectification-record-evidence-batch", result: { open_question: { prompt } } }, }), prompt); const { options, emitted } = runOptions({