7.3 KiB
7.3 KiB
调研发现:排队与运行中图片发送
用户需求
- 修复截图中的“排队发送暂不支持图片附件”。
- 同时接通 Codex App 运行中
turn/steer图片插入,避免只修表层提示。
已确认事实
queueMessageFromInput在pendingAttachments.length > 0时由前端主动拒绝。- 当前排队项只保存
id/text/createdAt,drainQueuedMessages只调用submitUserMessage(text)。 sendMessage的 Codex AppruntimeInsert分支在前端主动拒绝附件。handleCodexAppSteerMessage在后端再次拒绝附件,并调用codexAppInputFromMessage(runtimeTextValue, [])。- 普通 Codex App 新 turn 已通过
codexAppInputFromMessage把附件路径映射为localImage。 - 本机
codex-cli 0.144.1生成的TurnSteerParamsSchema 中,input为UserInput[],包含localImage。 - 工作树已有其他未提交修改,且
public/app.js、server.js、scripts/regression.js与本任务重叠,必须使用小范围补丁。 .planning/.active_plan和.trellis/.current-task指向其他任务,本任务不切换共享指针。resolveMessageAttachments先调用normalizeMessageAttachments,只接受可由服务端元数据记录解析且文件仍存在的附件;前端无法借附件字段直接注入任意本地路径。normalizeMessageAttachments会把已过期附件标记为expired并清理存储;因此排队期间如果跨过附件 TTL,出队时会自然降级为不可用附件,不能保证无限期保留。sanitizeMessageForPersist已对attachments统一调用normalizeMessageAttachments,steer 路径应复用同一格式而不是手工构造另一种持久化结构。- 目标代码区域本身未被工作树中的其他改动修改:
server.js现有差异仅是静态 MIME 新增 jpg/jpeg;public/app.js的重叠 diff 主要来自主题配置,队列与 steer 逻辑仍是原实现。 scripts/regression.js当前已有大量其他回归新增,Codex App 运行中 steer 用例位于现有综合回归中;新增断言必须就地追加,不能重排或覆盖这些改动。- mock app-server 的
textFromInput已把localImage渲染成[image:<basename>],可用现有text_delta直接验证 steer 图片确实进入协议输入,无需新增 mock 观测通道。 - 队列卡片目前只展示文本;图片接通后至少应展示附件数量/文件名,避免用户无法确认排队项是否仍带图。
- 队列删除目前只移除内存项;图片接通后应主动删除该队列独占的上传记录,避免留下直到 TTL 才清理的孤儿附件。
- 排队编辑是卡片内只改文本,附件可以原样保留;无需把附件搬回 composer。
- 普通发送支持“只有图片、没有文本”,而现有队列校验强制文本非空。为保持语义一致,队列校验应改为“文本或附件至少一个”,但 slash 限制仍只检查非空文本。
handleMessage已在分派到 steer 之前解析resolvedAttachments并生成安全的savedAttachments;最小后端改法是把两者通过options传入handleCodexAppSteerMessage,避免重复解析与格式漂移。- steer 可支持纯图片:只要
codexAppInputFromMessage('', resolvedAttachments)返回非空输入即可;提示文案需要在无文本时回退为图片文件名。 - stale turn 恢复调用
handleCodexAppMessage时应传resolvedAttachments,这样 replacement turn 与原 steer 使用同一批已校验路径。 - 回归脚本已有
--target机制和独立 stale-running 测试服务器。可新增runtime-image-send目标,复用该服务器同时覆盖成功 steer、stale fallback 与静态前端队列契约,避免每次运行完整回归超过测试时间预算。 uploadAttachment已返回前端同形态附件记录;mock 会把服务端解析后的本地图片路径显示在输出中,可同时断言协议输入和持久化的原始文件名。- mock 的图片 marker 来自服务端存储 basename(附件 ID + 扩展名),不是用户原始文件名;动态协议断言使用 ID marker,历史持久化断言仍使用原始 filename,分别验证两层契约。
- 前端队列实现已审阅:附件快照只在入队时复制,正常出队不触发上传删除;只有明确删除队列项时才调用
deleteUploadedAttachment。 - 纯图片队列在卡片内编辑时也把原附件传给校验函数,因此清空文本后仍可保存;slash 文本限制保持不变。
- 队列卡片复用
renderAttachmentPreviews,该函数会转义附件字段并通过服务端附件 ID 加载预览,不引入新的 HTML 注入面。 - 定向动态回归暴露了现有竞态:
activeCodexAppTurns已存在、UI 已显示 running 时,entry.turnId可能尚未由turn/start响应填充。steer 现在发送 pending 后最多等待 2 秒,每 50ms 检查一次,并在 entry 被替换/结束时停止。 - steer 持久化前重新
loadSession,避免startCodexAppTurn后续保存它持有的旧 session 快照时覆盖刚持久化的插入消息;该处理也保护纯文本 steer。 - 会话历史仍只保存去 path 的
savedAttachments;localImage.path仅使用resolvedAttachments,stale replacement 复用同一安全对象。 - 独立审查发现失效附件的 active steer 之前会在通用
handleMessage校验处提前返回,前端只收到普通 error,已创建的 steer 气泡收不到failed状态。现在 active turn 先分派到 steer handler,由其统一发送带clientMessageId的 failed status/error,且失败输入不持久化。 - 审查修复后保持普通消息原语义:只有 active steer 会在通用附件/空消息校验前分派;普通消息仍先校验,再取消容量重试,失效附件不会意外取消既有 retry。
- stale completion 通过用户消息的精确
timestamp + content把旧 assistant 输出插到该消息之前;附件不会改变定位键,因此在持久化消息附件后仍能保持顺序且不重复用户消息。 - Trellis 前后端质量规范目前仍是占位模板,唯一有实质约束的是跨层数据流指南;本任务应以现有代码契约、项目 AGENTS 规则和回归测试为准。
.codex/agents下没有预期的 Trellis 自定义代理定义,后续实现/检查代理必须在任务提示中显式要求读取 PRD、研究和相关规范。
技术决策
| 决策 | 原因 |
|---|---|
| 队列项保存附件元数据快照 | 出队发送需要保留用户当时选择的附件,不能依赖全局 composer 状态 |
| 后端仍重新解析附件记录 | 防止前端直接注入本地路径,保持既有上传安全边界 |
| steer 持久化与请求使用同一份已解析附件 | 避免历史显示与模型实际输入不一致 |
| stale fallback 复用已解析附件 | 旧 turn 刚结束时自动新开一轮也不能丢图 |
相关资源
public/app.js:队列、composer、运行中插入。server.js:附件解析、会话持久化、Codex App turn/steer。scripts/mock-codex-app-server.js:app-server 请求模拟。scripts/regression.js:WebSocket 与 Codex App 回归测试。
截图信息
- 会话处于“运行中”,composer 已选择一张 PNG;发送时 toast 提示“排队发送暂不支持图片附件,请先移除图片”。