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,2 @@
{"file":".trellis/spec/guides/cross-layer-thinking-guide.md","reason":"审查前后端附件格式、验证责任、持久化和协议输入是否一致。"}
{"file":".trellis/tasks/07-17-runtime-image-send/research/runtime-image-input.md","reason":"核对协议事实、测试预期和已有工作树改动保护。"}

View File

@@ -0,0 +1,2 @@
{"file":".trellis/spec/guides/cross-layer-thinking-guide.md","reason":"改动跨越 composer、WebSocket、附件解析、会话持久化和 app-server 协议,需要保持边界数据一致。"}
{"file":".trellis/tasks/07-17-runtime-image-send/research/runtime-image-input.md","reason":"包含本机 turn/steer 协议、附件安全边界、mock 测试能力和工作树冲突约束。"}

View File

@@ -0,0 +1,35 @@
# 技术设计
## 数据流
```text
上传接口返回附件元数据
├─ 普通 composer → submitUserMessage(text, attachments)
├─ 排队 → queueItem.attachments → drain → submitUserMessage
└─ 运行中插入 → WebSocket message.attachments
↓
handleMessage 统一解析
↓
resolvedAttachments(含服务端 path)
savedAttachments(无服务端 path)
↓
handleCodexAppSteerMessage
├─ 会话历史保存 savedAttachments
├─ turn/steer 使用 resolvedAttachments
└─ stale fallback 使用 resolvedAttachments
```
## 安全边界
- 前端只发送附件 ID 与公开元数据。
- 服务端通过 `resolveMessageAttachments` 重新读取元数据文件、检查 storageState 与真实文件存在性。
- `localImage.path` 只能来自服务端解析结果。
- 会话 JSON 只保存 `savedAttachments`,不泄露服务端本地路径。
## 边界情况
- 纯图片:允许;标题/提示回退为图片文件名。
- 部分附件失效:保持现有语义,只发送仍可解析的附件。
- 所有附件失效且无文本:返回既有 `attachment_unavailable`/`empty_message` 错误。
- 删除队列项:清理该项附件,避免孤儿上传。
- stale steer:旧 assistant 输出仍按 timestamp + content 插到已持久化用户消息之前。

View File

@@ -0,0 +1,58 @@
# 修复排队与运行中图片发送
## 背景
cc-web 已支持普通消息附带图片,也已通过 Codex app-server 的 `UserInput.localImage` 向新 turn 发送图片。但当前前端会拒绝排队消息与运行中插入消息携带图片,后端 steer 路径也会再次拒绝附件。
## 目标
在不改变现有纯文本、slash 指令和笔记模式语义的前提下,让现有图片附件可以:
1. 随排队消息保存,并在当前 turn 结束后发送。
2. 随 Codex App 运行中消息通过 `turn/steer` 插入。
3. 在 stale turn 自动转为新 turn 时继续保留。
4. 在会话历史与界面中保持与模型实际输入一致。
## 功能要求
### 排队发送
- 队列项保存创建时的附件元数据快照,不能依赖全局 composer 附件状态。
- 文本或附件至少存在一个即可入队;纯图片队列允许发送。
- 含 slash 的文本仍按现有规则禁止排队。
- 入队成功后清空 composer 文本和附件托盘。
- 队列卡片显示附件摘要,使用户能确认附件仍在队列中。
- 卡片内编辑只修改文本并保留附件。
- 删除未发送队列项时清理其上传附件。
- 出队时调用普通发送入口并传入附件数组。
### Codex App 运行中插入
- 前端运行中插入消息必须携带附件数组,用户气泡也显示相同附件。
- 插入成功提交后清空 composer 文本与附件托盘。
- 后端复用 `handleMessage` 已解析的安全附件,不信任前端路径。
- 持久化用户消息时保存规范化附件元数据。
- `turn/steer.input` 同时包含文本与 `localImage`;允许纯图片插入。
- 无文本时,系统提示使用图片文件名生成可读摘要。
- app-server 返回 `no active turn to steer` 时,replacement turn 必须继续使用同一批已解析附件。
## 非目标
- 不支持图片以外的任意文件附件。
- 不延长附件 TTL,也不保证超出既有 TTL 后仍可发送。
- 不改变 slash 指令、笔记模式、Claude/Codex CLI 进程模式的行为。
- 不重构现有附件存储体系。
## 验收标准
- 源码中不再存在排队图片和 Codex App 运行中图片的主动拒绝分支。
- 排队项保存附件,出队发送时附件不丢失,删除时清理附件。
- 成功 `turn/steer` 的 mock 输出能观察到 `localImage`。
- steer 用户历史包含原始附件文件名和可用状态。
- stale turn replacement 输出能观察到同一图片,历史中的用户消息只出现一次且保留附件。
- 新增 `node scripts/regression.js --target runtime-image-send` 在 60 秒内通过。
- `node --check public/app.js`、`node --check server.js`、`node --check scripts/regression.js` 通过。
## 改动保护
工作树中已有主题与回归相关未提交改动。实现必须使用局部补丁,禁止覆盖或格式化无关区域。

View File

@@ -0,0 +1,22 @@
# 运行中图片输入调研
## 协议事实
- 本机 `codex-cli 0.144.1` 生成的 `TurnSteerParams` Schema 定义 `input: UserInput[]`。
- `UserInput` 联合类型包含 `{ type: "localImage", path, detail? }`。
- cc-web 的普通 Codex App turn 已通过 `codexAppInputFromMessage` 使用这一结构。
## 代码事实
- `handleMessage` 在选择普通 turn 或 steer 之前已经调用 `resolveMessageAttachments`。
- `resolveMessageAttachments` 只返回元数据记录存在、未过期且真实文件仍存在的附件。
- `sanitizeMessageForPersist` 对历史附件统一调用 `normalizeMessageAttachments`。
- mock app-server 的 `textFromInput` 会把 `localImage` 显示为 `[image:<basename>]`,适合协议回归断言。
- stale completion 使用用户消息的 timestamp + content 定位插入点,保存附件不会破坏顺序。
## 工作树约束
- `public/app.js` 有其他主题相关改动。
- `scripts/regression.js` 有大量其他回归新增。
- `server.js` 现有未提交差异仅涉及 jpg/jpeg 静态 MIME。
- 所有修改必须限制在相关函数与测试邻近区域。

View File

@@ -0,0 +1,26 @@
{
"id": "runtime-image-send",
"name": "runtime-image-send",
"title": "修复排队与运行中图片发送",
"description": "",
"status": "planning",
"dev_type": null,
"scope": null,
"package": null,
"priority": "P2",
"creator": "shiyue",
"assignee": "shiyue",
"createdAt": "2026-07-17",
"completedAt": null,
"branch": null,
"base_branch": "main",
"worktree_path": null,
"commit": null,
"pr_url": null,
"subtasks": [],
"children": [],
"parent": null,
"relatedFiles": [],
"notes": "",
"meta": {}
}