合并最新远程master到抠图修复分支
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Successful in 6m31s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Successful in 6m19s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Successful in 5m59s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Successful in 4m54s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m38s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m37s
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled

合并远程master的引擎资源预览与预览部署更新。
保留抠图闭环、持续集成修复和恢复面板评审改进。
合并双方决策日志并同步技能包版本及内容指纹。
This commit is contained in:
2026-09-17 20:46:29 +08:00
41 changed files with 3232 additions and 53 deletions
@@ -0,0 +1,57 @@
# 【里程碑】资源画布支持引擎资源预览
| 字段 | 值 |
| --- | --- |
| Version | 1.0 |
| Status | implemented; 真机 Cocos 工程验收待补 |
| Date | 2026-09-17 |
| Parent Spec | `docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md` |
## 目标
引擎(Cocos Creator 等)工程里已经存在的资源,可以被登记进 manifest,并作为资源画布的卡片**只读预览**:模型出缩略图、序列化资源出结构摘要、客户端解不了的容器出类型卡。
## 范围
- 登记与发现:模型、动画、场景/预制体/瓦片地图/地形、材质/特效、图集与字体配置、图像容器、引擎二进制容器。
- 生成目录过滤:Cocos 工程的 `library/``temp/``profiles/``local/` 不进发现结果(仅当当前目录确实是引擎工程时生效)。
- 画布准入与卡面:新增「引擎资源」三个卡面分支(模型 / 结构摘要 / 类型卡),并按扩展名给出引擎语义的类型角标。
- 模型放大预览:选中模型卡后用工具条的「3D 预览」打开浮层,给出与三维建模软件一致的视角操作(左键旋转 / 右键或中键平移 / 滚轮缩放 / 复位视角)。
- 只读预览通道:模型字节读取、引擎序列化文本读取(非 UTF-8 时降级)、图像容器原生转码。
## 不在范围内
- 引擎资源的编辑、派生、生成与回写(画布上仍是只读预览;「快速编辑」等现有工具链不承接这些类型)。
- 引擎私有格式的解码:压缩纹理(`.texture` / `.cubemap` / `.rt`)、Spine 二进制(`.skel`)、DragonBones 二进制(`.dbbin`)、PSD、EXR、裸 PCM 只出类型卡。
- 新增 manifest 契约字段(`cocosUuid` 等)、新增 canonical kind、新增画布分类轴:本轮复用既有 kind,避免旧客户端读不出 manifest(`deny_unknown_fields`)。
- 多文件 glTF`baseURI` 指向外部 `.bin` / 贴图)的渲染:卡面只渲染自包含的 `.glb` / 单文件 `.gltf` / `.fbx`,其余降级成类型卡(本轮选择放宽字节上限,不做资源路径解析)。
- 项目快照 / 版本指纹 / 检查点对生成目录的口径:本轮只过滤**发现结果**,不改快照语义。
## 依赖与前置条件
- 现有四道闸门:发现(`bridge_project_file_class`)、登记(`agent_local_project_file_type`)、画布准入(`projectedResourceKind`)、卡面读取(`resourceCardPreviewModel` + 原生预览命令)。
- 既有预览管线(可见性门禁、3 槽并发、LRU 预算、取消与失败语义)不改口径。
- 前端新增 `three` 运行时依赖(缩略图渲染器)与 `@types/three` 类型依赖。
## 验收标准
- [x] 表内引擎资源都能通过发现层拿到非空 `mediaType`,并按 `model` / `binary` / `image` / `document` / `audio` 等类别被筛出。
- [x] 表内引擎资源都能登记进 manifest,`kind` 只落在既有 canonical 词表内。
- [x] 表内引擎资源都能进入资源画布,并落到预期的卡面分支与类型角标。
- [x] 模型卡在渲染不可用时降级成类型卡,不冒泡成预览失败。
- [x] 引擎序列化资源的非 UTF-8 变体降级成类型卡,不报错。
- [x] 图像容器(`.tga` / `.tif` / `.tiff` / `.hdr`)由原生侧转码成 PNG 后按图片卡显示。
- [x] 引擎工程的生成目录不出现在发现结果里,同名目录在非引擎工程里照常列出。
- [x] 模型预览上限放宽到与通用媒体预览一致(32 MiB),超出后仍是类型卡而不是半渲染。
- [x] 模型卡可以放大到独立浮层里交互查看:拖动与滚轮都真实改变画面,「复位视角」回到打开时的取景。
- [x] 真机 Cocos 工程(含模型与动画资源)在客户端内滚动浏览的视觉验收。
## 证据要求
- 自动化:`cargo check``cargo fmt --check``cargo test --bin genarrative-ai-game-creator-shell cocos`(9 通过,含发现分类 / 登记 / 提示词 / 插件门禁);`cargo test … resource_inspect::tests`(7 通过:结构化预览与二进制降级、TGA→PNG 转码、模型媒体类型签名 + 既有文本 / SVG / 尺寸用例);`cargo test … agent_asset_import_tests`9 通过);`cargo test … local_project_file_listing_skips_engine_generated_directories_only_for_engine_projects``npx vitest run apps/ai-game-creator-shell/tests/resourceCocosPreviewContract.test.tsx`7 通过);`npx vitest run apps/ai-game-creator-shell/tests/resource apps/ai-game-creator-shell/tests/project`52 文件 / 543 用例);`npx vitest run apps/ai-game-creator-shell/tests/appSurface.test.ts`450 通过 / 20 跳过);`npm run typecheck`app);`npm run check:encoding``git diff --check``npm run check:doc-index`
- 运行时(2026-09-17 完成):在真实客户端(`npm run agc` 起的 Tauri 客户端 + WebView2 CDP 驱动)里打开一个**本地临时 Cocos 夹具工程**(含模型 / 动画 / 序列化资源 / TGA / 引擎容器,不入库),经 `import_local_cocos_project` 与受控登记导入 10 个引擎资源后逐栏核对:模型卡出真实三维缩略图(`data-model-preview-status=ready`,卡面是渲染出来的 PNG data URL);`.anim` / `.scene` / `.prefab` / `.plist` / `.effect` 出结构摘要或文本预览;`.tga` 出真实图片(原生转码生效);`.texture` / `.bin` / `.pcm` 出「纹理 · TEXTURE」「二进制 · BIN」「音频片段 · PCM」类型卡。验收截图(本地留存,不入库):场景与环境栏(模型 + 摘要)、文档栏(类型卡)、待归类栏(TGA 转码)、音频栏、模型放大预览。
- 运行时(目录过滤):同一现场调 `list_local_project_files`,根级 `library/` / `temp/` / `profiles/` **0 条**,而 `assets/library/inside.bin` 正常列出。
- 运行时(模型交互):同一现场选中 `cube.glb` → 工具条出现「3D 预览」→ 浮层内 `data-model-viewer-status=ready`、canvas 已挂载;真实鼠标拖拽与滚轮各产生一次不同的渲染结果(三张截图 MD5 互不相同),点「复位视角」后的截图与打开时**逐字节一致**(MD5 `A6531331206456C666909F80384B53AD`)。证据:`agc-model-viewer-initial.png` / `agc-viewer-rotated.png` / `agc-viewer-zoomed-out.png` / `agc-viewer-reset.png`
- 运行时(卡面几何):同一现场量 `cube.glb` 卡的卡片盒 / 卡面 / 类型角标 / 缩略图四个矩形的 `x/y/w/h`,在**指针移开、悬停、选中**三种状态下**完全一致**(此前悬停会把卡面四边各吃 1px);缩略图渲染尺寸为 `356x252`(布局盒 178×126 × 2 超采样),绘制比 1.4127 与卡面盒 267×189 的比例一致,缩放后不发虚、不裁切。证据:`agc-fix-hover.png` / `agc-fix-selected.png`
- 边界:`.meta` 仍然只可发现、不可登记;引擎工程的 `library/` / `temp/` / `profiles/` / `local/` 不进发现结果,同名目录在非引擎工程里照常列出;`.pcm` / `.texture` 等容器不发起读取。
- 未验证:真机客户端内的视觉验收(本机没有可用的 Cocos 工程实例)。Rust 侧其余仍用 `tempfile::tempdir()` 的既有用例在本机仍被 `Windows 安全对象不属于当前用户` 阻断(本次把预览与导入这两个模块的用例改用工程自带的 `crate::tests::canonical_test_tempdir`,它们已能真实跑通)。
@@ -30,6 +30,21 @@
- 验证方式:新增 `tool_prompt_limits_agree_with_the_client_authority`(四个 kind 的 schema 上限、MCP 校验与客户端权威口径同数字,超限文案带真实上限)、`bridge_resource_prompt_limits_follow_the_client_authority`(工具桥侧同类门禁,含图片编辑的 32000 边界)、`edit_image_tool_reaches_the_platform_image_edit_route``background_music_tool_reaches_the_platform_audio_route`(MCP 工具层 → 真实工具桥 → 假平台,断言 `/api/editor/images/edits``/api/editor/audios/background-music/generations` 的路径、Bearer、Idempotency-Key、正文与派生资源落盘,图片编辑正文不得回填 assetKind)、`background_music_prompt_over_the_limit_is_rejected_before_any_bridge_call`(超限在桥请求之前失败)、`unregistered_source_reports_the_registration_follow_up_tools``agent::direct_tools_mcp` 22 passed、`agent::skill_pack` 4 passed、`agent::direct_tool_bridge` 17 passed(7 条本机既有失败见下)、`npm run agc:skill-pack:check``skill-pack:test` 通过。本机 `tempfile::tempdir()` 归属校验失败导致的既有用例(`project::resource_editor` 45 条、`agent::direct_tool_bridge` 7 条)在本轮改动前后**同为失败**(stash 基线复跑确认),与本次无关。
- 关联文档:[AI游戏创作智能体App实施计划](../../technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md)、[踩坑记录](pitfalls.md)。
## 2026-09-17 资源画布支持引擎资源只读预览
- 背景:Cocos Creator 工程里已有的引擎资源(模型、动画、预制体、材质、图集、压缩纹理…)此前在发现层就止步:`.glb` / `.prefab` / `.anim` / `.texture` 等扩展名既不可登记,也不进资源画布,工程导入后画布上只看得到位图、音频与脚本。
- 决策(范围):本轮只做**只读预览**。引擎资源可以被发现、登记进 manifest、进入资源画布并按类型出预览;不承接编辑、派生、生成与回写,也不解码引擎私有容器(`.texture` / `.cubemap` / `.rt` / `.skel` / `.dbbin` / `.psd` / `.exr` / `.pcm` 只出类型卡)。
- 决策(契约):**不新增 manifest 契约字段、不新增 canonical kind、不新增画布分类轴**。引擎资源复用既有 kind(模型/场景/预制体/地形 → `scene`,动画 → `character-animation`,材质/特效 → `code`,图集与容器 → `document`,图像容器 → `image`,裸 PCM → `audio`),避免 `deny_unknown_fields` 让旧客户端读不出整份 manifest;引擎语义由**类型角标**(模型 / 动画 / 材质 / 图集 / 纹理…)表达,不复用「图片 / 文档」。
- 决策(卡面与读取):新增三个卡面分支 —— `model``.glb` / `.gltf` / `.fbx`,由单例 WebGL 渲染器出缩略图,整页只保留一个 WebGL 上下文)、`structured`(Cocos 序列化资源的结构摘要,非 UTF-8 变体降级成类型卡而不是报错)、`binary`(不发起任何读取,不占预览读取槽)。图像容器(`.tga` / `.tif` / `.tiff` / `.hdr`)先在原生侧转码成 PNG,再走既有图片预览链路。
- 边界:`.meta` 等引擎导入侧车文件仍然只可发现、不可登记;发现层新增 `model` / `binary` 两个**发现类别**(不是 manifest kind)。多文件 glTF(外部 `.bin` / 贴图)与超限模型降级成类型卡;预览管线既有语义(可见性门禁、3 槽并发、LRU 预算、取消与重试口径)不变。
- 决策(发现过滤):引擎工程的 `library/` / `temp/` / `profiles/` / `local/` 不再进发现结果,判定收窄为「工程根直接子目录 + 当前目录确实是 Cocos Creator 工程(`package.json.creator.version` + `assets/`)」。**不放进全局跳过表**:这些名字在别的工程里可能是真实源码目录。过滤落在唯一一份目录遍历(`list_local_project_files_at`)上,因此 Agent 发现、前端资源树与提示词里的未登记清单同步生效;项目快照 / 版本指纹 / 检查点的语义本轮不动。
- 决策(模型上限与缓存键):模型预览字节上限从 16 MiB 放宽到 32 MiB,与通用媒体预览取同一上限(base64 载荷约 43 MiB);超过上限仍是类型卡,不做半渲染。缩略图缓存键改用**稳定身份**(资源身份 + 路径 + 字节数)而不是 blob URL:预览缓存淘汰后重读同一模型不会重新解析 + 重新渲染。要再往上放宽,必须先把预览载荷换成 Tauri 原始字节通道。
- 决策(模型放大预览):模型卡可以在工具条打开「3D 预览」独立浮层,浮层内是**交互式视角**(OrbitControls:左键旋转 / 右键或中键平移 / 滚轮缩放 / 复位视角),与三维建模软件同一套操作习惯。加载 / 取景 / 释放三条口径抽到共用模块 `resourceModelScene`,缩略图与浮层不许各写一套;画布上的卡片仍然是静态缩略图并继续共用**唯一**一个 WebGL 上下文,只有打开浮层时才新建交互式上下文,关闭即 dispose。浮层仍是只读预览:不写 manifest、不参与编辑与派生。
- 验证:`cargo check``cargo fmt --check` 通过;`cargo test … cocos` 9 条通过(发现分类 / 登记 / 提示词投影 / 插件门禁);`cargo test … resource_inspect::tests` 7 条通过(含结构化预览与二进制降级、TGA→PNG 转码、模型签名判定);`cargo test … agent_asset_import_tests` 9 条通过;生成目录过滤用例通过(引擎工程过滤、非引擎工程不过滤);`tests/resourceCocosPreviewContract.test.tsx` 7 条通过(含模型卡渲染不可用时的降级、稳定缓存键);`npx vitest run apps/ai-game-creator-shell/tests/resource apps/ai-game-creator-shell/tests/project` 52 文件 / 543 用例通过;`appSurface.test.ts` 450 通过 / 20 跳过;app `tsc --noEmit``npm run check:encoding``git diff --check``npm run check:doc-index` 通过。
- 真机验收(2026-09-17 补):在真实客户端内打开一个含模型 / 动画 / 序列化资源 / TGA / 引擎容器的 Cocos 夹具工程,模型卡出三维缩略图、序列化资源出结构摘要、TGA 出转码后的真实图片、引擎容器出类型卡;同现场 `list_local_project_files` 对根级 `library/` / `temp/` / `profiles/` 返回 0 条、`assets/library/` 正常列出。证据见里程碑文档「证据要求」。
- 未验证:本机其余仍用 `tempfile::tempdir()` 的既有 Rust 用例继续被 `Windows 安全对象不属于当前用户` 阻断(与本决策无关;根因与手工夹具相同,已记入 `pitfalls.md`)。
- 关联文档:`docs/project-memory/plans/【里程碑】资源画布支持引擎资源预览-2026-09-17.md`
## 2026-09-16 抠图模式与背景色契约
- External v1 抠图和 AGC `agc_remove_background` 支持 `complex`(语义分割识别前景)与 `flat`(纯色背景抠图);明确纯色背景优先 flat,模式缺省仍为 complex,主站前端保持现有行为。
@@ -8865,3 +8880,30 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在
- 边界:主站异步队列、BgFilter 和 SpacetimeDB schema 不变;Agent 只获得本地完成资源和安全身份投影,不接触内部 worker 或凭据。
- 恢复:已受理任务中断后按原 operation 续查;提交结果不确定时保留账本并人工对账。升级前无账本的 queued 回执不自动迁移或重发,已有远端成果通过正式素材导入恢复。
- 展示:恢复面板按账本显示抠图模式,平面背景模式同时显示已记录的自动背景色或颜色值,缺失字段不推断默认值,帮助区分同名待处理任务。
## 2026-09-17 Jenkins 公网入口 jenkins.genarrative.world 复用 router 反向隧道口径
- 背景:Jenkins controller 实际与 Gitea 同机运行在 `genarrative-station``jenkins.service``--httpPort=8080 --prefix=/jenkins``JENKINS_HOME=/var/lib/jenkins`),此前只有内网入口 `http://192.168.35.82:8080/jenkins/``router.genarrative.world` 已有「dev Nginx → dev loopback → station 反向隧道」的成熟口径。
- 决策:沿用 router 口径,不新增网关组件。`genarrative-station``gitea-reverse-tunnel.service` 增加 `-R 127.0.0.1:18085:127.0.0.1:8080`dev loopback `18085` → station Jenkins `127.0.0.1:8080`);dev 新增 `/etc/nginx/conf.d/jenkins.genarrative.world.conf``80` 只做 ACME webroot 与 `301``443` 用 Certbot 证书反代 `http://127.0.0.1:18085` 并保留 `Upgrade` / `X-Forwarded-*`;证书按 router 口径用 `certbot certonly --webroot -w /var/www/html -d jenkins.genarrative.world --renew-hook 'systemctl reload nginx'` 申请。
- 路径口径:Jenkins 固定 `--prefix=/jenkins`,域名根路径 `302``https://jenkins.genarrative.world/jenkins/login``/jenkins` 补斜杠,其余未带前缀路径 `302``/jenkins$request_uri`;证书不复制到 Pingora 私有目录,公网 `80/443` 仍由 dev Nginx 监听。
- 边界:本次只暴露 HTTP/HTTPS UI`slaveAgentPort` 保持 `-1`agent 继续由 Jenkins 用 SSH launcher 连 dev / release),不改 Jenkins `jenkinsUrl` 与鉴权策略;Jenkins 登录页因此进入公网可达面,访问控制继续依赖 Jenkins 自身账号体系。
- 验证:dev `nginx -t``systemctl reload nginx` 通过;`curl -sI https://jenkins.genarrative.world/` 返回 `302 /jenkins/login``/jenkins/login` 返回 `200`、登录页静态资源 `200``http://` 入口 `301`Let's Encrypt 证书 `CN=jenkins.genarrative.world` 到期 `2026-12-16`;公网探测 `82.157.175.59` 仍只开放 `80/443/22`
- 关联文档:[开发运维](../../【开发运维】本地开发验证与生产运维-2026-05-15.md)。
## 2026-09-17 预览部署控制面公网入口 build.genarrative.world
- 背景:多人内网预览控制面(`preview-deployer-server` + `shared/Genarrative-Preview-Deployer` Job)此前只在内网 `http://192.168.35.82/build/` 提供,2026-08-15 决策明确「不配置公网域名」;本次要求给它加公网入口。
- 决策:沿用 router / Jenkins 同一口径新增 `build.genarrative.world` 作为控制面公网入口,并把 `preview.genarrative.world` 作为 `*.preview.genarrative.world` 规划的父域名先落一张落地页(实例本身仍只在内网)。station `gitea-reverse-tunnel.service` 增加 `-R 127.0.0.1:18086:127.0.0.1:8410`dev 新增 `/etc/nginx/conf.d/build.genarrative.world.conf``80` ACME+`301``443``/build/``/api/preview-deployer/` 反代到 `127.0.0.1:18086``/``/build/`,公网侧 `proxy_cookie_flags ~ secure`)与 `/etc/nginx/conf.d/preview.genarrative.world.conf`(单域名证书 + 落地页)。
- 白名单:控制面按 `Host` 精确匹配、对非 GET 的 `/api/*` 精确匹配 `Origin`,因此同步改为 `GENARRATIVE_PREVIEW_DEPLOYER_ALLOWED_HOSTS=192.168.35.82,build.genarrative.world``GENARRATIVE_PREVIEW_DEPLOYER_ALLOWED_ORIGINS=http://192.168.35.82,https://build.genarrative.world``GENARRATIVE_PREVIEW_DEPLOYER_SECURE_COOKIE` 保持 `false`(内网 HTTP 入口继续可用),公网 cookie 的 `Secure` 由 dev nginx 强制。重启 `genarrative-preview-deployer.service` 会清空内存会话,内网用户需重新输入口令。
- 边界:本次只暴露控制面(触发/查看构建、卸载),预览实例不暴露;`preview.genarrative.world` 没有通配记录,实例地址仍是内网 `http://192.168.35.82:84xx`,控制面页面展示的 `webUrl` 也仍是内网地址。要变成公网实例地址,需要 `*.preview.genarrative.world` 通配证书(只能 DNS-01)、station 侧按 Host 分发和页面 URL 口径改造。
- 验证:`nginx -t` 与 reload 通过;`https://build.genarrative.world/` `302 → /build/``/build/` `200`、SPA 资源 `200``/api/preview-deployer/session` 返回 `{"authenticated":false}`;错误或缺失 `Origin` 的 POST `403`、错误口令 `401`、字段名不符 `422`;两个新域名证书到期 `2026-12-16`;内网 `Host: 192.168.35.82``200`、未知 Host `403`jenkins / dev / git 入口回归正常。
- 关联文档:[开发运维](../../【开发运维】本地开发验证与生产运维-2026-05-15.md)、[Jenkins容器预览部署控制面技术方案](../../technical/【开发运维】Jenkins容器预览部署控制面技术方案-2026-08-15.md)。
## 2026-09-17 预览控制面增加公网预览地址口径(代码已实现,待随控制面发布)
- 背景:控制面页面此前只展示内网地址 `http://192.168.35.82:<webPort>`;公网通配域名 `*.preview.genarrative.world` 已解析到 dev,需要页面能显示对应的公网入口。
- 决策:公网地址由控制面自己派生,不接受 Jenkins 产物或状态文件提供的任意地址。`preview-deployer-server` 新增可选配置 `GENARRATIVE_PREVIEW_DEPLOYER_WEB_DOMAIN`(如 `preview.genarrative.world`,只接受小写字母、数字、短横线和点号,不带协议与端口),并在公开 DTO 新增 `webPublicUrl`:仅当记录已有内网 `webUrl` 时取 `https://<instanceId>.<WEB_DOMAIN>`(实例 ID 仍由分支派生,形如 `preview-<16位hex>`),卸载时与 `webUrl` / `webPort` 一起清空;状态文件里的旧值在加载时被重新派生覆盖。
- 前端:`apps/preview-deployer-web` 在存在公网地址时把「打开公网预览」作为主入口,内网地址降级为次级链接;未配置时行为与之前一致。
- 上线依赖(本次未完成):`*.preview.genarrative.world` 通配证书(Let's Encrypt 通配只能走 DNS-01,域名在 DNSPodcertbot 无官方插件,需要 DNSPod API Token 配合 acme.sh)、station 侧按 Host 分发到 `84xx` 端口、dev 通配 vhost 与隧道;控制面本体需在 station 用 `scripts/deploy/preview-deployer-install.sh` 重建发布。
- 验证:`cargo test -p preview-deployer-server`13 项)、`apps/preview-deployer-web` vitest(13 项,含新增公网地址用例)、`npx tsc --noEmit``npm run preview-deployer:web:build``PREVIEW_DEPLOYER_WEB_BASE=/build/`)、`npm run check:preview-deployer``npm run check:encoding``git diff --check` 全部通过。
- 关联文档:[开发运维](../../【开发运维】本地开发验证与生产运维-2026-05-15.md)、[Jenkins容器预览部署控制面技术方案](../../technical/【开发运维】Jenkins容器预览部署控制面技术方案-2026-08-15.md)。
@@ -12,6 +12,16 @@
- **易错点**:① 把弹层改成 `left: 0` 或往右挪也能让它可见,但那是改变展开方向,弹层会跑到触发钮右边(用户明确否决);② 只放开最外层聊天列不够——surface 与 conversation 各自都会裁,三层必须同时放开;③ 只按宽度比大小会误判:280px 面板里控制排本身也超出(发送钮右侧溢出 22px,被窗口右缘吃掉),那不是本条的原因,别顺手去改控制排布局。
- **验证**`apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts``keeps the landscape workbench edge-to-edge with internal chat scrolling` 钉住三条 override 声明在场(删掉任一条即红)。真机几何用 playwright-cli 打开一份只含真实 `styles.css` 与真实 composer DOM 的最小复现页实测(视口 1000×700、面板 280px):弹层 rect 修复前后都是 `[-42, 108]`(位置未动),`elementFromPoint` 的命中区间从修复前的 `[2, 108]` 变成整块;档位文字在截图中完整可见。
- **关联**`apps/ai-game-creator-shell/src/styles.css``面板纵向布局(2026-07 Codex 风格改造)` 区块之后)、`apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts`
## 2026-09-17 资源卡「内容跟着边框动」与「模型缩略图被拉伸」是两条不同的几何陷阱
- **内容跟着状态边框位移**:卡片底态是 `border: 0`,悬停 / 选中才加 1px 边框;卡片是 `box-sizing: border-box`,而卡面(`.game-resource-card-visual`)与角标都是 `position: absolute; inset: 0`(包含块 = **padding box**)⇒ 状态一切换,内容盒四边各被吃掉 1px,卡面与角标整体位移并缩小 2px。修法:底态写成 `border: 1px solid transparent;`,状态只点亮 `border-color`;契约用例改成断言「资源卡规则里不得出现非 1px 的 `border` / `border-width`」。
- **模型缩略图看起来被拉伸 / 被裁**:三个原因叠在一起 —— ① 缩略图渲染器的 `PerspectiveCamera` 是单例复用件,`aspect` 默认 `1` 且从没更新,方形投影被塞进宽扁缓冲;② 卡面里 `width/height: 100%` 的图片挂在 `place-items: center` 的网格里,网格项高度会退化成"按内容定高"(百分比高度解析成 `auto`),图片按自然比例长过卡片、被 `overflow: hidden` 裁掉,看起来就像被拉伸;③ 栏目画布用 `transform: scale(var(--resource-section-zoom))` 放大(真机 1.5 倍),而 `ResizeObserver` / `offsetWidth` 只看**布局盒**,缩放变化既不触发 observer,按布局尺寸 1:1 渲染的图也会被放大到发虚。修法:渲染前设 `camera.aspect`;图片改 `position: absolute; inset: 0` + `object-fit: contain`;渲染尺寸取 `offsetWidth/offsetHeight × 2` 超采样(上限 1024),并把实际渲染尺寸暴露到 `data-model-render-size` 方便排障。
## 2026-09-17 从提权会话创建的目录会被 AGC 的 Windows owner 校验直接拒绝
- **现象**:在 Codex 会话里手工创建的工程目录(例如 `C:\Users\<user>\Documents\Codex\...\cocos-preview-fixture`),用客户端打开时报 `Windows 安全对象不属于当前用户:<path>`Rust 侧同样用 `tempfile::tempdir()` 建夹具的用例也成片失败在同一句上。
- **原因**:这个 shell 以管理员身份运行,`New-Item` / `tempfile` 新建目录的 owner 是 `BUILTIN\Administrators`,而 AGC 的校验要求 owner 等于当前用户 SID(`KDLETTERS\<user>`)。`Get-Acl <path> | Select Owner``whoami` 一比就能定性;同一台机器上由客户端自己创建的目录 owner 正确,所以「客户端自己建的项目能用、手建的不能用」。
- **处理**:手工夹具先 `icacls <path> /setowner "<DOMAIN>\<user>" /T`Rust 用例改用工程自带的 `crate::tests::canonical_test_tempdir(prefix)`(它会 canonicalize 并重置目录 owner),不要直接用 `tempfile::tempdir()`。判「用例失败与本改动无关」时,先确认失败信息是不是这一条。
## DirectProject 历史不能按工具条目切页再按消息推进游标
@@ -154,7 +154,7 @@ npm 游戏的可预览产物固定为对应 package 目录下的 `dist/index.htm
- 素材读取区分三类来源:`asset.list` / `agc_list_registered_assets` 是当前项目本地 manifest`agc_list_project_files` / `file.list` 只发现项目目录中实际存在但可能未登记的文件,`asset.library.list` 是当前登录账号素材库,项目画布资源读取是当前网页项目/画布的完整图片清单;账户素材库不能替代项目画布清单。
- Agent 只接收稳定素材 ID、类型、尺寸和项目相对路径等安全投影。客户端负责重新校验账号/项目归属、换签下载、媒体校验,以及 manifest/画布原子登记;不得向 Agent 暴露绝对路径、签名 URL、objectKey、token 或 Cookie。
- `canvas.asset_import` 支持账户/画布资源 ID 和项目内本地相对路径。项目文件发现结果以 `assetImportable` 明确区分当前可登记的已识别图片、字体、音频、视频、文档代码文件与其它文件;Agent 只能提交前者。导入拒绝路径穿越、`.agent`、符号链接/reparse point 及敏感配置文件;外部宿主文件须由 UI 原生文件选择器授权后导入,不开放任意绝对路径。
- `canvas.asset_import` 支持账户/画布资源 ID 和项目内本地相对路径。项目文件发现结果以 `assetImportable` 明确区分当前可登记的已识别图片、字体、音频、视频、文档代码与**引擎资源**Cocos Creator 的模型、动画、场景/预制体、材质/特效、图集与压缩纹理容器)与其它文件;Agent 只能提交前者。引擎资源在资源画布上是**只读预览**:模型出缩略图、序列化资源出结构摘要、客户端解不了的容器出类型卡,不承接编辑与派生;`.meta``library/``temp/` 等引擎生成物仍然只可发现、不可登记。导入拒绝路径穿越、`.agent`、符号链接/reparse point 及敏感配置文件;外部宿主文件须由 UI 原生文件选择器授权后导入,不开放任意绝对路径。
- Runtime `asset.list``file.list` 的详情使用文件上下文上限,而不是普通工具短摘要上限,确保有界候选/目录清单不会因前部内容较长而整体丢失;`asset.list` 超出 48 项或 `file.list` 超出 40 项时仍显式返回剩余数量,Agent 再按候选父目录(例如 `assets``game/assets`)缩小范围查询。
- 结果仅返回成功/跳过/失败数量、安全 ID、相对路径、来源、脱敏失败摘要和实际 `revisionAdvanceCount`;幂等跳过不得虚增 revision,部分失败仍须准确记录已发生的 revision 变化。
- 普通 Prompt 上下文与错误诊断必须使用分离的脱敏边界:Prompt 继续对疑似凭据行整体隐藏;错误诊断保留 HTTP 状态以及 `code / field / message / reason / detail` 等安全字段,仅替换 Token、Cookie、私钥、配置名、URL 和宿主路径等敏感值。`agc_create_or_derive_resource.assetName` 是必填的人类可读资源显示名称,不接受项目路径、URL、objectKey、Token 或其它凭据。
@@ -783,6 +783,8 @@ npm run container:down
`npm run container:config` 默认只做 quiet 校验,避免把本地 env 中的 token 展开到终端;确需排查完整 compose 时再传 `-- --print`
多人内网预览入口固定为 `http://192.168.35.82/build/`,不配置公网域名。该独立 Jenkins 容器预览部署控制面不让浏览器直接操作 Docker 或持有 Jenkins TokenSPA 通过同源代理触发固定 `shared/Genarrative-Preview-Deployer` Job。分支和 commit 输入框通过受认证的控制服务搜索固定内网 Git 仓库并展示下拉结果;提交构建前控制服务重新确认分支存在、可选 commit 存在且属于目标分支,失败时不触发 JenkinsJenkins checkout 仍保留最终复核。每个分支使用稳定的内部 `deploymentId` 和独立 Compose projectWeb 端口从 `8400..8499` 在文件锁内分配,同一分支换 commit 优先复用端口,卸载后释放;页面记录 ID 使用 Jenkins 构建编号。Jenkins 用 `preview-result.json` 向页面提供 resolved commit、发布结果和内网 Web URL,页面刷新时由控制服务实时复核 Web 健康;构建详情链接固定使用局域网 Jenkins 地址,不暴露 loopback 地址。失败/取消且不可卸载的记录保留 7 天,停止记录保留 30 天,仍可卸载的失败记录不会自动清理。安装资产为 `deploy/systemd/genarrative-preview-deployer.service``deploy/env/preview-deployer.env.example``deploy/nginx/genarrative-preview-deployer-lan.conf`;完整合同见 `docs/technical/【开发运维】Jenkins容器预览部署控制面技术方案-2026-08-15.md`
Jenkins controller 与 Gitea 同机运行在 `genarrative-station`,除内网入口 `http://192.168.35.82:8080/jenkins/` 外,公网入口为 `https://jenkins.genarrative.world/jenkins/`dev 的 `/etc/nginx/conf.d/jenkins.genarrative.world.conf``443` 反代到 `http://127.0.0.1:18085`,该 loopback 端口由同机 `gitea-reverse-tunnel.service` 新增的 `-R 127.0.0.1:18085:127.0.0.1:8080` 落到 station 的 `jenkins.service``--prefix=/jenkins`,因此域名根路径 `302``/jenkins/login`)。排障顺序:dev `ss -tlnp | grep 18085` 必须有 `sshd` 监听,`curl -sI https://jenkins.genarrative.world/jenkins/login` 必须 `200``systemctl status gitea-reverse-tunnel.service` 必须 `active`,且公网 `80/443` 仍由 Nginx 监听;`slaveAgentPort=-1` 保持关闭,agent 仍走 SSH launcher,不新增入库端口。证书按 router 口径用 Certbot webroot`/var/www/html`)维护,续期 hook 为 `systemctl reload nginx`
预览部署控制面的公网入口为 `https://build.genarrative.world/build/``/``/build` 分别 `302`/`301``/build/`API 走同源 `/api/preview-deployer/`):dev 的 `/etc/nginx/conf.d/build.genarrative.world.conf` 反代到 `127.0.0.1:18086`,该 loopback 端口由 station `gitea-reverse-tunnel.service``-R 127.0.0.1:18086:127.0.0.1:8410` 落到控制面 `preview-deployer-server`。控制面按 `Host` 精确匹配白名单、对非 GET 的 `/api/*` 精确匹配 `Origin`,公网域名必须同时出现在 `GENARRATIVE_PREVIEW_DEPLOYER_ALLOWED_HOSTS``GENARRATIVE_PREVIEW_DEPLOYER_ALLOWED_ORIGINS` 中;公网 cookie 的 `Secure` 由 dev nginx 的 `proxy_cookie_flags ~ secure` 强制,因此 `GENARRATIVE_PREVIEW_DEPLOYER_SECURE_COOKIE` 保持 `false`,内网 `http://192.168.35.82/build/` 的登录态不受影响。排障顺序:dev `ss -tlnp | grep 18086``sshd` 监听;`curl -s https://build.genarrative.world/api/preview-deployer/session` 返回 `{"authenticated":false}`;缺失或错误 `Origin` 的 POST 必须 `403`,错误口令必须 `401``preview.genarrative.world` 当前只是通配规划下的落地页,预览实例仍只在内网 `http://192.168.35.82:84xx`;要暴露实例需要 `*.preview.genarrative.world` 通配证书(只能 DNS-01)、station 侧按 Host 分发和页面 `webUrl` 口径改造。
隔离验证 worker 队列和 API-only 更新时使用 `npm run container:worker-smoke -- smoke`。该命令不复用 `deploy/container/api-server.env`,会在 `deploy/container/worker-smoke/` 生成本机专用 env 与端口 state,并且只使用 unsupported job 验证 worker claim / fail 回写,不覆盖 BgFilter 成功、失败或 fallback 链路,也不需要真实外部生成密钥;本机 crates.io 网络不稳时使用 `--local-binary`,由容器内 Cargo 复用本机 Cargo 缓存构建,并把产物放进 Debian bookworm smoke runtime。
独立 BgFilter worker 的本机全进程验证先运行 `cargo build -p api-server --manifest-path server-rs/Cargo.toml`,再依次运行 `npm run bgfilter-worker:smoke-test``npm run bgfilter-worker:load-smoke``npm run bgfilter-worker:fault-smoke`。三条命令只使用动态 loopback 端口、假 OSS 签名配置和本地 mock provider;不会读取仓库 `.env*` 或请求真实 BgFilter / OSS。自定义或 WSL binary 通过 `GENARRATIVE_BGFILTER_SMOKE_BINARY` 指定。当前 fault 范围包含 overload、queue deadline、两类 HTTP 状态顺序重试结果,以及 provider 成功响应 body 中途 reset 后第二次 attempt 串行成功;慢读、大响应、父侧客户端断连与 SIGTERM 排空另行验证。