From dc7feec426cf663788225cd609ee9f1b295ff6dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Wed, 30 Sep 2026 16:39:45 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=87=E6=A1=A3=EF=BC=9ADirectProject=20?= =?UTF-8?q?=E9=98=9F=E5=88=97=E6=9D=A1=E7=9B=AE=E4=BA=8B=E4=BB=B6=E6=8A=95?= =?UTF-8?q?=E5=BD=B1=E3=80=81resolved=5Ftext=20=E6=8C=81=E4=B9=85=E5=8C=96?= =?UTF-8?q?=E4=B8=8E=E5=9F=8B=E7=82=B9=E5=BD=92=E5=AE=BF=E4=B8=BB=E6=94=B6?= =?UTF-8?q?=E5=8F=A3=20-=20ADR=20=E7=8A=B6=E6=80=81=E6=94=B9=E4=B8=BA=2020?= =?UTF-8?q?26-09-30=20=E8=90=BD=E5=9C=B0=E5=AE=8C=E6=88=90=EF=BC=8C=C2=A72?= =?UTF-8?q?=20=E8=A1=A5=E5=85=A5=E9=98=9F=E4=BE=A7=E7=9A=84=E2=80=9C?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E6=9D=A1=E7=9B=AE=E6=A0=A1=E9=AA=8C=E4=B8=8E?= =?UTF-8?q?=E5=86=BB=E7=BB=93=E2=80=9D=EF=BC=8C=C2=A75=20=E5=9F=8B?= =?UTF-8?q?=E7=82=B9=E6=AE=B5=E5=8E=BB=E6=8E=89=E6=B8=B2=E6=9F=93=E4=BE=A7?= =?UTF-8?q?=20discard=20=E4=B8=8E=E5=8F=A5=E6=9F=84=E8=A1=A8=E6=8E=AA?= =?UTF-8?q?=E8=BE=9E=20-=20ADR=20=E8=AF=8D=E8=A1=A8=E6=94=B6=E5=8F=A3?= =?UTF-8?q?=EF=BC=9A=E5=B9=B6=E5=8F=91=E6=8B=92=E5=8D=95/=E6=94=BE?= =?UTF-8?q?=E8=A1=8C=E8=A1=A5=E6=8B=92=E5=8D=95=E5=87=BA=E5=8F=A3/?= =?UTF-8?q?=E6=94=BE=E8=A1=8C=E6=9C=9F=E6=8B=92=E5=8D=95=E4=B8=89=E5=A4=84?= =?UTF-8?q?=E6=94=B9=E6=88=90=E5=85=A5=E9=98=9F=E5=A4=B1=E8=B4=A5=E4=B8=8E?= =?UTF-8?q?=E6=94=BE=E8=A1=8C=E5=A4=B1=E8=B4=A5=E5=8F=A3=E5=BE=84=20-=20AD?= =?UTF-8?q?R=20=C2=A79=20=E4=B8=8E=E5=AE=9E=E6=96=BD=E8=AE=A1=E5=88=92?= =?UTF-8?q?=E5=90=8C=E6=AD=A5=E5=BD=92=E4=BD=8D=E5=90=8E=E7=9A=84=E7=AC=A6?= =?UTF-8?q?=E5=8F=B7=E5=90=8D=EF=BC=9Aactive=5Fturn=5Fid=5Fat=E3=80=81stal?= =?UTF-8?q?e=5Fturn=5Ffor=5Frelease=E3=80=81kick=5Fqueue=5Fdispatch?= =?UTF-8?q?=E3=80=81complete=5Fturn=20-=20=E5=AE=9E=E6=96=BD=E8=AE=A1?= =?UTF-8?q?=E5=88=92=E7=8A=B6=E6=80=81=E6=94=B9=E4=B8=BA=E7=AC=AC=200?= =?UTF-8?q?=E2=80=937=20=E6=AD=A5=E5=85=A8=E9=83=A8=E8=90=BD=E5=9C=B0?= =?UTF-8?q?=EF=BC=8C=E8=BF=9B=E5=BA=A6=E8=A1=A8=E7=AC=AC=206/7=20=E6=AD=A5?= =?UTF-8?q?=E8=A1=A5=E8=90=BD=E5=9C=B0=E8=AF=B4=E6=98=8E=E4=B8=8E=E6=8F=90?= =?UTF-8?q?=E4=BA=A4=E5=8F=B7=EF=BC=8C=E6=96=B0=E5=A2=9E=E3=80=8C2026-09-3?= =?UTF-8?q?0=20=E6=94=B6=E5=8F=A3=E8=AE=B0=E5=BD=95=E3=80=8D=20-=20?= =?UTF-8?q?=E5=AE=9E=E6=96=BD=E8=AE=A1=E5=88=92=E9=A1=B6=E9=83=A8=E8=B7=AF?= =?UTF-8?q?=E5=BE=84=E5=8F=A3=E5=BE=84=E8=A1=A5=20thread=5Fmanager=20?= =?UTF-8?q?=E4=B9=8B=E5=A4=96=E5=8E=BB=E6=8E=89=20direct=5F=20=E5=89=8D?= =?UTF-8?q?=E7=BC=80=E7=9A=84=E5=87=BD=E6=95=B0=E6=94=B9=E5=90=8D=E5=AF=B9?= =?UTF-8?q?=E7=85=A7=20-=20decision-log=202026-09-30=20=E6=9D=A1=E7=9B=AE?= =?UTF-8?q?=E6=8A=8A=E2=80=9C=E8=AE=BE=E8=AE=A1=E7=A8=BF=EF=BC=8C=E5=B0=9A?= =?UTF-8?q?=E6=9C=AA=E5=AE=9E=E6=96=BD=E2=80=9D=E6=8D=A2=E6=88=90=E8=90=BD?= =?UTF-8?q?=E5=9C=B0=E6=8F=90=E4=BA=A4=E9=93=BE=E4=B8=8E=E9=AA=8C=E8=AF=81?= =?UTF-8?q?=E7=BB=93=E8=AE=BA=EF=BC=8C=E5=B9=B6=E8=A1=A5=2009-24=20?= =?UTF-8?q?=E6=9D=A1=E7=9B=AE=E6=8C=87=E5=90=91=E8=90=BD=E5=9C=B0=E8=AF=81?= =?UTF-8?q?=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...命令入队化与待发消息队列归宿主-2026-09-24.md | 26 +++++++++---------- .../shared-memory/decision-log.md | 18 ++++++++++--- ...ject命令入队化与待发消息队列归宿主-2026-09-24.md | 23 +++++++++++++--- 3 files changed, 47 insertions(+), 20 deletions(-) diff --git a/docs/adr/【ADR】DirectProject命令入队化与待发消息队列归宿主-2026-09-24.md b/docs/adr/【ADR】DirectProject命令入队化与待发消息队列归宿主-2026-09-24.md index d3fcc9a62..4ce2c9e2b 100644 --- a/docs/adr/【ADR】DirectProject命令入队化与待发消息队列归宿主-2026-09-24.md +++ b/docs/adr/【ADR】DirectProject命令入队化与待发消息队列归宿主-2026-09-24.md @@ -1,8 +1,8 @@ # 【ADR】DirectProject命令入队化与待发消息队列归宿主 -状态:已接受(2026-09-24 设计定稿;**2026-09-30 落地完成**;**2026-09-30 修订**:队列条目不再另存产物, +状态:已接受(2026-09-24 设计定稿;**2026-09-30 落地完成**,含当日修订条目:队列条目不再另存产物, 引用的解析文本持久化进条目、`prompt` 与 canonical 形状放行时重投影,埋点候选 / settle 两阶段链退役 -(见 §3 / §4 / §5 与「备选方案与取舍」6–9 条)。实施顺序与验收见 +(见 §3 / §4 / §5 与「备选方案与取舍」6–9 条)。实施顺序、落地进度与验证证据见 [`【实施计划】DirectProject命令入队化与待发消息队列归宿主-2026-09-24`](../technical/【实施计划】DirectProject命令入队化与待发消息队列归宿主-2026-09-24.md)) ## 背景 @@ -32,11 +32,11 @@ ### 2. 命令 = 入队 `chat_with_game_creator_direct_codex` 改义改名(建议 `enqueue_direct_codex_turn`),主体是今天"接单前"那条链**原样**跑完: -`clientTurnId` 校验 → 工作流恢复 → 用户条目校验 → prompt 投影 → 前置条件 → 工程准备;通过后**只入队**—— +`clientTurnId` 校验 → 工作流恢复 → 用户条目校验与冻结(引用解析文本写进条目,§5)→ 前置条件 → 工程准备;通过后**只入队**—— 追加 `queue.enqueued`,不登记占用、不落盘用户条目、不发回合事件、不起 codex。 任何一步失败就是**入队失败**,走命令返回的 typed 载荷(`DirectTurnRejection` → `DirectTurnEnqueueFailure`),用户就在现场。 -入队按 `clientTurnId` 幂等,判重范围是"在队 ∪ 正在跑的那一轮";重复入队返回同一次成功,不再是并发拒单。 +入队按 `clientTurnId` 幂等,判重范围是"在队 ∪ 正在跑的那一轮";重复入队返回同一次成功,不再是并发入队失败。 ### 3. 队列归 Thread Manager @@ -62,7 +62,7 @@ Thread Manager 在一个回合收口**之后**原子地做:取队首 → 登 并在同一临界区写 `queue.removed{ dispatched }`(chip 消失与气泡出现在同一批 consume 里,中间没有空窗)。 **放行不重跑任何检查,也不存在"放行失败"这种状态**:放行之后的一切失败都是**回合失败**,走既有 `turn.completed.failure` 通道; -不新增任何失败通道,也不为放行补拒单出口。 +不新增任何失败通道,也不为放行补失败出口。 prompt 同样靠**重投影**,不靠另存:`userItem` 是唯一输入。引用 part 的解析文本(素材摘要 + 引用 UI 设计文档时要展开的 代码上下文,渲染它要往项目里写 `ui/generated-*.js`)由入队检查写进该 part 自己的 `resolved_text`, @@ -104,20 +104,20 @@ prompt 同样靠**重投影**,不靠另存:`userItem` 是唯一输入。引 - `DirectTaonierActiveInvocationGuard` 的**身份**与 Thread Manager 的 `active_turn.turn_id` 是同一件事的两份记录 (GUI 路径下同源字符串),而 09-23 ADR 立的是"同一件事只许有一处真相"。CLI 退役后它的硬阻塞消失:五个读者 (`direct_execution` / `direct_tool_bridge` / `direct_validation` / `direct_project_context` / 付费美术重生成) - 已改读 Thread Manager 的活动回合身份(`direct_active_turn_id_at`),守卫连同它的测试一起删掉,不再有第二份 + 已改读 Thread Manager 的活动回合身份(`active_turn_id_at`),守卫连同它的测试一起删掉,不再有第二份 进程内记录。 - 删守卫的前置判据**已核实(2026-09-30)**,不需要新增"取消路径补无条件终态":`d833ca9d3` 的兜底路径本来就 同时做三件事——释放占用、往事件流补一条 `turn.completed{aborted}`(否则前端会永远停在运行中)、调 - `complete_direct_thread_turn` 解除占用。删掉的只有 `DirectTaonierActiveInvocationGuard` 那张进程内表; + `complete_turn` 解除占用。删掉的只有 `DirectTaonierActiveInvocationGuard` 那张进程内表; 唯一独有的东西是 60 秒启动窗口闸门,改挂在 Thread Manager 的占用登记年龄上 - (`direct_stale_turn_for_release` 的 `NeverReachedExecutor` 分支)。连接死亡那条失败事实由 app-server + (`stale_turn_for_release` 的 `NeverReachedExecutor` 分支)。连接死亡那条失败事实由 app-server 连接层自己落地(`ab970b9fd`),与守卫无关。 ## 备选方案与取舍 1. **状态归宿主、放行留前端**(再用一条 `claim` 命令做 CAS 认领):前端机器原样保留,但队列的继续推进依赖至少一个窗口活着, 且"谁去认领"要靠竞态解决——正是要消掉的东西。作废。 -2. **入队只做形状校验、把前置检查留到放行**:会造出"放行期拒单"——一个没有调用方在等的失败,得为它发明新通道; +2. **入队只做形状校验、把前置检查留到放行**:会造出"放行失败"这种状态——一个没有调用方在等的失败,得为它发明新通道; 而且"首个回合还在建工程、第二条已经排队"这类合法流程会被入队误拒。作废(检查跟着入队走)。 3. **一条 `queue.changed{items:[…]}` 快照事件**代替两条细粒度事件:reducer 更傻,但每次变更搬全量、与既有细粒度事件风格不一致。不选。 4. **`reason` 用字符串**:前端只能猜、无法穷举、无法在类型层穷尽分支。不选,用 typed 枚举。 @@ -153,10 +153,10 @@ prompt 同样靠**重投影**,不靠另存:`userItem` 是唯一输入。引 - **失去手工验证手段**:CLI 退役同时带走"真实二进制驱动执行层生产验证"这条手工路径(夹具脚本一并退役)。 它不在 CI,损失的是排障时的一次性手段,不是门禁。 - **埋点**:成绩由宿主在回合终态直接写,中间不再有候选与 settle:放行那一刻读一次 `identity_generation`、终态再读一次,变了就整条不记。 - 于是前端不再生成、持有或回传任何埋点身份(`analyticsAttemptId` 命令参数、`beginDirectRunAnalytics`、句柄表与 - "入队失败就删掉句柄"一并退役),队列事件也不需要埋点字段。判据口径随放行搬家:从"发送时与终态同代"改成"放行时与终态同代", - 入队后、放行前发生的账号切换因此不再丢弃这一轮的成绩——那一轮确实是在新身份下跑的。 - 平台会话代际翻转的 `discard` 判定仍是渲染侧职责(结算时必须由渲染侧给出)。 + 于是前端不再生成、持有或回传任何埋点身份(`analyticsAttemptId` 命令参数、`beginDirectRunAnalytics`、句柄表、 + settle 调用与"入队失败就删掉句柄"一并退役),队列事件也不需要埋点字段。判据口径随放行搬家:从"发送时与终态同代" + 改成"放行时与终态同代",入队后、放行前发生的账号切换因此不再丢弃这一轮的成绩——那一轮确实是在新身份下跑的。 + 代际比对本身也不再是渲染侧职责:放行侧读一次代次、终态侧再读一次,都在宿主内完成。 ## 明确不做 diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index 23dc9e0c1..c01d2a426 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -9116,8 +9116,9 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在 - 代价:入队即写盘(引用 UI 设计文档的条目会生成 `ui/generated-.js`,取消不撤);检查是时间点事实、 放行不重跑(manifest / 权限 / 目录变化后照旧放行,偏差落到回合失败);队列随进程消失,不做跨进程持久化; 失去"真实二进制驱动执行层生产验证"这条手工路径。 -- 验证方式(设计稿,尚未实施):`docs/adr/【ADR】DirectProject命令入队化与待发消息队列归宿主-2026-09-24.md`、 - `docs/technical/【实施计划】DirectProject命令入队化与待发消息队列归宿主-2026-09-24.md`。 +- 验证方式:设计见 `docs/adr/【ADR】DirectProject命令入队化与待发消息队列归宿主-2026-09-24.md` 与 + `docs/technical/【实施计划】DirectProject命令入队化与待发消息队列归宿主-2026-09-24.md`;落地提交与验证证据 + 见下方 2026-09-30 三条,以及该实施计划的「落地进度」与「2026-09-30 收口记录」。 ## 2026-09-30 守卫清理前置判据核实:取消兜底不需要新增无条件终态,60 秒闸门改挂占用登记年龄 @@ -9145,7 +9146,8 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在 ## 2026-09-30 待发消息条目不再另存产物:队列成员由事件折出,prompt 改为条目重投影 -- 背景:09-24 落地时给 `StoredEvent` 加了一个"挂着的队列条目"字段(`Option`),条目上带七个字段。 +- 背景:09-24 落地时给 `StoredEvent` 加了一个"挂着的队列条目"字段(`Option`, + 归位后是 `agent/thread_manager/mod.rs` 的 `StoredEvent.pending: Option`),条目上带七个字段。 复核发现其中 `client_turn_id` / `user_item` / `creation_type` / `at` 与 `queue.enqueued` 的载荷是同一份事实的第二份拷贝, `canonical_user_item` 是 `serde_json::to_value(user_item)`(第三份),而这两项在构造事件之后再没被读过; `Some/None` 同时兼着"宿主产物袋"与"在队标记"两职,于是取消 / 放行要写两处事实、回收规则还要加"产物还在就不许回收"的防御。 @@ -9166,7 +9168,15 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在 于是 `analyticsAttemptId` 命令参数、候选表 `pending_runs`、`settle_direct_run_analytics`、前端 `beginDirectRunAnalytics` 与句柄表一起删,`queue.enqueued` 也不需要埋点字段。判据口径随放行搬家: 从"发送时与终态同代"改成"放行时与终态同代"——入队后、放行前发生的账号切换不再丢弃这一轮的成绩。 -- 验证方式(设计稿,尚未实施):ADR §3 / §4 / §5 与「备选方案与取舍」6–8 条、实施计划第 6 步。 +- 落地(`2b98b022c` 文档 → `15a495dc2` `resolved_text` 与 prompt 拆 freeze / 纯折叠 → `0270cd601` 埋点归宿主 + → `28b64cf25` 条目改事件投影):`PendingTurn` 只留 `client_turn_id` / `user_item` / `creation_type` / `at` 并配 + `from_event` / `enqueued_event` 往返;`StoredEvent` 上没有队列字段,`pending_turns` 一次折叠出成员与顺序; + `freeze_direct_codex_user_item` 是入队侧唯一算片段 / 写盘处,`direct_codex_user_item_to_prompt(&item)` 变纯折叠; + `direct_finished` 收放行代次并直写 `Request::Terminal`。 +- 验证:`cargo test -- thread_manager::`(62 passed,进程级计数器用例单线程)、`-- agent::`(948 passed)、 + `-- direct_runtime:: analytics:: direct_codex_user_item`(182 passed);`analytics::store_tests` 换成"终态直写" + 与"放行后代际变化不记"两条用例;`npx vitest run tests/appSurface.test.ts`(194 passed)与 chat / direct 定向套件 + (249 passed);`npm --workspace apps/ai-game-creator-shell run typecheck`、`npm run check:encoding`、`git diff --check`。 ## 2026-09-28 渲染层下沉分支与最新 master 对齐:活动回合事实源、维护态出口与诊断详情 diff --git a/docs/technical/【实施计划】DirectProject命令入队化与待发消息队列归宿主-2026-09-24.md b/docs/technical/【实施计划】DirectProject命令入队化与待发消息队列归宿主-2026-09-24.md index e8448b384..cf6e3b0c2 100644 --- a/docs/technical/【实施计划】DirectProject命令入队化与待发消息队列归宿主-2026-09-24.md +++ b/docs/technical/【实施计划】DirectProject命令入队化与待发消息队列归宿主-2026-09-24.md @@ -2,11 +2,14 @@ 更新时间:`2026-09-24` -状态:**第 0–5 步已落地;第 6、7 步进行中**(2026-09-30 修订) +状态:**第 0–7 步全部落地**(2026-09-30 修订并收口) > 文件路径口径(2026-09-30):本文里 2026-09-24 写的 `direct_thread_manager.rs` / `direct_thread_wire.rs` / > `direct_thread_queue.rs` / `direct_turn_dispatch.rs` 现在分别是 `agent/thread_manager/{mod,wire,queue,dispatch}.rs`, > 类型与函数去掉 `Direct` 前缀(`ThreadEvent`、`PendingTurn`、`subscribe_thread` …,见提交 `f121257cc`)。 +> 同一次归位里 thread_manager 之外的几个函数也去掉了 `direct_` 前缀:`direct_active_turn_id_at` → `active_turn_id_at`、 +> `direct_stale_turn_for_release` → `stale_turn_for_release`、`kick_direct_queue_dispatch` → `kick_queue_dispatch`、 +> `complete_direct_thread_turn` → `complete_turn`。 设计口径见 [`【ADR】DirectProject命令入队化与待发消息队列归宿主-2026-09-24`](../adr/【ADR】DirectProject命令入队化与待发消息队列归宿主-2026-09-24.md)。 本文件只排实施顺序、不变式与验收,不重复设计理由。 @@ -21,8 +24,8 @@ | 第 3 步 前端收口 | 已落地 | 见下面「第 3 步的落地细则」 | | 第 4 步 CLI 与夹具退役 | 已落地 | 删 `CliCommand::DirectCodexChat`(变体 / `project_path_mut` / 解析 / 派发)、`run_direct_game_creator_turn_at` 一对包装函数与夹具脚本;顺带删掉只剩测试在用的 `direct_turn_error_boundary_text`(判据只剩 `direct_turn_enqueue_failure` 一处),三条边界测试改打 `direct_turn_enqueue_failure(...).message`。**`TurnAlreadyRunning` 挪到第 5 步**(它最后一个生产点在调用身份守卫里) | | 第 5 步 守卫清理 | 已落地 | 五个身份读者改读 `direct_active_turn_id_at`;删 `DirectTaonierActiveInvocationGuard` / `DirectActiveTurnView` / 只读探测与 `release_stale_direct_taonier_active_invocation`(改 `direct_stale_turn_for_release` 只做前置校验);删 `DirectTurnError::TurnAlreadyRunning` 与前端分支 / 生成绑定;补前置判据用例 | -| 第 6 步 条目不再另存产物 | 进行中 | 删 `StoredEvent.pending` 与 `PendingTurn` 的存储形态(改事件投影);引用解析文本落 `AgcResourceReference.resolved_text`(持久化);`canonical` 与 `prompt` 放行时重投影 | -| 第 7 步 埋点成绩归宿主 | 进行中 | 删候选 / settle 两阶段与 `analyticsAttemptId`(含命令参数、前端句柄表与结算 effect);放行读 `identity_generation`、终态比对后直写 | +| 第 6 步 条目不再另存产物 | 已落地 | `PendingTurn` 就是 `queue.enqueued` 的投影(`from_event` / `enqueued_event` 往返),删掉 `canonical_user_item` 与 `prompt` 两个宿主私有产物字段;`StoredEvent` 上没有队列条目字段(`pending_turns` 一次事件折叠);引用解析文本落 `AgcResourceReference.resolved_text` 并随条目持久化;`canonical` 与 `prompt` 放行时重投影(提交 `15a495dc2` / `28b64cf25`) | +| 第 7 步 埋点成绩归宿主 | 已落地 | 删候选 / settle 两阶段与 `analyticsAttemptId`(`Request::DirectCandidate` / `Request::Settle` / `settle` / `pending_runs` / `settle_direct_run_analytics` / 前端句柄表与结算 effect);放行读一次 `identity_generation`、终态再读一次后直写 `Request::Terminal`(提交 `0270cd601`) | ## 第 0 步:词表切换(与代码同批,不单独提交) @@ -234,3 +237,17 @@ kick 幂等(并发两次只认领一次);队首在放行后被移除、rem `kill -9` 后重进队列消失(与 ADR 的已知边界一致)。 - 全仓:`npm run check:encoding`、`npm run check:doc-index`、`git diff --check`。 - 不涉及 SpacetimeDB schema,不需要 `npm run check:spacetime-schema`。 + +## 2026-09-30 收口记录 + +第 6、7 步按上面两节落地,提交按"文档定稿 → 引用解析文本持久化 → 埋点归宿主 → 条目改事件投影"切开: +`2b98b022c`(文档)、`15a495dc2`(`resolved_text` 与 prompt 拆 freeze / 纯折叠)、`0270cd601`(埋点成绩归宿主)、 +`28b64cf25`(条目改事件投影)。 + +- Rust:`cargo test --bin genarrative-ai-game-creator-shell -- thread_manager::`(62 passed,进程级计数器用例单线程跑)、 + `-- agent::`(948 passed;`design_runtime` 与两条历史并发用例在整包并行下偶发,单跑通过,与本次改动无关)、 + `-- direct_runtime:: analytics:: direct_codex_user_item`(182 passed)、`-- analytics_real_file_write_...`(1 passed)。 +- Node:`npx vitest run tests/appSurface.test.ts`(194 passed)、`tests/directThreadChat.test.ts`、 + `tests/directHistoryPaging.test.ts`、`tests/directProjectTurn.test.ts` 等 chat / direct 单测(249 passed)、 + `npm --workspace apps/ai-game-creator-shell run typecheck` 通过。 +- 全仓:`npm run check:encoding`、`npm run check:doc-index`、`git diff --check`。