diff --git a/docs/adr/【ADR】DirectProject命令接单化-2026-09-23.md b/docs/adr/【ADR】DirectProject命令接单化-2026-09-23.md index 4aa6d1989..92e88d287 100644 --- a/docs/adr/【ADR】DirectProject命令接单化-2026-09-23.md +++ b/docs/adr/【ADR】DirectProject命令接单化-2026-09-23.md @@ -153,3 +153,12 @@ (它与 `ProjectRootUnusable` 同类,是用户自己就能修的文件系统事实);§7 的"同级提示"补上认不出的 拒单(拒单不产生终态事件,聊天里必须由命令边界补一条说明)。失败说明的可见文案口径记在 `docs/project-memory/shared-memory/decision-log.md` 同日第二条。 + +后续更新(2026-09-24,接单化 review 收口第二轮续):§1 的"命令 = 接单 / 拒单"补上"接单成立之后的一切 +失败都回 `Ok(())`"——终态由占用对象写、命令返回值只表示接单或拒单,否则同一个失败会从"事件里的说明" +和"命令 `Err` 的横幅"两条通道下发,前端还会把已经开始的回合读成"没开始"(历史落盘失败即这一类,且 +**不继续起整轮**:`project.jsonl` 是这条对话的单一事实源,用户消息没落盘时继续跑只会得到一条没有开口 +用户消息的助手回复);§2 的"谁先到谁写"旁边补上"失败事实先于看门狗可见"——连接死亡的收口路径必须在 +失败事实写进执行适配器**之后**才让"连接已死"对看门狗可见(`closed` 不再兼作去重标志,去重改用私有的 +`connection_end_claimed`),否则 200ms 看门狗可能抢先把它收束成 `Interrupted`,那一轮退化成"本轮已结束、 +没有原因";失败事实是在模型终态那一刻被快照进终态上下文的,晚补记无用。 diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index b2f06cc66..d6117f16a 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -28,16 +28,30 @@ 脱敏摘要一直命中不了。口径定为"不加模式就只会看到通用文案",写在 `directTurnFailure.ts` 的注释里。 - 明确不做:不改线上载荷形状与 `kind` 取值;不加新的失败阶段取值(拒单仍落默认阶段);不动 `ProjectRootUnanchored` 之外的拒单分类。 -- 待决策(本轮没改代码,见交接清单):① "失败事实先于连接收束"的时序保证 - (`fail_game_creator_codex_app_server_connection` 先置 `inner.closed` 再 `record_execution_turn_failure`, - 200ms 看门狗可能抢先把它收束成 `Interrupted`);② 接单之后历史落盘失败仍从命令返回 `Err`, - 同一个失败经事件与命令两条通道下发(前端模型把 `Err` 当"这一轮没开始")。 -- 影响范围:Rust `apps/ai-game-creator-shell/src-tauri/src/agent/{direct_turn_error.rs,direct_turn_failure.rs,direct_turn_accept.rs,direct_thread_manager.rs,direct_runtime/user_input.rs}`; +- 决策(连接死亡的失败事实先于看门狗可见):`CodexAppServerInner::closed` 的语义定为"这一段已经收束 / + 失败事实已经记下",看门狗就盯着它,所以它不能再兼作死亡收口的去重标志——去重改用私有的 + `connection_end_claimed`,`fail_game_creator_codex_app_server_connection` 不再置 `closed`, + `closed` 只在 `shutdown_game_creator_codex_app_server_inner` 里、`record_execution_turn_failure` **之后** + 置位。改动前收口路径先置 `closed` 再做"两次加锁 + 一次日志写",200ms 看门狗可能在这一段里抢跑,把 + 这一轮收束成 `Interrupted`,typed `TransportClosed` 记不进去,终态退化成"本轮已结束、没有原因" + (失败事实是在模型终态那一刻被快照的,晚补记无用,所以只能保证"事实先于可见性")。代价是其它读 + `closed` 的地方会晚几十微秒看到"连接已死",两个并发的死亡观察者仍会各自走到幂等的收束函数。回归用例 + `connection_death_records_the_failure_fact_before_the_watchdog_seals_the_turn` 卡住 stderr 摘要锁把窗口 + 拉成确定性,把看门狗真正跑起来钉这条(顺序反了就红)。 +- 决策(接单之后的失败不回命令返回值):`chat_with_game_creator_direct_codex_typed` 在接单后的历史追加 + 写失败时仍然写 `turn.completed` 失败终态,但 `return Ok(())`——命令的 `Err` 只表示**拒单**。改动前同一 + 个失败从"事件里的说明"和"命令 `Err` 的横幅"两条通道下发(且 `EnvironmentNotReady` 会写诊断 + 上报), + 前端又把 `Err` 当"这一轮没开始",于是忙态与出队同时被事件和返回值两条路推。**不继续起整轮**: + `project.jsonl` 是这条对话的单一事实源,用户消息没落盘时继续跑只会得到一条没有开口用户消息的助手回复, + 失败还会被静默。用例:Rust `a_history_write_failure_after_accept_closes_the_turn_instead_of_rejecting` + (恰好一条失败终态、不带拒单收口文案、占用释放)、前端 appSurface 的落盘失败用例(说明只来自事件且 + 恰好一条、忙态放掉、下一条能发)。 +- 影响范围:Rust `apps/ai-game-creator-shell/src-tauri/src/agent/{codex_app_server/mod.rs,direct_turn_error.rs,direct_turn_failure.rs,direct_turn_accept.rs,direct_thread_manager.rs,direct_runtime/user_input.rs}`; 前端 `src/features/agent-runtime/model.ts`、`src/view/project-development/chat/{conversation/directCodexConversation.ts,conversation/directTurnFailure.ts,controller/useDirectProjectChatController.ts}`、 `src/view/project-development/chat/generated/DirectTurnFailureKind.ts` 与 `tests/{agentRuntimeModel.test.ts,directThreadChat.test.ts,appSurface/chat-composer.suite.ts,appSurface/project-conversation.suite.ts}`; 文档 `docs/adr/【ADR】DirectProject命令接单化-2026-09-23.md`、`docs/technical/【实施计划】DirectProject命令接单化-2026-09-23.md`。 -- 验证:Rust `cargo test --bins "agent::"`(900 passed / 5 ignored)、定向 +- 验证:Rust `cargo test --bins "agent::"`(902 passed / 5 ignored)、定向 `cargo test --bins "agent::direct_turn_error"`(15 passed)、`cargo fmt`;前端 `npx vitest run tests/{appSurface.test.ts,directRunAnalytics.test.ts,directProjectTurn.test.tsx,agentRuntimeModel.test.ts,directThreadChat.test.ts}` (277 passed / 9 skipped)、`npm --prefix apps/ai-game-creator-shell run typecheck`、`npm run check:encoding`、 diff --git a/docs/technical/【实施计划】DirectProject命令接单化-2026-09-23.md b/docs/technical/【实施计划】DirectProject命令接单化-2026-09-23.md index 7a7d96ea3..c289d0580 100644 --- a/docs/technical/【实施计划】DirectProject命令接单化-2026-09-23.md +++ b/docs/technical/【实施计划】DirectProject命令接单化-2026-09-23.md @@ -94,3 +94,9 @@ - 聊天里的提示分两条通道:认得的拒单给 `Display` 原文;认不出的拒单(宿主 / 环境事实)除上报 + 横幅 外也补一条同级提示,文案取宿主收口文案里的脱敏摘要与建议(不带阶段标签)。失败说明的文案映射 口径见 `docs/project-memory/shared-memory/decision-log.md` 与 `conversation/directTurnFailure.ts`。 +- 第 1 步的命令返回值只剩"接单 / 拒单"两种含义:接单成立之后的一切失败(含接单后的历史落盘失败)由 + 占用对象收口成 `turn.completed`,命令一律返回 `Ok(())`;落盘失败**不继续起整轮**。 +- 第 2 步的"谁先到谁写"加一条前提:连接死亡的**失败事实必须先于看门狗可见** + (`CodexAppServerInner::closed` 不再兼作去重标志,去重改用私有的 `connection_end_claimed`, + `closed` 在 `record_execution_turn_failure` 之后才置位);回归用例 + `connection_death_records_the_failure_fact_before_the_watchdog_seals_the_turn` 把看门狗真正跑起来钉这条。