Files

3.9 KiB
Raw Permalink Blame History

失败插入卡片链路与实现契约

现状链路

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 错误。再次失败后必须恢复可操作状态和明确错误提示。
  • 失败项按现有持久化边界保持客户端临时态,刷新后会消失;不新增任意历史消息删除协议。