文档:DirectProject 命令入队化与待发消息队列归宿主设计定稿
- 新增 ADR:命令=入队、放行归 Thread Manager、待发消息队列作为运行态事件归宿主 - 新增实施计划:五步落地顺序、标识符映射表、每步不变式与验收证据 - CONTEXT.md:接单/拒单词条替换为入队/入队失败/放行,新增待发消息队列与待发消息词条 - docs/README.md 补两条索引;decision-log.md 追加同日决策记录
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
# DirectProject 命令入队化与待发消息队列归宿主实施计划
|
||||
|
||||
更新时间:`2026-09-24`
|
||||
|
||||
状态:**待实施**(设计已定稿,尚未开工)
|
||||
|
||||
设计口径见 [`【ADR】DirectProject命令入队化与待发消息队列归宿主-2026-09-24`](../adr/【ADR】DirectProject命令入队化与待发消息队列归宿主-2026-09-24.md)。
|
||||
本文件只排实施顺序、不变式与验收,不重复设计理由。
|
||||
|
||||
## 第 0 步:词表切换(与代码同批,不单独提交)
|
||||
|
||||
「接单 / 拒单」退役,改成「入队 / 入队失败 / 放行」。逐处按角色改,**不做字面替换**:
|
||||
|
||||
| 旧写法 | 新写法 |
|
||||
| --- | --- |
|
||||
| 命令的成功 / 失败(接单 / 拒单) | 入队 / 入队失败 |
|
||||
| 这一轮真正成立的那一刻(接单) | 放行 |
|
||||
| 接单前的检查 | 入队时的检查 |
|
||||
| 接单成立 ⇔ 事件流里有开始有结束 | **放行成立** ⇔ 事件流里有开始有结束 |
|
||||
| 接单后的失败都是回合失败 | **放行后**的失败都是回合失败 |
|
||||
|
||||
三类出现点必须分开处理(全仓 384 处):
|
||||
|
||||
1. DirectProject 域(文档、注释、标识符、测试名):按上表改。主要落点 `docs/adr/【ADR】DirectProject命令接单化-2026-09-23.md`、
|
||||
`docs/technical/【实施计划】DirectProject命令接单化-2026-09-23.md`、
|
||||
`docs/technical/【技术方案】DirectProject Codex原始历史与异常恢复-2026-09-04.md`、`CONTEXT.md`、
|
||||
`agent/direct_runtime/user_input.rs`、`agent/direct_turn_accept.rs`、`agent/direct_turn_error.rs`、`agent/direct_thread_manager.rs`、
|
||||
`chat/controller/useDirectProjectChatController.ts`、`chat/conversation/directTurnPresentation.ts`、`tests/appSurface/chat-composer.suite.ts`。
|
||||
2. `features/agent-runtime` 域的「拒单文案」(`model.ts`、`tests/agentRuntimeModel.test.ts`):那里没有队列,
|
||||
改成「请求被拒 / 拒绝」,不要写成「入队失败」。
|
||||
3. 假阳性:`单测` 这类词不动(如 `server-rs/crates/api-server/src/editor_project.rs`)。
|
||||
|
||||
标识符同批改(映射表):
|
||||
|
||||
| 旧 | 新 |
|
||||
| --- | --- |
|
||||
| `chat_with_game_creator_direct_codex` | `enqueue_direct_codex_turn` |
|
||||
| `DirectTurnRejection`(ts-rs 导出) | `DirectTurnEnqueueFailure` |
|
||||
| 前端 `readDirectTurnRejection` / `directTurnRejectionNotice*` | `readDirectTurnEnqueueFailure` / `directTurnEnqueueFailureNotice*` |
|
||||
| Rust `direct_turn_rejection` | `direct_turn_enqueue_failure` |
|
||||
| `DirectTurnReservation::accept` | `DirectTurnReservation::start` |
|
||||
| `accept_direct_thread_turn` / `DirectThreadManager::accept_turn` | `start_direct_thread_turn` / `start_turn` |
|
||||
| `agent/direct_turn_accept.rs` | `agent/direct_turn_dispatch.rs` |
|
||||
| `DirectTurnError::TurnAlreadyRunning` | 删除(第 4 步判据确认零调用方后) |
|
||||
|
||||
两份已接受的 ADR 保留正文与文件名,只在顶部加一行词表注记,避免正文里的旧词变成假命题。
|
||||
|
||||
验收:`rg -n "接单|拒单"` 在 DirectProject 域与 `agent-runtime` 域均为 0;生成绑定重跑(`cargo test export_bindings`)后
|
||||
`git diff` 只剩映射表内的改动。
|
||||
|
||||
## 第 1 步:命令 = 入队(Rust)
|
||||
|
||||
改动点:
|
||||
|
||||
- `agent/direct_runtime/user_input.rs`:把命令主体拆成两半。
|
||||
**入队半**:`clientTurnId` 校验 → 工作流恢复 → 用户条目校验 → prompt 投影 → 前置条件 → 工程准备 → 入队;
|
||||
任何一步失败返回 typed 入队失败。**放行半**(第 2 步)从占用登记起。
|
||||
- 队列条目(宿主侧产物,只在内存):`PendingDirectTurn { client_turn_id, user_item: Value, prompt: String, creation_type: Option<String>, at: u64 }`。
|
||||
`prompt` 与 `creation_type` 是入队检查的产物,放行不再重算;事件里**不带** `prompt`。
|
||||
- `agent/direct_thread_manager.rs`:
|
||||
- `MAX_PENDING_DIRECT_TURNS = 5` 落在 Rust,只数在队条目;满队 → typed 入队失败。
|
||||
- 队列的成员与顺序**就是事件列表本身**:宿主侧产物挂在对应的 `queue.enqueued` 事件上(`StoredEvent` 增加一个非序列化的
|
||||
可选产物字段),不另建队列表。
|
||||
- `observe_event`:`queue.enqueued` 在队期间**不可回收**;`queue.removed` 可回收,并把同 `clientTurnId` 的 enqueued 标记为可回收。
|
||||
`is_bootstrap_event` 不改——live-set bootstrap 因此自动把当前队列交给新订阅者。
|
||||
- 入队幂等:判重范围「在队 ∪ 正在跑的那一轮」,重复入队返回同一次成功。
|
||||
- `remove_direct_project_pending_turn(project_path, client_turn_id)`:typed 结果枚举 `Removed | AlreadyDispatched | NotFound`,
|
||||
在临界区里判「是否仍未被认领」。
|
||||
- 线上形状:`DirectThreadEvent::QueueEnqueued { client_turn_id, user_item, creation_type?, at }`(`queue.enqueued`)、
|
||||
`QueueRemoved { client_turn_id, reason: DirectQueueRemovalReason }`(`queue.removed`),
|
||||
`DirectQueueRemovalReason` 是 ts-rs 导出的 typed 枚举 `cancelled | dispatched`。
|
||||
|
||||
不变式:入队不登记占用、不落盘、不发回合事件、不起 codex;入队失败不写用户条目、不产生事件。
|
||||
|
||||
## 第 2 步:放行与 kick
|
||||
|
||||
- `start_direct_thread_turn`:同一个临界区里「取队首 → 占用登记 → `turn.started` + `queue.removed{dispatched}`」;
|
||||
之后落盘用户条目 → 下发用户条目 → spawn 整轮(顺序与今天的接单后半段一致,`accept` 必须早于落盘与 `turn/start`)。
|
||||
- `kick_direct_queue_dispatch(thread_id)`:幂等;在临界区里判「无占用 + 队首存在 + 未被认领」,认领后 spawn 放行任务。
|
||||
调用点三个:回合任务收尾(正常 / 失败共用)、中止路径、入队之后。
|
||||
- panic 兜底:回合任务里的一个 drop 守卫负责踢一脚,保证任务 panic 或 future 被丢弃时队列不会永久停住。
|
||||
- 入队不取 `DirectTaonierActiveInvocationGuard`;该守卫继续由整轮持有。
|
||||
- 不变式:放行不重跑检查、没有放行失败;放行之后的一切失败都走 `turn.completed.failure`。
|
||||
|
||||
验收(Rust 单测):放行原子性(`queue.removed{dispatched}` 与 `turn.started` 同批、无中间窗口);
|
||||
kick 幂等(并发两次只认领一次);队首在放行后被移除、remove 对已放行条目返回 `AlreadyDispatched`;
|
||||
回合失败 / 中止后队列继续放行下一条;任务 panic 后队列仍能继续;多订阅者游标各自独立时 bootstrap 仍重建完整队列。
|
||||
|
||||
## 第 3 步:前端收口
|
||||
|
||||
退役:
|
||||
|
||||
- `chatComposerQueue.ts` 的 `enqueueChatTurn` / `dequeueChatTurn` / `removeQueuedChatTurn` / `isChatTurnQueueFull` /
|
||||
`MAX_QUEUED_CHAT_TURNS`(提示文案 `chatQueueFullNotice` 保留,改由 Rust 的 typed 入队失败驱动)。
|
||||
- controller 的 `queuedTurns` / `queuedTurnsRef` / `queueSequenceRef` / `completionPendingRef` / `handledCompletedTurnCountRef` /
|
||||
`busyBaselineTurnCountRef` / `dispatchNextQueuedTurn` / `beginTurnBusy` / `endTurnBusy` / `turnBusyRef` 与排序用的计数 effect。
|
||||
- 排队条目那部分 `pendingRunAnalyticsRef` 与 `beginDirectRunAnalytics` 的调用时序(埋点句柄改由宿主在放行时开)。
|
||||
|
||||
保留与改写:
|
||||
|
||||
- chip 由运行态事件的投影驱动(新增 pending 列表投影与两条队列事件的 reducer 分支);
|
||||
chip 文案仍用现成的派生(`directCodexContentToPromptText` + `resourceLabelResolver`),只是输入换成事件里的 `userItem`。
|
||||
- 取消 chip 改调 `remove_direct_project_pending_turn`;入队失败只由命令返回值驱动提示(保留"不丢草稿"行为)。
|
||||
- 忙态 = 事件投影 + 「队列非空」指示;`directProjectTurnStatus` 的"命令在飞"分支收成 IPC 在飞。
|
||||
- 写权限门 `ensureConversationWriteAllowed` 留在入队之前(确认框必须在用户在场时弹)。
|
||||
|
||||
验收:`tests/directThreadChat.test.ts` 补队列事件投影用例;`tests/appSurface/chat-composer.suite.ts` 的排队 / 取消 / 满队 /
|
||||
放行三组用例改成新语义;`npm --workspace apps/ai-game-creator-shell run typecheck` 通过。
|
||||
|
||||
## 第 4 步:CLI 与夹具退役
|
||||
|
||||
- `cli.rs`:删 `CliCommand::DirectCodexChat` 变体、`project_path_mut` 分支(`cli.rs:181`)与派发分支(`cli.rs:899-935`)。
|
||||
- `agent/direct_runtime/mod.rs`:删 `run_direct_game_creator_turn_at` 与 `run_direct_game_creator_turn_at_with_creation_type`
|
||||
(各自只有彼此与 CLI 一个调用方)。
|
||||
- 删 `apps/ai-game-creator-shell/scripts/direct-execution-production-fixture.mjs`(CLI 的唯一消费者)。
|
||||
- 删 `DirectTurnError::TurnAlreadyRunning`(两个生产点都没了)与前端"同一轮消息仍在处理中"文案、专属分支、
|
||||
`tests/appSurface/project-conversation.suite.ts` 的对应断言。
|
||||
- 文档同步:09-22 里程碑把 `--direct-codex-chat` 从"范围外(保留)"改成退役项;两份 Direct 技术方案的 CLI 承诺删掉;
|
||||
09-23 ADR 与 `decision-log.md` 里"CLI 保持 await"的口径改掉。
|
||||
|
||||
验收:`rg -n -- "--direct-codex-chat"` 与 `rg -n "run_direct_game_creator_turn_at"` 零命中;
|
||||
`cargo check --tests` 无新增 `dead_code` 告警;`check-config.mjs` 与 `.gitea/workflows/project-ci.yml` 不受影响(已核实无引用)。
|
||||
|
||||
## 第 5 步:守卫清理(CLI 退役之后)
|
||||
|
||||
- 五个身份读者(`agent/direct_execution.rs`、`agent/direct_tool_bridge.rs`、`agent/direct_runtime/mod.rs` 的付费美术重生成、
|
||||
`agent/direct_validation.rs`、`agent/direct_project_context.rs`)改读 Thread Manager 的活动回合身份
|
||||
(新增一个按项目路径取 `active_turn.turn_id` 的只读入口)。
|
||||
- 删 `DirectTaonierActiveInvocationGuard` 与 `release_stale_direct_taonier_active_invocation` 及其测试
|
||||
(`direct_runtime/mod.rs` 的守卫用例、`direct_project_context.rs`、`direct_tool_bridge.rs`、`direct_tools_mcp.rs`、
|
||||
`user_input.rs`、`codex_app_server/mod.rs` 的集成用例),取消路径改为直接走无条件的 `complete_direct_thread_turn("aborted")`。
|
||||
- 前置判据:补一条测试证明"回合任务被 park / 泄漏时,取消仍能清空占用并让队列继续放行"——那次 60 秒兜底来自真实事故
|
||||
(`d833ca9d3`),删它必须有等价保证。
|
||||
|
||||
## 验收与证据
|
||||
|
||||
- Rust:第 1、2、5 步各自的单测;`cargo test` 定向 + `cargo check --tests` 无新增告警。
|
||||
- Node:`npx vitest run tests/appSurface.test.ts`、`npm --workspace apps/ai-game-creator-shell run typecheck`。
|
||||
- 端到端(`chat-composer.suite.ts`):回合运行中入队两条 → 取消一条 → 终态后只放行剩下那条;
|
||||
入队只发生一次 IPC、没有第二次发送命令;满队提示;入队失败时草稿不丢。
|
||||
- 手工:两个窗口看同一项目(A 排队 B 可见可取消);离开工作台再回来队列仍在并继续放行;
|
||||
`kill -9` 后重进队列消失(与 ADR 的已知边界一致)。
|
||||
- 全仓:`npm run check:encoding`、`npm run check:doc-index`、`git diff --check`。
|
||||
- 不涉及 SpacetimeDB schema,不需要 `npm run check:spacetime-schema`。
|
||||
Reference in New Issue
Block a user