Merge branch 'master' into fix/preview-iframe-resize-loop
Project CI / Repository checks (pull_request) Successful in 2m28s
Project CI / Frontend tests (pull_request) Successful in 2m53s
Project CI / Backend tests (pull_request) Successful in 7m16s
Project CI / Native shell tests (pull_request) Successful in 17m40s

This commit is contained in:
2026-09-03 10:35:09 +08:00
112 changed files with 5286 additions and 412 deletions
+1
View File
@@ -25,6 +25,7 @@
- [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):当前进程错误事件、应用级日志和管理员查看器合同。
- [立项策划 AgentFast GDD](<./technical/【技术方案】立项策划AgentFast GDD-2026-08-10.md>):当前策划入口、审批和恢复合同。
- [GameAgent 资源自由画板与快速编辑](./technical/【技术方案】GameAgent资源自由画板与快速编辑-2026-08-20.md)
- [UI 工作流资源桥接与 Runtime 执行](./【技术方案】UI工作流资源桥接与Runtime执行-2026-08-24.md)
@@ -7901,6 +7901,23 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在
- 任务最终状态不再提前绑定平台画布、preview、static smoke 或发布产物检查;这些内容不参与该档位的完成判定,也不会因缺失而重置已完成任务。父 run 在任务图进入终态后直接收束并回复。
- 本档位仍沿用现有项目根和工具权限边界;本次调整只解除流程编排与平台产物验收前置,不新增第二套任务系统。
## 2026-08-31 AGC 错误报告与诊断上传
- AGC 采用 IDEA 风格的当前进程错误池:按 fingerprint 合并 React / window / Promise / Tauri / Agent 错误,重启后不恢复,不使用 run 或 run_id。
- 应用级日志持续写入 Tauri AppData 并滚动;报告系统完全忽略项目 `.agent/logs`、源码、prompt、配置、项目产物和截图。用户只可补充文字描述。
- 用户点击独立“报告问题”面板并确认后,批量提交当前进程事件和可取消的脱敏应用日志;失败只允许当前进程手动再次提交。
- 上传接口为登录态 `/api/error-reports`,后台新增 error-reports Tab、专用文件化诊断包、状态与受控下载;管理员查看/下载进入审计链路。
- `/bug-report` 仅作为打开该面板的快捷入口,追加简短提示,不再生成包含项目、run 或截图口径的缺陷模板。
- 2026-08-31 追加:事件 DTO 精简为 `eventId/fingerprint/source/message/stack/occurredAt/count`,提交请求携带 `submissionId` 做幂等。归档固定为 `events.jsonl`,服务端使用 `agc/error-reports/v1/{batchId}.zip` 私有 OSS key;元数据只保留 batch、用户、状态、大小、SHA-256 和 OSS key,事件正文/说明/日志从归档读取。OSS 不可用或上传失败时不写数据库,客户端可重新提交。
- 2026-09-01 追加:`application.log` 不再写结构化错误事件;Rust `app_log!` 和 WebView console 都写入普通文本 raw log,结构化事件仅保留在当前进程内,提交时才生成 ZIP 内的 `events.jsonl`
- 2026-09-01 review 收口:错误报告修复详情请求竞态、下载 anchor 生命周期、客户端采集脱敏/指纹降级与 4xx 噪声、用户级幂等隔离、`agc` 私有 OSS 前缀越权、日志读取链接检查、ZIP 同名日志和元数据/归档清理一致性;同步在 `review.txt` 标注仍需产品/运维决定的架构项。
- 2026-09-01 追加:错误报告不依赖 api-server 本地文件、目录锁或同步文件 I/O;ZIP 只在请求内存构建后上传 OSS。管理员详情路由不属于 External OpenAPI;不存在返回 404,归档损坏返回 500。
- 2026-09-01 追加:错误报告不落本地文件;请求内存构建 ZIP 后直接上传 OSS,成功后写入 SpacetimeDB `error_report` 元数据。OSS key 固定为 `agc/error-reports/v1/{batchId}.zip`,不含日期;同一用户 `userId + submissionId` 幂等。管理员查询 DB,详情/下载按 object key 读取 OSS;每日清理先删 OSS,再删 DB,失败留待下次重试。
- 2026-09-01 review minor 修复:错误报告每日清理改为完整分页扫描,DB 删除失败显式返回以便下周期重试;OSS 读取按 `Content-Length` 与流式累计执行大小上限;管理员归档解析限制解压后 `events.jsonl` 为 24 MiB / 100 条事件。
- 2026-09-02 review breaking 项落地:错误报告 create/update procedure 删除 `user_id``now_micros` 输入字段,分别改用 `ctx.sender()``ctx.timestamp`;首个 fingerprint/source 强制 512 字符上限,备注超过 2,000 字显式拒绝;内部 OSS 对象键收紧为 `agc/error-reports/v1/`,凭据脱敏覆盖空白/分隔符变体。发布需同步 module、spacetime-client bindings 与 api-server。
- 2026-09-02 客户端错误报告提交成功与本地队列 ack 解耦:POST 成功即显示提交成功并关闭,ack 使用有限重试且失败只记录日志;同一批 eventId 在进程内复用稳定 submissionId,避免 ack 失败后重新提交产生重复报告。
- 2026-09-02 review 后续收口:错误报告创建增加服务 identity 门禁与单 identity 每小时 100 次配额;开发期将已验证登录用户 `user_id` 由 api-server 注入 procedure 并由 module 校验;module 收紧固定 OSS key、SHA-256、归档大小和计数;管理员 PATCH 返回轻量元数据并在详情弹窗提供备注编辑器;列表无过滤查询使用 `created_at` BTree 索引。
## 2026-09-01 UI 编辑器代码导出与填充预览边界
- UI 编辑器导出的 `ui/generated-*.js` 是派生本地产物。代码生成只写文件,绝不推进项目 revision、UI State revision、manifest 阶段或 Runtime 验证门;写入失败只返回生成错误,不能把生成文件写入冒充项目 mutation。
+12 -5
View File
@@ -2,6 +2,13 @@
> 当前口径:本文件保留可复用的排障经验;历史条目的旧路由、旧版本和已删除文档仅作根因背景,不得据此恢复退役入口。当前命令、路由和 schema 以代码与 `docs/README.md` 为准。
## 2026-09-02 Tauri 事件桥在浏览器预览中必须 fail-safe
- **现象**Vitest/jsdom 挂载 AGC 客户端时,错误报告通知调用 `@tauri-apps/api/event.listen`,因缺少 `window.__TAURI_INTERNALS__` 产生未处理拒绝;测试断言虽通过,CI 仍以 unhandled errors 失败。
- **原因**:错误报告订阅是非阻塞唤醒通道,不能假定所有渲染环境都已初始化 Tauri IPC;模块级 `listen` 在 API 调用前就会访问 `transformCallback`,仅在调用方包一层 `.then` 无法消除该环境差异。
- **处理**:订阅桥先复用 `window.__TAURI__.event.listen`(含 globalTauri/native shim),其次仅在 `__TAURI_INTERNALS__` 存在时调用模块 API;浏览器预览或订阅失败统一返回 no-op,并在消费层收口 rejection。错误快照读取、焦点和可见性刷新仍是权威路径。
- **验证**`errorReporting.test.ts` 覆盖无 Tauri 环境无未处理拒绝;`appSurface.test.ts` 全部 385 条用例通过且无 Vitest unhandled errors。
## 2026-08-27 Provider 成功 handoff 失败时需要保留本地私有原始响应
- **现象**Provider 已返回响应,但 tool-plan handoff 因绝对路径或其它内容安全校验失败,Runtime 只留下 `failureKind`、哈希和被压平的 JSON pointer;排障时无法确认实际工具名和完整 arguments。
@@ -4296,13 +4303,13 @@
- 处理:通用配置只保留跨平台 bundle 项;Windows 原生侧车的完整白名单放入 Tauri 自动合并的 `tauri.windows.conf.json`。不要提交二进制占位文件,也不要让非 Windows build script 下载或伪造 Windows 资源。
- 验证:配置门禁断言通用配置没有 Windows resource、Windows 平台配置保留完整固定白名单;Linux 运行原生壳门禁必须越过 Tauri resource 校验,Windows release 仍由 build script 对 npm 原生包、SHA-256 清单和目标布局失败关闭。
## AGC Skill 指纹与相对路径校验必须跨平台一致(2026-08-21
## AGC Skill 指纹与安装内容必须跨平台一致(2026-08-21
- 现象:内置 Skill 文件集合没有缺失,原生测试却统一报内容指纹不匹配;另一个测试在 Linux 上把 `C:\\temp\\SKILL.md` 判为安全相对路径,受控资源工具可能继续处理 Windows 盘符或反斜杠遍历形式
- 原因:审核文件定稿后未按最终字节重新生成 manifest SHA-256同时 `std::path::Path` 只按当前宿主语义解析路径,Linux 不会把 Windows 盘符和反斜杠视为绝对路径或分隔符
- 处理:Skill 文件变化与 manifest 指纹更新必须同次提交,并提升审核包版本;资源引用只接受使用 `/` 的普通相对段,显式拒绝反斜杠、冒号盘符、UNC、绝对路径和父目录段,再查询审核清单。不要先把反斜杠替换成 `/` 后再做安全检查
- 现象:内置 Skill 文件集合没有缺失,原生测试却统一报内容指纹不匹配;安装后的 Skill 文件与 manifest 摘要不一致会导致构建校验失败
- 原因:审核文件定稿后未按最终字节重新生成 manifest SHA-256安装和原生 Skill loader 必须看到与清单一致的 UTF-8 文件集合
- 处理:Skill 文件变化与 manifest 指纹更新必须同次提交,并提升审核包版本;references 由 Codex 原生按 Skill 声明的相对路径读取,不再经过 AGC 自定义资源读取器
- 回归补充:即使 Skill 文件本轮没有变化,也不能从旧提交或旧构建结果复制清单指纹;必须对当前工作树按 UTF-8 读取、将 CRLF 规范为 LF 后现场重算,并在提交前运行原生 Skill Pack 校验。Git 的 `eol=lf` 不能阻止编辑器在干净工作树里留下少量混合 CRLF,而 Cargo `include_bytes!` 会读取这些原始字节;因此运行时计算与安装也必须使用同一规范化函数。运行时只报告排序后的首个不匹配项,不能据此假定其余 Skill 已通过。
- 验证:逐项按排序后的 `relativePath + NUL + canonical UTF-8 LF bytes + NUL` 重算并核对 manifestRust 单测同时覆盖 LF / CRLF 指纹等价安装结果只含 LF、POSIX 绝对路径、`..``C:\\...``C:/...`、UNC 和反斜杠相对路径,受控 MCP 工具也必须把 Windows 绝对路径投影为 `isError=true`
- 验证:逐项按排序后的 `relativePath + NUL + canonical UTF-8 LF bytes + NUL` 重算并核对 manifestRust 单测覆盖 LF / CRLF 指纹等价安装结果只含 LF,并确认隔离 HOME 中的根 Skill 与 references 可由 Codex 原生读取
## Gitea CI 预构建镜像不能只靠 tag 判断内容
@@ -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,并在启动时额外读取客户端扩展仓库中已启用的第三方 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 始终连接客户端内置的 `agc_tools` STDIO MCP,并在启动时额外读取客户端扩展仓库中已启用的第三方 MCP 独立项。第三方 STDIO/HTTP 配置只写入本次隔离 `CODEX_HOME`,单项非 required,启停、重命名和内容指纹进入 app-server pool identity;完整 Plugin Runtime、hooks/apps 和单文件脚本手动指定入口仍关闭。Skill 正文与 references 由 Codex 原生按需读取;`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 完成。
@@ -10,12 +10,12 @@
## 2. 当前基线与目标
当前后台由 `GENARRATIVE_ADMIN_USERNAME``GENARRATIVE_ADMIN_PASSWORD` 提供唯一管理员账号,`apps/admin-web/src/app/adminRoutes.ts` 定义 15 个可分配业务 Tab,并另有 owner-only 的“账号管理”Tab`server-rs/crates/api-server/src/modules/admin.rs` 中的后台路由只校验统一的管理员 JWT。
当前后台由 `GENARRATIVE_ADMIN_USERNAME``GENARRATIVE_ADMIN_PASSWORD` 提供唯一管理员账号,`apps/admin-web/src/app/adminRoutes.ts` 定义 16 个可分配业务 Tab,并另有 owner-only 的“账号管理”Tab`server-rs/crates/api-server/src/modules/admin.rs` 中的后台路由只校验统一的管理员 JWT。
改造后的目标如下:
1. 现有环境变量账号升级为 `owner`,仍由部署环境提供,不迁移、不复制到 SpacetimeDB。
2. owner 始终拥有全部 15 个业务 Tab 权限,并独占“账号管理”Tab 和账号管理 API。
2. owner 始终拥有全部 16 个业务 Tab 权限,并独占“账号管理”Tab 和账号管理 API。
3. owner 可以创建、修改、启停 membermember 保存在 SpacetimeDB 私有表 `admin_account`
4. member 按一级 Tab 分配常规权限;获得一个 Tab 权限即获得该页面内常规读写能力。历史花费手动对账必须另行授予 `profile-wallet-consumption-reconcile`,任何 Tab 都不隐式包含。
5. member JWT 每次请求都重新读取当前账号并校验 `enabled``token_version`、实时 Tab 权限和独立操作权限,权限、密码或启停变更应立即让旧 JWT 失效。
@@ -26,7 +26,7 @@
- owner 用户名和密码继续读取 `GENARRATIVE_ADMIN_USERNAME``GENARRATIVE_ADMIN_PASSWORD`
- owner 是环境变量构造的虚拟账号,不写入 `admin_account`,不允许通过后台改名、改密、禁用或删除。
- owner 始终拥有本文列出的全部 15 个 Tab 权限和全部独立操作权限,不能在前端取消,也不从数据库加载权限。
- owner 始终拥有本文列出的全部 16 个 Tab 权限和全部独立操作权限,不能在前端取消,也不从数据库加载权限。
- “账号管理”是 owner-only 能力。它可以作为新增一级路由 `accounts` / `#accounts` 展示,但 `accounts` 不进入 `ADMIN_TAB_PERMISSIONS``ADMIN_ACTION_PERMISSIONS`,不能写入 member 的权限 JSON。
- owner 会话返回 `accountRole = "owner"``roles = ["admin", "owner"]`;账号管理权限必须根据服务端确认的 `accountRole` 判断,不能只相信前端角色字符串。
- owner 配置缺失时,后台整体保持未启用状态;不能依赖数据库中的 member 绕过 owner 引导配置启动后台。
@@ -41,7 +41,7 @@
## 4. 权限标识
`ADMIN_TAB_PERMISSIONS` 必须是 shared-contracts 与 admin-web 共用的闭合集合,值与现有 `AdminRouteId` 一致。15 个可分配权限如下,顺序同时作为前端寻找“第一可访问项”的稳定顺序:
`ADMIN_TAB_PERMISSIONS` 必须是 shared-contracts 与 admin-web 共用的闭合集合,值与现有 `AdminRouteId` 一致。16 个可分配权限如下,顺序同时作为前端寻找“第一可访问项”的稳定顺序:
| permission id | 一级 Tab | hash |
| --------------------------- | --------- | ---------------------------- |
@@ -50,6 +50,7 @@
| `tables` | 表查询 | `#tables` |
| `debug` | API 调试 | `#debug` |
| `tracking` | 埋点数据 | `#tracking` |
| `error-reports` | 错误报告 | `#error-reports` |
| `gray-release` | 灰度发布 | `#gray-release` |
| `redeem` | 兑换码 | `#redeem` |
| `invite` | 邀请码 | `#invite` |
@@ -86,7 +87,7 @@ Tab 权限数组必须去重并按上表顺序规范化后保存。保存时拒
| `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 节 15 个值,空数组为 `[]` |
| `tab_permissions_json` | `String` | 规范化后的 Tab permission JSON;只允许第 4 节 16 个值,空数组为 `[]` |
| `enabled` | `bool` | 是否允许登录和继续使用现有 JWT |
| `token_version` | `u64` | 初始为 `1`;权限、密码或启停状态发生有效变化时加 `1` |
| `created_by` | `String` | 创建者后台 subject;当前只能是 owner subject |
@@ -168,7 +169,7 @@ tabPermissions: string[]
actionPermissions: string[]
```
owner 返回全部 15 个 Tab permission id 和全部独立操作权限;member 返回数据库中的两组实时规范化数组。`GET /admin/api/me` 同样执行逐请求校验并返回实时权限,供刷新页面后恢复导航和操作按钮。
owner 返回全部 16 个 Tab permission id 和全部独立操作权限;member 返回数据库中的两组实时规范化数组。`GET /admin/api/me` 同样执行逐请求校验并返回实时权限,供刷新页面后恢复导航和操作按钮。
后台所有面向运营展示的管理员身份统一使用 `displayName`。审计表继续保存稳定 subject,例如 owner subject 或 `admin-account-<uuid>`api-server 在返回兑换码、邀请码等操作记录时,按 owner 运行态和 `admin_account` 批量解析显示名称,同时兼容历史用户名记录。已无法解析的历史主体统一展示“已停用管理员”,前端不得直接渲染 `operatorUserId`、账号 ID 或登录用户名代替显示名称。对写接口,显示名目录必须在主事务前加载,或在主事务成功后降级为占位文案;不得因二次读取失败把已提交写入伪装成失败。
@@ -309,7 +310,7 @@ accounts: Array<{
### 11.1 路由与导航
- `adminRoutes` 增加权限元数据;15 个业务路由使用同名 permission id。
- `adminRoutes` 增加权限元数据;16 个业务路由使用同名 permission id。
- `accounts` 路由只在 `admin.accountRole === "owner"` 时加入侧栏和移动底栏,不属于 member 可分配列表。
- member 导航只渲染 `admin.tabPermissions` 包含的业务路由。页面组件也必须只在当前路由已授权时挂载,避免隐藏导航后仍发起无权限 API。
- owner 渲染全部业务路由和账号管理路由。
@@ -321,13 +322,13 @@ accounts: Array<{
1. 当前 hash 对应可访问路由时保持不变。
2. hash 未知、属于无权限业务 Tab,或 member 访问 `#accounts` 时,使用 `replaceState` 回落到按第 4 节顺序找到的第一可访问业务 Tab。
3. member 权限为空时,不回落 Dashboard;渲染独立的零权限空态,只保留账号信息和退出登录,不挂载任何业务页,也不发起业务 API。
4. owner 的默认项仍可保持 Dashboard;账号管理不改变 15 个业务路由的排序。
4. owner 的默认项仍可保持 Dashboard;账号管理不改变 16 个业务路由的排序。
后端返回 `403` 时,前端重新请求 `/me` 获取实时权限并执行上述回落。即使前端状态陈旧或被篡改,后端权限 middleware 仍必须拒绝越权请求。
### 11.3 账号管理页
- 权限编辑器分为 15 个 Tab checkbox 和独立操作权限区;当前独立区只显示“手动对账用户历史花费”。不能展示或提交 `accounts`
- 权限编辑器分为 16 个 Tab checkbox 和独立操作权限区;当前独立区只显示“手动对账用户历史花费”。不能展示或提交 `accounts`
- 创建和编辑使用独立弹窗或抽屉,不在列表下方追加表单。
- 编辑时密码字段默认空,空表示请求中省略 `password`;页面永不展示现有密码或 hash。
- 停用使用开关并二次确认。保存成功后以 API 返回 account snapshot 更新列表。
@@ -403,7 +404,7 @@ spacetime publish <database> \
### 14.2 前端
- owner 看到 15 个业务 Tab 和账号管理;member 只看到被分配的业务 Tab。
- owner 看到 16 个业务 Tab 和账号管理;member 只看到被分配的业务 Tab。
- 常规二级 Tab、弹窗和写操作继承一级权限;历史花费对账按钮只在用户详情返回 `canReconcileConsumption=true` 时显示。
- 直接输入无权限 hash 自动替换为第一可访问项,不短暂挂载无权限页面。
- 当前 Tab 权限被 owner 收回后,下一请求触发重新登录;新会话恢复后落到第一可访问项。
@@ -40,7 +40,7 @@ Windows x64),通过 `AGC_UPDATE_ARTIFACT` 指定要发布的安装包,通
OSS 前缀,通过 `AGC_RELEASE_VERSION` 指定三段版本号(仅在明确需要复现指定版本时使用),通过
`AGC_UPDATE_RELEASE_NOTES` 写入发布说明;`--no-bundle` smoke 构建不会读取 OSS、修改版本或生成清单。
每次发布安装包上传完成后,再上传同一目录生成的 `latest.json`,确保 `downloadUrl` 指向已存在的 OSS 对象;清单和安装包均使用公开可读对象,不在清单中保存凭据、签名或本地路径。构建脚本本身不负责上传 OSS,发布流水线通过 `release:upload` 完成上传。
每次发布安装包上传完成后,再使用 ossutil 的 `--force` 覆盖上传同一目录生成的 `latest.json`,确保固定的 latest 指针和 `downloadUrl` 指向已存在的 OSS 对象;未显式强制覆盖时,ossutil 在目标已存在时会交互询问并按默认值跳过,不能作为 Jenkins 非交互发布方式。清单和安装包均使用公开可读对象,不在清单中保存凭据、签名或本地路径。构建脚本本身不负责上传 OSS,发布流水线通过 `release:upload` 完成上传。
如需一键构建并上传,可执行 `npm run ai-game-creator-shell:release:upload`。该命令要求本机已安装并配置 `ossutil`
先按上述规则比较 OSS 版本、递增 patch、构建 Windows x64 NSIS,再上传安装包和 `latest.json`。默认上传到
@@ -0,0 +1,45 @@
# AGC 错误报告与诊断上传
## 范围
AI Game Creator Shell 采用 IDEA 风格的当前进程错误报告:错误事件在内存池中按 fingerprint 合并;应用级诊断日志持续写入 Tauri AppData 的滚动文件;用户打开“报告问题”面板并确认后,一次提交当前进程选中的错误事件和脱敏应用日志。项目目录 `.agent/logs/*`、源码、prompt、配置、项目产物和截图不进入报告包。
## 客户端
- 捕获 React render error、`window.onerror``unhandledrejection` 以及显式标记的 Tauri/API/Agent 错误。Agent Runtime 的终态失败、预算耗尽和启动确认失败由 Rust 失败投影统一入池;Direct Codex 与专业 Agent 的前台裸 Tauri invoke catch 作为补充入口,重复事件由同一 fingerprint 合并,主动取消和“同一 turn 已在运行”不作为错误采集。
- 事件字段包括 eventId、fingerprint、source、message、stack、时间和次数;重复事件合并。不再携带 severity、errorCode、page、action、requestId 等无法稳定关联的字段。
- 指纹计算可使用调用方的 page/action 及脱敏后的首个调用点作为进程内区分输入,但这些上下文不会作为事件字段上传;消息与 stack 在入池前统一脱敏,WebCrypto 失败时降级为稳定可读指纹,采集本身不得产生新的未处理拒绝。
- 客户端 API 自动采集只覆盖网络错误、408 和 5xx;预期的 4xx 登录/鉴权失败不进入错误报告池。
- Rust 侧通过 `app_log!` 将普通文本日志同时输出到 stderr 和 AppData `diagnostics/application.log`,超出 256 KiB 滚动到 `application.previous.log`WebView 的 console 输出通过 `append_application_log` 镜像到同一 raw log,并在客户端桥接处再次脱敏;`read_diagnostic_logs` 只读取应用级日志。
- 报告面板只由自动诊断通知中的“查看并报告”打开,不提供聊天命令、崩溃页按钮或其他手动入口;默认选中最新事件,其他事件可勾选。允许填写最多 2,000 字中文描述并取消日志附件;本版本不支持截图或任意文件附件。
- Rust 是结构化错误队列的唯一真相源:`src-tauri/src/error_report/` 负责脱敏、调用点指纹、eventId、计数、100 条上限、5 秒聚合和 ack 生命周期;WebView 仅通过 Tauri bridge 上报、读取快照并保存短暂 React 展示状态,不维护第二份事件 Map。Runtime 错误上报统一使用 `source=agent-runtime``action=agent-runtime` 和 Agent ID 作为 page;包含 `kind=codex-app-server-*``codex-app-server-error:*` 的消息在入池前归一为稳定类别(例如 `codex-app-server-error:other`),不把 public summary 中的动态 fingerprint/长度作为分桶输入。Rust emit 只作为无状态唤醒,携带单调递增的 generation,不携带错误正文或事件 ID。
- 错误事件先在当前进程内存池按 fingerprint 合并,经过 5 秒聚合后只发出一次非阻塞存在性唤醒;WebView 挂载、收到唤醒、重新获得焦点或恢复可见时都查询完整未 ack 快照,并在桥接暂时失败时做有限退避重试。通知支持“查看并报告”和“忽略”,同一 fingerprint 仅在新增时唤醒一次。通知不直接打开阻塞式报告面板。忽略只关闭当前 UI,不删除事件;提交成功后由 bridge ack/delete 选中事件。
- 上传失败只在当前进程显示失败并允许用户再次提交,不跨重启恢复事件池,不后台自动重试。
## HTTP 与存储
- 登录态客户端使用 `POST /api/error-reports`,请求 DTO 位于 `shared-contracts::error_reports`
- api-server 对请求体设置 24 MiB 上限,并校验 schemaVersion、submissionId、事件/日志数量和 20 MiB 压缩包上限;事件字段、用户说明和日志名/内容均做长度限制与凭据脱敏(匹配归一化覆盖 Unicode 空白字符),归档使用 `events.jsonl`(每行一个事件)。结构化事件只保存在当前进程内,用户提交时才生成 `events.jsonl`,不在磁盘单独持久化。submissionId 提供重放幂等;客户端对同一批事件复用稳定 submissionId,服务端提交成功后立即反馈成功,本地事件 ack 独立重试,不因 ack 失败误报提交失败;SpacetimeDB procedure 使用服务 identity 门禁,`user_id` 由 api-server 注入并在 module 校验,时间戳仍使用 `ctx.timestamp`。创建 procedure 额外限制单 identity 每小时 100 次提交。
- 归档构建只在请求生命周期内使用受 20 MiB 上限约束的内存 `Vec<u8>`,随后直接 PUT 到私有 OSS;服务端不写本地报告文件,也不保留本地索引。OSS 上传失败不写入数据库,调用方可稍后重新提交。
- 归档对象使用固定私有 OSS key:`agc/error-reports/v1/{batchId}.zip`;key 只由报告 UUID 决定,不包含时间戳。上传成功后才写入 SpacetimeDB `error_report` 元数据表;`userId + submissionId` 由唯一幂等键保证重放返回已有记录。完整事件、说明和日志只存在 OSS ZIP。
- `agc` 是服务端专用私有前缀;公共直传票据、通用 object-key 规范化和 legacy 公开路径均拒绝该前缀。归档内同名日志会自动加数字后缀,读取本机诊断日志时拒绝符号链接/非普通文件。
- 后台接口:`GET/PATCH /admin/api/error-reports/{batchId}``GET /admin/api/error-reports` 和受保护的 `/download`。列表支持 `limit`/`offset` 分页并返回 `total``hasMore`OSS 读取先检查 `Content-Length` 并在流式累计超过上限时立即中止,不把超限对象完整缓存在内存中。
- 这些是 api-server 内部登录/管理员路由,不属于 `/api/external/v1`,不纳入 External OpenAPI;管理员详情对不存在返回 404,对归档/元数据损坏返回 500。
- admin viewer 仅接受 error-reports Tab 权限,支持列表筛选、分页、详情、状态 `new/in-progress/resolved`、处理备注和受控下载;不存在的更新目标返回 404,存储损坏返回 500。列表行支持键盘 Enter/Space 打开详情,详情事件预览最多显示 20 条,完整内容通过诊断包下载获取。
- 管理员列表、筛选、状态和备注全部读取/更新 SpacetimeDB;错误报告的 list/get/update procedure 要求 `require_editor_generation_runtime_service_identity`,详情先读表再从 OSS 下载并解析 ZIP,下载接口直接从 OSS 返回 ZIP。PATCH 更新直接返回元数据,不重新下载归档;详情弹窗提供备注编辑器。无需新增管理员 DELETE HTTP 接口。每日清理任务按分页扫描全部报告,删除过期 OSS 对象后再删 DB;任一 DB 删除失败会保留错误并在后续周期重试。详情解析对解压后的 `events.jsonl` 设置 24 MiB(与请求体上限一致)与 100 条事件上限。管理员备注超过 2,000 字会被明确拒绝;module 同时校验固定 OSS key、SHA-256、归档大小和事件/日志计数上限;OSS 读取仅将明确不存在映射为 404,其余错误按请求无效或上游故障返回。内部 OSS 读写只允许 `agc/error-reports/v1/`。旧本地报告不迁移。
SpacetimeDB `error_report` 表字段:`batch_id` 主键、`user_id``submission_id``idempotency_key` 唯一键、`object_key``archive_sha256``archive_size_bytes``event_count``log_count`、首个 fingerprint/source、`review_status``admin_note``created_at``updated_at`;索引为 `(user_id, submission_id)``created_at``review_status`
## 验收
```text
npm run typecheck --workspace @genarrative/ai-game-creator-shell
npx tsc -p apps/admin-web/tsconfig.json --noEmit
npx vitest run apps/ai-game-creator-shell/tests/errorReporting.test.ts apps/ai-game-creator-shell/tests/clientRuntimeErrorBoundary.test.ts apps/admin-web/src/app/adminRoutes.test.ts
cargo test -p api-server --bin api-server error_reports -- --nocapture
cargo fmt -p api-server -p shared-contracts -- --check
npm run check:encoding
git diff --check
```
Rust 全量检查还受现有 `platform-llm` ProviderReasoningEffort::Max 编译错误阻塞;本变更未修改该历史问题。
@@ -1,5 +1,12 @@
# AI 游戏创作智能体 App 实施计划
## 2026-09-02 客户端会话恢复可观测性与超时兜底
- AGC 客户端启动恢复按“读取本地凭据 → 刷新会话(无 token 或失效时)→ 读取当前用户 → Tauri 本地运行时会话安装”阶段执行。界面必须展示当前阶段和已等待时间;不能以无期限的单一 loading 文案隐藏网络或 Runner 故障。
- 客户端 HTTP 传输默认使用 15 秒超时并通过独立 `AbortController` 终止请求;调用方可为确需长耗时的请求显式传入 `timeoutMs: null`。调用方主动取消仍保留原始 `AbortError`,超时使用稳定的 `ClientHttpTimeoutError`,由认证层转换为可操作的中文提示。
- 会话恢复或本地 Runner 连接超时后必须进入登录页并提供“重试登录状态检查”。重试递增恢复代次并以运行标识忽略旧恢复任务的迟到 UI 写回;不得清除仍可用于后续重试的 access token,也不得重复并发刷新同一服务器的 refresh 请求。
- Tauri Runner 的启动与 IPC 超时继续以 `runner/protocol.rs` 的 30 秒启动、10 秒读写为权威;启动等待循环会把 endpoint 探测预算裁剪到剩余启动期限,避免单次 ping 把 30 秒门禁延长。考虑复用旧 endpoint 前可能先消耗一次 IPC 等待,前端 UI 兜底取 45 秒,不改变 Runner 协议、启动策略或认证接口。
## 2026-08-26 运行中自主扩图提案
- `agent-runtime-orchestration` 提供严格 serde 的 `GraphProposal``TaskProposal``GraphEdge``GraphLimits`。宿主可把 LLM function-call arguments 解析后交给 `TaskGraph::apply_proposal`,在内存中得到新的、完整校验过的候选 DAG。
@@ -667,7 +674,7 @@ game-project/
- 聊天输入 `/test-plan` 可在普通聊天消息里准备手动测试计划,不读取文件、不启动或打开预览、不直接继续 run,也不新增普通用户测试面板。
- 聊天输入 `/audience` 可在普通聊天消息里查看首批试玩对象,不读取文件、不启动或打开预览、不导出试玩包、不写项目,也不新增普通用户对象面板。
- 聊天输入 `/invite` 可在普通聊天消息里准备试玩邀请文案,不读取文件、不启动或打开预览、不导出试玩包、不写项目,也不新增普通用户邀请面板。
- 聊天输入 `/bug-report` 可在普通聊天消息里准备缺陷复现记录,不读取文件、不启动或打开预览、不导出试玩包、不写项目,也不新增普通用户缺陷面板
- 聊天输入 `/bug-report` 只打开独立“报告问题”面板并允许填写用户说明;不读取项目文件、不启动或打开预览、不导出试玩包、不上传截图或任意文件、不写项目,也不依赖 run / run_id
- 聊天输入 `/survey` 可在普通聊天消息里准备试玩问卷问题,不读取文件、不启动或打开预览、不导出试玩包、不写项目,也不新增普通用户问卷面板。
- 聊天输入 `/cover` 可在普通聊天消息里准备封面与缩略图检查,不截屏、不裁剪、不读取文件、不启动或打开预览、不导出试玩包、不上传云端、不发布作品、不写项目,也不新增普通用户封面面板。
- 聊天输入 `/screenshots` 可在普通聊天消息里准备宣传截图清单,不截屏、不读取文件、不启动或打开预览、不导出试玩包、不写项目,也不新增普通用户截图面板。
@@ -742,7 +749,7 @@ game-project/
- `/test-plan` 聊天入口由 `appSurface.test.ts` 主窗口 smoke 覆盖:只基于当前已加载 manifest、最近 run trace 和 preview 状态准备手动测试用例、关联任务和试玩证据,提供 `/run``/open-preview``/trace``/review``/next` 草稿,不触发 Tauri 读写、不读取文件、不启动或打开预览、不直接继续 run、不新增普通用户测试面板。
- `/audience` 聊天入口由 `appSurface.test.ts` 主窗口 smoke 覆盖:只基于当前已加载 manifest、最近 run trace、preview 和试玩任务状态汇总首批试玩对象、邀请顺序和观察重点,提供 `/run``/feedback``/review``/trace``/next` 草稿,不触发 Tauri 读写、不读取文件、不启动或打开预览、不导出试玩包、不写项目、不新增普通用户对象面板。
- `/invite` 聊天入口由 `appSurface.test.ts` 主窗口 smoke 覆盖:只基于当前已加载 manifest、最近 run trace 和 preview 状态准备测试者邀请对象、邀请文案、发送前检查和收反馈口径,提供 `/run``/feedback``/review``/trace``/next` 草稿,并在 `/next``/help` 暴露入口;不触发 Tauri 读写、不读取文件、不启动或打开预览、不导出试玩包、不写项目、不新增普通用户邀请面板。
- `/bug-report` 聊天入口由 `appSurface.test.ts` 主窗口 smoke 覆盖:只基于当前已加载 manifest、最近 run trace 和 preview 状态准备问题一句话、复现步骤、期望 / 实际、设备 / 输入方式、严重度和附件口径,提供 `/run``/agent-resume 缺陷修复:``/review``/trace``/next` 草稿,并在 `/next``/help` 暴露入口;不触发 Tauri 读写、不读取文件、不启动或打开预览、导出试玩包、不写项目、不新增普通用户缺陷面板
- `/bug-report` 聊天入口由 `appSurface.test.ts` 主窗口 smoke 覆盖:只追加“已打开报告问题面板”提示并触发独立面板;面板提交前读取应用级诊断日志(不读取 `.agent/logs/*`),只上传当前进程错误事件、脱敏日志和用户文字说明,不上传截图或任意文件,不触发项目读写、预览、导出或 run / run_id
- `/survey` 聊天入口由 `appSurface.test.ts` 主窗口 smoke 覆盖:只基于当前已加载 manifest、最近 run trace 和 preview 状态准备首批测试者问卷、五个核心问题、记录格式和追踪方式,提供 `/run``/invite``/review``/trace``/next` 草稿,并在 `/next``/help` 暴露入口;不触发 Tauri 读写、不读取文件、不启动或打开预览、不导出试玩包、不写项目、不新增普通用户问卷面板。
- `/retention` 聊天入口由 `appSurface.test.ts` 主窗口 smoke 覆盖:只基于当前已加载 manifest、最近 run trace、preview、最近试玩证据、素材数量、发布说明和导出状态准备首轮复玩/留存观察清单,提供 `/run``/feedback``/review``/trace``/next` 草稿,并在 `/next``/help` 暴露入口;不触发 Tauri 读写、不读取文件、不启动或打开预览、不导出试玩包、不上传云端、不发布作品、不写项目、不新增普通用户留存面板,也不做真实埋点、留存报表、用户画像、A/B 实验、排行榜或账号留存。
- `/cover` 聊天入口由 `appSurface.test.ts` 主窗口 smoke 覆盖:只基于当前已加载 manifest、最近 run trace、preview 和资产状态准备封面与缩略图候选、用途尺寸、选择口径和补齐路径,提供 `/run``/art``/listing``/review``/trace``/next` 草稿,并在 `/next``/help` 暴露入口;不触发 Tauri 读写、不截屏、不裁剪、不读取文件、不启动或打开预览、不导出试玩包、不上传云端、不发布作品、不写项目、不新增普通用户封面面板。
@@ -1183,8 +1190,8 @@ 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 MCP2026-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 和完整插件能力继续关闭。
- `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` 注册并复核,完整正文与 references 均由 Codex 原生 Skill 机制按意图和声明的相对路径按需读取
- DirectProject 始终连接客户端内置的 `agc_tools` STDIO MCP2026-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 行为。
@@ -190,7 +190,6 @@ camelCase JSON。禁止出现附件正文、命令 stdout、patch diff、宿主
| `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 字。
@@ -633,6 +633,13 @@ Responses 的终态载荷既是工具调用的恢复源,也是正文的恢复
- 说明:外部 OpenAPI 调用使用的账号级 API Key 凭据表,只保存 key prefix、SHA-256 hash、作用域、撤销状态和使用时间;明文 Key 只在 `/api/profile/api-keys` 创建接口返回一次,不进入 SpacetimeDB,且 API Key 管理接口不写入外部 OpenAPI JSON。v1 默认作用域为 `editor:project``editor:canvas``editor:image-generate``editor:asset`;其中 `editor:project` 覆盖项目列表、最近项目、创建、读取、重命名和删除,`editor:canvas` 覆盖默认画布布局保存,`editor:image-generate` 覆盖编辑器现有图片生成、重绘 / 调整、去背景、规范图、宣发素材、图标 spritesheet 生成 / 拆分、UI 设计图素材拆分、角色动画、视频、音效和背景音乐生成,`editor:asset` 覆盖素材直传凭证、素材对象确认、签名读取、账号级素材库和项目画布资源记录操作。
- 索引:`by_external_api_key_owner_user_id` 用于登录态 API Key 列表;`key_hash` 唯一索引用于外部 API 鉴权。
### `error_report`
- Rust 结构体:`ErrorReport`
- 源码:`server-rs/crates/spacetime-module/src/error_report.rs`
- 说明:错误报告只在请求内存中构建 ZIP 并上传私有 OSSSpacetimeDB 仅保存 batch、幂等、对象键、摘要、计数和管理员审核元数据。管理员列表/筛选/更新走 `spacetime-client` facade,详情和下载再从 OSS 读取 ZIP。OSS key 固定为 `agc/error-reports/v1/{batchId}.zip`,不含时间戳;上传失败不写表。
- 索引:`by_error_report_user_submission` 用于用户提交幂等;`by_error_report_created_at``by_error_report_review_status` 用于后台查询与清理。
### `admin_account`
- Rust 结构体:`AdminAccount`
@@ -58,7 +58,7 @@ Linux 本机多用户并发开发时,`npm run dev`、`npm run dev:*` 单模块
后端日志默认写入 `logs/api-server/`,独立 BgFilter worker 日志默认写入 `logs/bgfilter-worker/`。后端 API smoke 使用 `npm run dev:api-server`,先检查 BgFilter worker `/readyz`,再检查 API `/healthz`;需要确认 API 实例可接生产流量时检查 API `/readyz`。不要使用旧 `api-server:maincloud` 或任何 `GENARRATIVE_SPACETIME_MAINCLOUD_*` 口径。
AI 游戏创作客户端使用 `npm run agc`。该入口由 `apps/ai-game-creator-shell/scripts/start-tauri-dev.mjs` 解析 AGC Vite 实际端口:Linux 默认取当前用户端口段的 `start + 5`,占用时只在本用户段内漂移;Windows / macOS 保留 `3080` 为兼容首选并允许统一漂移。最终端口通过 `GENARRATIVE_AGC_VITE_PORT` 传给 `beforeDevCommand` 和配套后端端口解析器,通过 Tauri CLI 动态 `build.devUrl` 配置传给 WebView,并通过 Vite CLI `--port` 启动严格监听;Vite 继续使用 `strictPort`,任何一层都不得自行改到另一个端口。启动器在创建原生窗口前预检最终地址;若竞态中该地址被 AGC Vite、无响应监听器或其它服务占用,一律失败关闭,不复用、也不擅自终止无法证明归属的进程。
AI 游戏创作客户端使用 `npm run agc`。该入口由 `apps/ai-game-creator-shell/scripts/start-tauri-dev.mjs` 解析 AGC Vite 实际端口:Linux 默认取当前用户端口段的 `start + 5`,占用时只在本用户段内漂移;Windows / macOS 保留 `3080` 为兼容首选并允许统一漂移。最终端口通过 `GENARRATIVE_AGC_VITE_PORT` 传给 `beforeDevCommand` 和配套后端端口解析器,通过 Tauri CLI 动态 `build.devUrl` 配置传给 WebView,并通过 Vite CLI `--port` 启动严格监听;Vite 继续使用 `strictPort`,任何一层都不得自行改到另一个端口。AGC 配套后端的 `backend` 模式启动 SpacetimeDB、独立 `bgfilter-worker``api-server`,并在复用现有后端前同时检查三者状态及 `/v1/ping``/readyz``/healthz`;worker 缺失时不得把不完整的 API/数据库组合误判为 ready。启动器在创建原生窗口前预检最终地址;若竞态中该地址被 AGC Vite、无响应监听器或其它服务占用,一律失败关闭,不复用、也不擅自终止无法证明归属的进程。
Tauri `beforeDevCommand` 默认与客户端构建并行,不能把上述检查只放在 `beforeDevCommand` 内:选定地址上若已有旧 Vite,Tauri 可能先创建加载旧前端的窗口,随后配套后端才因代理不匹配退出。外层启动器会把 Tauri CLI 放入受控进程树;CLI 正常退出、启动失败或收到终止信号后,POSIX 先向保留的 PGID 发送 `SIGTERM`、有界等待后升级 `SIGKILL`Windows 使用 `taskkill /PID <pid> /T /F`。Linux 容器中的孤儿后代退出后可能暂时保留为 zombie,`kill(-PGID, 0)` 仍会返回成功;启动器必须结合 `/proc/<pid>/stat` 判断同组是否还存在非 zombie 成员,不能把等待 PID 1 回收误报为清理失败。配套后端和 Vite 仍由 `start-dev-stack.mjs` 各自持有,退出时同样有界收束,避免只剩客户端、Runner、Cargo 或旧订阅进程。排障时同时核对控制台输出的 AGC Vite 实际地址及其 marker、`.app/dev-stack.json` 的实际 API URL 和进程 cwd;不要把“终端已返回”当成客户端及其 Runner 已退出的证据。