合并 origin/master 到 AGC 渲染层下沉分支并完成三处对齐

- 活动回合事实源统一到 Direct 线程管理器:删除 direct_runtime 的第二份快照,接单、进度内容变化与收口各广播一次活动回合变更事件
- 平台维护态判定移入 Rust 并在渲染层只订阅单一事件:新增 platform_maintenance 模块与各平台 facade 错误分支的分类入口
- 封面生成请求补 generationInputs.source,保持队列回填后仍能拿到平台素材 ID
- 渲染层按 master 5398a53e6 退役诊断详情入口:删除 agentRuntimeErrorDetail 与「查看详情」交互及其专属用例
- 冲突收口:nginx SPA 白名单、.gitignore、mobile 检查脚本、capabilities 描述取上游,两个已退役计划随上游删除,文档保留双方条目
- 新增并回写本里程碑取证、decision-log 与 pitfalls 的 2026-09-28 记录
This commit is contained in:
kdletters
2026-09-28 15:07:30 +08:00
403 changed files with 22103 additions and 16372 deletions
@@ -4,6 +4,8 @@
`apps/ai-game-creator-shell` 的 UI 编辑器以 `useUiEditorSession` 作为视图与 adapter 的唯一协调边界。会话持有资源加载、revision、预览请求、选择、节点可见性、步骤 gate、异步操作和保存意图;`UiDesignStateStore` 与 Tauri 调用仍由该模块注入,不进入视图组件。
实现层的深模块 seam 固定为:`features/ui-editor/stateTransition.ts`(React-free 语义 State transition)、`stateInvariants.ts`(保存前不变量 projection)、`nodeTransformGeometry.ts`(State 级节点几何)以及 `view/ui-editor/operationLifecycle.ts`(异步操作 adapter)。`useUiEditorState` 和 `useUiEditorSession` 只负责 React/history/lock 与平台 adapter 编排,不在视图中复制树遍历或几何反演。
页面与子视图不得再传递完整 controller。它们按职责读取以下小 projection:
- `input`:资源输入、节点树及调试操作。
@@ -19,6 +21,18 @@
保存与代码生成共享同一份持久化 State/revision。会话层在保存或生成进行期间互斥拦截,且代码生成必须基于已加载的持久化 revision;视图层的保存按钮和“保存并返回”按钮同步遵守该互斥状态。
Rust UI workflow 的结构化 LLM 动作共享 `commands::utils::required_tool_arguments` seam:它只负责必需 tool-call 定位与有界 JSON 解析;prompt、schema、领域校验和 materializer 继续留在 recognition/binding/merge 各自 command。
UI 编辑器的普通“保存”和“保存并生成代码”结果使用独立结果弹窗呈现,不在编辑器内容区追加状态条。普通保存成功仅提示保存成功;保存并生成成功展示生成器返回的项目相对路径(例如 `ui/generated-xxx.js`),复制按钮通过 Tauri clipboard manager 的 `writeText` 写入剪贴板。复制失败时保留可选中文本并提示手动复制;设置用户 fallback 后仍必须重新抛出原始错误,让 error report 链路收到未知 / 未处理的异常,不能把异常静默吞掉。此异常传播原则适用于所有 JS 边界,不限于生成路径或剪贴板。两类操作失败均在弹窗中展示;组合操作若保存成功但生成失败,明确提示项目已保存,重试动作仍复用原操作。保存并返回成功后直接返回,不打开结果弹窗;结果弹窗关闭后不保留路径状态。
## 2026-09-14 多树预览与树级偏移
UI 编辑器预览同时渲染 State 中全部 `ui_trees`。每棵树的 `root.offset` 包含 `min` / `max`;当前仅读取 `min` 作为树 wrapper 的左上角,`max` 由 `min + 对应界面图 pixel_size / pixels_per_unit` 派生。所有新树必须经 `createTree` 创建:首棵树位置为 `[0, 0]`,后续树按现有树实际右边界最大值加固定 padding 横向排列,并与现有树最小 top 对齐。删除或排序不重排已有树。
预览中原图与树 root 共享同一空间,root 空白区域可拖动整树,拖动结束一次性写回 root offset 并进入撤销/重做;root 不提供 resize,子节点沿用既有手势。预览选中节点不改变左侧图片面板的 `activeImageId`,Inspector 通过节点所属树反查编辑目标。只有首次进入预览和用户手动点击“适配画布”使用全部树联合边界进行 fit。
原有 render mode 已拆为三个会话级临时开关:`showFrame=true`、`showOriginImage=true`、`showComponent=false`。普通节点框线/名称受 `showFrame` 控制,选中节点强调始终保留;原图和组件显示互不耦合。
`UiDesignStateStore` 的 `generateCode(assetId)` 是必需能力,返回成功结果时不得为 nullable;所有注入的 adapter 与测试替身都必须实现该方法。
资源切换时,会话必须清理上一资源的保存/生成错误和生成中状态;普通保存开始时也清理代码生成错误。生成请求若因加载、锁定或 revision 等前置条件被拦截,必须向视图提供可见错误,而不是静默返回。
@@ -0,0 +1,146 @@
# DirectProject 命令接单化实施计划
更新时间:`2026-09-23`
状态:**四步全部落地**。
设计口径见 [`【ADR】DirectProject命令接单化-2026-09-23`](../adr/【ADR】DirectProject命令接单化-2026-09-23.md)。
本文件只排实施顺序、不变式与验收,不重复设计理由。
## 第 0 步:文档与既有缺陷清理(已落地)
- 设计定稿:ADR、`CONTEXT.md` 术语(逻辑回合 / 接单 / 拒单 / 在途回合)、两处旧文档的取代注。
- 前端删除由 invoke 拒绝驱动的认证重试(`directCodexSessionKeepalive.ts` 只留会话保活)。
- 用户可见文案不再带 `详情:` 引用、失败进错误上报池、失败说明不再写进项目历史,
只服务详情展开的 IPC `read_agent_runtime_error_detail` 已删除。
## 第 1 步:Thread Manager 拥有逻辑回合(Rust,一个原子提交)——已落地
改动点:
- 新模块 `agent/direct_turn_accept.rs`:按 thread 维护占用登记。`accept(thread, user_item_id, client_turn_id)`
在同一个临界区里完成"拒绝并发 + 登记占用 + 追加逻辑回合开始事件";`AcceptedTurn::finish(terminal)`
幂等写出 `turn.completed` 并解除占用;`Drop` 兜底补 `host-dropped` 终态。终态写出后占用才释放。
- `direct_thread_manager.rs`:登记与事件追加共用同一把锁(没有第二张静态表)。
- 删除了 `codex_app_server/mod.rs` 里镜像 Codex 原生回合的开始事件与终态追加,以及 app-server 侧
武装的 `DirectTurnFailureGuard`;终态统一交给 `AcceptedTurn::finish`。
- `direct_thread_wire.rs`:`userItemId` 的说明由"从已落盘条目读取"改成"由 `clientTurnId` 推导"。
不变式(已验证):线上仍只有一对生命周期事件;同一 thread 任意时刻至多一个占用;`turn.completed`
必带 `userItemId`。
## 第 2 步:命令改接单 + 后台跑整轮(Rust)——已落地
- 顺序固定为:`clientTurnId` 校验 → 占用调用身份 → 工作流恢复 → 用户条目校验 → 工程准备 →
`accept` → 落盘用户条目 → spawn 整轮。
- 接单前的检查从 `run_..._and_emitter` 上移到命令;分流判据改成位置(接单后一律回合失败),
`EnvironmentNotReady` 增加 `wire_kind() = "environment-not-ready"`,"调用级拒绝直通"的分支作废。
- spawn 出的任务在正常 / 失败 / 提前收场(早退:回合内任何没走到正常终态的收口点,如 `turn/start`
被拒、注入失败、panic)三条路径上都走 `AcceptedTurn::finish`;任务 panic 或被取消时由占用对象的
`Drop` 兜底。
- 落盘即接单:接单成功后落盘用户条目,再起 codex;落盘失败仍是接单后的回合失败(有回合事件解释)。
## 第 3 步:拒单返回 typed 错误(Rust + TS)——已落地
- `DirectTurnError` 加 `Serialize + TS`(含嵌套枚举)并导出到 `chat/generated/`;命令返回
`Result<(), DirectTurnError>`,文案仍由 `Display` 生成一次随载荷带出。
- 前端 catch 按变体分流(`readDirectTurnRejection` / `directTurnRejectionNotice`):认得的
前置 / 参数类 → 与用户消息同级的提示、不走 `captureAgentRuntimeError`;认不出的 → 抛出;
状态行只显示回合状态。
- 认可名单:`clientTurnIdMissing` / `clientTurnIdMalformed` / `turnAlreadyRunning` /
`projectRootUnanchored` / `projectRootUnusable` / `permissionRejected` / `inputRejected` /
`contentEmpty`;`environmentNotReady` / `hostStateUnavailable` 返回 `null`(抛出上报)。
- 拒单**不结算埋点**(埋点句柄只清不发)。
## 第 4 步:队列、埋点、快照、reducer(TS + Rust)——已落地
- 前端队列放行改听"回合完成或拒单":reducer 新增 `completedTurnCount`,作为放行与埋点结算的唯一
判据(不能用 `turnRunning` 的下降沿,一轮可能同批开始 + 结束)。**TODO(已写在代码里)**:这条
队列整体挪到 Rust 端,放行点就是 Thread Manager 的接单动作。
- 埋点结算挂到回合终态事件:句柄活过命令返回,接单成功才在终态结算,接单被拒不结算。
- 首页"运行中的项目"改由 TM 的逻辑回合导出(`list_direct_active_turns`);`DirectActiveTurnSnapshot`
移入 `direct_thread_manager.rs`,`DirectTaonierActiveInvocation` 退回纯单飞锁,不留两处事实。
- 删除取消占位的本地收口 `markTurnStopped()` 与 `turn.started` 的"重复起点保留第一次"兼容分支。
- 本地在途标签(`awaiting-start`)活到宿主认领,认领三判据:`turnUserItemId === pendingUserItemId`
(身份认领)、`currentTurnRunning`、`completedTurnCount > pendingTurnBaselineRef.current`。
## 验收证据
- Rust 定向:`cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml --bins "agent::"`
(949 passed);TM 单测覆盖并发接单被拒 / finish 幂等 / Drop 兜底 / 收口后可再次接单。
- 前端:`NODE_OPTIONS=--localstorage-file=/tmp/ls-gen.json npm test`(4473 passed)、
`npm run ai-game-creator-shell:typecheck`。
- 仓库门禁:`cargo fmt --check`、`npm run check:encoding`、`git diff --check`。
- 手工:连发两条确认第二条不被丢;重进页面忙碌态正确;真实客户端观感未复核。
## 已知坑
- `project.jsonl` 与项目主对话共用信封类型,不要为了"可见但不喂模型"新增行结构。
- 埋点 `settle` 早于成绩入库会静默丢事件(未来"进历史但不喂模型"的条目同理要落在注入侧,不在读取侧)。
- `cargo test export_bindings` 会重写全部 `chat/generated/`(引号风格漂移),跑完要 `git checkout --`
掉不是本次新增的文件。
- 本机 rust 全量 `--bins` 测试会挂在 mock server 的 `inet_csk_accept` 上,用 `--bins "agent::"` 之类过滤跑。
## review 收口第二轮(2026-09-24)
第 3 步的拒单表与第 4 步的界面口径按 review 收口后的状态为准:
- 失败载荷的 `kind` 从裸 `string` 收成 typed `DirectTurnFailureKind`(7 个变体,含先前漏登记的
`turn-interrupted`);线上形状与取值不变,TS 侧只是变成可穷尽收窄的联合类型。
- 并发拒单(`TurnAlreadyRunning`)的两个身份改成回合身份:`existingInvocationId` 是占用对象的
`turnId`、`incomingInvocationId` 是这一轮请求的 `clientTurnId`;占用对象自己的 `token` 仍是 UUID。
- 可留痕的拒单只剩 `environmentNotReady` / `hostStateUnavailable`:`projectRootUnanchored` 归到
"用户自己就能修"那一档,不再写诊断、界面按 `Display` 显示。
- 聊天里的提示分两条通道:认得的拒单给 `Display` 原文;认不出的拒单(宿主 / 环境事实)除上报 + 横幅
外也补一条同级提示,文案取宿主收口文案里的脱敏摘要与建议(不带阶段标签)。失败说明的文案映射
口径见 `docs/project-memory/shared-memory/decision-log.md` 与 `conversation/directTurnFailure.ts`。
- 第 1 步的命令返回值只剩"接单 / 拒单"两种含义:接单成立之后的一切失败(含接单后的历史落盘失败)由
占用对象收口成 `turn.completed`,命令一律返回 `Ok(())`;落盘失败**不继续起整轮**。
- 第 2 步的"谁先到谁写"加一条前提:连接死亡的**失败事实必须先于看门狗可见**
(`CodexAppServerInner::closed` 不再兼作去重标志,去重改用私有的 `connection_end_claimed`,
`closed` 在 `record_execution_turn_failure` 之后才置位);回归用例
`connection_death_records_the_failure_fact_before_the_watchdog_seals_the_turn` 把看门狗真正跑起来钉这条。
## 回合顺序修复(2026-09-24)
现场:用户在同一个项目里连发几条消息,每条都在 `turn/start` 之前失败(执行器版本未通过验收),
界面上"错误显示在用户消息上面",上一轮还显示出本轮的耗时(15.6 秒),本轮气泡自成一轮显示 0.0 秒;
后面再发一条,失败说明落进更早的分区里,用户以为"这条没报错"。
根因是**一条顺序**:本轮的开口用户条目原来在 `turn/start` 应答之后才下发,而失败说明按"当前回合"
归位(前端按条目顺序分回合),于是接单后、`turn/start` 前的失败没有用户条目可挂。
- 宿主:用户条目改成"落盘成功、起 codex 之前"下发(`emit_direct_thread_user_item`),删掉 `turn/start`
之后那一次;不变式:`接单 → 开口用户条目 → 整轮里其余一切`。
- 前端:失败说明带 `turnUserItemId`,`buildDirectChatTurns` 按身份分组(同一身份的条目永远同一轮),
本地乐观气泡按身份挂回自己的回合而不是另开一轮;收口早退只挡重复终态,不再吞掉还没写进界面的失败说明。
- 回归用例:宿主 `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`(补上同一发点);
前端 `本轮用户条目没到时,失败说明按身份挂回自己那一轮,本地气泡不再自成假回合`、
`失败说明带上它所属回合的身份,用户条目没到时投影层也能归位`、
`收口早退不吞掉还没写进界面的失败说明(订阅重建只回放生命周期锚点)`。
- 已知边界:`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`。
- 已知边界:条目下发之前(接单窗口、订阅重建窗口)聊天区里没有这一轮的任何显示,只有 composer 忙态、
状态行与「陶泥儿正在处理」卡片(卡片这一段不读秒:起点要等宿主的 `turn.started.at` 到);
`project.jsonl` 里的用户条目依旧只在首屏 / 翻页时读进前端。
@@ -1,5 +1,7 @@
# AGC 后台模型别名与对话选择
更新时间:`2026-09-24`。本次只改“目录初始值从哪来”:缺配置时不再回退写死的 `高质量 → gpt-6-astra`、`快速 → gpt-5.6-luna`,改为启动期从上游同步(这两条初始目录里的模型已从上游移除)。目录结构、后台维护字段和客户端契约都保持不变。
## 本地自定义 LLM
- 本地 `game-creator.config.json` 的 `llm.customEnabled` 默认 `false`;显式设为 `true` 后,常用设置展示 API 地址、API Key、读取模型列表与勾选区域。DirectProject 沿用 OpenAI Responses 协议,地址填写 API 根地址(例如 `https://provider.example/v1`)。开关只由配置文件控制。
@@ -33,7 +35,11 @@
## 官方路由契约
- 后台 owner 在“AGC 模型”维护列表;每项包含稳定 `id`、必填 `alias`、服务端 `modelId`、`enabled`。默认项必须启用。标识唯一,别名唯一,列表最多 32 项。
- 配置保存到私有 `agc_model_catalog` 单例表,使用 revision 乐观锁,重启及多 api-server 实例共享同一事实。缺少配置时使用初始目录,高质量对应 `gpt-6-astra`,快速对应 `gpt-5.6-luna`。
- 配置保存到私有 `agc_model_catalog` 单例表,使用 revision 乐观锁,重启及多 api-server 实例共享同一事实。
- 目录初始值来自上游同步:api-server(API/All 角色)启动时检查目录,缺失、结构与当前定义不符或校验不通过都算“未初始化”;此时调用上游 Router 控制面的分组定价列表 `GET {Router 控制面}/api/pricing?group=taonier`(控制面地址由 `{LLM Router 地址}` 去掉 `/v1` 得到;公开只读接口,不带凭据),读取 `data[].model_name` 作为“该分组可见的在售模型”,按模型名排序后生成目录:每项 `modelId` 与 `alias` 都用上游原始模型名(不再填“高质量/快速”这类人工别名),`id` 是模型名的稳定 slug(小写字母、数字、`-`、`_`,同名冲突追加 `-2`),`enabled = true`,默认项取排序后第一项,并以存量 revision 写回(`revision` 自增)。不使用管理面模型注册表 `/api/models/`——它会残留已下线、没有路由绑定的条目;也不使用 `/v1/models`——它要求 Router 用户 Key,平台没有服务级 Key。并发启动的多个实例里只有一个写入成功,其余接受既有目录。
- 同步失败(网络、非 2xx、空列表、响应超过 1 MiB、缺少目录行 revision、写回失败)只记录 error 日志,不写任何替代目录、不使用任何内置模型名;本次启动保持未初始化,下一次启动继续重试,直到目录里有数据。
- 目录未初始化时 `GET /api/llm/models`、`/api/llm/responses`、`/api/llm/chat/completions` 与后台 `GET/PUT /admin/api/agc-models` 一律失败关闭(`503`),错误文案指向“模型目录未初始化”。恢复路径是修好上游可达性后重启 api-server,或由运维清空 `agc_model_catalog` 该行后再重启。
- 上游变化不自动跟随:目录只在未初始化时重建;上游新增或移除模型由 owner 在后台增删条目或调整启用、默认项。
- `GET/PUT /admin/api/agc-models` 仅 owner 可用,返回完整配置;PUT 携带上次读取的 revision,冲突拒绝覆盖。
- `GET /api/llm/models` 返回启用项的 `id/displayName`、`defaultModelId` 和目录 `revision`,不返回实际模型名、Router 目录、凭据或能力原始数据。
- 客户端缓存最近 `revision`,在项目切换 / 对话表面挂载 / 下拉展开 / 窗口聚焦时条件刷新:`revision` 未变化不更新界面,同一时刻只保留一个在途请求,刷新失败保留上一次有效目录与本地选择。发起对话前用同一份快照校验所选模型仍启用,已停用或删除则回退默认模型并提示。
@@ -48,6 +54,9 @@
## 验收
- 空目录 + 上游可达:启动后目录自动生成(`id` 为模型名 slug、`alias`/`modelId` 为上游原名、`enabled` 全为真、默认项为排序后第一项),`revision` 自增一次,`GET /api/llm/models` 的 `displayName` 就是上游原名,界面不出现任何内置模型名。
- 空目录 + 上游不可达/空列表/非 2xx:启动只记录 error,不生成替代目录;AGC 接口与后台目录接口返回 `503`“模型目录未初始化”;下游可恢复后重启即同步成功(不需要人工造目录)。
- 同一模型集合重复同步结果一致(上游返回顺序不影响目录与默认项)。
- 目录领域校验、未知/停用模型拒绝、客户端响应不包含实际模型名。
- 后台鉴权、持久化 revision 冲突处理;客户端选择保存后重新读取,设置保存不覆盖选择。
- 目录 `revision` 条件刷新与并发触发去重、发送前回退默认模型、刷新失败可恢复。
@@ -122,6 +122,8 @@ host.rpc(method, params)
`EditorAdapter` 契约位于通用 crate `server-rs/crates/editor-adapter-api`,只定义 `detect`、`connect`、`disconnect`、`translate_rpc` 和原生 `rpc`。宿主只保存适配器 registry,并把插件声明的适配器名称路由到对应实现;具体编辑器如何查找进程、校验 PID/项目/版本、建立连接和翻译编辑器消息,由插件包自带模块实现。
宿主注册方法只在实际链接编辑器的 feature 或测试编译中存在:Cocos 对应 Windows 的 `cocos-editor-execute`,Runner 托管的 Unity/Godot 对应 Windows x64 的各自 execute feature。未启用这些 feature 的默认构建不编译专用适配器及其导入。插件操作统一通过 `host.rpc` 调用适配器的 `rpc`;没有调用方的宿主 detect/connect/disconnect/translate 包装不作为兼容接口保留,trait 方法、项目切换和禁用清理继续按现役合同执行。
宿主源码不包含编辑器专属进程名、注入逻辑或 Tauri 命令。第一个适配器 `cocos-editor` 由 `plugins/agc-cocos-editor` 提供:native 模块实现 `EditorAdapter`,由 `editor_adapters.rs` 在启动时按编译期链接注册。新增适配器不会改变 Plugin 生命周期、SDK 或权限协议。
当前 native 适配器仍由宿主在编译期链接(Cargo path 依赖);动态加载插件 native 模块不在本次范围,插件包格式与宿主协议不受此限制。
@@ -582,6 +582,8 @@ V1.11 把命令安全边界从“固定 program + argv 规则 + 隔离环境变
### V1.11.1 可信 launch 握手
Linux 一次性命令的退出确认按进程组最终状态判断:正常退出、取消与超时在同一有界清理预算内等待组内非 `Z / X` 成员消失,空组立即返回。bwrap leader 已回收而 namespace 后代尚在退出时,不凭一次瞬时扫描报错,也不向无法确认 leader 启动身份的进程组发送信号;持续存活、不可读或归属不明仍失败关闭。启动身份在放行目标前记录,terminal 协议错误也必须完成清理并停止输出 reader。回归用独立 subreaper 夹具控制后代退出时序,保留 CI 的现有并行与分片,不增加测试失败重试。
V1.11.1 必须把 `prepared -> child-created -> sandbox-ready -> commit-persisted -> exec-established -> running/exited` 做成 launcher 状态机,不能再把 bwrap 进程 spawn 或 `--json-status-fd` 的 `child-pid` 当作 sandbox-ready。实测 `child-pid` 会在 `--block-fd` 放行前出现,此时目标程序尚未执行;它只能证明 namespace child 已创建。关闭 block writer 也不能作为 abort,因为 bwrap 会把 EOF 当作可读并继续执行,失败关闭必须显式 kill + wait/reap。
- Linux 最终 `COMMAND` 必须先进入受信任 trampoline,而不是直接进入用户目标。trampoline 通过与 PTY/transcript 分离的私有控制通道发送带随机 nonce 的 `SANDBOX_READY`,等待 Runtime 完成 revision / verification gate / process record 的 durable commit 后接收 `COMMIT_EXEC`,再用 exec-error pipe 启动目标并回报 `EXEC_ESTABLISHED` 或 `TARGET_EXEC_FAILED`。commit 前的 EOF、错 nonce、协议错误和持久化失败都必须杀死并回收整个 bwrap 树,目标零执行。
@@ -1,5 +1,55 @@
# AI 游戏创作智能体 App 实施计划
## 2026-09-23 UI 编辑器退役界面图参考语义建议
本节覆盖下文“2026-08-18 UI Editor 从属页面、手势与保存失败边界”中的 `UIDesignImage.metadata.slave_to` 口径,以及“界面语义建议”相关描述。
UI 编辑器的“分析参考图”步骤、Rust 命令 `suggest_ui_design_semantic` 与 `UIDesignImage` 的 `metadata`(`name` / `description` / `role` / `slave_to`)整体退役,不保留兼容字段、回退路径或旧文档迁移:界面图只剩 `path`、`pixel_size`、`pixels_per_unit`;界面图之间不再存在持久化归属关系,结构识别按“每张界面图各自一棵树”执行;界面图在 UI 上的显示名统一取 `path` basename(`view/project-development/resourceAssetDisplayName.ts`),没有可选主页面过滤器、角色选择器和归属选择器。
多树合并当前不产生有效优先级:`merge` 的所有输入树优先级恒为 0(代码内留 `TODO`,等待重新设计),因此合并冲突时的代表节点取 `merged_from` 首位成员。旧的 `ui_design.json` 里残留的 `metadata` 字段由 serde 默认忽略,读取后不再写回;不新增拒绝或迁移逻辑。
| 要求 | 必须成立的行为 | 完成证据 |
| --- | --- | --- |
| 权威层收敛 | `resource/ui_design_image.rs` 只保留 `path` / `pixel_size` / `pixels_per_unit`;`commands/ui_design_suggestion.rs` 与 `suggest_ui_design_semantic` 注册删除;持久化校验不再有 `slave_to` 引用与环校验 | `cargo check`、`cargo test --bin genarrative-ai-game-creator-shell ui_editor`(160 passed) |
| 前端两步工作流 | `model.ts` 只保留“识别界面结构”“自动切分素材”;`ToolNavigation` 渲染两格;suggestion 操作、结果通知分支、role/slave_to 编辑器与 `ImportOverview`(原“分析参考图”步骤概览)全部删除 | `npx vitest run uiEditorPage/uiEditorState/uiDesignStateStore/previewWorkspaceZoom/appSurface` |
| 无迁移 | 旧文档中的 `metadata` 被静默忽略并在下次保存时消失,`ui_trees` 不受影响;不做迁移脚本或写入回填 | `persistence.rs` 既有加载/保存用例 |
## 当前策划入口与退役边界
策划 V1、策划会话 Runtime V2 均已删除,当前“做方案”只使用独立 Design Agent,现行合同见[策划 Agent 生产迁移与工作区浏览](./【技术方案】策划Agent生产迁移与工作区浏览-2026-09-10.md)。旧 V1/V2 Runtime、命令、会话、审批卡、身份白名单和专属测试不作为兼容或恢复目标;历史方案中的 lifecycle v3、planning binding 等要求不能作为孤立代码的保留依据。共享能力按现役调用判断,不因名称相似删除当前 Design Agent 或通用 Runtime。
前端测试遵循相同退役边界:`appSurface` harness 不保留无消费者的旧策划响应流、GDD 状态工厂及其导出,不再引用已删除的旧 PlanGdd 类型;现役 Design Agent 测试使用当前会话与工作区契约。
## 固定视觉门禁与任务身份的现行边界
图片产物按项目需求选择,不恢复按固定 Agent 身份要求视觉资源的旧完成门禁。已停用门禁的空调用、不可达检查和无消费者包装直接清理;现役 `validate_manifest_required_visual_asset`、图片检查、内部切片提交及各完成合同继续按各自调用场景执行,不因清理旧门禁而一并删除。
自主任务父身份校验只核对当前父任务及 Run Profile 绑定,不代表 Goal Contract 已持久化,也不新增等待 Goal 文件的前置条件。manifest seed 同步保留执行状态,不以素材检查结果重新推导任务状态。用户修订的持久状态判据继续供 lineage 重放与修订请求校验使用,不依赖已无消费者的查询包装或旧策划审批写入入口。现役 Design Agent 审批只更新自身会话;旧 `UserRevisionRequested` delivery 仍保留读取、claim 重放和完成门禁兼容,不为消除 warning 删除持久状态支持。
## OAuth 认证路线的契约冲突与待决边界
**状态(2026-09-23):明确暂缓,待决定 AGC 是否支持使用用户 Codex OAuth 登录态。** AuthBridge 认证桥尚未接通正式入口;既不能认定为现役已支持能力,也不能仅因生产无构造点而宣布整条链退役。
### 冲突事实与证据
- **生产入口**:`src/agent/codex_app_server/mod.rs` 的 `acquire_at_workspace` 只在正式路径构造 `PlatformSession` 或显式自定义连接的 `AppDataKey`。`find_game_creator_codex_auth_path`、`read_game_creator_codex_auth_bridge` 及旧凭据 resolver 限定为 `cfg(test)`;没有正式配置 / feature 接通 AuthBridge 构造。当前[模型别名与对话选择方案](./【技术方案】AGC后台模型别名与对话选择-2026-09-05.md)描述的是官方平台路由与显式自定义 Key。
- **仍存在的实现与承诺**:提交 `485ed50b26217d20fad2ac192c949fd3f20914da`(2026-09-21,PR #439)新增 `model_catalog.rs`、`model_catalog/auth_handoff.rs` 及相关测试,并在[本方案“SDK 串行工具的等价接入”](#sdk-串行工具的等价接入)写入 OAuth 模型目录来源校验、私有凭据轮换续传及身份隔离要求。这里“目录”指模型及能力列表,不是文件目录。
- **时间顺序**:正式凭据 fallback 被限定测试的记录见 `2e15289264`(2026-09-02);9 月 21 日提交新增下游 OAuth 处理,却仍保留该测试限定入口。因此不能简单把 OAuth 承诺当作早于现役路线的过期说明。
- **验证边界**:OAuth 目录测试使用真实 Codex 配合模拟认证和本地服务,轮换测试验证私有缓存及身份隔离;这些不能证明正式客户端已接通真实用户 OAuth 登录。每回合模型目录捕获也服务现役平台代理,不能随 OAuth 专属链整体删除。
### 暂缓期间的处理
保留认证桥相关 warning、现有实现及待决记录,不新增 `allow`,不为清零把整条业务链继续移入 `cfg(test)`,不擅自接通用户登录态读取,也不把现状记录为“已支持 OAuth”。该待决项与其它已完成的 warning 清理独立,不阻止其它改动提交。
### 下一决策点与关闭条件
| 决策 | 关闭条件 |
| --- | --- |
| 不支持用户 Codex OAuth 登录态 | 同步撤销相关文档承诺,删除 AuthBridge reader / variant、OAuth 专属目录分支、轮换链及专属测试;保留现役平台 / 自定义 Key 路由、共享模型目录和隔离运行环境,完成定向验证。 |
| 支持用户 Codex OAuth 登录态 | 先明确入口、授权与凭据隔离合同,再接通正式构造路径;验证真实登录、模型目录来源、轮换续传、身份切换与错误恢复,不能仅凭模拟测试或取消编译告警关闭。 |
当前尚未选择上述任一路线。本节是该冲突的维护位置,不依赖临时编译警告清单;决定后在此更新为最终合同,决策历史由 Git 保留。记录冲突本身不构成功能接入或退役决定。
## 2026-09-23 Direct 宿主继续请求输入修复
- 首次模型请求使用原始结构化用户输入,保留 Skill 提及及其它引用;未提供结构化输入时沿用请求正文与图片转换。
@@ -29,7 +79,7 @@
| 分层验证 | 视觉、定点玩法、项目测试和必要完整闭环按已登记标准执行;不混淆证明范围 | 双端正例与单端失败反例 |
| 统一预算 | 内置验证、托管脚本和原生命令执行受同一宿主预算约束;不以命令文本猜测“是不是试玩”,不把每条普通开发命令单独计为一次返修 | 捆绑 app-server 的执行前控制、拒绝无副作用、跨入口/重启/耗尽/超时用例 |
| 稳定基线 | 可复用的固定种子跑酷基线,真实短按/长按跳跃、单次收力、滑铲释放及公平越障窗口 | 物理单测、双端真实输入、原案例缺陷参数反例 |
| 速度可归因 | 已实现请求分段计时继续有效;新增实际并行批读与宿主首轮上下文预取 | 有界读取/并发屏障/安全边界/减少独立读取往返证据 |
| 速度可归因 | 实际并行批读与宿主首轮上下文预取按真实调用验证;界面耗时与模型使用记录各守其证明范围,旧请求分段计时链不算生产能力 | 有界读取/并发屏障/安全边界/减少独立读取往返证据 |
| 工具并行 | 现有全部工具并行、在途上限、同资源事务和付费防重继续有效 | 混合调用、图片双 POST 同时到达、同参防重回归 |
### 自动预检与可信脚手架
@@ -106,12 +156,12 @@
- 外部验证只通过客户端提供的 Node/npm 入口运行,在同一预算内保存退出码和有界输出;生产 Skill 明确禁止转到原生 shell 自建并重复执行另一套试玩来规避预算。任意原生 shell 的语义不能由字符串猜测可靠识别,本合同不声称已通过权限沙箱硬阻断所有绕行。
- 鉴权、权限、余额、项目身份、传输丢失、取消及付费结果不确定继续遵守原终止/对账边界;确定性参数错误先修参数,不原样重复付费请求。
### 分段耗时
### Direct 历史、审计与耗时的现行边界(2026-09-23 核准)
- 在既有 Direct 回合审计记录中追加计时,关联 clientTurnId、独立 attempt 和 request 身份。记录 configured/requested model、reasoning effort 与封闭的路由分类;上游返回的 model 单独标明,不能把配置值冒充实际模型。
- 区分连接准备、客户端回合锁等待、turn/start 应答、HTTP 发出到响应头、首 body chunk、首 SSE event、首内容 delta、流终态、工具与上下文压缩。不可见的上游排队/推理保持未知,缺字段不得补零。
- 并发时按区间并集计算占用,同时保留分维度统计,不能把重叠时间累加为整轮墙钟。条目记录达到上限后统计仍继续;流 EOF、错误、取消和 Drop 均正确收尾。
- 只保留时间、计数、模型安全标识和状态,不保留凭据、端点 URL、请求/响应正文或推理内容;统计失败不能覆盖本来的业务结果。旧历史不回填推测值。
- DirectProject 的完整回合条目只写入 `.agent/conversations/project.jsonl`,按 [原始历史与异常恢复](./【技术方案】DirectProject%20Codex原始历史与异常恢复-2026-09-04.md) 保存 canonical 用户条目和 Codex 完成的原始 item。工具调用与结果从同一事实源读取,前端继续使用安全投影;完整私有历史不能直接作为埋点或上传报告。
- GUI 回合不再创建 `.agent/runtime/direct-codex/turns/<clientTurnId>.jsonl` 平行审计日志,也不再由该链写入 `agent.db` 的 `direct.codex.turn` 摘要。旧 `DirectCodexTurnAudit`、`DirectTurnMetrics`、沿途审计 / 计时参数、专用批量 JSONL 追加包装和退役测试已删除。旧审计专题归入历史,不要求恢复其 writer、`offeredRead` / `firstDesign` 投影或分段计时落盘;代理的字节流透传、错误传递和独立模型记录测试继续维护。
- 会话运行中的对话和工具界面耗时仍按生命周期事件显示;模型请求 / 响应身份仍由 `.agent/model-usage.jsonl` 独立记录,Provider proxy 本体继续承担路由与响应型号观察。两者都不能证明 HTTP 首包、首 SSE、首内容 delta 或各阶段占用已形成生产分段计时记录;旧计时 fixture 通过也不能作为生产接入证据。`project.jsonl` 的 `recordedAt` 只是完成 item 的写入时间,不是 turn 起止时间;重进历史缺终态边界时不能承诺精确耗时,也不得补造请求阶段统计。
- 历史项目可能留有旧审计文件或摘要;本次契约收敛不删除、迁移或重写用户数据,也不要求新回合继续追加。现役模型使用记录、产品埋点、Runtime Agent 审计和付费 / 恢复凭证各守原有合同,不因 Direct 平行日志停用而退役。
### 工具并发
@@ -124,6 +174,8 @@
### SDK 串行工具的等价接入
> OAuth 状态(2026-09-23):下列 OAuth 目录与凭据轮换要求已有下游实现和测试,但正式凭据入口尚未接通 AuthBridge。是否支持用户 Codex OAuth 登录态仍待决,详见本方案的[契约冲突与待决边界](#oauth-认证路线的契约冲突与待决边界)。平台代理使用的共享模型目录能力继续有效。
- DirectProject 对外保留完整补丁和计划能力,由宿主 MCP 提供 `agc_apply_patch` 与 `agc_update_plan`。精确关闭 SDK 的旧计划工具注册;缺少合法回包通道的原生问答工具不再声明可用,需要用户信息时使用现有聊天。
- 通过同一可信捆绑 Codex 和身份/路由隔离的 HOME 取得完整模型目录,只置空 `apply_patch_tool_type` 以移除 SDK 全局串行补丁处理器。不得修改模型名称或其它 metadata,不为未知模型伪造显式条目;匹配和 fallback 仍由 SDK 执行。
- 代理模式的 bundled 目录和 OAuth 的实际远端/有效缓存来源分别核验,不能把模型目录导出 exit0 当作远端成功。每个 Direct 用户回合创建新模型目录快照和进程;旧执行器完全收束后才进入下一回合,不在活动执行中重启或重放。该行为是按回合冻结 metadata,不是实例内动态 overlay。
@@ -139,7 +191,7 @@
| 环境可用 | 运行时分发/完整性定向测试,真实 Node/npm 与浏览器 CDP smoke,缺失与损坏失败关闭 |
| 验证收敛 | 同轮跨入口与重启预算测试,超限停止,成功复用与源码/素材变更失效,层级不混淆 |
| 交付收尾 | 内置 Skill/提示词与工具合同一致,定向回归,无旁路无限试玩指引 |
| 可观测 | 本地 mock SSE 分片/错误/Drop/跨轮测试、区间并集测试、模型身份与敏感数据边界 |
| 可观测 | 现役历史写入与安全投影、界面生命周期耗时、模型使用记录及敏感数据边界;旧审计 / 计时测试不替代生产调用证据 |
| 工具并发 | 不同工具可在首个响应前开始且乱序按 ID 回包;两个不同图片同时到达 mock 平台,同参只提交一次,容量和 manifest 合并测试 |
| 整体 | 范围匹配 Rust/脚本测试、类型检查、Skill 包校验、文档索引、编码与 diff 检查;真实 Provider/安装包未运行时单独列明 |
@@ -356,7 +408,7 @@ DirectProject 的生图、素材处理、构建和试玩可能跨越短生命周
DirectProject 工作区只恢复自身对话,不按专业 Agent 默认任务占位行批量读取旧会话或生成专业 Agent 文本回执。专业 Agent 结果加载 effect 必须以当前 Runtime 模式为边界,并在模式切换时清空旧结果。仍供开发入口使用的 `read_local_conversation` 在 blocking worker 内完整执行权限校验、会话目录解析和历史读取,避免文件访问或锁等待阻塞 Tauri 窗口线程。
项目打开链路的目录检查、manifest 读取、项目 revision 读取和 Planning V2 hydrate 也必须通过 blocking worker 执行;它们可能碰到项目写锁,不能在 Tauri 窗口线程同步等待。
项目打开链路的目录检查、manifest 读取和项目 revision 读取也必须通过 blocking worker 执行;它们可能碰到项目写锁,不能在 Tauri 窗口线程同步等待。Planning V2 hydrate 已随旧策划 Runtime 删除,不再属于现役打开链路。
DirectProject 自身的 `read_direct_project_conversation` 也必须在 blocking worker 中执行权限校验、JSONL 历史解析和消息投影,不能因为它只读取一份项目历史就保留同步 Tauri command。
@@ -1173,7 +1225,7 @@ game-project/
- 聊天输入 `/export` 会生成待确认的 `project.export_package` 内置命令,确认后只把 `game/**`、`assets/**` 和 `exports/README.md` 打包到 `exports/playtest-package-*.zip`;缺少 `exports/README.md` 时先按项目 manifest 生成最小试玩说明,已有文件原样保留。发布前若 `code-prototype` 未完成且没有运行中的预览,直接阻止发布,不触发用户项目构建;可运行原型完成后才允许按 `build` 脚本补齐产物。发布进度使用独立模态弹窗展示,遮罩覆盖整个工作区并阻止交互,不再使用聊天确认卡;导出前重新校验可玩入口,拒绝符号链接和越界路径,不把 `.agent/`、`memory/`、日志、trace、运行时配置或密钥文件写入 ZIP。
- 聊天输入 `/exports` 会只读执行 `project.export_list`,列出当前项目 `exports/playtest-package-*.zip` 历史试玩包,并提供显示目录或继续 `/export` 的草稿;该命令不删除文件、不分享文件、不新增面板。
- 聊天输入 `/preview` 会生成待确认的 `preview.start` 内置命令,确认后启动只读本地 HTTP 预览并切换到客户端内运行视图;`/open-preview` 在本地项目已初始化后生成待确认的 `preview.open`,只激活当前授权项目对应的 `127.0.0.1` 运行容器;`/preview-status` 只查询当前授权项目的本地 HTTP 预览并写入 `preview.status` 命令日志;`/preview-stop` 只停止当前项目预览,不展示或停止其它项目遗留的全局预览。
- 聊天输入 `/memory [short|long|blackboard]` 读取短期、长期或黑板记忆;`/remember [short|long|blackboard] 内容` 生成待确认的 `memory.write` 并追加短期、长期或黑板记忆,未写 scope 时默认追加长期记忆;主窗口“记到黑板”“覆盖黑板”“清空黑板”只填入 `/remember blackboard `、`/memory-set blackboard ` 或 `/forget-memory blackboard` 草稿,仍由用户补内容并走聊天确认;`/memory-set [short|long|blackboard] 内容` 生成待确认的 `memory.write` 并覆盖保存对应记忆;`/forget-memory [short|long|blackboard]` 生成待确认的 `memory.delete`。
- 短期、长期和黑板记忆由现役 Runtime 工具与原生读写入口维护;记忆斜杠命令已按 2026-09-22 退役 ADR 清理,未接入正式调用的本地记忆删除 helper 及专属测试一并移除,不删除用户现存记忆文件。
- 聊天输入 `/canvas 画板项目ID` 会生成待确认的 `canvas.project_open`,只打开本机 Genarrative 编辑器里的指定画板项目,不开放任意 URL;确认后聊天先反馈正在打开,再回写真实打开 URL。画板项目 ID 为空或包含控制字符时在聊天侧直接拒绝。
- 聊天输入 `/sync-canvas-project 画板项目ID` 会生成待确认的 `canvas.project_sync`,通过 External Editor API 把该画板项目资源下载到 `assets/canvas-sync/` 并登记为画板来源资产;画板项目 ID 为空或包含控制字符时在聊天侧直接拒绝。
- 聊天输入 `/generate-art 提示词` 会生成待确认的 `canvas.asset_generate`,通过 External Editor API 生成首版美术素材并写入 `assets/canvas-generated/`;提示词为空时在聊天侧直接拒绝。
@@ -1192,7 +1244,7 @@ game-project/
- `check:native-shells` 会运行 `ai-game-creator-shell:check` 和 `ai-game-creator-shell:build -- --no-bundle`,并静态检查 release 与 debug 启动都只登记 `client / index.html` 这一个默认窗口、禁止 Tauri setup 自动打开 developer 窗口、开发面板必须挂在 `devMode` 分支内,正式用户 App 的运行容器只接受 `http://127.0.0.1:*`,release / dev CSP 都只为该 loopback origin 开放 `frame-src`,Tauri 预览激活命令不得调用 opener,用户主流程不得调用旧工作区窗口切换 command。
- 共享契约提供 `GAME_CREATION_AGENT_CAPABILITIES` 和内置命令权限枚举;开发模式会展示能力列表。
- 共享契约提供 manifest task schema 和 ready-task 选择器,用于记录任务拆分、专业组、角色模板、依赖、产物、验收条件和当前可执行任务。
- 开发模式可读取、保存、删除短期记忆和长期记忆文件;正式用户界面不提供记忆管理入口,短期 / 长期 / 黑板记忆只由 `project-supervisor` 运行期的记忆工具在授权项目内读写。
- 原生入口保留短期、长期和黑板记忆的读取与保存;正式用户界面不提供记忆管理入口,运行期的记忆工具只在授权项目内读写。项目命令权限 id 的去留与具体 helper 分开判断。
- 共享契约提供 `GAME_CREATION_APP_LIMITED_RUN_COMMANDS`;当前真实命令为 `game.static_smoke`,用于检查 `game/index.html` 的可玩原型门槛并写入 `.agent/logs/command.log`。
- 后台 Agent 的项目 revision 以 `.agent/runtime/project-revision.json` 为唯一事实源,per-run 验证门禁以 `.agent/runtime/verification/<agentId>/<runId>.json` 为事实源。每次 `file.write`、`file.patch`、`file.delete` 或 `project.restore` 都必须在实际修改前保守推进 revision,并永久记住当前 run 的 `requiresVerification=true`;失败或崩溃不回退。只有成功且绑定当前 revision 的 `project.verify` 或 `command.run_limited / game.static_smoke` 才能放行空 actions;未修改项目的只读任务不强制验证,但最终回复仍必须绑定请求开始时的 `responseRevision`。per-run context bundle 使用 v2,pending action 使用 v3 并绑定创建时的全局 revision;旧版恢复失败关闭。最终 assistant 和 completed 必须在项目写锁内重读 revision / gate 后依次落盘,文件回读、observation 或锁外旧快照都不能替代验证凭证。验收必须分别模拟待执行动作、修改 run 与只读 run 的跨 Agent revision 漂移,证明旧动作不执行、旧回复不落盘、不产生 completed 或 failed、per-Agent 锁不提前释放、原 run/session 在收到 blocker 后保持可恢复;stale continuation 经重启仍从原 `nextLoopIndex` 续跑,revision 数值或成功验证输出中的动态时间戳不能绕过 context stall。
- `.agent/manifest.json` 会记录当前 `preview` 状态和 `commandRuns` 受限命令运行结果,作为本地产物索引的最小真相源。
@@ -1392,10 +1444,11 @@ game-project/
- 普通项目对话由一个 project-bound Codex app-server thread 执行。客户端系统提示词包含最小工程合同、项目 prompts 和审核 Skill 索引;源码与 Skill 正文按任务需要读取。提示词、工具描述与 Skill 直接描述当前任务、输入和成功条件,细节按调用需要提供。
- 首页提供“做游戏 / 做方案”两个创作类型(`game` / `doc`),默认“做游戏”;“做素材”入口已退役,素材生成在项目内按实际工作流触发。每次首页提交自动创建一个新项目并进入项目工作台。用户正文原样进入项目对话,`game|doc` 作为受限结构化首轮上下文传给同一 Codex thread;持久草稿里遗留的 `art` 按 `game` 处理(`effectiveCreationType` 映射),不再产生第三种首轮上下文。
- 「策划补全」是“做游戏”专属的提交前勾选(2026-09-15 起):勾选时该档提交 `planning`,未勾选时提交 `direct-build`;“做方案”始终走立项策划链路,与本勾选无关。复选框只在“做游戏”档渲染;切换创作类型时首页表面整体重挂,勾选状态随之清除,切回来必须是未勾选——这条可观察契约由 `appSurface` 的「scopes the 策划补全 option to the game entry」与「submits the game entry with planning when 策划补全 is checked」两条用例钉住,实现侧不额外维护重置逻辑。
- 2026-09-23 清理了未注册的 DirectHome 用户对话命令及旧附件 sidecar 渲染链;首页仍先创建项目再进入 DirectProject,不恢复无项目对话。自动项目命名和提示润色仍调用内部 `direct_game_creator_home_codex_chat`,其 DirectHome 只读隔离通道与测试继续保留。附件作为 canonical `userItem.content` 中的 `agc_attachment_reference` 携带名称、媒体类型、大小、项目相对路径及状态,经现役 validation/wire 校验与投影;路径映射不等于灌入全文,也不按 GDD 特判。附件清洗与数量上限继续复用 `direct_codex_attachments.rs`,旧 sidecar DTO、header、专属 prompt key 和测试不再是保留合同。
- `agc-skill-pack.v1` 包含完整游戏交付流程、项目结构、陶泥儿美术、Web 游戏实现、真实浏览器试玩、客户端资源投影,以及 Unity/Godot 编辑器常用操作八项审核 Skill。清单记录用途、触发条件、所需工具、版本和内容 SHA-256;审核文本按 UTF-8 读取并将 CRLF 规范为 LF 后计算指纹和安装,避免混合换行造成 Windows / Linux 构建结果漂移,语义内容变化时必须同步重算对应清单指纹并提升版本。同步统一运行 `npm run agc:skill-pack:sync`,只读校验由 AGC `typecheck` 和 release build 自动执行,发现漂移时直接列出 Skill 与实际摘要,不让失配内容进入构建产物。客户端把审核文件安装到隔离目录后通过 app-server `skills/extraRoots/set + skills/list` 注册并复核,完整正文由 Codex 原生 Skill 机制按意图加载,一层引用只能经 `agc_read_skill_resource` 读取清单内 Markdown。引用路径按平台无关规则拒绝反斜杠、盘符、UNC、绝对路径和 `..`,不能依赖当前宿主的 `std::path` 语义判断其它平台路径。
- DirectProject 连接客户端内置的 `agc_tools` STDIO MCP,并在启动时接入客户端扩展仓库中用户已启用的独立第三方 STDIO/HTTP MCP 配置。内置工具包括审核引用读取、图片生成、标准陶泥儿美术准备、已登记资源有界查询、视频 / 角色动画 / 音效 / BGM 的 create-or-derive 语义生成、已登记图片去背景、desktop/mobile 浏览器试玩和受控 `agc_web_search`。内置 MCP 进程负责协议;真实浏览器、付费平台调用与受控搜索通过随机 loopback 地址回到客户端主进程,GUI 登录态、开发者 Key、项目路径、revision、operation 与幂等键由客户端持有并隔离于模型上下文。内置与用户启用的第三方 MCP 工具沿用 DirectProject 自动批准方式;付费资源工具由客户端绑定稳定回合身份、串行执行并优先恢复匹配账本。`llm.webSearchEnabled` 控制 DirectProject 的 AGC 受控搜索工具暴露与执行。原生工具与审批权限以下方“DirectProject Codex 完整访问覆盖”为准。
- 陶泥儿生成复用持久幂等账本、operation 恢复、来源/下载/PNG 解码和 manifest 登记;普通客户端使用当前 AGC 登录会话及账号路由,受控的 ExternalDeveloper 发布模式在客户端内部使用按服务器 origin 隔离的私有 Key。凭据失效、来源不明或结果未知时失败关闭,不能自动换 Key 或重新扣费。
- 自定义 LLM API Key 路由在 DirectHome/DirectProject 经 loopback `/responses` 流式代理转发。代理使用请求自带的 Bearer,并剥离开发网关错误携带的 `X-Codex-*` ChatGPT 账户额度头,按实际 API Provider 响应判断请求结果。
- 自定义 LLM API Key 路由在 DirectProject 及内部 DirectHome 辅助调用中经 loopback `/responses` 流式代理转发。代理使用请求自带的 Bearer,并剥离开发网关错误携带的 `X-Codex-*` ChatGPT 账户额度头,按实际 API Provider 响应判断请求结果。
- 2026-08-12 计划拒绝恢复:结构化 `runtime.plan_update` 被 Runtime 拒绝后,下一轮 Provider 请求按请求级目录收窄到实际项目 mutation 与 `respond_to_user`(已进入协作编排的 Supervisor 保留 `agent.delegate / agent.run_status`),并明确禁止再次规划、读取、搜索或验证;后续已有真实 mutation observation 后解除临时目录,不改变持久 executable policy。
@@ -1441,13 +1494,16 @@ game-project/
## 2026-08-23 Direct Codex 美术包显式重生成与切片投影
- 标准美术包在客户端将规范图、背景图和主图集统一保存为 PNG:下载仍校验来源、声明类型与文件签名,随后按真实内容接受 PNG/JPEG/WebP,在已有 20 MiB、4096 像素单边和 64 MiB 解码内存限制内完整解码。有效 PNG 原样保留,JPEG/WebP 编码成 PNG,最终内容也不得超过 20 MiB;本地媒体类型固定为 `image/png`。转码不补造透明度,主图集与独立切片继续执行真实 alpha、可见像素、尺寸和唯一性合同;平台独立切片仍须为 PNG。此行为仅属于美术包,普通图片工具的指定扩展名合同不变。
- 美术包的转码在项目提交锁和本地写入之前完成,文件摘要、已安装结果识别、替换恢复和 manifest 登记均使用最终 PNG。转码失败保留原生成账本与平台身份,同冻结意图重试重新读取已有结果,不提交新的付费生成;不改变请求快照、幂等身份、固定资源路径或旧 PNG 包的复用方式,无数据迁移。验收覆盖 JPEG/WebP 转码、PNG 字节不变、损坏/超限拒绝、真实透明度以及已有结果重复恢复零生成 POST;客户端真实 Provider 的整包验证单独记录。
- 转码自动化验收由 `canvas_generation_tests` 的格式/边界用例和 `retained_runtime_generation_retries_a_completed_stage_without_posting_again` 本地 HTTP 夹具覆盖:PNG/JPEG/WebP 均先下载损坏内容,再从同一已完成账本恢复两次,核对最终 PNG、稳定本地 asset ID、Canvas 来源身份、账本保留/清理及零生成 POST;替换与补偿继续由现有图集事务和 Direct 重生成用例覆盖。真实 Provider 的客户端整包效果不由这些夹具替代。
- `agc_tools.taonier_prepare_game_art` 的请求模式固定为 `reuse-or-create | regenerate`。缺省使用 `reuse-or-create`,完整且可信的本地包继续零付费复用;只有用户显式要求重做、替换或切换视觉风格时使用 `regenerate`,并绕过完整包短路,按规范图、背景图、透明图集顺序生成和原位替换。`regenerate` 的旧包前置门只要求规范图和背景图已经可下载、可解码、来源一致且存在可信 manifest 登记,使两项旧字节与登记可以完整 rollback;历史主图集、私有回执、公开清单或 canonical 切片可以缺失。客户端必须把八个严格路径的实际存在性和摘要,以及其中受管顶层 asset identity,逐项冻结为 `Present/Some` 或 `Missing/None`,不能把缺失状态伪造成空文件或虚假登记。规范图或背景图任一缺失或身份无效时才失败关闭并提示先用 `reuse-or-create` 修复基础素材。
- 显式重生成不放宽 External Editor 幂等与未知态边界。固定阶段已有 `prepared / accepted` 账本时,本次生成 prompt 必须与账本冻结 prompt 一致才可恢复;不一致返回 `platform-generation-result-unknown` 并保留原 `Idempotency-Key / operationId` 对账,禁止把旧结果解释为新意图,也禁止另起付费 POST。
- 整包重生成在首个付费阶段前建立客户端私有 v4 workflow,状态固定为 `resetting / in-progress / compensating / completed`,并同时绑定意图摘要和客户端稳定 `clientTurnId`。专用 `direct-codex-art` 跨进程执行锁覆盖整个付费重生成生命周期,但不持有通用项目写锁等待网络。规范图和背景图替换后立即持久化旧字节、旧 manifest entry 与本轮双 CAS 锚点;任一后续阶段失败时进入 `compensating`,可在进程重启后继续恢复旧文件及旧登记。已成功阶段的生成账本继续保留;`completed` 持久化经脱敏和数量 / 长度限制的完整工具结果,同一 `clientTurnId` 回包丢失时必须等值重放且零新 POST。新的显式用户回合先持久化目标回合所有的 `resetting` workflow,再清理上一轮三阶段账本并转回 `in-progress`,任一崩溃点都不得出现无 workflow 窗口。只有尚无任何阶段账本且无替换锚点的孤立 `in-progress` 空壳允许被新回合原子接管;其余身份冲突、未知版本以及缺少新恢复字段的旧 v2/v3 workflow 均失败关闭,不能用 serde 缺省值把旧状态升级成可执行状态。
- 恢复扫描必须把 `resetting`、`compensating` 和仍带替换锚点的 `in-progress` 识别为可恢复状态,并在 Direct app-server 启动前持有同一专用执行锁完成阶段清理、补偿和中性化。补偿只恢复旧文件并清除本地 replacement CAS 锚点;已 `prepared / accepted` 的阶段账本、原 `Idempotency-Key` 与 `operationId` 必须保留,同冻结意图续跑复用原请求身份,未知账本在文件 mutation 前失败关闭。冻结意图一致但进程 invocation 已变化时允许安全接管本轮;`completed` 则以外层原始 `clientTurnId` 为权威,忽略模型重采样 brief 并等值回放。客户端必须在启动 Direct Codex 前幂等落盘原始 User 消息与稳定回合 ID;最终 assistant 回复必须在 Tauri 成功返回和 `completed` 事件前,以同一稳定回合 ID 幂等写入项目主对话,重启后项目对话只续跑真正未回答的原始回合,不能生成新身份或重复应用已完成代码修改。
- workflow 在调用严格图集事务前必须先持久化 `strictSpritesheetPending`,并冻结严格事务覆盖的九项旧合同身份:`.agent/manifest.json` 中受管 asset identity、客户端私有回执、公开 `assets/manifest.art.json`、主图集、四张 canonical 切片和公开切片清单;旧路径允许按真实状态冻结为缺失。异步 Provider 返回终态后,客户端必须先把脱敏且可恢复的完成结果绑定到原 retained stage ledger,再允许本地严格事务提交。恢复在同一项目写锁内完成底层严格事务对账与 workflow CAS;若九项新合同与当前规范图身份完整一致、规范图/背景图替换锚点属于本轮,且私有回执的 resource/asset/task identity 与本轮 retained spritesheet 完成结果一致,才保留整组新结果并补写 `completed`。若九项仍逐项精确等于冻结的旧合同,严格合同判定、写入 `compensating`、恢复规范图/背景图与登记、回读验证和清除锚点必须全部位于同一项目锁内;`compensating` 重启也必须重新验证旧合同。任一文件存在性、摘要、顶层 asset identity、retained result 或 CAS 处于第三种状态时进入本地 reconciliation,保留 workflow、阶段账本和文件现场,禁止制造新旧混合包或重新付费。恢复若只能证明完整新合同而无法重建中断前尚未持久化的阶段告警,完成结果必须追加明确恢复告警,不能用空 warning 集合伪装为原阶段没有告警。
- 工具完成结果同时返回主包 `assetPaths`、实际成功持久化的 `slicePaths`、安全身份投影 `resources`,并把普通 `warnings` 与 `sliceWarnings` 分开。每张本地切片都以真实 Canvas `resourceId / assetObjectId / taskId` 和源图集 `sourceResourceId` 登记为顶层 manifest asset;同路径替换保留本地 asset ID。严格图集事务继续覆盖主图、四张 canonical 切片、公开切片清单、私有回执和 `.agent/manifest.json`,失败时整组恢复。旧项目缺顶层切片登记时只能由客户端私有回执授权补登记;可编辑的公开切片清单不能单独成为 `.agent` Canvas 身份来源。
- `regenerate` 授权只取当前请求中最新一条原始 `role=User` 消息,并绑定外层稳定 `clientTurnId`;引号或代码中的按钮文案/示例、历史消息、模型自行填写的 `mode`、MCP 自动批准和缺失 clientTurnId 均不能形成付费替换授权。授权判定先对完整原文做 Unicode NFKC 与常见撇号规范化,随后整串必须完整匹配审核过的独立立即执行指令,只允许句号/感叹号收尾;不得剥离引号、方括号或代码片段,动作前后也不得携带 brief、条件、否定、选择、确认、费用、延迟或任意其它文本。复杂风格需求必须先在非付费消息中描述,再由下一条独立“请重新生成美术”确认消息签发授权;不能靠开放式 deny 词表猜测当前付费同意。工具桥只保留授权判定和摘要,不保存或回传用户原文。同一进程重复水合相同 `clientTurnId` 时,“回合仍在运行”只属于瞬时占用状态,前端不得以稳定 assistant messageId 将其写成终态;原执行的成功回复仍由 Tauri 在返回前持久化。DirectProject app-server 的 cwd、sandbox writable root 和文件变更批准根统一为用户选择的整个项目根;canonical 项目根的原生 OS 路径字节与权威 manifest `projectId` 通过域标签和各自长度前缀编码后共同进入 Direct 连接池和 thread 身份,稳定符号链接改指其它项目、同路径重建项目、不同非 UTF-8 路径或内嵌 NUL 的项目 ID 都不能复用旧连接。`assets/`、`game/` 与其它项目文件可写,`.agent/`、`.git/`、密钥文件和 Runtime 控制面由项目文件层拒绝,网络关闭,命令执行、MCP 扩权和额外权限申请一律拒绝。受控 `agc_tools` 子进程从同一项目根 cwd 经相同权限校验反查 canonical 项目根供客户端内部桥使用,不能把该根加入其它 Codex writable roots。`resources` 只返回本地 asset/path/kind/media type、Canvas project/resource/asset/task ID 与 reference resource IDs,不返回 prompt、model、provider route、绝对路径、URL、Token、Cookie 或 API Key。客户端付费资源生成(图片、视频、角色动画、音效、背景音乐)统一调用站内 `/api/editor/...` 路由并复用平台登录态,不走 External v1;External v1 只保留给外部开发者模式和历史账本重放兼容。
- `regenerate` 的模式选择遵循 2026-09-03 MCP 能力边界:Codex 根据当前用户请求,经审核后的工具显式选择 `mode=regenerate`;客户端不再通过自然语言关键词、Unicode 归一化、否定词表或独立确认句式判断高层业务意图。工具桥继续校验项目权限,将操作绑定活动客户端回合与稳定 `clientTurnId`、冻结首次 `brief` 摘要,串行处理同一重生成动作,并在同回合等值重试时返回已完成结果;缺少活动回合或摘要冲突仍拒绝。账号、计费、幂等账本、锁、付费结果未知与恢复合同继续有效。2026-09-23 已删除无调用的旧文本判断函数,不恢复该旧语义门禁。同一进程重复水合相同 `clientTurnId` 时,“回合仍在运行”只属于瞬时占用状态,前端不得以稳定 assistant messageId 将其写成终态;原执行的成功回复仍由 Tauri 在返回前持久化。DirectProject 的 cwd 和 AGC 项目身份根使用用户选择的 canonical 项目根;其原生 OS 路径字节与权威 manifest `projectId` 通过域标签和独立长度前缀编码后绑定连接池及 thread 身份,项目被替换时不能复用旧连接。进程 sandbox 与文件、命令、权限请求的批准规则按本文件后续“DirectProject Codex 完整访问覆盖”;客户端 MCP 仍保持项目绑定和业务权限校验。`resources` 只返回本地 asset/path/kind/media type、Canvas project/resource/asset/task ID 与 reference resource IDs,不返回 prompt、model、provider route、绝对路径、URL、Token、Cookie 或 API Key。客户端付费资源生成(图片、视频、角色动画、音效、背景音乐)统一调用站内 `/api/editor/...` 路由并复用平台登录态,不走 External v1;External v1 只保留给外部开发者模式和历史账本重放兼容。
- 成功响应中的 `warnings / sliceWarnings` 与错误响应采用同一脱敏边界:逐条移除宿主绝对路径、凭据与 URL,并设置固定长度上限;非阻断告警不成为绕开错误分支隐私保护的旁路。
- Direct 同进程重复水合若收到“同一 stable turn 仍在运行”,必须释放当前 App 实例的恢复 claim;该结果不落 assistant 终态,后续显式刷新对话可按原 `clientTurnId` 再次读取已落盘回复或续跑,不要求重载整个 WebView,也不启动无界自动轮询。
- 对话恢复从新到旧扫描全部合法 Direct User 回合;较新的 User 已有稳定 assistant 时必须继续寻找更早未回答回合,不能提前结束扫描。普通成功回复或普通错误回复若终态 assistant 持久化失败,同样必须释放当前 App 实例的恢复 claim,使后续显式重新加载对话时能以原稳定 `clientTurnId` 重试;claim 只表示当前实例内正在恢复,不能成为磁盘终态的替代品。
@@ -1529,7 +1585,7 @@ DirectProject 使用 `approvalPolicy=never`,避免每次原生调用再经过
- `agc_write_file` 是用户直接触发、失败即整轮无法落盘的项目写入通道,原先却用零等待 `acquire_project_write_lock`:任何重叠都在 24-42ms 内被判成“项目正在被其他写操作占用”,而 `file.write / file.patch / file.delete` 等入口用的是约 10 秒有界等待。现统一为 `acquire_game_creator_agent_runtime_project_write_lock_with_wait`:短暂重叠排队等成功,只有预算耗尽才报出带持锁方身份的错误;同一轮并行写多个文件按同一把锁串行。这是 2026-07-22 同一形状修复在 Direct 通道上的补齐,与 2026-08-13 一节“这些结果统一投影为争用并进入既有有界等待”的口径一致。**失败耗时是判据**:几十毫秒说明该入口没等,不是锁没释放。
- 这条等待是**同步轮询**(2_000 × 5ms,最多约 10 秒),而 `handle_direct_tool_bridge` 是 async handler:直接在 handler 里跑完整条写路径会占住一个 tokio worker,争用窗口内同一轮并行写多个文件时会有多个 worker 被占,而这条 bridge 与只读端点、UI 命令共享同一个 runtime——Issue #318 现场“只读工具全部正常”这条诊断特征会在争用窗口内失效。因此写路径经 `bridge_write_file_in_blocking_pool` 走 `tokio::task::spawn_blocking`(仓库既有模式,如 `codex_app_server.rs` 的 DirectProject 历史落盘),等待语义与错误文案不变;定向用例用默认 `current_thread` runtime 加心跳任务锁住“等待期间 runtime 仍在推进”。
- 争用错误必须带持锁方身份才可行动:`项目正在被其他写操作占用:<锁路径>(持锁方 commandId=<命令> pid=<进程> createdAt=<创建时间> ownerIsSelf=<是否本进程>)`。锁文件处于 delete-pending 或尚未写完时读不到身份,也必须显式表达成“不可读”,不得默认成“没有持锁方”。前缀逐字不变:`project_gates.rs`、`provider_recovery.rs`、`planning_session_v2.rs`、`direct_runtime.rs` 和前端 `App.tsx` 都按它把争用识别成可等待的瞬时状态;这句话已是 `crate::project::PROJECT_WRITE_LOCK_CONTENTION_PREFIX` 单一真源,四个站点不再各自手写中文。
- 争用错误必须带持锁方身份才可行动:`项目正在被其他写操作占用:<锁路径>(持锁方 commandId=<命令> pid=<进程> createdAt=<创建时间> ownerIsSelf=<是否本进程>)`。锁文件处于 delete-pending 或尚未写完时读不到身份,也必须显式表达成“不可读”,不得默认成“没有持锁方”。现役调用方通过 `crate::project::PROJECT_WRITE_LOCK_CONTENTION_PREFIX` 单一真源识别争用,不各自手写中文;已删除的 `planning_session_v2.rs` 不再列为现役调用点。
- `create_new` 的失败必须分三类处置,不能再共用一句文案:可重试(目标已存在、Windows `sharing violation(32)` / `lock violation(33)` / `ACCESS_DENIED(5)`)进入有界等待;明确判定不是争用的权限 / ACL 拒绝(Unix `EACCES`)失败关闭且文案不含争用前缀;其它 I/O 错误原样上报。**重试性只能由错误码决定,不能用 `path.exists()` 这类一次 metadata 观察决定**:目标被删除时目录项先消失、删除挂起随后才结束,`create_new` 会在这个拆链窗口里返回 `ACCESS_DENIED(5)`,而 `exists()` 往往已经报 false(本机实测 6 万次建锁 / 删锁竞争里 396-538 例命中该组合)。按“目标不存在”当场判成权限拒绝,等待层就会立刻失败关闭——正是本次要消灭的“毫秒级直接失败”,只是换成更误导的 ACL 文案。平台判据以 `project_write_lock_open_failure_for(platform, error)` 保留、平台由参数传入而不是 `#[cfg]`:CI 只有 Linux runner,Windows 分支必须在 Linux 上也能断言。
- Windows 上真实 ACL 拒绝与删除拆链窗口在错误码上不可区分,所以终态改判放到**等待预算耗尽之后**:`ProjectWriteLockFailure::exhausted_projection(waited)` 只在“真的等过预算 + 目标此刻仍不存在 + 错误码是 `ACCESS_DENIED(5)`”三个条件同时成立时才投影成权限拒绝;单次试探(`max_attempts == 1`,例如 hydrate 的 `try_acquire_...`)没有等待证据,保持争用语义。代价是 Windows 上真实 ACL 拒绝会先等满等待窗口(约 10 秒)才报权限错误;Unix 的 `EACCES` 立即判定、不等待。
- 重试与否改由**类型**决定,不再解析错误文案:`acquire_project_write_lock_failure` 返回 `ProjectWriteLockFailure::{Retryable, Terminal}`,有界等待按 `is_retryable()` 分流,`acquire_project_write_lock` 只是它的文案包装。零等待入口前缀不变,只在“错误码不可区分且目标此刻不存在”时补一句“可能是删除挂起、删除拆链窗口或权限 / ACL 拒绝”,把两种处置都交给调用方,而不是替它猜一个。
@@ -1719,3 +1775,10 @@ Direct 回合的所有权属于进程内项目身份锁,不属于当前页面
`AgentRuntimeErrorEvent` 把失败投影到用户消息、运行面板和项目内 `.agent/runtime/errors/<eventId>.json` 时,同一份已脱敏诊断还要投影成 AppData `diagnostics/application.log` 的两行:`agent.runtime.error`(身份行:`eventId / source / stage / code / retryable / clientTurnId / elapsedMs / detailRef`)与 `agent.runtime.error.detail`(详情行:`hint / summary / detail / metadata`)。原因是项目内 sidecar 只在项目目录可见,而“报告问题”只上传应用级日志:没有这两行时,用户提交的失败消息里只剩一个 `详情:.agent/runtime/errors/...json` 路径,团队拿不到诊断正文。
口径:两行都由 `agent/runtime_error.rs` 从同一份 diagnosis 生成,字段不退化成第二份来源;`summary` 按 320 字符、`detail` 与 `metadata` 按(1200 / 200 字符)预算先脱敏再截断,落盘前还会被 `sanitize_diagnostic_message` 二次脱敏并按行截断,因此自由文本字段在行内先压平换行。拆两行是因为整行一旦出现凭据标记会被整体替换成脱敏占位:所以**自由文本(summary / hint / detail)只放详情行**,身份行只留程序生成与调用方常量字段,详情行被整体脱敏时事件仍能按 eventId / detailRef 定位。写日志先于写 sidecar:sidecar 失败不能连日志一起丢。
## 2026-09-23 AGC UI 设计文档 Agent 工具化重写
- `ui.workflow.run` 单工具(`discover → prepare → recognize → merge → binding → status → finalize`)整体退役:它的参数就是工作流状态,任一步失败只能整轮重来,步骤产物又由前端会话落盘,Agent 侧没有任何恢复点。同时退役 `merge.rs`、`binding.rs`、`workflow.rs`、`ensure_ui_design_resource_for_prototype`(原型 → 文档的幂等查找)、`ui/ui-workflow-<sha256前24>.json` 命名与 `ui-workflow.*` manifest 阶段,均不保留兼容、迁移或 fallback。
- 当前只保留三个工具:`ui-design-doc.from-images`(一至四张设计图 → 新建并登记文档,返回 `assetId` 与 `relativePath`,无状态)、`ui-design-doc.run-workflow`(`recognize → separate → write-back`,唯一带崩溃恢复的工具)、`ui-design-doc.into-js`(渲染 `ui/generated-<stem>-<digest>.js`,无状态、不推进 revision)。项目根目录、项目 ID 与 provider 身份由 Runtime 注入,模型只给设计图引用或文档 `assetId`。
- `run-workflow` 的恢复判据只有一条:这一步有没有对应、且带着 State 快照的检查点行。检查点是文档旁追加式 JSONL(`ui/.<文档名>-workflow.jsonl`),行类型为 `run` / `recognize` / `separate` / `write-back` / `outdated`,`run` 行带本轮起始 State,`recognize` / `separate` 行带该步应用完之后的 State。恢复只读快照、整步跳过已完成步骤,只把新完成那一步的改动应用到文档,绝不照 DTO 重放(重放会重复登记切图、重复累加回填出错说明);只有 DTO 的旧行按未完成重跑。一轮以 `run` 开头、以首个 `write-back` 或 `outdated` 结束;只有最后一轮没有结束行时才恢复。追加前先截断崩溃留下的半行,文档中途漂移(当前 State 既不是 `run` 行快照、也不是切分后那份快照)时追加 `outdated` 并返回错误,由下一次调用显式开新一轮,不在同一次调用里自动重启。切图资源失败不回滚,靠 manifest 的 by-path 复用接上;切分 op 内部更细粒度的恢复仍由 `SeparationState` sidecar 承担,检查点日志不复制它的进度。
- 三个工具的描述与参数文案都在 `prompts/runtime/texts/ui-design-doc.json`(目录 ID `uiDesignDoc`),Rust 侧不得硬编码面向模型的长文案。策划 `design-foundation` 的自主构建白名单同步登记这三个工具,命令映射复用 `asset.register` / `file.write`。
@@ -1,12 +1,18 @@
# DirectProject Codex 原始历史与异常恢复
更新时间:`2026-09-16`
更新时间:`2026-09-23`
> 注:本文件里"事件不带回合身份"、"`turn.started` 之前的早退不产生终态事件"这两条结论已被
> [`【ADR】DirectProject命令接单化-2026-09-23`](../adr/【ADR】DirectProject命令接单化-2026-09-23.md)
> 取代并落地:生命周期事件带可选的 `userItemId`,逻辑回合在**接单**时成对发出,接单之前的失败一律
> 是拒单(不产生回合事件)。下文相关段落已按该 ADR 修订;"失败说明不写进 `project.jsonl`"仍是
> 当前口径。
## 目标
DirectProject 只使用 `.agent/conversations/project.jsonl` 作为对话历史。历史保存 Codex Responses API 的完整 item,使聊天展示与新线程恢复使用同一份事实来源;两者只是不同读取动作。
本方案只适用于 DirectProject,不改变 DirectHome、Agent session 历史或 `runtime/direct-codex/turns` 审计账本。
本方案只适用于 DirectProject,不改变 Agent session 历史。DirectProject 已退役 `runtime/direct-codex/turns` 平行审计账本并删除旧审计 / 计时实现;完整回合条目统一来自本方案的 `project.jsonl`。用户项目中的旧日志不作为新回合必需产物,也不因代码清理被删除或迁移。
## 文件格式
@@ -27,13 +33,13 @@ DirectProject 自己的写侧只写新格式:格式切换(#282)时仍会
## 正常回合
1. 启动 `ephemeral: true` 线程,并启用 `experimentalRawEvents: true`。
2. 新线程先把历史 item 数组逐项投影为 Codex 可接受 item 后一次注入;注入成功后执行新的 `turn/start`。本轮 canonical user item 在发送前完成同样的投影校验,再写入项目历史。
2. 命令**接单**后先写本轮 canonical user item(发送前完成同样的投影校验),再在后台起 codex;新线程把历史 item 数组逐项投影为 Codex 可接受 item 后一次注入,注入成功后执行新的 `turn/start`。这一轮的逻辑回合在接单那一刻就已开始,落盘与注入、`turn/start` 都在回合内,失败由这一轮的终态事件解释(见「异常回合收尾」)。
3. 收到 `rawResponseItem/completed` 后立即追加其 `params.item` 并 flush。
4. 正常 `turn/completed: completed` 不生成额外记录。
## 异常回合收尾
AGC 判定本轮不会再产生新事件时收尾:用户中断、turn failed、无响应/idle timeout、硬超时、transport closed、stdout EOF 或 app-server 卡死终止均属于异常终态;正常 completed 不收尾。
AGC 判定本轮不会再产生新事件时收尾:用户中断、turn failed、无响应/idle timeout、硬超时、transport closed、stdout EOF 或 app-server 卡死终止均属于异常终态;正常 completed 不收尾。终态出口只有接单时登记的占用对象一个:正常 / 失败 / 中断 / 取消谁先算出来谁写 `turn.completed`,都写不出时由它的 `Drop` 补 `host-dropped`。
`item/agentMessage/delta` 正常带有 `itemId`;若协议异常缺失,AGC 记录 warning 并按当前 turn 生成稳定回退 id。AGC 在内存中按该 id 累计 assistant 文本,不实时写 delta。异常终态时,对仍有累计文本的 item 合成普通 Responses assistant `message` item:
@@ -55,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。
@@ -112,8 +118,8 @@ Thread 内所有公开事件共用一个单调递增 seq,但 **seq 只是 Thre
```ts
type DirectThreadEvent =
| { type: 'turn.started' }
| { type: 'turn.completed'; status: string }
| { type: 'turn.started'; at?: number; userItemId?: string }
| { type: 'turn.completed'; status: string; at?: number; userItemId?: string; failure?: { kind: string; message: string } }
| { type: 'item.started'; item: DirectThreadItem }
| { type: 'item.completed'; item: DirectThreadItem }
| { type: 'item.delta'; itemId: string; kind: 'message' | 'reasoning'; delta: string }
@@ -122,12 +128,42 @@ type DirectThreadEvent =
进入 Thread Manager 的是已经完成安全过滤和协议标准化的公开 raw event,不是未经审查的 app-server JSON。事件可交错包含多个并发 item:`item.started`、`item.delta`、`item.completed`、approval/request/resolved 事件,以及 `turn.started`、`turn.completed` 生命周期事件。前端按事件顺序 reduce,只用一个 reducer。
**事件不带回合身份。** DirectProject 同一时刻只有一个回合在跑,`turn.started` 无载荷、`turn.completed` 只带 `status`;条目、增量、请求与生命周期锚点都不带 turn id。前端 state 里只有一个 `turnRunning` 布尔,历史条目也不记录回合身份。
**回合身份只挂在生命周期事件上,且由 `clientTurnId` 现算。** DirectProject 同一时刻只有一个回合在跑;
`turn.started` / `turn.completed` 各带一个可选的 `userItemId`(本轮开口用户条目的 canonical id,
`direct-codex:{clientTurnId}:user`),`turn.completed` 另外带 `status` 与失败时必有的 `failure`。
条目、增量、请求与生命周期锚点仍不带 turn id:这个字段只把"这一轮的边界属于哪条用户消息"讲清楚,
不新增一套回合身份,**不读盘回填**(开始事件发生在用户条目落盘之前,落盘本身也可能失败)。前端 state
里的 `turnRunning` 仍是唯一的活动判定,历史条目不记录回合身份;身份缺失时不猜历史归属。
**终态只有 `turn.completed` 一种,失败靠 `failure` 载荷区分。** `status !== "failed"` 表示正常结束 / 中断 / 终止,事件不带 `failure`;`status === "failed"` 是失败终态,**必须**带 `failure { kind, message }`:`kind` 是稳定分类(`timeout` / `model-failed` / `transport-failed` / `request-rejected` / `environment-not-ready` / `host-dropped`,只给界面选语气,界面不拿它做流程分支),`message` 是脱敏截断后的失败原因。失败原因只走这一条通道——前端不从命令返回或另一条 IPC 里另造失败文案;`status="failed"` 却没有载荷视为协议违规。
**接单之前发生的不是回合失败,是拒单。** 判据是**发生位置**而不是错误种类:目录、权限、输入、
并发、工程准备未就绪这类"接单前就能判定"的失败由命令以结构化的 `DirectTurnError`(ts-rs 导出,
载荷 = 变体 + `Display` 生成的一句文案)返回,不产生任何回合事件、不写用户条目、不写失败诊断;
接单之后的连接、配置、历史注入、`turn/start` 被拒以及回合过程中的一切,都只走 `turn.completed`
带失败载荷这一条通道。`EnvironmentNotReady` 接单前后都可能出现,因此它有自己的失败分类
(`environment-not-ready`),不会被投影成 `model-failed`。
执行通道断开(app-server 进程退出、stdout 流断、回合事件通道关闭)也走同一条终态:`kind="transport-failed"`,`message` 是宿主当场记下的诊断(`exitStatus` + stderr 摘要,脱敏截断)。宿主在检测到连接终止时**第一时间**把这条事实记到本回合的执行适配器上,终态判定再从适配器读——执行适配器的看门狗盯着同一个 `closed` 标志,若只在调用点用局部变量记录,会与看门狗的收束竞争,输掉时就只剩 `status="interrupted"` 加一句收尾说明,界面只显示"本轮已结束"、看不到原因。判据是"适配器是否已由宿主主动关闭":宿主自己收束(正常终态 / 用户主动停止 / 预算与交付收尾)同样会发 `TransportClosed`,但那些不算失败。
宿主的异常收场同样靠这条事件:**接单**时登记占用对象并发出 `turn.started`,占用对象持有这一轮唯一的
终态出口——正常 / 失败 / 中断 / 取消谁先算出来谁写终态,都写不出时由它的 `Drop` 补一条
`status="failed"` + `failure.kind="host-dropped"`,因此"接单成功 ⇔ 事件流里有开始且有结束"是结构性
成立的,不依赖实现者记得给每条"接单后提前收场"(早退:回合内任何没走到正常终态的收口点,比如
`turn/start` 被拒、注入失败、panic)的路径补事件。唯一的已知边界是宿主进程被强杀(`kill -9`):没有任何
`Drop` 执行,队列随进程消失,新进程的订阅 bootstrap 因此不会看到"有开始没结束",界面不会卡在忙碌态。
接单**之前**的失败根本不产生回合(见上一条:那是拒单),所以不存在"没有事件可解释的回合"。
**终态由事实判定,不由收尾阶段反推。** `turn.completed.status` 不是收尾阶段的口径(`lifecycle_status` 只描述 ledger 阶段,没有终态否决权):判定按「宿主当场记下的失败(通道断开 / 等待超时 / app-server 单方面中断)→ 本回合的错误结果是 Err → 只有账本读不出来时才用交付报告」取原因,有载荷一定写 `status="failed"`。模型自报失败(原生 `turn/completed` 的 `error`,含 `codexErrorInfo`)复用同一条通道:宿主把它投影成 `LlmError` 后当作本回合的错误结果返回,原因文本里带着 `codex-app-server-error:<kind>` 前缀(前端 `projectRuntimeVisibleError` 已有对应中文映射),既不为载荷新增输入字段,也不让交付报告顶掉原因。`RepairRequired`(封口复核要求继续当前返修批次)**不是失败**:它是控制流,有独立的 typed 变体(宿主侧 `DirectTurnRunFailure::RepairRequired`,跨界后是 `DirectTurnError::RepairRequired`),不写终态、不进失败载荷、不上报,由返修循环把它写回提示词继续跑;伪装成 `LlmError` 会让"继续返修"被讲成一次用户可见的失败,还会让同一个逻辑回合写出第二条终态。
**终态的写点在整轮真正结束之后。** 执行结果收集(含执行器收尾)、历史落盘、structured output 解析都定型了才写 `turn.completed`,成功与失败共用这一个写点:解析失败也是这一轮的失败,必须落进同一份失败载荷。反过来(先写终态、再解析)会让"终态写完又失败"的回合在协议上无解——终态已经是 `completed`,占用对象的兜底变成空操作,用户看到的是"本轮结束、没有回复、没有任何解释"。
一个 thread 同时最多有一个 active turn;一个 turn 内允许多个并发 item。`turn.completed` 必须在该 turn 的完成 item 均成功持久化后进入队列,前端据此结束运行态;不能用“不存在 unfinished item”猜测 turn 是否完成。
前端 reducer 的活动回合判定只有一条:事件序列中出现 `turn.started` 且其后没有 `turn.completed` 时才是活动回合,界面才允许显示忙碌态。`subscribe` bootstrap 里没有这样的序列,就表示当前没有活动回合;Thread Manager 队列随进程消失,因此进程重启后历史里留下的半截回合一律按已结束渲染,前端不发明中断态,也不从历史条目反推忙碌态。
失败终态与正常终态同权:`turn.completed`(无论 `status`)都顶替更早的 `turn.started` 成为队列锚点,重放时新订阅既不会把已收口的回合看成"还在跑",也不会看到已经过期的失败原因。
生命周期锚点独立于 replay 队列保存:`turn.started` / `turn.completed` 事件即使已被队列前缀回收,`subscribe` 仍必须把最新的一条作为 bootstrap 事件返回。因此进程内任意时刻新建订阅,都能判定最新回合是运行中还是已结束,不依赖"未完成 item 恰好还在队列里"。
`item.started` 与 `item.completed` 必须携带与历史切片同形的**脱敏原始条目**(经同一套挑字段、脱敏、截断、路径归一),不得只给 item 类型或空 payload。前端不得依赖"按 `itemId` 单点取快照"补齐正文:Rust 不提供 `getItemSnapshot(itemId)`,未完成条目的正文随事件下发,已完成条目一律通过历史读取。
@@ -1,7 +1,11 @@
# DirectProject 本轮附件路径映射
> 文档状态:`historical`(旧 Home / sidecar 设计已由 canonical userItem 附件引用替代,不作为当前实现或保留代码的依据)
2026-09-23 核准:未注册的 DirectHome 与旧 sidecar DTO、渲染器、prompt key 和专属测试已清理。现役附件使用 `userItem.content` 中的 `agc_attachment_reference`,保留本轮名称到项目相对路径的映射、不灌正文、不按 GDD 特判;名称 / 媒体类型 / 路径清洗及数量上限仍服务 canonical validation/wire。当前权威合同见 [AGC 实施计划](./【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md)。以下为原设计记录,不要求恢复 Home 元数据文案、独立 attachments 参数或 sidecar。
- 日期:2026-08-31
- 状态:现行合同(已按本文落地)
- 状态:历史设计,原 sidecar 实现已退役
- 问题:Gitea issue #212(DirectProject 未消费用户上传权威文档)
- 关联入口:PR #210「批准 GDD 回填做游戏入口」(`feat/create_entrance`,未合入时仍按该 PR 的调用链理解)
- 原则:落地后代码简洁可维护,不为了 diff 最小而打补丁;附件一律同等对待,不给 GDD 开协议特例
@@ -27,7 +31,7 @@
- 不把附件全文拼进 prompt,不按扩展名决定是否读取。
- 不把「没读到就阻断」做成门禁。
- 不扫 manifest 里历史 `kind=uploaded`。
- 不做 native 读取审计;该项由 [`【技术方案】Direct回合行为审计账本-2026-08-31.md`](./【技术方案】Direct回合行为审计账本-2026-08-31.md) 承接。
- 不新增附件专用 native 读取审计。现役回合工具条目从 `project.jsonl` 完整历史读取;旧平行审计日志及 `offeredRead` / `firstDesign` 投影已停用,不再由旧审计专题承接。
- 不改 DirectHome 在「无项目路径」时的现有文案和列表格式。
- 不改 `enterCreatedHomeProject` 的空正文兜底句(与做方案共用)。
@@ -1,7 +1,11 @@
# Direct 回合行为审计账本
> 文档状态:`historical`(旧平行审计日志已停用,仅用于历史追溯,不作为当前实现或保留代码的依据)
2026-09-23 核准:DirectProject 已以 `.agent/conversations/project.jsonl` 保存完整回合条目,GUI 入口不再构造本方案的审计对象,也不承诺继续追加旧日志、`direct.codex.turn` 摘要或附属请求分段计时。当前边界见 [AGC 实施计划](./【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md) 的“Direct 历史、审计与耗时的现行边界”。既有用户项目内的旧审计数据不在本次文档修正中删除或迁移。以下保留原设计供追溯。
- 日期:2026-08-31
- 状态:现行合同(已按本文落地)
- 状态:历史设计,原实现已退出生产回合入口
- 问题:Gitea issue #212 的第二段(Direct 原生读 / 工具行为无法从项目产物判断);用于分析「附件已映射仍未按文档实施」
- 关联:[`【技术方案】DirectProject本轮附件路径映射-2026-08-31.md`](./【技术方案】DirectProject本轮附件路径映射-2026-08-31.md)、[`【技术说明】DirectProject未消费用户上传权威文档-2026-08-30.md`](./【技术说明】DirectProject未消费用户上传权威文档-2026-08-30.md)
- 原则:落地后代码简洁可维护;审计是 Direct 行为时间线,不是 GDD 特例,也不替代 sidecar
@@ -319,7 +323,7 @@ chat_with_game_creator_direct_codex
## 9. 代码落地
新增 [`apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_audit.rs`](../../apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_audit.rs):
原方案新增 `apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_audit.rs`(现已删除,以下仅作历史追溯):
- `DirectCodexTurnAudit`
- `start` / `observe_item` / `finish`
@@ -0,0 +1,78 @@
# 【技术方案】External v1 游戏场景生成路由
更新时间:`2026-09-24`
## 目标
为 `/api/external/v1` 补齐游戏场景生成的结构化专用路由,使 AGC 客户端(陶泥儿美术包背景阶段)在平台收紧 `kind = scene` / `assetKind = scene` 边界校验后仍有合规的场景生成入口:
```text
AGC 美术包背景阶段(结构化场景意图)
-> POST /api/external/v1/editor/scenes/generations
-> 后端确定性组装场景 Prompt(与站内场景路由同一实现)
-> 现有 editor_image_generation 队列与 Worker
-> 现有计费、幂等、失败、资源持久化与 canvasCompletion
```
同时修复 AGC 客户端背景阶段保留账本匹配口径与实际请求不一致的既有隐患。
## 非目标
- 保持站内 `/api/editor/scenes/generations` 的场景字段、Prompt 和计费规则;补齐 AGC 账号模式所需的可选幂等键与队列结果协议。
- 不放松通用 `/api/editor/images/generations` 与 `/api/external/v1/editor/images/generations` 对 `kind = scene` / `assetKind = scene` 的拒绝。
- 不新增场景 Worker、任务表、计费档位或 SpacetimeDB schema。
- 不改变美术包背景图的出图风格与尺寸(16:9 / 1K)。
- 不处理 issue #495 的抠图重放 409 与动画 compact 字段问题(独立排期)。
## 入口与边界
- 系统入口:AGC 客户端 Direct 美术包流程的背景阶段(`reuse-or-create` 与 `regenerate` 均经过)。
- 涉及模块:`api-server`(external v1 路由与场景 Prompt 组装)、`shared-contracts`(DTO 复用)、AGC `src-tauri`(请求构造、保留账本、身份对账与恢复扫描常量)。
- 正式状态来源:`external_generation_job` 队列记录与项目 manifest 登记,与现役外部生成入口一致。
## 必须成立的行为
### 正常路径
1. 新路由 `POST /api/external/v1/editor/scenes/generations` 接受与站内场景路由相同的 `EditorSceneGenerateRequest` 字段(`sceneContent`、`stylePreset`、`customStyle?`、`model?`、`aspectRatio?`、`imageSize?`、`referenceImageSrcs?`、`projectId?`、`generationInputs?`、`assetFolderId?`、`assetLabel?`、`canvasCompletion?`),不接受调用方组装后的完整 `prompt`。
2. 场景 Prompt 由后端经与站内路由完全相同的组装实现生成;两路由只共享这一份组装逻辑。
3. 鉴权复用现有 `editor:image-generate` scope;与现役外部生成入口一样强制 `Idempotency-Key` 请求头。
4. 受理响应与现役外部生成入口同形(operationId 异步受理信封),轮询继续走 `/api/external/v1/generations/{operation_id}`。
5. 入队后 `kind = scene`、`assetKind = scene`,队列类型、Worker、计费与持久化与站内场景路由一致;队列标题与任务摘要口径不变。
6. AGC 美术包背景阶段以 `stylePreset = custom` + `customStyle` 承载现有风格描述,`sceneContent` 承载 brief 衍生的画面内容,出图风格与比例不因迁移改变。
7. 普通账号自动映射到 `/api/editor/scenes/generations`,该入口读取可选 `Idempotency-Key` 并传给现有队列;未提供时保留站内按请求 ID 入队的行为。场景组装仅保留 `generationInputs.source = ai-game-creator-client` 这一精确标记,其余配方字段仍由服务端重建。该标记让账号任务沿用 AGC 幂等命名空间和包含可下载 `result` 的队列结果,轮询走 `/api/runtime/external-generation/jobs/{operation_id}`。
### 失败、重试与幂等
1. 缺少 `sceneContent`、非法 `stylePreset`、自定义风格缺少 `customStyle` 等参数错误返回 400,与站内路由同语义。
2. 缺少或非法幂等键、越权 scope 的拒绝语义与现役外部生成入口一致。
3. 同一幂等键 + 同一请求重放返回原任务,不新建任务、不重复扣费;同键不同请求返回 409。
4. Provider 失败、取消与 lease 耗尽沿用现有扣退费语义。
5. 账号场景入口拒绝非法幂等键(400);同键重放复用现有队列幂等实现。来源标记不参与权限授予,任务归属仍来自已认证用户。
### 权限、归属与数据边界
1. 资源归属、项目绑定与素材文件夹解析沿用现役外部生成入口的 owner 口径。
2. 任务摘要只展示 `generationInputs.fields` 的「画面内容」,不把后端完整 Prompt 暴露到任务侧栏。
3. 场景产物以 `assetKind = scene` 持久化并保存 `scene.generate` V2 配方,与站内产物口径一致。
## 契约与迁移
- API / DTO / OpenAPI:新增 external v1 场景路由,DTO 复用 `shared-contracts` 的 `EditorSceneGenerateRequest`;同一次变更同步 `docs/openapi/genarrative-external-v1.openapi.json` 与契约测试(路由矩阵、鉴权、参数 400、幂等重放)。
- SpacetimeDB schema / migration / bindings:不变。
- 兼容与迁移策略:AGC 客户端背景阶段的路由、manifest 身份登记、保留账本匹配与恢复扫描常量整圈迁移到新路由;历史已登记的背景身份(旧通用路由 + `kind = spec`)保持可读,不做数据迁移。
## 验收标准与证据
| 条款 | 验收方式 | 证据 |
| ---- | -------- | ---- |
| 账号入口幂等键校验与 AGC 下载结果 | 非法键路由测试、场景来源到结果序列化测试、共享队列幂等命名空间测试 | `cargo test --locked -p api-server scene`(18 项)、`editor_generation_queue::tests`(19 项)、`external`(158 项)通过;本地启动因 SpacetimeDB 连接拒绝未通过健康检查,真实 Provider 出图与账号同键重放尚未联调 |
| 新路由受理/参数校验/鉴权/幂等重放 | api-server 契约测试与单测 | 待补 |
| 与站内路由同一 Prompt 组装结果 | 共享实现的单测对照 | 待补 |
| OpenAPI 与实现一致 | 契约测试 + `check:openapi` 类门禁 | 待补 |
| 美术包背景端到端(reuse-or-create / regenerate) | 客户端定向测试 + 本地真实栈 smoke | 待补 |
| 中断恢复:保留账本匹配与身份对账 | 客户端定向测试 | 待补 |
## 未决问题与决策
- `stylePreset` 取舍:已决策——AGC 美术包背景固定 `custom` + `customStyle` 承载现有风格文案,不绑定预设风格(2026-09-24,与用户确认)。
@@ -0,0 +1,111 @@
# UI 编辑器 Agent 工具化重写
更新时间:`2026-09-23`
一句话定位:把 UI 编辑器今天由前端会话编排、由单个 `ui.workflow.run` 驱动的 Agent 链路,重写成三个各自只做一件事的工具——建文档、跑工作流、出 JS——且只有工作流工具带逐步骤崩溃恢复。
## 现状与问题
- `ui.workflow.run`(已退役)把发现页面、桥接设计图、结构识别、多树合并、组件绑定、回读、finalize 全塞进一个工具,工具参数本身就是工作流状态:模型不能只做其中一步,任何一步失败都只能整轮重来。
- 步骤产物由前端 `useUiEditorPage.ts` 落 State 再保存,Agent 侧没有任何恢复点。
- `merge` / `binding` 两条实验链路无现役价值,已随工具一起退役(`ui_editor/commands/merge.rs`、`binding.rs`、`ui_editor/workflow.rs` 已删除)。
## 工具契约
| 工具 | 输入 | 输出 | 恢复 |
| --- | --- | --- | --- |
| `ui-design-doc.from-images` | 1–4 张设计图,每张给 manifest `assetId` 或项目内相对路径 | 新建文档的 `assetId` 与 `relativePath` | 无状态 |
| `ui-design-doc.run-workflow` | 文档 `assetId` | 各步骤摘要与文档新 `revision` | 逐步骤 JSONL 检查点 |
| `ui-design-doc.into-js` | 文档 `assetId` | `ui/generated-<stem>-<digest>.js` 路径与导出树 | 无状态 |
工具名用 `<域>.<动作>` 形式,域内动作保留连字符(`from-images`、`run-workflow`、`into-js`);调用这些工具时项目根目录仍由 Runtime 注入,模型不得传入宿主路径。
### 建文档:`ui-design-doc.from-images`
- 文档内设计图 id 直接采用该图在 manifest 里的 `assetId`;输入给相对路径时先登记成资源再用它的 `assetId`,不额外发明文档内身份。
- 文档文件名沿用 `ui/UI 设计 N.json` 取号,不登记半成品命名(旧 `ui/ui-workflow-<sha256前24>.json` 口径废弃)。
- 每次调用都新建一份文档并登记:不做「原型 → 已存在文档」的幂等查找,原 `ensure_ui_design_resource_for_prototype` 的复用分支随之删除。
- 建项与登记在项目写锁内完成;写盘失败要回滚已登记的 manifest 条目,不留半成品。
### 工作流:`ui-design-doc.run-workflow`
只做三步,全部在 Rust 内完成,结果不回传前端编排:
| 步骤 | 实现 | 产物 |
| --- | --- | --- |
| `recognize` | `recognize_ui_impl_with_provider` | `ui_trees`(每棵树的 `src_ui_design` 指向文档内设计图) |
| `separate` | `separate_ui_impl` | 切分图落盘 → 登记 asset → 转 `SpriteAsset` → 写入 State → 回填 `target_graphic`、清 `component_status`、写 `NeedReview` |
| `write-back` | `save_ui_design_state_at` | 文档新 `revision`;没有回填问题且没有问题节点时再 `finalize_separation` 清理 sidecar |
不再有合并、组件绑定与页面级 profile/finalize 阶段;`recognize` 之前不做任何前置发现。
`NeedReview` 与 `component_status` 的清理是 `separation` DTO 的一部分:问题节点由 DTO 的
`problematic_nodes` 给出,编排阶段统一回写,重放时不需要模型再判一次;文案与判据镜像
前端 `features/ui-editor/separationStatus.ts`。
## 崩溃恢复
检查点是文档旁一条追加式 JSONL 日志,方案与被否方案见 [ADR:UI 工作流检查点用追加式 JSONL 日志](../adr/【ADR】UI工作流检查点用追加式JSONL日志-2026-09-23.md)。
- 位置:`ui/.<文档文件名去扩展名>-workflow.jsonl`,与文档同级,不进 manifest、不推进项目 revision。
- 行格式:`run`(原始 State 快照 + 起始 revision)、`recognize`(DTO + 该步应用完的 State 快照)、`separate`(DTO + 该步应用完的 State 快照 + 回填说明)、`write-back`(新 revision)、`outdated`(本轮作废原因);行内另带 `at` 时间戳。
- 判据只有一条:「本步有没有对应的行」。每行必须一次性原子追加,崩溃留下的半行一律视为该步未完成。
- 恢复只看快照、不重放步骤:已完成的步骤在检查点里带着那一步应用完之后的 State 快照,恢复时直接读回这份 State 并整步跳过,不按记录下来的 DTO 重放该步 delta——重放会重复登记切图、重复累加回填说明,把同一份错误报两遍。只有带状态快照的行才算已完成,旧格式(只有 DTO)的行按未完成重跑。
- 轮次:一轮以 `run` 行开头,以该轮第一行 `write-back` 或 `outdated` 结束;只有最后一轮没有结束行时才需要恢复。`outdated` 只作废未完成的那一轮,不删除既有行。
- 半行:追加前先把日志截断到最后一个换行,丢掉崩溃留下的半行,避免它夹在日志中间。
- 漂移:文档在轮次中途被改动(当前 State 既不是 `run` 行快照、也不是切分后那份快照)时,追加一行 `outdated` 并**返回错误**,不在同一次调用里自动重开新一轮;下一次调用看到 `outdated` 才从头开新一轮,且以当前文档为基准。
- 写回幂等:`save` 成功但 `write-back` 行没追加时,恢复读回的切分后 State 与文档当前 State 相等,即判为已写完,直接补 `write-back` 行并返回成功。这条判据成立的前提是工作流这条路不产生随机身份:识别树的根节点 id 来自 DTO,切图资源 id 走 manifest 的 by-path 复用。
- 切图资源不回滚:切分产出的图片与已登记资源在失败后保留,重放靠 by-path 复用接上,不做回滚清理。
- 写回成功后才清理 sidecar:与前端切分链路一致,`backfill_errors` 为空且 `problematic_nodes` 为空时调用
`finalize_separation`;有回填问题或问题节点时保留 sidecar,交给编辑器显示恢复入口。
- 切分 op 内部的细粒度恢复仍由 `SeparationState` 承担(`ui-editor-separation-state.v2` sidecar),日志只记录工作流层面的步骤完成,不复制它的进度。
- 不引入跨语言 fixture 比对:镜像的四个 seam 都是简单变换,靠同语义实现与各自单测覆盖,不为它们额外维护一套 golden。
## 模块布局
### Rust
```text
src/ui_editor/agent_tools/
├─ mod.rs 模块声明与三个工具的对外导出
├─ creation.rs from-images:登记图片、建文档、manifest 注册、命名取号
├─ checkpoint.rs JSONL 追加、读取、轮次判定
├─ run_workflow.rs recognize → separate → write-back 编排与恢复
├─ steps/
│ ├─ mod.rs 步骤子模块声明
│ ├─ recognize.rs 识别 DTO 落 State
│ ├─ separate/
│ │ ├─ mod.rs 切分 DTO 落 State(回填、清状态、写 NeedReview)
│ │ └─ cut_images.rs 切图图片登记与 SpriteAsset 构造
│ └─ write_back.rs 保存 State、记 write-back / outdated 行、漂移文案
└─ test_support.rs agent_tools 单测共用夹具
src/agent/runtime_tools/ui_design_doc.rs 工具参数解析与 Runtime 侧调用
```
`into-js` 不需要独立模块:它直接复用 `persistence.rs` 的 `generate_ui_design_code_at`,
该入口本来就只渲染 `ui/generated-<stem>-<digest>.js` 且不推进项目 revision。
每个文件只承担一件事;`checkpoint.rs` 不感知切分,`creation.rs` 不感知识别。
### 提示词目录模块
工具描述与参数文案一律进 `src-tauri/prompts/runtime/`,不在 Rust 里硬编码面向模型的中文长文案:
- 新增文本目录 `texts/ui-design-doc.json`,在 `manifest.json` 的 `textCatalogs` 登记为 `uiDesignDoc`。
- 键名规则 `<工具名>.<字段>`,工具名用下划线形式:`from_images.description`、`run_workflow.parameters.designDocAssetId` 等;Rust 侧用 `prompt_text!("uiDesignDoc.from_images.description")` 引用。
- 构建期 `build_support/runtime_prompt_bundle.rs` 会校验目录已登记、key 非空、bundle 内没有未登记的 `.md`/`.json`,因此新增文件必须同步 `manifest.json`。
## 落地顺序(全部已完成)
1. 清理 legacy:`ui.workflow.run`、`merge`、`binding` 与前端合并调用。
2. 术语与 ADR:`CONTEXT.md` 四个词条、检查点 ADR。
3. 本文档。
4. 提示词目录模块 + 两个无状态工具(`from-images`、`into-js`)。
5. `run-workflow` 与 JSONL 检查点(`agent_tools/{checkpoint,run_workflow}.rs` + `steps/separate/`)。
6. 前端收口:删 `uiDesignResourceBridge` 的 `ui-workflow.*` 优先级与 `project-development` 的自动打开分支。
7. 三个工具注册进 Runtime(`agent_native_tools`、`runtime_tools/ui_design_doc.rs`、可执行工具目录、并行账本映射、design-foundation 白名单)。
## 关联文档
- [UI 编辑器代码地图与模块职责](./【技术方案】UI编辑器代码地图与模块职责-2026-09-23.md)
- [UI 编辑器自动切分素材工作流](./【技术方案】UI编辑器自动切分素材工作流-2026-09-08.md)
@@ -0,0 +1,74 @@
# UI 编辑器代码地图与模块职责
更新时间:`2026-09-23`
一句话定位:`apps/ai-game-creator-shell` 的 UI 编辑器由「Rust/Tauri 权威层 + React 会话层 + 视图层」三段组成;Rust 持有可持久化 State、校验、LLM 工具链、预览渲染与代码生成,前端只负责语义编辑、会话编排和表现。
## 分层与数据流
```text
view/ui-editor (页面/组件)
└─ useUiEditorSession ← features/ui-editor (语义 + adapter)
├─ useUiEditorState / stateTransition / nodeTransformGeometry
├─ uiDesignStateStore → Tauri command
└─ invoke: recognize_ui / separate_ui / ...
└─ src-tauri/src/ui_editor (权威 State、校验、持久化、切分)
```
- 唯一事实来源是项目内的 `ui_design` JSON 文档(含 `revision`),前端 State 只是它的编辑副本。
- 所有跨进程类型由 Rust 经 `ts_rs` 生成到 `features/ui-editor/types/`(当前 53 个文件),前端不得手改。
## Rust 侧:`src-tauri/src/ui_editor`
| 模块 | 职责 |
| --- | --- |
| `state/mod.rs` | 权威 State 根类型:`State`(`ui_trees` + 界面图/sprite/字体三张资源表)、`UITree`;ts-rs 导出源 |
| `layout/` | 节点模型:`Node`、`NodeMetadata`/`StageStatus`、`ControlLayout`、`Container`、`NodeOffset`、`transform`、`ChildrenDisplayMode`(`Stack`/`Exclusive`) |
| `component/` | 组件枚举 `Component::{Image, Text}`、`NodeComponent`(LLM 工具载荷的 `PureNode`/`WithComponent` 判别式) |
| `resource/` | 界面图(`path` / `pixel_size` / `pixels_per_unit`)、sprite(含 `SpriteBorder` 九宫格)、字体(格式/媒体类型/CSS format)资源描述 |
| `persistence.rs` | 文档读写、`revision` 乐观并发保存、领域校验(重复 ID、树/资源引用、组件状态)、代码生成写盘 |
| `agent_tools/` | Agent 工具链路:`creation.rs` 用一至四张设计图新建文档(登记未登记图片、按 `ui/UI 设计 N.json` 取号、持项目写锁装 revision 0、失败回滚)、`checkpoint.rs` JSONL 检查点日志、`run_workflow.rs` 三步编排与崩溃恢复(恢复只读检查点里的 State 快照、整步跳过已完成步骤,不按 DTO 重放)、`steps/` 逐步落 State(`mod.rs` 步骤子模块声明、`recognize.rs` 识别、`separate/` 切分:`cut_images.rs` 登记切图与 `SpriteAsset` 构造、`mod.rs` 回填与问题状态、`write_back.rs` 保存与漂移文案) |
| `html_renderer/` | 由 State 生成 HTML 片段与 JS(maud + 布局/组件 CSS 映射),供预览与 `ui/generated-*.js` |
| `commands/` | LLM 工具链:`recognition`(结构识别)、`separation/`(自动切分素材)、`utils.rs`(LLM 请求、重试、`required_tool_arguments`) |
| `commands/separation/` | 切分批处理、截图/预切、sidecar 恢复(inspect / finalize / discard)、patch 回写 |
Agent 工具(`agent_native_tools.rs` + `agent/runtime_tools/ui_design_doc.rs`):`ui-design-doc.from-images` 新建文档并登记;`ui-design-doc.run-workflow` 在 Rust 内跑 `recognize → separate → write-back`,按文档旁 JSONL 检查点恢复;`ui-design-doc.into-js` 复用 `generate_ui_design_code_at` 产出 `ui/generated-*.js`,不推进 revision。旧 `ui.workflow.run` 与 `ensure_ui_design_resource_for_prototype` 已退役。
关键命令(`main.rs` 注册):`load_ui_design_state`、`save_ui_design_state`、`generate_ui_design_code`、`create_ui_design_doc_from_images`、`recognize_ui`、`separate_ui`、`inspect_separation_recovery`、`finalize_separation`、`discard_separation_recovery`。
保存语义(`save_ui_design_state_at`):`Saved` / `Unchanged` / `Conflict`(返回当前快照)三态;先 `validate_state` 再持锁重读比对 `expected_revision`,成功后推进项目 revision。代码生成只接受已保存的 revision,产物路径为 `ui/generated-<stem>-<digest>.js`。
## 前端:`src/features/ui-editor`(无视图依赖的语义层)
- `useUiEditorState.ts`:State reducer + 撤销/重做(`undo/redo/resetHistory`)、`isLocked` 与 `runWithStateLocked`、节点/资源/树偏移的语义写操作、`createTree`(新树横向排布 + `UI_TREE_PADDING`)、删除影响 projection。
- `stateTransition.ts`:React-free 的命令 → State 语义 transition(`set-tree-offset`、`set-node-metadata`、`set-node-component`)。
- `nodeTransformGeometry.ts`:State 级节点几何(页面矩形、父矩形、resize 手柄反演),预览/Inspector 共用。
- `stateInvariants.ts`:保存前不变量 projection,给视图稳定的中文失败信息;Rust 仍是权威校验。
- `uiDesignStateStore.ts`:`IUiDesignStateStore`(`load`/`save`/`generateCode`)+ Tauri 实现 + 内存替身。
- 结果应用 seam:`recognition.ts`、`separationStatus.ts`(问题节点 → `NeedReview`)。
- 概览 projection:`stageStatusOverview.ts`、`separationOverview.ts`。
- 前置校验:`requisites.ts` 在发起 LLM 操作前检查必需资源与结果完整性。
- 适配器:`importAdapter.ts`(图片解码、批量导入、字体准备)、`uiDesignResourceBridge.ts`(调用 `create_ui_design_doc_from_images` 新建文档)、`useUiEditorFontFaces.ts`(私有字体族加载)、`spriteBorder.ts`。
- `utils/`:State → CSS 映射(`componentToCss`、`controlLayoutToCss`、`textStyleToCss`、`transform/tf2css`)、`treeUtils`。
## 前端:`src/view/ui-editor`(表现与编排)
- `useUiEditorPage.ts`:`useUiEditorSession` 是视图与 adapter 的唯一协调边界,产出 `input` / `canvas` / `inspector` / `workflow` / `dialogs` / `save` 六个小 projection;视图不接收完整 controller。
- `index.tsx`:页面骨架(输入侧栏、预览、Inspector、工具栏、保存/生成结果弹窗、键盘快捷键绑定),由 `view/project-development` 挂载。
- `model.ts`:两步工作流(识别界面结构 / 自动切分素材)、导入种类、操作失败文案。
- `operationLifecycle.ts`:recognition/separation 共用的异步操作 adapter。
- `components/`:`InputSidebar`、`UiTreePanel`、`Inspector/*`(Transform、Components Text/Image、SpriteBorder)、`preview/*`(`PreviewWorkspace`、`UiTreeRenderer`、组件视图、排他子节点 tab、缩放/平移/拖拽手势)、工作流与结果弹窗。界面图没有独立显示名字段,列表与 Inspector 的统一显示名取 `path` basename(`view/project-development/resourceAssetDisplayName.ts`)。
- 已退役的 render mode 由会话级开关 `showFrame` / `showOriginImage` / `showComponent` 取代。
## 扩展指引
- 新增节点/组件字段:先改 `layout/`(或 `component/`)并让 ts-rs 重新导出,再补 `stateTransition`、`stateInvariants`、`persistence::validate_node`、Inspector 与预览映射。
- 新增 LLM 步骤:在 `commands/` 内自成 command(prompt + schema + 领域校验 + materializer),共用 `commands::utils` 的请求、重试与 tool-call 解析 seam。
- 新增界面图字段:改 `resource/ui_design_image.rs` 后让 ts-rs 重新导出,再补 `persistence::validate_state`、导入 adapter(`features/ui-editor/importAdapter.ts`)与 Inspector / 预览展示。
## 关联文档
- [UI 编辑会话模块边界](./【前端架构】UI编辑会话模块边界-2026-08-19.md)
- [UI 编辑器 Godot 容器布局模型](./【技术方案】UI编辑器Godot容器布局模型-2026-08-18.md)
- [UI 编辑器子节点显示规则](./【技术方案】UI编辑器子节点显示规则-2026-08-18.md)
- [UI 编辑器自动切分素材工作流](./【技术方案】UI编辑器自动切分素材工作流-2026-09-08.md)
@@ -0,0 +1,387 @@
# 外部 MCP 语义工具说明与参数设计
更新时间:`2026-09-24`
> 文档状态:`current`(工程实现合同;仓库已增加语义工具,线上可用性以实际部署版本为准)。
>
> 父规范:[外部 OpenAPI 与 API Key 接入方案](../【后端架构】外部OpenAPI与APIKey接入方案-2026-06-19.md)。
>
> 当前字段契约:[External v1 OpenAPI](../openapi/genarrative-external-v1.openapi.json)。本文记录工具划分、说明和参数合同;英文工具名、schema 构造、结果与兼容方式见第 5 节。
## 1. 目标与范围
在现有 API server 的托管 MCP 层增加按用户任务组织的工具。仅使用现有 External v1 已开放的能力,不直接接入尚未公开的画布功能。
工具可以存在自然的功能交集:例如参考图快速变体同时属于生成图片和修改图片,读取画布同时服务于项目查找和布局编辑。不以工具数量或 API 唯一归属为目标,不把不相关任务硬合成一个工具。
本文约定工具说明、操作选择、参数和业务边界,以及与当前工具配套的 instructions 和 resources。开发者发布页、文档存储与独立发布、CLI 改造不属于本轮范围。现有 REST 路由、DTO 和运行行为保持不变,新语义工具与全部原有工具并存。
## 2. 当前实现与目标调用链
当前 [external_mcp.rs](../../server-rs/crates/api-server/src/external_mcp.rs) 从 OpenAPI 自动建立原子工具:工具名由 `operationId` 转为 snake_case;description 优先读取 operation 的 `description`,缺省读取 `summary`,附加 HTTP 方法和路径;参数 schema 来自 OpenAPI。服务级 instructions 提供通用工作流,resources 提供详细说明。
新工具继续部署在同一个 API server 内,复用现有分派和 External REST router:
```text
Agent 调用语义工具
→ MCP 层选择对应 operation,并转换参数位置
→ 进程内调用现有 External REST router
→ 复用鉴权、scope、owner、校验、幂等、计费和业务处理
→ MCP 返回业务结果或结构化错误
```
多功能工具的一次调用只选择一个操作。工具聚合不意味着批量执行、自动连续写入或新增跨操作事务。文件上传仍由调用方完成二进制传输。
## 3. 说明与参数的共同设计
### 3.1 工具说明
每份说明包含:
1. 用途:用户希望完成什么任务时选择本工具。
2. 操作选择:多功能工具列出各 action 的用途,解释容易混淆的选择。
3. 结果:立即返回业务结果,还是返回异步任务 ID;有哪些重要的降级或结果边界。
字段格式、枚举、必填关系放入参数 schema 和字段说明,避免把全部接口校验细节堆进工具 description。付费、删除等重要影响应明确表达,但 description 和风险注解不替代后端权限校验;宿主负责结合用户已给出的授权作调用决策。
### 3.2 多功能与单功能工具
多功能工具采用 `action + input`,input 必须提供对应 action 的明确字段、类型、必填条件和枚举,不能只是任意 JSON 对象。服务端在分派前校验所选操作,避免不相干分支的参数混入底层请求。
示意调用(示例 ID 仅为说明):
```json
{
"action": "edit",
"input": {
"sourceReferenceId": "<resourceId-or-assetId>",
"prompt": "把衣服改成红色",
"projectId": "<projectId>"
},
"idempotencyKey": "<stable-logical-request-key>"
}
```
单功能工具直接声明业务字段,不强行添加 action 或 input 包装。`kind`、`sliceMode`、视频 `mode` 等原有业务选择保留原名,它们与工具级 action 职责不同。
工具参数中的 ID 由 MCP 层放入 REST 路径或查询,业务输入放入请求体,`idempotencyKey` 转为 `Idempotency-Key` header。具体参数保留现有命名、类型和条件;下表列的是关键字段,并非完整 schema 或排他白名单。未逐项列出的可选字段按对应 OpenAPI operation 核对,不把站内 DTO 的内部字段顺带开放。
### 3.3 共用行为
- 九类生成 POST 继续要求稳定幂等键,并返回 `202 + operationId`;工具不能把受理当成生成完成。
- 项目创建、项目资源登记、文件夹创建的 REST API 支持可选幂等键,新语义入口提供并转发这些可选 header;原工具继续保持仅按 required header 推导幂等参数的行为。素材记录创建 API 不在该可选幂等合同中。
- 相同 API 能力出现在不同语义入口时,参数约束、底层请求和结果语义保持一致。重试或切换入口恢复同一逻辑请求时,保留原 API 操作、规范请求和幂等键,不因换工具名创建新任务。
- `projectId`、`assetFolderId`、`assetLabel`、`canvasCompletion` 等目标参数只在对应 API 支持时提供。UI 素材提取使用 `spritesheetLabel`;各生成族不共享未经核对的字段全集。
- owner 由 API Key 确定,不让 Agent 指定身份,不把 API Key 放进工具参数。
- 业务结果继续使用结构化返回;业务失败沿用安全错误语义,不把失败包装成成功。生成成功、素材登记、画布落位、切片成功分别按实际结果判断。
- 读取、写入、付费与删除应在说明和注解中准确表达。注解作用于整个工具,因此将删除独立成工具;`openWorldHint` 不等同于付费标记。
## 4. 工具说明与操作映射
以下共 15 个新增工具。删除从项目管理、素材库整理中移出,统一放入 XV。本节说明仓库实现合同,不表示线上已部署。
### I. 查找画布项目
**说明:** 查找已有画布项目,或读取指定项目的完整内容。先通过列表或最近项目确定目标,再读取详情获取画布、图层和资源。
| action | 关键参数 | 现有 operationId |
| --- | --- | --- |
| `list` | 无 | `listEditorProjects`,固定 `view=summary` |
| `recent` | 无 | `loadRecentEditorProject` |
| `get` | 必填 `projectId` | `getEditorProject` |
列表使用既有摘要视图,不能退回全量项目快照。当前接口没有分页、limit 或名称搜索参数,工具不虚构这些字段。名称匹配基于读取结果;有歧义时向用户明确候选,不静默创建替代项目。
### II. 管理画布项目
**说明:** 创建新的画布项目,或修改已有项目的名称。
| action | 关键参数 | 现有 operationId |
| --- | --- | --- |
| `create` | 可选 `title`;可选幂等键 | `createEditorProject` |
| `rename` | 必填 `projectId`、`title` | `renameEditorProject` |
一次创建仅创建项目,不默认追加创建同名素材文件夹。删除使用 XV。
### III. 查找与读取素材
**说明:** 查看账号素材库或指定项目中的资源,并为已有素材获取临时下载地址。
| action | 关键参数 | 现有 operationId |
| --- | --- | --- |
| `list_library` | 无 | `getEditorAssetLibrary` |
| `get_project_resources` | 必填 `projectId` | `getEditorProject` |
| `get_download_url` | `objectKey` 或兼容的 `legacyPublicPath`;可选 `expireSeconds` | `getExternalAssetReadUrl` |
项目资源查询复用项目详情,不新增资源查询 API。本次返回完整项目详情,资源位于其中的 resources;保持与项目读取相同的结构,不新增裁剪投影。
读取素材记录、获取文件地址与查看媒体内容是不同操作。工具不提供本地下载、关键词检索或相似素材搜索。临时签名 URL 用于访问媒体,不作为持久化生成引用。语义入口保留 REST 的 `legacyPublicPath` 可选查询字段;优先使用稳定 objectKey,至少提供一种来源,由现有 API 校验来源,不改 REST 契约。
### IV. 办理素材上传
**说明:** 获取文件直传凭证,并在调用方完成上传后确认素材对象。文件传输由调用方执行。
| action | 必填参数 | 常用可选参数 | 现有 operationId |
| --- | --- | --- | --- |
| `create_upload_ticket` | `legacyPrefix`、`fileName` | `contentType`、`pathSegments`、`maxSizeBytes` 等 | `createExternalDirectUploadTicket` |
| `confirm_upload` | `objectKey`、`assetKind` | `bucket`、`contentType`、`contentLength`、`contentHash` 等 | `confirmExternalAssetObject` |
流程为“申请凭证 → 调用方按返回的 OSS 表单直传参数上传 → 确认对象”。不把上传协议笼统写成固定 PUT。工具不接受本地路径或 base64,不代上传。对象确认后若需要项目资源或素材库记录,再调用相应登记操作;确认对象不等于完成这些登记。
`ownerUserId` 由后端账号身份决定,不作为 Agent 可选参数。
### V. 生成图片
**说明:** 根据文字和可选参考图生成图片,支持普通图片、视觉规范、角色、UI 设计、宣发素材和快速参考变体。提交后通过任务 ID 查询结果。
映射 `generateExternalEditorImage`,无需 action。
| 参数 | 约束与用途 |
| --- | --- |
| `prompt` | 必填,生成内容描述 |
| `kind` | 可选;省略表示普通图,其他用途为 `spec`、`character`、`quick-edit`、`ui-design`、`publication-material` |
| `referenceImageSrcs` | 可选参考图;具体引用和组合约束沿用 API |
| `model`、`aspectRatio`、`imageSize`、`size` | 可选输出配置,枚举及组合以当前 schema 为准 |
| `style` | 可选风格;适用范围沿用对应 kind 的现有处理 |
| `screenColor` | 可选纯色抠像背景色,见下文 |
| 目标与落位字段 | 按当前接口支持提供项目、素材库和画布目标 |
**背景色已经通过 API 开放。** screenColor 可传画布支持的颜色 hex,例如 `#CFEFFF`;传 `"auto"`、null 或省略,由服务端自动选择。它描述生成及后续抠图流程的纯色背景,不保证最终产物保留该底色;不能据此承诺“最终图片指定底色”或通用背景替换能力。
不额外引入与 kind 重复的 action;不添加未开放的 `scene` 生成入口。
### VI. 修改图片
**说明:** 对已有图片做定向修改、生成参考变体或移除背景。定向修改使用已登记资源;快速变体通过参考图生成新版本。提交后通过任务 ID 查询结果。
| action | 关键参数 | 现有 operationId |
| --- | --- | --- |
| `edit` | 必填 `sourceReferenceId`、`prompt`;其余为编辑 API 的可选字段 | `editExternalEditorImage` |
| `variation` | 必填 `prompt`;参考输入使用 `referenceImageSrcs`;其他字段复用图片生成 | `generateExternalEditorImage`,适配层固定 `kind=quick-edit` |
| `remove_background` | 必填 `sourceImageSrc`;可选 `backgroundMode`、条件允许的 `screenColor` 及目标字段 | `removeExternalEditorImageBackground` |
来源参数保留真实差异:
- edit 的 sourceReferenceId 只接受当前账号已登记的项目资源 ID 或素材 ID,不能用 objectKey 或 URL 代替。
- 去背景的 sourceImageSrc 支持当前账号拥有的稳定 objectKey、项目资源 ID 或素材 ID,只处理允许的静态图片。
- 变体的参考来源沿用生成 API;不新造含糊的统一 source 字段。其参考输入必填性、有效引用等条件沿用 quick-edit 的现有契约,不从图片生成 schema 只有 prompt 必填就推断所有用途无需前置条件。
去背景 backgroundMode 省略或 null 时默认为 `complex`。complex 做语义分割;`flat` 用于纯色背景。screenColor 仅在 flat 下可传非 null 值,支持 `auto`、`#RRGGBB`,省略或 null 自动检测;complex 下传非 null screenColor 会被拒绝。不能直接照搬图片生成的全部背景色组合规则。
需要原位替换时,编辑和去背景沿用各自 API 的 `projectId + targetLayerId` 来源绑定要求;不能用目标图层 ID 绕过账号归属与对象一致性校验。变体入口不额外承诺原位替换。
variation 与 V 的 kind=quick-edit 是合理交集,复用同一输入定义、底层请求和结果语义,不让调用方重复提供固定 kind。
### VII. 生成图标图集
**说明:** 根据已登记的参考规范和图标清单生成图集,并按指定方式尝试拆分独立图标。
映射 `generateExternalEditorIconSpritesheet`,无需 action。
- 必填:`referenceId`、`iconDescriptions`、`sliceMode`。
- referenceId 为当前账号已登记为 `icon-spec` 的项目资源或素材 ID,不接受 objectKey 或 URL 代替。
- `sliceMode=grid`:提供需求对应的 `gridX`、`gridY`,各为 1–32,不传 sliceCount。
- `sliceMode=connected-components`:可用 `sliceCount`(1–256)约束张数,不提供 gridX/gridY。
- 可选:`referenceImageSrcs`、`screenColor`、`style`、模型、比例、尺寸及目标字段。
sliceMode 没有默认值。主规范引用使用 referenceId,不用辅助 referenceImageSrcs 替代。此工具包含生成步骤,不是任意已有图片的通用裁切工具。切片未完成时可能仍有完整图集,结果必须保留 sliceWarning。
### VIII. 提取 UI 素材
**说明:** 以已有 UI 设计图为参考,生成组件素材图集并尝试拆分,供后续界面制作使用。
映射 `extractExternalEditorUiDesignAssets`,无需 action。
- 必填:`sourceImageSrc`、`aspectRatio`、`imageSize`。
- 可选:`model`、`referenceImageSrcs`、`screenColor`、`spritesheetLabel` 和对应目标字段。
- 结果命名沿用 spritesheetLabel,不强行改成其他生成接口的 assetLabel。
接口包含生成过程,不保证把原图中的组件逐像素原样裁出。与 VII 的区别是:VII 以规范和明确图标清单生成,VIII 以已有 UI 设计图提取组件语义。两者都须分别判断图集与切片结果。
### IX. 生成角色动画
**说明:** 根据角色源图和动作描述,生成角色动画预览与帧序列。
映射 `generateExternalEditorCharacterAnimation`,无需 action。
- 来源必填:`sourceLayerId`、`sourceImageSrc`、`sourceWidth`、`sourceHeight`。
- 动作必填:`promptText`。
- 输出必填:`resolution`、`ratio`、`frameCount`、`durationSeconds`、`model`。
- 可选:`screenColor`、`sourceResourceId`、目标与落位字段等。
来源与尺寸先从真实资源中取得,不为简化调用伪造图层 ID 或源图尺寸。此工具不提供已有动画文件编辑。正式动画结果按现有帧序列合同消费,不重复用第一帧手工登记一份动画。
### X. 生成视频
**说明:** 根据文字和模型支持的参考图片、视频或音频生成视频片段。
映射 `generateExternalEditorVideo`,无需 action。
- 必填:`prompt`、`model`、`aspectRatio`、`durationSeconds`、`resolution`、`mode`、`sound`。
- 可选参考:`referenceImageSrcs`、`referenceVideoSrcs`、`referenceAudioSrcs`。
- 其他可选项:当前 API 支持的 `webSearchEnabled`、目标与落位字段等。
保留 mode 作为视频业务模式,与工具级 action 无关。参考媒体、时长、分辨率、声音和其他选项的组合以所选模型的当前契约为准,不暗示每个模型支持所有组合。
### XI. 生成音频
**说明:** 生成音效或背景音乐。短声音、环境声和交互反馈选择音效;配乐选择背景音乐。
| action | 必填参数 | 常用可选参数 | 现有 operationId |
| --- | --- | --- | --- |
| `sound_effect` | `prompt` | `duration`、`loop`、`model`、目标字段 | `generateExternalEditorSoundEffect` |
| `background_music` | `gptDescriptionPrompt`、`makeInstrumental` | 目标与落位字段 | `generateExternalEditorBackgroundMusic` |
当前音效 duration 是可选字段,省略或 null 表示自动,手动时长范围为 0.5–30 秒。不沿用“必须填写时长”的过期说法。两个分支保留各自提示词字段,不另外实现一套提示词翻译协议。两者均异步受理。
### XII. 编辑画布
**说明:** 读取画布、保存完整布局,或登记供图层引用的项目资源。保存布局需要基于最新画布版本。
| action | 关键参数 | 现有 operationId |
| --- | --- | --- |
| `get` | 必填 `projectId` | `getEditorProject` |
| `save_layout` | 必填 `projectId`、`viewport`、`layers`、`expectedRevision` | `saveEditorProjectCanvas` |
| `register_resource` | 必填 `projectId`、`imageSrc`、`width`、`height`、`sourceType`;支持可选幂等键 | `createEditorProjectResource` |
save_layout 保存完整默认画布布局,不提供只传一个图层的局部 patch 语义;expectedRevision 来自最新读取,版本冲突按现有 API 处理。
register_resource 只登记资源,并不自动创建画布图层。正常生成结果落画布优先使用生成接口的 canvasCompletion。可选 objectKey、assetObjectId、assetKind、帧序列等字段按资源登记 schema 提供,不从临时 UI 状态推导正式来源。
### XIII. 整理素材库
**说明:** 管理素材文件夹和素材记录,包括新建、改名、移动以及登记已有媒体。
| action | 关键参数 | 现有 operationId |
| --- | --- | --- |
| `create_folder` | 必填 `label`;可选 `sortOrder`、幂等键 | `createEditorAssetFolder` |
| `update_folder` | 必填 `folderId`;可选 `label`、`collapsed` | `updateEditorAssetFolder` |
| `create_asset` | 必填 `folderId`、`label`、`imageSrc`、`width`、`height`、`sourceType` | `createEditorAsset` |
| `update_asset` | 必填 `assetId`;可选 `label`、`folderId` | `updateEditorAsset` |
移动素材复用 update_asset 的 folderId,不额外拆一个重复操作。可选更新字段的有效性按现有 API 处理。create_asset 只登记元数据,不上传文件、不触发生成;生成接口已经完成入库时不重复登记。删除使用 XV。
### XIV. 查看生成进度与结果
**说明:** 查询一次已提交生成任务的当前状态。完成时返回结果,失败时返回错误;尚未完成时按建议间隔再次查询。
映射 `getExternalEditorGenerationJob`,无需 action;必填 `operationId`。
- 每次调用只查询一次,不在 MCP 服务端长时间阻塞到任务完成。
- `queued/running`:读取进度和 pollAfterMs,继续等待。
- `completed`:消费 result,并保留 warning 与 sliceWarning 等真实降级信息。
- `failed`:返回已有脱敏错误。
- 跨账号任务与不存在任务沿用同一不可见语义。
收到 operationId 后优先直接查询。若提交响应丢失、尚未取得 operationId,不能靠查询工具凭空恢复:按现有幂等合同,复用原 API 操作、原请求和原幂等键重试以取得受理结果;不能换键重提。查询超时不改变服务端任务状态。
### XV. 删除资源
**说明:** 删除指定项目、素材文件夹或素材记录。按精确 ID 操作,调用前明确目标及删除范围,并取得对应用户授权。
| action | 必填参数 | 现有 operationId |
| --- | --- | --- |
| `delete_project` | `projectId` | `deleteEditorProject` |
| `delete_folder` | `folderId` | `deleteEditorAssetFolder` |
| `delete_asset` | `assetId` | `deleteEditorAsset` |
工具统一标记删除风险。项目删除沿用默认画布与项目资源元数据级联清理语义。文件夹删除将其素材记录移到默认文件夹,默认文件夹不能删除;素材删除删除记录并处理关联精选审核状态。文件夹和素材记录删除不等于删除底层 OSS 媒体。
不增加批量、按名称或模糊匹配删除,不用一个可伪造的 confirm 参数替代真实用户授权和后端鉴权。
## 5. 实现合同与验收
### 5.1 工具名与兼容
| 编号 | 工具名 |
| --- | --- |
| I | `find_canvas_projects` |
| II | `manage_canvas_projects` |
| III | `find_assets` |
| IV | `prepare_asset_upload` |
| V | `generate_image` |
| VI | `modify_image` |
| VII | `generate_icon_spritesheet` |
| VIII | `extract_ui_assets` |
| IX | `generate_character_animation` |
| X | `generate_video` |
| XI | `generate_audio` |
| XII | `edit_canvas` |
| XIII | `organize_asset_library` |
| XIV | `check_generation` |
| XV | `delete_resources` |
现有 MCP 工具和 REST API 全部保留;旧工具名称、schema、注解、调用行为和可见性不变。新工具直接追加进同一个 tools/list,不加开关、不改旧工具前缀、不设隐藏目录。本轮包括工具说明和参数、instructions、resources 及其共用的下载 Skill 文档;独立配套 Skill、CLI、发布页与部署不属于本轮。
### 5.2 参数与结果
- 字段 schema 从对应现有 OpenAPI operation 构造,展开本地引用并保留嵌套类型、枚举、条件和字段说明。路径、查询与 body 字段平铺到单功能工具顶层或多功能工具的 input;不另写一份业务字段全集。
- 多功能工具以顶层 object 加 oneOf 表达互斥 action,每个分支含 action 常量和完整 input schema。input 必填,无业务参数时传 `{}`。idempotencyKey 仅放在工具顶层:九类生成必填,项目创建、资源登记、文件夹创建可选,其它 action 不接受。
- MCP 适配层在分派前校验 action、信封字段、所选分支允许的字段、必填参数及直接字段的类型/枚举/常量;嵌套字段值和业务组合继续由现有 REST DTO/校验处理,不创建第二套业务验证器。协议 schema 与分派校验共同限制错分支参数;未知顶层或 input 字段不能被静默丢弃。
- variation 不暴露 kind,固定注入 `quick-edit`;对象确认不暴露或接受 ownerUserId。其余公开字段全部保留。稳定 key、原 API 与规范请求保持一致,重复语义入口不新增幂等命名空间。
- 项目列表固定 summary。get_project_resources 返回完整项目响应,不裁剪;下载地址保留 objectKey、legacyPublicPath 与 expireSeconds。所有结果复用现有成功解包与结构化错误处理,不裁剪告警、revision 冲突或异步结果。
- 读取工具标记 readOnly;含删除、覆盖或移动已有状态的工具标记 destructive;生成类说明付费及异步语义,可能替换已有图层的生成工具也按潜在破坏性标记。openWorld 仅用于会访问外部服务的能力,不作为付费标志。注解不替代用户授权或后端校验。
- 语义工具的 destructive 按 API operation 显式声明,并取各 action 风险的并集,不根据 HTTP 方法或参数字段名推断。`prepare_asset_upload/confirm_upload` 可以更新同一 owner 的已有对象元数据,因此整个上传工具标记 destructive;申请上传票据本身仍属于新增操作。新增 operation 必须补充风险声明,原有工具的注解保持兼容。
### 5.3 验收
验证工具目录追加与旧定义一致、全部 action 路由与字段位置、必填和错分支拒绝、可选/必填幂等头、quick-edit 与普通生成入口等价、原 owner/scope 边界、结构化结果/告警和 revision 冲突透传。运行 api-server 定向测试与本地 healthz smoke;真实账号、付费 Provider 和具体 MCP 客户端尚未验证时明确记录,不能将单元测试当作线上验收。
### 5.4 实现与验收结果(截至 2026-09-24)
本轮工具、instructions 和 resources 更新完成。新增 15 个语义工具与原有 29 个工具同时可见,7 个资源 URI 保持不变。自动回归与本地业务主路径验证通过;真实生成测试发现的两项既有合同问题已独立记录在 [Issue #495](https://git.genarrative.world/git/GenarrativeAI/Genarrative/issues/495),不在本轮修复,不能将主路径通过表述为全部合同验收通过。已完成的里程碑和实施计划收口到本节。
| 验证范围 | 证据与结果 |
| --- | --- |
| 工具与参数合同 | `external_` 回归中的 25 项 `external_mcp` 测试通过,覆盖 44 个工具、31 个 action、旧定义保留、schema 展开、参数映射、错分支拒绝、必填/可选幂等、等价入口及 15 个语义工具的风险注解 |
| 认证与既有 API 回归 | 2026-09-24 在包含最新 master、instructions、resources 和风险注解修复的分支上运行 `cargo test --locked -p api-server external_`:156 项通过,含 MCP 内外认证、scope 拒绝、跨 owner 隔离、异步及 OpenAPI 回归 |
| 编译与文本检查 | `cargo check --locked -p api-server`、定向 rustfmt、文档索引、编码和 diff 检查通过 |
| 本地服务启动 | 先通过 `npm run dev:spacetime` 启动 SpacetimeDB 2.8.3 并发布隔离数据库,再通过 `npm run dev:api-server` 启动同一目标的 API 和 worker;`/v1/ping`、`/healthz`、`/readyz` 均返回 200 |
| MCP 真实 HTTP 链路 | 未认证返回 401;使用隔离数据库中的临时测试 API Key,initialize 成功,tools/list 返回 44 个工具且包含全部 29 个旧工具,resources/list 返回原有 7 个资源 |
| 本地数据库读写 | 新工具创建/列表/重命名/读取/删除成功;同键重复创建返回同一项目;旧工具读写与新工具互通;重复语义读取保留完整项目;ownerUserId 额外字段被拒绝且未产生写入 |
补充验证:
| 验证范围 | 证据与结果 |
| --- | --- |
| 全工具业务主路径 | 2026-09-23 通过本地 MCP HTTP 实测 44 个工具,全部至少一次主路径成功;15 个语义工具及其 action 均执行,含实际 OSS 上传/确认/下载、资源登记、素材整理和画布 revision 保存 |
| 真实付费生成 | 11 个任务全部 completed,覆盖普通图、编辑、变体、抠图、图标图集、UI 提取、角色动画、视频、音效、背景音乐及旧抠图入口;验证下载、媒体解码和持久化,无 warning/sliceWarning。动画持久化为 32 帧、4000ms;图集与 UI 用例分别产生 2 张和 1 张切片。两项额外合同检查失败另见 Issue #495 |
| instructions/resources 与下载包 | 更新后 24 项 MCP 测试、2 项 Skill 归档及 manifest 摘要测试通过;10 个 JSON 示例可解析,其中 6 个业务请求示例通过现有 OpenAPI schema 校验;Skill 格式、编码、文档索引和 diff 检查通过 |
运行核验使用独立测试身份和数据库,不经过真实用户登录或外部账号开通流程。初次 CRUD smoke 的测试项目和凭证已清理;后续付费全工具测试使用另一组独立凭证与产物,并在该轮结束时保留供复核,凭证不进入仓库。付费测试基于工具实现版本 `23b2a3325`;随后更新说明文档并完成合并后自动回归,未再次触发付费生成。服务当前是否运行需另行检查,本文不作为进程状态记录。
未验证:真实用户登录/发放 API Key 全流程、外部 MCP 客户端对 oneOf 参数的展示与使用、远端部署、多模型和全部参数组合、容量压测与完整视觉质量。UI 提取使用简单测试图片,不代表复杂 UI 多组件质量验收。合并后仍需部署 API server,并通过实际 MCP 客户端核验工具发现、调用和资源读取。
### 5.5 instructions 更新(2026-09-24)
初始化响应的 instructions 只描述服务能力和跨工具共同约定,不引入“语义工具 / 原有工具”分类,也不重复单个工具的参数分支。具体参数以工具 schema 和说明为准,详细流程由 resources 提供。
当前说明覆盖:按任务需要创建项目和素材文件夹、通过生成目标字段落画布或素材库且避免重复登记、付费异步提交与稳定幂等键、使用 `check_generation` 按返回间隔轮询、本地文件上传票据与实际传输分工、稳定媒体引用、按真实结果和告警判断完成情况。
不再要求所有任务先创建同名素材文件夹或同时写入画布与素材库。查询超时不代表生成失败,不应因此重新提交或更换幂等键。
权威文案位于 `external_mcp.rs` 的 `MCP_INSTRUCTIONS`,初始化响应与 `genarrative://external-editor/usage` 共用同一内容。该步骤只更新说明,不改变工具和 API;其它 resources 的内容更新见下一节。
### 5.6 resources 内容更新(2026-09-24)
沿用现有 7 个资源 URI。usage 共用 instructions,OpenAPI 继续提供现有 REST 契约;Skill 主入口和四份 references 按当前工具能力更新,不改变工具、路由、鉴权或 DTO。
- 主入口:服务能力、必要共同约定和按需阅读导航;不要求固定项目、同名文件夹或双重落库。
- capability-routing:按意图选择工具与 action,说明上传、登记、生成、落画布之间的边界。
- api-operations:工具到现有 REST 操作的映射,保留直接 REST 调用所需信息。
- authentication-and-safety:MCP 与 REST 凭证配置、上传分工、幂等键位置、重试与删除范围。
- requests-and-outputs:单功能及多功能 MCP 参数示例、异步轮询、按需指定生成目标、完整布局保存和结果/告警读取。
文档源位于 `.codex/skills/genarrative-external-editor-api/`,由 MCP resources 与下载的 Skill 包共用;本次不修改 Python helper、独立 CLI 或附件中的配套 Skill。说明仍随 API server 编译发布,不新增独立文档托管机制。验收覆盖资源读取、Skill 归档内容及 manifest 摘要一致性,并检查示例与当前 schema。
## 6. 核对入口
- [External v1 OpenAPI](../openapi/genarrative-external-v1.openapi.json):operation、参数、请求体、响应与公开字段权威。
- [External API 路由](../../server-rs/crates/api-server/src/modules/external_api.rs):实际路由挂载。
- [MCP 实现](../../server-rs/crates/api-server/src/external_mcp.rs):工具生成、schema、资源和进程内分派。
- [语义工具适配](../../server-rs/crates/api-server/src/external_mcp/semantic.rs):新增工具、action、同源 schema 与参数映射。
- [工具说明](../../server-rs/crates/api-server/prompts/external_mcp/semantic_tools.json):新增工具的中文用途、操作和结果说明。
- [External 编辑器接口](../../server-rs/crates/api-server/src/external_editor_api.rs):鉴权、请求处理与生成受理。
- [AGC 抠图模式与背景色透传](./【技术方案】AGC抠图模式与背景色透传-2026-09-16.md):去背景模式与背景色已有合同。
参数合同按 2026-09-23 的实现核对,说明与验收状态于 2026-09-24 收口;后续变更仍以代码与 OpenAPI 为准,并同步修订本文,不维护第二份脱离 API 的字段真相。
@@ -46,7 +46,7 @@ Date: 2026-09-21
| 现有记录 | 当前事实 | 本阶段复用方式 |
| --- | --- | --- |
| `.agent/conversations/project.jsonl` | Game Agent 正式对话历史,包含正文 | 复用正式受理、终态的业务入口;不复制正文到埋点 |
| `.agent/runtime/direct-codex/turns/<clientTurnId>.jsonl` | 回合工具审计,条目有上限,写入可失败 | 参考执行事实;不能把其条数作为完整产品事件数量 |
| `.agent/runtime/direct-codex/turns/<clientTurnId>.jsonl` | 已停用的平行审计日志,旧项目可能残留;新回合不再生成 | 不作为现役埋点来源或完整产品事件计数依据,不要求补写或回填 |
| `.agent/agent.db` | 逐行 JSON 本地索引与审计,非 SQLite | 保持原用途,不作为待上传队列 |
| `.agent/design-agent/session.json` | 策划会话、对话、工具结果、阶段、审批、当前回合与恢复状态 | 审批通过且实际推进阶段成功持久化后记录成果事件;不上传完整会话文件 |
| `design_artifacts/` | 正式策划成果 | 保持原文件保存行为,本版不为统计新增 revision;文件内容不进入事件 |
@@ -473,7 +473,7 @@ session.json 是本地恢复元数据,不是待上传事件;事件文件不
- 生命周期与配置:`apps/ai-game-creator-shell/src-tauri/src/main.rs`、`config.rs`、`platform_session.rs`。
- 项目创建与打开:`apps/ai-game-creator-shell/src-tauri/src/commands.rs`、`src/features/app-shell/useHomeProjectCreation.ts`;离开登记由 `WorkspaceLauncher.tsx` 保持。
- Direct 前端尝试与终态确认:`apps/ai-game-creator-shell/src/view/project-development/chat/controller/useDirectProjectChatController.ts`;沿用 `services/clientAnalytics.ts` 冻结账号代次、每次原生重试生成 attempt ID、只确认最后一次尝试。不在已退役的 App 聊天状态链恢复接线。
- Direct 执行与审计:`apps/ai-game-creator-shell/src-tauri/src/agent/direct_runtime/user_input.rs`、`agent/direct_runtime/mod.rs`、`agent/direct_codex_audit.rs`。
- Direct 执行与现役历史:`apps/ai-game-creator-shell/src-tauri/src/agent/direct_runtime/user_input.rs`、`agent/direct_runtime/mod.rs`、`agent/direct_project_history.rs`;旧 `direct_codex_audit.rs` 已删除,不作为埋点接入点。
- Design 执行与持久化:`apps/ai-game-creator-shell/src-tauri/src/agent/design_runtime.rs`、`agent/runtime_protocol/design_session.rs`、`agent/design_tools.rs`。
- revision:`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/project_gates.rs`,结合各实际写入调用方。
- 预览与保存:`apps/ai-game-creator-shell/src-tauri/src/preview.rs`、`ui_editor/persistence.rs`、`project/checkpoint.rs`。
@@ -1,10 +1,10 @@
# 立项策划 Agent(Fast GDD)技术方案
- 日期:2026-08-10
- 状态:**已退役**。本文描述的 V1 策划链路(`project-supervisor-plan` 根 Run、`project-planning` 子 Agent、`plan.submit_gdd` 工具、Fast GDD 审批门禁与恢复机制)已由策划会话 Runtime V2 取代,源码已于 2026-09 按四不写原则整体删除;现行方案见 `【技术方案】策划会话RuntimeV2接入与旧链路退役-2026-09-03.md`。本文仅作为历史推导记录保留。
- 状态:**历史方案,已退役**。策划 V1 和曾接替它的 Runtime V2 均已删除,V2 不是现行方案。当前策划入口统一使用独立 Design Agent,见[策划 Agent 生产迁移与工作区浏览](./【技术方案】策划Agent生产迁移与工作区浏览-2026-09-10.md)。本文仅供历史追溯,不要求恢复旧 Runtime、审批、工具、身份门禁、持久化协议或专属测试。
- 适用范围:AI 游戏创作独立 App、Project Supervisor、Agent Runtime、本地项目策划 sidecar 与后续完整构建准入
> 当前口径(2026-08-30):以本文件中标注的 D11 / 最新修订和当前 `apps/ai-game-creator-shell` 实现为准。D6~D9 等被明确标注为作废或被取代的段落仅保留推导背景,不得作为现行拓扑、入口或 Runtime 真相;产品入口与 DirectProject 总体口径见 `docs/README.md` 和 App 实施计划。
> 历史内容边界(2026-09-23):下文的 D11、版本修订、“当前”“必须”和验收要求均描述退役前的 V1,不能覆盖现役 Design Agent 合同。包括 exact planning lifecycle v3、`planningSessionBinding` 和 `plan.submit_gdd` 在内的旧要求,不构成恢复实现或保留孤立代码的依据。
## 1. 背景与目标
@@ -1,10 +1,12 @@
# 策划 Agent 生产迁移与工作区浏览方案
更新时间:2026-09-21
更新时间:2026-09-23
状态:已完成(2026-09-18)
> 现状说明(2026-09-18):本文记录的迁移已完成,当前策划入口统一使用 Design Agent。旧 Planning V1/V2 会话、专用命令、审批卡和展示适配已删除;文中提到的 V2 文件仅代表迁移时的参考来源,不得作为现行实现、回退路径或测试迁移目标。
策划 V1 和 V2 的退役均已确定,不再作为待实施迁移。旧 `plan.submit_gdd`、planning session binding、exact planning lifecycle v3、V1 专属工具身份白名单与 V2 IPC/Runtime 均不属于当前合同。清理孤立常量、未用参数、包装和旧说明时,不为满足这些历史要求恢复代码或迁移专属测试。共享锁、通用持久化、资源权限和当前 Design Agent 的会话、澄清、阶段审批按实际现役调用保留;用户已有文件不因源码清理而删除。
## 1. 目标
将 `local-scripts/design_agent_refactored` 中已经验证的自由协作型策划 Agent 迁移到生产 App。生产代码只提供可靠的运行基础设施,Agent 的工作方式以原型为准。
@@ -4,7 +4,7 @@
- 状态:**历史方案,已完成并退役**。Runtime V2 及其专用入口、命令、展示和测试已在 2026-09 按四不写原则删除;当前“做方案”统一使用独立 Design Agent。
- 适用范围:历史 AGC“做方案”入口、策划会话、GDD 产物与审批设计
> 本文只用于追溯 Runtime V2 的设计和退役过程,不是现行实现依据。不要恢复 `planning_session_v2`、`planning_policy_v2`、`hydrate_planning_session_v2` 或 V2 专用 UI;当前行为以 Design Agent 生产迁移方案和代码为准。
> 本文只用于追溯 Runtime V2 的设计和退役过程,不是现行实现依据。策划 V1、V2 均已删除,不保留兼容别名、双跑或回退路径;不要恢复 `planning_session_v2`、`planning_policy_v2`、`hydrate_planning_session_v2`、专属审批/UI 或旧测试。当前行为以[Design Agent 生产迁移方案](./【技术方案】策划Agent生产迁移与工作区浏览-2026-09-10.md)和代码为准,下文的版本要求与验收清单仅描述历史实现。
## 1. 决策摘要
@@ -2,11 +2,11 @@
## 目标
UI 编辑器的“分析参考图”“识别界面结构”“自动切分素材”三个工作流动作在每次运行结束后,用独立的阻塞通知弹窗明确反馈结果,避免仅依赖卡片内一行状态文本而被忽略。
UI 编辑器的“识别界面结构”“自动切分素材”两个工作流动作在每次运行结束后,用独立的阻塞通知弹窗明确反馈结果,避免仅依赖卡片内一行状态文本而被忽略。
## 交互约定
- 三个动作的每次运行在终态(成功或失败)时自动弹出一次通知。
- 两个动作的每次运行在终态(成功或失败)时自动弹出一次通知。
- 弹窗打开期间遮挡并阻塞工作台底层交互;关闭后恢复当前步骤,不自动切换步骤、不自动重跑。
- 使用现有 `ThemedModal` 的普通关闭行为(遮罩、Esc 和关闭按钮均可关闭)。
- 弹窗仅承载通知,不提供“继续”“重试”或其他业务操作。
@@ -19,7 +19,6 @@ UI 编辑器的“分析参考图”“识别界面结构”“自动切分素
成功状态的基线文案:
- 分析参考图:保留已应用的语义建议数量;若现有状态可可靠取得问题/待确认数量,则一并展示。
- 识别界面结构:保留替换的界面树数量,并展示识别结果中的待检查/必须修复数量(若可取得)。
- 自动切分素材:保留现有 `B/B` 批次计数,改为用户可读的切分结果。
@@ -28,12 +27,12 @@ UI 编辑器的“分析参考图”“识别界面结构”“自动切分素
## 实现边界
- 新增独立的工作流通知弹窗组件文件,组件只负责展示和关闭,不包含工作流领域规则或后端副作用。
- 在 UI 编辑器页面/会话投影中维护临时通知状态,并在三个异步动作的成功与失败终态写入。
- 在 UI 编辑器页面/会话投影中维护临时通知状态,并在两个异步动作的成功与失败终态写入。
- 不新增后端字段或公开契约;数量只能使用当前前端已有且可靠的数据。
## 验收
1. 三个动作成功和失败终态各弹出一次通知;绑定批次只弹最终一次。
1. 两个动作成功和失败终态各弹出一次通知;绑定批次只弹最终一次。
2. 弹窗打开时底层工作台不可操作,且无继续/重试等业务按钮。
3. 弹窗可通过标准关闭方式退出;关闭后卡片状态仍可见。
4. 每条成功文案保留原有数量信息并增加可用的检查数量,所有文案包含“请检查”。