Merge remote-tracking branch 'origin/master' into feat/error-report
# Conflicts: # apps/ai-game-creator-shell/src-tauri/src/main.rs # apps/ai-game-creator-shell/src-tauri/src/ui_editor/commands/merge.rs # apps/ai-game-creator-shell/src-tauri/src/ui_editor/commands/recognition.rs # apps/ai-game-creator-shell/src-tauri/src/ui_editor/commands/ui_design_suggestion.rs # docs/technical/【后台管理】多账号与Tab访问权限方案-2026-07-14.md
This commit is contained in:
@@ -20,6 +20,10 @@
|
||||
## AI 游戏创作与 Agent Runtime
|
||||
|
||||
- [AI 游戏创作智能体 App 实施计划](./technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md):当前 DirectProject、受控语义工具、UI workflow、资源和运行时合同。
|
||||
- [DirectProject 客户端 Skill 与 MCP 扩展导入方案](./technical/【技术方案】DirectProject客户端Skill与MCP扩展导入方案-2026-08-31.md):客户端扩展导入、按独立 Skill/MCP 拆分、命名、启用和启动时注入边界。
|
||||
- [AGC 客户端更新检查与下载](./technical/【技术方案】AGC客户端更新检查与下载-2026-08-31.md):启动版本检测、OSS 清单格式和下载约定。
|
||||
- [DirectProject 本轮附件路径映射](./technical/【技术方案】DirectProject本轮附件路径映射-2026-08-31.md):Direct 首轮只映射附件原名与项目相对路径,不灌正文、不区别 GDD。
|
||||
- [Direct 回合行为审计账本](./technical/【技术方案】Direct回合行为审计账本-2026-08-31.md):Direct GUI 回合把 native 读 / MCP / 写文件落成项目内有界时间线,用于判断有没有打开本轮附件。
|
||||
- [项目开发工作台 PRD](./prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md):当前工作台页面和验收边界。
|
||||
- [AGC 错误报告与诊断上传](./technical/【技术方案】AGC错误报告与诊断上传-2026-08-31.md):当前进程错误事件、应用级日志和管理员查看器合同。
|
||||
- [立项策划 Agent(Fast GDD)](<./technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md>):当前策划入口、审批和恢复合同。
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -322,7 +322,7 @@ type UpdateProjectResourceCanvasLayoutResult =
|
||||
- type 模式资源集合变化时保留全部仍存在的坐标,只为新 ID 计算默认位置,并删除已确认失效的旧 ID。dependency 模式只永久保留 `manuallyPlaced=true` 的历史坐标;`manuallyPlaced=false` 属于可派生自动位置,在 Rust 关系图首次就绪、`dependencyDepth` 或资源拓扑身份签名(精确引用端点和聚合 task-flow 成员)变化后按最终拓扑确定性重算。签名以稳定资源 ID 的规范端点 / 成员序列生成固定大小摘要,不使用显示名称或浏览器测量值;自动重算不得移动手动坐标,协调结果与持久布局逐项一致时不得产生 CAS 写入。
|
||||
- 搜索或筛选只隐藏卡片,不删除、压缩或重排其坐标;清空搜索后恢复原位置。
|
||||
- 窗口尺寸变化只改变当前栏目的可视范围,不回写或裁切持久坐标,也不因资源 extent 或 resize 把已平移的 viewport 拉回内容边界。当前客户端继续以 `1280×800` 横屏合同验收。
|
||||
- 任一栏目出现资源后,资源管理固定使用 `设计文档 -> 美术资源 -> 音乐音效 -> 游戏代码 -> 项目版本` 五栏目分页画布;每个栏目按 `projectId + mode + category` 保留独立 viewport。普通 wheel 切换栏目,`Ctrl/Cmd + wheel` 以指针位置为锚点缩放当前无限画布,空白拖动只平移当前栏目;非空状态不提供分区高度、分区内部滚动或分区内容倍率。搜索和详情开关不得重置 viewport,项目、mode 或栏目切换只恢复各自会话状态,显式复位才重新适配当前栏目内容。
|
||||
- 任一可见资源出现后,普通用户资源管理固定使用 `设计文档 -> 美术资源 -> 音乐音效 -> 项目版本` 四栏目分页画布;游戏代码仍保留在内部资源、布局和依赖事实中,但不进入普通资源画布的导航、分页、卡片、搜索或详情入口。每个可见栏目按 `projectId + mode + category` 保留独立 viewport,普通 wheel 切换栏目,`Ctrl/Cmd + wheel` 以指针位置为锚点缩放当前无限画布,空白拖动只平移当前栏目;非空状态不提供分区高度、分区内部滚动或分区内容倍率。搜索和详情开关不得重置 viewport,项目、mode 或栏目切换只恢复各自会话状态,显式复位才重新适配当前栏目内容。
|
||||
- 首次载入项目中的既有资源不显示未读标识。当前会话内,非当前栏目出现稳定 ID 的新资源时,在对应栏目名称右上角显示红点;当前栏目新增资源不显示红点,用户通过点击、滚轮或程序跳转进入该栏目后立即清除。未读状态只属于当前前端会话,并按 `projectPath + projectId` 隔离,切换项目时清空,不写入 manifest、布局 sidecar 或后端。
|
||||
- 打开项目、切换 mode 或当前 mode 首次出现新资源时执行“读取 -> 协调 -> 必要时 CAS 写入”;dependency 模式必须先等待与当前 `projectPath + projectId + resource inputs` 匹配的 Rust 图进入 `ready` 或 `failed` 终态,等待期间不得创建 fallback、读取 sidecar、协调资源或入队保存。`failed` 只允许以空图降级初始化一次。项目或 mode 已切换后返回的旧异步结果必须丢弃。
|
||||
- 同一 `projectPath + projectId + mode` 的首次读取与资源集合协调必须分开:资源集合变化不得取消已经发出的读取或保存。当前 scope 内资源自动协调写入使用单写者 FIFO,任一时刻最多一个 CAS 在途,后一笔必须使用前一笔成功返回的 revision。切换项目或 mode 后,旧 scope 的在途请求不能阻塞新 scope 队列;前端放弃旧请求槽位并丢弃其迟到响应,后端继续依靠 `expectedProjectId + expectedRevision + 系统锁` 仲裁已发出的请求。
|
||||
@@ -355,7 +355,7 @@ type UpdateProjectResourceCanvasLayoutResult =
|
||||
|
||||
### 5.3 资源类型与替换兼容性(P1)
|
||||
|
||||
实现状态(2026-08-23):当前资源投影与栏目页顺序收口为“设计文档 -> 美术资源 -> 音乐音效 -> 游戏代码 -> 项目版本”。设计文档接收受支持的 UTF-8 文档、代码资产中的文档类型和合法 Agent 文本回执;项目版本只接收显式 `ProjectVersionResourceSummary` read model,未知任务产物不得兜底为版本;美术资源接收图片、SVG、动画和视频类产物;音乐音效接收 manifest 资产、上传登记资产和已完成任务 `artifacts` 明确声明的音频产物;游戏代码接收 Direct Codex / 任务产物登记的 HTML、CSS 和 JavaScript。无法识别的二进制任务产物和附件不进入资源画布。受控读取、中央聚焦、失败空态与媒体播放不改变 manifest 真相;编辑成功后只追加新的 asset 或版本子记录。
|
||||
实现状态(2026-08-29):内部资源投影仍识别“设计文档、美术资源、音乐音效、游戏代码、项目版本”五类事实,但普通用户资源画布只展示“设计文档 -> 美术资源 -> 音乐音效 -> 项目版本”四个栏目;游戏代码不进入画布导航、分页、卡片、搜索或详情入口。设计文档接收受支持的 UTF-8 文档、代码资产中的文档类型和合法 Agent 文本回执;项目版本只接收显式 `ProjectVersionResourceSummary` read model,未知任务产物不得兜底为版本;美术资源接收图片、SVG、动画和视频类产物;音乐音效接收 manifest 资产、上传登记资产和已完成任务 `artifacts` 明确声明的音频产物;游戏代码继续接收 Direct Codex / 任务产物登记的 HTML、CSS 和 JavaScript,底层文件、manifest 事实、生成/编辑/运行能力、项目版本引用与依赖关系不变。无法识别的二进制任务产物和附件不进入资源画布。受控读取、中央聚焦、失败空态与媒体播放不改变 manifest 真相;编辑成功后只追加新的 asset 或版本子记录。
|
||||
|
||||
资源身份固定使用 manifest asset ID、正式 version ID、Agent ID + run ID 或已导入资源稳定路径;显示标题、来源文案变化不得改变 `resourceId`,从而避免布局、依赖边、选择和聚焦状态因改名失效。
|
||||
|
||||
@@ -533,12 +533,12 @@ type ProjectAgentMudPointAttribution = {
|
||||
|
||||
### 7.5 资源栏目分页无限画布验收
|
||||
|
||||
1. 完全空项目继续显示全部栏目的分区展览;任一栏目出现资源后,dependency / type 都切换为固定五栏目分页画布,悬浮 Dock、底部下一页标题和普通 wheel 可访问全部栏目,空栏目也可打开空画布。
|
||||
2. 每个 `projectId + dependency|type + document|art|audio|code|version` 组合保留独立 viewport;切换栏目、模式、项目和打开 / 关闭详情后恢复对应平移与缩放,窗口 resize、媒体测量和资源 extent 变化不得重置用户 viewport。
|
||||
1. 完全空项目继续显示四个可见栏目的分区展览;任一可见栏目出现资源后,dependency / type 都切换为固定四栏目分页画布,悬浮 Dock、底部下一页标题和普通 wheel 可访问全部可见栏目,空栏目也可打开空画布;游戏代码栏目和代码卡片不出现。
|
||||
2. 每个 `projectId + dependency|type + document|art|audio|version` 组合保留独立 viewport;隐藏代码资源的历史内部坐标和 sidecar 会话状态不被删除或重写,切换栏目、模式、项目和打开 / 关闭详情后恢复对应可见栏目平移与缩放,窗口 resize、媒体测量和资源 extent 变化不得重置用户 viewport。
|
||||
3. 当前栏目允许空白拖动无限平移;`Ctrl/Cmd + wheel` 以指针为锚点缩放,显式复位按包含负坐标资源在内的完整 bounds 适配内容。非空状态不显示分区高度、分区内部滚动或分区内容倍率操作。
|
||||
4. 资源卡超过 `5px` 阈值后进入拖动,预览和 dependency 线同步移动;成功释放只提交一次 `manuallyPlaced=true` CAS,取消、移出释放、媒体控制点击和未超过阈值均不写布局。
|
||||
5. dependency 引导线消费当前栏目的同类型精确引用,并与卡片共享同一 viewport transform;平移、缩放、拖动预览、搜索和 resize 后端点保持对齐,type 模式不渲染引导线。
|
||||
6. 栏目分页、viewport 和资源卡拖动只修改工作台会话状态或资源布局 sidecar,不改 manifest、项目 mutation revision、Runtime verification、Agent 权限和预览状态;图片、视频、音频、文档、代码、版本卡片及非模态详情回归全部通过。
|
||||
6. 栏目分页、viewport 和资源卡拖动只修改工作台会话状态或资源布局 sidecar,不改 manifest、项目 mutation revision、Runtime verification、Agent 权限和预览状态;图片、SVG、视频、音频、文档、任务产物、Agent 回执、项目版本卡片及非模态详情回归全部通过,游戏代码仅保留内部事实而不进入普通资源画布。
|
||||
|
||||
### 7.6 阶段七完整验收
|
||||
|
||||
@@ -550,7 +550,6 @@ type ProjectAgentMudPointAttribution = {
|
||||
|
||||
### 7.6 素材创作无限画布阶段一至五最终验收
|
||||
|
||||
|
||||
1. 网站与 Tauri 实际 import 同一份 `@genarrative/image-canvas-core` 和 `@genarrative/image-canvas-react`,客户端没有复制的主站画布目录;viewport、selection、变换、renderer 与 history 算法位于共享层,宿主只保留事件接线与 adapter 副作用。
|
||||
2. “新增资源”在当前产品切片中保持禁用;“编辑资源”只接受现有资源。图片 refine 为稳定 `sourceAssetId` 保存一个持续精修草稿和多个私有 `draft-media` 候选;只有用户显式“设为最终图”时才安装新的正式 PNG,并保持原 asset ID 不变、事务化更新其 manifest `localPath/mediaType`。候选图不进入 manifest 或资源总览。其他类型继续只追加派生文件/asset 或子版本。
|
||||
3. 草稿 schema、revision、容量、项目身份、OS 锁、CAS、恢复副本和媒体引用符合权威专题;损坏、未知 schema、身份错配和超限均失败关闭。应用重启后必须按 `projectId + intent + sourceAssetId + 可恢复/可继续状态` 从正式 sidecar 唯一发现原 refine 草稿并保留 `draftId`、候选图和生成占位,同时生成新 `sessionId`;零条才允许创建,多条必须进入对账,不得依赖进程内 Map 或按时间猜测。
|
||||
@@ -559,14 +558,14 @@ type ProjectAgentMudPointAttribution = {
|
||||
6. 搜索/筛选隐藏新资源时保留条件,明确提示“新资源已保存,当前筛选条件下不可见”,只通过显式动作清除条件并定位。
|
||||
7. 现有资源编辑、生成、保存、取消、失败和恢复必须覆盖权威专题 §13 中与当前非破坏性编辑切片对应的验收矩阵;只完成画布 UI 或只完成本地写文件都不能算正式闭环。
|
||||
8. 自动定位必须分别证明资源已投影、dependency/type 两份布局都 settled 且存在目标位置、目标卡 DOM 已提交;搜索隐藏走显式清除/定位,任何 commit 最多自动聚焦一次。
|
||||
10. 普通素材画布生成账本的服务身份固定为 `official-platform-v1 + 官方 origin + ownerUserId`,不绑定 Access Token;高级 External v1 账本只绑定显式服务 origin,不绑定 Developer API Key。两种模式的 `accepted/running` 都只恢复原 GET,`prepared` 只可精确重放冻结的原 POST、原正文和原幂等键。普通模式退出或换号后提升账号 generation,中止并脱离旧请求;旧账号账本在新账号下零网络、零安装,只有重新登录同一 owner 后才可恢复。不能通过更换 Token、Key、URL、请求正文或 operationId 绕过该隔离。
|
||||
11. 资源编辑恢复面板必须为独立 modal,展示后端权威队列的全部 operation。用户可继续任意可恢复项;`remote-failed` 只允许显式移出活动队列,并保留私有账本审计;`reconciliation-required` 只读展示对账。读取失败必须提供重试,不得伪装空队列;操作后必须重读后端。
|
||||
12. `remote-failed` 已是远端明确终态,重启后不再 POST、不再轮询、不再扣费;`archived` 仅表示用户已将它移出活动恢复队列,不等于 `committed`。`result-unknown`、鉴权临时失败和 `reconciliation-required` 均不允许归档或重新生成。
|
||||
13. 派生资产提交必须通过 durable asset transaction journal 串起最终文件、manifest 和 project revision。任一崩溃阶段恢复后只有一份派生文件、一条 manifest asset,revision 精确推进一次;manifest 已写而 revision 未写时只前向补 revision,无法证明的组合进入人工对账。journal 已证明目标 asset、媒体和 target revision 写入后,即使后续合法提交继续推进 manifest/revision,也应按目标 asset 精确身份与 `currentRevision >= targetRevision` 补齐 ledger/draft,不得要求整个 manifest 永远等于历史 after 快照。durable committed 后遗留的 staging 只有在 staging/正式媒体摘要一致,且 manifest 中按 asset ID 或路径唯一命中并与 journal asset 精确相等时才尽力删除;删除失败不降级已提交结果,身份或媒体漂移则保留 staging 并进入对账。
|
||||
14. generation progress、草稿保存队列、生成/提交回包与延迟 `loadDraft` 必须共用单调 revision 门禁,低 revision 不得覆盖已落地的新草稿。Shift 指针与键盘选择必须与共享 core 一致;单选自身 Shift 不能清空选择,已选多图层普通指针拖动应保持并同步移动选择集。零位移不得产生 undo、documentVersion 或草稿保存。
|
||||
15. 失败 UI 必须按 `generation / draft-save / asset-commit / recovery / cancellation` 五类 operation 显示可访问名称与安全动作。生成是任务级状态:排队、运行、失败和对账不得用全屏遮罩终结画布;失败占位保留原参数并允许重新修改。草稿、CAS、最终图提交、恢复和取消故障不得出现会发起新生成的按钮。真实写入和恢复阶段仍由 handler、revision 与锁负责门禁,视觉 disabled、旧闭包或遮罩不能充当业务门禁。
|
||||
16. 派生子版本 journal 必须冻结 project revision before/after 身份和目标 after 记录。manifest 已有目标子版本但 journal 缺失时失败关闭;旧 journal 缺少 revision 身份且当前 revision 已推进、无法证明是同一事务写入时进入人工对账,不得把现状猜测为已提交。
|
||||
17. 文本、SVG 与 Agent 回执编辑必须在调用 Provider 前持久化 request-issued;Provider 成功正文必须在解析、格式校验和 staging 之前原子写入私有 durable handoff,并绑定原 operation、请求指纹和内容摘要。issued 后缺少可信 handoff 只能对账,已有可信 handoff 则只消费原正文,两者都不得再次调用 Provider。普通模式首次 `401` 只允许当前账号 generation 的单飞 refresh,成功后以更高 generation CAS 安装 Token,并使用原正文、原幂等键重试一次;refresh 失败、换号或再次 `401` 时保留原 operation,停止网络。`403` 不触发 refresh。高级 External v1 模式修正 Developer Key 后仍只查询或恢复原 operation。
|
||||
9. 普通素材画布生成账本的服务身份固定为 `official-platform-v1 + 官方 origin + ownerUserId`,不绑定 Access Token;高级 External v1 账本只绑定显式服务 origin,不绑定 Developer API Key。两种模式的 `accepted/running` 都只恢复原 GET,`prepared` 只可精确重放冻结的原 POST、原正文和原幂等键。普通模式退出或换号后提升账号 generation,中止并脱离旧请求;旧账号账本在新账号下零网络、零安装,只有重新登录同一 owner 后才可恢复。不能通过更换 Token、Key、URL、请求正文或 operationId 绕过该隔离。
|
||||
10. 资源编辑恢复面板必须为独立 modal,展示后端权威队列的全部 operation。用户可继续任意可恢复项;`remote-failed` 只允许显式移出活动队列,并保留私有账本审计;`reconciliation-required` 只读展示对账。读取失败必须提供重试,不得伪装空队列;操作后必须重读后端。
|
||||
11. `remote-failed` 已是远端明确终态,重启后不再 POST、不再轮询、不再扣费;`archived` 仅表示用户已将它移出活动恢复队列,不等于 `committed`。`result-unknown`、鉴权临时失败和 `reconciliation-required` 均不允许归档或重新生成。
|
||||
12. 派生资产提交必须通过 durable asset transaction journal 串起最终文件、manifest 和 project revision。任一崩溃阶段恢复后只有一份派生文件、一条 manifest asset,revision 精确推进一次;manifest 已写而 revision 未写时只前向补 revision,无法证明的组合进入人工对账。journal 已证明目标 asset、媒体和 target revision 写入后,即使后续合法提交继续推进 manifest/revision,也应按目标 asset 精确身份与 `currentRevision >= targetRevision` 补齐 ledger/draft,不得要求整个 manifest 永远等于历史 after 快照。durable committed 后遗留的 staging 只有在 staging/正式媒体摘要一致,且 manifest 中按 asset ID 或路径唯一命中并与 journal asset 精确相等时才尽力删除;删除失败不降级已提交结果,身份或媒体漂移则保留 staging 并进入对账。
|
||||
13. generation progress、草稿保存队列、生成/提交回包与延迟 `loadDraft` 必须共用单调 revision 门禁,低 revision 不得覆盖已落地的新草稿。Shift 指针与键盘选择必须与共享 core 一致;单选自身 Shift 不能清空选择,已选多图层普通指针拖动应保持并同步移动选择集。零位移不得产生 undo、documentVersion 或草稿保存。
|
||||
14. 失败 UI 必须按 `generation / draft-save / asset-commit / recovery / cancellation` 五类 operation 显示可访问名称与安全动作。生成是任务级状态:排队、运行、失败和对账不得用全屏遮罩终结画布;失败占位保留原参数并允许重新修改。草稿、CAS、最终图提交、恢复和取消故障不得出现会发起新生成的按钮。真实写入和恢复阶段仍由 handler、revision 与锁负责门禁,视觉 disabled、旧闭包或遮罩不能充当业务门禁。
|
||||
15. 派生子版本 journal 必须冻结 project revision before/after 身份和目标 after 记录。manifest 已有目标子版本但 journal 缺失时失败关闭;旧 journal 缺少 revision 身份且当前 revision 已推进、无法证明是同一事务写入时进入人工对账,不得把现状猜测为已提交。
|
||||
16. 文本、SVG 与 Agent 回执编辑必须在调用 Provider 前持久化 request-issued;Provider 成功正文必须在解析、格式校验和 staging 之前原子写入私有 durable handoff,并绑定原 operation、请求指纹和内容摘要。issued 后缺少可信 handoff 只能对账,已有可信 handoff 则只消费原正文,两者都不得再次调用 Provider。普通模式首次 `401` 只允许当前账号 generation 的单飞 refresh,成功后以更高 generation CAS 安装 Token,并使用原正文、原幂等键重试一次;refresh 失败、换号或再次 `401` 时保留原 operation,停止网络。`403` 不触发 refresh。高级 External v1 模式修正 Developer Key 后仍只查询或恢复原 operation。
|
||||
|
||||
### 7.7 主站 UI 对齐验收
|
||||
|
||||
|
||||
@@ -15,8 +15,44 @@
|
||||
- 关联文档:相关 PRD、技术文档、提交或 Issue
|
||||
```
|
||||
|
||||
## 2026-08-31 DirectProject 客户端扩展按独立 Skill/MCP 导入
|
||||
|
||||
- 背景:DirectProject 需要使用用户在 AGC 客户端导入的市面原生 Skill、MCP 和 Plugin 内容,但第三方内容不应直接安装到运行时 Codex,也不应要求用户转换为 AGC 自定义格式。
|
||||
- 决策:客户端提供一个全局“扩展”入口,统一接受文件、目录、zip 和标准 Plugin;目录、zip、Plugin 只是导入来源,发现出的每个 Skill 和每个 MCP Server 分别成为独立扩展项,分别列表、重命名、启用、禁用和删除。已识别项导入后默认启用,下次 DirectProject Codex 启动时按原生 Skill root 和 MCP 配置注入。
|
||||
- 命名:客户端列表名称与 Codex 运行时名称使用同一个原生标识,不维护 display/runtime 两套名称;重复或同名项保留为新的独立项并自动追加 `-2`、`-3`。Skill 重命名只修改客户端运行时副本中的有效名称,原始导入内容不修改。
|
||||
- Plugin 边界:Plugin 只作为导入容器提取 Skill/MCP;当前 DirectProject 关闭的 hooks、apps、remote plugin 和完整 Plugin Runtime 不接入。单个可执行文件或脚本不提供手动指定为 MCP 入口的功能。
|
||||
- 信任边界:不审核第三方 Skill 文案、脚本、二进制、MCP tool 或网络行为;导入阶段不执行内容。客户端只做标准结构识别、必要配置解析和 zip staging 路径边界处理,且不向第三方扩展注入 AGC 凭据或内部路径。
|
||||
- 影响范围:AGC 客户端扩展设置 UI、客户端本地扩展存储、DirectProject Codex app-server 启动准备和 pool fingerprint;不新增 HTTP 服务、SpacetimeDB schema、公开 API 或独立 Plugin Runtime。
|
||||
- 当前实现:客户端导入/list、Skill 临时 root 和 MCP 隔离配置注入均已落地。第三方 MCP 只从客户端已启用独立项生成本次隔离 `CODEX_HOME/config.toml`,每项固定非 required;配置错误或 app-server 启动状态失败只更新对应 `last_error`,内置 `agc_tools` 继续由客户端单独注入。客户端已启用 Skill/MCP 的名称、来源路径和内容指纹共同参与 DirectProject app-server pool identity。
|
||||
- 验证方式:分三阶段验收:先验证导入拆分和完整列表,再验证 Skill 运行时发现和重命名,最后验证 MCP 配置合并、Plugin 提取和失败隔离;只增加对应的定向测试、`npm run check:encoding` 和 `git diff --check`。
|
||||
- 关联文档:`docs/technical/【技术方案】DirectProject客户端Skill与MCP扩展导入方案-2026-08-31.md`、`apps/ai-game-creator-shell/src/features/runtime-config/RuntimeConfigDialog.tsx`、`apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server.rs`。
|
||||
|
||||
## 2026-08-30 批准 GDD 直接进入做游戏链路
|
||||
|
||||
- 背景:立项策划 GDD 批准后需要给用户一个进入做游戏的自然出口,产品决策改为点击按钮后直接开始建造。
|
||||
- 决策:批准态 GDD 交付行提供“做成游戏”按钮。点击后读取当前项目的权威 `game/fast_gdd.md`,直接创建自动游戏工作区、导入 `text/markdown` 参考附件,并以固定建造指令自动启动 Direct Codex;不再回首页等待用户二次提交。该动作不复制原项目的 `approvedGddRef`、planning sidecar 或 approval receipt。
|
||||
- 影响范围:AGC 前端 GDD 交付行与现有自动建项/附件导入/Direct Codex 链路;移除首页 RichInputArea 的 GDD 一次性预填链路;不新增 HTTP API、SpacetimeDB schema、迁移、OpenAPI 或正式构建绑定。
|
||||
- 验证方式:批准态按钮直接创建工作区、导入附件、携带固定首条指令进入项目工作台且重复点击不重复创建的 appSurface 回归;类型检查、编码检查和 `git diff --check` 通过。
|
||||
- 关联文档:`docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md`。
|
||||
|
||||
---
|
||||
|
||||
## 2026-08-31 Direct 回合把 Codex item 落成有界行为账本
|
||||
|
||||
- 背景:sidecar 已让模型看见本轮附件路径,但 native 读 / MCP / 写文件只存在于隔离 `CODEX_HOME` 的瞬时 stdout,回合结束即删。无法判断「没读附件」还是「读了仍走默认收集类」。
|
||||
- 决策:GUI DirectProject 每个 `clientTurnId` 追加 `.agent/runtime/direct-codex/turns/<id>.jsonl`,并在 `agent.db` 写一条 `direct.codex.turn` 摘要。记 sidecar 提供的路径与文件 hash、`item/completed` 的 Read/List/Search/MCP/写文件(不含 stdout、patch、MCP result),以及 `offeredRead` / `firstDesign`。审计 fail-open,不阻断做游戏。Home、CLI、Supervisor 收据模型不接。不灌附件正文,不强制读取,不为 GDD 开特例。
|
||||
- 影响范围:`direct_codex_audit.rs`、Direct GUI command 边界、Codex collect 循环;前端 / jsonl 气泡 / sidecar 文案不变。
|
||||
- 验证方式:Rust fixture 覆盖 turn_start hash、绝对路径相对化、stdout/diff 不落盘、art brief 保留、list/search 不算已读、firstDesign 顺序、256 条截断、写盘失败不 panic;sidecar 渲染与 Direct 活动词测试保持通过。
|
||||
- 关联文档:`docs/technical/【技术方案】Direct回合行为审计账本-2026-08-31.md`、issue #212。
|
||||
|
||||
## 2026-08-31 Direct 本轮附件只映射路径,不灌正文、不区别 GDD
|
||||
|
||||
- 背景:issue #212。首页附件已经复制到 `assets/uploads/` 并登记,但 Direct 首轮只把用户原文发给 Codex,原文件名不是磁盘路径,模型会另起一套玩法。
|
||||
- 决策:Home 与 Project 共用 `DirectCodexTurnAttachment`。有项目路径或导入状态时,只在发给 Codex 的 user prompt 末尾附有界 sidecar(原名 → 项目相对路径、类型、大小、状态);无路径且无状态时保持首页元数据文案。不灌正文、不强制读取、不按 GDD 开特例。做成游戏固定 prompt 不改,同一条 Direct 首轮附件链自动吃到 sidecar。jsonl 与工作台气泡仍只写用户原文。
|
||||
- 影响范围:`direct_codex_attachments.rs`、Direct command 边界、首页建项 latch、工作台首轮 invoke;Supervisor / 做方案首轮忽略附件 sidecar。
|
||||
- 验证方式:Rust 渲染测试(Home 逐字兼容、Project 映射、非法路径);home.suite 附件 Direct invoke 含 `localPath`;无附件不出现 `attachments` 键;做方案首轮仍走 Supervisor 且无 sidecar;后续手打消息不带 attachments。
|
||||
- 关联文档:`docs/technical/【技术方案】DirectProject本轮附件路径映射-2026-08-31.md`、issue #212。
|
||||
|
||||
## 2026-08-26 运行中自主扩图提案留在编排层
|
||||
|
||||
- 背景:`agent-runtime-orchestration` 已能构造和调度动态 DAG,但 LLM 在执行中发现缺少步骤时没有通用的安全扩图合同。
|
||||
@@ -31,6 +67,7 @@
|
||||
- 产品边界:16 个游戏任务、六组角色、产物/验收条件、Evaluator Markdown 和中文语义路由继续留在 `platform-agent`;AGC 组合根使用公共层校验任务图与 `AgentCatalog`。Runtime store、Runner、Provider、权限、ToolHost、委派 journal、isolated write scope 和 `.agent/runtime/**` 不迁移、不双写。
|
||||
- 验证方式:非游戏 conformance 覆盖并行分支、汇合、repair closure、AgentCatalog 和非法图失败关闭;`platform-agent` 锁定种子 DAG 与现役波次/返工顺序,并验证环拒绝和 catalog 注入。根检查脚本必须执行新 crate 测试。
|
||||
- 关联文档:`docs/technical/【技术方案】AI游戏创作Agent Runtime V1.1-2026-07-12.md` V1.54。
|
||||
|
||||
## 2026-08-27 `plan.submit_gdd` 拒绝无审批决定的 `user_revision`
|
||||
|
||||
- 背景:结构校验允许 `round=0 + user_revision + confirmed`,提交闸原先只做结构、身份和 Session CAS。Provider 可在首次 collecting、澄清续跑或提交前质量返工里把未确认项标成用户审批修改,审批卡显示「已确认」。
|
||||
@@ -48,6 +85,7 @@
|
||||
- 关联文档:`docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md`、`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_coordinator.rs`。
|
||||
|
||||
## 2026-08-27 退款 emergency spool 容量溢出保持可恢复
|
||||
|
||||
## 2026-08-27 退款 emergency spool 容量溢出保持可恢复
|
||||
|
||||
- 背景:本机 emergency spool 仅作为 SpacetimeDB 完全不可达时的最后恢复路径,原有 `MAX_BYTES` 分支会直接返回 `Dropped`,导致扣费已经完成但没有可重放记录。
|
||||
|
||||
@@ -41,15 +41,15 @@ SpacetimeDB 任务统一先读取 `.codex/skills/genarrative-spacetimedb/SKILL.m
|
||||
|
||||
按改动范围选择定向门禁,不以无关全量扫描代替契约验证:
|
||||
|
||||
| 范围 | 至少运行 |
|
||||
| --- | --- |
|
||||
| 文档 / 中文文本 | `npm run check:encoding`、`git diff --check` |
|
||||
| 前端 | 相关 Vitest、类型检查;需要时做桌面/移动视口 smoke |
|
||||
| Rust 后端 | 对应 crate 的 `cargo test` / `cargo check`、`/healthz` smoke |
|
||||
| External v1 | OpenAPI 解析、实现/DTO 契约测试和鉴权 smoke |
|
||||
| SpacetimeDB schema | `npm run spacetime:generate`、`npm run check:spacetime-schema`、运行时访问检查 |
|
||||
| 范围 | 至少运行 |
|
||||
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| 文档 / 中文文本 | `npm run check:encoding`、`git diff --check` |
|
||||
| 前端 | 相关 Vitest、类型检查;需要时做桌面/移动视口 smoke |
|
||||
| Rust 后端 | 对应 crate 的 `cargo test` / `cargo check`、`/healthz` smoke |
|
||||
| External v1 | OpenAPI 解析、实现/DTO 契约测试和鉴权 smoke |
|
||||
| SpacetimeDB schema | `npm run spacetime:generate`、`npm run check:spacetime-schema`、运行时访问检查 |
|
||||
| AGC / DirectProject | 使用 `docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md` 中的当前定向门禁;真实 Provider/登录缺失必须记为未验证 |
|
||||
| 生产发布 | 当前开发运维文档、脚本门禁、主机进程、备份和公开端点证据 |
|
||||
| 生产发布 | 当前开发运维文档、脚本门禁、主机进程、备份和公开端点证据 |
|
||||
|
||||
只有 `cargo check/test/clippy/fmt/build`、`npm test`、规范命名的 npm 验证脚本和精确 `node --test` 形成验证凭证;`git`、`rg`、`cargo metadata` 和普通 `npm run` 只作为诊断。
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 文档地图与阅读索引
|
||||
|
||||
更新时间:`2026-08-25`
|
||||
更新时间:`2026-08-31`
|
||||
|
||||
## 阅读顺序
|
||||
|
||||
@@ -22,11 +22,14 @@
|
||||
AI 游戏创作 / DirectProject / UI workflow:
|
||||
|
||||
1. `docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`
|
||||
2. `docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md`
|
||||
3. `docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md`
|
||||
4. `docs/technical/【技术方案】GameAgent资源自由画板与快速编辑-2026-08-20.md`
|
||||
5. `docs/【技术方案】UI工作流资源桥接与Runtime执行-2026-08-24.md`
|
||||
6. UI 编辑器、宿主壳和当前测试专题文档
|
||||
2. `docs/technical/【技术方案】DirectProject客户端Skill与MCP扩展导入方案-2026-08-31.md`
|
||||
3. `docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md`
|
||||
4. `docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md`
|
||||
5. `docs/technical/【技术方案】DirectProject本轮附件路径映射-2026-08-31.md`
|
||||
6. `docs/technical/【技术方案】Direct回合行为审计账本-2026-08-31.md`
|
||||
7. `docs/technical/【技术方案】GameAgent资源自由画板与快速编辑-2026-08-20.md`
|
||||
8. `docs/【技术方案】UI工作流资源桥接与Runtime执行-2026-08-24.md`
|
||||
9. UI 编辑器、宿主壳和当前测试专题文档
|
||||
|
||||
图片画布 / 媒体生成:
|
||||
|
||||
|
||||
@@ -16,15 +16,15 @@
|
||||
|
||||
## 已完成
|
||||
|
||||
- [ ]
|
||||
- [ ]
|
||||
- [ ]
|
||||
- [ ]
|
||||
- [ ]
|
||||
- [ ]
|
||||
|
||||
## 未完成
|
||||
|
||||
- [ ]
|
||||
- [ ]
|
||||
- [ ]
|
||||
- [ ]
|
||||
- [ ]
|
||||
- [ ]
|
||||
|
||||
## 关键文件
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
|
||||
## 当前问题/风险
|
||||
|
||||
-
|
||||
-
|
||||
|
||||
## 已执行验证
|
||||
|
||||
@@ -43,9 +43,9 @@
|
||||
|
||||
## 建议下一步
|
||||
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
## 是否需要更新团队记忆
|
||||
|
||||
|
||||
@@ -76,7 +76,6 @@
|
||||
- 处理:本阶段不加机制,manifest 明确降级为 lineage 判定通过后的补充信号(见 decision-log 2026-08-12 条)。将来要做,必须同时覆盖两条写入路径,并统一走毫秒换算 helper。
|
||||
- 验证:`apps/ai-game-creator-shell/tests/agentRuntimeModel.test.ts` 的「keeps an unbound manifest out of the verdict until the current main is terminal」钉住了现有边界与残余风险;该用例最后一条断言即为已记录的残余风险,改动它就意味着重新裁决,必须同步更新决策记录。
|
||||
|
||||
|
||||
- 现象:前端 Runtime map 以 Agent ID 保存当前记录。新一轮仍会复用 `code-prototype`、`art-director`、`art-asset-plan` 这些 Agent ID;若旧 root 已终态但 main/美术 child 或 manifest 仍在收口,直接从 live map 归档会在新 root 接管后丢失旧后代,或把新轮证据误接到旧阶段记录。
|
||||
- 修复:根进入终态时按完整 `agent/session/run` 身份保存 root-scoped Runtime 快照,后续只合并同一稳定身份的更新;manifest 快照只在该 root 仍为当前 root 时捕获。归档前重新执行严格 lineage、全终态、单 main/单 active art 与 reconciliation 门禁,并用 root run 派生稳定 message ID。
|
||||
|
||||
@@ -109,6 +108,7 @@
|
||||
- 并发恢复补充:自主根任务 journal 写入后建立或重建 completion contract 时,初始 manifest reset 与 continuation reconciliation reset 不能重新使用 fail-fast 项目锁。异步 child finalization 可以合法插入两次取锁之间,使已入 journal 的新根被误记为 `completion-contract-failed`。这两条 reset 必须使用现有有界等待项目锁,超时仍失败关闭;只验证 scheduler 合同的测试应预占 child Runtime lane,不能真实启动后台 worker 后再手工改 manifest。确定性回归要显式持锁,分别证明初始合同与 continuation 合同等待释放后成功落盘。
|
||||
- 验证:夹具必须从 `init_local_game_project_at` 开始,先断言无 `package.json` 且占位入口 smoke 失败,再证明产物不齐阻断、齐全后内部验证通过、无 smoke trace、再次 mutation 失效;另覆盖四个 owner 路径矩阵、错误身份、恢复、跨 run 凭证、`art-director` 有/无 Key、动态美术借凭证拒绝、`code-prototype` project.verify-only 阻断和试玩 executor 身份。
|
||||
- 关联:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`、`docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md`。
|
||||
|
||||
## UI 设计 State 的 strict JSON round-trip 不能混用两种浮点序列化表示(2026-08-19)
|
||||
|
||||
- 现象:为节点拖拽/缩放生成非整数 Transform 后,保存报“UI 设计 State 安装后回读与待写内容不一致”;由于读取主文件失败关闭后恢复 `.previous`,后续回读表现为刚导入的 spirit/sprite 资产丢失。
|
||||
@@ -4364,7 +4364,6 @@
|
||||
- 处理:历史花费只累计 `asset_operation_consume` 负向流水绝对值,退款不冲减;通过 `profile_wallet_consumption_total` 在已有投影时按主键 O(1) 累加。首次上线必须在停写维护窗口由 owner 执行全量初始化,为每个已有钱包流水的用户建立投影,不能让所有存量用户的首次正常消费各自扫描历史;维护遗漏或新用户缺行时才在首次消费或详情读取中按用户索引兜底重建一次。手动对账扫描是独立高风险操作,member 必须单独持有 `profile-wallet-consumption-reconcile`,不能因为能打开共享用户详情就自动获得。
|
||||
- 验证:构造消费、退款、充值退款追回和赠送混合流水,断言只累计消费;维护初始化后正常消费只按主键累加;重复详情读取不得重复扫描或重复累计;任意 Tab 权限不能调用手动对账,同时确认充值订单列表的通用钱包快照没有新增历史流水扫描。
|
||||
|
||||
|
||||
- 症状:`code-prototype` 首次完成后 `.agent/logs/command.log` 已出现 `permission.confirm preview.start`,但客户端没有 iframe,`.agent/logs/preview.log` 也没有新的 running 记录;后续即使父 run 完成也不再启动。
|
||||
- 根因:旧实现调用 `start_local_game_preview` 前就把“项目 + parent run”的授权加入 attempted 集合并清空;首版完成投影与后续专业任务仍在写项目时,启动恰逢项目写锁竞争,catch 只显示错误却无法重试。
|
||||
- 约束:一次性语义应按“成功或确定性终态”消费,不按“函数调用次数”消费。项目写锁竞争保留同一授权并轮询重试;成功、显式 deny 与非瞬时失败才清除。授权需持久化项目路径和 accepted runId,重启恢复时仍必须逐项匹配,切换项目不得继承。
|
||||
@@ -4460,7 +4459,6 @@
|
||||
- 处理:严格继续意图必须在同一 Supervisor Session、同一持久 source 内继承最近失败根 run 的原始目标和 baseline,但保持新的 run/Provider/sidecar 身份;纯继续词表只能有一个权威实现,中英文短语都走同一入口,真正新需求仍独立 reset。非占位入口禁止 fallback 整体覆盖,也不能反复运行只读 smoke;当前 `code-prototype` 必须先读取并实际 patch,取得本人 mutation 后才能验证和交付。占位 fallback 只支持具备真实语义的显式模板,俄罗斯方块必须实际实现棋盘、下落、旋转、锁定和消行,未知玩法失败关闭。`art-spec.png` 只作规范参考,核心运行时位图必须来自独立派生的透明 `art-spritesheet.png` 及其 `iconImageSrcs` 本地切片;切片清单绑定当前图集 resourceId,Canvas 分别使用玩家、目标、场景和反馈四类素材。不得猜测图集是 2×2 等分、把规范板塞进画面或以纯代码核心实体绕过派生素材。
|
||||
- 验证:覆盖失败根任务“水晶俄罗斯方块”后输入“继续”、连续 successor、跨 Session、跨 source、正常完成后新输入、带具体新需求、既有非占位入口先 patch 后 smoke、初始化占位的俄罗斯方块真实语义、未知玩法失败关闭、纯继续目标缺失、规范图不在运行 DOM/Canvas、真实动作前后 `sequence` 与 RAF 空转。浏览器验收必须同时比较 baseline 玩法关键文本/控件/状态和当前 revision,不能只看 Canvas 非空与三个固定按钮。
|
||||
|
||||
|
||||
- 现象:用户要求把已有美术资源接入游戏时,固定 `code-director -> art-director / art-asset-plan -> code-prototype` 图会在缺少主 Agent 审计的情况下启动美术生成,或把“整体重做”错误实现为无条件生图;美术完成后又换了 Run,代码接入、静态检查和试玩无法形成连续责任链。
|
||||
- 原因:固定节点把“是否需要美术”的语义判断编码为 Runtime 前置流程,`code-director` 成为另一个主控,而不是让真正接入游戏的 `code-prototype` 基于权威资产事实决策;如果再把固定审计策略塞进用户意图字段,Supervisor 的理解也会被 Runtime 规则覆盖。两个素材槽都缺失时若先消耗不可重试的 `art-asset-plan` 委派,其 child 又必然因缺规范图失败,整个 Run 会进入无法补救的死路。
|
||||
- 处理:Supervisor 用 `intentSummary` 持久化自己对用户意图的理解,固定 `audit-existing-first` 只作安全执行策略,随后只启动 `code-prototype`。主 Agent 先 `asset.list`,完整覆盖就直接使用;仅在事实证明缺少规范图或核心图集时,才建立一个写入范围受限为 `assets/**` 的美术 durable delivery。两槽都缺失时必须先完成并认领 `art-director` 的 `EvidenceReady` delivery,再委派 `art-asset-plan`;回执返回同一主 Run 后再接入素材、原玩法语义检查、静态检查和双视口试玩。读取旧 v1 决策时必须复核其旧 fingerprint,并从完成合同绑定的有效任务迁移 intent;旧 `code-director` coverage/route 只能触发当前主 Agent 重新审计和原位替换,不能直接成为新完成证据。整体视觉重做意图同样必须经过这次审计,不能成为绕过资产复用或强制重生成的固定规则。完整 GUI / CLI DAG 不使用该例外。
|
||||
@@ -4489,7 +4487,6 @@
|
||||
- 验证:DOM 与截图不得出现外部品牌或 unsupported 列;AppSurface 覆盖 populated / invalid / empty、搜索与菜单;Playwright 在 `1280×720` 测量无页面级溢出。视频控制在有用时长内,清楚展示搜索、清除、菜单、状态反馈和项目打开结果,每一段都有可观察变化。
|
||||
- 关联:`apps/ai-game-creator-shell/src/features/app-shell/ProjectCreation.tsx`、`apps/ai-game-creator-shell/src/features/app-shell/model.ts`、`apps/ai-game-creator-shell/src/features/app-shell/useRecentProjects.ts`、`apps/ai-game-creator-shell/tests/appSurface/home.suite.ts`。
|
||||
|
||||
|
||||
- 现象:首波从单个美术任务扩展为三个 Director 后,hydration 若仍只容忍 seed lane 的第一个任务在 manifest 短暂恢复 `Pending` 时收束,另外两个已启动 Director 会被卡住。另外默认 `llm.stream=false` 下的专业 final reply 虽已由 finalization 提交,但后续阶段推进项目 revision 后,早期回复会从 Runtime 查询中消失。
|
||||
- 原因:hydration 例外把“首波”错误收窄成了单个固定或数组第一项任务;`visible_game_creator_agent_runtime_response_stream_at` 又把未提交流的 revision 新鲜度门误用到了已终态提交的 durable final reply。
|
||||
- 处理:从当前 root source 的 seed lane 动态解析全部零依赖首波任务,只对这些 child 容忍 hydration `Pending`,后续 code prototype / preview 仍严格要求 Running/Completed。`streaming / ready` 仍要求当前 revision,`committed` 回复改为依据 finalization 的稳定身份查询,不随后续项目 revision 失效。
|
||||
@@ -4865,7 +4862,6 @@
|
||||
- 现象二:图片预览已经读取真实宽高,但大量资源触发 LRU 淘汰后,卡片又退回 `180x128`,布局和连线随之跳动。原因是布局尺寸直接从可淘汰的 Blob/Object URL 预览缓存派生。处理时预览二进制仍按预算淘汰,但已验证的轻量 `pixelWidth/pixelHeight` 必须在当前项目、模式和资源 identity 作用域内独立保留;identity 或 scope 变化时再清理。
|
||||
- 验证:分别覆盖严格 edit body、错误分类、生成占位恢复,以及超过预览缓存条目上限后首张图片仍保持真实比例、布局碰撞和依赖端点不退化。
|
||||
|
||||
|
||||
# 2026-08-21 已有资源 ID 不等于可用于快速编辑的 canonical 来源
|
||||
|
||||
- manifest 中的 `source.resourceId` 可能指向历史按 `game-background` 等 Game Agent 私有 kind 登记的远端资源。只检查 ID 前缀并直接传给 `sourceReferenceId` 会在 External v1 入队前得到 `unsupported-source-kind`;本地 kind 映射只有在真正重新登记来源时才生效。
|
||||
@@ -4873,14 +4869,12 @@
|
||||
- 批量导入不能在安装首个媒体后继续执行带 `?` 的 ID、路径、层序或 revision 计算;这些步骤必须先完成。原子草稿写入返回错误后,回读失败属于提交结果未知,必须保留媒体并报对账错误,不能把回读错误压成“未提交”后删除可能已被草稿引用的文件。所有回滚删除失败也必须显式上报。
|
||||
- 失败 generation 同时存在私有 ledger 和 draft 投影,只在 React state 中 `filter` 会在重启后复活。删除 UI 必须调用只允许明确失败任务的后端归档操作,结果未知任务不能删除。
|
||||
|
||||
|
||||
## 运行中 generation 与 refine 来源身份不能按 create 链路处理(2026-08-21)
|
||||
|
||||
- 草稿 hydrate 后同步等待远端 generation recovery,会让生命周期长期停在 recovering,连平移和选图也被 inert。关键事务恢复与任务恢复必须拆开:前者先完成,后者后台推进且失败只进入任务/notice。
|
||||
- refine 提交若无条件校验 `source.resourceId == local-asset:<assetId>`,会拒绝本来合法的 `editor-resource-*` 已登记来源,并可能把事务卡在 `revision-installed`。创建与未登记本地资产才补 local identity;已登记 refine 必须保留并按解析后的来源身份回读。
|
||||
- 画布内部 absolute overlay 只能覆盖宿主网格,嵌在左右分栏时不会遮住整个窗口。阻断性失败必须 portal 到 `document.body`,并用 fixed inset 覆盖整个 WebView;测试应验证 portal 的直接宿主和 fullscreen modifier。
|
||||
|
||||
|
||||
## 自由画板 viewport 与资源 extent 分离(2026-08-24)
|
||||
|
||||
- 非空资源栏目使用无限画布:普通平移的 `x / y` 不按资源 extent 夹取,窗口 resize、媒体测量和资源 extent 变化也不得把用户 viewport 拉回内容边界;共享 `MIN_SCALE/MAX_SCALE` 只约束缩放比例。只有首次进入组合或用户显式复位时,才用真实卡片包围盒计算 fit。
|
||||
|
||||
@@ -52,7 +52,7 @@ SpacetimeDB crate、SDK、CLI / standalone 与生成 bindings 按 `2.8.3` 对齐
|
||||
## AGC DirectProject 与 UI workflow
|
||||
|
||||
- 通用 Agent Rust 分层为 `agent-runtime-core`(catalog、执行生命周期、ToolHost/spawn/all-join/Provider 契约)、`agent-runtime-orchestration`(动态无环任务图、ready、依赖波次、返工下游闭包和受限自主扩图提案)与 `platform-agent` 游戏适配器;循环返工通过新 pass / epoch 表达,不在单张依赖图中建立回边。LLM 可经宿主结构化 function call 提出新增节点/边,编排层只生成经校验的新候选图,epoch 与持久化仍由宿主掌控。
|
||||
- DirectProject 只连接客户端内置的 `agc_tools` STDIO MCP。它负责审核引用读取、标准美术准备、已登记资源有界查询、视频 / 角色动画 / 音效 / BGM 的 create-or-derive、已登记图片去背景、desktop/mobile 浏览器试玩和受控 `agc_web_search`;付费资源调用由客户端绑定回合、幂等账本、请求上限和投影权威。
|
||||
- DirectProject 始终连接客户端内置的 `agc_tools` STDIO MCP,并在启动时额外读取客户端扩展仓库中已启用的第三方 MCP 独立项。第三方 STDIO/HTTP 配置只写入本次隔离 `CODEX_HOME`,单项非 required,启停、重命名和内容指纹进入 app-server pool identity;完整 Plugin Runtime、hooks/apps 和单文件脚本手动指定入口仍关闭。`agc_tools` 继续负责审核引用读取、标准美术准备、已登记资源有界查询、视频 / 角色动画 / 音效 / BGM 的 create-or-derive、已登记图片去背景、desktop/mobile 浏览器试玩和受控 `agc_web_search`;付费资源调用仍由客户端绑定回合、幂等账本、请求上限和投影权威。
|
||||
- DirectProject 的 Codex 原生文件、搜索、命令、图片查看和 Skill 仅在真实 `game/` cwd 与 `workspaceWrite(writableRoots=[game])` 内可用;原生命令网络保持关闭。多 Agent、Apps、插件、hooks、图片生成、Goals、Workspace Dependencies、Tool Suggestion 和原生浏览器/电脑控制保持关闭。app-server 使用隔离 `CODEX_HOME`,provider 凭据只由 AGC 客户端代理持有,不能进入模型上下文或 shell 环境。
|
||||
- `ui-prototype`(设计图片)与 UI 编辑器 `UI` JSON 是不同资源。白名单 `ui.workflow.run` 按页面执行 `prepare → recognize → status → finalize`,由 provider-backed 识别、合并和组件绑定持久化 State/revision,并把 `reference-ready → structure-ready → merge-ready → binding-ready → application-ready → completed` 投影到 manifest。Provider 缺失、请求失败、工具缺失、结果不匹配或仍有待审节点时保留真实阶段并返回 blocker,不得用 deterministic seed 伪造完成。
|
||||
- UI workflow 的资源桥接与 Runtime 边界以 `docs/【技术方案】UI工作流资源桥接与Runtime执行-2026-08-24.md` 和 AGC 实施计划的 2026-08-24 覆盖段为准;只生成图片、登记空 JSON 或进入普通图片画布都不构成 workflow 完成。
|
||||
|
||||
@@ -19,14 +19,17 @@
|
||||
## 第一阶段模块
|
||||
|
||||
- `ImageCanvasEditorTypes.ts`
|
||||
|
||||
- 承载编辑器前端共享类型:素材、图层、视口、工具、生成对象、历史快照、剪贴板、右键菜单、拖拽状态等。
|
||||
- 只暴露类型,不承载运行时逻辑。
|
||||
|
||||
- `ImageCanvasEditorModel.ts`
|
||||
|
||||
- 承载画布基础模型:尺寸、缩放、背景色、素材默认文件夹、快照序列化 / 水合、素材库快照映射、吸附、右键菜单定位、DataTransfer 工具和通用数值格式化。
|
||||
- 保留“图片显示尺寸跟随 Resolution”“只保留一个默认素材文件夹”“右键菜单不滚动而是限制到视口内”等规则。
|
||||
|
||||
- `ImageCanvasGenerationModel.ts`
|
||||
|
||||
- 承载生成相关模型:生成占位尺寸、默认模型、规范表单默认值、角色动画选项、生成输入快照、规范 prompt 构建、生成对象识别和错误文案。
|
||||
- 保留角色动画优先用 `objectKey` 的体积保护规则。
|
||||
|
||||
@@ -111,6 +114,7 @@
|
||||
## 第十一阶段模块
|
||||
|
||||
- `ImageCanvasFileModel.ts`
|
||||
|
||||
- 承载图片文件判定和 `FileReader` Data URL 读取工具,供素材上传、生成参考图上传和后续导入能力复用。
|
||||
- 该模块不依赖素材库状态,避免把通用文件读取继续挂在素材库 hook 上。
|
||||
|
||||
@@ -221,6 +225,7 @@
|
||||
## 第二十五阶段模块
|
||||
|
||||
- `ImageCanvasStageControllerModel.ts`
|
||||
|
||||
- 承载舞台派生状态和右键菜单模型:选中图层、选中浮动工具栏位置、图片菜单图层、右键菜单目标图层,以及显示 / 解锁菜单文案判断。
|
||||
- 该模型复用既有图层命令模型与浮层定位模型,不重新实现右键目标和选中工具栏坐标规则;生成 Composer 锚点不属于舞台控制器,后续由生成表面编排统一负责。
|
||||
- 新增单测覆盖选中工具栏位置、右键目标集合、显示 / 解锁判断和菜单位置限制。
|
||||
|
||||
@@ -43,31 +43,31 @@
|
||||
|
||||
`ADMIN_TAB_PERMISSIONS` 必须是 shared-contracts 与 admin-web 共用的闭合集合,值与现有 `AdminRouteId` 一致。16 个可分配权限如下,顺序同时作为前端寻找“第一可访问项”的稳定顺序:
|
||||
|
||||
| permission id | 一级 Tab | hash |
|
||||
| --- | --- | --- |
|
||||
| `dashboard` | Dashboard | `#dashboard` |
|
||||
| `overview` | 服务总览 | `#overview` |
|
||||
| `tables` | 表查询 | `#tables` |
|
||||
| `debug` | API 调试 | `#debug` |
|
||||
| `tracking` | 埋点数据 | `#tracking` |
|
||||
| permission id | 一级 Tab | hash |
|
||||
| --------------------------- | --------- | ---------------------------- |
|
||||
| `dashboard` | Dashboard | `#dashboard` |
|
||||
| `overview` | 服务总览 | `#overview` |
|
||||
| `tables` | 表查询 | `#tables` |
|
||||
| `debug` | API 调试 | `#debug` |
|
||||
| `tracking` | 埋点数据 | `#tracking` |
|
||||
| `error-reports` | 错误报告 | `#error-reports` |
|
||||
| `gray-release` | 灰度发布 | `#gray-release` |
|
||||
| `redeem` | 兑换码 | `#redeem` |
|
||||
| `invite` | 邀请码 | `#invite` |
|
||||
| `profile-wallet` | 账号配置 | `#profile-wallet` |
|
||||
| `tasks` | 任务配置 | `#tasks` |
|
||||
| `recharge-products` | 充值商品 | `#recharge-products` |
|
||||
| `recharge-orders` | 充值管理 | `#recharge-orders` |
|
||||
| `editor-generation-pricing` | 模型定价 | `#editor-generation-pricing` |
|
||||
| `editor-showcase` | 精选审核 | `#editor-showcase` |
|
||||
| `editor-assets` | 素材查询 | `#editor-assets` |
|
||||
| `gray-release` | 灰度发布 | `#gray-release` |
|
||||
| `redeem` | 兑换码 | `#redeem` |
|
||||
| `invite` | 邀请码 | `#invite` |
|
||||
| `profile-wallet` | 账号配置 | `#profile-wallet` |
|
||||
| `tasks` | 任务配置 | `#tasks` |
|
||||
| `recharge-products` | 充值商品 | `#recharge-products` |
|
||||
| `recharge-orders` | 充值管理 | `#recharge-orders` |
|
||||
| `editor-generation-pricing` | 模型定价 | `#editor-generation-pricing` |
|
||||
| `editor-showcase` | 精选审核 | `#editor-showcase` |
|
||||
| `editor-assets` | 素材查询 | `#editor-assets` |
|
||||
|
||||
Tab 权限数组必须去重并按上表顺序规范化后保存。保存时拒绝未知值和 `accounts`;读取旧数据时遇到未知值应忽略并记录告警,绝不能将未知值解释为全权限。空数组合法,表示 member 可以登录但没有业务页面权限。
|
||||
|
||||
`ADMIN_ACTION_PERMISSIONS` 是独立操作权限闭合集合,当前只有:
|
||||
|
||||
| permission id | 操作 | 授权边界 |
|
||||
| --- | --- | --- |
|
||||
| permission id | 操作 | 授权边界 |
|
||||
| -------------------------------------- | -------------------- | ----------------------------------------------------------------------- |
|
||||
| `profile-wallet-consumption-reconcile` | 手动对账用户历史花费 | owner 默认拥有;member 必须在账号管理中单独勾选,不要求同时持有特定 Tab |
|
||||
|
||||
独立操作权限保存在 `action_permissions_json`,响应为 `actionPermissions`;未知值必须拒绝。后续新增一级 Tab 时,必须在同一次改动中更新:
|
||||
@@ -81,20 +81,20 @@ Tab 权限数组必须去重并按上表顺序规范化后保存。保存时拒
|
||||
|
||||
新增 SpacetimeDB 私有表 `admin_account`。表不能标记 `public`,浏览器不能订阅或直查;所有读写都由 `api-server -> spacetime-client facade -> 受限 procedure` 完成。
|
||||
|
||||
| 字段 | Rust / SpacetimeDB 类型 | 约束与语义 |
|
||||
| --- | --- | --- |
|
||||
| `account_id` | `String` | 主键;服务端生成不可变 opaque id,建议 `admin-account-<uuid>`,请求体不得指定 |
|
||||
| `username` | `String` | `unique`;登录名,创建后不可修改;按 `trim + ASCII lowercase` 规范化 |
|
||||
| `display_name` | `String` | 展示名,去除首尾空白后 1 至 64 字符 |
|
||||
| `password_hash` | `String` | Argon2id PHC 字符串;只在内部登录查询中返回给 api-server,永不进入 HTTP DTO、日志或前端状态 |
|
||||
| `tab_permissions_json` | `String` | 规范化后的 Tab permission JSON;只允许第 4 节 16 个值,空数组为 `[]` |
|
||||
| `enabled` | `bool` | 是否允许登录和继续使用现有 JWT |
|
||||
| `token_version` | `u64` | 初始为 `1`;权限、密码或启停状态发生有效变化时加 `1` |
|
||||
| `created_by` | `String` | 创建者后台 subject;当前只能是 owner subject |
|
||||
| `updated_by` | `String` | 最近更新者后台 subject;当前只能是 owner subject |
|
||||
| `created_at` | `Timestamp` | 创建时间,使用 `ctx.timestamp` |
|
||||
| `updated_at` | `Timestamp` | 最近更新时间,使用 `ctx.timestamp` |
|
||||
| `action_permissions_json` | `Option<String>` | 既有表末尾追加;旧行默认 `None` 并按 `[]` 读取,只允许第 4 节独立操作权限 |
|
||||
| 字段 | Rust / SpacetimeDB 类型 | 约束与语义 |
|
||||
| ------------------------- | ----------------------- | ------------------------------------------------------------------------------------------- |
|
||||
| `account_id` | `String` | 主键;服务端生成不可变 opaque id,建议 `admin-account-<uuid>`,请求体不得指定 |
|
||||
| `username` | `String` | `unique`;登录名,创建后不可修改;按 `trim + ASCII lowercase` 规范化 |
|
||||
| `display_name` | `String` | 展示名,去除首尾空白后 1 至 64 字符 |
|
||||
| `password_hash` | `String` | Argon2id PHC 字符串;只在内部登录查询中返回给 api-server,永不进入 HTTP DTO、日志或前端状态 |
|
||||
| `tab_permissions_json` | `String` | 规范化后的 Tab permission JSON;只允许第 4 节 16 个值,空数组为 `[]` |
|
||||
| `enabled` | `bool` | 是否允许登录和继续使用现有 JWT |
|
||||
| `token_version` | `u64` | 初始为 `1`;权限、密码或启停状态发生有效变化时加 `1` |
|
||||
| `created_by` | `String` | 创建者后台 subject;当前只能是 owner subject |
|
||||
| `updated_by` | `String` | 最近更新者后台 subject;当前只能是 owner subject |
|
||||
| `created_at` | `Timestamp` | 创建时间,使用 `ctx.timestamp` |
|
||||
| `updated_at` | `Timestamp` | 最近更新时间,使用 `ctx.timestamp` |
|
||||
| `action_permissions_json` | `Option<String>` | 既有表末尾追加;旧行默认 `None` 并按 `[]` 读取,只允许第 4 节独立操作权限 |
|
||||
|
||||
账号规则:
|
||||
|
||||
@@ -109,13 +109,13 @@ Tab 权限数组必须去重并按上表顺序规范化后保存。保存时拒
|
||||
|
||||
建议新增 `server-rs/crates/spacetime-module/src/admin_account.rs`,并在 `spacetime-client` 增加对应 admin facade。至少提供以下 typed procedures:
|
||||
|
||||
| procedure | 用途 | 是否可返回 `password_hash` |
|
||||
| --- | --- | --- |
|
||||
| `get_admin_account_by_username_and_return` | member 登录查询 | 是,仅返回给 api-server 内部认证路径 |
|
||||
| `get_admin_account_by_id_and_return` | member JWT 逐请求校验 | 否 |
|
||||
| `list_admin_accounts_and_return` | owner 账号列表 | 否 |
|
||||
| `create_admin_account_and_return` | owner 创建 member | 否 |
|
||||
| `update_admin_account_and_return` | owner 更新展示名、密码 hash、权限、启停 | 否 |
|
||||
| procedure | 用途 | 是否可返回 `password_hash` |
|
||||
| ------------------------------------------ | --------------------------------------- | ------------------------------------ |
|
||||
| `get_admin_account_by_username_and_return` | member 登录查询 | 是,仅返回给 api-server 内部认证路径 |
|
||||
| `get_admin_account_by_id_and_return` | member JWT 逐请求校验 | 否 |
|
||||
| `list_admin_accounts_and_return` | owner 账号列表 | 否 |
|
||||
| `create_admin_account_and_return` | owner 创建 member | 否 |
|
||||
| `update_admin_account_and_return` | owner 更新展示名、密码 hash、权限、启停 | 否 |
|
||||
|
||||
所有 `admin_account` procedures 都必须在事务入口调用现有 `require_editor_generation_runtime_service_identity(...)` 等价的统一 runtime service identity 守卫,只允许 api-server 当前 runtime service identity 调用。不能因为它们位于后台命名空间就接受任意 SpacetimeDB client identity,也不能新增 public table/view 暴露账号或 hash。
|
||||
|
||||
@@ -192,53 +192,53 @@ owner 返回全部 16 个 Tab permission id 和全部独立操作权限;member
|
||||
|
||||
下表覆盖 `server-rs/crates/api-server/src/modules/admin.rs` 当前全部路由,并追加账号管理 API。`A OR B` 表示 member 拥有任一权限即可;owner 对全部行自动通过。
|
||||
|
||||
| Method | 路径 | 权限 |
|
||||
| --- | --- | --- |
|
||||
| `POST` | `/admin/api/login` | 公开登录入口,不要求 JWT |
|
||||
| `GET` | `/admin/api/me` | 任意有效后台会话 |
|
||||
| `GET` | `/admin/api/overview` | `overview` |
|
||||
| `GET` | `/admin/api/dashboard` | `dashboard` |
|
||||
| `POST` | `/admin/api/debug/http` | `debug` |
|
||||
| `GET` | `/admin/api/tracking/events` | `tracking` |
|
||||
| `GET` | `/admin/api/tracking/event-keys` | `tracking OR tasks` |
|
||||
| `GET` | `/admin/api/database/tables` | `tables` |
|
||||
| `GET` | `/admin/api/database/tables/{table_name}/rows` | `tables` |
|
||||
| `GET` | `/admin/api/feature-gates` | `gray-release` |
|
||||
| `PUT` | `/admin/api/feature-gates` | `gray-release` |
|
||||
| `GET` | `/admin/api/editor-generation-pricing` | `editor-generation-pricing` |
|
||||
| `POST` | `/admin/api/editor-generation-pricing` | `editor-generation-pricing` |
|
||||
| `GET` | `/admin/api/editor-assets` | `editor-assets` |
|
||||
| `GET` | `/admin/api/assets/read-url` | `editor-assets OR editor-showcase` |
|
||||
| `GET` | `/admin/api/editor-showcase/assets` | `editor-showcase` |
|
||||
| `POST` | `/admin/api/editor-showcase/assets/review` | `editor-showcase` |
|
||||
| `POST` | `/admin/api/editor-showcase/assets/display` | `editor-showcase` |
|
||||
| `GET` | `/admin/api/editor-showcase/campaign` | `editor-showcase` |
|
||||
| `POST` | `/admin/api/editor-showcase/campaign` | `editor-showcase` |
|
||||
| `POST` | `/admin/api/editor-showcase/campaign/image-upload-ticket` | `editor-showcase` |
|
||||
| `GET` | `/admin/api/profile/redeem-codes` | `redeem` |
|
||||
| `POST` | `/admin/api/profile/redeem-codes` | `redeem` |
|
||||
| `POST` | `/admin/api/profile/redeem-codes/disable` | `redeem` |
|
||||
| `GET` | `/admin/api/profile/invite-codes` | `invite` |
|
||||
| `POST` | `/admin/api/profile/invite-codes` | `invite` |
|
||||
| `GET` | `/admin/api/profile/tasks` | `tasks` |
|
||||
| `POST` | `/admin/api/profile/tasks` | `tasks` |
|
||||
| `POST` | `/admin/api/profile/tasks/disable` | `tasks` |
|
||||
| `GET` | `/admin/api/profile/wallet-config` | `profile-wallet` |
|
||||
| `POST` | `/admin/api/profile/wallet-config` | `profile-wallet` |
|
||||
| `GET` | `/admin/api/profile/recharge-products` | `recharge-products` |
|
||||
| `POST` | `/admin/api/profile/recharge-products` | `recharge-products` |
|
||||
| `GET` | `/admin/api/profile/recharge-orders` | `recharge-orders` |
|
||||
| `POST` | `/admin/api/profile/recharge-refunds/preview` | `recharge-orders` |
|
||||
| `POST` | `/admin/api/profile/recharge-refunds/execute` | `recharge-orders` |
|
||||
| `POST` | `/admin/api/profile/recharge-refunds/register` | `recharge-orders` |
|
||||
| `POST` | `/admin/api/profile/recharge-refunds/manual-review/resolve` | `recharge-orders` |
|
||||
| `GET` | `/admin/api/profile/users/detail` | `tables OR tracking OR recharge-orders OR editor-showcase OR editor-assets` |
|
||||
| `POST` | `/admin/api/profile/users/reconcile-consumption` | 独立操作权限 `profile-wallet-consumption-reconcile` |
|
||||
| `POST` | `/admin/api/profile/users/initialize-consumption-projections` | owner-only 维护窗口操作 |
|
||||
| `POST` | `/admin/api/profile/wallet-restriction` | `recharge-orders` |
|
||||
| `GET` | `/admin/api/accounts` | owner-only |
|
||||
| `POST` | `/admin/api/accounts` | owner-only |
|
||||
| `PUT` | `/admin/api/accounts/{account_id}` | owner-only |
|
||||
| Method | 路径 | 权限 |
|
||||
| ------ | ------------------------------------------------------------- | --------------------------------------------------------------------------- |
|
||||
| `POST` | `/admin/api/login` | 公开登录入口,不要求 JWT |
|
||||
| `GET` | `/admin/api/me` | 任意有效后台会话 |
|
||||
| `GET` | `/admin/api/overview` | `overview` |
|
||||
| `GET` | `/admin/api/dashboard` | `dashboard` |
|
||||
| `POST` | `/admin/api/debug/http` | `debug` |
|
||||
| `GET` | `/admin/api/tracking/events` | `tracking` |
|
||||
| `GET` | `/admin/api/tracking/event-keys` | `tracking OR tasks` |
|
||||
| `GET` | `/admin/api/database/tables` | `tables` |
|
||||
| `GET` | `/admin/api/database/tables/{table_name}/rows` | `tables` |
|
||||
| `GET` | `/admin/api/feature-gates` | `gray-release` |
|
||||
| `PUT` | `/admin/api/feature-gates` | `gray-release` |
|
||||
| `GET` | `/admin/api/editor-generation-pricing` | `editor-generation-pricing` |
|
||||
| `POST` | `/admin/api/editor-generation-pricing` | `editor-generation-pricing` |
|
||||
| `GET` | `/admin/api/editor-assets` | `editor-assets` |
|
||||
| `GET` | `/admin/api/assets/read-url` | `editor-assets OR editor-showcase` |
|
||||
| `GET` | `/admin/api/editor-showcase/assets` | `editor-showcase` |
|
||||
| `POST` | `/admin/api/editor-showcase/assets/review` | `editor-showcase` |
|
||||
| `POST` | `/admin/api/editor-showcase/assets/display` | `editor-showcase` |
|
||||
| `GET` | `/admin/api/editor-showcase/campaign` | `editor-showcase` |
|
||||
| `POST` | `/admin/api/editor-showcase/campaign` | `editor-showcase` |
|
||||
| `POST` | `/admin/api/editor-showcase/campaign/image-upload-ticket` | `editor-showcase` |
|
||||
| `GET` | `/admin/api/profile/redeem-codes` | `redeem` |
|
||||
| `POST` | `/admin/api/profile/redeem-codes` | `redeem` |
|
||||
| `POST` | `/admin/api/profile/redeem-codes/disable` | `redeem` |
|
||||
| `GET` | `/admin/api/profile/invite-codes` | `invite` |
|
||||
| `POST` | `/admin/api/profile/invite-codes` | `invite` |
|
||||
| `GET` | `/admin/api/profile/tasks` | `tasks` |
|
||||
| `POST` | `/admin/api/profile/tasks` | `tasks` |
|
||||
| `POST` | `/admin/api/profile/tasks/disable` | `tasks` |
|
||||
| `GET` | `/admin/api/profile/wallet-config` | `profile-wallet` |
|
||||
| `POST` | `/admin/api/profile/wallet-config` | `profile-wallet` |
|
||||
| `GET` | `/admin/api/profile/recharge-products` | `recharge-products` |
|
||||
| `POST` | `/admin/api/profile/recharge-products` | `recharge-products` |
|
||||
| `GET` | `/admin/api/profile/recharge-orders` | `recharge-orders` |
|
||||
| `POST` | `/admin/api/profile/recharge-refunds/preview` | `recharge-orders` |
|
||||
| `POST` | `/admin/api/profile/recharge-refunds/execute` | `recharge-orders` |
|
||||
| `POST` | `/admin/api/profile/recharge-refunds/register` | `recharge-orders` |
|
||||
| `POST` | `/admin/api/profile/recharge-refunds/manual-review/resolve` | `recharge-orders` |
|
||||
| `GET` | `/admin/api/profile/users/detail` | `tables OR tracking OR recharge-orders OR editor-showcase OR editor-assets` |
|
||||
| `POST` | `/admin/api/profile/users/reconcile-consumption` | 独立操作权限 `profile-wallet-consumption-reconcile` |
|
||||
| `POST` | `/admin/api/profile/users/initialize-consumption-projections` | owner-only 维护窗口操作 |
|
||||
| `POST` | `/admin/api/profile/wallet-restriction` | `recharge-orders` |
|
||||
| `GET` | `/admin/api/accounts` | owner-only |
|
||||
| `POST` | `/admin/api/accounts` | owner-only |
|
||||
| `PUT` | `/admin/api/accounts/{account_id}` | owner-only |
|
||||
|
||||
两个共享读取接口必须按 OR 规则实现,不能为了复用简单中间件扩大成“任意 member 可访问”:
|
||||
|
||||
|
||||
@@ -8,23 +8,23 @@
|
||||
|
||||
## 1. 决策摘要
|
||||
|
||||
| 决策项 | 首版结论 |
|
||||
| --- | --- |
|
||||
| 调度单位 | 一次逻辑 BgFilter 调用;角色动画为单帧 |
|
||||
| 父流程 | 保持原 future、调用栈、lease 和 `attempt`,同步等待内部 HTTP |
|
||||
| 通用 worker 槽 | 等待期间继续占用;父 heartbeat 继续运行 |
|
||||
| 请求输入(父 → 子) | 只传私有 OSS `objectKey`、BgFilter 参数、排队预算 `maxQueueWaitMs`、调用预算 `callBudgetMs` 和有界审计关联;不重复传源图字节,也不传签名 URL |
|
||||
| 成功输出(子 → 父) | 内部 HTTP body 直接传回 BgFilter 结果图片的原始字节;不使用 Base64、不返回结果 object key、不先写 raw OSS |
|
||||
| BgFilter worker | 首版只运行一个内部 HTTP worker 实例 |
|
||||
| 并发 | 进程内 `Semaphore(N)`,并增加有界 admission 上限 `Q` |
|
||||
| 重试 | 子 worker 对一次逻辑调用最多做两次顺序 provider attempt;父侧不重试已被 worker 接收的内部 RPC,仅连接从未建立时按预算有界重连(§5.1 / §7.1) |
|
||||
| 超时 | 双预算:父侧派生排队预算 `maxQueueWaitMs` 与调用预算 `callBudgetMs`;attempt 上限由 `N × est × 2` 公式运行时派生(est 默认 `5s`),排队不侵蚀调用时间 |
|
||||
| flat / complex 熔断 | 迁到唯一子 worker;两种模式共享阈值和 `120s` cooldown,但分别维护独立进程内状态 |
|
||||
| 业务语义 | 父流程继续负责 Alpha / 尺寸恢复、flat fallback、最终 OSS、画布写回、计费和父终态 |
|
||||
| 动画失败 | 首版保持当前“所有已提交帧都等待并排空”语义,不新增跨帧取消组 |
|
||||
| 崩溃恢复 | 不查询、不恢复 BgFilter 结果;父 job 沿用现有 lease、失败和退款语义 |
|
||||
| 数据模型 | 不新增 SpacetimeDB 表,不修改 `external_generation_job` schema |
|
||||
| 配置加载 | 子 worker 先加载 API 基础环境,再加载 worker 专属环境覆盖;共享超时保持单一来源 |
|
||||
| 决策项 | 首版结论 |
|
||||
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| 调度单位 | 一次逻辑 BgFilter 调用;角色动画为单帧 |
|
||||
| 父流程 | 保持原 future、调用栈、lease 和 `attempt`,同步等待内部 HTTP |
|
||||
| 通用 worker 槽 | 等待期间继续占用;父 heartbeat 继续运行 |
|
||||
| 请求输入(父 → 子) | 只传私有 OSS `objectKey`、BgFilter 参数、排队预算 `maxQueueWaitMs`、调用预算 `callBudgetMs` 和有界审计关联;不重复传源图字节,也不传签名 URL |
|
||||
| 成功输出(子 → 父) | 内部 HTTP body 直接传回 BgFilter 结果图片的原始字节;不使用 Base64、不返回结果 object key、不先写 raw OSS |
|
||||
| BgFilter worker | 首版只运行一个内部 HTTP worker 实例 |
|
||||
| 并发 | 进程内 `Semaphore(N)`,并增加有界 admission 上限 `Q` |
|
||||
| 重试 | 子 worker 对一次逻辑调用最多做两次顺序 provider attempt;父侧不重试已被 worker 接收的内部 RPC,仅连接从未建立时按预算有界重连(§5.1 / §7.1) |
|
||||
| 超时 | 双预算:父侧派生排队预算 `maxQueueWaitMs` 与调用预算 `callBudgetMs`;attempt 上限由 `N × est × 2` 公式运行时派生(est 默认 `5s`),排队不侵蚀调用时间 |
|
||||
| flat / complex 熔断 | 迁到唯一子 worker;两种模式共享阈值和 `120s` cooldown,但分别维护独立进程内状态 |
|
||||
| 业务语义 | 父流程继续负责 Alpha / 尺寸恢复、flat fallback、最终 OSS、画布写回、计费和父终态 |
|
||||
| 动画失败 | 首版保持当前“所有已提交帧都等待并排空”语义,不新增跨帧取消组 |
|
||||
| 崩溃恢复 | 不查询、不恢复 BgFilter 结果;父 job 沿用现有 lease、失败和退款语义 |
|
||||
| 数据模型 | 不新增 SpacetimeDB 表,不修改 `external_generation_job` schema |
|
||||
| 配置加载 | 子 worker 先加载 API 基础环境,再加载 worker 专属环境覆盖;共享超时保持单一来源 |
|
||||
|
||||
首版明确不实现:
|
||||
|
||||
@@ -102,13 +102,13 @@ flowchart LR
|
||||
|
||||
请求和响应采用不同口径,不能把“请求不传源图字节”理解成“响应也不能传图片字节”:
|
||||
|
||||
| 阶段 | 传递内容 | 是否新增持久化 |
|
||||
| --- | --- | --- |
|
||||
| 父流程 → `bgfilter-worker` | JSON:源图 `objectKey`、参数和预算 | 否 |
|
||||
| `bgfilter-worker` → BgFilter provider | 子 worker 现场签发的源图短期 URL | 否 |
|
||||
| BgFilter provider → `bgfilter-worker` | 结果图片字节 | 否,只在子 worker 有界内存中读取和校验 |
|
||||
| `bgfilter-worker` → 父流程 | `2xx` HTTP body 中的原始结果图片字节 | 否,父侧直接读入有界字节缓冲 |
|
||||
| 父流程 → OSS / 业务写回 | 现有后处理后的最终图片 | 是,仍只走父流程现有最终持久化路径 |
|
||||
| 阶段 | 传递内容 | 是否新增持久化 |
|
||||
| ------------------------------------- | ------------------------------------ | -------------------------------------- |
|
||||
| 父流程 → `bgfilter-worker` | JSON:源图 `objectKey`、参数和预算 | 否 |
|
||||
| `bgfilter-worker` → BgFilter provider | 子 worker 现场签发的源图短期 URL | 否 |
|
||||
| BgFilter provider → `bgfilter-worker` | 结果图片字节 | 否,只在子 worker 有界内存中读取和校验 |
|
||||
| `bgfilter-worker` → 父流程 | `2xx` HTTP body 中的原始结果图片字节 | 否,父侧直接读入有界字节缓冲 |
|
||||
| 父流程 → OSS / 业务写回 | 现有后处理后的最终图片 | 是,仍只走父流程现有最终持久化路径 |
|
||||
|
||||
因此,本方案所说的“直接返回二进制”就是直接传图片字节:父侧内部 client 的成功结果是 `Bytes` / `Vec<u8>` 一类有界内存缓冲及可信的图片类型,而不是 Base64 字符串、临时 object key 或子任务结果记录。这里不是把 provider 响应边读边透明转发;子 worker 要先完整读取并校验结果,确认本次 attempt 成功后,再把同一份图片内容作为内部 HTTP body 返回,以保留第二次顺序尝试和无效图片拦截能力。
|
||||
|
||||
@@ -298,11 +298,11 @@ inline / External v1 当前没有显式 `RequestContext` deadline 时,内部 R
|
||||
|
||||
当前父 worker 可能提交的最大帧请求数并不只有 `96`:
|
||||
|
||||
| 场景 | 潜在同时提交的动画帧调用 |
|
||||
| --- | ---: |
|
||||
| 一个动画父 job | `48` |
|
||||
| 一个默认 external-generation-worker,父并发 `2` | `96` |
|
||||
| controller 最多 `8` 个父 worker、每个并发 `2` | `768` |
|
||||
| 场景 | 潜在同时提交的动画帧调用 |
|
||||
| ----------------------------------------------- | -----------------------: |
|
||||
| 一个动画父 job | `48` |
|
||||
| 一个默认 external-generation-worker,父并发 `2` | `96` |
|
||||
| controller 最多 `8` 个父 worker、每个并发 `2` | `768` |
|
||||
|
||||
理论最大 `768` 远低于保险丝 `2048`,正常业务不会触发 `overloaded`;过载时的降级路径改由 §5.2 的动态排队探测承担——排队超出合理预期的 flat 请求提早进入 fallback,complex 失败。
|
||||
|
||||
@@ -364,16 +364,16 @@ flat / complex 统一使用的 `GENARRATIVE_EDITOR_BGFILTER_CIRCUIT_FAILURE_THRE
|
||||
|
||||
## 8. 业务语义保持
|
||||
|
||||
| 场景 | 内部响应 | 父流程行为 |
|
||||
| --- | --- | --- |
|
||||
| flat 单图 / 动画帧 | 图片二进制 | 继续现有 Alpha / 尺寸恢复、finalizer 和最终持久化 |
|
||||
| flat,父业务预算仍有效 | `provider_exhausted / circuit_open / deadline_exceeded / overloaded / invalid_result / internal_error` 或内部断连 | 记录对应故障后进入现有“阿里云通用抠图 → 本地键色”;这些内部错误本身不都计入熔断 |
|
||||
| flat | `cancelled`(保留码,首版子 worker 不产生),或父 job cancellation / 绝对 deadline 已生效 | 立即向上退出,不再启动阿里云或本地 fallback |
|
||||
| flat | `invalid_request / unauthorized` | 作为内部契约或部署配置错误失败,不 fallback、不计入 BgFilter 熔断 |
|
||||
| complex 手动去背景 | 图片二进制 | 父流程继续最终 OSS、资源和画布写回 |
|
||||
| complex 手动去背景 | 任意非成功或断连 | 父流程直接失败;provider 失败只累计 complex 熔断,不得接 flat fallback 或修改 flat 熔断 |
|
||||
| 角色 / 图标 / UI 后处理最终失败 | BgFilter 与 fallback 都未得到可用结果 | 保留已持久化 provider 原图,以现有 `completed + warning` 收口 |
|
||||
| 动画任一帧最终失败 | 该帧完整 fallback / finalizer / PUT 仍失败 | 排空其它已提交帧后,整项动画按现有语义失败退款 |
|
||||
| 场景 | 内部响应 | 父流程行为 |
|
||||
| ------------------------------- | ----------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
|
||||
| flat 单图 / 动画帧 | 图片二进制 | 继续现有 Alpha / 尺寸恢复、finalizer 和最终持久化 |
|
||||
| flat,父业务预算仍有效 | `provider_exhausted / circuit_open / deadline_exceeded / overloaded / invalid_result / internal_error` 或内部断连 | 记录对应故障后进入现有“阿里云通用抠图 → 本地键色”;这些内部错误本身不都计入熔断 |
|
||||
| flat | `cancelled`(保留码,首版子 worker 不产生),或父 job cancellation / 绝对 deadline 已生效 | 立即向上退出,不再启动阿里云或本地 fallback |
|
||||
| flat | `invalid_request / unauthorized` | 作为内部契约或部署配置错误失败,不 fallback、不计入 BgFilter 熔断 |
|
||||
| complex 手动去背景 | 图片二进制 | 父流程继续最终 OSS、资源和画布写回 |
|
||||
| complex 手动去背景 | 任意非成功或断连 | 父流程直接失败;provider 失败只累计 complex 熔断,不得接 flat fallback 或修改 flat 熔断 |
|
||||
| 角色 / 图标 / UI 后处理最终失败 | BgFilter 与 fallback 都未得到可用结果 | 保留已持久化 provider 原图,以现有 `completed + warning` 收口 |
|
||||
| 动画任一帧最终失败 | 该帧完整 fallback / finalizer / PUT 仍失败 | 排空其它已提交帧后,整项动画按现有语义失败退款 |
|
||||
|
||||
父侧移除现有 flat / complex BgFilter retry loop 和本地 flat 熔断,避免父侧两次 × 子 worker 两次变成四次。所有入口只替换共同的低层 BgFilter helper;这样 External v1 直接调用 `_for_owner` 的路径也会自然经过内部 worker。
|
||||
|
||||
|
||||
@@ -60,29 +60,29 @@ BFF 只做鉴权、授权裁剪、字段脱敏和契约映射;worker 调度、
|
||||
|
||||
新增私有表 `external_generation_job`:
|
||||
|
||||
| 字段 | 说明 |
|
||||
| --- | --- |
|
||||
| `job_id` | 主键,`extgen-` 前缀 UUID |
|
||||
| `dedupe_key` | 唯一键,建议为 `play/action/session/scope` |
|
||||
| `job_kind` | 执行类型,当前覆盖 `puzzle_compile_draft`、`puzzle_generate_images`、`puzzle_generate_ui_background`、跳一跳 / 拼消消 / 敲木鱼生成动作,以及 `editor_image_generation`、`editor_image_edit`、`editor_background_removal`、`editor_icon_spritesheet_generation`、`editor_ui_design_asset_extraction`、`editor_character_animation_generation`、`editor_video_generation`、`editor_sound_effect_generation`、`editor_background_music_generation` |
|
||||
| `owner_user_id` | 触发用户 |
|
||||
| `source_module` | 玩法或能力名,例如 `puzzle` |
|
||||
| `source_entity_id` | session/profile/work 等作用域 |
|
||||
| `request_label` | 排障标签 |
|
||||
| `request_payload_json` | worker 执行入参 JSON |
|
||||
| `status` | `pending/running/completed/failed/cancelled` |
|
||||
| `attempt` / `max_attempts` | 当前尝试次数与最大尝试次数 |
|
||||
| `last_error_message` | 最近失败原因 |
|
||||
| `worker_id` | 当前 lease owner |
|
||||
| `lease_expires_at` | lease 到期时间 |
|
||||
| `lease_token` | 本次 claim 的 fencing token,用于阻止过期 worker 回写 |
|
||||
| `available_at` | 下次可领取时间 |
|
||||
| `result_payload_json` | 完成摘要 |
|
||||
| `created_at/started_at/completed_at/updated_at` | 审计时间 |
|
||||
| `price_mud_points` | 后端计算的本任务价格,用于任务列表展示和排障 |
|
||||
| `refund_ledger_id` | 失败退款产生的钱包退款流水 ID,便于从任务追到退款记录 |
|
||||
| `notification_acknowledged_at` | 用户已确认完成 / 失败提示的时间,未确认终态任务下次登录继续集中弹出 |
|
||||
| `phase` | 尾部可选字段;`null / generating / processing`,claim 时写 `generating`,进入正式后处理时写 `processing` |
|
||||
| 字段 | 说明 |
|
||||
| ----------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `job_id` | 主键,`extgen-` 前缀 UUID |
|
||||
| `dedupe_key` | 唯一键,建议为 `play/action/session/scope` |
|
||||
| `job_kind` | 执行类型,当前覆盖 `puzzle_compile_draft`、`puzzle_generate_images`、`puzzle_generate_ui_background`、跳一跳 / 拼消消 / 敲木鱼生成动作,以及 `editor_image_generation`、`editor_image_edit`、`editor_background_removal`、`editor_icon_spritesheet_generation`、`editor_ui_design_asset_extraction`、`editor_character_animation_generation`、`editor_video_generation`、`editor_sound_effect_generation`、`editor_background_music_generation` |
|
||||
| `owner_user_id` | 触发用户 |
|
||||
| `source_module` | 玩法或能力名,例如 `puzzle` |
|
||||
| `source_entity_id` | session/profile/work 等作用域 |
|
||||
| `request_label` | 排障标签 |
|
||||
| `request_payload_json` | worker 执行入参 JSON |
|
||||
| `status` | `pending/running/completed/failed/cancelled` |
|
||||
| `attempt` / `max_attempts` | 当前尝试次数与最大尝试次数 |
|
||||
| `last_error_message` | 最近失败原因 |
|
||||
| `worker_id` | 当前 lease owner |
|
||||
| `lease_expires_at` | lease 到期时间 |
|
||||
| `lease_token` | 本次 claim 的 fencing token,用于阻止过期 worker 回写 |
|
||||
| `available_at` | 下次可领取时间 |
|
||||
| `result_payload_json` | 完成摘要 |
|
||||
| `created_at/started_at/completed_at/updated_at` | 审计时间 |
|
||||
| `price_mud_points` | 后端计算的本任务价格,用于任务列表展示和排障 |
|
||||
| `refund_ledger_id` | 失败退款产生的钱包退款流水 ID,便于从任务追到退款记录 |
|
||||
| `notification_acknowledged_at` | 用户已确认完成 / 失败提示的时间,未确认终态任务下次登录继续集中弹出 |
|
||||
| `phase` | 尾部可选字段;`null / generating / processing`,claim 时写 `generating`,进入正式后处理时写 `processing` |
|
||||
|
||||
用户正式读取使用私有轻量投影 `external_generation_job_summary`。该表同步保存 owner、来源、状态、`phase`、价格、有限错误/告警摘要、通知确认和时间字段,不复制 request/result payload、worker lease 或 dedupe 内部字段;enqueue、claim、renew、phase update、complete、fail 与 acknowledge 都必须维护对应投影语义。
|
||||
|
||||
|
||||
@@ -30,17 +30,17 @@ preview gateway / 独立 preview origin
|
||||
|
||||
## 资产与威胁
|
||||
|
||||
| 资产 | 主要威胁 | MVP 缓解 |
|
||||
| --- | --- | --- |
|
||||
| 主站 access token / cookie | 预览代码同源读取、XSS 窃取 | 独立 preview origin;iframe 不带主站 cookie;预览页不能访问主站 storage |
|
||||
| 用户 Web 工程源码 | 跨租户读取、snapshot 枚举 | project / owner 校验;snapshotId 不可枚举;preview token 绑定 owner / project / snapshot |
|
||||
| preview artifact | 路径穿越、MIME 错误、旧 token 访问 | preview gateway 校验 token;禁止 `..`;按白名单 MIME 服务;短期 token 可撤销 |
|
||||
| runner 临时工作区 | 逃逸到宿主源码、读取密钥 | 独立临时目录或容器;非 root;无宿主源码挂载;任务结束销毁 |
|
||||
| 依赖缓存 | 缓存污染、恶意 postinstall | MVP 固定依赖;禁用 scripts;缓存 key 包含模板、Node 版本和 lock digest |
|
||||
| api-server / SpacetimeDB | runner 横向访问内部服务 | runner 默认无内网访问;阻断 api-server 管理端口、SpacetimeDB 和生产数据库 |
|
||||
| OSS / artifact store | 越权读写、签名 URL 泄露 | runner 只拿短期只读资产签名或受控写 artifact 能力;日志脱敏 |
|
||||
| 构建日志 | 泄露环境变量、宿主路径、签名 URL | 日志限长、脱敏、错误摘要化;不回显平台密钥 |
|
||||
| 用户浏览器 | 弹窗逃逸、下载、剪贴板、摄像头、Service Worker 常驻 | iframe sandbox;CSP;禁用 Service Worker;不授权敏感能力 |
|
||||
| 资产 | 主要威胁 | MVP 缓解 |
|
||||
| -------------------------- | --------------------------------------------------- | ---------------------------------------------------------------------------------------- |
|
||||
| 主站 access token / cookie | 预览代码同源读取、XSS 窃取 | 独立 preview origin;iframe 不带主站 cookie;预览页不能访问主站 storage |
|
||||
| 用户 Web 工程源码 | 跨租户读取、snapshot 枚举 | project / owner 校验;snapshotId 不可枚举;preview token 绑定 owner / project / snapshot |
|
||||
| preview artifact | 路径穿越、MIME 错误、旧 token 访问 | preview gateway 校验 token;禁止 `..`;按白名单 MIME 服务;短期 token 可撤销 |
|
||||
| runner 临时工作区 | 逃逸到宿主源码、读取密钥 | 独立临时目录或容器;非 root;无宿主源码挂载;任务结束销毁 |
|
||||
| 依赖缓存 | 缓存污染、恶意 postinstall | MVP 固定依赖;禁用 scripts;缓存 key 包含模板、Node 版本和 lock digest |
|
||||
| api-server / SpacetimeDB | runner 横向访问内部服务 | runner 默认无内网访问;阻断 api-server 管理端口、SpacetimeDB 和生产数据库 |
|
||||
| OSS / artifact store | 越权读写、签名 URL 泄露 | runner 只拿短期只读资产签名或受控写 artifact 能力;日志脱敏 |
|
||||
| 构建日志 | 泄露环境变量、宿主路径、签名 URL | 日志限长、脱敏、错误摘要化;不回显平台密钥 |
|
||||
| 用户浏览器 | 弹窗逃逸、下载、剪贴板、摄像头、Service Worker 常驻 | iframe sandbox;CSP;禁用 Service Worker;不授权敏感能力 |
|
||||
|
||||
## Runner 限制
|
||||
|
||||
|
||||
@@ -472,70 +472,70 @@ dev 根盘空间在安装后曾接近满盘;2026-06-17 进入 canary 前已清
|
||||
|
||||
## 环境变量
|
||||
|
||||
| 变量 | 默认值 | 说明 |
|
||||
| ------------------------------------------------------------------- | ------------------------------------------ | -------------------------------------------------------------------------------------------------- |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_LISTEN` | `127.0.0.1:18081` | Pingora 监听地址。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_TLS_LISTEN` | 空 | 可选 HTTPS 监听地址;启用时必须同时设置 `TLS_CERT_FILE` 和 `TLS_KEY_FILE`。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_TLS_CERT_FILE` | 空 | 可选 HTTPS 证书链文件;必须是网关运行用户可读取的文件。Certbot 证书建议先同步到 `/etc/genarrative/pingora-tls/<域名>/fullchain.pem`。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_TLS_KEY_FILE` | 空 | 可选 HTTPS 私钥文件;必须是网关运行用户可读取的文件。Certbot 私钥建议先同步到 `/etc/genarrative/pingora-tls/<域名>/privkey.pem`。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_HTTP_REDIRECT_LISTEN` | 空 | 可选 HTTP 重定向监听地址;启用时必须已配置 TLS 入口,ACME challenge 仍静态读取。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_HTTP_REDIRECT_TARGET_SCHEME` | `https` | HTTP 重定向目标 scheme,当前只允许 `https`。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_API_UPSTREAM` | `127.0.0.1:8082` | `api-server` 上游地址。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_SPACETIME_UPSTREAM` | `127.0.0.1:3101` | SpacetimeDB 上游地址。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_GITEA_HOSTS` | 空 | 可选 Gitea Host 白名单,逗号分隔;匹配时整站代理到 Gitea。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_GITEA_UPSTREAM` | 空 | 可选 Gitea 上游;配置 Gitea Host 时必须同时设置。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_WEB_ROOT` | `/srv/genarrative/web` | 前端静态文件根目录。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_ACME_ROOT` | `/var/www/html` | ACME challenge 静态目录。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_MAINTENANCE_FILE` | `/var/lib/genarrative/maintenance/enabled` | 存在即进入维护模式。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_FORWARDED_PROTO` | `http` | 写入 `X-Forwarded-Proto` 的值。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_MAX_API_BODY_BYTES` | `67108864` | `/api` 通用路由的 `Content-Length` 上限。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_COMPRESSION_ALGORITHMS` | `gzip` | 当前唯一允许的压缩算法白名单;Pingora 正式化口径固定为 gzip-only,Brotli 继续由 Nginx / 前置代理承担。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_GZIP_ENABLED` | `true` | 是否启用 gzip 响应压缩。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_GZIP_LEVEL` | `5` | gzip 压缩等级,必须在 `0..=9`。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_GZIP_MIN_LENGTH_BYTES` | `1024` | gzip 最小响应长度,默认对齐 Nginx `gzip_min_length 1024`,必须大于 `0`。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_HTML_CACHE_CONTROL` | `no-cache` | HTML、目录 index 和 SPA fallback 的缓存头,避免入口 HTML 被长期缓存。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_ASSET_CACHE_CONTROL` | `public, max-age=31536000, immutable` | `/assets/*` 与 `/admin/assets/*` 中带 Vite 指纹文件名的静态资源缓存头。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_STATIC_CACHE_CONTROL` | `no-cache` | 非指纹静态资源和 ACME challenge 的默认缓存头。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_UPSTREAM_CONNECT_TIMEOUT_MS` | `3000` | 连接上游的超时,必须大于 `0`。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_UPSTREAM_DEFAULT_READ_TIMEOUT_SECONDS` | `60` | 没有 Nginx 显式长超时的代理路由读取超时,必须大于 `0`。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_UPSTREAM_API_READ_TIMEOUT_SECONDS` | `3600` | 通用 `/api` 路由读取超时,对齐当前 Nginx `proxy_read_timeout 3600s`。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_UPSTREAM_LONG_READ_TIMEOUT_SECONDS` | `3600` | SpacetimeDB subscribe 长连接读取超时。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_UPSTREAM_WRITE_TIMEOUT_SECONDS` | `3600` | 写上游请求头 / 请求体超时,对齐当前 Nginx `proxy_send_timeout 3600s` 口径。 |
|
||||
| 变量 | 默认值 | 说明 |
|
||||
| ------------------------------------------------------------------- | ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_LISTEN` | `127.0.0.1:18081` | Pingora 监听地址。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_TLS_LISTEN` | 空 | 可选 HTTPS 监听地址;启用时必须同时设置 `TLS_CERT_FILE` 和 `TLS_KEY_FILE`。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_TLS_CERT_FILE` | 空 | 可选 HTTPS 证书链文件;必须是网关运行用户可读取的文件。Certbot 证书建议先同步到 `/etc/genarrative/pingora-tls/<域名>/fullchain.pem`。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_TLS_KEY_FILE` | 空 | 可选 HTTPS 私钥文件;必须是网关运行用户可读取的文件。Certbot 私钥建议先同步到 `/etc/genarrative/pingora-tls/<域名>/privkey.pem`。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_HTTP_REDIRECT_LISTEN` | 空 | 可选 HTTP 重定向监听地址;启用时必须已配置 TLS 入口,ACME challenge 仍静态读取。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_HTTP_REDIRECT_TARGET_SCHEME` | `https` | HTTP 重定向目标 scheme,当前只允许 `https`。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_API_UPSTREAM` | `127.0.0.1:8082` | `api-server` 上游地址。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_SPACETIME_UPSTREAM` | `127.0.0.1:3101` | SpacetimeDB 上游地址。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_GITEA_HOSTS` | 空 | 可选 Gitea Host 白名单,逗号分隔;匹配时整站代理到 Gitea。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_GITEA_UPSTREAM` | 空 | 可选 Gitea 上游;配置 Gitea Host 时必须同时设置。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_WEB_ROOT` | `/srv/genarrative/web` | 前端静态文件根目录。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_ACME_ROOT` | `/var/www/html` | ACME challenge 静态目录。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_MAINTENANCE_FILE` | `/var/lib/genarrative/maintenance/enabled` | 存在即进入维护模式。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_FORWARDED_PROTO` | `http` | 写入 `X-Forwarded-Proto` 的值。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_MAX_API_BODY_BYTES` | `67108864` | `/api` 通用路由的 `Content-Length` 上限。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_COMPRESSION_ALGORITHMS` | `gzip` | 当前唯一允许的压缩算法白名单;Pingora 正式化口径固定为 gzip-only,Brotli 继续由 Nginx / 前置代理承担。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_GZIP_ENABLED` | `true` | 是否启用 gzip 响应压缩。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_GZIP_LEVEL` | `5` | gzip 压缩等级,必须在 `0..=9`。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_GZIP_MIN_LENGTH_BYTES` | `1024` | gzip 最小响应长度,默认对齐 Nginx `gzip_min_length 1024`,必须大于 `0`。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_HTML_CACHE_CONTROL` | `no-cache` | HTML、目录 index 和 SPA fallback 的缓存头,避免入口 HTML 被长期缓存。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_ASSET_CACHE_CONTROL` | `public, max-age=31536000, immutable` | `/assets/*` 与 `/admin/assets/*` 中带 Vite 指纹文件名的静态资源缓存头。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_STATIC_CACHE_CONTROL` | `no-cache` | 非指纹静态资源和 ACME challenge 的默认缓存头。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_UPSTREAM_CONNECT_TIMEOUT_MS` | `3000` | 连接上游的超时,必须大于 `0`。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_UPSTREAM_DEFAULT_READ_TIMEOUT_SECONDS` | `60` | 没有 Nginx 显式长超时的代理路由读取超时,必须大于 `0`。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_UPSTREAM_API_READ_TIMEOUT_SECONDS` | `3600` | 通用 `/api` 路由读取超时,对齐当前 Nginx `proxy_read_timeout 3600s`。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_UPSTREAM_LONG_READ_TIMEOUT_SECONDS` | `3600` | SpacetimeDB subscribe 长连接读取超时。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_UPSTREAM_WRITE_TIMEOUT_SECONDS` | `3600` | 写上游请求头 / 请求体超时,对齐当前 Nginx `proxy_send_timeout 3600s` 口径。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_TRUST_X_FORWARDED_FOR` | `false` | 是否用 `X-Forwarded-For` 首个 IP 作为接流保护 client key;公网直连 Pingora 时必须保持 `false`,direct preflight 会阻断公网监听误开启。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_TRUSTED_FRONT_PROXY_CONFIRMED` | `false` | 开启 `TRUST_X_FORWARDED_FOR` 时必须显式设为 `true`,表示前置代理会清洗 `X-Forwarded-For`。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_PROTECTION_ENABLED` | `true` | 是否启用单进程接流保护。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_INSTANCE_COUNT` | `1` | 当前接流保护覆盖的 Pingora 实例数;必须是正整数。开启网关保护且大于 `1` 时必须确认共享保护层。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_SHARED_PROTECTION_CONFIRMED` | `false` | 多实例仍启用网关保护时必须显式设为 `true`,表示已落地共享限流 / 共享并发保护层。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_ADMIN_API_MAX_CONCURRENT` | `64` | `/admin/api/*` 每 client 并发上限;`0` 表示不限制并发。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_ADMIN_API_RATE_PER_SECOND` | `30` | `/admin/api/*` 每 client token bucket 回填速率;`0` 表示不限制 RPS。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_ADMIN_API_BURST` | `16` | `/admin/api/*` 每 client 额外 burst。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_API_MAX_CONCURRENT` | `64` | 通用 `/api` 路由每 client 并发上限。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_API_RATE_PER_SECOND` | `300` | 通用 `/api` 路由每 client RPS。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_API_BURST` | `64` | 通用 `/api` 路由每 client burst。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_SPACETIME_MAX_CONCURRENT` | `256` | SpacetimeDB 公开最小路由每 client 并发上限。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_SPACETIME_RATE_PER_SECOND` | `1000` | SpacetimeDB 公开最小路由每 client RPS。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_SPACETIME_BURST` | `256` | SpacetimeDB 公开最小路由每 client burst。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_PROBE_TOKEN` | 空 | 内部 shadow 探针 token;为空时探针端点关闭。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_LOG` | `info,pingora=info,pingora_gateway=info` | tracing 过滤器。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_ACCESS_LOG_FILE` | 空 | 可选 access log 文件路径;生产 shadow 示例使用 `/var/log/genarrative/pingora-gateway.access.log`。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_OTEL_ENABLED` | `false` | 是否启用共享 OpenTelemetry 初始化。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_TRUSTED_FRONT_PROXY_CONFIRMED` | `false` | 开启 `TRUST_X_FORWARDED_FOR` 时必须显式设为 `true`,表示前置代理会清洗 `X-Forwarded-For`。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_PROTECTION_ENABLED` | `true` | 是否启用单进程接流保护。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_INSTANCE_COUNT` | `1` | 当前接流保护覆盖的 Pingora 实例数;必须是正整数。开启网关保护且大于 `1` 时必须确认共享保护层。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_SHARED_PROTECTION_CONFIRMED` | `false` | 多实例仍启用网关保护时必须显式设为 `true`,表示已落地共享限流 / 共享并发保护层。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_ADMIN_API_MAX_CONCURRENT` | `64` | `/admin/api/*` 每 client 并发上限;`0` 表示不限制并发。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_ADMIN_API_RATE_PER_SECOND` | `30` | `/admin/api/*` 每 client token bucket 回填速率;`0` 表示不限制 RPS。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_ADMIN_API_BURST` | `16` | `/admin/api/*` 每 client 额外 burst。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_API_MAX_CONCURRENT` | `64` | 通用 `/api` 路由每 client 并发上限。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_API_RATE_PER_SECOND` | `300` | 通用 `/api` 路由每 client RPS。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_API_BURST` | `64` | 通用 `/api` 路由每 client burst。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_SPACETIME_MAX_CONCURRENT` | `256` | SpacetimeDB 公开最小路由每 client 并发上限。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_SPACETIME_RATE_PER_SECOND` | `1000` | SpacetimeDB 公开最小路由每 client RPS。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_SPACETIME_BURST` | `256` | SpacetimeDB 公开最小路由每 client burst。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_PROBE_TOKEN` | 空 | 内部 shadow 探针 token;为空时探针端点关闭。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_LOG` | `info,pingora=info,pingora_gateway=info` | tracing 过滤器。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_ACCESS_LOG_FILE` | 空 | 可选 access log 文件路径;生产 shadow 示例使用 `/var/log/genarrative/pingora-gateway.access.log`。 |
|
||||
| `GENARRATIVE_PINGORA_GATEWAY_OTEL_ENABLED` | `false` | 是否启用共享 OpenTelemetry 初始化。 |
|
||||
|
||||
## 当前路由口径
|
||||
|
||||
| 路由 | 行为 |
|
||||
| ----------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
|
||||
| `/.well-known/acme-challenge/*` | 从 `GENARRATIVE_PINGORA_GATEWAY_ACME_ROOT` 精确读取静态文件,默认 `Cache-Control: no-cache`,并带 `ETag` / `Last-Modified` / `Accept-Ranges: bytes`。 |
|
||||
| `/admin` | 301 到 `/admin/`。 |
|
||||
| `/admin/api/*` | 转发到 `api-server`。 |
|
||||
| `/admin/assets/*` | 从 Web 根目录精确读取静态文件;带 Vite 指纹的文件默认长期缓存,其它文件默认 `no-cache`,并支持条件请求返回 `304` 与单段 `Range: bytes=` 返回 `206` / 越界返回 `416`。 |
|
||||
| `/admin/*` | 先读取静态文件或目录 index,失败回退 `/admin/index.html`,HTML 默认 `no-cache`,并支持条件请求返回 `304` 与单段 `Range: bytes=` 返回 `206` / 越界返回 `416`。 |
|
||||
| `/assets/*` | 从 Web 根目录精确读取静态文件;带 Vite 指纹的文件默认长期缓存,其它文件默认 `no-cache`,并支持条件请求返回 `304` 与单段 `Range: bytes=` 返回 `206` / 越界返回 `416`。 |
|
||||
| `/api`、`/api/*` | 转发到 `api-server`,按配置执行 `Content-Length` 与流式 body 累计上限检查。 |
|
||||
| `/v1/database/{db}/subscribe`、`/v1/identity*` | 转发到 SpacetimeDB,保留 WebSocket Upgrade 头。 |
|
||||
| `/__genarrative_pingora/healthz` | 仅在携带 `X-Genarrative-Pingora-Probe` 且匹配配置 token 时返回 shadow JSON,否则 404。 |
|
||||
| `/v1/*`、`/generated-*`、`/healthz*`、`/readyz*` | 返回 404,保持生产公网不暴露口径。 |
|
||||
| 主站 SPA allowlist | 只对 `/`、`/creation`、`/project`、`/profile` 与 `/editor/canvas` 失败回退 `/index.html`;匹配大小写不敏感并允许一个尾部斜杠,HTML 默认 `no-cache`。 |
|
||||
| 其它 Web 路径 | 只读取真实静态文件或目录 index,缺失时返回真实 404;`/creation/not-exist`、`/runtime/not-exist`、`/puzzle/not-exist` 不进入 SPA fallback。 |
|
||||
| 路由 | 行为 |
|
||||
| ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `/.well-known/acme-challenge/*` | 从 `GENARRATIVE_PINGORA_GATEWAY_ACME_ROOT` 精确读取静态文件,默认 `Cache-Control: no-cache`,并带 `ETag` / `Last-Modified` / `Accept-Ranges: bytes`。 |
|
||||
| `/admin` | 301 到 `/admin/`。 |
|
||||
| `/admin/api/*` | 转发到 `api-server`。 |
|
||||
| `/admin/assets/*` | 从 Web 根目录精确读取静态文件;带 Vite 指纹的文件默认长期缓存,其它文件默认 `no-cache`,并支持条件请求返回 `304` 与单段 `Range: bytes=` 返回 `206` / 越界返回 `416`。 |
|
||||
| `/admin/*` | 先读取静态文件或目录 index,失败回退 `/admin/index.html`,HTML 默认 `no-cache`,并支持条件请求返回 `304` 与单段 `Range: bytes=` 返回 `206` / 越界返回 `416`。 |
|
||||
| `/assets/*` | 从 Web 根目录精确读取静态文件;带 Vite 指纹的文件默认长期缓存,其它文件默认 `no-cache`,并支持条件请求返回 `304` 与单段 `Range: bytes=` 返回 `206` / 越界返回 `416`。 |
|
||||
| `/api`、`/api/*` | 转发到 `api-server`,按配置执行 `Content-Length` 与流式 body 累计上限检查。 |
|
||||
| `/v1/database/{db}/subscribe`、`/v1/identity*` | 转发到 SpacetimeDB,保留 WebSocket Upgrade 头。 |
|
||||
| `/__genarrative_pingora/healthz` | 仅在携带 `X-Genarrative-Pingora-Probe` 且匹配配置 token 时返回 shadow JSON,否则 404。 |
|
||||
| `/v1/*`、`/generated-*`、`/healthz*`、`/readyz*` | 返回 404,保持生产公网不暴露口径。 |
|
||||
| 主站 SPA allowlist | 只对 `/`、`/creation`、`/project`、`/profile` 与 `/editor/canvas` 失败回退 `/index.html`;匹配大小写不敏感并允许一个尾部斜杠,HTML 默认 `no-cache`。 |
|
||||
| 其它 Web 路径 | 只读取真实静态文件或目录 index,缺失时返回真实 404;`/creation/not-exist`、`/runtime/not-exist`、`/puzzle/not-exist` 不进入 SPA fallback。 |
|
||||
|
||||
维护模式下,公网 API-like 路由返回 JSON `503`;公网 Web 静态路由先读取 `GENARRATIVE_PINGORA_GATEWAY_MAINTENANCE_PAGE_FILE` 指向的 release 外运行态公告,缺失时回退 `GENARRATIVE_PINGORA_GATEWAY_WEB_ROOT/maintenance.html`,两者都不存在时返回纯文本 `503`。版本化默认页不得包含日期或具体时段,临时公告由 `maintenance-on.sh --page-file` 安装并在 `maintenance-off.sh` 时清理。IPv4 loopback / RFC1918 / link-local 和 IPv6 loopback / ULA / link-local 来源绕过整站维护闸,主站页面与静态资源、普通 API、后台页面与后台 API、SpacetimeDB 路由均按非维护状态继续处理;应用层登录、管理员鉴权和其它业务鉴权保持不变。Pingora 直连按 TCP peer 判定来源;仅当 peer 是 loopback 的同机 Nginx 时才接受 Nginx 强制覆盖的 `X-Real-IP`,绝不使用客户端可伪造的 `X-Forwarded-For` 做维护放行。该放行只绕过网关维护响应;若 `pause-after-stdb` 已停止 api-server,内网普通 API 和后台 API 仍不可用。
|
||||
代理失败时,API / SpacetimeDB 等代理路由返回统一 JSON 网关错误;本地静态路由仍保持对应 HTTP 错误状态。
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
# AGC 客户端更新检查与下载
|
||||
|
||||
## 交付范围
|
||||
|
||||
AGC 每次启动时由根窗口检查一次公开 OSS 更新清单。清单默认位于
|
||||
`https://agc-dev.oss-rg-china-mainland.aliyuncs.com/agc/latest.json`,构建时可用
|
||||
`VITE_AGC_UPDATE_MANIFEST_URL` 覆盖为同一受信任 OSS 域名下的 HTTPS 地址。客户端版本取
|
||||
`apps/ai-game-creator-shell/package.json`,通过 `version` 与清单版本比较;只有远端版本更高时显示更新提示。
|
||||
|
||||
清单格式:
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "0.1.13",
|
||||
"downloadUrl": "https://agc-dev.oss-rg-china-mainland.aliyuncs.com/agc/0.1.13/Genarrative-AI-Game-Creator.exe",
|
||||
"sha256": "<64位十六进制摘要>",
|
||||
"size": 123456789,
|
||||
"releaseNotes": "修复与改进"
|
||||
}
|
||||
```
|
||||
|
||||
`downloadUrl` 必须是 HTTPS;如提供 `sha256` / `size`,Tauri 下载时会校验摘要和字节数。点击“下载更新”后,客户端将安装包流式写入系统临时目录并显示进度,校验成功后通过 Windows UAC 提权启动 NSIS 静默安装并退出旧客户端。
|
||||
|
||||
## 启动与失败策略
|
||||
|
||||
- 检查挂在 `WindowChrome` 根组件,覆盖首页、工作台和调试窗口;网络错误、格式错误或版本不高于当前版本均静默忽略,不阻塞客户端启动。
|
||||
- 更新请求使用单例 Promise,React StrictMode 或同一窗口重复挂载不会重复请求。
|
||||
- Tauri HTTP capability 与 CSP 仅放行默认 OSS 域名;若更换域名,需同步更新 `capabilities/main.json`、`tauri.conf.json` 和发布环境配置。
|
||||
|
||||
## 发布约定
|
||||
|
||||
当前发布目标固定为 Windows x64 NSIS。执行 `npm run ai-game-creator-shell:build` 会先读取
|
||||
`VITE_AGC_UPDATE_MANIFEST_URL`(默认 `https://agc-dev.oss-rg-china-mainland.aliyuncs.com/agc/latest.json`)的
|
||||
`latest.json`,取本地与 OSS 的较高版本并递增一个 patch,然后同步更新 package、Tauri 和 Cargo
|
||||
版本后再向 Tauri 传入 `--target x86_64-pc-windows-msvc` 构建。OSS 清单首次不存在时按本地版本递增;
|
||||
OSS 请求失败、清单格式错误或版本无效会终止发布,避免覆盖线上版本。构建完成后自动扫描 `.exe`
|
||||
安装包,并在 `apps/ai-game-creator-shell/src-tauri/target/x86_64-pc-windows-msvc/release/bundle/latest.json`
|
||||
生成包含版本、下载地址、大小和 SHA-256 的清单。可通过 `AGC_BUILD_TARGET` 显式覆盖目标(发布仍应使用
|
||||
Windows x64),通过 `AGC_UPDATE_ARTIFACT` 指定要发布的安装包,通过 `AGC_UPDATE_OSS_BASE_URL` 指定
|
||||
OSS 前缀,通过 `AGC_RELEASE_VERSION` 指定三段版本号(仅在明确需要复现指定版本时使用),通过
|
||||
`AGC_UPDATE_RELEASE_NOTES` 写入发布说明;`--no-bundle` smoke 构建不会读取 OSS、修改版本或生成清单。
|
||||
|
||||
每次发布安装包上传完成后,再上传同一目录生成的 `latest.json`,确保 `downloadUrl` 指向已存在的 OSS 对象;清单和安装包均使用公开可读对象,不在清单中保存凭据、签名或本地路径。构建脚本本身不负责上传 OSS,发布流水线通过 `release:upload` 完成上传。
|
||||
|
||||
如需一键构建并上传,可执行 `npm run ai-game-creator-shell:release:upload`。该命令要求本机已安装并配置 `ossutil`,
|
||||
先按上述规则比较 OSS 版本、递增 patch、构建 Windows x64 NSIS,再上传安装包和 `latest.json`。默认上传到
|
||||
`agc-dev` / `oss-rg-china-mainland.aliyuncs.com`,也可用 `AGC_OSS_BUCKET`、`AGC_OSS_ENDPOINT` 和 `OSSUTIL_BIN`
|
||||
覆盖;本机执行时凭据由 ossutil 本机配置读取,不能写入仓库或命令行参数。
|
||||
|
||||
## Jenkins Windows 构建节点
|
||||
|
||||
AGC 发布流水线使用 `jenkins/Jenkinsfile.ai-game-creator-shell-build`,当前节点标签为
|
||||
`windows && win2022`。节点应为 Windows Server 2022 x64 虚拟机,预装 Node.js 22、npm
|
||||
10.9.7、Rust 1.96.0、Visual Studio Build Tools(MSVC 与 Windows SDK)、NSIS、Git 和 ossutil;
|
||||
Jenkins Agent 服务必须能在同一用户环境中找到这些命令。流水线执行根 workspace 的 `npm ci`,然后调用
|
||||
`npm run ai-game-creator-shell:release:upload`,并归档 Windows 安装包、`latest.json` 与源码 commit。
|
||||
流水线会将未导出的空参数按空字符串处理:`COMMIT_HASH` 留空时沿用 Jenkins SCM 当前提交,`OSSUTIL_BIN` 留空时使用节点 PATH 中的 `ossutil`,不会因 PowerShell 对空环境变量调用 `.Trim()` 而提前失败。
|
||||
|
||||
Jenkins Job 在“Build and upload”阶段通过受保护凭据 ID `AliyunAccessKeyId` 和
|
||||
`AliyunaccessKeySecret` 注入 AccessKey,仅在当前进程运行时传给 ossutil,不写入仓库、workspace 或构建日志;
|
||||
本机运行仍使用 ossutil 配置。凭据必须具备 `PutObject` 权限;OSS 对客户端保持公共读即可,公共读本身不授予
|
||||
Jenkins 上传权限。由于版本号取决于 OSS 当前清单,Job 已关闭并发构建;若 Jenkins
|
||||
上存在多个 AGC 发布 Job,还应使用同一个 Lockable Resource 串行化发布。Job 参数
|
||||
`AGC_RELEASE_VERSION` 留空时自动递增,填写后会使用指定版本并更新对应的 `latest.json`,因此回滚或测试旧版本前应确认不会覆盖线上更新入口。
|
||||
@@ -1087,7 +1087,6 @@ Runtime 只在以下客观条件同时满足时写 `contractStatus=evidence-read
|
||||
|
||||
### 单层 repair
|
||||
|
||||
|
||||
repair 深度固定为 `1`;同一原 delivery 同时最多存在一个非 `suppressed` repair。相同 durable action/身份重放必须幂等复用已预留或已创建的 repair;不同 action 的重复或并发竞争必须在 delivery 锁内发现既有非 suppressed repair 后拒绝,不能创建第二个活跃目标 run、第二份可认领回执或 `-dup-*` repair。repair 结果继续唤醒、认领并收束到原可信父 Session/run;它不能创建第二条面向用户的 assistant。repair 再次 `needs-repair` 时不得继续嵌套委派,当前可信父 Run 只能基于现有证据裁决或由根 Supervisor 走用户输入门禁。`suppressed` repair 不视为已完成返工,原 `repairRequired` 门禁必须继续阻断 finalization;同一 durable action 可以在无终态字段时把原 delivery 恢复为 `dispatched`,若该 action 已持久失败,新 action 也只可在既有 repair 全部 suppressed 时创建替代的基础设施投递,不能形成第二轮语义返工。已 suppressed 且未形成 child task 的旧 delivery 不再参与 capability、claim 或 completion barrier 的身份验证,避免恢复入口被失败前置记录永久堵死;所有非 suppressed delivery 仍必须逐条通过完整可信链校验。
|
||||
|
||||
当前可信父 Run 认领回执后必须能够再次从 durable delivery 取回权威返工合同,不能依赖首次 `agent.run_status` observation 或模型记忆。普通 `agent.run_status` 要返回有界的 `claimedDelegateContracts` 目录,至少包含 `delegationId / targetAgentId / repairOfDelegationId / contractStatus / acceptanceCriteriaCount / expectedArtifactsCount`;带可选 `delegationId` 查询时,只允许当前可信父 Run 读取属于自己且已 `claimed-by-parent` 的 delivery,并返回未截断的 `delegationId / targetAgentId / acceptanceCriteria / expectedArtifacts / repairOfDelegationId / deliveryStatus / terminalStatus / contractStatus`。该查询是只读、可幂等重放的私有 observation,不返回 task 正文、Provider payload、凭据或绝对路径;合同超过明确有界输出上限时失败关闭,不能截断后让模型猜测。返工被“合同未完整继承”拒绝时,失败 observation 必须携带同一 durable delivery 的完整 `claimedDelegateContract` 权威快照,当前可信父 Run 可直接逐字段据此修正;该字段缺失或身份不确定时才必须按原 `delegationId` 重读,不得重复无目标地轮询状态或从 action history 的摘要反推。
|
||||
@@ -1756,6 +1755,6 @@ V1.54 的公共编排层可以在运行前构造动态 DAG,但 LLM 在执行
|
||||
|
||||
## DirectProject 原生工具边界覆盖(2026-08-24)
|
||||
|
||||
本文中 V1.1/V1.52 关于 app-server 全局关闭 native shell、network、browser、plugin 和 multi-agent 的表述继续适用于 ToolHost/DirectHome 与 legacy Runtime;不再作为 DirectProject 的现行实现。DirectProject 恢复原生文件/搜索/命令、图片查看和 Skill,并通过隔离 `CODEX_HOME` 只注入审核后的 `agc_tools` MCP。平台美术、资源投影、浏览器试玩、受控搜索、付费副作用和 durable delegation 仍必须走 AGC 权威链路。
|
||||
本文中 V1.1/V1.52 关于 app-server 全局关闭 native shell、network、browser、plugin 和 multi-agent 的表述继续适用于 ToolHost/DirectHome 与 legacy Runtime;不再作为 DirectProject 的现行实现。DirectProject 恢复原生文件/搜索/命令、图片查看和 Skill,始终注入审核后的 `agc_tools` MCP,并可在启动时从客户端扩展仓库接入用户已启用的独立第三方 MCP 配置;第三方配置不进入全局 Codex home,不开启完整 Plugin Runtime。平台美术、资源投影、浏览器试玩、受控搜索、付费副作用和 durable delegation 仍必须走 AGC 权威链路。
|
||||
|
||||
DirectProject 的写入根固定为真实 `game/`,审批策略为 `never`,原生命令网络保持关闭,联网资料继续走受控 `agc_web_search`;shell 使用 Codex `shell_environment_policy` 的 glob 排除 API key、proxy、loopback bridge 和受控开关。配置了 AGC LLM Key 或可解析的 `OPENAI_API_KEY` 登录态时,真实 provider 凭据只由 AGC 本地 provider proxy 持有,Codex 仅获得连接级随机代理令牌;无法安全代理的 OAuth `auth.json` 继续关闭 native shell/unified exec。Codex 原生子 Agent、Apps、插件、hooks、图片生成、Goals、Workspace Dependencies、Tool Suggestion 以及未接入 AGC 证据链的浏览器/电脑控制保持关闭。系统提示词只传入最小身份、工作区、Skill 索引和副作用边界,不再批量注入源码快照或 Skill 正文。sandbox writableRoots 不提供 deny-read;`.agent` 与 `../assets` 的不可读约束仍需通过 prompt/Skill 行为合同和真实 smoke 验证,不能误称为 OS 强制隔离。
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
- 素材读取区分三类来源:`asset.list` / `agc_list_registered_assets` 是当前项目本地 manifest,`agc_list_project_files` / `file.list` 只发现项目目录中实际存在但可能未登记的文件,`asset.library.list` 是当前登录账号素材库,项目画布资源读取是当前网页项目/画布的完整图片清单;账户素材库不能替代项目画布清单。
|
||||
- Agent 只接收稳定素材 ID、类型、尺寸和项目相对路径等安全投影。客户端负责重新校验账号/项目归属、换签下载、媒体校验,以及 manifest/画布原子登记;不得向 Agent 暴露绝对路径、签名 URL、objectKey、token 或 Cookie。
|
||||
- `canvas.asset_import` 支持账户/画布资源 ID 和项目内本地相对路径。项目文件发现结果以 `assetImportable` 明确区分当前可登记的 PNG/JPEG/WEBP 与仅可发现的 GIF/SVG/其它媒体,Agent 只能提交前者。导入拒绝路径穿越、`.agent`、符号链接/reparse point 及敏感配置文件;外部宿主文件须由 UI 原生文件选择器授权后导入,不开放任意绝对路径。
|
||||
- `canvas.asset_import` 支持账户/画布资源 ID 和项目内本地相对路径。项目文件发现结果以 `assetImportable` 明确区分当前可登记的已识别图片、字体、音频、视频、文档和代码文件与其它文件;Agent 只能提交前者。导入拒绝路径穿越、`.agent`、符号链接/reparse point 及敏感配置文件;外部宿主文件须由 UI 原生文件选择器授权后导入,不开放任意绝对路径。
|
||||
- Runtime `asset.list` 与 `file.list` 的详情使用文件上下文上限,而不是普通工具短摘要上限,确保有界候选/目录清单不会因前部内容较长而整体丢失;`asset.list` 超出 48 项或 `file.list` 超出 40 项时仍显式返回剩余数量,Agent 再按候选父目录(例如 `assets`、`game/assets`)缩小范围查询。
|
||||
- 结果仅返回成功/跳过/失败数量、安全 ID、相对路径、来源、脱敏失败摘要和实际 `revisionAdvanceCount`;幂等跳过不得虚增 revision,部分失败仍须准确记录已发生的 revision 变化。
|
||||
- 普通 Prompt 上下文与错误诊断必须使用分离的脱敏边界:Prompt 继续对疑似凭据行整体隐藏;错误诊断保留 HTTP 状态以及 `code / field / message / reason / detail` 等安全字段,仅替换 Token、Cookie、私钥、配置名、URL 和宿主路径等敏感值。`agc_create_or_derive_resource.assetName` 是必填的人类可读资源显示名称,不接受项目路径、URL、objectKey、Token 或其它凭据。
|
||||
@@ -169,6 +169,7 @@ Supervisor 认领该回执后,由父 run 自己为每个原 delivery 逐一创
|
||||
|
||||
- Windows AppData 安全迁移:首次创建客户端 AppData 时必须以进程 `TokenUser` SID 显式设置 owner,并写入当前用户私有 DACL,不能把可能为 Administrators 的 `TokenOwner` 当作用户身份。发现历史目录 owner 不属于当前 `TokenUser` 时,不在原目录上放宽权限,而是拒绝 reparse point / junction / symlink 后,将旧目录原子重命名到同级唯一 `.owner-mismatch-backup-*` 备份,再新建并验证当前用户 owner 与私有 DACL;迁移或备份失败必须失败关闭,不覆盖旧配置。
|
||||
- Windows 私有文件初始化:父目录已归当前 `TokenUser` 后,新建 `.agent/.manifest.json.lock`、`agent-runner.lock`、endpoint 临时文件、project-owner 诊断临时文件与 real-E2E 私有文件的 owner 仍可能采用 token 默认 owner `Administrators`。manifest 固定锁和 Runner 固定 stale lock 只有在 Windows 不共享独占句柄已取得、且句柄确认普通文件、非 reparse point、链接数为一时才允许初始化或修复为当前 `TokenUser`,随后必须再次复核句柄并按既有 owner/DACL 门禁验证;其它临时文件只允许在本进程 `create_new` 成功且仍持有同一独占句柄时初始化 `TokenUser` owner / DACL,再写入、原子安装并严格复核,初始化失败必须清理刚创建的文件。既有 durable endpoint / diagnostic 读取不得自动接管;活锁不得截断,只有 sharing / lock violation `32/33` 表示占用,access denied 等其它错误立即返回。父进程观察到 Runner 子进程退出后立即返回错误,不等待完整 30 秒 deadline。
|
||||
- Windows ACL 提权边界:自定义 `--config-dir` 的启动前置检查必须把 `managed / user-selected` scope 一并传入提权子进程,不能依赖父进程内存中的配置目录覆盖;native picker 返回的文件或项目目录在同一进程登记短时授权,后续导入 / 项目操作只对登记路径(目录可覆盖其后代)允许 `user-selected` 自动提权,直接伪造 IPC 绝对路径不得获得该能力。项目文件列表 / 索引递归逐项拒绝 symlink 与 Windows reparse point,并在 metadata / read 前先完成 ACL 准备。
|
||||
- 启动恢复和续跑边界:本条取代上一条中“只有 accepted 才可恢复”的窄口径。若进程在 Supervisor 用户消息已持久、accepted 未持久之间崩溃,只读 preflight 可以把该 `preparing` 识别为可恢复,但不改写 task/conversation;真实 resume 持有 Agent 锁后必须先幂等补写 accepted,再提升为 `pending / queued`。用户消息或 accepted conversation 已落盘而辅助审计失败时,以 conversation 为公开真相继续入队,不留下“已接收但永不执行”的任务;根终态首次公开写入的瞬时失败必须在终态投影后用相同 message ID 重试。receipt / isolated-join 等带 parent 的 Supervisor continuation 不再另写 Session 终态,只保留单一后端公开事件;`runtime-task-*` 与 `runtime-public-status-*` 共享同 run 的不透明关联摘要,秒级时间戳下多个连续任务必须按实际 run 对应的 `user -> accepted -> terminal` 顺序交错展示。
|
||||
- ready-task 启动活性:`background_task.queued`、`autonomous_ready_task.scheduled`、Runner heartbeat 或执行锁已移交都不等于 child 已启动。实际持有执行权的 Runner 必须在释放项目写锁后同步写入 child 的 running task、`turn.started` 与 started journal,再把已启动 state 和 per-Agent 执行锁交给已确认开始轮询的独立 execution worker;同步启动或 worker 接管失败时,要在仍持有执行锁期间依次把 child 和 manifest Graph 节点明确落为 failed,再释放锁并让 parent 收到调度错误。`autonomous_ready_task.scheduled` 只作诊断审计,其写入失败不能阻断 durable child 启动;external client 只 wake Runner,不在客户端抢占执行。Supervisor 进度卡通过 durable `startedAt`(旧 Run 从完整 task journal 恢复,最新 task-record fallback 保持 0)显示真实持续时间,并以父 Run 与当前关联专业 Agent 的最大事件时间计算运行态活跃度:运行超过 5 分钟无新事件时显示“运行中 · 疑似停滞”和静默时长;等待用户、等待确认、Provider retry、视觉资产、进程会话、pausing 与 paused 不误报。父 Run terminal 后,持续时间冻结在父 Run 自身最后活动,不随 child 晚到收口事件增长。消息时间统一校验为 JavaScript 可表示的 Date;越界值显示“时间未知”且不写无效 `datetime`。实时回复只显示 response stream 自己的 `updatedAt`,缺失时同样显示“时间未知”,不能借用其它 Runtime 活动时间或随前端时钟漂移。该提示只提供可观测性,不改变 Runtime/manifest 正式状态。
|
||||
- ready-task 对账取消续跑:未知工具结果仍停在 `needs-reconciliation` 且禁止自动重放;人工核对后显式取消原 child,保留 cancel tombstone,旧 child 和旧父 Run 按真实终态收口。若随后创建同 Session、同 Supervisor source、同有效任务语义的 continuation,新完成合同只对同时具有历史 `failed / needs-reconciliation`、最终 `cancelled` 和 durable tombstone 的 ready-task,把当前 manifest 对应 failed 节点恢复为 pending,并由 scheduler 创建全新 child Run。manifest 的读取、failed 筛选、每任务一次的 child journal 索引、证据重验和写回必须位于同一项目写锁域;较新的无 child 根 Run 只有在 durable journal 精确表明为旧 failed Graph 在进入调度前即失败时才能跨过,scheduler 自身失败必须阻断借用更老 tombstone。普通失败、无 tombstone、不同 source/Session/任务语义或证据冲突均保持失败关闭;不得复活旧 pending action、补造 observation 或把取消任务标成 completed。
|
||||
@@ -183,16 +184,13 @@ Supervisor 认领该回执后,由父 run 自己为每个原 delivery 逐一创
|
||||
- 增量预览刷新: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 并保持纯聊天行为,不继承本例外的自主构建、事件聚合或自动预览授权。
|
||||
|
||||
|
||||
- 对话输出中的 `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 文件锁竞态只可作为既有测试失败单独记录,不得将其改写为本次改动的通过证据。
|
||||
|
||||
|
||||
- Provider 次数:Supervisor 先用一个 Provider turn 理解目标并持久化条件路由;随后唯一 `code-prototype` 主 Agent 先 `asset.list`,只有审计的精确缺口才建立相应的受限美术委派。完整复用不得调用图片生成接口;图集复用或主 Agent 认领生成回执后,仍由该主 Agent 接入、执行 `game.static_smoke` 和 desktop/mobile `preview.validate`。软预算耗尽时只允许使用已登记图集和当前 resourceId 切片清单的确定性本地兜底,不得由 Runtime 或关键词强制生成图片。
|
||||
- 可玩兜底:软预算或首版 Provider 无法及时完成时,只能为已显式实现真实语义的玩法生成完整、自包含、无远程运行依赖的中文 HTML 模板;未知玩法失败关闭,不能只替换标题后套用固定收集游戏。俄罗斯方块模板必须包含 10×20 棋盘、下落、移动、旋转、锁定、消行和触顶失败;收集模板只匹配明确收集类目标。模板必须从 `ready` 开始,包含真实 Canvas 绘制、`requestAnimationFrame`、键盘 / 触控主要操作、唯一可见且启用的 start / primary-action / restart 控件,状态 JSON 只随真实输入、状态迁移或模拟状态变化推进,并能在 primary-action 后保持 `playing`、在 restart 后稳定回到 `ready | playing`;不得在开始前固定进入 `lost`,不得通过固定失败冒充试玩通过,也不得由纯渲染帧空转 `sequence`。兜底只允许写入缺失或精确初始化占位的 `game/index.html`;存在非占位入口时,当前 `code-prototype` 必须读取并实际 patch,取得本人 `mutationRevision` 后再静态检查和试玩,不得反复用只读 smoke 冒充续作。
|
||||
- 关联验收:快车道必须分别验证 Supervisor 决策前零 child、持久路由后只启动 `code-prototype`、主 Agent 成功 `asset.list` 后才可判断缺口、完整覆盖零图片生成/零委派、精确缺口只委派对应 owner、整体重做仍先审计且不产生无关委派、美术 child 对 `game/**` 写入拒绝而 `assets/**` 允许、回执恢复同一主 Run、主 Agent 自行完成接入/静态 smoke/desktop-mobile 试玩、4200 / 4500 秒累计预算、规范图到 icon-spritesheet 的真实引用、`iconImageSrcs` 本地持久化与资源 ID 绑定、失败续跑目标继承、非占位入口禁止整文件覆盖、纯代码核心画面、猜测单个 atlas 裁切与整图展示失败、四类独立切片可见使用通过与 action-driven `sequence`。完整 GUI / CLI 的固定 16 节点 DAG 另行保持原有回归。
|
||||
|
||||
|
||||
- 开发态启动必须在 Tauri CLI 之前解析并预检 AGC Vite 最终地址。Linux 使用系统级用户端口段的 `start + 5` 槽位并只在本段内漂移,Windows / macOS 以 `3080` 为兼容首选;启动器通过 `GENARRATIVE_AGC_VITE_PORT` 绑定 `beforeDevCommand` 和配套后端预留,通过 Tauri CLI `--config` 绑定 `build.devUrl`,并通过 Vite CLI `--port` 绑定 `strictPort` 监听。任何竞态中已存在的 AGC Vite、非 HTTP 监听器或其它服务都必须在原生窗口创建前失败关闭。启动器不擅自终止无法证明归属的旧服务,也不得把当前 Rust 壳 / Runner 与其它 worktree 的旧 Vite 前端混用。Tauri CLI 任意退出后,外层启动器必须有界收束已启动的客户端进程树,避免 `beforeDevCommand` 失败后留下假在线窗口。
|
||||
- source-aware lane 的主 Run 或其经授权美术 child 可能在 UI hydration 写回时短暂恢复为 `Pending`。该例外必须从当前 root source、持久工作流决策、单主 route 与 child delegation 解析本轮已开放工作,不得从旧七节点图硬编码重启 Director、验证或试玩节点;未授权 child、第二个活跃美术 child,或缺少成功 `asset.list` 审计的美术委派仍严格失败关闭。
|
||||
|
||||
@@ -555,7 +553,7 @@ game-project/
|
||||
- 阶段三的聚类已落在 `reconcileResourceCanvasLayout` 的 dependency 自动坐标派生步骤。它先按资源分类过滤 reference edge,并把 task-flow 按固定分类切成仅在同类 source / target 同时存在时有效的聚合超边;每个 section 再用精确边与 flow 临时节点建无向邻接表,以迭代遍历生成弱连通组。task-flow 只以“流节点 -> 成员”的线性成员关联参与布局,绝不展开 source × target 资源组合,不绘制 SVG,也不把搜索后的 visible set 用作输入。`dependencyDepth` 仍唯一决定横向业务层级;相关簇按 `minDependencyDepth + minStableResourceId` 排序,孤立集合置于所有相关簇之后,簇间使用单一布局常量留白。每个相关簇内先按稳定资源 ID 建同层初始序,再做固定两轮左至右 / 右至左的中位数扫描:精确引用读取相邻层的上下游 rank,task-flow 读取另一端成员 rank 的中位数,平局按稳定资源 ID 收口。dependency 自动位置使用 `48px` 列间走线区和 `40px` 行间走线区;每个相关簇以最大层行数确定高度,资源较少的层增加确定性半差偏移而在簇内居中,菱形 / 分叉两侧因此保持均衡。type 模式仍使用原 `16px` 行列间距。跨分类 read model 关系不进入前端聚类、边界偏置或拓扑签名,但 Rust 深度与原始图真相不改。显示坐标必须遵守前后端共享的 `0..=1_000_000` 上限;超深依赖在最后合法列确定性饱和,保留原始 `dependencyDepth`,同列资源继续按稳定顺序纵向避让。若任一自动 `x / y` 无法在合法域内落槽,协调必须在 IPC 前失败关闭,不持续提交必然被 Rust 拒绝的坐标。算法保持 `O(V + E)` 图遍历,加固定轮数的层内稳定排序和现有有界占用索引;4096 资源不允许全量配对。旧的 `manuallyPlaced=true` 坐标先占位并原样保留,聚类只派生自动坐标;同类型拓扑身份签名只记录有界的资源 ID 端点 / 成员,以便深度未变但邻接变化时触发重派生。图边、cluster ID 和签名都不写 sidecar。
|
||||
- 中间主视窗提供 `resource-overview / asset-canvas / resource-editor / run` 四种状态。2026-08-10 起普通用户“新增资源”显示为禁用态且处理函数拒绝 create;所有现役资源从聚焦态“编辑资源”进入非破坏性派生。静态图片继续进入 refine 素材创作无限画布,SVG、视频、音频、文档/代码、Agent 回执和项目版本进入统一资源编辑壳并按能力分流;底层 create 合同仅保留兼容。编辑面板只替换中央区域,不覆盖右侧 Supervisor 或底部 Agent。`code-prototype` 任务完成前运行入口保持视觉不可用,但仍可点击查看“当前无可运行版本”,不能使用会阻断说明交互的原生 `disabled` 或 `aria-disabled`;完成后才允许进入运行表现层。切回资源总览只修改前端展示态,不伪造后端预览暂停结果。
|
||||
- 资源管理从当前 `GameCreationAppManifest`(包含可选 `versions`)、合法 Agent 文本回执、已导入附件和已完成任务明确登记的产物派生资源,固定按文档、项目版本、美术资源、音乐音效资源分区;未知任务产物不再兜底为版本,未完成任务或未在 `artifacts` 中登记的任意本地音频也不冒充正式资源。`按依赖 / 按类型` 使用各自前端排列,dependency 模式额外绘制当前 manifest 与资源投影可证明的依赖关系。排列与图层都不写回 manifest,不能推断或伪造缺失依赖。
|
||||
- 资源卡支持点击聚焦、搜索和类型筛选。2026-07-28 起完成两套二维坐标与本地 CAS sidecar;2026-07-31 起 dependency 模式增加不持久化的原生 SVG 关系图层。2026-08-03 mentor 决定暂缓资源总览卡片拖动,当前卡片不挂载 Pointer Down / Move / Up / Cancel 拖动入口,只允许自动布局和点击聚焦。聚焦态替换中央主视窗内容,保留左侧导航、右侧对话和底部 Agent 状态栏,退出后恢复搜索、布局模式、滚动位置与选中资源;不提供通用工具栏、工具侧边栏或可拖动标题栏。阶段四已补齐安全本地文档、扩展美术媒体与音频聚焦,正文独立滚动,视频 / 音频使用内置媒体控件,失败显示空态。2026-08-26 视觉验收修正:资源总览初次适配与复位最多以 `1.5` 倍缩放卡片,避免把低尺寸卡片位图插值放大成糊图;用户主动缩放仍沿用通用画布倍率。美术资源聚焦态改为视口级大预览,保留原始资源读取与元数据,不生成第二份缩略图,图片 / 视频预览按弹窗可用高度展示并允许正文滚动。该资源总览边界不限制后续素材创作无限画布内的图片图层移动/缩放、生成和正式回写。
|
||||
- 资源卡支持点击聚焦、搜索和类型筛选。2026-07-28 起完成两套二维坐标与本地 CAS sidecar;2026-07-31 起 dependency 模式增加不持久化的原生 SVG 关系图层。2026-08-03 mentor 决定暂缓资源总览卡片拖动,当前卡片不挂载 Pointer Down / Move / Up / Cancel 拖动入口,只允许自动布局和点击聚焦。聚焦态替换中央主视窗内容,保留左侧导航、右侧对话和底部 Agent 状态栏,退出后恢复搜索、布局模式、滚动位置与选中资源;不提供通用工具栏、工具侧边栏或可拖动标题栏。阶段四已补齐安全本地文档、扩展美术媒体与音频聚焦,正文独立滚动,视频 / 音频使用内置媒体控件,失败显示空态。2026-08-30 视觉验收修正:资源总览所有栏目初次适配与复位最多以 `1.5` 倍缩放卡片,避免单个低尺寸卡片被插值放大成糊图;用户主动缩放仍沿用通用画布倍率,并按“排序模式 + 栏目”保留当前会话内的平移和缩放。美术资源聚焦态改为视口级大预览,保留原始资源读取与元数据,不生成第二份缩略图,图片 / 视频预览按弹窗可用高度展示并允许正文滚动。该资源总览边界不限制后续素材创作无限画布内的图片图层移动/缩放、生成和正式回写。
|
||||
- 运行表现层首版直接嵌入当前项目的 loopback 游戏画面,并保留素材信息和数值微调面板;两个面板保持原有 `156px` 最小高度,没有真实数据时只让正文为空,不渲染预设字段、默认数值、未载入控件或自然语言功能占位,也不随空内容收缩。`preview.start` 启动本地 server 后把真实 URL 回写工作台,`preview.open` 只激活客户端内运行视图,不再调用系统浏览器;参数调整首版仍只保留本地 UI 草稿,不修改代码或 manifest。preview server 对 UTF-8 HTML 响应注入固定同源尺寸桥脚本;注入点通过真实 HTML tokenizer 边界定位,保守处理注释异常结束、DOCTYPE 引号、script escaped / double-escaped、raw-text、template、plaintext、foreign content 与重复 `src`,并支持省略 `</body>` / `</html>`。桥以 `ResizeObserver` 观察 `documentElement / body` 根布局,结合页面 load、窗口 resize 与字体就绪重新测量;页面可见时另以 `500ms` 低频兜底探测至多 `512` 个元素的实际边界,探测截断时保留 body / scroll 上界,并按连续测量排除随 viewport 同步变化的 `100vh / 100% / bottom / right` 自反馈。相同尺寸元组去重后才以固定版本 `postMessage` 上报,不订阅整页 `MutationObserver`。宿主同时校验消息 origin 和 `event.source`,以实际内容宽高与当前容器宽高计算不超过 `1` 的等比缩放;首次适配后仍接受内容宽高的真实变化,但仅 viewport 回灌或重复内容尺寸不更新 React 状态。容器 resize 后回到原生视口重新测量;放得下时保持 `1:1`,超出时完整缩小并居中,iframe 禁止横纵滚动条,不能以 `overflow: hidden` 直接裁掉超出内容。非 UTF-8 HTML 原样返回,不因适配桥破坏已有预览。
|
||||
- 右侧继续复用现有 Project Supervisor 会话、Runtime 澄清和确认链路;输入区展示 `严格审批 / 风险审批 / 无需审批` 独立面板。P0 只有严格审批可选;风险审批和无需审批保持视觉不可用但允许点击查看原因,不替代 Runtime 的逐动作权限、确认、sandbox 或 reconciliation 门禁。风险 Rank 算法记录在 `docs/project-memory/todos/【待解决】AI游戏创作高风险审批Rank-2026-07-20.md`,前端不得自行计算。
|
||||
- 底部状态栏默认展示策划、美术、程序 3 组,并允许在同一栏展开数值、音频、发布组;状态来自 manifest 与当前 Supervisor run 的 Runtime,悬停显示当前任务与进度。累计泥点必须等待后端计费归因投影;Agent.md 编辑和自定义 Skill 在来源审核、版本、权限、sandbox 与回滚合同完备前不向普通用户开放。
|
||||
@@ -1086,7 +1084,6 @@ game-project/
|
||||
- 浏览器未发现、临时环境不可建、启动超时或在 WebSocket URL 解析前退出统一分类为 `preview-infrastructure-unavailable`。首个持久 observation 后收束当前 action batch并失败结束 child/root run,禁止继续用 Provider 逐轮规划同一 revision 的重复启动;普通页面/玩法验收失败仍保留为业务失败,不混入基础设施分类。
|
||||
- 规范 Agent 默认推理档覆盖全部 21 个角色:核心规划、生成、设计/美术/代码原型和质量角色使用 `high`,协调与结构化交付使用 `medium`,确定性预览 gate、音频总监和发布策略使用 `low`;显式 `agentLlm.<id>.reasoningEffort` 始终最高优先。规范默认由 Runtime resolver 解析,模板与 GUI 初始草稿保持 `agentLlm` 为空,避免默认值被误判成角色独立 LLM 路由;GUI 必须显示每个角色的实际默认档。全局与逐 Agent status/CLI 必须同时显示实际解析后的 reasoning、request timeout、max retries 和 retry backoff,区分运行快照与后来配置。
|
||||
|
||||
|
||||
- code-prototype 的确定性交付需要同时满足当前 Run 存在 `status=ok` 的真实 mutation action、本人当前 mutation revision 的 `game.static_smoke=passed` 与完整 autonomous completion gate 无阻塞;失败 patch 即使因保守失效旧凭证而推进 revision,也不能取得 mutation ownership。mutation ownership 使用最后一条同工具调用,并严格匹配当前 Agent/task/session/run/actionId/actionFingerprint/tool 的 durable receipt;pending action 的 `plannedSteerCursor` 必须进入 recent tool-call 与 receipt 的同一 fingerprint,不能因非零 steer cursor 把真实成功误判为外来动作。static smoke 通过但仍有素材或正式产物缺口时,Runtime 对尚未完成的计划用单一 in-progress repair 替换首个非终态步骤并保留后续 pending;只有计划全 completed 且未满 8 步时才追加 repair。下一轮交还 Provider 生成实际 `file.patch`,完整门未通过时禁止自动完成计划,也不得因 retained completed 步骤与 steer 新计划合并超限而进入空转;结构化计划只要已经包含不可改写的 failed 步骤,就在快车道入口明确失败关闭,不再依赖 mutation ownership 或完成门诊断是否仍存在。
|
||||
- durable active child 的权威性高于 stale Completed manifest 快照,但 Failed manifest 仍立即失败关闭。所有 ready child 的文件、patchset、命令产物和其它项目 mutation 在写锁内再次核对当前根 Run;新根 Run 建立后旧 child 只能安全收束/审计,不能再修改项目或推进 revision。
|
||||
- 大 classic 游戏脚本的函数可达性查询必须对固定 invocation graph 使用整轮 visited,每个 function node 最多访问一次;递归栈只负责去环、返回时删除节点会在 render/update 扇入图中指数回溯并阻塞 Runner 事件循环。Canvas alias 全空历史、稳定祖先初始化、整画布尺寸引用和可证明的 `COLS × ROWS × CELL` 格子目标使用有界静态路径,普通未知动态坐标继续失败关闭。
|
||||
@@ -1187,7 +1184,7 @@ game-project/
|
||||
- 普通项目对话只由一个 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,工具固定为审核引用读取、标准陶泥儿美术准备、已登记资源有界查询、视频 / 角色动画 / 音效 / BGM 的 create-or-derive 语义生成、已登记图片去背景和 desktop/mobile 浏览器试玩。MCP 进程只做协议;真实浏览器与付费 External v1 调用通过随机 loopback 地址回到客户端主进程,因此不复制 GUI 登录态、开发者 Key、项目路径、revision、operation 或幂等键到模型上下文。已登记工具固定自动批准,但付费资源工具仍由客户端绑定稳定回合身份、限制单回合请求数、串行执行并优先恢复匹配账本;通用 shell、Codex 原生 webSearch、任意网络、多 Agent、插件和外部 MCP 继续关闭。`codex_app_server` 模式要求 `llm.webSearchEnabled=false`。
|
||||
- DirectProject 始终连接客户端内置的 `agc_tools` STDIO MCP;2026-08-31 起还会在启动时接入客户端扩展仓库中用户已启用的独立第三方 STDIO/HTTP MCP 配置,但不读取用户全局 Codex MCP、不开启完整 Plugin Runtime。`agc_tools` 工具固定为审核引用读取、标准陶泥儿美术准备、已登记资源有界查询、视频 / 角色动画 / 音效 / BGM 的 create-or-derive 语义生成、已登记图片去背景和 desktop/mobile 浏览器试玩。内置 MCP 进程只做协议;真实浏览器与付费 External v1 调用通过随机 loopback 地址回到客户端主进程,因此不复制 GUI 登录态、开发者 Key、项目路径、revision、operation 或幂等键到模型上下文。内置与用户启用的第三方 MCP 工具都沿用 DirectProject 自动批准方式,但付费资源工具仍由客户端绑定稳定回合身份、限制单回合请求数、串行执行并优先恢复匹配账本;Codex 原生 webSearch、任意原生命令网络、多 Agent 和完整插件能力继续关闭。
|
||||
- 陶泥儿生成继续复用持久幂等账本、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 行为。
|
||||
|
||||
@@ -1251,10 +1248,10 @@ game-project/
|
||||
|
||||
本文早期关于“DirectProject 关闭通用 shell、原生网络和主动工具”的描述属于迁移前基线,现由以下覆盖规则取代:DirectProject 仅在真实 `game/` cwd 与 `workspaceWrite(writableRoots=[game])` 内恢复 Codex 原生文件/搜索/命令、图片查看和 Skill;其余 ToolHost/DirectHome 合同不变。客户端审核的 `agc_tools` MCP 继续承担平台美术、资源登记、去背景、浏览器试玩和受控搜索,并保留项目锁、幂等账本、下载校验、恢复与投影权威。
|
||||
|
||||
DirectProject 使用 `approvalPolicy=never`,避免每次原生调用再经过泛化 ToolHost 包装;原生命令网络保持关闭,联网资料继续走受控 `agc_web_search`。多 Agent、Apps、插件、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`,明确清空外部 MCP 后只注入 `agc_tools`;配置了 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 文件。
|
||||
DirectProject 使用 `approvalPolicy=never`,避免每次原生调用再经过泛化 ToolHost 包装;原生命令网络保持关闭,联网资料继续走受控 `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
|
||||
|
||||
- 2026-08-24 起,`ui-prototype` 与 UI 编辑器的 `UI` JSON 资源明确分离。设计图生成后必须由白名单 `ui.workflow.run` 按页面执行 `prepare → recognize → status → finalize`:为每个功能页面创建并关联 `UI` JSON,载入页面设计图和已登记图片/图标/字体,调用 UI Editor 的 provider-backed 结构识别、多树合并与分批组件绑定,持久化 State/revision,写入 `game/` 应用标记,并把 `reference-ready → structure-ready → merge-ready → binding-ready → application-ready → completed` 各阶段的 `generationKind` 和 manifest revision 投影给客户端。Provider 未配置、请求失败、工具调用缺失、结果不匹配、未知字体引用、未产出可渲染组件或仍有待审节点时保留最近真实阶段并返回 blocker,不得使用 deterministic seed 冒充完成。工作台点击 `ui-prototype` 时通过 `ensure_ui_design_resource_for_prototype` 幂等补齐关联资源;工作流完成后自动打开首个页面的 UI 编辑器 `visual-binding` 最终阶段,交给用户检查和手动调整。只生成图片、登记空 JSON 或进入普通图片画布均不构成 UI 工作流完成,详见 [`【技术方案】UI工作流资源桥接与Runtime执行-2026-08-24.md`](../【技术方案】UI工作流资源桥接与Runtime执行-2026-08-24.md)。
|
||||
- 2026-08-24 起,`ui-prototype` 与 UI 编辑器的 `UI` JSON 资源明确分离。设计图生成后必须由白名单 `ui.workflow.run` 按页面执行 `prepare → recognize → status → finalize`:为每个功能页面创建并关联 `UI` JSON,载入页面设计图和已登记图片/图标/字体,调用 UI Editor 的 provider-backed 结构识别、多树合并与分批组件绑定,持久化 State/revision,写入 `game/` 应用标记,并把 `reference-ready → structure-ready → merge-ready → binding-ready → application-ready → completed` 各阶段的 `generationKind` 和 manifest revision 投影给客户端。Provider 未配置、请求失败、工具调用缺失、结果不匹配、未知字体引用、未产出可渲染组件或仍有待审节点时保留最近真实阶段并返回 blocker,不得使用 deterministic seed 冒充完成。工作台点击 `ui-prototype` 时通过 `ensure_ui_design_resource_for_prototype` 幂等补齐关联资源;工作流完成后自动打开首个页面的 UI 编辑器 `visual-binding` 最终阶段,交给用户检查和手动调整。UI 编辑器独立的语义建议请求也必须复用统一 LLM 传输选择,`llm.stream=true` 时发送 `stream=true` 并聚合完整工具调用后再校验结果。只生成图片、登记空 JSON 或进入普通图片画布均不构成 UI 工作流完成,详见 [`【技术方案】UI工作流资源桥接与Runtime执行-2026-08-24.md`](../【技术方案】UI工作流资源桥接与Runtime执行-2026-08-24.md)。
|
||||
|
||||
## 2026-08-28 AGC 自主构建 relaxed 编排覆盖
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,262 @@
|
||||
# DirectProject 本轮附件路径映射
|
||||
|
||||
- 日期:2026-08-31
|
||||
- 状态:现行合同(已按本文落地)
|
||||
- 问题:Gitea issue #212(DirectProject 未消费用户上传权威文档)
|
||||
- 关联入口:PR #210「批准 GDD 回填做游戏入口」(`feat/create_entrance`,未合入时仍按该 PR 的调用链理解)
|
||||
- 原则:落地后代码简洁可维护,不为了 diff 最小而打补丁;附件一律同等对待,不给 GDD 开协议特例
|
||||
|
||||
## 0. 一句话
|
||||
|
||||
首页带进项目的附件已经复制并登记,但 Direct 首轮只拿到用户原文。本方案让 Direct 回合在发给 Codex 的 user prompt 末尾附上**有界路径映射**(原文件名 → 项目相对路径),不灌正文、不强制读取、不改做方案注入。
|
||||
|
||||
## 1. 目标与非目标
|
||||
|
||||
### 目标
|
||||
|
||||
1. Direct 首轮知道本轮用户附件的原文件名、项目相对路径、媒体类型、大小和导入状态。
|
||||
2. 用户原文不被改写;路径映射是独立 sidecar。
|
||||
3. 图片、Markdown、其它文件走同一条协议。
|
||||
4. 做成游戏(PR #210:读 `game/fast_gdd.md` → 当成 `fast_gdd.md` 附件 → `createHomeDraftAutomatically`)自动吃到效果,因为那条链就是「带附件的 Direct 首轮」。
|
||||
|
||||
### 非目标
|
||||
|
||||
- 不改 PR #210 的按钮、固定 prompt、`startGameFromApprovedGdd`、读 `game/fast_gdd.md` 的方式。
|
||||
- 不改 `approvedGddRef`、审批 receipt、策划项目里的 `game/fast_gdd.md` 投影。
|
||||
- 不改上传命名 `assets/uploads/upload-<ts>-<name>`。
|
||||
- 不把附件全文拼进 prompt,不按扩展名决定是否读取。
|
||||
- 不把「没读到就阻断」做成门禁。
|
||||
- 不扫 manifest 里历史 `kind=uploaded`。
|
||||
- 不做 native 读取审计;该项由 [`【技术方案】Direct回合行为审计账本-2026-08-31.md`](./【技术方案】Direct回合行为审计账本-2026-08-31.md) 承接。
|
||||
- 不改 DirectHome 在「无项目路径」时的现有文案和列表格式。
|
||||
- 不改 `enterCreatedHomeProject` 的空正文兜底句(与做方案共用)。
|
||||
|
||||
## 2. 现行断点
|
||||
|
||||
```text
|
||||
Home 附件 / 做成游戏 File(fast_gdd.md)
|
||||
→ upload_local_asset
|
||||
→ LauncherProjectContext.attachments // 已有,只给资源画布
|
||||
→ ProjectSupervisor // 无 attachments 字段
|
||||
→ chat_with_game_creator_direct_codex
|
||||
{ projectPath, prompt, clientTurnId, creationType? }
|
||||
```
|
||||
|
||||
做成游戏的固定 prompt 仍写「附件中的 `fast_gdd.md`」,磁盘文件却是 `assets/uploads/upload-<ts>-fast_gdd.md`。映射没有进 Direct。
|
||||
|
||||
DirectHome 已有 `{ name, mediaType, size }` 元数据注入,但标明「尚未打开项目,内容尚不可读取」。项目已落盘后这条元数据被丢掉。
|
||||
|
||||
## 3. 目标合同
|
||||
|
||||
### 3.1 唯一 DTO
|
||||
|
||||
Home 与 Project 共用一个附件结构,缺省字段表示 Home 现状:
|
||||
|
||||
```ts
|
||||
{
|
||||
name: string; // 原文件名
|
||||
mediaType: string;
|
||||
size?: number; // 缺省按 0
|
||||
localPath?: string; // 仅已落入项目时出现
|
||||
status?: 'imported' | 'failed';
|
||||
}
|
||||
```
|
||||
|
||||
- 不把 `error` 字符串送给模型。
|
||||
- 前端 `LauncherImportedAttachment` 继续给画布;invoke 前映射成上述瘦 DTO。
|
||||
- `importHomeAttachments` 把 `File.size` 写入可选 `size`,不改 `upload_local_asset` 返回值。
|
||||
|
||||
Rust:
|
||||
|
||||
```rust
|
||||
struct DirectCodexTurnAttachment {
|
||||
name: String,
|
||||
media_type: String,
|
||||
#[serde(default)]
|
||||
size: u64,
|
||||
#[serde(default)]
|
||||
local_path: Option<String>,
|
||||
#[serde(default)]
|
||||
status: Option<String>, // 只接受 imported | failed,其它忽略
|
||||
}
|
||||
```
|
||||
|
||||
`DirectCodexHomeAttachment` 删除,Home command 改用同一类型。现有 Home JSON(无 `localPath` / `status`)继续能反序列化。
|
||||
|
||||
### 3.2 渲染
|
||||
|
||||
一个函数 `render_direct_codex_user_prompt(prompt, attachments) -> Result<String, String>`:
|
||||
|
||||
| 输入 | 输出 |
|
||||
| ---------------------------------------- | --------------------------------------------------- |
|
||||
| 无附件 | `prompt.trim()`;若也空则 `Err("聊天内容不能为空")` |
|
||||
| 附件都没有 `localPath` 且都没有 `status` | 保持现有 Home 文案与行格式,测试须逐字兼容 |
|
||||
| 任一条有 `localPath` 或 `status` | Project 头 + Project 行格式 |
|
||||
|
||||
Home 行(禁止改字):
|
||||
|
||||
```text
|
||||
[首页附件说明:当前尚未打开项目,以下仅为附件元数据,附件内容尚不可读取]
|
||||
- {name};类型:{mediaType};大小:{n} 字节
|
||||
```
|
||||
|
||||
Project 头与行(禁止出现 GDD / 规格 / 权威 / 必须读取):
|
||||
|
||||
```text
|
||||
<用户原文>
|
||||
|
||||
[本轮用户附件:已复制到当前项目。请用「项目路径」读取;原文件名不是磁盘路径。]
|
||||
- 原文件名:fast_gdd.md;项目路径:assets/uploads/upload-1788083777445-fast_gdd.md;类型:text/markdown;大小:7944 字节;状态:imported
|
||||
```
|
||||
|
||||
规则:
|
||||
|
||||
- 条数上限仍为 8,超出写 `- 另有 N 个附件未展开`。
|
||||
- 名字清洗沿用 Home:basename、去掉控制字符、最多 160 字、空则「未命名附件」。
|
||||
- 媒体类型清洗沿用 Home。
|
||||
- `localPath` 只接受项目相对 POSIX 路径:无 `..`、无盘符/根路径、首段不是 `.agent` / `.git`,`\` 归一为 `/`,最长 512;不合法则该条不输出路径,状态按 `failed`。
|
||||
- 有附件时允许原文为空(Home 已如此)。Direct 内层若仍要求非空,在 command 边界先渲染再下传,避免空原文 + 有附件被拒。
|
||||
|
||||
不在 sidecar 里写「若用户要求按附件实施请先读取」。意图留在用户原文;做成游戏的固定 prompt 已经在说这件事。
|
||||
|
||||
### 3.3 谁渲染、谁看见
|
||||
|
||||
- sidecar **只在进 Codex 前由 Rust 拼装**。
|
||||
- `.agent/conversations/project.jsonl` 继续写用户原文(现有 `append_local_conversation_message`)。
|
||||
- 工作台气泡继续显示 latch / 输入框原文,不把 sidecar 画进 UI。
|
||||
- 未完成首轮的 hydration 重放目前只带 prompt:本期不把附件写进 jsonl,进程重启后的未完成首轮可能丢映射。完整成功首轮不受影响。不为此新增会话 schema。
|
||||
|
||||
## 4. 数据流
|
||||
|
||||
```text
|
||||
Home 上传 / 做成游戏 File
|
||||
→ upload_local_asset(已有)
|
||||
→ LauncherProjectContext.attachments(已有)
|
||||
→ ProjectSupervisor.initialAttachments
|
||||
→ 首轮 latch(与 prompt、creationType 同级)
|
||||
→ 仅 chat_with_game_creator_direct_codex.attachments
|
||||
→ render_direct_codex_user_prompt
|
||||
→ 现有 Direct turn(cwd = 项目根)
|
||||
```
|
||||
|
||||
Supervisor / 做方案首轮忽略 `attachments`,行为不变。
|
||||
|
||||
后续工作台手打消息不带 `attachments`。附件是这一轮带来的,不是项目终身上下文。
|
||||
|
||||
## 5. 代码落地(按最终结构,不按最小补丁)
|
||||
|
||||
### 5.1 Rust
|
||||
|
||||
新增 [`apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_attachments.rs`](../../apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_attachments.rs):
|
||||
|
||||
- DTO、清洗、上限常量、`render_direct_codex_user_prompt`
|
||||
- 单元测试(见第 6 节)
|
||||
|
||||
[`agent.rs`](../../apps/ai-game-creator-shell/src-tauri/src/agent.rs) 增加 `mod direct_codex_attachments`。
|
||||
|
||||
[`direct_runtime.rs`](../../apps/ai-game-creator-shell/src-tauri/src/agent/direct_runtime.rs):
|
||||
|
||||
- 删除 Home 专用 struct / sanitizer / `render_direct_codex_home_user_prompt`
|
||||
- `run_direct_game_creator_home_turn` 改为调用共享渲染
|
||||
- `chat_with_game_creator_direct_codex` 增加 `attachments: Option<Vec<DirectCodexTurnAttachment>>`,先渲染再调用现有 `run_direct_game_creator_turn_at_with_creation_type_and_emitter`
|
||||
- 把现有 Home 渲染测试迁到新文件;本文件不再保留一份平行实现
|
||||
|
||||
不要把 attachments 顺着 inner turn / emitter / CLI 往下传。CLI `run_direct_game_creator_turn_at` 不变。
|
||||
|
||||
### 5.2 前端
|
||||
|
||||
[`model.ts`](../../apps/ai-game-creator-shell/src/features/app-shell/model.ts) `ProjectSupervisorComponentProps` 增加:
|
||||
|
||||
```ts
|
||||
initialAttachments?: LauncherImportedAttachment[];
|
||||
```
|
||||
|
||||
[`WorkspaceLauncher.tsx`](../../apps/ai-game-creator-shell/src/features/app-shell/WorkspaceLauncher.tsx):
|
||||
|
||||
```ts
|
||||
initialAttachments={currentProjectContext.attachments}
|
||||
```
|
||||
|
||||
[`App.tsx`](../../apps/ai-game-creator-shell/src/App.tsx):
|
||||
|
||||
- props / latch 增加 `attachments`(默认 `[]`)
|
||||
- `executeChatAgentReply` 不要再加第 5 个位置参数,收成:
|
||||
|
||||
```ts
|
||||
{
|
||||
prompt: string;
|
||||
clientTurnId?: string;
|
||||
creationType?: HomeCreationType | null;
|
||||
attachments?: DirectCodexTurnAttachment[];
|
||||
}
|
||||
```
|
||||
|
||||
- Direct invoke:有 `creationType` 才写该字段(现有);`attachments?.length` 才写 `attachments`
|
||||
- Supervisor 分支完全不读 `attachments`
|
||||
- 现有 `executeChatAgentReply(prompt)` / 恢复未完成 turn 的调用改为对象形式,不传 attachments
|
||||
|
||||
瘦映射不要写在 1 万行的 `App.tsx` 里,放到例如 [`apps/ai-game-creator-shell/src/features/app-shell/directCodexTurnAttachments.ts`](../../apps/ai-game-creator-shell/src/features/app-shell/directCodexTurnAttachments.ts):`toDirectCodexTurnAttachments(imported)`,去掉 `error`,空 `localPath` 不输出该键。
|
||||
|
||||
[`useHomeProjectCreation.ts`](../../apps/ai-game-creator-shell/src/features/app-shell/useHomeProjectCreation.ts):`importHomeAttachments` 写入 `size: attachment.file.size`。不改 `initialPrompt` 兜底句,不改做成游戏(即便本分支尚未合入 PR #210,也不预埋 GDD 字段)。
|
||||
|
||||
[`types.ts`](../../apps/ai-game-creator-shell/src/app/types.ts):`LauncherImportedAttachment` 增加可选 `size?: number`。
|
||||
|
||||
### 5.3 文档
|
||||
|
||||
落地提交时(不是本方案文件自身):
|
||||
|
||||
- 本文件标为现行合同
|
||||
- `docs/README.md`、`docs/project-memory/shared-memory/document-map.md` 增加条目
|
||||
- `decision-log.md` 记一条:Direct 本轮附件只映射路径,不灌正文、不区别 GDD
|
||||
- 不把 issue #212 技术说明改写成「已修复」,等代码合入后再改状态
|
||||
|
||||
## 6. 测试
|
||||
|
||||
### Rust(新文件)
|
||||
|
||||
1. 无附件:原文 trim 后原样返回;空原文报错。
|
||||
2. Home 形态(无 path、无 status):与迁过来的两条现有测试逐字一致(含路径剥离、非法 mediaType、8 条上限)。
|
||||
3. Project 形态:原文保留;含原名与 `assets/uploads/...`;**断言不得出现「GDD」「规格」「权威」**。
|
||||
4. 同一列表里 png 与 md 行格式相同(只是 name/path/type 不同)。
|
||||
5. `status=failed` 且无 path:有状态、无项目路径、无 error 正文。
|
||||
6. 非法 `localPath`(`../`、`.agent/x`、绝对路径)不出现在输出中。
|
||||
7. 空原文 + 有附件:成功,且含 Project 头。
|
||||
|
||||
### 前端
|
||||
|
||||
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。
|
||||
5. 若本分支已能跑 PR #210 的 home.suite / plan-gdd 做成游戏用例:只断言它仍调用 `createHomeDraftAutomatically` / 仍使用原固定 prompt;**不要**给做成游戏加第二条附件协议。sidecar 由通用 Direct 断言覆盖。
|
||||
|
||||
### 不测
|
||||
|
||||
- 不把「必须生成弹幕射击」写成单测。
|
||||
- 不测 native `file.read` 是否进 `agent.db`。
|
||||
|
||||
## 7. 做成游戏为什么不用改
|
||||
|
||||
PR #210 `startGameFromApprovedGdd`:
|
||||
|
||||
1. 读策划项目 `game/fast_gdd.md`
|
||||
2. `new File([content], 'fast_gdd.md')`
|
||||
3. `createHomeDraftAutomatically({ creationType: 'game', prompt: APPROVED_GDD_BUILD_PROMPT, attachments }, 'direct-build')`
|
||||
|
||||
之后与首页拖一个 md 完全相同。sidecar 见到的是「原文件名 `fast_gdd.md` + 新项目 `assets/uploads/upload-…-fast_gdd.md`」。固定 prompt 继续说「读附件中的 fast_gdd.md」,映射补上真实路径。
|
||||
|
||||
## 8. 验收
|
||||
|
||||
1. 首页做游戏:上传任意文本或图片 + 一句话,Direct 首轮 prompt 含原名和 `assets/uploads/...`。
|
||||
2. 做成游戏(PR #210 合入后或该分支上):固定 prompt 一字不改,同时出现改写后的项目路径。
|
||||
3. 做方案首轮:Supervisor 行为与现在一致,无 sidecar。
|
||||
4. 无附件:Direct 入参与现在一致。
|
||||
5. `npm run check:encoding`、`git diff --check`、相关 `home.suite` / Direct Rust 测试通过。
|
||||
|
||||
## 9. 实现顺序
|
||||
|
||||
1. Rust 共享渲染 + 迁 Home 测试 + Direct command 接 `attachments`
|
||||
2. 前端 latch / invoke / 映射 / `size`
|
||||
3. 改 `home.suite` 附件断言
|
||||
4. encoding 与定向测试
|
||||
5. 合入时补 decision-log 与文档索引
|
||||
@@ -0,0 +1,395 @@
|
||||
# Direct 回合行为审计账本
|
||||
|
||||
- 日期:2026-08-31
|
||||
- 状态:现行合同(已按本文落地)
|
||||
- 问题:Gitea issue #212 的第二段(Direct 原生读 / 工具行为无法从项目产物判断);用于分析「附件已映射仍未按文档实施」
|
||||
- 关联:[`【技术方案】DirectProject本轮附件路径映射-2026-08-31.md`](./【技术方案】DirectProject本轮附件路径映射-2026-08-31.md)、[`【技术说明】DirectProject未消费用户上传权威文档-2026-08-30.md`](./【技术说明】DirectProject未消费用户上传权威文档-2026-08-30.md)
|
||||
- 原则:落地后代码简洁可维护;审计是 Direct 行为时间线,不是 GDD 特例,也不替代 sidecar
|
||||
|
||||
## 0. 一句话
|
||||
|
||||
Direct GUI 回合已经能看见 Codex `item/completed`,但只收成 UI 活动词,隔离 `CODEX_HOME` 随后删除。本方案在项目内留下有界、可共享的回合账本:本轮提供了哪些附件路径、按什么顺序做了读/搜/列表/MCP/写文件,以及第一次定玩法的动作是什么。用来区分「没读附件」和「读了仍走默认收集类」,不灌正文、不强制读取、不拷会话目录。
|
||||
|
||||
## 1. 目标与非目标
|
||||
|
||||
### 目标
|
||||
|
||||
一次带 `clientTurnId` 的 DirectProject GUI 回合结束后,只凭项目目录应能回答:
|
||||
|
||||
1. 本轮 sidecar 是否发出,原名映射到哪些项目相对路径,文件当时的 `contentSha256`。
|
||||
2. 模型是否用 native 命令 / 列表 / 搜索 / 看图 / MCP 打开过那些路径(路径 + 当时磁盘 hash,不是 stdout)。
|
||||
3. **顺序**:读附件是在第一次美术 brief / 第一次写 `game/` 之前还是之后。
|
||||
4. 第一次「定玩法」动作是什么(优先 `taonier_prepare_game_art.brief`,否则其它生成类 MCP 或对 `game/` 的写入)。
|
||||
|
||||
覆盖后续手打回合:只要 GUI Direct 有 `clientTurnId` 就记账本,附件可以为空。
|
||||
|
||||
### 非目标
|
||||
|
||||
- 不证明「理解并按 GDD 实施」。那是对照 `game/index.html`、美术产物做的产品判断;账本只提供行为时间线。
|
||||
- 不把 issue #212 原文的 NLP / 「关键内容进入上下文」做成自动判决。
|
||||
- 不灌附件正文进 prompt,不强制先读再继续,不为 GDD 开协议特例。
|
||||
- 不改 sidecar 文案、jsonl 用户原文、工作台气泡。
|
||||
- 不改做成游戏固定 prompt / PR #210 注入。
|
||||
- 不复用 Supervisor `agent.runtime.action_receipt` / `file.read`。
|
||||
- 不拷隔离 `CODEX_HOME`、不落 `auth.json`、不落 `aggregated_output` / MCP `result` / patch `diff` / `FunctionCallOutput` 正文。
|
||||
- 不把原始 item JSON 送进 Tauri 前端事件(现有 `DirectCodexTurnObservation` 仍只允许安全活动词和流式正文)。
|
||||
- 不扫 `kind=uploaded` 历史附件;只记本轮 sidecar 提供的集合。
|
||||
- DirectHome、ToolHost、CLI `--direct-codex-chat`(无 `clientTurnId`)本期不写这份账本。
|
||||
- 本期不改 UI,不在聊天面板展示审计。
|
||||
- 不把 issue #212 标成已修复;sidecar 与本账本是两段工作。
|
||||
|
||||
## 2. 现状
|
||||
|
||||
```text
|
||||
Codex item/completed
|
||||
commandExecution / mcpToolCall / fileChange / imageView / …
|
||||
│
|
||||
├─ 现用:收成 Activity("validation"|"controlled-tool"|…)
|
||||
│ → Tauri 进度,不落盘
|
||||
└─ 不用:隔离 CODEX_HOME session(含 stdout)→ tempdir Drop 删除
|
||||
```
|
||||
|
||||
项目里现有:
|
||||
|
||||
| 产物 | 记下的 | 缺的 |
|
||||
| ------------------------------------------ | ----------------------------------- | ------------------------------------- |
|
||||
| `.agent/conversations/project.jsonl` | 用户原文 + 助手终稿 | sidecar、工具调用 |
|
||||
| `.agent/agent.db` | init / upload / 美术登记 / 对话指针 | native 读、MCP 调用、`agc_write_file` |
|
||||
| `.agent/logs/command.log` | 权限确认 | 原生命令 |
|
||||
| `asset.register` / `canvas.asset_generate` | 路径、切片、部分 `source.prompt` | 与读附件的先后 |
|
||||
| 隔离 `CODEX_HOME` | Codex 自己的 session | 回合结束即删 |
|
||||
|
||||
Codex app-server 协议里,`commandExecution.commandActions` 已分类为 `Read | ListFiles | Search | Unknown`,`Read.path` 在协议侧会拼成 cwd 绝对路径。Direct cwd 就是项目根(`resolve_direct_codex_project_authority` 不再强制 `game/` 子目录)。抽取时把绝对路径收回项目相对 POSIX,失败则丢路径,不写宿主绝对路径。
|
||||
|
||||
`agc_write_file` 经 tool bridge 落盘,当前不写 `agent.db`。不给每个 MCP 单独打点;统一在 `item/completed` 抽一次。
|
||||
|
||||
## 3. 分析用判据(相对 issue 收窄)
|
||||
|
||||
落地后,对类似 `gameagent-9baa5293` 的 run,应能三分:
|
||||
|
||||
| 时间线 | 结论 | 下一刀不该打哪 |
|
||||
| ----------------------------------------------------------------------------------------- | ------------------------ | ------------------------------------------------------ |
|
||||
| `offeredRead.read=false`,`firstDesign` 已是 `taonier_prepare_game_art` 且 brief 是收集类 | 没打开附件就定了玩法 | 不是「GDD 解析不够」 |
|
||||
| 先 `Read` 且 hash 对上,brief 仍是收集类 | 读了但没用 | sidecar 已够;看四切片 / icon-spec「收集物」/ 完成合同 |
|
||||
| 只有 `ListFiles` / `Search` 命中 uploads,没有 `Read` | 发现了没读正文 | 映射可能够,缺的是读 |
|
||||
| `Read` 的 path 是 `fast_gdd.md` 而不是 `assets/uploads/…` | sidecar 没被当成磁盘路径 | 还是路径合同 |
|
||||
|
||||
不在账本里写「已遵循 GDD」或「未遵循 GDD」布尔。
|
||||
|
||||
## 4. 落点
|
||||
|
||||
两层,都在项目 `.agent/` 控制面内,模型读不到:
|
||||
|
||||
1. **权威时间线**(每回合一个 jsonl,只追加)
|
||||
`.agent/runtime/direct-codex/turns/<clientTurnId>.jsonl`
|
||||
2. **总索引一条摘要**(方便继续翻现有 `agent.db`)
|
||||
`recordType: "direct.codex.turn"`
|
||||
|
||||
`clientTurnId` 沿用现有规则:trim 后 6–160 位 ASCII 字母数字或连字符,首位字母或数字。文件名用规范化后的 id,不再二次编码。
|
||||
|
||||
不升级 `GAME_CREATOR_AGENT_DB_SCHEMA_VERSION`;新 `recordType` 走 Ordinary 追加。`updatedAt` / `schemaVersion` 仍由 `serialize_agent_db_record` 写入。
|
||||
|
||||
jsonl 每条自带 `recordedAtMs`(`unix_millis`)。同一 `clientTurnId` 若再次进入(当前 GUI 运行中互斥,结束后理论上可再来):只追加,不截断;后一次 `turn_start` 视为新 attempt。读摘要时按文件内最后一次 `turn_start` 到对应 `turn_end` 计算 `offeredRead`。`agent.db` 每次 `turn_end` 再追加一条摘要,分析取该 `clientTurnId` 最后一条。
|
||||
|
||||
## 5. 记录合同
|
||||
|
||||
camelCase JSON。禁止出现附件正文、命令 stdout、patch diff、宿主绝对路径、Token、URL 签名。
|
||||
|
||||
### 5.1 `turn_start`
|
||||
|
||||
在 sidecar **已经渲染之后**、Codex turn **启动之前**写入。`promptSha256` 哈希的是 **用户原文**(command 入参 `prompt`),不是带 sidecar 的全文。
|
||||
|
||||
```json
|
||||
{
|
||||
"recordType": "direct.codex.turn_start",
|
||||
"clientTurnId": "Abc123-def",
|
||||
"sidecarPresent": true,
|
||||
"promptSha256": "<sha256 hex of original user text UTF-8>",
|
||||
"promptChars": 120,
|
||||
"attachments": [
|
||||
{
|
||||
"name": "fast_gdd.md",
|
||||
"localPath": "assets/uploads/upload-1788164530559-fast_gdd.md",
|
||||
"mediaType": "text/markdown",
|
||||
"size": 8119,
|
||||
"status": "imported",
|
||||
"contentSha256": "<sha256 hex or omit>",
|
||||
"hashSkipped": null
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
- `attachments` 清洗复用 sidecar:`sanitize_attachment_name` / `media_type` / `status` / `local_path`。把这些函数改成 `pub(crate)`,审计模块不要复制一份。
|
||||
- 条数上限仍 8;超出只在 sidecar 文案里写「另有 N 个未展开」,账本 `attachments` 同样只留前 8,另加 `attachmentsOmitted: N`。
|
||||
- `sidecarPresent`:本轮渲染走了 Project 头(任一条有合法 path 或 status)。Home 形态不会出现在本账本(Home 不记账)。
|
||||
- `contentSha256`:对清洗后的 `localPath` 读项目文件做 SHA-256 小写 hex。文件不存在则省略 hash,`hashSkipped: "missing"`。超过 `DIRECT_CODEX_AUDIT_HASH_MAX_BYTES`(2 MiB)则 `hashSkipped: "too-large"`。`.agent` / `.git` / `..` 路径本来就不会出现在 sidecar 输出里。
|
||||
|
||||
无附件:`attachments: []`,`sidecarPresent: false`,仍然写 `turn_start`。
|
||||
|
||||
### 5.2 `item`
|
||||
|
||||
仅 `item/completed`。`item/started` 和 `outputDelta` 不落盘。
|
||||
|
||||
公共字段:
|
||||
|
||||
```json
|
||||
{
|
||||
"recordType": "direct.codex.item",
|
||||
"clientTurnId": "Abc123-def",
|
||||
"seq": 1,
|
||||
"itemId": "item-…",
|
||||
"itemType": "commandExecution",
|
||||
"status": "completed"
|
||||
}
|
||||
```
|
||||
|
||||
`seq` 从 1 起,按成功写入的 item 递增。`itemType` 取 Codex `item.type` 原词;未知类型仍记账 `itemType`,不附带未清洗 payload。
|
||||
|
||||
按类型附加字段:
|
||||
|
||||
| `item.type` | 追加 | 禁止 |
|
||||
| ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------- | -------------------------------------------------------------------- |
|
||||
| `commandExecution` | `command` 截断 240 字;`exitCode`;`durationMs`;`actions[]` | `aggregatedOutput` |
|
||||
| `mcpToolCall` | `tool`、`server`(可省略默认 `agc_tools`)、`durationMs`、§5.4 参数 | `result`、`error` 原文(只留 `status` / `errorKind`) |
|
||||
| `fileChange` | `changes: [{ path, kind }]`,`kind` 为 `add` / `delete` / `update` | `diff`、`movePath` 的宿主绝对路径(相对化失败则整条 change 丢 path) |
|
||||
| `imageView` | `path` | 图像字节 |
|
||||
| `functionCallOutput` | `name`、`namespace` | `output` |
|
||||
| `webSearch` | `query` 截断 400 字 | 结果页正文 |
|
||||
| `agentMessage` / `userMessage` / `plan` / `reasoning` / `contextCompaction` / `hookPrompt` | **整类跳过**(终稿已在 jsonl;推理正文不是本账本) | — |
|
||||
| 其它未知 | 只留公共字段 | 原始 `item` 对象 |
|
||||
|
||||
`commandExecution.actions[]`:
|
||||
|
||||
```json
|
||||
{ "type": "read", "path": "assets/uploads/upload-…-fast_gdd.md", "contentSha256": "…", "hashSkipped": null }
|
||||
{ "type": "listFiles", "path": "assets" }
|
||||
{ "type": "search", "query": "fast_gdd", "path": null }
|
||||
{ "type": "unknown" }
|
||||
```
|
||||
|
||||
- `Read.path` 先相对化再清洗;失败则该 action 记 `{ "type": "read", "pathRejected": true }`,不写绝对路径。
|
||||
- 相对化成功后,对磁盘文件按 §5.1 同一套 hash 规则补 `contentSha256`。
|
||||
- `command` 里若相对化失败,把 `command` 整段丢掉,改 `commandRedacted: true`(避免 `type C:\Users\…\fast_gdd.md` 进账本)。
|
||||
|
||||
### 5.3 MCP 参数白名单
|
||||
|
||||
只抄这些键,其它键丢弃。字符串再经 path 清洗或截断。
|
||||
|
||||
| 工具 | 落盘参数 | 正文类字段 |
|
||||
| ------------------------------- | ---------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- |
|
||||
| `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_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` | 无 |
|
||||
| `agc_list_account_assets` | `folderId`、`query`、`offset`、`limit` | 无 |
|
||||
| `agc_import_account_assets` | `assetIds`(最多 8 个 id,超出 `assetIdsOmitted`)、`localPaths`(清洗后相对路径,最多 8) | 无 |
|
||||
| `agc_remove_background` | `sourceLocalAssetId`、`assetName` | 无 |
|
||||
| `agc_browser_playtest` | `attempt` | 无 |
|
||||
| `agc_web_search` | `query` 截断 400、`maxResults` | 无 |
|
||||
| `agc_read_skill_resource` | `skillName`、`relativePath` | 不落 Skill 正文 |
|
||||
| 未知 MCP 名 | 只留 `tool` + `status` | 不落 `arguments` |
|
||||
|
||||
`brief` / 截断后的 `prompt` 是 **模型自己写的设计文本**,不是用户 GDD 转储。这是分析「仍走收集类」的关键,允许进 jsonl。`agent.db` 摘要只留 `briefPreview` 240 字。
|
||||
|
||||
### 5.4 `turn_end`
|
||||
|
||||
派生摘要,不是第二真相。字段必须能从本文件已写入的 `turn_start` + `item` 重算出来。
|
||||
|
||||
```json
|
||||
{
|
||||
"recordType": "direct.codex.turn_end",
|
||||
"clientTurnId": "Abc123-def",
|
||||
"completed": true,
|
||||
"itemCount": 17,
|
||||
"itemsTruncated": false,
|
||||
"offeredRead": [
|
||||
{
|
||||
"localPath": "assets/uploads/upload-1788164530559-fast_gdd.md",
|
||||
"read": false
|
||||
}
|
||||
],
|
||||
"firstDesign": {
|
||||
"kind": "mcp:taonier_prepare_game_art",
|
||||
"seq": 3,
|
||||
"tool": "taonier_prepare_game_art",
|
||||
"briefPreview": "俯视角收集冒险小游戏…"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`offeredRead.read=true` 当且仅当本 attempt 内存在 `actions.type=read` 或 `imageView` 或 MCP 参数里的 `path` / `localPaths`,清洗后与 `localPath` 字符串相等。hash 对不上仍记 `read: true`,另加 `contentSha256Match: false`(读了另一份同路径文件或读时文件已变)。没有 hash 可对则省略 `contentSha256Match`。
|
||||
|
||||
`firstDesign`:本 attempt 第一条满足任一条件的 item:
|
||||
|
||||
1. MCP:`taonier_prepare_game_art` / `agc_generate_image` / `agc_edit_image` / `agc_create_or_derive_resource`
|
||||
2. `agc_write_file` 且 path 以 `game/` 开头或文件名是 `index.html`
|
||||
3. `fileChange` 且任一条 change path 满足 2
|
||||
|
||||
列表、搜索、读、Skill 读取、账户素材查询、playtest、web_search **不算** firstDesign。没有则 `firstDesign: null`。
|
||||
|
||||
`kind` 取值:`mcp:<tool>` / `write:<path>` / `fileChange:<path>`。
|
||||
|
||||
### 5.5 `agent.db` 摘要
|
||||
|
||||
```json
|
||||
{
|
||||
"recordType": "direct.codex.turn",
|
||||
"clientTurnId": "Abc123-def",
|
||||
"turnLog": ".agent/runtime/direct-codex/turns/Abc123-def.jsonl",
|
||||
"sidecarPresent": true,
|
||||
"offeredCount": 1,
|
||||
"offeredRead": [
|
||||
{ "localPath": "assets/uploads/…-fast_gdd.md", "read": false }
|
||||
],
|
||||
"firstDesign": {
|
||||
"kind": "mcp:taonier_prepare_game_art",
|
||||
"seq": 3,
|
||||
"briefPreview": "…"
|
||||
},
|
||||
"itemCount": 17,
|
||||
"itemsTruncated": false,
|
||||
"completed": true,
|
||||
"auditWriteFailed": false
|
||||
}
|
||||
```
|
||||
|
||||
`turnLog` 必须是项目相对 POSIX。不要把 jsonl 全文复制进 `agent.db`。单条仍受 Ordinary 1 MiB 限制;摘要本身应远小于此。
|
||||
|
||||
### 5.6 上限
|
||||
|
||||
| 项 | 值 |
|
||||
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------- |
|
||||
| 每回合 item 条数 | 256;超出再写一条 `recordType: "direct.codex.items_truncated"`,之后 item 丢弃但仍把 `turn_end.itemsTruncated=true` |
|
||||
| `command` | 240 字 |
|
||||
| `brief` / 生成类 `prompt` 落盘 | 4000 字 |
|
||||
| `briefPreview` | 240 字 |
|
||||
| 文件 hash | 2 MiB |
|
||||
| 附件条数 | 8(与 sidecar 相同) |
|
||||
| jsonl 单行 | 沿用现有 jsonl 追加上限;超长截断正文类字段,不截断结构 |
|
||||
|
||||
## 6. 调用链
|
||||
|
||||
```text
|
||||
chat_with_game_creator_direct_codex
|
||||
规范化 clientTurnId
|
||||
DirectCodexTurnAudit::start(root, clientTurnId, originalPrompt, attachments)
|
||||
→ 写 turn_start(fail-open)
|
||||
render_direct_codex_user_prompt // 现有 sidecar,不变
|
||||
run_direct_game_creator_turn_at_with_creation_type_and_emitter(..., audit)
|
||||
→ Codex collect 循环在 DirectProject + item/completed 调 audit.observe_item
|
||||
Ok/Err 都 audit.finish(completed)
|
||||
→ 写 turn_end + agent.db 摘要
|
||||
```
|
||||
|
||||
- CLI `run_direct_game_creator_turn_at` **不** 接 audit(无 `clientTurnId`)。
|
||||
- Home command 不接 audit。
|
||||
- 不要把 attachments / audit 顺着 CLI inner、pool、ToolHost 往下传。
|
||||
- `DirectCodexTurnObservation` **不** 增加原始 `params`。审计走独立 `DirectCodexTurnAudit`,避免 stdout 正文进入 Tauri 事件。
|
||||
|
||||
`direct_game_creator_codex_chat_at_with_optional_observer` 增加可选 `audit: Option<&mut DirectCodexTurnAudit>`,再传到 `run_turn_with_direct_observer`。仅 `workspace_mode == DirectProject` 且 `audit` 为 Some 时抽取。
|
||||
|
||||
`run_direct_game_creator_turn_inner` 的 UI observer 保持只处理 `AccumulatedText` / `Activity`。
|
||||
|
||||
回合失败(生成失败、浏览器试玩失败、回复落盘失败):只要 `start` 过就 `finish(false)`,保留已观察到的 item。Codex 尚未启动则 `itemCount=0`。
|
||||
|
||||
## 7. 失败语义
|
||||
|
||||
审计 **不得** 把做游戏打失败。所有写盘包在 sink 内:
|
||||
|
||||
- 单次追加失败:记内存 `audit_write_failed=true`,后续 item 仍尝试写;`finish` 时摘要带 `auditWriteFailed: true`。
|
||||
- 连摘要都写不进去:只在 Direct debug / 现有进度通道能承受的前提下忽略;不新增用户可见报错文案。
|
||||
- 不引入新的 Tauri 事件名。
|
||||
|
||||
与 `conversation.write` 失败不同:助手终稿落盘失败仍按现有逻辑拒绝返回。审计失败不走那条。
|
||||
|
||||
## 8. 安全
|
||||
|
||||
- 路径:与 sidecar 同一套相对 POSIX 清洗;相对化失败不写原绝对路径。
|
||||
- 控制面:hash / 读文件只用 `resolve_local_project_path`;拒绝 `.agent` / `.git` / 敏感文件。这些路径若出现在 commandActions 里,只记 `pathRejected`。
|
||||
- 不把 `aggregated_output`、MCP result、function output、diff 暂存在内存再截断——抽取函数根本不读这些键。
|
||||
- jsonl 位于 `.agent/runtime/**`,现有 Direct 控制面边界禁止模型当普通项目文档读。
|
||||
- 前端观察者和审计 sink 分叉,禁止图省事 `observer(Item { params })`。
|
||||
|
||||
## 9. 代码落地
|
||||
|
||||
新增 [`apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_audit.rs`](../../apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_audit.rs):
|
||||
|
||||
- `DirectCodexTurnAudit`
|
||||
- `start` / `observe_item` / `finish`
|
||||
- 相对化、hash、MCP 白名单、`firstDesign` / `offeredRead` 派生
|
||||
- 单元测试(见 §11)
|
||||
|
||||
[`agent.rs`](../../apps/ai-game-creator-shell/src-tauri/src/agent.rs):`mod direct_codex_audit` + `pub(crate) use`。
|
||||
|
||||
[`direct_codex_attachments.rs`](../../apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_attachments.rs):清洗函数改 `pub(crate)`,行为不变。
|
||||
|
||||
[`codex_app_server.rs`](../../apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server.rs):
|
||||
|
||||
- `run_turn_with_direct_observer` / `direct_game_creator_codex_chat_at_with_optional_observer` 增加 `audit: Option<&mut DirectCodexTurnAudit>`
|
||||
- collect 循环 `Item { completed: true, .. }` 且 DirectProject 时 `audit.observe_item(¶ms)`
|
||||
- **不要** 把 `params` 塞进 `DirectCodexTurnObservation`
|
||||
- 现有 Home / ToolHost / 安全活动词测试保持逐字
|
||||
|
||||
[`direct_runtime.rs`](../../apps/ai-game-creator-shell/src-tauri/src/agent/direct_runtime.rs):
|
||||
|
||||
- `chat_with_game_creator_direct_codex` 创建 audit(原文 + attachments),Ok/Err 都 `finish`
|
||||
- 把 audit 传入 `run_direct_game_creator_turn_at_with_creation_type_and_emitter`
|
||||
- 该函数和 inner 增加可选 audit;CLI 入口签名不变
|
||||
|
||||
不要给 `agc_write_file` / 每个 MCP handler 再写一份平行审计。
|
||||
|
||||
前端、DTO、sidecar 文案、home.suite 附件断言:本期不改。不新增 UI。
|
||||
|
||||
文档:落地提交时把本文状态改为「现行合同(已按本文落地)」;`decision-log.md` 记一条;不要把 08-30 技术说明改成已修复。
|
||||
|
||||
## 10. 测试
|
||||
|
||||
全部是 Rust 单元测试,用 fixture item JSON,不拉真 Codex。
|
||||
|
||||
1. `turn_start`:原文 hash 稳定;sidecar 路径清洗后出现;非法 `../` 不进 attachments;无附件 `sidecarPresent=false`。
|
||||
2. 附件文件写入临时项目后 `contentSha256` 与直接 hash 一致;缺文件 `hashSkipped=missing`;超过 2 MiB `too-large`。
|
||||
3. `commandExecution` + `commandActions: [{type:read, path: <abs>}]` → 相对路径 + hash;`aggregatedOutput` 即使在 fixture 里也不出现在落盘 JSON。
|
||||
4. `Read` 相对化失败 → `pathRejected`,落盘 JSON 不含 `C:\\` / `Users`。
|
||||
5. `mcpToolCall` `taonier_prepare_game_art`:`brief` 保留;`result` 丢掉。
|
||||
6. `agc_write_file`:有 path 与 `contentChars`,无 content。
|
||||
7. `agc_generate_image`:32k prompt 只留 4000 + `promptChars` + sha256。
|
||||
8. `fileChange`:path + kind,无 diff。
|
||||
9. `offeredRead`:读路径等于 offered → `read=true`;只 list/search → `read=false`。
|
||||
10. `firstDesign`:先 read 再 art → kind 是 mcp art,seq 是 art 那条;只有 read → `null`。
|
||||
11. 第 257 条 item 触发 truncated,`turn_end.itemsTruncated=true`。
|
||||
12. `agent.db` 摘要含 `turnLog` 相对路径、`offeredRead`、`firstDesign.briefPreview`。
|
||||
13. 写盘注入失败:`finish` 不 panic、不返回 Err 给调用方(sink 方法是 `()`)。
|
||||
14. 未知 `item.type` 只留公共字段。
|
||||
15. 现有 DirectHome 活动词测试、sidecar 渲染测试不受影响。
|
||||
|
||||
不测:真模型是否读 GDD、是否生成弹幕射击、浏览器验收文案。
|
||||
|
||||
## 11. 验收(方案落地后的人工分析)
|
||||
|
||||
用一次「上传 md + 做成游戏 / 首页做游戏」的本地项目:
|
||||
|
||||
1. 存在 `.agent/runtime/direct-codex/turns/<clientTurnId>.jsonl`。
|
||||
2. `agent.db` 有对应 `direct.codex.turn`。
|
||||
3. `turn_start.attachments[].localPath` 与 sidecar 项目路径一致。
|
||||
4. jsonl **没有** GDD 正文、没有 `aggregatedOutput`、没有 patch。
|
||||
5. 能根据 `offeredRead` + `firstDesign` 填上 §3 四行表的其中一行,而不用猜隔离 session。
|
||||
6. 工作台气泡仍是用户原文;jsonl 对话仍无 sidecar。
|
||||
7. `npm run check:encoding`、`git diff --check`、相关 Rust 单测通过。
|
||||
|
||||
## 12. 实现顺序
|
||||
|
||||
1. `direct_codex_audit.rs` + 清洗函数 `pub(crate)` + fixture 测试
|
||||
2. `codex_app_server` collect 接 sink;观察者枚举不变
|
||||
3. GUI Direct command 创建 / finish sink
|
||||
4. encoding 与定向 `cargo test`
|
||||
5. 合入时改本文状态、decision-log、sidecar 文档里「native 审计另排期」那一行改为指向本文
|
||||
|
||||
## 13. 与 sidecar / issue 的边界
|
||||
|
||||
- sidecar:让模型 **知道路径**。已落地,合同不变。
|
||||
- 本账本:让人 **看见模型做了什么**。不替代 sidecar,也不在本方案里做强制读取。
|
||||
- issue #212 主问题仍是消费失败;本账本是为下一刀修复提供证据,不是把 212 关单。
|
||||
@@ -130,12 +130,12 @@ type ImageCanvasHostCommitResult = {
|
||||
interface ImageCanvasHostPort {
|
||||
readonly kind: ImageCanvasHostKind;
|
||||
readonly capabilities: ImageCanvasHostCapabilities;
|
||||
loadDraft(input: ImageCanvasHostScope): Promise<
|
||||
ImageCanvasHostResult<AssetCanvasDraft | null>
|
||||
>;
|
||||
createDraft(input: ImageCanvasHostScope): Promise<
|
||||
ImageCanvasHostResult<AssetCanvasDraft>
|
||||
>;
|
||||
loadDraft(
|
||||
input: ImageCanvasHostScope,
|
||||
): Promise<ImageCanvasHostResult<AssetCanvasDraft | null>>;
|
||||
createDraft(
|
||||
input: ImageCanvasHostScope,
|
||||
): Promise<ImageCanvasHostResult<AssetCanvasDraft>>;
|
||||
updateDraft(input: {
|
||||
scope: ImageCanvasHostScope;
|
||||
expectedDraftRevision: number;
|
||||
@@ -207,10 +207,10 @@ interface ImageCanvasHostPort {
|
||||
第一批通用视觉组件固定由 `@genarrative/image-canvas-react` 暴露:
|
||||
|
||||
```ts
|
||||
CanvasChromeButton
|
||||
CanvasToolbar
|
||||
CanvasToolbarGroup
|
||||
CanvasToolbarDivider
|
||||
CanvasChromeButton;
|
||||
CanvasToolbar;
|
||||
CanvasToolbarGroup;
|
||||
CanvasToolbarDivider;
|
||||
```
|
||||
|
||||
- `CanvasChromeButton` 统一原生 button 的可访问名称、tooltip、`aria-pressed`、`aria-expanded`、禁用态和画布 action 视觉;图标、短文案、业务事件和禁用条件由宿主传入。共享组件不得 import Lucide、平台账号 store、钱包 store 或宿主请求客户端。
|
||||
@@ -598,10 +598,7 @@ type CommitLocalProjectAssetSuccess = {
|
||||
|
||||
type CommitLocalProjectAssetConflict = {
|
||||
status: 'conflict';
|
||||
conflictKind:
|
||||
| 'project-identity'
|
||||
| 'project-revision'
|
||||
| 'draft-revision';
|
||||
conflictKind: 'project-identity' | 'project-revision' | 'draft-revision';
|
||||
expectedProjectId: string;
|
||||
projectId: string | null;
|
||||
expectedRevision: number;
|
||||
@@ -870,46 +867,46 @@ cancelling
|
||||
|
||||
## 13. 验收矩阵
|
||||
|
||||
| 编号 | 宿主/场景 | 前置或故障注入 | 必须结果 |
|
||||
| --- | --- | --- | --- |
|
||||
| A01 | Web + Tauri 共享源码 | 构建两个宿主 | 两者 import 同一 core/react;客户端无画布目录镜像 |
|
||||
| A02 | 新增图片 | create,导入/编辑/保存 | 新 asset 落盘并进入 manifest、投影、依赖图和两种布局;无需刷新 |
|
||||
| A03 | 精修图片 | refine 已有本地图片 | 原文件/asset 保留,新建 asset,source.resourceId 补齐且血缘包含源 |
|
||||
| A04 | 基础编辑 | 平移、缩放、多选、移动/缩放、层序、显隐、锁定、翻转、分组 | 两宿主行为和序列化 fixture 一致,undo/redo 最多 60 步 |
|
||||
| A05 | 生成成功 | 响应正常 | 只新增一次 generation 结果,草稿 CAS 递增且可继续编辑/保存 |
|
||||
| A06 | 生成失败 | 上游确定失败 | 状态可恢复,不创建正式资产,不用新幂等键自动重试 |
|
||||
| A07 | 生成响应丢失 | 上游已受理、客户端未收到结果 | 以原 operation/idempotency 对账,只产生一份结果/扣费 |
|
||||
| A08 | 保存成功 | project/draft revision 匹配 | file -> manifest/revision -> 回读 -> ledger/draft -> event 顺序成立 |
|
||||
| A09 | 重复保存 | 相同 commit/key/指纹 | 返回 already-committed,asset/revision/eventId 均不重复 |
|
||||
| A10 | 幂等冲突 | 同 key 或 commitId、不同指纹 | 失败关闭,原 ledger/文件/manifest 不变 |
|
||||
| A11 | 两窗口并发 | 相同 expectedRevision 同时提交 | 最多一笔 committed,另一笔 typed conflict,不覆盖成功方 |
|
||||
| A12 | draft 并发 | 相同 expectedDraftRevision 更新 | 最多一笔 updated,另一笔返回最新完整 draft |
|
||||
| A13 | 崩溃:首个/全部事务快照、journal 或 prepared 后 | 尚未装图片 | 无 ledger 的未发布 transaction 只在正式文件不存在且 manifest/revision 仍为 before 时清理;prepared 安全回滚或继续,不生成幽灵 asset |
|
||||
| A14 | 崩溃:图片后 | manifest 前 | 仅在摘要/before 全匹配时删除新文件,否则 reconciliation-required |
|
||||
| A15 | 崩溃:manifest 后 | revision 前 | before/after 匹配时前向补 revision,否则 reconciliation-required |
|
||||
| A16 | 崩溃:revision 后 | ledger/event 前 | 回读验证后补 ledger/draft,并重发相同 eventId |
|
||||
| A17 | 崩溃:emit 后 | 投递标记前 | 允许重复事件,前端 eventId 去重且不重复选中/布局 |
|
||||
| A18 | 切项目后的迟到保存 | 提交在途时打开其它项目 | 当前项目 UI 不变;旧项目缓存可按精确身份更新 |
|
||||
| A19 | 切模式/离开流程 | 提交在途时进入 run/overview/新 session | 不切回素材画布、不抢焦点,正式结果仍可投影到对应项目 |
|
||||
| A20 | 改选择后的迟到保存 | 等待时选择其它资源 | 保持用户选择,新资源只进入投影和布局 |
|
||||
| A21 | 搜索隐藏新资源 | query epoch 改变且不匹配新资源 | 不清搜索、不自动选中,提示并提供显式清除/定位动作 |
|
||||
| A22 | 即时投影 | 提交后不刷新/不重开 | manifest、资源卡、依赖图输入、布局和允许时的选中全部完成 |
|
||||
| A23 | 草稿损坏/身份错配 | 损坏 JSON、未知 schema、项目路径被重建 | 失败关闭,不用空草稿覆盖,不创建其它项目副作用 |
|
||||
| A24 | 锁与恢复 | 活锁 mtime 很旧、进程退出、Windows/Unix | 不按时间/PID删锁;句柄释放后正常取得同一锁入口 |
|
||||
| A25 | 容量边界 | 2 MiB/4096 层/64 MiB/像素上限边界及超限 | 边界内成功,超限零副作用且错误不泄露绝对路径/密钥 |
|
||||
| A26 | 导出 | PNG/JPEG/WebP | Web 下载/云端、Tauri 保存对话框均成功;共享 UI 不接收绝对路径 |
|
||||
| A27 | 取消 | clean、dirty、generating、staging、committing | 分别符合第 12 节;committing 不伪装成可取消 |
|
||||
| A28 | 恢复草稿 | 主文件损坏但恢复副本可信/不可信 | 可信副本恢复到 clean history 基线;不可信进入对账,不猜测 |
|
||||
| A29 | 登录刷新重放 | context、参考图准备或首次提交返回 401,刷新后以相同 generationId 和幂等身份重放 | 401 账本保持可恢复且第二次真实访问平台;403 直接失败且不刷新;远端最多受理一次 |
|
||||
| A30 | 生成卡片拖动 | 生成中拖动占位卡片 | 卡片位置按画布坐标更新并持久化到 generation record,任务状态刷新不覆盖用户位置 |
|
||||
| A31 | 精修最终图唯一性 | 选择另一候选图设为最终图 | 入口原图和所有其他候选保持各自快照,只有 `lastCommit.sourceLayerId` 标识唯一正式候选,更新正式 asset 不反向改写历史图层 |
|
||||
| A32 | 精修默认比例 | 打开图片的快速编辑 | 按原图宽高映射到最接近的支持比例(1:1、2:3、3:2、9:16、16:9);尺寸无效时回退 1:1 |
|
||||
| A33 | 旧提交被后继提交取代 | 同一 asset 的旧事务未收尾,且后继 committed 事务链、当前 manifest/revision 与最终文件全部可证明 | 旧事务进入 `superseded`,不回滚、不覆盖当前正式图、不重放旧事件;证据不完整仍进入对账 |
|
||||
| A34 | 同资源并发正式提交 | 同一 project/draft/asset 存在 prepared 或 reconciliation 事务时再次提交 | 拒绝新提交并要求先安全恢复;已 committed/rolled-back/superseded 事务不阻塞后续提交 |
|
||||
| A35 | 精修文件名包含历史提交后缀 | 后续精修重新打开当前 `localPath`,或再次生成 / 设为最终图 | 统一剥离文件名末尾一个或多个 `--<uuid>` 后缀并规范化为合法 1..=80 字符显示名;生成与最终提交使用同一结果 |
|
||||
| A36 | 确定性提交参数无效 | 候选提交名称或用途在校验阶段失败 | 在读取候选、staging、transaction 或 ledger 写入前零副作用失败;UI 作为输入校验错误允许继续编辑,不触发安全恢复 |
|
||||
| A37 | 候选首次确认 | 生成完成后与旧 autosave 并发,或重复打开已确认候选 | 前端把候选确认排入草稿保存 FIFO,并在提交、导入、生成、归档和放弃草稿前等待确认屏障;Tauri 在草稿锁内只为当前权威草稿中仍存在且尚未确认的候选更新私有 ledger,不改写草稿或推进 revision。普通 update 在确认前继续把候选层合回旧保存,重复确认无写入,确认后的显式删除仍允许 |
|
||||
| A38 | 稳定运行入口 | 精修替换已在游戏源码中引用的图片,或继续精修旧版本事务创建的资源 | manifest 指向不可变正式版本,同时原稳定入口路径不变并刷新为新版本字节;新事务可从旧事务 `manifest.before.json` 迁移稳定入口身份,幂等重放和事务恢复会修复缺失或不匹配入口,游戏源码不需要改路径 |
|
||||
| 编号 | 宿主/场景 | 前置或故障注入 | 必须结果 |
|
||||
| ---- | ----------------------------------------------- | ----------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| A01 | Web + Tauri 共享源码 | 构建两个宿主 | 两者 import 同一 core/react;客户端无画布目录镜像 |
|
||||
| A02 | 新增图片 | create,导入/编辑/保存 | 新 asset 落盘并进入 manifest、投影、依赖图和两种布局;无需刷新 |
|
||||
| A03 | 精修图片 | refine 已有本地图片 | 原文件/asset 保留,新建 asset,source.resourceId 补齐且血缘包含源 |
|
||||
| A04 | 基础编辑 | 平移、缩放、多选、移动/缩放、层序、显隐、锁定、翻转、分组 | 两宿主行为和序列化 fixture 一致,undo/redo 最多 60 步 |
|
||||
| A05 | 生成成功 | 响应正常 | 只新增一次 generation 结果,草稿 CAS 递增且可继续编辑/保存 |
|
||||
| A06 | 生成失败 | 上游确定失败 | 状态可恢复,不创建正式资产,不用新幂等键自动重试 |
|
||||
| A07 | 生成响应丢失 | 上游已受理、客户端未收到结果 | 以原 operation/idempotency 对账,只产生一份结果/扣费 |
|
||||
| A08 | 保存成功 | project/draft revision 匹配 | file -> manifest/revision -> 回读 -> ledger/draft -> event 顺序成立 |
|
||||
| A09 | 重复保存 | 相同 commit/key/指纹 | 返回 already-committed,asset/revision/eventId 均不重复 |
|
||||
| A10 | 幂等冲突 | 同 key 或 commitId、不同指纹 | 失败关闭,原 ledger/文件/manifest 不变 |
|
||||
| A11 | 两窗口并发 | 相同 expectedRevision 同时提交 | 最多一笔 committed,另一笔 typed conflict,不覆盖成功方 |
|
||||
| A12 | draft 并发 | 相同 expectedDraftRevision 更新 | 最多一笔 updated,另一笔返回最新完整 draft |
|
||||
| A13 | 崩溃:首个/全部事务快照、journal 或 prepared 后 | 尚未装图片 | 无 ledger 的未发布 transaction 只在正式文件不存在且 manifest/revision 仍为 before 时清理;prepared 安全回滚或继续,不生成幽灵 asset |
|
||||
| A14 | 崩溃:图片后 | manifest 前 | 仅在摘要/before 全匹配时删除新文件,否则 reconciliation-required |
|
||||
| A15 | 崩溃:manifest 后 | revision 前 | before/after 匹配时前向补 revision,否则 reconciliation-required |
|
||||
| A16 | 崩溃:revision 后 | ledger/event 前 | 回读验证后补 ledger/draft,并重发相同 eventId |
|
||||
| A17 | 崩溃:emit 后 | 投递标记前 | 允许重复事件,前端 eventId 去重且不重复选中/布局 |
|
||||
| A18 | 切项目后的迟到保存 | 提交在途时打开其它项目 | 当前项目 UI 不变;旧项目缓存可按精确身份更新 |
|
||||
| A19 | 切模式/离开流程 | 提交在途时进入 run/overview/新 session | 不切回素材画布、不抢焦点,正式结果仍可投影到对应项目 |
|
||||
| A20 | 改选择后的迟到保存 | 等待时选择其它资源 | 保持用户选择,新资源只进入投影和布局 |
|
||||
| A21 | 搜索隐藏新资源 | query epoch 改变且不匹配新资源 | 不清搜索、不自动选中,提示并提供显式清除/定位动作 |
|
||||
| A22 | 即时投影 | 提交后不刷新/不重开 | manifest、资源卡、依赖图输入、布局和允许时的选中全部完成 |
|
||||
| A23 | 草稿损坏/身份错配 | 损坏 JSON、未知 schema、项目路径被重建 | 失败关闭,不用空草稿覆盖,不创建其它项目副作用 |
|
||||
| A24 | 锁与恢复 | 活锁 mtime 很旧、进程退出、Windows/Unix | 不按时间/PID删锁;句柄释放后正常取得同一锁入口 |
|
||||
| A25 | 容量边界 | 2 MiB/4096 层/64 MiB/像素上限边界及超限 | 边界内成功,超限零副作用且错误不泄露绝对路径/密钥 |
|
||||
| A26 | 导出 | PNG/JPEG/WebP | Web 下载/云端、Tauri 保存对话框均成功;共享 UI 不接收绝对路径 |
|
||||
| A27 | 取消 | clean、dirty、generating、staging、committing | 分别符合第 12 节;committing 不伪装成可取消 |
|
||||
| A28 | 恢复草稿 | 主文件损坏但恢复副本可信/不可信 | 可信副本恢复到 clean history 基线;不可信进入对账,不猜测 |
|
||||
| A29 | 登录刷新重放 | context、参考图准备或首次提交返回 401,刷新后以相同 generationId 和幂等身份重放 | 401 账本保持可恢复且第二次真实访问平台;403 直接失败且不刷新;远端最多受理一次 |
|
||||
| A30 | 生成卡片拖动 | 生成中拖动占位卡片 | 卡片位置按画布坐标更新并持久化到 generation record,任务状态刷新不覆盖用户位置 |
|
||||
| A31 | 精修最终图唯一性 | 选择另一候选图设为最终图 | 入口原图和所有其他候选保持各自快照,只有 `lastCommit.sourceLayerId` 标识唯一正式候选,更新正式 asset 不反向改写历史图层 |
|
||||
| A32 | 精修默认比例 | 打开图片的快速编辑 | 按原图宽高映射到最接近的支持比例(1:1、2:3、3:2、9:16、16:9);尺寸无效时回退 1:1 |
|
||||
| A33 | 旧提交被后继提交取代 | 同一 asset 的旧事务未收尾,且后继 committed 事务链、当前 manifest/revision 与最终文件全部可证明 | 旧事务进入 `superseded`,不回滚、不覆盖当前正式图、不重放旧事件;证据不完整仍进入对账 |
|
||||
| A34 | 同资源并发正式提交 | 同一 project/draft/asset 存在 prepared 或 reconciliation 事务时再次提交 | 拒绝新提交并要求先安全恢复;已 committed/rolled-back/superseded 事务不阻塞后续提交 |
|
||||
| A35 | 精修文件名包含历史提交后缀 | 后续精修重新打开当前 `localPath`,或再次生成 / 设为最终图 | 统一剥离文件名末尾一个或多个 `--<uuid>` 后缀并规范化为合法 1..=80 字符显示名;生成与最终提交使用同一结果 |
|
||||
| A36 | 确定性提交参数无效 | 候选提交名称或用途在校验阶段失败 | 在读取候选、staging、transaction 或 ledger 写入前零副作用失败;UI 作为输入校验错误允许继续编辑,不触发安全恢复 |
|
||||
| A37 | 候选首次确认 | 生成完成后与旧 autosave 并发,或重复打开已确认候选 | 前端把候选确认排入草稿保存 FIFO,并在提交、导入、生成、归档和放弃草稿前等待确认屏障;Tauri 在草稿锁内只为当前权威草稿中仍存在且尚未确认的候选更新私有 ledger,不改写草稿或推进 revision。普通 update 在确认前继续把候选层合回旧保存,重复确认无写入,确认后的显式删除仍允许 |
|
||||
| A38 | 稳定运行入口 | 精修替换已在游戏源码中引用的图片,或继续精修旧版本事务创建的资源 | manifest 指向不可变正式版本,同时原稳定入口路径不变并刷新为新版本字节;新事务可从旧事务 `manifest.before.json` 迁移稳定入口身份,幂等重放和事务恢复会修复缺失或不匹配入口,游戏源码不需要改路径 |
|
||||
|
||||
阶段一至五最终审计只有在矩阵对应的纯模型、共享 React、Web adapter、Tauri adapter、Rust 持久化与 AppSurface 测试全部通过后,才可宣称图片素材创作正式闭环完成。
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,13 +8,13 @@
|
||||
|
||||
## 0. 摘要
|
||||
|
||||
| # | 缺陷 | 表现 | 性质 |
|
||||
|---|---|---|---|
|
||||
| 1 | 前端保存设置时把 `agentMode` 硬写成 `codex_app_server` | UI 里换 provider 只改了 `llm.*`,运行模式换不掉,且界面上看不到这个字段 | 产品缺陷 |
|
||||
| 2 | `codex_app_server` 模式把第三方端点喂给 codex | apiKind≠openai_responses 时秒挂;否则 413 + 工具误用,180 秒超时后留下待核对的孤儿请求 | 模式前提未被约束 |
|
||||
| 3 | `provider` 模式下 `tool_choice=required` 与 DeepSeek 思考模式互斥 | 首个 tool-plan 请求 400,整个 runtime 起不来 | 参数空间缺一个值 |
|
||||
| 4 | 普通 action 批次带 plan update 时,两条预检规则互斥 | 「更新计划 + 委派专业 Agent」同一轮返回就报「批次成员身份或顺序不匹配」 | **本分支回归**(已修) |
|
||||
| 5 | `llm.stream` 只记录配置,不驱动 Provider tool-plan 传输 | 要求 `stream=true` 的网关第一发 tool-plan 得到 HTTP 400,整轮不可用 | 传输配置失效(已修) |
|
||||
| # | 缺陷 | 表现 | 性质 |
|
||||
| --- | ----------------------------------------------------------------- | -------------------------------------------------------------------------------------- | ---------------------- |
|
||||
| 1 | 前端保存设置时把 `agentMode` 硬写成 `codex_app_server` | UI 里换 provider 只改了 `llm.*`,运行模式换不掉,且界面上看不到这个字段 | 产品缺陷 |
|
||||
| 2 | `codex_app_server` 模式把第三方端点喂给 codex | apiKind≠openai_responses 时秒挂;否则 413 + 工具误用,180 秒超时后留下待核对的孤儿请求 | 模式前提未被约束 |
|
||||
| 3 | `provider` 模式下 `tool_choice=required` 与 DeepSeek 思考模式互斥 | 首个 tool-plan 请求 400,整个 runtime 起不来 | 参数空间缺一个值 |
|
||||
| 4 | 普通 action 批次带 plan update 时,两条预检规则互斥 | 「更新计划 + 委派专业 Agent」同一轮返回就报「批次成员身份或顺序不匹配」 | **本分支回归**(已修) |
|
||||
| 5 | `llm.stream` 只记录配置,不驱动 Provider tool-plan 传输 | 要求 `stream=true` 的网关第一发 tool-plan 得到 HTTP 400,整轮不可用 | 传输配置失效(已修) |
|
||||
|
||||
缺陷 1~3 叠加的结果:**当前代码里没有任何一组配置能让 DeepSeek 跑起来**。缺陷 4 与 provider 无关,换成 `gpt-5.6-terra` 打通 LLM 链路后才暴露出来。
|
||||
|
||||
@@ -136,20 +136,25 @@ error=unable to locate image at `<project>/memory/README.md`: (os error 2)
|
||||
把 `agentMode` 切换为 `provider` 后重跑仍然失败;脱敏后的上游错误为:
|
||||
|
||||
```json
|
||||
{"error":{"message":"Thinking mode does not support this tool_choice",
|
||||
"type":"invalid_request_error","code":"invalid_request_error"}}
|
||||
{
|
||||
"error": {
|
||||
"message": "Thinking mode does not support this tool_choice",
|
||||
"type": "invalid_request_error",
|
||||
"code": "invalid_request_error"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 复现矩阵(直接打 DeepSeek,非流式)
|
||||
|
||||
| 请求 | 结果 |
|
||||
|---|---|
|
||||
| `tool_choice: "required"` | **400** Thinking mode does not support this tool_choice |
|
||||
| `tool_choice: "auto"` | 200 |
|
||||
| `tool_choice: "none"` | 200 |
|
||||
| `tool_choice: "required"` + `reasoning.effort: "none"` | **200**,且正常返回 `function_call` |
|
||||
| `tool_choice: "required"` + `reasoning.effort: "minimal"` | 400 |
|
||||
| `tool_choice: "required"` + `thinking: {type:"disabled"}` | 400 |
|
||||
| 请求 | 结果 |
|
||||
| --------------------------------------------------------- | ------------------------------------------------------- |
|
||||
| `tool_choice: "required"` | **400** Thinking mode does not support this tool_choice |
|
||||
| `tool_choice: "auto"` | 200 |
|
||||
| `tool_choice: "none"` | 200 |
|
||||
| `tool_choice: "required"` + `reasoning.effort: "none"` | **200**,且正常返回 `function_call` |
|
||||
| `tool_choice: "required"` + `reasoning.effort: "minimal"` | 400 |
|
||||
| `tool_choice: "required"` + `thinking: {type:"disabled"}` | 400 |
|
||||
|
||||
`/chat/completions` 与 `/responses` 两条 wire、`deepseek-v4-flash` 与 `deepseek-v4-pro` 两个模型表现完全一致。即:**DeepSeek 支持强制工具调用,但必须先关掉思考模式,而唯一能关掉它的开关是 `reasoning.effort: "none"`。**
|
||||
|
||||
@@ -186,10 +191,10 @@ Agent Runtime Provider action 批次成员身份或顺序不匹配:index=0
|
||||
|
||||
两轮的 `tool_plan.protocol` 记录:
|
||||
|
||||
| loop | 模型返回的 function call | 结果 |
|
||||
|---|---|---|
|
||||
| 1 | `runtime_tool_agent_goal_contract` | ok |
|
||||
| 2 | `update_agent_plan` + `runtime_tool_agent_delegate` | 预检失败 |
|
||||
| loop | 模型返回的 function call | 结果 |
|
||||
| ---- | --------------------------------------------------- | -------- |
|
||||
| 1 | `runtime_tool_agent_goal_contract` | ok |
|
||||
| 2 | `update_agent_plan` + `runtime_tool_agent_delegate` | 预检失败 |
|
||||
|
||||
即「更新计划 + 委派专业 Agent」同一轮返回——总控最常规的动作。
|
||||
|
||||
@@ -261,6 +266,7 @@ let expected_member_plan_update = batch
|
||||
### P0 —— 让第三方 provider 可用
|
||||
|
||||
1. **`reasoningEffort` 增加 `none`**
|
||||
|
||||
- [platform-llm lib.rs:195](../../server-rs/crates/platform-llm/src/lib.rs):`LlmResponseReasoningEffort` 加 `None` 变体,`as_str()` 返回 `"none"`;注意仓库内对该枚举有多处 exhaustive match(`Max` 刚加时踩过),需一并补齐。
|
||||
- [config.rs:145](../../apps/ai-game-creator-shell/src-tauri/src/config.rs) `parse_game_creator_llm_reasoning_effort` 接受 `none`,同步 :155 的错误文案。
|
||||
- [types.ts:605](../../apps/ai-game-creator-shell/src/app/types.ts) `gameCreatorLlmReasoningEfforts` 加 `'none'`;RuntimeConfigDialog 的默认表(:40~:65)与下拉项同步。
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
# 【技术说明】DirectProject 未消费用户上传权威文档
|
||||
|
||||
- 首次记录:2026-08-30
|
||||
- 问题类型:DirectProject 上下文消费缺陷 / 可审计性缺陷
|
||||
- 影响范围:用户上传文档并在正文中明确指定其为本次建造依据的“做游戏”链路
|
||||
- 当前状态:待排期,本文只用于提 Issue,暂不修复
|
||||
|
||||
## 0. Issue 摘要
|
||||
|
||||
当用户在正文中明确说明“我上传了一份 GDD,里面包含某些具体内容,请按照这份 GDD 做游戏”时,做游戏 Agent 没有可靠地把该附件当作本轮权威规格来检索、读取和消费。
|
||||
|
||||
这不是“上传附件功能失败”:附件已经成功复制到新项目并登记。问题在于,DirectProject 只收到用户正文和项目路径,没有收到“用户上传了哪些附件、附件的真实项目路径、哪个附件被正文指认为权威参考”这类一等上下文;Agent 只能自行猜测并搜索项目文件。
|
||||
|
||||
当前实现虽然存在条件性的 native 文件检索路径,但该路径既不是稳定的应用层契约,也没有对应的 Direct 读取审计记录。因此一次 run 结束后无法可靠回答:Agent 是否发现了附件、是否读取了附件、读取结果是否进入了后续设计和代码决策。
|
||||
|
||||
## 1. 预期行为
|
||||
|
||||
本 Issue 讨论的预期行为有一个重要前提:**不是所有上传文档都自动视为 GDD,也不是所有附件都必须被读取。**
|
||||
|
||||
只有当用户在正文或交互中明确表达类似以下意图时,相关文档才应被视为本轮权威参考:
|
||||
|
||||
> 我上传了一份 GDD,里面有探测艇、脉冲射击、敌方弹幕、模块选择和棱镜母体,请按照这份 GDD 做游戏。
|
||||
|
||||
在这一前提下,Agent 应能够:
|
||||
|
||||
1. 知道本轮存在用户上传的参考文档;
|
||||
2. 找到该文档在当前项目中的真实路径;
|
||||
3. 读取文档内容;
|
||||
4. 将文档内容用于后续游戏设计、代码和资源决策;
|
||||
5. 在可共享、可复核的审计信息中留下足以判断上述行为是否发生的记录。
|
||||
|
||||
用户手写 GDD、通过外部功能生成后导入的 GDD、普通上传的 GDD,以及“做成游戏”入口带入的 GDD,在这里都属于同一个用户意图场景。是否来自“做方案”审批链路不是必要前提。
|
||||
|
||||
## 2. 实际现象
|
||||
|
||||
在 `gameagent-77b5aa31` 这次 2026-08-30 的 Direct run 中:
|
||||
|
||||
- 用户正文明确要求先阅读附件中的 `fast_gdd.md`,并以其作为主要依据;
|
||||
- 文件成功复制到新项目:
|
||||
|
||||
```text
|
||||
assets/uploads/upload-1788083777445-fast_gdd.md
|
||||
```
|
||||
|
||||
- 原策划项目文件与上传副本大小均为 `7944` 字节,SHA-256 一致,说明复制没有损坏;
|
||||
- 最终游戏却生成了《星光收集者》:星星收集、荆棘碰撞、左右移动、生命值;
|
||||
- 原 GDD 的核心实体和循环(探测艇、脉冲射击、敌人、弹幕、模块、风险岔路、棱镜母体)没有体现在最终游戏中;
|
||||
- 最终美术生成 prompt 只有“轻量、明快、暖色纸张质感背景、可爱的主角、可收集物、障碍物和简洁 HUD”等泛化描述;
|
||||
- 浏览器验收的 `expectedText` 为空,没有对 GDD 语义做断言。
|
||||
|
||||
因此最终产物表现为一个内部自洽、但与用户指定 GDD 不同类型的通用收集类小游戏。
|
||||
|
||||
## 3. 代码层证据
|
||||
|
||||
### 3.1 附件不进入 Direct turn 的结构化输入
|
||||
|
||||
首页正文和附件在前端被分开处理;附件节点不会进入正文 prompt,而是作为单独的附件集合保存。
|
||||
|
||||
[richTextToPrompt.tsx](../../apps/ai-game-creator-shell/src/view/home/components/RichInputArea/richTextToPrompt.tsx:21)
|
||||
|
||||
进入项目工作台后,`ProjectSupervisor` 只收到项目路径、manifest、初始消息和创作类型,没有附件字段。
|
||||
|
||||
[WorkspaceLauncher.tsx](../../apps/ai-game-creator-shell/src/features/app-shell/WorkspaceLauncher.tsx:335)
|
||||
|
||||
[model.ts](../../apps/ai-game-creator-shell/src/features/app-shell/model.ts:29)
|
||||
|
||||
Direct Codex 调用的输入也只有:
|
||||
|
||||
```ts
|
||||
{
|
||||
projectPath, prompt, clientTurnId, creationType;
|
||||
}
|
||||
```
|
||||
|
||||
[App.tsx](../../apps/ai-game-creator-shell/src/App.tsx:5484)
|
||||
|
||||
其中没有附件列表、附件路径、附件 hash 或附件正文。
|
||||
|
||||
### 3.2 上传后路径被重写,Agent 不会自动得到真实路径
|
||||
|
||||
上传实现会把文件写入 `assets/uploads/upload-<timestamp>-<原文件名>`,例如 `fast_gdd.md` 会变成 `upload-...-fast_gdd.md`。
|
||||
|
||||
[assets.rs](../../apps/ai-game-creator-shell/src-tauri/src/assets.rs:460)
|
||||
|
||||
上传结果会写入项目的 `.agent/manifest.json` 和 `.agent/agent.db`,但这些是项目持久化产物,不是自动注入到 Direct LLM 请求的上下文;`.agent` 还是 Direct 的控制面边界,不能作为普通项目文档让 Agent 读取。
|
||||
|
||||
### 3.3 代码中存在条件性的主动检索路径,但不是稳定契约
|
||||
|
||||
DirectProject 的 app-server 使用项目根作为 `cwd`,并在可用配置下保留 native shell / 命令能力,因此 Agent 理论上可以:
|
||||
|
||||
```text
|
||||
搜索 fast_gdd
|
||||
→ 找到 assets/uploads/upload-...-fast_gdd.md
|
||||
→ 用 native 命令读取正文
|
||||
```
|
||||
|
||||
[codex_app_server.rs](../../apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server.rs:1482)
|
||||
|
||||
[codex_app_server.rs](../../apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server.rs:1160)
|
||||
|
||||
但这条路径有三个问题:
|
||||
|
||||
- 需要 Agent 自己判断“这份附件值得检索”,应用没有把附件关系告诉它;
|
||||
- `agc_list_project_files` 只能返回路径、大小和类型,不返回 Markdown 正文;
|
||||
- DirectProject 没有接入旧 Agent Runtime 的 `file.read` / `project.search` 工具目录,读取能力取决于 Direct app-server 的 native 工具配置。
|
||||
|
||||
[direct_tools_mcp.rs](../../apps/ai-game-creator-shell/src-tauri/src/agent/direct_tools_mcp.rs:214)
|
||||
|
||||
[agent_native_tools.rs](../../apps/ai-game-creator-shell/src-tauri/src/agent/agent_native_tools.rs:1349)
|
||||
|
||||
因此当前代码不能称为“附件已可靠进入 Agent 上下文”,只能称为“Agent 在部分配置下可能自行发现项目文件”。
|
||||
|
||||
## 4. 持久化与取证现状
|
||||
|
||||
### 4.1 这次 Direct run 没有持久化读取记录
|
||||
|
||||
`gameagent-77b5aa31/.agent/agent.db` 共 12 条记录,类型只有:
|
||||
|
||||
```text
|
||||
project.init 1
|
||||
asset.register 5
|
||||
canvas.asset_generate 3
|
||||
conversation.message 3
|
||||
```
|
||||
|
||||
其中没有:
|
||||
|
||||
```text
|
||||
file.read
|
||||
file.list
|
||||
project.search
|
||||
command.exec
|
||||
agent.runtime.tool_observation
|
||||
agent.runtime.action_receipt
|
||||
```
|
||||
|
||||
这能证明上传、资源生成、游戏入口写入和对话消息被记录,但不能证明 Direct app-server 没有执行过 native 文件读取。
|
||||
|
||||
### 4.2 Direct app-server 的 native read 不在当前 Agent DB 审计范围内
|
||||
|
||||
Direct app-server 使用 ephemeral thread。stdout 中的 `item/started`、`item/completed`、`commandExecution` 等事件只在运行期间被解析成有限的活动状态,再通过 Tauri event 发给前端;当前实现没有把 native 命令、读取路径、读取结果或读取 hash 追加到项目 `agent.db`。
|
||||
|
||||
[codex_app_server.rs](../../apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server.rs:883)
|
||||
|
||||
[codex_app_server.rs](../../apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server.rs:2496)
|
||||
|
||||
相对地,旧 Agent Runtime 的 `file.read` 会写入 `agent.runtime.action_receipt` 和 `agent.runtime.tool_observation`,因此旧路径可以审计到读取了哪个文件。
|
||||
|
||||
[main_loop.rs](../../apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/main_loop.rs:3347)
|
||||
|
||||
这说明问题不是项目完全没有持久化能力,而是 DirectProject 的文件读取路径绕过了现有可审计工具链。
|
||||
|
||||
## 5. 问题定性
|
||||
|
||||
本 Issue 应定性为:
|
||||
|
||||
> 当用户明确把某个上传文档指定为本轮游戏制作的权威参考时,DirectProject 没有可靠地把“用户—附件—当前任务”的关系传给 Agent,也没有让文档发现、读取和后续消费形成可观察的工作流证据。结果是 Agent 可以从泛化的游戏目标出发完成一个可运行产物,却不一定消费用户指定的文档规格。
|
||||
|
||||
这属于用户意图和 Agent 上下文消费之间的契约缺失,不属于 GDD 审批状态传递错误,也不属于附件复制损坏。
|
||||
|
||||
## 6. 明确排除的归因与修复方向
|
||||
|
||||
以下内容不应作为本问题的主要根因,也不应直接作为本 Issue 的修复目标:
|
||||
|
||||
### 6.1 不把 `approvedGddRef` / approval receipt 缺失视为根因
|
||||
|
||||
用户手写 GDD、外部生成后导入的 GDD、普通文件上传的 GDD,都不一定存在 `approvedGddRef` 或审批 receipt,但只要用户在 prompt 中明确指定“按照这份 GDD 做游戏”,Agent 就应该能够正确消费它。
|
||||
|
||||
因此,缺失审批状态绑定不能解释这类通用失败。
|
||||
|
||||
### 6.2 不把所有上传文档强制当成 GDD
|
||||
|
||||
用户上传的文件可能是图片、素材说明、参考资料、README、代码片段或与游戏无关的文档。不能因为文件被上传,就默认它是策划案或本轮的权威规格。
|
||||
|
||||
只有用户明确建立“这份文档用于本轮任务”的关系时,才进入本文讨论的语义范围。
|
||||
|
||||
### 6.3 不采用“把所有上传文档正文直接拼进 prompt”作为通用修复
|
||||
|
||||
附件可能很大、可能是二进制、可能包含不可信内容,也可能只是可选参考。把所有上传文件正文无条件塞入 prompt 会混淆普通附件、参考资料和权威任务规格,也改变当前附件模型的边界。
|
||||
|
||||
本文不要求把上传文件正文统一注入 prompt。
|
||||
|
||||
### 6.4 不采用“所有附件必须先读取,否则一律阻断”作为通用硬门禁
|
||||
|
||||
对于用户没有要求使用的附件,系统不应强制 Agent 读取;对于非文本附件,也不能套用 Markdown 文本读取规则。
|
||||
|
||||
本文关注的是用户明确指定文档为权威参考时的消费缺失,不要求把所有附件都改造成强制读取工作流。
|
||||
|
||||
## 7. Issue 验收口径
|
||||
|
||||
本问题修复完成后,至少应能验证以下事实,但具体实现方式不在本文展开:
|
||||
|
||||
- 用户明确指定某个上传文档为本轮任务依据时,Agent 能够发现并读取对应文档;
|
||||
- 用户未指定的普通附件不会被自动当成 GDD 或强制纳入任务;
|
||||
- Agent 是否发现、读取以及使用该文档,应能从可共享的 run 审计产物或等价的可审计证据中判断;
|
||||
- 同一语义在“做成游戏”、普通上传、外部生成后导入和用户手写文档等入口下不依赖审批 receipt 才成立;
|
||||
- 文档被读取后,至少有一种可验证方式能判断其关键内容是否进入了后续任务上下文,而不是只记录了文件存在。
|
||||
|
||||
具体修复方案、上下文协议设计和持久化字段设计另行讨论,本 Issue 不预设实现方案。
|
||||
|
||||
## 8. 关联产物
|
||||
|
||||
- 策划 run ID:`gameagent-cd7f6c81`
|
||||
- 做游戏 run ID:`gameagent-77b5aa31`
|
||||
- 上传 GDD(项目相对路径):`assets/uploads/upload-1788083777445-fast_gdd.md`
|
||||
- 审计账本方案(已落地,不关闭本 Issue):[`【技术方案】Direct回合行为审计账本-2026-08-31.md`](./【技术方案】Direct回合行为审计账本-2026-08-31.md)
|
||||
- 建议随 Issue 附上或引用对应 run 的以下复核材料:
|
||||
- Direct 对话:`.agent/conversations/project.jsonl`
|
||||
- Direct Agent DB:`.agent/agent.db`
|
||||
- Direct manifest:`.agent/manifest.json`
|
||||
- 最终游戏:`game/index.html`
|
||||
- 浏览器验证:`.agent/runtime/direct-codex-browser-validation/6/attempt-3/validation.json`
|
||||
|
||||
上述材料应以 Issue 附件、仓库归档或团队共享存储的形式提供;本文不依赖某位开发者电脑上的绝对路径。
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user