75 lines
3.9 KiB
Markdown
75 lines
3.9 KiB
Markdown
# 失败插入卡片链路与实现契约
|
||
|
||
## 现状链路
|
||
|
||
```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 错误。再次失败后必须恢复可操作状态和明确错误提示。
|
||
- 失败项按现有持久化边界保持客户端临时态,刷新后会消失;不新增任意历史消息删除协议。
|