合并 origin/master:随包资源仍由准备步骤生成,Claude Agent SDK sidecar 按同一架构归位

- 合并 origin/master(140 个提交),解决 4 处冲突:build.rs 取只读校验分支、package.json 同时保留 bundled-resources:test 与 check:generated-bindings、决策日志与踩坑记录两侧条目并存
- master 的构建期 staging(stage_bundled_claude_agent、prune_stale_codex_components)不进构建脚本;sidecar 改由声明与准备步骤产出
- package-layout.json 新增 claudeAgent section(锁定版本、sidecar 入口、上游 SDK 与平台原生运行时目标表、复制跳过规则),layoutVersion 递增到 2
- prepare-bundled-resources.mjs 新增 sidecar 整目录原子替换与缓存 key,复制规则按 section 参数化,Windows 真机 24 个文件、复跑命中缓存零写入
- package_layout.rs 新增 sidecar 只读校验(入口与仓库源码逐字节一致、上游包版本、可执行位、白名单外文件)与声明钉住用例
- build.rs 调用 sidecar 校验并导出 AGC_CLAUDE_AGENT_SDK_VERSION;claude_code_cli.rs 的 sidecar 身份串改用声明版本
- resources/claude-agent 映射从基线 tauri.conf.json 移到 windows/macos 平台配置,check-config.mjs 增加「基线不得声明 resources/**」守卫
- 修正 package_layout 的 Windows 路径期望用例(期望值改与实现同形,原用例在 Windows 必红)
- 同步技术方案 §4.5/§4.9、共享决策日志与踩坑记录
This commit is contained in:
2026-09-30 17:23:04 +08:00
497 changed files with 39302 additions and 20386 deletions
@@ -33,7 +33,7 @@
## 验收
- `src/services/sseStream.test.ts` 覆盖 CRLF / LF 边界、UTF-8 尾部 flush、异常 JSON 跳过和提前停止取消 reader。
- `src/services/llmClient.test.ts` 覆盖 OpenAI 兼容文本流、异常 JSON 跳过和 `[DONE]` 后提前停止。
- `src/services/llmClient.test.ts` 覆盖 OpenAI 兼容文本流、异常 JSON 跳过和 `[DONE]` 后提前停止。**2026-09-24 修订**:`src/services/llmClient.ts` 与其测试已随旧创作链路退役删除,该条不再是现行验收项;现行 SSE 传输层验收由 `src/services/sseStream.test.ts`(已放回根 `vitest.config.ts` 的 include,2026-09-24)与各业务 client 的既有用例承担。
- `src/services/image-editor/editorAgentClient.test.ts` 覆盖会话 CRUD 和 `/messages` 普通 JSON 路由;画布 Agent 不纳入 SSE parser 验收。
- 已有 OpenAI 兼容文本流、NPC 聊天流、创作 Agent、创意互动 Agent、视觉小说运行态和充值订单状态测试继续通过。
- `npm run typecheck` 不产生新的类型错误。
@@ -44,6 +44,37 @@
Web 宿主需要显式启用 Tailwind 4,并引入 `@genarrative/shared/styles.css` 和 `@genarrative/shared/theme.css`;Tauri WebView 与网站共用这套源码,移动端 React Native 不消费 DOM/CSS 组件。筛选工具栏所需的 `platform-category-*` chrome 样式也已放入共享样式文件,独立宿主无需依赖网站 `src/index.css`。组件库不提供页面壳或业务流程。`components.json` 只用于 shadcn CLI 定位源码,不允许覆盖现有业务组件。
## 后台管理端组件层(2026-09-29)
陶泥儿后台(`apps/admin-web`)原先每个页签各自手写页面壳、面板、按钮、提示条、空态、状态胶囊、标签、键值列表、数据表外壳、表单字段和分页条,同一套 `admin-*` 类名在 25 个页面里重复出现。现在这些跨页签重复的 chrome 统一收口到 `packages/shared/src/components/admin/`,由 `@genarrative/shared/components` 导出,页面只保留业务取数、筛选状态和领域文案:
- 页面壳与标题:`AdminPage`、`AdminPageHeading`(标题 / 说明 / 右侧操作)。
- 面板:`AdminPanel`(`as="div" | "section" | "form"`、`tone="warning"`)、`AdminPanelHeading`(`title` / `description` / `count` / `extra` / `titleProps`)、`AdminActionRow`。
- 动作:`AdminButton`(`primary` / `secondary` / `danger` / `ghost` / `text` / `icon` 六种既有类名变体,CVA 组织变体)。
- 反馈:`AdminAlert`(默认错误底色,`warning` / `success`,`role` 可选)、`AdminToast`、`AdminEmptyState`、`AdminStatusPill`(`ok` / `pending` / `error`)、`AdminTag`、`AdminTagList`。
- 数据与表单:`AdminTable`(滚动容器 + `compact` / `wide` 尺寸)、`AdminJsonPreview`、`AdminField` / `AdminFieldGroup` / `AdminFormRow`、`AdminInfoList` / `AdminInfoItem`、`AdminPagination`(统计 + 每页条数 + 上一页 / 下一页)。
- 列表:`AdminListPanel` + `AdminListColumn`——把「面板 + 加载态 + 空态 + 表格 + 分页」收成一个组件。表格形态由 `columns` 驱动(`render` 自定义单元格、`renderRow` 自定义整行、`header`/`headerProps` 支持排序表头、`dataLabel` 写移动端标签),卡片/网格或自定义表格走 `children`(`alwaysRenderChildren` 可让空列表也保留骨架);空态三态 `state`/`text`/`row`,加载两口径 `empty`/`line`,另有 `toolbar`、`pagination` 槽位和 `showEmpty` 判据覆盖。后台 20 个页签的 23 个列表已全部改用它。
- 列头排序:同一组件上用 `sortable` / `sortValue` / `sortDescription` 声明可排序列,交互与表查询列头一致(正序 → 倒序 → 不排序,同步 `aria-sort`,稳定排序)。它是**前端排序**,只适用于「一次取全」的列表(邀请码、灰度、任务配置等);分页明细列表要排序必须让后端接口接受 `sortColumn`/`sortDirection`(当前只有表查询行数据与 API Key 两个接口支持),不要在分页数据上做前端排序。
- 收敛范围:后台 21 个文件里的 26 处列表全部走 `AdminListPanel`,其中 24 处是 `columns` 形态(含 2 处弹窗内 `surface="plain"`);仅账号管理的卡片列表与账号配置的键值列表保留 `children` 形态(它们不是表格)。新增列表时请直接用 `columns` + `renderRow`,不要再在页面里手写 `<thead>` 或把 `<AdminTable>` 当 children 传进去。唯一例外是「上传模板」弹窗里的待上传队列表(逐行校验/进度的编辑态表格),有意不纳入列表组件。
- 弹窗:`AdminDialog`——遮罩 + 面板 + 标题行 + 关闭动作,统一 `role="dialog"`、`aria-modal`、Esc、点遮罩关闭、初始聚焦与 `aria-labelledby`;`as="form"` 支持表单弹窗(提交时可用 `closeDisabled` / `closeOnBackdrop` / `closeOnEscape` 冻结关闭),`panelClassName` / `backdropClassName` / `showClose` / `extra` 承接页面既有视觉类。后台 12 处弹窗外壳已全部改用它。
- 时间展示:`formatAdminDateTime` / `formatAdminDate`(`packages/shared/src/components/admin/AdminFormat.ts`)统一后台的 ISO/epoch/Date 渲染(`zh-CN`、24 小时、解析失败回落 `fallback`)。页面里原先 7 份重复的本地 `formatDateTime` / `formatTime` 已删除,列表和详情不再出现裸 ISO 串。
样式归属:组件的 `.admin-*` 规则随组件放在 `packages/shared/src/components/admin/admin.css`(与 `PlatformProfileRechargeModal/index.css` 同一种按组件自带样式的做法),由组件入口 `import './admin.css'` 引入;`apps/admin-web/src/styles/admin.css` 只保留后台壳层布局和页面专属规则。媒体查询里针对公共组件的覆盖(980px 表单单列、560px 面板内边距与标题字号等)同样放在共享表,保证共享组件样式表单独看也是完整的。`apps/admin-web/src/styles/admin.test.ts` 锁定这条边界:共享表必须定义公共 chrome,页面表不得再重复定义。
迁移里做的刻意归一:页面标题的 `<h1>` 和面板标题的 `<h2>` 统一为 `AdminPageHeading` / `AdminPanelHeading` 的 `<h2>` / `<h3>`;分页条统一走 `AdminPagination`(游标分页与偏移分页的取数逻辑仍由页面持有);弹窗遮罩从页面自有的 `.admin-detail-modal` 换成共享 `.admin-confirm-backdrop`(遮罩底色与层级随之统一),`AdminAgcTemplatesPage` 的列表从「页面直出表格」改为列表面板(多了白底卡片)。取数、筛选、分页状态、写操作确认流程仍留在页面。
本地核对渲染效果(不依赖 Rust 后端与 SpacetimeDB):
```powershell
node scripts/admin-web-fake-api.mjs 8099
$env:ADMIN_API_TARGET="http://127.0.0.1:8099"; node scripts/dev.mjs admin-web
# 打开 http://127.0.0.1:3111/admin/ ,localStorage 写入 genarrative_admin_token=local-fake-token 即可进入
```
`scripts/admin-web-fake-api.mjs` 提供 overview / dashboard / tracking / error-reports / feature-gates / invite-codes / project-snapshots / accounts / user-detail 等夹具,够逐页核对列表、空态、分页与弹窗;接口形状不符时页面会整页空白(后台目前没有 error boundary),加夹具时对照 `apps/admin-web/src/api/adminApiTypes.ts`。
后台组件的边界与平台组件一致:只接 props、图标节点和回调,不读 token、不发请求、不持有正式业务状态;路由、鉴权、`/admin/api/*` 调用和筛选/分页状态留在 `apps/admin-web`。
## 展示页
网站 `/components`(兼容别名 `/design-system`)是共享组件展示页,不经过账号 Gate。页面按“基础组件”“平台通用组件”“Token”“状态”分区,覆盖公共组件的主要变体、交互态、筛选/标签/媒体/上传/异步状态/指标列表等平台 chrome 和移动端布局;展示数据均为本地静态示例,不调用 API。平台组件分区中的筛选按示例素材状态过滤结果,排序按最近使用或名称重排结果,并在筛选按钮、排序按钮和独立筛选面板之间保持同一份本地状态。展示页可用于网站与客户端接入前的视觉回归和人工验收。平台通用组件示例优先从 `@genarrative/shared/components` 直接导入,业务代码仍可通过 `src/components/common` 的兼容出口渐进迁移;展示页只接入不读取请求、store 或业务实体的 chrome,不把账号、发布和编辑器业务流程嵌入展示页。
@@ -10,6 +10,7 @@
- 默认转发 `api-server` 到 `127.0.0.1:8082`。
- 默认转发最小 SpacetimeDB 公网路由到 `127.0.0.1:3101`。
- 默认静态目录为 `/srv/genarrative/web`,维护开关文件为 `/var/lib/genarrative/maintenance/enabled`。
- 清智创游官网独立域名的 `/home/` 静态资源仍由 Nginx 独立 vhost 提供;`/api/*` 必须进入 api-server,不能回退为静态 404。
- 静态响应会显式写入 `Cache-Control`:HTML / SPA fallback 默认 `no-cache`,Vite 指纹静态资源默认 `public, max-age=31536000, immutable`,其它静态资源默认 `no-cache`。三档分别由 `GENARRATIVE_PINGORA_GATEWAY_HTML_CACHE_CONTROL`、`GENARRATIVE_PINGORA_GATEWAY_ASSET_CACHE_CONTROL` 和 `GENARRATIVE_PINGORA_GATEWAY_STATIC_CACHE_CONTROL` 覆盖,配置值禁止换行或 NUL,避免响应头注入。静态文件还会按 metadata 写入弱 `ETag`、`Last-Modified` 与 `Accept-Ranges: bytes`,并对 `GET` / `HEAD` 的 `If-None-Match`、`If-Modified-Since` 返回 `304`;单段 `Range: bytes=` 返回 `206 + Content-Range`,越界范围返回 `416 + Content-Range: bytes */<len>`,保持 HTML `no-cache` 下的浏览器协商缓存和 Nginx 直连体验一致。
- `/api` 通用路由同时按 `Content-Length` 与实际流式请求体累计字节数执行大小上限,避免客户端省略长度头绕过网关保护。
- `GENARRATIVE_PINGORA_GATEWAY_PROBE_TOKEN` 非空时,`/__genarrative_pingora/healthz` 可用同名探针 header 做本机 shadow 健康检查;未带 token 或 token 不匹配时仍返回 404。
@@ -64,7 +65,7 @@ npm run check:pingora-release-readiness
`check:nginx-spa-routes` 从 `appPageRoutes.ts` 的 `STAGE_ROUTE_ENTRIES` / `APP_RUNTIME_ROUTES`、`appRoutes.tsx` 的精确路由判断和兼容恢复路径 `/creation/rpg/agent` 提取当前主站 SPA allowlist,确认生产、开发和容器三套 Nginx 模板集合一致,并验证大小写、尾部斜杠和 `/creation/not-exist`、`/runtime/not-exist`、`/puzzle/not-exist` 等未知反例。
`check:pingora-route-parity` 会先执行同一 Nginx SPA 路由门禁,再读取 `deploy/pingora/nginx-route-parity.matrix.json`,静态确认生产 / 开发 Nginx 模板、Pingora Rust 路由 allowlist / 单测和本文档都覆盖同一组核心路由。`cargo test -p pingora-gateway --manifest-path server-rs/Cargo.toml matches_nginx_route_parity_matrix` 会读取同一份矩阵,逐条断言 `classify_path` 的路由结果、body limit 和接流保护分组。
`check:pingora-route-parity` 会先执行同一 Nginx SPA 路由门禁,再读取 `deploy/pingora/nginx-route-parity.matrix.json`,静态确认生产 / 开发 Nginx 模板、Pingora Rust 路由 allowlist / 单测和本文档都覆盖同一组核心路由,并做**反向覆盖**(模板里的每条 `location` 都必须被矩阵声明)。`cargo test -p pingora-gateway --manifest-path server-rs/Cargo.toml matches_nginx_route_parity_matrix` 会读取同一份矩阵,逐条断言 `classify_path` 的路由结果、body limit 和接流保护分组。`check:nginx-spa-routes` 与 `check:pingora-route-parity` 已串进 `npm run lint`(因此 `check:repository-ci`、CI 与 pre-push 都会执行),接线本身由 `check:production-ops` 的 guardrail 锁定。
`check:nginx-pingora-canary` 会静态校验 `deploy/nginx/snippets/genarrative-pingora-canary.conf` 的本机来源限制、handoff 响应头、probe token 占位、前缀 rewrite、低缓冲和 WebSocket Upgrade 设置,也会校验 `deploy/nginx/snippets/genarrative-pingora-realpath-canary.conf` 只能作为独立 loopback `server` 片段使用、默认监听 `127.0.0.1:18083`、写独立 access log、没有 rewrite、覆盖真实 `/api` / `/v1` / `/assets` 代表路径。本机安装了 Nginx 时脚本会额外把两个 snippet 包进临时 `http {}` 执行 `nginx -t`;需要在 CI / 目标 agent 上强制要求真实 Nginx 语法检查时执行 `node scripts/check-nginx-pingora-canary.mjs --require-nginx`。
@@ -534,9 +535,15 @@ dev 根盘空间在安装后曾接近满盘;2026-06-17 进入 canary 前已清
| `/v1/database/{db}/subscribe`、`/v1/identity*` | 转发到 SpacetimeDB,保留 WebSocket Upgrade 头。 |
| `/__genarrative_pingora/healthz` | 仅在携带 `X-Genarrative-Pingora-Probe` 且匹配配置 token 时返回 shadow JSON,否则 404。 |
| `/v1/*`、`/generated-*`、`/healthz*`、`/readyz*` | 返回 404,保持生产公网不暴露口径。 |
| 主站 SPA allowlist | 只对 `/`、`/creation`、`/project`、`/profile` 与 `/editor/canvas` 失败回退 `/index.html`;匹配大小写不敏感并允许一个尾部斜杠,HTML 默认 `no-cache`。 |
| 主站 SPA allowlist | 只对 `/`、`/components`、`/creation`、`/design-system`、`/editor/canvas`、`/games`、`/games/detail`、`/games/mine`、`/games/play`、`/games/publish`、`/profile`、`/project` 失败回退 `/index.html`(集合与前端路由源、Nginx 三份模板逐条一致,由 `npm run check:pingora-route-parity` 与 `cargo test -p pingora-gateway matches_nginx_route_parity_matrix` 比对);匹配大小写不敏感并允许一个尾部斜杠,HTML 默认 `no-cache`。`/games/game_<32 位十六进制 id>/…` 是发行网关路由,不在 SPA allowlist 内。 |
| 其它 Web 路径 | 只读取真实静态文件或目录 index,缺失时返回真实 404;`/creation/not-exist`、`/runtime/not-exist`、`/puzzle/not-exist` 不进入 SPA fallback。 |
SPA allowlist 里属于游戏分发入口的深链(游戏目录 / 详情 / 游玩 / 我的 / 发布深链:`/games`、`/games/detail`、`/games/play`、`/games/mine`、`/games/publish`)与 Nginx 三份模板同口径;Pingora 侧由路由对照矩阵的 `games_spa_fallback` 用例与 `cargo test -p pingora-gateway matches_nginx_route_parity_matrix` 逐条断言。根路径 `/` 精确回退 `/index.html`(Nginx 在 `location = /` 里用 `try_files /index.html =404;`,不带 `$uri`),由矩阵的 `web_root_spa` 用例固定。发行网关路径 `/games/game_<32 位十六进制 id>/…` 不走 SPA,见下一节的对照说明。
`npm run check:pingora-route-parity` 同时对两份 Nginx 模板做**反向覆盖**检查:模板里出现的每条 `location` 都必须被矩阵某条用例的 `nginx` 片段声明,否则失败。这条是 2026-09-29 补的——此前只做正向检查(矩阵片段必须存在于模板),于是「模板加/改了路由、矩阵与 Pingora 没跟上」这类漂移(发行网关路由就是这么漏的)不会被门禁发现。
**平台同源发行入口**(`/games/game_<32 位十六进制 id>/…`)与 SPA allowlist 是两条不同的路由:Nginx 用 `location ~ "^/games/(?<game_id>game_[0-9a-f]{32})(?<game_path>/.*)?$"` 把它代理到 api-server 的发行网关(`proxy_set_header Cookie ""` + `proxy_pass .../api/game-distribution/releases/$game_id$game_path`),Pingora 侧对应 `RouteDecision::ReleaseGateway`:走 api 上游,但把上游路径重写成 `/api/game-distribution/releases/<gameId><asset 路径>`(与 Nginx 的 `proxy_pass` 同口径,原来的 query 不再拼接)、清空 `Cookie`,并按 Nginx 该 location 的语义既不进 SPA fallback、也不套用 `limit_conn` / `limit_req` 分组、不受维护闸拦截。只认小写、固定 32 位十六进制 id;`/games/detail` 这类 SPA 深链与 `/games/game/...` 这类形状不符的路径都不会被吞进发行网关。该口径由矩阵的 `games_release_gateway` 用例(含 `upstreamPath` 期望值)与 `cargo test -p pingora-gateway matches_nginx_route_parity_matrix` 固定。
维护模式下,公网 API-like 路由返回 JSON `503`;公网 Web 静态路由先读取 `GENARRATIVE_PINGORA_GATEWAY_MAINTENANCE_PAGE_FILE` 指向的 release 外运行态公告,缺失时回退 `GENARRATIVE_PINGORA_GATEWAY_WEB_ROOT/maintenance.html`,两者都不存在时返回纯文本 `503`。版本化默认页不得包含日期或具体时段,临时公告由 `maintenance-on.sh --page-file` 安装并在 `maintenance-off.sh` 时清理。IPv4 loopback / RFC1918 / link-local 和 IPv6 loopback / ULA / link-local 来源绕过整站维护闸,主站页面与静态资源、普通 API、后台页面与后台 API、SpacetimeDB 路由均按非维护状态继续处理;应用层登录、管理员鉴权和其它业务鉴权保持不变。Pingora 直连按 TCP peer 判定来源;仅当 peer 是 loopback 的同机 Nginx 时才接受 Nginx 强制覆盖的 `X-Real-IP`,绝不使用客户端可伪造的 `X-Forwarded-For` 做维护放行。该放行只绕过网关维护响应;若 `pause-after-stdb` 已停止 api-server,内网普通 API 和后台 API 仍不可用。
代理失败时,API / SpacetimeDB 等代理路由返回统一 JSON 网关错误;本地静态路由仍保持对应 HTTP 错误状态。
静态 `Range` 只支持单段 bytes range;多段 range 暂按完整文件返回,避免在正式替换前引入 multipart 响应面。`If-None-Match` / `If-Modified-Since` 优先于 `Range` 判定,命中时仍返回 `304`;`If-Range` 日期匹配时继续返回 `206`,日期旧于文件或弱 ETag 校验器时回完整 `200`;`206` / `304` / `416` 不做 gzip 压缩,避免 `Content-Range` 语义被响应体改写破坏。Gateway smoke 会用固定 `X-Request-Id` 对账静态 `304`、`405`、`206`、`416` 的 Pingora access log 行,确认本地响应状态也进入正式切换证据链。
@@ -52,6 +52,16 @@
- 项目右侧对话的模型选择器在对话进行中保持可交互:切换模型只写回客户端配置并作用于下一轮,当前回合不受影响;发送按钮仍由 `controlBusy` / `modelReady` 把关。
- 自定义开关关闭时,设置页不包含模型管理或模型选择,使用官方代理锁定。
## 模型绑定 Agent 执行模式
- 后台 AGC 模型目录每项新增 `agentMode`,只接受 `codex` 与 `cc`;缺少该字段的历史目录按 `codex` 解释,后台新增模型默认 `codex`。
- `codex` 仍表示现有 AGC Codex app-server 执行链路;`cc` 表示 AGC 客户端启动随包的 Claude Agent SDK sidecar,模型的 `modelId` 原样作为 Claude Agent SDK 的模型标识,不把 Claude 协议伪装成 OpenAI Responses。sidecar 随 AGC 安装包携带 SDK 及匹配平台的 Claude Code runtime,用户不需要预先安装 `claude` 命令。
- `/api/llm/models` 的启用模型摘要返回 `agentMode`,后台完整目录、管理 DTO 和客户端目录保持同一绑定快照。客户端在选择模型时同时持久化模型 ID 与执行模式;默认模型变化、模型被停用或目录刷新回退时一并更新执行模式。
- 客户端设置保存不得把后台模型绑定覆盖回 Codex。旧客户端配置缺少执行模式时继续按 Codex 运行;旧后台响应缺少 `agentMode` 时客户端按 Codex 兼容。
- `cc` 执行器必须使用 Claude Agent SDK 的 Anthropic 接入方式,通过隔离的 sidecar 运行环境和受控 MCP 桥接 AGC 工具;不得开放 Claude Code 原生 Bash、任意写文件、付费或编辑器执行能力绕过 AGC 宿主门禁。当前 Codex 模式、provider 模式和自定义 LLM 行为不因新增绑定改变。
- `cc` 模式的进程失败、超时、取消、MCP 断连和无效输出均按失败关闭,不能留下活动回合、执行租约或未确认的子进程;无法找到或启动 Claude Code 时给出可行动错误。
## 验收
- 空目录 + 上游可达:启动后目录自动生成(`id` 为模型名 slug、`alias`/`modelId` 为上游原名、`enabled` 全为真、默认项为排序后第一项),`revision` 自增一次,`GET /api/llm/models` 的 `displayName` 就是上游原名,界面不出现任何内置模型名。
@@ -1,6 +1,6 @@
# 【技术方案】AGC 后端框架整理与演进路线
更新时间:`2026-09-18`
更新时间:`2026-09-23`
## 结论
@@ -49,7 +49,7 @@ flowchart TB
PROJECT[Local Project Plane\nfiles / manifest / JSONL / locks / checkpoints]
UI --> HOST
UI --> API
HOST --> API
HOST --> CORE
HOST --> ORCH
HOST --> CONTRACT
@@ -190,7 +190,7 @@ UI
### 云端资源/生成操作
```text
AGC tool or UI
AGC tool or UI -> Local Rust Host
-> api-server route
-> auth + project ownership + idempotency
-> module-* validation
@@ -223,14 +223,28 @@ Local project snapshot
3. **文件系统单一入口**:任何 Agent 文件读写都经过项目 scope、写锁和受控工具;不能在 API 或前端加旁路写入。
4. **外部服务单一入口**:LLM、生成、OSS、认证等外部调用只经 `platform-*`,不能在业务 handler 里散落裸 HTTP。
5. **数据访问单一入口**:`api-server` 和本地需要的云端调用使用 `spacetime-client` facade,不直接拼 SpacetimeDB 请求。
6. **前端只消费投影**:前端不推导正式业务状态,不直接访问本地项目数据库或 SpacetimeDB。
6. **前端只消费投影**:AGC React 按离线前端设计,只负责展示、交互、输入草稿及短暂 UI 状态。正式数据、持久化、网络请求编排、重试和恢复由 Rust 本地宿主与云端控制面持有。React 通过领域命令表达用户意图,通过事件或 channel 接收结果,不在渲染/effect 中主动回写收到的正式状态,不直接访问本地项目数据库或 SpacetimeDB。
7. **身份与凭据分离**:同一身份的 access token 轮换不使在途 Run 失效;换号、退出或 origin 变化必须使旧 Run 停止后续副作用。
8. **可恢复优先**:持久动作、异步生成、Runner 续跑、外部结果回填和最终状态写入必须有稳定 ID、版本/CAS 或幂等键。
## 当前缺口
### 渲染层数据与 I/O 边界尚未完成
2026-09-23 已完成第一条可复核的网络下沉闭环:官方模型目录 GET /api/llm/models 由 Tauri `load_game_creator_llm_models` 持有会话凭据、HTTP 请求、响应 envelope 解析、响应大小上限和身份代次复核,React `llmModelCatalog` 在 Tauri 中只通过 typed invoke 取得目录快照;无 native host 时明确报告本地模型服务不可用,不回退到 React HTTP。该闭环不覆盖账户、素材直传、错误上报或发行网络,不能据此宣称全部 React 网络 I/O 已归 Rust。
本轮又完成发行网络闭环:`game_distribution_publish.rs` 从 Rust `platform_session` 读取当前 user/token/origin,在 Rust 内完成发布灰度、资料建议、封面定价/生成及异步任务收口、创建游戏/版本、分片续传和送审;`gameDistributionPublish.ts` 只保留 typed `invoke` façade,不再 import `requestClientApi` / `fetchClientHttp`、读取 token 或拼接远端 URL。封面队列由 Rust 统一做有界轮询,React 静止时不再按 1.6 秒访问远端任务;账户刷新、素材上传、错误上报和其它 clientApi 链路仍未迁移。
本文件的离线前端规则是目标合同,不能据此宣称当前代码已全部满足。2026-09-23 已将项目创建目录与最近工作区偏好迁到 Tauri Rust AppData JSON、typed command 和变更事件;发送前提醒偏好已在 2026-09-24 迁到同一份 Rust 客户端偏好(`set_chat_prompt_polish_reminder_disabled` + 偏好事件);素材上传、错误报告与账户/钱包也已改为 Rust typed command(`upload_platform_media_asset`、`submit_error_report`、`account_api` 五条命令),渲染层不再持有 token、拼远端 URL 或发平台请求。2026-09-24 已完成最后一段:登录、验证码、refresh、登出与开发服务器选择迁到 Rust `auth_session`(AppData 私有 `client-session.json` 持有 refresh 凭据,登录/续期/登出后 Rust 自己安装本机运行时会话并发出状态事件);渲染层 `services/clientApi.ts` 删除、`clientHttp.ts` 收敛为 origin 校验与渠道默认值,`src` 内不再有 `fetch(` 调用、也不再读写 access token 或 refresh cookie。账户钱包、素材上传、错误报告、模型目录、发行与工作区偏好同样已改为 Rust typed command。Direct 回合保活也已归 Rust:回合登记时启动保活任务(按 access token 签发时间自助续期),回合结束即停止,渲染层不再持有任何业务定时器。仍未完成的是运行时验收:真实账号登录/续期/登出、微信支付与多窗口并发刷新。迁移必须保留账号/origin 隔离、幂等请求身份、失败关闭与既有数据恢复,不能通过增加一个任意 URL 的 HTTP IPC 代理伪装完成。
优先消除能在本地重复验证的请求放大:事件订阅必须在页面卸载和迟到 bootstrap 时同时释放前端监听与 Rust subscriber;持久化失败不得由 `setState → effect` 自触发无限重写;活动回合、本地生成任务和插件状态使用事件变化通知与启动快照,静止时不按周期读列表。正式状态仍以 Rust 注册表/账本为准,重复通知合并为一次在途读取和最多一次补读,旧作用域结果不能覆盖新项目。订阅不可用或首次读取失败必须明确报告可观察性失败,不能伪造业务终态。
低频离散状态使用事件即可,高吞吐流采用有界 channel 或已有 subscribe/consume 队列;二者都必须有订阅释放、初始快照、顺序/代次隔离和有界积压。显示耗时、验证码倒计时、动画和长按交互属于短暂 UI 状态,不与业务轮询混淆。只有上游没有推送能力时,才允许由 Rust 统一管理有界、可取消的轮询。
验证包含:静止窗口的业务读取计数、重复挂载/卸载后的 Rust subscriber 数、失败写入调用次数上界、通知并发单飞、旧项目/迟到响应隔离。真实交互时延须区分用户动作到本地反馈、IPC 等待、Rust 执行和远端网络耗时,未实测不得给出“几百毫秒已消除”的结论。
### 缺少 AGC application facade
账户素材库快照和图片预览由 Rust typed command 提供:read_editor_asset_library 只返回稳定 assetId、文件夹与展示元数据,read_editor_asset_preview 在 Rust 内完成当前账号解析、换签、有界下载、图片 magic/重定向/session 校验并返回 data URL。AssetImporter/ImageImporterPreview 不请求素材库或 read-url,也不把签名地址交给 WebView。远程导入只提交 assetId 列表和现有 policy/requirements,由 Rust 重新解析权威素材、下载并登记;预览字节不作为导入事实。React 切换素材、项目或卸载后忽略旧预览结果。
能力散落在 `agent`、`editor_project.rs`、`external_editor_api.rs`、`ai_tasks.rs`、快照路由和生成队列中。它们各自可用,但调用方难以判断一项用户意图应该走本地 Run、云端 task 还是外部 generation operation。
**整理方向**:先建立逻辑 facade 和 capability catalog,保留现有路由作为 adapter;只有当两个以上入口共享同一业务流程时,才下沉 application service。
@@ -20,13 +20,13 @@
### 渠道与安装身份合同
- 渠道同时决定**更新端点**与**安装身份**,两者都由构建期写入产物。默认渠道 `dev` 保持基线身份 `productName = 陶泥儿`、`identifier = world.genarrative.ai-game-creator`;其它渠道(`release` 与自定义渠道)派生 `productName = 陶泥儿 <渠道显示名>`(`release` → `陶泥儿 Release`、`beta-2` → `陶泥儿 Beta-2`)与 `identifier = world.genarrative.ai-game-creator.<渠道>`。渠道显示名按连字符分段首字母大写,不改动渠道本身。
- 渠道同时决定**更新端点**与**安装身份**,两者都由构建期写入产物。默认渠道 `dev` 保持基线身份 `productName = 陶泥儿开发版`、`identifier = world.genarrative.ai-game-creator`;`release` 渠道使用正式产品名 `陶泥儿` 并派生 `identifier = world.genarrative.ai-game-creator.release`;其它自定义渠道派生 `productName = 陶泥儿 <渠道显示名>`(如 `beta-2` → `陶泥儿 Beta-2`)与 `identifier = world.genarrative.ai-game-creator.<渠道>`。渠道显示名按连字符分段首字母大写,不改动渠道本身。
- 默认渠道身份**不可变更**:既有安装目录、卸载项、快捷方式与已发布客户端的升级链都建立在基线身份上。渠道身份由 `apps/ai-game-creator-shell/scripts/channel-identity.mjs` 单点定义,构建入口、macOS 发布入口与配置门禁共同消费;基线 `tauri.conf.json` 必须逐字等于默认渠道身份。
- 安装身份决定的持久与可见事实:Windows 安装目录 `%LOCALAPPDATA%\<产品名>`、卸载项与 `HKCU\Software\genarrative\<产品名>`、WebView2 数据目录 `%LOCALAPPDATA%\<identifier>`、客户端数据目录 `%APPDATA%\<identifier>`;macOS `.app` 名、bundle id、DMG 卷名与菜单栏应用名。
- 同机并存:不同渠道的包体可以在同一台设备上同时安装并同时运行,互不覆盖、互不顶掉;同一渠道的新版本仍是原地升级,因为更新端点与安装身份同属一个渠道。
- 数据不跨渠道共享:本地项目、工程快照、模板、登录态、诊断日志与 Runner/项目锁按渠道身份分目录。切渠道等于换一个客户端,不迁移、不合并本地数据;渠道内的 origin 隔离规则不变。
- 主窗口与工作区/启动器窗口标题取构建期产品名,让同机并存的渠道客户端在任务栏与 Alt-Tab 中可区分;默认渠道标题仍是「陶泥儿」。
- 首装包与更新包的对象名包含产品名(如 `陶泥儿 Release_0.1.96_x64-setup.exe`、`陶泥儿 Release_0.1.96_aarch64.dmg`)。清单 `downloads` 地址由发布脚本按本次真实产物派生,禁止写死产品名;首装包选择按 `<版本>_<架构>.dmg` 唯一匹配,不依赖产品名字面量。
- 主窗口与工作区/启动器窗口标题取构建期产品名,让同机并存的渠道客户端在任务栏与 Alt-Tab 中可区分;`dev` 标题为「陶泥儿开发版」,`release` 标题为「陶泥儿」。
- 首装包与更新包的对象名包含产品名(release 如 `陶泥儿_0.1.96_x64-setup.exe`、`陶泥儿_0.1.96_aarch64.dmg`;自定义渠道仍带渠道显示名)。清单 `downloads` 地址由发布脚本按本次真实产物派生,禁止写死产品名;首装包选择按 `<版本>_<架构>.dmg` 唯一匹配,不依赖产品名字面量。
- 产品名中的空格在清单 URL 中必须使用标准百分号编码(如 `%20`);网站服务端允许这种合法文件名编码,但仍拒绝控制字符、路径分隔符、DEL 和非法百分号编码,不得通过改写真实产物名绕过校验。
- Windows 提权 ACL 修复助手按目录名识别安装身份:`<基线>` 与 `<基线>.<渠道>` 都在 AGC 自有的 managed 范围内;相似前缀(例如 `world.genarrative.ai-game-creator-backup`)不在范围内,落回 user-selected 范围或直接拒绝。
@@ -184,13 +184,13 @@
| 条款 | 验收方式 | 结果 |
| --- | --- | --- |
| 渠道身份派生与默认渠道不变 | `node --test apps/ai-game-creator-shell/scripts/build-release.test.mjs` | 通过;`dev` 逐字等于基线 `陶泥儿` / `world.genarrative.ai-game-creator`,`release` / `beta-2` 派生独立产品名与 identifier,非法渠道失败关闭 |
| 渠道身份派生与默认渠道不变 | `node --test apps/ai-game-creator-shell/scripts/build-release.test.mjs` | 通过;`dev` 逐字等于基线 `陶泥儿开发版` / `world.genarrative.ai-game-creator`,`release` 使用 `陶泥儿` 并保持独立 identifier,`beta-2` 派生带后缀的产品名与独立 identifier,非法渠道失败关闭 |
| 身份与端点同批注入 | 同上的渠道 `--config` 用例 | 通过;`productName` / `identifier` 与 `/<channel>-win|mac/latest.json` 来自同一次解析 |
| 渠道产物首装包选择 | 同上的渠道 DMG 夹具用例 | 通过;`陶泥儿 Release_<版本>_aarch64.dmg` 仍按 `<版本>_<架构>.dmg` 唯一匹配 |
| 渠道产物首装包选择 | 同上的渠道 DMG 夹具用例 | 通过;`陶泥儿_<版本>_aarch64.dmg` 仍按 `<版本>_<架构>.dmg` 唯一匹配 |
| 基线配置等于默认渠道身份 | `node apps/ai-game-creator-shell/scripts/check-config.mjs` | 通过;基线漂移与非默认渠道身份不隔离都会失败关闭 |
| 全量发布脚本回归 | `node --test build-release.test.mjs release-oss.test.mjs prepare-macos-codex.test.mjs cargo-features.test.mjs` | 通过(64/64,含 macOS 入口按渠道解析产品名的守卫) |
| AGC 自有 AppData 提权 ACL 范围 | `cargo test --locked --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml --bin genarrative-ai-game-creator-shell -- config::private_path_elevation_policy_tests` | 通过(12/12;含基线、`<基线>.release`、`<基线>.beta-2` 与相似前缀 `-backup` 的反向断言) |
| 渠道身份进入真实构建产物 | `AGC_UPDATE_CHANNEL=release npm --prefix apps/ai-game-creator-shell run build -- --no-bundle --debug` | 通过;Tauri 接受派生的 `productName` / `identifier` 并完成构建;产物字符串实测 `陶泥儿 Release` × 1、`agc/release-win/latest.json` × 1、`world.genarrative.ai-game-creator.release` × 1、`agc/dev-win/latest.json` × 0 |
| 渠道身份进入真实构建产物 | `AGC_UPDATE_CHANNEL=release npm --prefix apps/ai-game-creator-shell run build -- --no-bundle --debug` | 通过;Tauri 接受 `productName=陶泥儿` / `identifier=world.genarrative.ai-game-creator.release` 并完成构建;产物字符串实测 `陶泥儿` × 1、`agc/release-win/latest.json` × 1、`world.genarrative.ai-game-creator.release` × 1、`agc/dev-win/latest.json` × 0 |
未验证项(不得按已通过处理):
@@ -101,7 +101,7 @@ templates/
| --------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `fetch_game_template_library` | 读 `templates/index.json`(≤4 MiB),校验后缓存到 `<app_data>/templates/index.json`;网络失败时回退本机缓存并在 `source` 标 `cache`。远端已经答话但正文不是合法 UTF-8 或不符合 schema、以及缓存自己损坏时,一律失败关闭,不用缓存掩盖远端错误 |
| `download_game_template` | 取清单里对应条目,流式下载 zip(≤512 MiB),校验字节数与 SHA-256,解压到 `<app_data>/templates/installed/<id>/<version>/`,最后写 `installed.json` 作为安装完成的唯一标记 |
| `create_automatic_local_game_project_from_template` | 需要时先安装模板,然后在自动工作区根目录下按既有自动工作区规则建目录:先复制模板文件;Cocos 项目更新自身身份后走既有 Cocos 导入,Godot 项目改写工程显示名后走既有 Godot 导入,其余沿现有 `init_local_game_project_at` 初始化。根目录默认是 `<app_data>/projects/`,用户可选 `projectsRoot` 覆盖(必须来自本机目录选择器并通过私有路径门禁),见 [`【实施计划】AGC项目创建目录可选-2026-09-17.md`](../project-memory/plans/【实施计划】AGC项目创建目录可选-2026-09-17.md) |
| `create_automatic_local_game_project_from_template` | 需要时先安装模板,然后在自动工作区根目录下按既有自动工作区规则建目录:先复制模板文件;Cocos 项目更新自身身份后走既有 Cocos 导入,Godot 项目改写工程显示名后走既有 Godot 导入,其余沿现有 `init_local_game_project_at` 初始化。根目录默认是 `<app_data>/projects/`;用户在设置「工作区」里改选的创建目录由 Rust 客户端偏好(`workspace-preferences.json`)持有,建项时由 Rust 读取并执行同一套私有路径门禁(非空绝对路径、普通目录、拒绝链接与 reparse point、user-selected 一次性修复) |
安全与健壮性:
@@ -125,6 +125,7 @@ build script 只写 `OUT_DIR`/`target`;随包资源是它的输入。凡需要
| `resources/plugins/**` | 复制 + 三个编辑器分支的产物 | `plugin_host.rs`(`AGC_PLUGIN_WORKSPACE` → `resource_dir/plugins` → dev 回退仓库 `plugins/`)、`editor_adapters.rs` | dev 有仓库回退,但 cocos/unity/godot payload 仍以随包路径为准 |
| `resources/plugins/agc-unity-editor/**`、`agc-godot-editor/native/gdextension/**` | 外部工具链(Windows 专属) | `editor_adapters.rs` 候选链 | 仅 Windows |
| `resources/node-runtime/**` | 已有:`stage-node-runtime.mjs` | `environment_check.rs`(`agc-node-runtime.v1` 全量 sha256) | 发布与需要随包 Node 的 dev |
| `resources/claude-agent/**` | 纯复制(应用 `agent-sidecar/src/index.mjs` + 上游 `@anthropic-ai/claude-agent-sdk`、`@anthropic-ai/claude-agent-sdk-<platform>`) | `claude_code_cli.rs`(`exe_dir/claude-agent/index.mjs`、macOS `.app` 的 `Contents/Resources/claude-agent`) | Windows / macOS dev:准备步骤按声明 staging |
| `design-agent`、`vendor/*` 许可 | 受版本控制 | `design_tools.rs` 等 | 无需 staging |
### 4.6 入口接线
@@ -171,6 +172,8 @@ M2 之后构建脚本只做只读校验;M3 之后它也不再生成任何随
准备步骤的 Windows 侧命令执行(powershell / cargo)只在本机无法验证,验收清单见 M3 里程碑规范。
**2026-09-30(合并 master)**:新增随包组件 Claude Agent SDK sidecar(cc 执行模式)沿用同一口径——声明 `claudeAgent` section(上游 SDK 包与平台原生运行时包的目标表、复制跳过规则、锁定版本),staging 由准备步骤整目录原子替换,`build.rs` 只读校验(入口与仓库源码一致、SDK 与原生运行时版本等于声明、白名单外文件)。它的 `resources/claude-agent` 映射放在 `tauri.windows.conf.json` 与 `tauri.macos.conf.json`,**不得**回到基线 `tauri.conf.json`:基线同时服务 Linux(CI 只在那里编译壳 crate 且不装 npm 依赖),`tauri-build` 会因资源缺失直接失败。
## 5. 兼容与迁移
1. **产物兼容**:包内路径、manifest schema(`genarrative-codex-sidecar.v2`、`agc-node-runtime.v1`、插件 `plugin.json`)不变,安装包内容逐项对得上;升级路径不需要用户侧动作。
@@ -1,5 +1,28 @@
# AI 游戏创作智能体 App 实施计划
## 2026-09-29 Codex 私有运行目录路径解析
新建的私有运行目录先确认是普通目录并收紧权限,再解析真实路径,后续 `codex-home`、`workspace` 和隔离用户目录均在真实路径下创建,避免 macOS 系统临时目录的符号链接阻断启动。Windows 扩展 UNC 路径 `\\?\UNC\server\share\...` 必须转换为 `\\server\share\...`,盘符路径才直接去掉 `\\?\` 前缀;转换后保留绝对路径语义。私有子目录仍执行原有祖先符号链接与 reparse point 检查。
定向验证使用 Rust 单测过滤器 `codex_private_runtime_`,覆盖 UNC、盘符和普通路径转换,以及 Unix 祖先链接解析与内部链接拒绝;Windows 绝对路径断言在 Windows 测试环境执行。
## 2026-09-28 Web 环境版本探测的用户目录隔离
Node/npm 版本探测清空继承环境后,必须设置客户端创建的临时 `HOME`、`USERPROFILE`、`APPDATA` 和 `LOCALAPPDATA`,并指定空的用户及全局 npm 配置、临时缓存和临时工作目录。临时目录保留到探测子进程退出,不依赖 Windows 用户资料查询,也不读取用户或项目的 `.npmrc`。探测继续使用校验后的客户端运行时;初始化临时环境失败返回 `runtime-probe-home-unavailable`,不回退到系统 Node 或真实用户目录。
验证使用真实 Node/npm 的 `environment_check::tests::real_node_npm_environment_versions`,同时用 `src-tauri/tests/fixtures/environment-probe.cjs` 检查隔离目录及空配置,避免正常机器的 Windows 后备查询掩盖回归。测试通过 `include_str!` 引用独立夹具,不把 Node 环境断言内嵌到生产源码扫描范围。此测试需要本地 Node/npm,默认忽略,定向验收时显式运行;另运行 `npm run ai-game-creator-shell:typecheck` 覆盖配置静态检查。
## 2026-09-28 AGC 客户端现行分工:离线前端、事件驱动与维护态出口
本节是 AGC 客户端「React 只做展示与交互」的现行口径,取代更早文档里渲染层直连平台网络、按固定频率轮询、自行解析维护响应的做法。
- **网络与凭据只在 Rust**。平台 origin、Bearer/refresh 凭据、OSS 直传票据、Provider 与更新清单请求都由 `src-tauri` 承担;渲染层通过 typed command 提交结构化意图,不再持有 access token,也不再声明 `http:default` 权限(`capabilities/main.json`)。`src/services/clientApi.ts` 与 `fetchClientHttp` 已删除。
- **状态变更由 Rust 事件驱动**。正式状态归 Rust:Direct 活动回合的唯一事实源是 Direct 线程管理器的活动回合快照(`list_direct_active_turns` 只读它),登记、进度内容变化、收口各广播一次 `game-creator-direct-active-turns-changed`;素材生成与插件状态同理。渲染层进入入口时**先订阅、再读一次受控快照**,事件重复或内容未变时保持数组身份,卸载后迟到事件不写回;不恢复任何固定频率轮询(纯 UI 计时器、拖拽重复器与动画 tick 除外)。
- **维护态判定归 Rust,渲染层只订阅**。平台请求的错误分支统一经 `platform_maintenance::watch_platform_response` 分类(只有 `503` 且命中 `MAINTENANCE` 或「维护」才算,对象存储自身的 503 不误伤),命中后广播 `genarrative-client-maintenance-detected`;渲染层由 `clientMaintenance.subscribeClientMaintenanceEvent` 订阅并打开唯一的「系统维护中」弹窗,业务面板各自的错误文案保持不变,同一批并发失败只弹一次。
- **失败文案只展示宿主给的脱敏摘要**。`turn.completed.failure` 是失败说明的唯一来源,渲染层不做 HTTP 判定、不预读诊断正文;失败线索留在 `.agent/runtime/errors`、应用日志与错误上报池(`5398a53e6` 起用户可见文案不再带诊断引用)。
- **活动回合之外的两条配套约束**:命令返回 `Ok` 只代表接单成立,整轮收场只由 `turn.completed` 回答;渲染层的队列、忙态与提示都以事件流的这些终态为准。
证据入口:`cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml --offline -- direct_thread_manager --test-threads=1`、`-- platform_maintenance --test-threads=1`、`npx vitest run tests/directActiveTurns.test.tsx tests/maintenanceNotice.test.tsx --root apps/ai-game-creator-shell`。
## 2026-09-23 UI 编辑器退役界面图参考语义建议
本节覆盖下文“2026-08-18 UI Editor 从属页面、手势与保存失败边界”中的 `UIDesignImage.metadata.slave_to` 口径,以及“界面语义建议”相关描述。
@@ -182,6 +205,7 @@ UI 编辑器的“分析参考图”步骤、Rust 命令 `suggest_ui_design_sema
- OAuth 在目录捕获与模型进程内产生的轮换结果仅在宿主私有 runtime 中延续,按原始认证来源指纹、路由、项目及稳定账户/用户身份绑定后复制到下一回合的新私有 HOME;不回写用户原始认证文件,不缓存 API Key。来源、路由或身份改变时不继承,迟到的旧实例不得覆盖新回合结果;轮换结果未确认时禁止重用旧 token。
- 实例退役与验收完成使用不同判据:Windows 仍要求完整 Job 退出;Unix 已确认主进程退出且所控进程组为空时可退役并允许下一显式用户回合,但 group-only 证明不能使原会话从 Interrupted 升为 Completed,不能自动重放旧操作。主进程、所属组或退出状态仍未知时继续阻断新实例。
- 补丁完整复用固定版本官方语法解析与执行语义。宿主枚举每个源和目标(包括所有 Move 和重复操作),检查项目边界、受保护路径和链接,在本地短写事务中复核后执行;取消、预算、退出证明和未知结果仍走统一宿主控制。失败可能已有部分修改,不能声称全批回滚或自动原样重放。
- 2026-09-29:`agc_read_project_context` 与 `agc_list_project_files` 可以读取绝对路径,以及用 `..` 离开当前项目的路径。项目内相对路径仍拒绝 `.agent`、凭据文件名、符号链接和硬链接;项目外读取同样拒绝这些受保护名字和链接。项目外文件打开与目录列表须逐段检查访问路径(包括列表起始目录及其父目录),拒绝符号链接与 Windows 重解析点,不能只检查末段文件;目录扫描在进入子目录前再次检查。含系统目录别名的外部路径也遵守该规则,调用方应提供不含链接的真实路径。`agc_write_file`、`agc_apply_patch`、素材导入和原生补丁仍限定在当前项目。Codex 进程沙箱仍是 `read-only`。提示词未改。
- 模型计划进度保存到同一回合的宿主状态,仅作展示,不等于验收通过;计划更新和长资源调用可以同时推进。真正共享资源的修改仍保持必要顺序。
### 验收
@@ -668,7 +692,7 @@ Supervisor 认领该回执后,由父 run 自己为每个原 delivery 逐一创
- 模式合同:客户端 AppData 配置新增全局 `agentMode`,只接受 `codex_cli / provider`。缺省和新安装默认使用 `codex_cli`,原有 HTTP LLM Provider 路径完整保留并可显式切回 `provider`;切换只影响下一次节点请求,不新增 Runner、任务图、会话库、配置库或业务事实源。
- 调度边界:正式 DAG、manifest、Agent task/session/run 身份、队列、锁、委派、all-join、完成门、Provider lifecycle、持久 retry/handoff 与 `needs-reconciliation` 继续由现有 AGC Runtime 掌控。每个被调度节点在 `codex_cli` 模式下直接启动一次非交互 `codex exec` 充当该节点的推理 Agent;Codex 返回当前 Runtime 广告函数的结构化调用,Runtime 仍是唯一 ToolHost,不允许 CLI 自己写项目、执行命令、调用 MCP 或形成第二套 revision / verification 真相。
- 安装包侧车:Windows x64 release 固定随 Tauri resource 打包 `@openai/codex@0.155.1` 的原生 `codex.exe`;Rust build script 从 AGC 子包锁定依赖 stage 到 resource,并写入版本与 SHA-256 清单。固定版本只在 `build_support/codex_bundle.rs` 声明一次,构建脚本、宿主补丁执行器身份、逐次审批协议允许列表和模型目录捕获共同引用它,避免多处字面量漂移。Windows 侧车映射只写入 `tauri.windows.conf.json`,通用 `tauri.conf.json` 不得让 Linux / macOS 构建依赖未生成的 Windows 二进制。运行时只在文件摘要和 `codex-cli` 版本同时匹配清单时优先选内置侧车;缺失、损坏或版本漂移时跳过它,按既有 npm 安装、PATH 顺序回退。安装包同时携带 Apache-2.0 第三方声明;API Key、`auth.json`、Cookie、Token、用户 `CODEX_HOME`、用户配置和项目数据绝不打包。
- Windows x64 release 安装包只生成 NSIS,不生成 MSI:`tauri.windows.conf.json` 的 `bundle.targets` 固定为 `["nsis"]`,通用配置继续保留其它平台的默认打包目标。安装后的产品名、开始菜单 / 桌面快捷方式和 EXE 产品描述由 `tauri.conf.json` 的 `productName` 生成:基线是 `陶泥儿`,发布构建按渠道由 `--config` 覆盖为 `陶泥儿 <渠道显示名>`,identifier 同批派生 `<基线>.<渠道>`(默认渠道保持基线值),因此不同渠道的包体可在同一台设备并存;内部可执行文件名保持稳定。内置 Codex 资源安装到顶层 `coding-agent/win-x64/`,运行时从同一路径查找 `bin/codex.exe` 与 `manifest.json`;仓库 staging 仍使用 `resources/codex/win-x64/`,包内子目录、组件名、版本和完整性校验保持原合同。
- Windows x64 release 安装包只生成 NSIS,不生成 MSI:`tauri.windows.conf.json` 的 `bundle.targets` 固定为 `["nsis"]`,通用配置继续保留其它平台的默认打包目标。安装后的产品名、开始菜单 / 桌面快捷方式和 EXE 产品描述由 `tauri.conf.json` 的 `productName` 生成:`dev` 基线是 `陶泥儿开发版`,`release` 复用正式产品名 `陶泥儿`,自定义渠道才带 `陶泥儿 <渠道显示名>`;identifier 同批派生 `<基线>.<渠道>`(默认渠道保持基线值),因此不同渠道的包体可在同一台设备并存;内部可执行文件名保持稳定。内置 Codex 资源安装到顶层 `coding-agent/win-x64/`,运行时从同一路径查找 `bin/codex.exe` 与 `manifest.json`;仓库 staging 仍使用 `resources/codex/win-x64/`,包内子目录、组件名、版本和完整性校验保持原合同。
- Windows 渠道 AppData 目录归属判断及其调用分支统一保留 Windows 条件编译;通用项目路径策略测试仍可在 Linux 运行,实际 ACL 修复保持原平台门禁与授权范围。
- macOS 安装包必须携带锁定版本的原生 Codex、`codex-code-mode-host`、`rg`、上游 zsh、`codex-package.json` 和第三方声明,保留上游相对布局;构建时按 Cargo 目标选择 npm 原生依赖,缺文件、版本或目标不匹配立即失败,不借用开发机 PATH 里的 Codex。资源只在 `tauri.macos.conf.json` 映射到 `Contents/Resources/coding-agent/mac-native/darwin-arm64/` 与 `darwin-x64/`。构建与运行共享平台文件白名单,运行时由当前 `.app/Contents/MacOS` 定位相邻 `Resources`,完整性与版本验证通过后优先使用内置组件;失败沿既有外部安装回退,不能运行未校验的内置文件。macOS 当前只构建 arm64 单架构,但资源映射仍并列携带两套锁定原生 Codex 依赖(运行切片按 Cargo 目标只选择对应目录),恢复 Intel 时无需改动资源布局;随包 Node 只有宿主架构那一份,所以不得构建 universal 包。不读取全局 Codex。
- 内置插件的清单、运行入口与面板同时在 Windows/macOS 随包分发,继续由既有 PluginHost 的应用资源目录扫描入口发现;不携带开发依赖、缓存、测试或私有配置。插件文件随包不等于原生适配器跨平台:Cocos 进程桥接仍受现有 Windows 实现和 feature 门禁约束,macOS 原生桥接另行设计与验收,不复制 Windows DLL 冒充支持。系统 Node、用户 Cocos Creator、账号登录、网络和生成工程的 npm 工具链仍是现有外部前提,不在此次 Codex 侧车补齐中隐式变更。
@@ -1116,6 +1140,7 @@ game-project/
- 结构化对话记录按授权本地项目路径追加 JSONL;正式聊天读取 Supervisor active Session 与只读 legacy 项目历史,开发单 Agent 对话读取对应 Agent Session。开发入口已支持本地 Session 新建、切换、归档和分叉,但不提供云端同步。
- Agent 状态列表从 `.agent/manifest.json` 的任务 / 角色清单、`.agent/run.latest.json` / `.agent/runs/<runId>.json` 的 step、taskGraph、passPlans、lifecycleStatus,以及 `read_game_creator_agent_runtimes` 批量读取的 `.agent/runtime/agents/<taskId>.json` 和最近任务派生;v1 不新增独立状态数据库,也不承诺完整后台 runner。
- 首页发送、项目组目录选择和本地文件选择必须使用 Tauri 非阻塞原生 picker,并把选择器绑定到当前 `client` 窗口;禁止在同步 command 中调用 `blocking_pick_folder` / `blocking_pick_file` 阻塞 WebView 事件循环。选择器打开期间保留首页草稿可编辑,取消后恢复“开启创作”按钮并回显“已取消”。
- 外壳状态栏(首页输入框下方、项目组页面头部那一行)不占页面:`status` 一律由外壳的浮层承载(见 `LauncherNoticeToast`)。进行中进度与失败结论走粘性浮层,生存期与原来那行一致(被下一条状态替换或清空时才消失,切页回来仍可见);成功、取消这类回显走一次性浮层,成功与取消 2.6 秒、失败 6 秒收起。
- GameAgent 首页“灵感推荐”当前使用客户端随包发布的内置图片素材,不请求主站 `/creation` 的陶泥儿精选 `/api/editor/showcase/resources`,也不引入远端灵感数据源。图片按原始宽高比组成响应式瀑布流,首页内容区域允许纵向滚动;点击图片只打开全屏原图预览,点击图片外遮罩或按 Escape 关闭,不展示标题或其它业务动作。后续改接远端数据前仍须先明确独立数据契约和交互验收。
- 首页正式桌面版以约 `814px` 的居中内容栏组织品牌区、创作类型、输入框、最近项目与灵感推荐;品牌区和类型按钮在内容栏左缘对齐,输入框和下方模块仍保持整体居中。标题使用深色暖橙层级,固定副标题为“你的游戏创作管家”;选中的创作类型使用实心暖橙按钮,未选中项使用浅色描边,输入框使用更舒展的单行创作起始比例。
- 最近项目固定展示至多三个横向信息卡:左侧为本地渐变封面占位,右侧只投影项目名称、项目类型、目录真实修改时间和已有最近运行状态。目录检查通过 `modifiedAt` 提供修改时间;没有封面资产时不得假装读取了项目封面,也不新增封面持久化字段。
@@ -1442,7 +1467,8 @@ game-project/
## 2026-08-20 Direct Codex 审核 Skill Pack 与受控工具内核
- 普通项目对话由一个 project-bound Codex app-server thread 执行。客户端系统提示词包含最小工程合同、项目 prompts 和审核 Skill 索引;源码与 Skill 正文按任务需要读取。提示词、工具描述与 Skill 直接描述当前任务、输入和成功条件,细节按调用需要提供。
- 首页提供“做游戏 / 做素材 / 做方案”三个创作类型,默认“做游戏”。每次首页提交自动创建一个新项目并进入项目工作台。用户正文原样进入项目对话,`game|art|doc` 作为受限结构化首轮上下文传给同一 Codex thread。
- 首页提供“做游戏 / 做方案”两个创作类型(`game` / `doc`),默认“做游戏”;“做素材”入口已退役,素材生成在项目内按实际工作流触发。每次首页提交自动创建一个新项目并进入项目工作台。用户正文原样进入项目对话,`game|doc` 作为受限结构化首轮上下文传给同一 Codex thread;持久草稿里遗留的 `art` 按 `game` 处理(`effectiveCreationType` 映射),不再产生第三种首轮上下文。
- 「策划补全」是“做游戏”专属的提交前勾选(2026-09-15 起):勾选时该档提交 `planning`,未勾选时提交 `direct-build`;“做方案”始终走立项策划链路,与本勾选无关。复选框只在“做游戏”档渲染;切换创作类型时首页表面整体重挂,勾选状态随之清除,切回来必须是未勾选——这条可观察契约由 `appSurface` 的「scopes the 策划补全 option to the game entry」与「submits the game entry with planning when 策划补全 is checked」两条用例钉住,实现侧不额外维护重置逻辑。
- 2026-09-23 清理了未注册的 DirectHome 用户对话命令及旧附件 sidecar 渲染链;首页仍先创建项目再进入 DirectProject,不恢复无项目对话。自动项目命名和提示润色仍调用内部 `direct_game_creator_home_codex_chat`,其 DirectHome 只读隔离通道与测试继续保留。附件作为 canonical `userItem.content` 中的 `agc_attachment_reference` 携带名称、媒体类型、大小、项目相对路径及状态,经现役 validation/wire 校验与投影;路径映射不等于灌入全文,也不按 GDD 特判。附件清洗与数量上限继续复用 `direct_codex_attachments.rs`,旧 sidecar DTO、header、专属 prompt key 和测试不再是保留合同。
- `agc-skill-pack.v1` 包含完整游戏交付流程、项目结构、陶泥儿美术、Web 游戏实现、真实浏览器试玩、客户端资源投影,以及 Unity/Godot 编辑器常用操作八项审核 Skill。清单记录用途、触发条件、所需工具、版本和内容 SHA-256;审核文本按 UTF-8 读取并将 CRLF 规范为 LF 后计算指纹和安装,避免混合换行造成 Windows / Linux 构建结果漂移,语义内容变化时必须同步重算对应清单指纹并提升版本。同步统一运行 `npm run agc:skill-pack:sync`,只读校验由 AGC `typecheck` 和 release build 自动执行,发现漂移时直接列出 Skill 与实际摘要,不让失配内容进入构建产物。客户端把审核文件安装到隔离目录后通过 app-server `skills/extraRoots/set + skills/list` 注册并复核,完整正文由 Codex 原生 Skill 机制按意图加载,一层引用只能经 `agc_read_skill_resource` 读取清单内 Markdown。引用路径按平台无关规则拒绝反斜杠、盘符、UNC、绝对路径和 `..`,不能依赖当前宿主的 `std::path` 语义判断其它平台路径。
- DirectProject 连接客户端内置的 `agc_tools` STDIO MCP,并在启动时接入客户端扩展仓库中用户已启用的独立第三方 STDIO/HTTP MCP 配置。内置工具包括审核引用读取、图片生成、标准陶泥儿美术准备、已登记资源有界查询、视频 / 角色动画 / 音效 / BGM 的 create-or-derive 语义生成、已登记图片去背景、desktop/mobile 浏览器试玩和受控 `agc_web_search`。内置 MCP 进程负责协议;真实浏览器、付费平台调用与受控搜索通过随机 loopback 地址回到客户端主进程,GUI 登录态、开发者 Key、项目路径、revision、operation 与幂等键由客户端持有并隔离于模型上下文。内置与用户启用的第三方 MCP 工具沿用 DirectProject 自动批准方式;付费资源工具由客户端绑定稳定回合身份、串行执行并优先恢复匹配账本。`llm.webSearchEnabled` 控制 DirectProject 的 AGC 受控搜索工具暴露与执行。原生工具与审批权限以下方“DirectProject Codex 完整访问覆盖”为准。
@@ -1625,6 +1651,8 @@ DirectProject、Agent Runtime、Provider、app-server、内置 MCP、命令执
项目内统一落库目录为 `.agent/runtime/errors/`,事件记录采用幂等 JSONL 或 JSON sidecar;写入失败不能覆盖原始业务错误,但必须在事件中标记 `persistenceFailed`。DirectProject 对话历史必须持久化本轮用户消息、终态错误的安全 assistant 投影和诊断引用,使下一轮能够读取上一轮失败证据。前端只展示 `publicText`,点击详情后按 `detailRef` 读取有界、脱敏的诊断,不直接展示私有 `detail`。
前端取回 `detailRef` 的口径是失败文案末尾的固定后缀「;详情:<detailRef>」:Rust 侧 `direct_codex_failure_text_keeps_the_detail_ref_marker_for_the_renderer` 与前端 `tests/agentRuntimeErrorDetail.test.ts` 各自钉住同一份文案形状与它的解析,任一侧改文案或改解析都会变红。失败提示只展示映射后的安全 `publicText`(v1 历史形状与 v2 现行形状都映射成「阶段 + 摘要 + 建议 + 是否可直接重试」),诊断正文不在提示里预读、也不写入历史投影,而是由聊天状态栏下方的「查看详情」入口按需调用只读命令读取并二次脱敏;这条交互由 `tests/appSurface/chat-composer.suite.ts` 的「失败提示保留可执行原因,诊断正文只在「查看详情」时读取」与 `tests/agentRuntimeModel.test.ts` 的 v2 映射用例钉住,渲染层仍不参与错误分类。
`turn/completed` 等待超时必须区分 `idle-timeout`、`hard-timeout`、`transport-closed`、`failed-turn`、`invalid-terminal` 和 `tool-error`;收到内置工具参数错误后必须结束当前工具调用并进入可行动终态,不能继续使用越界的试玩 `attempt` 或无限等待。试玩次数由客户端按当前 `clientTurnId` 持久化分配,模型不能自由递增;超过上限必须返回一次终态并停止回合。
游戏素材完成门必须扫描实际参与构建的 `game/` 源码模块,读取 manifest 的登记身份与相对路径,并把构建后的 URL 映射回登记身份。固定素材路径只能作为兼容候选,不能作为唯一准入。已登记且被真实源码引用、被构建纳入并在浏览器证据中观察到的资源通过;未登记、来源不匹配或只存在于设计规范中的资源继续失败关闭。
@@ -1731,6 +1759,22 @@ Direct 回合的所有权属于进程内项目身份锁,不属于当前页面
- 运行时 smoke:AGC 开发态打开项目、观察索引写入与同步日志、关闭工作区窗口后确认关闭触发的那次同步执行;报告为"客户端 diff 已验证 / 服务端已配置环境联调"两层,不合并成一句"已通"。
- 边界:新增日志与错误文案不含 Access Token、AccessKey、绝对路径与项目内容。
| 验收面 | 对应证据 |
| --- | --- |
| 首次全量、单文件改动、删除与摘要复用 | `project_snapshot_diff_reuses_metadata_and_reports_a_single_modification`、`project_snapshot_diff_treats_touched_but_identical_content_as_metadata_only`、`project_snapshot_manifest_metadata_changes_sync_without_content_changes` |
| 排除口径与 `.agent` 整目录上传 | `project_snapshot_scan_skips_excluded_paths_and_oversized_files`、`project_snapshot_scan_uploads_whole_agent_directory`、`project_snapshot_sync_policy_keeps_agent_state_and_still_blocks_credentials` |
| 单次预算、延后与失败文件不推进索引 | `project_snapshot_diff_defers_files_over_the_sync_budget`、`project_snapshot_synced_files_exclude_failed_and_deferred_paths`、`project_snapshot_pending_files_include_every_unsynced_path_once` |
| 幂等跳过与确定性鉴权失败停止 | `project_snapshot_upload_marks_remote_duplicates_and_sends_marker_headers`、`project_snapshot_upload_stops_after_a_deterministic_authentication_failure` |
| 同步期间被改写的文件不上传也不推进索引 | `project_snapshot_diff_defers_files_that_changed_while_being_read`、`project_snapshot_upload_skips_files_that_changed_after_the_diff` |
| 同项目串行化、周期触发让位与退出等待 | `project_snapshot_sync_is_serialized_per_project`、`project_snapshot_periodic_trigger_yields_while_a_sync_is_in_flight`、`project_snapshot_exit_wait_includes_scheduled_tasks_waiting_for_the_project_lock` |
| 单窗口生命周期登记与项目身份校验 | `project_snapshot_workspace_lifecycle_tracks_the_single_client_window`、`project_snapshot_independent_window_url_supplies_a_fallback_project`、`project_snapshot_project_id_validation_rejects_paths_and_unsafe_values` |
| 索引往返、损坏恢复与旧清单完整性未知 | `project_snapshot_index_round_trips_and_recovers_from_corruption`、`project_snapshot_legacy_index_keeps_completeness_unknown` |
| 远端回收只删上一版登记过的对象、读不到上一版一张不删、单轮有上限 | `project_snapshot_reclamation_retires_only_objects_the_previous_manifest_registered`、`project_snapshot_reclamation_deletes_nothing_without_a_readable_previous_manifest`、`project_snapshot_reclamation_is_bounded_per_manifest_write` |
| 服务端校验、413 与 429、渠道失败关闭 | `project_snapshot_manifest_rejects_duplicates_and_out_of_range_values`、`project_snapshot_manifest_rejects_too_many_entries`、`project_snapshot_manifest_rejects_a_project_over_the_total_cap`、`project_snapshots_upload_accepts_empty_files_and_rejects_false_checksums`、`project_snapshot_user_quota_stops_after_the_hourly_file_limit`、`project_snapshot_channel_fails_closed_on_invalid_configuration` |
| 真实 OSS 往返 | `project_snapshot_live_sync_uploads_once_then_reports_no_op`(默认忽略,需要 `GENARRATIVE_AGC_PROJECT_SNAPSHOT_LIVE_PROJECT` 与真实 api-server / OSS 凭据) |
本轮定向证据:客户端 `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml project_snapshot -- --test-threads=1` 为 24 passed / 1 ignored;服务端 `cargo test -p api-server project_snapshot -- --test-threads=1` 为 21 passed / 1 ignored。真实 OSS 往返、开发态打开项目的索引写入与关窗触发仍属运行时 smoke,未在本轮取得证据。
### 后台工程列表与下载
- 正式客户端在单窗口内切换启动器和项目,不以窗口 URL 判断活动项目。前端把当前窗口的已打开项目登记给原生同步器;打开后发起首轮同步,离开/切换项目和物理关窗为旧项目补同步,周期扫描只读这份窗口登记。重复登记同一路径不重复发起;注册失败写诊断,不能假装已登记。
@@ -193,3 +193,20 @@ subscriber 不依赖 `unsubscribe` 或传输层断开清理。每次 `subscribe`
`subscribe` 必须在同一个 Thread Manager 边界注册 subscriber、捕获 queue 尾部、确定历史锚点和当前运行态事件;bootstrap 期间产生的新事件由该 subscriber 的内部 cursor 继续通过 `consume` 获取,不能丢失。
断线恢复优先调用 `consume(subscriptionId)`。subscription 仍有效时只返回该 subscriber 尚未消费的 queue 事件;subscription 已过期或 Thread Manager 重启后统一走新的 `subscribe`,再由前端按 `lastCompletedItemId` 从历史懒加载。Rust 不提供 `getItemSnapshot(itemId)`,已完成 item 始终通过历史读取。
### 钉住本节的用例(2026-09-24 复核)
本节的行为由以下用例守住;改动 Thread Manager 时按同一清单复核:
| 契约 | 用例 |
| --- | --- |
| 多 subscriber 各自游标、互不推进 | `direct_thread_manager::tests::subscribers_have_independent_cursors_on_one_global_queue` |
| 逻辑队头回收与落后订阅过期 | `queue_cleanup_only_removes_a_cleanable_prefix`、`slow_subscriber_is_expired_when_queue_limit_is_reached` |
| 完成事件只在 item 持久化后入队 | `completion_releases_item_events_only_after_the_completion_event_is_appended` |
| 生命周期锚点独立于 replay 队列 | `turn_completed_anchor_survives_empty_queue_for_new_subscriber`、`bootstrap_contains_lifecycle_anchor_and_unfinished_events_only` |
| 未完成 item 从 opener 起可重放、未决审批保留 | `bootstrap_replays_opener_user_item_id`、`unresolved_approval_is_kept_in_bootstrap_until_resolved` |
| 订阅释放幂等且能回收队头 | `unsubscribe_releases_the_subscriber_and_is_idempotent`、`unsubscribe_allows_a_cleanable_prefix_to_be_trimmed` |
| legacy 历史行失败关闭(不 fallback、不迁移) | `direct_project_history_item_from_parsed_line` 对非 canonical `type` 直接报错;见 `direct_project_history` 定向用例 |
| 前端按 `subscriptionId` 唤醒并处理回执竞态 | `apps/ai-game-creator-shell/tests/directThreadChatSubscription.test.tsx` |
运行时验收(关闭/切页后重进、并发 item、短暂断线 consume、过期重订阅)仍需真实客户端执行;自动化只覆盖到此表为止。
@@ -126,7 +126,7 @@ Project 头与行(禁止出现 GDD / 规格 / 权威 / 必须读取):
### 3.3 谁渲染、谁看见
- sidecar **只在进 Codex 前由 Rust 拼装**。
- `.agent/conversations/project.jsonl` 继续写用户原文(现有 `append_local_conversation_message`)。
- `.agent/conversations/project.jsonl` 继续由 Rust Direct/Design coordinator 写入用户原文;React 渲染层只保留当前页面投影,不再通过通用 `append_local_conversation_message` 追加聊天行。
- 工作台气泡继续显示 latch / 输入框原文,不把 sidecar 画进 UI。
- 未完成首轮的 hydration 重放目前只带 prompt:本期不把附件写进 jsonl,进程重启后的未完成首轮可能丢映射。完整成功首轮不受影响。不为此新增会话 schema。
@@ -1,6 +1,6 @@
# 策划 Agent 生产迁移与工作区浏览方案
更新时间:2026-09-23
更新时间:2026-09-29
状态:已完成(2026-09-18)
> 现状说明(2026-09-18):本文记录的迁移已完成,当前策划入口统一使用 Design Agent。旧 Planning V1/V2 会话、专用命令、审批卡和展示适配已删除;文中提到的 V2 文件仅代表迁移时的参考来源,不得作为现行实现、回退路径或测试迁移目标。
@@ -14,7 +14,7 @@
迁移后,策划 Agent 应能:
- 持续接收用户自然语言指示;
- 自由读取、创建、写入、局部修改、删除和搜索工作区文件;
- 自由读取、创建、写入、局部修改、删除和搜索工作区文件,并对绝对路径和工作区外路径做同样的读写;
- 按五个策划阶段推进,并在最后进入顾问态;
- 按阶段注入提示词和明确要求的必读资源;
- 通过澄清卡或普通文本向用户询问;
@@ -45,7 +45,7 @@
| Provider 重试 | 复用瞬态错误识别、退避、最大重试次数和失败持久化 |
| 会话持久化 | 复用项目级会话目录、原子写入和恢复入口,但使用新的设计会话数据结构 |
| 并发保护 | 保留项目级短时写锁和会话活跃保护,防止文件或状态写入损坏 |
| 文件底层能力 | 按原型工具契约筛选已有底层函数;绑定工作区,剥离旧业务门禁;缺少的目录删除、搜索等能力局部补齐 |
| 文件底层能力 | 按原型工具契约筛选已有底层函数;工作区内沿用原写入,并接受绝对路径与离开工作区的路径;缺少的目录删除、搜索等能力局部补齐 |
| 审计与 debug | 复用正式动作记录和诊断采集;恢复或审计必需资料保存在 `.agent`,额外 debug 改为只写、可删除、不阻塞的旁路 |
| Tauri 通信 | 复用命令注册、事件流、会话恢复通知和前端状态同步机制 |
| 用户文件浏览 | 复用 Game Agent 的文件列表、文件读取和工作区刷新模式 |
@@ -68,7 +68,7 @@
- 旧的“批准 / 修改 / 退回重做”审批语义;
- 多 Agent、Wiki、知识库、外部搜索和自动任务编排。
路径穿越、绝对路径、控制目录访问和凭据泄露防护属于安全边界,可以保留;它们不能扩展成限制正常策划创作的业务门禁。
2026-09-29:策划文件工具可以读取和写入绝对路径,以及 `design_artifacts` 之外的路径。相对路径仍以策划工作区为基准,`..` 可以离开工作区。用户工作区浏览和附件导入仍只使用 `design_artifacts`。删除允许路径经过祖先链接;删除目标本身是链接时只移除链接(包括悬空链接),递归删除目录不跟随其中的目录链接。删除普通目录前以真实路径检查目标,拒绝工作区根目录及其上级目录,绝对路径中的 `..`、大小写或祖先链接别名均不得绕过;检查必须在删除任何内容前完成。相对删除路径保留 `..`,按文件系统实际链接目标解析,不提前做字符串折叠。凭据不写入提示词或日志。
当前实现入口如下:
@@ -76,8 +76,8 @@
| --- | --- |
| `apps/ai-game-creator-shell/src-tauri/src/agent/design_runtime.rs` | Provider 请求、工具循环、重试、阶段审批与会话恢复 |
| `apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/design_session.rs` | 设计会话、阶段状态与待交互请求的持久化 |
| `apps/ai-game-creator-shell/src-tauri/src/agent/design_tools.rs` | 固定资源包、工作区文件读写、补丁、删除、搜索与路径安全边界 |
| `apps/ai-game-creator-shell/src-tauri/src/project/filesystem.rs` | 路径解析、文件列出和读取、基础写入;内部绝对路径字段不得传给模型 |
| `apps/ai-game-creator-shell/src-tauri/src/agent/design_tools.rs` | 固定资源包与文件工具。文件工具接受工作区相对路径、离开工作区的相对路径和绝对路径;用户浏览与附件导入仍限定在策划工作区 |
| `apps/ai-game-creator-shell/src-tauri/src/project/filesystem.rs` | 项目内路径解析、文件列出和读取、基础写入。该模块返回给界面的内部绝对路径字段仍不是模型输入 |
| `apps/ai-game-creator-shell/src/App.tsx` | Design Agent IPC、事件订阅、文件刷新、会话恢复与输入提交 |
| `apps/ai-game-creator-shell/src/features/project-workspace/DesignWorkspacePanel.tsx` | 策划工作区文件浏览与正文展示,直接打开文件视图 |
| `apps/ai-game-creator-shell/src/features/project-workspace/DesignAgentSurface.tsx` | 消息、reasoning、澄清与阶段审批交互 |
@@ -124,7 +124,7 @@ Runtime 不维护文档版本号,不解析文档版本,不提供版本回退
**交付结果**:策划 Agent 对话复用 GameAgent 的模型、推理档选择控件及配置通道,用户的新选择对后续策划回合实际生效,并保持 GameAgent 原有行为兼容。
本节是本次变更的唯一主规范。控件接入已完成并提交,运行时模型生效逻辑已实现、待验收。拆分与验收见 [控件接入里程碑](../project-memory/plans/【里程碑】策划Agent模型与推理档控件接入-2026-09-20.md) 和 [模型生效里程碑](../project-memory/plans/【里程碑】策划Agent回合模型选择生效-2026-09-20.md)。
本节是本次变更的唯一主规范。控件接入已完成并验收(对应开发期里程碑与实施计划已归档删除,结论以本节为准);运行时模型生效逻辑已实现、待验收,见 [模型生效里程碑](../project-memory/plans/【里程碑】策划Agent回合模型选择生效-2026-09-20.md)。
#### 范围与非目标
@@ -223,16 +223,28 @@ submit_phase_for_approval
get_workflow_status
```
工具使用相对工作区路径。工具执行结果继续通过 Runtime 统一记录和展示,但不向 Agent 暴露宿主绝对路径。
文件工具的 `path` 可以是工作区相对路径、含 `..` 的路径或绝对路径。相对路径以 `design_artifacts` 为基准。工具结果按实际目标返回:工作区内用工作区相对路径,工作区外用解析后的绝对路径,并写入 Runtime 会话记录。
`patch_file` 保留原型按唯一原文匹配、范围不重叠、全部通过才原子写入的语义、换行归一化和缺文件错误。批量 edits 会一次性完成全部校验,并把未找到、多处匹配、重叠等失败项汇总返回;未找到时同时给出候选行号和可见化缩进提示,帮助 Provider 基于当前文件修正锚点。正常工作区写入与删除不逐次请求用户审批;阶段审批不能被复用为文件操作许可。
`list_resources` 一次返回完整逻辑分类、资源 ID、标题和简介;`read_resource` 按一个资源 ID 读取一个文件。资源描述不增加 `required=true/false` 分类,也不增加引导同轮多次调用的说明。
`read_resource` 成功后的用户可见工具提示只显示“读取资源”,不附加资源 ID 或名称。文件工具继续显示原有路径,失败提示保持原样;资源目录、工具参数、实际读写及 Provider 收到的工具结果不变。此规则作用于新生成的工具提示,不回写已有会话消息。
`get_workflow_status` 只返回阶段列表、当前阶段、已批准阶段和待审批阶段,不修改状态。保留原型的常驻提示约束:用户口头表示批准或要求进入下一阶段时,Agent 先查询实际阶段,不能凭普通文本自行切换。现有 `agent.run_status` 的任务/委派状态不能替代这个工具。
## 6. 阶段与提示词注入
常驻提示词统一要求按具体项目的需求、规模和复杂度安排文档结构与内容密度。模板和样例仅供参考,章节与字段可按需增加、合并或删减;简单内容简述,复杂或易歧义处充分展开,不为填模板增加设计、重复论证或无关内容。精简须保留当前阶段判断与后续实现所需信息,TDD 仍须独立指导当前范围的实现。
2026-09-27:常驻提示词删除每次修改文件后汇报修改内容和相对路径、将不确定内容统一分为用户确认/Agent 建议/待原型验证事项的要求。汇报形式按当前协作需要决定;仍按后文规则标注暂定方案并就关键问题询问用户。提示词简化可以调整核心行为,以调整后的行为是否合理作为评估依据。
概念层规则、模板和样例围绕核心体验、主要吸引力与重要边界组织。先利用已有对话和资料,仅按影响当前设计的缺口补问;模板与样例按需读取,参照只说明具体借鉴点,不自动继承参照作品的全部设计。章节按项目需要选取,不要求固定句式、字数、唯一卖点、六项锚点、调性滑杆、T 编号或重复定稿,也不要求先回答固定定调问题或标注“零参照”。
概念层的设计原则帮助后续判断方向,不替代具体问题的分析。说明核心体验所需的数值、操作方式和界面形式可以保留,详细规则、数值平衡和界面规格留待后续展开;规模依据实际团队和项目条件确定。交付检查聚焦概念是否清楚、是否符合已有意图和约束、是否存在影响交付的矛盾或缺口,避免为填模板编造内容。
下游按相关内容或章节承接概念,不强制张力逐条对应或附编号;TDD 美术规则与样例不再依赖概念层固定第 2 节或 T 原则。美术圣经保留设计依据及来源版本,并在 TDD 内写全视觉规格;总册引用对应 TDD 分册,继续满足只看 TDD 即可完成当前范围实现的标准。资源路径、产物路径及阶段审批合同保持不变。
阶段顺序固定为:
```text
@@ -276,16 +288,67 @@ concept → top_design → architecture → systems → tdd → consultant
| 技术文档 | `project/04_tdd/01_技术实现.md`、`project/04_tdd/02_美术圣经.md`、`project/04_tdd/03_数据与配表.md`、`project/04_tdd/总册.md` |
| 顾问态 | 不提交下一阶段审批 |
`systems: []` 是已确定的行为,不是迁移时需要补齐的缺口。`project/analysis.md`、`project/决策台账.md`、`project/dialog.md` 保持原型中的共享过程文件口径,不升级为新的审批必需项。速览卡保留原型结构提示,但 Runtime 与 UI 不解析其章节或内容字段。
`systems: []` 是已确定的行为,不是迁移时需要补齐的缺口。`project/analysis.md`、`project/决策台账.md`、`project/dialog.md` 保持原型中的共享过程文件口径,不升级为新的审批必需项。速览卡采用下述简短概览结构,Runtime 与 UI 不解析其章节或内容字段。
提示词中的路径是策划 Agent 使用的相对路径约定:
- `project/...` 以策划工作区为根,Agent 将其传给 `read_file`、`write_file`、`patch_file` 等文件工具来读取和维护正式产物及过程文件。宿主将它映射到项目的 `design_artifacts/project/...`;阶段审批按登记的相对路径检查必需产物。提示词写明 `project/速览卡.md`、`project/analysis.md` 等目标位置,是在告诉 Agent 文件应写在哪里,并非泄露宿主绝对路径。
- `resources/skills/...`、`resources/templates/...`、`resources/exemplars/...` 和 `resources/modules/system-types/...` 指向随应用发布的固定策划资源包,用于定位分册、模板、例子及系统类型资料,不是策划工作区的写入目标。资源目录在 `resources/catalog.json` 中登记相对路径与资源 ID;Agent 可用 `list_resources` 查 ID,再用 `read_resource` 按 ID 读取。分册中省略 `resources/` 前缀的 `templates/...` 等写法仍指同一资源包内的位置。
这些路径直接服务于 Agent 的文件操作和资源查阅,属于提示词应保留的契约;即使阶段上下文也注入了某条产物路径,分册和模板中的路径仍提供目标文件与交叉引用的具体定位。去除客户端与宿主实现细节时,不应把这类相对路径当作意外暴露的内部实现;宿主安装目录、项目绝对路径及会话控制文件位置才不属于 Agent 的操作输入。
这些路径直接服务于 Agent 的文件操作和资源查阅,属于提示词应保留的契约;即使阶段上下文也注入了某条产物路径,分册和模板中的路径仍提供目标文件与交叉引用的具体定位。去除客户端与宿主实现细节时,仍保留这类相对路径。Runtime 文件工具同时接受绝对路径和离开 `design_artifacts` 的路径,包括项目内其他目录和宿主上的其他位置。常驻提示词写明工作区是相对路径根目录 `.`,并仍要求策划文件放在工作区内并使用相对路径。六个文件工具的说明不再把路径限定为相对路径或工作目录内。正式产物的相对路径约定保持不变。
过程文档记录关键依据、决定和待办,不要求实时完整,也不应重复正式设计文档;阶段提交前补齐影响验收的关键记录。顶层设计的分册、模板和示例保留核心定位及按需说明的易混淆方向与排除理由,不强制使用固定的“不是 X,而是 Y”句式。
共享文档按以下职责维护,文件路径和审批必需产物清单保持不变:
| 文档 | 用途与更新时机 |
| --- | --- |
| `project/analysis.md` | 按需保留重要取舍的依据与当前结论;存在实际备选时再比较,未决问题说明原因或下一步。结论稳定或依据变化时更新,可合并修订条目,不记录每次讨论。 |
| `project/决策台账.md` | 按需集中列出待处理事项和下一步,必要时引用分析或正式设计。事项变化时更新,完成后移出待办,不再重复保存全部已采用决定。 |
| `project/dialog.md` | 仅在用户需要对话摘要或交接记录时维护,不逐轮转录聊天。 |
| `project/速览卡.md` | 概念阶段形成简短游戏概览、当前范围与已有设计入口;仅在概览内容或入口变化时更新,不复制系统表、参数、素材数量、完整排除清单或验证计划。 |
正式设计承载当前采用的规则,共享文档按各自用途记录,不要求同一决定在多处重复登记。不强制连续编号、状态枚举、候选数、问题数量、推翻条件或完整历史流水;尚未确定的事项用自然语言说明,不冒充用户确认。阶段提交前检查影响交付的信息是否一致,不以补齐过程记录作为新门禁。已有项目文件不批量重写或删除。
速览卡让读者快速了解游戏、当前准备做什么及详细设计的位置。分类、支柱、循环和目标玩家可合入概述,不再分别展开;不设固定字数,也不要求填满参考结构。只提示会改变方向或当前范围的重要未决问题,并引用详细位置;设计入口仅链接已存在且有用的文档,不为填卡新增范围或未来计划。注入说明与星露谷样例同步,样例清除旧 NPC、图标和换装数量,按当前日常原型概括;`project/速览卡.md` 仍为概念阶段必需产物。
TDD 的决策记录采用相同原则:设计要求、当前采用值和实际工程约束直接写入对应分册,重要取舍按需保留分析,未决事项说明影响与下一步,不要求逐项登记到外部台账或附推翻条件。总册保留施工索引、跨分册约定与重要缺口,详细问题指向对应分册。问题解决后更新受影响的 GDD 与 TDD 正文并关闭待办,不能只登记处理去向。
“施工方只看 TDD,应能完成当前范围的实现”仍是完成标准:设计要求、内容与数值、实际工程约束和验收条件写全,施工方无需回查 GDD 或猜测关键设计。保留来源版本与变更同步,外部分析与台账不能代替正文。代码组织、算法、内部接口、字段与数据结构、配置载体、资源命名与打包由施工方自主决定;未预定这些实现选择不构成策划缺口。已有工程契约、实际数据/资源格式和用户明确交付约束必须记录;有依据的实现建议可供参考,不成为唯一方案,也不增加逐项登记或用户确认。
TDD 阶段验收对象是策划文档:当前范围的行为、内容、数值、界面、表现要求与实际接入约束须完整、自洽,完成文档一致性、内容关系和必要数值验算。奖励、失败后果、关键视觉状态等设计缺口须补齐;函数签名、源码目录、内部字段或打包格式未预定不阻塞验收。保留实际构建与交付约束,验收条件明确,必要时给代表性场景;测试脚本、执行顺序、截图数量和测量工具由施工方安排,性能指标与专门验证须有实际目标或风险依据。不要求产品或资产已经完成,只有实际执行过才能记录通过。当前采用的设计值明确,后续调优不能代替当前设计。
同一验证的场景、判据和结果在对应 TDD 分册完整写一次,其他位置引用并只补充独有要求;总册汇总重要结论、缺口与位置,不复制清单。不强制每册单列完整验证章节或新增统一验证表。数据册保留必要数值计算,不重复技术册的行为状态推演;文档验算与实际运行检查不能互相代替。当前采用值不自动成为调优待办,只有具体体验疑虑才记录验证问题,不默认安排替代版本对照。架构与 TDD 对范围外内容简述边界,只有已确定的后续计划或确实约束当前设计的要求才补充,不自动承诺里程碑或扩展实现。规则、模板与样例同步,已有用户项目不批量改写。
TDD 总纲与技术分册把选型限定在 Game Agent 已支持的 Web、Unity、Godot、Cocos Creator 范围,不逐框架分级说明支持程度。新 Web 使用 npm + Vite,二维使用 Phaser 4.2.1,三维按需求选择适用技术栈;依赖由 npm 管理,预览和导出使用包目录下的 `dist/index.html`,运行素材随构建进入 dist。已有工程沿用实际结构,不因模板自动迁移。该约束与 GameAgent 的 `prompts/runtime/texts/direct.json` 工程提示、`src/project/manifest.rs` 脚手架一致;本轮不修改 GameAgent 提示词或运行时。
TDD 按实际内容组织,可重组 GDD 规则而不要求逐段全文复制;来源版本集中在总册记录,设计变化同步受影响正文。取消固定编写顺序、必读样例、统一图表和逐项登记要求。技术分册写行为、工程约束、存档要求及验证,已有接口按契约描述,不为新项目预定代码目录或内部调用。数据分册写全当前内容、数值、关系与文案及可复核验算,不以示例行代替,也不强制写成可加载配置。美术分册写共性表现、对象与状态、用途及验收,已有素材记录真实接入约束,不通用强制帧键、图集、命名或目录。
总册保留当前范围、分册索引、来源版本、实际跨分册约定与重要缺口,不重复生产验收表或以 frozen 标签判定通过。`project/04_tdd/01_技术实现.md`、`02_美术圣经.md`、`03_数据与配表.md`、`总册.md` 四个产物继续保留,不涉及的方向在分册简述原因。十二份 TDD 规则、模板和样例同步这一口径;星露谷样例聚焦首个日常原型,数值与素材规格标明示例假设,未附证据的原作实证、构建通过和资产验收声明清理,规格与验算缺口如实保留。资源 ID、路径、阶段注入及 Runtime 的文件存在性检查不变。
数据模板将含义、关系与完整内容合写:少量参数直接列当前值与单位,同结构多条内容可共用属性说明;文案集中列一次,其他位置引用唯一权威定义。固定行为写在相关规则中,是否配置化由施工方选择,不为未来可能变更承诺尚未定义的模式或开关。派生值保留计算关系,不重复登记;已有数据格式或明确要求交付可加载数据时,才按实际契约补充字段、类型、默认或必填规则。当前范围完整内容、文案和验算要求保留。
写作规则由 `system-prompt.md`、`phase-context/`、资源目录登记的 `skills/` 与各分册承接;重复且过期的 `resources/SKILL.md` 合并总稿已删除,历史由 Git 保留。
顶层设计将概念展开为游玩过程、关键规则与反馈、资源与进展、选择后果、版本范围及验证计划。规则、模板和样例按实际玩法组织,不强制三层循环、三种互相供给的回报、资源“来源—储存—消耗”链、日历节奏、反馈层数、唯一原型片段或失败三选一;最优解是否成立取决于玩法,必要的规则与参数可以在顶层明确。保留核心定位及按需说明的易混淆方向与排除理由,不强制固定句式或结尾重复定稿。
原型范围由要验证的问题决定,与完整版本范围分别说明;验证可以结合试玩观察、完成情况、玩家反馈和指标,明确如何形成判断,不把未验证的预期写成结论。星露谷样例按选择双方的收益与代价比较,按场景说明损失与恢复,并区分单日、多日和后续内容的验证范围。
架构层承接顶层已有的玩法描述、能力范围、版本边界和验证计划,按实际职责、状态与数据边界拆分或合并系统,不要求顶层清单逐项对应系统。规则、模板与样例围绕系统职责、协作和数据归属、系统文档映射、实现范围及验证组织;取消固定系统数量、每个系统必须属于 P0、“不负责”必填列、图表格式、状态分类、数值基准分类和逐轮变更记录。重要取舍按需保留依据。
拆分应带来有用的独立规则边界,不因变量、操作不同或未来可能替换就单独设系统;简单职责可在同一系统内部表达。架构概述关键协作,保留影响结果的顺序与共享约束,职责、数据归属和文档位置可合写,不重复建表。完整行为流程在主要负责的系统文档展开,其他参与方说明自身接收、处理与返回,按需引用完整流程并就近保留理解本系统所需的前提和结果。
系统交互区分调用、通知、读取与玩法反馈;双向关系不自动判为架构错误,按实际问题检查职责纠缠和更新顺序。主数据归属明确,实际需要的副本或快照说明来源与预期结果,不要求额外设计更新机制。系统文档展开行为,TDD 收编并补齐设计要求、内容与数值及实际约束,内部实现由施工方决定。
保留 Sxx 编号与 `project/03_systems/...` 文档位置,可以按内容合并或拆分文档;该映射不决定代码目录,代码模块与文件组织由施工方选择。实现范围区分完整版本、首个原型及后续内容,不固定 P0/P1/P2,也不要求验证失败就退回顶层或禁止调整系统划分。星露谷样例明确 NPC 日程、资源点、物品与经济等归属,首个原型包含基础采集,按单日选择和多日成长分别验证。
系统总纲、相关系统类型资料与 TDD 的直接引用同步承接职责、协作、数据归属和当前实现范围,不再依赖架构 P0 清单、固定依赖图或仅限定性的数值基准。数据侧按实际玩法选择验算场景与跨度;技术侧按当前施工范围收编全部必需行为,不能因某系统标为后续优先级而遗漏当前所需规格。速览卡与 TDD 样例同步首个原型范围,基础采集、跨日结算规格和完整数值验算的缺口如实标明,局部推算不作为验算通过的证据。施工完备要求、产物路径与阶段审批合同保持不变。
系统层按实际行为展开职责、触发条件、状态变化、结果、协作与反馈,类型规则、模板和样例按需使用,不要求先归入十二类之一。取消固定十二节、顶层条目编号、取舍表列、循环层级、“三不”、全部枚举及禁止段落等填写纪律;未决事项按影响处理,不只保留结构问题,也不把全部数值问题自动推给原型。代表性验证场景说明预期结果和判断依据,不冒充已完成验证。
这些维度是信息覆盖要求,不是独立章节要求。参与方、数据来源、处理顺序和反馈可随行为一次说明,已讲清的内容不再另列协作表或反馈章节;简单同步处理不额外设计消息、确认或中间状态。相关类型模板将协作与反馈并入具体行为,架构样例合并职责与文档映射、战斗样例将协作归属就近写入行为。去重不降低关键行为边界、结果一致性和 TDD 独立施工要求,不改写已有项目产物。
十二类系统资料保留类型特有的设计问题,模板不预填未经选择的动作、状态、循环和数据表。日历、生产队列、成长分支、复杂叙事、节日专属玩法和战斗定位均由实际项目决定;系统职责与权威数据归属遵循架构,UI 可以维护交互、导航和临时状态,正式玩法校验与结算仍由对应系统负责。跨系统行动明确整体成功或失败的预期结果,TDD 收编设计约束,具体实现机制由施工方选择。
系统文档保留已定规则、单位和参数,TDD 从相关内容收编并补齐设计与实际接入缺口,不依赖固定交接章节。系统编号、文档位置、资源登记及审批语义不变。战斗样例明确为后续矿井原型的暂定方案:实时基础动作、安全撤退保留成果、倒下可能损失部分钱物,未定设计和待执行验证如实标明;TDD 日常原型样例不提前收编后续战斗。样例中的代码、数据和资源组织不再作为预先设计或验收要求,仍缺少的玩法、内容、数值及表现如实保留。
进入下一阶段必须由用户批准触发。Runtime 推进后向 Agent 追加明确的用户行为语义,例如“用户已批准上一阶段,现在进入顶层设计阶段”,避免 Agent 误认为 Runtime 自行推进。
@@ -327,7 +390,7 @@ UI 使用“批准”和“继续修改”两个文字按钮,分别配 Lucide
## 8. 用户工作区浏览
`design_artifacts` 同时是 Agent 工作区和用户查看策划资料的文件区。用户不需要通过聊天请求 Agent 才能看到文件。
`design_artifacts` 是用户查看策划资料的文件区,也是 Agent 的默认工作区。用户不需要通过聊天请求 Agent 才能看到这里的文件。用户文件树只列出该目录。Agent 文件工具还可以读写绝对路径和该目录之外的路径,那些路径不因此出现在用户文件树中。
第一版提供只读浏览:
@@ -383,7 +446,7 @@ UI 使用“批准”和“继续修改”两个文字按钮,分别配 Lucide
1. 核对原型行为基线,选择生产 Provider、恢复、审计、文件和事件通信的可复用函数;仅拆分实际阻碍复用的局部业务耦合。
2. 新增独立的设计会话状态结构和持久化路径。
3. 新增自由策划 Agent Provider 回合循环。
4. 将文件工具绑定到 `design_artifacts`,移除旧 Planning V2 的业务产物门禁。
4. 将文件工具绑定到 `design_artifacts`,移除旧 Planning V2 的业务产物门禁。2026-09-29 起,这一绑定只保留给用户文件浏览和附件导入;策划文件工具同时接受绝对路径和 `design_artifacts` 之外的路径。
5. 接入阶段提示、必读资源注入和 `get_workflow_status`。
6. 接入 `submit_phase_for_approval` 和 ✅/❌ 审批事件。
7. 复用 Game Agent 文件浏览实现,让用户查看 `design_artifacts` 文件。
@@ -209,7 +209,7 @@ cargo test -p spacetime-module web_project --manifest-path server-rs/Cargo.toml
```bash
npm run test -- src/services/sseStream.test.ts
npm run test -- src/components/editor/agent
npm run test -- src/services/image-editor/editorAgentClient.test.ts # 2026-09-24 修订:`src/components/editor/agent` 已随旧创作链路退役,现行画布 Agent 用例在 image-editor 目录
npm run typecheck
npm run check:encoding
git diff --check