Files

75 lines
3.9 KiB
Markdown
Raw Permalink 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.

# 失败插入卡片链路与实现契约
## 现状链路
```text
sendMessage
→ 创建本地用户卡片 + clientMessageId
→ WebSocket type=message
→ handleMessage
→ 活动 Codex App turn 时进入 handleCodexAppSteerMessage
→ codex_app_steer_status(pending / inserted / failed)
→ handleServerMessage
→ updateCodexAppSteerMessage
→ setCodexAppSteerStatusElement
```
关键位置:
- `public/app.js`:`sendMessage`、`setCodexAppSteerStatusElement`、`updateCodexAppSteerMessage`、`createMsgElement`、`clearUserMessageIndex`。
- `public/style.css`:`.codex-steer-*` 状态样式。
- `server.js`:`handleMessage`、`handleCodexAppSteerMessage`。
- `scripts/regression.js`:`runCodexAppRuntimeImageSteerRegression`、`runCodexAppStaleRunningRegression`。
## 已确认事实
1. 本地失败卡片以 `clientMessageId` / `data-message-id` 稳定定位,不应按文件名或 DOM 顺序匹配。
2. 当前 `userMessageIndex` 只保存内容与时间;重试还需要保存附件、会话、模式和 agent。
3. 当前状态渲染只有文本,没有操作容器、忙碌语义和并发保护。
4. 当前服务端在调用 `turn/steer` 前持久化用户消息,最终失败时不回滚,会留下模型未接收的幽灵消息。
5. 同步前置失败不会持久化;两类失败必须统一为“失败卡片仅存在于当前客户端”的语义。
6. 本地临时卡片当前提前增加 `currentSessionMessageCount`,失败后会造成消息索引漂移。
## 推荐实现
### 前端
- 新增独立的运行中插入记录 Map,以 `clientMessageId` 保存:元素、文本、附件克隆、sessionId、mode、agent、pending/committed 状态。
- `clearUserMessageIndex` 同时清理该 Map,防止切换会话后重试旧请求。
- `setCodexAppSteerStatusElement` 统一维护:
- `role=status`、`aria-live=polite`;
- 卡片 `aria-busy`;
- 失败态的“重试”“删除”按钮;
- 非失败态移除操作容器;
- 按钮使用 `type=button`、明确 `aria-label`,并阻止重复绑定。
- `retryCodexAppSteerMessage` 只在记录存在、仍属于当前会话、当前为 Codex App 运行中且 WebSocket 可用时发送;先切到 pending 并禁用操作,发送失败则恢复 failed。
- `deleteCodexAppSteerMessage` 只允许删除 failed 临时项,移除 DOM、两个索引记录并刷新轮廓/滚动条。
- 临时插入不提前递增 `currentSessionMessageCount`;收到 `inserted` 后才标记 session message 并递增,失败/删除不改变持久化索引。
### 服务端
- 给待持久化的 steer 用户消息写入稳定 `id`(优先使用 `clientMessageId`)。
- 最终 `turn/steer` 失败时,在发送 failed 状态前,从最新会话中按该 id 精确移除本次用户消息并保存。
- stale no-active-turn 自动恢复成功分支不得回滚;同步前置失败和 ready 超时本就没有持久化项。
- 异步错误消息补带 `clientMessageId`,便于前端和回归关联。
## 测试契约
- 前端独立单测或可执行契约测试:
- failed 创建两个按钮,pending/inserted 不展示;
- 快速双击重试只发送一次,载荷复用原文本与附件;
- retry 时 `aria-busy=true` 且操作禁用/移除;
- failed 恢复操作;
- delete 只移除目标项及对应索引;
- turn 已结束或 WebSocket 断开时不发送并保留可恢复失败态。
- 服务端定向回归:
- 现有同步附件失败仍不持久化;
- mismatch 异步失败后同名用户消息为 0;
- stale recovery / 成功 steer 用户消息仍恰好为 1。
- 静态契约:按钮文本、CSS 作用域和 status handler 保持存在。
## 已知限制
- 附件已经从服务端过期时,单纯重试仍会失败;按钮主要恢复临时 turn/server 错误。再次失败后必须恢复可操作状态和明确错误提示。
- 失败项按现有持久化边界保持客户端临时态,刷新后会消失;不新增任意历史消息删除协议。