Files
cc-web/.planning/runtime-image-send/findings.md
2026-07-18 10:14:38 +08:00

7.3 KiB
Raw Blame History

调研发现:排队与运行中图片发送

用户需求

  • 修复截图中的“排队发送暂不支持图片附件”。
  • 同时接通 Codex App 运行中 turn/steer 图片插入,避免只修表层提示。

已确认事实

  • queueMessageFromInputpendingAttachments.length > 0 时由前端主动拒绝。
  • 当前排队项只保存 id/text/createdAtdrainQueuedMessages 只调用 submitUserMessage(text)
  • sendMessage 的 Codex App runtimeInsert 分支在前端主动拒绝附件。
  • handleCodexAppSteerMessage 在后端再次拒绝附件,并调用 codexAppInputFromMessage(runtimeTextValue, [])
  • 普通 Codex App 新 turn 已通过 codexAppInputFromMessage 把附件路径映射为 localImage
  • 本机 codex-cli 0.144.1 生成的 TurnSteerParams Schema 中,inputUserInput[],包含 localImage
  • 工作树已有其他未提交修改,且 public/app.jsserver.jsscripts/regression.js 与本任务重叠,必须使用小范围补丁。
  • .planning/.active_plan.trellis/.current-task 指向其他任务,本任务不切换共享指针。
  • resolveMessageAttachments 先调用 normalizeMessageAttachments,只接受可由服务端元数据记录解析且文件仍存在的附件;前端无法借附件字段直接注入任意本地路径。
  • normalizeMessageAttachments 会把已过期附件标记为 expired 并清理存储;因此排队期间如果跨过附件 TTL出队时会自然降级为不可用附件不能保证无限期保留。
  • sanitizeMessageForPersist 已对 attachments 统一调用 normalizeMessageAttachmentssteer 路径应复用同一格式而不是手工构造另一种持久化结构。
  • 目标代码区域本身未被工作树中的其他改动修改:server.js 现有差异仅是静态 MIME 新增 jpg/jpegpublic/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 的 savedAttachmentslocalImage.path 仅使用 resolvedAttachmentsstale 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.jsapp-server 请求模拟。
  • scripts/regression.jsWebSocket 与 Codex App 回归测试。

截图信息

  • 会话处于“运行中”composer 已选择一张 PNG发送时 toast 提示“排队发送暂不支持图片附件,请先移除图片”。