feat: improve goal mode and Codex App resilience
This commit is contained in:
@@ -32,7 +32,12 @@ Questions to answer:
|
||||
|
||||
<!-- Try-catch patterns, error propagation -->
|
||||
|
||||
(To be filled by the team)
|
||||
### Codex App 过程错误与终态错误
|
||||
|
||||
- app-server 的 JSON-RPC 方法名为 `error` 时,不能仅凭方法名判定当前 turn 已结束;必须结合消息语义和后续终态事件处理。
|
||||
- 纯 `Reconnecting... N/M` 表示 provider 内部重连进度:应向客户端展示,但不得写入最终错误、清理 active turn 或启动新的 cc-web 业务重试。
|
||||
- `503 Service Unavailable`、`No available providers`、`service_unavailable_error` 等终态错误必须保留完整错误信息,再交给服务端重试分类。
|
||||
- 自动续接必须携带并校验原 `expectedThreadId`;恢复到不同 thread 时应停止,不能用新 thread 掩盖上下文丢失。
|
||||
|
||||
---
|
||||
|
||||
@@ -48,4 +53,5 @@ Questions to answer:
|
||||
|
||||
<!-- Error handling mistakes your team has made -->
|
||||
|
||||
(To be filled by the team)
|
||||
- 把每个 app-server `error` 通知都返回为 `done: true`,会在 provider 自身仍重连时提前关闭 turn,并与 cc-web 重试状态机形成交错重试。
|
||||
- 使用非锚定正则识别 `Reconnecting... N/M`,可能误吞同时包含最终 503 的组合错误;过程通知识别应匹配整条消息。
|
||||
|
||||
5
.trellis/tasks/08-18-codexapp-retry-resume/check.jsonl
Normal file
5
.trellis/tasks/08-18-codexapp-retry-resume/check.jsonl
Normal file
@@ -0,0 +1,5 @@
|
||||
{"file":".trellis/spec/backend/error-handling.md","reason":"核验错误分类、重试上限与最终失败行为"}
|
||||
{"file":".trellis/spec/backend/quality-guidelines.md","reason":"核验后端测试与代码质量"}
|
||||
{"file":".trellis/spec/frontend/state-management.md","reason":"核验重连状态、计数和任务终态一致性"}
|
||||
{"file":".trellis/spec/frontend/quality-guidelines.md","reason":"核验前端测试与代码质量"}
|
||||
{"file":".trellis/spec/guides/cross-layer-thinking-guide.md","reason":"核验跨层事件契约和恢复后的数据流"}
|
||||
@@ -0,0 +1,5 @@
|
||||
{"file":".trellis/spec/backend/error-handling.md","reason":"约束临时服务错误、终态错误和重试边界的处理方式"}
|
||||
{"file":".trellis/spec/backend/quality-guidelines.md","reason":"约束后端实现与回归测试质量"}
|
||||
{"file":".trellis/spec/frontend/state-management.md","reason":"约束重连计数和运行终态的前端状态管理"}
|
||||
{"file":".trellis/spec/frontend/quality-guidelines.md","reason":"约束前端实现与测试质量"}
|
||||
{"file":".trellis/spec/guides/cross-layer-thinking-guide.md","reason":"重试链路跨越 app-server、SSE/WebSocket 与前端状态机,需要校验边界契约"}
|
||||
27
.trellis/tasks/08-18-codexapp-retry-resume/prd.md
Normal file
27
.trellis/tasks/08-18-codexapp-retry-resume/prd.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# 修复 Codex App 重试中断
|
||||
|
||||
## 问题
|
||||
|
||||
Codex App 遇到临时 503 或网络错误时,界面虽然连续显示自动重试与“从中断处继续”,但当前任务仍被终止。第五次附近还会先展示终端 503,再把重连计数重置为 `1/5`,造成状态与真实执行不一致。
|
||||
|
||||
## 需求
|
||||
|
||||
1. 可恢复错误进入统一的续跑状态,不提前关闭当前任务。
|
||||
2. 同一恢复周期的尝试次数单调递增,不因终端事件或传输重连错误重置。
|
||||
3. 每次自动重试必须真正续接原线程/任务,而不只是显示通知。
|
||||
4. 恢复成功后清理重试状态并继续流式输出。
|
||||
5. 达到重试上限后才进入明确失败终态,并保留可理解的错误原因。
|
||||
|
||||
## 验收标准
|
||||
|
||||
- 自动化测试复现“连续临时失败后仍续跑”的场景,并在修复前失败、修复后通过。
|
||||
- 自动化测试验证计数不会在同一恢复周期中从 `5/5` 错误回到 `1/5`。
|
||||
- 自动化测试验证成功恢复与耗尽重试两种终态。
|
||||
- 现有 Codex App 相关回归测试和静态检查通过。
|
||||
|
||||
## 非目标
|
||||
|
||||
- 不修改上游 provider 的可用性或调度策略。
|
||||
- 不把不可恢复的协议、鉴权或用户输入错误纳入无限重试。
|
||||
- 不重做现有视觉主题。
|
||||
|
||||
31
.trellis/tasks/08-18-codexapp-retry-resume/task.json
Normal file
31
.trellis/tasks/08-18-codexapp-retry-resume/task.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"id": "codexapp-retry-resume",
|
||||
"name": "codexapp-retry-resume",
|
||||
"title": "修复 Codex App 重试中断",
|
||||
"description": "",
|
||||
"status": "completed",
|
||||
"dev_type": null,
|
||||
"scope": null,
|
||||
"package": null,
|
||||
"priority": "P2",
|
||||
"creator": "shiyue",
|
||||
"assignee": "shiyue",
|
||||
"createdAt": "2026-08-18",
|
||||
"completedAt": "2026-08-18",
|
||||
"branch": null,
|
||||
"base_branch": "main",
|
||||
"worktree_path": null,
|
||||
"commit": null,
|
||||
"pr_url": null,
|
||||
"subtasks": [],
|
||||
"children": [],
|
||||
"parent": null,
|
||||
"relatedFiles": [
|
||||
"lib/codex-app-runtime.js",
|
||||
"scripts/mock-codex-app-server.js",
|
||||
"scripts/regression.js",
|
||||
".trellis/spec/backend/error-handling.md"
|
||||
],
|
||||
"notes": "区分 Codex App 重连过程通知与终端错误;定向及完整回归通过。",
|
||||
"meta": {}
|
||||
}
|
||||
7
.trellis/tasks/08-18-goal-mode-title/check.jsonl
Normal file
7
.trellis/tasks/08-18-goal-mode-title/check.jsonl
Normal file
@@ -0,0 +1,7 @@
|
||||
{"file":".trellis/spec/frontend/index.md","reason":"核验前端检查清单和组件边界"}
|
||||
{"file":".trellis/spec/frontend/component-guidelines.md","reason":"核验标签 DOM、可访问性和普通消息隔离"}
|
||||
{"file":".trellis/spec/frontend/quality-guidelines.md","reason":"核验缓存版本、前端回归和视觉限制说明"}
|
||||
{"file":".trellis/spec/backend/index.md","reason":"核验后端会话处理检查清单"}
|
||||
{"file":".trellis/spec/backend/quality-guidelines.md","reason":"核验标题持久化、广播和回归覆盖"}
|
||||
{"file":".trellis/spec/guides/cross-layer-thinking-guide.md","reason":"核验 DOM、WebSocket、持久化和会话列表的一致契约"}
|
||||
{"file":".trellis/tasks/08-18-goal-mode-title/research/goal-message-title-chain.md","reason":"核验实现是否符合已确认链路与并行工作边界"}
|
||||
7
.trellis/tasks/08-18-goal-mode-title/implement.jsonl
Normal file
7
.trellis/tasks/08-18-goal-mode-title/implement.jsonl
Normal file
@@ -0,0 +1,7 @@
|
||||
{"file":".trellis/spec/frontend/index.md","reason":"加载前端开发检查清单与组件规范入口"}
|
||||
{"file":".trellis/spec/frontend/component-guidelines.md","reason":"约束 Goal 标签 DOM、可访问性与样式隔离"}
|
||||
{"file":".trellis/spec/frontend/quality-guidelines.md","reason":"约束前端静态契约、回归和缓存版本质量"}
|
||||
{"file":".trellis/spec/backend/index.md","reason":"加载后端开发检查清单与会话处理规范入口"}
|
||||
{"file":".trellis/spec/backend/quality-guidelines.md","reason":"约束会话持久化、标题保护与回归质量"}
|
||||
{"file":".trellis/spec/guides/cross-layer-thinking-guide.md","reason":"Goal 改动跨越 DOM、WebSocket 事件、会话持久化和列表广播"}
|
||||
{"file":".trellis/tasks/08-18-goal-mode-title/research/goal-message-title-chain.md","reason":"提供已验证的 Goal 消息、标题、主题和并行修改边界"}
|
||||
32
.trellis/tasks/08-18-goal-mode-title/prd.md
Normal file
32
.trellis/tasks/08-18-goal-mode-title/prd.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# 修复 Goal 模式标记与自动标题
|
||||
|
||||
## 问题
|
||||
|
||||
设置型 `/goal <目标>` 已经以普通用户气泡持久化和展示,但气泡与普通消息完全一致,用户无法识别这是 Goal。与此同时,`/goal` 由服务端 Slash/RPC 旁路处理,不会先创建模型回合,因此模型没有机会主动调用 `ccweb_set_title`,默认标题会一直保留为 `New Chat` 或 `Untitled`。
|
||||
|
||||
## 需求
|
||||
|
||||
1. 仅对 `role: user` 且 `ccwebGoalCommand.action === 'set'` 的消息增加 Goal 专用 class,并在正文上方显示固定标签“目标模式”。
|
||||
2. 实时 `session_message` 与刷新后的历史消息必须走同一渲染入口;普通用户消息、控制型 `/goal` 命令和其他 display-only 消息不得出现该标签。
|
||||
3. 标签保持紧凑、可读,不把用户气泡重新变成大系统面板;提供基础样式和 `wasteland` 暗金荒野主题适配,并保证长正文和窄屏不溢出。
|
||||
4. 设置型 `/goal` 持久化展示消息时,仅在当前标题仍为 `New Chat`/`Untitled` 且 `titleSource` 不是 `manual` 时,从 objective 派生标题。
|
||||
5. 派生标题压平连续空白、截取最多 60 个字符,写入 `session.title` 与 `titleSource: 'derived'`;不得伪造只接受 `source: 'llm'` 的标题历史事件。
|
||||
6. 标题改变后立即保存,并向查看该会话的客户端发送 `session_renamed`,再广播最新 `session_list`;手动标题和任何非默认标题必须保持不变。
|
||||
7. 保持 Goal RPC、稳定请求 ID 去重、display-only 持久化、失败草稿语义和控制型 `/goal` 行为不变。
|
||||
8. 修改 `style.css` 后推进样式 cache-busting 版本,并同步相关回归断言。
|
||||
|
||||
## 验收标准
|
||||
|
||||
- 回归证明 Goal 消息生成专用 class 与“目标模式”标签,普通用户消息不会误命中。
|
||||
- 回归证明默认标题会从多行 Goal objective 派生、持久化为 `derived`,并实时发出 `session_renamed`/`session_list`。
|
||||
- 回归证明手动标题和已有非默认标题不会被 Goal 覆盖。
|
||||
- 重复同一 Goal 命令仍只产生一条展示消息、一次 Goal RPC,且不会重复改标题。
|
||||
- `node --check`、相关前端契约测试、`timeout 60s npm run regression` 与 `git diff --check` 通过。
|
||||
- 在可用的真实浏览器中检查桌面、窄屏和 `wasteland` 主题;若本机仍无浏览器能力,必须如实记录限制,不得以自动化替代视觉结论。
|
||||
|
||||
## 非目标
|
||||
|
||||
- 不让 `/goal` 额外启动普通模型 turn,也不为了改标题调用内部 HTTP/MCP 回路。
|
||||
- 不覆盖用户手动标题,不把 Goal 自动标题记作 LLM 标题历史。
|
||||
- 不重做普通用户气泡、系统消息、其他主题或 welcome 页面。
|
||||
- 不修改当前工作树中 Codex App 重试任务的文件和区块。
|
||||
@@ -0,0 +1,22 @@
|
||||
# Goal 消息与标题链路
|
||||
|
||||
## 已确认入口
|
||||
|
||||
- `public/app.js`:`buildMsgElement()` 把完整消息对象作为 `meta` 传给 `createMsgElement()`,适合直接读取 `ccwebGoalCommand`,同时覆盖实时和历史消息。
|
||||
- `server.js`:`persistCodexAppGoalDisplayMessage()` 创建 `role: user`、`ccwebDisplayOnly: true`、带 `ccwebGoalCommand.action: 'set'` 的持久消息,并统一保存会话。
|
||||
- `server.js`:`handleCodexAppGoalSlashCommand()` 在普通消息运行时之前处理 `/goal`,因此不会触发模型主动调用 `ccweb_set_title`。
|
||||
- `server.js`:`ensureCodexAppGoalThread()` 复用 `codexAppThreadParams()`;`codexAppThreadConfig()` 会从 runtime MCP 配置写入 `mcp_servers.ccweb`。因此底层 Goal thread 已注册 ccweb MCP,缺口是 `thread/goal/set` 本身不是模型工具调用。
|
||||
- `server.js`:普通消息只在标题为 `New Chat`/`Untitled` 时取正文前 60 字并设置 `titleSource: 'derived'`。
|
||||
- `server.js`:`setCurrentConversationTitle()` 展示了 `session_renamed`、`publicTitleMetadata()` 和 `broadcastSessionList()` 的实时通知契约;该 MCP 路径使用 `titleSource: 'llm'`,不能直接复用其来源语义。
|
||||
- `server.js`:`handleRenameSession()` 将用户标题写为 `titleSource: 'manual'`;`isTitleLockedByUser()` 可阻止自动覆盖。
|
||||
- `server.js`:`normalizeTitleHistory()` 只保留 `source: 'llm'`,普通 `derived` 标题不进入标题历史。
|
||||
|
||||
## 视觉与缓存边界
|
||||
|
||||
- 暗金荒野主题 ID 为 `wasteland`。
|
||||
- Goal 标签只应命中专用 Goal user message selector,不能扩大到普通 `.msg.user`。
|
||||
- `app.js` 使用动态 `__CC_WEB_FRONTEND_ASSET_VERSION__`;`style.css` 使用固定 query,CSS 变化后需要推进版本并同步回归断言。
|
||||
|
||||
## 并行工作边界
|
||||
|
||||
当前工作树另有 Codex App 重试修复,涉及 `lib/codex-app-runtime.js`、`scripts/mock-codex-app-server.js`、`scripts/regression.js` 及 Trellis 规范文件。实现可在 `scripts/regression.js` 的 Goal 场景追加断言,但必须保留并发改动,不得重排或还原整文件。
|
||||
32
.trellis/tasks/08-18-goal-mode-title/task.json
Normal file
32
.trellis/tasks/08-18-goal-mode-title/task.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"id": "goal-mode-title",
|
||||
"name": "goal-mode-title",
|
||||
"title": "修复 Goal 模式标记与自动标题",
|
||||
"description": "",
|
||||
"status": "in_progress",
|
||||
"dev_type": null,
|
||||
"scope": null,
|
||||
"package": null,
|
||||
"priority": "P2",
|
||||
"creator": "shiyue",
|
||||
"assignee": "shiyue",
|
||||
"createdAt": "2026-08-18",
|
||||
"completedAt": null,
|
||||
"branch": null,
|
||||
"base_branch": "main",
|
||||
"worktree_path": null,
|
||||
"commit": null,
|
||||
"pr_url": null,
|
||||
"subtasks": [],
|
||||
"children": [],
|
||||
"parent": null,
|
||||
"relatedFiles": [
|
||||
"public/app.js",
|
||||
"public/style.css",
|
||||
"public/index.html",
|
||||
"server.js",
|
||||
"scripts/regression.js"
|
||||
],
|
||||
"notes": "Goal 用户气泡增加模式标记;默认标题由服务端旁路派生,手动标题受保护。",
|
||||
"meta": {}
|
||||
}
|
||||
@@ -8,8 +8,8 @@
|
||||
|
||||
<!-- @@@auto:current-status -->
|
||||
- **Active File**: `journal-1.md`
|
||||
- **Total Sessions**: 0
|
||||
- **Last Active**: -
|
||||
- **Total Sessions**: 1
|
||||
- **Last Active**: 2026-08-18
|
||||
<!-- @@@/auto:current-status -->
|
||||
|
||||
---
|
||||
@@ -19,7 +19,7 @@
|
||||
<!-- @@@auto:active-documents -->
|
||||
| File | Lines | Status |
|
||||
|------|-------|--------|
|
||||
| `journal-1.md` | ~0 | Active |
|
||||
| `journal-1.md` | ~37 | Active |
|
||||
<!-- @@@/auto:active-documents -->
|
||||
|
||||
---
|
||||
@@ -29,6 +29,7 @@
|
||||
<!-- @@@auto:session-history -->
|
||||
| # | Date | Title | Commits | Branch |
|
||||
|---|------|-------|---------|--------|
|
||||
| 1 | 2026-08-18 | 修复 Codex App 重试中断 | - | `main` |
|
||||
<!-- @@@/auto:session-history -->
|
||||
|
||||
---
|
||||
@@ -37,4 +38,4 @@
|
||||
|
||||
- Sessions are appended to journal files
|
||||
- New journal file created when current exceeds 2000 lines
|
||||
- Use `add_session.py` to record sessions
|
||||
- Use `add_session.py` to record sessions
|
||||
@@ -4,3 +4,34 @@
|
||||
> Started: 2026-04-24
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Session 1: 修复 Codex App 重试中断
|
||||
|
||||
**Date**: 2026-08-18
|
||||
**Task**: 修复 Codex App 重试中断
|
||||
**Branch**: `main`
|
||||
|
||||
### Summary
|
||||
|
||||
区分 Reconnecting N/M 过程通知与终端 503,保留原 thread 续接重试;定向和完整回归通过。
|
||||
|
||||
### Main Changes
|
||||
|
||||
(Add details)
|
||||
|
||||
### Git Commits
|
||||
|
||||
(No commits - planning session)
|
||||
|
||||
### Testing
|
||||
|
||||
- [OK] (Add test results)
|
||||
|
||||
### Status
|
||||
|
||||
[OK] **Completed**
|
||||
|
||||
### Next Steps
|
||||
|
||||
- None - task complete
|
||||
|
||||
Reference in New Issue
Block a user