Files
Jyotisha/docs/tasks/TASK-rectification-in-chat-step1-20261006.md
T

117 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# TASK · 生时校正并入聊天 · 第一步:从对话里进、做完回到原对话(2026-10-06)
- 基线:`origin/staging` @ `da226db1`
- 执行分支:`codex/rectification-in-chat-step1-20261006`,worktree `.worktrees/rectification-in-chat-step1-20261006`
- BUG 编号起点:**BUG-1247**(现最大 1246;开工时再核对)
- 串行:本单改 `use-consultation-run.ts`、`use-rectification-surface.ts`、`starter-home.tsx`、`rectification-agentic-chat.tsx`、consult 提示词与工具。同日 `TASK-rectification-delivery-dup-adopt-20261006`(含 fix 单,BUG-1241~1243/1246)也改校正交付与采用路径,**必须等它合入 staging 后再从最新 `origin/staging` 开分支**;开工前 `git log origin/staging -5 -- frontend/src/lib/rectification-agentic frontend/src/app/api/rectification` 确认。
- 第二步(另立单,本单不做):把校正的题目卡片直接嵌进同一条对话,不再切换画面。本单的设计不得堵死这条路(见 §4 第 6 条)。
## 1. 现状实证
| 事实 | 位置 |
| --- | --- |
| 校正没有独立页面,是首页 `page.tsx` 里的另一套界面:`rectificationSurfaceOpen && rectificationCaseId` 时渲染 `ConversationalBirthTimeRectification`,替换普通对话和输入框 | `app/(app)/page.tsx` |
| 首页有「生时校正」按钮(「开始新的生时校正 / 再次校正」),只支持本人 | `components/starter-home.tsx` 的 `openRectificationFromHomepage`;`lib/rectification-entry.ts` |
| 对话里进校正只靠前端精确文字匹配:用户打出「生时校正 / 先完成生时校正 / 出生时间校正」才跳,**新开一个校正会话**;原问题只当一行展示(「结束后新建对话,按采用的时间再问」) | `lib/consultation-entrypoint.ts` 的 `isRectificationHandoffQuestion`;`hooks/use-consultation-run.ts`(调用 `openRectificationFromHomepage(pendingQuestion, currentSession.id)`);`rectification-agentic-chat.tsx` 的 `pendingConsultationQuestion` |
| 普通对话 agent 遇到「推得不准」没有任何校正相关指令;👎「内容不准」只记反馈 | `mastra/index.ts`;`lib/reply-quality-labels.ts` |
| 两种会话都存在 `chat_sessions`,用 `session_type`(`consultation` / `birth_time_rectification`)区分,一个校正 case 绑一个会话;`/api/consult` 拒绝非咨询会话(`session_not_consultation`) | 迁移 `20260721110000`、`20260812010000_…v9_runtime.sql`;`app/api/consult/route.ts` |
| 采用(accept)直接写本人 `profiles` 的出生时间并置 `birth_time_status='accepted'`;**普通对话每轮都重新读 profiles**,下一轮就按新时间排盘 | `app/api/rectification/cases/[caseId]/candidates/accept/route.ts` → RPC;`consult/route.ts` 读 profile;`lib/birth-time-consultation-consent.ts` 的 `resolveBirthTimeConsultationRoute` |
| 但校正结束文案让用户「新建对话」 | `lib/rectification-agentic/user-copy.ts`:`postAdoptVerifyDone`、`已采用 ${clock},新建对话即按这个时间排盘。` |
| 计费:普通对话按轮扣;校正按 case 扣一次(第一个实质回合),功能开关 `isProductEnabled("rectification")` | `consult/route.ts`;`v9/agent-route-billing.ts` |
| `open` 接口已接受 `sourceSessionId`,目前只用来推产品领域 | `app/api/rectification/cases/open/route.ts`;`v9/open-request.ts`;`v9/product-domain.ts` |
## 2. 要解决的问题
用户要在聊天里自然地发现「可能是出生时间的问题」,校正完回到刚才那条对话继续聊。现在有三处断开:入口在首页而不在对话里;对话里只有一句固定口令能跳;做完被要求新建对话,原来那个问题和上下文都丢了。
## 3. 决策记录(产品 2026-10-06 授权)
产品当面选择:
- **P1 分两步做**:本单是第一步——从对话里进校正(仍暂时切到校正画面),做完自动回到原对话。第二步另立单。
- **P2 采用新时间后,原对话里之前的回答保留原文,在上方加一行「按校正前的时间」**;之后按新时间回答。
- **P3 删除首页「生时校正」按钮**(含「再次校正」文案)。以后入口只在对话里。
Claude 定(架构):
- **C1 由 agent 判断,不用正则。** 删掉 `isRectificationHandoffQuestion` 文字匹配这条路和 `pendingConsultationQuestion` 那行展示(多余入口宁可删除)。用户直接说「我想校正出生时间」,同样由 agent 判断后给卡。沿用 BUG-976/977「禁正则判意图」口径。
- **C2 先分清是读法问题还是时间问题。** 用户说不准时,agent 先看他说的是什么:只是不同意某个解释,按现有 `FOLLOW_UP_CORRECTION_RULE` 认下、改读;说出过去的具体经历和盘对不上、或说自己出生时间记不准,才提议校正。同一会话提议过一次、用户没接,之后不再主动提(用户再问另说)。
- **C3 提议是一张卡,不是一句话。** consult agent 新增一个只产出事件的工具(名字自定,例:`offer-birth-time-rectification`),服务端校验:问的是本人、`isProductEnabled("rectification")`、账户可用。不满足时工具返回原因,agent 用一句话说明(例:「给家人校正暂时还不支持」),不出卡。卡上写清:用几道过去经历的题核对出生时间、大概几题、会扣几点(价格从 `feature_pricing` 读,不写死)。点了才开 case;开 case 本身不扣费(沿用现有「第一个实质回合才扣」)。
- **C4 点卡 → 打开校正,并带上来源。** 复用 `openRectificationCase` 与 `sourceSessionId`;同时把**触发提议的那条用户问题**存成可恢复的来源信息(服务端存,刷新后仍在;字段放哪由执行方定,优先不动表,见 §4 第 3 条)。
- **C5 什么时候回去。** 以下任一情况回到来源对话:采用完成且采用后核对结束;校正结束但没有采用(含「先不改」);用户在校正画面点「回到刚才的对话」(新按钮,只在有来源对话时出现)。没有来源对话的老校正会话(历史里点开的)不出这个按钮,行为不变。
- **C6 回去之后。** 来源对话里追加一条分隔消息:采用了写「出生时间改为 HH:MM(生时校正)」,没采用写「出生时间没有改」。分隔线之前的助手回答都加一行小字「按校正前的时间」(P2,只在采用了时加)。分隔线下面给一个按钮「按新时间重新看:「原问题」」,**点了才发**,按普通对话一轮计费;不自动发,不偷扣点。
- **C7 让模型知道旧回答用的是旧时间。** 来源对话后续轮次的历史窗口 / 会话摘要里,分隔线之前的助手内容要带一句说明(例:「以下回答按校正前的出生时间,不要沿用其中的盘面判断」),避免模型拿旧盘的结论接着说。
- **C8 文案。** `user-copy.ts` 里「新建对话即按这个时间排盘」两处改成回到原对话的说法;无来源对话时改成「之后的对话按这个时间排盘」(不再叫人新建)。
- **不变**:校正内部流程、打分、出卡、采用门、采用后核对;校正会话仍是独立的 `chat_sessions` 行、仍出现在侧栏历史且能打开(BUG-621 教训);校正计费;首次引导里的出生时间步骤(`home-onboarding-shell.tsx`);只支持本人。
## 4. 硬红线
1. `frontend/src/app/(app)/page.tsx` 不得增长(冻结);新逻辑进 `hooks/`、`lib/` 或组件。
2. 不改校正打分、引擎、采用 RPC 的判定逻辑;不放宽采用门。
3. 优先不动数据库结构(来源问题、分隔消息能放进现有 JSON 字段就放)。必须动表时:新迁移 + 真跑 `npm run test:db`(无 Docker 用本机 PG17 替身法,见 `docs/` 既有说明),并在进度记录写明为什么不能不动表。
4. 不得用正则或关键词判断「用户觉得不准 / 想校正」。
5. 不自动替用户发起任何扣费请求(C6 的重看必须点按钮)。
6. 不堵第二步:来源对话 ↔ 校正 case 的关联做成数据(谁从哪来、回哪去),不要写死在画面切换里;第二步要在同一条对话里渲染校正回合时能复用。
7. 隐私:任务书、测试、Bug 历史里不写真实出生资料;fixture 用公开名人或虚构资料。
8. 改既有断言三栏;测试总数不低于基线;`tsc` 0、lint 0 error、build 后 `/` 仍 Static、首屏 gzip ±2%。
## 5. 任务分解
### T1 删首页入口与文字口令(P3、C1,BUG-1247)
- 删 `starter-home.tsx` 的「生时校正」按钮及其 props 链路;`rectification-entry.ts` 里只服务这个按钮的文案一并删。
- 删 `isRectificationHandoffQuestion` 及 `use-consultation-run.ts` 里的跳转分支;删 `pendingConsultationQuestion` 展示与传参。
- 侧栏历史里的校正会话照常能打开、能「重新校正」以外的既有操作不变(若「重新校正」只经首页按钮,就改成在对话里说)。
- **验收**:`git grep -n "isRectificationHandoffQuestion\|pendingConsultationQuestion\|openRectificationFromHomepage" frontend/src` 为 0 行(或只剩 C4 复用的函数,改名后列进进度记录);首页无该按钮的合同测试;历史校正会话可打开的既有测试仍绿。
### T2 对话里提议校正(C2、C3,BUG-1248)
- consult 提示词(形状只定义一处的规矩照旧)加入 C2 的判断规则与「同会话最多主动提一次」。
- 新工具:服务端校验本人 / 开关 / 价格,产出一个公开事件;前端在该条助手消息下渲染提议卡(复用现有卡片样式,不新造第二套)。卡片一条消息最多一张。
- **验收**:工具对非本人、开关关、已有进行中校正 case 三种情况不出卡并返回原因(单测);卡片价格来自 `feature_pricing`(单测);提示词合同测试锁 C2 两分法与「最多主动提一次」;不含任何意图正则(静态检查)。
### T3 带来源打开校正(C4,BUG-1249)
- 点卡调用 `open`,带 `sourceSessionId` 与来源问题;服务端持久化,`GET` case / entry-summary 能读回来源。
- **验收**:刷新校正画面后来源仍在(集成或路由测试);来源会话不属于本人时拒绝(单测)。
### T4 回到原对话(C5、C6、C8,BUG-1250)
- 三种回去时机按 C5 实现;校正画面新增「回到刚才的对话」按钮(仅有来源时)。
- 来源对话追加分隔消息;采用时给分隔线前的助手回答加「按校正前的时间」;分隔线下「按新时间重新看」按钮,点击按普通一轮发送原问题。
- 文案按 C8 改;对照 `frontend/docs/VOICE.md`。
- **验收**:采用 / 未采用 / 手动返回三条路径各一条测试;刷新来源对话后分隔线、小字、按钮仍在;按钮不点不发请求(测试断言无 `/api/consult` 调用)。
### T5 模型上下文(C7,BUG-1251)
- 来源对话后续轮次的历史窗口与会话摘要,对分隔线前的助手内容加说明。
- **验收**:历史窗口构造的单测:采用后分隔线前内容带说明、之后的不带;未采用时不带。
### T6 文档与记录
- `frontend/DESIGN.md`(入口、提议卡、分隔线、小字、返回按钮);`CONTEXT.md`(若新增术语如「来源对话」「提议卡」,先进 glossary);`CHANGELOG.md`;`docs/BUG_HISTORY.md`(BUG-1247~1251,部署 + 真机前不写 resolved);`docs/tasks/PROGRESS-rectification-in-chat-step1-20261006.md`;`docs/tasks/README.md` 状态板。
- 真机清单 `docs/testing/rectification-in-chat-step1-20261006.md`,至少包含:
1. 首页没有「生时校正」按钮。
2. 普通对话里说「你说我 2019 年换工作,其实那年什么都没发生」→ agent 先问 / 改读;再说「我出生时间是我妈估的」→ 出现提议卡,写明题数和点数。
3. 只说「这个解释我不认同」→ 不出卡,按纠正改读。
4. 给家人档案问同样的话 → 一句话说明暂不支持,不出卡。
5. 点卡 → 进入校正;刷新后仍记得从哪来。
6. 采用一个时间、做完采用后核对 → 自动回到原对话;有分隔线「出生时间改为 …」;之前的回答上方有「按校正前的时间」;有「按新时间重新看」按钮,点了才开始回答。
7. 不采用就结束 → 回到原对话,分隔线写「出生时间没有改」,没有小字。
8. 校正中途点「回到刚才的对话」→ 回去,校正会话仍在侧栏可再打开。
9. 侧栏里以前的校正会话都能打开,没有「回到刚才的对话」按钮。
10. 同一会话拒绝过一次提议后,再说不准,agent 不再主动出卡。
## 6. 让步顺序
T1 + T2 + T3 + T4 是一个可感知的整体,必须同一次交付;T5 > T6。时间不够时先砍 T4 里的「手动返回按钮」(保留自动返回),不得砍「按钮点了才发」。
## 7. 开工前置命令
```bash
cd /workspace/Jyotisha
git status -sb
git fetch origin --prune
git log origin/staging -5 --oneline -- frontend/src/lib/rectification-agentic frontend/src/app/api/rectification # 确认 dup-adopt 单已合入
git worktree add -b codex/rectification-in-chat-step1-20261006 .worktrees/rectification-in-chat-step1-20261006 origin/staging
cd .worktrees/rectification-in-chat-step1-20261006/frontend && npm ci
./node_modules/.bin/tsc --noEmit && npm run lint && npm test 2>&1 | grep -E "^# (tests|fail)" # 基线(Node 22,Linux 优先)
grep -o "BUG-1[0-9]\{3\}" ../docs/BUG_HISTORY.md | sort -t- -k2 -n | tail -1
```
交付门:`tsc` 0 错;lint 0 error;`npm test` 失败名单与基线**逐条比对后贴进进度记录**(Windows 跑不全就写明,由 Claude 在 Linux 复核);动表则 `npm run test:db`;`next build` 后 `/` Static、首屏 gzip ±2%。推 `codex/rectification-in-chat-step1-20261006` 后回报,Claude 验收后再推 staging。