diff --git a/docs/BUG_HISTORY.md b/docs/BUG_HISTORY.md index 590caade..61a421b7 100644 --- a/docs/BUG_HISTORY.md +++ b/docs/BUG_HISTORY.md @@ -7456,3 +7456,19 @@ - 相关记录:BUG-453、BUG-463、BUG-472 - 复发自:BUG-472(occupation 移出 blocking 后,deliveryCapability 仍用 coverageComplete 挡采用) - 修复版本:待发布 + +## BUG-485 | 生时校正开场正文与问题槽同时提问 + +- 状态:resolved +- 首次发现:2026-09-02 +- 最近更新:2026-09-02 +- 影响面:POST `/api/rectification/agent` opening、`openingBrief`、`.rectification-question-slot__prompt`、口述采集直播界面 +- 用户现象:开场助手气泡已经邀请用户说一件经历(例如哪年上大学、哪年换工作),气泡下方问题槽再用一句几乎相同的采集题再问一遍。用户发出「2016 年 9 月上大学」后,分析中的时间线下面仍挂着同一句旧题。 +- 触发条件:新 Case `action=opening` 且 `current_question.kind=collect_spoken`;或在该采集题仍有效时提交下一条自由文本。 +- 根因:两条规则并行。BUG-471 规定口述题干真源是 GET `current_question.prompt`,必须在问题槽可见。开场模型却在 `current_question` 落库之前生成正文,opening brief 还写「当前可询问范围」,于是正文自己提问。轮末 `persistNextInterviewIfIdle` 再写入 `GENERIC_COLLECT_QUESTION`。`shouldPersistFocusPromptTurn` 只挡住第二条助手消息,挡不住问题槽。采集题槽也没有选择题那样的 `!busy` 门,分析中仍渲染旧题。 +- 修复:开场流开始前先 `persistNextInterviewIfIdle`,让 read-case 能看到已持久化题干。opening brief 与系统指令改为:题干由问题槽承担,正文只打招呼、不得提问或举大学/工作/搬家的例子。问题槽口述题干与输入框 describedBy 在 `busy` / 重生成时隐藏,与选择卡一致。不 bump Skill,不改 Python 引擎,不放宽确认门。 +- 验证:`rectification-spoken-collect` 锁开场先持久化、brief 不再写「可询问范围」、槽位 `showCollectSpokenPrompt` 含 `!busy`;`rectification-v9-agent` 锁 opening brief;`agent-voice-copy-contract` 锁开场不得举例提问。 +- 防复发:口述采集直播题干只由问题槽展示。开场正文不得并行提问。采集槽与选择卡一样在忙碌时隐藏。不得用正文字符串判断「有没有问过」。不得把题干再追加成第二条 opening 消息。 +- 相关记录:BUG-441、BUG-449、BUG-461、BUG-469、BUG-471 +- 复发自:BUG-469(opening 正文仍提问)与 BUG-471(槽位必须可见题干)同时生效 +- 修复版本:待发布 diff --git a/frontend/src/app/api/rectification/agent/route.ts b/frontend/src/app/api/rectification/agent/route.ts index 82559768..9c23ea65 100644 --- a/frontend/src/app/api/rectification/agent/route.ts +++ b/frontend/src/app/api/rectification/agent/route.ts @@ -662,6 +662,19 @@ export async function POST(request: Request) { }; try { + if (action === "opening") { + try { + await persistNextInterviewIfIdle({ + accounting: accounting as never, + userId, + caseId, + }); + } catch (error) { + console.warn( + `[rectification-v9] persist opening interview failed case=${caseId} reason=${error instanceof Error ? error.name : "Unknown"}`, + ); + } + } const result = await runV9AgentTurn({ userId, caseId, diff --git a/frontend/src/components/rectification-agentic-chat.tsx b/frontend/src/components/rectification-agentic-chat.tsx index 71bbf121..e2b20830 100644 --- a/frontend/src/components/rectification-agentic-chat.tsx +++ b/frontend/src/components/rectification-agentic-chat.tsx @@ -1047,6 +1047,13 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) { const collectSpokenPrompt = currentQuestion?.kind === "collect_spoken" ? currentQuestion.prompt : null; + const showCollectSpokenPrompt = Boolean( + collectSpokenPrompt + && !showLiveChoiceCard + && !readonly + && !busy + && regeneratingMessageKey === null, + ); const resumableCase = caseStatus !== null && isResumableStatus(caseStatus); const showMissingQuestion = Boolean( caseSnapshotLoaded @@ -1199,7 +1206,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) { onStop={submitStop} /> )} - {collectSpokenPrompt && !showLiveChoiceCard && !readonly && ( + {showCollectSpokenPrompt && (
{collectSpokenPrompt}
@@ -1232,7 +1239,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) { onStop={submitStop} /> )} - {collectSpokenPrompt && !showLiveChoiceCard && !readonly && ( + {showCollectSpokenPrompt && ({collectSpokenPrompt}
@@ -1277,7 +1284,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) { )}