104 lines
8.9 KiB
Markdown
104 lines
8.9 KiB
Markdown
# 失败插入卡片重试与删除:发现记录
|
||
|
||
## 用户需求
|
||
|
||
- 用户希望“插入失败”的卡片增加“重试”和“删除”按钮。
|
||
- 截图中的失败项位于用户消息卡片内,包含图片预览、文件名、尺寸/状态信息和红色“插入失败”状态。
|
||
|
||
## 视觉观察
|
||
|
||
- 当前失败状态只有红点和“插入失败”文本,没有恢复或清理入口。
|
||
- 操作应靠近失败状态区域,保持卡片宽度和当前主题视觉,不应抢占消息正文。
|
||
|
||
## 工作区约束
|
||
|
||
- 当前分支为 `main`,已有另一任务留下的未提交改动。
|
||
- 已确认 `.trellis/.current-task` 指向 `08-11-task-board`,本任务不会覆盖该指针。
|
||
- 与本任务可能重叠的已修改文件包括 `public/app.js`、`public/index.html`、`scripts/regression.js` 和 `server.js`;后续必须做局部差异审查。
|
||
|
||
## 待确认的代码事实
|
||
|
||
- 失败状态的数据结构、稳定标识和持久化位置。
|
||
- 原始插入请求的入口、请求参数及重试时可复用的数据。
|
||
- 删除应落在草稿状态、消息附件状态还是服务端持久化层。
|
||
- 现有测试中与运行时图片插入相关的覆盖位置。
|
||
|
||
## 初步代码定位
|
||
|
||
- `codebase-memory-mcp` 项目 `home-cc-web` 索引状态为 `ready`(5737 节点、12312 边)。
|
||
- “插入失败”不是普通上传失败文案,而是 Codex App 运行中消息插入(steer)状态:前端入口命中 `codexAppSteerStatusLabel` 与相邻的 `setCodexAppSteerStatusElement`。
|
||
- 服务端入口命中 `handleCodexAppSteerMessage`:`turn/steer` 成功发送 `inserted`,失败发送 `failed`,且消息会先以 `persistedUserMessage` 写入会话。
|
||
- 下一步需要确认状态事件携带的稳定标识、前端如何找到对应用户消息,以及重试是否可以复用现有 `handleCodexAppSteerMessage` 消息形状。
|
||
|
||
## 已确认的数据与渲染契约
|
||
|
||
- 前端 `updateCodexAppSteerMessage(clientMessageId, status, message)` 使用 `userMessageIndex` 或消息元素的 `data-message-id` 精确定位卡片,稳定标识已经存在,无需按文件名匹配。
|
||
- `setCodexAppSteerStatusElement` 目前只切换 `pending / inserted / failed` 三种 CSS class,并把状态区域写成纯文本;操作按钮应由这里或紧邻的单一渲染函数统一维护,避免重复绑定。
|
||
- 服务端回归 `runCodexAppRuntimeImageSteerRegression` 已覆盖 `clientMessageId`、失败状态和成功插入;失败的“附件不可用”消息明确不会持久化,说明截图里的失败卡片至少有一类是客户端临时项。
|
||
- 现有成功分支会持久化一条带附件的用户消息,并最终发送 `inserted`;重试实现必须避免把失败项或成功项重复写入会话。
|
||
|
||
## 调用链与主要风险
|
||
|
||
- `handleServerMessage` 仅在 `codex_app_steer_status` 分支调用 `updateCodexAppSteerMessage`;当前状态事件已是最小可扩展协议面。
|
||
- `handleCodexAppSteerMessage` 在真正调用 `turn/steer` **之前** 把用户消息写入会话。若 `turn/steer` 异步失败,该持久化消息当前不会回滚,因此“重试”若直接重发原消息会造成重复持久化。
|
||
- 失败分为两类:附件解析/空消息等同步前置失败(未持久化)与准备/`turn/steer` 异步失败(可能已持久化)。删除和重试必须能区分或统一修正这两类,否则刷新后的行为会不一致。
|
||
- 下一步要确认本地消息对象是否保存 `clientMessageId`、服务端是否已有单条消息删除/替换协议,以及附件引用在失败后是否仍可用。
|
||
|
||
## 客户端入口补充
|
||
|
||
- `sendMessage` 为运行中 Codex App 插入创建本地 `messageId`,用该值注册 `userMessageIndex`,并作为 WebSocket 消息的 `clientMessageId` 发送;因此失败卡片已有天然的单项操作键。
|
||
- 当前前后端没有通用的“删除单条消息”协议;仅有排队消息、提示卡等其他局部删除实现,不能直接用于持久化的 steer 用户消息。
|
||
- 需要在服务端把 `clientMessageId` 与持久化消息关联起来,或在 steer 失败时回滚该消息,才能让删除和重试同时满足“不重复”和“刷新一致”。
|
||
|
||
## 可行实现方向
|
||
|
||
- 本地卡片由 `sendMessage` 创建,包含完整 `text` 与克隆后的 `attachments`;扩展 `userMessageIndex` 即可保存重试载荷,不需要从 DOM 反解析。
|
||
- `createMsgElement` 是失败状态和附件预览的共同装配点,`setCodexAppSteerStatusElement` 是状态区域的唯一更新点,适合统一创建/移除按钮并维护 `disabled` / `aria-busy`。
|
||
- 推荐服务端在真实 `turn/steer` 最终失败时按对象引用回滚刚持久化的用户消息。这样所有失败卡片都保持客户端临时态,删除无需新增持久化删除协议,重试也不会制造历史重复项。
|
||
- 重试仍需校验当前 Codex App turn 是否在运行;若运行已结束,应给出明确失败状态,不能让卡片永久停在 `pending`。
|
||
|
||
## 本地源码校验
|
||
|
||
- 当前样式集中在 `public/style.css` 的 `.codex-steer-*` 区域,失败卡片已有红色边框/背景和 11px 状态行;新操作可在同一区域追加,避免影响其他消息卡片。
|
||
- 项目没有 DOM 测试依赖,仅有 `node scripts/regression.js`;前端交互测试需要使用轻量假 DOM/纯函数抽取,或在现有回归中增加协议和静态契约断言。
|
||
- 本任务开始时的重叠文件基线:`public/app.js` 已有 +155 行、`server.js` +532/-7 行、`scripts/regression.js` +73/-11 行、`public/index.html` +14 行,均来自既有“任务看板”工作,后续差异必须按目标函数/样式区段核对。
|
||
- `public/style.css` 当前没有未提交改动,是本任务新增样式的低冲突落点。
|
||
|
||
## 测试与冲突边界
|
||
|
||
- 现有 `runCodexAppStaleRunningRegression` 已有一个稳定的 `expectedTurnId` 不匹配失败夹具,适合新增“失败后持久化消息已回滚”的服务端断言。
|
||
- 现有成功恢复分支已断言用户消息只持久化一次,可继续保护“重试/恢复不重复”。
|
||
- 既有未提交 diff 的 hunk 不覆盖 `setCodexAppSteerStatusElement`、`sendMessage` 或 `handleCodexAppSteerMessage` 主体;可以对这些区段做局部补丁,但最终仍需逐 hunk 复核。
|
||
- 仓库已有未跟踪的任务看板 MiniDOM 单测脚本,但它属于另一任务;本功能不能修改或依赖该未提交测试文件。若需要 DOM 单测,应建立独立、最小的测试入口。
|
||
|
||
## 失败夹具与规范结论
|
||
|
||
- mock app-server 的 mismatch 夹具会保留一个不同的活动 turn,因此每次 `turn/steer` 都稳定返回 `expectedTurnId does not match active turn`;可用于重复验证失败与回滚,但不能作为成功重试夹具。
|
||
- 前端 Trellis 规范除“静态资源版本握手”外大部分仍是模板占位,没有额外组件框架约束;本任务应遵循仓库现有原生 DOM + CSS 组织方式。
|
||
- 回归脚本已有按 target 执行的分支,可为本功能提供小范围测试入口,避免每次执行完整回归超过 60 秒。
|
||
|
||
## 资源
|
||
|
||
- 用户截图:`sessions/_attachments/8da11ed4-5989-4d30-a12a-f27522138799.png`
|
||
- Trellis 任务:`.trellis/tasks/08-11-failed-insert-card-actions/`
|
||
|
||
## 最终实现状态
|
||
|
||
- 前端新增 `codexAppSteerRecords`,保存失败项的原文本、附件、会话、模式和 agent;重试沿用同一 `clientMessageId`,并通过 `status + inFlight` 阻止重复发送。
|
||
- failed 状态统一渲染“重试”“删除”;pending/inserted 移除操作,卡片使用 `aria-busy`,状态行使用 `role=status` 与 `aria-live=polite`。
|
||
- 本地运行中插入不再提前增加 `currentSessionMessageCount`,只有收到 `inserted` 才提交消息索引;失败/删除不会制造普通路径的索引漂移。
|
||
- 服务端为临时 steer 用户消息写入稳定 `id`,真正的 `turn/steer` 最终失败时先按 id 从最新会话回滚,再发 failed 状态;stale recovery 成功路径保持一次持久化。
|
||
- 前端 MiniDOM 单测覆盖失败操作、双击保护、载荷复用、再次失败恢复、删除单项、断线/跨会话/turn 结束保护与成功索引提交。
|
||
|
||
## 验证结果
|
||
|
||
- `node scripts/failed-insert-card-unit.js`:通过。
|
||
- `node scripts/regression.js --target runtime-image-send`:通过。
|
||
- `node --check public/app.js`、`server.js`、`scripts/regression.js`:通过。
|
||
- `node scripts/regression.js`:因首次定向命令参数错误而实际执行,完整回归通过(约 46 秒)。
|
||
|
||
## 独立审查修正
|
||
|
||
- 检查代理发现 `options.emitUserMessage` 的程序化 `session_message` 虽会显示失败操作,但缺少原始重试载荷;现已由服务端附加临时 steer 元数据,前端据此建立同样的重试记录。
|
||
- 主线程进一步将这类 `session_message` 在结果未定时保持为临时项:不提前写入消息缓存、不提前占用 `currentSessionMessageCount`,收到 `inserted` 后才提交,避免失败删除后的缓存幽灵和索引漂移。
|