合并 origin/master 最新变更
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m50s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Failing after 2m8s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m25s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Failing after 3m38s
Project CI / Frontend tests (pull_request) Successful in 2m51s
Project CI / Backend tests (pull_request) Successful in 4m31s
Project CI / Repository checks (pull_request) Successful in 2m52s
Project CI / AI game creator shell web tests (pull_request) Successful in 1m46s
Project CI / Native shell tests (pull_request) Successful in 6m16s

合入主分支 73090437f,保留现役认证、发布、线程队列与 UI 工作流
协调桌面接线拆分与共享测试编译边界,迁移取消回合断言
通过配置、壳契约、编码及文档检查;Rust 检查受本地 Claude SDK 随包依赖缺失阻塞
This commit is contained in:
2026-10-01 06:43:54 +00:00
737 changed files with 68024 additions and 32955 deletions
+10 -1
View File
@@ -21,7 +21,9 @@
- [当前产品与工程约束](./【项目基线】当前产品与工程约束-2026-05-15.md):现役入口、账号钱包、UI 和后端分层。
- [平台入口与玩法链路](./【玩法创作】平台入口与玩法链路-2026-05-15.md):只描述现役平台壳与图片画布编辑器链路。
- [外部 OpenAPI 与 API Key 接入方案](./【后端架构】外部OpenAPI与APIKey接入方案-2026-06-19.md)
- [外部 MCP 语义工具说明与参数设计](./technical/【技术方案】外部MCP语义工具说明与参数设计-2026-09-23.md):15 个新增语义工具与全部原工具并存,复用现有 External API;包含工具说明、action、参数、幂等和兼容合同。
- [External v1 OpenAPI](./openapi/genarrative-external-v1.openapi.json):公开 HTTP 契约唯一机器可读来源。
- [External v1 游戏场景生成路由](./technical/【技术方案】ExternalV1游戏场景生成路由-2026-09-24.md):external v1 结构化场景生成专用路由与 AGC 美术包背景阶段迁移合同。
## AI 游戏创作与 Agent Runtime
@@ -36,14 +38,20 @@
- [AI 游戏创作智能体 App 实施计划](./technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md):当前 DirectProject、受控语义工具、UI workflow、资源和运行时合同。
- [AGC 后端框架整理与演进路线](./technical/【技术方案】AGC后端框架整理与演进路线-2026-09-18.md):共享 Runtime、本地执行宿主、云端控制面、领域/平台适配器及分阶段收口边界。
- [AGC 随包资源 staging 归位](./technical/【技术方案】AGC随包资源staging归位-2026-09-26.md):随包资源改由准备步骤在 `tauri dev|build` 之前一次性生成、`build.rs` 退化为校验者;含缓存与原子性合同、入口接线、验收判据与里程碑拆分。
- [AGC 异步操作可恢复闭环](./【技术方案】AGC异步操作可恢复闭环-2026-09-14.md):认证响应体、最近项目检查和首页自动创建的超时、逐项恢复与跨页防重合同。
- [AGC 客户端稳定版生命周期大切换](./【技术方案】AGC客户端稳定版生命周期大切换-2026-09-14.md):统一 operation、认证/Runner、项目入口、本地恢复和 dev-stack 身份边界。
- [策划会话 Runtime V2 接入与旧链路退役方案](./technical/【技术方案】策划会话RuntimeV2接入与旧链路退役-2026-09-03.md):历史方案,仅用于追溯 V2 的实现与退役过程,不作为当前实现依据。
- [DirectProject Codex 原始历史与异常恢复](<./technical/【技术方案】DirectProject Codex原始历史与异常恢复-2026-09-04.md>):原始 Responses item 持久化、线程注入与异常回合收尾。
- [DirectProject 对话历史单一事实源](./adr/【ADR】DirectProject对话历史单一事实源-2026-09-16.md):AGC 项目开发对话只以项目对话历史与运行态事件为真相源,聊天投影不落盘。
- [DirectProject 独立聊天容器与工作台钱包布局](./adr/【ADR】DirectProject独立聊天容器与工作台钱包布局-2026-09-18.md):DirectProject 与 Supervisor 等路径分容器,钱包入口由项目工作台布局独立承载。
- [UI 工作流检查点用追加式 JSONL 日志](./adr/【ADR】UI工作流检查点用追加式JSONL日志-2026-09-23.md):UI 设计文档的 Agent 工作流用文档旁追加式 JSONL 记录步骤完成,替代每步一个 sidecar 状态机。
- [退役 AGC 项目对话斜杠命令](./adr/【ADR】退役AGC项目对话斜杠命令与终端swarm chat入口-2026-09-22.md):AGC 项目对话与终端 swarm chat 均不再解析斜杠命令,终端聊天入口一并退役;实现、测试、门禁与文档承诺全部删除,命令 id 与权限位作为项目策略词汇表保留。
- [引用候选由宿主注入](./adr/【ADR】引用候选由宿主注入-2026-09-22.md):引用输入区只接受宿主注入的引用 provider,素材选择面板独立成组件,附件芯片成为本轮附件唯一事实源。
- [DirectProject 命令接单化](./adr/【ADR】DirectProject命令接单化-2026-09-23.md):命令只负责接单、事件流回答整轮结果;拒单前置、失败后置。
- [DirectProject 命令接单化实施计划](./technical/【实施计划】DirectProject命令接单化-2026-09-23.md):四步落地顺序、每步不变式与验收;四步均已落地。
- [DirectProject 命令入队化与待发消息队列归宿主](./adr/【ADR】DirectProject命令入队化与待发消息队列归宿主-2026-09-24.md):命令只负责入队,放行归 Thread Manager;待发消息队列作为运行态事件归宿主、前端只投影;CLI 直连入口与调用身份守卫一并退役。
- [DirectProject 命令入队化与待发消息队列归宿主实施计划](./technical/【实施计划】DirectProject命令入队化与待发消息队列归宿主-2026-09-24.md):五步落地顺序、每步不变式与验收;待实施。
- [GameAgent 对话工具调用卡片](./technical/【技术方案】GameAgent对话工具调用卡片-2026-09-14.md):把右侧对话里的执行命令 / 写文件投影成 Codex 风格可折叠卡片,含采集、独立历史文件、事件字段与回读契约。
- [DirectProject 客户端 Skill 与 MCP 扩展导入方案](./technical/【技术方案】DirectProject客户端Skill与MCP扩展导入方案-2026-08-31.md):客户端扩展导入、按独立 Skill/MCP 拆分、命名、启用和启动时注入边界。
- [AGC 通用插件宿主与编辑器适配](./technical/【技术方案】AGC通用插件宿主与编辑器适配-2026-09-09.md):通用插件宿主、SDK、权限审计、UI 挂载和 Cocos 编辑器适配边界。
@@ -64,7 +72,8 @@
- [AGC 资源派生与非破坏性编辑合同](./technical/【技术方案】AGC资源派生与非破坏性编辑合同-2026-09-09.md):AGC 全类型现有资源非破坏性编辑的权威合同,约束资源派生、替换与写回边界。
- [AGC 聊天素材引用](./【功能说明】AGC聊天素材引用-2026-09-08.md):聊天输入框 @ 引用项目素材的入口、引用模型与「当前版本素材」口径。
- [AGC 聊天 AI 润色与发送前提醒](./【功能说明】AGC聊天AI润色与发送前提醒-2026-09-10.md):提示词润色与发送前提醒的交互、失败与取消口径。
- [UI 工作流资源桥接与 Runtime 执行](./【技术方案】UI工作流资源桥接与Runtime执行-2026-08-24.md)
- [UI 编辑器代码地图与模块职责](./technical/【技术方案】UI编辑器代码地图与模块职责-2026-09-23.md):Rust `ui_editor` 模块、`features/ui-editor` 语义层与 `view/ui-editor` 视图层的职责划分与扩展指引。
- [UI 编辑器 Agent 工具化重写](./technical/【技术方案】UI编辑器Agent工具化重写-2026-09-23.md):三个工具(建文档 / 跑工作流 / 出 JS)的契约、两步工作流、JSONL 检查点与模块布局。
- [UI 编辑器 Godot 容器布局](./technical/【技术方案】UI编辑器Godot容器布局模型-2026-08-18.md)
- [UI 编辑器变换角点偏移编辑器](./technical/【设计】UI编辑器变换角点偏移编辑器-2026-09-03.md)
- [UI 编辑器子节点显示规则](./technical/【技术方案】UI编辑器子节点显示规则-2026-08-18.md)
@@ -0,0 +1,180 @@
# 【ADR】DirectProject命令入队化与待发消息队列归宿主
状态:已接受(2026-09-24 设计定稿;**2026-09-30 落地完成**,含当日修订条目:队列条目不再另存产物,
引用的解析文本持久化进条目、`prompt` 与 canonical 形状放行时重投影,埋点候选 / settle 两阶段链退役
(见 §3 / §4 / §5 与「备选方案与取舍」6–9 条)。实施顺序、落地进度与验证证据见
[`【实施计划】DirectProject命令入队化与待发消息队列归宿主-2026-09-24`](../technical/【实施计划】DirectProject命令入队化与待发消息队列归宿主-2026-09-24.md))
## 背景
待发消息队列今天是纯前端状态:回合运行中用户再发送就进本地 FIFO
(`chat/components/DirectProjectComposer/chatComposerQueue.ts`),回合终态事件到达后由**恰好开着的那个窗口**放行队首
(`useDirectProjectChatController.ts` 的完成计数 effect 与 `startTurn` 的 `finally`)。三个问题:
1. 队列是这条对话里唯一没有宿主持有者的事实:另一个窗口、另一个订阅者,或只是离开工作台再回来,都看不到已经排了什么队。
2. 放行落在"哪个窗口恰好开着"上。队列一旦共享(本 ADR 要做的),两个窗口都会去放行队首,必然双发。
3. 命令边界今天写的是"接单":校验通过就登记占用、落盘用户条目、起整轮。但用户按下发送时想要的是"这条消息会被依次处理"——
命令的成功含义与用户意图之间隔着一次长度未定义的等待。
前置口径:`【ADR】DirectProject命令接单化-2026-09-23` 已把逻辑回合收归 Thread Manager,并在 §8 留了 TODO
「以后这条队列挪到 Rust 端,落点就是 Thread Manager 的接单动作」,同时把 Rust 端发送队列列进"明确不做"。
本 ADR 就是那条 TODO 的收口,并顺带收掉两处已经没有现役价值的实现。
## 决策
### 1. 词表:入队 / 入队失败 / 放行
- 命令边界的成功与失败改叫 **入队 / 入队失败**;「接单」「拒单」两个词退役,不再出现在文档、注释、标识符与测试名里。
- 旧「接单」在语义上的角色(这一轮真正成立的那一刻)改叫 **放行**。
- 因此旧句「接单成立 ⇔ 事件流里有开始有结束」要改写成「**放行成立** ⇔ 事件流里有开始有结束」。这不是换词而是角色搬家:
逐处改写时按角色判,不做字面替换(命令边界→入队/入队失败;回合成立→放行;检查归属→入队时的检查)。
### 2. 命令 = 入队
`chat_with_game_creator_direct_codex` 改义改名(建议 `enqueue_direct_codex_turn`),主体是今天"接单前"那条链**原样**跑完:
`clientTurnId` 校验 → 工作流恢复 → 用户条目校验与冻结(引用解析文本写进条目,§5)→ 前置条件 → 工程准备;通过后**只入队**——
追加 `queue.enqueued`,不登记占用、不落盘用户条目、不发回合事件、不起 codex。
任何一步失败就是**入队失败**,走命令返回的 typed 载荷(`DirectTurnRejection` → `DirectTurnEnqueueFailure`),用户就在现场。
入队按 `clientTurnId` 幂等,判重范围是"在队 ∪ 正在跑的那一轮";重复入队返回同一次成功,不再是并发入队失败。
### 3. 队列归 Thread Manager
- 每个 thread(线程身份就是项目规范路径)一条 FIFO;本期只支持**按顺序追加**与**按身份移除**,不做重排、优先级、编辑。
- 队列**就是事件列表本身**:在队条目 = 事件窗口里"有 `queue.enqueued`、且还没有配对 `queue.removed`"的那些事件,事件顺序即队首到队尾。
宿主不为条目另存第二份产物(`StoredEvent` 上没有"挂着的队列条目"这种字段),成员与顺序都从事件折出来。
`queue.enqueued` 在队期间不可回收,离开队列(取消或放行)时与配对的 `queue.removed` 一起转可回收并被既有规则回收。
`is_bootstrap_event` 不改——`subscribe` 的 live-set bootstrap 因此天然把当前队列交给新订阅者,这就是"加入者看到的那几条"。
- 上限 5 只数**在队条目**(不数正在跑的那一轮),由 Rust 持有;满队时入队失败并给出既有提示文案。
### 4. 线上形状
- `queue.enqueued { clientTurnId, userItem, creationType?, at }`:`userItem` 是 canonical 用户条目,前端据此派生 chip 文案,
Rust 不渲染、不裁成展示形状。事件**不带** prompt(prompt 不是下发形状,放行时由条目重投影,§5),
也不带埋点身份(成绩由宿主自己结算,§5)。队列事件**没有**宿主私有的第二类字段:这一条消息的全部事实就是它自己。
- `queue.removed { clientTurnId, reason }`:`reason` 是 ts-rs 导出的 **typed 枚举**(`cancelled | dispatched`),永不用字符串,
形状与 `turn.completed{status, failure?}` 同构。
- 两条事件与其它运行态事件同一条流、同一个 reducer。
### 5. 放行 = 旧「接单」的语义角色
Thread Manager 在一个回合收口**之后**原子地做:取队首 → 登记占用 → 发 `turn.started` → 落盘用户条目 → 下发用户条目 → 起整轮,
并在同一临界区写 `queue.removed{ dispatched }`(chip 消失与气泡出现在同一批 consume 里,中间没有空窗)。
**放行不重跑任何检查,也不存在"放行失败"这种状态**:放行之后的一切失败都是**回合失败**,走既有 `turn.completed.failure` 通道;
不新增任何失败通道,也不为放行补失败出口。
prompt 同样靠**重投影**,不靠另存:`userItem` 是唯一输入。引用 part 的解析文本(素材摘要 + 引用 UI 设计文档时要展开的
代码上下文,渲染它要往项目里写 `ui/generated-*.js`)由入队检查写进该 part 自己的 `resolved_text`,
放行、历史回读、turn input 三个读点共用这一份——**历史必须回放出当初那条消息**,所以它是条目事实的一部分、随条目持久化,
不是内存备忘;字段缺省即合法(`serde(default)`),旧历史没有它照样解析、按当前 manifest 现算。于是放行侧的 prompt 是条目的纯投影:零 IO、零校验、
不可失败,与"放行不重跑任何检查"同一个口径。
埋点成绩同样不靠渲染侧结算:`identity_generation`(`platform_session`:只在登录主体 / 服务 origin / 登出状态变化时推进)
在**放行**那一刻被读一次,回合终态再读一次,变了就整条不记。所以队列事件上不需要任何埋点身份字段,
也不需要"先写候选、等渲染侧确认"的两阶段。
### 6. 放行的触发与监护顺序
- 唯一放行点:回合任务收尾之后的 `kick`(正常 / 失败 / 中止三条路径共用,外加一个 drop 守卫盖 panic),幂等,并且在临界区里原子认领队首。
- 入队时也踢一脚:「队列非空 + 线程空闲」是合法状态,对应今天的"直接发送"。
- 入队**不取** `DirectTaonierActiveInvocationGuard`:它必须整轮持有(它是这一轮的调用身份,付费美术、执行会话、MCP、校验、
上下文预取都靠它把工作归属到自己那一轮),入队若取它等于"回合运行中不能入队"。入队路径的并发由工程准备自身的项目写锁与队列兜。
### 7. 渲染侧
前端不再持有队列副本:chip 只由事件投影(入队被拒时只由命令返回值给反馈,保留既有提示文案与"不丢草稿"行为);
忙态只由事件投影加「队列非空」指示。排队消息在放行前**不写** `project.jsonl`,用户气泡仍然只来自宿主条目("落盘即放行"不变)。
### 8. 作用域与寿命
每项目一条、全进程共享:A 窗口排队 B 窗口可见可取消;切项目、离开工作台、关窗口都不影响队列继续放行;进程结束队列消失。
**不做跨进程持久化**。
### 9. 顺带退役
- `--direct-codex-chat` 整个退役(解析、派发,以及只服务它的 `run_direct_game_creator_turn_at` 一对函数)。
它的历史用途只有一个:手工生产验证夹具 `scripts/direct-execution-production-fixture.mjs`(PR #439 引入,不在 CI、
没有 npm 脚本或 harness 注册、没有任何测试钉它,唯一硬依赖是进程退出码)。没有产品入口价值,也不该在入队化之后
成为第二条直接起回合的路径;夹具脚本一并退役。
- 夹具的一键入口 `scripts/run-agc-direct-execution-fixture.mjs`(09-29 新增,只把 `--agc-exe` 默认成工作区
debug 二进制)与 `package.json` 的 `check:agc-direct-execution-fixture` 随夹具一起删:夹具没了它没有第二个消费者。
- CLI 一退,`DirectTurnError::TurnAlreadyRunning` 的两个生产点(调用身份守卫、占用登记)都没有调用方,
它连同前端"同一轮消息仍在处理中"文案、专属分支与测试一起删。
- `DirectTaonierActiveInvocationGuard` 的**身份**与 Thread Manager 的 `active_turn.turn_id` 是同一件事的两份记录
(GUI 路径下同源字符串),而 09-23 ADR 立的是"同一件事只许有一处真相"。CLI 退役后它的硬阻塞消失:五个读者
(`direct_execution` / `direct_tool_bridge` / `direct_validation` / `direct_project_context` / 付费美术重生成)
已改读 Thread Manager 的活动回合身份(`active_turn_id_at`),守卫连同它的测试一起删掉,不再有第二份
进程内记录。
- 删守卫的前置判据**已核实(2026-09-30)**,不需要新增"取消路径补无条件终态":`d833ca9d3` 的兜底路径本来就
同时做三件事——释放占用、往事件流补一条 `turn.completed{aborted}`(否则前端会永远停在运行中)、调
`complete_turn` 解除占用。删掉的只有 `DirectTaonierActiveInvocationGuard` 那张进程内表;
唯一独有的东西是 60 秒启动窗口闸门,改挂在 Thread Manager 的占用登记年龄上
(`stale_turn_for_release` 的 `NeverReachedExecutor` 分支)。连接死亡那条失败事实由 app-server
连接层自己落地(`ab970b9fd`),与守卫无关。
## 备选方案与取舍
1. **状态归宿主、放行留前端**(再用一条 `claim` 命令做 CAS 认领):前端机器原样保留,但队列的继续推进依赖至少一个窗口活着,
且"谁去认领"要靠竞态解决——正是要消掉的东西。作废。
2. **入队只做形状校验、把前置检查留到放行**:会造出"放行失败"这种状态——一个没有调用方在等的失败,得为它发明新通道;
而且"首个回合还在建工程、第二条已经排队"这类合法流程会被入队误拒。作废(检查跟着入队走)。
3. **一条 `queue.changed{items:[…]}` 快照事件**代替两条细粒度事件:reducer 更傻,但每次变更搬全量、与既有细粒度事件风格不一致。不选。
4. **`reason` 用字符串**:前端只能猜、无法穷举、无法在类型层穷尽分支。不选,用 typed 枚举。
5. **保留 CLI**:省掉夹具改写,但等于为手工验证工具长期保留第二条直接起回合的路径,与"命令只有入队一个入口"冲突。不选。
6. **宿主侧另存条目产物**(`StoredEvent.pending` 挂 `clientTurnId` / `userItem` / `canonical_user_item` / `prompt` / `creation_type` / `at`):
前四项与 `queue.enqueued` 的载荷是同一份事实的第二、第三份拷贝,取消与放行要同时改两处,回收规则里还得加"产物还在就不算可回收"
的防御来兜住不一致。作废:条目就是事件载荷的投影,claim 时现推、不存。
7. **把整条 prompt 存进条目**:prompt 是 `userItem` 的投影,存整条等于把 `userItem` 的 JSON 再存一遍;唯一的例外是引用 UI 设计文档时
要写盘的那段代码上下文。改为只把算不出的片段冻结在引用 part 上,整条 prompt 从条目重投影。
8. **用 `clientTurnId` 兼作埋点身份**:埋点候选的 `attempt_id` 必须是 UUID(`analytics/run.rs` 的 `validate`),
而 `clientTurnId` 在 WebView 没有 `crypto.randomUUID` 时会退化成时间戳 + 序号的形状。不选,两个身份各留在自己那一层。
9. **保留"宿主写候选 + 渲染侧 settle"两阶段埋点**:入队化之后一次发送只有一次尝试(命令只入队、放行由宿主自己做),
"哪一次尝试算数"不再是渲染侧才知道的事;宿主自己就有 `identity_generation`,直接写终态即可。
候选表、`settle` 命令与 attempt id 一起退役,成绩也不再依赖"恰好有一个窗口在消费终态事件"。
## 影响与代价
- **入队即写盘**:引用 UI 设计文档的条目在 prompt 投影时会生成 `ui/generated-<stem>.js`(`ui_editor/persistence.rs`);
从队列里取消不撤该文件。
- **检查是时间点事实**:放行不重跑,按入队那一刻的结论放行;manifest、权限、目录在入队之后变化也照旧放行,偏差落到回合失败。
- **prompt 形状随冻结片段走**:引用的 UI 设计文档代码上下文不再统一追加在 prompt 末尾,而是跟在它所属的引用片段里。
只引用一条时 prompt 与改前逐字节相同;多条引用时块的先后变、语义不变。
- **队列随进程消失**:待发消息只在内存与事件流里,`kill -9` / 退出后重进看不到(与 09-16 ADR 的 `kill -9` 口径一致)。
- **退役面**:前端 `completionPendingRef`、`handledCompletedTurnCountRef`、`busyBaselineTurnCountRef`、`dispatchNextQueuedTurn`、
`queueSequenceRef`、`queuedTurns*` 与 `chatComposerQueue.ts` 整体退役(含它的上限常量与"队列已满"文案——
上限只留宿主一处);`turnBusy`/`beginTurnBusy`/`endTurnBusy` 改名 `commandInFlight` 并收窄成 IPC 在飞;
`directProjectTurnStatus` 的忙态改成「原生在跑 ∨ IPC 在飞 ∨ 待发消息非空」。
队列投影落在 `chat/conversation/directPendingTurns.ts`,chip 文案落在
`chat/components/DirectProjectComposer/pendingTurnChipLabel.ts`。
- **词表切换是一次性跨文档动作**:仓库里「接单/拒单」共 384 处,并非全属同一个域
(`features/agent-runtime` 的"拒单文案"属另一个域,改成"请求被拒";`单测` 这类是假阳性)。
两份已接受的 ADR 保留正文与文件名,顶部加词表注记。
- **失去手工验证手段**:CLI 退役同时带走"真实二进制驱动执行层生产验证"这条手工路径(夹具脚本一并退役)。
它不在 CI,损失的是排障时的一次性手段,不是门禁。
- **埋点**:成绩由宿主在回合终态直接写,中间不再有候选与 settle:放行那一刻读一次 `identity_generation`、终态再读一次,变了就整条不记。
于是前端不再生成、持有或回传任何埋点身份(`analyticsAttemptId` 命令参数、`beginDirectRunAnalytics`、句柄表、
settle 调用与"入队失败就删掉句柄"一并退役),队列事件也不需要埋点字段。判据口径随放行搬家:从"发送时与终态同代"
改成"放行时与终态同代",入队后、放行前发生的账号切换因此不再丢弃这一轮的成绩——那一轮确实是在新身份下跑的。
代际比对本身也不再是渲染侧职责:放行侧读一次代次、终态侧再读一次,都在宿主内完成。
## 明确不做
- 不做跨进程持久化(不在入队时写 `project.jsonl`:那会在历史里留下一条永远不会跑的假消息,破坏"用户气泡只来自宿主条目")。
- 不做重排、优先级、编辑待发条目;也不做"队列挂起 / 继续"状态——入队化之后队列里不存在会被拒的条目。
- 不给放行新增失败通道,不为"放行不重跑检查"补兜底。
- 不保留 CLI,也不为夹具保留别名或兼容入口。
## 落地时要同步的文档与注释
- `CONTEXT.md`:词条(待发消息队列 / 待发消息 / 入队 / 入队失败 / 放行 / 逻辑回合 / 在途回合)。
- `docs/adr/【ADR】DirectProject命令接单化-2026-09-23.md`:§8 的 TODO 与"明确不做:Rust 端发送队列"、§7 的"命令在飞"措辞、
CLI 保持 await 的分工,全部改为入队化口径(正文保留,顶部加词表注记)。
- `docs/technical/【技术方案】DirectProject Codex原始历史与异常恢复-2026-09-04.md`:事件协议一节补两个队列事件与放行时序。
- `docs/technical/【实施计划】DirectProject命令接单化-2026-09-23.md`:第 4 步的队列 TODO 指向本 ADR。
- `docs/technical/【技术方案】Direct回合行为审计账本-2026-08-31.md`、`【技术方案】DirectProject本轮附件路径映射-2026-08-31.md`、
`docs/project-memory/plans/【里程碑】退役AGC项目对话斜杠命令与终端swarm chat入口-2026-09-22.md`:删掉对 CLI 入口的承诺
(最后那处现在写在"范围外(保留)"里,口径反转)。
- `docs/project-memory/shared-memory/decision-log.md`:追加本决定,并修正"CLI 保持 await"那条。
- 代码注释:`agent/direct_runtime/user_input.rs` 模块注释里的 CLI 分工一段删掉;`agent/thread_manager/` 的
`dispatch.rs` / `mod.rs` 与 `useDirectProjectChatController.ts` 的旧措辞与 TODO 一并改。
@@ -0,0 +1,187 @@
# 【ADR】DirectProject命令接单化
状态:已接受(2026-09-23 落地,实施顺序与验收见
[`【实施计划】DirectProject命令接单化-2026-09-23`](../technical/【实施计划】DirectProject命令接单化-2026-09-23.md))
## 背景
`chat_with_game_creator_direct_codex` 现在从校验一路 await 到交付验证结束,一个命令调用覆盖整轮。
于是命令边界承担了两件不属于它的事:
1. **回合失败的可见文案有两条来源。** 事件载荷 `turn.completed.failure.message` 是聊天里那条失败说明的
来源,命令 Err 是横幅与 `详情:` 引用的来源。两者各有分工,但都由"这一轮结束"这个时刻触发,
前端 `runTurn` 的 catch 因此同时兼职"接单被拒"与"回合失败"两种回执。
2. **认证失败重试只能挂在这条 Err 上。** `withDirectCodexSessionRefresh` 在登录态失效后刷新会话并
**重跑整个 operation**。重跑会再写一条用户消息:单飞锁随命令返回就已经释放,所以这条重跑路径今天
会往历史里写第二条一样的用户消息。
还有一个先天的洞:回合边界今天**镜像 Codex 原生回合**——开始事件只在 `turn/start` 成功应答之后才进队列
(`direct_runtime/user_input.rs:81` 之后要一路走到 app-server),于是"接单到 `turn/start` 之间"的失败
(连不上 app-server、配置未就绪、历史注入失败、`turn/start` 被拒)没有任何事件可以解释,只能靠命令 Err。
命令一旦不再 await,这些路径就会静默。
## 决策
### 1. 命令 = 接单 / 拒单
命令只做:`clientTurnId` 校验 → 占用调用身份(并发拒单)→ 工作流恢复 → 用户条目校验 → 工程准备
→ 接单成立 → 用户条目落盘 → 起 codex。成功后立刻返回,不在命令里等回合。
这里的"占用调用身份"只挡并发(早于工程准备,避免两个请求同时做准备),与 §2 的"登记逻辑回合占用"
不是同一件事:后者拥有这一轮的终态出口。
**接单成立之前的任何失败都是拒单**:不产生回合事件、不写用户条目、不写失败诊断。
### 2. 逻辑回合由 Thread Manager 拥有
- 接单动作在 Thread Manager 内**原子地**完成"拒绝并发 / 登记占用 / 发出逻辑回合开始事件"。
- 这条生命周期**不是** Codex 原生回合的镜像:发点在接单时,不在 `turn/start` 应答后;Codex 原生回合事件
留在适配器内部,不再进事件队列。线上仍然只有**一对** `turn.started` / `turn.completed`。
- 这一轮的**占用对象是唯一终态出口**,并且幂等:正常 / 失败 / 中断 / 取消 / 连接断开谁先到谁写;任务
panic 或被取消时由它兜底补一条终态(保留 `host-dropped` 分类,只给"说不出原因"的这一种)。终态写出后
占用才释放。
- 因此"接单成功 ⇔ 事件流里有开始且有结束"是结构性成立,不依赖实现者记得给每条"接单后提前收场"
(早退:回合内任何没走到正常终态的收口点,比如 `turn/start` 被拒、注入失败、panic)的路径补事件。
- **终态的写点在整轮真正结束之后**(执行结果收集、历史落盘、structured output 解析都定型):解析失败
也是这一轮的失败,落进同一份失败载荷。终态一旦先写成 `completed`,后面再失败的步骤就没有出口——
占用对象只兜"早退",解释不了"终态之后又失败"。
- **封口返修要求不是回合失败**:`HostOutcome::RepairRequired` 走独立的 typed 控制流变体
(`DirectTurnRunFailure::RepairRequired` → `DirectTurnError::RepairRequired`),不写终态、不进载荷、
不上报,由返修循环写回提示词继续跑。
### 3. 通道判据从"错误种类"改成"发生位置"
- **接单前发生的 = 拒单**:目录、权限、输入、并发、工程准备未就绪、宿主状态取不到。
- **接单后发生的 = 回合失败**:连接、配置、历史注入、`turn/start` 被拒,以及回合过程中的一切。
- `DirectTurnError::EnvironmentNotReady` 作为公共错误保留,接单前后都可能出现;它需要自己的失败分类
(`environment-not-ready`),否则回合失败投影会把它写成 `model-failed`,界面语气就错了。
### 4. 拒单载荷 = 现有 typed 错误
命令返回类型改成结构化的 `DirectTurnError`(ts-rs 导出到 `chat/generated/`,与 `DirectThreadEvent` 同一套
`cargo test export_bindings` 流程),并随载荷带一条由 `Display` 生成的用户文案(文案仍只在一处生成)。
前端按变体分流:
- 认得的"前置条件不满足 / 用户参数无效"→ 与用户消息同级的提示,不上报;
- 认不出的变体或非结构化错误 → 抛出,走既有捕获上报链路。
### 5. 回合身份由 `clientTurnId` 推导
`turn.started` / `turn.completed` 的 `userItemId` 由 `clientTurnId` 按现有规则算出
(`direct-codex:{clientTurnId}:user`,与前端 `directCodexConversationMessageId` 同规则),**不读盘回填**:
开始事件发生在用户条目落盘之前,落盘本身也可能失败。
### 6. 诊断留痕与错误上报都在宿主侧
`.agent/runtime/errors` + 应用日志 + 错误上报池由宿主投影写出;回合失败进池的责任从前端 catch 移到宿主。
命令边界不再负责回合失败的文本。
### 7. 界面:同级提示,删除 `详情:`
- 失败说明与接单被拒提示都与用户消息**同级**,按事件顺序排在它后面,不嵌在这条用户消息里。
- 删除 `详情:`:用户可见文案里不再出现该引用,前端删除解析与对应的第二次 IPC。
- 顶部状态行只显示回合状态,不再承载错误文本。
### 8. 队列与埋点
- 前端发送队列的放行改为监听"回合完成"(收到终态事件,或接单被拒),不再由命令返回驱动。加 TODO:
以后这条队列挪到 Rust 端,落点就是 Thread Manager 的接单动作。
- 埋点结算挂在"回合完成";不能在接单返回时结算——成绩是回合末才入 `pending_runs` 的,提前结算会变成空操作。
- 首页"运行中的项目"快照由 Thread Manager 的逻辑回合导出,任务侧不再单独维护一张表。
### 9. 认证失败不再重跑整轮
删掉 `withDirectCodexSessionRefresh` 的"刷新 + 重跑整轮";登录态失效按普通回合失败呈现。
`cancel_direct_codex_turn` 用的是同一个包装,一并去掉。
### 10. 回合失败原因本轮不落历史
失败原因只走事件载荷与宿主诊断,不写进 `project.jsonl`——重进项目只会看到那条没有回复的用户消息。
加 TODO:以后要做"进历史但不喂模型"的失败条目(暂定做法见「备选方案」第 3 条)。
## 影响与代价
- 命令返回后不再有 Err 兜底:回合一侧只剩事件流,宿主的占用对象必须真的兜住所有路径。
- **落盘即接单**:接单成功但回合失败时,历史里会留下一条没有回复的用户消息,而且失败原因不在历史里
(只在当轮界面与诊断文件里)。
- 前端可以删掉的东西:`markTurnStopped()`(取消成功但事件未到时手动放掉忙碌态)、`turn.started` 的
"重复开始保留第一次起点"分支、`详情:` 正则与 `read_agent_runtime_error_detail` 调用。
- `kill -9` 的自愈变好:Thread Manager 随进程消失,新进程的订阅 bootstrap 不会出现"有开始没结束",
界面不会卡在忙碌态。
- 必须同步的注释:`chat/controller/useDirectProjectChatController.ts`(catch 的职责)、
`chat/conversation/directTurnPresentation.ts`("`invoke` 直到整轮结束才返回"这句会变成错的)。
- ~~CLI 保持 await(它要那段回复文本),两个入口的分工在命令模块里写清楚。~~ **口径反转**:`--direct-codex-chat`
整个退役(见 [`【ADR】DirectProject命令入队化与待发消息队列归宿主-2026-09-24`](./【ADR】DirectProject命令入队化与待发消息队列归宿主-2026-09-24.md)),
DirectProject 只剩「入队」一个命令入口。
## 备选方案与取舍
1. **保留"刷新 + 重跑整轮"**:省掉用户重新登录,但重跑会重复落盘用户消息(现状即有),且重试语义与
"命令在飞"绑死。已作废。
2. **让 Codex 原生回合事件继续进队列**:等于线上有两对生命周期,接单后的前置失败仍然只能靠人工补事件。
已作废。
3. **"可见但不喂模型"的条目**:(a) 按条目 id 前缀在注入侧过滤;(b) 条目上挂显式标记(如 `agcLocal`);
(c) 新增一种行结构。注意 `project.jsonl` 是项目主对话与 DirectProject **共用**的文件,信封类型两侧共用,
改新行结构要连带改共享合同与读取侧(非 `response_item` 行现在是"失败关闭")。本轮不做,TODO 记 (b)
为暂定做法。
## 明确不做
- 不给失败载荷加字段(不加 `detailRef`):横幅不再展开详情,诊断引用只留在宿主侧。
- 不恢复 invoke 拒绝通道,也不为"接单后的前置失败"新增事件类型——它们走同一对逻辑回合事件。
- 本轮不做"失败条目进历史但不喂模型"(TODO),不做 Rust 端发送队列(TODO)。
## 落地时要同步的文档与注释(已同步 2026-09-23)
- `docs/technical/【技术方案】DirectProject Codex原始历史与异常恢复-2026-09-04.md`:事件带 `userItemId` 的事实、
失败原因的通道、"`turn.started` 之前的早退不产生终态事件"(作废)、失败说明是否落历史。
- `docs/adr/【ADR】DirectProject对话历史单一事实源-2026-09-16.md`:影响里的两条已知边界与"事件不带回合身份"
"宿主侧 Drop 守卫兜底"两条决策形状被本 ADR 取代。
- `docs/technical/【实施计划】DirectProject命令接单化-2026-09-23.md`:四步标记落地,补验收证据与已知坑。
- `docs/project-memory/shared-memory/decision-log.md`:`host-dropped` 的两条口径加取代注,并追加一条
2026-09-23 的接单化决策。
- `docs/README.md`:索引行去掉"未实施"。
- 代码注释:`direct_runtime/user_input.rs` 的 `TODO`(分工改成 CLI 保持 await)、
`chat/controller/useDirectProjectChatController.ts` 的 catch TODO(队列挪 Rust)、
`direct_thread_wire.rs` 里 `userItemId`"由原生从已落盘条目上读取"的说明。
后续更新(2026-09-24,接单化 review 收口):§2 补"终态的写点在整轮结束之后"与"封口返修要求不是回合
失败"两条不变式;`docs/technical/【技术方案】DirectProject Codex原始历史与异常恢复-2026-09-04.md` 的
"终态由事实判定"一段同步改写;`docs/project-memory/shared-memory/decision-log.md` 追加同日条目。
后续更新(2026-09-24,接单化 review 收口第二轮):§4 的拒单载荷 `kind` 收成 typed 枚举
(`DirectTurnFailureKind`,线上形状与取值不变)、并发拒单的两个身份改成回合身份;§5 的"回合身份由
`clientTurnId` 推导"补上"命令边界的拒单载荷也不例外";§6 的可留痕判据收掉 `ProjectRootUnanchored`
(它与 `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`,那一轮退化成"本轮已结束、
没有原因";失败事实是在模型终态那一刻被快照进终态上下文的,晚补记无用。
后续更新(2026-09-24,回合顺序修复:开口用户条目先于整轮里的一切失败):§2 补一条**顺序不变式**——
本轮的开口用户条目是这一轮的**第一条运行态条目**,发点在"接单成立、用户条目落盘成功、起 codex 之前"
(`emit_direct_thread_user_item`,调用点在 `direct_runtime/user_input.rs` 的命令主体),不再等 `turn/start`
应答。它以前在 `turn/start` 之后才下发,于是"接单到 `turn/start` 之间"的失败(连不上 app-server、执行器
未通过验收、历史注入失败)没有用户条目可挂:界面把失败说明按位置落进**上一轮**的分区,显示成"错误
在用户消息上面",上一轮还顶替本轮显示耗时(现场:17:22:46 发的那条消息下面显示上一轮的 15.6 秒),
本轮的用户气泡再自成一个 0.0 秒的假回合;下一条消息同样看不到自己的失败说明。§7 的界面口径据此补上
"回合归属只认身份":失败说明条目带 `turnUserItemId`,投影层按开口条目身份分组,本地乐观气泡按身份挂回
自己的回合;reducer 的收口早退也不再吞掉"订阅重建只回放生命周期锚点"时那条还没写进界面的失败说明。
后续更新(2026-09-24,删掉本地乐观用户气泡):§7 的"同级提示"再收一层——**本地不再造用户消息**。
前端把乐观气泡、`awaiting-start` 展示态、`pendingUserItemId` / `messageAppended` / `messageText`
这一整套一起删掉,用户气泡**只**来自宿主条目(发点=接单成立、落盘成功、起 codex 之前)。三条口径
随之固定:① 接单窗口(按下发送到 `turn.started` 落进 reducer)与订阅重建窗口里聊天区没有这一轮的
任何条目,反馈只有 composer 忙态、状态行与「陶泥儿正在处理」卡片(卡片这一段不读秒:起点要等宿主的
`turn.started.at` 到);② 回合起点只认 `turn.started.at`、终点只认
`turn.completed.at`,用户气泡显示的时钟是宿主落盘 / 观测时间(不再有更早的本地发送时间),两边都
拿不到(重进项目读回来的历史回合)时整条「本轮结束于 … 」隐藏,不再兜出 0.0 秒;③ 拒单提示带自己
的身份(`…:rejected`),投影据此在会话末尾自成一组,不挂进上一轮。§7 里"排在用户消息后面"在没有
用户消息的回合里指"这一组提示自己"。代价(已知并接受):条目下发之前用户看不到自己那句话,
`project.jsonl` 里的用户条目也依旧只在首屏 / 翻页时读进前端。
@@ -2,6 +2,12 @@
状态:已接受
> 注:本文件下列口径已被 [`【ADR】DirectProject命令接单化-2026-09-23`](./【ADR】DirectProject命令接单化-2026-09-23.md)
> 重新决策并已落地,本文件不再作为它们的依据:「影响」一节里的两条已知边界(① `kill -9` 后前端停在运行态
> ——队列随进程消失,订阅 bootstrap 不会留下"有开始没结束";② `turn.started` 之前的早退不产生终态事件
> ——"早退"被拆成接单前的拒单,接单后由占用对象统一收口),以及「决策」里"事件不带回合身份"与
> "宿主侧 Drop 守卫兜底"两条的实现形状(见下)。
## 背景
AGC 项目开发聊天框当前同时从三处取数据:Direct 回合事件(实时)、`turn-stream.jsonl`(文本段与工具交替顺序)、`tool-calls.jsonl`(已脱敏工具卡片),重进页面时还要额外接管活动回合快照。同一段文本和同一张工具卡片因此存在多个来源,实时与回读会互相覆盖,恢复路径也只能靠"哪个源先到"决定。
@@ -20,10 +26,15 @@ AGC 项目开发聊天框当前同时从三处取数据:Direct 回合事件(
- 两侧的过滤口径必须完全一致,包含「哪些条目根本不是本项目的聊天条目」:Codex app-server 回显的用户消息(`userMessage` / 非 AGC 的 `role=user`)在落盘侧被过滤,在运行态事件侧也必须被过滤(`direct_thread_visible_item`)。少一侧就会出现「实时比历史多出两条同文本用户条目、各自开出一个耗时 0 秒的假回合,重进页面又正常」这类只有其中一侧的事实源缺陷。
- 搬运层不生成展示形状:Thread Manager 只下发脱敏原始条目(`itemType` 原样透传),工具卡片的 `kind`、标题、折叠摘要都由前端生成。
- 条目身份只有一套:进队列前归一成一个 `itemId`。工具条目在 `project.jsonl` 里带两个 id(调用 id 与 response item id,调用与输出共用前者),归一只在 Rust 边界做一次,Thread Manager 与前端都不暴露第二个 id 概念。
- 事件不带回合身份:DirectProject 同一时刻只有一个回合在跑,`turn.started` 无载荷、`turn.completed` 只带 `status`;前端 state 里只有一个 `turnRunning` 布尔,没有 `turnId`。`subscribe` 返回的条目、增量、请求与队列锚点都不带 turn id。
- 事件不带回合身份:DirectProject 同一时刻只有一个回合在跑,`turn.started` 无载荷、`turn.completed` 只带 `status`;前端 state 里只有一个 `turnRunning` 布尔,没有 `turnId`。`subscribe` 返回的条目、增量、请求与队列锚点都不带 turn id。(**按 2026-09-23 ADR §5 补充**:`turn.started` / `turn.completed` 现在带 `userItemId`,由 `clientTurnId` 推导、不读盘回填;事件仍不带 turn id,判据仍是"只有一对逻辑回合事件"。)
- 合并只在前端,规则只保留「先到定形、后到补空白」:第一次见到的快照决定卡片形状,后续快照只补输出与状态,不做逐字段优先级表。只有"后到信息一定更全"时才例外:正文取更长的一份、工具状态允许从 `running` 升级到终态、`updatedAt` 取较新的时间。
- 前端不保留增量缓冲:`item.delta` 直接追加到运行态条目的正文(正文只增不减)。`turn.completed` 把当前回合的运行态条目并入历史再清空,条目既不消失也不重复。
- 活动回合的唯一判据是「出现过 `turn.started` 且未出现 `turn.completed`」;进程重启后队列消失,历史里的半截回合一律按已结束渲染。
- 终态事件只有 `turn.completed` 一种,它同时承载三种语义:`status !== "failed"` 是正常结束 / 中断 / 终止,`status === "failed"` 是**失败**,且必须再带 `failure { kind, message }`(`message` 已脱敏截断)。失败原因只走这一条通道:前端不再从命令返回或另一条 IPC 里另造失败文案,聊天里那条失败说明仍落在同一个展示位上(本轮最后一条助手气泡、只在运行期显示),只是数据来源换成事件载荷;命令返回只用于运行错误横幅与诊断留痕。
- 宿主侧兜底:`turn.started` 发出之后才武装 Drop 守卫,正常写完终态即解除;panic、future 被丢弃、终态之前的早退由守卫补一条 `status="failed"` + `failure.kind="host-dropped"` 的终态,避免前端永远停在"还在跑"。已知边界见「影响」一节。(**按 2026-09-23 ADR §2 改写**:守卫换成"接单即登记"的占用对象,终态写出后占用才释放。)
- 执行通道断开同样是失败终态,也必须带 `failure`:连接级故障(app-server 进程退出 / stdout 流断 / JSON 行越界)与回合事件通道关闭都算,`kind="transport-failed"`、`message` 用宿主当场写下的那份诊断(含 `exitStatus` 与 stderr 摘要,已脱敏截断)。宿主在检测到连接终止的第一时间把这条事实记到本回合的执行适配器上,终态判定再从适配器读:执行适配器的看门狗盯着同一个 `closed` 标志,用调用点局部变量会输给这场调度竞争,失败原因就只剩日志、界面只会看到"本轮已结束"。判据是"适配器是否已由宿主主动关闭"——宿主自己收束(正常终态 / 用户主动停止 / 预算与交付收尾)走的是同一个 `TransportClosed` 事件,但这些不算失败。
- 终态由**事实**判定,不由收尾阶段反推:判定按优先级取「宿主当场记下的失败(通道断开 / 等待超时 / app-server 单方面中断)→ 本回合的错误结果是 Err → 只有收尾阶段的账本读不出来时才用交付报告」,**有载荷一定写 `status="failed"`**,没载荷才用收尾阶段推出来的 `status`。收尾会把 ledger 阶段推成 `Interrupted`,让阶段决定终态就会把已经失败的一轮讲成"已结束"。模型自报失败(原生 `turn/completed.status="failed"` 的 `error`,带 `codexErrorInfo` 分类)不为载荷新增输入字段:宿主把原生 `error` 的 `codexErrorInfo` 解析成 typed 分类后当作本回合的错误结果,走同一条通道进载荷;交付报告只说明"收束到哪一步",不得顶掉原因。
- 回合失败在宿主内部是 **typed** 的:`agent/direct_turn_error.rs` 的 `DirectTurnError` 每个变体自带字段(并发拒绝带两个 invocation id、模型失败带分类、超时带撞的是哪条上限、通道断开带宿主诊断),**调用级拒绝**(这一轮没有开始)与**回合级失败**(这一轮已开始并被判失败)不共用判据,分流只认 `is_turn_failure()`。判据不再对原因文本做子串匹配,`LlmError` 只在平台层入口出现一次(`DirectTurnError::from_model_call`)。线上载荷 `{kind, message}`、命令边界字符串与 CLI 返回值都由这一个出口投影出来,Rust 侧任何地方都不再解析它们。
- 分页锚点取原始条目 id;一次翻页操作在前端自动连拉,直到出现可显示条目或 `hasMore=false`,上限 5 页。
- `notify` 是唯一唤醒来源:`subscribe` 的 bootstrap 事件本身就是该 subscriber 此刻要处理的事件(游标已在队尾),前端直接 reduce 它们,不需要为了取这批事件再补一次 `consume`,之后完全由 `notify` 驱动,不设低频 tick 或任何轮询兜底。唯一例外是回执竞态:Rust 侧一注册完 subscriber 就开始 `notify`,前端却要等回执才知道自己的 `subscriptionId`,这段窗口内的通知只能记成欠账,回执到达后立刻补一次 `consume` 取回,否则该回合的尾部事件会卡在队列里等一个可能永不出现的下一次通知。
- 迁移按一次干净切换落地:不做灰度、不做运行时开关、不双跑;允许提交序列里存在「新源已启用、旧代码尚未删除」的中间窗口,禁止反向的「新源未启用、旧源已删」。
@@ -50,7 +61,12 @@ AGC 项目开发聊天框当前同时从三处取数据:Direct 回合事件(
- 旧项目磁盘上遗留的 `turn-stream.jsonl` / `tool-calls.jsonl` 保留不动,不迁移、不清理、不再由 DirectProject 聊天框读取。
- 工具卡片的脱敏与截断必须在读取期执行一次,不能因为"原始条目已在磁盘"就把未脱敏内容直接渲染到界面。
- 回合结束语义务必由 `turn.completed` 判定;缺少该事件的残留回合不得被渲染成运行中。
- 回合结束语义务必由 `turn.completed` 判定(失败时同一事件带 `failure` 载荷,不新增事件类型);缺少该事件的残留回合不得被渲染成运行中。
- 两条已知边界,都**不**在本次补路径,且已被 [`【ADR】DirectProject命令接单化-2026-09-23`](./【ADR】DirectProject命令接单化-2026-09-23.md) 取代(§3、§2):① 宿主进程被强杀(`kill -9`)时没有任何 `Drop` 会执行,但队列随进程消失,新进程的订阅 bootstrap 因此不会看到"有开始没结束",界面不会卡在忙碌态;② `turn.started` 之前的失败按发生位置分流——接单**之前**的是拒单,根本不产生回合(不写用户条目、不写失败诊断),接单**之后**的由这一轮的占用对象统一收口成 `turn.completed`,不存在"有回合却没有事件解释"的路径。
- 失败原因里的 `message` 是宿主侧脱敏 + 截断后的可展示文本,前端仍按既有口径做一次可见文案映射(`projectRuntimeVisibleError`),映射规则不因这次改动改变。
- 执行通道断开时用户看到的仍是既有映射结果(诊断命中不了专门规则,落到通用兜底),真实诊断在事件载荷、宿主交付报告与运行日志里;把"连接断开"改成专门文案属于映射规则变更,不在本 ADR 范围内。
- 模型自报失败时用户看到的也仍是既有映射结果(`codex-app-server-error:<kind>` 那张中文表),区别只是原因现在从事件载荷来、同时命令返回带出运行错误横幅——这就是"事件出聊天文案、命令返回出横幅"的既有分工;前端可见文案的映射规则不因这次改动改变。
- **调用级拒绝**(同一 `clientTurnId` 并发复用 / 项目已有另一条回合在跑 / 权限策略拒绝 / 目录锚不定 / 输入校验 / 环境与凭据未就绪)不属于回合失败:这一轮没有开始,只把原因回给命令边界(界面出运行错误横幅),不写失败诊断、不发 `failed` 事件、不进交付报告。此前它们与回合失败混在同一层、共用同一份错误文本,现在分流只认 typed 判据。
- 「活动回合的唯一判据」约束的是**原生回合**:界面上的「本地已发出、原生还没认领」是投影的展示态(`DirectChatTurn.state = 'awaiting-start'`),由本地在途用户条目身份派生,不构成第二套原生生命周期,也不参与 `turnRunning` 的判定。
- 三层数据流、变量归属与一次发送的时序写在代码里:`apps/ai-game-creator-shell/src/view/project-development/chat/controller/useDirectProjectChatController.ts` 的模块注释;回合三态的定义与判据真值表在 `apps/ai-game-creator-shell/src/view/project-development/chat/conversation/directTurnPresentation.ts` 的 `DirectChatTurnState`。改判据时同步这两处与对应测试。
- 验收证据是端到端行为,不是单元测试:回合进行中杀掉应用进程后重开项目,应看到部分文本与工具卡片按原顺序出现且不显示忙碌;正常结束后重进应与实时渲染一致;文件系统不得再新增 `turn-stream.jsonl` / `tool-calls.jsonl`。
@@ -0,0 +1,18 @@
# UI 工作流检查点用追加式 JSONL 日志
UI 设计文档的工作流由 Agent 工具驱动、跨多次外部调用,崩溃后需要能续跑。检查点定为**文档旁一条按行追加的 JSONL 日志**:每一步做完就追加一行——起始一轮记原始 State 快照,识别与切分各记自己的 DTO **以及该步应用完之后的那份 State 快照**,最后一行是回写标记——"某步是否完成"只看日志里有没有对应的行,没有就是没完成。恢复只做两件事:读回已完成步骤留下的 State 快照,然后从第一个没有快照的步骤继续。
## 考虑过的方案
- 每步一个 sidecar 状态机(步骤状态 + sealedAtRevision + evidence 清单):要额外维护状态迁移、封条判据和证据校验,等于给每一步发明一套小协议。
- 把切分专用的 `SeparationState` 泛化成通用检查点:切分需要"每个批次、每个节点"的细粒度恢复语义,其它步骤不需要,泛化会把这份复杂度摊给所有步骤。
追加式日志只需要"存在即完成"这一条判据,新增步骤类型等于新增一种行,天然可扩展。每步应用完的 State 快照随该步行一同落下,恢复就只是"读回最新快照",不必再维护一份"照 DTO 重算一遍"的镜像逻辑。
## 后果
- 日志是恢复用的派生信息,不是项目内容:不登记为 manifest 资产,不推进项目 revision。
- 每行必须一次性原子追加;崩溃时可能留下写了一半的最后一行,未形成完整行的步骤一律视为未完成。
- 同一份文档可以被多次运行,日志必须能区分轮次:每一轮以一行原始 State 快照开头,该轮的第一行回写标记即为这一轮结束;恢复只针对最后一个没有回写标记的轮次。
- 恢复只看快照、不重放步骤:已完成步骤在日志里带着"那一步应用完"的 State,恢复直接采纳并整步跳过。若照 DTO 再跑一遍,会重复登记切图、把同一条回填出错原因重复累加。只有带 State 快照的行才算已完成,旧格式(只有 DTO)按未完成重跑。
- 切分 op 内部的细粒度恢复仍由 `SeparationState` 承担,日志只记录工作流层面的步骤完成,不顺带复制它的进度。
@@ -1052,7 +1052,60 @@
"$ref": "#/components/responses/UpstreamError"
}
},
"description": "支持普通生图、规范图、角色图、快速编辑参考图、UI 设计图和宣发素材生成。kind 可取 spec、character、quick-edit、ui-design、publication-material。"
"description": "支持普通生图、规范图、角色图、快速编辑参考图、UI 设计图和宣发素材生成。kind 可取 spec、character、quick-edit、ui-design、publication-material。游戏场景不接受本接口的 kind/assetKind = scene,必须使用 /api/external/v1/editor/scenes/generations 提交结构化场景意图。"
}
},
"/api/external/v1/editor/scenes/generations": {
"post": {
"x-mcp-excluded": true,
"tags": ["Editor Images"],
"operationId": "generateExternalEditorScene",
"summary": "生成编辑器游戏场景(结构化场景意图)",
"description": "只接受结构化场景意图:sceneContent + stylePreset(custom 时必须提供 customStyle),完整 Provider Prompt 由服务端组装,不接受调用方提交的完整 prompt。入队后按 kind/assetKind = scene 持久化,产物保存 scene.generate V2 配方;队列、计费、资源入库与画布写回与站内场景路由一致。",
"security": [
{
"ExternalApiKey": []
}
],
"parameters": [
{
"$ref": "#/components/parameters/IdempotencyKey"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EditorSceneGenerationRequest"
}
}
}
},
"responses": {
"202": {
"description": "生成任务已持久化入队",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ExternalEditorGenerationSubmissionResponse"
}
}
}
},
"400": {
"$ref": "#/components/responses/BadRequest"
},
"401": {
"$ref": "#/components/responses/Unauthorized"
},
"403": {
"$ref": "#/components/responses/Forbidden"
},
"502": {
"$ref": "#/components/responses/UpstreamError"
}
}
}
},
"/api/external/v1/editor/images/edits": {
@@ -2965,6 +3018,69 @@
}
}
},
"EditorSceneGenerationRequest": {
"type": "object",
"required": ["sceneContent", "stylePreset"],
"properties": {
"sceneContent": {
"type": "string",
"minLength": 1,
"description": "画面内容(结构化场景意图主体)。纯空白在入队前返回 400。"
},
"stylePreset": {
"type": "string",
"enum": ["anime", "watercolor", "flat", "stop-motion", "custom"],
"description": "视觉风格预设。custom 时必须同时提供非空 customStyle,否则返回 400。"
},
"customStyle": {
"type": ["string", "null"],
"description": "自定义画风描述,仅 stylePreset = custom 时使用。"
},
"model": {
"type": ["string", "null"],
"description": "图片模型,省略时使用服务端默认场景模型。"
},
"aspectRatio": {
"type": ["string", "null"],
"default": "16:9"
},
"imageSize": {
"type": ["string", "null"],
"default": "1K"
},
"referenceImageSrcs": {
"type": "array",
"items": {
"type": "string"
},
"description": "可选参考图,沿用普通图片生成的参考图口径。"
},
"projectId": {
"type": ["string", "null"]
},
"generationInputs": {
"$ref": "#/components/schemas/JsonValue",
"description": "场景配方由服务端重建;仅保留 source 精确等于 ai-game-creator-client 的客户端来源标记,用于选择 AGC 队列结果与幂等命名空间。调用方 fields、action 和引用 provenance 不会覆盖服务端配方。"
},
"assetFolderId": {
"type": ["string", "null"]
},
"assetLabel": {
"type": ["string", "null"],
"description": "省略或纯空白时统一使用「游戏场景」。"
},
"canvasCompletion": {
"anyOf": [
{
"$ref": "#/components/schemas/EditorCanvasGenerationCompletion"
},
{
"type": "null"
}
]
}
}
},
"EditorImageGenerationRequest": {
"type": "object",
"required": ["prompt"],
+16
View File
@@ -27,9 +27,25 @@ docs/project-memory/
- `plans/` 只保存正在执行、具有明确剩余项和验收门禁的计划;完成或作废后立即删除,稳定结论融合进当前专题或共享记忆。
- `todos/` 只保存真实开放、有人接手即可执行的事项;每项应写明状态、下一决策点和关闭条件。已退役对象不保留未来 TODO。
- 分支、提交、测试轮次和阶段流水账不进入长期记忆;需要追溯时使用 Git 历史。
- `decision-log.md` 与 `pitfalls.md` 可以直接修改、合并和删除旧条目:被覆盖的决定、退役对象专属说明和重复记录不继续保留;仍有效的风险、兼容约束和未完成事项应保留或融合到当前专题。
- 自动验证检查实际代码、配置和行为,不要求决策记录或踩坑记录包含固定措辞,也不强制把同一规则复制到多个记忆文件。
- 若本目录与代码或最新 `docs/` 冲突,以代码和最新专题为准,并在同次变更中修正记忆。
- 禁止写入个人配置、API Key、Token、Cookie、会话记录、认证文件、本地私密路径、构建产物、日志、缓存和数据库 dump。
## 决策记录格式
以下格式按需使用,简单决策不必凑齐所有字段。
```md
## YYYY-MM-DD 决策标题
- 背景:为什么需要这个决策
- 决策:最终决定是什么
- 影响范围:涉及哪些模块/文档/流程
- 验证方式:如何确认决策仍有效
- 关联文档:相关 PRD、技术文档、提交或 Issue
```
## RAG 索引
本目录是本地 RAG 的高权重索引源,但检索结果只作为候选上下文。索引脚本位于 `scripts/rag/`;运行时依赖和 `.rag/` 数据默认不安装、不提交,启用前需先征得用户确认。
@@ -3,7 +3,7 @@
| 字段 | 值 |
| --- | --- |
| Version | 1.0 |
| Status | runtime-smoke-passed(存储原语、服务端入口、原生上传器、渲染进程接线与真实栈分片续传 smoke 均已落地) |
| Status | implemented-awaiting-runtime-acceptance(存储原语、服务端入口、原生上传器、渲染进程接线与真实栈分片续传 smoke 均已落地) |
| Date | 2026-09-23 |
| Parent Milestone | `docs/project-memory/plans/【里程碑】AGC发行包分片续传上传-2026-09-23.md` |
@@ -3,7 +3,7 @@
| 字段 | 值 |
| --------- | ----------------------------------------------------------------------------------- |
| Milestone | `docs/project-memory/plans/【里程碑】AGC客户端更新切换到官方更新插件-2026-09-17.md` |
| Status | ready |
| Status | implemented-awaiting-runtime-acceptance |
| Owner | Codex |
## 修改边界
@@ -35,3 +35,11 @@
- Windows 安装模式由插件配置决定(本里程碑固定 `quiet`,与旧 PowerShell `/S` 一致);若改为 `passive` 会多出安装进度条 UI。
- 插件在 Windows 上安装成功后自行退出进程,前端不再有机会更新界面;提示面板的完成态只在 macOS / Linux 可见。
- 回滚点:改动集中在客户端与配置,回滚后即可退回自研链路;旧 OSS `agc/latest.json` 在发布管线渠道化前不删除。
## 本轮复核(2026-09-24)
状态原为 `approved` / `ready`(规范评审态),实现其实已经落地,本轮按当前工作树复核并改记为 `implemented-awaiting-runtime-acceptance`:
- 原生侧接入官方插件:`apps/ai-game-creator-shell/src-tauri/Cargo.toml` 有 `tauri-plugin-updater = "2.11.0"`;`tauri.conf.json` 打开 `createUpdaterArtifacts` 并写入 `plugins.updater.pubkey` 与渠道端点。
- 自研更新链路已删除:`download_ai_game_creator_update` / `install_ai_game_creator_update` / `check_ai_game_creator_update` 在 `src-tauri/src` 与 `src` 内均无命中;前端更新服务 `src/services/appUpdate.ts` 只消费官方插件包,而该包是 IPC 薄壳,网络与安装都在 Rust 侧。
- 未执行(需要签名私钥与发布授权):真实签名打包、真实 OSS 发布、安装后真机升级与回滚。
@@ -0,0 +1,54 @@
# 【实施计划】AGC 工作区偏好 IO 下沉 Rust
Version: `v1`
Status: `implemented-awaiting-runtime-acceptance`
Date: `2026-09-23`
Parent Spec: `docs/project-memory/plans/【里程碑】AGC工作区偏好IO下沉Rust-2026-09-23.md`
## 修改边界
1. 新增 `src-tauri/src/workspace_preferences.rs`:定义版本化 AppData JSON、路径规范化、原子写入、command 输入/输出和偏好变更事件。
2. 在 `main.rs` 注册 typed commands;首页自动建项和模板建项由 Rust command 内部读取项目创建目录,保持现有目录安全校验。
3. `features/app-shell/model.ts` 保留纯校验/展示模型,移除 `localStorage` 读写,新增一次快照/迁移所需的 invoke façade。
4. `useProjectCreationDirectory.ts` 与 `useRecentProjects.ts` 订阅 Rust 事件,首次挂载读取一次快照;浏览器/Vitest 无 Tauri 时保留内存 fallback,不恢复周期轮询。
5. 增加 Rust/React 定向测试,更新现有 localStorage 断言为 command/event 合同;不修改 auth、wallet、prompt polish 和 Home draft。
## 实施顺序
- 先完成 Rust 偏好模块和单测,再注册 command/event,最后切换 React 读取/写入边界。
- 先让自动建项闭环不再传 `projectsRoot`,再切换模板建项;确认旧参数仍兼容后删除前端读取。
- 迁移旧 localStorage 只做一次、在 Tauri 环境显式触发;迁移失败不得覆盖 Rust 已存在值。
## 验证命令
```text
cargo fmt --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml -- --check
cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml workspace_preferences
npm exec vitest run -- apps/ai-game-creator-shell/tests/projectCreationDirectory.test.ts apps/ai-game-creator-shell/tests/recentProjectsHook.test.tsx
npm --prefix apps/ai-game-creator-shell run typecheck
npm run ai-game-creator-shell:check:web
npm run check:encoding
npm run check:doc-index
git diff --check
```
## 风险与回滚点
- AppData 偏好文件不可写时,读取继续返回安全空态,写入返回可见错误;React 不回退到隐式 `localStorage` 持久化。
- 旧 localStorage 键可能包含无效或已不存在的路径,迁移只接受规范化绝对路径,并受 Rust 端每次建项校验保护。
- 若事件订阅竞态导致首次通知丢失,挂载快照读取作为唯一补读;不引入 `setInterval`。
## 当前验证记录
- Rust workspace preference 定向测试:`3 passed / 0 failed`。
- React 定向测试:`projectCreationDirectory.test.ts` 与 `recentProjectsHook.test.tsx` 共 `7 passed`。
- AGC TypeScript:`npx tsc -p tsconfig.json --noEmit` 通过;定向 Prettier 与新 Rust 模块 rustfmt 检查通过。
- 完整 `npm --prefix apps/ai-game-creator-shell run typecheck` 已通过,发行 typed command allowlist 已同步。
## 本轮取证(2026-09-24)
- 行为合同 1/2/3(偏好文件缺失不阻断启动、最近工作区最多 8 条去重幂等、创建目录只表达意图且建项仍过 Rust 门禁):`cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml --offline -- workspace_preferences --test-threads=1` → **6 passed**。
- 范围项(React 不再直接访问 `localStorage`、建项不再由 React 传 `projectsRoot`):`rg localStorage apps/ai-game-creator-shell/src` 只剩 `features/app-shell/workspacePreferences.ts` 的旧键迁移 shim(`isTestRuntime()` 门控);`rg projectsRoot apps/ai-game-creator-shell/src` **0 命中**。
- 前端 façade:`npx vitest run tests/recentProjectsHook.test.tsx tests/projectCreationDirectory.test.ts --root apps/ai-game-creator-shell` → **5 + 4 passed**(命令/事件投影、失败重试与偏好读写)。
- Rust 侧变异验证(2026-09-24):把 `MAX_RECENT_WORKSPACES` 从 8 改成 16 后,`normalizes_recent_paths_with_a_strict_eight_item_limit` 立刻红(`left: 10, right: 8`),说明「最近项目最多 8 条」这条上限确实被钉住;改回后 `workspace_preferences` 定向用例 exit 0。
- 仍未验证(需要真实客户端):多窗口并发改偏好、事件丢一次后的受控快照读取、真实建项落到用户所选目录。
@@ -0,0 +1,79 @@
# 【实施计划】AGC 工作区网络 I/O 下沉 Rust
Version: `v1`
Status: `implemented-awaiting-runtime-acceptance`
Date: `2026-09-23`
Parent Spec: `docs/project-memory/plans/【里程碑】AGC工作区网络IO下沉Rust-2026-09-23.md`
## 修改顺序
1. 先盘点现有 Rust HTTP/auth/session facade 与发行/资源命令,冻结 typed request/response 和事件名。
2. **发行链路(已完成)**:新增 `game_distribution_publish.rs` typed facade 和五个 Tauri commands;创建游戏、创建版本、分片上传、送审、灰度读取、资料建议、封面价格/生成与队列收口均由 Rust 持有。保留既有分片上传 command 与幂等/续传语义,React service 只保留 `invoke` façade。
3. **模型目录闭环(已完成)**:官方 `/api/llm/models` 改由 Rust typed command 持有会话与 HTTP;React 只消费目录投影。素材库/素材直传仍待后续切片。
4. 为每条命令补 Rust 单测、前端 invoke/事件测试和静止状态无轮询断言。
5. 更新主规范、事件目录与验证记录;账户/钱包调用保留并明确排除。
## 验证命令
- `npm --prefix apps/ai-game-creator-shell run typecheck`
- `npm run ai-game-creator-shell:check:web`
- `cargo fmt --all --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml -- --check`
- 相关 Rust `cargo test --locked` 定向测试
- `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml game_distribution_publish`
- `npx vitest run apps/ai-game-creator-shell/tests/gameDistributionPublish.test.ts`
- `npm run check:encoding`
- `npm run check:doc-index`
- `git diff --check`
## 回滚点
保留现有 Rust 上传命令和旧 TS service 的提交边界;迁移每个 typed facade 时先让 Rust command 可独立读取/写入,再切换 React 调用方,避免出现“旧网络调用已删但新 command 未注册”的窗口。不得用重新启用 React 轮询作为回滚。
## 当前批次证据:官方模型目录
- 新增 Tauri typed command load_game_creator_llm_models;Rust 持有当前平台会话、固定 API origin、Bearer、AGC marker、响应 envelope 解析、1 MiB 响应上限和账号身份代次复核。
- React llmModelCatalog 在 Tauri 官方目录路径只通过 typed invoke 读取;自定义模型继续使用本地配置,浏览器无 native host 时保留兼容读取。
- Rust 定向测试 4/4、clientApi 与 ConversationModelSelect 定向测试 38/38、appSurface 211 passed / 9 skipped。
- typecheck 的 tsc 与 skill-pack、check:encoding、check:doc-index、git diff --check 通过;check-config 仍受共享工作树既有发行命令缺口阻断,发行资料 facade 另按本实施计划处理。
## 当前切片证据(2026-09-23)
- `apps/ai-game-creator-shell/src-tauri/src/game_distribution_publish.rs` 读取 Rust `platform_session` 的 user/token/origin,只接受结构化参数;每个 HTTP 步骤复核身份代次,保留 Bearer、`X-Genarrative-Client: agc`、响应 envelope、幂等键和稳定错误分类。
- `publish_local_project_game` 在 Rust 内完成本地包暂存、创建 game/version、分片续传和 submit;渲染进程不再取得 token、origin 或整包字节。已有 `upload_local_project_game_package` command 未改动。
- 封面异步任务由 Rust 以有界 1.6 秒间隔、20 分钟/800 次上限等待;React 不再执行远端 job 轮询。
- `npx vitest run apps/ai-game-creator-shell/tests/gameDistributionPublish.test.ts`:7 passed;`npm exec tsc -- --noEmit -p apps/ai-game-creator-shell/tsconfig.json` 通过。
- `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml game_distribution_publish`:4 passed;`rustfmt --edition 2021 --check` 针对新 Rust 文件通过。
- 尚未宣称全部工作区网络已迁移:素材库/素材上传、账户刷新、错误上报及其它 clientApi 链路仍在各自里程碑范围内;真实 Tauri + Provider/生产发行 smoke 仍待环境验证。
- 发行 facade 追加 session identity 保护:每个 JSON 步骤与每个分片前重新读取当前 token,换号/origin 变化失败关闭;旧 native-only 分片 command 保留显式 token 兼容,不属于 React 工作区入口。
## 当前切片证据:编辑器素材库与预览
- 范围补充:远程导入同步改成 assetId-only,删除旧 remoteAssets/objectKey/downloadUrl 透传。UI 继续传现有 import policy/requirements;Rust 复用账户素材导入核心,按实际字节落实单文件/批次上限和正式 kind,预览字节不作为导入事实。预览切换/卸载后迟到响应不能回写。
- Rust 新增 read_editor_asset_library:复用当前账号 session、账户素材静态图片筛选和安全投影,只返回 folders/assets 展示字段、稳定 assetId、尺寸/大小和 previewAvailable;不序列化 objectKey、imageSrc、assetObjectId、token 或本地路径。
- Rust 新增 read_editor_asset_preview:按当前账号 assetId 重新解析权威素材,有界下载并校验图片大小、Content-Type/magic、重定向和 session identity,返回 data URL;没有稳定 objectKey/legacy path 的记录失败关闭。
- React AssetImporter/ImageImporterPreview 改为 editorAssetHost typed invoke;旧 loadEditorAssetLibrary/resolveClientAssetReadUrl 不再由该入口调用。import_ui_editor_assets 的旧 remote 入参保留,downloadUrl 暂由 Rust 返回的 data URL 提供,Rust 兼容解析该 data URL,WebView 不再直接发素材预览/导入网络请求。
- Rust 定向资源投影/预览 data URL/legacy import 测试 13/13、AssetImporter 定向测试 4/4、AGC typecheck tsc/skill-pack 通过;待补完整 web gate、编码/文档索引/diff 检查后再提交该切片验收。
## 当前切片证据:素材上传与错误报告(2026-09-24)
- 新增 `src-tauri/src/platform_asset_upload.rs` 与命令 `upload_platform_media_asset`:凭证申请、对象存储表单直传、confirm、会话身份复核和地址白名单全部在 Rust;React `assetDirectUpload.ts` 只读取用户选择的文件字节并提交结构化元数据,不再持有 token、拼 URL 或发平台请求。
- 新增 `src-tauri/src/error_report/submit.rs` 与命令 `submit_error_report`:Rust 持有 origin、Bearer、稳定 submissionId(按事件 ID 派生)与 envelope 解析;React 只提交脱敏事件、说明与诊断日志,`errorReporting.ts` 不再 import `fetchClientHttp` / `getStoredAuthAccessToken` / `getClientServerBaseUrl`。
- 渲染层偏好补齐:`chatPromptPolish` 的「不再提醒」从 localStorage 迁到 Rust 客户端偏好(`set_chat_prompt_polish_reminder_disabled` + 既有偏好事件),组件改为「挂载读一次快照 + 订阅事件」,并修掉迟到快照覆盖用户新选择的竞态。
- 验证:Rust 定向 `platform_asset_upload` 3/3、`error_report::submit` 4/4、`workspace_preferences` 4/4;前端定向 `assetDirectUpload` 3/3、`errorReporting` 11/11、`ErrorReportDialog` 3/3、`chatPromptPolish` 18/18、`resourceReferenceInput` 43/43;`npm run ai-game-creator-shell:check:web` 1857 passed / 14 skipped;编码、文档索引、`git diff --check`、`cargo fmt --check` 通过。
## 当前切片证据:账户与钱包(2026-09-24)
- 新增 `src-tauri/src/account_api.rs` 与五个命令(recharge-center / wallet-ledger / create order / confirm wechat order / redeem code):Rust 从当前平台会话取 origin 与 Bearer,解析 envelope 并映射稳定错误分类;401 返回 `authentication-required` 由渲染层按既有会话续期链路重试一次,付费订单未知结果不自动重放。
- 渲染层新增 `src/services/accountHost.ts` 与 `src/services/platformSessionRetry.ts`;`useAccountWallet` 与 `stores/useWalletStore` 改为 typed invoke,`clientApi` 不再承载账户与钱包请求。Direct Codex 的登录失效判定与账户命令共用同一份分类,避免两处口径漂移。
- 验证:Rust 定向 `account_api` 4/4;前端定向 `accountHost` 3/3、`walletStore` 14/14;`npm run ai-game-creator-shell:check:web` 186 passed / 1857 passed / 14 skipped;编码、文档索引、`git diff --check`、`cargo fmt --check` 通过。
- 仍未迁移:登录、验证码、refresh、登出与开发服务器选择,见 `【里程碑】AGC认证会话与账户网络下沉Rust-2026-09-24.md`;真实账号登录/续期/微信支付仍需环境证据。
## 本轮取证(2026-09-24)
- 验收 1(React 不再直连):`rg "requestClientApi|fetchClientHttp|accessToken|refreshToken" apps/ai-game-creator-shell/src` → **0 命中**;`rg projectsRoot apps/ai-game-creator-shell/src` → 0 命中。
- 验收 3(静止时无 React 轮询):`rg setInterval apps/ai-game-creator-shell/src` 只剩两类非远端任务——计时/动画(`AuthenticatedClient` 的登录耗时与验证码倒计时、`useLiveNow`、`useDesignReplyAnimation`、`DirectProjectConversation` 的本地时钟)和拖拽重复器(`TransformEditor`)。
- 验收 4/5 的定向用例:`npx vitest run tests/gameDistributionPublish.test.ts tests/assetDirectUpload.test.ts tests/pluginHost.test.ts tests/directActiveTurns.test.tsx tests/resourceCanvasAssetGenerationQueue.test.ts tests/recentProjectsHook.test.tsx tests/projectCreationDirectory.test.ts --root apps/ai-game-creator-shell` → **7 文件 / 63 passed**。
- 变异验证(2026-09-24):拿掉 `platform_asset_upload::validate_upload_host` 的「只允许回环 http 与 `*.aliyuncs.com` https」判定后,`validates_upload_hosts_without_accepting_arbitrary_origins` 精确挂在 `https://evil.example.com/upload` 那条断言上,证明这条防任意主机上传的用例不是空跑;改回后该用例与 `corrupt_or_foreign_session_files`(见认证里程碑)各自 exit 0。
- 脱敏用例的强度复核(2026-09-24):禁用 `error_report::sanitize::redact_urls` 后,只有 `keeps_api_route_while_redacting_url_origin_and_query`(精确相等断言)变红,`redacts_credentials_urls_paths_and_ids` 仍然通过——`PATH_PATTERN` 的盘符分支大小写不敏感,`http://` 里的 `p:` 也会命中并把整段 URL 吞成 `http<path>`,所以「URL 被换成占位符」这件事当时没有被断言到。已给该用例补一条 `assert!(sanitized.contains("<url>"))`:规则禁用时新断言精确变红,恢复后 `error_report` 定向用例全绿(sanitize 4 条 + submit 4 条)。
- 变异验证之三/四(2026-09-24):① 去掉 `error_report::submit::stable_submission_id` 里的 `ids.sort_unstable()` 后,`submission_id_is_stable_and_order_independent` 立刻红(同一批事件两种顺序给出两个不同 `agc-error-report-*`),证明「同批重放命中服务端幂等键」的排序不变性被守住;② 把 `account_api::map_http_error` 的 401 分支前缀 `authentication-required: ` 去掉后,`expires_and_permissions_keep_stable_categories` 精确变红——这正是渲染层 `platformSessionRetry` 用来决定「续期一次再重放」的分类契约,两端各有一条用例钉住。改回后 `account_api::tests` 与 `error_report::submit::tests` 各自 exit 0。
- 仍未验证(需要真实环境):真实 OSS 发行包上传与送审、真实账户下的发行资料生成、发布取消/未知结果的端到端表现。
@@ -3,7 +3,7 @@
| 字段 | 值 |
| --------- | ------------------------------------------------------------------------- |
| Milestone | `docs/project-memory/plans/【里程碑】AGC更新发布管线渠道化-2026-09-17.md` |
| Status | ready |
| Status | implemented-awaiting-runtime-acceptance |
| Owner | Codex |
## 修改边界
@@ -35,3 +35,12 @@
- 签名缺失即失败关闭:构建机未注入签名私钥时发布中止,不产生半成品清单。
- 渠道端点写进产物:渠道名一旦发布不可改名(改名等于已发布客户端再也找不到更新)。
- 回滚点:发布脚本与流水线都在本里程碑内,回滚后客户端仍可用原先的自研清单协议;迁移桥可独立停用。
## 本轮复核(2026-09-24)
状态原为 `approved` / `ready`(规范评审态),实现其实已经落地,本轮按当前工作树复核并改记为 `implemented-awaiting-runtime-acceptance`:
- 渠道单点定义 `apps/ai-game-creator-shell/scripts/channel-identity.mjs`;`build-release.mjs` 由它解析渠道,渠道清单 URL 与构建期端点随渠道变化。
- 流水线渠道参数:`jenkins/Jenkinsfile.ai-game-creator-shell-build` 有 `AGC_UPDATE_CHANNEL`(dev / release / 自定义小写名)并把部署目标绑定为渠道对应的构建分区。
- 定向测试(本轮实跑):`node --test apps/ai-game-creator-shell/scripts/build-release.test.mjs apps/ai-game-creator-shell/scripts/release-oss.test.mjs apps/ai-game-creator-shell/scripts/prepare-macos-codex.test.mjs apps/ai-game-creator-shell/scripts/cargo-features.test.mjs` → **60 passed / 0 failed**,含 `channel manifest carries version, platform keys and signature`、`missing signature fails the channel manifest closed`、`channel manifest URL and build-time endpoint follow the channel`、`release windows uploads only its own partition and cannot write the dev bridge`。
- 未执行(需要签名私钥与发布授权):真实签名打包、真实 OSS 上传、各渠道真机升级验证。
@@ -0,0 +1,49 @@
# 【实施计划】AGC 本地状态事件化第一批
Version: `v1`
Status: `implemented-awaiting-runtime-acceptance`
Date: `2026-09-23`
Parent Spec: `docs/project-memory/plans/【里程碑】AGC渲染层事件化与Rust数据源-2026-09-23.md`
## 修改边界
1. `src-tauri/src/agent/direct_runtime` 与 `runtime_driver`:增加活动回合快照事件,在注册、状态变化和 guard 释放后发送;不改变既有 Tauri command 与运行语义。
2. `src/features/agent-runtime/directActiveTurns.ts`:订阅事件并去重,首次只读一次快照,移除 5 秒定时器。
3. `src-tauri/src/asset_generation_tasks.rs`:账本写入成功后发送项目任务变更事件;`resourceCanvasAssetGenerationQueue.ts` 通过事件唤醒单次读取,恢复/丢事件仍允许显式读取。
4. `src-tauri/src/plugin_host.rs`:插件状态变更后发送插件快照事件;`RuntimeConfigDialog.tsx` 订阅事件并移除 1 秒定时器。
5. 对应 Vitest/Rust 测试与必要的 AGC 文档更新。
6. `direct_thread_manager`、Tauri handler 与聊天订阅 hook 补幂等 unsubscribe;`App.tsx` 保存失败后解除自触发重写,定向覆盖故障注入与策略读取单飞。
## 实施顺序
- 先增加 Rust 事件与单元测试,再改 React 订阅,避免出现“旧读取已删、新事件未发”的中间态。
- 事件 handler 只接受当前项目/当前代次,按内容签名去重;清理函数必须幂等。
- 事件不可用时只保留首次快照与用户触发的显式刷新,不增加隐式定时器。
## 验证命令
- `npm exec vitest run -- apps/ai-game-creator-shell/tests/directActiveTurns.test.tsx apps/ai-game-creator-shell/tests/resourceCanvasAssetGenerationQueue.test.ts apps/ai-game-creator-shell/tests/tauriEventSubscription.test.ts`
- `npm exec vitest run -- apps/ai-game-creator-shell/tests/appSurface.test.ts`
- `npm --prefix apps/ai-game-creator-shell run typecheck`
- 对 AGC 壳 Rust 运行相关单测分片或定向 `cargo test`
- `npm run check:encoding`
- `git diff --check`
## 回滚点
事件名与 payload 是内部 Tauri 合同;若运行时验证发现订阅或事件顺序问题,可保留 Rust 事件发送、临时恢复一次性快照读取,并回退 React reducer,不修改持久化账本和既有 command。
## 当前验证记录
- `npm run ai-game-creator-shell:check:web`:类型检查、配置契约和 AGC Web 全量门禁通过,`1858 passed / 14 skipped`。
- 定向事件/生命周期测试:`41 passed`;其中活动回合 `11/11`、资源生成队列 `19/19`、线程订阅 `2/2`。
- Rust 定向测试:Direct Thread Manager `13/13`、活动回合快照 `1/1`、素材生成账本 `15/15`、插件宿主 `17/17`。
- `cargo fmt --check`、`npm run check:encoding`、`npm run check:doc-index`、`git diff --check` 通过。
- 未验证真实 Provider、打包后 Tauri 窗口时延和外部发行网络迁移;账户、素材上传、错误上报与发行网络仍按后续里程碑保留。官方模型目录已在本轮单独由 `load_game_creator_llm_models` 下沉到 Rust,但不能据此宣称全部 React 网络 I/O 已归 Rust。
## 本轮取证(2026-09-24)
- 验收 1(活动回合与素材任务不再定时轮询):`rg setInterval apps/ai-game-creator-shell/src` 全仓只剩计时/动画与拖拽重复器;`directActiveTurns.ts` 走 `DIRECT_ACTIVE_TURNS_CHANGED_EVENT` 事件 + 单飞读取并在注释里写明「不恢复定时轮询」,`resourceCanvasAssetGenerationQueue.ts` 走 `game-creator-asset-generation-task-changed` 事件(Rust 在账本状态变更处 emit),事件桥不可用时才回退注入的 wait。
- 验收 3/7(订阅先于快照、重复事件不换数组身份、卸载后迟到事件不写回、subscriber 幂等注销):`npx vitest run tests/directActiveTurns.test.tsx tests/resourceCanvasAssetGenerationQueue.test.ts --root apps/ai-game-creator-shell` → **11 + 19 passed**;Rust 侧 `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml --offline -- direct_thread_manager --test-threads=1` → **13 passed**。
- 验收 4(插件状态:进入读一次 + 事件更新 + 显式刷新):`npx vitest run tests/pluginHost.test.ts --root apps/ai-game-creator-shell` → **14 passed**。
- 仍未验证(需要真实客户端):长回合中插件状态事件与素材任务事件的真实时序、真实 Provider 生成下的任务账本事件密度。
@@ -0,0 +1,47 @@
# 【实施计划】AGC模型Agent模式绑定与Claude Code执行器
| 字段 | 值 |
| --------- | --- |
| Milestone | `docs/project-memory/plans/【里程碑】AGC模型Agent模式绑定与Claude Code执行器-2026-09-30.md` |
| Status | ready |
| Owner | Codex |
## 修改边界
- 允许修改:
- `server-rs/crates/module-runtime`、`shared-contracts`、`api-server`、SpacetimeDB JSON 目录读写相关测试。
- `apps/admin-web` AGC 模型目录页面、类型和测试。
- `apps/ai-game-creator-shell` 客户端模型目录/选择、Tauri 配置、Agent Runtime 与 Claude Agent SDK sidecar/adapter、测试。
- 当前 AGC 模型目录/Runtime 权威文档和共享记忆(仅写入最终稳定结论)。
- 明确不修改:
- 现有 Codex app-server 协议实现的行为与私有凭据。
- SpacetimeDB 表结构、migration、生成绑定(目录 JSON 载荷兼容即可)。
- 与本功能无关的工作树已有修改。
## 实现顺序
1. 先补齐目录领域字段、默认/兼容反序列化、后台 DTO/API/admin-web 编辑与测试。
2. 扩展公开模型摘要和客户端缓存/选择,将后台 `codex`/`cc` 映射为内部执行模式并修复设置保存覆盖问题。
3. 新增 Claude Agent SDK sidecar 执行器:隔离环境、stream-json 输入输出、模型/Anthropic 配置、MCP 配置、文本与工具事件、超时取消和进程回收;不修改 Codex 执行器。
4. 在 Runtime/DirectProject 分发处按模型绑定路由;补齐 cc 的配置检查、错误分类、客户端状态展示和回归测试。
5. 运行定向验证、隔离真实 smoke,回写文档证据;若发现合同变化,先回到主规范再继续。
## 验证命令
1. `npm run check:encoding`
2. `git diff --check`
3. `npm --prefix apps/admin-web run test -- --run src/pages/AdminAgcModelsPage.test.tsx`
4. `npm --prefix apps/admin-web run typecheck`(以仓库实际 script 为准)
5. `cargo test -p module-runtime agc_models`
6. `cargo test -p api-server agc_model`
7. `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml` 的配置/Claude/Direct 定向测试
8. `npm --prefix apps/ai-game-creator-shell run test -- --run` 与类型检查(按实际 script 收窄)
9. 隔离临时项目运行 Claude Code 文本、受控 MCP、取消/回收 smoke
## 风险与回滚点
- Claude Agent SDK sidecar 的 stream-json 事件或输入格式变化:adapter 只接受白名单事件,失败关闭;可将模型绑定回 `codex`,不影响现有链路。
- 后台目录新增字段与旧 JSON:使用缺省 `codex`,保存时写回字段;若兼容测试失败,保留目录载荷原格式并只在读投影填默认。
- 客户端模型选择与 Agent 模式不同步:选择命令以模型目录快照的 `agentMode` 为输入并做枚举校验;同步失败不得切换活动回合。
- Claude 原生工具越权:默认只接入受控 MCP,禁止原生 Bash/写文件;任何未经宿主确认的工具请求转失败。
- 取消/进程归属不确定:保持失败关闭,不复用旧 cc 进程;Codex 模式继续走原有执行器。
@@ -0,0 +1,45 @@
# AGC 模型目录上游同步实施计划
| 字段 | 值 |
| --- | --- |
| Version | 1.0 |
| Status | implemented-awaiting-runtime-acceptance |
| Date | 2026-09-24 |
| Parent Milestone | `docs/project-memory/plans/【里程碑】AGC模型目录上游同步-2026-09-24.md` |
## 修改边界与顺序
1. **领域模型(`module-runtime/src/agc_models.rs`)**:删除写死的 `Default` 实现(原 `quality → gpt-6-astra`、`fast → gpt-5.6-luna`),新增 `from_upstream_models`:按上游模型名排序去重后生成目录项(`modelId`/`alias` = 上游原名,`id` = 模型名 slug,`enabled = true`),默认项取排序后第一项;新增 `resolve_requested`(未选或 `platform-default` 用默认项)。字段、校验规则(32 项上限、id/alias/model_id 约束)与 `resolve` 保持原样。
2. **procedure(`spacetime-module/src/agc_models.rs`)**:`read_agc_model_catalog` 缺行返回 `AGC_MODEL_CATALOG_NOT_INITIALIZED`,不再返回内置目录;`save_agc_model_catalog` 不变。无表结构变化,不改 `migration.rs`。
3. **api-server 目录模块(`src/agc_models.rs`)**:新增启动期 `ensure_agc_model_catalog_initialized`(读 → 解析/校验 → 缺行或非法则 `GET {控制面}/api/pricing?group=taonier` → 生成目录 → 按存量 revision 写回;冲突后重读确认可用);上游请求 10s 超时、1 MiB 流式上限、禁止重定向、不带凭据;未初始化统一 `503` 文案;后台 PUT 增加未初始化门禁。
4. **api-server 接线(`src/main.rs`、`src/external_api_keys.rs`)**:`try_restore_app_state_for_startup` 按 HTTP 角色调用初始化,失败只 `error!` 记录;抽出 `ensure_llm_router_url_allowed`(只校验地址/scheme,避免被已下线的固定模型哨兵挡住),`LLM_ROUTER_TOKEN_GROUP` / `router_control_origin` 供同步复用。
5. **客户端与后台**:不改。`GET /api/llm/models` 形状、admin DTO、后台「AGC 模型」页、客户端 `select_game_creator_model` 校验全部保持原样。
## 不改的部分
目录字段语义、后台 DTO 与页面、公开 DTO 形状、客户端模型标识校验、`/api/external/v1` 与 OpenAPI、SpacetimeDB 表结构、Router provisioning/额度。
## 验证命令
- `cargo test --locked -p module-runtime --lib agc_models::`
- `cargo test --locked -p api-server --bin api-server agc`、`... llm::`
- `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml --bins configuration::`
- `cargo fmt --all -- --check`(两套 workspace)、`npx vitest run apps/ai-game-creator-shell/tests/conversationModelSelect.test.tsx`、admin-web 页面定向 Vitest 与 typecheck
- `npm run check:encoding`、`npm run check:doc-index`、`npm run check:spacetime-schema`、`git diff --check`
- 运行时 smoke:本地 dev 栈清空 `agc_model_catalog` 后启动 api-server,确认日志 `已按上游模型列表初始化 AGC 模型目录`、库中 `catalog_json` 为「slug id + 上游原名 alias/modelId」、`GET /api/llm/models` 返回原名;再把上游地址指向不可达端口验证 `503` 与「无替代目录」。
## 风险与回滚点
- **上游端点与鉴权**:分组定价列表端点为实测确认的公开只读接口;若上游改版,同步失败只会让目录保持未初始化(接口 503 + 启动 error),不会写入错误模型。
- **混合版本**:module 的缺行语义变化要求 module 与 api-server 同批发布/回滚;未升级的 api-server 会把自己的 AGC 接口打到 `503`(后台 DTO 未变,admin-web 可独立发布)。回滚点必须同时覆盖 module 与 api-server。
- **存量目录**:结构合法的旧目录(含 `quality/fast`)不会自动重建,需要 owner 在后台修改或清空该行后重启。
- **目录规模**:目录项上限仍是 32;上游在售模型超过 32 条时同步会失败并记录原因,需要 owner 在后台维护子集。
## 本轮核对(2026-09-28)
上游已用同主题提交落地,合并后工作树与计划完全对齐:
- `module-runtime`:`AgcModelCatalog::from_upstream_models`(含 slug/默认项/32 项上限与单测)在 `server-rs/crates/module-runtime/src/agc_models.rs`。
- `api-server`:`ensure_agc_model_catalog_initialized` 在 `src/agc_models.rs`,接线在 `src/main.rs`(启动期按 HTTP 角色调用,失败只记 error),未初始化统一 `503` 与 `AGC_MODEL_CATALOG_NOT_INITIALIZED` 语义在 `agc_models.rs` 里。
- 上游提交:`4234a21ee 服务端:AGC 模型目录缺配置时改为启动期从上游同步,保留原目录格式`;本地真实上游 smoke(清空目录 → 启动日志「已按上游模型列表初始化 AGC 模型目录 revision=1 model_count=6」→ `GET /api/llm/models` 返回原名 → 上游不可达时 503 且不写替代目录)已经记在 `decision-log` 里。
- 仍待补:生产发布(module 与 api-server 必须同批发布/回滚)与超过 32 条上游模型时的 owner 维护动作,都属于外部环境/发布范畴。
@@ -3,7 +3,7 @@
| 字段 | 值 |
| --- | --- |
| Milestone | `docs/project-memory/plans/【里程碑】AGC渠道安装身份隔离-2026-09-21.md` |
| Status | ready |
| Status | implemented-awaiting-runtime-acceptance |
| Owner | 当前 Agent |
## 修改边界
@@ -37,7 +37,7 @@
- `node --test build-release.test.mjs release-oss.test.mjs prepare-macos-codex.test.mjs cargo-features.test.mjs`:64/64 通过(新增渠道身份、身份注入与渠道 DMG 首装选择三条用例)。
- `node apps/ai-game-creator-shell/scripts/check-config.mjs`、`npm --prefix apps/ai-game-creator-shell run typecheck`:通过。
- `cargo test --locked --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml --bin genarrative-ai-game-creator-shell -- config::private_path_elevation_policy_tests`:12/12 通过。
- `AGC_UPDATE_CHANNEL=release npm --prefix apps/ai-game-creator-shell run build -- --no-bundle --debug`:Tauri 接受派生的 `productName` / `identifier` 并完成构建;产物字符串实测 `陶泥儿 Release` × 1、`agc/release-win/latest.json` × 1、`world.genarrative.ai-game-creator.release` × 1、`agc/dev-win/latest.json` × 0。
- `AGC_UPDATE_CHANNEL=release npm --prefix apps/ai-game-creator-shell run build -- --no-bundle --debug`:Tauri 接受 `productName=陶泥儿` / `identifier=world.genarrative.ai-game-creator.release` 并完成构建;产物字符串实测 `陶泥儿` × 1、`agc/release-win/latest.json` × 1、`world.genarrative.ai-game-creator.release` × 1、`agc/dev-win/latest.json` × 0。
- `cargo fmt --check`(AGC 壳)、`npm run check:encoding`、`npm run check:doc-index`、`git diff --check`:通过。
- 未执行:真实渠道打包(需要签名私钥与发号/上传授权)、双渠道真机安装与并存、macOS 节点实跑。
@@ -54,3 +54,8 @@
- `channel-identity.mjs` 是展示名单一来源。发布构建将同一 `productName` 同时注入 Tauri 安装配置、原生窗口标题和 `VITE_AGC_PRODUCT_NAME`;React 自绘标题栏及关于/运行时配置展示从该注入值读取。
- 因此 Windows NSIS 默认生成的快捷方式、开始菜单/卸载注册表展示名随 Tauri `productName` 变为 `陶泥儿开发版`;未新增自定义注册表或快捷方式实现。
- 静态 Tauri 基线配置同步为 `陶泥儿开发版`,本地壳与正式 `dev` 包的显示名保持一致。
## 本轮复验(2026-09-24)
- `node --test apps/ai-game-creator-shell/scripts/build-release.test.mjs apps/ai-game-creator-shell/scripts/release-oss.test.mjs apps/ai-game-creator-shell/scripts/prepare-macos-codex.test.mjs apps/ai-game-creator-shell/scripts/cargo-features.test.mjs`:**60 passed / 0 failed**,含「release / beta 的 Windows 与 macOS 构建只写自己渠道分区、不能写 dev 桥」四条分区隔离用例。
- 仍未执行:真实渠道打包(需要签名私钥与发号/上传授权)、双渠道真机并存安装、macOS 构建节点实跑。
@@ -0,0 +1,46 @@
# 【实施计划】AGC 渲染层下沉与最新 master 对齐
| 字段 | 值 |
| --- | --- |
| Milestone | `docs/project-memory/plans/【里程碑】AGC渲染层下沉与最新master对齐-2026-09-28.md` |
| Status | implemented-awaiting-runtime-acceptance |
| Owner | Codex |
## 一句话交付结果
`codex/agc-renderer-io-downshift` 合入 `origin/master` 后,AGC 渲染层仍然是「只做展示与交互」的离线前端,
网络 IO 与正式数据仍全部在 Rust 侧,且首页「运行中的项目」、封面生成队列与维护态提示三种能力在合并后
行为正确、有定向测试证明。
## 任务清单
1. 冲突收口:机械冲突按「本分支退役渲染层传输」取舍(nginx SPA 白名单、`.gitignore`、mobile 脚本、
capabilities 描述、已退役计划随 master 删除、测试保留双方)。
2. 活动回合事实源统一:删除 `direct_runtime` 里的重复快照函数;把 `emit_direct_active_turns_changed()`
移到 Direct 线程管理器的登记 / 进度 / 收口变更点;迁移对应的 Rust 用例。
3. 封面生成补 `generationInputs`,并为「队列未完成 → 轮询 → 拿到素材 ID」保留 Rust 侧用例。
4. 维护态:Rust 给出稳定分类标记,渲染层在统一的 invoke 边界消费该标记并广播 `CLIENT_MAINTENANCE_EVENT`;
`MaintenanceNotice.tsx` 与 `tests/maintenanceNotice.test.tsx` 跟着新入口更新。
5. 文档收口:保留双方条目的冲突文档合并;`decision-log` / `pitfalls` 追加本轮结论;实施计划与里程碑
回写取证。
## 证据要求
- 每个任务都要有可复跑的定向用例,并在本计划内写明命令与结果。
- 涉及行为变化的用例必须做变异验证:拿掉修复后用例变红。
- 区分「源码级通过」与「需要真实客户端 / 真实 Provider 才能取得的运行时证据」。
## 完成后的临时文档处理
本里程碑验收通过后,把仍然有效的长期边界同步回现行技术方案与项目记忆,再删除本里程碑与实施计划两份
临时文档。
## 本轮取证(2026-09-28)
五步任务全部落地,逐条证据与「与 master 既定决策的分歧」见同名里程碑的《本轮取证》一节,这里只记范围事实:
1. 冲突收口:29 处冲突全部解决,机械取舍按「本分支退役渲染层传输」定向(nginx SPA 白名单、`.gitignore`、mobile 检查脚本、capabilities 描述、两个已退役计划随 master 删除、测试保留双方)。
2. 活动回合事实源统一:删除 `direct_runtime` 的第二份快照与旧注册表的通知位点,改由 Direct 线程管理器三处变更点广播;Rust 定向用例 17 passed。
3. 封面生成补 `generationInputs`:`tests/gameDistributionPublish.test.ts` 7 passed。
4. 维护态:Rust 统一分类 + 单一事件,渲染层只订阅;Rust 1 passed、Web 2 passed。
5. 文档收口:`decision-log` 与 `pitfalls` 各追加一条 2026-09-28 记录;两份冲突文档保留双方条目。
@@ -1,7 +1,7 @@
# AGC 画布交互稳定性修复实施计划
- Date: 2026-09-16
- Status: awaiting-runtime-acceptance
- Status: implemented-awaiting-runtime-acceptance
- Milestone: [画布交互稳定性修复](./【里程碑】AGC画布交互稳定性修复-2026-09-16.md)
## 修改顺序与边界
@@ -1,7 +1,7 @@
# AGC 统一错误诊断与验收反馈实施计划
Version: 1.0
Status: active
Status: implemented-awaiting-runtime-acceptance
Date: 2026-09-15
Parent Milestone: `【里程碑】AGC统一错误诊断与验收反馈-2026-09-15.md`
@@ -40,3 +40,24 @@ Parent Milestone: `【里程碑】AGC统一错误诊断与验收反馈-2026-09-1
- `apps/ai-game-creator-shell/src/components/modal/MaintenanceNotice.tsx` 在客户端根部统一展示不可被局部业务兜底替代的大弹窗;发布、上传、资源换签等共用 `requestClientApi` 的请求均进入同一出口。
- 普通 500、资源损坏和本地 `blob:` 图片预览失败不自动归类为维护;图片换签接口在维护期间失败时会触发统一弹窗,但本地刚选中的图片预览仍不依赖后端。
- 验收补充:维护期间发布接口不能只显示“创建平台游戏失败”等局部文案;维护弹窗出现一次即可覆盖并发失败请求,关闭后业务页仍可重试。
## 当前进度(2026-09-24)
- 已落地并验证:统一错误事件模型与脱敏(`cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml --offline -- --test-threads=1 runtime_error` → 7 passed)、DirectProject 失败安全投影与项目内 sidecar、下一轮诊断上下文、app-server 终态与超时分类、试玩 attempt 上限。
- 本轮补缺口:渲染层取回 `detailRef` 的口径抽成 `apps/ai-game-creator-shell/src/services/agentRuntimeErrorDetail.ts`,新增 `tests/agentRuntimeErrorDetail.test.ts`(4 条);Rust 侧新增 `direct_codex_failure_text_keeps_the_detail_ref_marker_for_the_renderer` 钉住同一份文案。两侧用例互相引用用例名,改文案或改解析都会变红(文案标记过去只在渲染层内联正则里,两侧都没有测试)。
- 仍未完成的是验收标准 6 的交互项:诊断正文目前随失败文案一次性读回并展示,尚未做成「点击详情后才读取」的展开入口。要继续的话先在主规范里定交互,再改 UI;在那之前本里程碑保持 `active`,不按已完成收口。
## 报告缺口关闭(2026-09-24)
上一轮记录里「验收标准 6 的交互项未完成」已经实现,本轮把计划从 `active` 改记为 `implemented-awaiting-runtime-acceptance`:
- 修掉一个真实缺陷:Native 已经下发 `direct-codex-failure:v2`(在 v1 之上多了 `code=` 与末尾 `;详情:<detailRef>`),但渲染层映射只认 v1,任何 DirectProject 失败都被兜底成「执行失败,请稍后重试」,Rust 精心产出的阶段、摘要与建议全部被丢掉。现在 v1 / v2 共用同一套脱敏与阶段映射(v2 用例在 `tests/agentRuntimeModel.test.ts`,移除 v2 分支该用例变红;2026-09-24 复核:把正则里的 `:v2` 改成 `:v3` 后该文件精确 1 条失败、其余 18 条通过,声明已被独立复现)。
- 失败提示不再预读也不内联诊断正文:控制器只把安全 `publicText` 交给面板,`detailRef` 与提示文案分开持有;聊天状态栏下方新增「查看详情 / 收起详情」入口,展开时才调用 `read_agent_runtime_error_detail`(原生命令仍做目录校验与二次脱敏),读不到只显示一句说明,不影响提示本身。
- 钉住这条交互的用例:`tests/appSurface/chat-composer.suite.ts` 的「失败提示保留可执行原因,诊断正文只在「查看详情」时读取」(断言点击前没有 `read_agent_runtime_error_detail` 调用、正文不在提示或历史消息里,点击后按 `projectPath + detailRef` 读取并展示,再点收起)。
- 自动化:`npx vitest run tests/agentRuntimeModel.test.ts tests/agentRuntimeErrorDetail.test.ts`(23 passed)、`npx vitest run tests/appSurface.test.ts`(191 passed / 9 skipped)、`npx tsc -p apps/ai-game-creator-shell/tsconfig.json --noEmit`、`cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml --offline -- --test-threads=1 runtime_error`(7 passed)。
- 仍未验证(需要真实账号与 Provider):真实失败回合下从提示进入详情、以及下一轮追问从历史读取失败证据的端到端表现。
## 2026-09-28 上游取代:诊断详情入口按 master 收口
合并 `origin/master` 时发现上游提交 `5398a53e6 宿主与前端:用户可见文案不再带诊断引用,失败也进错误上报池` 已经明确退役本计划第 6 条的「查看详情」交互:失败文案不再带 `;详情:…` 引用,`read_agent_runtime_error_detail` 命令与渲染层 `agentRuntimeErrorDetail.ts` 一并删除,失败线索只留在 `.agent/runtime/errors`、应用日志与错误上报池。
本里程碑接受上游口径,主题的其余验收(统一错误事件、失败安全投影、下一轮诊断上下文、终态分类、试玩 attempt 上限、v1/v2 文案映射)保持不变;本次对齐删除了该交互的专属实现与用例,失败说明继续由 `turn.completed.failure` 事件投影成同一份安全文案。结论同时记入 `decision-log` 2026-09-28 条目。
@@ -0,0 +1,65 @@
# 【实施计划】AGC 认证会话与账户网络下沉 Rust
Version: `v1`
Status: `implemented-awaiting-runtime-acceptance`
Date: `2026-09-24`
Parent Spec: `docs/project-memory/plans/【里程碑】AGC认证会话与账户网络下沉Rust-2026-09-24.md`
## 修改边界
1. 新增 `src-tauri/src/auth_session.rs`:AppData 私有凭据文件(`client-session.json`)、
origin 校验、refresh cookie 解析与原子写入、并发续期合并、401 续期重放与事件发送。
2. 新增 `src-tauri/src/account_api.rs`:profile dashboard / 充值中心 / 创建订单 /
确认微信充值 / 钱包账单 / 兑换码 typed commands。
3. `src-tauri/src/main.rs` 注册新 command 与新模块;`platform_asset_upload.rs`、
`game_distribution_publish.rs`、`llm_catalog.rs`、`error_report/submit.rs` 改为经
`auth_session` 获取会话并在 401 时续期重放一次,而不是只读当前快照。
4. 渲染层 `clientAuth.ts`、`clientHttp.ts`、`clientApi.ts`、`platformSession.ts`、
`AuthenticatedClient.tsx`、`useAccountWallet.ts`、`stores/useWalletStore.ts`
改为 typed invoke / 事件;
## 修改顺序
1. 先落 Rust 凭据存储与 origin 校验,配单元测试;此时尚无调用方,不改变现役行为。
2. 落认证 commands 与事件,Rust 内部安装/清除本地运行时会话(复用既有
`install_platform_session` / `clear_platform_session` 与 Runner 传播),保留旧 command。
3. 落账户/钱包 commands,复用同一会话获取与 401 续期重放。
4. 切换渲染层:`AuthenticatedClient` 与钱包改为 command/事件;删除 token 持久化与
`fetchClientHttp` 直连调用点。
5. 最后清理:删除 `clientApi`/`clientHttp` 中已无调用方的直连实现与旧 localStorage 迁移键,
更新主规范与共享记忆。
## 验证命令
- `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml auth_session`
- `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml account_api`
- `npm --prefix apps/ai-game-creator-shell run typecheck`
- `npx vitest run apps/ai-game-creator-shell/tests/clientAuth.test.ts apps/ai-game-creator-shell/tests/accountWallet.test.tsx`
- `npm run ai-game-creator-shell:check:web`
- `cargo fmt --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml -- --check`
- `npm run check:encoding`、`npm run check:doc-index`、`git diff --check`
## 风险与回滚点
- refresh 凭据落盘是本计划的最高风险项:必须使用与其它 AGC 私有文件相同的 ACL/普通文件
门禁,并且只写 refresh cookie(不写 access token)。
- 401 续期重放必须只重放一次;付费创建订单的未知结果按 `result-unknown` 处理,不自动重放。
- 回滚点:保留旧 `install/clear_platform_account_session` 与旧 `clientAuth` 分支直到验收;
回退时删除 Rust 凭据文件即回到未登录状态。
## 当前验证记录(2026-09-24)
- 新增 `src-tauri/src/auth_session.rs`:AppData 私有 `client-session.json`(Unix 0o600、Windows reparse 检查、私有目录树与原子替换)、origin 校验(纯 origin、非本机必须 HTTPS、发布构建限渠道 origin)、refresh cookie 解析与轮换、登录/验证码/续期/登出 HTTP、`install_client_session_locally` / `clear_client_session_locally` 复用既有 Runner 传播与 app-server 停机路径。
- 新增 6 条 typed command:`read_client_auth_state`、`refresh_client_auth_session`、`send_client_phone_login_code`、`login_client_with_password`、`login_client_with_phone_code`、`logout_client_session`,并发出 `agc-client-auth-state-changed` 状态事件(只含状态、用户展示字段与 origin)。
- 路由语义分离:会话路由的 401/403 归类成 `authentication-required` / `permission-denied`(可据此清会话),登录路由保留服务端原因(「手机号或密码错误」),避免把用户输入错误显示成登录失效。
- Direct 回合保活改为 Rust 持有:`DirectTaonierActiveInvocationGuard` 在登记回合时启动 `spawn_client_session_keepalive`(30 秒 tick、按 access token 签发时间 4 分钟后才真正续期,drop 即 abort),渲染层删除 5 分钟 `setInterval` 与 `DIRECT_CODEX_SESSION_KEEPALIVE_MS`;401 补续期重试路径保留。\n- 渲染层:`clientAuth.ts` 收敛为 typed invoke façade;`platformSession.ts` 收敛为身份代次投影(Rust 事件驱动,同一账号续期不推进代次);`AuthenticatedClient` 的三态恢复、登录、登出全部改走 command/事件;服务器选择迁到 Rust 客户端偏好(`set_client_server_selection`)。
- 删除:`services/clientApi.ts`、`clientHttp` 的 `fetchClientHttp`/超时封装/服务器选择 localStorage、`clientAuth` 的 token 持久化、`install/clear/read platform_account_session` 三条渲染层写入入口(Rust 认证内部直接调用)。渲染层 `src` 内已无 `fetch(`、无 access token / refresh cookie 读写。
- 验证:Rust 定向 `auth_session` 12/12(含并发续期复用、事件不含凭据、保活窗口判定与未初始化不启动任务)、`direct_runtime` 97/97、`workspace_preferences` 6/6、`account_api` 4/4、`platform_asset_upload` 3/3、`error_report::submit` 4/4;前端 `clientAuthHost` 6/6、`conversationModelSelect` 29/29、`useTemplateLibrary` 11/11、appSurface 认证切片 12/12;`npm run ai-game-creator-shell:check:web` 184 passed / 1800 passed / 14 skipped;`cargo fmt --check`、`check:encoding`、`check:doc-index`、`git diff --check` 通过。
- 未验证:真实账号登录/续期/登出、微信支付、多窗口并发刷新、真实 Provider 回合跨 token 过期的保活,仍需环境证据。Direct 回合的 5 分钟保活仍由渲染层定时触发(真正续期已在 Rust),后续可改为由 Rust 在出站调用前按到期时间自续期。
## 本轮取证(2026-09-24)
- 原生定向(同一批过滤一次跑完 42 项):`cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml --offline -- workspace_preferences account_api platform_asset_upload auth_session error_report::submit direct_thread_manager --test-threads=1` → **42 passed**。
- 分组明细:`auth_session::` **12 passed**(会话存储所有权与权限、origin 校验、6 条命令的错误分类)、`account_api::` **4 passed**、`platform_asset_upload::` **3 passed**、`workspace_preferences` 6、`direct_thread_manager` 13、`error_report::submit` 含在其中。
- 结构:`rg "accessToken|refreshToken" apps/ai-game-creator-shell/src` → 0 命中(渲染层不再持有凭据);`localStorage` 只剩偏好迁移 shim。
- 仍未验证(需要真实账号):真实登录/续期/登出、微信支付、多窗口并发刷新、真实 Provider 回合跨 token 过期。
@@ -1,7 +1,7 @@
# AGC 资源 JSON 语义识别实施计划
- Date: 2026-09-16
- Status: awaiting-runtime-acceptance
- Status: implemented-awaiting-runtime-acceptance
- Milestone: [AGC 资源 JSON 语义识别](./【里程碑】AGC资源JSON语义识别-2026-09-16.md)
## 实施边界
@@ -1,61 +0,0 @@
# 【实施计划】AGC 资源 kind Rust 枚举与 ts-rs 绑定
| 字段 | 值 |
| --- | --- |
| Milestone | `docs/project-memory/plans/【里程碑】AGC资源kind枚举化Rust绑定与内部收紧-2026-09-15.md` |
| Status | complete |
| Owner | Codex |
## 修改边界
允许修改:
- `server-rs/crates/shared-contracts` 内 GameCreationApp 资源 kind 的独立 Rust 模块;
- shared-contracts 的可选 ts-rs 绑定 feature;
- ai-game-creator-shell 的 shared-contracts feature 引用和生成测试入口;
- shell 内生成的 GameCreationApp kind TypeScript 文件;
- 对应 Rust/TS 定向测试与文档 todo 证据。
本切片已落地:
- `GameCreationAppAssetManifestEntry.kind` 由 `String` 收紧为 `GameCreationAppAssetKind`:这是公开 Rust 类型的破坏性变更,按「不写历史兼容代码」直接改类型,不加兼容层、不做数据迁移;manifest 反序列化未知值收口为 `Unknown`。
- 上传内容推断的字体写入 `Font`,未知上传类型写入 `Unknown`;图集切片 manifest 写入 `Icon`。
- shell 内 manifest kind 的写入侧与读取侧统一为正式枚举成员:资源登记、字体导入、direct runtime 产物、平台美术登记与恢复、manifest 必填校验、资源编辑派生、UI 设计桥接与工作流。
- 资源编辑请求的 `sourceSubtype` 只传正式枚举成员;资源编辑内部源身份拆成「正式 kind / 项目版本 / Agent 回执」三类,项目版本与 Agent 回执不再冒充 manifest kind。
- ts-rs 绑定改由 `cargo test export_bindings` 生成:枚举自带 `#[ts(export, export_to = …/src/contracts/generated/)]`;退役的 `BindingDTO` / `BindingChange` 不再导出,手写的 `exports_ui_editor_types` 已删除。
- `packages/shared/src/contracts/generated/` 作为受控生成目录在 `.prettierrc.json` 中使用与 ts-rs 原始输出一致的格式选项,并在 `.gitattributes` 标记为 `linguist-generated`;生成文件仍参与 Prettier/ESLint 检查,`cargo test export_bindings` 可幂等复现且不再污染工作树。
- `Unknown` 表示解析边界无法判定的输入;写入方可以保留该结果,但必须明确决定拒绝写入或交由后续归类,不得把它误写成某个具体 kind。
- shell TS 资源引用边界消费生成绑定,资源 kind 集合从生成的 union 穷举派生,并为未知值保留原始值与上下文日志。
- api-server 图片快速编辑来源校验改为严格解析 `GameCreationAppAssetKind`,只允许共享枚举的静态图片成员;删除原有 legacy/兼容字符串白名单,退役值不再放行。
明确不修改:
- `category`、`mediaType`、`source.kind`、`source.generationKind`;
- api-server 的其他资源 taxonomy、SpacetimeDB schema、OpenAPI;本切片只改图片快速编辑的 GameCreationApp manifest 来源校验。
- shared-contracts 的 canonical alias 表与读时自愈:alias 表已删除,严格解析和 `Unknown` 口径保持不变;
- `packages/shared` 中重复的资源 kind 手写契约与其余前端裸 `string` 字段(后续切片)。
## 实现顺序
1. 新建独立 kind 模块,定义当前正式成员:17 个现有 canonical kind + `font` + `Unknown`,serde 使用 kebab-case。
2. 为 enum 增加可选 ts-rs derive,默认 shared-contracts 构建不引入 ts-rs。
3. 让 shell 的 shared-contracts 依赖打开绑定 feature;生成目录 2026-09-17 起收敛到 `packages/shared/src/contracts/generated/`(kind 是跨端契约,不再放 shell 私有目录)。
4. 增加 Rust serde round-trip 与 Unknown 解析/序列化测试;TS 侧不再手写生成值集合,改为从生成 union 穷举派生。
未知值通过 `parse_with_context` 收口到 `Unknown`,并把原始值与解析上下文交给壳层注册的 `app_log!` 回调(2026-09-17 起不再用 `tracing` / `kind-observability`)。
5. 运行 shared-contracts 与 shell 定向测试,并用 `export_bindings` 复现生成文件;完成后将稳定结论回写里程碑并关闭本计划。
## 验证命令
1. `cargo fmt --all -- --check`
2. `cargo test --locked -p shared-contracts game_creation_app --manifest-path server-rs/Cargo.toml`
3. `cargo test --locked -p shared-contracts --features ts-bindings export_bindings --manifest-path server-rs/Cargo.toml`(生成 `packages/shared/src/contracts/generated/GameCreationAppAssetKind.ts`)
4. `cargo test --locked --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml export_bindings`(生成 shell 自己的 ts-rs 绑定)
5. `npx vitest run apps/ai-game-creator-shell/tests/assetKind.test.ts packages/shared/src/contracts/gameCreationApp.test.ts`(解析 Rust 源码的 `assetKindCanonicalMapping.test.ts` 2026-09-17 已删除:手写词汇表被移除后它没有比对对象)
6. `npm run check:encoding`
7. `git diff --check`
## 风险与回滚点
- 风险:ts-rs 默认生成 enum/union 形态与 serde rename 不一致;必须以生成文件和 round-trip 测试为准。
- 风险:shared-contracts 被 SpacetimeDB/WASM 使用;ts-rs 必须是 optional feature,默认构建不可依赖。
- 回滚点:本切片仅新增 enum/绑定,不改变现有 manifest JSON 解析;若生成链路不稳定,可回滚本提交而不影响现有业务。
@@ -1,43 +0,0 @@
# 【实施计划】AGC 资源 kind 词汇审计与绑定架构
| 字段 | 值 |
| --- | --- |
| Milestone | `docs/project-memory/plans/【里程碑】AGC资源kind枚举化词汇审计与绑定架构-2026-09-15.md` |
| Status | complete |
| Owner | Codex |
## 修改边界
允许修改:
- 主规范中的资源 kind 当前口径;
- `docs/project-memory/plans/` 下本任务的里程碑/实施计划;
- `docs/project-memory/todos/` 下本任务的扫描清单;
- 为审计提供只读扫描脚本(如确有必要,脚本也只覆盖 shell)。
明确不修改:
- 已完成的 `apps/ai-game-creator-shell` 业务收口不再属于未决范围;后续只允许按新里程碑变更;
- server-side 代码、OpenAPI、SpacetimeDB schema;
- MIME、`source.kind`、`generationKind` 和其他字符串领域。
## 实现顺序
1. 按“manifest asset kind / 其他字段 / 仅文本同名”三类扫描 shell 的 `kind`、`assetKind`、`asset_kind` 和 manifest 构造器。
2. 对每个候选值回到调用上下文,标记生产写入、读取投影、协议输入、测试 fixture 或纯文本/路径。
3. 对照 api-server 当前有效资源 kind,排除 legacy、billing/storage synthetic taxonomy 和开放字段。
4. 记录正式候选 enum、Unknown 输入行为、保留字符串字段和后续 PR 边界。
5. 形成下一里程碑的文件级修改顺序和验证矩阵;本里程碑结束后暂停,等待验收。
## 验证命令
1. `rg -n 'kind|assetKind|asset_kind' apps/ai-game-creator-shell/src apps/ai-game-creator-shell/src-tauri/src`
2. `npm run check:doc-index`
3. `npm run check:encoding`
4. `git diff --check`
## 风险与回滚点
- 风险:同一字符串可能是 manifest kind、workflow kind 或路径名;任何未回到上下文确认的替换都禁止进入代码。
- 风险:shared-contracts 被多个 server crate 使用;ts-rs 生成必须通过 feature/独立入口隔离默认构建。
- 回滚点:本里程碑只有文档和审计清单变更;若词汇边界未能闭合,删除本计划和 todo,不进入代码里程碑。
@@ -2,6 +2,8 @@
对应:[里程碑](./【里程碑】AGC资源菜单收纳-2026-09-17.md),Issue #409,产品已确认方案 A。
Status: implemented-awaiting-runtime-acceptance
## PR #410 CI 修复
以远端合并提交 959beebf 为基线:修复菜单文件 import 排序、Web 角标结构断言、活动回合空快照与晚到请求竞态;窗口发布次数断言对齐稳定快照合同。原生 HTTP scope 检查对齐官方 updater 当前权限,不恢复退役 OSS 白名单;Rust 图集测试补齐显式切片模式与 strict schema 字段,不放宽正式校验。按故障项定向测试后运行前端全套及原生契约检查;Rust 使用独立 target,实际未执行的检查必须单独列出。推送需再次确认。
@@ -14,3 +16,10 @@
4. 并行执行定向 Vitest、AGC 类型检查、编码与文档索引检查,再自审整体调用链。
风险:portal 浮层点击外部判定、缩放角标与拖拽冲突、原测试依赖完整工具栏。回滚仅撤销本分支 UI 与文档修改;无数据迁移。首个检查点为组件用例通过,第二个为工作台集成与类型检查。真实客户端未测则明确保留待验收状态。
## 本轮复验(2026-09-24)
- 共用收纳控件与 UI 基元:`npx vitest run packages/shared/src/components/OverflowActions.test.tsx packages/shared/src/components/ui.test.tsx` → **14 passed**(浮层开关、外部关闭、键盘、视口约束与表格/标签等基元语义)。
- AGC 资源画布菜单与卡面:`npx vitest run tests/resourceCanvasBottomToolbar.test.tsx tests/resourceCanvasCardName.test.tsx tests/resourceCanvasToolbarModel.test.ts`(`--root apps/ai-game-creator-shell`)→ **39 passed**。
- 全量界面门禁:`npm run ai-game-creator-shell:check:web` → 185 files passed / 1 skipped、1806 tests passed(含工作台整表回归)。
- 仍未验收(人工、需要真实客户端):真实客户端视觉、原生保存对话框、触摸板操作手感。
@@ -0,0 +1,64 @@
# 【实施计划】AGC 随包资源改由校验器读入
| 字段 | 值 |
| --------- | --------------------------------------------------------------- |
| Milestone | `docs/project-memory/plans/【里程碑】AGC随包资源改由校验器读入-2026-09-26.md` |
| Status | ready(待里程碑规范评审通过后开工) |
| Owner | suzmii / Agent |
## 修改边界
允许修改:
- `apps/ai-game-creator-shell/src-tauri/build.rs`:新增只读校验调用点;本里程碑内保持现有写入分支不变(不改变既有构建行为)。
- `apps/ai-game-creator-shell/src-tauri/build_support/**`:把平台布局、组件白名单、摘要校验整理为可被构建脚本之外的独立工具复用的一处声明。
- 新增随包资源准备工具及其测试(位置见「待确认决策」)。
- 需要时扩展 `apps/ai-game-creator-shell/scripts/check-config.mjs` 的断言。
- 文档:主规范未决问题收口、开发运维文档对应段落。
明确不修改:
- 三份 tauri 配置的 `resources` 映射、包内路径与安装包形态。
- 运行时资源解析与完整性校验(`codex_cli.rs`、`plugin_host.rs`、`editor_adapters.rs`、`environment_check.rs`)。
- 发布脚本流程、版本号机制、签名与上传。
- dev 启动器与发布入口的接线(下一里程碑)。
- 编辑器分支产物(Unity/Godot/Cocos)的生成方式(最后一个里程碑)。
## 实现顺序
1. **共用能力可复用**:确认 `build_support` 内的平台布局与组件白名单能被独立工具引用(现状先例:`src/agent/codex_cli.rs` 与 `main.rs` 已通过 `#[path]` 复用同一模块),把「布局 + 白名单 + 摘要校验」收敛为单一入口,避免准备工具另写一份清单。
2. **准备工具骨架**:目标目录与清单写出、缓存 key(上游 lockfile 的 `resolved` + `integrity` + 布局版本 + 目标三元)、临时目录 + 原子替换、所有权与符号链接校验、并发串行化、单行汇总日志。先实现纯复制两条路径(随包组件、插件工作区),编辑器分支产物本轮仍由构建脚本生成。
3. **幂等与失败关闭**:重复执行不改变内容与时间戳;上游缺失、摘要不匹配、目录被非本工具占用、目标平台不支持四类场景各自失败并给出可定位原因。
4. **校验路径上线**:构建脚本在既有产物上执行只读校验(默认不影响现有写入行为),校验失败以明确原因中止。
5. **测试与证据**:按里程碑「证据要求」补齐用例与运行记录。
## 验证命令
1. 声明唯一性与门禁:`npm run agc:bundled-resources:check`(已进 `agc:typecheck` 链),不一致时用 `npm run agc:bundled-resources:sync` 重新生成。
2. 准备工具用例(含幂等与失败关闭):`npm run agc:bundled-resources:test`。
3. 校验路径独立运行(跳过写入分支):`AGC_SKIP_RESOURCE_STAGING=1 cargo check --no-default-features --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml`。
4. Rust 用例:`cargo test --no-default-features --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml package_layout` 与 `cargo test --no-default-features --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml codex_bundle`。
5. 幂等(真实工作区):连续两次 `node apps/ai-game-creator-shell/scripts/prepare-bundled-resources.mjs`,第二次必须全部「命中缓存」,且两次之后的目录快照(相对路径、大小、mtime、sha256)完全一致。
6. 并存一致:准备步骤产物与构建脚本产物逐文件比对(相对路径、大小、sha256)一致。
7. 行为不回归:`cargo build --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml --no-default-features`(本里程碑不承诺构建变快,仅确认行为与改造前一致,并记录当前构建耗时作为后续里程碑基线)。
8. 门禁:`node apps/ai-game-creator-shell/scripts/check-config.mjs`、`npm run check:encoding`、`npm run check:doc-index`、`git diff --check`,以及改动范围内相关 vitest/Rust 测试。
## 风险与回滚点
| 风险 | 影响 | 处理 |
| --- | --- | --- |
| 校验器误判把构建卡死 | 影响所有本机构建 | 只读校验先以「不影响写入行为」的方式接入;出现误判可先关闭校验调用点回滚 |
| 准备工具与构建脚本并存产生双写 | 两处结果漂移、时间戳变化 | 并存期以「准备工具产物 == 构建脚本产物」逐文件比对作为过渡判据;不一致视为失败 |
| 缓存 key 漏掉上游变化 | 静默用旧组件 | key 含 lockfile `resolved` + `integrity` + 布局版本 + 三元;清单校验作为第二道闸 |
| 准备工具实现形态选错 | 返工 | 见「待确认决策」,评审时一次定清 |
回滚点:本里程碑不改变既有构建行为,回滚只需移除校验调用点与准备工具,不影响产物与发布流程。
## 已定决策
准备工具的实现形态(主规范未决问题 1)**已定为混合**(2026-09-27,机制见主规范 §4.8):
- 上游获取、`integrity` 校验、归档安全与原子替换复用 Node 侧既有范式(`scripts/stage-node-runtime.mjs`、`scripts/prepare-macos-codex.mjs`);
- 平台布局、组件白名单与逐文件摘要校验复用 Rust 侧既有声明(`build_support/codex_bundle.rs`、`build_support/godot_bundle.rs`),由准备工具与校验路径共用同一份声明文件承载,不再各写一份清单。(上游原生包元数据的期望值后来并入同一份声明;`build_support/codex_package_metadata.rs` 已在 M2 因失去调用方删除。)
理由:避免出现第二份组件白名单,同时不必重写 registry 下载、`integrity` 与 tar 安全校验;缺点是声明需要经过一次生成步骤才能在 Rust 侧使用,由 `check-package-layout.mjs` 门禁保证两者一致。

Some files were not shown because too many files have changed in this diff Show More