合并 origin/master 至 feat/agc-use-ui-design

- 修复 ui-editor 调用 LLM 与实际配置不一致 (#235)
- 本地资源注册与后台表查询 (#232)
- 清除策划 agent 不可达分支 (#234)
- Direct 首轮带上本轮附件的项目路径映射 (#223)
- 修复项目页布局高度 (#228)
- 修复 AGC Windows ACL 提权与文件访问边界 (#211)
- 批准 GDD 回填做游戏入口 (#210)
- 优化 Game Agent 资源画布展示与默认缩放 (#208)

Co-authored-by: Junie <junie@jetbrains.com>
This commit is contained in:
2026-09-01 16:45:15 +08:00
74 changed files with 10339 additions and 1223 deletions
+2
View File
@@ -20,6 +20,8 @@
## AI 游戏创作与 Agent Runtime
- [AI 游戏创作智能体 App 实施计划](./technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md):当前 DirectProject、受控语义工具、UI workflow、资源和运行时合同。
- [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):当前工作台页面和验收边界。
- [立项策划 AgentFast GDD](<./technical/【技术方案】立项策划AgentFast GDD-2026-08-10.md>):当前策划入口、审批和恢复合同。
- [GameAgent 资源自由画板与快速编辑](./technical/【技术方案】GameAgent资源自由画板与快速编辑-2026-08-20.md)
@@ -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 阶段七完整验收
@@ -15,8 +15,32 @@
- 关联文档:相关 PRD、技术文档、提交或 Issue
```
## 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/【技术方案】立项策划AgentFast 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 条截断、写盘失败不 panicsidecar 渲染与 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、工作台首轮 invokeSupervisor / 做方案首轮忽略附件 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 +55,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、澄清续跑或提交前质量返工里把未确认项标成用户审批修改,审批卡显示「已确认」。
@@ -1,6 +1,6 @@
# 文档地图与阅读索引
更新时间:`2026-08-25`
更新时间:`2026-08-31`
## 阅读顺序
@@ -24,9 +24,11 @@ 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/【技术方案】立项策划AgentFast GDD-2026-08-10.md`
4. `docs/technical/【技术方案】GameAgent资源自由画板与快速编辑-2026-08-20.md`
5. `docs/【技术方案】UI工作流资源桥接与Runtime执行-2026-08-24.md`
6. UI 编辑器、宿主壳和当前测试专题文档
4. `docs/technical/【技术方案】DirectProject本轮附件路径映射-2026-08-31.md`
5. `docs/technical/【技术方案】Direct回合行为审计账本-2026-08-31.md`
6. `docs/technical/【技术方案】GameAgent资源自由画板与快速编辑-2026-08-20.md`
7. `docs/【技术方案】UI工作流资源桥接与Runtime执行-2026-08-24.md`
8. UI 编辑器、宿主壳和当前测试专题文档
图片画布 / 媒体生成:
@@ -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。
@@ -555,7 +556,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 sidecar2026-07-31 起 dependency 模式增加不持久化的原生 SVG 关系图层。2026-08-03 mentor 决定暂缓资源总览卡片拖动,当前卡片不挂载 Pointer Down / Move / Up / Cancel 拖动入口,只允许自动布局和点击聚焦。聚焦态替换中央主视窗内容,保留左侧导航、右侧对话和底部 Agent 状态栏,退出后恢复搜索、布局模式、滚动位置与选中资源;不提供通用工具栏、工具侧边栏或可拖动标题栏。阶段四已补齐安全本地文档、扩展美术媒体与音频聚焦,正文独立滚动,视频 / 音频使用内置媒体控件,失败显示空态。2026-08-26 视觉验收修正:资源总览初次适配与复位最多以 `1.5` 倍缩放卡片,避免低尺寸卡片位图插值放大成糊图;用户主动缩放仍沿用通用画布倍率。美术资源聚焦态改为视口级大预览,保留原始资源读取与元数据,不生成第二份缩略图,图片 / 视频预览按弹窗可用高度展示并允许正文滚动。该资源总览边界不限制后续素材创作无限画布内的图片图层移动/缩放、生成和正式回写。
- 资源卡支持点击聚焦、搜索和类型筛选。2026-07-28 起完成两套二维坐标与本地 CAS sidecar2026-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 与回滚合同完备前不向普通用户开放。
@@ -1254,7 +1255,7 @@ game-project/
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 文件。
## 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 编排覆盖
@@ -0,0 +1,262 @@
# DirectProject 本轮附件路径映射
- 日期:2026-08-31
- 状态:现行合同(已按本文落地)
- 问题:Gitea issue #212DirectProject 未消费用户上传权威文档)
- 关联入口: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 turncwd = 项目根)
```
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,389 @@
# 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_startfail-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(&params)`
- **不要** 把 `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 artseq 是 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 关单。
@@ -3,7 +3,7 @@
- 日期:2026-08-10
- 适用范围:AI 游戏创作独立 App、Project Supervisor、Agent Runtime、本地项目策划 sidecar 与后续完整构建准入
> 当前口径(2026-08-25):以本文件中标注的 D11 / 最新修订和当前 `apps/ai-game-creator-shell` 实现为准。D6~D9 等被明确标注为作废或被取代的段落仅保留推导背景,不得作为现行拓扑、入口或 Runtime 真相;产品入口与 DirectProject 总体口径见 `docs/README.md` 和 App 实施计划。
> 当前口径(2026-08-30):以本文件中标注的 D11 / 最新修订和当前 `apps/ai-game-creator-shell` 实现为准。D6~D9 等被明确标注为作废或被取代的段落仅保留推导背景,不得作为现行拓扑、入口或 Runtime 真相;产品入口与 DirectProject 总体口径见 `docs/README.md` 和 App 实施计划。
## 1. 背景与目标
@@ -180,7 +180,7 @@ D9/D10 描述的「manifest ready-task 调度器在 Supervisor 下游启动策
| 构建引用 | `approvedGddRef {gddId, version, fingerprint}` |
| 现有 design 组 UI 名 | `设计实现组`,替代原“策划 Agent”卡片名称 |
| 新组件名 | `GDD 审批卡` |
| 固定入口动作 | `直接开建``开始完整制作``批准并开建` |
| 固定入口动作 | 当前客户端为 `做成游戏`:读取 `game/fast_gdd.md`,直接创建自动游戏工作区、导入参考附件并以固定建造指令启动 Direct Codex;不再回首页等待用户二次提交 |
`approvalRequestId``responseId` 是两个不同的持久 ID:前者由 Runtime 在 GDD 提交前生成、进入不可变 GDD,一张审批卡终身不变;后者由 UI 在用户执行一次决定时生成,并在传输重试中复用。不得继续用含义不明的单个 `requestId` 同时承担两种职责。
@@ -278,7 +278,7 @@ flowchart TD
SPLAN -->|"在自己的 runtime/session 上建 pending,向用户提问"| U
SPLAN -->|"answersSha256 绑回 delivery,再发 continuation 委派<br/>(最多 3 轮,受 clarification_round 上限约束,见第 23.5 节)"| PLANAGENT
PLANAGENT --> GDD["不可变 GDD + approve receipt"]
GDD -->|"用户动作:开始完整制作"| BUILD
GDD -->|"用户动作:做成游戏;读取并导入 game/fast_gdd.md"| BUILD["自动创建游戏工作区<br/>参考附件:fast_gdd.md<br/>固定建造指令"]
U -.->|"直接开建"| BUILD
BUILD --> DAG["现行 16 任务 DAG"]
U --> CHAT
@@ -1517,7 +1517,8 @@ type PlanningBaselineInput =
- 仅首页“做方案”新建项目提交 `standard + project-supervisor-plan`2026-08-13 按 D11 更正,旧值 `project-supervisor-plan-chat` 作废);“做游戏”和“做素材”保持 `autonomous-game-build` 直接开建。前端只提交 Supervisor 根 run 的身份,**不提交也不感知策划子 Agent**——后者由 Supervisor 在服务端通过 `agent.delegate` 派生,页面侧不得直接创建或引用它。
- 项目页新建、打开既有项目和 Godot 导入不新增“进入立项策划”入口,保持现行构建/打开语义;只有已存在 planning sidecar 或 active plan lineage 的项目恢复原有策划链路。
- 策划阶段聊天输入属于当前 run:有活跃决策卡/审批卡时,输入回到该卡片对应 action;无 active run 时才可创建新的 plan continuation。
- approved 后显示“开始完整制作”;“批准并开建”只是先审批、后开建的快捷交互,不合并后端命令或 durable 记录
- approved 后显示“做成游戏”。点击后读取当前有效的 `game/fast_gdd.md`,直接沿现有自动做游戏链路创建新的工作区、导入 `text/markdown` 参考附件,并以固定建造指令作为 `initialSupervisorMessage` 自动启动 Direct Codex;固定指令只说明 GDD 覆盖的栏目,不根据具体 GDD 内容生成总结。该动作不回首页等待二次提交,不把原项目的 `approvedGddRef` 复制到新项目
- 用户仍可在项目工作台继续补充需求;普通首页“做游戏”入口的手动提交行为保持不变。
### 18.2 GDD 审批卡
@@ -1529,7 +1530,7 @@ type PlanningBaselineInput =
- command 进行中禁用重复点击;另一个窗口先决定后,当前卡刷新为 `already-decided`,不能覆盖。
- `recoveryPending=true` 时显示可恢复状态,只允许重试同一 ID,不允许提交新版本或启动构建。
- 待审版本的 receipt 已存在时隐藏对应 stale pending 卡;hydrate 只恢复精确 project/session/run/action identity。
- approved 后只在所有必需投影恢复完成时启用完整构建按钮
- approved 后只在所有必需投影恢复完成时启用“做成游戏”;恢复态不提供该出口
决策卡继续复用现有用户输入卡,不新建平行提问系统。现有完整构建 design 组用户名称改为“设计实现组”,与新阶段“立项策划”区分;内部 Agent ID 不改。
@@ -1703,7 +1704,7 @@ receipt 永远压过 stale pending:一旦该版本存在有效 receiptpendi
| agent.db | 专用幂等 helpersame key conflict;日志达到普通容量、尾部截断与压缩后仍能补齐并保留决定记录 |
| source/security | durable exact identity;三个 action tool`file.read` / `file.list` / `plan.submit_gdd`)广告与执行;MCP 空且 webSearchEnabled=falsecontrol functions 单列;tool-plan/batch/ledger/context/repair/completion 全部跳过 collaborationplan retry 保留 source/profile;除 Runtime-owned submit 外的副作用工具拒绝 |
| Prompt | **2026-08-13 按 D11 改写**:不新增 compositionSupervisor 根 run 沿用现役 supervisor composition、策划子 Agent 复用 `runtime` composition(见第 4.2 节);`decision-checkpoint` 请求 kind 随 D10 作废(见第 5.1 节)。仍冻结:3 轮/单题/固定选项;回答后设计解释与 prototype item;平台事实;恢复摘要;direct-out 条件 |
| frontend | 首页“做方案”目录提交与 Enter 自动创建均为 `standard + project-supervisor-plan`;“做游戏/做素材”均保持 `autonomous-game-build`;项目页新建/打开不首次注入 planningstable approvalRequestId/responseIdbusystale cardhydrate strict input/view;无目录空态;receipt 隐藏 stale pendingcorrupt authority typed errorproject open/reload/resume/submit/decision 刷新;recovery pending;批准并开建两命令顺序 |
| frontend | 首页“做方案”目录提交与 Enter 自动创建均为 `standard + project-supervisor-plan`;“做游戏/做素材”均保持 `autonomous-game-build`;项目页新建/打开不首次注入 planningstable approvalRequestId/responseIdbusystale cardhydrate strict input/view;无目录空态;receipt 隐藏 stale pendingcorrupt authority typed errorproject open/reload/resume/submit/decision 刷新;recovery pending;批准 GDD 后“做成游戏”直接创建自动工作区、导入 `fast_gdd.md` 并自动启动 Direct Codex;重复点击不重复创建;普通首页链路不回归 |
| M2 integration | explicit approved/direct mode;锁内重验 receiptref 贯穿 task/run/completion/context;无 ref 非回归;恢复不换稿 |
关键强杀点逐项覆盖:
@@ -1994,7 +1995,7 @@ M0 完成不表示完整策划闭环已经上线。`M1A-1``M1A-4`、`M1B-1`
| `M1C-2b` | 澄清中转接线、轮次派生、预算注入 | `M1C-2a` | **实现与本包门禁已完成并已快进合回 `feat/five_min_design`**:首 child 的 revision 1 session、`NeedsUserInput → awaiting_user_input`、回答绑定后 continuation 的确定性 session 投影、审批后 `revise/reject` 用户修订谱系及 Provider 活跃时间 usage fact/fold 已接线;末次 `plan.submit_gdd` usage 在 receipt/session successor 落盘且 standalone/v4 anchors 精确消费后于同一项目锁内折叠,真实 receipt 回归证明累计值恰好推进一次,重复审批与 recovery reconcile 不二次推进。`planning_clarification_*` **13 passed / 0 failed**(M1C-2c 语义回归另见本包),另有 static deliveries 44、planning storage 13、planning submit 53、Provider usage 4、末次 usage receipt 1、barrier detail 3 条定向回归通过;格式、offline all-targets、编码与 diff 门禁通过。锁序承诺只适用于 **M1C-2b 新增的 planning 澄清写投影路径**`main_loop` 既有通用 completion blocker 的 execution→project 路径不在本包。第 4 轮信封在正常路径不可达:`agent.delegate` 已在工具边界按血缘上限硬拒并返回 failed observationcoordinator 的超三轮 reconciliation 仅用于损坏血缘纵深防御。审批 UI、hydrate、构建准入和下游完整构建不在本包范围 |
| `M1C-2c` | 决策卡 A/B 语义(第 23.9 节,2026-08-18 实现完成并合回):选项 → 台账映射改为 A/B 均 `confirmed/user_option`、信封 label 形状校验、planning role brief 与 Supervisor playbook/final-reply 文案(B 必须是真实岔路、第 3 项恒定且 description 须给出可执行验证方式、改口转述规则、提问纪律) | `M1C-2b` | **实现与门禁完成,已由 `6e4bd9703` 合回 `feat/five_min_design`**Runtime 已实现 A/B/固定第三项校验、B 不再生成 `default_pending``answerSummary` 逐字保真;非法 C/缺项 fail-closedA/B/自由填写回归已通过。`planning_clarification_*` 13、`project_planning` prompt 5、`planning_submit` 定向回归、prompt bundle、格式、编码、diff、offline all-targets 均通过;不含 M1D-1 前端、hydrate、构建准入或下游完整构建 |
| `M1D-1` | 前端 hydrate 与 GDD 审批卡;决策卡按第 23.9 节实现(label 动态渲染、默认焦点 A、Other 槽不变) | `M1C-2b` | **已完成并合入 `feat/five_min_design`(落地 `0052a80da`,其后 ESLint 修正 `5b11a0530`**:新增严格 `{projectPath}` hydrate command、`plan-gdd-state-view.v1` Rust read model、审批卡与独立 GDD 正文详情弹层;页面只消费 hydrate,决定 responseId 按审批请求/动作复用,`recoveryPending` 仅提供恢复重试;审批前置 pending 与错绑 session 继续 fail-closed。 |
| `M1D-2` | 入口分流与阶段进度 | `M1D-1` | **2026-08-19 更正 `bf2185fba` 的错误入口映射**:仅首页“做方案”新项目以 `standard + project-supervisor-plan` 启动;“做游戏/做素材”保持 `autonomous-game-build`,不再展示额外“直接开建”按钮;项目页新建、打开和 Godot 导入不新增策划入口,仅恢复已有 planning lineage。阶段进度显示轮次 x/3、当前版本和状态徽章;实际项目总控页面挂载 hydrate/审批卡,并将 `project-planning` / 设计组展示名收口。未接 M2 approved-GDD 构建绑定或完整构建按钮。 |
| `M1D-2` | 入口分流与阶段进度 | `M1D-1` | **2026-08-19 更正 `bf2185fba` 的错误入口映射**:仅首页“做方案”新项目以 `standard + project-supervisor-plan` 启动;“做游戏/做素材”保持 `autonomous-game-build`,不再展示额外“直接开建”按钮;项目页新建、打开和 Godot 导入不新增策划入口,仅恢复已有 planning lineage。阶段进度显示轮次 x/3、当前版本和状态徽章;实际项目总控页面挂载 hydrate/审批卡,并将 `project-planning` / 设计组展示名收口。**2026-08-30 变更:批准 GDD 后的“做成游戏”直接创建自动工作区、导入 `game/fast_gdd.md` 并自动启动 Direct Codex;仍未接入正式 `approvedGddRef` 构建绑定。** |
| `M1E` | 端到端与故障注入收口 | `M1D-2` | **已完成**planning 覆盖审计与 submit 拒绝上限收口完成。`PLAN_INVALID_REQUEST` / Provider input 或候选 GDD 的 `PLAN_SIZE_LIMIT` 每 child run 最多 5 次 rejected observation,第 5 次在 observation durable 后终态失败;counter durable,重启不清零。若在第五条 rejected observation 落盘与终态失败之间崩溃,恢复入口会按 durable counter 直接终态失败,不请求第六次 Provider tool-plan。既有不可变 GDD/receipt 的超限读取及 lineage 版本已耗尽均改走 reconciliation,不误耗 Provider 重试额度。第 21 节已存在的三轮、续跑、receipt/replay、投影恢复及 hydrate 回归复核通过;不为“拼接已有单测”新增脆弱大 E2E。 |
**2026-08-18 M1D 审查修复快照**:对 `14c00017c..bf2185fba` 做规格对照审查后,修复三条决定链路缺陷并补齐回归。① `decidePlanGdd` 的失败分支原来不 hydrate,命中后端任一 `PLAN_STALE_APPROVAL` 分支后卡片会停在已失效的 pending 身份上、`recoveryPending` 永不翻真导致「重试恢复」入口不渲染,现已按第 18.3 节在失败分支同样重灌(顺序钉死:`hydratePlanGddState` 入口会清空错误,必须先 hydrate 再写决定错误)。② responseId 复用键原为 `approvalRequestId:action`,不含 comment,违反第 13.2 节「改变 action/comment 必须换新 responseId」,现改为比对 `{action, comment}` 完整意图,判据方向为宁可多换不可少换。③ 第 18.2 节「`recoveryPending` 时不允许提交决定」原来只作用于三个触发按钮,已打开的评论弹层仍可提交,现已同门控并保留用户已输入内容。回归位于 `tests/appSurface/plan-gdd.suite.ts`,三条均经变异验证(逆转对应修复即变红);`appSurface.test.ts` 381 passed`agc:typecheck`、ESLint、编码检查通过。其中锁错误回传绝对路径、阶段进度轮次差一格与 design 组展示名收口三条已于同日补修(见 decision-log 同日两条);仅 hydrate 身份校验与落盘投影修复的顺序一条单列后续,未并入。
@@ -0,0 +1,217 @@
# 【技术说明】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 附件、仓库归档或团队共享存储的形式提供;本文不依赖某位开发者电脑上的绝对路径。
@@ -901,7 +901,9 @@ SELECT * FROM profile_recharge_product_config ORDER BY sort_order ASC;
后台通用表查询已经处理 SpacetimeDB 无载荷枚举的 SATS 形态。新增后台表展示时,枚举列优先按表名和列名做业务映射,再落回通用解码。
后台通用表查询的“每页条数”不是筛选前的 SQL 截断量。API Server 通过单次 `SELECT * ... LIMIT 50001` 读取哨兵行,最多保留前 50,000 条候选;关键词 / JSON 过滤、所选列的完整候选集稳定排序和 1-based `page` 分页都基于这一次 SQL 结果,`totalMatched` 不再依赖另一份 `COUNT(*)` 快照。请求页码超过实际总页数时钳制到末页,零结果固定返回第 1 页。存在第 50,001 条哨兵行时响应必须返回 `scanLimitReached=true`,后台固定分页栏上方明确提示匹配总数和分页结果可能不完整,不得把扫描范围外的数据误报为不存在。候选 SQL 响应体仍受 32 MiB 和 20 秒硬限制;宽表即使每页条数很小也可能整次拒绝,不会返回部分结果。实时写入仍可能改变相邻请求的候选快照,精确审计应使用对应业务表的专用查询而不是通用浏览页。
后台通用表查询的“每页条数”不是筛选前的 SQL 截断量。API Server 通过单次 `SELECT * ... LIMIT 50001` 读取哨兵行,最多保留前 50,000 条候选;关键词 / 字段条件过滤、所选列的完整候选集稳定排序和 1-based `page` 分页都基于这一次 SQL 结果,`totalMatched` 不再依赖另一份 `COUNT(*)` 快照。`filters` 支持两种 JSON 形式:object(列名到等值,如 `{"user_id":"u1"}`,兼容旧入口)与条件数组(如 `[{"column":"points","op":"gt","value":"5"}]`,运算符覆盖 `eq``ne``gt``gte``lt``lte``contains``notContains``startsWith``endsWith``in``notIn``isEmpty``isNotEmpty`,允许同列多条件,条件间为 AND);两种形式的用户输入都不进入 SQL,只在 API Server 内存中过滤。请求页码超过实际总页数时钳制到末页,零结果固定返回第 1 页。存在第 50,001 条哨兵行时响应必须返回 `scanLimitReached=true`,后台固定分页栏上方明确提示匹配总数和分页结果可能不完整,不得把扫描范围外的数据误报为不存在。候选 SQL 响应体仍受 32 MiB 和 20 秒硬限制;宽表即使每页条数很小也可能整次拒绝,不会返回部分结果。实时写入仍可能改变相邻请求的候选快照,精确审计应使用对应业务表的专用查询而不是通用浏览页。
后台表查询页的结构化筛选条件支持逐条勾选启用或停用;停用条件不会进入请求,但会保留当前字段和值。表单和标题区的查询入口共用同一完整性校验,未完成的启用条件不会静默丢弃。`eq` / `ne` 和比较、文本运算符一样必须提供标量 `value`;显式空值判断使用 `isEmpty` / `isNotEmpty``in` / `notIn` 使用逐项值标签编辑,支持粘贴多行值,避免把字符串中的逗号误拆成多个条件。行内交互控件的键盘事件不会冒泡触发行详情,空字段详情复制会给出失败反馈。
## Issue 与交接