chore: rebuild release package and commit updates
This commit is contained in:
50
.planning/ccweb-unrouted-notification-fix/findings.md
Normal file
50
.planning/ccweb-unrouted-notification-fix/findings.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# 调查结论
|
||||
|
||||
## 2026-07-12 根因证据
|
||||
|
||||
- `ccweb` PID 1606383 持续占满单个 CPU 核心,RSS 约 130 MiB,系统可用内存约 4.4 GiB。
|
||||
- `curl http://127.0.0.1:8002/` 能立即建立 TCP,但 8–10 秒无响应,符合事件循环阻塞而非端口未监听。
|
||||
- `logs/process.log` 每约 0.6 秒出现 `codex_app_notification_unrouted`,来源主要是三个 Codex App threadId。
|
||||
- 三个 threadId 均存在于当前 `sessions/*-run/codexapp-state.json`,却未恢复进内存路由映射。
|
||||
- `findCodexAppRouteByRuntime` 在内存命中失败后调用 `adoptCodexAppUnroutedTurn`。
|
||||
- `findCodexAppSessionByThreadId` 对每条未路由通知执行 `readdirSync(SESSIONS_DIR)`,再逐个 `loadSession`;当前 113 个会话 JSON 合计约 61.3 MiB。
|
||||
- 高频 delta × 同步全量扫描导致 Node 主线程单核 100%,所有 HTTP 请求饥饿。
|
||||
- PM2 error log 中的 V8 OOM 修改时间为 2026-06-15,是历史故障,不是当前直接原因。
|
||||
- 当未路由子线程通知停止后,未部署补丁的旧进程 CPU 从单核 100% 降至 0–2%,本地首页恢复为 HTTP 200/约 2.3 ms;这证明故障与通知风暴强相关,而非持续内存不足或端口问题。
|
||||
|
||||
## 工作区约束
|
||||
|
||||
- 用户已有修改:`public/app.js`、`public/style.css`、`scripts/regression.js`。
|
||||
- 现有未跟踪计划/任务目录属于其他工作,不得覆盖或删除。
|
||||
- `.trellis/.current-task` 当前指向 `00-bootstrap-guidelines`,另有 `07-11-subagent-card-metadata` 进行中;不得切换共享 current-task,以免干扰并发会话。
|
||||
- 项目要求重启前检查运行会话;只有除当前会话外没有其他 running 会话时才能重启。
|
||||
- 当前磁盘运行态检查只发现本对话 `4b9700f7-...` 的一个 `codexapp-state.json`,状态为 `running`;没有发现其他会话 run 目录或 classic PID 文件。最终重启前仍需再次复核。
|
||||
- 重启前通过运行中 ccweb 的内部 MCP `ccweb_list_conversations` 检查:仅当前对话为 `running`,其余全部为 `idle`,满足项目重启条件。
|
||||
|
||||
## 部署验证
|
||||
|
||||
- 执行 `pm2 restart ccweb --update-env` 成功,新 PID 为 1802039,PM2 restart 计数从 60 增至 61。
|
||||
- 启动日志出现 `ccweb_mcp_child_threads_recovered`,`restored=3`,证明真实恢复文件中的三个子线程路由被重建。
|
||||
- 连续 5 次本地 HTTP 探针均返回 200,后续最终探针约 3.6 ms。
|
||||
- 新进程 RSS 约 64 MiB,事件循环 P95 约 1.5 ms,短时 CPU 回落至 0–12%,没有持续占满单核。
|
||||
- 重启后的日志窗口仅包含 recovery/server_start 事件,没有新的 `codex_app_notification_unrouted` 风暴。
|
||||
|
||||
## 项目上下文
|
||||
|
||||
- Trellis 将项目识别为单仓库,规范层为 backend/frontend;本次只涉及 backend。
|
||||
- codebase-memory 项目 `home-cc-web` 索引状态为 `ready`,当前 3091 个节点、7446 条边。
|
||||
- 独立计划审查已通过;建议在实施记录中明确测试入口与重启检查。
|
||||
- 项目测试入口为 `npm run regression`(实际执行 `node scripts/regression.js`),服务入口为 `node server.js`。
|
||||
- backend 规范目录当前仍是模板状态,没有额外项目特定限制;本次重点遵守现有结构化 `plog`、同步 I/O 热路径规避和回归脚本惯例。
|
||||
- 预计修改位置:`recoverCodexAppTurnState`、`findCodexAppSessionByThreadId`、`adoptCodexAppUnroutedTurn`、`handleCodexAppNotification`,以及独立/现有回归脚本。
|
||||
- 当前 `codexapp-state.json` 只持久化父线程 entry;子线程 ID 仅间接存在于 `toolCalls[*].input.agentThreadId` 等协作工具数据中,`recoverCodexAppTurnState` 没有重建 `ccwebMcpChildThreads`。
|
||||
- `findCodexAppRouteByRuntime` 当前先调用父线程磁盘收养逻辑,再检查 `ccwebMcpChildThreads`;对子线程通知会先触发昂贵的父会话全盘扫描,路由顺序本身也需要调整。
|
||||
- 现有 `scripts/regression.js` 含用户未提交的“子代理卡片元数据”测试,修复必须追加且保留这些改动。
|
||||
- 真实恢复文件中,协作子线程以 `toolCalls[].name/kind = subAgentActivity` 持久化;`input.agentThreadId` 是路由键,`input.agentPath` 可作为恢复标签,`input.kind` 表示 started/interacted 等活动。
|
||||
|
||||
## HAPI 对比
|
||||
|
||||
- HAPI 的“归档”主要是会话生命周期操作:将 active session 断开并把 `metadata.lifecycleState` 设为 `archived`,支持后续 reopen;它不是专门为解决 ccweb 本次每条通知全盘扫描而设计。
|
||||
- HAPI 的归档确实能减少活动会话集合和实时订阅压力,但历史记录仍保留在存储/缓存层,因此只有配合索引化 SessionCache/SyncEngine 才能避免热路径扫描。
|
||||
- 对 ccweb 而言,未来增加归档有产品和容量管理价值,但不能替代当前 threadId 路由索引、恢复映射和负缓存修复。
|
||||
- 源码确认 HAPI `archiveSession` 调用 `rpcGateway.killSession` 后进入 `handleSessionEnd`;Hub 存储基于 `bun:sqlite`,`SessionCache` 维护内存态并以 `active` 作为权威运行标志。
|
||||
27
.planning/ccweb-unrouted-notification-fix/prd.md
Normal file
27
.planning/ccweb-unrouted-notification-fix/prd.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# ccweb 未路由通知阻塞修复需求
|
||||
|
||||
## 背景
|
||||
|
||||
ccweb 重启后,仍在运行的 Codex App 协作子线程继续发送通知,但 `ccwebMcpChildThreads` 是进程内 Map,没有从 `codexapp-state.json` 恢复。通知无法命中路由,随后对每条 delta 同步遍历并解析全部会话 JSON,导致 Node 主线程单核 100% 和 HTTP 524。
|
||||
|
||||
## 必须实现
|
||||
|
||||
1. 恢复父会话状态时,从已持久化的协作 toolCalls 中重建足够的子线程路由信息,至少包含 child threadId、parentSessionId、parentThreadId、spawnToolId、状态和可用标签。
|
||||
2. 子线程内存路由必须在父线程磁盘收养之前判断,避免已知子线程也触发全盘扫描。
|
||||
3. 对真正未知的 threadId 增加有界负缓存,使高频 delta 在 TTL 内最多触发一次磁盘查找。
|
||||
4. `codex_app_notification_unrouted` 日志按 threadId/method 节流,保留诊断能力但不逐条写盘。
|
||||
5. 会话创建、更新、删除后应正确维护 threadId 索引或使缓存失效,不产生长期错误路由。
|
||||
6. 保留现有行为:父线程未路由通知仍可被收养;新产生的 collabAgentToolCall 仍可注册和更新子线程。
|
||||
|
||||
## 测试要求
|
||||
|
||||
- 先添加在旧实现上失败的回归断言,再实现最小修复。
|
||||
- 覆盖恢复态子线程映射重建、子线程优先路由、未知线程负缓存和日志节流。
|
||||
- 运行定向测试、`node --check server.js`、`npm run regression`。
|
||||
- 测试总超时不超过 60 秒。
|
||||
|
||||
## 约束
|
||||
|
||||
- 不覆盖 `public/app.js`、`public/style.css`、`scripts/regression.js` 中的用户现有改动。
|
||||
- 不通过增大堆上限或单纯重启掩盖根因。
|
||||
- 代码注释使用简体中文;日志不得包含敏感数据。
|
||||
32
.planning/ccweb-unrouted-notification-fix/progress.md
Normal file
32
.planning/ccweb-unrouted-notification-fix/progress.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# 进度日志
|
||||
|
||||
## 2026-07-12
|
||||
|
||||
- 完成当前服务状态复核:确认 HTTP 超时、单核 100%、内存正常。
|
||||
- 确认未路由通知来自恢复态 Codex App 子线程。
|
||||
- 建立持久化修复计划、调查记录和验收标准。
|
||||
- 检查 Trellis 状态;由于共享 current-task 正被其他任务使用,决定不切换该全局指针。
|
||||
- 独立计划审查通过;根因、范围、验收标准与并发约束已固化。
|
||||
- 明确测试入口 `npm run regression`;codebase-memory 后续查询因传输关闭降级为本地精确读取。
|
||||
- 确认恢复状态没有直接持久化/重建子线程映射,且当前路由顺序会让子线程通知先走父线程磁盘扫描。
|
||||
- 初步运行会话检查只发现当前父会话处于 running;等待实现代理完成后将在重启前再次检查。
|
||||
- 首轮实现代理因检索耗时被中止;第二轮已追加失败回归契约并开始最小实现。
|
||||
- 对比 HAPI:归档是生命周期/活动集合管理能力,可降低长期压力但不是本次热路径问题的直接修复。
|
||||
- 实现代理已写入 `server.js` 核心补丁和回归契约;正在收口语法与定向验证。
|
||||
- 旧进程在通知风暴结束后暂时恢复 HTTP 200,但补丁尚未部署,仍需完成审查和重启验证。
|
||||
- 独立 check 代理已追加审查修正,等待其验证回报。
|
||||
- check 代理未及时结束,已中止;主线程复核确认负缓存与日志节流 Map 均有 1000 项上限和 30 秒 TTL/节流窗口。
|
||||
- 当前阶段:重新运行定向回归并处理问题。
|
||||
- 定向契约复跑通过,`server.js` 语法检查通过。
|
||||
- 当前阶段:执行完整回归与静态检查。
|
||||
- `timeout 60s npm run regression` 完整回归通过。
|
||||
- 当前阶段:最终差异审查与运行风险确认。
|
||||
- 重启前会话列表确认仅当前对话 running,其余全部 idle。
|
||||
- 执行 PM2 重启成功,新进程恢复 3 个子线程路由。
|
||||
- 连续 HTTP 探针均为 200;最终响应约 3.6 ms,CPU/内存/事件循环正常。
|
||||
- 重启后未发现新的未路由通知风暴,任务完成。
|
||||
- 回归契约已追加;旧实现缺少契约要求的索引、恢复与节流符号,必然失败。
|
||||
- 实现代理写入补丁后未及时结束,已中止并由主线程接管验证。
|
||||
- `node --check server.js` 通过。
|
||||
- `node scripts/regression.js --target codexapp-unrouted-routing` 通过。
|
||||
- 当前阶段:冷审查负缓存、日志节流和恢复一致性。
|
||||
72
.planning/ccweb-unrouted-notification-fix/task_plan.md
Normal file
72
.planning/ccweb-unrouted-notification-fix/task_plan.md
Normal file
@@ -0,0 +1,72 @@
|
||||
# ccweb 未路由通知阻塞修复计划
|
||||
|
||||
## 目标
|
||||
|
||||
修复 Codex App 子线程通知在进程恢复后无法路由、反复同步扫描全部会话文件并占满 Node.js 主线程的问题,恢复 ccweb HTTP 可用性并防止复发。
|
||||
|
||||
## 阶段
|
||||
|
||||
### Phase 1:建立任务上下文并固化根因与验收标准
|
||||
|
||||
**Status:** complete
|
||||
|
||||
### Phase 2:补充可复现的失败回归测试
|
||||
|
||||
**Status:** complete
|
||||
|
||||
### Phase 3:实现线程路由索引与恢复逻辑
|
||||
|
||||
**Status:** complete
|
||||
|
||||
### Phase 4:限制未路由通知的磁盘扫描与日志风暴
|
||||
|
||||
**Status:** complete
|
||||
|
||||
### Phase 5:运行定向回归测试并修正问题
|
||||
|
||||
**Status:** complete
|
||||
|
||||
### Phase 6:执行完整回归与静态检查
|
||||
|
||||
**Status:** complete
|
||||
|
||||
### Phase 7:审查变更与运行风险
|
||||
|
||||
**Status:** complete
|
||||
|
||||
### Phase 8:安全重启并验证本地/外部访问
|
||||
|
||||
**Status:** complete
|
||||
|
||||
## 验收标准
|
||||
|
||||
- 恢复后,父线程及子线程通知均能通过内存索引命中,不对每个 delta 同步扫描全部会话 JSON。
|
||||
- 未知 threadId 的高频通知采用有界、缓存或节流策略,不造成日志风暴和单核持续 100%。
|
||||
- 回归测试覆盖恢复态子线程路由、未知线程负缓存/节流和现有通知路由行为。
|
||||
- 定向回归、完整回归和语法检查通过。
|
||||
- 重启前确认除当前会话外没有其他 running 会话;满足条件才重启。
|
||||
- 重启后本地 8002 健康响应,CPU 回落且日志不再持续刷未路由通知。
|
||||
|
||||
## 决策
|
||||
|
||||
- 不通过增大 Node.js 堆上限掩盖问题。
|
||||
- 优先建立 O(1) threadId 路由索引,并在恢复 `codexapp-state.json` 时重建子线程映射。
|
||||
- 为真正未知的通知增加负缓存/节流,避免重复全量磁盘扫描与逐条日志。
|
||||
- 保留用户已有未提交改动,不覆盖无关文件。
|
||||
|
||||
## 错误记录
|
||||
|
||||
| 错误 | 尝试 | 处理 |
|
||||
|---|---:|---|
|
||||
| `strace` 附加进程被系统拒绝 | 1 | 改用实时 CPU、HTTP 探针、日志频率和代码调用链交叉定位 |
|
||||
| codebase-memory `search_graph` 返回 `Transport closed` | 1 | 索引状态此前为 ready,但运行时传输关闭;按项目降级规则改用已确认的 qualified_name 与 `rg`/`sed` 精确读取 |
|
||||
| 更新计划文件时上下文定位失败 | 1 | 重新读取当前文件结构后使用更精确的补丁上下文 |
|
||||
| 首个实现代理在检索阶段长时间无补丁 | 1 | 中止该轮后复用同一代理,禁止 MCP,提供精确状态形状并收敛为本地 TDD 补丁任务 |
|
||||
| 第二轮实现代理写入补丁后未及时结束回报 | 1 | 中止代理并由主线程接管语法、定向与完整回归验证 |
|
||||
| 首次读取运行中 app-server 的本地 MCP token 使用了错误环境变量名 | 1 | 只列出环境变量键名确认实际为 `CC_WEB_CODEX_APP_MCP_TOKEN`,未输出 token 值,并成功完成会话状态检查 |
|
||||
| planning-with-files 完成检查首次报告 `0/0 phases` | 1 | 计划阶段原为中文编号列表;已改为检查脚本可识别的标准阶段标题与状态字段 |
|
||||
|
||||
## 计划审查
|
||||
|
||||
- 独立计划审查代理已通过,无阻断性问题。
|
||||
- 执行时显式记录测试命令、预计修改位置和重启前运行会话检查结果。
|
||||
Reference in New Issue
Block a user