文档:DirectProject 命令接单化实施计划

- 新增实施计划:四步落地顺序(Thread Manager 逻辑回合 → 命令接单 + 后台整轮 → typed 拒单 → 队列/埋点/快照/reducer),每步给改动点、不变式与验收
- 记录已落地三项(重试删除、详情引用删除、失败进池与不落历史)与三条已知坑
- docs/README.md 索引补上该计划
This commit is contained in:
2026-09-23 17:59:34 +08:00
parent 5398a53e6e
commit f672a04ebb
2 changed files with 67 additions and 0 deletions
@@ -0,0 +1,66 @@
# DirectProject 命令接单化实施计划
更新时间:`2026-09-23`
设计口径见 [`【ADR】DirectProject命令接单化-2026-09-23`](../adr/【ADR】DirectProject命令接单化-2026-09-23.md)。
本文件只排实施顺序、不变式与验收,不重复设计理由。
## 已落地
- 设计定稿:ADR、`CONTEXT.md` 术语(逻辑回合 / 接单 / 拒单 / 在途回合)、两处旧文档的取代注。
- 前端删除由 invoke 拒绝驱动的认证重试(`directCodexSessionKeepalive.ts` 只留会话保活)。
- 用户可见文案不再带 `详情:` 引用、失败进错误上报池、失败说明不再写进项目历史、
只服务详情展开的 IPC `read_agent_runtime_error_detail` 已删除。
## 第 1 步:Thread Manager 拥有逻辑回合(Rust,一个原子提交)
改动点:
- 新模块 `agent/direct_turn_accept.rs`:按 thread 维护占用登记。`accept(thread, user_item_id)` 在
同一个临界区里完成"拒绝并发 + 登记占用 + 追加逻辑回合开始事件";`AcceptedTurn::finish(terminal)`
幂等写出 `turn.completed` 并解除占用;`Drop` 兜底补 `host-dropped` 终态。终态写出后占用才释放。
- `direct_thread_manager.rs`:登记与事件追加必须共用同一把锁(不要再加第二张静态表)。
- 删除 `codex_app_server/mod.rs` 里镜像 Codex 原生回合的开始事件与终态追加,以及 app-server 侧
武装的 `DirectTurnFailureGuard`;终态统一交给 `AcceptedTurn::finish`。
- `direct_thread_wire.rs`:`userItemId` 的说明由"从已落盘条目读取"改成"由 `clientTurnId` 推导"。
不变式:线上仍只有一对生命周期事件;同一 thread 任意时刻至多一个占用;`turn.completed` 必带
`userItemId`。
验收:TM 单测(并发接单被拒 / finish 幂等 / Drop 兜底 / 收口后可再次接单)+
`cargo test agent::direct_runtime agent::direct_thread`。
## 第 2 步:命令改接单 + 后台跑整轮(Rust)
- 顺序固定为:`clientTurnId` 校验 → 占用调用身份 → 工作流恢复 → 用户条目校验 → 工程准备 →
`accept` → 落盘用户条目 → spawn 整轮。
- 接单前的检查从 `run_..._and_emitter` 上移到命令;分流判据改成位置(接单后一律回合失败),
`EnvironmentNotReady` 增加 `wire_kind() = "environment-not-ready"`,"调用级拒绝直通"的分支作废。
- spawn 出的任务在正常 / 失败 / 早退三条路径上都要走 `AcceptedTurn::finish`。
验收:`cargo test agent::direct_runtime`;手工把 app-server 配错,界面应收到
`turn.completed{failed, environment-not-ready}`,而不是只有横幅。
## 第 3 步:拒单返回 typed 错误(Rust + TS)
- `DirectTurnError` 加 `Serialize + TS`(含嵌套枚举)并导出到 `chat/generated/`;命令返回
`Result<(), DirectTurnError>`,文案仍由 `Display` 生成一次随载荷带出。
- 前端 catch 按变体分流:认得的前置 / 参数类 → 与用户消息同级的提示、不走 `captureAgentRuntimeError`;
认不得的 → 抛出;状态行只显示回合状态。
验收:`npm run ai-game-creator-shell:typecheck`;手工触发一次拒单(并发 / 空内容)确认提示位置与无上报。
## 第 4 步:队列、埋点、快照、reducer(TS + Rust)
- 前端队列放行改听"回合完成或拒单",加 TODO:以后挪到 Rust 端(落点就是接单动作)。
- 埋点结算挂到回合终态事件。
- 首页"运行中的项目"改由 TM 的逻辑回合导出。
- 删除取消占位的本地收口与 `turn.started` 的重复起点兼容分支。
验收:typecheck;手工连发两条确认第二条不被丢;重进页面忙碌态正确。
## 已知坑
- `project.jsonl` 与项目主对话共用信封类型,不要为了"可见但不喂模型"新增行结构。
- 埋点 `settle` 早于成绩入库会静默丢事件(未来"进历史但不喂模型"的条目同理要落在注入侧,不在读取侧)。
- `list_game_creator_direct_active_turns` 今天读的内存表与单飞锁是同一张,搬迁时别留两处事实。