合并远端 master 到美术包合同修复分支
Project CI / AI game creator shell Rust crates (pull_request) Successful in 4m20s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 6m48s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 7m16s
Project CI / Backend tests (pull_request) Successful in 7m25s
Project CI / Frontend tests (pull_request) Successful in 3m10s
Project CI / AI game creator shell web tests (pull_request) Failing after 3m28s
Project CI / Repository checks (pull_request) Successful in 6m39s
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (pull_request) Successful in 4m20s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 6m48s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 7m16s
Project CI / Backend tests (pull_request) Successful in 7m25s
Project CI / Frontend tests (pull_request) Successful in 3m10s
Project CI / AI game creator shell web tests (pull_request) Failing after 3m28s
Project CI / Repository checks (pull_request) Successful in 6m39s
Project CI / Native shell tests (pull_request) Has been cancelled
同步 origin/master 的最新代码、测试与文档变更 保留图集按实际产物返回的提示词并移除已退役发布说明 合并技能包清单并递增版本,保留双方项目经验记录
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
# AGC 错误具体文本展示实施计划
|
||||
|
||||
Version: 1.1
|
||||
Status: implemented-awaiting-runtime-acceptance
|
||||
Date: 2026-10-04
|
||||
Parent Milestone: `【里程碑】AGC错误具体文本展示-2026-10-04.md`
|
||||
|
||||
## 修改边界
|
||||
|
||||
1. 在前端回合失败映射中新增统一的安全 detail 投影,按 typed variant 保留 HTTP 状态、错误分类和脱敏文本。
|
||||
2. 对 `ModelCallKind`、`TransportClosed`、`TurnInterrupted`、阶段失败和 `Unclassified` 使用各自载荷,不再无条件返回通用文案。
|
||||
3. 检查并补强 Rust 错误脱敏测试,确保敏感值替换而不是整行删除。
|
||||
4. 补充 upstream、transport、stream、IPC、内存不足与 HostDropped 边界测试。
|
||||
5. 更新 ADR 的当前行为口径。
|
||||
6. HostDropped 的可选 detail 由 Rust 单点脱敏;panic hook 把可读负载和位置关联到原回合占用,Drop 与旧无字段事件分别验证,不更改回合排队、重试或计费行为。
|
||||
7. DirectProject 控制器的取消、上传、历史读取和前置异常统一复用前端精确脱敏出口,保留 IPC/OS 正文并替换凭据值、URL 与路径。
|
||||
8. 项目资源编辑/上传/预览/恢复队列/生成结果回读的 UI 错误统一使用 `normalizeDiagnosticText`,不再直出 `Error.message`。
|
||||
9. 应用壳、运行配置、认证状态、插件启动、运行预览和策划会话的非结构化错误统一使用 `visibleClientErrorMessage`。
|
||||
10. 资产导入、邀请码、策划工作区、发布封面/截图和素材命令的 UI 错误沿用同一共享出口。
|
||||
11. Codex CLI / Claude sidecar 的解析失败、失败终态、缺失终态、超时和空回执保留有界脱敏正文;即使平台 `LlmError` 变体没有 detail 字段,也由统一 Direct `ModelCallFailed.detail` 继续携带原始原因。
|
||||
12. 扩展统一正文口径到账户、模型目录、External Editor/资源编辑、发布、素材上传、错误报告与客户端受控工具桥,保留 HTTP 状态、响应安全片段、JSON 解析原因、网络因链和 IPC/桥接原因。
|
||||
13. 浏览器启动/DevTools 握手与 Codex model-catalog 子进程错误保留 stderr、退出状态、解析原因和阶段信息,机器码只作为分类字段。
|
||||
14. Node/npm 探测与 Web scaffold 构建失败保留有界脱敏 stdout/stderr、退出状态和超时原因;`preflight_web_game_creation` 不再只返回机器码。
|
||||
|
||||
## 实现顺序
|
||||
|
||||
先锁定脱敏输入输出用例,再实现前端可见文案;随后补 Rust 映射/诊断测试,最后运行类型、编码和差异检查。
|
||||
|
||||
## 风险与回滚
|
||||
|
||||
- 风险:直接展示未经精确脱敏的 provider detail 会泄露凭据或项目路径;前端和 Rust 双重检查,发现敏感 assignment 时替换值,不删除整条错误。
|
||||
- 风险:错误文本过长遮住分类;保留分类前缀并将 detail 限制在有界字符数。
|
||||
- 回滚:恢复 `directTurnFailureNoticeText` 的分类文案,保留 typed detail 和诊断落盘,不改协议。
|
||||
|
||||
## 验证结果
|
||||
|
||||
- 前端 DirectProject 错误展示:18 个定向测试通过;含 HostDropped detail / 旧载荷兼容。
|
||||
- AGC shell TypeScript 类型检查:通过。
|
||||
- Rust app-server 上游 detail 映射:6 passed。
|
||||
- CC sidecar 状态码/超时映射与非支付 409 回归通过;侧车非零退出、RPC error、解析失败统一补充有界 stderr detail。
|
||||
- 真实 AGC dev smoke 复现并定位 `Reached maximum number of turns (8)` → `transport-closed`;修复后客户端已热重编译重启,未再次触发付费 Provider 请求。
|
||||
- Rust 应用日志/启动诊断精确脱敏回归:通过。
|
||||
- Rust HostDropped panic detail + 旧无字段载荷兼容回归:定向通过。
|
||||
- DirectProject 控制器错误正文:新增脱敏与空错误专用提示回归,前端定向测试通过。
|
||||
- 资源工作台错误正文:项目开发页所有用户可见资源错误路径完成统一脱敏收口,TypeScript 与编码检查覆盖。
|
||||
- 真实 Provider、IPC 断链和内存压力尚未执行。
|
||||
- 2026-10-05 当前代码真实 `npm run agc` 构建启动通过;客户端与 Runner 已启动,前端/后台/API/worker/数据库健康检查通过,停止本次自有客户端后保留原有后端。
|
||||
@@ -0,0 +1,44 @@
|
||||
# 【实施计划】CC复用Codex运行态事件
|
||||
|
||||
| 字段 | 值 |
|
||||
| --------- | --- |
|
||||
| Milestone | `docs/project-memory/plans/【里程碑】CC复用Codex运行态事件-2026-10-04.md` |
|
||||
| Status | implemented-pending-acceptance |
|
||||
| Owner | Codex |
|
||||
|
||||
## 修改边界
|
||||
|
||||
- 允许修改:CC sidecar stdout 事件读取、`claude_code_cli.rs` 事件投影与历史收尾、Rust/前端定向测试、AGC 主技术方案和本里程碑文件。
|
||||
- 明确不修改:Codex app-server 协议、外部 API、SpacetimeDB schema、MCP 工具实现、现有前端通用展示组件。
|
||||
|
||||
## 实现顺序
|
||||
|
||||
1. 给 sidecar 回合读取增加逐事件回调,同时保留终态和错误收口。
|
||||
2. 在 Rust CC adapter 中建立有界、可归属的事件投影:文本/过程增量、工具开始、工具结果、进度与未知帧忽略。
|
||||
3. 复用 ThreadEvent/ThreadItem 和现有历史追加逻辑,处理重复、缺失 id、失败结果和最终回复兜底。
|
||||
4. 补 Rust parser/adapter、Thread wire 与前端 reducer/工具卡片定向测试。
|
||||
5. 运行编码、diff、Rust/TS 定向门禁;若环境允许再做真实 CC Direct smoke。
|
||||
|
||||
## 验证命令
|
||||
|
||||
1. `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml --offline --bin genarrative-ai-game-creator-shell agent::claude_code_cli --test-threads=1`
|
||||
2. `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml --offline --bin genarrative-ai-game-creator-shell agent::thread_manager::wire --test-threads=1`
|
||||
3. `npx vitest run tests/directThreadChat.test.tsx tests/appSurface/tool-call-group.suite.ts apps/ai-game-creator-shell/tests/AgentMessageContent.test.tsx --root apps/ai-game-creator-shell`
|
||||
4. `npm run check:encoding`
|
||||
5. `git diff --check`
|
||||
|
||||
## 风险与回滚点
|
||||
|
||||
- SDK 事件类型会随版本增加:只读取白名单字段,未知事件忽略,不能因未知帧打断整轮。
|
||||
- 文本/工具事件可能在同一 SDK 消息中并行出现:身份必须由 message id、content block index 或 tool_use id 组成,不能按到达顺序猜测。
|
||||
- 真实 Provider/SDK 不可用时不宣称实时 smoke 通过;保留 fixture 测试作为替代证据。
|
||||
- 若 Rust 事件投影破坏 Codex 回归,回滚只限 CC adapter 与其测试,不回退 ThreadEvent 合同。
|
||||
|
||||
## 当前验收证据
|
||||
|
||||
- Rust adapter `cargo check` 通过;本地缺失随包资源校验通过临时环境开关跳过,未改变最终代码。
|
||||
- CC stream fixture 定向测试通过:增量正文、MCP started/completed、观察器状态均已验证。
|
||||
- `cargo fmt --check`、`npm run check:encoding`、`git diff --check` 通过。
|
||||
- 前端 Vitest 未执行:当前 worktree 缺少 `@tailwindcss/vite`、`vite` 等 npm 依赖。
|
||||
- 真实 CC Provider smoke 未执行:当前 worktree 的随包资源校验素材不完整,且未启动真实项目回合。
|
||||
- `npm run check:doc-index` 被既有未分类文件 `docs/technical/【技术方案】Ubuntu离线AI游戏创作部署与传统工作流对比-2026-10-03.md` 阻断。
|
||||
@@ -0,0 +1,227 @@
|
||||
# 【实施计划】陶泥儿导出文件草稿化与发布媒体直传
|
||||
|
||||
- Version: `v1`
|
||||
- Status: 已实施(M1–M4 已落地;行为已回写主规范与共享记忆)
|
||||
- Date: `2026-10-06`
|
||||
- Retention: 本计划与配套里程碑规范长期保留在 `docs/project-memory/plans/`;原 §5.4「收尾删除计划与里程碑规范」不再执行。
|
||||
- Parent Spec(已回写):
|
||||
- `docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`(AGC 导出面板章节)
|
||||
- `docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`(游戏分发媒体合同)
|
||||
- `docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`(游戏分发领域合同)
|
||||
- `docs/project-memory/shared-memory/decision-log.md`、`pitfalls.md`
|
||||
|
||||
> 本文档是 grilling 后的完整迁移计划(临时产物)。按《规范驱动开发工作流》,先回写主规范并通过评审,再按里程碑逐个实现;实现发现行为变化时按「主规范 → 未完成里程碑规范 → 当前实现计划 → 代码」回改。
|
||||
|
||||
---
|
||||
|
||||
## 0. 一句话交付与验收判据
|
||||
|
||||
把陶泥儿(Taonier)导出迁移到小红书(xhs)重构后的文件草稿模式:**草稿 = `.export/taonier.json`(唯一真相)**、**打包 = `vite-export-taonier` skill + `build:taonier` 脚本产出 `.export/taonier.zip` 并由 code agent 适配任意工程布局**、**发布 = 宿主把草稿声明图片以 `multipart/form-data` 原始二进制直传游戏分发 API(未变图片带 OSS objectKey 沿用)**;同时**整体退役 `exports/`** 与**发布链路中的 assetId**。
|
||||
|
||||
验收判据(逐条可复核):
|
||||
|
||||
1. 未适配工程进入陶泥儿页先弹「让陶泥儿帮我调通」;适配后 `build:taonier` 成功产出 `.export/taonier.zip`,包内根目录存在 `index.html`。
|
||||
2. 表单校验只在前端;宿主 save 不做业务校验;校验不过时「打包」停在表单、不触宿主、不跑构建(对齐 xhs `157b4eb86` 断言)。
|
||||
3. 构建/草稿失败按 xhs 分类:可修复的转发给 code agent(「让陶泥儿来修」),不可修复的只提示人。
|
||||
4. 发布请求为 `multipart/form-data`:元数据 + 新/换图片原始二进制 + 沿用图片的 objectKey;`exports/playtest-package-*.zip` 不再存在,`.export/taonier.zip` 走现有整包/分片上传。
|
||||
5. 发布后线上读取显示「线上值」;草稿不自动回填、不被覆盖;每字段/图片可「沿用线上值」。
|
||||
6. 仓库中不存在 `exports/` 相关创建、读取、包路径、prompts、run-trace 白名单与命令;`assetId` 不再出现在游戏分发媒体请求/冻结资料/游戏行/公开读授权判定中。
|
||||
7. 全部定向测试、`check:spacetime-schema`、`check:generated-bindings`、`check:encoding`、`git diff --check`、`check:doc-index` 通过(`agc:skill-pack:check` 的 taptap 未登记红为已知接受项,见 §7)。
|
||||
|
||||
## 1. 背景与现状
|
||||
|
||||
- xhs 重构**已落盘**:`a6974f3ca`(表单校验只留前端,删 `validation.rs` 与 `FormInvalid`/`XHSMiniToolExportField`)、`157b4eb86`(多字段报错 + 交给陶泥儿)。重构后 xhs 形状 = `read_*`(内容)/`read_*_hash`(独立基线)/`save_*_form`(零校验、只做 baseHash 冲突)/`run_*_build`;前端 `xhsMinitoolFields.ts` 是唯一表单门;错误按 `code agent 可修 / 仅人可修` 分类;`.export/` flat 目录。
|
||||
- 陶泥儿现状:`useTaonierTab.ts` 调宿主 `export_local_project_package` 产出 `exports/playtest-package-*.zip`;`useGameDistributionPublishForm.ts`(968 行)做静默预填 + AI 资料建议 + 封面生成 + 素材登记上传;发布走 `publish_local_project_game`,staging 只读 `exports/playtest-package-*.zip`。
|
||||
- `exports/` 绑定面:项目初始化、包生成/读取/列举、4 个 Tauri 命令、生成任务产物声明(shared-contracts / platform-agent / 前端镜像)、prompts、run-trace 白名单、5 处安全/排除列表、`README` 交付物。
|
||||
- 发布媒体耦合面:`resolve_owned_game_media` → `resolve_owned_image_object_key`(客户端先登记素材、再带 assetId 发布);公开读授权 `game_distribution_asset_has_public_read_grant` 按 assetId 匹配;`axum` multipart 已启用且有两处先例(`raw_image.rs`、`admin_templates.rs`)。
|
||||
- 游戏分发模块不在线、无历史数据:可硬切、可删/改名 SpacetimeDB 字段,但必须同步 `migration.rs` 与生成绑定,并运行 `check:spacetime-schema`。
|
||||
|
||||
## 2. 已定决策(grilling 结论)
|
||||
|
||||
| # | 决策 |
|
||||
| --- | --- |
|
||||
| D1 | 对齐 xhs 重构后模式:文件草稿、typed error 转发 code agent、删除宿主业务校验。 |
|
||||
| D2 | 草稿 `.export/taonier.json` 形状 `{ form: { title, summary, category, coverPath, screenshotPaths[] } }`;不含 description/tags/deviceSupport/inputModes/orientation。 |
|
||||
| D3 | 每个目标独立、语义清晰的命令接口;不做带 target 枚举/flag 的 all-in-one 命令;内部复用共享 util。 |
|
||||
| D4 | 保留现有平台发布链(AGC native → `publish_local_project_game` 语义);打包改为 skill + `build:taonier` + code agent 首次适配,走与 xhs 相同的 adapt/fill/repair(未适配弹「让陶泥儿帮我调通」)。平台本身无额外适配概念,适配只针对打包。 |
|
||||
| D5 | 彻底退役 AI 资料建议与封面生成(前端 service、Rust 命令、api-server 路由、测试、专属 DTO);图片生成计费链路本身不动。 |
|
||||
| D6 | 删除 `useGameDistributionPublishForm`、`GameDistributionPublishFormView`、`GamePublishBlockedDialog`(及其测试);保留 `GamePublishProgressDialog` / `GamePublishPhaseSteps`。 |
|
||||
| D7 | 草稿为唯一真相;线上值只读展示;新增「沿用线上值」操作;不自动 seed、不回填覆盖。 |
|
||||
| D8 | 移除 `hasRunnablePrototype` 与 `ensure_publish_project_stack_at` 的 phaser/vite 门(含 `stage_publish_package` 的重复检查);新门 = `build:taonier` 存在、退出 0、产物 `.export/taonier.zip` 根有 `index.html`;保留符号链接/越界拒绝与 `checking`/`unavailable` 按钮禁用。 |
|
||||
| D9 | 整体退役 `exports/`(E1,含 `exports/README.md` 交付物):目录创建、README 生成、playtest 包链、4 命令、任务产物声明、prompts、run-trace 白名单、5 处排除列表、前端类型、测试、文档。 |
|
||||
| D10 | 新增 `vite-export-taonier` skill + `build:taonier` 脚本 + **`dist-taonier`** + `.export/taonier.zip`(与 xhs/taptap 同构,不做 native 特例);标准 `build`/`dist` 不被占用。 |
|
||||
| D11 | 发布 API 改为 `multipart/form-data` + 原始图片二进制(`4b`:不做单独 upload 命令,formdata 直发);整包 zip 保持现有 raw `Bytes`/分片上传,不并入 multipart。 |
|
||||
| D12 | 硬切(hard cut):不保留旧 JSON / assetId 分支与兼容 DTO。 |
|
||||
| D13 | 服务端解耦深度取 (b):发布图片落**项目快照桶** `agc/project-snapshots/v1/game-distribution/media/...`,**不建 `asset_object`**;公开读走新路由 `GET /api/game-distribution/media/read-url`(先抽公共签名/中转逻辑复用),与素材库 ACL 分离。删除请求/游戏行/冻结资料/授权判定中的 `*_asset_id`。 |
|
||||
| D14 | web 平台端一起迁到 formdata + objectKey(`gameDistributionClient.ts`、`GamePublishPage.tsx`、`GameWorkMetadataEditor.tsx`、`gamePublishAssets.ts`、`gamePublishMediaDefaults.ts`)。 |
|
||||
| D15 | skill pack 只登记 `vite-export-taonier`;未跟踪的 `vite-export-taptaph5` WIP 不登记,接受 `agc:skill-pack:check` 红(Q23①,风险见 §7)。 |
|
||||
|
||||
## 3. 主规范(行为合同)
|
||||
|
||||
### 3.1 草稿文件
|
||||
|
||||
- 路径:`<projectRoot>/.export/taonier.json`(flat `.export/`,与 xhs 同层级)。
|
||||
- 形状:`{ "form": { "title", "summary", "category", "coverPath", "screenshotPaths": [] } }`;`coverPath`/`screenshotPaths` 是**项目相对路径**;
|
||||
- `deny_unknown_fields`,camelCase;宿主只做 IO 事实守护(内容 hash 冲突、目录不可用),不做业务校验。
|
||||
- 写:前端 600ms 防抖自动保存 + 冲突逐字段选择;`save` 带 `baseHash`,格式差异且内容相同则吸收,否则返回 `SaveConflict`,绝不覆盖。
|
||||
- 读:`read`(内容)+ `read_hash`(独立基线,避免刷新移动写基线);前端 2s 轮询,输入中不覆盖草稿,只更新 `hasScript` 等外部事实。
|
||||
|
||||
### 3.2 打包
|
||||
|
||||
- 约定(对齐 xhs/taptap,不做特例):项目根或 `game/` 的 `package.json` 提供 `build:taonier`;宿主 `run_taonier_export_build` 以 `command.exec`(子命令 `build:taonier`,300s)运行;退出 0 且产出非空、非符号链接、≤200 MiB 的 `.export/taonier.zip` 才算成功。
|
||||
- 独立产物目录:`build:taonier` 由 skill 的 `vite.config.taonier.mjs` 构建到 **`dist-taonier`**,再由 `pack.mjs` 打成 `.export/taonier.zip`;**标准 `build` 脚本与标准 `dist` 不被导出占用**(标准 build 不负责打包,预览/开发仍用项目自己的 `dist`)。
|
||||
- 宿主不传任何构建 flag;skill 的 `pack.mjs` 沿用 xhs 约定自带默认值(`--vite-built-dir dist-taonier`、`--zip-out ../.export/taonier.zip`(cwd 为 `game/` 时)/ `.export/taonier.zip`(根工程时))。非标准工具链/输出目录由 agent 在项目自己的 `build:taonier` 包装脚本里适配。
|
||||
- skill `vite-export-taonier` 指导 code agent 为任意布局补齐 `build:taonier` 与必要配置。
|
||||
- 失败语义(typed error,forward 给 code agent):脚本缺失、命令被拒、命令失败(含输出尾)、产物缺失、导出目录不可用。
|
||||
- 预览不受影响:预览直接从 `project_game_root` 起服务,不读该 zip;`dist` 与 `dist-taonier` 互不干扰。
|
||||
|
||||
### 3.3 发布媒体直传
|
||||
|
||||
- 端点:`POST /api/game-distribution/games`、`POST /api/game-distribution/games/{game_id}/versions`、`PATCH /api/game-distribution/my-games/{game_id}` 改为 `multipart/form-data`。
|
||||
- 字段:`metadata` 文本 part(JSON,即原请求体的元数据部分,去掉媒体字段);`cover` 二进制 part(可选,出现时覆盖 `metadata.coverObjectKey`);`screenshot` 二进制 parts(可重复,按出现顺序消费)。`metadata.coverObjectKey` 为沿用线上封面时的 objectKey;`metadata.screenshots` 为 `(string|null)[]`,`string` 沿用该 objectKey、`null` 取下一个 `screenshot` part,数组顺序即最终截图顺序。
|
||||
- 存储:新图直接落**项目快照桶** `agc/project-snapshots/v1/game-distribution/media/<gameId>/{cover|screenshot}-<uuid>.<ext>`(与发行包同桶、与素材库解耦,不建 `asset_object`);沿用图校验 objectKey 必须属于该游戏当前媒体;构建冻结资料(只含 objectKey)。
|
||||
- 读:新增 `GET /api/game-distribution/media/read-url`,按 objectKey 返回签名读地址;判定 = 已发布且 active 的 game 且 objectKey 命中 `cover_object_key` / `screenshots_json`,**不经素材库 ACL**;需要同源字节时再补 `.../media/read-bytes`。签名与字节中转先抽公共函数,供 `/api/assets/read-url` 与新路由共用。
|
||||
- 整包:`PUT .../versions/{version_id}/package` 及分片端点保持现状(raw zip / octet-stream)。
|
||||
- 幂等:`request_digest` 重定义为「规范化元数据 + 图片字节 hash + 沿用 objectKey」的稳定摘要,覆盖 create / version / update 三条写路径。
|
||||
- 所有权/权限:仅 owner 可发布/编辑;沿用图校验 objectKey 必须属于该游戏当前媒体;附图校验 `image/*` 与张数/体积上限;公开读由新路由的域内判定负责。
|
||||
- 硬切:旧 JSON / assetId 请求直接不支持,不保留兼容分支。
|
||||
|
||||
### 3.4 线上值与沿用
|
||||
|
||||
- 读命令除草稿外返回只读 `online`:live `GameDistributionGame` 行的 `title/summary/category` + `coverObjectKey` / 截图 objectKey 列表 + 预览地址。
|
||||
- UI 逐字段内联显示「草稿值 vs 线上值」(线上值一行更小,跟在该字段输入框下,首次发布线上为空时不铺空对照);读取自动进行、失败静默自动重试,不提供手动「重新读取」入口;不自动 seed、不覆盖草稿。
|
||||
- 「沿用线上值」操作:文字字段一键把线上值写入草稿(标 dirty → 自动落盘);图片字段「替换为本地图」/「沿用线上」(清空本地路径,表示沿用);`screenshotPaths` 为空表示整组沿用线上。
|
||||
- 发布:路径非空的图发原始二进制;路径为空的图带线上 objectKey;文案沿用现有状态语义,不混淆在审/驳回状态。
|
||||
|
||||
### 3.5 错误与修复
|
||||
|
||||
- 前端错误分类(对齐 xhs):code agent 可修(草稿格式错、脚本缺失、命令失败、产物缺失)→「让陶泥儿来修」/「让陶泥儿帮我调通」;仅人可修(命令被拒、导出目录不可用)→ 只提示。
|
||||
- 失败对话框把**原始结构化错误**经项目对话转发给 code agent;适配指令引用 `vite-export-taonier` skill 与草稿路径/形状;「帮我填」只写元数据,不改打包适配。
|
||||
- 表单字段级红字 + 「交给陶泥儿」批量转发当前值,转发后清除红字并允许自动重读采纳 agent 修复。
|
||||
|
||||
### 3.6 退役(E1)
|
||||
|
||||
- `exports/`:项目初始化目录、`exports/README.md` 生成与渲染、`playtest-package-*.zip` 生成/读取/列举、`prepare/upload/list` 命令、staging 依赖。
|
||||
- 生成契约:publish-package 任务对 `exports/README.md` 的 artifact 声明(3 处)+ prompts(3 个 JSON)+ run-trace 白名单 slot。
|
||||
- 5 处安全/排除列表中的 `exports` 条目。
|
||||
- 发布媒体中的 assetId 与素材库耦合:请求 DTO、游戏行字段、冻结资料字段、公开读授权判定、`GameDistributionFrozenScreenshot.asset_id`;发布图片不再建 `asset_object`。
|
||||
- AI 资料建议/封面生成的前端调用、Rust 命令、api-server 路由与测试。
|
||||
|
||||
### 3.7 非目标
|
||||
|
||||
- 不改图片生成计费/额度链路本身;不改素材工作台对 `platform_asset_upload` 的既有使用(仅移除发布表单里的调用)。
|
||||
- 不新增 `/api/external/v1` 路由/契约(游戏分发不在 external v1,OpenAPI 不动)。
|
||||
- 不做历史数据迁移(模块不在线)。
|
||||
- 不登记 `vite-export-taptaph5`(另一条 WIP)。
|
||||
|
||||
## 4. 里程碑
|
||||
|
||||
| 里程碑 | 目标 | 依赖 | 验收证据 |
|
||||
| --- | --- | --- | --- |
|
||||
| M1 发布媒体合同解耦 | 游戏分发 API/契约/schema 改为 objectKey + multipart 直传;媒体落项目快照桶并新增公开读路由;web 平台端同迁 | 无 | 后端定向测试、`check:spacetime-schema`、`check:generated-bindings`、api-server smoke、web 端发布用例 |
|
||||
| M2 AGC 打包链路与 `exports/` 全退役 | 新 skill + `build:taonier`/`dist-taonier` + `run_taonier_export_build`;删 `exports/` 全链与旧打包门 | 无(可与 M1 并行) | Rust 定向测试、skill pack 同步(taonier 部分)、AGC typecheck、契约测试 |
|
||||
| M3 AGC 陶泥儿面板与发布媒体直传 | 草稿 hook/面板/错误转发/线上值与沿用;删 AI 建议/封面与旧表单组件;发布改 multipart | M1、M2 | vitest(字段校验、hook、面板、失败转发)、Rust 命令测试、真实栈发布 smoke |
|
||||
| M4 文档与共享记忆回写、临时计划清理 | 主规范/decision-log/pitfalls 回写;删临时计划 | M1–M3 | `check:doc-index`、`check:encoding`、`git diff --check` |
|
||||
|
||||
## 5. 实现计划
|
||||
|
||||
### 5.1 M1 发布媒体合同解耦
|
||||
|
||||
**Step 0(先做,重构优先)**:抽出可复用的「按 objectKey 签名读 + 同源字节中转」公共函数。
|
||||
- 现状:`assets.rs` 的 `get_asset_read_url_with_query` / `get_asset_read_bytes` 把「解析 target → 授权 → `sign_get_object_url` → 响应/中转」写在一起并绑定 `state.oss_client()`。
|
||||
- 目标:把「签名 + 响应形状 + 同源字节中转」拆成公共 helper(输入 OSS client、objectKey、expire、已授权标志),`/api/assets/read-url`/`read-bytes` 与游戏分发媒体路由共用,避免复制。
|
||||
- 授权保持域内分离:素材域继续走素材授权;游戏分发域走「已发布且 active 的 game + objectKey 命中 cover/screenshots」。
|
||||
- 必须用素材域现有测试锁定 `/api/assets/read-url`/`read-bytes` 的授权与过期语义不变。
|
||||
|
||||
**Step 1 shared-contracts**
|
||||
- `server-rs/crates/shared-contracts/src/game_distribution.rs`:`GameDistributionCreateGameRequest` / `GameDistributionUpdateGameMetadataRequest` 去掉 `cover_asset_id`,`screenshots` 语义改 objectKey;新增 multipart 表单 DTO(元数据字段 + `coverObjectKey` + 沿用 `screenshots`)。
|
||||
- `packages/shared/src/contracts/gameDistribution.ts`:镜像同步;`GameDistributionGameSummary.coverObjectKey` / `screenshots` 维持 objectKey。
|
||||
|
||||
**Step 2 spacetime-module**
|
||||
- `GameDistributionGame`:**保留 `cover_asset_id` 并标记退役**(保持在原字段位置、宿主不再写入,仅占位,读逻辑不得依赖),避免 SpacetimeDB 破坏性 schema 变更;`cover_object_key` 保留;`screenshots_json` 改为 objectKey 数组。
|
||||
- `GameDistributionFrozenMetadata`:删 `cover_asset_id`;`screenshots: Vec<String>`(objectKey);删 `GameDistributionFrozenScreenshot.asset_id`。
|
||||
- `apply_game_distribution_frozen_metadata`、`GameDistributionCreateGameInput` / `GameDistributionUpdateMetadataInput` / `GameDistributionGameSnapshot` 同步。
|
||||
- 公开读授权:删除 `game_distribution_asset_has_public_read_grant(ctx, asset_object_id)` 及其在 `editor_project_storage::asset_location_has_public_showcase_read_grant` 的调用(发布图不再经素材库读);公开判定迁到新路由的域内判定(已发布且 active + objectKey 命中)。
|
||||
- `migration.rs` + 表目录 + 生成绑定;运行 `npm run check:spacetime-schema`。
|
||||
|
||||
**Step 3 api-server**
|
||||
- `modules/game_distribution.rs`:`create_game` / `create_version` / `update_owner_game_metadata` 换 `Multipart` 解析(参照 `raw_image.rs` / `admin_templates.rs`);媒体写入用 `project_snapshot_oss_client` 的 `put_internal_object_with_retry`(专用前缀 + 内容类型/大小校验,不建 `asset_object`);沿用图校验改为「objectKey 必须属于该游戏当前媒体」;`resolve_version_metadata_json` 输出 objectKey;`request_digest` 重定义。
|
||||
- 新增 `GET /api/game-distribution/media/read-url`(+ 按需 `read-bytes`):匿名可读,用公共签名 helper + `project_snapshot_oss_client` + 游戏分发公开判定;更新平台 web 与 AGC 的封面/截图读地址来源。
|
||||
- 删除 `POST /api/game-distribution/publish-metadata/suggestions` 与封面生成相关路由;更新模块测试(冻结 JSON 形状、上传校验)。
|
||||
- `DefaultBodyLimit` 按图片大小设限(参照 `raw_image` 64 MB 级别,按 6 张截图 + 封面估算)。
|
||||
|
||||
**Step 4 web 平台端**
|
||||
- `src/services/gameDistributionClient.ts` / `GamePublishPage.tsx` / `GameWorkMetadataEditor.tsx` / `gamePublishAssets.ts` / `gamePublishMediaDefaults.ts`:发布/编辑改 `FormData`;未变图带 objectKey;删除发布前的素材登记步骤;封面/截图展示改走 `/api/game-distribution/media/read-url`。
|
||||
- 平台端测试与类型检查。
|
||||
|
||||
**Step 5 文档**:回写后端架构与玩法链路主规范 + OpenAPI 无需改动说明。
|
||||
|
||||
### 5.2 M2 AGC 打包链路与 `exports/` 全退役
|
||||
|
||||
**Step 1 skill**
|
||||
- 新增 `apps/ai-game-creator-shell/src-tauri/resources/agc-skills/vite-export-taonier/`,镜像 xhs:`SKILL.md`、`scripts/vite.config.taonier.mjs`(`outDir: 'dist-taonier'` + 产物收尾插件)、`scripts/pack.mjs`(默认 `--vite-built-dir dist-taonier`、`--zip-out ../.export/taonier.zip`,纯 Node zip,根目录须有 `index.html`)、以及隐藏测试 `scripts/.pack.test.mjs`、`scripts/.vite.config.taonier.test.mjs`。
|
||||
- 项目侧 `build:taonier` = 用该 config 构建到 `dist-taonier` 后跑 `pack.mjs` 产出 `.export/taonier.zip`(具体命令由 agent 写进项目 `package.json`)。
|
||||
- 登记:`scripts/skill-pack-manifest.mjs` 的 `EXPECTED_SKILL_NAMES`、`skill_pack.rs` 的 `AGC_SKILL_PACK_EXPECTED_NAMES` / `AGC_SKILL_PACK_FILES`、`manifest.json`(`agc:skill-pack:sync` 生成 sha256 并 bump version)、`codex_app_server/mod.rs` 三处测试 mock 名单。
|
||||
|
||||
**Step 2 宿主打包命令**
|
||||
- 新增 `src-tauri/src/export/draft/taonier/`:`layout.rs`(`SCRIPT_NAME="build:taonier"`、`REGISTRY_FILE_NAME="taonier.json"`、`ARTIFACT_FILE_NAME="taonier.zip"`)、`build.rs`(跑脚本 + 产物校验)、`script.rs`(npm 工作目录发现,复用 xhs 逻辑)、`registry.rs`、`dto.rs`、`error.rs`、`commands.rs`(M3 接前端;本里程碑先落 build 侧与注册)。
|
||||
- `src/desktop.rs` 注册新命令。
|
||||
|
||||
**Step 3 `exports/` 全退役**
|
||||
- `project/manifest.rs:614`:从初始化目录移除 `exports`。
|
||||
- `project/export.rs`:删 `export_local_project_package_at`、`export_local_project_package_for_publish_at`、`ensure_project_export_readme`、`render_project_export_readme`、`next_project_export_package_relative_path`、`collect_project_export_package_files`、`read_local_project_export_package_at`、`list_local_project_export_packages_at` 及专属测试。
|
||||
- `commands/desktop.rs`:删 `export_local_project_package`、`prepare_local_project_game_package`、`upload_local_project_game_package`、`list_local_project_export_packages`;同步 `src/desktop.rs` 与 `scripts/check-config.mjs` 白名单。
|
||||
- `game_distribution_publish.rs`:`stage_publish_package` 改从 `.export/taonier.zip` 读取;删 L1194 的 phaser 重复检查。
|
||||
- 新门:删 `hasRunnablePrototype` 调用与 `ensure_publish_project_stack_at` 的 phaser/vite 门;保留符号链接/越界拒绝。
|
||||
- 生成契约:`shared-contracts/game_creation_app.rs:~427`、`platform-agent/game_creation.rs:~987`、`packages/shared/src/contracts/gameCreationApp.ts:~438` 移除 `exports/README.md` artifact;`prompts/runtime/texts/{execution,generation,media}.json` 同步;`main.rs:1346` 白名单删 slot。
|
||||
- 排除列表:`preview.rs:1112/1156`、`agent/direct_runtime/mod.rs:3606`、`agent/direct_validation.rs:261`、`repository_context.rs:720` 移除 `exports`。
|
||||
- `src/app/types.ts` 的 `LocalProjectExportPackageResult` 等类型与前端调用清理。
|
||||
|
||||
**Step 4 验证**:Rust 定向测试;`agc:skill-pack:check`(taonier 部分绿,taptap 红为已知接受项);AGC typecheck;契约测试。
|
||||
|
||||
### 5.3 M3 AGC 陶泥儿面板与发布媒体直传
|
||||
|
||||
**Step 1 草稿模块补齐**:`export/draft/taonier/{dto,error,registry,commands}.rs` 落地 4 命令(`read_taonier_export` / `read_taonier_export_hash` / `save_taonier_export_form` / `run_taonier_export_build`,前端另需线上值读取)。
|
||||
|
||||
**Step 2 前端状态与错误**
|
||||
- `export/state/taonierFields.ts`:`TaonierField = 'title'|'summary'|'category'`;`TAONIER_FORM_FIELDS`;`validateTaonierForm()` 返回**全部**错误(必填 + 长度,码点计数)。
|
||||
- `export/state/taonierApi.ts` / `taonierFailure.ts` / `taonierInstruction.ts`:三命令 Error 载体 + guard + 包装;可修复分类;adapt/fill/repair 指令(引用 `vite-export-taonier` 与 `.export/taonier.json` 形状)。
|
||||
- `export/state/useTaonierExport.ts`:自动保存(baseHash)、2s 轮询、冲突、失败、`packageAll`、`adapt`、`repair`、`fillForm`、`handFieldErrorsToAgent`、`online` 与「沿用线上值」动作;返回面与 xhs hook 同构。
|
||||
|
||||
**Step 3 UI**
|
||||
- `tabs/taonier/`:`Page.tsx`(2 列)、`ArtifactsPane.tsx`(表单槽/读取中/重试/冲突/唯一的「打包并发布」入口/两个 dialog)、`FormCard.tsx`(逐字段红字 + 帮我填 + 交给陶泥儿 + 线上值逐字段内联沿用)、`ConflictCard.tsx`、`FailureDialog.tsx`、`AdaptPromptDialog.tsx`。
|
||||
- 复用 `common/PaneButton`、`ThemedModal`,去掉旧 `PublishPane` 对 `GameDistributionPublishFormView` 的依赖。
|
||||
|
||||
**Step 4 发布媒体**
|
||||
- `src-tauri/src/game_distribution_publish.rs`:读 `.export/taonier.json` 与图片路径,构建 multipart 请求(新图读文件字节;空路径发线上 objectKey),沿用 `publish_local_project_game` 语义与异步任务状态;幂等与错误语义对齐 M1。
|
||||
- 删前端 `useGameDistributionPublishForm`、`GameDistributionPublishFormView`、`GamePublishBlockedDialog` 与 `services/gameDistributionPublish.ts` 中 `suggestGameDistributionPublishMetadata` / `generateGameDistributionCover` / `readGameCoverGenerationPrice` 调用;删对应 Rust 命令与注册。
|
||||
- 保留 `GamePublishProgressDialog` / `GamePublishPhaseSteps` / 发布状态机。
|
||||
|
||||
**Step 5 测试**:`tests/taonierFields.test.ts`、`tests/taonierExport.test.tsx`(对齐 xhs 用例面:指令、左半 UI、逐命令失败表、hook 状态与「校验不过不触宿主」)、`tests/taonierContract.test.ts`(前端常量 vs Rust `layout.rs`);Rust 命令与发布单测。
|
||||
|
||||
### 5.4 M4 文档与共享记忆
|
||||
|
||||
- 回写主规范章节;更新 `decision-log.md`(命名约定、硬切、objectKey 授权、E1、skill-pack 例外)、`pitfalls.md`(multipart 幂等摘要、幂等键、taptap 未登记红)。
|
||||
- 删除本临时计划与里程碑规范;运行 `npm run check:doc-index`、`npm run check:encoding`、`git diff --check`。
|
||||
|
||||
## 6. 验证与证据矩阵
|
||||
|
||||
| 证据 | 内容 |
|
||||
| --- | --- |
|
||||
| 规范对照 | §3 各条 vs §4 里程碑验收逐项结果 |
|
||||
| 自动化验证 | 后端 `cargo test`、`check:spacetime-schema`、`check:generated-bindings`;前端 `typecheck` + 定向 vitest;`check:encoding`、`git diff --check`、`check:doc-index` |
|
||||
| 运行时验证 | `npm run dev:api-server` + `/healthz`;multipart 创建/编辑/发布 smoke;AGC 真实栈一次适配→打包→发布 |
|
||||
| 边界验证 | owner 归属、沿用 objectKey 校验、图片大小/张数上限、幂等重放、路径越界/符号链接拒绝、公开读授权只对已发布游戏生效 |
|
||||
| 未验证项 | 真实 OSS 与对象生命周期、web 平台端真实发布、`agc:skill-pack:check` 的 taptap 红 |
|
||||
|
||||
## 7. 风险与回滚
|
||||
|
||||
- **读签名重构回归**:抽公共 helper 会同时动 `/api/assets/read-url`/`read-bytes`;必须用素材域现有测试锁定授权与过期语义不变。
|
||||
- **SpacetimeDB 删字段**:无历史数据但仍需 `migration.rs` 与绑定;回滚 = 撤销 M1 提交,schema 随代码回滚。
|
||||
- **multipart 幂等**:digest 输入必须覆盖图片字节,否则重放会重复建图;以测试锁定。
|
||||
- **`agc:skill-pack:check` 红**:由未跟踪 taptap 造成,接受为已知项;若 CI 必须绿,需先收掉或登记 taptap(D15 之外)。
|
||||
- **发布范围大**:M1 与 M3 都改发布;小程序化落地顺序 = M1 → M2 → M3,M1 未验收前 M3 不依赖其接口。
|
||||
|
||||
## 8. 未决项
|
||||
|
||||
无。§2 决策与 §3 主规范已覆盖全部边界;taonier 与 xhs/taptap 同构(`build:taonier` + `dist-taonier`,标准 `build`/`dist` 不被占用),构建 flag 由 skill 的 `pack.mjs` 自带默认值、宿主不传;web 平台端不设灰度开关(硬切,D12)。实现中若发现行为需变化,按《规范驱动开发工作流》顺序回改:主规范 → 未完成里程碑规范 → 当前实现计划 → 代码与测试。
|
||||
@@ -0,0 +1,54 @@
|
||||
# 【里程碑】AGC命令沙箱Node版本管理器支持-2026-10-07
|
||||
|
||||
| 字段 | 值 |
|
||||
| ----------- | ------------------------------------------------------------------- |
|
||||
| Version | 1.0 |
|
||||
| Status | implemented-awaiting-runtime-acceptance |
|
||||
| Date | 2026-10-07 |
|
||||
| Parent Spec | `docs/technical/【技术方案】AI游戏创作Agent Runtime V1.1-2026-07-12.md` |
|
||||
|
||||
## 目标
|
||||
|
||||
开发构建的 Linux 命令沙箱能原生解析并复用宿主 fnm / nvm 托管的 Node 安装:宿主发现与沙箱只读挂载使用同一套窄叶校验;`.nvmrc` / `.node-version` pin 权威、`engines.node` 仅为偏好;npm 以受信任 `node <npm-cli.js>` 形态启动且不丢失 `npm install` 的联网判定。不再要求用户改系统 Node、把宿主 shim 指向 `/usr/bin`,或把托管目录软链进系统路径。
|
||||
|
||||
## 范围
|
||||
|
||||
- 移除把 `node` / `npm` / `npx` 指向 `/usr/bin/*` 的宿主 shim 依赖,开发构建默认解析托管安装。
|
||||
- 枚举 fnm `node-versions/<version>/installation`(含 `aliases/default`)与 nvm `versions/node/<version>`,按窄叶规则校验完整安装前缀。
|
||||
- 版本选择:`.nvmrc` / `.node-version` pin > PATH 可解析的可用 Node > 版本管理器回退链(`engines.node` 最高匹配 > 活动版本 > 默认别名 > 已安装最高版本)。
|
||||
- Linux 命令沙箱把通过校验的完整安装前缀只读挂载,并以 `node <npm-cli.js>` 启动 npm;`npm install` 保持联网判定。
|
||||
- 沙箱内联环境不继承 fnm multishell 等临时版本管理器变量。
|
||||
|
||||
## 不在范围内
|
||||
|
||||
- 不把 `fnm` / `nvm` CLI 本身做成沙箱内可用工具。
|
||||
- 不改变发布构建的 bundle / 系统 Node 解析策略。
|
||||
- 不改变 Windows 的 npm.cmd 处理与 V1.10 固定 program 口径。
|
||||
- 不引入 fnm / nvm 之外的版本管理器。
|
||||
- 不为了兼容挂载整个用户 HOME、`FNM_DIR` / `NVM_DIR` 根或 `aliases` 目录。
|
||||
|
||||
## 依赖与前置条件
|
||||
|
||||
- 主机已安装 fnm 或 nvm,且至少有一个 Node 22 安装;项目按 `@types/node ^22.14` 面向 Node 22。
|
||||
- Linux bubblewrap 可用;测试主机需能创建 user / mount / pid namespace。
|
||||
|
||||
## 验收标准
|
||||
|
||||
- [x] 开发构建在没有 `/usr/bin` shim 的情况下从 fnm / nvm 找到 Node,并在沙箱内运行出真实 `node --version` 与 `npm --version`。
|
||||
- [x] `.nvmrc` / `.node-version` 指定的版本未安装时命令失败关闭,且不回退到其它已安装版本;`engines.node` 不匹配时不阻塞。
|
||||
- [x] 不支持的 pin 写法(如 `iojs`、`>20`、`<=20`)按未 pin 处理并回退,而不是误判为「已理解但未命中」。
|
||||
- [x] 只有通过窄叶校验的完整安装前缀会被只读挂载;HOME、管理器根、`aliases`、宽泛目录、不完整前缀和逃逸 symlink 全部失败关闭。
|
||||
- [x] npm 在 Linux 上以受信任 `node <npm-cli.js> ...` 启动,`npm install` 仍被判定为联网命令,普通 `npm run` 仍离线。
|
||||
- [x] 宿主发现与沙箱挂载共用同一套窄叶校验,不存在第二份信任口径。
|
||||
- [x] 真实 nvm(v0.40.8 + Node v22.23.3)安装前缀的端到端沙箱运行,以及在仅 nvm 环境(`NVM_DIR` + 空 PATH + 临时 HOME)下的托管解析;同时修正 nvm `alias/default` 只写主版本号(如 `22`)时被当作 `(22,0,0)` 而匹配不到已安装补丁版本的问题。
|
||||
|
||||
## 证据要求
|
||||
|
||||
- 自动化:
|
||||
- `cargo test --locked -p genarrative-ai-game-creator-shell --bin genarrative-ai-game-creator-shell -- environment_check:: --test-threads=1`
|
||||
- `cargo test --locked -p genarrative-ai-game-creator-shell --bin genarrative-ai-game-creator-shell -- command_sandbox:: command_exec:: process_session:: --test-threads=1`
|
||||
- 可选真机:`GENARRATIVE_COMMAND_SANDBOX_REAL_TEST=1 cargo test … -- command_sandbox_real_linux_opt_in_runs_host_node_and_npm_cli`
|
||||
- 可选真机 nvm:`NVM_DIR=<nvm 目录> GENARRATIVE_COMMAND_SANDBOX_REAL_TEST=1 cargo test … -- command_sandbox_real_linux_opt_in_runs_nvm_installation_prefix`;仅 nvm 解析:`env -i HOME=<临时家目录> NVM_DIR=<nvm 目录> PATH=/nonexistent GENARRATIVE_COMMAND_SANDBOX_REAL_TEST=1 <测试二进制> --exact environment_check::tests::real_node_npm_environment_versions --ignored`
|
||||
- `cargo fmt --check`、`npm run check:encoding`、`git diff --check`。
|
||||
- 运行时:真实 fnm v22 前缀下,bwrap 内 `node -e 'process.stdout.write(process.version)'` 与 `node <npm-cli.js> --version` 均成功且版本为 v22。
|
||||
- 边界:`.nvmrc` pin 未安装、`engines.node` 不匹配、不支持 pin、宽泛 / 不完整前缀、逃逸 symlink、`npm run` 离线与 `npm install` 联网。
|
||||
@@ -0,0 +1,108 @@
|
||||
# AGC 错误具体文本展示
|
||||
|
||||
Version: 1.1
|
||||
Status: implemented-awaiting-runtime-acceptance
|
||||
Date: 2026-10-04
|
||||
Parent Spec: `docs/adr/【ADR】AGC命令错误结构化与错误报告口径-2026-10-01.md`
|
||||
|
||||
## 目标
|
||||
|
||||
DirectProject 回合失败在确认不是客户端内部不可归类故障时,向用户显示可行动的脱敏事实:上游 HTTP 状态、上游返回文本、网络/IPC/进程传输原因、流协议错误、内存不足等操作系统错误。保留机器字段用于诊断,但不再用通用“执行通道中断”覆盖可识别原因。
|
||||
|
||||
## 范围
|
||||
|
||||
- `turn.completed.failure` 的前端展示:按 typed 变体显示分类、状态码和脱敏后的 `detail` / `diagnostic`。
|
||||
- Rust 侧错误文本脱敏:只替换敏感值、URL、绝对路径和私钥内容,保留 HTTP 状态、错误码、字段名和可行动描述。
|
||||
- app-server `codexErrorInfo` 只有嵌套机器字段时,保留经有界脱敏的结构化值;`error` 是字符串或未知 JSON 形状时也保留正文;`fields=codexErrorInfo` 不能成为唯一正文。
|
||||
- 素材/参考图/资源编辑上传、资源编辑轮询、模型目录初始化和 Direct MCP stdio 的网络、JSON、写入、读写失败保留底层错误正文;不能由 `map_err(|_| 固定句)` 把它们压成无因的连接或失败提示。
|
||||
- 插件 stdout RPC 的 `fill_buf`、UTF-8 和响应大小错误通过挂起 RPC 传播,不能在读取线程里吞掉后统一说“插件进程已退出”。
|
||||
- Codex CLI/Agent Runner 及本地素材/字体读取保留 stderr、join、序列化/解析、UTF-8 和 OS 错误正文;路径边界拒绝仍保留稳定安全码。
|
||||
- Codex Provider proxy 的请求体改写、上游发送、响应构造和 SSE 错误保留底层正文,避免代理层再次把具体网络/解析故障压成单一 unavailable。
|
||||
- 浏览器健康检查不再把发现、配置、DevTools 版本和清理失败只返回机器码;阶段码保留,同时带底层错误或明确的超时预算。
|
||||
- WebView 结构化对象错误保留 `message/detail/reason/code` 或有界 JSON 正文,避免 `[object Object]` 触发普通错误兜底。
|
||||
- Direct 工具桥图片与真实试玩证据读取/哈希错误保留 OS 正文,安全边界拒绝继续使用稳定码。
|
||||
- `agc_read_project_context` 保留 `file-read-failed` 等稳定码,同时在能取得时附带文件打开/读取/复核的 OS 正文。
|
||||
- Direct 交付复核和执行账本的 worker、产物读取、执行器摘要及宿主目录/锁失败保留底层原因,分类码不再单独承载正文。
|
||||
- Transport / Stream / IPC / host process / memory exhaustion 的回归测试与错误事件证据。
|
||||
|
||||
## 不做
|
||||
|
||||
- 不把 access token、Cookie、API Key、私钥、完整 URL 查询参数、绝对路径原文显示给用户。
|
||||
- 不改变上游协议、重试预算、计费和项目写入安全边界。
|
||||
- 无法获得更深原因时仅展示宿主真实观测事实,不把 Drop 伪造成网络中断;不回填旧版本缺失的原因。
|
||||
|
||||
## 验收标准
|
||||
|
||||
1. HTTP 401/403/408/429/5xx 显示状态码与脱敏后的上游错误文本。
|
||||
2. `connectionFailed`、`transportBroken`、`streamUnavailable`、`transportClosed` 显示对应分类及安全 detail;detail 不为空时不得落到通用错误句。
|
||||
3. 包含 `authorization=...`、`token=...`、Cookie、URL query、Windows/Unix 路径、私钥块的错误,只替换敏感值并保留同一行中的状态码和其它诊断字段。
|
||||
4. 包含 `out of memory`、`ENOMEM`、Windows 内存不足文本或 IPC/stdio 失败文本时,用户能看到对应安全文本。
|
||||
5. HostDropped 保留 panic hook 可取得的负载和位置,普通 Drop 明确仅观测到任务未写终态退出;显式捕获的 panic/transport/IPC 错误继续走 typed failure。旧无 detail 载荷可重放,新 detail 脱敏后进入同一条失败事件。
|
||||
6. 运行前端 DirectProject 错误映射测试、Rust redaction/runtime_error/turn_error 定向测试、TypeScript 类型检查、编码检查和 `git diff --check`。
|
||||
|
||||
## 当前证据
|
||||
|
||||
- `npx vitest run tests/directTurnFailure.test.ts`:15 passed。
|
||||
- `npx tsc --noEmit -p apps/ai-game-creator-shell/tsconfig.json`:通过。
|
||||
- Rust app-server 上游映射定向测试:9 passed;覆盖嵌套 `codexErrorInfo`、只有机器字段和非对象错误正文;应用日志脱敏测试与启动诊断脱敏测试均通过。
|
||||
- CC 错误分类回归:Claude sidecar 状态码/超时映射与非支付 409 保留上游状态的定向测试通过;侧车失败统一补充有界 stderr detail。
|
||||
- 真实 AGC dev smoke:客户端使用 `3080`、后端 `8084`、数据库 `3101`、后台 `3103` 启动;真实 CC 回合复现 `Reached maximum number of turns (8)` 被旧代码错误记为 `transport-closed`,修复后 Tauri 已热重编译重启。修复后的真实 Provider 回放未再次发送,避免无必要的付费请求。
|
||||
- 真实 AGC 项目诊断发现真实请求 HTTP 400 曾被摘要为 `codex-app-server-error:other detail=fields=codexErrorInfo`,另有 401 仅显示 `codex-app-server-error:unauthorized`;该漏损已在 app-server 投影边界修复。真实 Provider、真实 IPC 断链和真实内存压力仍未在本轮主动制造。
|
||||
- 继续扫描发现素材/参考图/资源编辑上传、模型目录初始化和 Direct MCP stdio 仍有固定 `map_err(|_| ...)` 丢正文,已改为保留底层网络、serde、写入和 OS 错误;平台素材上传定向 3 项、模型目录定向 4 项、Direct MCP 断输出定向 1 项通过。资源编辑全套并行测试本轮 54 项通过、11 项因共享 fixture/本地端口或账号环境超时失败,不能把该套结果记为全绿。
|
||||
- 本轮继续扫出三类未收敛口:外部 MCP HTTP 的 401/429/桥上下文缺失原先只有空状态码响应,现改为保留 HTTP 状态、稳定错误码和中文正文;认证 5xx 结构化载荷、账户/素材上传/发行发布的 401/403 现保留服务端 message/code(仍精确脱敏)。
|
||||
- 前端历史错误文本此前会把含有敏感字段的整条 Direct 诊断直接丢弃,现改为只替换凭据、链接和路径;旧版已经落盘且无法恢复正文的“执行通道中断”条目会明确追加“宿主未提供具体错误正文”,不再伪装成当前仍在吞错。
|
||||
- 新增/通过:外部 MCP HTTP 401/429 定向 2 项、前端 Direct/Runtime 错误正文 25 项、旧历史通用中断脱敏回归 1 项;认证 Rust 定向 19 项、素材上传响应错误定向 1 项通过。`appSurface` 全套仍有环境/fixture 相关失败,不能据此宣称全绿。
|
||||
- 继续扫描又发现画板项目/资产下载、模板库、图片分离、参考图上传、External Editor 生成恢复、资源编辑上传/轮询、项目快照和错误报告提交仍有“HTTP 只有编号”或未脱敏正文路径;已统一补上状态码 + 有界正文 + 精确脱敏。资源编辑拒绝原因继续复用既有 `details.message` 解析,避免丢掉平台具体原因。
|
||||
- 前端继续发现两个静默出口:模板库权限检查失败会直接清空入口,Direct 线程订阅/consume/bootstrap 的 IPC 失败会只 settle anchor 而不显示正文;现分别保留权限检查错误,并把订阅链错误正文送入项目状态栏/运行错误出口。认证失败回归已验证显示 `authentication-required`,不再变成泛化“执行失败,请稍后重试”。
|
||||
- UI 编辑器复制路径的原生剪贴板 IPC 失败也曾只显示“复制失败,请手动复制路径”,现保留脱敏后的剪贴板错误正文并同时给出手动复制指引。
|
||||
- 受控联网搜索的上游非 2xx 响应原来只返回 HTTP 状态;现保留有界、脱敏的服务端正文,错误码/限流原因不再丢失。
|
||||
- 窗口标题栏的 Direct 活动回合快照读取失败原来只给“读取失败”标志并保留旧快照;现额外保留脱敏后的 IPC/读取正文,面板不会把真实原因吞掉。
|
||||
- 项目打开时读取 `conversation.read` 权限策略的 IPC 失败原来静默 return;现把脱敏错误正文写入项目状态和聊天错误提示。
|
||||
- 错误报告通知读取本地待报告快照失败原来只重试后静默消失;现显示“项目诊断读取失败”及安全正文,并提供重试入口。
|
||||
- 共享 Prompt Polish hook 的注入请求/规范化/回填异常原来只显示固定失败提示;现保留精确脱敏后的异常正文,聊天输入和资源润色共用同一口径。
|
||||
- 资源画布历史生成任务账本读取失败原来统一显示“暂时无法恢复历史任务”;现在保留 Tauri/文件读取正文,非数组响应仍单独保留契约错误提示。
|
||||
- 发现默认 `requestChatPromptPolish` 还会把 Tauri 润色命令异常吞成 `null`,导致共享 hook 无法看到平台正文;现仅对空回包/桥不可用保留 `null`,原生异常继续上抛并统一脱敏。
|
||||
- 润色提醒偏好读取失败原来被当成“未关闭”默认值,写入失败只进错误上报池;现把读取/保存的 IPC 正文显示在输入区状态提示中,同时仍维持安全默认和回退行为。
|
||||
- Direct 项目清单首次读取和清单事件订阅失败原来只让 `@` 候选退化为空;现把脱敏后的读取/订阅正文送入项目聊天状态条。
|
||||
- Direct 活动回合事件桥注册失败原来只退回启动快照,面板没有说明后续更新已失联;现保留事件桥 IPC 正文并在标题栏/活动项目面板显示。
|
||||
- 首页富文本输入的原生剪贴板图片/文本读取失败原来直接按空剪贴板处理;现通过输入区错误提示保留脱敏后的剪贴板 IPC 正文。
|
||||
- 账号邀请码复制和微信充值状态确认仍有固定“复制失败/暂时没能确认到账”路径;现保留剪贴板/支付查询错误正文并继续给操作指引。
|
||||
- 游戏发布面板的截图上传、发布进度订阅和最终发布异常也有固定文案/裸 `Error.message` 路径;现统一通过可见错误脱敏出口展示正文。
|
||||
- 进入项目时预览活体核验的 IPC 失败原来只按“没有可复用预览”处理;现在保留安全正文并写入工作区状态,同时不误停未知活体。
|
||||
- 继续真实客户端启动和旁路扫描又发现三处仍会损失事实:旧历史通用中断文案遇到换行/多空格时未命中“正文缺失”标记,非 2xx 响应正文读取失败被 `unwrap_or_default()` 抹掉,CC sidecar 的 stderr 排空超时/读取任务失败也被当成“没有 stderr”。现分别改为折叠空白识别、保留“响应正文读取失败:底层原因”和保留 stderr 排空阶段/底层原因;不改变状态码、重试和脱敏边界。
|
||||
- 第二轮 Agent/CC 旁路扫描继续发现执行许可/执行会话/MCP 本地读取/编辑器 worker/Cocos worker 的 JoinError,以及 MCP future panic,会被固定成“任务未返回/异常/需要核对”;现把 join/panic 正文并入既有 ToolFailure / MCP 错误载荷,保留 `needs-reconciliation` 的不可自动重放语义。Native 定向测试:Direct MCP 33 项、Direct Tool Bridge 41 项、CC 14 项均通过。
|
||||
- 最新漏扫又补出 Direct Tools MCP 启动/运行退出码、Skill `resources/read` 索引与资源读取、交付 `finish_sealing` 错误三处固定句;现在分别写入 stderr、MCP 错误正文和交付复核底层原因,避免只剩退出码或“暂不可用/复核失败”。
|
||||
- 前端继续实测发现预览活体复用的 IPC / 权限拒绝会被当成“没有活体”并静默重启,资源生成事件订阅失败也只回退轮询;现在两条回退仍保留,但分别把复用失败正文附到重启结果、把订阅失败正文附到任务阶段。预览与生成队列定向测试 25 项通过。
|
||||
- 继续扫前端外围又发现 Direct 回合后的清单刷新、资源卡片预览未知错误、UI 编辑器单图预览读取失败仍会静默或使用无因固定句;现分别进入运行错误出口、保留脱敏后的预览错误正文并在编辑器状态提示中显示。
|
||||
- 资源依赖图读取失败原先只切到 fallback 布局状态,用户看不到 IPC/权限正文;现把安全错误保存到状态并在依赖画布内联显示。
|
||||
- 资源画布布局读写失败原先只把原因写日志、界面仍显示固定“布局失败”;现把脱敏后的 revision/IPC/权限/磁盘正文附到读取与保存提示,原有回退布局行为不变。
|
||||
- 又扫出发布状态、扩展列表、应用更新和生成任务的“无正文时固定句”分支;这些分支现在明确显示“未提供具体错误正文”,不再让空原因伪装成完整错误。
|
||||
- 项目权限策略读取还有两条调用方会在 IPC 异常时直接返回 `false`;现把脱敏后的读取错误同时写入工作台状态和项目聊天错误,不再把“未获权限”与“策略读取失败”混为一谈。
|
||||
- 策划 Agent 的 Tauri 事件订阅失败原先只放行回合等待、没有任何用户提示;现把订阅 IPC 正文写入项目聊天错误出口,避免只收到不完整的最终态。
|
||||
- 专业 Agent 历史回读与项目清单同步的后台 Promise 失败原先只返回 `failed` 或静默结束;现把具体读取/同步正文写入项目聊天错误出口。
|
||||
- Direct 交付计划/合同 JSON 解析、策划工作区目录枚举与文本搜索还有 `map_err(|_|)` / `Err(_)` 丢细节;现保留 serde、目录和文件读取正文,安全路径边界仍单独拒绝。
|
||||
- 直连 Codex 输出指纹此前把除 `NotFound` 之外的所有读取错误都当成“文件不存在”,可能误判产物未变化;现只对确实不存在的文件保留空位,其余磁盘/权限错误沿回合失败正文上送。
|
||||
- DirectProject 上下文的外部路径元数据读取失败原先统一成 `file-not-found`;现区分确实不存在与权限/磁盘读取错误,后者保留底层正文。
|
||||
- 错误报告对话框读取最新错误事件或诊断日志失败时原先只显示“暂不可用/使用快照”;现把读取 IPC/文件正文附在状态提示中,快照回退行为保持不变。
|
||||
- WorkspaceLauncher 的清单版本读取、失效事件重读和事件订阅失败原先只写日志或静默回退;现把安全正文送入启动器状态提示,仍保留已有回退刷新路径。
|
||||
- Direct 补丁事务的目标指纹读取原先把元数据、打开、正文读取和文件超限都折叠为 `None`,可能误判文件缺失或继续比较;现只对确实 `NotFound` 保留 `missing`,其余失败返回目标相对路径、阶段和底层正文,避免补丁结果被错误归因。
|
||||
- CC/Direct 工具桥的执行前后成果指纹、回执结算 JoinError/落盘失败,以及外部 MCP journal 的读取/JSON 行解析原先仍会被 `.ok()`、固定回执句或“无记录”吞掉;现分别保留指纹阶段、回执任务/落盘原因、journal 行号和底层正文,只有真实 `NotFound` 才视为空记录。
|
||||
- 外部 MCP `resources/read` 仍把已能返回具体原因的 Codex journal 读取错误 `unwrap_or_default()` 成空资源;现把读取失败按 MCP 错误响应返回,Direct 执行会话当前项目 canonicalize 也保留底层 I/O 正文。
|
||||
- Direct 交付终态读取原先把 `session.snapshot()` 锁/状态错误折叠成 `None`,上层继续显示“尚未确认交付完成”;现沿 `code-generation` 失败出口保留终态读取正文,并在已有回合失败时同时保留原始失败与终态读取失败。
|
||||
- 对话模型选择器读取 native 配置原先把 IPC/文件异常 `.catch(() => null)` 后只显示“读取客户端配置失败”;现通过统一可见错误脱敏出口保留具体正文,并保留配置不可读时不猜测模型路由的安全行为。
|
||||
- 策划工作区事件订阅失败仍有一处直接静默结束,导致文件刷新失联却没有提示;现把订阅 IPC 正文写入工作区错误状态,继续保留手动刷新与已有清单读取错误出口。
|
||||
- 新一轮原生扫出 app-server 进程与上下文漏损:Codex 执行器核验/OAuth 保存/回合身份的 JoinError、图片 base64 解码、Direct 上下文 canonicalize,以及进程树归属、Job、等待、终止和 Drop 清理错误原先只剩固定码;现保留 JoinError、解码器、OS/Job、等待和终止阶段正文,并为进程树 Drop 清理失败写入诊断日志。
|
||||
- 继续扫出浏览器收割的 Job/root kill/wait 错误原先压成 `bool`、External Editor 本机凭据 JSON/目录创建/保存错误丢底层正文,以及生成任务中断收口把项目清单读取失败当成“状态未知”;现分别保留浏览器清理阶段正文、凭据解析/存储正文,并让非“项目尚未初始化”的清单读取失败直接返回具体错误。
|
||||
- 受控 command 执行路径仍会把 leader 启动身份读取、进程组终止、root kill、Job/wait 失败折叠为“身份未确认/退出未确认”;现拒绝无身份接管并保留身份读取、终止兜底、等待和 OS 错误正文。
|
||||
- 继续扫到本地资源导入、平台素材 data URL、External Editor binding origin、项目模型记录读取/JSON 解析等错误仍丢底层正文;现区分真实 NotFound 与权限/元数据失败,并保留 base64、URL、文件读取和 JSON 解析细节。
|
||||
- 资源编辑上游 4xx 正文的流读取失败、正文超限、非 UTF-8 和未知 JSON 形状原先都会回落到笼统失败;现保留读取阶段、大小上限、编码错误和有界脱敏原文。
|
||||
- 认证状态订阅底层原先先把 IPC 失败转成空注销函数,导致上层新增的错误提示也收不到;现让订阅拒绝向登录页传播,后台身份投影失败时清除身份并留痕。
|
||||
- 发布构建预检原先把 `package.json` 元数据/读取/解析错误当成“没有 build”,把 `package-lock.json` 读取/解析错误当成“没有锁定版本”;现分别区分 NotFound 与真实读盘/JSON 错误,保留相对工作区路径和底层正文。
|
||||
- 发布尝试账本和工作区偏好文件原先把损坏、权限、类型和 JSON 解析失败当成空账本/默认偏好;现只有文件不存在才使用默认,其他读取错误沿发布幂等和建项目录命令返回具体正文。
|
||||
- 插件主机扫描工作区、读取进程状态、停止插件和禁用插件时原先吞掉目录项/`try_wait`/kill/wait 错误;现保留插件状态错误并阻止“停止失败却显示已禁用”,正常主动 kill 的非零退出码不再误报。
|
||||
- 插件进程 stderr 原先使用 `Stdio::null()`,即使进程退出也只能显示退出码;现以有界脱敏 sidecar 保留插件 stderr,并在退出状态中附带正文。
|
||||
- 新增发布尝试账本损坏回归:损坏记录现在阻止生成新的根幂等键,并返回账本 JSON 解析正文,避免静默重复发布。
|
||||
- 首页建项/打开项目读取本地 revision 失败原先吞成 `null` 并继续进入项目;现保留降级进入行为,同时把 IPC/文件读取正文写入首页状态。
|
||||
- 发布面板读取封面生成价格失败原先只清空价格并继续显示泛化消耗提示;现保留价格查询的脱敏正文并在确认弹窗中展示。
|
||||
- 资源模型卡、素材引用选择器和本地图片导入预览原先失败只降级成图标/文件名;现保留脱敏后的 WebGL、IPC、远程读取原因,并通过卡面 title/aria 或提示文本展示,降级预览行为不变。
|
||||
- 项目快照工作区登记失败原先只写固定诊断键,实际 IPC/权限/路径原因丢失;现把统一脱敏正文写入应用诊断日志,并保留重试登记行为。
|
||||
@@ -0,0 +1,45 @@
|
||||
# 【里程碑】CC复用Codex运行态事件
|
||||
|
||||
| 字段 | 值 |
|
||||
| ----------- | --- |
|
||||
| Version | 1.0 |
|
||||
| Status | implemented-pending-acceptance |
|
||||
| Date | 2026-10-04 |
|
||||
| Parent Spec | `docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md` |
|
||||
|
||||
## 目标
|
||||
|
||||
让 CC DirectProject 的流式文本、过程、MCP 工具调用和工具结果实时进入现有 Thread Manager 事件合同,并由现有聊天 reducer、工具卡片和历史投影统一展示。
|
||||
|
||||
## 范围
|
||||
|
||||
- sidecar 事件逐条回调到 Rust。
|
||||
- Claude stream event、assistant tool_use、user tool_result、工具进度和终态的安全投影。
|
||||
- CC 事件对应的 `ThreadItem` 历史追加与重复事件幂等。
|
||||
- Rust adapter、ThreadEvent/ThreadItem 现有合同的定向测试和前端 reducer/工具卡片回归。
|
||||
|
||||
## 不在范围内
|
||||
|
||||
- Claude Code 原生权限、MCP server、模型路由、会话恢复和取消语义重构。
|
||||
- 新增前端 CC 专用组件、公开 API、OpenAPI 或 SpacetimeDB 迁移。
|
||||
- 将未经安全投影的内部控制帧或完整思维链直接展示。
|
||||
|
||||
## 依赖与前置条件
|
||||
|
||||
- 现有 `ThreadEvent` / `ThreadItem` / `directThreadChat` 合同保持兼容。
|
||||
- Claude Agent SDK 当前 stream-json 消息包含 assistant content blocks 和 user tool_result。
|
||||
- 工作树已有的无关未跟踪文档保持不动。
|
||||
|
||||
## 验收标准
|
||||
|
||||
- [ ] CC `stream_event` 文本在 `result` 前生成 `item.delta`。
|
||||
- [ ] CC 工具调用先生成运行中 `mcpToolCall`,结果生成同身份完成/失败条目。
|
||||
- [ ] CC 过程文本使用现有 reasoning 条目,不增加前端 agentMode 分支。
|
||||
- [ ] 成功回合历史可重读,重复追加幂等;失败回合不伪造成功终态。
|
||||
- [ ] Codex 现有定向测试保持通过。
|
||||
|
||||
## 证据要求
|
||||
|
||||
- 自动化:Rust `claude_code_cli` / thread wire / Direct runtime 定向测试,前端 `directThreadChat` 与工具卡片测试。
|
||||
- 运行时:若本地 Claude SDK/Provider 可用,执行一次真实 CC Direct 回合;否则用 SDK stream-json fixture 明确记录未完成项。
|
||||
- 边界:tool_result 错误、缺失 tool id、未知控制帧、重复结果、取消/失败终态。
|
||||
@@ -0,0 +1,33 @@
|
||||
# 【里程碑】陶泥儿导出文件草稿化与发布媒体直传
|
||||
|
||||
- Version: `v1`
|
||||
- Status: 已实施(M1–M4 已落地)
|
||||
- Date: `2026-10-06`
|
||||
- Parent Spec:
|
||||
- `docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`(导出产物面板与陶泥儿导出)
|
||||
- `docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`(游戏分发发布媒体直传合同)
|
||||
- `docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`(游戏分发媒体与 schema)
|
||||
- 实现计划:`docs/project-memory/plans/【实施计划】陶泥儿导出文件草稿化与发布媒体直传-2026-10-06.md`
|
||||
|
||||
## 目标
|
||||
|
||||
把陶泥儿导出迁移到文件草稿 + agent 适配打包,并把游戏分发发布媒体改为 formdata 原始二进制/objectKey 直传,整体退役 `exports/` 与发布链路里的 assetId。
|
||||
|
||||
## 边界
|
||||
|
||||
- 含:`.export/taonier.json` 草稿、`build:taonier`/`dist-taonier`/`.export/taonier.zip`、adapt/fill/repair、线上值与「沿用线上值」、发布媒体 formdata 直传与新媒体读路由、`exports/` 全退役、AI 建议/封面与旧发布表单退役、web 平台端同步迁移。
|
||||
- 不含:图片生成计费链路、`/api/external/v1`、历史数据迁移、`vite-export-taptaph5` 登记。
|
||||
|
||||
## 里程碑与验收标准
|
||||
|
||||
| 里程碑 | 目标 | 验收标准 | 依赖 |
|
||||
| --- | --- | --- | --- |
|
||||
| M1 发布媒体合同解耦 | 公共读签名重构;shared-contracts/spacetime-module/api-server/web 改 objectKey + multipart;新媒体读路由 | 后端定向测试、`check:spacetime-schema`、`check:generated-bindings`、api-server smoke、web 发布用例 | 无 |
|
||||
| M2 AGC 打包链路与 exports 全退役 | skill + `build:taonier`/`dist-taonier` + `run_taonier_export_build`;删 `exports/` 全链与旧打包门 | Rust 定向测试、skill pack(taonier 部分)、AGC typecheck、契约测试 | 无 |
|
||||
| M3 AGC 陶泥儿面板与发布媒体直传 | 草稿模块与前端状态/UI;线上值与沿用;发布直传;退役 AI 建议/封面与旧表单 | vitest、Rust 命令测试、真实栈发布 smoke | M1、M2 |
|
||||
| M4 文档与共享记忆 | 主规范回写、decision-log/pitfalls、清理临时计划 | `check:doc-index`、`check:encoding`、`git diff --check` | M1–M3 |
|
||||
|
||||
## 依赖与门禁
|
||||
|
||||
- M1、M2 可并行;M3 依赖两者。
|
||||
- 每个里程碑验收通过前不得进入下一个;实现中行为变化按《规范驱动开发工作流》顺序回改主规范。
|
||||
@@ -1,5 +1,81 @@
|
||||
# 决策记录
|
||||
|
||||
## 2026-10-07 AGC 命令沙箱原生支持 fnm/nvm:只读挂载窄叶安装前缀,npm 改走 node + npm-cli.js
|
||||
|
||||
- 背景:开发构建里 AGC 让命令沙箱执行 `npm run build` / `npm install` 时,宿主 Node 由 fnm 托管,`node` / `npm` 实际是随 shell 会话变化的 fnm multishell 目录里的 shim;bwrap `--tmpfs /run` 会抹掉该路径,而只按单文件挂载 `<前缀>/bin/npm`(它软链到 `lib/node_modules/npm/bin/npm-cli.js`)会因 `Cannot find module '../lib/cli.js'` 失败。此前把宿主 `node` / `npm` / `npx` shim 指到 `/usr/bin/*` 是错误取舍:系统 Node 26 默认启用实验性 Web Storage,会顶掉 vitest 0.34 jsdom 的 localStorage,使 AGC 测试套件在 HEAD 即失败(见 `pitfalls.md` 2026-10-03 条)。
|
||||
- 决策(宿主发现与沙箱挂载共用窄叶校验):新增 `validate_node_installation_prefix`,canonicalize 后拒绝 `/home`、`/root`、`/tmp`、`/var`、`/etc`、`/proc`、`/dev`、`/run`、`/sys`、`/boot`、`/srv` 根、HOME 及其祖先和相对路径,并要求前缀同时含 `bin/node` 与 `lib/node_modules/npm/bin/npm-cli.js`(bundle 形态为 `<前缀>/node` + `node_modules/npm/bin/npm-cli.js`)。宿主版本枚举与 Linux 沙箱只读挂载都调用它,避免两处信任口径漂移。
|
||||
- 决策(版本选择):`.nvmrc` / `.node-version` 是权威 pin,能理解但未安装时返回 `node-version-pinned-not-installed` 失败关闭;`package.json` `engines.node` 只是偏好,永不阻塞;不支持的写法(`iojs`、`||`、部分 `>` / `<=`、hyphen range、prerelease)按未 pin 回退。整体顺序为 pin 命中 > PATH 可解析的可用 Node > 版本管理器回退链(`engines` 最高匹配 > 活动版本 > 默认别名 > 已安装最高版本),只实现文档化比较子集(精确三元组、major、`>=` / `>` / `<=` / `<`、`^`、`~`、`x` / `*`、`lts/*`);`v22.23.3` 这类带 `v` 的 `.nvmrc` 必须先剥前缀。nvm 的 `alias/default` 常只写主版本号(如 `22`),必须按同一 pin 子集在已安装版本里选最高匹配,不能当成完整三元组 `(22,0,0)`。
|
||||
- 决策(沙箱内启动形态):Linux npm 改为 `node <npm-cli.js>`,因为单文件挂载 npm 软链必然丢 `../lib/cli.js`;只读挂载整棵已验证的安装前缀(不是整个 HOME、`FNM_DIR` 或 `NVM_DIR`)。`npm install` 的联网判定跟随真实启动形态(`node` + `npm-cli.js` + `install`),不因包装变化丢 `--share-net`。
|
||||
- 决策(范围与非目标):托管版本管理器发现只在 `debug_assertions` / development 生效,发布构建继续只认随包 bundle;不把 fnm / nvm CLI 做成沙箱内工具;Windows 不变;不新增 fnm / nvm 之外的版本管理器。
|
||||
- 影响范围:`apps/ai-game-creator-shell/src-tauri/src/environment_check.rs`(窄叶校验、托管安装枚举、pin / engines 解析、选择与回退)、`command_sandbox.rs`(Node 工具链挂载收集与合并、`command_sandbox_requests_npm_install`、`FNM_MULTISHELL_PATH` 清理)、`command_exec.rs`(Linux npm `node_launcher`、`project_command_actual_target`、非 Node 程序 PATH 前置工具链 bin)、`process_session_bridge.rs`(`ProcessSessionLaunchPlan::from_launch` 改用实际目标)。
|
||||
- 验证方式:`environment_check` 24 passed、`command_sandbox` 14 passed、`command_exec` 19 passed、`process_session` 27 passed(均 `--test-threads=1`);`GENARRATIVE_COMMAND_SANDBOX_REAL_TEST=1` 真机 bwrap 内 fnm v22.23.3 的 `node --version` 与 npm 10.9.9 通过;另装 nvm v0.40.8 + Node v22.23.3,`command_sandbox_real_linux_opt_in_runs_nvm_installation_prefix` 证明真实 nvm 前缀可在 bwrap 内跑 node / npm,`real_node_npm_environment_versions`(仅 `NVM_DIR` + 空 PATH + 临时 HOME)证明托管解析确实选中 nvm;`cargo fmt --check`、`npm run check:encoding`、`git diff --check` 通过。验证后 fnm 仍是宿主默认,nvm 未写入任何 shell profile。
|
||||
- 边界:nvm 已在验证主机安装(v0.40.8 + Node v22.23.3)并跑通真实前缀与仅 nvm 解析;CI 仍由临时目录夹具覆盖 fnm / nvm 布局,真实安装路径测试保持 opt-in。真实验收前不宣称发布构建也支持 fnm / nvm。
|
||||
|
||||
## 2026-10-06 小红书导出 validate/pack:Chrome 61 能力按硬性 ERROR 拦下,pack 自带白名单不再共享
|
||||
|
||||
- 背景:真实项目适配反馈。① `validate.mjs` 的 `/#[A-Za-z_$][\w$]*/g`(class 私有字段)直接在原始文本上匹配,把 `"#e8f4ff"`、`document.querySelector('#hit')` 误判为 ES2018 语法并报 ERROR,把排查引向「构建链没转译」。② `CSS_MODERN_PATTERNS` / `MODERN_RUNTIME_API` 告警被移除后,`flex gap`(Chrome 84+)、`min()/max()/clamp()`(Chrome 79+)等晚于 Chrome 61 的写法被静默忽略却无人拦截,`references/manual-checks.md` 只有人读清单没有工具兜底。③ 宿主在**项目根** `.export/xhs-minitool.zip` 找产物,而 `--zip-out` 相对 cwd 解析;npm 脚本挂在 `game/` 子工程时 `.export/...` 会落到 `game/.export/`,没有任何提示。④ `pack.mjs` 直接 `import './validate.mjs'`,只复制 pack.mjs 会 `ERR_MODULE_NOT_FOUND`。⑤ `validate.mjs` 的 `[project]` 默认值只在源码 USAGE 里,SKILL 参数表没写默认值。
|
||||
- 决策(扫描先剥噪音):`scanJsSyntax` 先过 `stripJsNoise`——把注释与字符串字面量替换成等长空白(保留换行),只对真代码跑 ESM 与 ES2018 检测;模板 `${...}` 仍按代码处理。`"#e8f4ff"`、`'#hit'`、注释里的 `obj?.c` 不再命中,真 `#count` 私有字段仍报错。
|
||||
- 决策(Chrome 61 是硬性要求,不是「增强层」):恢复 `CSS_UNSUPPORTED_PATTERNS` / `MODERN_RUNTIME_API` 并判 **ERROR**(`CSS_UNSUPPORTED_FEATURE`),覆盖 `gap`/`row-gap`/`column-gap`(仅 `grid-gap` 与 `-webkit-` 前缀写法豁免)、`aspect-ratio`、`min()/max()/clamp()`、逻辑属性、`overflow: clip`、`:focus-visible`、`:has()`、`@container`、`subgrid`、`@layer`/`@property`、`dvh/svh/lvh`、现代颜色、`backdrop-filter`、`text-wrap: balance`,以及 `Object.hasOwn`/`structuredClone`/`replaceAll`/`.at`。`--strict` 保持移除:退出码只由 ERROR 决定。references 同步删除「基线层 + 增强层」与能力检测启用现代 CSS / 新 API 的写法;`flex gap` 一律用子项 `margin`。`:hover` 关键操作与安全区仍需人读清单。
|
||||
- 决策(产物落点有确定写法与预警):`SKILL.md` 写明产物是项目根的 `.export/xhs-minitool.zip`;`--zip-out` 默认 `../.export/xhs-minitool.zip`(假定 cwd 是 `game/`),相对路径落点位于 `<cwd>/.export/` 时打 `[warn]`,打印 cwd 与绝对落点。
|
||||
- 决策(pack 自带实现 + 参数表补默认值):`pack.mjs` 不再 `import './validate.mjs'`,把 `ALLOWED_EXTENSIONS` / `SKIP_DIRS` / 遍历与 `findUnsupportedFiles` 复制进 pack(改动时两处需同步);`SKILL.md` 参数表补 `validate.mjs [project]` 默认 `dist-xhs-minitool`、`pack.mjs --vite-built-dir` 默认 `dist-xhs-minitool`、`--zip-out` 默认 `../.export/xhs-minitool.zip`、`vite.config` `build.outDir` 默认 `dist-xhs-minitool`。
|
||||
- 影响范围:`resources/agc-skills/vite-export-xhs-minitool/{SKILL.md,references/{css-compatibility.md,js-compatibility.md,manual-checks.md,zip-artifact-spec.md},scripts/{validate.mjs,pack.mjs,vite.config.xhs-minitool.mjs}}`、`resources/agc-skills/manifest.json` 指纹(version=2026-08-26.62)、隐藏测试 `scripts/.validate.test.mjs`、`scripts/.pack.test.mjs`。
|
||||
- 验证方式:`node --test scripts/.validate.test.mjs scripts/.pack.test.mjs scripts/.vite.config.xhs-minitool.test.mjs`(36 passed)、`npm run agc:skill-pack:check`、`git diff --check`、`npm run check:encoding`。
|
||||
- 边界:「web 与小工具共用同一 `game/index.html`」是默认姿势,不需要另起 root;只有入口 HTML 确实不同才需要并存 recipe,本次未写,留待需要时补。
|
||||
|
||||
## 2026-10-06 新增 agc_install_skill_resource:Skill 附件由宿主直接落盘,不走模型正文
|
||||
|
||||
- 背景:`vite-export-xhs-minitool` 等审核 Skill 要把自带脚本(`scripts/validate.mjs` 约 40 KB、`pack.mjs`、`vite.config.*.mjs`)原样复制进用户项目。`agc_read_skill_resource` 的大文件正文会被模型上下文截断,重抄必然失真;原生 `cp` 在 DirectProject 只读沙箱下被审批闸门拒绝。两条路都走不通。
|
||||
- 决策:新增内置 MCP 工具 `agc_install_skill_resource`,入参 `skillName` / `relativePath` / `destinationPath`。宿主在 MCP 进程内直接读 `AGC_SKILL_PACK_FILES` 里已审核的内置字节(复用 `read_agc_skill_resource` 的清单校验与路径拒绝口径),组装成 `agc_write_file` 的 `{path, content}` 转发给客户端受控工具桥。写入因此走与 `agc_write_file` 相同的合同、lease、项目写锁与保护面校验;正文不经过模型上下文,没有截断,也不需要原生 `cp` 及其审批。
|
||||
- 边界:工具只暴露 `agc_write_file` 的两个入参,模型无法自带正文旁路审核;一次调用落一个文件,不打包、不改写、不建目录以外的副作用。DirectProject 的只读原生沙箱与 #439 审批口径不变,本工具是替代 `cp` 的受控通道,不是放开沙箱。
|
||||
- 影响范围:`apps/ai-game-creator-shell/src-tauri/src/agent/direct_tools_mcp.rs`(工具目录、分发、`install_skill_resource_write_arguments`)、`prompts/runtime/texts/direct-tools.json`、`prompts/runtime/texts/direct.json`,权威说明同步到技术方案。
|
||||
- 验证:`cargo test --offline -p genarrative-ai-game-creator-shell --bin genarrative-ai-game-creator-shell agent::`(736 passed,含新增 `install_skill_resource_*` 两条与 `tool_catalog_preserves_reviewed_resource_contracts`)、`npm run check:encoding`、`git diff --check`。
|
||||
|
||||
## 2026-10-06 小红书导出 skill:dist 收尾归 Vite 插件,pack 只读打 zip
|
||||
|
||||
- 背景:`vite-export-xhs-minitool` 的 `pack.mjs` 之前会就地改写 dist(相对路径 / 去 `type="module"` / `crossorigin` / 脚本移到 body 末尾 / 清空目录 / 兜底复制 icon)再打 zip;dist 只有在跑完 pack 后才合规,`validate.mjs` 看到的是中间态,打包脚本也因此同时承担「整理产物」与「打 zip」两件事。
|
||||
- 决策(职责边界):dist 收尾移进 `scripts/vite.config.xhs-minitool.mjs` 的 `xhsMinitoolArtifactPlugin`(`apply: 'build'` + `enforce: 'post'` 的 `closeBundle`);`minitoolBaseConfig` 只保留编译约束。构建结束 dist 即合规,`pack.mjs` 只做白名单预检与打 zip,不再写盘;`--vite-built-dir` / `--zip-out` 契约与产物落点不变,已适配项目无需改脚本。
|
||||
- 决策(失败语义):rollup 在构建失败时也会调 `closeBundle`,插件用 `buildEnd(error)` 记下失败并直接返回,避免留下只复制了 icon 的半成品 dist,也避免用插件的「index.html 不存在」盖掉真正的语法报错。
|
||||
- 注:本条同时取代 2026-10-05 条目里 `--out-dir` 与「就地删掉非白名单扩展名文件」的旧描述——现行参数是 `--vite-built-dir`,且只预检、不删除、不改写。
|
||||
- 影响范围:`resources/agc-skills/vite-export-xhs-minitool/{SKILL.md,.selective_rule.txt,scripts/{pack.mjs,vite.config.xhs-minitool.mjs}}` 与其 `resources/agc-skills/manifest.json` 指纹、`scripts/.pack.test.mjs`(补「pack 不改写 index.html」)、新增隐藏开发辅助 `scripts/.vite.config.xhs-minitool.test.mjs`(真跑 `vite build` 断言收尾、`copyPublicDir: false` 的 icon 兜底、失败不收尾、`pruneEmptyDirs` 只删空目录)。隐藏测试不进清单、不改 `skill_pack.rs` 的 `include_bytes!` 条目数(仍 36),manifest version=2026-08-26.51。
|
||||
- 验证方式:`node --test scripts/.vite.config.xhs-minitool.test.mjs scripts/.pack.test.mjs scripts/.validate.test.mjs`(30 passed)、`npm run agc:skill-pack:check`、`git diff --check`、`npm run check:encoding`。
|
||||
- 边界:仍是小红书小工具一个目标的适配脚本;宿主 `export/draft/xhs_minitool` 链路不变,第二目标出现时再抽 target 描述符。
|
||||
|
||||
## 2026-10-05 导出产物面板:适配归 code agent,宿主只校验并运行项目脚本
|
||||
|
||||
- 背景:AGC 需要把项目导出成第三方平台制品,首个目标是小红书小工具。平台不给上传 API,用户只能人工上传;而制品规范(zip 根目录、扩展名白名单、尺寸上限、`index.html` 要求)会随平台变化。把规范复刻进 Rust 等于在客户端养一份会过期的第二真源。
|
||||
- 决策(边界):宿主只做四件确定性的事——读写项目内 `.export/` 注册表、校验表单与脚本是否存在、以既有 `command.exec` 边界运行项目自己的 npm 脚本、把产物交给用户;适配与打包由 code agent 首次实验出来并固化成脚本落在用户项目里。宿主不解析 zip、不代跑 `pack.mjs`、每次导出都重新构建。
|
||||
- 决策(注册表):`.export/xhs-minitool.json` 首次打开时自动创建且字段全空;不存 status、不存「是否已适配」、不存经验文本,agent 的经验写进它自己拷进项目的脚本注释。「有没有脚本」永远现场从 npm 包 `package.json` 的 `scripts["build:xhs-minitool"]` 现算(`hasScript`)。状态一律现算,不落盘。
|
||||
- 决策(冲突):注册表有两个写者(agent 改文件、用户在面板编辑)。不引入 `revision` / `updatedAt` 这类 token 字段,改由宿主返回 `contentHash`、保存时带 `baseHash`;hash 不同再逐字段 diff,有字段真的不同才判冲突,返回两侧值让前端逐字段选择,纯格式化改动静默吸收,任何情况下不静默覆盖用户输入。
|
||||
- 决策(工作目录):`EXPORT_WORK_RELATIVE_DIR = ".export"` 锚在项目根并保持 flat;共享 icon 放 `.export/` 根,注册表存项目内相对路径,多目标复用同一文件;agent 拷贝的脚本按 `<target>` 前缀命名避免第二目标撞名。`.export/` **只**排除在项目快照同步之外(`PROJECT_SNAPSHOT_SYNC_ONLY_EXCLUDED_COMPONENTS`),恢复或换机后需重新适配(已知代价);**不得**并进通用排除口径——`agent/direct_patch.rs` 直接拿它拒绝路径,`agc_apply_patch` 会以「不得修改受保护或排除路径」拒绝 `.export/`,首次适配就落不了地;同机 checkpoint 仍含 `.export/`,可作恢复适配脚本的兜底。
|
||||
- 决策(skill 落点):`pack.mjs` 新增 `--zip-out <path>`(相对 cwd 解析、父目录自动创建),让「产物必须落在项目根 `.export/xhs-minitool.zip`」有确定写法;`--out-dir` 只允许指向 vite 构建输出目录(它会就地删掉该目录内非白名单扩展名的文件)。宿主只认结果:脚本名 + 非空产物路径,产物结构与平台规范仍归 skill 与 agent。
|
||||
- 决策(权限与副作用):跑脚本复用 `command.exec` 的权限口径(只查 deny,UI 按钮即用户确认),不新增 `GAME_CREATION_APP_COMMANDS` 条目;导出链路不拿项目写锁、不推进全局 revision,`.export/` 不进 manifest、素材、UI State 或客户端投影;发布包白名单收集,`.export/` 不会进入。
|
||||
- 决策(prompt 归属):首次适配与失败修复的指令正文、契约常量与组装都放前端(`view/project-development/export/state/xhsMinitoolInstruction.ts` 纯函数;失败说法在 `xhsMinitoolFailure.ts`),宿主不提供 `enqueue_*` 命令;前端经 `useDirectProjectChatController` 的 `chat.submit` 入队,复用它既有的会话写权限门与 clientTurnId。Rust 侧因此只有 per-target 的四个命令(读内容 / 读指纹 / 存表单 / 跑构建);内容与指纹为什么分成两条见下面 2026-10-05 的同步细节决策。
|
||||
- 影响范围:新增 `apps/ai-game-creator-shell/src-tauri/src/export/{mod,draft/mod,draft/xhs_minitool/{mod,commands}}`(`main.rs` 仅加 `pub mod export;`)、前端 `view/project-development/export/{tabs/xiaohongshu,state,generated}`、`resources/agc-skills/vite-export-xhs-minitool/scripts/*` 与其 `manifest.json` 指纹、`src-tauri/src/agent/skill_pack.rs` 的 `include_bytes!` 内容(文件数不变,仍 36 条)。不触碰 `GAME_CREATION_APP_COMMANDS`、`shared-contracts`、`server-rs`、SpacetimeDB、OpenAPI、`api-server` 与现有 `project/export.rs` 发布链路。
|
||||
- 验证方式:`cargo test ... export::` 与 `export_bindings` + `npm run check:generated-bindings`、前端 vitest(复制 / 下载 / 冲突逐字段选择 / 适配指令组装与入队 mock)、`npm run ai-game-creator-shell:typecheck`、`npm run agc:skill-pack:sync` + `agc:skill-pack:check`、`npm run check:doc-index`、`npm run check:encoding`、`git diff --check`;真实 vite 项目上验证首轮适配产出可上传 zip 与二次导出零 agent 调用。
|
||||
- 边界:本决策只覆盖小红书小工具这一个目标;第二个目标出现时再抽 target 描述符,不预留动态注册或通用表单引擎。
|
||||
|
||||
## 2026-10-05 导出面板的同步细节与文案归属:指纹不进对外状态
|
||||
|
||||
- 背景:导出面板(同上一条)落地后回头核 [`【ADR】DirectProject对话滚动与历史自动加载-2026-10-02`](../../adr/【ADR】DirectProject对话滚动与历史自动加载-2026-10-02.md),发现两件事与既有口径不一致:读状态把内容与指纹混在一条命令里、宿主自己拼了「省略前 N 个字符」这句用户可见文案。两条都在这里收口。
|
||||
- 决策(同步细节不进对外状态):命令拆成 `read_xhs_minitool_export`(`form` + `hasScript`,自动刷新反复调的高频路径)与 `read_xhs_minitool_export_hash`(写回基线),后者只在进编辑会话、或新内容真的被采纳时取一次。`contentHash` 不进前端对外状态(hook 里只在 `baselineRef`),界面不显示任何同步状态。理由:刷新这条高频路径在**类型上**就动不了写回基线,否则「顺手刷新」会把两次刷新之间的外部改动认成自己的基线、把冲突吞掉。
|
||||
- 决策(自动重读,取代「刷新按钮」,作废提交 `90f651ddf` 的「刷新取消未保存输入」):取消刷新按钮,按 2 秒固定间隔自动重读(窗口在后台时跳过)。`hasScript` 是宿主现算事实,任何时刻都采纳;表单只在用户手上没有未保存输入(也不在冲突/字段错误里)时才覆盖,否则绝不覆盖正在打的字。代价是「刷新会取消未保存输入」这条旧行为被有意作废,对应的回归用例同步替换。
|
||||
- 决策(文案归属的延伸):宿主不预拼用户可见文案这条也管载荷里的说明——构建输出尾部只回 `outputTail` + `omittedCharacters` 两个事实,「已省略前 N 个字符」由前端拼(`export/state/xhsMinitoolOutputTail.ts`,失败卡片与成功提示共用)。与上面那条 prompt 归属同源:机器事实在 Rust,句子在前端。
|
||||
- 契约常量的单一来源:Rust `export/mod.rs` 与 `draft/xhs_minitool/layout.rs` 是契约值(`.export`、脚本名、注册表名、产物名)的唯一权威;前端在 `export/state/xhsMinitoolInstruction.ts` 保留一份用于拼指令,由 `tests/xhsMinitoolContract.test.ts` 直接读 Rust 源码把两份钉住(ts-rs 不能导出 `const`,所以钉法是测试而不是生成物)。改 Rust 常量而前端没跟,测试红。
|
||||
- 影响范围:`export/draft/xhs_minitool/{commands,error,build,dto}.rs`、`export/generated/*.ts`、`export/state/{useXhsMinitoolExport,xhsMinitoolFailure,xhsMinitoolOutputTail,xhsMinitoolApi}.ts`、`export/tabs/xiaohongshu/*`。不新增命令、不碰权限位、不动发布链路。
|
||||
- 验证方式:`cargo test ... export::`(含 `export_bindings`)、`npm run check:generated-bindings`、`npx vitest run apps/ai-game-creator-shell/tests/xhsMinitoolExport.test.tsx apps/ai-game-creator-shell/tests/xhsMinitoolContract.test.ts`、`npm run ai-game-creator-shell:typecheck`、`npm run ai-game-creator-shell:check:rust:shell`、`npm run check:doc-index`、`npm run check:encoding`、`git diff --check`;真实 vite 项目上的首轮适配与二次导出仍是未验证项。
|
||||
- 边界:只覆盖小红书小工具这一个目标;第二个目标出现时再抽 target 描述符,不预留动态注册或通用表单引擎。
|
||||
|
||||
## 2026-10-04 AGC 审核 Skill Pack 新增平台抽象与小红书小工具两项,隐藏文件永不进包
|
||||
|
||||
- 背景:`resources/agc-skills` 新增两个审核 Skill `platform-abstract` 与 `vite-export-xhs-minitool`。后者自带本地开发辅助文件(`.selective_rule.txt`、`scripts/.pack.test.mjs`、`scripts/.validate.test.mjs`),旧清单工具会走目录把它们当成必须声明的审核文件,指纹、安装与只读读取也无法区分「审核正文」和「本地辅助」。
|
||||
- 决策(新规则):`agc-skill-pack.v1` 引入「`.` 前缀即隐藏、永不进包」的全局约定。JS `isHiddenSkillEntryName` / `isSafeSkillRelativePath` / `collectBundledFiles` 与 Rust `is_hidden_skill_entry_name` / `is_safe_skill_relative_path` 共用同一口径:隐藏文件与目录不写清单、不参与 SHA-256、不安装到隔离 `.agents/skills`,也不能经 `agc_read_skill_resource` / `agc://skills/*` 读取。新增 Skill 只需声明非隐藏文件,无需为 `.selective_rule.txt` 之类补 `include_bytes!`;声明了隐藏路径会直接被拒绝。
|
||||
- 决策(注册):白名单两处同步扩到 10 项——`scripts/skill-pack-manifest.mjs` 的 `EXPECTED_SKILL_NAMES` 与 `src-tauri/src/agent/skill_pack.rs` 的 `AGC_SKILL_PACK_EXPECTED_NAMES`;`AGC_SKILL_PACK_FILES` 增加 13 条 `include_bytes!`(`platform-abstract` 1 + `vite-export-xhs-minitool` 12),数组长度 23 → 36。`agc-skill-pack.v1` manifest 版本 `2026-08-26.37` → `2026-08-26.40`,两个新条目的 `sha256` 由 `npm run agc:skill-pack:sync` 对当前工作树现场重算,不从旧构建复制。`vite-export-xhs-minitool` 的 `pack.mjs` / `validate.mjs` 顺带修掉阻止 pre-commit eslint 的问题(`simple-import-sort` 导入排序、未用参数改名 `_line`、NUL 正则加 `no-control-regex` 行内说明),并去掉 `platform-abstract/SKILL.md` 的行尾空格与两个 references 的多余 EOF 空行以通过 `git diff --check`,指纹随之同步到 .40;隐藏的 `.pack.test.mjs` / `.validate.test.mjs` 被 eslint 按点文件默认忽略,pre-commit 会经 `isPathIgnored` 过滤掉。
|
||||
- 决策(lint 边界):`.eslintrc.cjs` 的 `ignorePatterns` 增加 `apps/ai-game-creator-shell/src-tauri/resources/agc-skills/**`。审核 Skill 脚本按 SHA-256 定址并用 `include_bytes!` 编进客户端,`eslint --fix` 改写会让 manifest 指纹漂移,而 pre-commit 不跑 `skill-pack:check`,只能等 CI 暴露;`.prettierignore` 本已排除该目录,eslint 补齐同一口径。仓库自身的 `scripts/skill-pack-manifest.mjs` / `check-skill-pack.test.mjs` 仍正常 lint。
|
||||
- 决策(测试夹具):`codex_app_server` 里三条 fake app-server 的 `skills/list` 响应补上两个新名字;否则 `validate_direct_project_skill_catalog` 会判定审核 Skill Pack 不完整,让 DirectProject 启动失败关闭。
|
||||
- 影响范围:`apps/ai-game-creator-shell/scripts/{skill-pack-manifest.mjs,check-skill-pack.test.mjs}`、`apps/ai-game-creator-shell/src-tauri/src/agent/skill_pack.rs`、`apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server/mod.rs`(仅测试夹具)、`apps/ai-game-creator-shell/src-tauri/resources/agc-skills/manifest.json`、`.eslintrc.cjs`(审核 Skill 目录加入 `ignorePatterns`);`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md` 的 2026-08-20 审核 Skill Pack 条目同步。`docs/project-memory/plans/【里程碑】AGC编辑器分支产物归位与症状层补丁清理-2026-09-26.md` 里「`list_agc_skill_catalog` 返回 8 条」是当时实测记录,保留不改。
|
||||
- 验证方式:`npm run agc:skill-pack:check`(version=2026-08-26.40)、`node --test scripts/check-skill-pack.test.mjs`(5 passed,含隐藏路径拒绝与收集器忽略隐藏文件)、`cargo test --bin genarrative-ai-game-creator-shell -- --test-threads=1 agent::skill_pack`(7 passed)、`agent::codex_app_server::tests`(72 passed)、`agent::direct_runtime::tests`(92 passed)、`cargo fmt --all --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml -- --check` 通过;`npx eslint`(变更的仓库脚本)通过,且 `resources/agc-skills/**` 经 `isPathIgnored` 全部返回 true。
|
||||
- 边界:隐藏文件规则只约束随包审核与只读读取,不改变这些文件在仓库中的存在、权限或本地用途。
|
||||
|
||||
## 2026-10-05 播放会话前缀在三处入口清空 Cookie,网关 403 纵深防御不变
|
||||
|
||||
- 背景:付费游戏的可玩入口是创建播放会话后拿到的 `/api/game-distribution/play-sessions/<token>/`(sandbox iframe 的 `src`,包内相对资源沿同一前缀解析)。该前缀落在 `/api/*` 上,边缘通用 `/api` location 必须转发 Cookie(`/api/auth/*` 需要 refresh cookie),而 `api-server` 播放网关对带可解析平台 refresh Cookie 的请求返回 403,导致真实浏览器里 iframe 与每个包内资源都 403、付费游戏实际不可玩。
|
||||
@@ -189,23 +265,6 @@
|
||||
- 影响范围:根开发脚本、AGC 开发启动编排、本地开发运维文档;不改变 API、schema、生产部署和独立 `npm run agc` 行为。
|
||||
- 验证方式:参数/状态单测、开发栈健康端点 smoke、`.app/dev-stack.json` 身份复用检查、进程树收束检查。
|
||||
|
||||
## 2026-10-01 AGC 命令错误结构化与错误报告口径
|
||||
|
||||
- 决策:AGC 命令失败按**具体变体**建模(Rust `#[derive(Serialize, TS)]` 枚举 + `#[serde(tag = "type", rename_all = "camelCase")]` + ts-rs 导出,生成物不手改),`#[tauri::command]` 的 `Err` 直接携带结构化枚举;前端先按 `type` 选类别、可枚举细分再按类型化 `reason` 分流,**任何地方都不对错误文案做判断**。做法沿用 DirectProject 既有约定(`enqueue_direct_codex_turn -> Result<(), DirectTurnEnqueueFailure>`),不是新机制。
|
||||
- 决策:错误报告池只收**没有任何调用方处理**的错误。预期业务拒绝(用户输入 / 前置条件 / 预期 4xx)由调用方消化并给反馈,永不进池;真故障由调用方带上下文交给错误池(`ClientAuthErrorWrapper` 承载 `source/action/page`,`captureClientError` 用 `instanceof` 解包),`window.onerror` / `unhandledrejection` 只兜底没人接手的错误;408/5xx/网络的判定由调用方在 catch 里做(4xx 一律不报);Rust agent 终态失败仍由失败投影入池。删除 WebView 侧 `shouldCaptureClientError`。
|
||||
- 边界:变体按**可判定的事实**命名——服务端 400 只给 `status + message`(`AppError.code` 仍是通用 `BAD_REQUEST`),所以 400 变体按"哪条请求的输入被拒"命名(如 `passwordLoginRejected`),不假装能区分密码长度/手机号格式。报告面板默认全选、只由通知打开的既有承诺不变。`captureAgentRuntimeError`、`ResourceReferenceInput` 偏好写盘、`invokeDiagnostic` 三处显式采集点保持原行为,按同一口径改造或删除留在后续变更。
|
||||
- 影响范围:`apps/ai-game-creator-shell/src-tauri/src/{auth_error.rs,auth_session.rs}`、`apps/ai-game-creator-shell/src/services/{clientAuthErrorWrapper.ts,errorReporting.ts,clientAuth.ts}`、`apps/ai-game-creator-shell/src/app/AuthenticatedClient.tsx`、`apps/ai-game-creator-shell/src/services/generated/`(ts-rs 生成:`ClientAuthError.ts` + 有字段变体的载荷文件)。
|
||||
- 决策(补充):TS 形状**只有带载荷的变体才有具名载荷类型**——无字段变体在 ts-rs 里就是 `{ type: 'x' }`,有字段的变体是 newtype 变体持有同名 `#[ts(export)]` 结构体,生成 `{ type: 'x' } & X` 与 `src/services/generated/X.ts`;可枚举的细分原因是类型化枚举字段(`ServerAddressReason` / `AuthNetworkReason` / `AuthResponseInvalidReason`),不是字符串、也不各拆一个顶层变体。前端 `switch (error.type)` 的无字段分支用固定文案,带载荷分支先 `as X` 再读它自己的字段,`reason` 是枚举时再 `switch (payload.reason)`(`default` 同样用 `expectNever`)。不允许在前端手写这层类型,也不再包派生分类 / 提示文案函数(`clientAuthErrorKind`、`clientAuthErrorNotice`、`resolveClientAuthFailure` 已删除)。
|
||||
- 验证:见 [`【ADR】AGC命令错误结构化与错误报告口径-2026-10-01`](../../adr/【ADR】AGC命令错误结构化与错误报告口径-2026-10-01.md) 的验收清单;关键判据是"登录 400/401 业务变体不产生 `report_client_error`、不弹「发现问题」"。
|
||||
- 决策(2026-10-01,JS 侧载体与抛出时机):认证命令统一经 `invokeClientAuth(command, args)` 调用;拒绝值原样装进已有的 `ClientAuthErrorWrapper`(载体只有一个 `ClientAuthError` 类型的 `error` 字段,值就是 ts-rs 生成的判别联合;不读变体字段、不塞 `context`,构造时把整份载荷 `JSON.stringify` 进 `Error.message`,上报事件因此拿到机器事实),**不新增手写错误类**(`ClientAuthFailure` 已删除),形状完全信任 tauri + ts-rs 映射、不做运行时嗅探。形状读取 / 文案回落 / 提示分类三层(`isClientAuthError`、`getClientAuthErrorMessage`、`presentAuthFailure`)全部删除。
|
||||
- 决策(2026-10-01,判定位置与出口):要不要上报只由 catch 子句里的 `switch (failure.type)` 判,`failure = error.error`;无字段业务 / 会话变体用本 catch 的固定文案,带载荷变体先 `as` 取自己的具名类型、再用它自己的 `reason` / `serverMessage` / `status` 拼上本次操作的上下文前缀(`reason` 是枚举时再 `switch (payload.reason)`);Rust 不预拼用户可见文案、服务端原文缺失就是 `null`(无兜底文案)。系统变体原样 `throw` 经全局 `unhandledrejection` 入池(`captureClientError` 用 `instanceof` 解包 `error` 字段取原始错误),`default: expectNever(failure)` 让漏接变体编译失败。取代"未识别变体上调是故意的"。
|
||||
- 决策(2026-10-01,Rust 侧不再降级):`refresh_session_inner` 的非权威失败直接 `Err(ClientAuthError)`,`ClientAuthStateView` / `ClientAuthRefreshView` 删除 `errorMessage`,续期结果删除 `failed`;`ClientAuthState` 收敛为 `authenticated | unauthenticated`,`ClientAuthRefreshResult` 收敛为 `refreshed | unauthenticated | stale`。
|
||||
- 追加(2026-10-02,429 按路由判定):`/api/auth/phone/login` 验证码错误次数耗尽返回的 429 是用户可修正的输入问题,映射为 `phoneCodeLoginRejected`(复用现有业务变体、不进错误池);发码路由仍是 `smsCodeThrottled`,其余路由的 429 仍是 `unexpectedRejection`。
|
||||
- 追加(2026-10-02,401 归 phoneCodeLoginRejected):`/api/auth/phone/login` 的 401 只来自「用户不存在」(验证码错误/失效/过期在服务端都是 400,已由 `phoneCodeLoginRejected { serverMessage }` 带原文);顶层变体 `smsCodeRejected` 退役删除,前端三个 catch 去掉了它那个「验证码错误或已过期」的固定分支,`phoneCodeLoginRejected` 的文案统一为「验证码登录失败:<服务端原文>」。
|
||||
- 追加(2026-10-02,读 body 失败按已确认状态码归类):AGC 认证请求拿到 `status` 后 `response.text()` 失败,不再一律压成 `authNetworkFailure { unreachable }`;非 2xx 走既有分类(`serverMessage` 为 `None`,如 503 → `authServiceUnavailable { 503 }`),只有 2xx 响应没收完才算传输层故障。分类收敛在 `classify_unreadable_body`。
|
||||
- 追加(2026-10-02,系统类失败保留原始错误载荷):`clientSessionPersistFailed` / `runtimeSessionInstallFailed` / `authClientInitFailed` 都带 `detail: string`(原始 error),既让调用方有机会分流处理,也让报告包带够诊断信息;原始 error 同时经 `app_log!`(落盘前过 `sanitize_diagnostic_message`)记一行本地日志。`detail` 不贴到界面上:三个 catch 用本操作的固定文案(登录检查 / 发码 / 登录各自不同)。取代上一版"本机 IO 失败不进载荷、原始 error 只进日志"。
|
||||
- 影响范围(2026-10-01 第二轮):`apps/ai-game-creator-shell/src/services/{clientAuth.ts,platformSession.ts}`(`clientAuthError.ts` 删除)、`apps/ai-game-creator-shell/src/app/AuthenticatedClient.tsx`、`apps/ai-game-creator-shell/src-tauri/src/auth_session.rs`、对应 vitest 用例。
|
||||
|
||||
## 2026-10-02 launcher 页面高度契约:外壳分高度,页面不再自己算窗口高度
|
||||
|
||||
- 背景:PR #228(`6d2c275d3`)只给项目页补了「外壳纵向 flex + 页面 `flex: 1 1 auto`」的高度修复;其余页面仍各自算高度——帮助页没写高度也没有内层滚动容器,内容一长就被外壳 `overflow: hidden` 裁掉且无法滚动;首页用 `h-screen` / `h-[calc(100vh-32px)]`,模板库用 JS 量父级高度写内联 `height`。
|
||||
@@ -9849,3 +9908,16 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在
|
||||
- Codex 15/110 分钟事件静默与 120 分钟硬上限、Claude 独立超时保持不变;取消、回合归属、并发、交付复核次数与进程退出证明继续有效。
|
||||
- 正式执行账本为 v4,旧上限只在已发布数据的加载迁移边界识别;不保留旧运行时路径。App 管理配置安全写回,共享只读配置不改源文件;旧终态与报告不重开。
|
||||
- 权威行为与验证边界见 [AGC 主规范](../../technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md#direct-宿主运行时间上限移除2026-10-06)。
|
||||
|
||||
## 2026-10-06:陶泥儿导出文件草稿化与游戏分发发布媒体直传
|
||||
|
||||
- 导出目标命名约定与落点:草稿 `.export/taonier.json`(唯一真相、flat `.export/` 目录)、npm 脚本 `build:taonier`、独立构建目录 `dist-taonier`、交付/发布产物 `.export/taonier.zip`(存档根必须有 `index.html`);标准 `build`/`dist` 不被占用。新增 `vite-export-taonier` skill(`vite.config.taonier.mjs` + `pack.mjs`),宿主 `export/draft/taonier` 提供读内容 / 读指纹 / 存表单 / 跑构建四个命令。
|
||||
- 草稿模型对齐小红书重构:宿主零业务校验,校验只在前端 `taonierFields`;600ms 自动保存带 `baseHash`、2s 轮询只更新外部事实、冲突逐字段选择;失败现场原样转发 code agent,adapt/fill/repair 三类指令与小红书同构;线上值只读并排,可逐字段/逐图「沿用线上值」。
|
||||
- `exports/` 整体退役(E1):目录创建、README 生成/渲染、试玩包打包/读取/列举、`prepare/upload/list` 命令、staging 依赖、生成任务产物声明、prompts、run-trace 白名单与 5 处排除列表全部移除;发布暂存固定读取 `.export/taonier.zip`,phaser/vite 发布门删除。
|
||||
- 发布媒体硬切:`POST /api/game-distribution/games`、`POST /api/game-distribution/games/{gameId}/versions`、`PATCH /api/game-distribution/my-games/{gameId}` 改收 `multipart/form-data`(`metadata` 文本 JSON 含 `coverObjectKey` 与 `screenshots: (string|null)[]`,可选 `cover` part,可重复 `screenshot` part);`string` 槽位沿用线上 objectKey,`null` 槽位按序消费二进制 part。不保留旧 JSON / assetId 兼容分支;AGC 发布先读草稿本地路径发字节、空路径发线上 objectKey,幂等摘要纳入图片字节。
|
||||
- 媒体公开读按域内判定:新图直落项目快照桶 `agc/project-snapshots/v1/game-distribution/media/<gameId>/{cover|screenshot}-<uuid>.<ext>`,不建 `asset_object`;公开读走 `GET /api/game-distribution/media/read-url`(及 `read-bytes`),判定 = 已发布且 active 的 game 且 objectKey 命中 `cover_object_key` / `screenshots_json`,不经素材库 ACL。删除 `game_distribution_asset_has_public_read_grant` 与发布链路中的 assetId;游戏行与冻结资料只存 objectKey,删除 `GameDistributionFrozenScreenshot`。
|
||||
- schema 删字段属 breaking:按既有先例用 `SPACETIME_SCHEMA_GUARD_ALLOW_BREAKING=1 npm run check:spacetime-schema` 验证,并同步 `migration.rs`、表目录与生成绑定。
|
||||
- AI 资料建议 / 封面生成退役:删前端调用、Rust 命令与注册、api-server 路由与专属 DTO;保留发布进度对话框。
|
||||
- skill pack 例外:`agc-skill-pack.v1` 只登记 `vite-export-taonier`;未跟踪的 `vite-export-taptaph5` 是另一条 WIP,不登记,`npm run agc:skill-pack:check` 对其未声明文件报红为已知接受项。
|
||||
- 权威行为已回写 AGC 实施计划「2026-10-06」章节、玩法链路「游戏分发发布媒体直传合同」与后端数据契约;实施计划《【实施计划】陶泥儿导出文件草稿化与发布媒体直传-2026-10-06.md》与对应里程碑规范**保留在 `docs/project-memory/plans/`**,不再按该计划 §5.4 在收尾时删除。
|
||||
- owner 作用域媒体读(同日补充):公开读只覆盖已发布作品,作者预览未发布 / 待审 / 被驳回作品的封面与截图会退化成占位图。新增 `GET /api/game-distribution/my-games/{gameId}/media/read-url`(及 `.../media/read-bytes`,需 bearer),判定 = `gameId` 属于当前登录主体且 objectKey 命中该作品当前行的 `cover_object_key` / `screenshots_json`;复用同一签名器与 `get_game_distribution_game(owner_user_id)`,不新增 procedure、不给素材库 ACL 开特例,公开读判定不变。AGC `resolve_preview_url`、平台 web `useGameDistributionMediaReadUrl`(带 `gameId`)、`MyGamesPage` 封面与发布/编辑回填全部切到 owner 路由。
|
||||
|
||||
@@ -10,6 +10,31 @@
|
||||
- **AGC 美术包**:核心图集新请求将原 brief 与共用的风格、内容和间距要求分别写入 `iconDescriptions`;普通图标入口仍原文单项透传。新增模板只影响新请求,旧请求继续按冻结正文和操作身份恢复,不改原 brief 的意图判据。真实 HTTP 载荷与旧请求恢复必须同时验证,不能只测试 `artSpec` 包含文案。
|
||||
- **权威说明**:[External v1 OpenAPI](../../openapi/genarrative-external-v1.openapi.json) 与 [API 指南的 Generation Inputs Metadata](../../../.codex/skills/genarrative-external-editor-api/references/requests-and-outputs.md#generation-inputs-metadata)。本条记录现有行为,不引入 API、存储或 UI 行为变更。
|
||||
|
||||
## 2026-10-06 陶泥儿导出与发布媒体直传:幂等摘要、幂等账本、skill-pack 红与 zip 根入口
|
||||
|
||||
- **multipart 发布的幂等摘要必须纳入图片字节**:写路径改 `multipart/form-data` 后,`metadata` 文本 part 只描述槽位;若摘要只序列化元数据,同一 `Idempotency-Key` 换一张新图或换一个沿用 objectKey 会被判成同请求重放,服务端静默复用旧结果、新图丢失。现行口径:服务端 `publish_request_digest` = 规范化元数据 JSON + 每个 `cover`/`screenshot` part 的 SHA-256 + 槽位里的 objectKey;AGC 侧 `metadata_digest` 同样把图片原始字节喂进哈希。create / create version / update metadata 三条写路径共用;回归时「只换图不改文案」必须得到不同摘要。
|
||||
- **幂等键与发布账本口径**:AGC 根幂等键由账本按「账号 + origin + 本地项目 + 包摘要 + 目标游戏/版本 + 资料摘要」解析,同一次用户发布的重试、响应丢失后的重发与进程重启后的分片续传都复用同一 root key,包内容 / 目标版本 / 资料任一变化才换键;分包上传再用 `<root>:upload`。服务端 `game_distribution_idempotency_receipt` 以 `owner_user_id + action + idempotency_key` 唯一,同 key 同摘要回 `replayed=true` 并复用结果 ID,同 key 不同摘要返回 409。不要把重试实现成新 key,也不要让摘要漏掉图片字节或沿用 objectKey,否则媒体直传下的重放语义失效。
|
||||
- **`npm run agc:skill-pack:check` 因未登记 taptap 报红是已知接受项**:`vite-export-taptaph5` 是另一条 WIP,未进 `AGC_SKILL_PACK_EXPECTED_NAMES` / `EXPECTED_SKILL_NAMES`;check 会列出它的未声明文件并退出非零。当前接受该红,不为它补登记或改清单;要恢复全绿必须先收口或正式登记 taptap skill。taonier 自身的清单与指纹已锁步。
|
||||
- **taonier zip 必须存档根 `index.html`**:`.export/taonier.zip` 的存档根必须直接有 `index.html`,不能套外层文件夹(`taonier/index.html`、`game/index.html` 都算失败)。`vite-export-taonier/scripts/pack.mjs` 在落盘前按根入口校验并拒绝;即使绕过,服务端发行合同只认根 `index.html`,会以 `ReleasePackageError::MissingEntry` 映射 422 `PACKAGE_VALIDATION_FAILED` 拒收。适配非标准工程布局时,让 `build:taonier` 把 `--vite-built-dir` 指到真正含 `index.html` 的目录,或让 `pack.mjs` 不套外层;客户端 `run_taonier_export_build` 也在产物收尾复核根入口。
|
||||
- **作者预览未发布作品必须走 owner 媒体读,不能再回退素材库 ACL**:发布媒体是项目快照桶对象、不建 `asset_object`,`/api/assets/read-url` 不会给它授权;公开读路由 `GET /api/game-distribution/media/read-url` 只认「已发布且 active」,两条都不覆盖"作者看自己未发布/被驳回作品"。症状是作者中心与 AGC 线上值封面/截图静默退化成占位图(换签失败被吞成空地址),不报错、不阻断。现行口径:作者侧一律走 `GET /api/game-distribution/my-games/{gameId}/media/read-url`(`read-bytes` 同理,需 bearer),判定 = 作品归属 + objectKey 命中该作品当前行的 `cover_object_key` / `screenshots_json`;AGC `resolve_preview_url` 与平台 web 的 `useGameDistributionMediaReadUrl({ gameId })`、`resolveGamePublishImagePreview(objectKey, gameId)` 都已切换。新增读路径时先确认它属于公开面还是 owner 面,别再让作者预览落到公开判定上。
|
||||
|
||||
## 2026-10-07 fnm/nvm 托管的 Node 进 bwrap:单文件挂载 npm 必失败,`--tmpfs /run` 会抹掉 multishell PATH
|
||||
|
||||
- **现象**:开发构建在 Linux 命令沙箱里执行 `npm run build` / `npm install` 时,宿主 shell 里明明能跑通的 fnm Node,进沙箱后报 `Node.js v26.10.0` 与 `Cannot find module '../lib/cli.js'`,或 npm 命令在路径解析阶段就失败。
|
||||
- **根因 1(单文件挂载软链前缀)**:fnm / nvm 的 `<前缀>/bin/npm` 是指向 `<前缀>/lib/node_modules/npm/bin/npm-cli.js` 的软链。bwrap `--ro-bind <前缀>/bin/npm <前缀>/bin/npm` 只挂载这一个文件,`npm-cli.js` 里的 `require('../lib/cli.js')` 找不到同安装内的相对目标,于是报错;必须整棵只读挂载通过窄叶校验的完整安装前缀(含 `bin/node`、`lib/node_modules/npm`),不能只挂 shim 或 `bin/`。
|
||||
- **根因 2(`--tmpfs /run` 抹掉活动版本)**:fnm 的活动 `PATH` 项是 `/run/user/<uid>/fnm_multishells/<pid>/bin`;sandbox 的 `--tmpfs /run` 会清空该目录,sandbox 内解析到的 `node` 随之失效或退回系统版本。不要依赖宿主 `PATH` 原样进入沙箱:canonicalize 路径,并把活动版本管理器变量(如 `FNM_MULTISHELL_PATH`)从 sandbox 环境里剔除。
|
||||
- **根因 3(错误取舍会打穿测试)**:把宿主 `node` / `npm` / `npx` shim 指到 `/usr/bin/*` 能让沙箱借用系统 Node,但在本机系统 Node 26 上会默认启用实验性 Web Storage,顶掉 vitest 0.34 jsdom 的 localStorage,AGC 测试在 HEAD 即红(见下方 2026-10-03「AGC 测试不在任何 tsconfig 里」条的环境提示)。正确方向是原生支持托管安装,而不是改宿主 shim。
|
||||
- **补充(nvm default 别名是主版本号)**:`nvm alias default 22` 写进 `$NVM_DIR/alias/default` 的内容是 `22`,不是完整三元组。若按精确 `(22,0,0)` 去匹配 `versions/node/v22.23.3` 会永远落空,默认别名形同不存在;必须用与 `.nvmrc` 相同的比较子集在已安装版本里选最高匹配。
|
||||
- **现行口径**:宿主发现与沙箱挂载共用 `validate_node_installation_prefix`;`.nvmrc` / `.node-version` 权威、`engines.node` 偏好;Linux npm 以 `node <npm-cli.js>` 启动并保留 `npm install` 联网判定。契约见技术方案 V1.11.2。
|
||||
- **验证**:`GENARRATIVE_COMMAND_SANDBOX_REAL_TEST=1` 跑 `command_sandbox_real_linux_opt_in_runs_host_node_and_npm_cli`,在真实 fnm v22 前缀下 bwrap 内 `node --version` 与 `node <npm-cli.js> --version` 均通过;单元用例覆盖 pin / engines / 不支持写法 / 宽叶与逃逸前缀 / 联网判定。
|
||||
|
||||
## 2026-10-07 cargo 目标目录里被"刷新 mtime"的陈旧 shared-contracts 会让编译报源文件里明明存在的字段缺失
|
||||
|
||||
- **现象**:`cargo check` 在 AGC shell 上报 `unresolved import shared_contracts::runtime::ProfileMembershipUpgradeQuoteResponse`、`no field project_version / publication`、`LlmModelsResponse: Deserialize` 等一整组「契约落后」错误;但 `server-rs/crates/shared-contracts/src` 里这些符号确实存在,`git status` 干净,刚重建的 rlib 也含符号。
|
||||
- **根因**:`target/debug/deps` 里留着早先构建的 `libshared_contracts-<hash>.rmeta`,其 `.d` 依赖文件停留在旧时间戳,而 `.rmeta` 的 mtime 在快照 / 拷贝过程中被刷新成新时间;cargo 按 mtime 判定该 crate 仍然新鲜,于是把 `--extern shared_contracts=` 指到旧 rmeta,下游就看到旧 API。多份不同 feature 组合的 `shared-contracts-<hash>` 并存时更容易踩中。
|
||||
- **处理**:删除该 crate 的全部指纹与产物后重编即可,不必清整棵 target:`rm -rf target/debug/.fingerprint/shared-contracts-* target/debug/deps/*shared_contracts*`,再跑 `cargo check`。判断依据是错误集中在某个 `shared-contracts` API,而源文件与 `git status` 都正常;先用 `cargo check -v 2>&1 | grep -m1 -- '--extern shared_contracts='` 找到实际使用的 rmeta,再核对它同名 `.d` 里的源文件路径与时间戳。
|
||||
- **边界**:这是构建缓存 / 快照产物问题,不是契约真源问题;不要因为这类报错去改 `shared-contracts` 或回退下游代码。
|
||||
|
||||
## 2026-10-05 PR #607 复核修复:档位点对齐/对比度、状态文案也走浮层、键盘去重、卸载 flush
|
||||
|
||||
- **档位圆点已删除(D1 的收口)**:这一轮把档位圆点**整体删除**(半透明备选方案未采用)。现在滑块只剩轨道 + 圆钮:轨道 6px 圆头、已选段 `--platform-accent` 由 `--strength-ratio` 驱动、**终点落在圆钮中心**(`calc(10px + ratio * (100% - 20px))`)、圆钮 20px 实心暖白(`--platform-panel-fill` + `--platform-subpanel-border` 1px 描边 + `color-mix` 柔影);强度区横向内边距 `4px 6px 2px` → `4px 0 2px`(滑块铺满卡片内容宽度,填充段与圆钮两端与轨道两端贴齐);相关 CSS(`space-between` 排布 / `z-index: 2` 抬层 / `.is-active{opacity:0}` / 点的 `color-mix` 底色 / 只为点对齐的 `padding: 0 7px`)与渲染标记一并删除。**判据**:`chatDialogFrameLayout.test.ts` 反向守卫(样式表里不再有 `.project-chat-composer-strength-stops` 规则、组件源码不再渲染该类名;滑块契约仍在:宽 100% / 高 26 / 圆钮 20×20、强度区左右内边距 0)+ `home.suite.ts` 首页菜单里查不到那组点。实测(447 视口,像素扫描):轨道 90..324(宽 234 = 卡片内容宽),档位 0 时圆钮左缘 90.5(距轨道左端 0.5px)、档位 4 时圆钮右缘 322.3(距右端 1.8px,扫描行不在圆钮正中所以略窄),填充段终点落在**圆钮中心**(`calc(10px + ratio * (100% - 20px))`,被圆钮盖住),因此圆钮右侧不会露出橙色(终点曾写成 `20px + …` = 圆钮右缘,4× 设备像素下能看到一小截溢出)。**历史成因(只留一句,细节由 Git 追溯)**:圆点此前被 6px 轨道盖住、且与圆钮两端错位 ±12.2px,曾用「抬到轨道之上 + space-between 对齐 + 浅暖色」修过一轮,最终整体删除。
|
||||
@@ -220,7 +245,7 @@
|
||||
|
||||
- **现象**:登录页密码输错(或密码长度不合规)后弹出「发现问题」,报告面板「错误事件(2)」列出 `密码长度需要在 6 到 128 位之间 — auth · 1 次` 与 `手机号或密码错误 — auth · 1 次`,默认全选,与 react-render / 5xx / agent-runtime 终态失败视觉等价。
|
||||
- **原因**:① 登录已下沉 Rust,`login_client_with_password` 等命令失败返回 `Err(String)`,Tauri 以**裸字符串**拒绝 `invoke`,前端拿不到任何类型信息;② `shouldCaptureClientError` 对非 object 值走默认 `return true`,`handleLoginSubmit` 的 catch 把预期业务拒绝报进了错误池。技术方案里"预期 4xx 登录/鉴权失败不进池"的口径早就成立,是错误通道的实现方式违背了它。
|
||||
- **处理(现行口径)**:命令错误一律按具体变体结构化(`Result<_, ClientAuthError>` + ts-rs 导出),认证命令统一经 `invokeClientAuth` 调用:结构化拒绝原样装进已有的 `ClientAuthErrorWrapper`(只有一个 `error` 字段,值是判别联合),UI 在 catch 里按具体变体分流——认得的业务 / 会话变体只给用户反馈,系统变体原样 `throw` 经 `unhandledrejection` 入池,非结构化拒绝原样抛出;删除 `shouldCaptureClientError`。详见 [`【ADR】AGC命令错误结构化与错误报告口径-2026-10-01`](../../adr/【ADR】AGC命令错误结构化与错误报告口径-2026-10-01.md) 与 [`【ADR】AGC认证失败的JS侧载体与抛出时机-2026-10-01`](../../adr/【ADR】AGC认证失败的JS侧载体与抛出时机-2026-10-01.md)。
|
||||
- **处理(现行口径)**:命令错误一律按具体变体结构化(`Result<_, ClientAuthError>` + ts-rs 导出),认证命令统一经 `invokeClientAuth` 调用:结构化拒绝原样装进已有的 `ClientAuthErrorWrapper`(只有一个 `error` 字段,值是判别联合),UI 在 catch 里按具体变体分流——认得的业务 / 会话变体只给用户反馈,系统变体原样 `throw` 经 `unhandledrejection` 入池,非结构化拒绝原样抛出;删除 `shouldCaptureClientError`。
|
||||
- **判据/取证**:`npx vitest run apps/ai-game-creator-shell/tests/authFailureReporting.test.tsx`——登录返回结构化业务变体时 `report_client_error` 不被调用;系统变体只上报一次(`source` 取全局 `unhandledrejection` handler 的显式入参;载体不再携带 `action`)。Rust 侧 `cargo test --locked --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml auth_error` 钉住变体 `type` 与 400/401/429/5xx/网络映射。
|
||||
- **形状约定**:`ClientAuthError` 顶层只放调用方要分流的类别,可枚举细分收进类型化枚举 `reason` 字段——无字段变体在 TS 里就是 `{ type: 'x' }`;带载荷变体是 `{ type: 'x' } & X`,`X` 由 ts-rs 导出到 `src/services/generated/X.ts`(Rust 侧是 newtype 变体持有同名结构体),细分原因枚举(`ServerAddressReason` / `AuthNetworkReason` / `AuthResponseInvalidReason`)同样由 ts-rs 生成。**不要手写这些类型**,也不要在前端再加一层分类 / 提示文案派生函数——判别一律写在 catch 子句里:`const failure = error.error; switch (failure.type)`,无字段 `case` 用本 catch 的固定文案,带载荷 `case` 先 `as X` 再读自己的字段,`reason` 是枚举时再 `switch (payload.reason)`,两处 `default` 都用 `expectNever` 保证漏接编译失败。改形状只能改 Rust 再跑 `cargo test` 重新导出,生成物保持 ts-rs 原始输出(不再经 prettier / eslint 二次改写,见本节「生成绑定」口径);ts-rs 只写文件、不删文件,变体从有载荷改成无字段时要手动清掉孤立的 `X.ts`(本次 `AuthResponseServerRejected.ts` 就是这样删的)。
|
||||
- **Rust 侧不得把结构化错误降级成字符串**:`refresh_session_inner` 的非权威失败直接返回 `Err(ClientAuthError)`,视图不带 `errorMessage`;一旦折成 `String`,前端就只能拿文案判断,变体信息永久丢失。
|
||||
@@ -6560,3 +6585,20 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/`
|
||||
- **CPU 边界**:发行网关是公开无鉴权端点,压缩按请求实时算。release 构建实测 level 6 为 1.3 MiB→10 ms、8 MiB→59 ms、64 MiB(单文件上限)→522 ms 纯 CPU,因此在 `RELEASE_COMPRESSION_FAST_ABOVE_BYTES`(2 MiB)以上改用 level 1(zlib 端实测 level 1 约为 level 6 的 1/3 耗时、压缩比只差约 3%)。若后续要再做减法,优先把压缩结果按 `(对象键, 资源路径)` 缓存,而不是放宽级别。
|
||||
- **验证**:`cargo test -p api-server --bin api-server -- game_distribution`(新增 ETag 作用域、`If-None-Match` 列表/弱校验命中、`gzip;q=0` 拒绝、文本压缩与二进制/小文件不压、304 无正文、`*` 对包内缺失路径仍 404 等用例);`npx vitest run packages/shared/src/components/PlatformGameLoadingSurface.test.tsx`、`src/components/game-distribution/GameDistributionPages.test.tsx`、`apps/admin-web/src/pages/AdminGameDistributionReviewPage.test.tsx`;`npm run check:game-distribution-ops-rollback-e2e` 47 项通过(含压缩/Vary/ETag/304/不接受 gzip 四条新断言)。真实栈同链路复跑:`phaser.min.js` 1,375,976 B → 353,336 B(gzip,4 Mbps 下 2724 ms → 774 ms),用户端游戏画面 3298 ms → 1543 ms、后台试玩 4587 ms → 2694 ms。
|
||||
- **关联**:`packages/shared/src/components/PlatformGameLoadingSurface.tsx`、`src/components/game-distribution/GamePlayPage.tsx`、`apps/admin-web/src/pages/AdminGameDistributionReviewPage.tsx`、`server-rs/crates/api-server/src/modules/game_distribution.rs`、`deploy/nginx/README.md`。
|
||||
|
||||
## 2026-10-07 cc 会话恢复只活在进程内存里,「离开项目再进入」就看不到历史对话
|
||||
|
||||
- **现象**:用户反馈「离开后再进入项目,cc 那边看不到历史对话」——聊天面板里的历史还在(`.agent/conversations/project.jsonl` 是 UI 的事实源),但模型完全不知道之前说过什么。
|
||||
- **根因**:`CLAUDE_DIRECT_SESSIONS` 是进程级 `Mutex<HashMap<PathBuf, String>>`,只负责把上一个回合的 `session_id` 交给 SDK 的 `resume`。用户离开项目再进入、重启客户端,或上一回合失败(失败路径根本不写这张表)之后,表里没有 id,cc 就以全新会话开工;而 SDK 自己的轨迹其实一直落在隔离 home 里:`<项目>/.agent/runtime/claude-code/home/claude/projects/<sanitized-cwd>/<sessionId>.jsonl`。现场:项目 `gameagent-0514673b` 的隔离 home 里留下两份独立轨迹(14:52 与 15:05),第二份的首次用户输入只有一句「现在呢」;`gameagent-754f4779` 更累积到 8 份。
|
||||
- **现行口径**:恢复顺序固定为「进程内会话表 → 本项目目录下 mtime 最新的会话轨迹」。目录名按 Claude Code 自己的派生规则算(cwd 里非字母数字一律换成 `-`,实例 `C:\Users\...\projects\gameagent-0514673b` → `C--Users-...-projects-gameagent-0514673b`),并且只认本项目目录——SDK 的 `resume` 也只在那里查这份轨迹,从别的目录捞来的 id 会被判成「No conversation found」,比不恢复更糟。日志 `agent.direct_codex.claude_resume source=memory|disk|none` 记录本回合的恢复来源,用户再报「看不到历史对话」时可以一眼分辨是内存命中、磁盘回放,还是本项目确实没有历史会话。
|
||||
- **验证**:`cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml --features=cocos-editor-execute,unity-editor-execute,godot-editor-execute --bin genarrative-ai-game-creator-shell claude_` 34 passed(新增 `claude_code_session_lookup_uses_the_project_directory_and_newest_trace`、`claude_direct_resume_falls_back_to_disk_and_prefers_the_process_map`)。真实环境用随包 `claude.exe`(2.1.285)复验:把项目隔离 home 的 `projects/` 拷进临时 `CLAUDE_CONFIG_DIR`、cwd 设为项目根,`--resume 00000000-…` 立刻回 `No conversation found with session ID`;`--resume f79dae28-…`(磁盘上最新那份)不报找不到,直接进入模型请求,落在本地抓包桩上的请求体里带着完整历史(`messages` 13 条,首条就是 14:23 的「做个废土风的扫雷…」原文)。
|
||||
- **关联**:`apps/ai-game-creator-shell/src-tauri/src/agent/claude_code_cli.rs`。
|
||||
|
||||
## 2026-10-07 cc 回合超时后宿主不重试,只能用户手动点「重试」
|
||||
|
||||
- **现象**:用户截图 `等待模型回执超时(已尝试 1 次),本轮未完成;请稍后重试:Claude Agent SDK sidecar 回合超时:连续 180000 ms 没有任何事件(已收到 3 个事件)`——一轮卡满 3 分钟就判失败,用户只能自己点「重试」,而那次手动重试 58 秒就成功了;同一项目当天 15:07 又原样复现一次(15:10 失败)。
|
||||
- **根因**:cc 执行器一轮只跑一次 sidecar,超时就直接判回合失败;只有 Codex 那条路径带重试意识(`ModelCallKind::ResponseTimedOut { attempts }` 的 attempts 在 cc 侧被硬编码成 `1`)。挂住的是平台网关(一个字节都不回),不是模型拒绝——重放本来是有意义的。
|
||||
- **边界证据(真实二进制)**:把随包 `claude.exe`(2.1.285)指向一个"收下请求就不回任何字节"的本地桩,默认配置和显式 `CLAUDE_ENABLE_BYTE_WATCHDOG=1` + `CLAUDE_BYTE_STREAM_IDLE_TIMEOUT_MS=60000` + `CLAUDE_STREAM_IDLE_TIMEOUT_MS=60000` + `CLAUDE_CODE_MAX_RETRIES=2` 两种配置下,300 秒(第二组 180 秒)内都只有 `system/init` 一条事件、桩上也没有第二次请求,**没有任何重试或报错**。所以"超时后重试"这件事只能由宿主做,不能指望 CLI 或那些 watchdog 环境变量自己兜。
|
||||
- **现行口径**:`direct_game_creator_claude_code_chat_at` 改成一次用户回合最多 `CLAUDE_DIRECT_TURN_MAX_ATTEMPTS = 3` 次尝试(第一版:2 次自动重试),**只有两个条件同时成立才自动重放**:①失败是"静默超时"(`连续 N ms 没有任何事件`;45 分钟硬上限不重放,否则一次回合能被拖到两小时以上);②这一轮从头到尾**没有请求过任何工具**——模型一旦发过 `tool_use`,重跑就可能把付费生成 / 构建 / 试玩再执行一遍(哪怕结果还没回来,操作也可能已经在途),这时改为落一条"本轮已经请求过工具,不会自动重放;请手动重试"的过程行。每次重试前先落一条 `role:"system"` 过程行(`等待模型回执超时,正在自动重试(第 2/3 次)`),重试同样走 `resume`,历史与上下文不丢。最终失败时的 `已尝试 N 次` 由宿主回填:详情尾部的 `;本轮已自动重试 N 次` 只是结构化传递标记,分类器会把它摘掉,不落到用户可见详情里。
|
||||
- **验证**:`cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml --features=cocos-editor-execute,unity-editor-execute,godot-editor-execute --bin genarrative-ai-game-creator-shell claude_` 37 passed。新增三条:`claude_direct_retry_policy_retries_only_safe_timeouts`(逐格钉住重试/停下的四种决定)、`claude_direct_timeout_is_only_auto_replayed_without_tool_requests`(硬上限不重放、请求过工具不重放)、`claude_direct_retry_attempts_reach_the_structured_error_without_leaking`(attempts 结构化,标记不落到用户可见详情)。
|
||||
- **关联**:`apps/ai-game-creator-shell/src-tauri/src/agent/claude_code_cli.rs`。
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
- AGC 模板库灰度复用 `agc:template-library`:未配置关闭,已配置时遵循现有灰度启停、用户 ID/标签和比例规则;服务端返回权威结论,客户端入口和原生清单/下载/建项均执行门禁,主体切换丢弃旧异步结果。公开 OSS 不是保密边界,已创建项目不受影响。
|
||||
|
||||
- 画布卡片类型与信息角标共用 `CanvasCardCornerActions`;菜单收纳共用 `OverflowActions`,宿主决定展示数量和资源命令。AGC 选中菜单前 5 项直显,Web 默认不折叠;浮层 portal 继续接入现有画布关闭与滚轮归属判据。
|
||||
- 断言只写在能失败的地方:`getBy*` / `findBy*` 找不到就抛,不要再包一层 `expect(...).not.toBeNull()` / `toBeDefined()`(那种写法恒真,还让每条查询多一层噪声);等元素出现用 `findBy*` 或 `await waitFor(() => screen.getBy*())`,负向用 `queryBy*` + `toBeNull()`。同理不写复述实现的断言(例如把被测函数的表达式在测试里再抄一遍),改为钉关系、边界或行为,并能逐条反证会红。
|
||||
- 修改范围保持聚焦;优先扩展现有系统、页面、组件、DTO 和脚本。
|
||||
- Agent 可见内容直接描述当前任务、输入和成功条件,细节按调用需要提供。
|
||||
- AGC 外置智能体提示词只写模型必须遵守的指令与契约(工具名与参数、调用顺序、禁止项、失败处理、产物要求),不写客户端/宿主怎么实现:内部预算字段与限额、内部状态机与枚举、执行器锁与沙箱技术栈、进程/插件部署、注入与投影管线都属于宿主实现,一律改写成对应行为要求或删除。
|
||||
@@ -52,7 +53,7 @@
|
||||
- 后端遵循 `module-*`、`spacetime-module`、`spacetime-client`、`api-server`、`platform-*`、`shared-contracts` 的现役边界。
|
||||
- 前端只负责表现、交互和临时 UI 状态;正式状态来自后端投影、API 或持久化契约。
|
||||
- AGC 渲染层是离线前端:平台接口、OSS 素材直传、Provider、错误上报与账户/认证的网络 IO 全部在 Rust 的 reqwest facade,渲染层不得再出现 `fetch(` / `XMLHttpRequest` / `EventSource` / `sendBeacon(` / `new WebSocket(` 或 `@tauri-apps/plugin-http`;客户端窗口 capability 也不得授予 `http:default`。`check:native-shells --groups=contract` 的 `ai-game-creator-shell-user-dev-boundary` 用负向门禁钉住这条,改动渲染层网络边界必须同批改该门禁与 `build-release.test.mjs`。
|
||||
- AGC 工作区的正式状态变化走 Rust 事件 + 一次受控快照读取,不建立固定频率轮询(纯 UI 计时器、拖拽重复器与动画 tick 除外):渲染层先订阅再读快照,事件重复或内容未变时保持数组身份,卸载后迟到事件不写回。Direct 活动回合的唯一事实源是 Direct 线程管理器的活动回合快照(`list_direct_active_turns` 只读它;登记、进度内容变化与收口各广播一次 `game-creator-direct-active-turns-changed`),渲染层与 Rust 都不得再引第二份注册表。
|
||||
- AGC 工作区的正式状态变化走 Rust 事件 + 一次受控快照读取,不建立固定频率轮询(纯 UI 计时器、拖拽重复器与动画 tick 除外;导出面板对 `.export/` 注册表与 npm 脚本的 2 秒重读是显式例外——那两件事实由用户项目和 agent 直接改文件,没有事件源,只能现读):渲染层先订阅再读快照,事件重复或内容未变时保持数组身份,卸载后迟到事件不写回。Direct 活动回合的唯一事实源是 Direct 线程管理器的活动回合快照(`list_direct_active_turns` 只读它;登记、进度内容变化与收口各广播一次 `game-creator-direct-active-turns-changed`),渲染层与 Rust 都不得再引第二份注册表。
|
||||
- AGC 维护态(平台 `503` 且命中 `MAINTENANCE` 或「维护」)由 Rust 在平台请求的错误分支经 `platform_maintenance::watch_platform_response` 分类并广播 `genarrative-client-maintenance-detected`;渲染层只订阅并打开唯一的「系统维护中」弹窗,不自行解析 HTTP,业务面板各自的错误文案保持不变。
|
||||
- 对已明确退役且无现役调用方、公开契约、持久化数据、活跃实例或迁移要求的对象,直接清理实现、专属测试和说明,将权威文档更新为当前状态;历史由 Git 保存。公开契约、持久化数据和正式迁移按实际需求保留最小兼容及对应测试。
|
||||
- 修改 `/api/external/v1` 时,同批更新 `docs/openapi/genarrative-external-v1.openapi.json` 与契约测试。
|
||||
|
||||
Reference in New Issue
Block a user