导出的文档按 ADR 收口回写:失败通道、输出尾部事实与门禁口径

- decision-log 新增一条:同步细节不进对外状态(内容与指纹两条命令)、自动重读取代刷新按钮、失败通道按 ADR 两道口子分流、宿主不预拼用户可见文案、契约常量以 Rust 为唯一权威并用跨语言测试钉住;顺带修掉上一条里「只有 3 个 per-target 命令」的过时说法
- 主规范导出一节补失败通道与文案归属;README 的状态从「未实现」改成已实现待运行时验收
- 里程碑与实施计划的证据口径同步:export:: 50 例(xhs_minitool 34 例)、export_bindings 116 例、生成文件 118、前端 25 例(19 + 契约 2 + 既有 4);记下本机 check:rust:shell 唯一失败是 4 个 process_session 真 PTY 用例(既有环境噪声),并补上 dffe4de55 / df3a6826b 两条提交
This commit is contained in:
2026-10-05 19:25:56 +08:00
parent df3a6826b1
commit 9a3e211205
5 changed files with 28 additions and 11 deletions
+1 -1
View File
@@ -32,7 +32,7 @@
## AI 游戏创作与 Agent Runtime
- [导出产物面板与小红书小工具导出](./technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md#2026-10-05-导出产物面板与小红书小工具导出)、[里程碑](./project-memory/plans/【里程碑】导出产物面板与小红书小工具导出-2026-10-05.md)与[实施计划](./project-memory/plans/【实施计划】导出产物面板与小红书小工具导出-2026-10-05.md):宿主只校验并运行项目内 `build:xhs-minitool`,适配由 code agent 首次实验固化;`.export/` flat 工作目录、内容 hash 冲突逐字段选择;未实现。
- [导出产物面板与小红书小工具导出](./technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md#2026-10-05-导出产物面板与小红书小工具导出)、[里程碑](./project-memory/plans/【里程碑】导出产物面板与小红书小工具导出-2026-10-05.md)与[实施计划](./project-memory/plans/【实施计划】导出产物面板与小红书小工具导出-2026-10-05.md):宿主只校验并运行项目内 `build:xhs-minitool`,适配由 code agent 首次实验固化;`.export/` flat 工作目录、内容 hash 冲突逐字段选择;失败按 typed 变体分流(策略拒绝留面板、宿主故障与未分类拒绝原样抛出进错误池)。已实现并通过本地定向验证,真实 vite 项目上的首轮适配与二次导出待运行时验收。
- [客户端本地埋点与主站入库契约](./technical/【技术方案】客户端本地埋点与主站入库契约-2026-09-21.md):本地 12 类事件采集、每 15 分钟上传、私有事件表、确认后清理与后台明细查询已完成隔离环境验收;不扩充采集范围、不做加密,未部署生产。配置要求及验证边界见第 13 节。
@@ -10,9 +10,9 @@
- Rust 模块:`src-tauri/src/export/mod.rs`(`EXPORT_WORK_RELATIVE_DIR` 与共用注册表读写、规范序列化 hash 助手)、`src-tauri/src/export/draft/mod.rs`、`src-tauri/src/export/draft/xhs_minitool/mod.rs`(表单 / 状态 / 结果 / 错误 DTO 与常量)、`src-tauri/src/export/draft/xhs_minitool/commands.rs`(小红书专属 `#[tauri::command]`)。
- 现存草稿的归位:`export/command/`(空 `mod.rs`)是上一轮「共享命令目录」方案的残留,最终落点是 per-target `commands.rs`,实现时删除该目录与 `mod.rs` 里的 `pub mod command;`。同一轮把草稿常量改名落定:`xhx-minitool.zip` → `xhs-minitool.zip`、`xhx-minitool.json` → `xhs-minitool.json`、`build-xhx-minitool` → `build:xhs-minitool`;表单字段随 `#[serde(rename_all = "camelCase")]` 在 JSON / TS 侧为 `iconPath`(Rust 侧仍是 `icon_path`)。
- Rust 命令(4 个,全部 per-target):`read_xhs_minitool_export` → `XHSMiniToolExportState { form, hasScript }`(内容,自动刷新反复调);`read_xhs_minitool_export_hash` → 注册表指纹字符串(写回时的 `baseHash`,只在内容被采纳时取一次);`save_xhs_minitool_export_form(project_path, form, base_hash)` → 写回后的新指纹;`run_xhs_minitool_export_build(project_path)` → `XHSMiniToolExportRunResult { outputTail }`(构建输出尾部,stdout 与 stderr 已合并;成功出口的退出码必然是 0,因此不带 `exitCode`,失败侧由 `CommandFailed.exitCode` 承担)。失败走 typed error `XHSMiniToolExportError`(ts-rs 导出);不新增 `GAME_CREATION_APP_COMMANDS` 条目,复用 `command.exec` 的 `enforce_project_permission_policy`。内容与指纹分两条命令是刻意的:刷新这条高频路径在类型上就动不了写回基线。
- Rust 命令(4 个,全部 per-target):`read_xhs_minitool_export` → `XHSMiniToolExportState { form, hasScript }`(内容,自动刷新反复调);`read_xhs_minitool_export_hash` → 注册表指纹字符串(写回时的 `baseHash`,只在内容被采纳时取一次);`save_xhs_minitool_export_form(project_path, form, base_hash)` → 写回后的新指纹;`run_xhs_minitool_export_build(project_path)` → `XHSMiniToolExportRunResult { outputTail, omittedCharacters }`(构建输出尾部**原文**与省略量两个事实,stdio 已合并;成功出口的退出码必然是 0,因此不带 `exitCode`,失败侧由 `CommandFailed.exitCode` 承担)。失败走 typed error `XHSMiniToolExportError`(ts-rs 导出):`buildScriptMissing` / `formInvalid` / `registryMalformed` / `saveConflict` / `commandDenied` / `commandFailed` / `artifactMissing` / `exportUnavailable`,前七类是预期拒绝,`exportUnavailable` 是宿主侧事实故障——两者在调用方要做的判断不同(留在面板 vs 原样抛出进错误池)。不新增 `GAME_CREATION_APP_COMMANDS` 条目,走 `command.exec` 的 typed `enforce_project_permission_policy_rejection`(不用那版拼好人话的 `enforce_project_permission_policy`,否则调用方分不出「策略拒绝」与「策略读不出来」)。内容与指纹分两条命令是刻意的:刷新这条高频路径在类型上就动不了写回基线。
- 复用而非新造:运行脚本走 `command_exec` 的 `resolve_project_command_spec_at → prepare/stage/spawn`(或 `run_project_verification_with_commit_at`),cwd 沿用 `project/export.rs` 的 `resolve_publish_build_cwd` 口径;icon 与 zip 下载复用 `save_local_project_asset_file`;不拿项目写锁、不推进 revision。
- 前端:`src/view/project-development/export/tabs/xiaohongshu/ArtifactsPane.tsx`(表单 + 产物 + 冲突/失败卡片,只有「适配 / 导出」两颗显式按钮)、新增 `state/useXhsMinitoolExport.ts`(状态、防抖自动保存、按固定间隔自动重读内容;指纹只在 hook 内部的写回基线里,不进对外状态)、新增 `state/xhsMinitoolInstruction.ts`(适配与修复指令的纯函数 + 契约常量)、新增 `state/{xhsMinitoolApi,xhsMinitoolFailure,xhsMinitoolFields,xhsMinitoolDownload}.ts`、新增 `src/view/project-development/export/generated/`(ts-rs 产物)。
- 前端:`src/view/project-development/export/tabs/xiaohongshu/ArtifactsPane.tsx`(表单 + 产物 + 冲突/失败卡片,只有「适配 / 导出」两颗显式按钮)、新增 `state/useXhsMinitoolExport.ts`(状态、防抖自动保存、按固定间隔自动重读内容;指纹只在 hook 内部的写回基线里,不进对外状态)、新增 `state/xhsMinitoolInstruction.ts`(适配与修复指令的纯函数 + 契约常量)、新增 `state/{xhsMinitoolApi,xhsMinitoolFailure,xhsMinitoolFields,xhsMinitoolDownload,xhsMinitoolOutputTail}.ts`、新增 `src/view/project-development/export/generated/`(ts-rs 产物)。宿主不预拼用户可见文案:输出尾部的「已省略前 N 个字符」与策略拒绝那句话都在前端拼(`xhsMinitoolOutputTail.ts` / `xhsMinitoolFailure.ts`)。失败分流按 ADR 两道口子:预期拒绝留在面板,宿主侧事实故障与认不出形状的拒绝先给现场再原样抛出(按指纹去重,面板 2 秒一轮重读不会把报告池的 `count` 刷成轮询次数)。
- 快照排除:`src-tauri/src/project/filesystem.rs` 新增 `PROJECT_SNAPSHOT_SYNC_ONLY_EXCLUDED_COMPONENTS = [".export"]`;**不能**并进通用排除列表(`agent/direct_patch.rs` 直接拿它拒绝路径,`agc_apply_patch` 会拒 `.export/`,首次适配无法落地)。
- skill:`resources/agc-skills/vite-export-xhs-minitool/scripts/pack.mjs`(新增 `--zip-out <path>`,相对 cwd 解析、父目录自动创建;原 `--zip <name>` 行为不变)、`scripts/vite.config.xhs-minitool.mjs` 注释点明 `outDir` 与 `--out-dir` 必须一致、`SKILL.md` 补「被 AGC 导出面板调用时」三条硬契约;同步 `manifest.json` 指纹与 `skill_pack.rs` 的 `include_bytes!` 内容(文件数不变,仍 36 条)。
- 文档:主规范一节、本里程碑与实施计划、`docs/README.md` 入口、`docs/project-memory/shared-memory/decision-log.md` 一条。
@@ -58,9 +58,11 @@
- `5e2426c8a` 导出收尾判据抽成 `conclude(...)` 并补 5 例确定性用例(非零退出 / 超时 / 产物缺失 / 空产物 / 成功),同时去掉 `XHSMiniToolExportRunResult.exitCode` 这个恒为 0 的死字段;`463341c4b` 导出成功后把脚本输出尾部报给用户;`b3be3ee71` 证据口径修正;`5dd158ee1` 补默认 ignore 的真实起进程用例。
- `1b7741ac6` 适配与修复指令补上「包根在 game/ 时落点写成 `../.export/xhs-minitool.zip`」,skill 契约同步(manifest `2026-08-26.42`);`f8ed4e898` 保存对话框的默认文件名收进下载助手并复用既有取文件名工具;`90f651ddf` 刷新时先撤掉待触发的自动保存(附回归用例;该行为已被下面的自动刷新取代——轮询不再撤掉待保存的输入,因为「磁盘为准」的刷新动作没有了)。
- `ac666daad` 之后:未适配提示改成带入队按钮的卡片、失败卡片按变体分派适配/修/重试、提示只在注册表读回来之后出现(同一时刻只有一张卡片、一个入口)。
- `dffe4de55` 导出链路按 ADR 收口(策略拒绝与宿主故障拆成两个变体、输出尾部只回原文与省略量);`df3a6826b` 前端失败通道按 ADR 分流(宿主故障与未分类拒绝原样抛出并去重)与复制失败可见反馈。
- 复制按钮改用 Tauri 剪贴板插件(原 `navigator.clipboard` 在 WebView 里可能根本没有,失败还会静默吞掉),失败态在按钮上可见。
- 指令正文里的契约字符串全部从常量组合(`XHS_MINITOOL_EXPORT_DIR_RELATIVE_PATH` + 三个脚本落点),用例的期望值也从常量推;`9d3018a49` / `6dd0dc00c` 之后:去掉刷新按钮,改成按固定间隔自动重读内容;内容与注册表指纹拆成两条命令(自动刷新拿不到写回基线);同步细节(指纹、保存中、待保存)退出前端对外状态与界面。
已验证:Rust `export::` 49 例(`export::draft::xhs_minitool` 33 例)、`export_bindings` 115 例、`project_snapshot` 22 例、`check:generated-bindings`(117 个文件)、前端 vitest 21 例(新增 15 + 契约 2 + 既有 4)、`ai-game-creator-shell:typecheck`、`agc:skill-pack:check`、`check:doc-index`、`check:encoding`、`git diff --check`。
已验证:Rust `export::` 50 例(`export::draft::xhs_minitool` 34 例)、`export_bindings` 116 例、`project_snapshot` 22 例、`check:generated-bindings`(118 个文件)、前端 vitest 25 例(新增 19 + 契约 2 + 既有 4)、`ai-game-creator-shell:typecheck`、`ai-game-creator-shell:check:rust:shell`(本机 4 个 shard 都跑到了底,唯一失败是 4 个 `process_session` 真 PTY 用例——与下面的已知环境噪声同一批:沙箱里的 npm 由另一个 node 版本执行、`Cannot find module '../lib/cli.js'`;本次改动涉及的 `export::` 与 `project_snapshot` 全绿)、`agc:skill-pack:check`、`check:doc-index`、`check:encoding`、`git diff --check`。
未验证(唯一开口项):真实 vite 项目上的首轮适配闭环——agent 跑通 `build:xhs-minitool`、产出可上传 zip、二次导出零 agent 调用;以及 `.export/` 换机后重新适配的体感。宿主侧这一段已尽力自动化:`build.rs` 的 `executes_the_project_script_and_requires_a_non_empty_artifact_each_time`(`#[ignore]`)会真起进程执行项目脚本,覆盖「第一次产出 → 删掉产物再跑一次重新产出 → 脚本成功但没产出报 artifactMissing」。本机尝试运行被容器环境挡下,读日志时注意两点、别误判成 bug:
- 子进程输出里的 `running 1 test` 是**预期现象**:命令沙箱的垫片就是当前可执行文件本身(`desktop.rs` 按 `--command-sandbox-trampoline` 分流),编译到测试态时 `sandbox_trampoline_arguments()` 会改成 `--exact ...trampoline_child_fixture --ignored`,所以垫片进程是测试二进制在跑那一个 fixture 用例。
@@ -39,19 +39,20 @@
- [x] 首次打开小红书 tab,`.export/xhs-minitool.json` 自动生成且 `name` / `introduction` / `iconPath` 全为空字符串;注册表损坏或字段不合法时给字段级错误,不静默吞掉。
- [x] `name` / `introduction` 为空或超 14 个 Unicode 字符、icon 不存在/越界/符号链接/扩展名不在 `png|jpg|jpeg`/超 5 MiB 时被拒,首个错误即返回。
- [x] `hasScript` 只由 npm 包 `package.json` 的 `scripts["build:xhs-minitool"]` 现算;注册表内没有 status、没有「已适配」、没有经验文本字段。
- [x] 点「导出」每次都运行脚本并重新产出 `.export/xhs-minitool.zip`;脚本缺失、运行非零退出(带 exit code 与输出尾部,stdout 与 stderr 已合并)、产物缺失各有独立 rich error。
- [x] 点「导出」每次都运行脚本并重新产出 `.export/xhs-minitool.zip`;脚本缺失、运行非零退出(带 exit code 与输出尾部,stdout 与 stderr 已合并)、产物缺失各有独立 rich error。输出尾部只装原文与省略量两个事实,漏掉多少字那句话由前端拼。
- [x] 表单字段可复制;icon 与 zip 经原生保存对话框落盘;复制与下载不触发任何 agent 调用。
- [x] agent 改过注册表后,用户保存旧值触发字段级冲突面板,可逐字段选「我的 / 文件里的」,纯格式化改动静默吸收,任何情况下不静默覆盖用户输入。
- [x] 一键适配只入队一条指令且必须过会话写权限门;被拒时不落消息、不改注册表。
- [x] 全链路不自动跑构建、不自动叫 agent(内容与脚本判据按固定间隔自动重读);`.export/` 不进发布包、不进快照。
- [x] 失败按 ADR 的 typed 变体分流:权限策略拒绝(`commandDenied`)留在面板并给固定话术;宿主侧事实故障与认不出形状的拒绝先给现场、再原样抛出进错误池;面板每 2 秒重读,同一个故障只抛一次。
- [x] ts-rs 绑定生成到前端目录且 `npm run check:generated-bindings` 通过。
## 证据要求
- 自动化(已落):
- Rust `cargo test ... --bin genarrative-ai-game-creator-shell export::` 49 例,其中 `export::draft::xhs_minitool` 33 例:注册表自动建空表单、格式漂移静默吸收、严格解析拒绝、表单首错、icon 越界/符号链接/扩展名、`hasScript` 现算、`contentHash` 冲突、产物路径守卫,以及构建收尾判据的逐条映射(脚本缺失、非零退出带退出码与输出尾部、超时无退出码、产物缺失、空产物不算、成功回输出尾部)。
- `export_bindings` 115 例 + `npm run check:generated-bindings`(117 个生成文件)通过。
- 前端 `npx vitest run tests/xhsMinitoolExport.test.tsx`(15 例)、`tests/xhsMinitoolContract.test.ts`(2 例,把前端契约常量的值逐条对到 Rust `layout.rs` / `export/mod.rs`)与既有 `tests/artifactsPanel.test.tsx`(4 例)通过;`npm run ai-game-creator-shell:typecheck` 通过。其中 15 例覆盖(期望值一律从契约常量推,不另抄字面量):指令正文三条硬契约与 game/ 落点、失败现场拼装、失败变体人话映射与三向 action 分派(适配 / 修 / 重试)、未适配卡片自带入队按钮且按钮行不重复、注册表读回来之前不下「还没适配」结论、表单复制、icon 与 zip 下载、冲突逐字段二选一、自动保存带回读到的 `baseHash`、自动重读只更新脚本判据而不动用户未保存的表单、agent 事后改注册表/加脚本不用点刷新就能带回来、导出把脚本输出尾部报给用户、以及「只有适配/修复才叫 agent」。
- Rust `cargo test ... --bin genarrative-ai-game-creator-shell export::` 50 例,其中 `export::draft::xhs_minitool` 34 例:注册表自动建空表单、格式漂移静默吸收、严格解析拒绝、表单首错、icon 越界/符号链接/扩展名、`hasScript` 现算、`contentHash` 冲突、产物路径守卫,以及构建收尾判据的逐条映射(脚本缺失、非零退出带退出码与输出尾部、超时无退出码、产物缺失、空产物不算、成功回输出尾部、截断只回省略量不回句子)。
- `export_bindings` 116 例 + `npm run check:generated-bindings`(118 个生成文件)通过。
- 前端 `npx vitest run tests/xhsMinitoolExport.test.tsx`(19 例)、`tests/xhsMinitoolContract.test.ts`(2 例,把前端契约常量的值逐条对到 Rust `layout.rs` / `export/mod.rs`)与既有 `tests/artifactsPanel.test.tsx`(4 例)通过;`npm run ai-game-creator-shell:typecheck` 通过。其中 19 例覆盖(期望值一律从契约常量推,不另抄字面量):指令正文三条硬契约与 game/ 落点、失败现场拼装、失败变体人话映射与三向 action 分派(适配 / 修 / 重试)、未适配卡片自带入队按钮且按钮行不重复、注册表读回来之前不下「还没适配」结论、表单复制、icon 与 zip 下载、冲突逐字段二选一、自动保存带回读到的 `baseHash`、自动重读只更新脚本判据而不动用户未保存的表单、agent 事后改注册表/加脚本不用点刷新就能带回来、导出把脚本输出尾部报给用户、失败变体的产出量与前端拼出的省略说明、认不出形状的拒绝原样抛出且不落成业务提示、宿主侧事实故障先给卡片再抛一次且同一故障只抛一次,复制失败在按钮上可见(不静默吞掉),以及「只有适配/修复才叫 agent」。
- `.export/` 快照排除有定向用例:`project_snapshot::tests::project_snapshot_sync_policy_keeps_agent_state_and_still_blocks_credentials` 同时钉住「同步排除」与「通用口径不排除(Agent 仍可写)」。
- `npm run agc:skill-pack:sync` → `agc:skill-pack:check` 通过(`manifest.json` 版本 `2026-08-26.42`,文件数不变,仍 36 条)。
- 运行时(**未验证**,本里程碑唯一开口项):在真实 vite 项目上由 agent 完成首轮适配、产出可人工上传的 zip、二次导出零 agent 调用;`.export/` 换机后需重新适配的体感也需一并确认。宿主侧这一段已有默认 `#[ignore]` 的真起进程用例(`executes_the_project_script_and_requires_a_non_empty_artifact_each_time`:第一次产出 → 删掉产物重跑必须重新产出 → 脚本成功但没产出报 artifactMissing),一键复跑命令见实施计划;本次尝试被容器环境的 npm/沙箱解析挡下,不是代码问题。
@@ -9,11 +9,24 @@
- 决策(工作目录):`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 侧因此只有 3 个 per-target 命令(读状态 / 存表单 / 跑构建),typed error 首个错误即返回,不带问题数组。
- 决策(prompt 归属):首次适配与失败修复的指令正文、契约常量与组装都放前端(`view/project-development/export/state/xhsMinitoolInstruction.ts` 纯函数;失败说法在 `xhsMinitoolFailure.ts`),宿主不提供 `enqueue_*` 命令;前端经 `useDirectProjectChatController` 的 `chat.submit` 入队,复用它既有的会话写权限门与 clientTurnId。Rust 侧因此只有 per-target 的四个命令(读内容 / 读指纹 / 存表单 / 跑构建),typed error 首个错误即返回,不带问题数组;内容与指纹为什么分成两条见下面 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([`【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)、[`【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` 是宿主现算事实,任何时刻都采纳;表单只在用户手上没有未保存输入(也不在冲突/字段错误里)时才覆盖,否则绝不覆盖正在打的字。代价是「刷新会取消未保存输入」这条旧行为被有意作废,对应的回归用例同步替换。
- 决策(失败通道):预期拒绝留在面板(一句话按变体写死 + 宿主现场),**宿主侧事实故障与认不出形状的拒绝先给现场再原样抛出**,由全局 `unhandledrejection` 进错误池——面板不再把裸 message 当成业务失败的解释糊给用户,也不再静默吞掉真故障。抛出按指纹去重:面板每 2 秒重读,同一个故障只抛一次、变了再抛,否则报告池的 `count` 统计的是「面板开了多久」。策略拒绝(`commandDenied`)与宿主故障(`exportUnavailable`)必须是两个变体:`enforce_project_permission_policy` 那版把「策略拒绝」与「策略读不出来」拼成同一句人话,调用方无从判该不该上报,导出链路因此改用 typed 的 `enforce_project_permission_policy_rejection`。
- 决策(文案归属的延伸):宿主不预拼用户可见文案这条也管载荷里的说明——构建输出尾部只回 `outputTail` + `omittedCharacters` 两个事实,「已省略前 N 个字符」由前端拼(`export/state/xhsMinitoolOutputTail.ts`,失败卡片与成功提示共用)。与上面那条 prompt 归属同源:机器事实在 Rust,句子在前端。
- 刻意保留的差异:`xhsMinitoolFailure.ts` 这个「变体 → 一句话 + 下一步按钮」的纯映射没有按 ADR「判定只写在 catch 里、不抽 presenter」内联进 catch。那条规则的落点是只有一个消费方的认证 catch;这里有两个消费方(面板卡片与交给 agent 的修复指令),必须说同一句话,否则用户在面板里看到的和 agent 拿到的会分叉。它不判「要不要上报」——上报判据在 hook 的 catch 里。
- 契约常量的单一来源: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/{CommandDenied,CommandFailed,XHSMiniToolExportRunResult,XHSMiniToolExportError}.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`),旧清单工具会走目录把它们当成必须声明的审核文件,指纹、安装与只读读取也无法区分「审核正文」和「本地辅助」。
@@ -10,11 +10,12 @@
- 构建契约:项目 npm 包里存在脚本 `build:xhs-minitool`(冒号形式是既有 `command.exec` 白名单 `build:*` 的要求),由宿主负责运行;跑完必须存在 `.export/xhs-minitool.zip`。脚本内部怎么 `vite build`、怎么调 `pack.mjs`、用什么中间目录由 agent 决定并写进脚本;**每次导出都重新构建 zip**,不做「产物已存在」的短路。
- 首次适配:用户点显式按钮 → 前端组装适配指令(正文在 `view/project-development/export/state/xhsMinitoolInstruction.ts`,纯函数 + 契约常量)→ 经 `useDirectProjectChatController` 的 `chat.submit` 入队(该链路自带会话写权限门与 clientTurnId),进的就是同一个项目主 Direct 会话,不另开「直接调 agent」的旁路。不自动跑构建、不自动叫 agent;内容与脚本判据按固定间隔(2 秒,窗口在后台时跳过)自动重读,所以没有刷新按钮——agent 回合结束后用户只需要点「导出」。重读时两条事实分开处理:`hasScript` 是宿主的现算事实,任何时刻都采纳;表单是用户的编辑对象,只有用户手上没有未保存输入(也不在冲突里)时才覆盖,否则只更新 `hasScript`,绝不覆盖正在打的字。没有构建脚本时,「还没适配」的结论只由注册表读回来之后下(`hasScript` 初值是 `false`,读取中不下结论),提示卡片自带那颗入队按钮,按钮行里就不再重复一颗;失败卡片按变体的 `action` 分派三颗按钮——`adapt` 发首次适配指令(还没适配,修复指令会引用一份还不存在的适配说明)、`repair` 发同一份契约加宿主失败现场、`retry` 只重试,同一时刻只有一张卡片、一个入口。
- 命令切分:`read_xhs_minitool_export` 只返回**内容**(`form` + `hasScript`),自动刷新反复调的就是这一条;**注册表指纹**(写回时的 `baseHash`)由 `read_xhs_minitool_export_hash` 单独给,只在进编辑会话、或重读到的新内容真的被采纳时取一次。两者分开是刻意的:刷新这条高频路径在类型上就动不了写回基线,否则「顺手刷新」会把两次刷新之间的外部改动认成自己的基线、把冲突吞掉。指纹是同步细节,不进前端对外状态(hook 里只在 `baselineRef`),界面也不显示任何同步状态(没有「保存中」「待保存」文案)。`save_xhs_minitool_export_form` 成功后回新的指纹。
- 失败通道:命令失败是 typed 变体(`XHSMiniToolExportError`,ts-rs 导出到 `export/generated/`),前端只按 `type` 分流、`expectNever` 钉死穷尽性,**不对任何文案做判断**,也不把裸 message 当成业务失败的解释。分流沿用 [`【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) 的两道口子:预期拒绝(表单不合法、脚本缺失、冲突、权限策略拒绝 `commandDenied`、脚本非零退出、产物不在、注册表坏)留在面板里就地消化,一句话按变体写死、宿主现场照原样展示;**宿主侧事实故障(`exportUnavailable`)与认不出形状的拒绝(Tauri 传输失败 / 命令 panic / 参数序列化失败)先给现场再原样抛出**,由全局 `unhandledrejection` 送进错误池。抛出按指纹去重:面板每 2 秒重读,同一个故障只抛一次、变了再抛,否则报告池里的 `count` 统计的是「面板开了多久」。策略拒绝与宿主故障必须是两个变体:`enforce_project_permission_policy` 那版把「策略拒绝」与「策略读不出来」拼成同一句人话,调用方就分不出该不该上报,所以导出链路走 typed 的 `enforce_project_permission_policy_rejection`。宿主也不预拼用户可见文案:构建输出尾部只回原文 + `omittedCharacters` 两个事实,「已省略前 N 个字符」那句话由前端拼(`export/state/xhsMinitoolOutputTail.ts`,失败卡片与成功提示共用)。同一口径也管界面自己的失败:复制字段走 Tauri 剪贴板插件(`clipboard-manager:allow-write-text`,与 `UiEditorCopyPathButton` 等三处同一条路;`navigator.clipboard` 在 WebView 里可能根本没有、失败还静默),写失败时按钮显示「复制失败」,不假装成功。
- 冲突:注册表可被 agent(改文件)与用户(面板编辑)两方写入。宿主保存时带 `baseHash`;宿主重读比对,hash 不同再逐字段 diff,只要有字段真的不同就判冲突,**绝不覆盖**,返回两侧值让前端逐字段选择;纯格式化改动静默吸收。注册表内不存 revision / updatedAt 之类 token。
- 权限与副作用:跑脚本复用 `command.exec` 的权限口径(只查 deny,UI 按钮即用户确认),**不新增** `GAME_CREATION_APP_COMMANDS` 条目;导出链路**不拿项目写锁、不推进全局 revision**(`.export/` 不进 manifest、素材、UI State 或客户端投影)。发布包是白名单收集,`.export/` 不会进入。
- 非目标:平台自动上传(无 API)、PNG/JPG 之外的 icon 格式转换、宿主侧 zip 结构校验、非 vite 项目、第二个导出目标,以及 `GAME_CREATION_APP_COMMANDS` / `shared-contracts` / SpacetimeDB / OpenAPI 的任何改动。
- 验收:注册表自动建空表单与严格解析拒绝、表单首错、`hasScript` 判定、内容与指纹两条命令的切分(自动刷新拿不到写回基线)、`contentHash` 冲突与逐字段选择、icon 越界与符号链接拒绝、产物路径;自动重读不覆盖用户未保存的输入、agent 事后加上脚本不用点刷新就能亮起导出按钮;首次适配在真实 vite 项目上由 agent 跑通并产出可人工上传的 zip,二次导出零 agent 调用;变更 skill 脚本后指纹同步。证据与未验证项见[里程碑](../project-memory/plans/【里程碑】导出产物面板与小红书小工具导出-2026-10-05.md)。
- 落地情况:Rust(`export/{mod,registry,draft/xhs_minitool/*}` 四个命令)、前端(`export/{state,tabs/xiaohongshu,generated}`)、skill(`pack.mjs --zip-out`)与 `.export/` 快照同步排除均已实现;定向单测与前端 vitest 通过,**真实 vite 项目上的首轮适配仍是唯一未验证项**。
- 验收:注册表自动建空表单与严格解析拒绝、表单首错、`hasScript` 判定、内容与指纹两条命令的切分(自动刷新拿不到写回基线)、`contentHash` 冲突与逐字段选择、icon 越界与符号链接拒绝、产物路径;失败按 typed 变体分流(策略拒绝与宿主故障是两个变体、认不出形状的拒绝与宿主故障原样抛出、同一故障只抛一次、输出省略量由前端写进提示);自动重读不覆盖用户未保存的输入、agent 事后加上脚本不用点刷新就能亮起导出按钮;首次适配在真实 vite 项目上由 agent 跑通并产出可人工上传的 zip,二次导出零 agent 调用;变更 skill 脚本后指纹同步。证据与未验证项见[里程碑](../project-memory/plans/【里程碑】导出产物面板与小红书小工具导出-2026-10-05.md)。
- 落地情况:Rust(`export/{mod,registry,draft/xhs_minitool/*}` 四个命令)、前端(`export/{state,tabs/xiaohongshu,generated}`)、skill(`pack.mjs --zip-out`)与 `.export/` 快照同步排除均已实现;失败通道已按上面两条 ADR 收口(`commandDenied` 与 `exportUnavailable` 分开、宿主故障与未分类拒绝原样抛出并去重、输出尾部只回事实)。定向单测与前端 vitest 通过,**真实 vite 项目上的首轮适配仍是唯一未验证项**。
## 2026-10-02 退役自建 Agent Runtime 与 CLI 执行面