chore: rebuild release package

This commit is contained in:
shiyue
2026-07-18 10:14:38 +08:00
parent fa15b54469
commit a76de06c47
112 changed files with 8858 additions and 399 deletions

View File

@@ -0,0 +1,64 @@
# 调研发现:排队与运行中图片发送
## 用户需求
- 修复截图中的“排队发送暂不支持图片附件”。
- 同时接通 Codex App 运行中 `turn/steer` 图片插入,避免只修表层提示。
## 已确认事实
- `queueMessageFromInput``pendingAttachments.length > 0` 时由前端主动拒绝。
- 当前排队项只保存 `id/text/createdAt``drainQueuedMessages` 只调用 `submitUserMessage(text)`
- `sendMessage` 的 Codex App `runtimeInsert` 分支在前端主动拒绝附件。
- `handleCodexAppSteerMessage` 在后端再次拒绝附件,并调用 `codexAppInputFromMessage(runtimeTextValue, [])`
- 普通 Codex App 新 turn 已通过 `codexAppInputFromMessage` 把附件路径映射为 `localImage`
- 本机 `codex-cli 0.144.1` 生成的 `TurnSteerParams` Schema 中,`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 提示“排队发送暂不支持图片附件,请先移除图片”。

View File

@@ -0,0 +1,116 @@
# 进度记录:排队与运行中图片发送
## 2026-07-17
### 阶段 1核对附件生命周期与现有改动边界
- **状态:** complete
- 已完成:
- 读取 `planning-with-files``todo-list-csv` 与 Trellis 工作流。
- 运行 session catchup 并检查工作树,确认存在其他活跃任务及重叠文件改动。
- 使用 `codebase-memory-mcp` 定位队列、普通发送、steer 和 Codex App 输入构造函数。
- 使用本机 Codex 0.144.1 Schema 验证 `turn/steer` 协议接受 `localImage`
- 确认 `resolveMessageAttachments` 提供服务端元数据解析、文件存在性和过期校验,可直接复用于 steer。
- 确认会话持久化已有统一的 `attachments` 归一化入口。
- 核对目标函数所在 diff确认可做局部补丁且不会覆盖当前主题与回归改动。
- 确认 mock 已能把 `localImage` 反映到 steer 输出,测试可直接断言图片文件名。
- 已创建:
- `.planning/runtime-image-send/task_plan.md`
- `.planning/runtime-image-send/findings.md`
- `.planning/runtime-image-send/progress.md`
- `.trellis/tasks/07-17-runtime-image-send/` 下的 PRD、技术设计、研究和代理上下文
### 阶段 2补充失败回归测试
- **状态:** complete
- 已完成:
- 新增 `runtime-image-send` 定向回归目标。
- 覆盖前端队列附件契约、成功 steer 图片、stale fallback 图片与历史持久化。
- 审阅并修正 mock 图片 marker协议断言使用服务端附件 ID历史断言保留原始文件名。
### 阶段 3运行目标回归确认失败
- **状态:** complete
- 已执行:
- `timeout 60s node scripts/regression.js --target runtime-image-send`
- 结果3.8 秒退出,状态码 1active steer、stale replacement、前端队列契约均按预期失败。
### 阶段 4实现前端附件传递
- **状态:** complete
- 已实现:
- 队列附件快照、纯图片校验、摘要、删除清理与出队发送。
- 运行中插入附件气泡、WebSocket 发送和 composer 清空。
- 卡片编辑保留附件,正常出队不误删上传记录。
- 阶段验证:
- `node --check public/app.js` 通过。
- 定向回归中的前端静态契约已通过,剩余失败均为后端。
### 阶段 5实现后端 steer 附件链路
- **状态:** complete
- 已实现:
- `handleMessage` 向 steer 传递已解析附件和去 path 的持久化附件。
- active steer、纯图片、历史保存和 stale replacement 复用附件。
- 最多等待 2 秒获取 ready turn并在持久化前重载最新 session修复两个竞态。
- 阶段验证:
- `node --check server.js` 通过。
- 代理运行定向回归已通过。
### 阶段 6运行目标回归和静态检查
- **状态:** complete
- 已通过:
- `node --check public/app.js`
- `node --check server.js`
- `node --check scripts/regression.js`
- `timeout 60s node scripts/regression.js --target runtime-image-send`4.9 秒)
- `timeout 60s node scripts/regression.js --target codexapp-stale-running`2.9 秒)
- 已通过:
- `timeout 60s node scripts/regression.js`(完整回归)。
### 阶段 7审查差异并修正问题
- **状态:** complete
- 已完成审查:
- 修复 active steer 失效附件只返回普通 error、用户气泡停在 pending 的问题。
- error/status 现在带 sessionId/clientMessageId失败输入不持久化。
- 恢复普通消息“校验通过后才取消容量重试”的原有顺序,避免无关语义变化。
- 主代理复验:
- 三个修改文件的 `node --check` 通过。
- `runtime-image-send` 定向回归通过。
- 完整回归通过。
### 阶段 8检查会话状态并安全重启服务
- **状态:** in_progress
- 待执行:
- 查询当前会话列表并排除本对话。
- 无其他 running 会话时重启并健康检查;否则暂缓。
## 测试结果
| 测试 | 预期 | 实际 | 状态 |
|------|------|------|------|
| 尚未执行 | - | - | 待执行 |
| 定向回归红灯 | `--target runtime-image-send` | 三类缺口失败 | 三类缺口失败3.8 秒 | ✓ |
| 定向回归绿灯 | `--target runtime-image-send` | 通过 | 通过4.9 秒 | ✓ |
| stale-running 回归 | `--target codexapp-stale-running` | 通过 | 通过2.9 秒 | ✓ |
| JavaScript 语法 | 三个修改文件 `node --check` | 通过 | 全部通过 | ✓ |
| 完整回归 | `timeout 60s node scripts/regression.js` | 通过 | Regression checks passed | ✓ |
## 错误日志
| 时间 | 错误 | 次数 | 处理 |
|------|------|------|------|
| 2026-07-17 | Trellis 自定义代理 TOML 路径不存在 | 1 | 改用标准协作代理并显式传递任务文件与规范路径 |
## 5 问恢复检查
| 问题 | 回答 |
|------|------|
| 当前在哪? | 阶段 8检查会话并安全重启 |
| 接下来去哪? | 先补失败回归,再做前后端最小实现 |
| 目标是什么? | 排队与运行中插入均支持图片附件 |
| 已掌握什么? | 见 `findings.md` |
| 已完成什么? | 见上方进度记录 |

View File

@@ -0,0 +1,90 @@
# 任务计划:修复排队与运行中图片发送
## 目标
让图片附件能够随排队消息发送,并能够通过 Codex App 的 `turn/steer` 在运行中插入;附件在会话记录、失败回退和界面状态中保持一致,且不破坏现有纯文本路径。
## 当前阶段
阶段 8检查会话状态并安全重启服务。
## 阶段
### 阶段 1核对当前附件生命周期与现有改动边界
- [x] 梳理上传附件从前端到后端解析、持久化和 Codex App 输入的路径。
- [x] 核对工作树中重叠文件的既有改动,避免覆盖其他会话内容。
- **状态:** complete
### 阶段 2补充排队图片和运行中插入图片的失败回归测试
- [x] 覆盖队列保存并发送附件。
- [x] 覆盖运行中 `turn/steer` 接收 `localImage`
- [x] 覆盖 stale turn 回退为新 turn 时保留附件。
- **状态:** complete
### 阶段 3运行目标回归确认测试失败
- [x] 只运行相关回归,确认测试能捕获当前缺陷。
- **状态:** complete
### 阶段 4实现前端队列和运行中插入附件传递
- [x] 队列项保存附件快照并在出队时传给普通发送路径。
- [x] 运行中插入消息携带附件并正确清空 composer。
- **状态:** complete
### 阶段 5实现后端 turn/steer 附件解析持久化与回退
- [x] 复用现有附件解析与安全校验。
- [x] 持久化运行中插入消息的附件元数据。
- [x] `turn/steer` 和 stale turn 新轮回退均传递 `localImage`
- **状态:** complete
### 阶段 6运行目标回归和静态检查
- [x] 运行相关回归、语法检查和项目级快速验证。
- **状态:** complete
### 阶段 7审查差异并修正问题
- [x] 检查规范、跨层一致性、附件生命周期和用户既有改动保护。
- [x] 修正审查发现并重新验证。
- **状态:** complete
### 阶段 8检查会话状态并安全重启服务
- [ ] 查询除当前对话外的运行中会话。
- [ ] 仅在无其他运行中会话时重启 `ccweb` 并做健康检查。
- **状态:** in_progress
## 关键问题
1. 队列延迟期间附件记录是否仍能被后端安全解析?
2. `turn/steer` 失败转新 turn 时如何避免附件丢失或重复持久化?
3. 当前回归框架如何断言 `localImage` 已进入 app-server 请求?
## 决策记录
| 决策 | 原因 |
|------|------|
| 使用现有附件上传记录和 `codexAppInputFromMessage` | 避免引入第二套附件格式与安全边界 |
| 不切换共享 `.planning/.active_plan``.trellis/.current-task` | 当前工作树存在其他活跃任务,避免跨会话干扰 |
| 先补失败回归再实现 | 同时覆盖队列、steer 与 stale fallback 三条容易丢附件的路径 |
| 队列允许纯图片并显示附件摘要 | 与普通消息能力一致,同时让用户可验证图片仍在队列中 |
| 删除未发送队列项时清理其上传附件 | 避免无主附件占用存储直到 TTL 到期 |
| 新增 `runtime-image-send` 定向回归目标 | 在 60 秒预算内覆盖前端契约、成功 steer 与 stale fallback |
| steer 最多等待 2 秒获取 ready turn | 前端 running 状态可能早于 app-server 返回 turnId短等待避免图片/文本插入竞态误失败 |
| steer 持久化前重新加载 session | 避免启动 turn 仍持有的旧 session 快照覆盖刚插入的用户附件消息 |
## 错误记录
| 错误 | 次数 | 处理 |
|------|------|------|
| `.codex/agents/trellis-implement.toml``trellis-check.toml` 不存在 | 1 | 项目仅将这些代理定义为可选;改用标准协作子代理并在提示中显式注入任务、规范和边界 |
## 范围边界
- 只支持现有图片附件类型,不扩展任意文件附件。
- 不改变纯文本排队、slash 指令和笔记模式的既有语义。
- 不覆盖工作树中与本任务无关的主题、样式和其他功能改动。