合并 origin/master 到 feat/tribo3d-integeration:ts-rs 一律常开、Cargo 清单去重
- server-rs/Cargo.toml:删除与 workspace git 固定重复的 registry `ts-rs = "12.0.1"`,保留固定 commit 并写明「ts-rs 一律常开、绑定命令固定为 cargo test -p shared-contracts export_bindings」 - server-rs/crates/shared-contracts/Cargo.toml:删除 master 侧新增的 `ts-bindings` feature,`ts-rs` 保持常开依赖 - server-rs/crates/shared-contracts/src/game_creation_app/asset_kind.rs:去掉 `cfg_attr(feature = "ts-bindings", …)` 门控,derive 与字段级 `ts(...)` 无条件展开 - apps/ai-game-creator-shell/src-tauri/Cargo.toml:shared-contracts 依赖去掉 `features = ["ts-bindings"]`,继续保留本分支的 ts-rs git 固定 - apps/ai-game-creator-shell/src-tauri/Cargo.lock:按上述依赖变更同步(shared-contracts 指向 git 源 ts-rs) - docs:同步 5 处绑定生成命令(去掉 `--features ts-bindings`),并在 decision-log 记本次合并的决策、代价与验证 - 其余暂存改动为 origin/master 带入的内容,冲突按两侧目的逐一合并 验证:cargo metadata --locked(server-rs 与 AGC 两个 workspace)、cargo test -p shared-contracts(119 + 5 + 5 + 2 + 2 全绿且无告警)、npm run contracts:model3d:generate 后 packages/shared 零 diff、cargo check -p spacetime-module --target wasm32-unknown-unknown、npm run check:encoding、npm run check:rustfmt、npm run check:spacetime-schema、git diff --check、暂存集 eslint / prettier 全过
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
|
||||
## 1. 一句话
|
||||
|
||||
功能画布 = 资源栏目页;栏目页左下角渲染一条由栏目 `category` 决定的工具栏,图片类入口走本地 `start_local_project_asset_generation`(提交即返回、后台生成),音频入口复用既有无源生成链路,上传复用 `upload_local_asset`;生成 / 上传成功后一律走既有 manifest 刷新与资源定位。
|
||||
功能画布 = 资源栏目页;栏目页左下角渲染一条由栏目 `category` 决定的工具栏,图片类与音频入口都走本地 `start_local_project_asset_generation`(提交即返回、后台生成,见 2026-09-20 音频并入后台任务账本),上传复用 `upload_local_asset`;生成 / 上传成功后一律走既有 manifest 刷新与资源定位。
|
||||
|
||||
## 2. 入口矩阵(事实源)
|
||||
|
||||
@@ -31,20 +31,22 @@
|
||||
|
||||
## 4. 请求载荷映射
|
||||
|
||||
| 入口 | IPC | 载荷要点 |
|
||||
| --------------------- | --------------------------------- | ------------------------------------------------------------------------------------------- |
|
||||
| 生成图片 | `start_local_project_asset_generation` | `kind: 'image'`,比例 / 尺寸可调,默认 `1:1 · 1K` |
|
||||
| 生成角色形象 | 同上 | `kind: 'character'`,默认 `1:1 · 1K` |
|
||||
| 生成规范 → 图标规范 | 同上 | `kind: 'icon-spec'`,固定 `1:1 · 1K`;缺权威规范图时 `outputPath: 'assets/art-spec.png'` |
|
||||
| 生成规范 → 角色规范 | 同上 | `kind: 'spec'`,固定 `1:1 · 1K` |
|
||||
| 生成规范 → 自定义规范 | 同上 | `kind: 'spec'`,固定 `1:1 · 1K` |
|
||||
| 生成图标素材 | 同上 | `kind: 'art-spritesheet'`,默认 `1:1 · 1K`;前置:已登记 `assets/art-spec.png` |
|
||||
| 生成 UI 设计图 | 同上 | `kind: 'ui-prototype'`,默认 `16:9 · 1K`;前置同上 |
|
||||
| 生成背景音乐 | `derive_local_project_resource` | `editKind: 'background-music'`、`generationMode: 'create'`、`sourceMediaType: 'audio/mpeg'` |
|
||||
| 生成音效 | `derive_local_project_resource` | `editKind: 'sound-effect'`、其余同上 |
|
||||
| 上传 | `upload_local_asset` | `{ projectPath, fileName, mediaType, bytes }` |
|
||||
| 入口 | IPC | 载荷要点 |
|
||||
| --------------------- | -------------------------------------- | ------------------------------------------------------------------------------------------- |
|
||||
| 生成图片 | `start_local_project_asset_generation` | `kind: 'image'`,比例 / 尺寸可调,默认 `1:1 · 1K` |
|
||||
| 生成角色形象 | 同上 | `kind: 'character'`,默认 `1:1 · 1K` |
|
||||
| 生成规范 → 图标规范 | 同上 | `kind: 'icon-spec'`,固定 `1:1 · 1K`;缺权威规范图时 `outputPath: 'assets/art-spec.png'` |
|
||||
| 生成规范 → 角色规范 | 同上 | `kind: 'icon-spec'`,固定 `1:1 · 1K`;与图标规范 / 自定义规范共用同一条规范通道 |
|
||||
| 生成规范 → 自定义规范 | 同上 | `kind: 'icon-spec'`,固定 `1:1 · 1K` |
|
||||
| 生成图标素材 | 同上 | `kind: 'icon-spritesheet'`,默认 `1:1 · 1K`;前置:已登记 `assets/art-spec.png` |
|
||||
| 生成 UI 设计图 | 同上 | `kind: 'ui-design'`,默认 `16:9 · 1K`;前置同上 |
|
||||
| 生成背景音乐 | `start_local_project_asset_generation` | `kind: 'background-music'`、`idempotencyKey`;任务 id 即该次生成的 operation id |
|
||||
| 生成音效 | `start_local_project_asset_generation` | `kind: 'sound-effect'`、其余同上 |
|
||||
| 上传 | `upload_local_asset` | `{ projectPath, fileName, mediaType, bytes }` |
|
||||
|
||||
`start_local_project_asset_generation` 的完整参数是 `{ projectPath, projectId, taskId, kind, prompt, aspectRatio, imageSize, assetName, outputPath }`(Rust `src-tauri/src/asset_generation_tasks.rs`),**提交即返回**一条任务记录(`taskId / status / phaseDetail / assetId / error` 等);生成在 `tauri::async_runtime::spawn` 出来的后台任务里跑,账本落在项目内 `.agent/runtime/asset-generation-tasks/tasks.json`,进度由 `list_local_project_asset_generations` 读回。截图面板不再「等生成结束」,所以**点「生成」即同步关闭面板**(不等 IPC),面板内不出现阶段文案;只有「点击瞬间就失败」才带草稿重开(关闭 ≠ 取消)。
|
||||
`start_local_project_asset_generation` 的完整参数是 `{ projectPath, projectId, taskId, kind, prompt, aspectRatio, imageSize, assetName, outputPath, idempotencyKey }`(Rust `src-tauri/src/asset_generation_tasks.rs`);图片类入口沿用 `{ projectPath, projectId, taskId, kind, prompt, aspectRatio, imageSize, assetName, outputPath }` 逐字不变,音频入口只带 `{ projectPath, projectId, taskId, kind, prompt, assetName, idempotencyKey }`(`idempotencyKey` = 该次生成的幂等键,任务 id 即 operation id;原生命令内部仍复用既有音频无源生成链路,不新增平台路由与请求体口径),**提交即返回**一条任务记录(`taskId / status / phaseDetail / assetId / error` 等);生成在 `tauri::async_runtime::spawn` 出来的后台任务里跑,账本落在项目内 `.agent/runtime/asset-generation-tasks/tasks.json`,进度由 `list_local_project_asset_generations` 读回。截图面板不再「等生成结束」,所以**点「生成」即同步关闭面板**(不等 IPC),面板内不出现阶段文案;只有「点击瞬间就失败」才带草稿重开(关闭 ≠ 取消)。
|
||||
|
||||
入参 `kind` 一律是共享 `GameCreationAppAssetKind` 的 canonical 成员(图片类 `image` / `character` / `icon-spec` / `icon-spritesheet` / `ui-design` / `publication-material`,音频 `sound-effect` / `background-music`);`spec` / `art-spritesheet` / `ui-prototype` 这类平台请求词汇只由 Rust 侧派生到 `source.generationKind`,不再出现在 IPC 载荷或 manifest kind 里。
|
||||
|
||||
## 4a. 本地排队与「生成任务」侧栏
|
||||
|
||||
@@ -57,8 +59,10 @@
|
||||
- **工具条动作行的间距只有一套**:行内按钮之间统一 24px(行 `gap: 7px` + 按钮左右 `padding: 11px`;「依赖 / 类型」分段控件内部是 0 间距的连通分段,靠 `margin-left: 17px` 与前一按钮保持同样的 24px)。**布局状态提示(`game-resource-reorder-status`)不进这一行**——它是随保存过程变长的文案(空 →「保存中」→「布局已保存」→ 失败原因),作为 flex 子项会把右侧按钮按文本宽度顶开,同一行不同时刻的按钮间距就会不一样;它改为工具条内的绝对定位,位置固定。
|
||||
|
||||
- **复用**:比例 / 尺寸选项与默认值来自网页端美术画布的纯模型 `src/components/image-editor/ImageCanvasGenerationModel.ts`(`EDITOR_IMAGE_DIMENSION_OPTIONS` / `IMAGE_MODEL_NANOBANANA2`),尺寸标签复用 `resolveEditorImageSizeLabel`;提示词上限复用 AGC 自己的 `resourceEditPromptMaxLength`(图片类默认 32000,与 Rust `LOCAL_PROJECT_ASSET_MAX_PROMPT_CHARS` 同口径);「AI 润色」复用 `ResourcePromptPolishSlot`(`polish_local_project_prompt`);面板外壳复用 AGC 的 `ThemedModal`(与「生成素材」面板同一套宿主 chrome)。
|
||||
- **参考图(`@` 引用)**:提示词输入区就是聊天 / 快速编辑同一个共享组件 `ResourceReferenceInput`(`@` 按钮的文案是「插入素材引用」,候选集只放当前项目已登记的位图,SVG 与未落盘条目排除)。是否挂它只由**一条**判据决定:`resourceCanvasAssetGenerationAcceptsReferences`(前端)与 `platform_art_asset_kind_accepts_user_reference_assets`(Rust)——**唯一例外是图集 `icon-spritesheet`**(合同只接受单张权威规范引用,原生对多余参考是显式拒绝)。图标规范 `icon-spec` 虽然产出权威规范图,但生成时同样可以带参考,上限与普通生成一致(5 张;带规范前置的入口是「权威规范图 1 张 + 用户参考 4 张」)。面板里那句「图标规范不给选择器」是 kind 词汇收敛前的残留注释,已按现行判据改写,并补了「逐条入口的 `@` 与判据一致」的用例防止再漂。
|
||||
- **图集(生成图标素材)为什么没有 `@`**:图集走的是**另一条平台通道** `POST /api/external/v1/editor/icon-spritesheets/generations`,客户端提交体是 `referenceId`(唯一一张独立权威规范图)+ `iconDescriptions` + 切片参数(`sliceMode` / `sliceCount` / `gridX` / `gridY` / `screenColor`),**没有** `referenceImageSrcs`;结果绑定阶段还硬校验「参考集合恰好一张、且不等于产物自身」(`strict spritesheet 图集必须绑定唯一且独立的 art-spec resourceId`)。也就是说图集的「参考」在客户端合同里被定义为那一张权威规范图,图标内容靠 `iconDescriptions`(由提示词派生)描述。因此前端不给选择器、原生对用户参考是**显式拒绝**(错误原文:`透明美术图集只接受规范图引用,不接受用户参考素材`),而不是静默丢弃;面板里那句「该入口走平台图集通道……」就是这条口径的当面说明。**可行但未做**:平台 OpenAPI 的 `EditorIconSpritesheetGenerationRequest` 里**有** `referenceImageSrcs` 字段(客户端目前不发),所以「放开图集的用户参考」是产品 + 平台口径变更,需要同时改 Rust(判据 / 请求体 / 结果绑定校验与上限)与前端,不在本轮范围内。
|
||||
- **收窄**:本地 IPC 的比例白名单是 `1:1 / 2:3 / 3:2 / 9:16 / 16:9`,网页端还有 `4:3`。前端做的是「主站纯模型 ∩ 本地白名单」,不是另抄一份选项表(测试钉住两者包含关系与 `4:3` 缺席)。
|
||||
- **没有复用到的**:网页端的生成 composer 子视图(`ImageCanvasBasicGenerationComposerView` / `ImageCanvasCharacterGenerationComposerView` / `ImageCanvasIconSpritesheetComposerView` / `ImageCanvasSpecGenerationPanelView` / `ImageCanvasGenerationImageOptionsView`)。原因有二:① 它们的比例选项含本地通道拒绝的 `4:3`;② 它们自带模型选择器、参考图槽位、BFF 直连(`ImageCanvasSpecGenerationPanelView` 直连 `/api/editor/llm/icon-specs/*`,AGC 无该通道),本地 IPC 既没有 `model` 也没有参考图入参,照搬会渲染一批改不了请求的假控件。因此面板是 AGC 自己的薄壳,只把**纯模型**接进来。
|
||||
- **没有复用到的**:网页端的生成 composer 子视图(`ImageCanvasBasicGenerationComposerView` / `ImageCanvasCharacterGenerationComposerView` / `ImageCanvasIconSpritesheetComposerView` / `ImageCanvasSpecGenerationPanelView` / `ImageCanvasGenerationImageOptionsView`)。原因有二:① 它们的比例选项含本地通道拒绝的 `4:3`;② 它们自带模型选择器与 BFF 直连(`ImageCanvasSpecGenerationPanelView` 直连 `/api/editor/llm/icon-specs/*`,AGC 无该通道),本地 IPC 没有 `model` 入参,照搬会渲染一批改不了请求的假控件。参考图入参**不属于**这类缺口:AGC 侧由 `referenceAssetIds` 交原生按当前账号重新绑定(见上一条),面板照常呈现 `@`。因此面板是 AGC 自己的薄壳,只把**纯模型**接进来。
|
||||
- **不做的**:不改 Rust、不改 external v1 / OpenAPI、不改 `packages/`(共享包只被消费)、不复制主站 `src/components/image-editor/` 目录。
|
||||
|
||||
## 6. 前置规范图与不可用原因
|
||||
@@ -66,12 +70,14 @@
|
||||
- 判据与 Rust `canonical_art_spec_reference_at` 逐字对齐(`projectHasIconSpecReference`):manifest 里必须存在 `localPath === 'assets/art-spec.png'`、`kind === 'icon-spec'`、`mediaType` 以 `image/` 开头、`source.kind === 'canvas'` 的资产。
|
||||
- 缺前置时入口**保持可点击**(`aria-disabled="true"`,不是原生 `disabled`),点击后在工具栏上沿给 `role="alert"` 的原因说明:`需要先完成并登记图标规范(assets/art-spec.png);请先用「生成规范 → 图标规范」生成`;此时**不发任何生成请求**。
|
||||
- 「图标规范」入口在缺前置时把 `outputPath` 指向 `assets/art-spec.png`,让前置条件能由工具栏自身满足;已有权威规范图时 `outputPath` 为 `null`。这条策略对应 Rust 侧硬约束:`prepare_local_project_asset_generation` 的 `replace_existing` 固定为 `false`,指向已存在文件会被拒绝(`图片生成 outputPath 已存在,禁止静默覆盖`)。
|
||||
- 已知能力缺口(本轮不扩接口,记账备查):本地 IPC 没有 ① `model` 入参(面板因此不给模型选择)、② `specType` 入参(角色规范与自定义规范共用 `spec` 通道,靠素材名与提示词区分)、③ `replaceExisting` 入参(因此不能重写已存在的权威规范图)。
|
||||
- 已知能力缺口(本轮不扩接口,记账备查):本地 IPC 没有 ① `model` 入参(面板因此不给模型选择)、② `specType` 入参(角色规范与自定义规范共用 `icon-spec` 通道,靠素材名与提示词区分)、③ `replaceExisting` 入参(因此不能重写已存在的权威规范图)。
|
||||
|
||||
## 7. 音频入口收敛
|
||||
|
||||
- 音频画布的「生成背景音乐 / 生成音效」由工具栏承载:`ResourceCanvasGenerationPanelView` 新增 `kinds` prop,工具栏各自只放行自己那一种,单类型入口不再渲染类型选择器,面板标题与提交文案跟该类走。
|
||||
- 既有「生成素材」浮层入口传 `kinds={['video']}`,只保留视频;视频能力不删,只是不出现在工具栏里。
|
||||
- 音频入口与图片类入口共用同一份后台任务账本(2026-09-20):提交即返回任务记录、面板同步关闭、状态与阶段文案只由后端账本提供、任务出现在「生成任务」侧栏并在重开项目后恢复。音频任务的**请求身份**(operation id 与幂等键)随任务一起走:任务 id 即 operation id,重试复用同一对身份,不会变成第二次付费生成;音频面板与图片类面板一致,只有「点击瞬间就失败」(未受理)才带原草稿与原请求身份重开。
|
||||
- 音频生成在客户端后台跑时仍复用既有音频无源生成链路(`generationMode: 'create'`、`editKind` = `sound-effect` / `background-music`、源快照媒体类型由 `editKind` 推为 `audio/mpeg`——Create 模式不读 `sourceMediaType` 入参、平台路由与请求体口径不变);音频生成没有可播报的中间进度,因此阶段文案仍是账本的「排队中。」/「正在生成。」/「生成已完成。」/失败原因四档,不新增假阶段。
|
||||
|
||||
## 8. 验证
|
||||
|
||||
@@ -91,6 +97,7 @@ cargo test -p genarrative-ai-game-creator-shell asset_generation_task
|
||||
|
||||
- 模型层:四个栏目的工具集与顺序、未命中栏目返回空、「所有资源」与 `null` 返回空、二级菜单分流、比例 / 尺寸白名单收窄、前置判据、不可用原因、`outputPath` 策略。
|
||||
- 组件层:工具栏渲染与二级菜单开合、不可用入口可点击 + 原因可关闭、上传回调。
|
||||
- 生成任务层:`resourceCanvasAssetGenerationTaskModel`(状态机 / 在途判据 / 恢复 / 耗时文案)、`resourceCanvasAssetGenerationQueue`(第二条不发提交 IPC、前一条终态后自动补发、失败与账本丢失都能收口)、`ResourceCanvasAssetGenerationTasksPanelView`(非模态、阶段文案来自后端记录、按 `assetId` 定位)、两块生成浮层在提交期间可关闭、「后台运行并关闭」文案。
|
||||
- 生成任务层:`resourceCanvasAssetGenerationTaskModel`(状态机 / 在途判据 / 恢复 / 耗时文案、音频任务载荷分流)、`resourceCanvasAssetGenerationQueue`(第二条不发提交 IPC、前一条终态后自动补发、失败与账本丢失都能收口、音频只发任务身份 + 幂等键)、`ResourceCanvasAssetGenerationTasksPanelView`(非模态、阶段文案来自后端记录、按 `assetId` 定位)、两块生成浮层点「生成」即同步关闭(面板 DOM 里没有阶段文案与「后台运行并关闭」这类在途按钮)。
|
||||
- Rust 层:`asset_generation_tasks` 的账本落项目内、排队阶段文案由后端拥有、进程重启把无人推进的记录收口为中断失败、账本上限与 task id 边界。
|
||||
- AppSurface 层:进入栏目出现工具栏 / 回总览与展开态消失、每个图片类入口的 `start_local_project_asset_generation` 载荷、前置缺失可点击说明且零请求、音频入口走 `derive_local_project_resource`、上传走 `upload_local_asset` + 配对读清单、生成面板关闭后任务仍在「生成任务」面板且第二条按本地排队补发、工具栏与右下 Dock 的几何契约。
|
||||
- 音频后台化:音频 kind 的提交返回任务记录且账本 `kind` 为音频、提示词上限与幂等键在提交时收口(未知 kind / 非法身份在提交即拒绝)、第二条提交在本地排队期间不发 IPC、未受理失败重开面板并复用同一 operation 与幂等键、受理后失败的收口与落卡复用图片类同一链路。
|
||||
- AppSurface 层:进入栏目出现工具栏 / 回总览与展开态消失、每个图片类入口的 `start_local_project_asset_generation` 载荷、前置缺失可点击说明且零请求、音频入口走同一条命令的音频载荷(`idempotencyKey` + 任务 id 即 operation id)、上传走 `upload_local_asset` + 配对读清单、生成面板关闭后任务仍在「生成任务」面板且第二条按本地排队补发、工具栏与右下 Dock 的几何契约。
|
||||
|
||||
@@ -71,7 +71,7 @@ plugins/agc-cocos-editor/
|
||||
|
||||
宿主按 `AGC_PLUGIN_WORKSPACE`、随包 `<resource_dir>/plugins`、开发构建仓库 `plugins/` 的顺序解析工作区;插件包内的 `native/payload` 由构建脚本随包映射,生成的 DLL 不入库。插件协议、权限和面板挂载全部复用通用宿主,Cocos 专属逻辑只存在于本插件包:进程名与 `--project` 解析、Creator 版本校验、named pipe 协议和 Windows 注入。
|
||||
|
||||
该插件是**内置插件**:随客户端分发、不能卸载,只能通过 `set_agc_plugin_enabled` 控制是否可用。除开关和 feature 外,还必须满足“当前受控项目已识别为 Cocos Creator 项目”这一门禁;没有当前项目或项目类型不是 Cocos 时,插件不出现在插件列表、面板、Agent 工具目录或 MCP tools/list 中,启动、面板读取、RPC 和编辑器执行也会失败关闭。项目切换离开 Cocos 后,已运行实例立即停止。Cocos 编辑器操作统一优先通过该内置插件的 `cocos.editor.execute` / `cocos.editor.operation`(DirectProject 对应 `agc_cocos_execute`);不得改走项目目录 `extensions/`、`package.json` 插件或第三方 MCP。开关状态保存在 AppData `extensions/builtin-plugins.json`,隔离 MCP 每次 tools/list 都向绑定宿主询问当前状态与项目门禁。
|
||||
该插件是**内置插件**:随客户端分发、不能卸载,只能通过 `set_agc_plugin_enabled` 控制是否可用。插件列表、启动、面板读取、插件 RPC、Agent 工具目录和 MCP tools/list 不按当前工程类型过滤;无当前项目或非 Cocos 项目仍沿用相同的开关、原生适配器、平台和 feature 规则。项目切换不因工程类型不同而停止插件,仍更新受控项目上下文并失效旧连接。实际编辑器操作必须取得当前受控项目对应的真实 Creator 目标并通过原有目录、PID、版本与握手校验;无项目或非引擎目录不能仅凭工具可见就通过执行校验。Cocos 编辑器操作统一优先通过该内置插件的 `cocos.editor.execute` / `cocos.editor.operation`(DirectProject 对应 `agc_cocos_execute`);不得改走项目目录 `extensions/`、`package.json` 插件或第三方 MCP。开关状态保存在 AppData `extensions/builtin-plugins.json`,隔离 MCP 每次 tools/list 都向绑定宿主询问当前可用状态,不自行按工程类型过滤。
|
||||
|
||||
## AGC 项目打开入口
|
||||
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
# AGC Godot 编辑器插件接入
|
||||
|
||||
> 文档状态:`current`
|
||||
> 规范关系:承接 AGC 通用插件宿主与编辑器适配主规范
|
||||
|
||||
更新时间:`2026-09-21`
|
||||
|
||||
2026-09-21 更新:解除工作区发现上的两条限制——一层子目录多命中时按目录名排序取第一个(不再整体失败关闭),`project.godot` 允许是符号链接 / Windows reparse point / 硬链接(按链接目标判定);同时设置→扩展 的内置插件行去掉手动「启动 / 停止」,启动统一由前端自动完成、停止改走该行启用开关。
|
||||
|
||||
## 目标与边界
|
||||
|
||||
常用操作指导随客户端审核 Skill pack 提供,入口为 `agc-godot-editor`。DirectProject 按需读取 [Skill 入口](../../apps/ai-game-creator-shell/src-tauri/resources/agc-skills/agc-godot-editor/SKILL.md) 和其常用操作参考;Runtime 的 `godot.editor.execute` 工具说明包含同一参考正文。指导覆盖场景/节点、owner、PackedScene、资源、UI、保存与撤销,不新增专用操作工具,不改变执行授权。
|
||||
|
||||
原文示例在 Godot 4.7.2 标准版 headless 工程验证了节点回读、局部撤销、PackedScene 存读、居中 UI 结构、无缩略图保存重开及只读旧文件写失败时保留内存修改。`save_scene_as` 不返回错误码,指南在重开前核验磁盘包含本次预期变更,不能以旧文件可加载作为保存成功证据。复验时设置 `AGC_GODOT_TEST_EXECUTABLE`,运行 `node --test plugins/agc-godot-editor/native/gdextension/tests/guide-examples.test.mjs`。正式 Ctrl+Z 历史、运行中停止、GUI 缩略图保存及 UI 视觉仍未由该测试验收。
|
||||
|
||||
独立图形环境补验已确认该示例在 800×600、480×800、1280×720 三种实际渲染尺寸下中文和按钮正常显示、容器居中且无裁切;此结果只覆盖示例布局,不代表按钮已接入游戏逻辑或其他 UI 已验收。
|
||||
|
||||
将 Godot 编辑器操控接入现有 AGC PluginHost、EditorAdapter、Runner、内置插件开关、权限审计和 Agent 工具链。Windows x64 的 Godot 4.7 及以上标准编辑器是首个实现目标,实机验收使用 4.7.2;其他平台和 .NET 编辑器不得从该结果推断支持。
|
||||
|
||||
初版工程路径支持 Windows 本地盘符目录;UNC/网络共享路径在准备描述文件前明确拒绝。受管描述文件、缓存与运行缓存继续按同一文件边界对链接/reparse point 失败关闭;唯一例外是工作区发现读取的 `project.godot`,它允许是链接并按目标判定。
|
||||
|
||||
DLL 原件随 AGC 安装包放在插件资源目录中;Godot Windows 加载器会在被加载文件旁生成 `~DLL`,因此宿主在 AGC 私有配置目录的运行缓存中按编辑器实例和构建身份准备临时加载副本。AGC 向项目新增一个可扫描的受管 `.gdextension` 描述文件,通过绝对路径引用该实例的加载副本;Godot 可自动生成其同名 `.uid` 伴生文件。DLL 不复制进工程。重新聚焦 Godot 后,由官方文件扫描完成首次加载;不需要用户打开或运行脚本,不创建 EditorPlugin addon,不修改 project.godot 或业务场景文件。编译工具链只属于开发与打包环境,不要求终端用户安装编译器。
|
||||
|
||||
本次包含连接、状态、GDScript 执行和真实回执、断开及资源清理,并验证通过代码读取和修改独立测试场景。专用截图/输入/场景工具目录、云服务、额外 MCP 服务、公开后端 API 和发布上传不在本次范围;通用执行可以调用 EditorInterface,不能把文件生成冒充编辑器内执行。
|
||||
|
||||
## 入口与归属
|
||||
|
||||
- 插件 id 为 `agc-godot-editor`,适配器为 `godot-editor`;命令 `godot.editor.execute`、连接能力 `godot.editor.connection`,DirectProject 工具为 `agc_godot_execute`。复用已有扩展列表和启用开关,不建立平行插件管理页面。
|
||||
- 项目发现沿用现有 Godot 工作区合同:工作区根保持用户选定目录;实际 Godot 根由根目录或一层直接子目录中的 project.godot 确定,一层命中多个时按目录名排序取第一个(确定性,不再报歧义),`project.godot` 本身是链接时按链接目标判定。准备描述文件和读取 Godot 缓存只作用于实际 Godot 根,通用文件工具/Runtime 的工作区根不改变。
|
||||
- 平台、内置开关、项目及目标身份必须在执行入口重新检查。插件只能处理宿主传入的当前受控项目,模型不能覆盖项目路径、DLL 路径、端口、令牌或目标实例。
|
||||
- Godot 的插件列表、启动和 Agent 工具目录继续按当前 Godot 项目过滤;Cocos/Unity 沿用各自不按工程类型过滤的合同。前端统一消费宿主列表自动启动三种编辑器插件,不在界面重复推断项目类型;设置→扩展 的内置插件行只保留启用/禁用与重载,不再提供手动启动/停止入口。Godot 的项目切换仍撤销旧插件上下文并停止旧实例。
|
||||
- 插件启动先完成项目事件订阅并接收当前受控项目快照,再注册命令和连接能力;命令可见时必须已经具备执行上下文。订阅期间收到的新项目事件优先于迟到的初始快照。
|
||||
- 只连接已打开且唯一匹配真实工程路径的 Godot Editor;校验 PID、进程启动身份、Godot 版本、握手中的工程路径与会话代次。多个候选、非编辑器、路径不符或已退出的进程均拒绝,不启动或关闭用户编辑器。
|
||||
- GUI、DirectProject 和 Agent Runtime 的原生操作统一由长寿命 Runner 持有。项目切换使连接失效,迟到回执不能改变新项目状态。
|
||||
|
||||
## 分发与描述文件
|
||||
|
||||
- 原生引导使用官方 `godot-cpp` 的 C++ 类型和初始化接口,不自行声明 Variant 存储或直接装配 ABI 函数指针。绑定源码固定到 `godot-4.5-stable` 的 `e83fd0904c13356ed1d4c3d09f8bb9132bdc6b77`,以该版本的稳定 API 构建并在 Godot 4.7.2 验证;产品最低版本仍为 4.7,因为嵌入脚本使用该版本能力。Windows x64 构建使用 Visual Studio C++、CMake 和 Python,静态链接绑定及 C++ runtime,用户无需这些构建工具。
|
||||
- Jenkins 的 `Genarrative-Agc-Windows-Build` 用 `AGC_WINDOWS_PATH` 整体替换阶段 PATH,因此 Godot 原生扩展所需的 CMake 与 Python 3 必须显式列入该白名单(当前为 Build Tools 自带的 `C:\BuildTools\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin` 与 `C:\Python312`);Toolchain preflight 校验 CMake ≥3.25、Python 3 和 Visual Studio 17 2022 生成器,缺失即失败关闭。
|
||||
- 构建仅从已固定的官方归档获取绑定源码,并核对 SHA256;下载和生成内容只进入 `.build/`。CMake 构建包含引导源码、嵌入脚本、绑定版本/归档摘要和构建配置的身份指纹。许可证与来源继续随 DLL 分发,安装包不包含 SDK、源码缓存、生成绑定或构建工具。
|
||||
- C++ 状态仅在扩展有效期间持有 GDScript 引用和桥节点身份;终止回调先停用仍存活的桥,再释放绑定对象,不能让静态 Godot 对象析构晚于绑定退出。延迟 bootstrap、原生卸载、Node 已退出及同 PID 重连均须实测。受管文件、会话身份、协议、执行回执与不确定阻断沿用现有合同。
|
||||
- EDITOR 阶段晚加载不会取得 CORE 阶段终止回调;引导终止时须通过官方绑定接口解除 Node/GDScript 的实例包装回调,并完成单例包装清理。只移除 C++ 包装,不同步销毁仍在 GDScript 调用栈或等待 `queue_free` 的引擎对象;先用 Variant 保活脚本,再解除 Ref 和绑定,避免卸载 DLL 后跳到失效回调。
|
||||
|
||||
- 安装资源布局为 `plugins/agc-godot-editor/native/gdextension/bin/win-x64/agc_godot_editor.dll`,邻接元数据记录协议、构建身份和 DLL SHA256。开发模式允许宿主提供仓库插件目录中的同结构产物;RPC 不接受自定义 DLL 候选。
|
||||
- 缓存根仅由宿主提供,为其私有配置目录下的 `godot-editor-runtime`;按 `PID + startedFileTime + buildId` 隔离,所有路径分量受控且拒绝链接/reparse point。复制前验证安装原件及元数据,缓存已有文件必须匹配来源、归属及 SHA,不能加载被替换的同名文件。受管描述同时保留原件与加载副本身份,重启恢复不得把工程给出的任意 DLL 路径当作受信任来源。
|
||||
- 实际模块核验同时覆盖加载副本及 Godot 在同目录生成的精确 `~agc_godot_editor.dll`,要求规范化路径和 DLL 字节身份一致。确认原生卸载后,只清理本实例、本构建、内容未变的缓存文件;不能跨编辑器删除或复用影子副本。安装位置更新和实例 PID 重用都必须重新验证。
|
||||
- 原生扩展只加载自身受信任包内的实现;GDScript 桥源码编译进 DLL,目标工程不能替换引导脚本。保留 Godot 官方 ABI 来源及 MIT 许可;生成的 DLL、缓存和机器路径不提交。
|
||||
- 描述文件固定为实际 Godot 根下的 `agc-editor-bridge.gdextension`,带 AGC 所有权标记和构建身份。首次使用可创建,内容一致时不重写;安装路径或构建身份变化时只更新本插件拥有的文件。已有同名非受管文件、链接/reparse point 或未知内容必须拒绝覆盖。
|
||||
- `.agent`、点号目录和 `.gdignore` 路径不会被 Godot 自动扫描,因此描述文件不能放在那里。会话发现文件仅放在 `.godot/agc/` 缓存内,不进入 manifest、对话或日志;校验缓存各级目录没有链接跳转。
|
||||
- 描述文件及引擎自动生成的 `.uid` 按同一归属管理:创建描述文件前已有孤立同名 UID 时拒绝接管;扫描生成后在 `.godot/agc/` 记录描述内容及 UID 内容指纹。重连、升级和清理核对此记录,仅删除原内容未变的本插件伴生文件;记录丢失、用户改动或未知同名文件时保留并报告,不把“格式合法”当作删除授权。
|
||||
- connect/execute 可以准备受管描述文件,并尝试把经过验证的目标编辑器窗口置前触发扫描;若系统不允许聚焦或握手未就绪,返回明确的未派发错误,提示重新聚焦后连接。连接重试不重放业务代码。
|
||||
- 升级、断开和禁用时先通过有效旧连接停用内存桥/卸载原生扩展,再清理与本会话匹配的受管描述文件和会话缓存。用户改动过的文件不删除;无可信握手时不能把删除文件当作已卸载。编辑器已退出时允许清理已确认归属的本地痕迹。
|
||||
- 安装目录可能只读;插件不得向 DLL 所在目录写令牌、状态或日志。
|
||||
- 安装位置/构建身份升级必须先确认旧扩展已卸载,再原子更新受管描述文件。旧 DLL 仍被目标进程加载、shutdown 结果不明或旧会话无法核实时,保留痕迹并返回可诊断错误,不用替换引用冒充完成升级。
|
||||
- 正式描述文件禁用自动 DLL 热重载;首次聚焦发现加载不受此设置影响。版本/路径更新走上述受控卸载和新加载,防止引擎自动热重载越过正在执行或待核对的任务。
|
||||
|
||||
## 执行协议与结果
|
||||
|
||||
引擎端协议固定为 `agc.godot.editor.v1`。本机回环 TCP 的 JSONL 每个请求包含 `protocol`、正整数 `id`、`generation`、`token`、`method` 和 `params`;方法为 `status`、`execute`、`shutdown`。会话缓存字段为 `protocol`、`buildId`、`pid`、`startedFileTime`(字符串)、`generation`、`projectPath`、`version`、`port`、`token`。文件名为 `editor-bridge-<pid>.json`,单文件最多 64 KiB。每次加载产生随机代次和令牌,端口只监听 127.0.0.1。
|
||||
|
||||
每条响应回显 `protocol/id/generation/pid/projectPath/buildId`,并包含 `result`;执行的 result 沿用 AGC 结构:`ok`、`status`、`dispatched`、`retryAllowed:false`、成功 `result` 或失败 `error:{code,message}`,可附有界日志。状态为 `completed`、`failed`、`needs-reconciliation`。连接状态沿用 `EditorConnectionInfo`,可增加代次、就绪与版本诊断字段。
|
||||
|
||||
`execute.params` 固定为 `{code:string,timeoutMs:integer}`;`timeoutMs` 为 1..60000 的剩余预算。`status.params` 和 `shutdown.params` 均为空对象。status 的 result 为 `{connected:true,pid,projectPath,version,generation,buildId,executing:boolean}`。shutdown 在没有在途执行时返回 `{accepted:true,status:"shutting-down"}`,仅表示停机请求已受理;桥在发送该回执后移除自己的会话缓存、关闭监听并卸载扩展。宿主必须继续校验同一目标进程的原生模块已卸载、原代次会话消失,才允许删除/替换描述文件或投影为断开完成。拒绝停机返回 `{accepted:false,error:{code,message}}`,不能把 accepted 当作卸载完成回执。
|
||||
|
||||
- GDScript 是可含 return/await 的函数体,非空、不含 NUL,最多 128 KiB;请求和回执最多 2 MiB,日志有界。执行运行于编辑器主线程,临时桥的 owner=null,不加入用户场景。
|
||||
- 只有真实执行完成且没有捕获到脚本运行错误才返回 completed。编译失败和确定运行失败返回可修复诊断,不以 nil 返回值伪装成功;空值返回本身仍是合法结果。执行日志不暴露令牌或宿主凭据。
|
||||
- 使用同一总期限覆盖发现、准备、连接、发送和读取;并发写执行立即拒绝,不积压截止后可能被派发的代码。原生服务不自动重发 execute。
|
||||
- 发送前的参数/路径/权限/平台/未连接错误为 `failed, dispatched:false`;发送后的超时、断线、损坏或身份不符回执为 `needs-reconciliation, dispatched:true, retryAllowed:false`。
|
||||
- 主线程无限循环不能承诺硬中止。async 未返回或运行状态不明时必须保留不确定阻断,不能因重连、插件启停、切换工程或 Runner 自动重启消除。
|
||||
- await 全程保持单执行占用;未完成或结果不确定期间,shutdown/禁用/切换只能禁止新增执行,不能卸载正在使用的桥或清除 fence。待状态已知且无在途执行后再完成资源清理;不确定时返回明确待核对状态。
|
||||
- 复用现有执行回执确认机制:Runner 派发前持久化请求身份;调用者确认完整匹配的终态回执后才能清理 pending。丢失最后一跳回执保持阻断,只有核对后退出全部 AGC/Runner 并重新打开才能恢复。
|
||||
- fence 的持久范围是同一完整宿主会话:自动重启 Runner、新窗口、JS 插件重载均不能清除。只有用户已核对编辑器状态、全部旧 AGC/Runner 退出,且新 GUI 同时独占现有 GUI 参与锁与 Runner 实例锁时,才能沿用现有恢复入口开始新的宿主会话;该规则与 Unity 一致,不建立另一套自动 reconcile。
|
||||
|
||||
## 契约与兼容
|
||||
|
||||
不修改服务端 API、DTO 结构、SpacetimeDB schema 或游戏持久业务数据。已有项目命令目录增加 `godot.editor.execute`,默认权限为 confirm,Rust 与 TypeScript 镜像保持一致;实际执行继续服从当前运行档及项目权限策略。通用 EditorAdapter 文档允许编辑器适配器按自身已授权合同维护受管引导文件;Cocos/Unity 的不写工程连接行为保持原约定。内置开关和审计继续使用已有存储。现有项目没有描述文件时按首次连接创建,不引入历史兼容路径。
|
||||
|
||||
## 验收
|
||||
|
||||
| 条款 | 必须取得的证据 |
|
||||
| --- | --- |
|
||||
| 包与来源 | 原生 DLL 构建、官方 ABI/许可、源码内无机器路径;staging 校验 DLL 及元数据只进入 Windows x64 资源 |
|
||||
| 受管文件 | 首次生成、幂等、安装路径更新、非受管文件冲突、路径穿越/链接拒绝、正常卸载及异常保留 |
|
||||
| 目标身份 | 根/一层 Godot 项目,PID/启动身份/工程/代次/构建身份校验;错误目标拒绝 |
|
||||
| 宿主接入 | manifest、启停、开关、权限、Runner RPC、DirectProject/Runtime 工具和项目切换定向测试 |
|
||||
| 执行 | 42、场景读取与独立场景修改/撤销、nil、编译错、运行错、async、有界日志和拒绝并发 |
|
||||
| 不确定结果 | 发送前后失败分类、超时/断线/损坏回执、ACK 归属、插件及 Runner 重启不解除阻断 |
|
||||
| 实机 | 已打开的独立 Godot 4.7.2 工程中无需脚本 UI 操作的首次加载、执行、卸载、重新连接;DLL 保持安装资源布局且原有工程文件哈希不变 |
|
||||
| 仓库门禁 | 相关 Rust/JS/前端定向测试、类型检查、文档索引、编码和 git diff --check;真实编辑器、打包资源与安装包 UI 的结果分别说明 |
|
||||
|
||||
## 已确定的产品选择
|
||||
|
||||
用户明确选择 DLL 原件留在 AGC 安装目录,并确认每个编辑器的临时加载副本放 AGC 缓存,工程内不复制 DLL。正式实现已按下列证据重新验收,前置 PoC 不作为交付依据。
|
||||
|
||||
## 本地验收结果(2026-09-20)
|
||||
|
||||
Windows x64、Godot 4.7.2 标准编辑器的本地实现验收通过。证据保存在 gitignored 的 `.app/diagnostics/`,不随源码提交;复验入口保留在插件源码和现有测试中。下列测试集合存在交叉,不相加为总用例数。
|
||||
|
||||
| 验收面 | 已取得的证据 |
|
||||
| --- | --- |
|
||||
| 原生服务与文件边界 | `godot-cache-tests-final.log`:30 项通过;覆盖受管文件及 UID、目标身份、缓存来源/哈希、安装更新、实例隔离、链接拒绝、异常保留和不确定状态 |
|
||||
| 真实引擎原生执行 | `godot-native-final-tests.log`:12 项通过、0 跳过;真实 headless Godot 验证同步/async、编译/运行错、超时占用、有界输出、代次拒绝及卸载重连 |
|
||||
| 插件与宿主 | `godot-plugin-js-final.log`:Cocos/Unity/Godot JS 共 31 项通过;`godot-shell-final-tests.log`:Godot 过滤 29 项通过;`godot-host-final-tests.log`:PluginHost 16 项通过,含切项目撤销旧上下文、派发前取消及派发后回执丢失 |
|
||||
| 共享权限与前端 | `godot-web-final-tests.log`:28 项通过;Rust 命令权限契约通过,AGC typecheck 通过;Godot 命令继续使用默认 confirm |
|
||||
| 现有宿主回归 | EditorAdapter、Unity、Runner 重启 fence 和缺失 endpoint 清理的定向测试通过;构建脚本、workspace、CI 路由和 rustfmt hook 定向测试通过 |
|
||||
| 原生 GUI | `godot-plugin-verified-20260920/evidence/native-gui-smoke.log`:同一编辑器内返回 42/null、读取场景、增加节点后撤销、await、编译/运行错误、卸载、重连及再次卸载成功 |
|
||||
| Jenkins Windows 节点 | 2026-09-21 在修复分支按 `Genarrative-Agc-Windows-Build` 等价流程实跑(dry-run):preflight 报告 `cmake=3.31.6-msvc6`、`python=3.12.10`,Godot 原生载荷 staging 出 345600 字节 `agc_godot_editor.dll` 与元数据,NSIS `陶泥儿_0.1.88_x64-setup.exe`、签名、渠道清单和更新摘要全部生成 |
|
||||
| 多实例与只读安装 | 同目录 `parallel-live-modules.json`、`parallel-verified-summary.json`:两个编辑器同时使用不同缓存路径的官方 `~DLL`,均返回 42、确认卸载,安装原件只读且未变 |
|
||||
| 安装位置更新 | 同目录 `install-location-smoke.log`:旧连接卸载、新安装来源生成新代次和引用、返回 42、清理两代缓存;两个安装来源及原有工程文件均未变 |
|
||||
| 真实 Runner | 同目录 `runner-smoke-psapi.log`:standalone Runner 经正式执行/ACK 路径取得真实 Godot 回执,拒绝错误 ACK,接受正确 ACK,完成场景修改/撤销及断开;`runner-restart-before.log`、`runner-restart-after.log`:新 Runner 无需重新连接即可恢复原归属并清理旧桥 |
|
||||
| 资源与收尾 | 完整 Windows feature debug 构建通过;当前 `src-tauri/resources/plugins/agc-godot-editor` 仅含 manifest、入口、DLL、元数据、许可和来源六个文件,其 DLL 与实机测试一致;同目录 `final-cleanup.json` 确认工程及安装来源哈希未变、自建 GUI 正常退出 |
|
||||
|
||||
正式 Runner smoke 使用安装资源布局下的 debug 可执行文件,不等同于 NSIS 安装包 UI 验证。本次未运行安装包 UI smoke、真实 Provider 生成或远端 CI,未制作发布包或上传发布;其他 Godot 版本、.NET 编辑器与其他平台仍须各自验收。AGC 全量聚合门禁不在上述定向结果中。
|
||||
|
||||
### 复验入口
|
||||
|
||||
从仓库根运行,原生构建要求 Visual Studio 2022 C++ x64、CMake 3.25 及以上和 Python 3;通过 Visual Studio generator 自动选择完整编译环境。首次构建需访问固定的官方归档,校验后的依赖缓存支持离线复用;`build.ps1` 可用 `-CMake`、`-Python` 指定构建工具。先把 `AGC_GODOT_TEST_EXECUTABLE` 设置为待验证的标准 Godot 编辑器绝对路径;未设置时 headless 测试会跳过,不能视为实机通过。
|
||||
|
||||
```powershell
|
||||
powershell -NoProfile -File plugins/agc-godot-editor/native/gdextension/build.ps1
|
||||
python -X utf8 -B plugins/agc-godot-editor/native/gdextension/tests/test_dependencies.py
|
||||
node --test plugins/agc-godot-editor/native/gdextension/tests/native-smoke.test.mjs
|
||||
node --test plugins/agc-godot-editor/native/gdextension/tests/guide-examples.test.mjs
|
||||
cargo test --locked --manifest-path plugins/agc-godot-editor/native/godot-editor-bridge/Cargo.toml
|
||||
npm run agc:plugins:test
|
||||
cargo test --locked --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml --features cocos-editor-execute,unity-editor-execute,godot-editor-execute godot
|
||||
cargo test --locked --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml --features cocos-editor-execute,unity-editor-execute,godot-editor-execute plugin_host
|
||||
npx vitest run apps/ai-game-creator-shell/tests/pluginHost.test.ts packages/shared/src/contracts/gameCreationApp.test.ts --threads=false
|
||||
npm run typecheck --workspace @genarrative/ai-game-creator-shell
|
||||
npm run check:doc-index
|
||||
npm run check:encoding
|
||||
git diff --check
|
||||
```
|
||||
|
||||
真实 GUI 使用 `native/godot-editor-bridge/examples/live_smoke.rs`;安装位置变更使用同目录的 `install_location_smoke.rs`。二者要求显式传入自有可丢弃工程、已打开编辑器 PID、可信安装 DLL、工程外私有缓存及 `--allow-fixture-mutations`,具体参数见源码用法。多实例验证分别传入两个工程和 PID,通过 `live_smoke` 的 `--hold-ms` 让加载时间重叠,同时核验原生模块路径。Runner 复验须经正式长度前缀 RPC、完整 ACK 和私有配置恢复路径,不能用原生示例替代 Runner 证据。
|
||||
|
||||
### C++ 引导验收边界
|
||||
|
||||
官方 C++ 绑定版已通过实际 MSVC 构建、原生/指南 headless 回归、Rust 缓存与连接回归、受管资源 staging 校验,以及同一 Godot 4.7.2 GUI 进程中的首次加载、执行、卸载和重新聚焦后的重连。DLL 的导入依赖只有 KERNEL32,生成 SDK/编译缓存不进入 staging;重复构建命中同一载荷身份,损坏归档和改动过的依赖缓存拒绝构建。验证中首次创建的空工程由 Godot 补写版本特征,按编辑器初始化后的基线确认插件运行不修改原有工程文件。
|
||||
|
||||
GUI 自动聚焦若未触发扫描,会按原合同返回未派发错误;重新聚焦后再连接,不重放未知执行。此次 C++ 替换未重新运行完整 AGC 发布构建、真实 Provider 或安装包 UI 验证,前述旧版本证据不代替这些验收。
|
||||
@@ -0,0 +1,97 @@
|
||||
# AGC Unity 编辑器插件接入
|
||||
|
||||
> 文档状态:`current`
|
||||
> 规范关系:承接 AGC 通用插件宿主与编辑器适配主规范
|
||||
|
||||
更新时间:`2026-09-20`
|
||||
|
||||
## 目标与非目标
|
||||
|
||||
常用操作指导随客户端审核 Skill pack 提供,入口为 `agc-unity-editor`。DirectProject 按需读取 [Skill 入口](../../apps/ai-game-creator-shell/src-tauri/resources/agc-skills/agc-unity-editor/SKILL.md) 和其常用操作参考;Runtime 的 `unity.editor.execute` 工具说明包含同一参考正文。指导覆盖查询、对象/组件、Prefab、资源、UI、保存与撤销,不新增专用操作工具,不改变执行授权。
|
||||
|
||||
指南示例在独立 Unity 6000.3.7f1 Mono 工程经真实 Attach 验证,覆盖查询、创建/修改、Undo、Prefab override 保存重开、Canvas 及播放切换;不据此推断 UI 视觉、第三方包或其他版本已验收。复验入口为 `native/unity-editor-bridge/tests/guide_examples.rs`:按文件头显式设置 fixture 的 helper、项目与 PID 环境变量后,运行 `cargo test --locked --manifest-path plugins/agc-unity-editor/native/unity-editor-bridge/Cargo.toml --test guide_examples -- --ignored --nocapture`;它直接提取随包指南代码,而非维护示例副本。
|
||||
|
||||
将 DotCraft.Unity 0.4.3 对应的 Attach 执行核心接入 AGC 现有插件系统,使当前 Unity 项目能够探测编辑器、建立连接、执行 C# 并获得真实结果。复用既有扩展列表、内置插件开关、权限、审计、EditorAdapter 和 Agent 工具通路。
|
||||
|
||||
首期只支持 Windows x64 的 Unity Mono Editor。连接不修改项目文件、不安装 UPM 包、不启动或关闭用户编辑器。不引入 DotCraft.Harness、另一套 Agent Runtime、MCP 服务或聊天界面;截图、热重载专用工具、macOS、Linux 和 Unity CoreCLR 不属于本次交付。
|
||||
|
||||
## 来源与分发
|
||||
|
||||
源码固定为 `DotHarness/dotcraft-unity` 的 `a65f091c162ebcfde5ec9b399d188fcc57261947`(发布插件 0.4.3 的 provenance)。只吸收 Attach 与必要 Shared 源码,保留 Apache-2.0 许可、第三方声明、来源及本地修改记录;构建产物不提交。Roslyn 版本固定为 5.3.0。Windows helper 自包含分发,用户不需要预装 .NET;构建端需要 .NET 10 SDK 和 x64 C++ 工具链。
|
||||
|
||||
## 入口与行为合同
|
||||
|
||||
- Unity 项目以当前受控根目录中的普通 `ProjectSettings/ProjectVersion.txt`、`Assets/` 和 `Packages/` 识别;复用现有打开项目入口,不创建平行工作台。
|
||||
- 插件列表、启动、面板、插件 RPC 和 Agent 工具暴露不按当前工程类型过滤;无项目或非 Unity 项目也沿用相同的内置开关及平台适配器规则。禁用会停止插件实例,跨工程类型切换保留插件管理能力并失效旧连接。前端按宿主列表中各插件自身状态投影自动启动,不因项目类型在 Cocos/Unity 之间二选一,也不自动展开面板。执行入口继续检查开关、权限和真实项目身份;工具可见不代表任意目录可作为 Unity 执行目标。
|
||||
- 探测只读取进程和项目身份。连接必须匹配规范化项目路径、PID、进程启动身份与实际握手;多个候选时失败,不选择任意实例。助手只接受受控项目、操作和代码,不接受任意可执行文件或 payload 路径。
|
||||
- 执行接收 UTF-8 C# 代码,最多 128 KiB,拒绝空值和 NUL。连接及执行有总期限,消息最多 2 MiB,并发执行直接拒绝,不积压写请求。
|
||||
- 成功仅由 Unity 真实执行回执决定;编译或运行错误返回结构化失败与脱敏诊断。主线程同步代码不承诺可硬中止。
|
||||
- 执行发送后超时、连接丢失、回执损坏或上游 unknown/lost 一律返回 `needs-reconciliation`、`retryAllowed=false`,阻断后续执行。停止/重启 JS 插件、断开连接和切换项目不能清除该阻断;用户核对后重启宿主才恢复。发送前参数、项目、平台或缺少 helper 的失败不进入执行不确定状态。
|
||||
- 探测不等于连接;连接不等于执行成功。Domain Reload 后重新验证身份与 generation,不能重放上一条写操作。
|
||||
- 宿主禁止 RPC 覆盖 manifest 声明的 adapter,并校验显式项目路径等于当前受控项目;缺省路径由宿主填充。编辑器路由不允许在全局锁后无界等待。切换项目使旧连接失效,旧请求回执只能归属原请求,不更新新项目连接状态。连接尝试先撤销旧连接,失败保持 disconnected。
|
||||
- GUI 项目切换采用线性化处理:已有插件执行先在受控期限内结束,再切换宿主项目和失效旧连接;切换在后台线程等待,不阻塞 UI 线程。前端项目设置与 cleanup 按顺序送达宿主,旧 cleanup 不能覆盖新项目。
|
||||
- 插件不继承 Provider 凭据。helper 使用受控可执行路径、最小环境、受限消息和超时;任意 C# 在 Unity 权限下执行,宿主 RPC 权限不构成 OS 沙箱。
|
||||
|
||||
## 模块与契约
|
||||
|
||||
插件包放在 `plugins/agc-unity-editor`,使用现有 Agent Plugins manifest 和 `agc.plugin.v1`。JS 入口通过 `host.rpc` 使用编译期注册的 `unity-editor` 适配器。AGC Runner 是 Unity 执行服务的唯一 owner:GUI 适配器复用现有经过鉴权的 Runner RPC 转发,Runtime 和 DirectProject 在同一个长寿命 Runner 中调用原生服务;不创建另一条跨进程 IPC。执行服务调用独立 Attach helper,再由上游本地协议连接 Unity。GUI 项目切换通过同一路径使连接失效;JS 或连接池重建不得重置 owner 内的不确定门闩,恢复要求核对后重启该执行 owner。
|
||||
|
||||
插件命令 `unity.editor.execute`,连接能力 `unity.editor.connection`;Agent Runtime 同名工具与 DirectProject `agc_unity_execute` 共享同一原生服务、项目检查与不确定结果阻断。面向模型的执行参数只有 `code`,项目路径由宿主注入。
|
||||
|
||||
Runner 在执行前保存请求身份,不保存代码或用户路径;GUI 完整验证确定性终态回执后才确认交付。回执丢失、损坏及插件最后一跳失败均保留持久阻断;正常并发或等待确定回执确认返回未派发失败,不误判执行不确定。Runner 自动重启和新增窗口都不清除阻断;用户核对后退出全部 AGC/Runner,再次启动客户端时,只有同时独占既有 GUI 参与锁和 Runner 实例锁才能恢复。启动、派发、读取和确认共享 80 秒客户端总期限,传给 helper 的预算扣除已用启动时间,过期请求不得派发。
|
||||
|
||||
helper 使用一行一条 JSON 请求/响应,请求包含 `id`、`method`、`params`,响应回显 `id` 与 `result` 或结构化 `error`。方法为 `detect`、`connect`、`status`、`execute`、`disconnect`;`projectPath` 始终来自受控宿主,`processId` 为可选探测约束,`timeoutMs` 不超过 60000。执行响应至少有 `ok`、`status`、`retryAllowed`、`result`/`error`,状态为 `completed`、`failed` 或 `needs-reconciliation`。detect/connect/status 结果包含 `adapter`、`connected`、`pid`、`projectPath`、`version`。
|
||||
|
||||
协议名为 `agc.unity.attach.v1`,每条请求均带 `jsonrpc: "2.0"` 和 `protocol`,id 为正整数。helper 是由原生服务持有的单个常驻子进程;Rust 服务在插件与 Runtime 之间共享,执行不确定门闩独立于 helper 生命周期。请求的 `params` 每次包含当前 `projectPath`,无单独 initialize/wait 公开方法;execute 内部完成连接、执行与有界 wait,总 deadline 包含发现、连接与等待,内部不重发 execute。detect 不进行注入。非执行方法的协议错误为 `{code, message}`;execute 的已知未发送失败同样放在结构化 result,只有带完整 `status: failed, ok: false, retryAllowed: false, dispatched: false` 的回执可判为未执行,未知错误不得据此解锁。执行成功含 `dispatched: true`;不确定结果含 `dispatched: true`。连接成功另含 `startedUtc` 与 `generation`,以实际目标身份/握手为准。
|
||||
|
||||
明确收到可信 `failed, ok: false, dispatched: true` 回执表示 Unity 已确定执行失败,不进入不确定门闩;可以由 LLM 修复代码后再执行。`retryAllowed: false` 禁止自动原样重放,不阻止确定性编译或运行错误后的修复。只有完整且匹配请求的终态回执可以支持这一判断。
|
||||
|
||||
首次通过现有打开项目入口导入 Unity 时允许沿用 AGC 的 `.agent` 元数据初始化;不修改 `Assets`、`Packages`、`ProjectSettings` 或 Unity 工程文件。此动作与只读 detect、不改文件 connect 分开。
|
||||
|
||||
不修改服务端 API、SpacetimeDB schema 或现有持久项目数据。新增插件开关沿用既有内置开关存储;不兼容或模拟 DotCraft.Harness 插件 ABI。
|
||||
|
||||
## 验收标准
|
||||
|
||||
| 条款 | 必须取得的证据 |
|
||||
| --- | --- |
|
||||
| 来源与构建 | 固定源码版本、许可、helper 构建和自包含发布检查 |
|
||||
| 插件接入 | manifest/协议测试,发现、启停、开关、无项目/跨工程类型的工具暴露和前端启动投影测试 |
|
||||
| 执行闭环 | helper 与原生适配器定向测试,Agent 参数及结果映射测试 |
|
||||
| 失败边界 | 跨项目、并发、超时、损坏回执、不确定阻断、重启插件不解除阻断测试 |
|
||||
| 分发 | Windows 构建脚本准备 helper,staging 仅包含目标平台运行文件及许可 |
|
||||
| 运行时 | 若有可用 Unity,使用临时测试项目验证连接和无副作用 C# 回执;没有可用编辑器时明确标为未验证,不以 mock 代替实机 |
|
||||
| 仓库门禁 | 定向测试、类型检查、文档索引、编码检查及 git diff --check |
|
||||
|
||||
真实 Unity、安装包及全版本兼容验收与单元测试分别报告。
|
||||
|
||||
## 当前验证范围与复验入口
|
||||
|
||||
- .NET helper 的 27 项定向测试与 Windows x64 自包含发布通过;发布程序在最小环境中完成协议 smoke。
|
||||
- 插件 JS 的参数/消息/并发/项目切换测试通过;Unity/Cocos 打开入口、插件启停投影及开发构建参数的前端定向验证通过。
|
||||
- 原生 helper 进程测试覆盖写入阻塞、EOF、错误 id、超长帧、期限、环境隔离及不确定结果阻断。
|
||||
- 宿主 Unity 定向 8 项、PluginHost 13 项、原生工具目录 16 项和引擎识别 5 项通过;Cocos 10 项、MCP 25 项通过,两组中同一个默认忽略的 Cocos 实机用例未在本任务运行。双 feature 编译检查和调试构建通过。
|
||||
- Unity `6000.3.7f1` 的独立临时工程已完成真实连接、C# 执行、编译错误回传及修复、确定运行错误、临时对象创建/销毁、断开重连验证。项目没有安装 DotCraft UPM 包。
|
||||
- `tests/live_unity.rs` 已显式执行通过,覆盖共享原生服务、编译修复以及 Domain Reload 后重新握手和 generation 更新;它默认 ignored,避免普通测试连接开发者项目。
|
||||
- 新构建的 AGC Runner 使用独立临时配置,完成真实 Unity 执行、ACK、错误 ACK 拒绝、未确认回执的并发拒绝,以及不确定状态跨 Runner 重启保留的验证。
|
||||
- 安装包 UI smoke、其它 Unity 版本及 CoreCLR 不属于上述已验证范围;不能从单一版本实机通过推断全版本兼容。
|
||||
- Gitea 的现有 AGC web 与 Rust crates 门禁分别执行 Cocos/Unity 插件 JS 和原生 crate 测试;Linux CI 不执行 Windows Attach helper 或真实 Unity。Windows Jenkins 构建要求 PATH 可解析 .NET 10 SDK,并由 helper 构建脚本执行 .NET 测试及自包含发布;实机与安装包验收仍单独报告。
|
||||
|
||||
复验命令:
|
||||
|
||||
```powershell
|
||||
powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File plugins/agc-unity-editor/dotnet/build.ps1
|
||||
node --test plugins/agc-unity-editor/src/entry.test.mjs
|
||||
cargo test --locked --manifest-path plugins/agc-unity-editor/native/unity-editor-bridge/Cargo.toml
|
||||
```
|
||||
|
||||
实机复验先自行创建、打开隔离测试工程,核对其 Unity PID,并设置
|
||||
`AGC_UNITY_SMOKE_HELPER`、`AGC_UNITY_SMOKE_PROJECT`、`AGC_UNITY_SMOKE_PID` 后执行:
|
||||
|
||||
```powershell
|
||||
cargo test --locked --manifest-path plugins/agc-unity-editor/native/unity-editor-bridge/Cargo.toml --test live_unity -- --ignored --nocapture
|
||||
```
|
||||
|
||||
该实机用例会触发 Domain Reload,不能指向有未保存工作的用户工程。
|
||||
|
||||
独立评审已核对项目归属、并发拒绝、总期限、ACK 原子归属、最后一跳回执丢失、
|
||||
进程重启阻断及发布许可边界;定向证据与上述真实运行时验证共同覆盖本次接入合同。
|
||||
@@ -0,0 +1,315 @@
|
||||
# 【技术方案】AGC 后端框架整理与演进路线
|
||||
|
||||
更新时间:`2026-09-18`
|
||||
|
||||
## 结论
|
||||
|
||||
可以整理,而且不需要从零重写。当前 AGC 已经具备一套可复用的后端骨架,但它还不是一个可以直接部署的统一 AGC backend:共享 Runtime 目前是库级内核,本地 Tauri 持有项目执行事实,`server-rs` 持有云端平台业务,三者之间还需要 application adapter 和契约收口。现有骨架包括:
|
||||
|
||||
- `server-rs/crates/agent-runtime-core` 是不依赖产品和框架的 Runtime 执行内核。
|
||||
- `server-rs/crates/agent-runtime-orchestration` 是任务图、依赖波次和修复影响计算。
|
||||
- `server-rs/crates/platform-agent` 是 AGC 壳独立 path dependency 使用的游戏任务图、角色和游戏产物规则适配层;它被 `server-rs` 默认 workspace 排除,不能反向成为通用 Runtime 内核。
|
||||
- `apps/ai-game-creator-shell/src-tauri/src/agent`、`project`、`runner` 是本地 AGC 执行宿主。
|
||||
- `server-rs/crates/module-*`、`spacetime-module`、`spacetime-client`、`api-server` 和 `platform-*` 已覆盖领域、持久化、HTTP/BFF 和外部服务适配。
|
||||
|
||||
现在的主要问题是能力已经分布在多个入口,缺少一个对开发者可见的 AGC backend application facade 和统一的跨边界状态合同。目标应是整理边界和调用方向,逐步收拢入口,不新建第二套 Agent Runtime、第二套会话库或第二个业务真相。
|
||||
|
||||
## 目标
|
||||
|
||||
AGC backend 对外提供一个稳定的“项目开发运行时”能力面:接收用户意图,绑定项目身份,规划或执行 Agent Run,调用受控工具和平台能力,持久化可恢复状态,发布可观察事件,并把本地项目产物或云端资源结果返回给客户端。客户端本地宿主通过共享契约和平台 adapter 与云端交互,不直接把 `module-*` 当作本地 IO 层。
|
||||
|
||||
框架需要同时支持两种执行位置:
|
||||
|
||||
1. **本地宿主执行**:Agent 需要直接读写用户项目、启动本地进程、访问本地预览或编辑器时,在 Tauri Rust 宿主中执行。
|
||||
2. **云端控制面执行**:认证、账户、模型目录、编辑器资源、异步生成、计费、快照上传和公开 API 在 `server-rs` 中执行。
|
||||
|
||||
两者共享领域合同、Runtime 语义和错误分类,但不共享不适合跨进程的本地副作用实现。
|
||||
|
||||
## 非目标
|
||||
|
||||
- 不把 Tauri 本地文件、进程、浏览器或 Codex app-server 搬进云端。
|
||||
- 不把 `api-server` 变成任意代码执行器,也不让前端直接访问 SpacetimeDB。
|
||||
- 不引入 LangChain、AutoGen、OpenAI Agents SDK sidecar 或另一套调度器作为 AGC 核心。
|
||||
- 不恢复已退役的旧创作入口、旧作品架或旧后端服务。
|
||||
- 不在本次整理中改变现有公开 API、SpacetimeDB 表字段、OpenAPI 或本地项目文件格式。
|
||||
|
||||
## 逻辑分层
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
UI[AGC Web UI / Tauri commands]
|
||||
HOST[AGC Local Host\nTauri Rust]
|
||||
CORE[Shared Agent Runtime\nagent-runtime-core]
|
||||
ORCH[Task Graph\nagent-runtime-orchestration]
|
||||
CONTRACT[Shared contracts\nshared-contracts]
|
||||
DOMAIN[Cloud domain modules\nmodule-*]
|
||||
API[Cloud Control Plane\napi-server Axum]
|
||||
DATA[Data plane\nspacetime-client -> SpacetimeDB]
|
||||
PLATFORM[Platform adapters\nplatform-llm / image / oss / auth / ...]
|
||||
PROJECT[Local Project Plane\nfiles / manifest / JSONL / locks / checkpoints]
|
||||
|
||||
UI --> HOST
|
||||
UI --> API
|
||||
HOST --> CORE
|
||||
HOST --> ORCH
|
||||
HOST --> CONTRACT
|
||||
HOST --> PROJECT
|
||||
API --> CONTRACT
|
||||
API --> DOMAIN
|
||||
API --> DATA
|
||||
API --> PLATFORM
|
||||
PLATFORM --> DATA
|
||||
```
|
||||
|
||||
这张图是调用方向,不表示所有层都必须变成新的 crate。当前 `api-server` 不依赖或执行本地 `agent-runtime-core`/`agent-runtime-orchestration`;如果未来明确引入云端 Runner,再单独增加云端 Runtime host,不把本地项目 Run 事实迁移到 API handler。现有目录已经能承载大部分边界,先用 facade、trait 和契约测试固化边界,再决定是否需要物理拆 crate。
|
||||
|
||||
### 1. Shared Runtime kernel
|
||||
|
||||
`agent-runtime-core` 只负责通用运行事实和确定性决策:Run、Action、Observation、Tool execution、Completion、Recovery、Provider contract、Capability registry 和 Runtime event。它不能依赖 Tauri、Axum、SpacetimeDB、具体提示词、权限实现或 AGC 业务表。
|
||||
|
||||
`agent-runtime-orchestration` 只负责任务图:Agent catalog 校验、依赖关系、ready task、wave 和下游修复影响。它不持有线程、Provider、工具、数据库或产品持久化。
|
||||
|
||||
这两层是 AGC 与其他未来 Agent 宿主共享的内核,不能把 AGC 特有规则反向塞回去。
|
||||
|
||||
### 2. Domain and contract layer
|
||||
|
||||
领域规则留在现有 `module-*`:
|
||||
|
||||
| 领域 | 当前承载位置 | 负责内容 |
|
||||
| --- | --- | --- |
|
||||
| 主站画布 Agent 会话 | `module-editor-agent` | 画布会话归属、标题、消息元数据、领域校验;不是 DirectProject 本地会话事实源 |
|
||||
| Runtime / AI task | `module-runtime`、`module-ai` | Runtime 设置、模型目录、AI task 状态和输入校验 |
|
||||
| 编辑器项目和资源 | `module-assets`、`spacetime-module` 相关 storage | 项目、资源、版本绑定、生成任务和事务规则 |
|
||||
| 认证和账户 | `module-auth`、`platform-auth` | 身份、会话和认证平台适配 |
|
||||
|
||||
跨端 DTO、公开请求/响应和错误码留在 `shared-contracts`。目标边界是领域层不发 HTTP、不读本地文件、不调用 LLM、不直接拿 OSS 客户端;当前部分 `module-*` 仍通过 feature 或 service 依赖平台 crate,这属于后续收口债务,不应继续扩大。
|
||||
|
||||
### 3. Local AGC host
|
||||
|
||||
`apps/ai-game-creator-shell/src-tauri` 是本地 backend,不只是 UI 的 IPC 薄壳。普通 Tauri command 只负责鉴权、确认和 UI bridge;真正的 LLM、工具循环、队列和恢复由独立的 `--agent-runner` 执行者完成。它负责:
|
||||
|
||||
- `agent`:DirectProject、Runtime driver、Runtime protocol、工具桥、Codex app-server/CLI、Provider handoff 和恢复。
|
||||
- `project`:项目根边界、manifest、JSONL 会话、checkpoint、资源编辑、文件读写、项目写锁和版本替换。
|
||||
- `runner`:跨窗口共享的 Agent Runner、loopback 协议、owner/watchdog、连接和生命周期。
|
||||
- `browser`、`preview`、`process_session`:受控预览、浏览器试玩、本地进程会话及证据。
|
||||
- `editor_adapter`、`plugin_host`:编辑器桥接和插件能力,不向 Agent 暴露原始 pipe、句柄或未绑定项目身份的执行面。
|
||||
- `platform_session`、`http_client`:平台身份/凭据和云端 API 调用。
|
||||
|
||||
本地宿主可以复用共享 Runtime crate 和 `platform-agent`,但项目文件、资源权限、完成门、Prompt Bundle、DirectProject/Planning V2 和 Runner IPC 都属于 AGC adapter/host,不能迁回 Runtime core。
|
||||
|
||||
`main.rs`、`agent.rs` 和命令注册只应是组合层。新的业务规则应进入对应功能域或 Runtime contract,不能继续堆进入口文件。
|
||||
|
||||
### 4. Cloud control plane
|
||||
|
||||
`api-server` 是云端 HTTP/SSE/BFF 和跨模块编排层。它不持有本地 `--agent-runner` 的项目 Run 事实;除非未来明确引入云端 Runner,否则云端只持有平台业务、异步 operation 和可公开投影。现有入口包括:
|
||||
|
||||
- `/api/ai/tasks`:AI task 生命周期。
|
||||
- `/api/editor/*` 与 `/api/external/v1/editor/*`:编辑器项目、资源和生成操作。
|
||||
- `/api/agc/project-snapshots/*`:AGC 项目文件与 manifest 快照上传。
|
||||
- `/admin/api/agc-models`:AGC 模型目录管理。
|
||||
- 编辑器 Agent 会话、外部生成任务、运行设置和诊断/追踪相关接口。
|
||||
|
||||
这些路由可以继续按领域模块存在,但需要分成两个语义清晰的 facade:
|
||||
|
||||
- **本地 host coordinator**:在 Tauri 宿主内统一 `start_run`、`resume_run`、`cancel_run`、`get_run` 和本地事件投影,事实源仍是项目 `.agent/runtime/**` 与 Runner。
|
||||
- **云端 application service**:在 `api-server` 统一 `submit_operation`、`get_operation`、`reconcile_operation`、AI task、外部生成和快照调用,复用现有领域 service、队列和 SpacetimeDB procedure。
|
||||
|
||||
两者可以共享 DTO、错误分类和 correlation 字段,但当前不新增统一的云端 `/runs` API,也不把本地 Run 伪装成云端持久化记录。
|
||||
|
||||
### 5. Platform adapters
|
||||
|
||||
LLM、图片/音频/视频、OSS、认证、语音和编辑器外部服务继续放在 `platform-*`。适配器只返回受约束的 Provider/Job/Asset 结果;重试、幂等、扣费、结果落库和公开错误映射由上层 application/domain contract 决定。
|
||||
|
||||
## 数据真相和存储边界
|
||||
|
||||
### 本地项目平面
|
||||
|
||||
以下事实属于当前授权项目,由本地宿主负责读写和恢复:
|
||||
|
||||
- 项目源文件、`manifest`、版本和资源绑定的本地投影。
|
||||
- `.agent` 下的会话 JSONL、Runtime journal、action/observation、checkpoint 和诊断摘要。
|
||||
- 项目写锁、Runner owner、进程会话、预览注册和浏览器证据。
|
||||
|
||||
本地写入必须经过项目身份校验、项目根边界、普通文件/reparse point 检查、写锁和现有权限策略。云端 API 不直接读写用户项目目录。
|
||||
|
||||
### 云端业务平面
|
||||
|
||||
以下事实属于服务端:
|
||||
|
||||
- 登录身份、refresh session、钱包/计费和模型目录。
|
||||
- 编辑器项目、资源、版本绑定、生成任务、任务事件和结果引用。
|
||||
- 错误报告、追踪事件和公开 read model;快照当前以受控 OSS manifest/对象键、审计和诊断能力为主,不假定已经存在 SpacetimeDB 快照元数据表。
|
||||
|
||||
当前 SpacetimeDB 没有通用的 AGC Agent Run/Action/Event/Confirmation/Plan 表;`runtime_snapshot` 不能直接当作 AGC Agent Runtime 持久化。若未来把 Run 迁到云端,必须另立清晰的持久化合同;本路线默认本地 `.agent/runtime/**` 继续是 DirectProject 的事实源。
|
||||
|
||||
SpacetimeDB 表、reducer、procedure 和事务 adapter 只留在 `spacetime-module`;服务端访问统一走 `spacetime-client` facade。
|
||||
|
||||
### OSS / 大对象平面
|
||||
|
||||
会话正文、项目快照文件和媒体大对象继续按现有合同进入 OSS;SpacetimeDB 只保存归属、对象键、版本、状态和必要摘要。OSS key 必须由服务端根据已验证的身份/项目 ID 生成,客户端不能把任意对象键当作归属证明。
|
||||
|
||||
## 统一执行合同
|
||||
|
||||
本地 Run 和云端异步操作可以有不同实现,但公开状态应映射到同一组语义:
|
||||
|
||||
```text
|
||||
intent
|
||||
-> accepted
|
||||
-> queued
|
||||
-> running
|
||||
-> waiting (user / confirmation / provider retry / external job)
|
||||
-> succeeded | failed | cancelled | needs-reconciliation
|
||||
```
|
||||
|
||||
- `accepted` 表示请求已经被持久化并得到稳定身份,不表示 child 已经开始执行。
|
||||
- `running` 必须由真正持有执行权的 Runner/worker 写入 started 事实后才能公开。
|
||||
- `waiting` 必须有可恢复的原因和下一步信息,不能由前端计时器伪造。
|
||||
- 外部结果未知、不能安全重放时进入 `needs-reconciliation`,禁止自动重放或伪造成功。
|
||||
- 取消、失败、恢复、重试和最终回复都必须保留同一 Run/operation 身份及可追踪的 event 顺序。
|
||||
|
||||
现有 `RunStatus`、`ActionStatus`、`ObservationStatus`、AI task 状态、ExternalGenerationJob 状态和项目生成账本可以继续各自保留;application facade 只提供上述语义映射,不强行把它们合并成一张跨域表。
|
||||
|
||||
## 核心调用链
|
||||
|
||||
### 本地 DirectProject
|
||||
|
||||
```text
|
||||
UI
|
||||
-> Tauri command
|
||||
-> DirectProject / Runtime driver
|
||||
-> Runner / Codex app-server
|
||||
-> Runtime tool policy
|
||||
-> project owner + write lock
|
||||
-> local files / manifest / JSONL / checkpoint
|
||||
-> Runtime event projection
|
||||
-> UI
|
||||
```
|
||||
|
||||
工具执行失败时,属于可修复的构建、验证或试玩错误,可以把脱敏上下文反馈给同一 LLM 回合并有界重试;鉴权、项目身份、权限、传输断开、取消、历史损坏和付费操作不确定等边界必须失败关闭。
|
||||
|
||||
### 云端资源/生成操作
|
||||
|
||||
```text
|
||||
AGC tool or UI
|
||||
-> api-server route
|
||||
-> auth + project ownership + idempotency
|
||||
-> module-* validation
|
||||
-> spacetime-client facade
|
||||
-> durable operation / queue
|
||||
-> platform-* provider
|
||||
-> OSS result
|
||||
-> SpacetimeDB result projection
|
||||
-> API polling/SSE
|
||||
```
|
||||
|
||||
计费、外部 job、结果安装和重试必须围绕 durable operation 编排,不能把 Provider 返回成功当成业务完成的唯一证据。
|
||||
|
||||
### 项目快照
|
||||
|
||||
```text
|
||||
Local project snapshot
|
||||
-> authenticated /api/agc/project-snapshots/*
|
||||
-> body-size and file contract checks
|
||||
-> object storage
|
||||
-> snapshot metadata / diagnostic projection
|
||||
```
|
||||
|
||||
快照上传用于备份和诊断,不改变本地项目作为开发态文件真相的职责。
|
||||
|
||||
## 边界规则
|
||||
|
||||
1. **Runtime 与宿主分离**:核心 Runtime 只做状态决策;Tauri host、Runner 和 server worker 提供执行、持久化、时间和能力实现。
|
||||
2. **领域与 IO 分离**:`module-*` 不依赖 Axum、Tauri、Provider 或 OSS;HTTP handler 不复制领域校验。
|
||||
3. **文件系统单一入口**:任何 Agent 文件读写都经过项目 scope、写锁和受控工具;不能在 API 或前端加旁路写入。
|
||||
4. **外部服务单一入口**:LLM、生成、OSS、认证等外部调用只经 `platform-*`,不能在业务 handler 里散落裸 HTTP。
|
||||
5. **数据访问单一入口**:`api-server` 和本地需要的云端调用使用 `spacetime-client` facade,不直接拼 SpacetimeDB 请求。
|
||||
6. **前端只消费投影**:前端不推导正式业务状态,不直接访问本地项目数据库或 SpacetimeDB。
|
||||
7. **身份与凭据分离**:同一身份的 access token 轮换不使在途 Run 失效;换号、退出或 origin 变化必须使旧 Run 停止后续副作用。
|
||||
8. **可恢复优先**:持久动作、异步生成、Runner 续跑、外部结果回填和最终状态写入必须有稳定 ID、版本/CAS 或幂等键。
|
||||
|
||||
## 当前缺口
|
||||
|
||||
### 缺少 AGC application facade
|
||||
|
||||
能力散落在 `agent`、`editor_project.rs`、`external_editor_api.rs`、`ai_tasks.rs`、快照路由和生成队列中。它们各自可用,但调用方难以判断一项用户意图应该走本地 Run、云端 task 还是外部 generation operation。
|
||||
|
||||
**整理方向**:先建立逻辑 facade 和 capability catalog,保留现有路由作为 adapter;只有当两个以上入口共享同一业务流程时,才下沉 application service。
|
||||
|
||||
### 本地和云端状态语义重复
|
||||
|
||||
本地 Runtime、AI task、ExternalGenerationJob 和项目资源 operation 都有自己的 accepted/running/failed/retry 表达。状态不能强行合表,但需要统一事件字段:`subject_id`、`run_or_operation_id`、`stage`、`revision`、`occurred_at`、`retryable`、`public_summary` 和 `correlation_id`。
|
||||
|
||||
### 组合层偏重
|
||||
|
||||
AGC Tauri 的 `main.rs`、`agent.rs` 和部分命令模块已经包含大量注册与兼容出口。后续工作应按功能域继续拆分,但每次只移动一个边界并保持公开导出、命令名称和测试合同不变。
|
||||
|
||||
### 云端大文件模块偏重
|
||||
|
||||
`api-server` 的编辑器项目、External Editor API 和生成队列承担了较多请求解析、领域校验、计费、队列和结果投影。后续应把领域输入校验和 operation service 继续下沉到 `module-*`/application 层,handler 只保留鉴权、解析、调用和响应映射。
|
||||
|
||||
### 契约目录需要集中
|
||||
|
||||
AGC 本地 IPC、Runner 协议、Runtime snapshot、云端 API、SpacetimeDB procedure 和 OSS key 规则目前分布在代码及专题文档中。应建立一个只读的 AGC capability/contract catalog,列出入口、调用方、状态源、权限、幂等键、恢复策略和证据位置;它不是新的运行时注册表。
|
||||
|
||||
## 演进路线
|
||||
|
||||
### 第一阶段:框架定界
|
||||
|
||||
- 将本文件作为 AGC backend 主规范。
|
||||
- 维护 capability catalog,给每个现有入口标注 `local-host`、`cloud-control-plane` 或 `shared-runtime`。
|
||||
- 为本地 Run、云端 task、外部 generation 和快照上传补齐统一 correlation/idempotency 字段的映射表。
|
||||
- 不改 API、schema 和文件格式。
|
||||
|
||||
### 第二阶段:统一 application facade
|
||||
|
||||
- 在现有 `api-server` modules 之上增加薄的 AGC application service/facade。
|
||||
- 先收拢 Run 查询、事件查询、取消、恢复和 operation reconcile;生成业务仍复用现有队列和平台适配器。
|
||||
- 本地 Tauri 侧将 DirectProject、Runtime driver 和 Runner 的入口分成 `command adapter -> application coordinator -> runtime host` 三层。
|
||||
- 用契约测试锁定调用方向,防止前端或 handler 绕过 domain/client facade。
|
||||
|
||||
### 第三阶段:持久化与恢复收口
|
||||
|
||||
- 为本地 Runtime journal、云端 AI task、外部 generation job 和项目资源 operation 逐项定义 accepted/running/waiting/terminal 的写入点。
|
||||
- 补齐重启、重复提交、迟到回调、同身份 token 轮换、换号和 unknown outcome 的恢复测试。
|
||||
- 继续保持本地项目文件与云端业务数据的双平面,不做隐式云同步。
|
||||
|
||||
### 第四阶段:能力插件化
|
||||
|
||||
- 以 capability contract 接入编辑器 adapter、图像/音频/视频生成、浏览器试玩和 UI workflow。
|
||||
- 每个能力声明输入约束、权限、写入范围、计费/异步语义、产物身份和验证证据。
|
||||
- Cocos 等编辑器桥接继续通过通用 plugin host,不把编辑器进程控制细节泄漏给 Runtime core。
|
||||
|
||||
### 第五阶段:观测和发布门禁
|
||||
|
||||
- 统一 Run/operation 的 request ID、correlation ID、client marker、错误分类、诊断事件和公开摘要。
|
||||
- 建立 AGC backend smoke:健康检查、鉴权、最小 Run、工具拒绝、快照体积边界、异步生成轮询和恢复。
|
||||
- 把真实 Provider、登录态、Windows Runner 和本地预览作为单独运行时证据,不用静态检查替代。
|
||||
|
||||
## 验收标准与证据
|
||||
|
||||
| 条款 | 验收方式 | 证据 |
|
||||
| --- | --- | --- |
|
||||
| 共享 Runtime 不依赖产品 IO | `cargo check/test -p agent-runtime-core -p agent-runtime-orchestration` 与依赖检查 | crate 依赖和测试输出 |
|
||||
| 本地工具只能在授权项目内执行 | AGC Rust 项目 scope、symlink/reparse、写锁和权限定向测试 | `apps/ai-game-creator-shell/src-tauri` 测试报告 |
|
||||
| 云端路由不绕过 domain/client | api-server 定向测试和源码依赖检查 | handler、module、spacetime-client 调用证据 |
|
||||
| 重复提交不产生重复副作用 | 本地 Run、AI task、ExternalGenerationJob 和生成 operation 幂等测试 | 稳定 ID/receipt/CAS 断言 |
|
||||
| 未知外部结果失败关闭 | provider/queue/reconcile 测试 | `needs-reconciliation` 终态证据 |
|
||||
| 认证续期不误杀在途 Run | session 与 Runtime 定向测试 | 同身份凭据轮换、换号失效证据 |
|
||||
| 快照上传受控 | `/api/agc/project-snapshots/*` API smoke | 鉴权、体积、项目归属和 OSS key 断言 |
|
||||
| 公开状态可追踪 | request/correlation/client marker 与错误摘要测试 | tracking、诊断事件和公开 response |
|
||||
| 文档与代码口径一致 | `npm run check:doc-index`、`npm run check:encoding`、`git diff --check` | 命令输出 |
|
||||
|
||||
## 决策
|
||||
|
||||
1. AGC backend 采用“共享 Runtime 内核 + 本地执行宿主 + 云端控制面 + 领域/平台适配器”的逻辑框架;`platform-agent` 继续承载 AGC 游戏特化规则,不能被当作通用内核。
|
||||
2. 先整理本地 host coordinator、云端 application service、contract catalog 和测试边界,再决定是否需要新增物理 crate;当前不新建平行 `agc-runtime`。
|
||||
3. 本地项目文件和云端业务数据保持双平面,快照是受控上传能力,不是开发态主数据同步。
|
||||
4. `agent-runtime-core` 与 `agent-runtime-orchestration` 保持产品无关;AGC 规则进入 host、application 或 `module-*`。
|
||||
5. 公开 API、SpacetimeDB schema、生成绑定和现有本地文件合同在后续实现阶段保持兼容,任何 breaking change 单独走 SDD 和迁移评审。
|
||||
|
||||
## 未决问题
|
||||
|
||||
- AGC application facade 第一批要覆盖哪些调用方:仅客户端本地 Run,还是同时纳入外部编辑器 API。
|
||||
- 云端是否需要持久化 AGC Run 元数据,还是继续以本地 Run 为主、云端只持久化资源和异步 operation。
|
||||
- capability catalog 最终作为文档、生成的共享 DTO,还是仅作为测试清单;在未证明需要运行时发现前,不把它做成新的动态插件注册中心。
|
||||
- 是否有明确的云端 Runner 需求;在没有多租户后台执行需求前,不把本地 Runner 迁移到服务端。
|
||||
@@ -1,15 +1,34 @@
|
||||
# AGC 客户端更新检查与下载
|
||||
|
||||
更新时间:`2026-09-17`
|
||||
更新时间:`2026-09-21`
|
||||
|
||||
本文件是 AGC 客户端自动更新的主规范:更新能力由 Tauri 官方插件 `tauri-plugin-updater` 承担,并按下文渠道分发。
|
||||
|
||||
## 目标
|
||||
|
||||
- 客户端自动更新改用 Tauri 官方 `tauri-plugin-updater`:清单请求、版本比较、更新包下载、签名校验、安装与退出全部在原生侧完成;前端只负责触发、展示和渠道选择。
|
||||
- 更新按渠道分发。当前渠道集合为 `dev-win`(Windows x64)与 `dev-mac`(macOS);构建管线按渠道产出并上传清单,客户端只读取自己渠道的清单。
|
||||
- 更新按渠道分发。渠道为 `dev`、`release` 或自定义名称;Windows/macOS 是独立的系统维度,每个渠道分别维护各系统已发布的版本、清单和安装包。客户端只读取构建时确定的渠道及系统对应的清单。
|
||||
|
||||
## 渠道与网站配置合同
|
||||
|
||||
- 构建参数 `AGC_UPDATE_CHANNEL` 默认 `dev`,支持 `release` 和自定义小写名称;名称符合 `[a-z][a-z0-9-]{0,31}`,不能以连字符结尾,不能为 `win/mac/windows/macos/darwin/linux` 或以 `-win/-mac` 结尾。构建目标独立决定系统和架构。
|
||||
- 为延续已发布客户端地址,OSS 继续使用 `agc/<channel>-win/` 和 `agc/<channel>-mac/` 作为物理分区;`dev-win/dev-mac` 是分区键,不是可填写的渠道。每个分区独立维护 `latest.json` 与版本目录,发布 release 不覆盖 dev。旧 `agc/latest.json` 迁移桥与其版本高水位仅属于 dev 的 Windows 分区。
|
||||
- 网站由服务端配置 `GENARRATIVE_CLIENT_DOWNLOAD_CHANNEL` 选择检测渠道,默认 `dev`;更改后重启 API 服务生效。`GET /api/client-downloads` 返回该渠道 Windows/macOS 的真实已发布版本。请求参数不能覆盖配置或指定 URL;配置非法时失败关闭,不悄悄改读 dev。
|
||||
- 同一渠道中不同系统仍可具有不同 release 版本;未发布的系统隐藏,单系统失败不影响另一系统。不会从其他渠道补齐缺失版本。
|
||||
- 验收必须覆盖 dev/release/自定义渠道各自端点和对象地址、独立版本、非法名称、旧 dev 地址延续、release 不写旧迁移桥、网站配置贯通、跨渠道链接拒绝和部分失败。
|
||||
- 更新链路的信任来源从「清单里的 sha256 + 受信域名」升级为「发布签名 + 受信域名」:清单里的 `signature` 由构建期私钥生成,客户端用内置公钥校验,校验不过就拒绝安装。
|
||||
|
||||
### 渠道与安装身份合同
|
||||
|
||||
- 渠道同时决定**更新端点**与**安装身份**,两者都由构建期写入产物。默认渠道 `dev` 保持基线身份 `productName = 陶泥儿`、`identifier = world.genarrative.ai-game-creator`;其它渠道(`release` 与自定义渠道)派生 `productName = 陶泥儿 <渠道显示名>`(`release` → `陶泥儿 Release`、`beta-2` → `陶泥儿 Beta-2`)与 `identifier = world.genarrative.ai-game-creator.<渠道>`。渠道显示名按连字符分段首字母大写,不改动渠道本身。
|
||||
- 默认渠道身份**不可变更**:既有安装目录、卸载项、快捷方式与已发布客户端的升级链都建立在基线身份上。渠道身份由 `apps/ai-game-creator-shell/scripts/channel-identity.mjs` 单点定义,构建入口、macOS 发布入口与配置门禁共同消费;基线 `tauri.conf.json` 必须逐字等于默认渠道身份。
|
||||
- 安装身份决定的持久与可见事实:Windows 安装目录 `%LOCALAPPDATA%\<产品名>`、卸载项与 `HKCU\Software\genarrative\<产品名>`、WebView2 数据目录 `%LOCALAPPDATA%\<identifier>`、客户端数据目录 `%APPDATA%\<identifier>`;macOS `.app` 名、bundle id、DMG 卷名与菜单栏应用名。
|
||||
- 同机并存:不同渠道的包体可以在同一台设备上同时安装并同时运行,互不覆盖、互不顶掉;同一渠道的新版本仍是原地升级,因为更新端点与安装身份同属一个渠道。
|
||||
- 数据不跨渠道共享:本地项目、工程快照、模板、登录态、诊断日志与 Runner/项目锁按渠道身份分目录。切渠道等于换一个客户端,不迁移、不合并本地数据;渠道内的 origin 隔离规则不变。
|
||||
- 主窗口与工作区/启动器窗口标题取构建期产品名,让同机并存的渠道客户端在任务栏与 Alt-Tab 中可区分;默认渠道标题仍是「陶泥儿」。
|
||||
- 首装包与更新包的对象名包含产品名(如 `陶泥儿 Release_0.1.96_x64-setup.exe`、`陶泥儿 Release_0.1.96_aarch64.dmg`)。清单 `downloads` 地址由发布脚本按本次真实产物派生,禁止写死产品名;首装包选择按 `<版本>_<架构>.dmg` 唯一匹配,不依赖产品名字面量。
|
||||
- Windows 提权 ACL 修复助手按目录名识别安装身份:`<基线>` 与 `<基线>.<渠道>` 都在 AGC 自有的 managed 范围内;相似前缀(例如 `world.genarrative.ai-game-creator-backup`)不在范围内,落回 user-selected 范围或直接拒绝。
|
||||
|
||||
## 非目标
|
||||
|
||||
- 不做灰度放量、分批更新、强制更新和自动回滚;渠道只决定「取哪份清单」。
|
||||
@@ -30,12 +49,24 @@
|
||||
|
||||
## 必须成立的行为
|
||||
|
||||
### 官网下载与客户端服务地址
|
||||
|
||||
- 官网首页提供无需登录的「下载客户端」入口,桌面和移动视口均可访问;入口打开独立下载面板,复用平台按钮、弹窗和状态组件。
|
||||
- 每次打开下载面板请求同源公开 `GET /api/client-downloads`,后端并行读取配置渠道的 `<channel>-win/latest.json` 与 `<channel>-mac/latest.json` 并汇总已发布平台,网页和接口均禁用缓存。平台列表与每项版本完全来自清单;不在网页写死版本或猜测文件名。
|
||||
- 接口返回 `{ downloads: [{ platform, architecture, version, downloadUrl }], unavailablePlatforms: [] }`,平台为 `windows` / `macos`,架构为 `x86_64` / `aarch64`;Windows 仅支持 x86_64,Mac 按清单实际提供的架构展示 Apple Silicon / Intel 下载项。DTO 由 `shared-contracts` 与 `packages/shared` 对齐,不接受请求参数指定上游 URL,不触及 SpacetimeDB。
|
||||
- 渠道清单新增可选 `downloads` 字典,键与 updater 平台键一致,值为 `{ url }`,只登记首装包。Windows `.exe` 可同时用于首装和更新;macOS 首装必须为 `.dmg`,不得将 `.app.tar.gz` 当首装包。已发布且没有 `downloads` 字段的 Windows 清单可读取既有 `platforms.windows-x86_64.url`;Mac 没有首装元数据时隐藏,不推导 DMG 地址。
|
||||
- 渠道 404 视为尚未发布并隐藏该平台;两端均未发布时显示空状态。单渠道请求失败、超时或格式非法时保留另一端有效下载项,同时提示部分平台暂不可用并允许重试;没有任何有效项且存在失败时返回可读 502。关闭面板取消请求,迟到响应不能覆盖下一次打开的状态。
|
||||
- 每个上游请求总超时 10 秒、响应体上限 128 KiB、不跟随重定向、不附加用户凭据;返回的链接仅接受固定 OSS 来源、对应 `/agc/<channel>-win|mac/<version>/` 分区下的 HTTPS `.exe` / `.dmg` 对象,架构键必须属于对应系统。不提供陈旧、未知来源或版本不匹配的下载地址,不泄露上游正文。
|
||||
- AGC 本地 debug 态保留服务器选择,可选择 `release`、`dev` 或自定义 HTTPS / 本机 HTTP 地址;正式打包产物隐藏服务器选择。打包产物的平台服务地址跟随构建渠道:`release` 渠道连接 `https://www.genarrative.world`,`dev` 渠道连接 `https://dev.genarrative.world`,不会被旧的本地服务器偏好覆盖。自定义 LLM 配置不属于平台服务器选择。
|
||||
- access token 与 origin 一起保存;登录、刷新、退出和原生会话回写都使用同一个已冻结的 origin。没有 origin 的旧 token 一律清除,因为旧版可以单独修改服务器偏好,偏好不能证明 token 来源。
|
||||
- 验收覆盖 debug 服务器选择与自定义地址、release/dev 渠道服务地址、origin 隔离、旧服务器偏好、首页入口挂载、动态最新版本链接、清单失败与重试、关闭取消、桌面和移动布局,以及公开清单和安装包的真实可读性。
|
||||
|
||||
### 正常路径
|
||||
|
||||
- 正式包启动时检查一次渠道清单;仅当清单版本高于当前版本时显示更新提示,提示包含目标版本与发布说明。
|
||||
- 用户确认后下载更新包:下载期间显示进度与已下载字节数;下载完成后按平台安装。
|
||||
- Windows 使用静默安装模式(NSIS `quiet`),安装启动成功后客户端退出并由安装程序重启新版本;macOS 由客户端在安装完成后重启进程接管新版本。
|
||||
- 渠道在构建期确定并烘焙进产物:`dev-win` 产物只读 `dev-win` 清单,`dev-mac` 产物只读 `dev-mac` 清单,同一份二进制不会在运行期跨渠道切换。
|
||||
- 渠道与系统在构建期确定并烘焙进产物:如 dev 的 Windows 产物只读 `dev-win` 分区,release 的 Mac 产物只读 `release-mac` 分区,同一份二进制不会在运行期跨渠道切换。
|
||||
- 开发态(`npm run agc` / `agc:serve` 由 Vite dev server 提供前端)不检查更新、不显示更新入口,也不下载任何更新包。
|
||||
|
||||
### 失败、重试与幂等
|
||||
@@ -66,46 +97,77 @@
|
||||
"signature": "<.sig 文件内容>",
|
||||
"url": "https://<oss>/agc/dev-win/0.1.48/<安装包文件名>"
|
||||
}
|
||||
},
|
||||
"downloads": {
|
||||
"windows-x86_64": { "url": "https://<oss>/agc/dev-win/0.1.48/<安装包文件名>" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- 渠道与平台映射:
|
||||
- 渠道与平台分区映射(`<channel>` 为 dev、release 或自定义名称):
|
||||
|
||||
| 渠道 | 构建目标 | 清单平台键 | 更新包 | 清单地址 |
|
||||
| 系统 | 构建目标 | 清单平台键 | 更新包 | 清单地址 |
|
||||
| --------- | ------------------------ | ---------------------------------------------- | ------------------------ | ------------------------------------ |
|
||||
| `dev-win` | `x86_64-pc-windows-msvc` | `windows-x86_64` | NSIS `.exe` + `.exe.sig` | `<OSS base>/agc/dev-win/latest.json` |
|
||||
| `dev-mac` | `universal-apple-darwin` | `darwin-aarch64` + `darwin-x86_64`(同一对象) | `*.app.tar.gz` + `.sig` | `<OSS base>/agc/dev-mac/latest.json` |
|
||||
| Windows | `x86_64-pc-windows-msvc` | `windows-x86_64` | NSIS `.exe` + `.exe.sig` | `<OSS base>/agc/<channel>-win/latest.json` |
|
||||
| macOS | `aarch64-apple-darwin` | `darwin-aarch64` | `*.app.tar.gz` + `.sig` | `<OSS base>/agc/<channel>-mac/latest.json` |
|
||||
|
||||
- 对象布局:清单固定写成 `agc/<channel>/latest.json`;安装包与签名写成 `agc/<channel>/<version>/<file>` 与 `<file>.sig`。
|
||||
- macOS 使用 universal 包:`dev-mac` 按 universal 目标构建(Intel 与 Apple Silicon 共用一个包),清单把同一个 `.app.tar.gz` 与同一个签名分别写入 `darwin-aarch64` 与 `darwin-x86_64`,升级后仍是 universal 包。这是 Tauri 官方发布工具对 universal 产物的既有写法。
|
||||
- 上一条的两个键不能合成单一 `darwin-universal` 键:更新插件按运行时实际架构解析清单键(Apple Silicon 命中 `darwin-aarch64`,Intel 命中 `darwin-x86_64`),不存在自动命中 `darwin-universal` 的情形。将来真要单独发该键,必须在客户端同时设置自定义 target,否则清单里这一项永远不会被读取。
|
||||
- 对象布局:清单固定写成 `agc/<channel>-win|mac/latest.json`;安装包与签名写成同一分区的 `<version>/<file>` 与 `<file>.sig`。
|
||||
- macOS 当前只出 Apple Silicon 单架构包(`aarch64-apple-darwin`),清单只登记 `darwin-aarch64`;**不再**登记 `darwin-x86_64`,避免把 arm64 产物发给 Intel 客户端。恢复 Intel(universal)的前置条件是随包 Node 也能按架构各带一份:`stage-node-runtime.mjs` 对 universal 目标失败关闭,因为一台构建机只能提供宿主架构的官方 Node,只带一份会让 Intel 上该运行时不可执行,而通用包自检按 `process.arch` 校验、抓不到这个问题。
|
||||
- 主程序当前只出 arm64 单架构,但 `tauri.macos.conf.json` 仍并列映射 `darwin-arm64` / `darwin-x64` 两套原生 Codex 组件:每个组件保持上游单架构布局与独立 SHA-256 清单,运行中的主程序切片只选择同架构目录,不得把两套原生包的元数据或辅助程序混装;随包 Node 只有宿主架构那一份(本地自检会按架构核对并实际执行它)。
|
||||
- 构建期要求:打开 `bundle.createUpdaterArtifacts` 以生成 `.sig`;构建环境提供签名私钥与密码(私钥内容不得入库);公钥写入客户端配置。公钥在首个带更新能力的版本发布后不可更换,更换等于放弃自动更新(只能手动重装)。
|
||||
- 版本递增按渠道独立进行:发布脚本读取该渠道远端 `latest.json` 的 `version`,与本地版本取较高者递增 patch;两个渠道的版本号互不影响。
|
||||
- 版本高水位:发布脚本取「渠道清单版本」与「旧协议迁移指针版本」(迁移窗口内)中的较大值再递增。只看渠道清单会在渠道启用初期把版本链改小 —— 2026-09-17 首次渠道发布即把旧指针的 0.1.57 退回 0.1.48,随后以显式 0.1.60 纠偏;迁移窗口结束(旧指针 404)后自动只剩渠道清单,`dev-mac` 不参与旧指针比较。
|
||||
- 版本递增按渠道及系统分区独立进行:发布脚本读取该分区远端 `latest.json` 的 `version`,与本地版本取较高者递增 patch;不同分区的远端版本互不影响。
|
||||
- 版本高水位:仅 dev 的 Windows 分区在迁移窗口内取「分区清单版本」与「旧协议迁移指针版本」较大值再递增,避免已发布旧客户端版本倒退。迁移窗口结束(旧指针 404)后只读分区清单;release、自定义渠道与所有 Mac 分区均不参与旧指针比较。
|
||||
- 迁移(旧协议 → 渠道清单):
|
||||
- 迁移起点:已发布客户端(含当前线上版本)内置自研清单地址 `agc/latest.json`(sha256 格式),下载与安装由自研 Rust 命令完成。
|
||||
- 迁移策略见「未决问题与决策」。迁移完成后,自研清单解析、下载命令、下载进度事件以及为此放行的 CSP / HTTP 白名单条目按「四不写」整条删除,不留兼容分支与墓碑说明。
|
||||
|
||||
- 渠道安装身份映射(`<channel>` 为 `dev`、`release` 或自定义名称;`<Channel>` 为渠道显示名):
|
||||
|
||||
| 渠道 | productName | identifier | Windows 安装目录 | 客户端数据目录 |
|
||||
| -------------------- | ------------------ | ---------------------------------------------- | ----------------------------------- | ----------------------------------------------- |
|
||||
| `dev`(默认) | `陶泥儿` | `world.genarrative.ai-game-creator` | `%LOCALAPPDATA%\陶泥儿` | `%APPDATA%\world.genarrative.ai-game-creator` |
|
||||
| `release` / 自定义 | `陶泥儿 <Channel>` | `world.genarrative.ai-game-creator.<channel>` | `%LOCALAPPDATA%\陶泥儿 <Channel>` | `%APPDATA%\world.genarrative.ai-game-creator.<channel>` |
|
||||
|
||||
- 安装身份迁移:`dev` 客户端保持原身份,升级链路连续;`release` 与自定义渠道首次以新身份安装,**不接管也不迁移**任何既有 `dev` 安装、本地项目或登录态,设备上因此可以同时存在两个渠道的客户端,由用户自行决定是否卸载其一。
|
||||
|
||||
## 构建与发布
|
||||
|
||||
- 发布入口:`npm run ai-game-creator-shell:release:upload`(构建 + 按渠道上传);仅构建不发布的 smoke 使用 `--no-bundle` 分支,不读远端版本、不改版本、不生成清单。
|
||||
- 渠道由构建参数显式指定,并按目标平台校验:Windows 目标只允许 `dev-win`,macOS 目标只允许 `dev-mac`;未显式指定时按目标平台取默认渠道。
|
||||
- 发布入口只解析一次目标,优先级为 CLI `--target value` / `--target=value` / `-t value`、`AGC_BUILD_TARGET`、Windows 默认值;重复/空目标与不支持目标失败关闭。版本高水位、构建 feature/渠道端点、bundle 路径、产物后缀、清单平台键及摘要必须消费同一个发布上下文,不能分别回读默认目标。
|
||||
- 渠道由 `AGC_UPDATE_CHANNEL` 显式指定,默认 dev;Windows 与 macOS 目标均支持 dev、release 和自定义渠道,目标校验独立进行。
|
||||
- 渠道 `--config` 在 Tauri 构建前最后合并,同时注入 `productName`、`identifier` 与 updater 端点:安装身份与更新端点必须来自同一个渠道,不能各自回读默认值。macOS 发布入口构建 `*.app`、updater 归档与 DMG 前先按发布渠道解析产品名,产物名一律派生而不写死。
|
||||
- 定时调度只在本轮到达的提交包含 AGC 相关路径(客户端、共享包、`server-rs/crates`、AGC 插件、桌面壳图标、根依赖清单)时才触发渠道发布;纯文档或流水线自身的提交只跑 Full Build,不推高客户端版本号。判定失败或勾选强制触发时按"需要发布"处理。
|
||||
- 更新摘要自动生成:发布脚本用渠道清单里的 `commit` 字段(上一次发布的提交)到本次提交之间、且只覆盖客户端相关路径的提交列表生成 `notes`(每条 `- 提交标题(短 SHA)`,最多 12 条、主题 80 字、整体 900 字,超出折叠或截断),同时写入旧协议清单的 `releaseNotes` 和归档文件 `release-notes.txt`。`AGC_UPDATE_RELEASE_NOTES` 非空时以手动文案为准;无法判定起点(缺少上次 `commit` 或本地没有该提交)时不写摘要。清单缺少 `commit` 时回退用上一次成功构建的 `COMMIT_HASH`(CI 通过 `AGC_UPDATE_PREVIOUS_COMMIT` 传入)作为锚点,因此首次启用摘要或更换渠道后也能立即产出摘要。锚点仍不可得(清单读取失败或没有 CI 锚点)时降级为「最近客户端改动」列表并注明可能与上一版重复 —— 摘要属于附注,任何情况下都不允许因为它让发布失败。
|
||||
- 清单里的 `commit` 是非标准字段:更新插件忽略未知字段,发布脚本用它定位下一次摘要的起点。
|
||||
- 上传:安装包与 `.sig` 上传到 `agc/<channel>/<version>/`,清单以 `--force` 覆盖上传到 `agc/<channel>/latest.json`,保证 latest 指针与清单内 URL 指向已存在的对象。
|
||||
- 上传:安装包与 `.sig` 上传到 `agc/<channel>-win|mac/<version>/`,清单以 `--force` 覆盖上传到对应分区的 `latest.json`,保证 latest 指针与清单内 URL 指向已存在的对象。
|
||||
- 首装发布:发布脚本生成 `downloads`,Windows 复用已选 NSIS `.exe`,Mac 选择本次版本和目标架构匹配的非空 `.dmg`;缺失、歧义或版本/架构不匹配时失败,不发布带悬空地址的清单。上传顺序为更新包、签名及首装包全部成功后再更新渠道清单,Windows 相同对象只上传一次。`dry-run` 不写 OSS。各渠道独立写自己的清单,由 BFF 汇总,Windows 与 Mac 发布不会覆盖彼此的下载项;Mac 跨架构合并仍遵循现有单架构发布约束。
|
||||
- Jenkins 流水线需要新增渠道参数与签名凭据;签名私钥与密码只以受保护凭据注入当前进程,不写入 workspace、日志或归档产物。
|
||||
- 归档证据:安装包、`.sig`、渠道清单与源码 commit。
|
||||
|
||||
## 验收标准与证据
|
||||
|
||||
渠道与系统分离的定向验证覆盖发布脚本、上传计划、网站配置贯通及跨渠道链接拒绝:`build-release.test.mjs`、`release-oss.test.mjs`、`platform-oss client_downloads` 和 `api-server client_download`。本地隔离数据库的 API smoke 验证 `/healthz` 成功、配置 release 时仅读取 release 分区、查询参数不能覆盖渠道、未发布版本返回空列表且 `no-store`;这不代表已构建或上传 release 安装包。真实 Windows/macOS 安装、签名和更新仍由发布验收单独执行。
|
||||
|
||||
官网下载与渠道服务地址已于 `2026-09-19` 完成源码验收:
|
||||
|
||||
| 条款 | 验收方式 | 结果 |
|
||||
| --- | --- | --- |
|
||||
| 渠道服务地址、旧凭据来源隔离与登录恢复 | HTTP/API/存储定向测试、登录会话界面测试、AGC 类型检查 | 通过;无 origin token 不发送,渠道 origin 与会话回写保持一致 |
|
||||
| 动态链接、失败重试、取消与重开竞态 | 下载组件与站点壳定向 Vitest、Web 类型检查 | 通过;桌面与移动首页均挂载入口 |
|
||||
| 已发布平台自动出现与各平台独立版本 | 多平台聚合测试、组件测试及桌面/320px 浏览器受控清单 | 通过;未发布隐藏,重新打开后展示 Windows、Apple Silicon、Intel 的对应版本与链接,部分失败仍可下载有效项 |
|
||||
| 首装元数据、DMG 选择与上传顺序 | 发布脚本 39 项临时夹具测试 | 通过;限定当版/当架构唯一非空 DMG,更新包/签名/首装包先于 latest,失败不更新指针,dry-run 不执行上传 |
|
||||
| 清单传输与下载地址边界 | platform-oss 12 项与 api-server 3 项 `client_downloads` 定向 Cargo 测试 | 通过;含双渠道并行、部分失败、残缺 Windows 清单、响应体超时、大小上限、重定向与非法来源 |
|
||||
| 官网同源完整链路 | 标准本地后端 `/healthz`、匿名 `/api/client-downloads`、Vite 同源代理与真实浏览器 | 健康检查 200,下载接口 200/no-store;真实列表保留 Windows 0.1.73、隐藏未发布 Mac;桌面和窄屏可操作 |
|
||||
| 发布对象可读取 | 公开清单、安装包 HEAD 与 Range | 当日 Windows 0.1.73 清单 200,安装包 104867522 字节、Range 206 且为 EXE;macOS 清单 404,不展示下载 |
|
||||
|
||||
本次未构建或安装新客户端包,未上传或部署官网;Mac 浏览器证据使用受控清单,不能替代 Mac 原生构建、签名与安装验证。真实安装升级及生产部署后的 smoke 属于发布验收。
|
||||
|
||||
已获得的证据:
|
||||
|
||||
| 条款 | 验收方式 | 证据 |
|
||||
| ---------------------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| 渠道与端点映射、渠道校验 | `node --test apps/ai-game-creator-shell/scripts/build-release.test.mjs` | 通过(默认渠道、错配失败关闭、未知渠道失败关闭) |
|
||||
| universal 包挂两个平台键 | 同上 + 本地发布烟测(伪造 bundle) | 通过(两键同 URL 同签名,不生成迁移清单) |
|
||||
| macOS 单架构清单与随包资源 | 定向发布脚本与安装包验证 | 清单只有 `darwin-aarch64`;DMG/更新包按架构命名并唯一匹配;真机验收另记 |
|
||||
| 缺签名时失败关闭 | 同上 | 通过 |
|
||||
| 开发态不检查更新 | `vitest run apps/ai-game-creator-shell/tests/appUpdate.test.ts` | 通过(开关关闭时不请求清单) |
|
||||
| 旧自研链路整条删除 | 代码检索无残留命令、事件与白名单条目 | 通过(`download_agc_update` / 下载事件 / 清单常量均无残留) |
|
||||
@@ -116,6 +178,25 @@
|
||||
| 真实更新闭环(含升级后重启) | 0.1.47 客户端按提示下载安装并重启 | 通过(2026-09-17 用户实测:提示 → 下载 → 安装 → 关于页显示新版本,再次检查为已是最新) |
|
||||
| 更新摘要端到端展示 | 公网读取渠道清单 `notes` 与客户端更新提示 | 通过(2026-09-17 用户实测:0.1.62 清单带 8 条自动摘要,客户端提示正常显示多行内容) |
|
||||
|
||||
渠道安装身份隔离已于 `2026-09-21` 完成源码验收:
|
||||
|
||||
| 条款 | 验收方式 | 结果 |
|
||||
| --- | --- | --- |
|
||||
| 渠道身份派生与默认渠道不变 | `node --test apps/ai-game-creator-shell/scripts/build-release.test.mjs` | 通过;`dev` 逐字等于基线 `陶泥儿` / `world.genarrative.ai-game-creator`,`release` / `beta-2` 派生独立产品名与 identifier,非法渠道失败关闭 |
|
||||
| 身份与端点同批注入 | 同上的渠道 `--config` 用例 | 通过;`productName` / `identifier` 与 `/<channel>-win|mac/latest.json` 来自同一次解析 |
|
||||
| 渠道产物首装包选择 | 同上的渠道 DMG 夹具用例 | 通过;`陶泥儿 Release_<版本>_aarch64.dmg` 仍按 `<版本>_<架构>.dmg` 唯一匹配 |
|
||||
| 基线配置等于默认渠道身份 | `node apps/ai-game-creator-shell/scripts/check-config.mjs` | 通过;基线漂移与非默认渠道身份不隔离都会失败关闭 |
|
||||
| 全量发布脚本回归 | `node --test build-release.test.mjs release-oss.test.mjs prepare-macos-codex.test.mjs cargo-features.test.mjs` | 通过(64/64,含 macOS 入口按渠道解析产品名的守卫) |
|
||||
| AGC 自有 AppData 提权 ACL 范围 | `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;含基线、`<基线>.release`、`<基线>.beta-2` 与相似前缀 `-backup` 的反向断言) |
|
||||
| 渠道身份进入真实构建产物 | `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 |
|
||||
|
||||
未验证项(不得按已通过处理):
|
||||
|
||||
- 真实 Windows 双渠道安装与并存:尚未在同一台设备安装 `dev` 与 `release` 两个渠道的安装包,安装目录/卸载项/数据目录的分离与两个客户端同时运行属于发布验收,本次只到源码与脚本层级。
|
||||
- 未生成安装包:本轮构建烟测止于 `--no-bundle`,NSIS 安装目录 / 卸载项 / 快捷方式按渠道分开、以及「装完 release 后 dev 仍在」的现场证据需要一次真实渠道打包与安装。
|
||||
- macOS 侧只验证到入口派生逻辑:`.app` 名、bundle id 与 DMG 卷名的渠道派生没有在 macOS 节点实跑。
|
||||
- 客户端数据隔离的运行期事实(`%APPDATA%\<identifier>` 分目录、登录态不跨渠道)未做真机对照。
|
||||
|
||||
待执行证据(首次渠道发布后回填):
|
||||
|
||||
| 条款 | 验收方式 | 证据 |
|
||||
@@ -127,11 +208,15 @@
|
||||
|
||||
已决策:
|
||||
|
||||
- macOS 采用 universal 包,同一产物同时挂 `darwin-aarch64` 与 `darwin-x86_64` 两个清单键(见「契约与迁移」)。
|
||||
- macOS 当前只验 arm64:主程序用 `lipo` 检查架构,Codex 与随包 Node 做原生身份/摘要与启动验证。恢复 Intel 后必须补回双架构 smoke 与 Intel 真机验收,Rosetta 结果不能替代。
|
||||
- 旧客户端迁移桥:保留一个版本周期。渠道清单上线后,发布管线同时把旧的 `agc/latest.json`(sha256 格式)指向 `dev-win` 最新安装包,让已发布客户端自动升级到新协议;下个周期整条删除。
|
||||
- 签名密钥:由本仓库维护者生成并保管,私钥保存在仓库外(`%USERPROFILE%\.tauri\genarrative-agc-updater.key`),只有公钥进入客户端配置;Jenkins 用受保护凭据 `AgcUpdaterSigningKey` 与 `AgcUpdaterSigningKeyPassword` 注入为 Tauri 打包器读取的 `TAURI_SIGNING_PRIVATE_KEY` 与 `TAURI_SIGNING_PRIVATE_KEY_PASSWORD`,本机可用 `TAURI_SIGNING_PRIVATE_KEY_PATH` 指向同一私钥。当前密钥不带密码;首次发布前仍可重新生成,首次发布后不可更换。
|
||||
- macOS 发布方式:`dev-mac` 产物在本机 mac 上执行发布入口上传,Jenkins 暂不新增 macOS 节点;macOS 代码签名与公证凭据未确认前,相关闭环记为未验证项,不静默通过。
|
||||
- macOS 发布方式:已接入专用 macOS Jenkins 节点(label `genarrative-agc-macos`,EXCLUSIVE 单 executor),由 `Jenkinsfile.ai-game-creator-shell-macos-build` 执行 `scripts/build-macos-ci.mjs` 完成 arm64 单架构构建、arm64 隔离 smoke、arm64 DMG(`<产品名>_<版本>_aarch64.dmg`)、分区清单生成、更新包验签与 OSS 上传。`AGC_RELEASE_DRY_RUN` 默认为关(与 Windows 渠道对称,即直接发布),只有勾选后才退化为「只打印上传计划、不写 OSS」的演练。
|
||||
- macOS 代码签名与公证暂缺:产物为未签名 + 未公证,构建入口剥离 `APPLE_*` 凭据跳过 Apple 签名,不传 `--no-sign`(它还会跳过 updater 的 minisign 签名,产物将没有 `.sig`);构建清单实测记录 `appleSigned` 与签名类型,`latest.json` 侧固定记录 `notarized=false`,首装需用户在 Gatekeeper 中手动放行。该限制作为已知未验证项记录,不静默通过;「安装 → 重启接管新版本」的自动更新闭环仍需实机验收。
|
||||
- 更新包验签门禁:构建完成、上传 OSS 之前,用产物内烘焙的 `plugins.updater.pubkey` 复核 `<更新包>.sig`(Tauri 使用 minisign 的 `ED` 预哈希模式)。keyId 不一致或校验失败立即失败关闭,禁止上传——客户端校验失败会直接拒绝安装,且公钥发布后不可更换。
|
||||
- 渠道安装身份(2026-09-21):渠道此前只决定更新端点,`productName` / `identifier` 与渠道无关,导致不同渠道的包体共用 `%LOCALAPPDATA%\陶泥儿`、同一个卸载项与同一份 `%APPDATA%\world.genarrative.ai-game-creator` 数据目录,后装的渠道静默顶掉先装的渠道并接管更新端点与本地登录态。现决策为「渠道进安装身份」:默认渠道保持基线身份不动,其它渠道派生 `<产品名> <渠道显示名>` 与 `<基线>.<渠道>`,渠道内仍原地升级,同机并存与数据隔离成立。窗口标题、macOS 产物名与 Windows 提权 ACL 的 managed 识别范围同批跟随该身份。
|
||||
|
||||
待办:
|
||||
|
||||
- macOS `dev-mac` 渠道落地(macOS 构建机、签名与公证、安装后重启验证、是否接入 Jenkins macOS 节点)暂缓,由后续独立变更单独完成;在此之前 `dev-mac` 渠道只有构建与清单能力,不发布。
|
||||
- macOS 分区(`<channel>-mac`)已落地构建与发布能力:Mac Jenkins 节点、release 入口(更新包 + 签名 + 首装包 + 分区清单)、验签门禁与调度接入均已就绪,默认直接发布。
|
||||
- 剩余待办:Apple 代码签名与公证凭据(未就绪期间以未验证项记录)、macOS 安装后重启接管新版本的实机验证、Intel 真机 smoke(当前 x86_64 侧为 Rosetta)。
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
# AGC 总版本号与发号
|
||||
|
||||
更新时间:`2026-09-20`
|
||||
|
||||
## 背景
|
||||
|
||||
AGC 客户端此前按「渠道各自比高水位自增」发号:`dev-win` 与 `dev-mac` 各自读自己的渠道清单,`prepareReleaseVersion()` 取本地版本与远端高水位的较大值再 `patch + 1`。两个渠道因此天然拿到不同的号,统一构建无法保证 Windows 与 macOS 是同一个版本,手工填号或并发发号还会出现重号与回退。
|
||||
|
||||
本方案把客户端版本号收敛成单一发号源,渠道只写自己本次拿到的号。
|
||||
|
||||
## 版本源
|
||||
|
||||
- 唯一事实源是 OSS 对象 `agc/global-version.json`,字段:`version`、`updatedAt`、`channel`、`commit`、`buildId`。
|
||||
- 渠道清单(`agc/<channel>/latest.json`)仍写各自本次的版本号,但不再承担发号职责。
|
||||
- 仓库里由构建改写的 5 个版本文件(`apps/ai-game-creator-shell/package.json`、根 `package-lock.json`、`src-tauri/tauri.conf.json`、`src-tauri/Cargo.toml`、`src-tauri/Cargo.lock`)继续按现状由构建改写,**只作构建输入参考、不作为事实源**,不提交、不参与发号。
|
||||
|
||||
## 发号规则
|
||||
|
||||
- `next = 总号 + 1`;总号不存在时先一次性播种,见下节。
|
||||
- 顺序固定为「先写总号 → 再构建 → 再发渠道清单」;任何一步失败都不回滚,只烧号。
|
||||
- 统一构建:发一次号,通过 `AGC_RELEASE_VERSION` 同时传给 `dev-win`、`dev-mac`(以及后续渠道),各渠道共用同一个号。
|
||||
- 单渠道热修:发一次号,只传给该渠道;其他渠道清单保持原值。
|
||||
- 显式传入 `AGC_RELEASE_VERSION` 的构建不再自行发号,只做高水位断言。
|
||||
|
||||
## 并发控制
|
||||
|
||||
- 发号收口到专用 Jenkins Job `Genarrative-Agc-Global-Version-Issue`(`disableConcurrentBuilds()` + 写后回读校验)。集群未安装 `lockable-resources` 插件,因此以 Job 级串行 + 写后回读兜底:写完总号后立刻回读,若远端值与本次写下不一致即失败关闭(号已烧,不重试、不回滚),由人工确认后再发。
|
||||
- 各渠道构建只接收号,不自己加;本地手工兜底路径(未传 `AGC_RELEASE_VERSION`)同样走「读总号 → +1 → 写回 → 回读校验」。
|
||||
- 不允许裸读改写:任何路径都必须经过发号模块,写后回读不一致即失败关闭。
|
||||
|
||||
## 一次性播种
|
||||
|
||||
- 基线 `seed = max(仓库当前版本, agc/dev-win/latest.json, agc/dev-mac/latest.json, agc/latest.json 旧指针)`。
|
||||
- 播种只写基线本身,不递增、不烧号;首个发放号是基线 + 1。
|
||||
- 播种入口:`node apps/ai-game-creator-shell/scripts/issue-global-version.mjs --seed-only`,或发号 Job 勾选 `SEED_ONLY`。
|
||||
|
||||
## 实现位置
|
||||
|
||||
- 发号模块:`apps/ai-game-creator-shell/scripts/agc-global-version.mjs`(读总号 / 播种 / 发号 / 写后回读 / 渠道高水位断言 / dry-run 预览)。
|
||||
- 发号入口:`apps/ai-game-creator-shell/scripts/issue-global-version.mjs`(CI 与本地共用,输出固定为 `AGC_GLOBAL_VERSION=<version>`)。
|
||||
- 构建侧:`build-release.mjs` 的 `prepareReleaseVersion()` 优先采用传入总号,未传入时现场发号;`resolveRemoteHighWaterVersion()` 降级为断言来源,只用于「请求号低于本渠道清单版本即失败关闭」。
|
||||
- CI:`jenkins/Jenkinsfile.agc-global-version-issue` + `jenkins/agc-global-version-issue-job-config.xml`;调度管线 `jenkins/Jenkinsfile.scheduled-revision-trigger` 与手动管线先调发号 Job,再把号透传给 AGC Build。
|
||||
- 发号 Job 的 Copy Artifact 采用生产权限模式,`options` 里的 `copyArtifactPermission(...)` 必须显式列出**全部**消费者:定时调度 `Genarrative-Scheduled-Revision-Trigger` 与手动发布 `Genarrative-Manual-Build-And-Deploy`。用户触发的构建按「认证用户」判权(SYSTEM 定时构建短路放行),漏列时手动发布会报 `Unable to find project for artifact copy: Genarrative-Agc-Global-Version-Issue`,且失败点在发号之后。改完该 `options` 后必须先单独跑一次发号 Job,让 Declarative Pipeline 把 Job property 写回 Jenkins。
|
||||
|
||||
## 不改的东西
|
||||
|
||||
- `appUpdate.ts` 与官方 updater 的比较逻辑、渠道清单端点。
|
||||
- `release-oss.mjs` 上传流程。
|
||||
- `agc/latest.json` 只由 `dev-win` 写入。
|
||||
- 主站下载检查的读取源,不接总号。
|
||||
|
||||
## 边界约定
|
||||
|
||||
- `dev-mac` 在 macOS 构建机本地发布时同样只接收号(发号 Job 或本地发号脚本),不允许手动填号。
|
||||
- `AGC_RELEASE_DRY_RUN` 只读总号并预览 +1,不写回、不烧号。
|
||||
|
||||
## 验收标准
|
||||
|
||||
1. 统一构建后,`dev-win` 与 `dev-mac` 清单版本相同且等于总号。
|
||||
2. 单渠道热修后,只有该渠道清单变化,总号 +1,其他渠道清单原值不动。
|
||||
3. 热修后再统一构建,总号继续 +1,其他渠道版本跳过中间号。
|
||||
4. 并发两次发号不出现重号。
|
||||
5. 传入低于本渠道当前版本的号时构建失败关闭。
|
||||
@@ -61,10 +61,25 @@ OpenAPI 与客户端工具的对外说明只描述模式用途、参数约束和
|
||||
}
|
||||
```
|
||||
|
||||
客户端保留旧参数调用;新字段不填时不改变旧调用语义。客户端不读取图片、不自动选色、不把 `auto` 改写为具体颜色,使用原有 Bearer 认证、幂等键和队列返回模型。
|
||||
客户端保留旧参数调用;新字段不填时不改变模式语义。客户端不自动选色、不把 `auto` 改写为具体颜色;源资源校验、认证、异步任务查询、结果下载和本地登记由客户端负责。
|
||||
|
||||
工具 schema、桥接参数校验和随包 `agc-client-projection` Skill/契约说明必须保持一致。模式与颜色属于请求意图,必须参与客户端幂等指纹;同一图片与名称的不同模式不能复用同一次请求。缺省 complex 且没有颜色时保留既有指纹。主站在默认值归一化之前计算 External 请求指纹,缺失的新字段不序列化,避免旧请求重放发生冲突。
|
||||
|
||||
客户端提交前建立的本地项目绑定会返回主站远端 `projectId` 与 `assetFolderId`,抠图请求必须使用这两个远端身份;本地 manifest `projectId` 仅用于绑定和本地状态,不能直接提交给主站。
|
||||
|
||||
### 本地结果与恢复合同
|
||||
|
||||
`agc_remove_background` 复用本地资源编辑账本,类型为 `background-removal`。源图片保持不变,抠图结果作为新资源写入项目。模式和背景色随账本持久化并参与请求指纹,其它编辑类型的历史指纹保持不变。
|
||||
|
||||
1. 客户端建立源图片的正式资源绑定,在提交前保存 operation、幂等键和请求意图。普通登录态提交 `/api/editor/images/background-removals`,从 `data.queueState.operationId` 读取受理身份;开发者模式提交 External v1 对应路由,从 `data.operationId` 读取身份。
|
||||
2. 受理后持续查询账号路由 `/api/runtime/external-generation/jobs/{operationId}` 或 External v1 对应状态路由。`queued`、`running` 只描述远端返回状态;固定进度值、本地文件缺失或 pending 清单为空均不能证明 BgFilter 排队。
|
||||
3. 远端 completed 后按稳定资源身份换取有效下载 URL,校验结果为带 alpha 通道的有效 PNG,随后复用 staging、manifest 和 revision 提交。只在本地登记完成后向 Agent 返回 `completed`、`operationId`、`resource.localAssetId`、相对路径和安全告警,不暴露临时 URL 或凭据。
|
||||
4. 轮询中断、超时或下载失败保留已受理 operation;`agc_list_registered_assets.pendingOperations` 与客户端恢复面板可见。相同源资源、结果名称、模式和颜色的后续调用优先恢复同一任务,不再次提交。不同账号不能恢复原账号任务;切回原账号后按既有恢复规则续接。
|
||||
5. 远端 failed 明确失败;提交响应不确定且无法确认 operation 时进入人工对账状态,不自动换键重发。失败/未知均不得伪造透明图或自动切换本地抠图方式。
|
||||
6. 升级前仅返回 queued、没有本地账本的任务不自动迁移;已有远端结果须通过正式资源查询和导入恢复,不据旧回执重新发起付费请求。
|
||||
|
||||
本修复只扩展 AGC 客户端现有工作流,不修改主站队列、BgFilter 或 SpacetimeDB schema。验收覆盖账号与开发者两种响应封装、queued/running/completed、已受理中断恢复不重复 POST、远端失败不登记结果,以及既有资源编辑回归。
|
||||
|
||||
## 实施任务
|
||||
|
||||
### 任务一:冻结 BgFilter 契约
|
||||
@@ -93,6 +108,13 @@ OpenAPI 与客户端工具的对外说明只描述模式用途、参数约束和
|
||||
|
||||
## 验收证据
|
||||
|
||||
2026-09-17 客户端闭环验证:
|
||||
|
||||
- `cargo test --locked --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml --bin genarrative-ai-game-creator-shell project::resource_editor:: -- --test-threads=1` 通过。新增 HTTP fixture 覆盖开发者 queued/running/completed、账号 HTTP 200 queueState 与 `data.job`、换签下载、PNG alpha 校验、原图保留和新资源提交。
|
||||
- 已受理任务的首次轮询失败后,pending 保留模式与颜色;恢复只查询原 operation,整个流程只 POST 一次,成功后清除 pending。远端 failed 不新增结果资源。既有账号隔离、提交原子性和崩溃恢复用例通过。
|
||||
- `cargo test --locked --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml --bin genarrative-ai-game-creator-shell agent::direct_tool_bridge::tests -- --test-threads=1`、AGC 类型检查、技能包校验、Rust 格式、编码、文档索引与 diff 检查通过。
|
||||
- 本轮未运行真实登录客户端 → 本地主站 → BgFilter 的端到端 smoke;当前本地后端已停止,自动化证据使用模拟 HTTP 服务。此前 BgFilter 成功日志只证明上游处理完成,不证明主站结果持久化或客户端导入成功。
|
||||
|
||||
2026-09-16 实测:
|
||||
|
||||
- 主站 `cargo test -p api-server background_removal`:36 项通过,覆盖非法请求入队前拒绝、缺省 complex、队列参数保留、旧请求指纹、父侧内部 RPC 和 provider multipart。
|
||||
|
||||
@@ -8,51 +8,100 @@ AGC 客户端接入公共 OSS 上的**游戏模板库**(真·游戏模板,
|
||||
- 客户端侧:Rust `template_library` 模块(读清单、下载、安装、建项目)+ 模板库全屏页 + 首页模板推荐 + 左侧导航入口。
|
||||
- 不在本次范围:模板制作工具、模板审核、模板计费、增量更新、已建项目的模板回填。
|
||||
|
||||
## 模板库灰度访问
|
||||
|
||||
- 后台现有灰度发布页登记 `agc:template-library`,控制整个模板库;复用用户 ID 白名单、用户标签、拒绝名单和稳定用户分桶比例,不新增数据库表或字段。未配置此 Gate 时默认关闭;已配置且停用灰度时遵循现有语义全量开放,启用灰度时拒绝名单优先于白名单及比例。
|
||||
- `/api/runtime/frontend-config` 增加 `agcTemplateLibraryEnabled`,按认证主体返回服务端权威结论。客户端尚未得到结果、匿名或请求失败时关闭入口;权限结果不作为离线缓存。
|
||||
- 登录、主体变化、窗口重新获得焦点和每次模板操作时刷新权限,不承诺后台修改的实时推送。未获准时首页模板推荐和左侧模板库导航隐藏,不读取 OSS 清单;已在模板页检测到失去权限时回到首页。原生命令明确拒绝或权限检查失败后同样清空并关闭入口。账号切换或退出后立即清空前一主体的清单、操作状态和可见性,旧异步响应不能恢复权限或导航。
|
||||
- 原生清单读取、下载和模板建项命令均独立核对当前平台会话及服务端权限,不能依靠前端隐藏;远端等待后的会话变化必须拒绝,安装和建项写入使用当前会话身份保护。已缓存清单和已安装模板不能绕过权限。
|
||||
- 此灰度控制当前客户端的产品功能,不承诺公开 OSS 模板内容的保密性,也不限制已创建项目的正常打开和编辑。旧客户端升级后才接入此控制。
|
||||
- 验收覆盖缺省关闭、明确全量、允许/拒绝名单、标签和比例、请求失败、缓存绕过、原生命令阻断、首页/导航/模板页以及账号切换竞态。
|
||||
- 验证入口:后台灰度页面测试、`useTemplateLibrary.test.tsx`、AppSurface 实际挂载的 template 用例、原生 `template_library` 测试和后端 `frontend_runtime_config` 测试。退出开始使用既有平台会话代次立即撤销,建项返回、revision 读取和预览核验后的旧回调均不得导航或登记最近项目;同主体 token 轮换不误撤销原生身份。
|
||||
- 本地隔离数据库已验证 Gate 经后台 API 保存后可重新读回,匿名运行时配置为 false;后台受控浏览器 smoke 验证桌面和 320px 布局及保存确认交互。线上 OSS 下载和正式安装包登录后的端到端操作不由这些测试替代。
|
||||
|
||||
## 后台模板管理
|
||||
|
||||
- 后台新增 `#agc-templates`「模板管理」页签,复用现有后台布局、列表、公共表单/独立弹窗及写入确认。提供名称/ID/标签搜索、运行时和上下架筛选,展示封面、名称、简介、标签、引擎/版本、包大小与上架状态。
|
||||
- 编辑接口只允许改名称、简介、标签、封面和上架状态,不修改 ID、运行时、引擎或模板版本;**模板新增与 ZIP 上传见「后台模板上传」章节**。两条路径都不删除包、历史对象或已建项目。
|
||||
- 唯一数据真相仍是 OSS `templates/index.json`:`templates` 保存上架条目,新增可选 `inactiveTemplates` 保存下架条目,两个数组之间 ID 唯一。后台合并展示两组;AGC 客户端仍只读取 `templates`,刷新后不展示下架项。下架不是资源访问撤销,旧清单缓存和已下载项目不受影响。下架条目元数据位于公开清单,不承载私密草稿。
|
||||
- 后台读取/写入分别为 `GET /admin/api/agc-templates`、`PUT /admin/api/agc-templates/{id}`,均经过现有后台认证及 `agc-templates` 页签权限。owner 默认可用,member 需显式分配该权限;不新增数据库表或 schema。部署时后台、API 与引用权限白名单的 SpacetimeDB 模块需同步更新,成员账号才可保存新页签权限。
|
||||
- GET 返回 `{ revision, writable, templates }`:revision 是完整原始清单字节的 SHA-256;每条包含 `id/title/summary/tags/runtime/engine/engineVersion/templateVersion/enabled/coverUrl/zipSizeBytes`。不可用或格式错误返回可诊断错误,不能当作空模板库。
|
||||
- PUT 请求 `{ expectedRevision, title, summary, tags, enabled, cover? }`,禁止未知字段。名称 trim 后 1–80 字符、简介最多 1000 字符、最多 16 个去重标签(每项 1–32 字符)。封面可选 `{ contentType, dataBase64 }`,仅接受真实 PNG/JPEG/WebP,解码前后校验,原始字节最多 5 MiB、单边最多 4096 像素且不超过 1600 万像素;原有 SVG 引用可继续展示。HTTP body 上限 8 MiB。返回与 GET 同形的最新快照。
|
||||
- 写入复用同一 OSS 发布锁、版本控制前置检查及不确定清单写入留锁协议;在锁内读取最新清单并比较 expectedRevision,过期或锁忙返回 409,不自动重试写入。封面及更新后的 template.json 以内容摘要键写入并回读校验,再一次提交清单;ZIP、版本和未知元数据字段保留。清单响应不明返回 503 并留锁,后续需核对;任何 API 错误不回显凭据或上游正文。
|
||||
- 页面保存/上下架前使用既有确认交互。编辑弹窗保持独立;保存中防重复提交,失败保留输入并展示错误,409 引导刷新后重新编辑;刷新、切页、换账号和晚到响应不能覆盖新页面状态。封面仅作为待保存草稿预览,成功后使用后端返回的正式地址。
|
||||
- 模板 OSS 目标固定为当前 AGC 公开 bucket/endpoint,不能误用通用素材 Bucket。凭据优先成套读取 `GENARRATIVE_AGC_TEMPLATE_LIBRARY_OSS_ACCESS_KEY_ID/SECRET`,两项均未配置时可成套复用已有 `ALIYUN_OSS_ACCESS_KEY_ID/SECRET`;半配置不拼接回退且禁止写入。无可用写凭据时后台可只读,写接口返回 503,页面明确不可保存。
|
||||
- CLI 与后台共享锁和清单合同。CLI 合并保留未选条目与 `inactiveTemplates`,更新下架模板仍保持下架;新增模板默认上架,CLI 不承担删除或上下架。显式发布选中 ID 时,展示字段按该模板源更新,后台编辑结果持续有效直到下一次显式发布该 ID。两端都必须保留另一组条目,不能因本地模板源较旧而抹掉后台记录。
|
||||
- 验收包含权限、入口挂载、过滤、编辑与图片校验、上下架往返、未知字段保留、过期 revision/锁争用、上传失败与未知提交、CLI 对下架项的更新/保留,以及桌面/窄屏真实浏览器验证。真实 OSS 写入和生产部署不在本轮验证范围,使用隔离存储替身。
|
||||
|
||||
## 后台模板上传
|
||||
|
||||
- 后台「模板管理」页提供「上传模板」入口:一次可多选 `.zip`,再按模板 ID 同名多选封面,逐行确认 ID / 名称 / 版本 / 运行时 / entry 后整批提交。简介、标签与引擎可在上传后用编辑接口补齐。
|
||||
- 接口 `POST /admin/api/agc-templates/import`(`multipart/form-data`):`manifest` 文本字段 + `zip_<index>` / `cover_<index>` 文件字段,下标与 manifest 条目顺序一一对应。manifest 为 `{ expectedRevision, templates: [{ id, title, summary, tags, runtime, engine, engineVersion, entry, templateVersion, zipField, coverField }] }`,禁止未知字段。
|
||||
- 限制:单批最多 20 个模板;单个 ZIP ≤ 64 MiB;单张封面 ≤ 5 MiB;请求体 ≤ 200 MiB;`runtime` 仅接受 `html / unity / godot / cocos`,`id` / `templateVersion` / `entry` 走既有标识符与相对路径白名单。存储层为 `application/zip` 单独放宽单对象上限到 64 MiB,图片与元数据仍是 5 MiB。
|
||||
- 语义:一批**全有或全无**。任一模板的 manifest 字段、归档或封面不合法,都在任何写入之前整批拒绝,并逐项给出模板 ID 与原因。
|
||||
- 归档校验不落盘:合法 zip、无符号链接、无绝对路径 / `..` / 盘符 / 反斜杠条目、必须包含 manifest 声明的 `entry`、条目数 ≤ 4096 且解压后总大小 ≤ 512 MiB;并拒绝含 `.agent` / `.git` / `.svn` / `node_modules` 段(任意层级)与根目录 `dist` / `build` / `library` / `temp` / `local` / `.idea` / `.vscode` 的包(与 CLI 打包同一门禁,见 [`【模板规范】AGC 模板包组织指南`](../【模板规范】AGC模板包组织指南-2026-09-21.md))。
|
||||
- 封面每个模板必填,按字节嗅探格式(只接受真实 PNG / JPEG / WebP),不信任 multipart 声明的 content-type;上限与编辑路径相同(5 MiB、单边 4096、1600 万像素)。SVG 仍只可能来自 CLI 历史发布。
|
||||
- 发布复用既有协议:ZIP 按上传字节原样发布(不重新打包、不删除历史对象),对象键为 `templates/v1/<id>/sha256/<摘要>/{template.zip,cover.*,template.json}`;在发布锁内先 CAS 校验 `expectedRevision`(过期返回 409),写入后逐个回读校验,最后提交一次清单;清单写入结果不明时保留锁并返回 503。断连由独立任务持有,不会在清单 PUT 在途时提前解锁。
|
||||
- 版本与保留语义:新 ID 默认上架;已存在 ID 就地更新并保留 `enabled` 分组、其它条目与未知扩展字段(含下架条目);同一 ID 同一 `templateVersion` 的 ZIP 字节不同时拒绝并要求递增版本(与 CLI 同一句文案),字节完全一致时按内容复用,响应里以 `reusedObjects` 标出。
|
||||
- 后台页面沿用既有写入确认、防重复提交与刷新语义:409 提示刷新后重试;上传失败只在弹窗内交代,401 仍交由会话处理。
|
||||
|
||||
## OSS 契约
|
||||
|
||||
```text
|
||||
templates/
|
||||
index.json # 模板库清单,客户端唯一读取入口
|
||||
.publish-lock.json # 发布互斥锁,不供客户端读取
|
||||
v1/<templateId>/
|
||||
template.json # 单模板元数据(含文件级摘要)
|
||||
template.zip # 模板正文,zip 根 == AGC 项目根(如 game/index.html)
|
||||
cover.(png|jpg|webp|svg) # 封面图(卡片展示,客户端 <img> 直接取)
|
||||
sha256/<zipSha256>/template.zip # 模板正文,zip 根 == AGC 项目根
|
||||
sha256/<coverSha256>/cover.<ext> # 封面图
|
||||
sha256/<metadataSha256>/template.json # 单模板元数据(含文件级摘要)
|
||||
```
|
||||
|
||||
`index.json`(schema `agc-template-library.v1`):
|
||||
|
||||
| 字段 | 说明 |
|
||||
| --- | --- |
|
||||
| `schemaVersion` | 固定 `agc-template-library.v1`;破坏性变更换 schema,不原地改语义 |
|
||||
| `library` / `libraryVersion` / `updatedAt` | 库标识、库格式版本、本次更新时间 |
|
||||
| `templates[].id` | 稳定标识,`[a-z0-9][a-z0-9._-]{0,63}`,同时是目录名 |
|
||||
| `templates[].title/summary/tags[]` | 展示与搜索/筛选用文案;`tags` 参与标签筛选与关键词命中 |
|
||||
| `templates[].runtime` | `html` / `unity` / `godot` / `cocos` |
|
||||
| `templates[].engine` / `engineVersion` | 引擎标识与版本(如 `phaser` 4.2.1、`three.js` 0.180.0) |
|
||||
| `templates[].templateVersion` / `updatedAt` | 模板内容版本;客户端按它判断是否需要重新下载 |
|
||||
| `templates[].entry` | 解压后的项目入口相对路径,如 `game/index.html` |
|
||||
| `templates[].zipKey` / `zipSizeBytes` / `zipSha256` | 模板包对象键、字节数、SHA-256(下载后强校验) |
|
||||
| `templates[].coverKey` / `coverWidth` / `coverHeight` / `coverSha256` | 封面对象键与尺寸/摘要 |
|
||||
| `templates[].metadataKey` | 单模板元数据对象键(`template.json`) |
|
||||
| 字段 | 说明 |
|
||||
| --------------------------------------------------------------------- | ----------------------------------------------------------------- |
|
||||
| `schemaVersion` | 固定 `agc-template-library.v1`;破坏性变更换 schema,不原地改语义 |
|
||||
| `library` / `libraryVersion` / `updatedAt` | 库标识、库格式版本、本次更新时间 |
|
||||
| `templates[].id` | 稳定标识,`[a-z0-9][a-z0-9._-]{0,63}`,同时是目录名 |
|
||||
| `templates[].title/summary/tags[]` | 展示与搜索/筛选用文案;`tags` 参与标签筛选与关键词命中 |
|
||||
| `templates[].runtime` | `html` / `unity` / `godot` / `cocos` |
|
||||
| `templates[].engine` / `engineVersion` | 引擎标识与版本(如 `phaser` 4.2.1、`three.js` 0.180.0) |
|
||||
| `templates[].templateVersion` / `updatedAt` | 模板内容版本;客户端按它判断是否需要重新下载 |
|
||||
| `templates[].entry` | 解压后的项目入口相对路径,如 `game/index.html` |
|
||||
| `templates[].zipKey` / `zipSizeBytes` / `zipSha256` | 模板包对象键、字节数、SHA-256(下载后强校验) |
|
||||
| `templates[].coverKey` / `coverWidth` / `coverHeight` / `coverSha256` | 封面对象键与尺寸/摘要 |
|
||||
| `templates[].metadataKey` | 单模板元数据对象键(`template.json`) |
|
||||
|
||||
约束:
|
||||
|
||||
- 所有对象键必须落在 `templates/` 前缀内;客户端只用「受信任 OSS 主机 + 对象键」自行拼 URL,**不直接信任清单里的地址**。
|
||||
- 任何一项校验失败(schema、标识符、sha256、尺寸、键前缀)都让整次清单读取失败,前端拿到的是全有或全无的清单。
|
||||
- 模板源在仓库 `apps/ai-game-creator-shell/template-library/`:`v1/<id>/{meta.json, project/**, cover.(png|jpg|webp|svg)}`,`template.zip` **不落仓库**,由脚本按 `project/` 现场打包(条目排序、固定时间戳,同内容重复打包摘要一致)。
|
||||
- 上传与校验由 [`scripts/agc-template-library-publish.mjs`](../../scripts/agc-template-library-publish.mjs) 完成:`--source apps/ai-game-creator-shell/template-library [--dry-run] [--prune]`,脚本生成 `template.json` 与 `index.json`、上传后回读 zip 摘要;`--prune` 清理该模板前缀下本次没有产出的旧对象(例如换封面扩展名后的残留)。
|
||||
- 当前模板:`blank-web`(空白网页)、`blank-2d-canvas`(空白二维画布)、`blank-3d-scene`(空白三维场景)、`phaser-2d-starter`(Phaser 2D 起步工程)、`threejs-3d-starter`(Three.js 3D 起步工程)。
|
||||
- 模板包内容怎么组织(根目录结构、Cocos 工程保留项、不要放的东西、封面与体积上限、发布前自检)见 [`docs/【模板规范】AGC模板包组织指南-2026-09-21.md`](../【模板规范】AGC模板包组织指南-2026-09-21.md)。
|
||||
- 上传与校验由 [`scripts/agc-template-library-publish.mjs`](../../scripts/agc-template-library-publish.mjs) 完成:`--source apps/ai-game-creator-shell/template-library [--dry-run] [--only <id,id,...>]`。ZIP、封面和元数据分别以自身字节的 SHA-256 定位,只创建新对象或复用逐字节校验一致的已有对象;全部对象回读一致后才更新 `index.json`。失败不回收已上传对象,旧清单及其引用始终可读。打包阶段执行与后台上传相同的正文门禁(`readProjectFiles`):正文含 `.agent` / `.git` / `.svn` / `node_modules` 段(任意层级)或根目录 `dist` / `build` / `library` / `temp` / `local` / `.idea` / `.vscode` 的模板直接报错,不产生任何上传对象。
|
||||
- 只更新指定模板时使用 `--only <id,id,...>`,在发布锁内读取最新清单,只替换指定 ID,其余条目和未知扩展字段保留。首次清单 404 可由本次选择初始化;读取异常或清单非法时停止。全量发布也遵守相同锁与版本门禁。
|
||||
- `templates/README.md` 与上述正文不同:它不是内容寻址对象,而是**覆盖写的说明文档**,源在仓库 `apps/ai-game-creator-shell/template-library/README.md`(上限 64 KiB),由同一次发布在清单之前写入并回读校验。客户端从不读它,改契约只改仓库源即可,不要再手工维护线上副本。
|
||||
- 正式发布先通过 `GetBucketVersioning` 确认 Bucket 从未开启版本控制,再用 `x-oss-forbid-overwrite: true` 原子创建 `.publish-lock.json`;版本控制 Enabled、Suspended、检查无权限或无法判定时均在写入前停止。所有写同一清单的发布进程必须使用此锁,发布期间不得改变 Bucket 版本控制配置。锁没有自动过期或抢占机制,已被占用时直接失败,重新执行须重新获取锁并读取最新清单。
|
||||
- 只释放本任务已明确获取且 owner 标识仍一致的锁。获取结果不明时不猜测删除。正文对象不可变,其写入失败可安全释放本任务的锁;清单 PUT 已发起后若遇到断连、超时或服务端 5xx 等不确定结果,必须保留锁并报错,防止旧在途请求晚于下一发布者写入。清单收到确定成功或确定拒绝响应后才进入正常解锁路径;不自动重发清单写入,也不凭一次 GET 猜测在途 PUT 已结束。遗留锁须在确认原请求及进程已终止或完成后由运维处理;释放失败必须报告,不伪装为发布成功。
|
||||
- `--dry-run` 仅构造和读取合并计划,不读取凭据、不获取锁、不 PUT/DELETE。发布不删除历史对象,也不提供随发布清理的选项;旧客户端缓存和未完成下载可能仍引用旧键。
|
||||
- 同一 ID、同一 `templateVersion` 的 ZIP 大小或摘要改变时,在上传正文前拒绝,要求更新模板版本;只把相同 ZIP 迁移到新键可保留版本。客户端现有 `<id>/<templateVersion>` 缓存行为不变。
|
||||
- 当前模板:`blank-web`(空白网页)、`blank-2d-canvas`(空白二维画布)、`blank-3d-scene`(空白三维场景)、`phaser-2d-starter`(Phaser 2D 起步工程)、`threejs-3d-starter`(Three.js 3D 起步工程)、Cocos Creator 3.8.8 的 `cocos-empty-2d`、`cocos-empty-3d`、`cocos-empty-3d-hq`、`cocos-hello-world`,以及 Godot 4.7 的 `godot-empty-2d`、`godot-empty-3d`、`godot-hello-world`、`godot-platformer-2d`。
|
||||
- Cocos 内容来自 Creator 3.8.8 随附的 `resources/templates/{empty-2d,empty,empty-quality,hello-3d-world}`,保留官方资源、`.meta`、设置和模板预设;补齐 `package.json.creator.version`,空模板以 `assets/.gitkeep` 保证资源目录进入 Git 和 ZIP。`entry` 为 `package.json`,不打包编辑器生成的缓存或用户项目数据。
|
||||
- Cocos 建项在复制后按实际 `package.json.creator.version + assets/` 识别,复用既有 Cocos 导入流程,写入 `cocosProjectRoot: "."`;每个新项目重建 `package.json.uuid` 并写入所选项目名。只创建 `.agent` 管理目录,不生成 Web 占位入口;模板源与本机安装缓存不被改写。
|
||||
- Godot 内容为仓库内手写的 Godot 4.7 工程(`project.godot` + `scenes/` + `scripts/`,GL Compatibility 渲染,只用内置 `ui_*` 输入动作,不依赖外部贴图),`entry` 为 `project.godot`,不打包 `.godot/` 编辑器缓存与导出产物。
|
||||
- Godot 建项在复制后按 `project.godot` 识别,复用既有 Godot 导入流程,写入 `godotProjectRoot: "."`,并把工程显示名改写成用户选择的项目名(只改 `[application]` 段的 `config/name` 一行,其余字节逐字保留);不生成 `game/` 占位入口与 `assets/`、`memory/`、`exports/` 并行目录,也不改写模板源与本机安装缓存。
|
||||
- 客户端可用 `AGC_TEMPLATE_LIBRARY_BASE_URL` 覆盖库地址;只接受 `https://agc-dev.oss-rg-china-mainland.aliyuncs.com`(拒绝其他主机、路径、http)。
|
||||
|
||||
## 客户端实现
|
||||
|
||||
### Rust:`apps/ai-game-creator-shell/src-tauri/src/template_library.rs`
|
||||
|
||||
| 命令 | 行为 |
|
||||
| --- | --- |
|
||||
| `fetch_game_template_library` | 读 `templates/index.json`(≤4 MiB),校验后缓存到 `<app_data>/templates/index.json`;网络失败时回退本机缓存并在 `source` 标 `cache` |
|
||||
| `download_game_template` | 取清单里对应条目,流式下载 zip(≤512 MiB),校验字节数与 SHA-256,解压到 `<app_data>/templates/installed/<id>/<version>/`,最后写 `installed.json` 作为安装完成的唯一标记 |
|
||||
| `create_automatic_local_game_project_from_template` | 需要时先安装模板,然后在 `<app_data>/projects/` 下按既有自动工作区规则建目录:先复制模板文件,再走 `init_local_game_project_at` 补 `.agent` 清单与标准目录 |
|
||||
| 命令 | 行为 |
|
||||
| --------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `fetch_game_template_library` | 读 `templates/index.json`(≤4 MiB),校验后缓存到 `<app_data>/templates/index.json`;网络失败时回退本机缓存并在 `source` 标 `cache`。远端已经答话但正文不是合法 UTF-8 或不符合 schema、以及缓存自己损坏时,一律失败关闭,不用缓存掩盖远端错误 |
|
||||
| `download_game_template` | 取清单里对应条目,流式下载 zip(≤512 MiB),校验字节数与 SHA-256,解压到 `<app_data>/templates/installed/<id>/<version>/`,最后写 `installed.json` 作为安装完成的唯一标记 |
|
||||
| `create_automatic_local_game_project_from_template` | 需要时先安装模板,然后在自动工作区根目录下按既有自动工作区规则建目录:先复制模板文件;Cocos 项目更新自身身份后走既有 Cocos 导入,Godot 项目改写工程显示名后走既有 Godot 导入,其余沿现有 `init_local_game_project_at` 初始化。根目录默认是 `<app_data>/projects/`,用户可选 `projectsRoot` 覆盖(必须来自本机目录选择器并通过私有路径门禁),见 [`【实施计划】AGC项目创建目录可选-2026-09-17.md`](../project-memory/plans/【实施计划】AGC项目创建目录可选-2026-09-17.md) |
|
||||
|
||||
安全与健壮性:
|
||||
|
||||
@@ -60,6 +109,7 @@ templates/
|
||||
- 安装目录名由标识符白名单拼出,不拼接远端字符串;重装时只清理该模板自己的安装目录。
|
||||
- 模板文件与安装记录统一走 `write_game_creator_private_file` / `ensure_game_creator_private_directory_tree`,保持项目目录的私有 DACL 口径。
|
||||
- 建项目失败时删除刚创建的项目目录,不留半成品。
|
||||
- 大小 / 摘要校验在落盘之前完成,被拒绝的模板包不产生任何安装目录;`installed.json` 是「已下载」的唯一判据,目录残留(例如解压中途失败)不构成已安装,下次安装会先清理该模板自己的安装目录。
|
||||
|
||||
### 前端
|
||||
|
||||
@@ -75,6 +125,20 @@ templates/
|
||||
|
||||
## 验收与验证
|
||||
|
||||
并发发布验收必须覆盖真实发布编排的离线故障注入:正文写入或清单提交失败后旧清单引用保持一致;两个发布者互斥、后续重试从新清单合并;条件请求头正确签名;版本控制状态不安全时零写入;外部锁不被删除;同版本改 ZIP 被拒绝;已存在内容对象冲突及释放失败可诊断。普通打包和纯合并函数测试不能替代此门禁。OSS 协议依据:[PutObject](https://help.aliyun.com/zh/oss/developer-reference/putobject)、[GetBucketVersioning](https://help.aliyun.com/zh/oss/developer-reference/getbucketversioning) 与 [V1 签名](https://help.aliyun.com/zh/oss/developer-reference/include-signatures-in-the-authorization-header)。
|
||||
|
||||
Cocos 回归分别覆盖仓库模板和线上真实 ZIP 的安装、连续建项、独立 UUID、`cocosProjectRoot`、原文件保留与安装缓存不变;Web 模板继续走原有回归。此处验证的是模板下载与建项,不替代 Creator 内场景运行验收。Cocos 原生建项分流需要包含此实现的客户端,旧二进制须重新构建或更新。
|
||||
|
||||
`2026-09-19` 发布一致性验收:Node 发布回归 22 项通过,覆盖两个发布者竞争、正文/清单写入失败、迟到清单 PUT、锁归属、版本控制拒绝、V1 签名及真实 CLI 的无写入 dry-run。Rust 定向回归 15 项通过,新增内容地址的清单解析与 URL 保留校验;3 项线上用例本轮未重复执行,此前同日线上下载及原生建项已通过。只读 dry-run 保留线上九个模板并仅计划更新四个 Cocos 条目。格式、编码、文档索引、定向 ESLint 与 diff 检查通过。全部并发/故障写入证据来自离线替身,未执行真实 OSS 锁写入或发布,也未验证 Creator 内场景运行。
|
||||
|
||||
`2026-09-21` 客户端接入复核:Rust 定向 24 项通过(新增清单来源判定 5 项:合法远端正文优先并标 `network`、远端失败回退缓存并标 `cache`、无缓存时暴露远端错误、远端正文非法时不用缓存掩盖、缓存损坏时失败关闭;新增建项目失败清理 1 项;并在大小/摘要不一致、越界归档两条用例上补「拒绝后不留安装目录、不产生已下载判据」断言)。3 项线上用例(读线上清单、下载安装线上模板、下载并原生建项 Cocos 模板)本轮全部真实执行通过;匿名 `GET https://agc-dev.oss-rg-china-mainland.aliyuncs.com/templates/index.json` 返回 200、`schemaVersion=agc-template-library.v1`、9 个模板。前端 38 项通过(模型 9、网格 6、页面 12、控制器 11),`appSurface` 首页模板用例 2 项通过(灰度外隐藏入口与推荐位、点击进入模板库不建项),AGC `typecheck` 通过。仍未验证:Creator 内场景运行,以及真机 iOS / Android 观感。
|
||||
|
||||
`2026-09-21` 模板库重发布:把线上库从 09-17 的旧产物对齐到仓库当前源。线上 9 个模板里 7 个(`blank-3d-scene`、`cocos-empty-2d/3d/3d-hq/hello-world`、`phaser-2d-starter`、`threejs-3d-starter`)的 ZIP 与仓库不一致(旧产物来自 PR 定稿前的源,例如模板内嵌 `package-lock.json` 已由 `5e4ff54a9` 删除、`game.js` / `main.js` 后来改过),其中 2 个原本一致(`blank-2d-canvas`、`blank-web`)。按「同版本 ZIP 不得变」门禁,为这 7 个模板递增 `templateVersion` 到 `0.1.1` 后重发:28 个内容寻址对象逐个回读校验通过,`index.json` 与 `templates/README.md` 在锁内提交;线上清单现在全部指向 `v1/<id>/sha256/<摘要>/` 键,`libraryVersion=1`、9 个模板、无下架条目。发布脚本本次同时补齐说明文档受管写入(源文件即 `apps/ai-game-creator-shell/template-library/README.md`)并修掉「守卫拒绝后直接 `process.exit(1)` 触发 libuv 断言崩溃、看不到原因」的问题;线上 README 与仓库源已逐字节一致。
|
||||
|
||||
`2026-09-21` 模板正文目录门禁:CLI 打包(`readProjectFiles`)与后台上传(`validate_import_archive`)统一拒绝正文含 `.agent` / `.git` / `.svn` / `node_modules` 段(任意层级)与根目录 `dist` / `build` / `library` / `temp` / `local` / `.idea` / `.vscode` 的模板,两处用同一份段名单与同一句文案。同名目录段只在根目录受限:正文内 `game/dist/**` 与 `.gitignore` 仍合法(`.gitignore` 不等于 `.git`),Cocos 模板的 `.creator` / `.gitignore` 不受影响。证据:Node 发布回归 28 项通过(新增 1 项,13 条拒绝用例 + 2 条放行断言);Rust `admin_templates` 8 项回归通过(新增 `template_import_archive_rejects_identity_and_build_directories`);仓库现有 9 个模板源无一条命中门禁,本地重打包的 9 个 ZIP 摘要与线上 `index.json` 的 `zipSha256` 逐条一致(匿名读清单 200、9 个模板、0 个下架条目),即门禁未改变任何已发布字节。未执行真实 OSS 写入。
|
||||
|
||||
`2026-09-21` Godot 模板入库:新增 `godot-empty-2d` / `godot-empty-3d` / `godot-hello-world` / `godot-platformer-2d` 四个仓库内手写模板,并给建项补 Godot 分流。模板内容用本机 Godot `4.7.2.stable` 逐项验证:四个模板的主场景都能实例化并跑满 3 帧、全部 GDScript 过 `--check-only`(同一条命令对故意写错的脚本报告 Parse Error,证明检查有效);平台跳跃模板另做真机物理试玩——角色落地 y=627.99(地面顶 656 − 半高 28)、按住右 1 秒位移 320px、连按跳跃后落在平台上 y=515.93(平台顶 544 − 28)、三枚金币全部收集后 HUD 变为「已收集 3 / 3 —— 全部完成!Esc 重来」。Rust 侧 4 项定向回归通过:`template_library::tests::godot_template_creates_native_project_with_relative_root_and_display_name`(Godot 模板建项得到 `godotProjectRoot: "."`、无 `game/` 占位入口、`config/name` 改写成所选名称且其余行逐字保留)、`import_tests::rewrites_only_the_godot_display_name_line`、`import_tests::keeps_a_godot_project_without_a_display_name_line_untouched`,以及既有 Cocos/Web 建项回归未受影响。发布保持 `--only` 定向:只新增四个 Godot 模板对象并重写清单,其余 9 个模板的键与版本不变;未在客户端「模板库」里实际建一次项目。
|
||||
|
||||
## 本地压测假数据注入(feature 控制)
|
||||
|
||||
模板库的数据源在 Rust 侧(清单校验、安装状态、下载与建项目都在这里),TS 只消费快照做渲染,所以假数据注入也放在 Rust 侧,走与真实完全一致的链路。
|
||||
@@ -111,6 +175,10 @@ AGC_TEMPLATE_LIBRARY_SYNTHETIC_COUNT=300 AGC_DEV_CARGO_FEATURES=template-library
|
||||
- 前端回归:1000 条渲染 + 已安装过滤(334)/标签过滤(50)/关键词过滤数量自洽,见 `apps/ai-game-creator-shell/tests/templateLibraryView.test.tsx`。
|
||||
|
||||
```bash
|
||||
# 模板内容、确定性打包与定向发布合并回归
|
||||
node --test scripts/agc-template-library-publish.test.mjs
|
||||
# 仅发布 Cocos 模板(先加 --dry-run 核对合并清单)
|
||||
node scripts/agc-template-library-publish.mjs --source apps/ai-game-creator-shell/template-library --only cocos-empty-2d,cocos-empty-3d,cocos-empty-3d-hq,cocos-hello-world
|
||||
# 模板库单测(清单校验、键安全、解压路径逃逸、安装与建项目)
|
||||
cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml --bin genarrative-ai-game-creator-shell template_library
|
||||
# 模板库真连检查(可选,需要网络):读线上清单、下载安装线上模板包并据此建项目
|
||||
@@ -133,4 +201,4 @@ curl -s https://agc-dev.oss-rg-china-mainland.aliyuncs.com/templates/index.json
|
||||
|
||||
- 清单读不到且没有本机缓存:模板库页显示错误与重试,首页推荐位显示「模板库暂时没有可用的模板」。
|
||||
- 版本落后:`installedVersion != templateVersion` 视为需要重新下载,点「使用模板」会先重下再建项目。
|
||||
- 需要回退整条链路时,删除 `templates/` 前缀即可让客户端回到"空模板库";客户端代码路径不受影响。
|
||||
- 回退清单同样必须使用发布互斥协议并保留其引用的历史对象,不能删除整个 `templates/` 前缀来回退。
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
> 规范关系:AGC 插件与编辑器适配主规范
|
||||
> 验收范围:插件 manifest、宿主生命周期、RPC、Capability/权限审计、UI 挂载和编辑器适配器边界
|
||||
|
||||
更新时间:`2026-09-09`
|
||||
更新时间:`2026-09-20`
|
||||
|
||||
## 目标与边界
|
||||
|
||||
@@ -19,6 +19,8 @@ apps/ai-game-creator-shell/src-tauri/src/editor_adapters.rs
|
||||
packages/agc-plugin-sdk/src/index.ts
|
||||
server-rs/crates/editor-adapter-api/src/lib.rs
|
||||
plugins/agc-cocos-editor/ (第一个编辑器插件包)
|
||||
plugins/agc-unity-editor/ (Unity Mono 编辑器插件包)
|
||||
plugins/agc-godot-editor/ (Godot GDExtension 编辑器插件包)
|
||||
```
|
||||
|
||||
现有 DirectProject 的 Skill/MCP 导入仍保留。它们是 Codex 扩展注入链路,不等同于本宿主管理的可运行 AGC Plugin。
|
||||
@@ -69,6 +71,19 @@ OpenAI 的标准模型是“Plugin 作为可安装包,组合 Skills、可选 M
|
||||
|
||||
除 AppData 导入外,宿主还扫描 `plugins/` 工作区:每个含根目录 `plugin.json` 的一级子目录是一个插件包。解析顺序为环境变量 `AGC_PLUGIN_WORKSPACE`、随包资源目录 `<resource_dir>/plugins`、开发构建的仓库 `plugins/`。仓库工作区约定见 [`plugins/README.md`](../../../plugins/README.md)。
|
||||
|
||||
Windows 与 macOS 构建都将内置插件的清单、JS 入口与面板复制到应用资源目录;staging 每次重建,避免已删除插件或跨目标原生 payload 残留。macOS 不携带 Windows native payload。Cocos 进程桥接仍仅按既有 Windows 平台实现提供,插件文件可被发现不代表 macOS 已支持编辑器控制;JS 入口的系统 Node 前提不变。
|
||||
|
||||
Cocos 与 Unity 插件的可见性、启动、面板、插件 RPC 和 Agent 工具暴露不按当前工程类型过滤;无当前项目以及普通 AGC、Godot、Cocos、Unity 项目使用同一套插件可用性规则。宿主必须已注册插件声明的原生适配器;没有适配器时从插件/扩展列表隐藏,直接启动或读取面板也在产生子进程前拒绝。正式适配器与 Agent 工具继续受各自平台和编译 feature 约束,禁用开关继续阻止启动与工具执行。前端只按后端列表投影判断是否自动启动,不自行推断平台能力或再次按工程类型过滤。
|
||||
|
||||
### 工程上下文与真实编辑器目标
|
||||
|
||||
- 工程类型只用于工程识别及对应工程工作流,不作为 `agc-cocos-editor` / `agc-unity-editor` 的管理、面板或工具目录门禁。Runtime、DirectProject MCP、工具策略快照和模型上下文使用一致规则;工具已暴露不代表真实编辑器已连接或操作已成功。
|
||||
- 前端对宿主列表中的 Cocos、Unity 与 Godot 插件分别根据适配器支持、启用、Runtime 入口和运行状态投影自动启动,不按项目类型二选一;自动启动不自动展开编辑器面板。Godot 是否属于当前项目由宿主过滤,Cocos/Unity 保持工程类型独立性。
|
||||
- 项目切换不因新工程类型不同而停止插件或隐藏工具;当前受控项目上下文仍须按既有顺序更新,旧编辑器连接失效。旧请求与回执保留原项目归属,不能更新新项目连接状态。
|
||||
- 实际编辑器操作仍须取得有效的当前受控项目和与之匹配的真实编辑器目标。宿主注入项目路径,显式路径必须与当前受控项目一致;适配器继续校验真实工程结构、目标 PID、进程身份、版本与握手。无项目、不匹配的工程目录、无编辑器或不支持的平台均应在发送编辑器操作前明确失败,不回退到其它项目或任意编辑器进程。
|
||||
- 插件启用状态、manifest 适配器绑定、`editor.rpc` 权限、超时与并发拒绝、执行结果不确定阻断均保持原合同。取消工程类型过滤不增加自动重试,不清除项目切换或重启插件前已经产生的不确定状态。
|
||||
- 不新增或迁移项目类型、内置插件开关、API/DTO、SpacetimeDB schema 或持久项目数据;不扩大 Cocos/Unity 原生适配器的平台支持,也不把任意非引擎目录解释为可执行的编辑器工程。
|
||||
|
||||
### 内置插件与可用开关
|
||||
|
||||
`plugins/` 工作区里的插件是**内置插件**:随客户端分发,用户不能卸载或删除,只能通过可用开关控制是否生效。开关状态持久化在 AppData `extensions/builtin-plugins.json`(`schemaVersion = agc.builtin-plugins.v1`,`enabled` 是 id 到布尔的映射);文件缺失按插件 manifest 的 `enabled` 处理,坏文件失败关闭。内置插件优先级高于同名导入插件,AppData 里的同名 Plugin 不会覆盖或间接卸载它。
|
||||
@@ -111,8 +126,36 @@ host.rpc(method, params)
|
||||
|
||||
当前 native 适配器仍由宿主在编译期链接(Cargo path 依赖);动态加载插件 native 模块不在本次范围,插件包格式与宿主协议不受此限制。
|
||||
|
||||
Unity 插件复用此扩展点,GUI 适配器通过已有 Runner RPC 转发到唯一的 Unity 执行
|
||||
服务,避免 GUI、DirectProject 与 Runtime 分别持有连接或执行不确定门闩。
|
||||
宿主 RPC 将目标 adapter 绑定到 manifest,并校验显式项目路径等于当前项目;切换
|
||||
项目使旧连接失效,禁止互斥锁后积压的请求在调用方超时后继续派发。
|
||||
Windows x64 的 Attach helper 来源、构建工具链和执行回执合同见
|
||||
[Unity 插件接入](./【技术方案】AGC Unity编辑器插件接入-2026-09-18.md)。
|
||||
|
||||
Godot 使用同一 Runner 执行与回执确认层,按引擎分别保存 pending/uncertain 状态,不能相互确认或清除。`godot-editor` 的受控连接允许按用户已选方案维护项目内 `.gdextension` 引用及 Godot 自动生成的 UID;DLL 随安装资源分发,探测仍只读,原始项目配置和场景不改。具体文件归属、GDScript 错误/async、升级卸载与分发合同见 [Godot 插件接入](<./【技术方案】AGC Godot编辑器插件接入-2026-09-20.md>)。
|
||||
|
||||
## 编辑器常用操作指导
|
||||
|
||||
Unity 与 Godot 的常用操作指导由客户端既有审核 Skill pack 随包提供;每个引擎有独立入口和常用操作参考,不新增执行工具或任意文件读取入口。DirectProject 在隔离 Skill 目录发现指导,也可通过已有 `agc_read_skill_resource` 按审核名称和相对路径读取。Agent Runtime 的对应执行工具说明包含同一份常用操作参考,避免只覆盖 Codex 原生 Skill 路径。正文只有一个源码来源,清单指纹与安装投影必须一致。
|
||||
|
||||
指导覆盖场景/层级查询、对象或节点的创建/修改/删除、组件或属性、Prefab/PackedScene、资源引用、基础 UI、打开/保存场景、运行/停止及诊断。示例是提交给现有 execute 的代码正文,说明前置状态、预期回读和保存/撤销语义,不写宿主路径、PID、令牌或桥接安装动作。读说明不连接编辑器、不授权修改;执行仍受原插件开关、平台、项目和权限门禁控制。
|
||||
|
||||
每个引擎的操作参考不超过 14 KiB UTF-8,并独立包含执行参数和失败边界;Direct 常驻提示只给读取路由,16K 字符预算内必须保留两个引擎的指南入口。Runtime 的最终工具定义须完整包含对应参考,不能因截断丢失末尾内容,也不能在该执行工具不可用时额外注入正文。
|
||||
|
||||
通用 CapabilityRegistry 保留原有短描述和 4000 字符约束;完整参考只在已注册能力转换为 Provider 函数工具时附加,按 UTF-8/LF 规范化并校验 14 KiB 上限。不扩大 core 的任务、能力或摘要长度合同。
|
||||
|
||||
执行载荷仅有 `code`;Direct 工具的顶层参数为 `{code}`,Runtime 原生函数沿用 `{reason,input:{code}}` 外层,指南必须按实际工具 schema 区分这两种调用格式。
|
||||
|
||||
确定失败也可能已经产生部分修改;未知结果继续禁止自动重放。Unity 使用实际场景与对象身份,区分 Undo、Prefab override、保存和 Domain Reload。Godot 使用真实编辑场景根、为需保存的新节点设置 owner,区分独立 UndoRedo 回滚与编辑器历史,避免承诺未验证的 Ctrl+Z。主线程同步死循环不可硬中止。
|
||||
|
||||
验收要求:两个入口均能取得审核正文,源码与安装后的字节一致;非法路径及未登记资源继续拒绝;系统提示能够发现指南但不塞入全部示例;从指南原文提取代码做真实临时工程验证,至少覆盖查询、修改与回读、局部撤销、场景持久化、资源实例化和 UI。未实测操作和平台须在交付记录中明确,不把 API 示例当作已有独立工具。
|
||||
|
||||
当前证据覆盖本地指南读取/安装、最终工具描述、真实编辑器执行示例及 Godot 示例图形渲染。Unity GUI 视觉和真实 Provider 读取指南后调用编辑器的端到端链路尚未验收,不能由定向测试或前置状态检查推断通过。
|
||||
|
||||
## Tauri 命令
|
||||
|
||||
|
||||
`list_agc_extensions` 返回统一的 Plugin/Skill/MCP catalog;`list_agc_plugins`、`refresh_agc_plugins`、`start_agc_plugin`、`stop_agc_plugin`、`reload_agc_plugin`、`call_agc_plugin` 和 `read_agc_plugin_panel` 提供 Runtime Plugin 管理入口;`set_agc_plugin_project_path` 设置当前项目的受控上下文。编辑器适配器通过宿主 registry 和 Plugin RPC 使用,不增加编辑器专属 Tauri 命令。
|
||||
|
||||
编辑器操作统一走 `host.rpc`:插件用 `extensions.world.genarrative.agc.adapter` 或显式 `adapter` 参数选择适配器,宿主校验 `editor.rpc` 权限后调用 `EditorAdapter::rpc`。项目上下文通过 `host.events.subscribe` 的响应和 `project.changed` 事件 payload 下发,插件不需要自己扫描目录。
|
||||
@@ -129,6 +172,7 @@ OpenAI 官方 Plugins 文档将 Skills、MCP Server 和可选 UI 定义为同一
|
||||
- Rust:manifest 路径/权限校验、目录扫描、权限拒绝和通用适配器 registry 边界单测;`cargo check --locked --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml`。
|
||||
- 前端:`agc-plugin-sdk` TypeScript 编译、宿主服务类型检查,以及 `PluginPanelHost` 的挂载/卸载测试。
|
||||
- 内置插件开关:`builtin_plugins` 单测覆盖默认值、持久化往返、坏文件失败关闭,以及“禁用后工具目录里不再出现该工具”;`plugin_host` 单测覆盖禁用后不能启动、启用后回到 stopped。
|
||||
- Cocos/Unity 工程类型独立性:覆盖无当前项目、普通 AGC、Godot、Cocos、Unity 上下文中的插件列表、启动、面板、RPC 与 Runtime/DirectProject 工具目录一致性;项目切换不因类型变化停止这两个插件。Godot 单独验证当前工程门禁、切项目撤销旧上下文与停止旧实例。保留禁用开关、缺失原生适配器、平台/feature、显式跨项目路径拒绝与真实编辑器目标校验的独立反例;非引擎目录不能仅因工具可见就通过实际操作校验。
|
||||
- 插件工作区:`cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml` 覆盖工作区扫描与 manifest 启用状态;`cargo test --manifest-path plugins/agc-cocos-editor/native/cocos-editor-bridge/Cargo.toml` 覆盖 Cocos 适配器;`node --test plugins/agc-cocos-editor/src/entry.test.mjs` 覆盖插件入口协议与 manifest 一致性。
|
||||
- 通用仓库门禁:`npm run check:encoding`、`git diff --check`;发布前仍需单独执行 AGC package smoke 和安装包 smoke。
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ AI Game Creator Shell 采用 IDEA 风格的当前进程错误报告:错误事
|
||||
|
||||
## 客户端
|
||||
|
||||
- 诊断 URL 保留可定位的 API 路由路径,隐藏 origin、URL 账号密码、查询参数、fragment 和路径中的敏感标识;普通资源 URL 与本地文件路径继续隐藏。网络错误、HTTP 错误与响应体超时均应带安全路由,不能只剩 `<url>`。历史已经脱敏的归档不推测或补造原路由。
|
||||
|
||||
- 捕获 React render error、`window.onerror`、`unhandledrejection` 以及显式标记的 Tauri/API/Agent 错误。Agent Runtime 的终态失败、预算耗尽和启动确认失败由 Rust 失败投影统一入池;Direct Codex 与专业 Agent 的前台裸 Tauri invoke catch 作为补充入口,重复事件由同一 fingerprint 合并,主动取消和“同一 turn 已在运行”不作为错误采集。
|
||||
- 事件字段包括 eventId、fingerprint、source、message、stack、时间和次数;重复事件合并。不再携带 severity、errorCode、page、action、requestId 等无法稳定关联的字段。
|
||||
- 指纹计算可使用调用方的 page/action 及脱敏后的首个调用点作为进程内区分输入,但这些上下文不会作为事件字段上传;消息与 stack 在入池前统一脱敏,WebCrypto 失败时降级为稳定可读指纹,采集本身不得产生新的未处理拒绝。
|
||||
@@ -28,6 +30,8 @@ AI Game Creator Shell 采用 IDEA 风格的当前进程错误报告:错误事
|
||||
- 后台接口:`GET/PATCH /admin/api/error-reports/{batchId}`、`GET /admin/api/error-reports` 和受保护的 `/download`。列表支持 `limit`/`offset` 分页并返回 `total`、`hasMore`;OSS 读取先检查 `Content-Length` 并在流式累计超过上限时立即中止,不把超限对象完整缓存在内存中。
|
||||
- 这些是 api-server 内部登录/管理员路由,不属于 `/api/external/v1`,不纳入 External OpenAPI;管理员详情对不存在返回 404,对归档/元数据损坏返回 500。
|
||||
- admin viewer 仅接受 error-reports Tab 权限,支持列表筛选、分页、详情、状态 `new/in-progress/resolved`、处理备注和受控下载;不存在的更新目标返回 404,存储损坏返回 500。列表行支持键盘 Enter/Space 打开详情,详情事件预览最多显示 20 条,完整内容通过诊断包下载获取。
|
||||
- 列表提交时间与详情事件时间接受 RFC3339、`seconds.microsZ`、Unix 秒/毫秒/微秒字符串;非法或越界时间显示 `-`,不显示 `Invalid Date`。仅调整呈现,不改变数据库字段或历史归档。
|
||||
- 详情按后台现有弹窗、按钮与表单样式分层呈现报告摘要、事件消息、可展开调用栈、用户说明、日志附件和处理备注;支持 Esc、焦点管理、窄屏内部滚动。加载/下载/保存失败在当前可见面板内提示;关闭或打开另一报告后,旧请求不得覆盖新报告。状态与备注保存以后端响应为准。
|
||||
- 管理员列表、筛选、状态和备注全部读取/更新 SpacetimeDB;错误报告的 list/get/update procedure 要求 `require_editor_generation_runtime_service_identity`,详情先读表再从 OSS 下载并解析 ZIP,下载接口直接从 OSS 返回 ZIP。PATCH 更新直接返回元数据,不重新下载归档;详情弹窗提供备注编辑器。无需新增管理员 DELETE HTTP 接口。每日清理任务按分页扫描全部报告,删除过期 OSS 对象后再删 DB;任一 DB 删除失败会保留错误并在后续周期重试。详情解析对解压后的 `events.jsonl` 设置 24 MiB(与请求体上限一致)与 100 条事件上限。管理员备注超过 2,000 字会被明确拒绝;module 同时校验固定 OSS key、SHA-256、归档大小和事件/日志计数上限;OSS 读取仅将明确不存在映射为 404,其余错误按请求无效或上游故障返回。内部 OSS 读写只允许 `agc/error-reports/v1/`。旧本地报告不迁移。
|
||||
|
||||
SpacetimeDB `error_report` 表字段:`batch_id` 主键、`user_id`、`submission_id`、`idempotency_key` 唯一键、`object_key`、`archive_sha256`、`archive_size_bytes`、`event_count`、`log_count`、首个 fingerprint/source、`review_status`、`admin_note`、`created_at`、`updated_at`;索引为 `(user_id, submission_id)`、`created_at`、`review_status`。
|
||||
|
||||
@@ -254,6 +254,7 @@ npm run ai-game-creator-shell:agent-runtime:real-e2e -- --config-dir <AppData> -
|
||||
- `cwd` 必须是项目内规范相对目录,拒绝符号链接、绝对路径、`..`、Windows 盘符 / UNC / ADS 和整个 `.agent` 控制面;超时固定在 1-300 秒,stdin 关闭,stdout / stderr 采用有界头尾保留并先做凭据清洗。
|
||||
- 默认权限为 `confirm`。确认摘要包含程序、argv 摘要、cwd 和超时;精确动作继续绑定 actionId、repository fingerprint、project revision 和 execution owner。Runner 在 `executing` 阶段退出时保持 `needs-reconciliation`,不得自动重放命令。
|
||||
- 子进程继承环境清空;可执行文件必须从项目外安全绝对目录解析为绝对路径,子进程 PATH 只保留这些已规范化目录,并注入隔离 HOME / TMP / cache、离线包管理配置和不可达代理。超时或读流失败时 Runtime 请求终止受控进程组并检查终止调用结果,但这不等同于完整 detached-process / 容器隔离。首版安全等级与现有 `project.verify` 相同:固定程序和参数策略加用户确认,不宣称已经具备 Codex CLI 的完整 OS sandbox;在完成平台沙箱前不得把 `command.exec` 默认改为 `auto`。
|
||||
- Linux 一次性命令确认 target 终态并回收主进程后,按 `/proc/<pid>/stat` 核对同 PGID 成员;空组或只剩 `Z / X` 成员无需再发送信号,不因容器 PID 1 未回收孤儿僵尸而误报 reconciliation。存在存活成员时仍必须核对原 leader 启动身份,身份缺失或不匹配时拒绝发送信号;读取或解析进程状态失败同样进入 reconciliation。此判断不扩展为 detached-process 隔离证明,也不改变诊断命令与验证 gate 的区分。
|
||||
- Cargo / npm 缓存固定写入项目私有 `.agent/runtime/command-env/cache`,不复用或改写用户宿主缓存,也不允许联网补依赖。依赖未进入项目 vendor、现有 `node_modules` 或隔离缓存时,命令应以真实失败输出回到 Agent;首版不为“跑通命令”复制宿主的 Cargo registry、凭据或用户级配置。
|
||||
- `command.exec` 的执行前后源码指纹各自最多遍历 20,000 个目录项、10,000 个受保护文件和 512 MiB 正文;执行前超预算直接拒绝启动,执行后无法完成指纹则进入 `needs-reconciliation`,不得把截断扫描当成完整验证凭证。
|
||||
- 命令结束后重建安全项目文件指纹。若命令改写了受保护项目文件,则保持 verification gate 未通过并要求 Agent 重新检查;每次真正启动命令前已经保守推进一次 revision。可签发验证凭证的命令仅限 `cargo check/test/clippy/fmt/build`、`npm test`、命名为 `check/typecheck/test/lint/build/verify/validate` 的 npm 验证脚本及精确 `node --test`;`git`、`rg`、`cargo metadata` 和普通 `npm run` 即使退出码为 0 也只作为诊断结果。只有验证型命令退出码为 0、未超时、未改写受保护文件,且命令日志、manifest 投影和 Agent DB 审计全部成功后,才允许绑定当前 revision 的 passed gate;任一审计失败必须先保持 failed gate,再进入 `needs-reconciliation`。
|
||||
|
||||
@@ -1,5 +1,246 @@
|
||||
# AI 游戏创作智能体 App 实施计划
|
||||
|
||||
## 2026-09-21 Godot 工作区发现放宽与内置插件行去掉手动启动
|
||||
|
||||
本节覆盖下文“打开项目自动识别 Godot”中的旧口径:判定从「唯一命中」放宽为「确定性命中」,`project.godot` 从「必须是普通文件」放宽为「按链接目标判定」。
|
||||
|
||||
| 要求 | 必须成立的行为 | 完成证据 |
|
||||
| --- | --- | --- |
|
||||
| 确定性多命中 | 根目录未命中时,一层直接子目录中多个 `project.godot` 命中按目录名排序取第一个;不再报「多个 Godot 项目」,也不再要求用户改选具体工程 | `cargo test --locked -p genarrative-ai-game-creator-shell --bin genarrative-ai-game-creator-shell -- project::manifest::import_tests::` 与 `-- tests::project::` |
|
||||
| 链接标记 | `project.godot` 是符号链接 / Windows reparse point / 硬链接时按链接目标判定,目标解析为普通文件即命中;目录与悬空链接仍不算命中 | `import_tests::accepts_symbolic_link_project_marker`(unix)、`import_tests::accepts_windows_hard_link_project_marker`、`import_tests::accepts_windows_reparse_project_marker` |
|
||||
| 保持不变的边界 | 工作区根本身是链接、候选子目录是链接、二层及更深目录不递归,这三条既有边界不动 | `import_tests::ignores_symbolic_link_child_candidate_without_writing_agent_metadata`、`import_tests::ignores_windows_reparse_child_candidate_without_writing_agent_metadata`、`import_tests::ignores_godot_projects_below_the_first_child_level` |
|
||||
| 内置插件行 | 设置→扩展 的内置插件行不再渲染手动「启动 / 停止」按钮;启动由项目切换时的前端自动启动承担,停止走该行启用开关(禁用即停止并断开编辑器连接);导入扩展行的启动按钮保留 | `apps/ai-game-creator-shell/src/features/runtime-config/RuntimeConfigDialog.tsx`、`tests/pluginHost.test.ts` |
|
||||
|
||||
## 2026-09-20 DirectProject 七项效率闭环(补齐合同)
|
||||
|
||||
本节补齐并覆盖下节中仅靠 Skill 要求预检、收尾、批读和原生命令预算的部分。完整目标仍为:自动预检、宿主验收与收尾、分层验证、统一执行/返修预算、稳定测试基线、请求耗时与批量读取、所有工具并行。已有代码及测试不等于全部目标已完成;按下表逐项验收。
|
||||
|
||||
| 要求 | 必须成立的行为 | 完成证据 |
|
||||
| --- | --- | --- |
|
||||
| 自动预检 | 新建 Web 游戏由实际用户入口和宿主自动执行,不依赖模型主动调用;失败不得启动正式生成或付费素材 | 首页/后端正反用例、真实构建与双端截图 |
|
||||
| 验收与收尾 | 必需范围和验收项在宿主持久化;证据绑定当前输入;全部必需项通过后关闭本轮新的修改/执行/付费扩项并产生交付报告 | 真实工具链状态转移、重启/并发/新增扩项拒绝用例 |
|
||||
| 分层验证 | 视觉、定点玩法、项目测试和必要完整闭环按已登记标准执行;不混淆证明范围 | 双端正例与单端失败反例 |
|
||||
| 统一预算 | 内置验证、托管脚本和原生命令执行受同一宿主预算约束;不以命令文本猜测“是不是试玩”,不把每条普通开发命令单独计为一次返修 | 捆绑 app-server 的执行前控制、拒绝无副作用、跨入口/重启/耗尽/超时用例 |
|
||||
| 稳定基线 | 可复用的固定种子跑酷基线,真实短按/长按跳跃、单次收力、滑铲释放及公平越障窗口 | 物理单测、双端真实输入、原案例缺陷参数反例 |
|
||||
| 速度可归因 | 已实现请求分段计时继续有效;新增实际并行批读与宿主首轮上下文预取 | 有界读取/并发屏障/安全边界/减少独立读取往返证据 |
|
||||
| 工具并行 | 现有全部工具并行、在途上限、同资源事务和付费防重继续有效 | 混合调用、图片双 POST 同时到达、同参防重回归 |
|
||||
|
||||
### 自动预检与可信脚手架
|
||||
|
||||
- 首页只有结构化的“做游戏 + 直接构建”入口执行预检;先于自动命名和项目生成。策划、文档、素材与已有项目普通对话不按文字关键词触发。后端再按 creationType 与实际工程类型核验,Godot/Unity/Unreal/Cocos 不强制 Web 工具链。
|
||||
- 全局预检使用独立临时目录和已校验 Node/npm,执行自带的最小构建脚本、生成 dist,再用正式受限浏览器完成 desktop/mobile 页面与 PNG 检查。此步骤无网络、无平台生成,不运行用户脚本,不修改已有项目。
|
||||
- 对客户端刚创建且内容仍匹配可信模板的 Web 脚手架,在正式生成前由宿主执行受控依赖准备和真实 Vite 构建。依赖安装禁用生命周期脚本;不自动安装或覆盖导入/用户修改过的工程。
|
||||
- 准备凭证的 `ready` 只证明初次环境准备成功,不代表当前游戏已验收,后续正常修改不得因此重新安装。`preparing` 中断恢复必须证明原拥有者已结束且其执行子树已回收;身份或归属未知时保留阻断,不重复执行。
|
||||
- 输出明确区分“Node/npm 构建能力通过”与“本项目 Vite 构建通过”;任何一步失败保留可行动错误,不降级为预检成功。
|
||||
- 安装载荷提供相同的安全预检 CLI,验证无系统 Node 的独立运行、缺包/篡改失败关闭。NSIS 解包载荷 smoke 与真实安装器注册流程分开报告,不覆盖当前用户的既有安装。
|
||||
|
||||
### 跑酷固定基线
|
||||
|
||||
- 新增固定场景 runner-v1。客户端使用固定种子 20260920,通过 URL 初始化参数提供;游戏从初始化状态读取并投影实际种子。模拟随机数与装饰随机数分离,模拟采用固定步长。
|
||||
- 在现有只读玩法状态接口中扩展 runner 状态:实际模拟 tick、课程指纹、角色位置/速度/落地、滑铲、碰撞尺寸、跳跃/收力计数、真实按住状态和最近障碍几何。字段缺失或类型不符即不支持该验收,不补造成功。
|
||||
- desktop 使用真实键盘/鼠标,mobile 使用真实触摸;检查短按和长按、松手单次收力、落地、滑铲释放与同种子重开。不得调用游戏内部动作方法、改状态、替换 Math.random 或直接设置胜利。
|
||||
- 公平窗口以观测到的可越障空中区间减去碰撞区横穿时间计算,基线至少 180ms;窄窗口参数作为负例并报告实际观测值。固定场景只证明短时动作与几何,不冒充原案例数值的精确复现或完整长关卡通关。
|
||||
- 配套可复用、可单测的物理/种子模块;不把所有新游戏都强制改成跑酷,不替换用户选定引擎。
|
||||
|
||||
### 有界并行读取
|
||||
|
||||
- 新增批量项目上下文读取:最多 8 个去重文件,最多 4 个同时读取;单文件实际读取最多 1MiB,单项正文最多 32KiB,整个序列化回包最多 256KiB。
|
||||
- 复用项目路径、权限、敏感内容和私有控制面边界,拒绝越界/链接/非普通文件;先有界读取,再按 UTF-8 行裁切。返回行号、实际内容摘要、截断和下一页位置。
|
||||
- 单项失败不丢弃其余成功项;返回安全项目身份、回合身份和读取前后 revision。检测到文件或回合变化必须标 stale,不能把它宣传为原子快照。
|
||||
- 宿主在正式 Direct 首轮批量预取受支持的基础源码/包信息,作为明确的数据上下文交给模型;不把文件内容提权成系统指令。大型或不支持文件明确留给后续批读,不循环逐文件请求模型。
|
||||
|
||||
### 宿主控制与后续验收边界
|
||||
|
||||
- 统一预算按执行/验证批次与实际运行输入管理,允许正常构建和相关测试在一个批次内执行;原生读取和结构化文件编辑不按每条命令消耗返修次数。任意代码执行必须具备当前回合的有效执行许可及累计执行时长边界。
|
||||
- 原生命令入口必须以捆绑版本的真实协议证明可在执行前拒绝;不得用执行后的日志通知或文本分类器冒充执行门。能力检测失败不得静默退回无控制模式。
|
||||
- 原生执行控制的精确协议与许可持久化,在对应里程碑评审后落地;不得提前宣布这一项已完成。
|
||||
- 不修改 Provider 的 maxRetries;不减少引擎或任意代码执行的合法能力,不引入平行 Agent 框架,不改公开 API/数据库,不提交私密运行记录。
|
||||
|
||||
### 宿主验收与执行许可合同
|
||||
|
||||
- 正式 GUI 和 CLI 的共同 Direct 回合入口建立宿主控制状态,绑定 canonical 项目路径、稳定 clientTurnId 和原始用户输入摘要;宿主私有目录保存权威账本并独占该回合,项目 `.agent` 仅允许保存展示副本。配置或项目侧文件被改写、工具切换、Provider 重试和进程重启不得刷新同一回合的预算。
|
||||
- Direct 回合集成测试也按生产入口计算原始用户输入的 SHA-256 十六进制摘要(64 字符),不能用请求名称替代。用户回显过滤回归继续覆盖实时消息去重、回合起止身份关联及历史落盘过滤。
|
||||
- 直接启动 Direct 工具桥的图片生成通知测试,须复用真实宿主执行会话与已登记交付合同夹具,再发起工具请求;继续验证资源提交后发出 manifest 失效通知,以及空提示词被参数校验拒绝且不发通知,不绕过执行许可门禁。
|
||||
- 普通聊天与读取不要求交付合同。首次修改、代码执行或付费扩项之前,模型通过结构化工具登记本轮必需范围与验收项;合同非空、有界且只冻结一次。模型只能声明要求,不能提交“通过”作为证据。后续扩项留到新的用户回合。
|
||||
- 明确新 Web 创建由宿主可信脚手架凭证及尚未交付的宿主记录判定,CLI 同样据此判定,不从提示文本猜测;这种回合即使模型没有调用工具或没有登记合同,也不得按普通聊天宣布交付。已有项目只有未激活合同且从未产生副作用时才允许直接聊天结束。
|
||||
- 验收项为明确类型的产物、构建/测试命令、双端视觉或指定固定场景的双端玩法。可信新 Web 游戏由宿主补充构建、双端视觉和玩法底线,不能由模型声明“已有项目”降低。已有项目按冻结的变更范围选择层级;平台美术只在用户目标要求时成为必需项。
|
||||
- 同一份双端玩法证据可同时满足视觉项,避免重复浏览器运行。构建证据分别绑定源码输入摘要与输出摘要,正常生成 dist 不算源码漂移;浏览器证据绑定构建后的实际运行输入。只有宿主验证完成产生的结构化结果和证据文件摘要能满足合同,项目内自行写出的验证 JSON 无效。
|
||||
- 产物项的初始摘要由宿主冻结,模型不能提供或在重放时重算。仅登记已经存在的文件不能立即交付:产物必须实际变化/新出现,或有当前指纹的宿主可信验证证据;原生修改和工具修改遵守相同判据。
|
||||
- `validation.maxRuns` 保留已配置值,语义为执行/返修批次上限;首次执行开启第一批。开发期正常成功命令和源码编辑共享本批,不逐条消耗次数。开始验证后绑定输入,执行失败或验证期间输入漂移使本批进入排空状态,关闭新的执行入口,等已受理操作结束后才开启下一返修批次。读取与结构化编辑不单独消耗次数。
|
||||
- `validation.maxExecutionSeconds` 默认 900,必须为正整数,是整个 clientTurnId 的累计执行时间上限,换批次不清零。并行操作分别计时累加,内置工具不与 app-server 的外层 MCP 事件重复计费。时间耗尽立即拒绝新执行、写入和付费扩项,保留最近证据与未完成项;Provider 的重试次数保持独立。
|
||||
- `validation.maxTurnSeconds` 默认 1800,必须为正整数,是同一宿主回合从开始起的墙钟上限,重启不重置,用于约束模型空转和超出单个工具事件边界的后台会话。墙钟上限与累计执行时间分别记录,任一耗尽都收束自有执行器;不能把模型等待时间报告成工具执行时间。
|
||||
- 捆绑 app-server 的原生命令使用已验证的逐次审批能力;宿主只返回单次接受/拒绝,不允许会话授权或 exec policy 修订。第三方 MCP 必须显式启用逐调用询问,不能依赖不可信 readOnlyHint。询问缺少调用 ID 时,按服务器与回合中的并发组保守管理,不能解析展示文案猜测归属。
|
||||
- 原生、内置与第三方所有入口都经过同一宿主状态;独立工具继续并行,只有身份、批次切换、收尾与必要资源冲突形成短临界区。能力检测失败不得退回无控制执行。
|
||||
- 上述回合控制适用于 DirectProject。独立客户端 HTTP MCP 显式使用 ExternalClient 来源,保持其原有权限、幂等和浏览器能力,不借用当前项目另一条 Direct 回合的预算或可信证据;新交付合同与托管验证命令工具要求 Direct 会话。服务端 external_mcp 不变。
|
||||
- 必需证据齐备后,宿主先进入封口状态,拒绝新副作用,再确认在途归零、收束模型执行器并取得进程退出证明,最后重新核对源码、产物和证据摘要;核验成功原子进入 completed 并产出宿主报告。不能先写 completed 再尝试停止后台进程。宿主主动结束模型回合属于交付终态,不触发普通错误反馈或重试。未达标不得用模型最终回复替代验收。
|
||||
- Windows app-server 在任何模型工具执行前绑定不可脱离的自有 Job,超时/取消/断连时验证整个 Job 已退出。其它平台继续保留受控进程组;未取得完整子树退出证据时按不确定状态报告,不宣称全部后台执行已停止。已受理的远端付费任务保留原不确定围栏,断连不构成自动重放授权。
|
||||
- 付费许可始终绑定原回合和原租约,不能在容量或同动作锁排队结束后借用新回合。排队可取消,每次新 POST 前与封口共用宿主状态短锁,核对原许可、期限与阶段并持久化提交边界;封口、终止或耗尽后不得新增提交。已经越过提交边界的请求不丢弃,其 operation ID 和不确定状态继续持久化并允许原 GET 对账,多阶段生成的下一次 POST 仍须重新核验。ExternalClient 与手工资源操作保持既有语义。
|
||||
- 本地写事务未结算或失败仍需核对时不得封口。与失败写入重叠的旧写入/旧验证不能清除该围栏;只有失败之后新准入的成功修复或可信验证可以恢复验收。普通文件写入与账户/本地资产导入显式携带原写入许可,取得项目锁后再与宿主状态锁共同核验期限并提交短本地事务;等待锁或下载期间终止的请求不得继续落盘,网络等待不持宿主状态锁。
|
||||
|
||||
|
||||
## 2026-09-20 DirectProject 交付效率与可观测性
|
||||
|
||||
本节是 DirectProject 新建 Web 游戏和已有游戏修改的交付合同,覆盖下文要求所有修改重复完整试玩、模型自报 attempt 作为预算、工具桥串行生成的旧表述。目标是提前发现环境问题、复用稳定验证能力、在本轮目标达标后结束,并用真实记录区分执行与等待。非目标:降低素材来源或玩法验收要求、修改 Provider 重试配置、改变原生 shell 的权限模式、自动修改系统环境或无授权付费再生。
|
||||
|
||||
### 环境与工作流
|
||||
|
||||
- 客户端交付配套 Node/npm;发布包从本机已安装且与目标平台/架构一致的工具链制作受校验资源,保留许可并校验内容摘要。安装态不依赖系统 PATH 的 Node;开发态可使用已验证的宿主运行时。不得从项目或相对 PATH 加载伪造运行时。随包运行时是**单架构**官方发行版,因此 macOS 当前只构建 `aarch64-apple-darwin` 单架构包;要出 universal 必须先让 staging 支持按架构各带一份同版本运行时,在此之前 universal 目标失败关闭,不得只带宿主架构那一份糊过去。
|
||||
- 新建 Web 游戏在生图和大量实现前执行客户端环境预检,检查 Node/npm 的实际版本、浏览器启动和 CDP 可用性。报告只包含安全状态、版本、耗时和错误码。缺失或异常必须尽早返回阻塞,不能指示模型改宿主环境、全盘搜索或自行下载一套运行时。编辑器工程不强制 Web 工具链。
|
||||
- 预检不安装依赖、不修改项目 revision、不请求平台生成;构建仍执行项目自己的 npm 脚本。Codex 隔离 HOME 与平台凭据边界保持不变,客户端把已验证的运行时加入执行 PATH,不能把宿主凭据目录交给模型。
|
||||
- 第一轮先明确本次必需玩法、素材和验收项。同批独立读取尽量合并,必需图片一次规划;已有且可用的资产复用。已有目标全部通过后给出交付结果,非阻塞的新点子列为后续工作,不在收尾时主动开启新的生产链。
|
||||
|
||||
### 分层验证与预算
|
||||
|
||||
- 复用客户端浏览器与现有固定玩法场景。视觉检查采集双端画面/布局/资源/诊断;玩法检查分别在 desktop/mobile 执行明确的固定场景和真实输入,按视口保存结果。旧报告缺少移动端玩法结果时保持未知,不补通过。报告必须声明检查层级;视觉通过不能宣称玩法通过,固定场景通过也不能宣称覆盖未执行的完整关卡。
|
||||
- 输入或碰撞改变先做定点玩法检查;纯图像/颜色变化做视觉检查;首次交付和影响闭环的修改做所需玩法验证。新增失败或相关代码变化才重跑对应层,不因改说明文字重复完整验证。
|
||||
- 内置试玩和客户端托管的外部 Node/npm 验证共用当前 clientTurnId 的持久化预算。客户端分配递增执行序号,模型提供的旧 attempt 仅作兼容输入,不能减少计数或重置预算;同一轮错误反馈、工具切换和进程重启均不能刷新已消费次数。
|
||||
- 新增本地 validation.maxRuns(默认 3,正整数)独立于 llm.maxRetries;显式配置原样使用,不按角色或运行模式改写。超限直接返回已用/上限和最近证据,停止新的验证。预检与正常构建不计作重复试玩。
|
||||
- 同一层级、场景/验证命令和项目输入指纹已有成功证据时复用,不再次运行;真实源码或素材变更使缓存失效,失败不缓存为成功。所有验证回执标明是否复用、项目指纹、实际序号和预算剩余。
|
||||
- 外部验证只通过客户端提供的 Node/npm 入口运行,在同一预算内保存退出码和有界输出;生产 Skill 明确禁止转到原生 shell 自建并重复执行另一套试玩来规避预算。任意原生 shell 的语义不能由字符串猜测可靠识别,本合同不声称已通过权限沙箱硬阻断所有绕行。
|
||||
- 鉴权、权限、余额、项目身份、传输丢失、取消及付费结果不确定继续遵守原终止/对账边界;确定性参数错误先修参数,不原样重复付费请求。
|
||||
|
||||
### 分段耗时
|
||||
|
||||
- 在既有 Direct 回合审计记录中追加计时,关联 clientTurnId、独立 attempt 和 request 身份。记录 configured/requested model、reasoning effort 与封闭的路由分类;上游返回的 model 单独标明,不能把配置值冒充实际模型。
|
||||
- 区分连接准备、客户端回合锁等待、turn/start 应答、HTTP 发出到响应头、首 body chunk、首 SSE event、首内容 delta、流终态、工具与上下文压缩。不可见的上游排队/推理保持未知,缺字段不得补零。
|
||||
- 并发时按区间并集计算占用,同时保留分维度统计,不能把重叠时间累加为整轮墙钟。条目记录达到上限后统计仍继续;流 EOF、错误、取消和 Drop 均正确收尾。
|
||||
- 只保留时间、计数、模型安全标识和状态,不保留凭据、端点 URL、请求/响应正文或推理内容;统计失败不能覆盖本来的业务结果。旧历史不回填推测值。
|
||||
|
||||
### 工具并发
|
||||
|
||||
- 所有工具调用均允许并行受理,不能按工具类型或由 STDIO 逐行 await 将独立工作整体串行化。完整 JSON 行通过单一写端输出并按请求 ID 回包;真正有依赖的调用由调用方等待前置结果。
|
||||
- DirectProject 的 Responses 代理明确发送 `parallel_tool_calls=true`,保持选定模型、推理参数与其它请求字段不变;不依赖捆绑 SDK 对未知模型的串行回退。MCP 服务使用真实支持的 `supports_parallel_tool_calls` 选项,保留工具真实读写标记及执行前许可。上游拒绝并行能力时如实返回,不自动改成串行或切换模型重发。
|
||||
- MCP 调度的在途容量为 8 并具有背压;只为同一资源的冲突操作、项目短事务、编辑器单实例或同一付费动作保留必要串行边界。不同文件/不同资源/不同独立工具可同时推进,不保留覆盖所有资源远端等待的粗粒度锁。
|
||||
- 客户端独立图片在途上限为 2,这是客户端自己的资源上限,不代表探测到了服务端配额。不得通过放松同一动作槽幂等锁、账户身份栅栏或付费不确定性围栏换并发。
|
||||
- 不同动作允许同时远端执行,manifest 只在提交阶段短持项目锁并重读合并;同一精确动作在排队和执行期间均不得重复 POST。容量释放、进程/通道关闭、同参排队和失败恢复有专门回归覆盖。
|
||||
- 不改公开 External v1、计费、SpacetimeDB schema 或平台 worker 配置。
|
||||
|
||||
### SDK 串行工具的等价接入
|
||||
|
||||
- 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。
|
||||
- OAuth 在目录捕获与模型进程内产生的轮换结果仅在宿主私有 runtime 中延续,按原始认证来源指纹、路由、项目及稳定账户/用户身份绑定后复制到下一回合的新私有 HOME;不回写用户原始认证文件,不缓存 API Key。来源、路由或身份改变时不继承,迟到的旧实例不得覆盖新回合结果;轮换结果未确认时禁止重用旧 token。
|
||||
- 实例退役与验收完成使用不同判据:Windows 仍要求完整 Job 退出;Unix 已确认主进程退出且所控进程组为空时可退役并允许下一显式用户回合,但 group-only 证明不能使原会话从 Interrupted 升为 Completed,不能自动重放旧操作。主进程、所属组或退出状态仍未知时继续阻断新实例。
|
||||
- 补丁完整复用固定版本官方语法解析与执行语义。宿主枚举每个源和目标(包括所有 Move 和重复操作),检查项目边界、受保护路径和链接,在本地短写事务中复核后执行;取消、预算、退出证明和未知结果仍走统一宿主控制。失败可能已有部分修改,不能声称全批回滚或自动原样重放。
|
||||
- 模型计划进度保存到同一回合的宿主状态,仅作展示,不等于验收通过;计划更新和长资源调用可以同时推进。真正共享资源的修改仍保持必要顺序。
|
||||
|
||||
### 验收
|
||||
|
||||
| 条款 | 必需证据 |
|
||||
| --- | --- |
|
||||
| 环境可用 | 运行时分发/完整性定向测试,真实 Node/npm 与浏览器 CDP smoke,缺失与损坏失败关闭 |
|
||||
| 验证收敛 | 同轮跨入口与重启预算测试,超限停止,成功复用与源码/素材变更失效,层级不混淆 |
|
||||
| 交付收尾 | 内置 Skill/提示词与工具合同一致,定向回归,无旁路无限试玩指引 |
|
||||
| 可观测 | 本地 mock SSE 分片/错误/Drop/跨轮测试、区间并集测试、模型身份与敏感数据边界 |
|
||||
| 工具并发 | 不同工具可在首个响应前开始且乱序按 ID 回包;两个不同图片同时到达 mock 平台,同参只提交一次,容量和 manifest 合并测试 |
|
||||
| 整体 | 范围匹配 Rust/脚本测试、类型检查、Skill 包校验、文档索引、编码与 diff 检查;真实 Provider/安装包未运行时单独列明 |
|
||||
|
||||
|
||||
## 项目主模型使用记录
|
||||
|
||||
- 项目在 `.agent/model-usage.jsonl` 保存主模型记录,供复制或压缩完整项目后查询,不新增界面展示。每条只保存版本、记录时间、来源、请求模型标识、可确认的模型名称,以及可用的客户端回合和线程身份;不保存提示词、响应正文、连接地址或凭据。
|
||||
- DirectProject 每次提交回合前保存本次请求的模型快照,来源为 `turn-request`。官方目录标识与真实型号分开:官方标识只写 `requestedModel`,不能把 `platform-default`、目录 ID、`Direct Codex` 或 `codex-app-server` 当成模型名;自定义路由保存明确提交的型号。响应返回的模型名以 `provider-response` 追加,使用请求发出时冻结的项目/回合归属,后续切模型不会覆盖历史。
|
||||
- 模型名从成功 Responses JSON 的顶层 `model` 或 SSE `response.created` / `response.in_progress` / `response.completed` 中的 `response.model` 提取;只读取白名单字段,有界处理分块和异常数据,响应内容仍原样转发。上游没有返回型号时保留请求证据,不推测实际模型。
|
||||
- 旧项目通过 `project.status` 读取 manifest 成功后补录一次:在有界范围内优先恢复项目内可明确识别的主模型运行记录,超出历史扫描预算时跳过对应候选;没有找到可信历史型号时写入当前配置快照并标注 `current-config-backfill`,不声称其为历史事实。官方配置只有目录标识时保留标识,模型名为空;以后真实回合仍继续追加。补录幂等,不覆盖或改写原项目历史。
|
||||
- 记录复用项目受控路径与追加锁;无写权限、损坏或超限时保留已有文件并写安全诊断,不因此中断项目打开、模型响应或触发额外付费重试。响应记录在阻塞工作线程中追加,文件锁等待不阻塞响应原始块转发。补录仅发生在项目 `project.status` 命令和本次回合入口,不扫描机器上的其它项目或私人会话。
|
||||
- 验收覆盖:请求与响应的模型差异、跨回合/项目隔离、切模型保留历史、旧项目补录及幂等、分块 SSE/JSON、不含模型/失败响应不伪造型号,以及凭据/正文不落盘。使用本地 HTTP fixture 验证代理透传和落盘,真实供应商调用另行报告。
|
||||
|
||||
| 合同 | 自动化证据入口 |
|
||||
| --- | --- |
|
||||
| 来源区分、切模型保留历史、补录幂等、损坏/超限保护 | `project::model_usage::tests` |
|
||||
| JSON/SSE 字节透传、模型落盘与跨回合归属 | `agent::codex_provider_proxy::tests::model_usage_proxy_*` |
|
||||
| 分块 UTF-8、换行、多行事件和有界解析 | `agent::codex_provider_proxy::model_usage::tests` |
|
||||
| 文件锁争用不阻塞响应、流中断仍保留已确认型号 | Provider 代理的锁争用与流错误 fixture |
|
||||
|
||||
记录格式版本为 `schemaVersion: 1`。`recordedAtMs` 是记录时间,`historicalModelConfirmed` 仅在响应观测或可信历史恢复时为 `true`;它在请求快照和当前配置补录时为 `false`。上述本地 fixture 不替代真实供应商或安装包验收。
|
||||
|
||||
## 2026-09-20 Godot 编辑器插件
|
||||
|
||||
已有 Godot 工程通过内置 `agc-godot-editor` 接入 `godot.editor.execute` / `agc_godot_execute`,复用通用 PluginHost、EditorAdapter、Runner、可用开关和权限审计。DLL 随 AGC 安装资源分发,项目内受管描述文件触发官方 GDExtension 聚焦加载;工作区与实际 Godot 根继续遵守双根合同。GDScript 的真实完成、编译/运行错误、await 和不确定回执按 [Godot 编辑器插件接入](<./【技术方案】AGC Godot编辑器插件接入-2026-09-20.md>) 验收,不能用原型或模拟回执替代正式实机结果。
|
||||
|
||||
## 2026-09-17 GameCreationApp 资源 kind:唯一词汇表、严格解析与 `app_log!` 留痕
|
||||
|
||||
本节覆盖 2026-09-15 节里关于「canonical 字符串列表 / legacy 别名表 / `tracing` 留痕 / ts-rs 生成路径」的表述;枚举成员集合、「不迁移、不静默转换」的总体口径不变。
|
||||
|
||||
- **唯一词汇表**:kind 的变体、线上值(kebab-case)、`as_str()`、`ALL`、严格解析与 ts-rs 绑定全部由 `server-rs/crates/shared-contracts/src/game_creation_app/asset_kind.rs` 的声明表派生。`GAME_CREATION_APP_CANONICAL_ASSET_KINDS`、`canonical_game_creation_app_asset_kind()`(含 `font → document` 特例与 legacy 别名表)已删除;TS 侧同名的 `GAME_CREATION_APP_CANONICAL_ASSET_KINDS`、`GameCreationAppCanonicalAssetKind`、`GAME_CREATION_APP_LEGACY_ASSET_KINDS`、`canonicalGameCreationAppAssetKind()` 一并删除,仓库里不再有第二份 kind 列表。
|
||||
- **TS 生成路径**:`GameCreationAppAssetKind` 由 ts-rs 生成到 `packages/shared/src/contracts/generated/GameCreationAppAssetKind.ts`(不再是 `apps/ai-game-creator-shell/src/contracts/generated/`);`packages/shared/src/contracts/gameCreationApp.ts` 直接 re-export 该 union,运行期 kind 列表只有一份 `GAME_CREATION_APP_ASSET_KINDS`(穷举 `Record<GameCreationAppAssetKind, true>` 保证不会与生成 union 分叉)。重新生成:`cargo test --locked -p shared-contracts export_bindings --manifest-path server-rs/Cargo.toml`,之后 `git diff` 必须为空。两张生成目录的忽略规则同步登记在 `.prettierignore` 与 `.eslintrc.cjs`。
|
||||
- **严格解析只有一个入口**:`GameCreationAppAssetKind::parse_with_context(value, context)` 是唯一公开解析入口(`FromStr`、serde、所有外部边界都走它),内部严格匹配是私有 `match_canonical()`;先前那批易混名字(`from_str_lossy()`、公开的 `from_str_or_unknown()`)都已删除,认不出 canonical 值只有这一处收口 + 留痕。口径是等值匹配——不 trim、不 lowercase、不查别名、不迁移;`"UI"`、`"ui"`、`" image "`、`"art-spritesheet-slice"` 一律收口成 `unknown`(分类落 `unclassified`)。**接受 unknown 是显式决定**:留痕日志给出原始串与上下文,供回查仍在写 legacy kind 的代码;不写兼容、不做迁移。
|
||||
- **登记边界同口径**:外部登记 kind(Tauri 命令 `commands::register_local_asset`、`commands::import_canvas_asset`、平台导入)统一走 `assets::registration_asset_kind()`——空白入参按「没有信息」落中性 `image`(→「待归类」),非空但认不出的值走严格解析收口成 `Unknown` 并留痕。三条边界不再各写一份 trim/兜底分支;解析只发生在命令边界,`import_canvas_asset_at` 等内部函数直接接收 `GameCreationAppAssetKind`,不再接受字符串 kind。
|
||||
- **登记内容的 kind 必须与内容相符**:`normalize_local_project_raster_resource_at` 的源 subtype 只接受图片族成员(`image / scene / character / character-animation / icon / icon-spritesheet / icon-spec / ui-design`)。未提供或空白时按没有类型信息落 `image`;音频、视频、文档、字体、代码等非图片族值,以及未知字符串,均明确报错,不再静默改成 `image`,以暴露调用方错误。派生资源写回 manifest 时不得主动写 `Unknown`:Agent 回执派生物是 markdown 文本,按 `Text => Document` 同口径落 `document`。
|
||||
- **切片残留判据只看路径**:`register_existing_platform_art_slices_at` 判定「已有半成品切片登记」时只看 `assets/art-spritesheet-slices/` 前缀,不再附带 `kind == Icon`——历史切片刻写的是非 canonical kind,严格解析后是 `unknown`,再带 kind 判据会漏掉这些登记并静默跳过回填。
|
||||
- **sprite 身份不含展示串**:UI 工作流比较 sprite 资源身份时忽略 `metadata.asset_type`(它随 canonical kind 派生),只看真正影响渲染的字段;否则对既有 State 重跑工作流会误报「与已有 State 资源冲突」。
|
||||
- **TS 侧判据也只留一份**:`isGameCreationAppUiDesignDocAsset(asset)` 是「`kind == ui-design-doc` 且 `mediaType == application/json`」的唯一定义,资源画布入口、UI 编辑器桥接、资源引用缩略图三处统一调用它。
|
||||
- **严格口径的已知代价**:既有项目 manifest 里若存的是 legacy kind(`"UI"`、`"ui-prototype"`、`"art-spritesheet"`、`"game-background"`…),读入后就是 `unknown`,依赖 kind 等值比较的运行门禁会按「缺少该资源」处理。这是「不迁移、不 fallback」的必然结果,由项目决策接受;若将来要迁就存量数据,必须单独立项(一次性迁移或读侧白名单),不得把别名表加回解析路径。
|
||||
- **留痕走 `app_log!`**:`shared-contracts` 不再依赖 `tracing`(`kind-observability` feature 删除),改为暴露可注册回调 `set_non_canonical_asset_kind_reporter()`;AGC 壳在 `main()` 里注册成 `app_log!`,日志同时含**原始输入串**与调用上下文,用于回查还有谁在写 legacy kind。TS 侧对应 `parseGameCreationAppAssetKind()` 的 `console.warn`。
|
||||
- **分类映射**:`GAME_CREATION_APP_ASSET_CATEGORY_BY_KIND` 改为按 `GameCreationAppAssetKind` 变体穷举(含 `unknown → unclassified`),与 `GameCreationAppAssetKind::ALL` 的对齐由单测 `asset_category_mapping_covers_every_kind` 守住;TS 侧同表按生成 union 穷举。
|
||||
- **画布生成与图片快速编辑共用枚举**:`canvas.asset_generate` 的 `assetKind` 白名单直接复用 `GameCreationAppAssetKind::CANVAS_ASSET_KINDS`(当前为 `image / icon-spec / ui-design / icon-spritesheet`),工具 schema、prompt 和执行前校验均从该枚举派生,不再维护 `&[&str]` 字符串目录。图片快速编辑来源同样严格解析 `GameCreationAppAssetKind`,只允许 `is_static_image()` 的 canonical 成员并要求 `mediaType=image`;原有 `EDITOR_IMAGE_EDIT_STATIC_IMAGE_ASSET_KINDS` 兼容白名单已删除,退役值不再放行。平台适配层若仍需要其他领域的外部请求字面值,只能在发送边界显式映射;manifest 始终写入枚举的 canonical 值。
|
||||
- **版本素材替换**:`subtypeEqual` 改为枚举相等(不再经别名字符串归一),`categoryEqual` 仍用 `game_creation_app_asset_effective_category` 的读时口径。
|
||||
|
||||
## 2026-09-15 GameCreationApp 资源 kind 枚举化(当前权威口径)
|
||||
|
||||
本节覆盖并替代下方 2026-09-09 资源 kind 别名、canonical fallback 与读时自愈相关口径。当前仍在开发期,不为旧本地 manifest 提供 migration、alias 兼容或静默转换;旧值只会在解析时落入 `Unknown` 并记录结构化日志,后续按扫描清单清理产生旧值的代码。
|
||||
|
||||
本次重构范围限定在 `apps/ai-game-creator-shell` 内表达 **GameCreationApp 资源 kind** 的字段、参数、投影、筛选和 manifest 读写边界。`mediaType`、MIME、文件扩展名、`source.kind`、`source.generationKind`、任务/工作流 kind、路径名,以及 server-side 其他 `asset_kind` 不在本次重构范围内,继续使用各自现有类型。
|
||||
|
||||
Rust 侧在 `server-rs/crates/shared-contracts` 维护唯一权威 `GameCreationAppAssetKind` enum,并使用 `serde` kebab-case 序列化;`Unknown` 是正式解析结果,序列化值为 `unknown`。TypeScript 绑定由 `ts-rs` 从 Rust 生成到 `apps/ai-game-creator-shell` 内,删除 `packages/shared/src/contracts/gameCreationApp.ts` 中重复的手写 kind 契约。正常写入路径不得主动构造 `Unknown`;未知外部值进入 `Unknown` 时必须记录原始值、来源和调用上下文的 `tracing`/日志。
|
||||
|
||||
当前候选正式成员以 api-server 仍有效的资源 kind 命名为基线,并保留 shell 已实际使用的字体与通用音频语义:`image`、`scene`、`character`、`character-animation`、`icon`、`icon-spritesheet`、`icon-spec`、`ui-design`、`ui-design-doc`、`publication-material`、`spec`、`video`、`audio`、`sound-effect`、`background-music`、`font`、`document`、`code`、`unknown`。`audio` 表示通用上传音频,`sound-effect` 与 `background-music` 表示更具体的生成/资源语义,不因同属 `audio` category 而合并。`asset`、`ui`、`animation`、`game-background`、`art-spritesheet` 等旧值必须逐处审计:进入 GameCreationAppAssetKind 的生产写入改为正式成员;仅作为 workflow、generation、路径或协议标识的值保持原字段,留待后续独立 PR。
|
||||
|
||||
实施要求:先完成 shell 内 raw kind / typed kind 扫描清单与生产写入审计,再分小提交实现 Rust enum、生成绑定、内部字段收紧、写入方修正、Unknown 可观测性和旧 alias 删除。每个提交只形成一个可验证的深模块,不把不同领域的字符串类型强行合并;完成后补充 `ts-rs` 生成/校验门禁、serde round-trip、未知值日志和所有 manifest writer 的 canonical/Unknown 边界测试。
|
||||
## 游戏画布居中指引
|
||||
|
||||
开发 Agent 的系统工程提示与 `agc-web-game-development` Skill 明确约束同一 canvas 的居中只能由一方负责:Phaser `FIT + CENTER_BOTH` 配合尺寸明确的普通块级父容器,不叠加同一父容器的 Grid/Flex 居中、自动外边距或居中 transform;如由 CSS 居中则设置 Phaser `NO_CENTER`,外围页面的 Grid/Flex 不受此限制。布局修改后构建实际 dist,并在桌面、移动和 resize 下核对 canvas 对游戏父容器中心偏差不超过 1 CSS px、无溢出与意外滚动条。出现偏移先检查游戏项目的 CSS/scale,不修改 AGC 预览固定偏移掩盖问题;这些要求通过 Agent 指引执行,不新增运行时门禁或平行校验系统。
|
||||
|
||||
## 画布验收返修边界
|
||||
|
||||
- 生成浮层的实例、输入、失败状态与请求身份必须按占位草稿身份隔离;切换到另一张占位时不能沿用上一张的工具类型、输入或 operation。切换未提交草稿仍须保留各自输入,回到原占位可继续编辑。
|
||||
- 失败任务重试必须保留原请求的完整参考资产 ID 集合。参考资产失效时明确提示并阻止旧请求提交,不得静默过滤后用改变的参考集恢复原 operation,也不得自动创建新的付费请求。
|
||||
- 显式整理的待写意图不能因切换栏目而覆盖其他栏目的排队范围。每个受影响范围与撤销步骤保持一致;拖动预览和最终写回使用同一份按下时冻结的起点快照,即使拖动期间布局收到异步更新也不跳位。
|
||||
|
||||
## 多选素材批量标签
|
||||
|
||||
- 画布与资源面板共用选中集合。选择至少两项同项目已登记素材后,从已有“编辑标签”入口或资源面板的“批量标签”动作打开同一标签编辑器的批量模式;入口遵循既有“常用操作 + 更多”收纳,不新增平行资源管理页。
|
||||
- 批量模式只将用户填写的标签追加到整组选中素材。输入按既有中英文逗号、顿号、换行拆分,trim、去空及去重;每份素材原有标签、正式分类、类型、路径、来源、版本关系均保留。批量删除/替换既有标签、批量改素材类型及 AI 自动分类不在本次范围。
|
||||
- 操作对象在打开面板时冻结为去重后的素材 ID 列表,不随后续选择变化扩散;项目切换关闭面板并忽略迟到响应。混合选择含虚拟版本、未登记附件或已删除资源时,不允许只对其中一部分静默保存,入口禁用并给出原因。
|
||||
- 批量范围为当前入口展示的完整选中集合:资源面板保留跨筛选的选择时,也必须显示实际目标数量;不得仅修改第一项或仅修改当前可见项。每批最多 200 个不同素材,超过上限要求缩小选择。
|
||||
- 原生新增局部命令 `add_local_project_resource_tags`,参数 `input: { projectPath, expectedProjectId, expectedProjectRevision, assetIds, tags }`;仅消费当前 manifest 素材 ID。响应 `{ assets, committedProjectRevision }` 返回整组选中素材最新条目。沿用 `asset.register` 权限、项目身份、写锁及 revision CAS;不新建 manifest schema、数据库字段或外部 API。
|
||||
- 原生按锁内最新 manifest 为每项合并标签,沿用现有单标签长度与标签总量限制,先校验全部目标及合并后上限,再一次写 manifest;非法 ID、标签、项目身份或版本冲突不写入任何一项。重复标签重试不重复追加;整批无变化时不推进 revision、不追加修改审计。已写 manifest 后的审计或 revision 异常必须明确返回“已写入”状态信息,沿用既有单资源写入错误语义,不能谎称回滚。
|
||||
- 保存期间禁用重复提交及关闭;失败保留待追加标签,CAS 冲突需明确报错而非自动覆盖。成功用原生返回条目刷新宿主 manifest 与筛选统计,不循环调用单素材保存、不按旧闭包覆盖最新其他素材。
|
||||
- 单素材标签编辑仍保持既有增删标签行为;菜单信息/类型入口、批量移动、文档与引擎资源预览不得回退。验收覆盖不同原始标签与分类、资源面板/画布多选入口、重复标签、超限及混合选择、批次失败零部分写、一次 revision、项目切换迟到回包和单素材回归。
|
||||
- 批量模式的标签输入仅显示待追加草稿,不把各素材已有标签并集作为提交值。资源面板入口放入现有动作行,画布入口沿用选中工具栏的收纳。响应条目按去重后请求 ID 的首次出现顺序返回,无变化时返回当前 revision;DTO 使用 camelCase 并拒绝未知字段,批次上限按去重后数量计算。
|
||||
|
||||
## 资源画布生成、展示与布局合同
|
||||
|
||||
- 客户端「生成图标素材」只将用户描述去除首尾空白后,保留内部换行并作为唯一 `iconDescriptions` 元素提交;不添加小游戏首版原型、核心美术或默认美术 brief,不截断、不按段落或字数拆条。面板与原生入口均要求描述非空且最多 `200` 个 Unicode 字符(按码点计数),超限在提交前明确拒绝;其余图片生成提示词维持 `32000` 字符上限。规范图、纯色背景、素材排布等生图约束由网站与客户端共用的 API Server 流程统一添加。完整请求合同见 [画板图标素材生成入口设计](../【编辑器】画板图标素材生成入口设计-2026-06-15.md)。
|
||||
- 生成工具点击后先在当前栏目创建临时占位卡,并以卡片为锚点展示独立生成浮层;占位不登记为正式素材、不进入 Agent 可引用资源集。上传仍沿用文件选择,不创建虚假生成任务。关闭编辑浮层不应丢失正在执行的任务;切换项目不得将旧项目结果或草稿写入新项目。
|
||||
- 图片生成支持从现有素材选择器添加真实参考;引用携带稳定资源身份并经既有原生权限、归属与类型校验传至生成链路,不能仅拼接名称。缺少规范图不阻止打开面板;确有规范前置的操作必须在提交前满足要求,不能绕过后端校验。用户可通过现有工具栏先生成规范图。
|
||||
- 占位可移动。提交复用正式生成任务、幂等与结果登记链路;成功结果使用占位最新位置,失败保留输入与引用供重试。已受理但响应不确定时先对账,不能无条件再次发起付费生成。关闭、删除占位和后台任务的行为需保持既有任务所有权,不把隐藏展示当作取消任务。
|
||||
- 所有资源卡显示正式素材名称(Agent 生成的 assetName 或用户重命名),没有名称时才使用既有文件名兜底;长名称省略但可查看完整名称。文档卡以居中图标呈现,不显示无效正文片段;独立详情保留原文预览、UI JSON 识别、权限及读取预算。
|
||||
- “整理画布”显式重排当前栏目全部素材,包括手动坐标;筛选不缩小整理集合,其他栏目不变。重排作为一次可撤销操作保存,失败继续使用现有写队列及冲突处理,不伪报保存成功。
|
||||
- 框选多个素材后拖动任一已选卡,按统一位移移动整个选择集并保持相对位置;拖动未选卡保留单选语义。松手统一提交,整次操作可一次撤销;缩放坐标、指针取消、窗口失焦、保存失败和项目切换不得导致选择丢失或布局串写。
|
||||
- 本合同不包含拖入对话批量 @、复制聊天引用、历史替换交互、SpacetimeDB schema 或新的远程公开 API。共享表现与交互优先扩展公共组件,正式资源状态仍由宿主/原生链路维护。
|
||||
- 参考选择范围为同一项目已登记图片,可跨栏目、多选,无规范前置时最多 5 张,有规范前置时最多 4 张用户参考(总计最多 5 张);复用资源引用选择组件,不允许文档、音视频、占位或跨项目素材。本地生成命令补最小引用 ID 参数并转换为当前账号绑定下的远端资源 ID,沿用图片生成 API 已有 `referenceImageSrcs`。需要规范图的普通图片请求合并并去重规范引用,总数不超过现有 API 限制;只接受单规范引用的图集操作不显示用户参考选择器,原生提交拒绝额外参考而非静默丢弃。不得降级成纯提示词。
|
||||
- 占位由宿主按项目与独立草稿 ID 管理,提交后关联任务 ID;失败重试使用同一占位。切项目清理未提交草稿与界面位置,已提交任务继续沿用账本恢复,重开后不承诺恢复未持久化的占位位置。迟到结果先核对项目和任务归属;只有本会话仍存在的占位才应用最新位置。删除占位只隐藏展示,不取消后台任务或丢弃正式结果。
|
||||
- 参考必须是原生可解码的栅格图片,SVG 不进入参考候选;原生在上传任何引用前预校验整组素材的归属、受控路径、文件及解码,失败不静默丢图。需要重新上传当前账号绑定的参考遵循 `asset.upload` 权限。manifest 读侧的引用形状检查不证明远端账号归属,实际生成始终通过当前账号 binding 解析,不凭历史来源 ID 发起请求。
|
||||
- 图片类 GUI 生成通过可选 `targetCategory` 在原生登记时写入入口栏目,使用既有 manifest `category` 字段及合法分类词表;不传时保留按 kind 派生的行为,Agent 不传。该值不改变远端生成内容及计费幂等槽,仅决定本地生成结果分类;重试保持原占位栏目。同路径重新生成时,主产物按本次入口栏目更新分类(包含覆盖此前手动分类),图集附属切片保持既有独立分类规则。
|
||||
- 生成面板打开后,占位和面板需处于当前画布标题栏与底部工具栏之间;面板复用公共外观,空间不足时面板内部滚动,提交按钮可达。音频/BGM 占位绑定原有 operation/idempotency 身份,进行中不允许换身份重复提交;失败可用原身份重试,成功结果与图片一样接管占位。关闭未提交浮层保留可继续编辑的草稿,删除占位才丢弃该草稿。
|
||||
- 整理范围为当前栏目页全部资源;“所有资源”页为当前项目所有可展示资源,总览不新增整理行为。重排结果成为自动坐标,可撤销恢复原坐标与手动标记;历史仅保留当前会话,切项目清空。多选仅作用于当前画布可见选中资源,不携带筛选隐藏或跨栏目残留选择;取消手势恢复拖动前坐标,切项目清空选择。
|
||||
- 当前素材名以现有正式命名链路为准:生成时 assetName 参与落盘名称,重命名更新文件名;卡片消费正式资源 label,不从临时输入或历史任务名覆盖后续重命名,不新增平行显示名持久化。若原有命名链路丢失 assetName,则修复原链路,而非只在卡片本地伪造。文档卡不显示任何正文摘要,但详情原文与 JSON 识别读取不变。
|
||||
- 验收覆盖空素材项目进入工具、真实引用入参、成功/失败/重试与迟到响应、占位移动后落点、全类型名称、文档详情、当前栏目重排/撤销、不同缩放的多选移动/撤销以及其他栏目不变。自动化、真实客户端和真实 Provider 验证分别报告;未实际运行的路径不得标为通过。
|
||||
|
||||
## 平台服务与官网分发
|
||||
|
||||
客户端正式包的平台服务跟随构建渠道:release 连接正式服务,dev 连接开发服务;本地 debug 登录页保留 release/dev/custom 服务器选择。凭据按 origin 隔离;官网下载入口汇总最新渠道清单,自动展示已有首装包的 Windows/Mac 平台及架构。完整合同见 [AGC 客户端更新检查与下载](./【技术方案】AGC客户端更新检查与下载-2026-08-31.md) 的“官网下载与客户端服务地址”。
|
||||
|
||||
## Agent 提示词与回合测试边界
|
||||
|
||||
预览快捷操作的 UI 回归按独立命令及必要的连续操作拆分,每例使用新的项目 fixture、invoke mock 和页面,项目打开后再采样副作用调用计数。保留命令输出、草稿填入、焦点、权限确认和原生参数断言;预览启停、快照回滚取消、导出确认与取消作为各自短流程验证,使用默认测试超时。定向运行 `npx vitest run apps/ai-game-creator-shell/tests/appSurface.test.ts -t "预览快捷操作:"`。
|
||||
|
||||
提示词拼装测试核对动态错误、外置片段和实际 Provider 请求结构,避免绑定可自由润色的中文原句。源码载荷限制由实际动作校验与 Provider 修复测试覆盖;工具参数自动修复和超预算上下文压缩继续验证恢复结果、状态及私有信息隔离。Direct 消息原样转发复用生产请求转换测试,文件变化与版本幂等直接测试生产文件投影函数,删除测试专用的重复回合编排。
|
||||
|
||||
Rust 分片日志在失败时输出有界 stdout 尾部中的失败段,保留 panic 位置、断言详情和 Rust 汇总;selected 只表示该分片选中的用例数量。成功分片保留简短摘要。定向验证使用相关 Rust 用例与 `node --test apps/ai-game-creator-shell/scripts/run-rust-shell-test-shards.test.mjs`。
|
||||
|
||||
## 策划 Agent 批量局部修改
|
||||
|
||||
`patch_file` 的所有 edits 均匹配同一份原文件,参数顺序不影响结果。完成唯一匹配与不重叠校验后,按原文起点升序拼接未修改片段与替换文本,最后一次性写入;任一校验失败时不写文件。回归用例覆盖乱序 edits、中文内容与替换长度增减,并核对完整落盘内容。此行为仅属于策划 Agent 文件工具。
|
||||
@@ -12,6 +253,9 @@
|
||||
|
||||
## 资源画布交互与工作台状态同步
|
||||
|
||||
- 未完成抠图的恢复项在现有类型行显示账本中的复杂/平面背景模式;平面模式显示已记录的自动背景色或颜色值,缺失模式/颜色不补默认值,恢复仍按原 operation 身份执行。
|
||||
- 活动回合初始空快照、首次成功读取的空结果及禁用后的空态保持同一数组引用;快照签名初值与空态重置值均为 `[]`。无原生 invoke 的窗口测试只期待首次状态发布,异步空结果测试显式控制请求完成,不以初始空数组作为请求已完成的证据。
|
||||
|
||||
- 活动回合轮询的初始空快照与后续空结果保持同一引用;停用或切换读取器使旧请求失效,晚到快照不得恢复已停用的活动回合或覆盖新轮询结果。无原生读取器时窗口只发布一次空状态,不通过额外空数组触发重复发布。
|
||||
- 工作台向窗口标题栏发布正在运行的项目时,输入未变化不得形成重复发布与清理的渲染循环;打开项目动作始终使用当前工作台处理逻辑,退出工作台后清除其标题栏状态。
|
||||
- 资源子画布(含「所有资源」)保留空白处左键框选、资源卡左键选中/拖动、触摸板双指平移及捏合缩放;右键按住空白处或资源卡拖动时平移画布,不改变资源选择与布局。中键和空格抓手继续可用。总览保留既有左键平移,并支持右键平移。
|
||||
@@ -41,14 +285,10 @@
|
||||
|
||||
## DirectProject 用户消息契约验证
|
||||
|
||||
`chat_with_game_creator_direct_codex` 必须携带 `projectPath`、`prompt`、稳定的 `clientTurnId` 和完整 `userItem`;`creationType` 与 `attachments` 按实际输入传递。Rust 通过 `projectPath` 解析项目身份,不接收额外 `projectId`。界面测试必须核对 `userItem` 的消息身份、角色、正文及附件内容,拒绝回合用例仍验证实际返回的错误原因。重开项目的历史恢复测试使用 `read_direct_project_history_slice` 的 canonical raw items 与 `hasMore`,首屏 `limit: 20`。资源图和生成任务的读取继续遵守原有工作台恢复逻辑,不因聊天断言失败延迟、关闭或改变它们。
|
||||
`chat_with_game_creator_direct_codex` 必须携带 `projectPath`、稳定的 `clientTurnId` 和完整 `userItem`(文本、`@` 素材引用与附件引用都在同一份 content 里);`creationType` 按实际输入传递。Rust 只从 canonical `userItem` 派生回合输入,不接收前端渲染的 `@显示名` 文本投影。Rust 通过 `projectPath` 解析项目身份,不接收额外 `projectId`。界面测试必须核对 `userItem` 的消息身份、角色、正文及附件内容,拒绝回合用例仍验证实际返回的错误原因。重开项目的历史恢复测试使用 `read_direct_project_history_slice` 的 canonical raw items 与 `hasMore`,首屏 `limit: 20`。资源图和生成任务的读取继续遵守原有工作台恢复逻辑,不因聊天断言失败延迟、关闭或改变它们。
|
||||
|
||||
## 2026-09-16 DirectProject 回合展示唯一归属
|
||||
|
||||
- 聊天历史使用 `read_direct_project_history_slice` 的 `messagesOnly: true` 模式,按有正文的 user/assistant 消息分页,默认 20 条;工具/推理原始记录不占聊天页名额、不进入聊天分页响应,也不从磁盘删除。接口省略该选项时维持原始 item 切片语义。响应给出明确的 `oldestItemId` 游标;消息投影不能重新发明分页位置。
|
||||
- 消息模式读取逐行过滤原始记录,不在内存中积累整份工具输出;正文、原始 ID 和信封时间原样保留。旧的无 ID 消息不能凭空生成身份,必要时向前扩展到已有消息 ID 边界;没有更早消息时结束分页。
|
||||
- 首屏和加载更早消息共用分页解析;重复点击只发一个请求,重叠消息按原始 ID 去重并保留当前显示版本。切项目、同项目重新加载及 A→B→A 的迟到响应不得覆盖当前消息、游标或加载状态;失败保留已有消息与分页位置并允许重试。
|
||||
- 历史读取来源与 Runtime 所有权分开记录;从磁盘分页读出的消息不能当作未落盘实时消息保留到新页末尾。重新读取历史时回到最新页,旧页仍可从原生游标再次向前加载;真正尚未回读到的实时消息继续保留,不改原始日志、时间或内容。
|
||||
- 交付合同:实时消息、历史回读、工具详情与最终回复先归一为按 `clientTurnId` 唯一的回合,再渲染一次。用户消息始终保留;同一回合的正文、工具和耗时不能从消息、实时尾部、未归属尾部等多个出口重复展示。
|
||||
- 归属来自 `direct-codex:{clientTurnId}:{role}`、文本流中保留的原始 item ID,以及项目历史内明确用户记录之后的 assistant 记录。先在已加载的完整消息集合中关联,再做可见分页;持久历史继续通过 canonical item 切片懒加载,`hasMore` 为真时,未加载回合的工具流不得漂到当前页尾部。禁止将第 N 个有工具回合配给第 N 条用户消息,禁止按文本长度、标点或时间窗猜测归属。缺身份的旧记录保留,不猜造其与其它回合的关联。
|
||||
- 有回合流时正文与工具位置仅来自 item 边界与 `seq`,工具详情按该回合的 `callId` 关联;没有流时同一个回合容器显示历史消息与工具。整轮累计文本仅在活动回合尚无流和持久 assistant 时作兜底,不另建实时消息出口。
|
||||
@@ -117,9 +357,9 @@ App 界面测试中,关闭 Agent 弹窗后的迟到读取用例先等待「刷
|
||||
|
||||
## 2026-09-09 manifest 资源功能分类与自定义标签(数据层)
|
||||
|
||||
本地 manifest 资产条目末尾新增 `category`(单值,6 类 `ui-interaction / character / scene / audio / document / unclassified`,默认 `unclassified`)与 `tags`(字符串数组,默认空数组);字段始终序列化,不进 api-server、外部 OpenAPI 或 SpacetimeDB schema。默认分类由 `GAME_CREATION_APP_ASSET_CATEGORY_BY_KIND` 按 canonical kind 穷举映射:`icon / icon-spritesheet / icon-spec / ui-design → ui-interaction`,`character / character-animation → character`,`scene → scene`,`audio / sound-effect / background-music → audio`,`document / spec → document`,`image / video / code / publication-material → unclassified`;alias 表**大小写不敏感**(UI 设计资产的现役写入侧写的是大写 `"UI"`,`font` 是字体上传登记的 kind),`"UI" → ui-design → ui-interaction`、`font → document → document`;其余映射不到 canonical kind 的原始 kind(例如 `test`)才经 `canonicalGameCreationAppAssetKind` 落 `image`、归 `unclassified`。读显示口径在 TS(`gameCreationAppAssetCategory`)与 Rust(`game_creation_app_asset_effective_category`)同构,写回 manifest 必须用落盘原值 `gameCreationAppAssetPersistedCategory`。
|
||||
本地 manifest 资产条目末尾新增 `category`(单值,6 类 `ui-interaction / character / scene / audio / document / unclassified`,默认 `unclassified`)与 `tags`(字符串数组,默认空数组);字段始终序列化,不进 api-server、外部 OpenAPI 或 SpacetimeDB schema。默认分类继续由资源 kind 到 category 的显式映射决定:`icon / icon-spritesheet / icon-spec / ui-design → ui-interaction`,`character / character-animation → character`,`scene → scene`,`audio / sound-effect / background-music → audio`,`document / spec / font → document`,`image / video / code / publication-material / unknown → unclassified`。kind 枚举、未知值与写入边界以本文件顶部 2026-09-15 口径为准;本节不再定义 alias、canonical fallback 或旧 manifest 自愈。
|
||||
|
||||
历史 manifest 缺少字段时读取按 `kind` 派生分类、`tags` 取空数组;显式写入的合法分类原样保留,未知分类字符串按前向兼容退回 `kind` 派生。新建资源条目写 `kind` 派生默认值,更新既有条目保留已有 `category` / `tags`,不覆盖用户值。`tags` 归一化(trim、去空、去重)只以纯函数交付,本轮不接入写入路径。本轮不做任何 UI(画布功能画布与筛选、@ 面板标签筛选、标签管理面板)、数据迁移脚本、标签库聚合派生与 Agent 检索工具。
|
||||
开发期 manifest 只按当前枚举解析;旧字段兼容、迁移脚本与 alias 不属于当前目标。新建资源条目必须由已验证的 `GameCreationAppAssetKind` 写入;未知输入只能产生 `Unknown` 解析结果并记录日志,不能由 writer 静默改成其他 kind。`tags` 归一化(trim、去空、去重)保持现有规则。本节原有 UI、标签库和 Agent 检索范围不因本次 kind 枚举化扩大。
|
||||
|
||||
## 2026-09-08 Web 游戏 npm 与 Phaser 4 产物合同
|
||||
|
||||
@@ -164,10 +404,10 @@ npm 游戏的可预览产物固定为对应 package 目录下的 `dist/index.htm
|
||||
|
||||
- `agc_tools` 新增 `agc_list_registered_assets` 与 `agc_create_or_derive_resource`。前者按 `kind / assetId / offset / limit` 有界查询客户端权威 manifest,并可显式返回角色动画正式序列帧的稳定 objectKey、assetObjectId 和尺寸;结果不包含完整 manifest、prompt、model、provider route、签名 URL、宿主路径或凭据。后者只接受 `kind / mode / sourceLocalAssetId / prompt / assetName`,`create` 仅允许无源视频、音效和背景音乐,`derive` 必须引用当前项目已登记的 localAssetId,角色动画固定为 derive。
|
||||
- `prompt` 上限按 `kind` 分别生效,且工具 schema、MCP 校验、客户端工具桥与提交校验共用同一权威口径(`resource_edit_prompt_max_chars`):背景音乐 140、音效 1900、视频与角色动画 4000、图片编辑 32000。schema 逐 kind 声明 `maxLength` 并在 `prompt` 描述里写明数字,超限必须在发起任何桥请求与付费提交之前失败并回报真实上限;`sourceLocalAssetId` 不是当前项目已登记资源时,错误文案必须直接给出 `agc_list_registered_assets` 与 `agc_list_project_files` → `agc_import_account_assets.localPaths` 两步后续动作。
|
||||
- 项目路径、projectId、当前 revision、源文件路径与媒体类型、operationId、Idempotency-Key、登录态、项目锁、付费提交、轮询恢复、下载校验与 manifest 事务全部由客户端持有。模型不能提交或覆盖这些字段。同一 Direct `clientTurnId + 规范语义参数` 生成稳定 UUID v4 身份;单回合同参重试复用原 operation,不同请求串行且最多四项。跨回合存在完全匹配的 pending 账本时优先恢复原 operation,不能换键重发。
|
||||
- 项目路径、projectId、当前 revision、源文件路径与媒体类型、operationId、Idempotency-Key、登录态、项目锁、付费提交、轮询恢复、下载校验与 manifest 事务全部由客户端持有。模型不能提交或覆盖这些字段。同一 Direct `clientTurnId + 规范语义参数` 生成稳定 UUID v4 身份;单回合同参重试复用原 operation,不同请求串行提交,不设每回合计数上限(2026-09-19 起,见决策记录)。跨回合存在完全匹配的 pending 账本时优先恢复原 operation,不能换键重发。
|
||||
- 资源查询同时投影未完成 operation 的安全状态。媒体工具成功只返回 operation、本地相对路径、资源类型、Canvas/resource/asset/task 身份、正式序列帧以及脱敏后的 `warnings / sliceWarnings`;错误继续使用统一脱敏边界。客户端资源账本持久化 completed 结果的两类告警,committed replay 不能把历史告警伪装成空集合。
|
||||
- 角色动画、视频、音效和背景音乐在构造新的远端请求前统一准备当前项目同名画布与素材目录上下文,并在端点支持时携带 `projectId / assetFolderId / canvasCompletion`。角色动画 placeholder 使用源图片真实宽高,避免非方形角色进入画布时失真;正式 resource/asset 与序列帧继续直接复用 External 返回身份,不从首帧伪造重复资源。已有冻结 request body 或已受理 operation 保持不变,不因本次升级重建请求或重复扣费。
|
||||
- 抠图通过新增 `agc_remove_background` 语义工具开放:模型只提交当前 manifest 的图片 `sourceLocalAssetId` 与结果名称;客户端解析稳定 `resourceId`,准备同名画布/素材目录并生成稳定 operation/idempotency 身份,调用 External v1 `/api/external/v1/editor/images/background-removals` 后只返回有界队列状态。抠图服务仍由客户端和服务端负责源校验、BgFilter、素材登记与画布事务,Codex 不获得内部 worker、凭据或任意 API 调用权。
|
||||
- 抠图通过新增 `agc_remove_background` 语义工具开放:模型只提交当前 manifest 的图片 `sourceLocalAssetId` 与结果名称;客户端解析稳定 `resourceId`,准备同名画布/素材目录并生成稳定 operation/idempotency 身份。普通登录态使用账号鉴权的 `/api/editor/images/background-removals`,ExternalDeveloper 模式使用 External v1 `/api/external/v1/editor/images/background-removals`;客户端接收异步受理后轮询任务状态,下载完成媒体并登记到本地 manifest,未知结果保留同一 operation 供恢复。抠图服务仍由客户端和服务端负责源校验、BgFilter、素材登记与画布事务,Codex 不获得内部 worker、凭据或任意 API 调用权。
|
||||
|
||||
## 2026-08-23 AGC 资源生成补齐(视频 / 动画 / 音效 / 背景音乐)
|
||||
|
||||
@@ -297,8 +537,8 @@ Supervisor 认领该回执后,由父 run 自己为每个原 delivery 逐一创
|
||||
|
||||
## 技术选择
|
||||
|
||||
- 桌面壳:新建 `apps/ai-game-creator-shell`,与现有 `apps/desktop-shell` 分离,避免把游戏创作本地能力塞进主站宿主壳;启动时先检查平台登录态,未登录只展示登录页,登录后进入单窗口客户端首页;正式用户窗口常驻左侧栏和顶部栏,并在首页、项目组、指南 / 反馈和项目开发页之间切换。发布和 debug 启动都只登记并打开 `client` 用户窗口;`index.html?agent-chat` 仅保留为显式前端调试路由,不是 Tauri 自动启动入口。
|
||||
- 窗口外壳:`client`、动态 `main` / `launcher` / `supervisor-chat` 窗口统一关闭原生 decorations,由前端 `WindowChrome` 绘制陶泥儿品牌 Logo、当前页面 / 项目标题和最小化 / 最大化 / 关闭控制。标题栏不重复展示项目列表或本地工作区入口:首页及非项目页面的居中标题固定为“创作工作台”,打开项目后切换为当前项目名;泥点账户入口通过标题栏右侧插槽渲染,标题文本使用独立的窗口几何居中层,不参与左右入口宽度分配,项目名过长时仅在可用宽度内省略。标题栏只复用 `packages/shared/src/theme.css` 的暖陶土变量与现有产品 IP,不引入另一套主题;浏览器预览或非 Tauri 宿主中窗口控制安全降级,不能阻断页面渲染。
|
||||
- 桌面壳:新建 `apps/ai-game-creator-shell`,与现有 `apps/desktop-shell` 分离,避免把游戏创作本地能力塞进主站宿主壳;启动时先检查平台登录态,未登录只展示登录页,登录后进入单窗口客户端首页;正式用户窗口常驻左侧栏和顶部栏,并在首页、项目组、指南 / 反馈和项目开发页之间切换。发布和 debug 启动都只登记并打开 `client` 用户窗口。
|
||||
- 窗口外壳:`client`、动态 `main` / `launcher` 窗口统一关闭原生 decorations,由前端 `WindowChrome` 绘制陶泥儿品牌 Logo、当前页面 / 项目标题和最小化 / 最大化 / 关闭控制。标题栏不重复展示项目列表或本地工作区入口:首页及非项目页面的居中标题固定为“创作工作台”,打开项目后切换为当前项目名;泥点账户入口通过标题栏右侧插槽渲染,标题文本使用独立的窗口几何居中层,不参与左右入口宽度分配,项目名过长时仅在可用宽度内省略。标题栏只复用 `packages/shared/src/theme.css` 的暖陶土变量与现有产品 IP,不引入另一套主题;浏览器预览或非 Tauri 宿主中窗口控制安全降级,不能阻断页面渲染。
|
||||
- 平台后端:继续使用 `server-rs + Axum + SpacetimeDB`;本地开发启动独立客户端时,`agc` / Tauri dev 会先启动或复用配套 SpacetimeDB 与 `api-server`,再启动固定端口 Vite,并通过 `/api` 代理访问实际后端端口。
|
||||
- 本地能力:使用 Tauri Rust command;正式用户 App 在项目运行工作台内承载 `127.0.0.1` 本地 HTTP preview,不再调用系统外部浏览器。运行容器只接受当前授权项目由 `PreviewRegistry` 返回的 loopback URL,release / dev CSP 都只为 `http://127.0.0.1:*` 开放 `frame-src`,并使用受限 iframe sandbox 隔离游戏脚本;远程 URL、`file://` 和任意手填地址均不得进入该容器。
|
||||
- Agent Runtime:扩展 `server-rs/crates/platform-agent`,不引入 LangChain、AutoGen、Microsoft Agent Framework 或 OpenAI Agents SDK sidecar 作为核心。
|
||||
@@ -308,13 +548,6 @@ Supervisor 认领该回执后,由父 run 自己为每个原 delivery 逐一创
|
||||
- 代码组织:桌面客户端入口保持为薄组合层。前端把认证、Tauri 桥接、Runtime 配置、Agent Runtime 展示和项目摘要分别放入 `src/app`、`src/services` 与 `src/features`;Rust 项目能力和测试按功能域使用目录模块;界面测试与真实 Runtime E2E 使用薄 suite registry / entry 保留原执行顺序。后续拆分必须保持公开导出、命令契约、测试名称和行为不变,不能用 `include!`、整文件文本拼接或只移动到另一个超大文件代替真实模块边界。
|
||||
- 源码门禁:`check:native-shells` 等源码扫描必须跟随真实模块归属;入口组合层只验证受控组件的挂载关系,具体实现由所属模块单独验证。模块拆分后不得为了满足旧字符串扫描把实现搬回 `App.tsx`,也不得用跨文件文本拼接代替组件归属检查。
|
||||
|
||||
## 开发态 Project Supervisor 纯聊天独立窗口
|
||||
|
||||
- 入口边界:当前仅 Tauri dev 提供独立窗口,路由为 `index.html?supervisor-chat&projectPath=...`,其中 `projectPath` 传入 URL 编码后的项目绝对路径。该窗口仅通过显式开发调试动作打开,不增加正式用户入口,也不随 `npm run agc` 自动弹出;重复打开同一项目只恢复并聚焦原窗口,切换项目时在同一窗口导航。
|
||||
- Runtime 边界:窗口固定对话 Agent 为 `project-supervisor`,新 Run 使用 `standard` profile,复用现有 active Session、External Runner、Agent Runtime、AppData 配置和持久 conversation;不新建平行会话库、Runner 或配置存储,也不把该测试入口隐式切成自动修改项目的自主构建模式。
|
||||
- 界面边界:只显示持久消息区、输入框、必要的等待 / 错误状态、工具确认 / 用户追问卡片和设置入口;不显示 Agent picker、Session 面板、Goal 面板、完整 Runtime 面板或专业 Agent 协作栏。会话历史仍绑定 `project-supervisor` 的 active Session 持久化,不因隐藏 Session 控制面而变为临时聊天;`supervisor-chat` 窗口必须具备只读 Tauri event listen / unlisten capability,以实时接收 Runtime 更新。
|
||||
- 产品边界:正式用户 `client` 窗口、登录后首页和项目开发流程保持不变,不暴露该开发验证面。
|
||||
|
||||
## 2026-07-29 “游戏运行 + 聊天”独立构建入口
|
||||
|
||||
- Windows AppData 安全迁移:首次创建客户端 AppData 时必须以进程 `TokenUser` SID 显式设置 owner,并写入当前用户私有 DACL,不能把可能为 Administrators 的 `TokenOwner` 当作用户身份。发现历史目录 owner 不属于当前 `TokenUser` 时,不在原目录上放宽权限,而是拒绝 reparse point / junction / symlink 后,将旧目录原子重命名到同级唯一 `.owner-mismatch-backup-*` 备份,再新建并验证当前用户 owner 与私有 DACL;迁移或备份失败必须失败关闭,不覆盖旧配置。
|
||||
@@ -332,7 +565,7 @@ Supervisor 认领该回执后,由父 run 自己为每个原 delivery 逐一创
|
||||
- 固定试玩契约:`generic-v1` 初始状态必须为 `ready` 且 `level > 0`;点击 start 后 sequence 必须推进、phase 必须进入 `playing`,并先持续观察 2 秒、取得至少 8 个实际样本,期间保持 `playing`,以确认玩家获得正常操作机会。随后必须点击唯一可见、启用且真实可交互的 `data-playtest-id="primary-action"` 控件;该控件必须映射游戏的真实主要玩法操作,并以 sequence 相对点击前严格推进证明操作已被接受。玩家获得这次正常操作机会之前进入 `won | lost` 属于过早结束并失败;操作被接受后的单次 `lost` 是合法游戏结局,但不能成为所有受控尝试的唯一结果;若主要操作后仍为 `playing`,则继续观察 3 秒并取得至少 12 个实际样本,`won` 可提前证明非失败推进。点击 restart 后 sequence 必须再次推进并恢复到 `ready | playing`,随后持续观察 3 秒且取得至少 12 个实际样本。若首轮结果为 `lost`,重开稳定后必须再执行一次必要的 start、2 秒 / 8 样本操作机会和真实 primary-action;第二次必须进入或保持 `playing`(再观察 3 秒 / 12 样本且不得转为 `lost`)或进入 `won`,两次都固定 `lost` 代表无法正常推进的恶性 bug,必须失败。各观察窗口内 sequence 不得回退,restart 窗口只能保持 `ready | playing`;样本数门槛不能替代时长门槛,窗口末端必须强制再读取一次有效状态,不能只在前段快速取得足够样本后提前通过。控件 selector、观察时长、最少样本数、终态边界、非失败推进、末端覆盖、sequence 单调 / 严格推进规则及完整 required assertions 都进入 scenario fingerprint。读取旧 fingerprint 回执和检查 plan liveness 时,把合同升级造成的 fingerprint 不匹配视为 stale missing,允许同一 run 重新执行 `preview.validate` 自愈;身份、路径、digest 或内容完整性篡改仍失败关闭。最终完成门每次按当前合同重算 fingerprint,并严格拒绝旧 fingerprint、旧 assertion 集或仅保存历史 `passed=true` 的证据。
|
||||
- 一次性自动预览授权:用户在该入口成功提交本轮自主生成需求,即视为对“当前项目 + 当前 Supervisor 父 run”的一次 `preview.start` 授权。授权以仅含项目路径与 accepted parent runId 的客户端本地记录持久化,App / WebView 重启后仍可恢复,但项目或 run 身份不匹配时不得使用。只有当前 accepted parent run 成功完成 `preview.validate` 且给出有效 revision 后,客户端才可消费授权,由 Tauri 首次启动并自动展示该 revision 的用户可见预览;一次授权最多成功启动一个 Tauri preview server,并必须继续走现有权限、项目写锁、审计和客户端 `PreviewRegistry` 链路。项目或 Agent 策略的显式 deny 始终优先,不得被此授权绕过。启动成功、显式 deny、非瞬时失败、父 run 在首版验证前终止或切换项目后授权失效;`preview.start` 恰逢项目写锁竞争属于瞬时失败,不消费授权,释放写锁后由同一轮询链路重试。
|
||||
- 增量预览刷新:Tauri 客户端记录当前 iframe 已展示的 validated revision;同一当前 run 后续成功 `preview.validate` 的 revision 严格高于已展示 revision 时,只在原 Tauri preview server 和原 loopback origin 上刷新 iframe,不得再次调用 `preview.start`、新增 server 或切换到 Runner registry。相同或更低 revision 不触发刷新。preview HTTP server 对 HTML、脚本、样式、资源和错误响应统一发送 `Cache-Control: no-store`,iframe 刷新必须读取新 revision,不能继续命中 WebView 缓存中的旧版本。自动预览轮询回归的等待上限必须严格大于生产 `1000ms` 轮询间隔,不得使用同为 `1000ms` 的默认上限制造 CI 边界竞争。
|
||||
- 系统边界:该页面是既有 AI 游戏创作工作台的独立构建例外,不新增平台玩法入口、后端 API、会话库、Runner 或预览服务,也不把入口并回普通正式客户端。原 `supervisor-chat` 继续固定使用 `standard` profile 并保持纯聊天行为,不继承本例外的自主构建、事件聚合或自动预览授权。
|
||||
- 系统边界:该页面是既有 AI 游戏创作工作台的独立构建例外,不新增平台玩法入口、后端 API、会话库、Runner 或预览服务,也不把入口并回普通正式客户端,也不继承本例外的自主构建、事件聚合或自动预览授权。
|
||||
|
||||
- 对话输出中的 `eventId + publicText` 只指需要独立进入聊天的进度事件;`turn.started` 和根 Run 终态失败事件由上一条 `runtime-public-status-*` 硬门覆盖,不得同时转成事件消息。专业 Agent child 的失败消息继续留在其 Agent Session,根项目聊天只接收 Supervisor 终态失败、明确公开进度和安全 final-reply,避免一项失败被 Runtime event 与 conversation 各播报一次。
|
||||
- 验证:前端运行时模型定向测试、Rust completion/source/asset 合同测试、`cargo fmt --check`、`npm run check:encoding` 与 `git diff --check` 必须全部执行;Windows 文件锁竞态只可作为既有测试失败单独记录,不得将其改写为本次改动的通过证据。
|
||||
@@ -351,8 +584,14 @@ Supervisor 认领该回执后,由父 run 自己为每个原 delivery 逐一创
|
||||
|
||||
- 模式合同:客户端 AppData 配置新增全局 `agentMode`,只接受 `codex_cli / provider`。缺省和新安装默认使用 `codex_cli`,原有 HTTP LLM Provider 路径完整保留并可显式切回 `provider`;切换只影响下一次节点请求,不新增 Runner、任务图、会话库、配置库或业务事实源。
|
||||
- 调度边界:正式 DAG、manifest、Agent task/session/run 身份、队列、锁、委派、all-join、完成门、Provider lifecycle、持久 retry/handoff 与 `needs-reconciliation` 继续由现有 AGC Runtime 掌控。每个被调度节点在 `codex_cli` 模式下直接启动一次非交互 `codex exec` 充当该节点的推理 Agent;Codex 返回当前 Runtime 广告函数的结构化调用,Runtime 仍是唯一 ToolHost,不允许 CLI 自己写项目、执行命令、调用 MCP 或形成第二套 revision / verification 真相。
|
||||
- 安装包侧车:Windows x64 release 固定随 Tauri resource 打包 `@openai/codex@0.147.0` 的原生 `codex.exe`;Rust build script 从 AGC 子包锁定依赖 stage 到 resource,并写入版本与 SHA-256 清单。Windows 侧车映射只写入 `tauri.windows.conf.json`,通用 `tauri.conf.json` 不得让 Linux / macOS 构建依赖未生成的 Windows 二进制。运行时只在文件摘要和 `codex-cli` 版本同时匹配清单时优先选内置侧车;缺失、损坏或版本漂移时跳过它,按既有 npm 安装、PATH 顺序回退。安装包同时携带 Apache-2.0 第三方声明;API Key、`auth.json`、Cookie、Token、用户 `CODEX_HOME`、用户配置和项目数据绝不打包。
|
||||
- Windows x64 release 安装包只生成 NSIS,不生成 MSI:`tauri.windows.conf.json` 的 `bundle.targets` 固定为 `["nsis"]`,通用配置继续保留其它平台的默认打包目标。安装后的产品名、开始菜单 / 桌面快捷方式和 EXE 产品描述统一由 `tauri.conf.json` 的 `productName: "陶泥儿"` 生成;应用 identifier 与内部可执行文件名保持稳定。内置 Codex 资源安装到顶层 `coding-agent/win-x64/`,运行时从同一路径查找 `bin/codex.exe` 与 `manifest.json`;仓库 staging 仍使用 `resources/codex/win-x64/`,包内子目录、组件名、版本和完整性校验保持原合同。
|
||||
- 安装包侧车:Windows x64 release 固定随 Tauri resource 打包 `@openai/codex@0.155.1` 的原生 `codex.exe`;Rust build script 从 AGC 子包锁定依赖 stage 到 resource,并写入版本与 SHA-256 清单。固定版本只在 `build_support/codex_bundle.rs` 声明一次,构建脚本、宿主补丁执行器身份、逐次审批协议允许列表和模型目录捕获共同引用它,避免多处字面量漂移。Windows 侧车映射只写入 `tauri.windows.conf.json`,通用 `tauri.conf.json` 不得让 Linux / macOS 构建依赖未生成的 Windows 二进制。运行时只在文件摘要和 `codex-cli` 版本同时匹配清单时优先选内置侧车;缺失、损坏或版本漂移时跳过它,按既有 npm 安装、PATH 顺序回退。安装包同时携带 Apache-2.0 第三方声明;API Key、`auth.json`、Cookie、Token、用户 `CODEX_HOME`、用户配置和项目数据绝不打包。
|
||||
- Windows x64 release 安装包只生成 NSIS,不生成 MSI:`tauri.windows.conf.json` 的 `bundle.targets` 固定为 `["nsis"]`,通用配置继续保留其它平台的默认打包目标。安装后的产品名、开始菜单 / 桌面快捷方式和 EXE 产品描述由 `tauri.conf.json` 的 `productName` 生成:基线是 `陶泥儿`,发布构建按渠道由 `--config` 覆盖为 `陶泥儿 <渠道显示名>`,identifier 同批派生 `<基线>.<渠道>`(默认渠道保持基线值),因此不同渠道的包体可在同一台设备并存;内部可执行文件名保持稳定。内置 Codex 资源安装到顶层 `coding-agent/win-x64/`,运行时从同一路径查找 `bin/codex.exe` 与 `manifest.json`;仓库 staging 仍使用 `resources/codex/win-x64/`,包内子目录、组件名、版本和完整性校验保持原合同。
|
||||
- Windows 渠道 AppData 目录归属判断及其调用分支统一保留 Windows 条件编译;通用项目路径策略测试仍可在 Linux 运行,实际 ACL 修复保持原平台门禁与授权范围。
|
||||
- macOS 安装包必须携带锁定版本的原生 Codex、`codex-code-mode-host`、`rg`、上游 zsh、`codex-package.json` 和第三方声明,保留上游相对布局;构建时按 Cargo 目标选择 npm 原生依赖,缺文件、版本或目标不匹配立即失败,不借用开发机 PATH 里的 Codex。资源只在 `tauri.macos.conf.json` 映射到 `Contents/Resources/coding-agent/mac-native/darwin-arm64/` 与 `darwin-x64/`。构建与运行共享平台文件白名单,运行时由当前 `.app/Contents/MacOS` 定位相邻 `Resources`,完整性与版本验证通过后优先使用内置组件;失败沿既有外部安装回退,不能运行未校验的内置文件。macOS 当前只构建 arm64 单架构,但资源映射仍并列携带两套锁定原生 Codex 依赖(运行切片按 Cargo 目标只选择对应目录),恢复 Intel 时无需改动资源布局;随包 Node 只有宿主架构那一份,所以不得构建 universal 包。不读取全局 Codex。
|
||||
- 内置插件的清单、运行入口与面板同时在 Windows/macOS 随包分发,继续由既有 PluginHost 的应用资源目录扫描入口发现;不携带开发依赖、缓存、测试或私有配置。插件文件随包不等于原生适配器跨平台:Cocos 进程桥接仍受现有 Windows 实现和 feature 门禁约束,macOS 原生桥接另行设计与验收,不复制 Windows DLL 冒充支持。系统 Node、用户 Cocos Creator、账号登录、网络和生成工程的 npm 工具链仍是现有外部前提,不在此次 Codex 侧车补齐中隐式变更。
|
||||
- macOS 安装包验收必须包括:脱离仓库位置的 `.app` 资源与架构检查、受限 PATH/隔离 HOME 下内置 Codex 启动和 app-server 握手、必需文件缺失/篡改/平台错误的拒绝测试,以及 DMG 完整性检查。真实登录、Provider 对话、GUI 和 Cocos 操作必须独立列出证据,不能用压缩包生成或 `--version` 成功替代。未配置正式签名、公证的本地测试包不得作为公开发行包。
|
||||
- macOS 安装包的系统下限取主程序和全部原生组件中的最高要求;锁定 Codex 0.155.1 原生依赖中 `codex-resources/zsh/bin/zsh` 的 `LC_BUILD_VERSION` 下限为 macOS 15.0(`bin/codex`、`codex-code-mode-host`、`codex-path/rg` 分别为 11.0 / 10.12),因此 `bundle.macOS.minimumSystemVersion` 明确为 `15.0`。更新原生依赖时重新检查 Mach-O 的系统下限,不能只按 AGC 主程序宣称兼容版本。0.155.1 的 macOS 原生包新增 `codex-resources/voice/`(语音宿主与 GStreamer 动态库);AGC 不启用语音能力,侧车清单只 stage 上述组件,不打包该目录,未来如启用语音需重新评估依赖与许可。
|
||||
- 发布链路的目标解析和 universal 清单以《AGC客户端更新检查与下载》为准:CLI 目标优先,版本、构建、端点、bundle 与更新清单共用单一发布上下文。插件能力以《AGC通用插件宿主与编辑器适配》为准:无已注册 Cocos 原生适配器时隐藏且拒绝启动,前端自动启动只消费后端可用性投影。
|
||||
- CLI 安全边界:CLI 固定使用 argv 启动,禁止 shell 拼接;工作目录使用本次请求专用的空临时目录,不把游戏项目绝对路径写入 prompt、stdout、stderr 或持久记录。调用固定使用 ephemeral、忽略用户配置和 exec rules、read-only sandbox、never approval,并关闭 Codex shell tool;只继承 CLI 运行和认证所需的最小环境,显式移除宿主 `CODEX_API_KEY`。用户级 Codex 登录态继续由本机 Codex 自己读取,API Key、auth 文件、Cookie、Token、`CODEX_HOME` 私有内容不得复制到项目配置、Runtime sidecar、Agent DB、conversation 或日志;stdout / stderr 无换行时也受硬上限约束,stderr 诊断只记录固定分类、字节数和 SHA-256。
|
||||
- 协议边界:Runtime 把既有 `LlmRunRequest` 的消息和当前函数目录编码为有界 prompt,并从同一函数 JSON Schema 生成 Codex structured-output schema。CLI 输出转换为现有 `LlmRunResponse / LlmToolCall` 后,继续经过 native tool / MCP 参数校验、动作上限、权限、pending、receipt、验证与格式修复链;最终回复仍走唯一提交路径,不新增平行响应协议。
|
||||
- 取消与恢复:Codex 子进程绑定当前 Provider request lifecycle,取消、暂停、Runner draining 或 GUI owner 丢失时终止并回收当前进程;started 后没有可信终态仍沿现有 Provider reconciliation 处理。`agentMode`、CLI 可执行身份和影响输出的 Codex 参数进入 `providerConfigFingerprint`,模式切换不得消费另一模式遗留的 retry/handoff。
|
||||
@@ -391,6 +630,8 @@ V1.11 的受保护仓库控制目录同时包含 `.git / .agent / .agents / .cod
|
||||
|
||||
Prompt 静态门禁必须断言上述 Bundle section 当前定义的权威语义与组合关系;身份文案调整后应同步更新旧断言,不得继续依赖已经退出 Bundle 的历史连续措辞,也不得在测试或 Provider builder 中复制一份平行 Prompt。
|
||||
|
||||
Agent 可见的系统指令、工具与参数说明、恢复指引和上下文模板统一由外置提示词文件维护。AGC 沿用 `prompts/runtime/manifest.json`:已有 composition/section 保持原有组合关系,独立调用的文本按职责登记在 `textCatalogs`,目录为 `prompts/runtime/texts/`,每份 JSON 是稳定文本键到正文的映射。构建期校验目录、文件、重复键和空正文,并生成可供 `format!` 使用的编译期文本宏;变量填充沿用 Rust 格式语法。Runtime 状态、用户内容、schema 类型与枚举、权限和校验继续由代码生成。服务端 Agent 的独立 crate 使用各自 `prompts/` 中的编译期文本文件。迁移以当前组装结果和工具 schema 等价为验收依据,源码门禁检查各提示词入口的内联正文与外置引用。
|
||||
|
||||
2026-07-12 起,通用开发能力的 Runtime V1.1 增量以 [`【技术方案】AI游戏创作Agent Runtime V1.1-2026-07-12.md`](<./【技术方案】AI游戏创作Agent Runtime V1.1-2026-07-12.md>) 为编码级事实源。它补充仓库启动上下文、同一发布二进制独立 Runner、受限本地预览浏览器验证、动态隔离子 Agent 和真实 Provider 全链路验收;本文件中“进程内 tokio task”“首轮不预加载项目内容”和“不创建动态执行实例”的旧口径由 V1.1 明确替代,未涉及能力继续沿用本文件。
|
||||
|
||||
同一文档的“V1.2 对标 Codex CLI 增量”继续作为受控命令与推理档位的事实源。对一次性 `command.exec` 而言,只接受 Runtime 白名单内的固定 `program` 和逐项 `args` argv,默认 `confirm`,可执行文件解析为项目外绝对路径且子进程只使用安全 PATH;不解析 shell 字符串,不提供管道、重定向、PTY 或后台进程。这里对 PTY 和后台进程的排除仅适用于 `command.exec`,不能用来否定 V1.10 的独立持久进程工具,也不能把 `command.exec` 自身改成长驻入口。`command.exec` 的 action、stdout / stderr、退出码、超时与源码指纹结果统一进入现有 `action / observation`、project revision、verification gate 和 `needs-reconciliation` 链路;只有明确验证型命令且退出码、源码指纹、命令日志、manifest 与 Agent DB 审计全通过才签发 passed gate,Git / rg / cargo metadata / 普通 npm run 只作诊断。首版只请求终止受控进程组,安全等级与 `project.verify` 相同,不宣称已具备完整 OS sandbox 或 detached-process 隔离。
|
||||
@@ -515,7 +756,7 @@ Agent Runtime 负责:
|
||||
## Agent 能力清单
|
||||
|
||||
- 用户能力:项目开发工作台、陶泥儿聊天、上传文件、manifest 资源视图、客户端内运行表现层和专业 Agent 紧凑状态;正式用户窗口仍不展示原始任务 / 文件 / 日志、开发预览调试面板、能力清单或开发专用单 Agent 聊天入口。
|
||||
- 开发调试能力:`index.html?agent-chat` 可作为显式前端调试路由;开发者可选择 Agent、授权本地项目路径,并通过 `read_local_conversation` / `append_local_conversation_message` 读写 `.agent/conversations/agents/<agentId>.jsonl`,通过 `agentLlm.<agentId>` 调用该 Agent 的独立 LLM 路由做真实对话,用于单独调试某个 Agent 的长期对话上下文。这里的 `<agentId>` 以 manifest taskId 为规范值,旧 `group-role` 别名只作为兼容输入映射到 taskId。
|
||||
- 开发调试能力:无 GUI 调试使用 `npm run agc:chat` / `npm run agc:swarm` 终端入口,复用 External Runner、active Session、conversation 与各 Agent 私有记忆;`<agentId>` 以 manifest taskId 为规范值,旧 `group-role` 别名只作为兼容输入映射到 taskId。
|
||||
- 命令能力:内置命令调用、权限 gate、执行日志;v1 只允许白名单受限命令,不执行任意 shell。
|
||||
- 编排能力:任务拆分、任务图依赖、专业组调度、多智能体协作;Runtime V1 会为单 Agent 对话和生成 loop 中的角色 brief 写入独立 runtime state / event,先解决“每个 Agent 正在做什么、跑到哪一步、最近一次 task/run 是什么”的可观测性。
|
||||
- 历史边界说明:下一条“后台任务能力”保留 V1.1 前的进程内演进记录,其中 App 内 tokio task、进程内 drain、旧工具箱和“不是独立 OS 进程”的描述均已失效;当前执行边界以上文独立 Runner 说明为准。
|
||||
@@ -1092,7 +1333,7 @@ game-project/
|
||||
- 开发模式可执行 `canvas.asset_import`,将项目内已有文件按画板来源导入 manifest;也可执行 `canvas.export_import`,把现有画板素材导出 ZIP 回流为本地项目资产。`game.generate_draft` 在普通模式登录态有效或高级模式凭据有效时复用同一平台图片生成能力,不新增平行资产模型。
|
||||
- 普通模式已在登录后的单窗口项目开发页渲染 GameAgent 工作台、`project-supervisor` 主聊天和专业 Agent 协作只读状态,不提供专业 Agent / child 单 Agent 对话;首页首条需求直接投递 active Supervisor Session,全新项目没有该 Session 时先通过既有 Session 命令创建并设为 active,已有项目恢复该 Session 与历史后继续交互。Tauri 主窗口承载当前授权项目的 loopback 游戏画面,但不承载开发 Agent picker、调度工具台或任意地址预览调试面板。
|
||||
- 聊天生成草案后会尝试启动只读 `127.0.0.1:<port>` 静态 HTTP server,把预览地址回写客户端项目运行工作台并切换到运行视图;不再调用系统外部浏览器。
|
||||
- 开发模式仅在 Vite dev 环境响应 `?dev` 或 `#dev`;`npm run agc` 与 release 都只打开 `client` 用户窗口,显式 `?agent-chat` 与 `supervisor-chat` 调试入口不随启动自动弹出。正式构建忽略 dev 参数,release 配置只登记一个用户窗口,登录后同窗口进入首页并在项目组 / 项目开发页之间切换。
|
||||
- `npm run agc` 与 release 都只打开 `client` 用户窗口。正式构建与 release 配置只登记一个用户窗口,登录后同窗口进入首页并在项目组 / 项目开发页之间切换。
|
||||
- `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 选择器,用于记录任务拆分、专业组、角色模板、依赖、产物、验收条件和当前可执行任务。
|
||||
@@ -1294,12 +1535,12 @@ game-project/
|
||||
|
||||
## 2026-08-20 Direct Codex 审核 Skill Pack 与受控工具内核
|
||||
|
||||
- 普通项目对话只由一个 project-bound Codex app-server thread 执行。客户端系统提示词只放最小工程合同、当前游戏源码有界快照、项目 prompts 和审核 Skill 索引;不再批量读取项目 `.codex/.agents` Skill 正文,也不恢复 Supervisor、专业 Agent 或 harness。
|
||||
- 首页恢复“做游戏 / 做素材 / 做方案”三个创作类型,默认“做游戏”。该选择与设置页的 Agent Runtime 模式无关;每次首页提交仍只自动创建一个新项目并进入项目工作台。用户正文原样进入项目对话,`game|art|doc` 仅作为受限结构化首轮上下文传给同一 Codex thread,不拼接“初始意图”文案、不产生首页对话、不切换 Provider 或恢复旧 Runtime 编排。
|
||||
- `agc-skill-pack.v1` 只包含项目结构、陶泥儿美术、Web 游戏实现、真实浏览器试玩、客户端资源投影五项 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;2026-08-31 起还会在启动时接入客户端扩展仓库中用户已启用的独立第三方 STDIO/HTTP MCP 配置,但不读取用户全局 Codex MCP、不开启完整 Plugin Runtime。内置工具固定为审核引用读取、标准陶泥儿美术准备、已登记资源有界查询、视频 / 角色动画 / 音效 / BGM 的 create-or-derive 语义生成、已登记图片去背景、desktop/mobile 浏览器试玩和受控 `agc_web_search`。内置 MCP 进程只做协议;真实浏览器、付费 External v1 调用与受控搜索通过随机 loopback 地址回到客户端主进程,因此不复制 GUI 登录态、开发者 Key、项目路径、revision、operation 或幂等键到模型上下文。内置与用户启用的第三方 MCP 工具都沿用 DirectProject 自动批准方式,但付费资源工具仍由客户端绑定稳定回合身份、限制单回合请求数、串行执行并优先恢复匹配账本;通用 shell、Codex 原生 webSearch、任意原生命令网络、多 Agent 和完整插件能力继续关闭。`llm.webSearchEnabled` 只控制 DirectProject 的 AGC 受控搜索工具暴露与执行,Codex 原生 `web_search` 始终保持 disabled;Provider、ToolHost、DirectHome 不纳入本次联网主链路。
|
||||
- 陶泥儿生成继续复用持久幂等账本、operation 恢复、来源/下载/PNG 解码和 manifest 登记;普通客户端优先使用当前 AGC 登录会话及账号路由,只有受控的 ExternalDeveloper 发布模式才在客户端内部使用按服务器 origin 隔离的私有 Key。用户和模型都不需要提供或配置 API Key;凭据失效、来源不明或结果未知时失败关闭,不能自动换 Key 或重新扣费。
|
||||
- 自定义 LLM API Key 路由只在 DirectHome/DirectProject 经 loopback `/responses` 流式代理转发。代理不注入 Key,只要求请求自带 Bearer,并剥离开发网关错误携带的 `X-Codex-*` ChatGPT 账户额度头,防止隔离 app-server 把 API Provider 误判为余额 0;旧 ToolHost 保持原 Provider 行为。
|
||||
- 普通项目对话由一个 project-bound Codex app-server thread 执行。客户端系统提示词包含最小工程合同、项目 prompts 和审核 Skill 索引;源码与 Skill 正文按任务需要读取。提示词、工具描述与 Skill 直接描述当前任务、输入和成功条件,细节按调用需要提供。
|
||||
- 首页提供“做游戏 / 做素材 / 做方案”三个创作类型,默认“做游戏”。每次首页提交自动创建一个新项目并进入项目工作台。用户正文原样进入项目对话,`game|art|doc` 作为受限结构化首轮上下文传给同一 Codex thread。
|
||||
- `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 响应判断请求结果。
|
||||
|
||||
- 2026-08-12 计划拒绝恢复:结构化 `runtime.plan_update` 被 Runtime 拒绝后,下一轮 Provider 请求按请求级目录收窄到实际项目 mutation 与 `respond_to_user`(已进入协作编排的 Supervisor 保留 `agent.delegate / agent.run_status`),并明确禁止再次规划、读取、搜索或验证;后续已有真实 mutation observation 后解除临时目录,不改变持久 executable policy。
|
||||
|
||||
@@ -1310,7 +1551,7 @@ game-project/
|
||||
|
||||
## 2026-08-17 UI Editor 项目资源 State 持久化
|
||||
|
||||
- UI 编辑器复用现有 manifest `kind: "UI"`、`mediaType: "application/json"` 资源,不增加平行 asset kind。资源文件固定为严格 `game-creator-ui-design-state.v1` JSON envelope:`projectId`、`assetId`、每资源 `revision` 和 Rust 唯一源 `State`;旧空对象、未知字段、身份错配、超限、无效内部引用和不安全相对路径均失败关闭。内部引用校验同时覆盖 `Image.target_graphic -> sprite_assets` 与 `Text.font -> font_assets`,可选引用非空时必须命中同一 State 内已登记资源。
|
||||
- UI 编辑器使用唯一的 manifest `kind: "ui-design-doc"`、`mediaType: "application/json"` 资源,不接受旧 `UI` / `ui` / `ui-design`,也不增加 fallback 或迁移。资源文件固定为严格 `game-creator-ui-design-state.v1` JSON envelope:`projectId`、`assetId`、每资源 `revision` 和 Rust 唯一源 `State`;旧空对象、未知字段、身份错配、超限、无效内部引用和不安全相对路径均失败关闭。内部引用校验同时覆盖 `Image.target_graphic -> sprite_assets` 与 `Text.font -> font_assets`,可选引用非空时必须命中同一 State 内已登记资源。
|
||||
- Tauri 专用 load/save command 只接受项目路径、期望项目 ID、manifest asset ID 和(保存时)资源 revision;Rust 按 manifest 解析受控本地路径并在项目写锁内做 CAS。相同 `State` 返回 unchanged 且不推进 project revision;不同内容安装并回读一致后才推进 revision,后续推进失败返回 `reconciliation-required`,不伪装为完整保存。
|
||||
- UI 编辑器代码导出仅写入用户项目目录下的 `ui/generated-*.js` 派生文件,绝不推进项目 revision、UI State revision、manifest 阶段或 Runtime 验证门;写入失败只返回生成错误,不得把生成文件写入冒充为项目 mutation。
|
||||
- UI State 原子安装保留最近一个可解析、canonical 的 `.previous` 恢复候选,作为最佳努力恢复来源;写入主文件前不把完整 State 语义校验重复执行一遍。主文件损坏时,恢复候选仍必须通过同一严格 schema、project/asset identity、revision、引用和 State 校验后才能安装;恢复安装与保存共用项目写锁,并在持锁后重新读取主文件,已有并发保存的有效新版本时直接返回而不安装旧副本。任一候选均不可信则停在加载错误,前端禁编辑和保存。新建 UI 资源先登记并安装合法 envelope,任一步失败补偿 manifest/文件,避免把空 JSON 留给资源卡。
|
||||
@@ -1360,13 +1601,13 @@ game-project/
|
||||
|
||||
## DirectProject 工具权限现行覆盖(2026-08-24)
|
||||
|
||||
本文早期关于“DirectProject 关闭通用 shell、原生网络和主动工具”的描述属于迁移前基线;2026-09-14 起,DirectProject 的 Codex sandbox 与审批规则由下方“完整访问覆盖”取代。其余 ToolHost/DirectHome 合同不变。客户端审核的 `agc_tools` MCP 继续承担平台美术、资源登记、去背景、浏览器试玩和受控搜索,并保留项目锁、幂等账本、下载校验、恢复与投影权威。
|
||||
DirectProject 的 Codex sandbox 与审批规则以下方“完整访问覆盖”为准。客户端审核的 `agc_tools` MCP 承担平台美术、资源登记、去背景、浏览器试玩和受控搜索,并持有项目锁、幂等账本、下载校验、恢复与投影权威。
|
||||
|
||||
## DirectProject Codex 完整访问覆盖(2026-09-14)
|
||||
|
||||
DirectProject 现明确采用 Codex app-server 的 `danger-full-access` sandbox:thread 使用 `sandbox="danger-full-access"`,turn 使用 `sandboxPolicy.type="dangerFullAccess"`,不再发送 `workspaceWrite`、`writableRoots` 或项目根文件批准白名单。DirectProject 收到 app-server 的文件变更、命令执行和权限请求时直接接受,Codex 原生能力不再按项目路径做二次白名单裁剪;用户选择的项目目录仍作为 cwd 和 AGC 业务身份根,用于连接池、审计与客户端受控 MCP 的项目绑定。
|
||||
DirectProject 采用 Codex app-server 的 `danger-full-access` sandbox:thread 使用 `sandbox="danger-full-access"`,turn 使用 `sandboxPolicy.type="dangerFullAccess"`。DirectProject 收到 app-server 的文件变更、命令执行和权限请求时直接接受;用户选择的项目目录作为 cwd 和 AGC 业务身份根,用于连接池、审计与客户端受控 MCP 的项目绑定。
|
||||
|
||||
这项覆盖只改变 Codex 原生 app-server 的 sandbox 与审批边界:首页只读对话、AGC `agc_tools` MCP 的业务授权、Provider 凭据隔离、Runtime 审计与客户端 `agc_write_file` 的产品契约继续有效。系统提示词不再把 `.agent/`、`.git/`、项目外路径等描述为 Codex 原生能力禁区,但仍要求不要把 Token、Cookie、auth.json、`.env` 或 Runtime 私有控制面主动输出到对话、工具参数和日志。
|
||||
首页按只读对话执行;AGC `agc_tools` MCP 按业务授权执行,Provider 凭据保持隔离,Runtime 审计与客户端 `agc_write_file` 遵守各自产品契约。Token、Cookie、auth.json、`.env` 或 Runtime 私有控制面禁止主动输出到对话、工具参数和日志。
|
||||
|
||||
DirectProject 使用 `approvalPolicy=never`,避免每次原生调用再经过泛化 ToolHost 包装;原生命令网络随完整 sandbox 开放;联网资料仍可走受控 `agc_web_search`。多 Agent、Apps、完整插件 Runtime、hooks、Goals、Workspace Dependencies、Tool Suggestion 和原生浏览器/电脑控制仍关闭,避免绕过 AGC durable delegation、浏览器证据和副作用审计;图片生成通过客户端审核的 `agc_tools.agc_generate_image` 暴露普通单图、角色图、视觉规范图和 UI 设计图,完整游戏美术包继续使用 `agc_tools.taonier_prepare_game_art`,两者都复用同一客户端登录态、幂等账本、下载校验和 manifest/revision 投影,不开放 Codex 原生 image tool。app-server 使用隔离 `CODEX_HOME`:内置 `agc_tools` 由客户端启动参数注入,用户在客户端扩展列表启用的独立第三方 MCP 以原生配置写入该次隔离 home;全局 Codex MCP、禁用项、Plugin hooks/apps 和其它插件能力不进入 DirectProject。第三方项固定非 required,配置或启动失败只记录该项,不替换 `agc_tools`;provider session token、工具桥地址和受控搜索标记不得通过第三方 MCP 的环境转发字段泄露。配置了 AGC LLM Key 或可解析的 `OPENAI_API_KEY` 登录态时,真实 provider 凭据只由 AGC 本地 provider proxy 持有,Codex 仅使用连接级随机代理令牌;无法安全代理的 OAuth `auth.json` 继续关闭 native shell/unified exec。`agc_tools` 的平台授权由 AGC 客户端当前登录会话和受控后端完成,普通客户端不得把 DirectProject 请求改成外部 API Key 请求;401/403 只投影为客户端登录或权限异常,不向用户索要凭据或暴露内部 URL。shell 子进程采用 `shell_environment_policy` core 继承及 secret/proxy/bridge 排除,provider key 和桥接凭据不得进入命令环境。系统提示词不再预注入项目源码快照或 Skill 正文,Codex 按需读取当前 cwd 文件。
|
||||
## 2026-08-24 AGC UI 原型桥接与自主 UI workflow
|
||||
@@ -1453,7 +1694,7 @@ DirectProject 使用 `approvalPolicy=never`,避免每次原生调用再经过
|
||||
- **命令**:`read_local_project_version_resource_replacement_candidates`(只读,`asset.list`)与 `replace_local_project_version_resource`(写,`asset.register`),实现在 `src-tauri/src/project/version_resource_replacement.rs`。入参都是单个 `input` 对象、`deny_unknown_fields`:读 `{projectPath, sourceVersionId, sourceResourceId}`,写额外要求 `expectedProjectId + expectedProjectRevision + replacementResourceId`;出参 `{versionId, committedProjectRevision, replacement:{versionId, sourceResourceId, replacementResourceId, compatibility, warning}}`(**没有** `parentVersionId`,因为没有新版本)。这两个 DTO 是 Tauri 本地 DTO,**不进跨端契约**。
|
||||
- **写入语义**:持项目写锁(与删除 / 重命名 / 标签同一把)→ 锁外与锁内各复核一次 `projectId`,锁内复核 durable revision(CAS 失败报 `project-identity-conflict` / `project-revision-conflict` 且零写入)→ 走上面的绑定改写通道,放行集合固定为 `[sourceVersionId]` → 成功后推进一次项目 revision(改绑定属于 `versions` 变化,跨面快照门禁要求 revision 前进)→ 追加一条 `asset.version_binding.replace` 审计(复用既有 `append_agent_db_record`)。审计写失败会报错但不回滚,与 `asset.register` 同口径。
|
||||
- **绑定改写语义**(恒等绑定口径的硬约束):`resourceBindings` 是"该版本使用的素材集合"而不是槽位表,因此改写 = **源素材从集合里消失 + 保证替换素材在集合里**;替换素材是源版本创建之后才登记时按源素材原来的位置插回(顺序稳定),早已登记时只做摘除 —— 不能把源素材那条槽位改写成替换素材,那会撞「资源槽位重复」。
|
||||
- **准入与提示(后端权威,前端只呈现)**:**硬门禁**只有 `categoryEqual`(用 PRD §5.3 的**读时自愈**口径,Rust 侧 `game_creation_app_asset_category_with_read_time_healing` 与 `packages/shared` 的 `gameCreationAppAssetCategory` 逐分支一致并配用例锁定)与 `subtypeEqual`(canonical `kind`);`sizeSpecEqual` **降级为提示**(「格式与源素材不同」)不再拒绝 —— 它的完整判据今天不存在(manifest 没有 `width / height / durationMs`,现役写入侧几乎全写 `imageSequenceFrames: None`,实际只等于"媒体格式相等"),硬拦会误拒 `png ↔ webp` 这类最常见需求;要变成硬判据须先给 manifest asset 增尺寸字段并在写入侧回填(跨端契约变更)。
|
||||
- **准入与提示(后端权威,前端只呈现)**:**硬门禁**只有 `categoryEqual`(用 PRD §5.3 的**读时自愈**口径,Rust 侧 `game_creation_app_asset_effective_category` 与 `packages/shared` 的 `gameCreationAppAssetCategory` 逐分支一致并配用例锁定)与 `subtypeEqual`(canonical `kind`);`sizeSpecEqual` **降级为提示**(「格式与源素材不同」)不再拒绝 —— 它的完整判据今天不存在(manifest 没有 `width / height / durationMs`,现役写入侧几乎全写 `imageSequenceFrames: None`,实际只等于"媒体格式相等"),硬拦会误拒 `png ↔ webp` 这类最常见需求;要变成硬判据须先给 manifest asset 增尺寸字段并在写入侧回填(跨端契约变更)。
|
||||
- **入口**:资源卡选中工具条的宿主 `extraActions` 新增「替换素材」,判据复用现役 `isResourceUsedByCurrentVersion`(manifest 身份 + 被当前版本绑定),未绑定素材不渲染入口;不动 `ImageCanvasSelectedLayerToolbarAction` 共享 union、不改 `resourceCanvasToolbarModel` 的 `supportedActions`。候选弹窗复用 `ImageCanvasProjectAssetPickerDialog`(该弹窗在 AGC 侧首次使用),以可选 prop 扩展:`singleSelect` / `assetBlockedReasons` / `assetHints` / `renderAssetMedia` / `selectionNoun` / `errorMessage`,**默认值保持网页端美术画布行为逐字不变**。候选行渲染类型占位而不挂 `<img>`(AGC 的预览要经带 scope 的原生调度器拿 Blob URL,弹窗内没有同步 `src`)。
|
||||
- **成功后行为**:重读 manifest,**不切换版本**(没有新版本可切),**不自动重载 / 重启运行中的预览**(PRD §3.2 末条),不做运行时资源重映射。可见变化只有资源卡「当前使用」高亮移到替换素材、`@` 面板「当前版本素材」更新。
|
||||
- **已知代价(用户已确认接受)**:**替换历史不可回溯**——替换前身份只剩那条审计与 manifest 的 `.previous` 副本;需要"某版本历史上换过什么"时要另立切片(PRD §3.2 / §5.3 保留为未来合同正是为此)。
|
||||
@@ -1539,23 +1780,23 @@ Direct 回合的所有权属于进程内项目身份锁,不属于当前页面
|
||||
### 目标与非目标
|
||||
|
||||
- 目标:AGC 在项目工作区打开期间按固定周期把用户项目增量上传到 OSS `agc-dev`,并在项目关闭时立即补一次同步;重复内容不重复上传,远端占用跟随当前清单收敛。
|
||||
- 非目标:不做云端下载/恢复、不做跨设备合并、不保留多版本历史、不新增面向用户的上传界面、不修改 `/api/external/v1` 与 OpenAPI、不新增 SpacetimeDB 表。
|
||||
- 非目标:不做客户端云端恢复、不做跨设备合并、不保留多版本历史、不新增面向用户的上传界面、不修改 `/api/external/v1` 与 OpenAPI、不新增 SpacetimeDB 表。
|
||||
- 非目标:不把 OSS AccessKey 放进客户端;客户端不直连 OSS。
|
||||
|
||||
### 参与入口、状态与跨模块边界
|
||||
|
||||
- 触发入口有两个:工作区窗口 `main` 存活期间的周期定时器、工作区窗口关闭事件(`CloseRequested`)。两者共用同一个进程内同步器,同一项目的同步串行执行,周期触发在已有同步进行时直接让位,不排队堆积。
|
||||
- 应用退出(`RunEvent::Exit`)不重复发起同步:该时刻窗口已销毁,按窗口重新枚举项目只会得到空集;退出路径只负责在有界预算(15 秒)内等待在途同步收尾,让关窗触发的那一次同步能写完索引再退出。
|
||||
- 触发入口为项目生命周期登记、已登记项目的周期定时器及窗口关闭事件(`CloseRequested`)。它们共用同一个进程内同步器,同一项目的同步串行执行,周期触发在已有同步进行时直接让位,不排队堆积。
|
||||
- 应用退出(`RunEvent::Exit`)不重复发起同步;退出路径只负责在有界预算(15 秒)内等待在途同步收尾,让关窗触发的那一次同步有机会写完索引再退出。超过预算不能声明最后状态已经上传。
|
||||
- 客户端扫描、差异对比、索引持久化与上传编排都在 Tauri Rust 进程(`src-tauri/src/project_snapshot/`);WebView 只读状态,不参与差异计算。
|
||||
- 本地索引是增量对比的唯一依据:`<AppData>/project-snapshots/<projectId>/index.json` 保存上次成功同步的相对路径、校验和、字节数和修改时间。项目根使用现有 manifest 的稳定 `project_id` 作为远端身份,路径不再作为身份。
|
||||
- 可观测性按产品口径收敛到本机日志:同步结果、失败分类、延后与跳过计数只写入 AppData 诊断日志(`project_snapshot.sync.*` 前缀),客户端界面不暴露上传状态、时间线或入口按钮。`read_local_project_snapshot_state` 与 `sync_local_project_snapshot` 两条命令仅作为 native-only 的排障与联调入口登记,不在渲染层调用。
|
||||
- 远端写入经 `api-server`,客户端只持平台登录态 Access Token。两条登录态路由:`POST /api/agc/project-snapshots/files`(单文件,正文为原始字节,元数据走查询串)与 `POST /api/agc/project-snapshots/manifest`(本次同步后的完整清单)。
|
||||
- 对象键与清单由服务端决定:文件键为 `agc/project-snapshots/v1/{userId}/{projectId}/files/{sizeBytes}-{checksumDigest}/{relPath}`,清单键为 `agc/project-snapshots/v1/{userId}/{projectId}/manifest.json`。键里带字节数与摘要,因此"对象已存在且长度一致"可以作为内容一致的判据;路径按原始大小写保留,不走 `put_object` 的低位规范化。`agc` 前缀继续是服务端专用私有前缀,通用对象键解析与客户端直传票据都不覆盖它。
|
||||
- 目标 bucket 使用独立配置 `GENARRATIVE_AGC_PROJECT_SNAPSHOT_OSS_BUCKET` / `_ENDPOINT` / `_ACCESS_KEY_ID` / `_ACCESS_KEY_SECRET`,默认 `agc-dev` + `oss-rg-china-mainland.aliyuncs.com`,未配置时回退 `ALIYUN_OSS_*`;与"资源 bucket 与备份 bucket 分离"的既有口径一致。
|
||||
- 对象键与清单由服务端决定:文件键为 `agc/project-snapshots/v2/{channel}/{userId}/{projectId}/files/{sizeBytes}-{checksumDigest}/{relPath}`,清单键为 `agc/project-snapshots/v2/{channel}/{userId}/{projectId}/manifest.json`;`channel` 是本部署渠道(`GENARRATIVE_AGC_PROJECT_SNAPSHOT_CHANNEL`,缺省沿用 `GENARRATIVE_CLIENT_DOWNLOAD_CHANNEL`),同一 bucket 因此天然按渠道分区,开发与正式部署互不可见对方项目。键里带字节数与摘要,因此"对象已存在且长度一致"可以作为内容一致的判据;路径按原始大小写保留,不走 `put_object` 的低位规范化。`agc` 前缀(含历史无渠道的 `agc/project-snapshots/v1/`)继续是服务端专用私有前缀,通用对象键解析与客户端直传票据都不覆盖它。后台“项目工程”按渠道查询与下载,渠道名非法时失败关闭,历史 v1 对象不再列出。
|
||||
- 目标 bucket 使用独立配置 `GENARRATIVE_AGC_PROJECT_SNAPSHOT_OSS_BUCKET` / `_ENDPOINT` / `_ACCESS_KEY_ID` / `_ACCESS_KEY_SECRET`,默认 `agc-dev` + `oss-rg-china-mainland.aliyuncs.com`。只允许凭据回退 `ALIYUN_OSS_ACCESS_KEY_ID` / `_ACCESS_KEY_SECRET`;bucket 与 endpoint 不跟随资源存储的 `ALIYUN_OSS_BUCKET` / `_ENDPOINT`,避免默认写入其它 bucket。显式快照目标配置继续优先;不自动搬迁其它 bucket 的现存数据。
|
||||
|
||||
### 正常、失败、重试与幂等行为
|
||||
|
||||
- 差异对比口径:先按 `相对路径 + 字节数 + 修改时间` 判定是否候选变更,命中旧记录则复用已存 `sha256`,只有 `(size, mtime)` 变化才重算摘要。产出新增、修改、删除三类集合,只上传新增与修改的文件。
|
||||
- 差异对比口径:先按 `相对路径 + 字节数 + 修改时间` 判定是否候选变更,命中旧记录则复用已存 `fnv1a64`,只有 `(size, mtime)` 变化才重算摘要。产出新增、修改、删除三类集合,只上传新增与修改的文件;清单元数据变化单独提交。
|
||||
- 每次成功同步的最后一步上传该项目的 `manifest.json`(当前全量文件清单:相对路径、摘要、字节数、同步序号)。清单描述的是项目当前全量内容,因此清单体积就是该项目在 OSS 上的常驻占用。
|
||||
- 远端回收:清单写入成功后,服务端读取上一版清单,按 `(路径, 字节数, 摘要)` 反推出不再被当前清单引用的对象键并删除。只处理上一版清单登记过的键,不做 LIST,因此不可能误删其它项目或其它功能的对象;单次最多回收 2000 个对象,剩余部分留到下一次清单写入继续;上一版清单读不到或解析失败时整轮跳过回收(fail-closed)。单个删除失败只记日志,不影响本次同步语义。
|
||||
- 因此本功能是"当前状态镜像 + 清单",不保留历史版本:同一路径的内容变化会覆盖式替换远端对象,回滚能力不在本轮范围内。
|
||||
@@ -1579,6 +1820,18 @@ Direct 回合的所有权属于进程内项目身份锁,不属于当前页面
|
||||
- 运行时 smoke:AGC 开发态打开项目、观察索引写入与同步日志、关闭工作区窗口后确认关闭触发的那次同步执行;报告为"客户端 diff 已验证 / 服务端已配置环境联调"两层,不合并成一句"已通"。
|
||||
- 边界:新增日志与错误文案不含 Access Token、AccessKey、绝对路径与项目内容。
|
||||
|
||||
### 后台工程列表与下载
|
||||
|
||||
- 正式客户端在单窗口内切换启动器和项目,不以窗口 URL 判断活动项目。前端把当前窗口的已打开项目登记给原生同步器;打开后发起首轮同步,离开/切换项目和物理关窗为旧项目补同步,周期扫描只读这份窗口登记。重复登记同一路径不重复发起;注册失败写诊断,不能假装已登记。
|
||||
- 清单补充可选 `projectName` 与 `pendingFiles`:名称来自本地 manifest;`pendingFiles` 是本轮失败、延后、并发变动与非策略排除的跳过文件数量。`0` 表示扫描范围已同步;大文件等被跳过不能标成完整。旧清单字段缺失表示完整性未知,维持可读取兼容,不反写旧清单。
|
||||
- 项目名称或完整性发生变化时,即使文件内容没有差异也要提交新清单;本机索引记录上次已提交的这两个字段。实际客户端下一次正常同步可补齐历史清单元数据;后台只读访问不迁移旧清单。缺失 `pendingFiles` 不能默认成 0,临时跳过原因消失后允许无文件上传的 `partial → ready` 转换。
|
||||
- 后台增加“项目工程”入口,仅 owner 及拥有 `project-snapshots` 页签权限的管理员可访问。`GET /admin/api/project-snapshots?cursor=&limit=20` 读取私有 OSS 清单并返回 `{items,nextCursor}`;单页最多 100 个,游标由服务端校验,目录与清单读取有界。条目为 `{userId,projectId,projectName,syncRevision,syncedAtMs,fileCount,totalBytes,status}`,状态为 `ready / partial / unverified`,名称缺失时显示 projectId。
|
||||
- `GET /admin/api/project-snapshots/{userId}/{projectId}/download` 只读取该用户/项目的固定清单与其引用对象,返回 `application/zip` 附件。ZIP 中路径直接使用原始相对路径,不包含 userId、摘要目录或 OSS 前缀;名称使用经过安全处理的项目名和 revision。下载固定本次读到的清单,远端并发回收导致对象缺失则整体失败,不能静默遗漏。
|
||||
- `partial` 快照下载返回 409;`unverified` 历史快照可导出已同步文件,列表明确显示“完整性未知”,动作称“下载已存文件”。`ready` 才显示“下载完整工程”。ZIP 构建核验每一文件的长度与 fnv1a64 摘要,拒绝穿越、绝对路径、重复/大小写冲突路径、非法项目身份;缺失或损坏整体失败,不返回成功的残缺 ZIP。
|
||||
- ZIP 使用服务端临时文件并限制并发,不将 2 GiB 工程整体驻留内存;成功、失败、客户端取消均清理临时文件。单文件、总量、文件数沿用上传上限,超限明确拒绝。零字节工程文件可以上传和导出。OSS 凭据与签名不下发浏览器,列表失败保留错误而非伪造空列表。
|
||||
- 工程归档范围与上传策略一致:源码、素材及引擎配置按原路径保存,依赖/构建缓存、凭据、`.agent` 会话与运行日志排除;此 ZIP 不声明能恢复 AGC 对话历史。后台读取需要目标前缀的 ListObjects 与 GetObject 权限,不新增数据库表,不改 External API。
|
||||
- 验收覆盖真实单窗口生命周期登记、首次/增量/零字节上传、后台列表分页和权限、ZIP 解压目录及摘要、部分/历史清单、缺失对象、路径拒绝、下载取消清理,并分别报告定向测试和真实环境证据。
|
||||
|
||||
### 未决问题
|
||||
|
||||
- 用户侧看不到同步状态与失败原因(界面按产品口径不暴露),排障只能读 AppData 诊断日志或调用 native-only 命令;如果后续要支持用户自助排查,需要先确认是否允许在客户端出现上传相关 UI。
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# DirectProject Codex 原始历史与异常恢复
|
||||
|
||||
更新时间:`2026-09-15`
|
||||
更新时间:`2026-09-16`
|
||||
|
||||
## 目标
|
||||
|
||||
@@ -18,7 +18,7 @@ DirectProject 只使用 `.agent/conversations/project.jsonl` 作为对话历史
|
||||
|
||||
`project.jsonl` 的 `payload` 必须是未经改写的 Responses item。AGC 前端 user input 先以 canonical user message item 形式写入;发送给 app-server 前由 Rust 投影为 Codex 可接受的 `message` item,AGC 私有 content part 不会穿透到 wire。Codex 返回的 `rawResponseItem/completed.params.item` 原样追加。native 工具、MCP 工具、reasoning、调用参数和调用结果都保留完整内容,不截断、不摘要、不保存运行态 delta/started 事件。
|
||||
|
||||
Thread Manager 的运行态事件是另一份内存协议:app-server 通知先经过安全投影,再只发送 item 类型、item ID、delta 文本和 turn 终态等必要字段;不得把完整 item、工具参数或调用结果转发到前端。完整 item 仍只通过上述 JSONL 历史读取。
|
||||
Thread Manager 的运行态事件是另一份内存协议:app-server 通知先经过安全投影(挑字段、脱敏、截断、路径归一),再按与历史切片同形的脱敏原始条目(`itemType`、唯一 `itemId`、正文或工具明细)加 delta 文本与 turn 终态下发;搬运层不生成卡片形状,也不把未经脱敏的完整 item 转发到前端。注入 Codex 用的完整 item 仍只从上述 JSONL 历史读取。
|
||||
|
||||
DirectProject 自己的写侧只写新格式:格式切换(#282)时仍会写旧行的路径已收口——显式 Codex 返回只落在自己的 journal `.agent/conversations/codex-responses.jsonl`,不再投影进 `project.jsonl`。
|
||||
|
||||
@@ -96,26 +96,54 @@ readHistory(threadId, { beforeItemId?, limit }) -> {
|
||||
|
||||
`consume` 不接收或返回 cursor。每个 subscriber 在 Rust 内部持有自己的 cursor,并在加锁的临界区内完成过期判断、读取和 cursor 前进。前端只持有 `subscriptionId` 与 reducer state。并发 `consume` 不重复返回同一批事件。
|
||||
|
||||
`notify` 只负责唤醒,不携带事件、cursor 或持久化状态。前端收到通知后调用 `consume`;通知可合并、重复或丢失,事件完整性由 `consume` 保证。
|
||||
`notify` 只负责唤醒,不携带事件、cursor 或持久化状态。前端收到通知后调用 `consume`;通知可合并、重复或丢失,事件完整性由 `consume` 保证——但前提是前端确实唤醒了 `consume`,见下条的回执竞态。
|
||||
|
||||
`subscribe` 在同一个边界内先把新 subscriber 的游标钉在当时的队尾,再收集 bootstrap 的运行态事件,因此 bootstrap 返回的那批事件**就是**该 subscriber 此刻应处理的事件:前端直接 reduce 它们即可,不存在"先补一次 `consume` 才能拿到已暂存事件"的步骤。第一个例外只有回执竞态:Rust 侧一注册完 subscriber 就开始 `notify`,而前端要等回执到达才知道自己的 `subscriptionId`,这段窗口内的通知拿不到订阅身份。前端因此必须记一笔欠账,回执到达后立刻补一次 `consume` 取回那批事件;否则事件会卡在队列里等下一次通知,而一次回合的最后一个事件之后可能再也没有通知。除此之外不轮询,也不设任何定时 `consume`——唤醒只由 `notify` 负责。用定时器兜底既自举不了(判断"有活动回合"本身依赖事件),也把唤醒机制变成两套。
|
||||
|
||||
首屏历史不通过"读取整份对话"的命令获取:`subscribe` 返回的 `lastCompletedItemId` 就是首屏锚点,前端据此调用 `readHistory` 取最近的切片,再按滚动或按钮继续向前分页。系统不提供返回整份对话历史的命令。
|
||||
|
||||
### 事件和顺序
|
||||
|
||||
Thread 内所有公开事件共用一个单调递增 seq;seq 允许跳号,前端不要求连续。事件 envelope 至少包含:
|
||||
Thread 内所有公开事件共用一个单调递增 seq,但 **seq 只是 Thread Manager 的内部游标事实,不下发**:同一个 subscriber 的 `consume` 按队列顺序返回事件数组,数组顺序就是前端要处理的顺序,前端因此不需要 item 级 cursor 或第二套 reducer。
|
||||
|
||||
线上模型是 ts-rs 导出的 tagged enum(`agent/direct_thread_wire.rs`),前端消费 `src/view/project-development/chat/generated/` 里的生成绑定,改 Rust 模型后跑 `cargo test export_bindings` 重新生成;毫秒时间戳标 `#[ts(as = "f64")]`,因为 ts-rs 默认把 `u64` 映射成 `bigint`,而 Tauri 的 JSON 通道传的是 `number`。
|
||||
|
||||
事件按 `type` 区分,条目按 `itemType` 区分:
|
||||
|
||||
```ts
|
||||
{
|
||||
seq: number,
|
||||
type: string,
|
||||
turnId: string,
|
||||
itemId?: string,
|
||||
payload: unknown,
|
||||
}
|
||||
type DirectThreadEvent =
|
||||
| { type: 'turn.started' }
|
||||
| { type: 'turn.completed'; status: string }
|
||||
| { type: 'item.started'; item: DirectThreadItem }
|
||||
| { type: 'item.completed'; item: DirectThreadItem }
|
||||
| { type: 'item.delta'; itemId: string; kind: 'message' | 'reasoning'; delta: string }
|
||||
| { type: 'request'; kind: 'approval.requested' | 'ask.requested' | 'request.resolved'; requestId: string | null };
|
||||
```
|
||||
|
||||
进入 Thread Manager 的是已经完成安全过滤和协议标准化的公开 raw event,不是未经审查的 app-server JSON。事件可交错包含多个并发 item:`item.started`、`item.delta`、`item.completed`、approval/request/resolved 事件,以及 `turn.started`、`turn.completed` 生命周期事件。前端按 `turnId` / `itemId` 分发并 reduce,不需要 item 级 cursor 或第二套 reducer。
|
||||
进入 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` 布尔,历史条目也不记录回合身份。
|
||||
|
||||
一个 thread 同时最多有一个 active turn;一个 turn 内允许多个并发 item。`turn.completed` 必须在该 turn 的完成 item 均成功持久化后进入队列,前端据此结束运行态;不能用“不存在 unfinished item”猜测 turn 是否完成。
|
||||
|
||||
前端 reducer 的活动回合判定只有一条:事件序列中出现 `turn.started` 且其后没有 `turn.completed` 时才是活动回合,界面才允许显示忙碌态。`subscribe` bootstrap 里没有这样的序列,就表示当前没有活动回合;Thread Manager 队列随进程消失,因此进程重启后历史里留下的半截回合一律按已结束渲染,前端不发明中断态,也不从历史条目反推忙碌态。
|
||||
|
||||
生命周期锚点独立于 replay 队列保存:`turn.started` / `turn.completed` 事件即使已被队列前缀回收,`subscribe` 仍必须把最新的一条作为 bootstrap 事件返回。因此进程内任意时刻新建订阅,都能判定最新回合是运行中还是已结束,不依赖"未完成 item 恰好还在队列里"。
|
||||
|
||||
`item.started` 与 `item.completed` 必须携带与历史切片同形的**脱敏原始条目**(经同一套挑字段、脱敏、截断、路径归一),不得只给 item 类型或空 payload。前端不得依赖"按 `itemId` 单点取快照"补齐正文:Rust 不提供 `getItemSnapshot(itemId)`,未完成条目的正文随事件下发,已完成条目一律通过历史读取。
|
||||
|
||||
条目形状的职责边界固定为三条:
|
||||
|
||||
1. **搬运层不生成展示形状**。Thread Manager 只下发 Codex 原始条目(`itemType` 原样透传,正文与工具明细脱敏后带上限截断),不生成工具卡片的 `kind`、标题、折叠摘要,也不判断哪些条目要显示。
|
||||
2. **只有一个条目身份**。工具条目在 `project.jsonl` 里带两个 id(调用 id 与 response item id,同一调用的调用与输出共用前者;codex-rs `thread_history.rs` 中所有工具 item 都是 `id: payload.call_id.clone()`),所以在进队列前归一成一个 `itemId`。Thread Manager 与前端都不得再出现第二个 id 概念。
|
||||
3. **合并只在前端,且只保留"先到定形、后到补空白"**。第一次见到的快照决定卡片形状,后续快照只补输出与状态;同一调用只出现一张卡片。只有"后到信息一定更全"时才例外:正文取更长的一份、工具状态允许从 `running` 升级到终态、`updatedAt` 取较新的时间。
|
||||
|
||||
前端不保留增量缓冲:`item.delta` 直接追加到运行态条目的正文(正文只增不减)。`turn.completed` 把当前回合的运行态条目并入历史再清空,条目既不消失也不重复;失败与中止说明只在运行期显示,不写进 `project.jsonl`。
|
||||
|
||||
历史切片的 `firstItemId` 不是上述归一身份:分页锚点必须是 `project.jsonl` 里的原始 item id,由 Rust 从文件扫描单独算出。
|
||||
|
||||
思考正文以 `item.delta{kind:"reasoning"}` 流式下发(来源是 app-server 的 `item/reasoning/summaryTextDelta` 与 `item/reasoning/textDelta`)。这不放宽可见文本范围:被下发的就是此前已在 `item.completed` 展示、并已落进 `project.jsonl` 的同一段文本;plan 文本与命令输出仍然只降级为活动状态,不下发正文。
|
||||
|
||||
### 队列、subscriber 和回收
|
||||
|
||||
每个 thread 一个 Vec-based append-only replay queue,使用逻辑 head 偏移清理前缀,不做中间删除。完成 item 的事件在持久化成功后才可进入普通 replay 回收流程;unfinished item 的事件必须保留到 item 完成,不能被普通上限截断。
|
||||
|
||||
@@ -227,7 +227,7 @@ initialAttachments={currentProjectContext.attachments}
|
||||
1. [`home.suite.ts`](../../apps/ai-game-creator-shell/tests/appSurface/home.suite.ts)「imports home attachments…」:Direct invoke 必须带 `attachments`,其中 `name` 为 `角色参考.png`、`localPath` 为 upload 返回路径、`status: 'imported'`。用 png 证明不是 md 特例。
|
||||
2. 无附件的 Direct invoke 仍不得出现 `attachments` 键(或等价:不传该字段)。
|
||||
3. `planningStartMode` 首轮仍走 Supervisor,`chat_with_game_creator_agent` 的 payload 不含附件 sidecar。
|
||||
4. 工作台后发的普通消息:`chat_with_game_creator_direct_codex` 只有 `projectPath/prompt/clientTurnId`(及既有 creationType 规则),不带 attachments。
|
||||
4. 工作台后发的普通消息:`chat_with_game_creator_direct_codex` 只有 `projectPath/clientTurnId`(及既有 creationType 规则),不带 attachments。
|
||||
5. 若本分支已能跑 PR #210 的 home.suite / plan-gdd 做成游戏用例:只断言它仍调用 `createHomeDraftAutomatically` / 仍使用原固定 prompt;**不要**给做成游戏加第二条附件协议。sidecar 由通用 Direct 断言覆盖。
|
||||
|
||||
### 不测
|
||||
|
||||
@@ -185,7 +185,7 @@ camelCase JSON。禁止出现附件正文、命令 stdout、patch diff、宿主
|
||||
| `agc_list_project_files` | `path`、`query`(120)、`kind`、`offset`、`limit` | 无 |
|
||||
| `agc_write_file` | `path`、`contentChars`(`content` 的字符数,不是正文) | 不落 `content` |
|
||||
| `taonier_prepare_game_art` | `mode`、`brief`(截断 4000)、`briefChars`、`briefSha256` | **要 brief 原文**(分析定玩法的吸烟枪;上限已是 MCP 合同) |
|
||||
| `agc_generate_image` | `kind`、`aspectRatio`、`imageSize`、`assetName`、`outputPath`、`prompt` 截断 4000、`promptChars`、`promptSha256` | 不落 32k 全文 |
|
||||
| `agc_generate_image` | `kind`、`sliceMode`、`sliceCount`、`screenColor`、`aspectRatio`、`imageSize`、`assetName`、`outputPath`、`prompt` 截断 4000、`promptChars`、`promptSha256` | 不落 32k 全文 |
|
||||
| `agc_edit_image` | `sourceLocalAssetId`、`assetName`、`prompt` 截断 4000、`promptChars`、`promptSha256` | 同上 |
|
||||
| `agc_create_or_derive_resource` | `kind`、`mode`、`sourceLocalAssetId`、`assetName`、`prompt` 截断 4000、`promptChars`、`promptSha256` | MCP 上限已是 4000 |
|
||||
| `agc_list_registered_assets` | `kind`、`assetId`、`includeSequenceFrames`、`offset`、`limit` | 无 |
|
||||
|
||||
@@ -1,8 +1,48 @@
|
||||
# 【技术方案】GameAgent 对话工具调用卡片(Codex 风格)-2026-09-14
|
||||
|
||||
## 2026-09-16 修订(当前状态)
|
||||
|
||||
本方案的**卡片表现层**(折叠 / 展开、标题与摘要文案、耗时与时间显示、脱敏、无障碍、样式)仍然是有效契约;**数据来源层**已被 `docs/adr/【ADR】DirectProject对话历史单一事实源-2026-09-16.md` 取代,边界改为:
|
||||
|
||||
- DirectProject 聊天框的工具卡片由**运行态事件 + 项目对话历史**在前端投影生成(`features/project-workspace/directThreadItemProjection.ts`),不再读取 `tool-calls.jsonl`;`read_direct_tool_calls` 命令与 Rust 侧 `read_direct_tool_calls_at` 回读函数已删除,该文件现在只有 DirectRuntime 的写入。
|
||||
- 报文中不再有 `toolCalls` 增量字段与 `GameCreatorDirectTurnUpdateEvent` 这条实时链路:卡片形状由前端从脱敏原始条目生成,事件里只有 `item.started` / `item.completed` / `item.delta`(线上模型见 `agent/direct_thread_wire.rs`,由 ts-rs 导出绑定)。
|
||||
- 卡片身份只有一个 `itemId`(工具条目在 `project.jsonl` 里带的两个 id 已在 Rust 边界归一),前端卡片形状是 `Omit<GameCreatorDirectToolCall, 'turnId'>`:聊天卡片不再有回合身份。
|
||||
- 下面「### 1. 工具调用条目」「### 2. 实时事件」「### 3. 回读命令」三节描述的是 DirectRuntime 自己的账本(`tool-calls.jsonl` 的写入形状与脱敏规则仍然有效,DirectRuntime 保留),**不再是 DirectProject 聊天框的读路径**;「### 4. 前端合并与渲染」中按 `turnId` 归并、按 `turn-stream.jsonl` 的 `seq` 交替的规则已作废,改为按事件顺序 + 历史文件顺序投影。
|
||||
|
||||
## 一句话交付
|
||||
|
||||
把 GameAgent 右侧对话面板里的「执行命令 / 写文件 / 调工具」从一行中文进度文本,改成 Codex 桌面客户端那样的**可折叠卡片**(折叠态一行摘要,展开态看命令与文件明细),并且在**刷新页面、重开项目后仍然存在**。
|
||||
把 GameAgent 右侧对话面板里的「执行命令 / 写文件 / 调工具」改成可折叠卡片,整轮总耗时只在回合状态/完成小结显示一处,工具组与内部工具分别显示各自耗时;运行中的计时动态增长,不足一分钟保留一位小数,达到分钟后显示整数秒。
|
||||
|
||||
## 总耗时与动态工具计时
|
||||
|
||||
### 紧凑过程入口与 Markdown
|
||||
|
||||
- 思考过程、执行过程和工具组统一为无大块底色的单行折叠入口:左侧小图标/简短预览,最右侧展开箭头;长文本省略,不把箭头挤出窄聊天列。沿用共享过程色和 12px 层级,键盘可展开、有可见焦点。
|
||||
- 思考折叠态直接显示浅色的内容预览,而不是只有“思考过程”标题;预览不显示 Markdown 控制符、原始 HTML 或链接地址。展开后复用现有安全 Markdown 渲染链路,支持段落、强调、列表、链接、代码块等,不开启原始 HTML 执行。实时与历史、当前 Agent 与策划 Agent 使用同一呈现。
|
||||
- 思考入口使用灯泡图标;展开后入口文字切换为“思考过程”,原内容仅在 Markdown 正文显示一份,不同时保留相同摘要。收起后恢复浅色内容预览;键盘开合与鼠标开合行为一致。
|
||||
- 回合结束后最外层“执行了 N 个操作,耗时 XXX”使用正文颜色;收进该折叠层的内部过程保持运行中原有颜色、行高和块间距,不能因额外 Grid gap 与子元素 margin 叠加而拉大间距。
|
||||
- 思考与工具组按紧凑连续列表呈现,相邻过程块间距 4px,内部工具行使用紧凑的 2px 上下内边距;运行与完成后的容器使用同一口径。失败工具的名称、图标、状态及耗时使用错误红色,所属组摘要有“有操作失败”提示且标红;同组其它工具仍运行时也不能吞掉失败标识,成功行不得被连带染红。
|
||||
- 工具组摘要只统计本组工具条目总数,显示“执行了 N 个操作,耗时 XXX”;运行中另有状态提示,不把操作数称作成功数,失败仍保留明确状态。回合外层执行过程统计所有工具块的操作数,不把思考段落或文本消息当工具操作;缺失耗时不伪造。
|
||||
- 所有 AGC 耗时统一用中文时分秒:不足一分钟显示 `5.2秒`,达到分钟后显示整数秒,如 `2分05秒`、`1时02分05秒`。省略前导零单位,带小时则保留两位分钟,带分钟则秒补齐两位整数;先整体舍入再拆分单位,避免出现 60 秒/60 分。工具行、组、整轮、生成任务及策划耗时共用一个纯格式化函数,不改变各自计时来源和动态刷新。
|
||||
- Direct 对话发送后、本轮首个可见思考/文字/工具响应到达前,在消息列表内、工具调用块外临时显示“思考中…”。收到首个响应后移除,失败、取消和空闲时同样不显示;直接从现有忙碌状态与本轮投影派生,不新增原生事件、定时器或持久化消息。工具间等待不在本次轻量实现范围内。
|
||||
- 独立计时保持不变,组状态/用时在单行中作为次要信息,空间不足可移至展开内容,不能占第二行破坏紧凑入口;整轮总耗时仍只显示一次。工具输入/输出、失败信息与操作明细不因样式变更丢失。
|
||||
- 只改展示与对应测试,不改变消息身份、分组顺序、生成或原生执行语义。验证覆盖 Markdown 安全与语义、预览省略、准确计数、展开/键盘操作、窄屏布局及原有计时冻结。
|
||||
|
||||
- “总耗时”表示同一轮用户请求从发送到回合终态的墙钟跨度,包含 LLM 推理、工具调用和等待;只在本轮运行状态或完成小结显示,不重复放进各工具组。
|
||||
- 工具组顶部“执行了 N 个操作,耗时 XXX”中的耗时范围是本组首个工具开始到最后一个工具完成,包含组内等待但不是工具耗时之和。不同工具组独立计时;本组全部终态后立即固定,即使本轮或后面的工具组仍运行,也不能显示“进行中”或继续增加。组头不展示发送/结束时刻,不沿用用户发送时间计算本组。
|
||||
- 回合仍运行但全部工具暂时结束时,只增长本轮总耗时。成功、失败或终止到达后整轮按实际终态时间固定;随后打开折叠块、翻页或其它回合的新事件不得改变已完成的组或回合用时。
|
||||
- 单条工具从其实际开始到完成计时。运行中的工具按当前时间持续增长,不能把最近一次快照更新时间当作当前时间;已经结束的工具必须立即固定,即使同组其它工具或 LLM 仍在运行。
|
||||
- 对话的组、工具及整轮耗时运行中均每 100 毫秒刷新,统一使用上述中文时分秒格式。以时间戳计算而不是按 tick 累加,避免后台节流后的累计漂移;缺失或倒序边界不伪造 `0.0`。非对话场景只统一文本格式,不改变原有刷新或后端累计时间语义。
|
||||
- 各层时间范围与对应耗时使用相同的起止边界。运行标记也按本层状态判定;完成后如果展示起止时间,其精度不得造成范围差与用时矛盾。组内存在缺失或倒序的工具边界时,不能用其他组或整轮的时间补造本组耗时。
|
||||
- 原生事件保留各阶段的时间语义:开始与完成不能都优先折叠成开始时间。优先采用上游明确提供的阶段时间或实际调用时长,缺失时使用宿主观察该阶段的时间;重放沿用原事件时间,不能在前端收到或重放时重新取当前时间。
|
||||
- 工具开始/完成的权威边界是事件级 `at`,不是条目展示字段 `item.at`。整轮起点优先采用该轮实际用户消息的发送时间(与气泡一致,不取所有条目的最小时间),缺失时采用原生 `turn.started.at`;终点只采用 `turn.completed.at` 或明确的终止/失败收口事件。首次补到更早的真实发送时间可以校正起点,但旧历史或重复事件不能覆盖已经固定的终点。
|
||||
- 回合阶段使用宿主观测阶段的毫秒时间,或上游明确的毫秒边界/可靠时长;不把上游已截断的整秒时间冒充十分之一秒精度。回合是否开始以 Thread Manager 的事件顺序为准,不能用时间戳大小拒绝取消后同一秒内的新请求。回合完成的展示时间在当前会话中冻结;重新打开后若历史未保存完整生命周期边界,则隐藏未知用时而非补造。
|
||||
- 无效边界包括缺字段、非有限值、时间戳为 0、结束早于开始;这些情况隐藏不能证明的耗时。合法开始时刻的运行中 `0.0秒` / `0.0s` 则正常显示。只有完成快照而没有开始事件的工具不猜测开始时间。
|
||||
- 沿用 Thread Manager 的生命周期与条目身份,不引入第二套回合状态源、计时账本、远程 API 或数据库字段。前端仅保存原事件的展示时间边界;旧历史缺少完整边界时不声称知道精确总耗时或工具耗时,不为补齐计时启动模型请求。
|
||||
- 必须覆盖:前组已完成而后组运行时各自时间独立、整轮总耗时仅出现一次、无新事件仍增长、工具完成后 LLM 继续、并行工具分别计时、完成/失败/终止冻结、同身份重放不改终态时间、切项目与卸载停止计时、缺失与倒序时间、0.0 / 59.9 / 60.0 秒边界。真实 Provider 验证与模拟事件的客户端验证分开报告。
|
||||
- Direct 对话的初始消息占位仅在正式用户消息尚未进入当前显示链路时显示;正式条目或乐观用户条目出现后撤下占位,不在消息列表外额外保留一份。只控制占位是否显示,不按文本合并或删除用户真实重复发送的消息;分页已有更早历史时不把初始占位补到当前页。
|
||||
- 同一用户消息从本地发送转为正式条目时必须保留原发送时间,不能因去重丢掉该时间而改用启动应答后的观测时间。回合明确结束时,即使当前 live 集合为空,也应将终态边界关联到已知的本轮用户条目;不得仅按“历史最后一项”猜测或向无关旧回合补时间。
|
||||
- 生命周期事件可携带已有用户条目的 `userItemId`,用于把时间边界精确关联到同一条历史消息(不产生新的回合 ID 或持久化字段)。恢复时该关联随 Thread Manager 原事件重放;带旧用户身份的终态不能收口另一条新请求。缺少身份的旧事件不据时间猜测归属。
|
||||
|
||||
## 背景与现状(已核实)
|
||||
|
||||
@@ -47,15 +87,15 @@ toolCalls?: DirectTurnToolCall[] | null;
|
||||
- 字段**可选**:老版本事件解析路径必须保持兼容(前端拿到 `undefined` 时行为与现在一致)。
|
||||
- `DirectTurnToolCall` 与上面 payload 同形(去掉 `turnId`)。
|
||||
|
||||
### 3. 回读命令
|
||||
### 3. 落盘契约(写侧)
|
||||
|
||||
新增 Tauri 命令 `read_direct_tool_calls(projectPath)`,返回按时间正序的 `DirectTurnToolCall[]`。
|
||||
DirectRuntime 写 `<projectRoot>/.agent/conversations/tool-calls.jsonl`;回读命令与 Rust 侧回读函数已随聊天读路径退役删除,下面的语义约束的是**写进文件的行**。
|
||||
|
||||
- **上限语义**:200 条是「按时间保留最新 200 条」。超出时更早回合的卡片会被**静默丢弃**(老回合卡片会消失),不做分页、不做历史回填;同一 `id` 的多条记录先按 `updatedAt` 合并,再按时间正序裁剪。
|
||||
- 历史文件缺失 → 返回空数组,不报错。
|
||||
- 单行损坏 → 逐行读字节并逐行解码,跳过该行继续,不整体失败;只有损坏字节与下一行黏成一行(例如写入被截断、缺失换行)时,被丢掉的也只是那**一行**,其后的合法记录必须继续读回(与 Codex item 流一样是"尽力而为"的展示数据,不是业务真相)。
|
||||
|
||||
### 4. 前端合并与渲染(回合唯一归属,连续工具成块)
|
||||
### 4. 前端合并与渲染(回合唯一归属,连续工具成块)——已作废,见文首修订
|
||||
|
||||
- 加载对话时按 `turnId` 归并为唯一回合容器,用户消息保留在该回合前部。有 `turn-stream.jsonl` 时,文本与工具按 item `seq` 交替,连续工具合为一块,遇到文本另起一块;没有流的历史回合才采用“工具块 + 历史正文”。
|
||||
- 回合完成后,中间文本及所有工具块统一收进默认关闭的“执行过程”;最终回复及失败提示留在外面。展开后仍按原顺序查看中间输出和工具详情;运行中不使用外层折叠区。用户消息的发送时间从消息自身的历史时间读取,不能拿工具起点补造。
|
||||
@@ -65,21 +105,20 @@ toolCalls?: DirectTurnToolCall[] | null;
|
||||
```html
|
||||
<section class="agent-tool-call-group" data-testid="agent-tool-call-group" data-status="completed">
|
||||
<button type="button" class="agent-tool-call-group-head" aria-expanded="false" aria-controls="…"
|
||||
aria-label="已执行 2 个命令、1 个文件变更,用时 42秒">
|
||||
aria-label="执行了 3 个操作,耗时 42.0秒">
|
||||
<span class="agent-tool-call-group-icon" aria-hidden="true"></span>
|
||||
<span class="agent-tool-call-group-summary">已执行 2 个命令、1 个文件变更</span>
|
||||
<span class="agent-tool-call-group-time">14:20:05 → 14:21:02</span>
|
||||
<span class="agent-tool-call-group-duration">用时 42秒</span>
|
||||
<span class="agent-process-summary-preview">执行了 3 个操作</span>
|
||||
<span class="agent-process-summary-meta">,耗时 42.0秒</span>
|
||||
<svg class="agent-tool-call-group-chevron" aria-hidden="true"></svg>
|
||||
</button>
|
||||
<div class="agent-tool-call-group-body" hidden>
|
||||
<ul class="agent-tool-call-group-rows">
|
||||
<li class="agent-tool-call-group-row" data-testid="agent-tool-call-row" data-kind="command" data-duration-ms="12300">
|
||||
<button type="button" class="agent-tool-call-row-head" aria-expanded="false" aria-controls="…"
|
||||
aria-label="已运行 npm run build,耗时 12.3s">
|
||||
aria-label="已运行 npm run build,耗时 12.3秒">
|
||||
<span class="agent-tool-call-row-icon" aria-hidden="true"></span>
|
||||
<span class="agent-tool-call-row-text">已运行 npm run build</span>
|
||||
<span class="agent-tool-call-row-duration">12.3s</span>
|
||||
<span class="agent-tool-call-row-duration">12.3秒</span>
|
||||
<svg class="agent-tool-call-row-chevron" aria-hidden="true"></svg>
|
||||
</button>
|
||||
<div class="agent-tool-call-row-detail" hidden>
|
||||
@@ -94,10 +133,10 @@ toolCalls?: DirectTurnToolCall[] | null;
|
||||
```
|
||||
|
||||
- 文案规则(按 kind,不允许自由发挥):
|
||||
- 块头汇总按 kind 计数、顺序固定 `command → file_change → mcp_tool → web_search → context_compaction → other`,标签 `命令`/`文件变更`/`工具调用`/`联网搜索`/`上下文整理`/`其他操作`,形如 `已执行 5 个命令、2 个文件变更`;空集合不渲染块。
|
||||
- 块头按实际工具条目计总数,形如 `执行了 7 个操作,耗时 1分05秒`;空集合不渲染块。操作数不是成功数,失败与运行状态仍单独可见。
|
||||
- 行文案:展示工具摘要,不重复添加动词前缀;`context_compaction` 固定为“整理上下文”。状态单独放在行尾(执行中 / 已执行 / 失败),`failed` 使用现有 `--platform-*` 错误色;已结束回合不因残留 `running` 快照显示“执行中”。
|
||||
- 耗时:单条工具 = `startedAt` → `updatedAt`,块头总用时 = 该回合所有工具的 `min(startedAt)` → `max(updatedAt)`。单条格式:`<1s` → `0.4s`、`<60s` → `12.3s`(整秒省略小数)、`≥60s` → `2m 5s`;块头格式:`42秒` / `4分钟` / `5分钟 45秒`。`startedAt` 为 0 或 `updatedAt < startedAt` 时不显示耗时(不显示 `0s` / 负数),耗时为 0 时同样不显示 `0s`。
|
||||
- 时间:块头显示该回合结束时间(`max(updatedAt)` 的本地 `HH:mm:ss`);同一回合能拿到用户消息时间(`updatedAt > 0`)时显示 `HH:mm:ss → HH:mm:ss`(发送 → 结束),取不到就只显示结束时间,不编造。
|
||||
- 耗时:执行“总耗时与动态工具计时”合同。块头是本组用时,单条是该工具独立耗时,整轮总耗时只在本轮状态/小结显示;不足一分钟显示一位小数,达到分钟后显示整数秒,运行中每 100 毫秒刷新,各自终态固定。
|
||||
- 时间:范围与对应层级用时采用同一边界,不把用户发送起点与局部工具组终点混搭。缺失的历史时间不编造。
|
||||
- 回合结束时间与耗时在正文下方右对齐;Direct 对话输入框提示统一为“描述你的想法,或 @ 引用素材”,引用按钮保留输入盒的 12px 内边距,不使用负边距贴边。
|
||||
- 当前 Agent 与策划 Agent 共用 `packages/shared` 的 `AgentMessageContent` 表现组件:正文为 14px / `--platform-text-strong`,思考、中间输出和工具调用为 12px / `--platform-text-soft`。实时与历史思考共用同一个折叠入口;工具输入输出继承过程色,失败状态保留错误色。Markdown 标题、表格及代码高亮在过程区同步弱化,最终回复和文档预览仍保留正常排版,不按 Agent 类型复制样式。输入提示与禁用状态保持原有反馈。
|
||||
- Windows 命令展示:仅 `command` 卡片识别 `pwsh` / `powershell`(含完整路径、`.exe`、常见启动选项)的 `-Command` / `-c` 外层包装,摘要和展开输入只展示脚本正文,并解开单个 shell 参数的引用拼接。摘要优先读取已脱敏的 `detail.command`,再按首行 120 字符截断,避免历史摘要被可执行文件路径占满。无法识别的启动方式、`-File`、`-EncodedCommand`、普通命令和 MCP 输入原样展示;执行参数、持久化原文、脱敏和输出均不改变。
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
# 策划 Agent 生产迁移与工作区浏览方案
|
||||
|
||||
更新时间:2026-09-10
|
||||
状态:实施中
|
||||
更新时间:2026-09-21
|
||||
状态:已完成(2026-09-18)
|
||||
|
||||
> 现状说明(2026-09-18):本文记录的迁移已完成,当前策划入口统一使用 Design Agent。旧 Planning V1/V2 会话、专用命令、审批卡和展示适配已删除;文中提到的 V2 文件仅代表迁移时的参考来源,不得作为现行实现、回退路径或测试迁移目标。
|
||||
|
||||
## 1. 目标
|
||||
|
||||
@@ -23,11 +25,11 @@
|
||||
|
||||
生产侧复用 Provider、会话恢复、文件读写、审计和 UI 通信等基建,不复用现有立项策划 Agent 的行为协议。
|
||||
|
||||
原型行为是新的策划 Agent 契约。现有 `Planning V2` 只作为 Provider 调用、持久化和恢复实现的参考来源,不作为行为、提示词、产物或审批契约。
|
||||
原型行为是新的策划 Agent 契约。已退役的 `Planning V2` 仅作为迁移历史中的 Provider 调用、持久化和恢复参考来源,不作为行为、提示词、产物、审批契约或回退路径。
|
||||
|
||||
常驻提示词、阶段提示、速览卡结构说明、工具名称与参数、资源目录和注入映射以迁移时核对的原型文件为基线。迁移不顺便重写提示词,不增加模型输出内容门禁。旧需求文档中已明确舍弃的行为不恢复。
|
||||
|
||||
本方案描述生产迁移目标。当前已接入独立设计会话、自由工具循环、阶段审批命令和工作区浏览命令;生产入口对无旧 Planning V2 会话的策划项目切换到新设计 Agent,已有 V2 会话仍走原链路。旧 Planning V2 专用展示尚未清理。
|
||||
本方案描述已完成的生产迁移。当前入口使用独立设计会话、自由工具循环、阶段审批命令和工作区浏览命令;旧 Planning V2 会话不再继续运行,旧专用展示和命令已删除。历史项目按当前 Design Agent 入口重新开始,不做旧会话转换或旧测试迁移。
|
||||
|
||||
## 3. 复用与丢弃清单
|
||||
|
||||
@@ -66,16 +68,17 @@
|
||||
|
||||
路径穿越、绝对路径、控制目录访问和凭据泄露防护属于安全边界,可以保留;它们不能扩展成限制正常策划创作的业务门禁。
|
||||
|
||||
现有参考入口如下。复用对象是其中的可用函数和通信机制,不是整个模块:
|
||||
当前实现入口如下:
|
||||
|
||||
| 代码入口 | 参考内容与注意点 |
|
||||
| 代码入口 | 职责与边界 |
|
||||
| --- | --- |
|
||||
| `apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_session_v2.rs` | Provider 配置、请求、重试、会话恢复;丢弃问询计数策略、强制工具和输出纠错协议 |
|
||||
| `apps/ai-game-creator-shell/src-tauri/src/agent/runtime_tools/file_ops.rs` | 读写、补丁和删除实现;不可原封不动继承 owner、产物和任务门禁 |
|
||||
| `apps/ai-game-creator-shell/src-tauri/src/agent/design_runtime.rs` | Provider 请求、工具循环、重试、阶段审批与会话恢复 |
|
||||
| `apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/design_session.rs` | 设计会话、阶段状态与待交互请求的持久化 |
|
||||
| `apps/ai-game-creator-shell/src-tauri/src/agent/design_tools.rs` | 固定资源包、工作区文件读写、补丁、删除、搜索与路径安全边界 |
|
||||
| `apps/ai-game-creator-shell/src-tauri/src/project/filesystem.rs` | 路径解析、文件列出和读取、基础写入;内部绝对路径字段不得传给模型 |
|
||||
| `apps/ai-game-creator-shell/src/App.tsx` | Tauri 事件订阅、文件刷新、会话恢复入口;不把旧策划状态投影当作新契约 |
|
||||
| `apps/ai-game-creator-shell/src/features/project-workspace/ProjectWorkspaceChatPane.tsx` | Game Agent 文件列表与读取入口;新的文件浏览应直接打开文件视图,不往聊天中追加文件正文 |
|
||||
| `apps/ai-game-creator-shell/src/features/project-workspace/GddApprovalCard.tsx` | 仅参考现有面板和通用交互样式;不沿用结构化 GDD 展示及三按钮决定模型 |
|
||||
| `apps/ai-game-creator-shell/src/App.tsx` | Design Agent IPC、事件订阅、文件刷新、会话恢复与输入提交 |
|
||||
| `apps/ai-game-creator-shell/src/features/project-workspace/DesignWorkspacePanel.tsx` | 策划工作区文件浏览与正文展示,直接打开文件视图 |
|
||||
| `apps/ai-game-creator-shell/src/features/project-workspace/DesignAgentSurface.tsx` | 消息、reasoning、澄清与阶段审批交互 |
|
||||
|
||||
不为此次迁移先建设通用 Agent 框架。已有函数能够直接使用就直接使用,只有确实需要拆除业务耦合时才做局部拆分。
|
||||
|
||||
@@ -113,7 +116,75 @@ Runtime 不维护文档版本号,不解析文档版本,不提供版本回退
|
||||
|
||||
会话/回合/工具调用身份用于生产恢复和重复请求处理,与 Agent 自行写在策划文档头部的版本号无关。
|
||||
|
||||
策划会话在创建时保存入口选择的 AGC 模型目录 ID(例如 `quality`、`fast`),同一会话后续回合沿用该 ID;客户端不保存或推断上游真实模型名。官方 `platform-llm` 直连 api-server 时携带 AGC 客户端标记,由 api-server 根据模型目录解析实际模型,不能在客户端硬编码某个上游模型替代目录选择。
|
||||
策划每次用户发起执行时采用客户端当前保存的模型和推理档,保存在当前回合中;同一执行内的工具调用与自动重试保持该选择,后续用户执行重新读取。官方模式保存 AGC 模型目录 ID(例如 `quality`、`fast`),自定义模式保存已选择的型号;客户端不推断官方上游真实模型名。官方 `platform-llm` 直连 api-server 时携带 AGC 客户端标记,由 api-server 根据模型目录解析实际模型,不能在客户端硬编码某个上游模型替代目录选择。
|
||||
|
||||
### 4.1 策划对话模型与推理档选择
|
||||
|
||||
**交付结果**:策划 Agent 对话复用 GameAgent 的模型、推理档选择控件及配置通道,用户的新选择对后续策划回合实际生效,并保持 GameAgent 原有行为兼容。
|
||||
|
||||
本节是本次变更的唯一主规范。控件接入已完成并提交,运行时模型生效逻辑已实现、待验收。拆分与验收见 [控件接入里程碑](../project-memory/plans/【里程碑】策划Agent模型与推理档控件接入-2026-09-20.md) 和 [模型生效里程碑](../project-memory/plans/【里程碑】策划Agent回合模型选择生效-2026-09-20.md)。
|
||||
|
||||
#### 范围与非目标
|
||||
|
||||
- 必须项:复用已有控件及客户端配置读写;补齐策划入口;修正旧策划会话固定使用创建时模型的行为;验证 GameAgent 兼容性。
|
||||
- 风险项:界面选择与实际请求不一致、执行中途切换配置、恢复旧会话,以及策划窄面板新增控件的布局。
|
||||
- 可选项:无。发现与上述验收无关的问题,只记录发现,不扩展本次实现。
|
||||
- 明确不做:不评估或重构 GameAgent 已有控件的本征不足,不重做模型目录、缓存、配置同步、下拉交互、通用保存队列或错误处理;不调整 GameAgent Runtime、推理默认值、供应商适配、提示词或阶段审批规则;不新建 Agent 专属设置、模型管理页、配置框架或测试框架。
|
||||
- 优先在现有共享对话容器中复用同一组组件,不复制控件源码,不因新增一个使用方迁移整套配置型组件。若需要局部接口扩展,默认调用必须保持 GameAgent 现有行为。
|
||||
|
||||
#### 控件和配置合同
|
||||
|
||||
1. 策划对话输入框附近显示与 GameAgent 相同的模型和推理档控件,读取、选项、保存、错误反馈沿用已有能力;只调整策划宿主必要的显隐和布局。按用户确认,控件保持原样,不给策划发送、审批、澄清或重试新增模型可用性检查,也不以模型目录状态新增提交门禁;GameAgent 已有提交逻辑保持原样。
|
||||
2. 继续使用客户端全局模型选择与全局推理档,不新增第二份策划设置。它们是客户端偏好,可能影响其它后续对话;进入或重开策划界面时显示当前保存值。既有 GameAgent 配置解析保持不变,不承诺新增跨窗口实时同步。
|
||||
3. 策划运行时最终以选择器保存的全局模型和推理档作为本次用户选择;策划专属底层配置不得在这两个字段上静默覆盖选择。连接、鉴权、超时等其余字段继续按既有生产配置解析,不清理或改写用户其它配置。
|
||||
4. 模型的官方目录别名、自定义模型目录、默认项跟随及失效项处理复用已有能力;不新增供应商能力探测或模型自动降级。推理档的枚举与默认值维持现状。
|
||||
5. 可以在执行中修改后续选择;不会中断或重启正在执行的策划回合。策划保留原有输入、发送、审批和澄清忙态规则,不引入 GameAgent 的消息队列、素材引用或语音功能。
|
||||
|
||||
#### 生效边界
|
||||
|
||||
| 触发 | 目标行为 |
|
||||
| --- | --- |
|
||||
| 首次发送、后续普通发送 | 读取最新已保存的选择并开始新回合,不新增提交前模型检查 |
|
||||
| 回答澄清、批准/拒绝阶段后实际继续调用 Provider | 同样使用本次继续前保存的选择;不更改审批结果和请求身份语义 |
|
||||
| 用户主动点击失败重试 | 使用最新选择开始本次执行;继续现有工具恢复规则,不重复已完成副作用 |
|
||||
| 同一执行内的工具循环、HTTP/流式瞬态自动重试 | 始终使用该次执行开始时确定的模型和推理档,不在每次 Provider 调用前重新采样这两个字段 |
|
||||
| 纯读取、展示历史、未触发 Provider 的操作 | 不创建新回合,不覆盖历史使用的模型信息 |
|
||||
| 进程中断后的自动续跑 | 优先沿用持久化的活动回合模型和推理档;不把它当成用户重新选模型后的新回合,不重复文件副作用 |
|
||||
|
||||
“后续回合生效”按上表定义,不以底层函数是否创建新 turn ID 为判断依据。无需重建会话或清空历史才能切模型,正式策划阶段、产物和上下文继续保留。
|
||||
|
||||
#### 失败、兼容与数据约束
|
||||
|
||||
- 控件保存失败和目录不可用沿用控件现有提示;策划发送、审批、澄清、重试和 Provider 报错流程保持原样。原控件内部的目录同步与默认模型处理不在本次改造范围,也不在策划宿主另加同类逻辑。
|
||||
- Provider 不接受所选模型或历史上下文时,沿用现有可见错误和重试;不静默换模型、清历史或另建跨模型上下文转换系统。
|
||||
- 已有设计会话无需离线迁移:下次用户发起执行时采用新选择。自动恢复的旧活动回合优先保留已有模型;若尚无推理档快照,使用当次有效配置补齐一次并固定,不伪称还原了历史档位。
|
||||
- 持久化在当前回合追加可缺省的 `modelSelection`,只含 `model` 和 `reasoningEffort`;已有会话 `modelId` 表示最近一次用户执行采用的模型,兼作旧活动回合恢复依据。旧记录缺字段可读;不保存完整配置、端点凭据、Token 或 API Key。不增加平行会话账本,不改模型目录 ID 与真实型号的边界。
|
||||
- 本次不涉及公开 HTTP API、OpenAPI 或 SpacetimeDB schema。若本地设计会话投影需要新增字段,同步其现有 Rust/TypeScript 定义和恢复用例。
|
||||
|
||||
#### 两步交付与验收
|
||||
|
||||
| 步骤 | 交付边界 | 完成证据 |
|
||||
| --- | --- | --- |
|
||||
| 第一步:控件接入 | 策划宿主显示并使用原控件,选择写入现有全局配置;保留策划原提交流程;不改策划请求的模型解析与快照逻辑 | 策划控件读写、忙态与失败提示定向测试;GameAgent 相关现有用例;宽/窄面板 smoke |
|
||||
| 第二步:模型生效 | 在策划执行边界采样并固定模型与推理档,旧会话下次执行采用新选择,自动恢复保留活动回合选择 | 本地 Provider fixture 验证真实请求字段、跨工具调用一致性、重试与恢复;界面到请求 smoke;GameAgent 兼容回归 |
|
||||
|
||||
第一步是内部可验收的接入结果,不能宣称“策划旧会话切模型已生效”,也不能作为完整功能单独发布。第二步验收前保持这一已知限制明确。
|
||||
|
||||
两个步骤分别形成最小闭环;检查点分别为“控件与兼容验收”和“请求与恢复验收”。实现中的新增发现只有影响本节交付判据时才扩大范围。每步先用一个工作时段完成定向实现与验证;超过时段仍未闭环时,说明剩余阻碍并重估,不以顺手改造组件扩大任务。
|
||||
|
||||
验收至少覆盖:保存后重新进入策划显示一致;旧会话切换模型后实际请求改变;当前执行不被中途改档;下一次发送/澄清/审批继续/主动重试生效;自动恢复不重复工具副作用;GameAgent 原有选择、发送和运行行为不回归。真实 Provider 或桌面环境缺失时明确记为未验证,不用 fixture 冒充实机结果。
|
||||
|
||||
### 4.2 策划参考附件导入
|
||||
|
||||
- 首页策划入口与策划聊天框将用户附件按原始字节导入 `design_artifacts/references/`,Agent 通过现有工作区文件工具自行发现。有用户文字时,导入完成后才开始首页首轮策划;只有附件时只进入工作区,不合成消息或自动启动策划。聊天框导入中暂缓发送、阶段批准、澄清与重试,并提示等待导入完成再切换游戏制作,避免新回合读取未完成文件或模式切换丢失导入结果。
|
||||
- 文件名保留安全的原始名称;同名自动追加序号,禁止覆盖已有文件。沿用项目权限与工作区路径边界,禁止通过文件名、符号链接或目录链接写出工作区。空文件也可导入。
|
||||
- 导入只写策划工作区,不登记游戏资产、不更新项目资源清单或游戏 revision,不触发上传后的清单刷新。不组装回合附件、不把文件路径或清单注入 Agent 消息。
|
||||
- 每个文件独立返回结果;部分失败不丢弃成功文件,界面显示成功数量及失败文件和原因。用户可重新选择失败文件重试;成功导入不自动重放,再次手动导入同名文件视为新副本。
|
||||
- 游戏入口继续使用现有资产上传链路。已有 `assets/uploads/` 文件不自动搬迁;转入游戏制作时沿用现有策划工作区登记行为。
|
||||
- 本次不修改 Agent 提示词、工具或消息协议,不增加 PDF、Word 等二进制文档解析;保存成功不代表格式可由现有 UTF-8 文件读取工具解析。
|
||||
- 验收覆盖:首页与聊天框导入、多文件部分失败、同名与空文件、工作区工具可列出并读取文本、路径与链接边界、资源清单和 revision 保持不变、游戏上传兼容。证据由定向前端测试、本地 Rust 文件往返测试、类型检查和编码检查提供;真实桌面交互另行记录。
|
||||
|
||||
验证证据(2026-09-21):`appSurface.test.ts` 中策划导入、导入中审批、首页游戏附件与模型控件定向用例通过;`designWorkspaceDebug.test.tsx`、`designProjectRestore.test.tsx` 与 `workspaceLauncherManifestMerge.test.tsx` 通过,覆盖文件事件刷新、项目恢复和导入中模式切换。Rust `agent::design_tools::tests::` 8 项通过,覆盖文件往返及写入边界。AGC typecheck(含命令注册检查)、定向 ESLint、Rust 格式、编码、文档索引与差异检查通过。未运行安装包桌面点击验证或真实 Provider;Windows 链接用例在宿主不支持建链接时跳过。
|
||||
|
||||
## 5. Agent Runtime
|
||||
|
||||
@@ -177,6 +248,8 @@ concept → top_design → architecture → systems → tdd → consultant
|
||||
|
||||
模板、范例、类型资料和没有明确要求自动注入的文档继续保持选读。必读资源缺失、为空或读取失败时,只记录诊断并继续请求 Provider,不阻断阶段推进。
|
||||
|
||||
星露谷分析示例的资源路径统一为 `templates/stardew-analysis.md`,分册与示例中的引用保持一致;通过 `read_resource` 读取时使用目录登记的资源 ID `templates.stardew_analysis`。
|
||||
|
||||
根据当前原型 `resources/catalog.json`,自动注入清单为:
|
||||
|
||||
| 当前阶段 | 全文注入的资源 ID | 资源文件 |
|
||||
@@ -203,13 +276,15 @@ concept → top_design → architecture → systems → tdd → consultant
|
||||
|
||||
`systems: []` 是已确定的行为,不是迁移时需要补齐的缺口。`project/analysis.md`、`project/决策台账.md`、`project/dialog.md` 保持原型中的共享过程文件口径,不升级为新的审批必需项。速览卡保留原型结构提示,但 Runtime 与 UI 不解析其章节或内容字段。
|
||||
|
||||
过程文档记录关键依据、决定和待办,不要求实时完整,也不应重复正式设计文档;阶段提交前补齐影响验收的关键记录。顶层设计的分册、模板和示例保留核心定位及按需说明的易混淆方向与排除理由,不强制使用固定的“不是 X,而是 Y”句式。
|
||||
|
||||
进入下一阶段必须由用户批准触发。Runtime 推进后向 Agent 追加明确的用户行为语义,例如“用户已批准上一阶段,现在进入顶层设计阶段”,避免 Agent 误认为 Runtime 自行推进。
|
||||
|
||||
## 7. 审批与澄清交互
|
||||
|
||||
### 7.1 阶段审批
|
||||
|
||||
Agent 完成当前阶段后必须调用 `submit_phase_for_approval`。普通文本中的“批准”“确认”“进入下一阶段”等内容不改变 Runtime 阶段。
|
||||
Agent 完成五个策划阶段中的当前阶段后必须调用 `submit_phase_for_approval`。需要用户选择的关键问题先通过问询解决,阶段审批用于检阅已完成的产物;可以保留不阻塞当前阶段的后续事项和待原型验证项。普通文本中的“批准”“确认”“进入下一阶段”等内容不改变 Runtime 阶段。
|
||||
|
||||
提交时 Runtime 只检查:
|
||||
|
||||
@@ -231,7 +306,7 @@ UI 使用“批准”和“继续修改”两个文字按钮,分别配 Lucide
|
||||
- 选择“继续修改”后,再次出现新的审批请求前,不能重复批准旧请求;
|
||||
- 不实现 `/approve`、`批准`、`确认` 等文本检测。
|
||||
|
||||
批准与拒绝通过带请求身份的结构化命令处理。后端只接受当前待审批请求;重复点击同一已处理请求不再次推进,旧卡不能批准新的请求。此处校验请求身份与会话状态,不对文件增加指纹、快照或内容校验。进入顾问态后不自主安排新任务。
|
||||
批准与拒绝通过带请求身份的结构化命令处理。后端只接受当前待审批请求;重复点击同一已处理请求不再次推进,旧卡不能批准新的请求。此处校验请求身份与会话状态,不对文件增加指纹、快照或内容校验。顾问阶段遵照用户的具体指示行动,不自主推进项目或主动安排下一步,不提交阶段审批;完成单次用户请求不结束顾问态。
|
||||
|
||||
### 7.2 澄清
|
||||
|
||||
@@ -239,6 +314,8 @@ UI 使用“批准”和“继续修改”两个文字按钮,分别配 Lucide
|
||||
|
||||
澄清卡提供独立的选项回答和自由文本回答。点击选项只提交请求身份和所选选项,不携带文本框草稿;Runtime 根据已保存的问题/选项形成明确的回答内容,不只传递“3”之类的序号。用户也可不选择任何选项,直接填写文本并点击“提交回答”,发送请求身份、`optionIndex: null` 和文本正文,Runtime 将其表述为“用户回答”,不将它当作某个选项的补充。新澄清请求出现时清空上一题的文本草稿。不沿用生产旧问题数量上限和固定问答字段。澄清卡待回答状态应可在重启后恢复。
|
||||
|
||||
用户向上滚动查看策划历史时暂停自动跟随;发送新的普通消息后恢复跟随最新消息和回复,仅编辑输入内容不改变历史阅读位置。
|
||||
|
||||
## 8. 用户工作区浏览
|
||||
|
||||
`design_artifacts` 同时是 Agent 工作区和用户查看策划资料的文件区。用户不需要通过聊天请求 Agent 才能看到文件。
|
||||
@@ -307,7 +384,7 @@ UI 使用“批准”和“继续修改”两个文字按钮,分别配 Lucide
|
||||
|
||||
实现落在生产 Rust 会话/工具层与现有 React 策划入口,不随应用再启动 Python 原型进程。保持单一用户入口,不为迁移建设第二套工作台。
|
||||
|
||||
切换前盘点旧 Planning V2 活跃会话和持久化数据。旧结构化 GDD 不能直接推断为新五阶段中的某个阶段,不自动转换或覆盖已有项目。旧会话的继续运行或只读保留方式需在实际盘点后明确,再处理入口退役;这不要求新 Agent 兼容旧 GDD 行为。
|
||||
迁移时不把旧 Planning V2 会话或结构化 GDD 转换为新五阶段状态。旧命令、旧展示和旧测试已删除;当前入口不读取旧 V2 authority,也不要求新 Agent 兼容旧 GDD 行为。
|
||||
|
||||
## 12. 验收标准
|
||||
|
||||
@@ -340,7 +417,7 @@ UI 使用“批准”和“继续修改”两个文字按钮,分别配 Lucide
|
||||
- 外部搜索;
|
||||
- 多个策划身份提示词同时存在;
|
||||
- 多 Agent 协作;
|
||||
- 旧 Fast GDD 展示和 GDD schema 兼容。
|
||||
- 不迁移旧 Fast GDD 展示、V2 schema 或旧测试;新 Design Agent 使用自己的会话和阶段产物测试。
|
||||
|
||||
## 14. 开发调试入口
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# 策划会话 Runtime V2 接入与旧链路退役方案
|
||||
|
||||
- 日期:2026-09-03
|
||||
- 状态:P0 合同冻结、P1 内核、P2 产物闭环、P3 入口/UI 接入、P4 灰度回归验收与 P5 旧链路退役均已完成;本文是新生产实现的目标方案与阶段验收合同
|
||||
- 适用范围:AGC 桌面 App 的“做方案”入口、策划会话、GDD 产物与审批
|
||||
- 状态:**历史方案,已完成并退役**。Runtime V2 及其专用入口、命令、展示和测试已在 2026-09 按四不写原则删除;当前“做方案”统一使用独立 Design Agent。
|
||||
- 适用范围:历史 AGC“做方案”入口、策划会话、GDD 产物与审批设计
|
||||
|
||||
> 本文规定新策划 Agent 的生产接入和旧链路退役方式。P3 开始修改正式 AGC 入口与工作台,但旧 `project-supervisor-plan` / `project-planning` 源码仍保留,直到 P5 完成退役;V2 切换时旧链路直接封存,所有未完成旧会话强制失败,旧 Fast GDD 文档之后只作为历史记录依据。
|
||||
> 本文只用于追溯 Runtime V2 的设计和退役过程,不是现行实现依据。不要恢复 `planning_session_v2`、`planning_policy_v2`、`hydrate_planning_session_v2` 或 V2 专用 UI;当前行为以 Design Agent 生产迁移方案和代码为准。
|
||||
|
||||
## 1. 决策摘要
|
||||
|
||||
@@ -586,13 +586,13 @@ hydrate_planning_session_v2
|
||||
|
||||
### 7.3 UI 复用边界
|
||||
|
||||
第一版可复用现有:
|
||||
策划会话的入口容器、审批卡与工作台布局已经落地在当前模块结构里:
|
||||
|
||||
- `ProjectSupervisorView` 的聊天区域和工作台布局;
|
||||
- `GddApprovalCard` 的产物展示与审批交互;
|
||||
- 现有耗时展示和会话历史加载。
|
||||
- `view/project-development/planning/PlanningChatView.tsx`:策划会话容器,只消费 V2 状态;
|
||||
- `view/project-development/planning/GddApprovalCard.tsx` 与 `PlanningLaneRuntimeStrip.tsx`:产物展示、审批交互与窄条运行态;
|
||||
- 现有耗时展示和会话历史加载继续复用。
|
||||
|
||||
但数据来源必须改为 V2 状态,不再把“页面组件叫 Supervisor”当作运行时身份。后续再把组件重命名为 `PlanningSessionView`,不作为本次切换前置。
|
||||
数据来源只有 V2 状态;旧 `project-supervisor-plan` 运行时不参与页面渲染,也不再作为组件身份。
|
||||
|
||||
## 8. 最小安全与业务校验
|
||||
|
||||
|
||||
@@ -0,0 +1,269 @@
|
||||
# Responses API 与 Agents SDK 迁移评估
|
||||
|
||||
更新时间:`2026-09-07`
|
||||
|
||||
## 结论
|
||||
|
||||
本次评估不建议把 Genarrative 的核心 Agent Runtime 整体迁移到 OpenAI Agents SDK。建议保留 `platform-llm` 的 Responses / Chat / Anthropic 协议适配、Tauri/Rust Runtime、项目文件与进程工具、审批、权限与沙箱、持久会话、任务图、重试、恢复、计费和现有 Codex app-server 路径。
|
||||
|
||||
可以保留一个有明确边界的 Agents SDK 试验:只在可信的服务端或受控 Tauri bridge 中,为 Project Supervisor / 专业 Agent 的只读交互聊天提供可选的 `Agent + Runner` 编排、只读专家路由和统一 tracing。该试验必须通过现有 Runtime 工具网关和事件投影,不能让 SDK 直接读写项目、启动进程、提交媒体任务、扣费或持有正式状态。
|
||||
|
||||
理由是当前系统已经拥有比 SDK 默认能力更严格的业务控制面。技术方案已经明确规定 AGC Runtime 由 Genarrative 自己掌控,不把 OpenAI Agents SDK sidecar 作为核心,只借鉴 Agent、Tools、Handoffs、Guardrails、Tracing 抽象(见 `docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md` 的“技术选择”)。OpenAI 官方 Agents 文档也把 SDK 定位为建立在 Responses API 之上的编排层:SDK 用 `Agent` 和 `Runner` 管理 turns、tools、guardrails、handoffs 和 sessions;如果应用自己拥有这套循环,直接使用 Responses API 是合理路径。[Agents SDK Agents 指南](https://openai.github.io/openai-agents-python/agents/)
|
||||
|
||||
## 现状盘点
|
||||
|
||||
### Responses 协议层
|
||||
|
||||
`server-rs/crates/platform-llm/src/lib.rs` 定义了协议中立的 `LlmRunRequest` / `LlmRunResponse`。请求包含模型、system/user/assistant 消息、图像输入、`max_output_tokens`、web search、reasoning effort、text verbosity、function tools 和 `tool_choice`(约第 167–194 行)。默认 `LlmApiKind` 是 `openai_responses`,同时显式支持 `openai_chat` 和 `anthropic`。
|
||||
|
||||
Responses 请求在 `build_request_body` 中映射到 `/responses`,包括 `input`、`stream`、`max_output_tokens`、原生 `web_search`、function tools、`tool_choice`、`reasoning.effort` 和 `text.verbosity`(约第 2321–2404 行)。消息映射保持 Responses 的角色差异:system/user 文本使用 `input_text`,assistant 文本使用 `output_text`,图像使用 `input_image`(约第 2895–2973 行)。
|
||||
|
||||
非流式响应会从 `output_text` 和 `function_call` 中恢复正文、工具调用、usage、finish reason 和 response id。`LlmClient::run` 负责请求验证、HTTP 执行、响应读取和协议解析(约第 1550–1585 行)。
|
||||
|
||||
流式 `LlmClient::stream_run` 自己维护 SSE 解析、UTF-8 分块、Responses `response.output_text.delta`、`response.output_item.added`、`response.function_call_arguments.delta/done`、`response.completed` / `response.incomplete`、错误和尾部异常处理(约第 1596–1869 行)。工具调用按 Responses `output_index` 聚合,完整参数由终态事件校正;工具分片没有完成信号时会失败关闭;只把文本增量和 finish reason 交给上层,工具调用留在最终 `LlmRunResponse.tool_calls`。
|
||||
|
||||
`server-rs/crates/platform-llm/src/provider_adapter.rs` 把该 client 接入 `agent-runtime-core` 的 Provider registry。Responses 适配器宣告 Streaming、FunctionTools、RequiredToolChoice、ImageInput、WebSearch、ReasoningEffort 和 TextVerbosity 能力。`platform-llm/README.md` 明确规定该 crate 只负责文本协议适配,不承接上下文、后台执行、业务状态或工具副作用。
|
||||
|
||||
### AGC 的生成工作流
|
||||
|
||||
`apps/ai-game-creator-shell/src-tauri/src/agent/generation/loop_orchestration.rs` 仍保留一条文件驱动的确定性生成链:
|
||||
|
||||
1. Planner 读取用户需求、短期/长期记忆和项目黑板,生成 `.agent/spec.md`。
|
||||
2. Orchestrator 根据 spec、findings 和 manifest 生成 agenda / task graph。
|
||||
3. 按依赖 wave 并行生成设计、美术、程序等角色 brief,并写入本轮 pass 产物。
|
||||
4. Generator 根据 spec、findings、brief、素材和 agenda 生成结构化游戏草稿。
|
||||
5. Evaluator 写入 `.agent/findings.md`,失败时进入下一 pass 的修复范围。
|
||||
6. 产物写入、静态检查和试玩验证由 Runtime 完成,而不是由模型回复宣称完成。
|
||||
|
||||
Planner / Generator 使用独立的 system/user prompt 和严格结构化输出。Generator 选择 `run` 或 `stream_run`,但 legacy 生成 loop 的 streaming callback 为空,流式主要用于传输和容错;Generator 对 `EmptyResponse` 有有界重试,随后解析和校验 JSON。
|
||||
|
||||
这条链路的任务图、文件写入、Evaluator 反馈和产物门禁具有明确业务语义,不适合交给一个 SDK run loop 隐式管理。
|
||||
|
||||
### 后台 Runtime tool-plan 工作流
|
||||
|
||||
`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/provider_request_builders.rs` 和 `provider_tool_plan.rs` 为每个后台 turn 动态构造请求:
|
||||
|
||||
- 读取当前 Agent、Session、Run、Goal Contract、Acceptance Graph、黑板、项目摘要、受控观察和运行中追加指令。
|
||||
- 按 Agent 身份、run profile、项目阶段、审批策略和验证门动态收窄工具目录。
|
||||
- 以严格 JSON Schema function tools 暴露 `file.*`、`project.*`、`command.*`、`preview.*`、`canvas.*`、`asset.*`、`memory.*`、`task.*`、`agent.*`、`goal_contract`、`acceptance_update`、`update_agent_plan` 和 `respond_to_user` 等能力。
|
||||
- 普通 Runtime tool-plan 要求模型直接调用白名单函数,并将计划更新、工具动作、观察和最终回复拆成可持久化的协议阶段。
|
||||
- 工具调用解析后进入 `agent/runtime_tools/*`,再经过权限、路径、项目写锁、确认、revision、验证和 receipt 门禁;模型不能直接执行副作用。
|
||||
- Provider 成功结果先写入 handoff / lifecycle / Agent DB,再由 Runtime 消费;重复、迟到、身份冲突或无法确定终态时进入 `needs-reconciliation`,不自动重放。
|
||||
|
||||
`agent_native_tools.rs` 生成严格工具 schema,工具策略与动态目录由 Runtime 控制。当前工具目录和工具执行已经是领域合同,不是简单的 JSON function-call wrapper。
|
||||
|
||||
### 交互聊天和流式行为
|
||||
|
||||
交互层在 `apps/ai-game-creator-shell/src-tauri/src/agent/interaction.rs` 中使用一个受限 interaction kernel:普通问答直接回复,只有确实需要宿主持久能力时才调用一个 function tool;高影响请求先澄清,不擅自启动 Runtime。它复用现有 Provider registry,流式异常可按稳定错误类型回退一次非流式请求。
|
||||
|
||||
Tauri command `chat_with_game_creator_role_agent_stream` 会先写入用户消息,再发出 `started` 事件,随后发出带 `delta_text`、`accumulated_text` 和 `finish_reason` 的 `delta` 事件,最终发出 `completed` 或 `failed`(`apps/ai-game-creator-shell/src-tauri/src/commands.rs` 约第 1199–1335 行)。React 侧按项目、Agent、Run 和加载代次过滤迟到事件,以 `requestAnimationFrame` 合并正文,失败时保留已接收的完整正文。
|
||||
|
||||
后台 final reply 还使用 `AgentRuntimeResponseStreamPublisher` 将流持久化到 `.agent/runtime/response-streams/<hashed-agent>/<hashed-run>.json`。记录绑定 Agent/Task/Session/Run、request slot、steer cursor、响应 revision、sequence、status、正文和 finish reason,并支持 `streaming → ready → committed/failed/discarded` 的恢复和去重。SDK 流必须先转译到这一合同,不能绕过它直接推送 UI。
|
||||
|
||||
### Codex app-server 路径
|
||||
|
||||
AGC 当前默认路线可以是 `codex_app_server`。`codex_app_server.rs` 通过 JSON-RPC `turn/start` / `turn/completed` 管理独立 thread/turn,消费 agent message、activity、item、MCP 工具和终态事件,并把增量正文映射回 `LlmStreamDelta`。`config.rs` 要求该模式使用 `apiKind=openai_responses`。
|
||||
|
||||
这条路径已经是另一种 agentic transport,且包含 workspace mode、进程生命周期、MCP 审计、硬超时和 passive-item 边界。它不能与 Agents SDK 直接互换;任何试验都必须保留该模式及其回滚能力。
|
||||
|
||||
### API Server、Router 和其他调用方
|
||||
|
||||
`server-rs/crates/api-server/src/llm/mod.rs` 提供账号鉴权的 `/api/llm/responses` 代理:客户端只传平台 access token,服务器解析账号 Router credential、模型目录和计费,删除客户端的 `apiKey`、`baseUrl`、provider、api kind、agent mode 等控制字段,再将 `stream`、`input`、`tools`、`metadata` 转发到 Responses endpoint。流式代理等待 `response.completed` / `response.incomplete` 后才结算额度。
|
||||
|
||||
`platform-editor-agent` 仍有自己的 `Agent`/memory/tool harness,但其编辑器对话目前固定使用 OpenAI Chat;图片、音频、UI 识别、画布生成等 helper 具有各自的严格结构化合同。它们不应因为 AGC 聊天试验而一起迁移。
|
||||
|
||||
## Prompt、工具和状态边界
|
||||
|
||||
### Prompt 分类
|
||||
|
||||
| 类别 | 当前入口 | 主要约束 | 迁移判断 |
|
||||
|---|---|---|---|
|
||||
| Planner / Generator | `generation/prompt_context.rs`、`loop_orchestration.rs` | 严格 JSON、文件产物、Evaluator pass、模型输出不能伪造写入/验证 | 保留 Responses / 当前编排 |
|
||||
| Runtime tool-plan | `runtime_actions/provider_request_builders.rs`、`prompt.rs` | 动态工具目录、身份策略、Goal/Acceptance、动作数量、确认和完成门 | 保留 Rust Runtime;SDK 只可作为未来只读 adapter |
|
||||
| Final reply | `provider_final_reply.rs`、`provider_request_builders.rs` | 只能依据 observation,不能声称未发生副作用;规划 Agent 有固定信封 | 保留现有路径,若试验仅转译文本事件 |
|
||||
| Interaction chat | `interaction.rs`、`prompt.rs` | 普通问答与单宿主工具决策;可流式;适合路由/澄清 | 唯一优先试验边界 |
|
||||
| Editor / media helpers | `platform-editor-agent`、`api-server/llm`、各媒体 prompt | Chat/Responses 混合、严格 JSON、计费和外部副作用 | 保留现有协议和专用 harness |
|
||||
|
||||
### 工具、委派和 handoff 的语义差异
|
||||
|
||||
当前 `agent.delegate` / isolated join 是持久任务调度动作,包含 parent/child ID、delivery、认领、all-join、恢复和完成门;Provider handoff sidecar 是跨 Runner 边界保存一次成功 Provider 响应。这两者都不等于 Agents SDK 的 handoff。
|
||||
|
||||
Agents SDK 的 handoff 是模型可见的工具式路由:一次 handoff 后由专业 Agent 接管同一 run 的对话;SDK 文档还区分了“manager + agents as tools”和“handoffs”。[Agents 组合模式](https://openai.github.io/openai-agents-js/guides/agents/)
|
||||
|
||||
因此:
|
||||
|
||||
- Supervisor 的只读问答可以使用 manager + `agent.asTool()`,保留 Supervisor 作为唯一面向用户的回复所有者。
|
||||
- 需要真正转移对话控制的只读专家咨询才考虑 SDK handoff,并通过 `inputFilter` 限定传入历史。
|
||||
- 任何涉及文件、命令、进程、媒体、审批、项目 revision、manifest、验证或 Git 的意图,必须先转成 Runtime 可验证的动作/委派请求,由 Rust 决定是否允许;SDK handoff 不能创建正式 child Run,也不能绕过 `agent.delegate`。
|
||||
|
||||
### 状态管理
|
||||
|
||||
当前正式状态由 `.agent/conversations/**/*.jsonl`、`.agent/runtime/**/*.json`、Agent DB、任务队列、manifest/revision、handoff/retry/confirmation ledger 和 finalization journal 组成。`AgentRuntimeProviderRequestSnapshot` 绑定 project/agent/task/session/run/source、goal revision/fingerprint、steer cursor、request kind/slot、web search 和 planning session binding。
|
||||
|
||||
Agents SDK 可以使用 `Session`、`conversationId`、`previousResponseId` 或 `RunState`,但官方文档提醒不要把多种历史策略叠加使用。[Agents SDK Sessions](https://openai.github.io/openai-agents-js/guides/sessions/)
|
||||
|
||||
本项目即使引入试验,也只能选择一个“临时模型上下文”策略;`.agent` 对话、Runtime sidecar 和 Agent DB 仍是唯一正式事实源。SDK session 不拥有 project revision、工具 action、审批、billing、retry、resume 或 finalization。重启恢复必须先恢复 Genarrative sidecar,再决定是否重建 SDK run。
|
||||
|
||||
## 是否需要更 agentic 的架构
|
||||
|
||||
核心 Runtime 已经具备更 agentic 的架构,而且它比通用 SDK loop 多了本项目所需的持久治理:
|
||||
|
||||
- 工具:严格 schema、身份目录、按角色/阶段动态收窄、auto/confirm/deny、项目锁和 revision。
|
||||
- 交接:静态专业 Agent、动态 isolated child、delivery receipt、all-join、恢复和 parent run 收束。
|
||||
- Guardrails:路径/文件类型、sandbox、权限、确认、Goal Contract、Acceptance Graph、验证 gate、最终回复门禁和敏感信息脱敏。
|
||||
- Tracing / audit:run trace、event、Agent DB、tool receipt、Provider lifecycle、request fingerprint 和 response stream sidecar。
|
||||
- Streaming:Responses SSE / Codex item events → Runtime stream publisher → Tauri events → 前端按 identity/sequence/revision 去重。
|
||||
- 状态:session catalog、JSONL conversation、Runtime state、task queue、retry/handoff sidecar、context compaction 和 crash recovery。
|
||||
|
||||
因此,整体迁移只会把已有能力复制一遍,带来两套 turn loop、两套状态、两套重试和两套工具授权;收益不足以抵消安全和恢复风险。
|
||||
|
||||
Agents SDK 对局部交互仍有增量价值:
|
||||
|
||||
| 能力 | 在本项目的真实收益 | 可接受边界 |
|
||||
|---|---|---|
|
||||
| Agent loop | 减少只读聊天中手写单轮/多轮和路由 glue code | 只用于交互聊天或只读专家问答 |
|
||||
| Tools | 统一工具参数解析、工具结果与模型 turn | function tool 只调用 Rust RPC 的窄只读 facade |
|
||||
| Handoffs / agents-as-tools | 更容易表达 Supervisor→专家的语言路由 | 不创建正式 Runtime child;推荐 manager + agents-as-tools |
|
||||
| Guardrails | 可增加聊天输入/输出格式和工具输入校验 | 不能替换 Rust 权限、确认、sandbox、revision gate |
|
||||
| Streaming | 提供 raw model、run item、agent updated 等统一事件 | 先转成现有 response-stream 记录,必须等待 `stream.completed` |
|
||||
| Tracing | 补充 generation/tool/handoff/guardrail latency 和 token span | 关联现有 run IDs,关闭敏感数据或自定义脱敏 processor |
|
||||
|
||||
官方 Streaming 文档明确说明 `toTextStream()` 只给正文,工具、handoff、approval 等需要消费完整事件流;流结束后还可能进行 session 持久化或 compaction,因此必须等待 `completed`。[Agents SDK Streaming](https://openai.github.io/openai-agents-js/guides/streaming/)
|
||||
|
||||
官方 Tracing 文档说明默认会记录 run/task/agent/turn、generation、function tool、guardrail 和 handoff spans,并支持自定义 trace processor;这对排查延迟和工具路由有价值,但也意味着 prompt 和工具数据必须显式设置敏感数据策略。[Agents SDK Tracing](https://openai.github.io/openai-agents-js/guides/tracing/)
|
||||
|
||||
## 推荐架构边界
|
||||
|
||||
```text
|
||||
Tauri / React
|
||||
│ 现有 invoke + Tauri events
|
||||
▼
|
||||
Genarrative Rust Runtime(正式事实源)
|
||||
├─ session / conversation / task / revision / approval / recovery
|
||||
├─ tool policy / sandbox / filesystem / process / media / billing
|
||||
├─ existing response-stream sidecar and Agent DB
|
||||
└─ Provider seam
|
||||
├─ current LlmClient + platform-llm (Responses/Chat/Anthropic)
|
||||
├─ Codex app-server (existing route)
|
||||
└─ optional Agents SDK adapter (experimental, read-only chat only)
|
||||
│
|
||||
└─ trusted Node/TS process or Tauri bridge; never browser bundle
|
||||
```
|
||||
|
||||
可选 adapter 的职责应是:接收 Rust 生成的已脱敏 prompt/context snapshot;创建有限的 `Agent`、工具和可选只读专家;调用 `run(..., {stream: true})`;把 SDK event 转成现有 `ProviderStreamEvent` / `LlmStreamDelta` / Runtime observation;将最终文本和工具意图返回 Rust。Adapter 不得读取项目绝对路径、凭据、签名 URL 或任意环境变量,也不得自行重试有副作用的工具。
|
||||
|
||||
建议在试验中优先使用 manager + agents-as-tools,而不是让 SDK handoff 成为对话所有权切换。这样 Supervisor 仍是唯一的用户回复所有者,SDK 专家只返回短的只读咨询结果。若确实需要 handoff,必须由 Rust 提前给出允许目标集合,并在 SDK handoff callback 中再次校验目标和输入;模型传入的 routing metadata 只能作为受限说明,不能改变权限或项目目标。
|
||||
|
||||
## 模型与 Provider 建议
|
||||
|
||||
OpenAI 官方模型页当前建议:复杂推理和编码优先 GPT-6 Astra,平衡能力与成本选择 GPT-5.6 Terra,高量低成本选择 GPT-5.6 Luna;最新模型可通过 Responses API 和 Client SDK 使用。[OpenAI Models](https://developers.openai.com/api/docs/models)
|
||||
|
||||
这不构成一次模型迁移任务:AGC 当前已有官方 Router / catalog alias 和每 Agent 配置,官方 route 已使用 `gpt-6-astra` 语义,客户端不能绕过服务器模型目录直接选择任意模型。迁移试验应冻结当前已配置模型和 reasoning/verbosity,先测编排差异;若要对 GPT-5.6 Terra/Luna 或其他新模型做基准,应作为独立模型评估,不与 SDK 迁移同时变更。
|
||||
|
||||
Agents SDK 试验只适合经过验证的 OpenAI Responses 路径。`openai_chat`、`anthropic`、VectorEngine/Ark 等兼容网关继续走 `platform-llm`,不要假设它们支持 SDK 的 hosted tools、server-managed conversation、Responses WebSocket 或完整事件形状。若自定义 base URL 不能稳定返回 function tool 和 SSE 终态事件,直接回退现有 Provider adapter。
|
||||
|
||||
## 测试与文档建议
|
||||
|
||||
现有测试应作为不可削弱的迁移门禁:
|
||||
|
||||
- `cargo test -p platform-llm --manifest-path server-rs/Cargo.toml`:Responses body、tool schema、图像输入、非流式 function call、SSE text/tool 聚合、completed/incomplete 恢复、参数截断、尾部错误、超时和重试。
|
||||
- `cargo test -p platform-agent-harness --manifest-path server-rs/Cargo.toml`:staged memory 提交/回滚、非法 JSON 修复、deadline、顺序工具和确认。
|
||||
- `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml provider -- --test-threads=1`:Provider、stream fallback、工具计划、retry、handoff、redaction 和 context compaction。
|
||||
- `apps/ai-game-creator-shell/src-tauri/src/tests/response_stream.rs` 及 `runtime_actions/response_stream_tests.rs`:stream sidecar 的 identity、sequence、reconnect、commit、discard、steer 和恢复。
|
||||
- `apps/ai-game-creator-shell/tests/appSurface/response-stream.suite.ts`、conversation/session、supervisor runtime、handoff、retry、runner-kill suites:UI 状态和真实恢复合同。
|
||||
- 现有 deterministic E2E 和 opt-in live Provider smoke 继续保留;live 测试不能成为默认 CI 依赖。
|
||||
|
||||
如果实施 SDK 试验,再增加:
|
||||
|
||||
1. **差分 fixture**:同一脱敏输入同时跑现有 Responses adapter 和 SDK adapter,比较最终文本、tool name/arguments、finish reason、usage、模型和错误分类;不执行真实副作用。
|
||||
2. **事件转译合同**:覆盖 raw model delta、message output、tool called/output、agent updated、handoff requested/occurred、approval interruption、completed 的顺序、重复和取消。
|
||||
3. **持久流恢复**:中途进程退出、tail error、重复事件、sequence 回退、steer cursor 变化和 finalization 竞态,都必须回到现有 sidecar 语义。
|
||||
4. **Session/RunState 隔离**:证明 SDK session/RunState 不能替换 `.agent/conversations`、`.agent/runtime` 和 Agent DB;恢复后不得重放已提交 file/media/process/billing action。
|
||||
5. **工具授权和幂等**:未经 Rust policy 允许的 tool/handoff 被拒;retry、handoff、approval resume 不产生重复副作用。
|
||||
6. **Tracing 脱敏**:trace processor 中不存在 API key、Cookie、签名 URL、绝对路径、原始工具参数、项目源码和用户私密正文;trace/group/workflow ID 可关联现有 project/session/run/agent ID。
|
||||
7. **兼容网关能力探测**:分别验证 HTTP Responses、function tools、SSE completed/incomplete、图像输入和错误事件;不因模型名推断 endpoint 能力。
|
||||
8. **回滚差分**:SDK 失败、超时、unsupported event、handoff 拒绝和 stream 取消都回到现有 `provider` 或 `codex_app_server`,且响应和 conversation 只落一次。
|
||||
|
||||
文档建议在真正开始试验时再更新:
|
||||
|
||||
- 为 AGC 增加 setup README:只在可信 Node/TS 进程或 bridge 安装并 pin `@openai/agents` 与 `zod`;说明 custom base URL、HTTP Responses transport、AppData credential、tracing key 与禁用/脱敏策略。SDK 不得打进浏览器 bundle,不能使用 Vite 暴露的 API key。
|
||||
- 更新 `docs/【开发运维】本地开发验证与生产运维-2026-05-15.md` 的 LLM/Responses 段,补充 adapter feature flag、custom provider 能力限制、trace privacy、canary 与 rollback 命令。
|
||||
- 更新 `server-rs/crates/platform-llm/README.md`,声明 SDK 若加入只位于协议层之上的编排 adapter,Responses/Chat/Anthropic parser 和错误/stream 合同仍由 `platform-llm` 负责。
|
||||
- 新增迁移 runbook,写明 `.agent` 状态不迁移、不删除、不由 SDK 接管,测试矩阵和恢复证据要求。
|
||||
|
||||
## 分阶段迁移计划
|
||||
|
||||
### Phase 0:合同冻结(先做)
|
||||
|
||||
- 固定当前 `LlmRunRequest`、有效工具 schema、prompt bundle、model/api kind、reasoning/verbosity、stream 开关、retry/backoff、request fingerprint 和错误分类的 golden fixture。
|
||||
- 固定现有 response-stream sidecar 的 schema、identity、sequence、status、revision、steer cursor 和 finalization 行为。
|
||||
- 固定 `platform-llm`、AGC provider tests、response-stream tests、supervisor/handoff/recovery E2E 为 baseline。
|
||||
- 明确试验只允许 `provider` 的 OpenAI Responses 交互聊天;`codex_app_server`、Chat、Anthropic、legacy Planner/Generator 和项目副作用不进入首轮。
|
||||
|
||||
退出条件:所有 baseline 通过,且没有任何 SDK 依赖或状态格式变更。
|
||||
|
||||
### Phase 1:受控 SDK adapter(开发环境)
|
||||
|
||||
- 在受信任的 Node/TS 服务或 Tauri bridge 中增加可选 adapter;不要在 React/browser bundle 中调用 SDK。
|
||||
- Adapter 只收 Rust 传来的脱敏 context snapshot,使用 `Agent`、窄只读 function tools 和 manager-style `agents as tools`。
|
||||
- Rust 继续分配 project/session/run/request slot 和 action identity;SDK 的 run id、session 或 trace id 只作关联元数据。
|
||||
- 所有 tool function 必须调用 Rust RPC 的只读 facade;写文件、命令、进程、媒体、外部 editor、审批、billing 和 Git 均拒绝。
|
||||
- Adapter 将 SDK stream events 映射成既有 response-stream publisher 输入,等待 `stream.completed` 后才返回最终结果。
|
||||
|
||||
退出条件:没有 SDK 直连项目文件/凭据;只读聊天的 text/tool/stream parity fixture 通过;中止和重复事件不破坏 sidecar。
|
||||
|
||||
### Phase 2:影子差分与 tracing bridge
|
||||
|
||||
- 对脱敏的历史聊天 fixture 同时运行 current Provider 和 SDK adapter,但 SDK 侧不执行工具副作用。
|
||||
- 记录 final text、tool intent、事件延迟、首 token 延迟、token usage、错误类型、重试次数和取消行为,不记录原始 secret/prompt/tool payload。
|
||||
- SDK tracing 使用稳定 workflow name,例如 `genarrative-agc-interaction`;group id 绑定 session,metadata 只放哈希后的 project/agent/run/request identity。默认关闭敏感数据,或用自定义 processor 做字段级脱敏,并在任务结束 flush。
|
||||
- 统计差异而不是立即替换当前路径:结构化工具意图必须相同,文本差异需人工抽样,SDK 不得增加重复请求或扣费。
|
||||
|
||||
退出条件:在代表性 fixture 上,SDK 没有重复副作用、身份错配、公共信息泄漏、流式重复和不可解释的额外成本;否则停止试验。
|
||||
|
||||
### Phase 3:小流量可选 canary
|
||||
|
||||
- 用按 Agent / 项目 / build 的 feature flag 只开放给开发调试入口或内部用户。
|
||||
- 运行时检测 custom endpoint 是否支持 function tools、SSE terminal event 和目标 Responses 字段;不支持就选择旧 Provider。
|
||||
- SDK stream、handoff、approval 或 tracing 任一异常时,按“尚未产生副作用”条件回退;若已经产生 Rust action,必须先以 action ledger / reconciliation 处理,禁止盲目重试。
|
||||
- 保持同一个 UI event 和 conversation projection,不让用户看到两种状态模型。
|
||||
|
||||
退出条件:canary 的成功率、首 token 延迟、终态延迟、工具意图一致率、恢复成功率、重复副作用数、泄漏数和成本达到事先设定门槛,并连续多个版本稳定。
|
||||
|
||||
### Phase 4:仅在数据支持时扩大
|
||||
|
||||
最多把只读 Supervisor/role chat 扩到更多内部聊天场景。不要因此迁移:
|
||||
|
||||
- Planner→Generator→Evaluator 文件工作流。
|
||||
- 自主构建和正式 Runtime tool-plan。
|
||||
- `agent.delegate`、isolated child、all-join、DAG 和 manifest scheduler。
|
||||
- 文件、命令、进程、媒体、External Editor、Git、preview 和审批工具。
|
||||
- `platform-llm` 的 Chat/Anthropic/兼容网关和 API-server Router/billing。
|
||||
- Codex app-server DirectProject 路径。
|
||||
|
||||
只有在未来需求明确要求 SDK 的原生能力,并能证明它不会与上述合同冲突时,才另立架构决策评估核心迁移;本报告不预设该迁移。
|
||||
|
||||
## 回滚说明
|
||||
|
||||
- 首要回滚是关闭 feature flag,将该 Agent route 指回现有 `provider` 或 `codex_app_server`;不改 `.agent` 对话、Runtime state、Agent DB、manifest、revision 或 receipt schema。
|
||||
- SDK adapter 只允许生成可丢弃的临时 run/trace 记录。关闭后这些记录不参与恢复和最终回复;已有 Rust response-stream / finalization 记录继续按原逻辑收口。
|
||||
- 如果 SDK 已返回 tool intent 但 Rust 尚未提交 action,直接丢弃该意图并重新走旧 route;如果 Rust 已提交 action,必须按现有 receipt、ledger 和 reconciliation 恢复,不能用 SDK 重试覆盖。
|
||||
- 不通过自动降级重复调用可能产生计费或媒体副作用的请求。重试仍由现有 Runtime retry identity、attempt、backoff 和 provider handoff 控制。
|
||||
- 不把 SDK Session、Conversation ID、Previous Response ID 或 trace export 当作恢复依据;它们失效时不影响本地正式状态。
|
||||
|
||||
## 验证清单
|
||||
|
||||
- [ ] `platform-llm` 的 Responses request/response/SSE/tool parser 全量通过。
|
||||
- [ ] Chat、Anthropic、Codex app-server 和 legacy Planner/Generator 路径行为未变化。
|
||||
- [ ] SDK adapter 只在可信服务端/bridge 运行,浏览器 bundle 和前端环境变量没有 SDK key。
|
||||
- [ ] current Responses 与 SDK adapter 的 prompt、tool schema、tool choice、model、reasoning、verbosity 和 output budget 差分通过。
|
||||
- [ ] SDK stream event → `LlmStreamDelta` / `ProviderStreamEvent` → response-stream sidecar 的映射通过。
|
||||
- [ ] `started → delta → completed/failed` Tauri event 合同、sequence/revision/steer/session/run identity 和 UI 去重通过。
|
||||
- [ ] `stream.completed`、session persistence、approval interruption 和 cancellation 都被正确等待/收口。
|
||||
- [ ] SDK tool/handoff 不能绕过 Rust policy、confirmation、sandbox、project lock、revision、billing 或 finalization gate。
|
||||
- [ ] handoff/agents-as-tools 只作用于只读咨询;正式 `agent.delegate` 和 isolated join 仍由 Rust scheduler 管理。
|
||||
- [ ] 进程退出、网络尾错、重复事件、Runner kill、retry、steer 和 resume 不产生重复副作用或重复 assistant message。
|
||||
- [ ] trace 关联到现有 project/session/run/agent identity,敏感 prompt、路径、源码、凭据和 tool args 均不出现在 trace/export/log。
|
||||
- [ ] custom Responses endpoint 的 function tools、SSE terminal event、图像输入和错误事件能力已显式探测。
|
||||
- [ ] canary 指标满足门槛;若不满足,feature flag 可立即切回旧 route。
|
||||
- [ ] 文档已更新 setup、tracing privacy、provider capability、测试矩阵和 rollback runbook。
|
||||
@@ -7,12 +7,12 @@
|
||||
|
||||
## 1. 验收基线的先决条件
|
||||
|
||||
| 事实 | 依据 | 对验收的影响 |
|
||||
| --- | --- | --- |
|
||||
| 基线 commit 为 `ea9668e4a`(2026-09-11 15:37) | `git log -1`:`ea9668e4a`「资源总览「所有资源」卡补上资源预览」 | 验收记录表回填此 hash 作为冻结基线 |
|
||||
| 事实 | 依据 | 对验收的影响 |
|
||||
| --------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| 基线 commit 为 `ea9668e4a`(2026-09-11 15:37) | `git log -1`:`ea9668e4a`「资源总览「所有资源」卡补上资源预览」 | 验收记录表回填此 hash 作为冻结基线 |
|
||||
| 三个验收重点改动已入库,但**必须重新构建 / 重启客户端才会生效** | `2a157ea6f`「预览可见性门禁等 root 就绪再建 observer,并把登记表补挂齐」、`6bdc8bbd9`「AGC 资源分类面板改为「编辑素材标签」:只编辑标签、分类不再有手动入口」、`ea9668e4a`「资源总览「所有资源」卡补上资源预览」 | ① 可见卡停 `idle` 的修复;② 「编辑标签」只改 tags;③ 「所有资源」卡渲染真实预览。客户端若仍在跑旧构建(真机当前进程即属此列),这三条会得到**假失败** |
|
||||
| 工作区仍有 1 个非本用例范围的文件处于修改态 | `git status --short`:`apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts`(3 insertions / 2 deletions) | 只影响 AGC 全量测试结果,不影响手工验收;该文件相关用例失败时先与基线比对再定性 |
|
||||
| 真机项目落盘分类分布与旧构建指纹一致 | 真机 `.agent/manifest.json` 落盘 `category` = `unclassified 57 / ui-interaction 3 / scene 1` | 未用新构建复测时,卡片预览、标签面板、所有资源预览三条会得到**假失败** |
|
||||
| 工作区仍有 1 个非本用例范围的文件处于修改态 | `git status --short`:`apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts`(3 insertions / 2 deletions) | 只影响 AGC 全量测试结果,不影响手工验收;该文件相关用例失败时先与基线比对再定性 |
|
||||
| 真机项目落盘分类分布与旧构建指纹一致 | 真机 `.agent/manifest.json` 落盘 `category` = `unclassified 57 / ui-interaction 3 / scene 1` | 未用新构建复测时,卡片预览、标签面板、所有资源预览三条会得到**假失败** |
|
||||
|
||||
> 结论:验收开始前先固化"客户端构建版本 + api-server 是否重启 + 基线 commit"三件事,否则后续结论不可信。
|
||||
|
||||
@@ -28,72 +28,72 @@
|
||||
|
||||
### A 阶段 · 从零进项目
|
||||
|
||||
| 步骤 | 操作 | 期望结果 | 对应 PRD 条款 | 怎么判"过了"(可观察判据) | 已知例外 / 未做项 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| **S1** 建项 | 首页「做游戏」→ 输入需求 → 选择目录 → 确认为新项目 | 建项成功并直接进入项目工作台,不新建第二套页面或资产系统 | L7(四区)、L23(创作链路)、L108 | 进入后四区同时在 DOM:左侧平台导航、`.game-workbench-stage`(`aria-label="项目主视窗"`)、右侧 Supervisor 对话栏、底部 Agent Dock | 普通用户「新增资源」入口禁用(L108、L587) |
|
||||
| **S2** 落地视图 | 进项目后不动鼠标,看中央区 | 落 `resource-overview`;manifest 里 `preview.status=running` 的**陈旧记录**不得把你直接丢进运行界面 | L145–146、**L165**(`run` 自动进入只认内存 registry 活体确认) | `document.querySelector('.game-workbench-stage')?.dataset.resourceViewState` ∈ `resources.list` / `resources.selected.<category>` / `resources.ui-editor`;**该属性为 `undefined` 即说明已在 run 视图** | 陈旧记录对齐:命中"记录 running 但 registry 没跑"时调 `stop_local_game_preview` 并按真相投影 `{status:'stopped'}`(`sessionPreview.ts:87-101`) |
|
||||
| **S3** 项目页面板(可跳过) | 「项目组」页:搜索、行尾更多菜单、Escape 关闭 | 表格只显示权威数据列;Escape 关菜单并把焦点还给触发按钮;移除只改本机最近项目记录、不动磁盘 | L135–139 | 无匹配状态提供「清除搜索」;document/body 无页面级横纵向溢出 | **L139 要求 populated fixture 截图 + 视频演示搜索/清除/行尾菜单** —— 视频条款,见 7.1 |
|
||||
| 步骤 | 操作 | 期望结果 | 对应 PRD 条款 | 怎么判"过了"(可观察判据) | 已知例外 / 未做项 |
|
||||
| --------------------------- | -------------------------------------------------- | --------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **S1** 建项 | 首页「做游戏」→ 输入需求 → 选择目录 → 确认为新项目 | 建项成功并直接进入项目工作台,不新建第二套页面或资产系统 | L7(四区)、L23(创作链路)、L108 | 进入后四区同时在 DOM:左侧平台导航、`.game-workbench-stage`(`aria-label="项目主视窗"`)、右侧 Supervisor 对话栏、底部 Agent Dock | 普通用户「新增资源」入口禁用(L108、L587) |
|
||||
| **S2** 落地视图 | 进项目后不动鼠标,看中央区 | 落 `resource-overview`;manifest 里 `preview.status=running` 的**陈旧记录**不得把你直接丢进运行界面 | L145–146、**L165**(`run` 自动进入只认内存 registry 活体确认) | `document.querySelector('.game-workbench-stage')?.dataset.resourceViewState` ∈ `resources.list` / `resources.selected.<category>` / `resources.ui-editor`;**该属性为 `undefined` 即说明已在 run 视图** | 陈旧记录对齐:命中"记录 running 但 registry 没跑"时调 `stop_local_game_preview` 并按真相投影 `{status:'stopped'}`(`sessionPreview.ts:87-101`) |
|
||||
| **S3** 项目页面板(可跳过) | 「项目组」页:搜索、行尾更多菜单、Escape 关闭 | 表格只显示权威数据列;Escape 关菜单并把焦点还给触发按钮;移除只改本机最近项目记录、不动磁盘 | L135–139 | 无匹配状态提供「清除搜索」;document/body 无页面级横纵向溢出 | **L139 要求 populated fixture 截图 + 视频演示搜索/清除/行尾菜单** —— 视频条款,见 7.1 |
|
||||
|
||||
### B 阶段 · 素材产生与登记
|
||||
|
||||
| 步骤 | 操作 | 期望结果 | 对应 PRD 条款 | 怎么判"过了" | 已知例外 / 未做项 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| **S4** 首轮生成 | 在右侧 Supervisor 对话里发起首轮素材生成 | 生成由**对话启动**(不用画布按钮);素材写入对应栏目并登记进 manifest;未生成的素材不显示为可编辑对象 | L25、L94、L500;飞书 §4 首轮生成 | 对话完成后 `.agent/manifest.json` 的 `assets[]` 长度增加;新卡 `data-resource-card-id` 为 `asset:<id>` 形态 | 「首轮进度投影」只在 Issue 交付范围的"应该"清单出现、**无编码级判据**;现有可见性由底部 Agent 状态栏承载 |
|
||||
| **S5** 栏目归属 | 看资源总览的栏目缩略卡与各栏目卡片 | 固定 7 栏:`UI 交互 → 角色与对象 → 场景与环境 → 音频 → 文档 → 待归类 → 项目版本`;分区口径是 manifest `assets[].category`。**用户可经「编辑标签」面板的类型选择器改分类**(选择器显示读显示口径;写回:没碰过控件回传落盘原值、主动选过写用户选的值),改完卡片应落到新分类对应栏目、角标同步 | L50、L56、L324、L358–360、L539 | `[...document.querySelectorAll('.game-resource-book-thumbnail[data-resource-book-category]')].map(e=>e.dataset.resourceBookCategory)`;再数每栏 `.game-resource-card` 数量;角标读 `.game-resource-card-type-badge` 的 `data-resource-type` | **读时自愈**:落盘 `unclassified` 而 `kind` 能明确分类时按派生值显示(L360)。真机 57 条落盘 `unclassified` 在 UI 上应显示到正确栏目,**判定必须看 UI 计数,不能看 manifest 文件计数**。⚠ 反过来**不能**把自愈值当落盘值:判定「只改标签时分类有没有被改」必须比 manifest 文件(自愈只在读显示层,写回用 `gameCreationAppAssetPersistedCategory`)。⚠ 显式把 `kind` 已能明确分类的资产设为「待归类」会被自愈覆盖回派生栏目——已知盲区,不作为本用例的通过判据 |
|
||||
| **S6** 卡片预览台账 | 逐栏目看卡片是否显示真实主体 | 图片与安全 SVG 直接显示主体并保留透明棋盘底;视频、音频、文档、项目版本用稳定固定卡;失败显示类型占位、不挂破图、不降级为项目外 URL | L55、L61–62、**L67**、L347–349 | 见 5.1 的四条脚本:区分"没读 / 读失败 / 解码为空 / 不适用" | **可见卡停 `idle` 的真 bug 已修但未提交**(`useProjectResourceCardPreviews.ts`);未用新构建时会出现假失败 |
|
||||
| 步骤 | 操作 | 期望结果 | 对应 PRD 条款 | 怎么判"过了" | 已知例外 / 未做项 |
|
||||
| ------------------- | ---------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **S4** 首轮生成 | 在右侧 Supervisor 对话里发起首轮素材生成 | 生成由**对话启动**(不用画布按钮);素材写入对应栏目并登记进 manifest;未生成的素材不显示为可编辑对象 | L25、L94、L500;飞书 §4 首轮生成 | 对话完成后 `.agent/manifest.json` 的 `assets[]` 长度增加;新卡 `data-resource-card-id` 为 `asset:<id>` 形态 | 「首轮进度投影」只在 Issue 交付范围的"应该"清单出现、**无编码级判据**;现有可见性由底部 Agent 状态栏承载 |
|
||||
| **S5** 栏目归属 | 看资源总览的栏目缩略卡与各栏目卡片 | 固定 7 栏:`UI 交互 → 角色与对象 → 场景与环境 → 音频 → 文档 → 待归类 → 项目版本`;分区口径是 manifest `assets[].category`。**用户可经「编辑标签」面板的类型选择器改分类**(选择器显示读显示口径;写回:没碰过控件回传落盘原值、主动选过写用户选的值),改完卡片应落到新分类对应栏目、角标同步 | L50、L56、L324、L358–360、L539 | `[...document.querySelectorAll('.game-resource-book-thumbnail[data-resource-book-category]')].map(e=>e.dataset.resourceBookCategory)`;再数每栏 `.game-resource-card` 数量;角标读 `.game-resource-card-type-badge` 的 `data-resource-type` | **读时自愈**:落盘 `unclassified` 而 `kind` 能明确分类时按派生值显示(L360)。真机 57 条落盘 `unclassified` 在 UI 上应显示到正确栏目,**判定必须看 UI 计数,不能看 manifest 文件计数**。⚠ 反过来**不能**把自愈值当落盘值:判定「只改标签时分类有没有被改」必须比 manifest 文件(自愈只在读显示层,写回用 `gameCreationAppAssetPersistedCategory`)。⚠ 显式把 `kind` 已能明确分类的资产设为「待归类」会被自愈覆盖回派生栏目——已知盲区,不作为本用例的通过判据 |
|
||||
| **S6** 卡片预览台账 | 逐栏目看卡片是否显示真实主体 | 图片与安全 SVG 直接显示主体并保留透明棋盘底;视频、音频、文档、项目版本用稳定固定卡;失败显示类型占位、不挂破图、不降级为项目外 URL | L55、L61–62、**L67**、L347–349 | 见 5.1 的四条脚本:区分"没读 / 读失败 / 解码为空 / 不适用" | **可见卡停 `idle` 的真 bug 已修但未提交**(`useProjectResourceCardPreviews.ts`);未用新构建时会出现假失败 |
|
||||
|
||||
### C 阶段 · 资源管理闭环
|
||||
|
||||
| 步骤 | 操作 | 期望结果 | 对应 PRD 条款 | 怎么判"过了" | 已知例外 / 未做项 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| **S7** 栏目导航 | 点总览第 0 张「所有资源」卡 → 栏目缩略卡片进栏目 → 页内底部「下一页」 | 「所有资源」是入口(第 0 张);进入后**与栏目页是同一套画本场景**——卡片宿主同为 `.game-resource-book-scene-card`、每张卡可拖、可滚轮缩放平移,各栏目按**竖向分带**平铺在同一张画布上(展开态**不出每栏一条标题栏**,整个场景只有 `all` 那一条钉在视口上的标题栏,与栏目页"一条钉住的标题栏 + 一张平铺画布"同形);全部栏目(含空栏目)都可到达;左侧悬浮栏目大纲导航已删除;滚轮不切栏目 | L56、L512、L539–541 | `[data-resource-book-view="main"\|"child"]`、当前栏目 `.game-resource-book-scene-titlebar.is-active[data-resource-book-category]`(展开态这一条是 `data-resource-book-category="all"`,带「资源总览」收起入口;**展开态 `data-resource-book-category` 非 `all` 的标题栏一个都不该有**——空栏目曾因拿不到分带矩形而集体回落到世界原点叠成一摞)、卡片宿主 `.game-resource-book-scene-card[data-resource-book-category="<真实栏目>"]`(每张资源**恰好一个**宿主)、`[data-resource-book-section-scroll]`。**旧的独立网格判据已随分支删除**:`[data-resource-book-all-page="true"]` / `[data-resource-book-all-host="true"]` / `.game-resource-all-*` 都不应再出现。拖动的判据:在展开态拖一张卡,再进它所属栏目页看同一张卡**落在新位置**(两页共用同一份 `resource-layouts/{dependency,type}.json` 的栏目内局部坐标) | 「所有资源」卡渲染真实预览(`game-resource-book-preview-card`)**已提交**(`ea9668e4a`);旧构建只有占位摞。展开态的视口是内存态(不落盘),只有卡片坐标落盘 |
|
||||
| **S8** 搜索 / 筛选 | 点右下角 `搜索资源`(放大镜)按钮或按 `Ctrl/Cmd+F` → 看到关键词 / 所在区域 / 自定义标签三字段同屏 → 输入 → Esc | Dock 上只有这一颗按钮,叫出的是**唯一**的筛选浮层(不存在第二个只放关键词的搜索浮层);浮层收起**不清筛选条件**;`type=search` 原生 Esc 清空被 `preventDefault` 挡掉;条件生效时 Dock 按钮高亮 | L55、L322(筛选只隐藏卡片、不重排坐标)、L329–330、L336–337 | `button[aria-label="搜索资源"]` 的 `aria-expanded`、`title="搜索资源(Ctrl/Cmd+F)"`、`.is-active`;浮层 `.game-resource-filter-panel`(`role="dialog"` 名「筛选资源」)内三字段 `查找素材` / `所在区域` / `自定义标签`;浮层容器 `id` 等于按钮的 `aria-controls`;Esc 后浮层不在 DOM、条件仍生效、焦点回到该按钮 | 匹配字段 = **名称 / 路径 / 媒体类型 / 任务标题**,不含 `sourceLabel` |
|
||||
| **S9** 选择与多选 | 单击卡片 → `Shift`/`Ctrl`/`Meta` 再点 → 空白处拖拽框选 | 点卡 = 选中并在卡片旁浮出工具条;三种修饰键等效追加;框选可见选框 | L51、L61–62、L318–319 | `data-resource-card-id` + class 含 `is-selected` + `aria-pressed="true"`;浮出容器 `role="toolbar"`(图片 `aria-label="图片工具栏"`,音频 `"素材工具栏"`) | ⚠️ **PRD L61–62 要求的「打开详情」按钮在代码中不存在**(测试显式断言为 `null`),按 Issue #309 C3「取消资源详情面板与全屏编辑路由」判,不要判为缺失;真正入口是 `aria-label="选中资源:<栏目> <名>"` 按钮。**只读信息浮层已回归**(2026-09-11,工具条「信息」动作,见 `decision-log`):**C3 取消的仍只是可编辑详情面板与全屏编辑路由** |
|
||||
| **S10** 快速编辑(图片) | 选中 PNG/JPEG/WEBP 卡 → 工具条「快速编辑」→ 输入 → 提交(提示词下方另有「AI 润色」) | 先 `normalize_local_project_raster_resource`,再 `derive_local_project_resource(editKind='image-reference')` → 服务端 `/api/editor/images/edits`;**产出一张新素材**,源素材与源文件不变,新资源带 `referenceResourceIds` 血缘 | L123、L193、L476–478、L557;#309 C3/C10 | 提交后 `assets[]` +1、卡片 +1 且 `data-resource-card-id` 为新 id;**源卡仍在且内容未变**;工具条真渲染的只有 `quick-edit` 与 `download`;「AI 润色」`aria-label="AI 润色"` + `aria-busy`,成功回填并出现「恢复原文」,失败保留原文并给「AI 润色失败,可重试」 | ① 服务端白名单 17 项(`editor_project.rs:4120-4141`),`character-animation`/`video`/`audio`/`document`/`code` 等**仍会 400**;② 工具条 7 个动作(重绘 / 裁剪扩图 / 去背景 / 像素完美 / 切图集 / 提取 UI 素材 / 角色动画)按 opt-in **不渲染**;③ 润色结果超 `resourceEditPromptMaxLength` 会**截断并提示「已按长度上限截断」**;④ 提示词变了(改写或润色回填)必须换 `operationId`/幂等键,否则 Rust `request_fingerprint` 判「已绑定到不同资源编辑请求」 |
|
||||
| **S11** 画布生成入口(视频) | 画布级入口 → 只呈现视频 → 填名与提示词 → 提交(提示词下方另有「AI 润色」) | 无源新建视频 → 新卡产出并定位(音效 / 背景音乐已移到栏目工具栏,见 S11a) | 飞书 §4(首轮由对话启动)、L122 | 提交后 `assets[]` +1;面板是独立浮层(非在当前面板下方追加);「AI 润色」走 `polish_local_project_prompt` 并带上「这是素材生成提示词(类型)」的场景约束,成功回填、失败保留原文 | ⚠️ **Issue #309 写「画布生成入口未接」,但当前代码已接三类**(`resourceCanvasGenerationModel.ts` 仅 `video`/`sound-effect`/`background-music`);图片等类型会直接报「当前资源类型不支持无源生成」;润色结果超该类型上限(背景音乐 140 / 音效 1900 / 视频 4000)会截断并提示「已按长度上限截断」 |
|
||||
| **S12** 编辑标签 | 选中已登记素材 → 工具条「编辑标签」→ 增删标签 → 保存 | 面板**只编辑 `manifest.assets[].tags`**;`category` 读一次权威值后**原样回传**;顶部没有分类 chip 排 | L360(2026-09-11 收口)、#309 C2 | 面板标题「编辑素材标签」,底部只有「取消 / 保存标签」;保存后 `assets[n].tags` 变化而 `assets[n].category` **逐字不变** | 面板内的「删除资源」按钮保留(删除入口另见 S15 的选中卡工具条);「用户手动设置 category」不再放在本面板,改为独立入口(工具条「素材类型」与信息浮层「分类 → 设置」,选中即落盘,见 decision-log 2026-09-14) |
|
||||
| **S13** 重命名 | 工具条「重命名」→ 新文件名 → 确认 | 磁盘文件名与 manifest `localPath` 更新;**asset id 不变**;写盘失败回滚文件 | L362(资源身份稳定)、L515 | 弹窗 `ariaLabel="重命名素材"`;改名后「选中资源:…」按钮的无障碍名同步变化;引用 chip 与 @ 候选列表显示名刷新 | ⚠️ **不改游戏源码里对旧 `assets/<name>` 的引用**,后果由用户自负 |
|
||||
| **S14** 下载 | 选中可下载卡 → 工具条「下载」 | 弹出**原生保存对话框**选目标路径 → 分块复制;取消即停止 | L507、飞书 §11.1.2 | 出现 `title="保存素材"` 的原生对话框;成功后状态提示含 `已保存 <路径>` | 语义是"另存为",不是静默直下;虚拟版本条目没有文件、不放行 |
|
||||
| **S15** 删除 | 工具条 / 标签面板「删除」→ 确认弹窗 | 三分支:① 无引用→直接删;② 被引用未勾选→只删素材、版本保留**悬空绑定**、界面不合成幽灵资源卡;③ 勾选→素材与该批版本在**同一次 manifest 写入**内一起删 | L413(版本只追加的唯一例外)、L532–533;#309 C5 | 弹窗 `ariaLabel="确认删除资源"`;被引用时出现 `被 N 个游戏版本使用` + 版本列表 + checkbox `把相关游戏版本一并删除`;无引用时该 body 整块不渲染 | **只摘登记、不删磁盘文件**;读引用命令精确名是 `read_local_project_asset_references` |
|
||||
| **S15a** 替换素材(含点选替换) | 选中被**当前版本**绑定的素材 → 工具条「替换素材」→ 候选弹窗(弹窗内可筛可选);再点 footer「点选替换」→ 弹窗关闭、进入画布点选态 → 在画布上直接点目标素材 | 弹窗确认与点选是**同一条**写入链路(`replace_local_project_version_resource`,载荷逐字一致):改该版本绑定、不建新版本;点选态下合法目标直接提交并自动退出点选态,非法目标零写入、**留在**点选态并在提示条上说明原因 | PRD §5.3 / §7.8 第 8 条;#309「直接替换」口径 | 点选态判据:候选弹窗 `role="dialog"` 已卸载;画布出现 `.game-resource-canvas-pick-hint`,文案含「在画布上点选要替换成的素材」与「点击空白处不会退出」,并有「取消」按钮;非法目标后提示条仍在且出现 `role="alert"`(「替换素材不兼容:分类不同」/「替换素材与源素材相同」/「替换素材未登记或已被删除」);点选期间卡片 `aria-pressed` 不变(单击只用于点选)、源素材选中与工具条不因点空白或点非法目标而丢;Esc 退出点选(画布全局 Esc 的清选中不随之触发);点选态下滚轮平移与缩放照常可用 | 点选只能点**当前画布上可见**的卡:目标在别的栏目时先切栏目(点选会话跨栏目存活,不因「收起资源」或切栏目结束);空白点击不退出、也不清画布选中 |
|
||||
| 步骤 | 操作 | 期望结果 | 对应 PRD 条款 | 怎么判"过了" | 已知例外 / 未做项 |
|
||||
| -------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **S7** 栏目导航 | 点总览第 0 张「所有资源」卡 → 栏目缩略卡片进栏目 → 页内底部「下一页」 | 「所有资源」是入口(第 0 张);进入后**与栏目页是同一套画本场景**——卡片宿主同为 `.game-resource-book-scene-card`、每张卡可拖、可滚轮缩放平移,各栏目按**竖向分带**平铺在同一张画布上(展开态**不出每栏一条标题栏**,整个场景只有 `all` 那一条钉在视口上的标题栏,与栏目页"一条钉住的标题栏 + 一张平铺画布"同形);全部栏目(含空栏目)都可到达;左侧悬浮栏目大纲导航已删除;滚轮不切栏目 | L56、L512、L539–541 | `[data-resource-book-view="main"\|"child"]`、当前栏目 `.game-resource-book-scene-titlebar.is-active[data-resource-book-category]`(展开态这一条是 `data-resource-book-category="all"`,带「资源总览」收起入口;**展开态 `data-resource-book-category` 非 `all` 的标题栏一个都不该有**——空栏目曾因拿不到分带矩形而集体回落到世界原点叠成一摞)、卡片宿主 `.game-resource-book-scene-card[data-resource-book-category="<真实栏目>"]`(每张资源**恰好一个**宿主)、`[data-resource-book-section-scroll]`。**旧的独立网格判据已随分支删除**:`[data-resource-book-all-page="true"]` / `[data-resource-book-all-host="true"]` / `.game-resource-all-*` 都不应再出现。拖动的判据:在展开态拖一张卡,再进它所属栏目页看同一张卡**落在新位置**(两页共用同一份 `resource-layouts/{dependency,type}.json` 的栏目内局部坐标) | 「所有资源」卡渲染真实预览(`game-resource-book-preview-card`)**已提交**(`ea9668e4a`);旧构建只有占位摞。展开态的视口是内存态(不落盘),只有卡片坐标落盘 |
|
||||
| **S8** 搜索 / 筛选 | 点右下角 `搜索资源`(放大镜)按钮或按 `Ctrl/Cmd+F` → 看到关键词 / 所在区域 / 自定义标签三字段同屏 → 输入 → Esc | Dock 上只有这一颗按钮,叫出的是**唯一**的筛选浮层(不存在第二个只放关键词的搜索浮层);浮层收起**不清筛选条件**;`type=search` 原生 Esc 清空被 `preventDefault` 挡掉;条件生效时 Dock 按钮高亮 | L55、L322(筛选只隐藏卡片、不重排坐标)、L329–330、L336–337 | `button[aria-label="搜索资源"]` 的 `aria-expanded`、`title="搜索资源(Ctrl/Cmd+F)"`、`.is-active`;浮层 `.game-resource-filter-panel`(`role="dialog"` 名「筛选资源」)内三字段 `查找素材` / `所在区域` / `自定义标签`;浮层容器 `id` 等于按钮的 `aria-controls`;Esc 后浮层不在 DOM、条件仍生效、焦点回到该按钮 | 匹配字段 = **名称 / 路径 / 媒体类型 / 任务标题**,不含 `sourceLabel` |
|
||||
| **S9** 选择与多选 | 单击卡片 → `Shift`/`Ctrl`/`Meta` 再点 → 空白处拖拽框选 | 点卡 = 选中并在卡片旁浮出工具条;三种修饰键等效追加;框选可见选框 | L51、L61–62、L318–319 | `data-resource-card-id` + class 含 `is-selected` + `aria-pressed="true"`;浮出容器 `role="toolbar"`(图片 `aria-label="图片工具栏"`,音频 `"素材工具栏"`) | ⚠️ **PRD L61–62 要求的「打开详情」按钮在代码中不存在**(测试显式断言为 `null`),按 Issue #309 C3「取消资源详情面板与全屏编辑路由」判,不要判为缺失;真正入口是 `aria-label="选中资源:<栏目> <名>"` 按钮。**只读信息浮层已回归**(2026-09-11,工具条「信息」动作,见 `decision-log`):**C3 取消的仍只是可编辑详情面板与全屏编辑路由** |
|
||||
| **S10** 快速编辑(图片) | 选中 PNG/JPEG/WEBP 卡 → 工具条「快速编辑」→ 输入 → 提交(提示词下方另有「AI 润色」) | 先 `normalize_local_project_raster_resource`,再 `derive_local_project_resource(editKind='image-reference')` → 服务端 `/api/editor/images/edits`;**产出一张新素材**,源素材与源文件不变,新资源带 `referenceResourceIds` 血缘 | L123、L193、L476–478、L557;#309 C3/C10 | 提交后 `assets[]` +1、卡片 +1 且 `data-resource-card-id` 为新 id;**源卡仍在且内容未变**;工具条真渲染的只有 `quick-edit` 与 `download`;「AI 润色」`aria-label="AI 润色"` + `aria-busy`,成功回填并出现「恢复原文」,失败保留原文并给「AI 润色失败,可重试」 | ① 服务端白名单 17 项(`editor_project.rs:4120-4141`),`character-animation`/`video`/`audio`/`document`/`code` 等**仍会 400**;② 工具条 7 个动作(重绘 / 裁剪扩图 / 去背景 / 像素完美 / 切图集 / 提取 UI 素材 / 角色动画)按 opt-in **不渲染**;③ 润色结果超 `resourceEditPromptMaxLength` 会**截断并提示「已按长度上限截断」**;④ 提示词变了(改写或润色回填)必须换 `operationId`/幂等键,否则 Rust `request_fingerprint` 判「已绑定到不同资源编辑请求」 |
|
||||
| **S11** 画布生成入口(视频) | 画布级入口 → 只呈现视频 → 填名与提示词 → 提交(提示词下方另有「AI 润色」) | 无源新建视频 → 新卡产出并定位(音效 / 背景音乐已移到栏目工具栏,见 S11a) | 飞书 §4(首轮由对话启动)、L122 | 提交后 `assets[]` +1;面板是独立浮层(非在当前面板下方追加);「AI 润色」走 `polish_local_project_prompt` 并带上「这是素材生成提示词(类型)」的场景约束,成功回填、失败保留原文 | ⚠️ **Issue #309 写「画布生成入口未接」,但当前代码已接三类**(`resourceCanvasGenerationModel.ts` 仅 `video`/`sound-effect`/`background-music`);图片等类型会直接报「当前资源类型不支持无源生成」;润色结果超该类型上限(背景音乐 140 / 音效 1900 / 视频 4000)会截断并提示「已按长度上限截断」 |
|
||||
| **S12** 编辑标签 | 选中已登记素材 → 工具条「编辑标签」→ 增删标签 → 保存 | 面板**只编辑 `manifest.assets[].tags`**;`category` 读一次权威值后**原样回传**;顶部没有分类 chip 排 | L360(2026-09-11 收口)、#309 C2 | 面板标题「编辑素材标签」,底部只有「取消 / 保存标签」;保存后 `assets[n].tags` 变化而 `assets[n].category` **逐字不变** | 面板内的「删除资源」按钮保留(删除入口另见 S15 的选中卡工具条);「用户手动设置 category」不再放在本面板,改为独立入口(工具条「素材类型」与信息浮层「分类 → 设置」,选中即落盘,见 decision-log 2026-09-14) |
|
||||
| **S13** 重命名 | 工具条「重命名」→ 新文件名 → 确认 | 磁盘文件名与 manifest `localPath` 更新;**asset id 不变**;写盘失败回滚文件 | L362(资源身份稳定)、L515 | 弹窗 `ariaLabel="重命名素材"`;改名后「选中资源:…」按钮的无障碍名同步变化;引用 chip 与 @ 候选列表显示名刷新 | ⚠️ **不改游戏源码里对旧 `assets/<name>` 的引用**,后果由用户自负 |
|
||||
| **S14** 下载 | 选中可下载卡 → 工具条「下载」 | 弹出**原生保存对话框**选目标路径 → 分块复制;取消即停止 | L507、飞书 §11.1.2 | 出现 `title="保存素材"` 的原生对话框;成功后状态提示含 `已保存 <路径>` | 语义是"另存为",不是静默直下;虚拟版本条目没有文件、不放行 |
|
||||
| **S15** 删除 | 工具条 / 标签面板「删除」→ 确认弹窗 | 三分支:① 无引用→直接删;② 被引用未勾选→只删素材、版本保留**悬空绑定**、界面不合成幽灵资源卡;③ 勾选→素材与该批版本在**同一次 manifest 写入**内一起删 | L413(版本只追加的唯一例外)、L532–533;#309 C5 | 弹窗 `ariaLabel="确认删除资源"`;被引用时出现 `被 N 个游戏版本使用` + 版本列表 + checkbox `把相关游戏版本一并删除`;无引用时该 body 整块不渲染 | **只摘登记、不删磁盘文件**;读引用命令精确名是 `read_local_project_asset_references` |
|
||||
| **S15a** 替换素材(面板内选 + 画布点选目标) | 选中被**当前版本**绑定的素材 → 工具条「替换素材」→ 画布右上角出现非模态候选面板(面板内可筛可选)→ 直接在画布上点目标素材(或点面板里的候选)→ 面板「确认」 | 面板确认与画布点选是**同一条**写入链路(`replace_local_project_version_resource`,载荷逐字一致):改该版本绑定、不建新版本;画布点选只把目标落成面板的当前选择,确认后才写入 | PRD §5.3 / §7.8 第 8 条;#309「直接替换」口径 | 面板判据:`role="dialog"` 名为「选择替换素材」、挂 `.image-canvas-editor__project-asset-picker--floating`、**没有** `.platform-overlay` 遮罩;点画布候选后面板里该候选 `aria-selected="true"`、面板里的搜索词与分类筛选保持原样、零写入;非法目标(源素材本身 / 不在权威候选里 / 分类不同 / 未登记资源)在面板里出现 `role="alert"`(「替换素材与源素材相同」/「替换素材未登记或已被删除」/「替换素材不兼容:分类不同」)且零写入;面板关闭 = 卸载;Esc 只收面板(画布全局 Esc 的清选中不随之触发) | 点选只能点**当前画布上可见**的卡:目标在别的栏目时先切栏目(替换会话跨栏目存活,不因「收起资源」或切栏目结束);面板开着时空白处仍可框选 / 平移,卡片单击语义的恢复发生在面板关闭之后 |
|
||||
|
||||
| **S11a** 栏目画布底部工具栏 | 进「UI 交互 / 角色与对象 / 场景与环境 / 音频」任一栏目 → 点左下角工具栏里的入口生成 → 「收起资源」回总览看工具栏消失 | 工具栏只在矩阵四个栏目(功能画布)渲染;图片类入口走 `start_local_project_asset_generation`(提交即返回、生成在后台跑),音频入口复用既有无源生成链路,上传复用 `upload_local_asset`;生成 / 上传成功后走既有 manifest 刷新与资源定位 | PRD §3.10、§7.9 | DOM 判据 `[data-resource-bottom-toolbar="<category>"]`(资源总览、「所有资源」展开态、文档、待归类、项目版本都**没有**这个节点);工具栏是 `.game-resource-book-manager` 的直接子节点(`closest('.game-resource-book-scene')` 为 `null`);每个入口一次 `start_local_project_asset_generation`,载荷逐字为 `{ projectPath, projectId, taskId, kind, prompt, aspectRatio, imageSize, assetName, outputPath }`(`kind` 映射见 PRD §7.9 第 3 条;`taskId` 是前端每次提交新铸的本地任务 id);该命令提交即返回,之后有 `list_local_project_asset_generations` 轮询与 `get_local_game_project_revision` + `get_local_game_manifest` 的配对读;提交期间点 × / 遮罩 / Esc /「后台运行并关闭」任一都能关面板且请求继续(画布立即恢复可交互),关闭后任务仍出现在「生成任务」面板(入口按钮 `aria-label="生成任务"`,非模态浮层、无 `aria-modal`)并显示后端 `phaseDetail`;第一条未终态时提交第二条 → 第二条显示「排队中。」且生成提交 IPC 次数仍为 1,第一条终态后自动补发(次数变 2);缺 `assets/art-spec.png` 时「生成图标素材 / 生成 UI 设计图」仍可点击(`aria-disabled="true"` 但**不是**原生 disabled)并给出含该路径的 `role="alert"` 原因、零生成请求;音频入口面板标题即「生成背景音乐」/「生成音效」且没有类型选择器 | ① 本地通道没有 `model` / `specType` / `replaceExisting` 入参:面板不渲染模型选择器,角色规范与自定义规范共用 `spec` 通道(靠 `assetName` 与提示词区分),「图标规范」在项目已有权威规范图时不再指向 `assets/art-spec.png`(否则会被 Rust 的防覆盖校验硬拒);② 本轮不做生成视频 / 宣发素材 / 生成游戏场景 / 选择工具 / 抓手工具;③ 既有「生成素材」浮层入口只保留生成视频,音频入口只在音频栏目工具栏出现 |
|
||||
| **S11a** 栏目画布底部工具栏 | 进「UI 交互 / 角色与对象 / 场景与环境 / 音频」任一栏目 → 点左下角工具栏里的入口生成 → 「收起资源」回总览看工具栏消失 | 工具栏只在矩阵四个栏目(功能画布)渲染;图片类入口走 `start_local_project_asset_generation`(提交即返回、生成在后台跑),音频入口走同一条命令的音频载荷,上传复用 `upload_local_asset`;生成 / 上传成功后走既有 manifest 刷新与资源定位 | PRD §3.10、§7.9 | DOM 判据 `[data-resource-bottom-toolbar="<category>"]`(资源总览、「所有资源」展开态、文档、待归类、项目版本都**没有**这个节点);工具栏是 `.game-resource-book-manager` 的直接子节点(`closest('.game-resource-book-scene')` 为 `null`);每个入口一次 `start_local_project_asset_generation`:图片类载荷逐字为 `{ projectPath, projectId, taskId, kind, prompt, aspectRatio, imageSize, assetName, outputPath }`(`kind` 映射见 PRD §7.9 第 3 条;`taskId` 是前端每次提交新铸的本地任务 id),音频类载荷为 `{ projectPath, projectId, taskId, kind, prompt, assetName, idempotencyKey }`(`taskId` 即该次生成的 operation id,不发图片类那套比例 / 尺寸 / 参考 / 落点参数);该命令提交即返回,之后有 `list_local_project_asset_generations` 轮询与 `get_local_game_project_revision` + `get_local_game_manifest` 的配对读;点「生成」即把这次输入交给后台账本并**同步关闭**面板(不等 IPC、不等排队、不等生成,画布立即恢复可交互;面板 DOM 里没有阶段文案与「后台运行并关闭」这类在途按钮),关闭不等于取消,关闭后任务仍出现在「生成任务」面板(入口按钮 `aria-label="生成任务"`,非模态浮层、无 `aria-modal`)并显示后端 `phaseDetail`;第一条未终态时提交第二条 → 第二条显示「排队中。」且生成提交 IPC 次数仍为 1,第一条终态后自动补发(次数变 2);缺 `assets/art-spec.png` 时「生成图标素材 / 生成 UI 设计图」仍可点击(`aria-disabled="true"` 但**不是**原生 disabled)并给出含该路径的 `role="alert"` 原因、零生成请求;音频入口面板标题即「生成背景音乐」/「生成音效」且没有类型选择器 | ① 本地通道没有 `model` / `specType` / `replaceExisting` 入参:面板不渲染模型选择器,角色规范与自定义规范共用 `spec` 通道(靠 `assetName` 与提示词区分),「图标规范」在项目已有权威规范图时不再指向 `assets/art-spec.png`(否则会被 Rust 的防覆盖校验硬拒);② 本轮不做生成视频 / 宣发素材 / 生成游戏场景 / 选择工具 / 抓手工具;③ 既有「生成素材」浮层入口只保留生成视频,音频入口只在音频栏目工具栏出现 |
|
||||
|
||||
### D 阶段 · 版本与运行
|
||||
|
||||
| 步骤 | 操作 | 期望结果 | 对应 PRD 条款 | 怎么判"过了" | 已知例外 / 未做项 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| **S16** 版本切换 | 运行模块**右上角**版本选择器 → 切到另一版本 | 触发钮显示当前版本;切换后资源卡「当前使用」高亮与 **@ 面板「当前版本素材」同步更新**,并重载当前预览 | L165、L190、L324、L530;#309 C7 | `aria-label="当前版本:<label>"`、菜单 `role="listbox" aria-label="切换游戏版本"`、选项 `role="option" aria-selected`;`[data-used-by-current-version="true"]` 集合随切换变化 | **C7 只做记录层 + UI 层**;"历史版本独有资源集合被运行时按版本加载"需新增版本化资源解析机制,**本轮未实现**;版本卡卡面**不渲染**「项目修订 / 父版本」文字 |
|
||||
| **S17** 播放 | 顶部「播放」 | 直接切运行视图并启动本地预览;**不再弹 `game.run_local` 二次确认**;预览占满中央可用区、无 iframe 滚动条 | L16、L71–72、L122、L493 | `data-resource-view-state` 变为 `undefined`;`await window.__TAURI__.core.invoke('get_local_game_preview_status',{projectPath})` → `status==='running'` 且有 `url`(形如 `http://127.0.0.1:<随机端口>/`) | 播放按钮 `disabled={!runAvailable \|\| !onPlay \|\| uiEditorRoute!==null}`,**不可用时点不动**;不可用提示实现文案为「首个可运行原型尚未完成,运行视图暂不可用」,与 PRD L165 表述不同(见 7.4) |
|
||||
| **S18** 停止与退出收尾 | 聊天侧工程操作组点「停止预览」;或关闭客户端 | 预览停止、manifest `preview` 对齐 `stopped`(清 url/port);退出时也统一收尾 | L165 | 停止后 `get_local_game_preview_status` 非 running;`.agent/logs/preview.log` 追加 `stopped`;退出后重开同一项目应落回 `resource-overview` | 停止按钮**不在**运行视图工具栏,在工作台右侧工程操作组;退出失败会打 `preview.gui_exit.stop_failed` |
|
||||
| 步骤 | 操作 | 期望结果 | 对应 PRD 条款 | 怎么判"过了" | 已知例外 / 未做项 |
|
||||
| ---------------------- | -------------------------------------------- | -------------------------------------------------------------------------------------------------------- | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| **S16** 版本切换 | 运行模块**右上角**版本选择器 → 切到另一版本 | 触发钮显示当前版本;切换后资源卡「当前使用」高亮与 **@ 面板「当前版本素材」同步更新**,并重载当前预览 | L165、L190、L324、L530;#309 C7 | `aria-label="当前版本:<label>"`、菜单 `role="listbox" aria-label="切换游戏版本"`、选项 `role="option" aria-selected`;`[data-used-by-current-version="true"]` 集合随切换变化 | **C7 只做记录层 + UI 层**;"历史版本独有资源集合被运行时按版本加载"需新增版本化资源解析机制,**本轮未实现**;版本卡卡面**不渲染**「项目修订 / 父版本」文字 |
|
||||
| **S17** 播放 | 顶部「播放」 | 直接切运行视图并启动本地预览;**不再弹 `game.run_local` 二次确认**;预览占满中央可用区、无 iframe 滚动条 | L16、L71–72、L122、L493 | `data-resource-view-state` 变为 `undefined`;`await window.__TAURI__.core.invoke('get_local_game_preview_status',{projectPath})` → `status==='running'` 且有 `url`(形如 `http://127.0.0.1:<随机端口>/`) | 播放按钮 `disabled={!runAvailable \|\| !onPlay \|\| uiEditorRoute!==null}`,**不可用时点不动**;不可用提示实现文案为「首个可运行原型尚未完成,运行视图暂不可用」,与 PRD L165 表述不同(见 7.4) |
|
||||
| **S18** 停止与退出收尾 | 聊天侧工程操作组点「停止预览」;或关闭客户端 | 预览停止、manifest `preview` 对齐 `stopped`(清 url/port);退出时也统一收尾 | L165 | 停止后 `get_local_game_preview_status` 非 running;`.agent/logs/preview.log` 追加 `stopped`;退出后重开同一项目应落回 `resource-overview` | 停止按钮**不在**运行视图工具栏,在工作台右侧工程操作组;退出失败会打 `preview.gui_exit.stop_failed` |
|
||||
|
||||
### E 阶段 · 对话侧
|
||||
|
||||
| 步骤 | 操作 | 期望结果 | 对应 PRD 条款 | 怎么判"过了" | 已知例外 / 未做项 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| **S19** @ 引用 | 输入区「@」→ 两个页签 + 筛选(功能分类 + 多标签)+ 搜索 + 多选 → 「添加到对话」 | 两页签**各自独立**的搜索与筛选状态(含各自一份标签库),**不影响**资源画布筛选;已选素材不因筛选或搜索隐藏而被取消;chip 整体插入 | L187、L420–426、L453–461;#309 C4 | `role="tablist"`(`aria-label="素材范围"`)内两个 `role="tab"`:`当前版本素材` / `全部画布素材`;输入框 aria-label 分别为 `搜索当前版本素材` / `搜索全部画布素材`;标签行是共享筛选条里 `aria-label="素材筛选标签"` 的分组,chip 用 `aria-pressed` 表达选中;chip = `span.resource-reference-chip[data-resource-reference-id]` | **chip 原子性有正式定义**:chip 是 Lexical `DecoratorNode`,纯文本表示固定占 **1 个字符** `U+FFFC` ⇒ 一次 Backspace 只能整体删掉一个 chip,不可能删半截;引用身份是稳定 `resourceId`,改名后按 id 刷新显示名。**多标签是 AND**(口径来自 `packages/shared` 的 `assetTagsMatchSelection`,标签取自 manifest `assets[].tags`),并与搜索词、功能分类三者叠加 |
|
||||
| **S19a** 快速编辑内 @ 引用 | 资源卡「快速编辑」→ 提示词输入区「插入素材引用」或直接输入 `@` → 选素材 → 「修改」 | 快速编辑提示词输入区与聊天输入区**同一套**引用输入区:候选、插入动作、引用模型一致;提示词文本里的引用与聊天同字面量(`@显示名`),提交后该文本原样进入 `derive_local_project_resource` 的 `prompt` | #309 C4 / C10 | 快速编辑面板内存在 `aria-label="插入素材引用"` 与 `aria-label="快速编辑提示词"`;插入后编辑区出现 `[data-resource-reference-id]` chip;派生请求 `prompt` 等于 `文本@显示名` | 该输入区不渲染内置「AI 润色」(润色仍由面板的「AI 润色」承担,带资源编辑场景上下文与长度上限);多行输入区的 Enter 只换行、不提交;点 `@` 选择器里的候选不会被判成「点外部」而收起面板 |
|
||||
| **S20** 润色与发送前提醒 | ① 点「AI 润色」→ 看回填与「恢复原文」;② 直接发送(≥40 字)→ 处理提醒弹窗三条动作与「不再提醒」 | 润色只优化文本、不发送不提交;第一次成功润色落下原文快照、可重复润色、失败保留原文;未润色直接发送时弹「发送前提醒」 | L120(气泡对比度)、飞书 §12.1/12.2;#309 C8 | 润色按钮 `aria-label="AI 润色"` + `aria-busy`;出现「恢复原文」按钮;提醒弹窗为 portal `role="dialog"`、标题「发送前提醒」、动作 `关闭` / `使用原文提交` / `AI 润色` + checkbox `不再提醒`;偏好存 `localStorage['agc.chat.prompt-polish-reminder.disabled']` | 触发门禁:trim 后 ≥40 字、不以 `/` 开头、本轮未润色未确认、未勾「不再提醒」。计费不自建,走 server-rs `/api/llm/chat/completions` 与 `/api/llm/responses`;「1 泥点」这个数字源码中未硬编码 |
|
||||
| 步骤 | 操作 | 期望结果 | 对应 PRD 条款 | 怎么判"过了" | 已知例外 / 未做项 |
|
||||
| -------------------------- | ----------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **S19** @ 引用 | 输入区「@」→ 两个页签 + 筛选(功能分类 + 多标签)+ 搜索 + 多选 → 「添加到对话」 | 两页签**各自独立**的搜索与筛选状态(含各自一份标签库),**不影响**资源画布筛选;已选素材不因筛选或搜索隐藏而被取消;chip 整体插入 | L187、L420–426、L453–461;#309 C4 | `role="tablist"`(`aria-label="素材范围"`)内两个 `role="tab"`:`当前版本素材` / `全部画布素材`;输入框 aria-label 分别为 `搜索当前版本素材` / `搜索全部画布素材`;标签行是共享筛选条里 `aria-label="素材筛选标签"` 的分组,chip 用 `aria-pressed` 表达选中;chip = `span.resource-reference-chip[data-resource-reference-id]` | **chip 原子性有正式定义**:chip 是 Lexical `DecoratorNode`,纯文本表示固定占 **1 个字符** `U+FFFC` ⇒ 一次 Backspace 只能整体删掉一个 chip,不可能删半截;引用身份是稳定 `resourceId`,改名后按 id 刷新显示名。**多标签是 AND**(口径来自 `packages/shared` 的 `assetTagsMatchSelection`,标签取自 manifest `assets[].tags`),并与搜索词、功能分类三者叠加 |
|
||||
| **S19a** 快速编辑内 @ 引用 | 资源卡「快速编辑」→ 提示词输入区「插入素材引用」或直接输入 `@` → 选素材 → 「修改」 | 快速编辑提示词输入区与聊天输入区**同一套**引用输入区:候选、插入动作、引用模型一致;提示词文本里的引用与聊天同字面量(`@显示名`),提交后该文本原样进入 `derive_local_project_resource` 的 `prompt` | #309 C4 / C10 | 快速编辑面板内存在 `aria-label="插入素材引用"` 与 `aria-label="快速编辑提示词"`;插入后编辑区出现 `[data-resource-reference-id]` chip;派生请求 `prompt` 等于 `文本@显示名` | 该输入区不渲染内置「AI 润色」(润色仍由面板的「AI 润色」承担,带资源编辑场景上下文与长度上限);多行输入区的 Enter 只换行、不提交;点 `@` 选择器里的候选不会被判成「点外部」而收起面板 |
|
||||
| **S20** 润色与发送前提醒 | ① 点「AI 润色」→ 看回填与「恢复原文」;② 直接发送(≥40 字)→ 处理提醒弹窗三条动作与「不再提醒」 | 润色只优化文本、不发送不提交;第一次成功润色落下原文快照、可重复润色、失败保留原文;未润色直接发送时弹「发送前提醒」 | L120(气泡对比度)、飞书 §12.1/12.2;#309 C8 | 润色按钮 `aria-label="AI 润色"` + `aria-busy`;出现「恢复原文」按钮;提醒弹窗为 portal `role="dialog"`、标题「发送前提醒」、动作 `关闭` / `使用原文提交` / `AI 润色` + checkbox `不再提醒`;偏好存 `localStorage['agc.chat.prompt-polish-reminder.disabled']` | 触发门禁:trim 后 ≥40 字、不以 `/` 开头、本轮未润色未确认、未勾「不再提醒」。计费不自建,走 server-rs `/api/llm/chat/completions` 与 `/api/llm/responses`;「1 泥点」这个数字源码中未硬编码 |
|
||||
|
||||
### F 阶段 · 台账核对
|
||||
|
||||
| 步骤 | 操作 | 期望结果 | 对应 PRD 条款 | 怎么判"过了" | 已知例外 / 未做项 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| 步骤 | 操作 | 期望结果 | 对应 PRD 条款 | 怎么判"过了" | 已知例外 / 未做项 |
|
||||
| ---------------- | -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
|
||||
| **S21** 全程台账 | 不刷新、不重开项目,回看资源画布与布局 | 新增资源即时投影;两套布局(dependency / type)各自坐标独立、历史 `manuallyPlaced=true` 保留;搜索与详情开关不重置 viewport | L47–51、L262–265、L320–323、L327–329、L500 | 两份 sidecar 都在 `.agent/workbench/resource-layouts/{dependency,type}.json`;拖动超过 5px 阈值才提交一次 CAS,成功后 `revision` 递增;**存量项目「第一次打开」时读路径也会写回一次**(旧 `art` / `code` 分区坐标归并到新分区,`revision` +1),并给一次性提示条,条数含**无法对齐被跳过的坐标**(`data-resource-canvas-layout-*`),所以「`revision` 从 1 开始」不是异常 | 双窗口同 revision 的 CAS 冲突(L509)需开两个客户端窗口才可验;`A→B→A` 复用 epoch 的取消语义只能靠定向测试 |
|
||||
|
||||
## 4. 前置条件清单
|
||||
|
||||
| 项 | 结论 | 依据 |
|
||||
| --- | --- | --- |
|
||||
| **是否要重启客户端 / 重新构建** | dev 态 `npm run agc` 走 `devUrl`(默认 3080,运行时被覆盖为实际端口),前端改动走 Vite HMR、**不需要 build**;**Rust 侧改动必须重启客户端**;未提交改动若不在当前进程里,也必须重启一次客户端 | `apps/ai-game-creator-shell/src-tauri/tauri.conf.json:6-11`、`scripts/start-tauri-dev.mjs:26-44` |
|
||||
| **是否要重新构建产物** | 只有 `agc:build` / `ai-game-creator-shell:build` 才产出 `../dist`;若验的是打包后的客户端,改前端必须重新 build 并重装 | `tauri.conf.json` 的 `frontendDist: "../dist"` |
|
||||
| **是否要重启 api-server** | **服务端改动需要重启**。本地 `.env` / `.env.local` / `.env.secrets.local` 修改后必须重启 `api-server`;已在 `npm run dev` 终端里可直接敲 `rs api-server`;provider / worker / fallback 配置改动要按角色重启对应进程 | `docs/【开发运维】本地开发验证与生产运维-2026-05-15.md:114/136/209/215/809` |
|
||||
| **启动命令** | 客户端:`npm run agc`(等价 `agc:dev` / `ai-game-creator-shell:dev`);只起前后端不起窗口:`npm run agc:serve`;后端:`npm run dev:api-server` | `package.json:20/155-159/169/22` |
|
||||
| **端口与健康检查** | api-server 默认 `127.0.0.1:8082`,健康检查 `GET /healthz`(先查 BgFilter worker `/readyz`);**端口会漂移,实际值以 `.app/dev-stack.json` 为准** | `scripts/dev.mjs:237`、`scripts/start-dev-stack.mjs:28-29/351-353` |
|
||||
| **登录与余额** | 需要登录;**不需要余额也能进工作台与资源画布**,消耗泥点的是付费生成与 Agent 请求 | `src/app/AuthenticatedClient.tsx:546/568-574`、`index.tsx:500` |
|
||||
| **测试项目** | 真机 `What do u wanna do kitten` 已有 61 assets / 5 versions、两份布局 sidecar 齐全,适合当**存量数据**验收对象(只读);要验删除与版本三分支时建议另建新项目 | 只读统计 |
|
||||
| **门禁命令** | `npm run ai-game-creator-shell:typecheck`、`npm run test -- apps/ai-game-creator-shell/tests`、`npm run check:encoding`、`git diff --check`;Rust 侧 `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml -- --test-threads=1` | `package.json:123/164/193-194/63` |
|
||||
| **build-gate 代理假红** | 本地带系统代理跑 `npm run build` 必假红:`NODE_USE_ENV_PROXY=1` 加 `HTTP_PROXY/https_proxy` 会触发 `[UNDICI-EHPA] Warning: EnvHttpProxyAgent is experimental`,不被 `ExperimentalWarning` 忽略规则放行。跑前清空 `NODE_USE_ENV_PROXY/HTTP_PROXY/http_proxy/HTTPS_PROXY/https_proxy/ALL_PROXY` | `scripts/build-gate.mjs:33-37/57-67` |
|
||||
| 项 | 结论 | 依据 |
|
||||
| ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
|
||||
| **是否要重启客户端 / 重新构建** | dev 态 `npm run agc` 走 `devUrl`(默认 3080,运行时被覆盖为实际端口),前端改动走 Vite HMR、**不需要 build**;**Rust 侧改动必须重启客户端**;未提交改动若不在当前进程里,也必须重启一次客户端 | `apps/ai-game-creator-shell/src-tauri/tauri.conf.json:6-11`、`scripts/start-tauri-dev.mjs:26-44` |
|
||||
| **是否要重新构建产物** | 只有 `agc:build` / `ai-game-creator-shell:build` 才产出 `../dist`;若验的是打包后的客户端,改前端必须重新 build 并重装 | `tauri.conf.json` 的 `frontendDist: "../dist"` |
|
||||
| **是否要重启 api-server** | **服务端改动需要重启**。本地 `.env` / `.env.local` / `.env.secrets.local` 修改后必须重启 `api-server`;已在 `npm run dev` 终端里可直接敲 `rs api-server`;provider / worker / fallback 配置改动要按角色重启对应进程 | `docs/【开发运维】本地开发验证与生产运维-2026-05-15.md:114/136/209/215/809` |
|
||||
| **启动命令** | 客户端:`npm run agc`(等价 `agc:dev` / `ai-game-creator-shell:dev`);只起前后端不起窗口:`npm run agc:serve`;后端:`npm run dev:api-server` | `package.json:20/155-159/169/22` |
|
||||
| **端口与健康检查** | api-server 默认 `127.0.0.1:8082`,健康检查 `GET /healthz`(先查 BgFilter worker `/readyz`);**端口会漂移,实际值以 `.app/dev-stack.json` 为准** | `scripts/dev.mjs:237`、`scripts/start-dev-stack.mjs:28-29/351-353` |
|
||||
| **登录与余额** | 需要登录;**不需要余额也能进工作台与资源画布**,消耗泥点的是付费生成与 Agent 请求 | `src/app/AuthenticatedClient.tsx:546/568-574`、`index.tsx:500` |
|
||||
| **测试项目** | 真机 `What do u wanna do kitten` 已有 61 assets / 5 versions、两份布局 sidecar 齐全,适合当**存量数据**验收对象(只读);要验删除与版本三分支时建议另建新项目 | 只读统计 |
|
||||
| **门禁命令** | `npm run ai-game-creator-shell:typecheck`、`npm run test -- apps/ai-game-creator-shell/tests`、`npm run check:encoding`、`git diff --check`;Rust 侧 `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml -- --test-threads=1` | `package.json:123/164/193-194/63` |
|
||||
| **build-gate 代理假红** | 本地带系统代理跑 `npm run build` 必假红:`NODE_USE_ENV_PROXY=1` 加 `HTTP_PROXY/https_proxy` 会触发 `[UNDICI-EHPA] Warning: EnvHttpProxyAgent is experimental`,不被 `ExperimentalWarning` 忽略规则放行。跑前清空 `NODE_USE_ENV_PROXY/HTTP_PROXY/http_proxy/HTTPS_PROXY/https_proxy/ALL_PROXY` | `scripts/build-gate.mjs:33-37/57-67` |
|
||||
|
||||
## 5. 每步"失败怎么看"
|
||||
|
||||
@@ -129,13 +129,13 @@
|
||||
.reduce((m, e) => { const k = e.dataset.previewHasAlpha ?? '(无 alpha 判据)'; m[k] = (m[k] || 0) + 1; return m; }, {})
|
||||
```
|
||||
|
||||
| 现象 | 说明 | 第一眼做什么 |
|
||||
| --- | --- | --- |
|
||||
| 可读类型 + `idle` | 该卡**从未发出预览请求**(可见性门禁没挂上,或卡片在被裁切的容器里) | 先确认跑的是包含修复的新构建(见第 1 节),再核对 observer 补挂是否生效 |
|
||||
| `failed` + `data-preview-error` | 读取或解码真失败,读 `data-preview-error` 原文 | `retryable=false` 表示永久失败(超尺寸、损坏、类型不支持、不安全 SVG);可见性预取遇 failed 会直接放弃,不会自愈 |
|
||||
| `loaded` 但无主体节点 | 原生返回的 dataUrl 为空、或解码不出画面 | 这类**不报错**,只能靠脚本 ④ 抓 |
|
||||
| `placeholder` + `idle` | **不适用**(游戏代码、无法识别的二进制产物等),正常 | 不要当 bug 报 |
|
||||
| 卡面有棋盘格但 `data-preview-has-alpha` 缺失 | **这张图其实不透明**:棋盘格是图里画进去的(旧构建会额外再铺一层卡面棋盘格,两者叠在一起) | 脚本 ⑤ 分组确认;真透明卡才允许有 `'true'` |
|
||||
| 现象 | 说明 | 第一眼做什么 |
|
||||
| -------------------------------------------- | ------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------- |
|
||||
| 可读类型 + `idle` | 该卡**从未发出预览请求**(可见性门禁没挂上,或卡片在被裁切的容器里) | 先确认跑的是包含修复的新构建(见第 1 节),再核对 observer 补挂是否生效 |
|
||||
| `failed` + `data-preview-error` | 读取或解码真失败,读 `data-preview-error` 原文 | `retryable=false` 表示永久失败(超尺寸、损坏、类型不支持、不安全 SVG);可见性预取遇 failed 会直接放弃,不会自愈 |
|
||||
| `loaded` 但无主体节点 | 原生返回的 dataUrl 为空、或解码不出画面 | 这类**不报错**,只能靠脚本 ④ 抓 |
|
||||
| `placeholder` + `idle` | **不适用**(游戏代码、无法识别的二进制产物等),正常 | 不要当 bug 报 |
|
||||
| 卡面有棋盘格但 `data-preview-has-alpha` 缺失 | **这张图其实不透明**:棋盘格是图里画进去的(旧构建会额外再铺一层卡面棋盘格,两者叠在一起) | 脚本 ⑤ 分组确认;真透明卡才允许有 `'true'` |
|
||||
|
||||
**IPC 侧**:预览只走 4 条命令 `read_local_project_image_preview`、`read_local_project_media_preview`、`read_local_project_text_preview`、`cancel_local_project_resource_preview_scope`。媒体那条的 `category` **只接受 `art` / `audio`**,传栏目值会被原生拒绝,表现是卡片全空、没有缩略图。
|
||||
|
||||
@@ -153,26 +153,26 @@ window.__TAURI__.core.invoke = (cmd, args) => {
|
||||
|
||||
### 5.2 按步骤的排障索引
|
||||
|
||||
| 步骤 | 出问题时第一眼看什么 |
|
||||
| --- | --- |
|
||||
| S2 | `.game-workbench-stage` 的 `data-resource-view-state`;缺失说明误进 run。再调 `get_local_game_preview_status` 看活体,不要看 `manifest.preview` |
|
||||
| S4 | 对话后 `.agent/manifest.json` 的 `assets[]` 是否增长;底部 Agent Dock 的状态文案 |
|
||||
| S5 | 各 `[data-resource-book-category]` 下的卡片数量;某栏全空而 manifest 中明显有该类资产时,通常不是分区轴坏了 |
|
||||
| S6 | 5.1 的四条脚本 |
|
||||
| S7 | `[data-resource-book-view]` 是否为预期值,`.game-resource-book-scene-titlebar.is-active` 的 category 是否为预期栏目 |
|
||||
| S8 | `button[aria-label="搜索资源"]` 的 `aria-expanded`;`.game-resource-filter-panel` 是否真在 DOM(三字段同屏)。注意 jsdom 不校验可见性,只有真机截图或 `getBoundingClientRect` 才算数 |
|
||||
| S9 | `.is-selected` 数量与 `aria-pressed`;工具条容器 `role="toolbar"` 是否存在 |
|
||||
| S10 | 快速编辑失败文案;若为「当前素材类型不支持图片快速编辑」,说明源资源权威 `assetKind` 不在 17 项白名单里,这不是 V3 新缺陷 |
|
||||
| S11 | 生成入口在"没有 invoke 桥或项目未就绪"时**整体不渲染**,而不是渲染了但点了没反应 |
|
||||
| S12 | 保存后对比 `assets[n].tags` 与 `assets[n].category`,分类应逐字不变 |
|
||||
| S13 | 弹窗内 `role="alert"` 的错误文案;写盘失败时应看到文件已回滚原名 |
|
||||
| S14 | 是否有原生保存对话框;用户取消时是否停止后续批量保存 |
|
||||
| S15 | 弹窗是否出现 `被 N 个游戏版本使用`;不勾选时版本记录仍在(可读但悬空) |
|
||||
| S16 | `aria-label="当前版本:…"` 是否随切换更新;`[data-used-by-current-version="true"]` 集合是否变化 |
|
||||
| S17 | `get_local_game_preview_status` 返回的 `status/url/port`;`.agent/logs/preview.log` 的 `running` 行 |
|
||||
| S18 | 退出后重开项目是否落回资源总览;Rust 日志中的 `preview.gui_exit.stop_failed` |
|
||||
| S19 | chip 的 `data-resource-reference-id`;两页签 `role="tab"` 的 `aria-selected`;空态文案(`当前版本还没有绑定素材` / `当前项目还没有已登记素材` / `没有匹配的素材`) |
|
||||
| S20 | 弹窗标题「发送前提醒」;`localStorage['agc.chat.prompt-polish-reminder.disabled']` 的值;`润色中…` 或 `AI 润色失败,可重试` 状态行 |
|
||||
| 步骤 | 出问题时第一眼看什么 |
|
||||
| ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| S2 | `.game-workbench-stage` 的 `data-resource-view-state`;缺失说明误进 run。再调 `get_local_game_preview_status` 看活体,不要看 `manifest.preview` |
|
||||
| S4 | 对话后 `.agent/manifest.json` 的 `assets[]` 是否增长;底部 Agent Dock 的状态文案 |
|
||||
| S5 | 各 `[data-resource-book-category]` 下的卡片数量;某栏全空而 manifest 中明显有该类资产时,通常不是分区轴坏了 |
|
||||
| S6 | 5.1 的四条脚本 |
|
||||
| S7 | `[data-resource-book-view]` 是否为预期值,`.game-resource-book-scene-titlebar.is-active` 的 category 是否为预期栏目 |
|
||||
| S8 | `button[aria-label="搜索资源"]` 的 `aria-expanded`;`.game-resource-filter-panel` 是否真在 DOM(三字段同屏)。注意 jsdom 不校验可见性,只有真机截图或 `getBoundingClientRect` 才算数 |
|
||||
| S9 | `.is-selected` 数量与 `aria-pressed`;工具条容器 `role="toolbar"` 是否存在 |
|
||||
| S10 | 快速编辑失败文案;若为「当前素材类型不支持图片快速编辑」,说明源资源权威 `assetKind` 不在 17 项白名单里,这不是 V3 新缺陷 |
|
||||
| S11 | 生成入口在"没有 invoke 桥或项目未就绪"时**整体不渲染**,而不是渲染了但点了没反应 |
|
||||
| S12 | 保存后对比 `assets[n].tags` 与 `assets[n].category`,分类应逐字不变 |
|
||||
| S13 | 弹窗内 `role="alert"` 的错误文案;写盘失败时应看到文件已回滚原名 |
|
||||
| S14 | 是否有原生保存对话框;用户取消时是否停止后续批量保存 |
|
||||
| S15 | 弹窗是否出现 `被 N 个游戏版本使用`;不勾选时版本记录仍在(可读但悬空) |
|
||||
| S16 | `aria-label="当前版本:…"` 是否随切换更新;`[data-used-by-current-version="true"]` 集合是否变化 |
|
||||
| S17 | `get_local_game_preview_status` 返回的 `status/url/port`;`.agent/logs/preview.log` 的 `running` 行 |
|
||||
| S18 | 退出后重开项目是否落回资源总览;Rust 日志中的 `preview.gui_exit.stop_failed` |
|
||||
| S19 | chip 的 `data-resource-reference-id`;两页签 `role="tab"` 的 `aria-selected`;空态文案(`当前版本还没有绑定素材` / `当前项目还没有已登记素材` / `没有匹配的素材`) |
|
||||
| S20 | 弹窗标题「发送前提醒」;`localStorage['agc.chat.prompt-polish-reminder.disabled']` 的值;`润色中…` 或 `AI 润色失败,可重试` 状态行 |
|
||||
|
||||
**通用日志面**:WebView 的 `console.*` 会被镜像进 Rust `application.log`(标记 `__agcWebviewLogBridgeInstalled`),因此 F12 里打的日志事后也能在日志文件里复核。与本次验收最相关的两条是 `preview.gui_exit.stop_failed` 与 `[chat-markdown] render failed`。
|
||||
|
||||
@@ -185,32 +185,32 @@ api-server 是否本次重启:□ 是 □ 否
|
||||
测试项目:□ 真机 What do u wanna do kitten(只读) □ 新建项目 ____________________
|
||||
```
|
||||
|
||||
| 步骤 | 结果 | 备注(现象 / 证据 / 截图名) |
|
||||
| --- | --- | --- |
|
||||
| S1 建项并进工作台 | ☐ 过 ☐ 不过 | |
|
||||
| S2 落 `resource-overview` | ☐ 过 ☐ 不过 | |
|
||||
| S3 项目页面板(可选) | ☐ 过 ☐ 不过 ☐ 跳过 | |
|
||||
| S4 对话生成 → manifest 登记 | ☐ 过 ☐ 不过 | assets:___ → ___ |
|
||||
| S5 7 栏目归属正确 | ☐ 过 ☐ 不过 | 各栏计数:______ |
|
||||
| S6 卡片预览台账(idle / failed / 空) | ☐ 过 ☐ 不过 | idle:__ failed:__ 空:__ |
|
||||
| S7 栏目导航(含「所有资源」/ 下一页 / 空栏目) | ☐ 过 ☐ 不过 | |
|
||||
| S8 单一筛选浮层(三字段)+ 不清条件 | ☐ 过 ☐ 不过 | |
|
||||
| S9 选择 / 多选 / 框选 / 工具条 | ☐ 过 ☐ 不过 | 实际渲染的动作:______ |
|
||||
| S10 图片快速编辑(新素材 + 血缘) | ☐ 过 ☐ 不过 | 是否 400:☐ 否 ☐ 是(原因:____) |
|
||||
| S11 生成入口(视频 / 音效 / 背景音乐) | ☐ 过 ☐ 不过 ☐ 未验 | |
|
||||
| S12 编辑标签(只改 tags) | ☐ 过 ☐ 不过 | category 是否原样:☐ 是 ☐ 否 |
|
||||
| S13 重命名(id 不变) | ☐ 过 ☐ 不过 | |
|
||||
| S14 下载(另存为) | ☐ 过 ☐ 不过 | |
|
||||
| S15 删除三分支 | ☐ 无引用 ☐ 引用未勾选 ☐ 勾选连带 | |
|
||||
| S16 版本切换 + 当前使用高亮 + @ 同步 | ☐ 过 ☐ 不过 | |
|
||||
| S17 播放 → 运行视图 + 本地预览 | ☐ 过 ☐ 不过 | url:______________ |
|
||||
| S18 停止预览 / 退出收尾对齐 stopped | ☐ 过 ☐ 不过 | |
|
||||
| S19 @ 引用两页签 + 独立筛选 + chip 原子性 | ☐ 过 ☐ 不过 | |
|
||||
| S19a @ 面板标签筛选(多标签 AND,叠加关键字与功能分类) | ☐ 过 ☐ 不过 | |
|
||||
| S19b 快速编辑提示词内 @ 引用(同聊天引用模型与字面量) | ☐ 过 ☐ 不过 | |
|
||||
| S20 润色 + 发送前提醒 + 不再提醒 | ☐ 过 ☐ 不过 | |
|
||||
| S21 台账(即时同步 / 双布局 / 无溢出) | ☐ 过 ☐ 不过 | 1280×800 ☐ 1280×720 ☐ |
|
||||
| 门禁:typecheck / AGC tests / check:encoding / git diff --check | ☐ 过 ☐ 不过 | |
|
||||
| 步骤 | 结果 | 备注(现象 / 证据 / 截图名) |
|
||||
| --------------------------------------------------------------- | -------------------------------- | ------------------------------------- |
|
||||
| S1 建项并进工作台 | ☐ 过 ☐ 不过 | |
|
||||
| S2 落 `resource-overview` | ☐ 过 ☐ 不过 | |
|
||||
| S3 项目页面板(可选) | ☐ 过 ☐ 不过 ☐ 跳过 | |
|
||||
| S4 对话生成 → manifest 登记 | ☐ 过 ☐ 不过 | assets:**_ → _** |
|
||||
| S5 7 栏目归属正确 | ☐ 过 ☐ 不过 | 各栏计数:**\_\_** |
|
||||
| S6 卡片预览台账(idle / failed / 空) | ☐ 过 ☐ 不过 | idle:** failed:** 空:\_\_ |
|
||||
| S7 栏目导航(含「所有资源」/ 下一页 / 空栏目) | ☐ 过 ☐ 不过 | |
|
||||
| S8 单一筛选浮层(三字段)+ 不清条件 | ☐ 过 ☐ 不过 | |
|
||||
| S9 选择 / 多选 / 框选 / 工具条 | ☐ 过 ☐ 不过 | 实际渲染的动作:**\_\_** |
|
||||
| S10 图片快速编辑(新素材 + 血缘) | ☐ 过 ☐ 不过 | 是否 400:☐ 否 ☐ 是(原因:\_\_\_\_) |
|
||||
| S11 生成入口(视频 / 音效 / 背景音乐) | ☐ 过 ☐ 不过 ☐ 未验 | |
|
||||
| S12 编辑标签(只改 tags) | ☐ 过 ☐ 不过 | category 是否原样:☐ 是 ☐ 否 |
|
||||
| S13 重命名(id 不变) | ☐ 过 ☐ 不过 | |
|
||||
| S14 下载(另存为) | ☐ 过 ☐ 不过 | |
|
||||
| S15 删除三分支 | ☐ 无引用 ☐ 引用未勾选 ☐ 勾选连带 | |
|
||||
| S16 版本切换 + 当前使用高亮 + @ 同步 | ☐ 过 ☐ 不过 | |
|
||||
| S17 播放 → 运行视图 + 本地预览 | ☐ 过 ☐ 不过 | url:******\_\_****** |
|
||||
| S18 停止预览 / 退出收尾对齐 stopped | ☐ 过 ☐ 不过 | |
|
||||
| S19 @ 引用两页签 + 独立筛选 + chip 原子性 | ☐ 过 ☐ 不过 | |
|
||||
| S19a @ 面板标签筛选(多标签 AND,叠加关键字与功能分类) | ☐ 过 ☐ 不过 | |
|
||||
| S19b 快速编辑提示词内 @ 引用(同聊天引用模型与字面量) | ☐ 过 ☐ 不过 | |
|
||||
| S20 润色 + 发送前提醒 + 不再提醒 | ☐ 过 ☐ 不过 | |
|
||||
| S21 台账(即时同步 / 双布局 / 无溢出) | ☐ 过 ☐ 不过 | 1280×800 ☐ 1280×720 ☐ |
|
||||
| 门禁:typecheck / AGC tests / check:encoding / git diff --check | ☐ 过 ☐ 不过 | |
|
||||
|
||||
## 7. 风险与盲区
|
||||
|
||||
@@ -218,30 +218,30 @@ api-server 是否本次重启:□ 是 □ 否
|
||||
|
||||
需求源头文档《GameAgent客户端画布与资源工作台 V3.0》(飞书 wiki 文档,文档 token 以 `N4Kiw…` 开头)的文本层共 **796 行文本、5 段视频、2 张图片**。以下条款**只能靠视频或截图验收**,文本层只有"标题式"描述,缺可判定细节:
|
||||
|
||||
| # | 位置 | 媒体 | 文本只到哪 | 缺什么(无法从文本验收的部分) |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 1 | §3.1 默认视图 | **截图**(图注「资源管理视图」) | "默认进入'全部资源'视图……功能画布分类:UI 与交互、角色与对象、场景与环境、音频、文档、待归类" | 总览的**版式**:缩略卡片排布、层叠张数、缩略图尺寸与间距、标题栏位置 |
|
||||
| 2 | §3.2 筛选 | **视频** `20260907-0309-29.2729736.mp4`("增加自定义标签") | "点击已有标签 / 输入新标签 / 保存后同步进全局标签库" | 面板**长什么样**、pill 排布、输入框提示文案、保存按钮位置 |
|
||||
| 3 | §3.2 筛选 | **视频** `20260907-0321-46.9223141.mp4`("查看素材标签") | "标签不默认显示在卡片上,需在'编辑标签'中查看" | 查看入口与展示形态 |
|
||||
| 4 | §3.2 筛选 | **视频** `20260907-0322-33.1365605.mp4`("按照标签筛选素材") | 文本给了筛选逻辑(条件即时生效 / 清空恢复 / 不影响归属) | 筛选面板的交互与视觉:多标签是 AND 还是 OR 的**界面表达**、标签汇总列表样式 |
|
||||
| 5 | §3.2 筛选 | **视频** `20260907-0323-34.3948031.mp4`("删除标签") | "删除标签时从所有素材和筛选条件中移除;'管理全部标签'里可删" | 「管理全部标签」这个入口 / 面板本身**只存在于视频**,AGC 侧未找到对应实现 |
|
||||
| 6 | §10.1 / §10.2 @ 引用 | **视频** `20260906-0812-23.9790394.mp4` + **截图**(提示框"请把@开始按钮并保持原位置",截图注明"参考 loveart") | "资源卡工具栏提供 @引用 工具";"所有@后产生的标签都在对话框当中,用户可以在标签前后编辑上下文" | chip 的**外观**(是否带图标 / 缩略图 / 颜色)、插入后光标落点、与 Lovart 的对照样式 |
|
||||
| 7 | PRD L139(项目管理页) | 文档**明确要求**提交截图 + 视频 | "截图必须使用含 Web、Godot 和无效状态的 populated fixture;视频必须演示搜索、清除、行尾菜单及可观察的项目操作结果" | 这条本身就是"必须交视频"的要求,不是可由文本判定的功能条款 |
|
||||
| # | 位置 | 媒体 | 文本只到哪 | 缺什么(无法从文本验收的部分) |
|
||||
| --- | ---------------------- | --------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------- |
|
||||
| 1 | §3.1 默认视图 | **截图**(图注「资源管理视图」) | "默认进入'全部资源'视图……功能画布分类:UI 与交互、角色与对象、场景与环境、音频、文档、待归类" | 总览的**版式**:缩略卡片排布、层叠张数、缩略图尺寸与间距、标题栏位置 |
|
||||
| 2 | §3.2 筛选 | **视频** `20260907-0309-29.2729736.mp4`("增加自定义标签") | "点击已有标签 / 输入新标签 / 保存后同步进全局标签库" | 面板**长什么样**、pill 排布、输入框提示文案、保存按钮位置 |
|
||||
| 3 | §3.2 筛选 | **视频** `20260907-0321-46.9223141.mp4`("查看素材标签") | "标签不默认显示在卡片上,需在'编辑标签'中查看" | 查看入口与展示形态 |
|
||||
| 4 | §3.2 筛选 | **视频** `20260907-0322-33.1365605.mp4`("按照标签筛选素材") | 文本给了筛选逻辑(条件即时生效 / 清空恢复 / 不影响归属) | 筛选面板的交互与视觉:多标签是 AND 还是 OR 的**界面表达**、标签汇总列表样式 |
|
||||
| 5 | §3.2 筛选 | **视频** `20260907-0323-34.3948031.mp4`("删除标签") | "删除标签时从所有素材和筛选条件中移除;'管理全部标签'里可删" | 「管理全部标签」这个入口 / 面板本身**只存在于视频**,AGC 侧未找到对应实现 |
|
||||
| 6 | §10.1 / §10.2 @ 引用 | **视频** `20260906-0812-23.9790394.mp4` + **截图**(提示框"请把@开始按钮并保持原位置",截图注明"参考 loveart") | "资源卡工具栏提供 @引用 工具";"所有@后产生的标签都在对话框当中,用户可以在标签前后编辑上下文" | chip 的**外观**(是否带图标 / 缩略图 / 颜色)、插入后光标落点、与 Lovart 的对照样式 |
|
||||
| 7 | PRD L139(项目管理页) | 文档**明确要求**提交截图 + 视频 | "截图必须使用含 Web、Godot 和无效状态的 populated fixture;视频必须演示搜索、清除、行尾菜单及可观察的项目操作结果" | 这条本身就是"必须交视频"的要求,不是可由文本判定的功能条款 |
|
||||
|
||||
**补齐方式**:① 由产品口述 2–3 句关键视觉约束后改写为判据;② 用 `lark-cli docs +media-download` 取出这 5 段视频与 2 张图并抽取静帧后逐帧写判据;③ 降级为"人工目视,不计入自动化判据",记录表里只打勾。
|
||||
|
||||
### 7.2 本地环境无法验证 / 需要额外条件的项
|
||||
|
||||
| 项 | 为什么本地不可验 | 建议 |
|
||||
| --- | --- | --- |
|
||||
| 双窗口布局 CAS 冲突(L509) | 需同一项目同时开两个客户端窗口、基于同一 revision 同时写入 | 标为"需专项构造",或只跑 `resourceCanvasLayoutContract` 定向测试 |
|
||||
| 4096 资源链式 fixture 性能(L522) | 需构造大规模 fixture | 交给定向测试,不进人工主线 |
|
||||
| `1280×720` / `1280×800` 视觉验收(L512、L577) | 必须真实窗口 + 截图或测量 `document/body` 的 `client` 与 `scroll` | 每条视觉结论附截图;jsdom 不做布局,vitest 中的可见性断言是假守卫 |
|
||||
| Windows 平台性断言 | 非 Linux 平台本地预览用**随机临时端口**;端口池耗尽回退、`GENARRATIVE_DEV_PORT_RANGE`、命令沙箱模块**只在 Linux 生效**;文件锁 Unix 用 `flock`、Windows 不共享句柄 | 涉及这些的判据不能反推平台行为。反之"delete-pending 等行为级 Windows 用例 CI 没有 runner,只在 Windows 本地跑"——本地反而能验到 CI 验不到的 |
|
||||
| `build-gate` 代理假红 | 见第 4 节 | 跑前清代理变量,或用 `build:raw` |
|
||||
| worktree 专属假红 | worktree 里 `check-repository-ci.sh` 第一步即"比较基线不可用";jsdom 拿不到 fit key | 改用 Windows git 逐条等价执行;这两类红不当缺陷 |
|
||||
| 既有失败用例 | 技术方案记录过 AGC 前端 5 条既有失败(C3 交互改造后待更新用例);Rust 并行跑 `project::` 约 10 条环境依赖失败,仓库口径是 `--test-threads=1` | 预置"已知红名单",避免重复排查 |
|
||||
| F12 看不到 Rust → api-server 的 HTTP | 见 5.1 更正 | 用 api-server 日志或 `.agent/logs` |
|
||||
| 项 | 为什么本地不可验 | 建议 |
|
||||
| ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| 双窗口布局 CAS 冲突(L509) | 需同一项目同时开两个客户端窗口、基于同一 revision 同时写入 | 标为"需专项构造",或只跑 `resourceCanvasLayoutContract` 定向测试 |
|
||||
| 4096 资源链式 fixture 性能(L522) | 需构造大规模 fixture | 交给定向测试,不进人工主线 |
|
||||
| `1280×720` / `1280×800` 视觉验收(L512、L577) | 必须真实窗口 + 截图或测量 `document/body` 的 `client` 与 `scroll` | 每条视觉结论附截图;jsdom 不做布局,vitest 中的可见性断言是假守卫 |
|
||||
| Windows 平台性断言 | 非 Linux 平台本地预览用**随机临时端口**;端口池耗尽回退、`GENARRATIVE_DEV_PORT_RANGE`、命令沙箱模块**只在 Linux 生效**;文件锁 Unix 用 `flock`、Windows 不共享句柄 | 涉及这些的判据不能反推平台行为。反之"delete-pending 等行为级 Windows 用例 CI 没有 runner,只在 Windows 本地跑"——本地反而能验到 CI 验不到的 |
|
||||
| `build-gate` 代理假红 | 见第 4 节 | 跑前清代理变量,或用 `build:raw` |
|
||||
| worktree 专属假红 | worktree 里 `check-repository-ci.sh` 第一步即"比较基线不可用";jsdom 拿不到 fit key | 改用 Windows git 逐条等价执行;这两类红不当缺陷 |
|
||||
| 既有失败用例 | 技术方案记录过 AGC 前端 5 条既有失败(C3 交互改造后待更新用例);Rust 并行跑 `project::` 约 10 条环境依赖失败,仓库口径是 `--test-threads=1` | 预置"已知红名单",避免重复排查 |
|
||||
| F12 看不到 Rust → api-server 的 HTTP | 见 5.1 更正 | 用 api-server 日志或 `.agent/logs` |
|
||||
|
||||
### 7.3 已知未做 / 已取消(不要报成缺陷)
|
||||
|
||||
@@ -252,7 +252,7 @@ api-server 是否本次重启:□ 是 □ 否
|
||||
5. **「首轮进度投影」无编码级判据**,可见性由底部 Agent 状态栏承载。
|
||||
6. **浮出工具条的视觉位置未做真机核验**(jsdom 不执行 Web Animations,只覆盖结构与 class)。
|
||||
7. **重命名不改游戏源码中的旧 `assets/<name>` 引用**。
|
||||
8. **C9 参考图弹窗在 AGC 侧入口未确认**:全仓检索 `添加参考图` 只命中共享 `src/components/image-editor/*`,AGC 的 `ResourceCanvasGenerationPanelView` 未引用它,可能处于"组件已实现、AGC 入口未接"状态。建议真机点一次生成 / 编辑面板确认;若无入口,C9 不计入本轮主线。**注**:资源替换复用同一个 `ImageCanvasProjectAssetPickerDialog`,它在 AGC 侧是首次使用(同一个弹窗、不同的 opt-in 参数)。
|
||||
8. **C9 参考图弹窗在 AGC 侧入口未确认**:全仓检索 `添加参考图` 只命中共享 `src/components/image-editor/*`,AGC 的 `ResourceCanvasGenerationPanelView` 未引用它,可能处于"组件已实现、AGC 入口未接"状态。建议真机点一次生成 / 编辑面板确认;若无入口,C9 不计入本轮主线。**注**:资源替换复用同一个 `ImageCanvasProjectAssetPickerDialog`,它在 AGC 侧是首次使用(2026-09-21 起走该组件的 `nonModal` 非模态形态:不铺遮罩、面板锚在画布容器右上角,见 S15a)。
|
||||
9. **运行视图存在「点选素材」按钮**,但 #309 的"不做"清单包含运行画面点选,口径冲突待定。
|
||||
10. **替换成功后运行画面不会立刻变**:按 C7 与本轮口径只做记录层 + UI 层,可见变化是资源卡"当前使用"高亮移到替换素材与 `@` 面板"当前版本素材"更新;**不会新增版本卡、也不切换版本**,这不等于替换失败。
|
||||
11. **替换候选弹窗不加载缩略图**:AGC 的素材预览要经带 scope 的原生读取器拿 Blob URL,弹窗内没有同步 `src`,因此候选行只渲染类型占位(不给 `<img>` 喂空串、不挂破图)。
|
||||
@@ -261,15 +261,15 @@ api-server 是否本次重启:□ 是 □ 否
|
||||
|
||||
### 7.4 文档与代码的偏差(需明确按哪个判)
|
||||
|
||||
| 偏差 | 文档表述 | 代码实际 | 建议 |
|
||||
| --- | --- | --- | --- |
|
||||
| 资源卡「打开详情」 | PRD L61–62 要求"打开详情"与"播放 / 暂停"是可分别键盘聚焦的同级按钮 | 不存在该按钮(测试显式断言为 `null`);真正入口是 `选中资源:<栏目> <名>`,播放按钮是 `播放/暂停 <名>`;**只读信息浮层已回归**(2026-09-11 工具条「信息」动作) | 按 #309 C3(取消资源详情面板与全屏编辑路由)判,**C3 取消的仍只是可编辑详情面板与全屏编辑路由**;PRD L61–62 建议同步修订 |
|
||||
| 运行入口不可用文案 | PRD L165 显示"当前无可运行版本",但仍允许点击 | 源码中无此字符串;实现为「首个可运行原型尚未完成,运行视图暂不可用」;且播放按钮 `disabled`,只有"运行"tab 不 disabled、用 `data-unavailable` 表达 | 建议按"tab 可点 + 播放按钮置灰"判,并把 PRD L165 文案对齐实现 |
|
||||
| 画布生成入口 | #309「画布生成入口未接」 | 已接两条:浮层入口只做视频;音频与图片类在栏目画布底部工具栏(见 S11a / PRD §3.10) | 按代码与当前产品口径判,并更新 #309 进度段 |
|
||||
| 分类手设入口 | 旧版 PRD「用户可在分类与标签面板手动设置 category」 | 已移除;面板只编辑 tags(已随 `6bdc8bbd9` 入库) | 按新口径判 |
|
||||
| 真机 manifest 分类分布 | — | 落盘 `unclassified 57 / ui-interaction 3 / scene 1` | **读时自愈**会把 kind 可明确分类的资产显示到正确栏目,判定必须看 UI 栏目计数,不能看 manifest 文件,否则必然误报"分类不生效" |
|
||||
| 偏差 | 文档表述 | 代码实际 | 建议 |
|
||||
| ---------------------- | ------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
|
||||
| 资源卡「打开详情」 | PRD L61–62 要求"打开详情"与"播放 / 暂停"是可分别键盘聚焦的同级按钮 | 不存在该按钮(测试显式断言为 `null`);真正入口是 `选中资源:<栏目> <名>`,播放按钮是 `播放/暂停 <名>`;**只读信息浮层已回归**(2026-09-11 工具条「信息」动作) | 按 #309 C3(取消资源详情面板与全屏编辑路由)判,**C3 取消的仍只是可编辑详情面板与全屏编辑路由**;PRD L61–62 建议同步修订 |
|
||||
| 运行入口不可用文案 | PRD L165 显示"当前无可运行版本",但仍允许点击 | 源码中无此字符串;实现为「首个可运行原型尚未完成,运行视图暂不可用」;且播放按钮 `disabled`,只有"运行"tab 不 disabled、用 `data-unavailable` 表达 | 建议按"tab 可点 + 播放按钮置灰"判,并把 PRD L165 文案对齐实现 |
|
||||
| 画布生成入口 | #309「画布生成入口未接」 | 已接两条:浮层入口只做视频;音频与图片类在栏目画布底部工具栏(见 S11a / PRD §3.10) | 按代码与当前产品口径判,并更新 #309 进度段 |
|
||||
| 分类手设入口 | 旧版 PRD「用户可在分类与标签面板手动设置 category」 | 已移除;面板只编辑 tags(已随 `6bdc8bbd9` 入库) | 按新口径判 |
|
||||
| 真机 manifest 分类分布 | — | 落盘 `unclassified 57 / ui-interaction 3 / scene 1` | **读时自愈**会把 kind 可明确分类的资产显示到正确栏目,判定必须看 UI 栏目计数,不能看 manifest 文件,否则必然误报"分类不生效" |
|
||||
|
||||
## 8. 验收边界
|
||||
|
||||
- 本用例覆盖:资源总览 / 栏目分页画布、资源卡预览与本地受控读取、资源卡选择与多选、快速编辑派生(含提示词内 @ 引用)、画布生成入口(浮层视频 + 栏目工具栏图片类与音频)、编辑标签、重命名、下载、删除三分支、版本切换与当前使用高亮、**资源替换(入口放行判据 / 候选禁用与原因 / 格式提示 / 写入载荷 / 拒绝零副作用 / 成功后不切版本不重载预览 / 弹窗与画布点选两条入口同一条写入链路)**、聊天 @ 引用(两页签 + 功能分类 + 多标签筛选 + 搜索)与原子 chip、AI 润色与发送前提醒、本地预览启动与退出收尾、两份布局 sidecar 台账。
|
||||
- 本用例覆盖:资源总览 / 栏目分页画布、资源卡预览与本地受控读取、资源卡选择与多选、快速编辑派生(含提示词内 @ 引用)、画布生成入口(浮层视频 + 栏目工具栏图片类与音频)、编辑标签、重命名、下载、删除三分支、版本切换与当前使用高亮、**资源替换(入口放行判据 / 候选禁用与原因 / 格式提示 / 写入载荷 / 拒绝零副作用 / 成功后不切版本不重载预览 / 非模态面板 + 画布点选目标同一条写入链路)**、聊天 @ 引用(两页签 + 功能分类 + 多标签筛选 + 搜索)与原子 chip、AI 润色与发送前提醒、本地预览启动与退出收尾、两份布局 sidecar 台账。
|
||||
- 本用例不覆盖(另走专项或定向测试):双窗口 CAS 冲突、大规模 fixture 性能、素材创作无限画布阶段一至五的草稿 / 事务 / 恢复矩阵(见 `【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md` 与配套专题)、UI 编辑器子路由、主站图片编辑器回归(见 PRD §7.7)。资源替换的后端矩阵(版本绑定改写放行的六条不变式与两组互斥、绑定改写两条路径、硬门禁与提示、CAS、四条拒绝路径、读时自愈口径、审计留痕)见 `apps/ai-game-creator-shell/src-tauri/src/project/manifest/version_binding_rewrite_tests.rs` 与 `apps/ai-game-creator-shell/src-tauri/src/tests/version_resource_replacement.rs`,前端矩阵见 `apps/ai-game-creator-shell/tests/resourceVersionReplacement*.test.ts(x)`。
|
||||
|
||||
Reference in New Issue
Block a user