前端:删掉 Direct 的本地乐观用户气泡,用户气泡只来自宿主条目

- controller 删 `pendingUserItemId` / `beginTurnCommand` / `endTurnCommand`:忙态改由 `beginTurnBusy` / `endTurnBusy` 持有,宿主认领判据 = `turnRunning` 或收口计数变过(一轮在同一次 consume 里开始并结束)
- controller 删 `startTurn` 的乐观追加与 `messageAppended` 重跑参数、`DirectProjectTurnInput.messageText` 与首轮的 `directInitialTurnText`;controller 不再需要 `assets`
- 投影删 `awaiting-start` 展示态(只剩 `running` / `finished`)、`localSentTimes` / `sameIdentitySentAt`、本地用户气泡与它开回合的路径;带身份的拒单提示在会话末尾自成一组,不挂进上一轮,也不开运行态标记
- 时间口径:起点只认 `turn.started.at`、终点只认 `turn.completed.at`,用户气泡时钟取宿主落盘 / 观测时间;两边都空的回合整条「本轮结束于 … 」隐藏,不再出现 0.0 秒
- 用例:改造 `directTurnPresentation`(本地用户消息不进回合、拒单提示自成一组、两态判据、失败说明按身份归位)、`directProjectTurn` / `directProjectTurnStatus` / appSurface 窗口期用例,`directProjectTurn` 补 `afterEach(cleanup)`
- 注释与文档:ADR「命令接单化」后续更新、实施计划新增「删掉本地乐观用户气泡」、decision-log 与 pitfalls 同日条目、Codex 原始历史方案的口径句、`codex_app_server` 用户条目时间注释
This commit is contained in:
2026-09-24 18:20:24 +08:00
parent d6f2ae157a
commit 1ff1a9965b
19 changed files with 301 additions and 428 deletions
@@ -171,3 +171,14 @@
本轮的用户气泡再自成一个 0.0 秒的假回合;下一条消息同样看不到自己的失败说明。§7 的界面口径据此补上
"回合归属只认身份":失败说明条目带 `turnUserItemId`,投影层按开口条目身份分组,本地乐观气泡按身份挂回
自己的回合;reducer 的收口早退也不再吞掉"订阅重建只回放生命周期锚点"时那条还没写进界面的失败说明。
后续更新(2026-09-24,删掉本地乐观用户气泡):§7 的"同级提示"再收一层——**本地不再造用户消息**。
前端把乐观气泡、`awaiting-start` 展示态、`pendingUserItemId` / `messageAppended` / `messageText`
这一整套一起删掉,用户气泡**只**来自宿主条目(发点=接单成立、落盘成功、起 codex 之前)。三条口径
随之固定:① 接单窗口(按下发送到 `turn.started` 落进 reducer)与订阅重建窗口里聊天区没有这一轮的
任何条目,反馈只有 composer 忙态与状态行;② 回合起点只认 `turn.started.at`、终点只认
`turn.completed.at`,用户气泡显示的时钟是宿主落盘 / 观测时间(不再有更早的本地发送时间),两边都
拿不到(重进项目读回来的历史回合)时整条「本轮结束于 … 」隐藏,不再兜出 0.0 秒;③ 拒单提示带自己
的身份(`…:rejected`),投影据此在会话末尾自成一组,不挂进上一轮。§7 里"排在用户消息后面"在没有
用户消息的回合里指"这一组提示自己"。代价(已知并接受):条目下发之前用户看不到自己那句话,
`project.jsonl` 里的用户条目也依旧只在首屏 / 翻页时读进前端。
@@ -9480,3 +9480,13 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在
- 影响面:`apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server/mod.rs`、`.../agent/direct_runtime/user_input.rs`、`.../chat/conversation/{directThreadChat.ts,directTurnPresentation.ts}`。
- 边界:`project.jsonl` 里的用户条目依旧只在首屏 / 翻页时读进前端,本轮不改读取时机——开口条目的运行态下发 + 身份归位已经让"说明挂错回合"不成立。
- 验证:宿主 `cargo test --bins "agent::"`、`the_opening_user_item_is_emitted_before_anything_that_can_fail_in_the_turn`、`direct_project_turn_does_not_forward_codex_user_echo_as_chat_items`(补上同一发点);前端 `directTurnPresentation.test.ts` 的"本轮用户条目没到时,失败说明按身份挂回自己那一轮,本地气泡不再自成假回合"、`directThreadChat.test.ts` 的两条(身份字段、收口早退不吞说明)。
## 2026-09-24 DirectProject 删掉本地乐观用户气泡:用户气泡只来自宿主条目
- 背景:接单化之后,"接单窗口期"只服务本地乐观气泡(`awaiting-start` 展示态 + `pendingUserItemId` 身份)。上一轮把开口用户条目的发点提前到接单之后,"说明挂错回合"已不再需要气泡兜底;用户确认按"这条消息就像从来没存在过"处理,直接删干净。
- 决策(不造用户消息):删 `pendingUserItemId` / `beginTurnCommand` / `endTurnCommand`(忙态改由 `beginTurnBusy` / `endTurnBusy` 持有,宿主认领判据 = `turnRunning` 或收口计数变过)、权限确认重跑的 `messageAppended` 参数、`DirectProjectTurnInput.messageText`(含首轮 `directInitialTurnText`)、投影里的 `awaiting-start` 与本地用户气泡路径(展示态只剩 `running` / `finished`);controller 不再需要 `assets`。
- 决策(时间口径):回合起点只认 `turn.started.at`、终点只认 `turn.completed.at`;用户气泡的时钟是宿主落盘 / 观测时间,不再有"本地更早的真实发送时刻"(`sameIdentitySentAt` 删除)。两边都拿不到(重进项目读回来的历史回合)时整条「本轮结束于 … 」隐藏,不再兜出 0.0 秒。
- 决策(本地说明):拒单提示带自己的身份(`…:rejected`),投影据此在会话末尾自成一组,不挂进上一轮;壳层 `announce`(无身份)照旧挂当前回合末尾。带身份的本地说明不开运行态标记,避免把真正在跑的那一轮读成已结束。
- 代价(已知并接受):接单窗口与订阅重建窗口里聊天区没有这一轮的显示,反馈只有 composer 忙态与状态行;`project.jsonl` 用户条目依旧只在首屏 / 翻页读进前端。
- 影响面:`apps/ai-game-creator-shell/src/view/project-development/chat/conversation/{directTurnPresentation.ts,directCodexConversation.ts}`、`.../chat/controller/{useDirectProjectChatController.ts,useDirectProjectTurnStatus.ts}`、`.../chat/DirectProjectChatView.tsx`、`.../chat/components/DirectProjectConversation/DirectProjectTurn.tsx`、`src-tauri/src/agent/codex_app_server/mod.rs`(用户条目时间的注释口径)、对应 ADR 与实施计划。
- 验证:`npx vitest run tests/directTurnPresentation.test.ts tests/directProjectTurn.test.tsx tests/directProjectTurnStatus.test.ts tests/directThreadChat.test.ts tests/chatComposerAttachmentCap.test.tsx`、`tests/appSurface.test.ts`(213 passed / 9 skipped)、`npx tsc -p tsconfig.json --noEmit`、`eslint`、`prettier --check`、`npm run check:encoding`、`git diff --check` 全绿。真实客户端观感未复核。
@@ -5969,3 +5969,10 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/`
- **排查提示**:先分清两层 —— 逻辑回合的 `turn.started` / `turn.completed`(Thread Manager,一定有、成对)vs app-server 协议的 `turn/start` 请求(连接拿到之后才发)。"失败说明挂错回合"永远先看这条顺序,不要先怀疑事件丢了。
- **验证**:宿主 `the_opening_user_item_is_emitted_before_anything_that_can_fail_in_the_turn`、前端 `本轮用户条目没到时,失败说明按身份挂回自己那一轮,本地气泡不再自成假回合` 与 `收口早退不吞掉还没写进界面的失败说明(订阅重建只回放生命周期锚点)`。
- **关联**:`apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server/mod.rs`、`.../agent/direct_runtime/user_input.rs`、`.../chat/conversation/{directThreadChat.ts,directTurnPresentation.ts}`、`docs/adr/【ADR】DirectProject命令接单化-2026-09-23.md`。
## 2026-09-24 DirectProject「接单窗口里看不到自己刚发的话」是设计,不是丢消息
- **现象**:按下发送后聊天区里不会立刻出现自己那句话;宿主还在接单 / 落盘的那段时间只能看到 composer 忙态与状态行,滚动也停在原地。订阅重建的窗口同理。容易被读成"消息丢了 / 没发出去"。
- **原因**:本地乐观用户气泡已删(ADR「DirectProject命令接单化」后续更新 2026-09-24)。用户气泡的唯一来源是宿主下发的开口条目(发点=接单成立 + 用户条目落盘成功 + 起 codex 之前)。删它的收益是"回合归属只认身份"不再需要给本地消息一份同名身份,投影也少一个展示态(`awaiting-start`)。
- **排查提示**:窗口期不要拿"有没有本地气泡"当发送成功的证据;证据是 `invoke` 返回 `Ok`(接单成立)与随后到达的 `turn.started` / 开口条目。显示时间与耗时也全以宿主事件为准:起点 `turn.started.at`、终点 `turn.completed.at`;重进项目读回来的历史回合两边都空,整条「本轮结束于 … 」直接隐藏(不再出现 0.0 秒)。
- **关联**:`apps/ai-game-creator-shell/src/view/project-development/chat/conversation/directTurnPresentation.ts`、`.../chat/controller/useDirectProjectChatController.ts`、`.../chat/components/DirectProjectConversation/DirectProjectTurn.tsx`、`docs/adr/【ADR】DirectProject命令接单化-2026-09-23.md`。
@@ -120,3 +120,26 @@
`收口早退不吞掉还没写进界面的失败说明(订阅重建只回放生命周期锚点)`。
- 已知边界:`project.jsonl` 里的用户条目依旧只在首屏 / 翻页时读进前端,本次不改这条读取时机——
开口条目的运行态下发与身份归位已经让"说明挂错回合"不再成立。
## 删掉本地乐观用户气泡(2026-09-24)
上一节的"按身份归位"落地后,本地乐观气泡只剩一个作用:把"接单窗口期"变成一种展示态
(`awaiting-start`),并给投影多带一份与宿主条目同身份的本地用户消息。用户确认按"这条消息就像从来
没存在过"处理,于是整套删掉。
- controller:删 `pendingUserItemId`、`beginTurnCommand` / `endTurnCommand`;忙态保留(改叫
`beginTurnBusy` / `endTurnBusy`),宿主认领判据 = `turnRunning` 或收口计数变过(一轮在同一次
consume 里开始并结束)。同时删掉 `startTurn` 的乐观追加、权限确认重跑的 `messageAppended` 参数、
`DirectProjectTurnInput.messageText` 与首轮的 `directInitialTurnText`;controller 不再需要 `assets`。
- 投影 / 渲染:`DirectChatTurnState` 只剩 `running` / `finished`;删 `localSentTimes` /
`sameIdentitySentAt`、本地用户气泡与它开回合的那条路径。本地说明保留:带身份的拒单提示在会话末尾
自成一组(不挂上一轮,也不造耗时文案),不带头身份的壳层 `announce` 照旧挂当前回合末尾。
- 时间口径:起点只认 `turn.started.at`(运行中读实时值、收口后读盖在条目上的值),终点只认
`turn.completed.at`;用户气泡的时钟就是宿主落盘 / 观测时间。历史回合两边都是 0 → 整条
「本轮结束于 … 」隐藏,不再出现 0.0 秒。
- 回归用例:`directTurnPresentation.test.ts`(本地用户消息不进回合、带身份的本地说明自成一组、
两态判据、失败说明按身份归位)、`directProjectTurn.test.tsx`(`running` 不显示终态文案;无边界的
历史回合整条隐藏)、`directProjectTurnStatus.test.ts`、`appSurface` 的
`keeps the accept window silent in the chat and busy in the composer`。
- 已知边界:条目下发之前(接单窗口、订阅重建窗口)聊天区里没有这一轮的任何显示;`project.jsonl`
里的用户条目依旧只在首屏 / 翻页时读进前端。
@@ -61,7 +61,7 @@ Codex 启动时注入的 `host_skills.instructions`、`permissions.instructions`
聊天界面只从 message item 提取 user/assistant 内容;工具 item 不再拼成 `tool: ...` 假文本。
DirectProject 的浏览器层只负责显示和乐观状态,不再调用通用对话写入器。历史读写与回合累计分别位于 `agent/direct_project_history.rs` 和 `agent/direct_project_turn_history.rs`。
DirectProject 的浏览器层只负责显示与本地忙态,不再调用通用对话写入器,也不再造用户消息(本地乐观气泡已删,见 [`【ADR】DirectProject命令接单化-2026-09-23`](../adr/【ADR】DirectProject命令接单化-2026-09-23.md) 的后续更新)。历史读写与回合累计分别位于 `agent/direct_project_history.rs` 和 `agent/direct_project_turn_history.rs`。
`project.jsonl` 的 DirectProject 现行合同只允许 `response_item` envelope。其它模式产生的旧 conversation 行不属于本合同,不得注入 DirectProject。