diff --git a/docs/BUG_HISTORY.md b/docs/BUG_HISTORY.md index e8f3037a..509a15a3 100644 --- a/docs/BUG_HISTORY.md +++ b/docs/BUG_HISTORY.md @@ -7061,7 +7061,7 @@ - 首次发现:2026-09-01 - 最近更新:2026-09-01 - 影响面:POST `/api/rectification/agent` 的 `opening`、`message`、`answer_choice`、`stop_and_review` 推进轮,`decideRectification` 的 probe 耗尽转向,以及服务端问题在 turn 历史中的可见性 -- 用户现象:真实 Case `645ba774-67fb-4d6f-b842-f59e4d97adc0` 已有 7 条证据、3 个领域并答完 6 道区分题后,仍保持正确的 `can_adopt=false`,但 `current_question=null`,界面永久显示等待服务端更新。 +- 用户现象:真实校正会话已有多条证据、多个领域并答完多道区分题后,仍保持正确的 `can_adopt=false`,但 `current_question=null`,界面永久显示等待服务端更新。 - 触发条件:最后一步走 `answer_choice`,或 `message` 命中任一 HTTP 200 确定性早退;同时普通 discriminator 已耗尽、候选分离不足,原决策链直接返回区间而未继续尝试 holdout、带日期收集或 Nakshatra 边界题。 - 根因:BUG-456 只在当时命中的 opening/message 分支附近补了兜底调用,没有建立所有推进轮共享的后置条件;后来新增或既有的 structured choice 与多个消息早退仍可绕过。选择题历史另以「接下来请点选下面这一问。」代替服务端 focus prompt,导致回看时看不到真实问题。 - 修复:所有成功的推进轮统一经过 awaited 公共出口闸;immediate 路径必须等 focus 持久化完成后才返回 HTTP 200,stream 路径必须在 `done` 前完成。`read_only` 显式无副作用。闸门最终重读 Case 并验证 `current_question || canAdopt || 已终态`,无法建立后置条件时 fail-closed。probe 耗尽按普通 discriminator → holdout → dated collect → Nakshatra 边界题 → 明确区间出口转向,所有路径继续使用既有 delivery capability,事故 Case 的 `canAdopt=false` 不变。服务端 focus prompt 直接写入 turn 历史,不让模型复述。 @@ -7070,3 +7070,35 @@ - 相关记录:BUG-459 - 复发自:BUG-456 - 修复版本:待发布 + +## BUG-461 | 口述采集卡重复助手正文,选择题卡题干与气泡不对齐 + +- 状态:resolved +- 首次发现:2026-09-01 +- 最近更新:2026-09-01 +- 影响面:`rectification-agentic-chat` 当前问题槽、`RectificationChoiceCard` legend、`.rectification-question-slot` 对齐 +- 用户现象:口述采集时卡片把助手已经说过的题干再显示一遍,并多出灰色提示「请在下方输入框回答…」。选择题卡在未点选时与助手气泡左缘错位,点选后回看才对齐。 +- 触发条件:当前问题是 `collect_spoken`;或当前问题是选择题且卡片仍在问题槽、尚未变成消息附件。 +- 根因:BUG-449/460 已把服务端题干写入助手正文,BUG-441/452 也禁止为无选项问题新造视觉容器,但后续测试把口述采集卡和可见 legend 锁成了必有。问题槽是消息列表的兄弟节点,没有 `--assistant-content-inset`;已回答卡片在 `.rectification-message-wrap` 内,所以只有回看才对齐。 +- 修复:口述采集不再渲染第二块视觉卡,题干只留在正文,`collectSpokenPrompt` 只驱动输入框占位。选择题 legend 改为 `sr-only`。问题槽与助手内容使用同一 inset,且不给槽内选择题再套一层 inset。 +- 验证:`rectification-spoken-collect`、`rectification-agentic-entry`、`rectification-varga-style-copy` 改为锁新行为;口述采集不得再出现 `__spoken` / `__prompt` / `aria-describedby`。未做登录后的真实页面点选。 +- 防复发:不得为 `collect_spoken` 新造视觉容器或复用选择卡样式;选择题可见题干不得与助手正文重复;问题槽与已回答卡片必须共用 `--assistant-content-inset`。不得用正文文本判断助手有没有问出来。 +- 相关记录:BUG-441、BUG-449、BUG-452、BUG-460 +- 复发自:BUG-441 +- 修复版本:待发布 + +## BUG-462 | 区间出口采集撞上开场题号后,成功计费轮被误报 run_failed + +- 状态:resolved +- 首次发现:2026-09-01 +- 最近更新:2026-09-01 +- 影响面:`exhaustionSpokenCollectFollowup`、`persistCollectFocus`、POST `/api/rectification/agent` 流式成功出口 +- 用户现象:助手已经记下带年份事件并完成计费后,界面同时出现「回答未完成,已保留现有内容;本次不会扣点。」和「生时校正暂时不可用,请稍后再试。」 +- 触发条件:非收敛区间出口后需要再问口述采集,且开场通用采集题号 `collect:unknown:collect_method_evidence` 已作为历史行存在;或职业/健康压力等域不在 SQL `target_domain` 白名单里。 +- 根因:耗尽采集只轮转家庭/升学/财务后就退回 `domain: null`,题号与开场题相同。`unique (case_id, question_id)` 含已结束行,落库报 `focus_idempotency_conflict`。出口闸因此认为没有下一步并抛 `agentic_rectification_nonterminal_exit_missing`。该异常发生在 `billing.settled` 与 `run.completed` 之后,被映射成 `run_failed`;前端只要看到 `error` 就把本轮标成失败并提示不扣点。 +- 修复:耗尽采集在家庭/升学/财务之后问职业,再问健康压力/搬家/事业/感情,最后用 `domain: "other"`。职业落库映射为 `other`,健康压力映射为 `health`。同一题号冲突时改写 `questionId:next`。流式 `result.ok` 后出口闸失败只记日志,仍发送 `done`,不再发 `run_failed`。 +- 验证:`rectification-range-offer-deadend` 锁职业与 `other` 回退;`rectification-server-focus` 锁域映射与 `:next` 重试;`rectification-spoken-collect` 锁成功出口不 `send error`;`rectification-collect-stall` 冲突路径改为两次落库。未做登录后的真实页面复现。 +- 防复发:耗尽回退不得再用 `collect:unknown:*`。SQL 不允许的域必须在落库前映射。流式成功轮在计费完成后不得把出口闸失败发成用户可见的 `run_failed`。出口闸仍须在 `send done` 之前 awaited。不得放宽 `canAdopt`。 +- 相关记录:BUG-450、BUG-452、BUG-460 +- 复发自:BUG-460 +- 修复版本:待发布 diff --git a/frontend/src/app/api/rectification/agent/route.ts b/frontend/src/app/api/rectification/agent/route.ts index 6110349a..545165b7 100644 --- a/frontend/src/app/api/rectification/agent/route.ts +++ b/frontend/src/app/api/rectification/agent/route.ts @@ -716,12 +716,17 @@ export async function POST(request: Request) { } else { // Shared gate owns persistNextInterviewIfIdle then ensureNonTerminalTurnExit; // The shared gate replaces the old message/opening-only cleanup. - await finalizeSuccessfulTurnExit({ - accounting: accounting as never, - userId, - caseId, - action, - }); + try { + await finalizeSuccessfulTurnExit({ + accounting: accounting as never, + userId, + caseId, + action, + }); + } catch (error) { + const code = error instanceof RectificationToolServiceError ? error.code : "run_failed"; + console.warn(`[rectification-v9] turn exit after success case=${caseId} code=${code}`); + } send({ type: "done", emitted: true }); } } catch (error) { diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css index 302ff338..81f96f5c 100644 --- a/frontend/src/app/globals.css +++ b/frontend/src/app/globals.css @@ -2945,22 +2945,10 @@ input:not([type="radio"]):not([type="checkbox"]):not([class^="ant-"]):not([class .rectification-question-slot { display: grid; gap: var(--space-2); + width: calc(100% - var(--assistant-content-inset)); margin-block: var(--space-3); + margin-inline-start: var(--assistant-content-inset); } -.rectification-question-slot__spoken { - display: grid; - gap: var(--space-1); - padding: var(--space-3) var(--space-4); - border: 1px solid var(--color-border); - border-radius: var(--radius-md); - background: var(--color-canvas-soft); -} -.rectification-question-slot__prompt { - margin: 0; - color: var(--color-ink); - font-weight: 600; -} -.rectification-question-slot__hint, .rectification-question-slot__status { margin: 0; color: var(--color-ink-secondary); @@ -2993,6 +2981,17 @@ input:not([type="radio"]):not([type="checkbox"]):not([class^="ant-"]):not([class border-radius: var(--radius-lg); background: var(--color-canvas-soft); } +.rectification-choice-card .birth-time-choice-question legend.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} .rectification-choice-card .birth-time-choice-question:disabled .birth-time-choice-option { cursor: default; opacity: .48; diff --git a/frontend/src/components/rectification-agentic-chat.tsx b/frontend/src/components/rectification-agentic-chat.tsx index 1296a2ff..58a70251 100644 --- a/frontend/src/components/rectification-agentic-chat.tsx +++ b/frontend/src/components/rectification-agentic-chat.tsx @@ -325,7 +325,6 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) { const [boardDiff, setBoardDiff] = useState(() => diffRectificationBoard(null, null)); const boardId = useId(); const boardTitleId = useId(); - const questionHintId = useId(); useLayoutEffect(() => { const query = window.matchMedia(`(max-width: ${RECTIFICATION_BOARD_SPLIT_MIN_PX - 1}px)`); @@ -1230,14 +1229,6 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) { onStop={submitStop} /> )} - {collectSpokenPrompt && ( -
{collectSpokenPrompt}
-- 请在下方输入框回答,可以写你记得的年份、经过和结果。 -
-当前没有可回答的问题,正在等待服务端更新。 @@ -1287,7 +1278,6 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {