聊天面板:任务结束时用户气泡与"确认理解"卡片重复出现(刷新后自愈)
- Dominant language
- TypeScript
- Stars
- 0
- Forks
- 0
- Avg merge
- 1h 7m
- Merged PRs (30d)
- 969
Description
## 现象
用户直接报告(附截图):一个"确认一下我的理解,再开始"的 HITL 确认任务跑完之后,
**不刷新页面**的情况下,会话里出现:
1. 用户的原始提问气泡**重复**了一次;
2. 已经裁决过的"确认一下我的理解,再开始"卡片作为**多余的 UI** 残留在消息流中。
刷新页面后(重新挂载 hydration)两个问题都自愈——只剩一条用户提问和最终带 PDF 附件的回复。
## 根因
`apps/web/components/chat/copilotkit-v2-panel-body.tsx` 的两处"把服务端已落库消息
与内存里 `agent.messages` 合并"的去重逻辑(挂载 hydration 的 `live.length > 0` 分支,
以及 `handleRunRestored`)都只按消息 `id` 去重。
但乐观插入的用户消息(`send()`)在 `agent.messages` 里用的 id 是前端生成的
`clientMessageId`;这条消息落库后,`chat_messages` 的真实主键是服务端另外
`randomUUID()` 出来的一个**不同的 id**(`message-roundtrip.ts`)。`client_message_id`
只作为幂等去重的独立列存在,从未在 `listMessages`/`readAllPersistedMessages` 里投影
给前端。
于是:HITL 裁决("继续")之后 run 收尾时触发的 `handleRunRestored`(不需要刷新页面
就会跑)用"服务端真实 id"去重,认不出这就是刚才那条乐观插入的用户消息,把它整条
再插一遍 —— 这段消息里同时挂着 CopilotKit 内联渲染的 `confirm_task_intent` 中断卡片,
于是这条已经决裁过的确认卡片也跟着被渲染了第二次。
## 修复
- `apps/web/lib/copilotkit-v2-persisted-messages.ts`:`PersistedMessage` 新增
`clientMessageId` 投影(契约里 `DurableMessage.clientMessageId` 早就有,只是没读出来)。
- `apps/web/components/chat/copilotkit-v2-panel-body.tsx`:上述两处去重改为同时比对
`id` 与 `clientMessageId`,命中任一个都算"已经在场",不再重复插入。
验证:`pnpm typecheck` / `pnpm lint`(改动文件)/ `pnpm vitest run` 相关用例(31 个测试
文件、223 个测试)全绿。
Contributor guide
No contributing guide indexed for this repository
Research direction
Start with apps/web/lib/copilotkit-v2-persisted-messages.ts to trace how persisted messages are projected, then inspect the two merge paths in apps/web/components/chat/copilotkit-v2-panel-body.tsx. Run pnpm typecheck, pnpm lint on the changed files, and the related pnpm vitest run covering 31 files and 223 tests. Done means the post-HITL flow shows one user message and no duplicate confirmation card without a refresh.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- frontend
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 88/100