feat: improve goal mode and Codex App resilience

This commit is contained in:
shiyue
2026-08-18 21:27:58 +08:00
parent 389690af64
commit 8fe31e0a5e
27 changed files with 631 additions and 24 deletions

View File

@@ -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 的组合错误;过程通知识别应匹配整条消息。

View 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":"核验跨层事件契约和恢复后的数据流"}

View 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":"重试链路跨越 app-server、SSE/WebSocket 与前端状态机,需要校验边界契约"}

View 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 的可用性或调度策略。
- 不把不可恢复的协议、鉴权或用户输入错误纳入无限重试。
- 不重做现有视觉主题。

View 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": {}
}

View 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":"核验实现是否符合已确认链路与并行工作边界"}

View 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 消息、标题、主题和并行修改边界"}

View 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 重试任务的文件和区块。

View File

@@ -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` 使用固定 queryCSS 变化后需要推进版本并同步回归断言。
## 并行工作边界
当前工作树另有 Codex App 重试修复,涉及 `lib/codex-app-runtime.js``scripts/mock-codex-app-server.js``scripts/regression.js` 及 Trellis 规范文件。实现可在 `scripts/regression.js` 的 Goal 场景追加断言,但必须保留并发改动,不得重排或还原整文件。

View 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": {}
}

View File

@@ -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

View File

@@ -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