发布独立游戏聊天0.1.1并强化试玩验收

新增只能打开游戏聊天页的0.1.1独立release构建配置。
内置Runner启动、诊断日志、Windows后台进程与退出收束。
修复自动预览启动、同页版本刷新和结构化验收状态展示。
强化试玩协议、窗口末端采样与固定失败识别,允许正常输赢但拒绝无法推进。
完善旧验收回执自愈和当前场景指纹完成门。
补齐Rust、前端、真实Chrome回归测试及项目文档。
This commit is contained in:
2026-07-31 13:32:07 +08:00
parent 216407d93e
commit dcd8901246
56 changed files with 6891 additions and 364 deletions
@@ -26,6 +26,60 @@
- 验证方式:序列化与字段上限测试、缺文件 / 损坏 / 原子恢复 / 链接安全测试、同 revision 双写最多一个成功、两种 mode 跨重启独立恢复、新增资源不移动旧坐标、`1280×800` 横屏无页面级溢出,以及 `npm run agc:typecheck`、定向测试、`npm run check:encoding`、`git diff --check`。
- 关联文档:`docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md`、`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
## 2026-07-31 generic-v1 使用稳定观察与当前场景指纹关闭试玩假阳性
- 背景:`game01` 的 start handler 会先同步发布 `playing`,再因碰撞错误在首个动画帧进入 `lost`;旧 `generic-v1` 只捕获 start / restart 后的瞬时 phase,同一 revision 因采样时机不同既能通过也能失败。与此同时,game-chat 把 `image.inspect` 工具调用的 `status=ok` 显示成绿色“截图已检查”,让工具执行成功进一步被误读为视觉验收通过。
- 决策:`generic-v1` 初始状态固定要求 `ready` 且 `level > 0`。start 后必须进入 `playing`,并先持续观察 2 秒、取得至少 8 个实际样本,期间保持 `playing`,以确认玩家获得正常操作机会;再点击唯一可见、启用且真实可交互的 `data-playtest-id="primary-action"`。该控件必须执行真实主要玩法操作,并以 sequence 相对点击前严格推进证明操作已被接受。操作被接受前进入 `won | lost` 代表玩家没有获得正常操作机会,必须失败;操作被接受后的单次 `lost` 是合法结局,但不能成为所有受控尝试的唯一结果;若主要操作后仍为 `playing`,则继续观察 3 秒并取得至少 12 个实际样本,`won` 可提前证明非失败推进。随后 restart 必须推进 sequence、恢复到 `ready | playing`,并持续观察 3 秒、取得至少 12 个实际样本。若首轮结果为 `lost`,重开稳定后必须再执行一次必要的 start、2 秒 / 8 样本操作机会和真实 primary-action;第二次必须进入或保持 `playing`(再观察 3 秒 / 12 样本且不得转为 `lost`)或进入 `won`,两次受控尝试都固定 `lost` 代表无法正常推进的恶性 bug,必须失败。观察期间 sequence 不得回退,restart 窗口只能保持 `ready | playing`;样本数门槛与持续时长必须同时满足,窗口末端必须强制再读取一次有效状态。selector、观察时长、样本门槛、终态边界、非失败推进、末端覆盖、sequence 规则和 required assertions 全部纳入 scenario fingerprint。合同升级导致旧 fingerprint 不匹配时,回执读取与 plan liveness 把它视为 stale missing,允许同一 run 重新 `preview.validate` 自愈;身份、路径、digest 或内容完整性篡改仍失败关闭,最终完成门仍现场重算 fingerprint 并严格拒绝旧证据。game-chat 只有在结构化 `preview.validate` 同时给出 `passed=true` 与 `playtestPassed=true` 时才显示试玩通过并产生可玩 revision;`image.inspect status=ok` 只表示工具成功,`passed=null` 必须保持中性展示。
- 影响范围:`generic-v1` 浏览器试玩执行器、scenario fingerprint、自主完成回执与持久浏览器报告校验、game-chat 进度证据和自动预览门禁;不改变 `lane-defense-v1` 合同或通用工具执行状态语义。
- 验证方式:默认 Rust 回归锁定初始状态、真实 `primary-action`、操作机会前后终态边界、2 秒 / 8 样本 start 操作机会、操作后仍为 `playing` 时的 3 秒 / 12 样本、restart 的 3 秒 / 12 样本、首轮 `lost` 后的第二次非失败推进、强制窗口末端采样、sequence 规则、stale 自愈与当前 fingerprint;显式运行 `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml real_chrome_generic_playtest_ --features game-chat-release -- --ignored --nocapture --test-threads=1`,同时确认真实 Chrome 中未接受主要操作就瞬时 `lost`、两次受控尝试都固定 `lost` 均判失败,首轮合法 `lost` 后重开并证明非失败推进判通过;前端定向测试确认缺少双 true 的 preview 证据不显示通过,`image.inspect passed=null` 使用中性色。
- 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`、`docs/project-memory/shared-memory/development-workflow.md`。
## 2026-07-30 game-chat 0.1.1 的用户预览由 Tauri 持有并按验证 revision 原位刷新
- 背景:External Runner 和 Tauri 客户端各自拥有进程内 `PreviewRegistry`。Runner 完成 `preview.validate` 后,其 server 与 running 状态不会出现在 Tauri registry,导致已有可玩版本时用户预览不自动出现;后续 revision 即使验证成功,既有 iframe 也可能继续显示 WebView 缓存中的旧资源。顶部只写“未启动”还会让用户无法判断是 Runner、Runtime 还是预览未启动。
- 决策:game-chat 用户可见 preview 只由 Tauri 客户端启动、持有和停止,不共享或复用 Runner registry 的 server。客户端只接受当前 accepted Supervisor 父 run 下真实 `preview-playtest` scheduler child 的结构化 `preview.validate` 证据;同 revision 以最新事件为准,时间相同时失败优先,且证据 revision 必须精确等于项目当前原子 sidecar revision。首次满足门禁后,Tauri 自动启动一次 preview server 并显示 iframe;启动命令携带 `expectedRevision`,后端取得项目写锁后再次原子比对,避免检查与启动之间的 revision 漂移。same-run steer 的一次性授权使用 v2 持久 cursor 与唯一 generation ID,旧验证或旧异步 attempt 不能消费新授权;旧 attempt 的补偿清理按完整 preview identity 原子停止 registry server,资源停止不得被项目 `preview.stop` policy 或项目锁等待阻断,后续状态持久化不能覆盖同项目新 server 的 running 状态。同一 run 后续成功验证的更高 revision 只刷新原 iframe,不重复 `preview.start` 或新增 server,相同或更低 revision 不刷新。preview HTTP 响应统一使用 `Cache-Control: no-store`,确保刷新读取当前 revision;无用户可见预览时顶部明确显示“预览未启动”。本次专用 release 版本推进为 `0.1.1`。
- 影响范围:game-chat 自动预览授权、Tauri / Runner `PreviewRegistry` 进程边界、用户可见 preview server 生命周期、iframe revision 刷新、preview HTTP 缓存策略、顶部状态文案和专用 release 版本;普通客户端入口、Runner 验证语义和平台后端不变。
- 验证方式:以当前 run 成功 `preview.validate` revision N 后断言 iframe 自动出现且 server 归 Tauri registry;再完成 revision N+1,断言 server 进程和 loopback origin 不变、iframe 重新加载新内容且所有响应为 `no-store`。Runner registry 单独 running 不得让页面显示预览;相同 / 更低 revision 不得刷新;停止预览后顶部必须显示“预览未启动”;构建产物和安装信息必须为 `0.1.1`。
- 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`、`docs/project-memory/shared-memory/development-workflow.md`。
## 2026-07-30 Provider 503 等待与耗尽状态使用严格字段派生的安全摘要
- 背景:game-chat 的进度卡只显示“等待 Provider upstream-5xx 瞬态故障退避到期”,没有 HTTP 状态、重试次数或等待时间;重试耗尽后,Runtime 和持久 conversation 又可能直接展示 `fingerprint/chars` 或 `<absolute-path> [redacted sensitive context]`,用户既无法判断是否在恢复,也看不到可操作的失败原因。
- 决策:重试资格继续按稳定错误类别判断,durable retry record 额外保留精确且不含正文的 `upstream-<status>`;等待态从 record 派生 HTTP 状态、真实 attempt 上限和退避剩余秒数。耗尽态只在 `kind/httpStatus/fingerprint/chars/retryAttempt/maxRetries/retryState` 全部严格匹配时派生安全中文摘要;Runtime 私有机器字段可供确定性投影,但状态卡和持久 conversation 不显示 fingerprint、字符数、绝对路径、脱敏占位符或 Provider 正文。所有写入“后台任务失败”conversation 的生产分支统一经过同一安全 formatter,其它错误只显示固定失败文案。
- 验证方式:Rust mock 503 覆盖等待、恢复与耗尽,断言 exact HTTP status、attempt、sidecar 清理和正文零泄漏;前端模型覆盖状态卡优先级、严格字段解析、字段不一致与尾随正文失败关闭;conversation 测试覆盖 Provider URL/query、API Key、绝对路径、fingerprint、chars 和 redaction marker 均不可见。
- 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`、`docs/project-memory/shared-memory/development-workflow.md`。
## 2026-07-30 game-chat 独立版退出时收束 Runner 整棵 Windows 进程树
- 背景:独立包原先在 Tauri 最终退出时仅调用一次 `runner.shutdown_if_idle`;若 Runner 正忙便返回 busy 且没有稍后关闭闩锁,Runner 和后台命令会永久残留。`command.exec / project.verify` 只有进程组 flag,STDIO MCP、Git 和清理命令也缺少 `CREATE_NO_WINDOW`,因此 Windows release 会连续弹出多个控制台窗口。
- 决策:仅 game-chat release 新增认证 `runner.shutdown_for_client_exit`:立即进入 draining、拒绝新写并请求结束当前 boot,不删除 durable sidecar、不伪造任务 completed,重启后继续走 reconciliation。Windows 客户端以 `CREATE_SUSPENDED` 创建自己启动的 Runner,在其执行前加入由客户端持有的 kill-on-close Job,复核并恢复唯一主线程;Job 分配或恢复失败必须 kill + wait 并令启动失败,使客户端崩溃、IPC 关闭失败或 Runner 异常退出时整棵未 breakaway 后代仍能收束。普通 dev / release 与 CLI 保持 `shutdown_if_idle`。所有非交互后台命令统一使用 `CREATE_NO_WINDOW`,需要独立终止边界时叠加 `CREATE_NEW_PROCESS_GROUP`,不使用 `DETACHED_PROCESS`;独立 flavor 同时拒绝再打开 launcher / workspace 平行窗口。
- 验证方式:定向测试覆盖专用 RPC 的 draining / shutdown 与普通 idle 语义不变;正式安装包在活跃任务期间确认无后台控制台窗口,关闭主窗口后核对 Runner、MCP、command、ConPTY 和孙进程全部退出,再启动确认 durable 状态正确 reconciliation。
- 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`、`docs/project-memory/shared-memory/development-workflow.md`。
## 2026-07-30 Windows Runner 新建私有控制文件在原子安装前初始化 TokenUser owner
- 背景:Windows 即使已把 game-chat AppData 目录 owner 设为当前 `TokenUser`,本进程新建 `agent-runner.lock`、endpoint 临时文件、项目 execution-owner 诊断临时文件和 real-E2E 私有文件的 owner 仍可能来自 token 默认 owner `Administrators`。Runner 会依次在 lock 或 endpoint 的严格 `TokenUser` 复核处退出;父进程此前还会在已经观察到子进程退出后继续等待完整 30 秒。
- 决策:既有 durable 文件的读取继续严格拒绝 foreign owner。只有本进程以 `create_new` 创建且仍持有 Windows `share_mode(0)` 独占句柄、并通过普通文件 / 非 reparse / 单链接检查的临时文件,才在写入和原子安装前初始化 `TokenUser` owner 与 protected 私有 DACL,失败时关闭句柄并清理刚创建的文件;安装后继续严格复核。固定 `agent-runner.lock` 的 stale 恢复还要求父目录是已验证的私有 AppData;活锁不可接管或截断,只有 sharing / lock violation `32/33` 表示占用。父进程观察到 Runner 子进程退出后立即返回真实错误,不再空等启动 deadline。Tauri `.setup()` 内的致命失败在记录日志后直接显示诊断路径。
- 影响范围:Windows External Runner 单实例锁、endpoint、项目 execution-owner 诊断、real-E2E 私有 checkpoint、启动失败耗时和 game-chat release 错误可见性;既有配置 / endpoint / 项目 durable 文件的读取边界、普通 dev / release 与 Runner 活跃任务语义不放宽。
- 验证方式:Windows 定向测试覆盖 endpoint 原子写入与 TokenUser 复核、new/stale lock owner 修复、活锁不截断、hardlink / reparse 不触碰目标、project-owner 诊断 TokenUser 复核、错误码精确分类,以及子进程退出在 2 秒内返回;正式包在现场 TokenOwner 为 Administrators 的机器上必须依次出现 `startup.runner.start.complete`,创建项目任务时 execution-owner 诊断也必须成功。
- 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`、`docs/project-memory/shared-memory/development-workflow.md`。
## 2026-07-29 game-chat 使用独立 release flavor
- 背景:game-chat 已完成开发态页面和自主 Runtime 链路验证,需要交付一个无参数启动、只能进入该页面的 release 包,同时不能改变普通客户端入口、安装身份、AppData 或 Runner 的持久任务语义。
- 决策:新增 `npm run agc:build:game-chat-release`,通过 Rust `game-chat-release` feature 和编译期前端入口锁定生成独立 NSIS 包。该 flavor 的 `productName` 为 `Genarrative Game Chat`,`identifier` 为 `world.genarrative.ai-game-creator.game-chat`,使用独立安装身份与 AppData;前端直接渲染本地 `GameChatReleaseApp`,不经过平台 `AuthenticatedClient`,本地工作台不依赖 `api-server`。普通 dev / release 与 debug game-chat 保持原认证入口和配置。独立客户端退出语义已由 2026-07-30 专项决策收口为 `runner.shutdown_for_client_exit + Windows kill-on-close Job`,不再沿用活跃 Runner 返回 busy 后继续驻留的旧规则。
- 影响范围:AI 游戏创作壳的构建脚本、Tauri 配置、Rust feature、前端入口选择、独立 AppData 和 game-chat release 退出处理;普通 AI 游戏创作 dev / release、debug 认证、Supervisor Runtime 协议和后端接口不变。
- 验证方式:执行壳配置门禁、AppSurface game-chat 测试、前端类型检查、相关 Rust 定向测试、`npm run check:encoding` 与 `git diff --check`;运行 `npm run agc:build:game-chat-release` 后 smoke 无参数首屏、断开 `api-server` 的本地工作台、页面隔离、独立 identifier / AppData、普通/debug 认证不回归,以及空闲 / 活跃两种 Runner 退出分支。
- 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`、`docs/project-memory/shared-memory/development-workflow.md`。
## 2026-07-29 Windows 客户端 AppData 以 TokenUser 安全迁移并为 game-chat 提供有界诊断
- 背景:Windows 安装包首次创建 AppData 时若沿用继承 owner,目录 owner 可能是 Administrators 而非当前登录用户;后续严格 owner 校验会让客户端在 `.setup()` 阶段退出,且无控制台 release 缺少可见诊断。直接修改 foreign-owner 旧目录的 ACL 还可能覆盖其它主体持有的数据或跟随 reparse 路径。
- 决策:新建客户端 AppData 时以进程 `TokenUser` SID 显式设置 owner 和当前用户私有 DACL,不使用 `TokenOwner` 代表用户。历史 foreign-owner 真实目录先原子重命名为同级唯一 `.owner-mismatch-backup-*`,再重建并回读验证安全目录;任何 reparse / junction / symlink、备份冲突或迁移失败都失败关闭,不在旧目录上放宽权限。独立 release 的 `startup.log` 和记录 Runner stdout / stderr 摘要的 `agent-runner.log` 均采用 256 KiB 上限、仅一份 previous 和脱敏写入;AppData 日志不可写时 `startup.log` 回退系统 TEMP,Tauri URL、AppData、Runner、`.setup()` 或 `.build()` 初始化失败时在 Windows 显示包含诊断日志位置的错误对话框。
- 影响范围:AI 游戏创作客户端 AppData 首次创建与历史目录迁移、Windows owner / DACL 校验,以及 game-chat release 的启动和 Runner 诊断、初始化失败体验;不改变项目目录、Runtime durable 数据合同或活跃 Runner 退出语义。
- 验证方式:Windows 定向测试覆盖 TokenUser owner、私有 DACL、foreign-owner 同级备份不覆盖和 reparse 拒绝;诊断测试覆盖 256 KiB 单 previous 轮转、凭据与绝对路径脱敏、AppData 不可写时 TEMP 回退和初始化失败对话框。安装包 smoke 后保留旧备份证据并确认新 AppData 可写、Runner 可启动。
- 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`、`docs/project-memory/shared-memory/development-workflow.md`。
## 2026-07-29 game-chat 在创建 WebView 前确定初始 URL
- 背景:`agc:game-chat` 曾在 Tauri `.setup()` 中读取仍可能是 `about:blank` 或配置期地址的 `client.url()`,再导航到 game-chat;Windows WebView2 首航被覆盖后只剩黑边白块或全白原生窗口,刷新无法恢复。
@@ -59,6 +59,34 @@ AI 游戏创作独立客户端常用短命令:
npm run agc
```
需要构建只能打开“游戏运行 + 聊天”页面的独立 release 包时使用:
```bash
npm run agc:build:game-chat-release
```
该命令启用 Rust `game-chat-release` feature,并配合编译期前端入口锁定生成独立 NSIS 包。当前专用 release 版本为 `0.1.1`;产物的 `productName` 为 `Genarrative Game Chat`,`identifier` 为 `world.genarrative.ai-game-creator.game-chat`,安装身份和 AppData 不与普通 AI 游戏创作客户端混用。独立包直接渲染本地 `GameChatReleaseApp`,绕过平台 `AuthenticatedClient`,进入本地项目工作台不依赖 `api-server`;普通 `npm run agc`、`npm run agc:dev`、debug game-chat 和 `npm run agc:build` 继续走既有认证入口与配置。
game-chat 的用户可见 preview 必须由 Tauri 客户端 `PreviewRegistry` 持有。External Runner 和 Tauri 的 registry、server 句柄与 running 状态是进程内资源,不得互相推断或把 Runner 验证用 server 直接交给 iframe。当前 accepted Supervisor 父 run 下真实 `preview-playtest` scheduler child 首次给出结构化成功证据、且其 revision 精确等于项目当前 revision 后,客户端才消费一次性授权并启动一个 Tauri preview server,随后自动显示 iframe;`preview.start` 必须携带 `expectedRevision`,Tauri 在取得项目写锁后再次原子比对。same-run steer 授权必须记录授权前 revision / validation cursor 和唯一 generation ID,旧证据、旧 policy await 或旧 start 返回均不能清除新授权。同一 run 的更高 validated revision 只刷新原 iframe,不能重复 `preview.start` 或新增 server,相同 / 更低 revision 不刷新;同 revision 的最新失败证据必须关闭该 revision 的可玩判定。preview HTTP 的 HTML、脚本、样式、资源和错误响应都必须返回 `Cache-Control: no-store`。没有 Tauri 用户预览时,顶部状态必须显示“预览未启动”,不能只写“未启动”。
`generic-v1` 的真实试玩必须从 `ready` 且正整数 level 开始;start 推进到 `playing` 后,必须先持续观察 2 秒并取得至少 8 个实际样本,期间保持 `playing`,以确认玩家获得正常操作机会;随后点击唯一可见、启用且真实可交互的 `data-playtest-id="primary-action"`,由该控件触发真实主要玩法操作,并以 sequence 相对点击前严格推进证明操作已被接受。操作被接受前进入 `won | lost` 代表玩家没有获得正常操作机会,必须失败;操作被接受后的单次 `lost` 是合法结局,但不能成为所有受控尝试的唯一结果。若主要操作后仍为 `playing`,则继续观察 3 秒并取得至少 12 个实际样本;`won` 可提前证明非失败推进。之后 restart 必须推进 sequence、恢复到 `ready | playing`,并持续观察 3 秒、取得至少 12 个实际样本;若首轮结果为 `lost`,重开稳定后必须再执行一次必要的 start、2 秒 / 8 样本操作机会和真实 primary-action,第二次必须进入或保持 `playing`(再观察 3 秒 / 12 样本且不得转为 `lost`)或进入 `won`。两次受控尝试都固定 `lost` 代表无法正常推进的恶性 bug,必须失败。各观察窗口内 sequence 不得回退,restart 窗口只能保持 `ready | playing`。样本数和观察时长必须同时满足,窗口末端必须强制再读取一次有效状态,不能靠前段样本数提前通过。selector、时长、样本门槛、终态边界、非失败推进、窗口末端覆盖、required assertions 和 sequence 规则都属于 scenario fingerprint。旧 fingerprint 回执在读取和 plan liveness 检查时按 stale missing 处理,让同一 run 可重新 `preview.validate`;身份、digest、路径或内容完整性篡改仍失败关闭,最终完成门仍须现场重算当前 fingerprint 并严格拒绝旧证据。
进度展示只把结构化 `preview.validate` 的 `passed=true && playtestPassed=true` 认作试玩通过,不从 `summary` 的 `:ok` 推断结论。`image.inspect status=ok` 只表示工具成功;`passed=null` 或缺少结构化布尔结论时显示中性“截图分析完成”,不得显示绿色通过。
真实 Chrome 回归必须用同一前缀同时覆盖尚未接受 `primary-action` 就瞬时进入 `lost` 的 FAIL、两次受控尝试都固定 `lost` 的 FAIL,以及首轮合法 `lost` 后重开并在第二轮证明非失败推进的 PASS:
```bash
cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml real_chrome_generic_playtest_ --features game-chat-release -- --ignored --nocapture --test-threads=1
```
修改 game-chat release flavor 后,至少执行壳配置门禁、AppSurface game-chat 定向测试、前端类型检查、AppData / 诊断日志 / release flavor 相关 Rust 定向测试、`npm run check:encoding` 和 `git diff --check`。打包 smoke 必须确认:安装信息和产物版本为 `0.1.1`;无参数启动直接进入且只能停留在 game-chat 页面;停止或断开 `api-server` 后本地工作台仍能打开;普通 dev / release 与 debug game-chat 仍走原认证入口;独立 AppData 生效。预览 smoke 应先让当前 run 成功验证 revision N,确认 Tauri registry 启动一个 server 且 iframe 自动出现;在 validate 后、start 取得锁前推进项目 revision,必须确认原子 `expectedRevision` 门禁拒绝启动且授权保留等待新证据;再验证 revision N+1,确认 server 进程和 loopback origin 不变、iframe 显示新版本且响应为 `no-store`。same-run steer 还要覆盖旧验证不消费新授权、旧异步 attempt 不清新 generation;Runner registry 单独 running、失败 / 相同 / 更低 revision 均不能触发用户预览或重复刷新,停止后顶部显示“预览未启动”。独立包退出时必须通过 `runner.shutdown_for_client_exit` 先进入 draining 再结束本 boot,保留 durable sidecar 供下次 reconciliation,不把中断任务写成 completed;Windows Runner 必须 `CREATE_SUSPENDED -> AssignProcessToJobObject -> ResumeThread`,分配或恢复失败时 kill + wait,客户端持有 kill-on-close Job 兜底,关闭主窗口后 Runner、MCP、command、ConPTY 及其后代都应消失。普通 dev / release 和 CLI 继续使用 `runner.shutdown_if_idle`。
Windows release 的非交互后台命令统一使用 `CREATE_NO_WINDOW`,包括 `command.exec / project.verify`、STDIO MCP、Repository Context Git、`git.inspect / project.git_commit` 和 `taskkill` 清理命令;需要进程组终止时再叠加 `CREATE_NEW_PROCESS_GROUP`,不要使用 `DETACHED_PROCESS`。smoke 时应在实际任务运行期间观察无额外控制台窗口,并在关闭客户端后核对整棵后台进程树为零,再重启确认 reconciliation 可继续。
Provider 失败回归必须同时检查等待态和耗尽态:用本地 mock 503 证明 Runtime 从 durable retry record 显示精确 HTTP 状态、真实 `nextAttempt/maxRetries` 和退避秒数;最后一次重试仍失败时,状态卡与持久 conversation 只显示安全中文摘要。测试正文应包含诱饵 Provider URL/query、API Key 和 Windows / Unix 绝对路径,并断言这些正文、内部 fingerprint / chars 与 `[redacted ...]` 占位符均未进入用户可见消息;不能只验证状态卡而漏掉 `SupervisorChatOnlyView` 直接渲染的 conversation。
Windows 安装包还必须覆盖 AppData 与诊断失败路径:新建目录的 owner 必须等于进程 `TokenUser` SID 且 DACL 仅允许当前用户;历史 foreign-owner 目录应先拒绝任何 reparse / junction / symlink,再原子重命名为同级唯一 `.owner-mismatch-backup-*` 并重建安全目录,旧配置不得覆盖。Windows 新建 Runner lock、endpoint temp、project-owner diagnostic temp 与 real-E2E 私有文件的 owner 可能仍是 token 默认 owner `Administrators`;只允许本进程 `create_new` 后仍持有不共享独占句柄、且句柄确认普通 / 非 reparse / 单链接的对象在写入或原子安装前初始化为 `TokenUser`,失败时清理该新文件。既有 durable 文件读取、活锁、access denied、硬链接或非固定 lock 路径不得删除、接管或截断。`startup.log` 与 `agent-runner.log` 达到 256 KiB 时只保留一份 `.previous.log`,并扫描 API Key、Authorization、AppData 和其它绝对路径零泄漏;AppData 不可写时 `startup.log` 应回退系统 TEMP 的 `Genarrative-Game-Chat-Diagnostics`,故意注入 `.setup()` / `.build()` 初始化失败时必须出现带诊断日志位置的 Windows 对话框;`.setup()` 发生在 `app.run` 阶段,错误对话框必须由 setup 失败路径直接触发,不能只处理 `.build()` 返回值。Runner 子进程已退出时父进程应立即报告,不得继续等待完整启动期限。
开发侧需要无 UI 验收某个单 Agent 的完整 Runtime 时使用:
```bash
@@ -23,17 +23,32 @@
- 界面边界:只显示持久消息区、输入框、必要的等待 / 错误状态、工具确认 / 用户追问卡片和设置入口;不显示 Agent picker、Session 面板、Goal 面板、完整 Runtime 面板或专业 Agent 协作栏。会话历史仍绑定 `project-supervisor` 的 active Session 持久化,不因隐藏 Session 控制面而变为临时聊天;`supervisor-chat` 窗口必须具备只读 Tauri event listen / unlisten capability,以实时接收 Runtime 更新。
- 产品边界:正式用户 `client` 窗口、登录后首页和项目开发流程保持不变,不暴露该开发验证面。
## 2026-07-27 开发态“游戏运行 + 聊天”临时入口
## 2026-07-29 “游戏运行 + 聊天”独立构建入口
- 入口例外:只有 debug 构建接受 CLI `--game-chat`,并可选接受 `--project-path <absolute-path>` 与内部验收用 `--initial-message <首条消息>`;宿主内部统一映射为 `index.html?game-chat&projectPath=...`。该 URL 必须在 Tauri 创建 `client` WebView 前写入初始 `WindowConfig`,禁止在 `.setup()` 阶段读取尚未完成首航的 `client.url()` 后二次导航。页面首屏读取初始消息后必须立即从当前 URL 删除 `initialMessage`,并以页面级闩锁绑定启动项目;React StrictMode、Runtime 终态、HMR、整页重载或项目切换都不得再次提交同一启动消息。release 构建不注册这些参数或窗口,无参数的正常启动流程不变。未传入项目路径时由页面选择目录:已有 AI 游戏项目直接打开,空目录按目录名初始化,非空且未初始化目录继续复用现有二次确认。
- 入口例外:debug 构建接受 CLI `--game-chat`,并可选接受 `--project-path <absolute-path>` 与内部验收用 `--initial-message <首条消息>`;宿主内部统一映射为 `index.html?game-chat&projectPath=...`。独立 game-chat release flavor 使用 `npm run agc:build:game-chat-release` 构建,并由 Rust `game-chat-release` feature 在无参数启动时固定同一初始页面;前端也在编译期固定为 game-chat 页面,不能导航回普通客户端首页、项目组或开发窗口。该 URL 必须在 Tauri 创建 `client` WebView 前写入初始 `WindowConfig`,禁止在 `.setup()` 阶段读取尚未完成首航的 `client.url()` 后二次导航。页面首屏读取初始消息后必须立即从当前 URL 删除 `initialMessage`,并以页面级闩锁绑定启动项目;React StrictMode、Runtime 终态、HMR、整页重载或项目切换都不得再次提交同一启动消息。普通 dev / release 的入口、参数与页面集合保持不变,普通 release 不因该 flavor 注册 game-chat 参数或窗口。未传入项目路径时由页面选择目录:已有 AI 游戏项目直接打开,空目录按目录名初始化,非空且未初始化目录继续复用现有二次确认。
- 独立发布身份:game-chat release 使用独立 Tauri 配置,`productName` 固定为 `Genarrative Game Chat`,`identifier` 固定为 `world.genarrative.ai-game-creator.game-chat`,当前专用 release 版本为 `0.1.1`,因此安装身份和 AppData 均与普通 `Genarrative AI Game Creator` / `world.genarrative.ai-game-creator` 隔离。该 flavor 只生成自己的 NSIS release 包,不覆盖普通客户端构建配置或已有 AppData。
- 本地页面边界:独立 game-chat release 在前端入口直接渲染本地 `GameChatReleaseApp`,不经过平台 `AuthenticatedClient`,启动和使用本地项目工作台不依赖 `api-server` 或平台登录态。该例外只由编译期 game-chat-only 标记启用;普通 release、`npm run agc` 和 debug game-chat 继续经过既有认证包装,认证与后端依赖均不改变。
- Windows AppData 安全迁移:首次创建客户端 AppData 时必须以进程 `TokenUser` SID 显式设置 owner,并写入当前用户私有 DACL,不能把可能为 Administrators 的 `TokenOwner` 当作用户身份。发现历史目录 owner 不属于当前 `TokenUser` 时,不在原目录上放宽权限,而是拒绝 reparse point / junction / symlink 后,将旧目录原子重命名到同级唯一 `.owner-mismatch-backup-*` 备份,再新建并验证当前用户 owner 与私有 DACL;迁移或备份失败必须失败关闭,不覆盖旧配置。
- Windows Runner 私有文件初始化:父 AppData 已归当前 `TokenUser` 后,新建 `agent-runner.lock`、endpoint 临时文件、project-owner 诊断临时文件与 real-E2E 私有文件的 owner 仍可能采用 token 默认 owner `Administrators`。固定 stale lock 只有在父目录已验证为当前用户 protected 私有 DACL、Windows 不共享独占句柄已取得、且句柄确认普通文件、非 reparse point、链接数为一时才允许修复;其它三类文件只允许在本进程 `create_new` 成功且仍持有同一独占句柄时初始化 `TokenUser` owner / DACL,再写入、原子安装并严格复核,初始化失败必须清理刚创建的文件。既有 durable endpoint / diagnostic 读取不得自动接管;活锁不得截断,只有 sharing / lock violation `32/33` 表示占用,access denied 等其它错误立即返回。父进程观察到 Runner 子进程退出后立即返回错误,不等待完整 30 秒 deadline。
- 启动诊断:独立 release 的 `startup.log` 和 `agent-runner.log` 只记录有界、脱敏的阶段与 stdout / stderr 摘要,凭据、AppData 路径和其它绝对路径不得原样落盘;单文件达到 256 KiB 后只轮转保留一份 `.previous.log`。`startup.log` 优先写独立 AppData,目录不可写时回退到系统 TEMP 下的 `Genarrative-Game-Chat-Diagnostics`;Tauri context、窗口 URL、AppData、Runner 或 `.setup()` / `.build()` 初始化失败时,Windows 必须显示可见错误对话框并给出诊断日志位置,不能只在无控制台 release 中静默退出。
- 对话与事件:窗口固定使用 `project-supervisor + autonomous-game-build`,继续复用 active Session、External Runner、持久 conversation、流式回复、same-run steer、工具确认与用户追问。以 `/` 开头的输入必须继续走现有内置命令解析,例如 `/preview` 只能生成 `preview.start` 确认卡,不得作为自主构建任务投递给 Supervisor。界面聚合当前 Supervisor 父 run 及其直接委派专业 Agent 的最新事件,按时间倒序稳定去重并标注 Agent;默认显示 4 条,可展开至最新 20 条。这些原始事件只是 Runtime 状态投影,不写入 conversation,不伪装成用户或 assistant 消息。
- Supervisor 进度播报:聊天消息流内保留且只保留一条当前 run 的 Runtime-owned 播报卡,由客户端从 manifest 任务图、Supervisor 结构化计划、`loopIteration`、当前动作、直接委派专业 Agent 及其持久事件确定性整理;显示当前轮次、任务 / 计划进度、活跃 Agent、最近试玩与静态检查、返工决定、代码修改和截图检查证据。同一 run 原位更新,切换 run 时替换,不调用额外模型、不追加持久 conversation,也不改变最终 assistant 回复的唯一性;任意详情必须有界且不展示绝对路径、Provider 元数据或内部指纹。
- Provider 故障展示:Provider retry 的“是否可重试”继续使用 `upstream-5xx` 等稳定类别判断,但 durable retry record 保留安全的精确 `upstream-<HTTP status>` 身份。等待态必须从真实 record 显示 HTTP 状态、`nextAttempt/maxRetries` 与当前持久退避剩余秒数,例如“Provider 上游返回 HTTP 503,准备自动重试 1/3;预计 8 秒后重试”;不得以动画或前端自增计时伪造 attempt。重试耗尽的 Runtime 私有错误只保存 `kind/httpStatus/fingerprint/chars/retryAttempt/maxRetries/retryState`,前端和持久 conversation 仅在字段顺序、范围、状态一致且无尾随正文时派生“上游服务返回 HTTP 503;自动重试已耗尽(3/3)”;其它错误使用固定安全摘要。Provider 响应正文、URL/query、凭据、本地绝对路径、fingerprint、字符数和 `[redacted ...]` 占位符均不得进入用户可见消息。
- 跨轮阶段记录:game-chat 确实观察过活跃态的父 run 进入 completed / failed / cancelled 等终态,且正式 Supervisor conversation 已刷新后,客户端把本轮轮次、任务 / 计划完成度、最新试玩 / 静态检查、最近返工决定和已登记成果图片路径整理成一条 `【Supervisor 阶段记录】` 项目 assistant 消息。每个“项目 + 父 run”最多追加一次,进入现有 `conversation.write` 权限与项目 conversation 持久化链路,下一轮及重载后继续保留;加载时已经终态但本窗口未观察其活跃过程的旧 run 不补写,防止每次启动重复归档。阶段记录不是 Supervisor Runtime 正式回复,不写入 Agent Session、不增加 final assistant 数量,也不逐条复制原始事件或内部正文。
- 图片成果:当前 manifest 新增或恢复已登记的 PNG / JPEG / WebP 资源时,聊天消息流同步显示 Runtime-owned “Supervisor 成果图片”卡,最多展示最新 4 张并随 manifest 原位更新。图片必须通过现有 `read_local_project_image_preview` 读取,只允许当前授权项目中 `assets/` 下的已登记资源,继续执行 `file.read` auto 权限、真实格式、大小、尺寸、普通文件、祖先目录和项目根边界校验;前端只接受返回路径、媒体类型和 `data:` 前缀与请求完全一致的结果。缩略图点击后使用独立模态查看器,支持按钮与滚轮缩放、指针拖拽、双击 / 按钮复位、Esc / 按钮 / 遮罩关闭,移动端占满视口;不得在聊天卡下方追加展开区。图片卡不写入 conversation,不解析 assistant 文本中的任意 Markdown / 绝对路径,也不开放 `.agent` 验收截图读取。
- Run 接管:External Runner 模式下首次提交可能返回“旧 canonical state + 新 `acceptedRunId`”;页面必须以 `acceptedRunId` 作为本轮权威身份,在 state 尚未切换时显示“已投递,正在同步 Agent Runner”,并允许该 run 的 Tauri event 或轮询结果接管。不得把旧 idle state 当作本轮结果、过滤新 run 事件,自动预览授权也必须绑定 `acceptedRunId`。
- 运行容器:当前项目没有由共享 `PreviewRegistry` 返回的有效 `running` 预览时,页面只渲染聊天,不显示游戏区域或占位文案;预览运行后自动显示 iframe,桌面端按“游戏 2 / 聊天 1”分栏,移动端改为上下布局。预览停止、失败或切换项目后立即移除 iframe。运行容器继续只接受当前授权项目的 `http://127.0.0.1:*`,复用现有 CSP、iframe sandbox、autoplay、fullscreen 和 gamepad 约束;远程 URL、`file://`、手填地址或陈旧 manifest 状态均不得显示。
- 一次性自动预览授权:用户在该入口成功提交本轮自主生成需求,即视为对“当前项目 + 当前 Supervisor 父 run”的一次 `preview.start` 授权。授权以仅含项目路径与 accepted parent runId 的客户端本地记录持久化,App / WebView 重启后仍可恢复,但项目或 run 身份不匹配时不得使用。对应首版代码产物完成后只能成功启动一次,并必须继续走现有权限、项目写锁、审计和 `PreviewRegistry` 链路;项目或 Agent 策略的显式 deny 始终优先,不得被此授权绕过。启动成功、显式 deny、非瞬时失败、父 run 在首版完成前终止或切换项目后授权失效;`preview.start` 恰逢项目写锁竞争属于瞬时失败,不消费授权,释放写锁后由同一轮询链路重试。
- 系统边界:该页面是既有 AI 游戏创作工作台的临时开发态例外,不新增平台玩法入口、后端 API、会话库、Runner、预览服务或正式用户入口。原 `supervisor-chat` 继续固定使用 `standard` profile 并保持纯聊天行为,不继承本例外的自主构建、事件聚合或自动预览授权。
- 运行容器:当前项目没有由 Tauri 客户端 `PreviewRegistry` 返回的有效 `running` 预览时,页面只渲染聊天,不显示游戏区域或占位文案,顶部运行状态必须明确显示“预览未启动”,不得再使用含义不明的“未启动”;预览运行后自动显示 iframe,桌面端按“游戏 2 / 聊天 1”分栏,移动端改为上下布局。预览停止、失败或切换项目后立即移除 iframe。运行容器继续只接受当前授权项目的 `http://127.0.0.1:*`,复用现有 CSP、iframe sandbox、autoplay、fullscreen 和 gamepad 约束;远程 URL、`file://`、手填地址或陈旧 manifest 状态均不得显示。
- 预览进程归属:External Runner 与 Tauri 客户端位于不同进程,双方 `PreviewRegistry`、server 子进程句柄和 running 状态严格进程内隔离;Runner 为 `preview.validate` 持有或回收的预览进程不能作为用户可见预览,也不能据此伪造 Tauri registry 的 running 状态。game-chat 用户可见 preview 必须由 Tauri 客户端启动、持有和停止,iframe 只使用同一 Tauri registry 返回的 loopback URL。
- 自动启动门禁:客户端只接受当前 accepted Supervisor 父 run 下真实 `preview-playtest` scheduler child 的结构化 `preview.validate` 事件,同 revision 采用最新事件且同时间失败优先;成功证据的 revision 必须精确等于当前项目原子 sidecar revision。客户端向 Tauri `preview.start` 传入 `expectedRevision`,后端在取得项目写锁后再次比对再启动,以闭合检查 / 启动 TOCTOU。same-run steer 的一次性授权使用带 revision / validation cursor 和唯一 generation ID 的 v2 记录,旧事件和旧异步 attempt 不得消费后续授权。旧 attempt 返回后的补偿清理按完整 preview identity 原子停止 registry server,不能让项目 stop policy 或写锁竞争造成后台 server 泄漏;若同项目新 server 已接管,则不能把其持久状态覆盖为 stopped。
- 固定试玩契约:`generic-v1` 初始状态必须为 `ready` 且 `level > 0`;点击 start 后 sequence 必须推进、phase 必须进入 `playing`,并先持续观察 2 秒、取得至少 8 个实际样本,期间保持 `playing`,以确认玩家获得正常操作机会。随后必须点击唯一可见、启用且真实可交互的 `data-playtest-id="primary-action"` 控件;该控件必须映射游戏的真实主要玩法操作,并以 sequence 相对点击前严格推进证明操作已被接受。玩家获得这次正常操作机会之前进入 `won | lost` 属于过早结束并失败;操作被接受后的单次 `lost` 是合法游戏结局,但不能成为所有受控尝试的唯一结果;若主要操作后仍为 `playing`,则继续观察 3 秒并取得至少 12 个实际样本,`won` 可提前证明非失败推进。点击 restart 后 sequence 必须再次推进并恢复到 `ready | playing`,随后持续观察 3 秒且取得至少 12 个实际样本。若首轮结果为 `lost`,重开稳定后必须再执行一次必要的 start、2 秒 / 8 样本操作机会和真实 primary-action;第二次必须进入或保持 `playing`(再观察 3 秒 / 12 样本且不得转为 `lost`)或进入 `won`,两次都固定 `lost` 代表无法正常推进的恶性 bug,必须失败。各观察窗口内 sequence 不得回退,restart 窗口只能保持 `ready | playing`;样本数门槛不能替代时长门槛,窗口末端必须强制再读取一次有效状态,不能只在前段快速取得足够样本后提前通过。控件 selector、观察时长、最少样本数、终态边界、非失败推进、末端覆盖、sequence 单调 / 严格推进规则及完整 required assertions 都进入 scenario fingerprint。读取旧 fingerprint 回执和检查 plan liveness 时,把合同升级造成的 fingerprint 不匹配视为 stale missing,允许同一 run 重新执行 `preview.validate` 自愈;身份、路径、digest 或内容完整性篡改仍失败关闭。最终完成门每次按当前合同重算 fingerprint,并严格拒绝旧 fingerprint、旧 assertion 集或仅保存历史 `passed=true` 的证据。
- 试玩证据展示:game-chat 的进度卡、可玩 revision 和自动预览只接受结构化 `preview.validate` detail 同时满足 `passed=true` 与 `playtestPassed=true`;工具 summary 中的 `:ok` 不能作为兜底。`image.inspect` 的 `status=ok` 只代表工具执行成功,不代表视觉验收通过;结构化 `passed=null` 或缺少布尔结论时,UI 必须以中性“截图分析完成”展示,只有显式 `passed=true` 才能显示“截图检查通过”。
- 一次性自动预览授权:用户在该入口成功提交本轮自主生成需求,即视为对“当前项目 + 当前 Supervisor 父 run”的一次 `preview.start` 授权。授权以仅含项目路径与 accepted parent runId 的客户端本地记录持久化,App / WebView 重启后仍可恢复,但项目或 run 身份不匹配时不得使用。只有当前 accepted parent run 成功完成 `preview.validate` 且给出有效 revision 后,客户端才可消费授权,由 Tauri 首次启动并自动展示该 revision 的用户可见预览;一次授权最多成功启动一个 Tauri preview server,并必须继续走现有权限、项目写锁、审计和客户端 `PreviewRegistry` 链路。项目或 Agent 策略的显式 deny 始终优先,不得被此授权绕过。启动成功、显式 deny、非瞬时失败、父 run 在首版验证前终止或切换项目后授权失效;`preview.start` 恰逢项目写锁竞争属于瞬时失败,不消费授权,释放写锁后由同一轮询链路重试。
- 增量预览刷新:Tauri 客户端记录当前 iframe 已展示的 validated revision;同一当前 run 后续成功 `preview.validate` 的 revision 严格高于已展示 revision 时,只在原 Tauri preview server 和原 loopback origin 上刷新 iframe,不得再次调用 `preview.start`、新增 server 或切换到 Runner registry。相同或更低 revision 不触发刷新。preview HTTP server 对 HTML、脚本、样式、资源和错误响应统一发送 `Cache-Control: no-store`,iframe 刷新必须读取新 revision,不能继续命中 WebView 缓存中的旧版本。
- 退出与 Runner:独立 game-chat release 正常退出 Tauri 事件循环时调用仅供该 flavor 使用的认证 `runner.shutdown_for_client_exit`。Runner 先进入 draining、拒绝新的 Runtime 写请求,再无条件请求结束本 boot;已有 durable task / handoff / retry / reconciliation 状态不得伪装为 completed 或被删除,下次启动按既有 reconciliation 合同恢复。Windows game-chat 客户端以 `CREATE_SUSPENDED` 创建 Runner,在其执行用户代码前立即加入由客户端持有的 `JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE` Job,严格复核唯一主线程后再恢复运行;分配或恢复失败必须 kill + wait 并令客户端启动失败。Runner 的 command、MCP、Git、PTY 及其它未 breakaway 后代随客户端异常退出或 Job handle 关闭一起终止;Runner 正常退出前仍先执行既有进程会话收束。普通 dev / release 与 CLI 继续使用 `runner.shutdown_if_idle`,不改变共享 Runner 的原生命周期。
- Windows 后台进程可见性:所有不需要交互控制台的 `command.exec / project.verify`、STDIO MCP、Repository Context Git、`git.inspect / project.git_commit` 和清理用 `taskkill` 必须使用 `CREATE_NO_WINDOW`;需要独立终止边界的命令可同时使用 `CREATE_NEW_PROCESS_GROUP`,不得使用会增加孤儿风险的 `DETACHED_PROCESS`。game-chat release 禁止再打开 launcher、workspace 或其它平行 Tauri 页面。
- 真实浏览器回归:显式运行 `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml real_chrome_generic_playtest_ --features game-chat-release -- --ignored --nocapture --test-threads=1`,同时用真实 Chrome / Chromium / Edge 证明尚未接受真实 `primary-action` 就瞬时进入 `lost`、以及两次受控尝试都固定 `lost` 的页面必须判失败;首轮合法 `lost`、restart 恢复后第二轮进入 `playing | won` 的页面可以判通过。
- 系统边界:该页面是既有 AI 游戏创作工作台的独立构建例外,不新增平台玩法入口、后端 API、会话库、Runner 或预览服务,也不把入口并回普通正式客户端。原 `supervisor-chat` 继续固定使用 `standard` profile 并保持纯聊天行为,不继承本例外的自主构建、事件聚合或自动预览授权。
- 验证要求:每次修改该 flavor 至少执行壳配置门禁、AppSurface game-chat 定向测试、前端类型检查、AppData / 诊断日志 / release flavor 相关 Rust 定向测试、`npm run check:encoding` 和 `git diff --check`;正式打包必须执行 `npm run agc:build:game-chat-release`,安装或启动产物后确认首屏只能进入本地 game-chat、断开或停止 `api-server` 仍可进入工作台、普通/debug 认证未变化、AppData 使用独立 identifier,并分别 smoke“新目录 owner / DACL 正确”“历史 foreign owner 原目录被同级备份且配置不覆盖”“foreign-owner stale `agent-runner.lock` 在独占句柄下立即修复并启动”“活锁不被截断或接管”“reparse point / hardlink 失败关闭”“AppData 不可写时日志回退 TEMP”“`.setup()` 初始化失败显示日志位置对话框”“503 等待态显示真实 HTTP 状态、重试次数和退避秒数”“503 耗尽后状态卡与 conversation 只显示安全摘要”“后台命令执行期间不出现控制台窗口”“启动活跃任务后关闭主窗口,Runner、MCP、command、ConPTY 及孙进程均消失”“再次启动后 durable 状态进入正确 reconciliation”。日志检查必须同时验证 256 KiB 轮转、仅一份 previous、凭据和绝对路径零泄漏。
## Runtime 边界