解决 DirectProject 合并冲突
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Successful in 6m2s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Successful in 5m54s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m20s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m31s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Successful in 5m3s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Successful in 4m49s
Project CI / Frontend tests (pull_request) Failing after 6m56s
Project CI / Repository checks (pull_request) Failing after 6m23s
Project CI / Native shell tests (pull_request) Successful in 10m17s
Project CI / Backend tests (pull_request) Successful in 11m8s
Project CI / AI game creator shell web tests (pull_request) Failing after 6m0s

合并双方 DirectProject 历史分页、锚点闸门与订阅竞态处理。

保留 canonical content 输入、聊天 reducer、附件发送和历史分页行为。

合并 Codex 用户回显过滤、增量脱敏及 Direct 回合完成状态处理。

补齐历史截断、损坏行 fail-closed、首屏 anchor 与 appSurface 定向测试。

同步合并应用壳层、后端绑定和项目文档变更。
This commit is contained in:
2026-09-18 10:47:55 +08:00
258 changed files with 18457 additions and 1876 deletions
@@ -3,6 +3,40 @@
> 用途:记录已经确认、会影响后续开发的长期技术/产品/协作决策。短期讨论不要写在这里。
> 当前口径:历史条目的旧路径、旧版本和已退役对象只用于追溯,不构成现行实现依据;如与当前代码或 `docs/README.md` 冲突,以当前代码和最新专题文档为准。
## 2026-09-18 Provider 瞬态重试次数严格按设置执行(游戏开发 Agent 与策划 Agent 不再被档位收进区间)
- 背景:AGC 客户端此前把 `agentLlm.<agent>.maxRetries` 按运行档位重新收进固定区间——`autonomous-game-build` 档位(自主构建的游戏开发 Agent 及其继承档位的专业子 Agent)被抬到 12~16`standard` 档位(含立项策划入口的策划 Agent 与普通 Agent 对话)被压到最多 3;瞬态分类里的上游 400 还在同一预算上再收窄到 2 次。现场把 `maxRetries` 设成 5 时,游戏开发与策划两条链路都不按设置执行。
- 决策:删除这三处区间限制,`maxRetries` 严格等于允许的物理重试次数(显式 `0` 仍表示不重试)。运行档位只保留「上游 400 是否算瞬态错误」的判定(autonomous 档位才重试 400),不再改写次数:`AGENT_RUNTIME_PROVIDER_TRANSIENT_RETRY_LIMIT``AGENT_RUNTIME_AUTONOMOUS_PROVIDER_TRANSIENT_RETRY_FLOOR``AGENT_RUNTIME_AUTONOMOUS_PROVIDER_TRANSIENT_RETRY_LIMIT``AGENT_RUNTIME_AUTONOMOUS_PROVIDER_UPSTREAM_400_RETRY_LIMIT` 四个常量与 `game_creator_agent_runtime_provider_transient_max_retries_at` 一并删除,换成返回 `{max_retries, retry_upstream_400}``game_creator_agent_runtime_provider_transient_retry_policy_at`
- 边界:只改重试次数的来源。瞬态错误分类(`timeout / connectivity / transport / 408 / 429 / 5xx / empty-response / deserialize / stream-unavailable`,以及 autonomous 档位下的 400)、`retryBackoffMs` 指数退避(仍封顶 30s)、durable retry sidecar / lifecycle / `-transient-N` slot 身份、cancel / steer / goal 门禁、耗尽后的 reconciliation 口径与「泥点不足不重试」都不变;前端设置面板与 `agentLlm.<agent>.maxRetries` 契约不变。
- 验证方式:`provider_transient_retry_` 7 项中重写后的档位用例与 upstream-400 用例通过(断言 `maxRetries` 直取设置值、400 与其它瞬态共用同一预算),`provider_retry_` 其余 26/28 通过;该组 2 项(`provider_transient_retry_transport_failure_closes_then_stable_retry_succeeds``provider_transient_retry_backoff_is_exponential_and_capped_at_thirty_seconds`)与 `provider_retry_waiting_final_reply_*` 2 项在本机改动前后同为失败(`stash` 基线复跑确认,现象是等待自动重试唤醒超时)。本机串行全量套件另有既有环境失败(`tempfile::tempdir()` 归属校验、缺少 npm 构建产物、Windows 启动失败 MessageBox 阻塞 `startup_log_slot_fail_without_path...`);抽查其中 5 项在 `stash` 基线上同样失败,与本次改动无关。仓库 `cargo fmt --check``npm run check:encoding``git diff --check` 通过。
- 关联文档:[AI游戏创作智能体App实施计划](../../technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md)、[踩坑记录](pitfalls.md)。
## 2026-09-17 AGC 抠图提交使用远端画布项目身份
- 背景:AGC 已通过本地项目 ID 建立并持久化本地项目到主站远端画布项目的绑定,但 `agc_remove_background` 提交请求仍把本地 `manifest.project_id` 放入 `projectId``assetFolderId` 已使用远端素材目录 ID。主站因此按项目不存在或不属于当前账号返回 404,主站抠图和 BgFilter 本身均正常。
- 决策:抠图请求及工具回执统一使用 `prepare_external_canvas_generation_context` 返回的远端 `context.project_id`;本地 manifest 项目 ID 只用于绑定键和本地状态,不得作为主站业务请求的 `projectId`
- 验证:客户端定向 Rust 测试、格式、编码和 diff 检查通过;未修改主站路由或 BgFilter。
## 2026-09-17 图集切分模式改为显式声明
## 2026-09-17 DirectProject 首屏历史锚点只认订阅回执的 lastCompletedItemId
- 背景:ADR「首屏历史由 `subscribe` 返回的 `lastCompletedItemId` 锚定,再取最近切片」只落了一半。`DirectThreadManager` 是搬运层,内存里没有「已完成条目」的锚点,`subscribe` 一律返回 `last_completed_item_id: None``commands.rs``subscribe_direct_project_thread` 在为空时用 `read_direct_project_last_item_id_at` 从磁盘回填,所以线上回执里的值是真的(订阅那一刻文件里最后一条可显示条目的原始 item id)。前端侧:首屏一直在 `loadProjectConversation` 里用 `beforeItemId: null` 直接取文件尾一屏,`lastCompletedItemId``1b40f030e` 起不再被任何代码读取。
- 决策(锚点语义):首屏切片的新端(较新一侧)边界就是这个锚点,**含锚点条目本身**;切片命令新增 `throughItemId` 参数表达「取到这条为止」。比锚点更新的条目只从运行态事件来,历史切片与实时流因此不重叠(原来的文件尾读取会把订阅回执之后才完成的条目也拉进历史,与运行态事件同 id 重叠,只靠前端合并兜住)。
- 决策(读取时机):订阅回执到达之前不读首屏,也不退化成「取文件尾」;锚点缺失(订阅不可用 / 失败 / 历史为空)时才按文件尾取尾屏。`/history` 手动重读保持「按当前文件尾取尾屏」的恢复语义,不锚定。
- 决策(翻页不变):向后翻页仍用切片返回的 `firstItemId``beforeItemId`(不含锚点),`hasMore` 与连拉口径不变。
- 影响范围:`agent/direct_project_history.rs`(切片锚点 + `through_item_id` 参数)、`commands.rs``read_direct_project_history_slice` 命令参数)、AGC 前端首屏读取接线与测试骨架。**未改**:DirectRuntime 的 `turn-stream.jsonl` / `tool-calls.jsonl` 写入与进度事件、`list_game_creator_direct_active_turns`、SpacetimeDB 与 HTTP 契约。
- 验证方式(已跑):Rust 侧 `cargo test agent::direct_project_history`(22 passed,含「窗口取到锚点那条、排除比锚点更新的条目、`beforeItemId``throughItemId` 互斥报错」三类用例);前端 `npx vitest run .../directHistoryAnchorGate.test.ts`10 passed)与 appSurface 的 `anchors the first history page at the subscribe receipt instead of the file tail`(全量 475 tests / 457 passed / 17 skipped;唯一失败 `edits the published runtime config without leaking API keys into chat` 与本次改动无关,stash 掉本次前端改动后同样变红);`tsc` / ESLint / prettier / `check:encoding` / `check:doc-index` / `git diff --check` 全绿。变异验证:闸门忽略「已消费」、首屏不等闸门两处改动各自让对应用例变红。
- 关联文档:[ADR](../../adr/【ADR】DirectProject对话历史单一事实源-2026-09-16.md)、[里程碑](../plans/【里程碑】DirectProject聊天真相源收敛-2026-09-16.md)、[实施计划](../plans/【实施计划】DirectProject聊天真相源收敛-2026-09-16.md)。
- 决策:`sliceMode` 在图标图集生成入口成为必填字段且不保留任何默认值。省略、`null` 或空字符串必须在引用解析、定价、入队和 provider / OSS 副作用之前返回 `400``field=sliceMode`);`grid` 必须同时提供 `gridX`/`gridY``connected-components` 不得携带网格尺寸,二者矛盾同样在副作用前失败关闭。
- 决策要求:只有用户或需求明确要求等分网格、固定槽位或指定行列数时才使用 `grid`,且行列数必须来自该需求;自由排布、数量不定或只要求一张图集时显式传 `connected-components`,需要约束素材张数时用 `sliceCount`,不得用网格参数表达张数,也不得用固定 `2×2` 表达“四类素材”。
- 影响面:平台两个图集生成入口(`/api/editor/...``/api/external/v1/editor/...`)、OpenAPI、画板 Agent 工具、画板前端提交计划、AGC 客户端 MCP 工具说明与桥接校验、AGC 原生工具 schema 与观察器、AGC Skill 与外部编辑器 Skill。
- 迁移影响:省略 `sliceMode` 的旧调用方(含已发布但未更新的 AGC 客户端和第三方外部 API 调用方)会在图集生成上收到 `400`;本次同时把仓库内自有调用方改为显式声明,不为旧客户端保留兜底分支。
- 错误可执行性:缺失、空白、未知取值都以 `400` + `field=sliceMode` 返回允许取值和决策分支,`grid` 缺维度提示 `sliceCount` 才是张数约束;`sliceCount` 的公开契约上限与切片上限统一为 `256`(识别数量与目标不一致返回 `422` 并回报实际数量)。
- 反馈闭环:图集生成结果回显生效的 `sliceMode`/`gridX`/`gridY``slicePaths`;严格图集提交前必须证明平台回显的模式(`grid` 时含行列数)与请求显式声明一致,缺失或不一致一律失败关闭。
- 标准美术包:客户端显式声明 `sliceMode=connected-components` + `sliceCount=4`,本地按用途位置写四张 canonical 切片前再次校验数量正好为四,数量不符时失败关闭,禁止截断或补位。
- 测试环境:在提权 shell 的 Windows 主机上,`%TEMP%` 下新建目录的默认所有者是 `BUILTIN\Administrators` 而不是当前 TokenUser,AGC 的所有者校验会拒绝测试自己创建的项目根;测试构建对该情形(仅限 `%TEMP%` 内、且失败原因为所有者不匹配)先按“本调用创建的对象”初始化所有者后重试,临时目录之外的越权所有者继续失败关闭。
- 权威合同:[画板图标素材生成入口设计](../../【编辑器】画板图标素材生成入口设计-2026-06-15.md)。
## 2026-09-17 `agc_tools` 媒体资源提示词上限收敛为单一口径,并按 kind 暴露给模型
- 背景:有人反馈「客户端没法由 agent 调用图片快速编辑功能以及背景音乐生成功能」。核查后工具本身都在(`agc_edit_image` / `agc_create_or_derive_resource`),图片快速编辑在 2026-09-14 的真实项目日志里也有成功记录;但存在三类真实缺陷:① `agc_create_or_derive_resource``prompt` 在 schema 里只声明 4000,真实上限却是按 kind 分的(背景音乐 140、音效 1900、视频/角色动画 4000、图片 32000),MCP 层还额外写死了一条 140 判断,模型从 schema 与 skill 都看不出 140/1900,写一句正常长度的背景音乐描述就当场被拒;② 客户端 UI 用同一口径但会截断并提示,agent 侧却只有硬拒,形成「UI 能做、agent 调不动」的观感;③ `sourceLocalAssetId` 不是已登记资源时只报「不属于当前项目已登记资源」,模型会原地重试而不会先登记。
@@ -13,6 +47,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,主站前端保持现有行为。
@@ -8197,8 +8246,8 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在
## 2026-08-24 AGC Direct 抠图语义工具
- 决策:将 External v1 `/api/external/v1/editor/images/background-removals` 通过 `agc_remove_background` 加入受控 `agc_tools`。工具只接受当前 manifest 的图片 `sourceLocalAssetId` 与结果名称;客户端负责正式 resourceId、画布/素材目录、稳定 operation/idempotency 身份、权限和错误脱敏,不向 Codex 暴露内部 BgFilter worker、凭据或任意 API。
- 约束:异步结果只投影有界队列状态,不允许模型自行构造源 URL 或在不确定提交后更换请求身份;External v1 负责 API Key、幂等接收与统一 operation 查询,客户端不得绕过该契约。
- 决策:将抠图能力通过 `agc_remove_background` 加入受控 `agc_tools`。工具只接受当前 manifest 的图片 `sourceLocalAssetId` 与结果名称;普通登录态使用账号鉴权的 `/api/editor/images/background-removals`ExternalDeveloper 模式使用 External v1 `/api/external/v1/editor/images/background-removals`客户端负责正式 resourceId、画布/素材目录、稳定 operation/idempotency 身份、权限和错误脱敏,不向 Codex 暴露内部 BgFilter worker、凭据或任意 API。
- 约束:异步结果与恢复语义以本文「2026-09-17 AGC 抠图接入本地资源编辑恢复闭环」决策为准。不允许模型自行构造源 URL 或在不确定提交后更换请求身份;两种路由都接收客户端稳定幂等身份,External v1 继续负责 API Key、幂等接收与统一 operation 查询,客户端不得绕过该契约。
## 2026-08-24 资源详情动作、空态滚动与最终图多步恢复
@@ -8834,6 +8883,16 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在
- 决策:采用后台继续运行语义。Direct 回合由进程内项目身份锁持有,页面离开不取消;重进项目通过活动回合只读快照与 Thread Manager bootstrap/consume 恢复忙碌态和进度。左上角面板复用同一快照列出正在运行的 Direct 项目并支持进入。
- 边界:快照不写项目文件、不进入公共 API、不跨应用重启恢复;读取失败保留上一份结果并单独提示,不改写成权限或审批失败。身份锁排他性、付费身份和项目写锁不变。
## 2026-09-17 AGC 模板库落在 oss://agc-dev/templates/
- 背景:AGC 需要「真·游戏模板」库,让用户能浏览、筛选、下载模板并直接由模板创建项目,且模板内容更新不依赖客户端发版。
- 决策:模板库固定在 bucket `agc-dev`endpoint `oss-rg-china-mainland.aliyuncs.com`)的 `templates/` 前缀,公共读;**不再嵌套 `agc/` 这一层**`agc/` 继续只放客户端安装包与 `latest.json` 更新通道)。目录为 `templates/index.json` + `templates/v1/<templateId>/{template.json,template.zip,cover.png}`zip 根等于 AGC 项目根。
- 决策:清单 schema 为 `agc-template-library.v1`,每条模板带 `tags``coverKey/coverWidth/coverHeight/coverSha256``zipKey/zipSizeBytes/zipSha256``templateVersion`;客户端只信「受信任 OSS 主机 + 对象键」自行拼 URL,清单里的地址字段不参与请求。
- 决策:客户端缓存与安装根为 `<app_data>/templates/``index.json` 缓存 + `installed/<id>/<version>/`,安装完成才写 `installed.json`);建项目在 `<app_data>/projects/` 下走既有自动工作区规则,先铺模板文件再补 `.agent` 清单。
- 决策:模板库首页推荐位替换原「灵感推荐」本机图片目录(已删除 `InspirationGallery.tsx``assets/inspiration/`);左侧导航新增模板库入口,打开独立全屏页。`tauri.conf.json``img-src` 放行受信任 OSS 主机用于封面图。
- 关联规范:`docs/technical/【技术方案】AGC模板库与模板建项-2026-09-17.md`;开发期计划见 `docs/project-memory/plans/【里程碑】AGC模板库客户端接入-2026-09-17.md` 与对应实施计划。
- 验证:Rust 模板库 8 项定向单测、前端模型 9 项单测、AGC `tsc` 类型检查通过;`templates/index.json` 匿名可读且每个 `zipKey` 回读 SHA-256 与清单一致;发布脚本 `scripts/agc-template-library-publish.mjs` 支持 `--dry-run` 与上传后回读校验。
## 2026-09-16 DirectProject 上传文件统一使用附件 content part
- 决策:DirectProject 本地上传不区分图片与其它文件。用户使用同一个文件入口,前端不按 `mediaType` 做图片判断,所有上传文件统一写成 `agc_attachment_reference`;项目资源 `@` 引用仍使用 `agc_resource_reference`
@@ -8857,3 +8916,38 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在
- 边界:Deploy 阶段在远端 dev / release agent 执行,不受该上限约束。调整只动这两处:`systemctl set-property / revert jenkins.service``docker update --cpus=<n> gitea-runner` 加同步 compose(备份 `/opt/gitea-stack/compose.yml.bak-<时间戳>`)。
- 验证:限速后 `Genarrative-Full-Build-And-Deploy` #289 / #290 SUCCESS;采样期 Jenkins 峰值 10.2~10.5 核、限流不足 2s(可忽略),runner 峰值 12.07 核且持续出现 throttling,整机回落到 2.6%~19.8%。
- 关联文档:[开发运维](../../【开发运维】本地开发验证与生产运维-2026-05-15.md)。
## 2026-09-17 AGC 抠图接入本地资源编辑恢复闭环
- 背景:`agc_remove_background` 原先只提交 `/api/editor/images/background-removals` 并返回 `queued`,没有轮询远端任务、下载完成媒体或写入本地 manifestBgFilter 已成功处理但 Agent 因此永远只能看到受理回执。
- 决策:抠图作为 `LocalProjectResourceEditKind::BackgroundRemoval` 接入现有资源编辑账本,模式和背景色写入 operation 身份;提交后复用同一套轮询、结果下载、staging、manifest 提交和恢复逻辑。已有账本优先恢复,禁止在未知结果时换 operation/idempotency 重发。
- 边界:主站异步队列、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)。
@@ -51,6 +51,8 @@
## 验证路由
AGC 运行时配置默认值调整时,同步核对 Rust 默认值、分发配置模板、设置弹窗默认草稿和 `runtime-settings.suite.ts` 的恢复默认断言;显式传入旧值的配置读取用例仍验证原值保留,不批量替换测试数据。
AGC 测试构造单 HTML 项目时,必须在初始化之前写入 HTML,避免自动建立 npm 工程;npm 预览和导出测试应提供 dist 产物。已有图片生成 pending/operation 属于持久化恢复合同,修改工具默认参数后仍须验证旧动作恢复不重复提交、不因默认值变化被误判为新意图。
SpacetimeDB 任务统一先读取 `.codex/skills/genarrative-spacetimedb/SKILL.md`;该项目适配层按需调用已安装的官方 `spacetimedb` 插件 skill,插件提供通用 SDK/CLI/MCP 知识,项目 skill 负责 Genarrative 架构边界和验证门禁。
+30 -2
View File
@@ -1,5 +1,13 @@
# 踩坑与排障记录
## AGC 空快照测试必须等待请求完成
`waitFor(() => expect(activeTurns).toEqual([]))` 在 Hook 初始状态就能成功,不能证明首次异步读取已经完成。引用稳定性回归应显式控制 Promise 完成,并同时检查首次空响应与禁用后的引用;快照签名初值必须与初始空数组一致。窗口同步测试应验证未变化状态不重复发布,不能依赖一次多余的空态更新。
## AGC Windows 开发态首次页面加载缓慢
Vite 默认监听应用根下的 Rust `src-tauri/target`,构建产物较多时会创建大量 Windows 文件监听器。AGC 配置通过 `server.watch.ignored: ['**/src-tauri/target/**']` 排除此目录,不关闭业务源码、CSS、共享组件监听或 HMR。排查时区分后端就绪、Vite 扫描和原生窗口首绘;监听目录回归不能代替实机首绘测量,验证入口见本地开发运维文档。
## 2026-09-17 AGC 输入盒的「推理档」弹层被祖先裁切:要放开裁切而不是挪弹层
- **现象**:窄窗口下(视口 ≤1000px 时右侧对话面板只有 280px 宽)点开输入盒右下角的「推理档」,弹层是个**空盒子**:档位文字(默认 / 低 / 中 / 高 / 最高)整片看不见,只剩一个方框。
@@ -8,6 +16,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()`。判「用例失败与本改动无关」时,先确认失败信息是不是这一条。
## JSON 卡片显示与 UI 编辑能力必须同源
@@ -17,6 +35,8 @@ JSON 的文本读取分支不等于卡面应该展示原始 State 摘要。卡
工作台向窗口标题栏发布运行项目时,若 effect 依赖普通函数派生的回调,发布 Context 会重新渲染工作台,进而再次发布并清理,形成更新深度循环。转发入口须稳定,并在提交阶段更新实际处理器引用;发布数据变化与卸载清理分开。回归测试必须组合真实窗口 Provider 和工作台消费者,只有独立画布测试无法覆盖这条反馈链;回归时用有界发布次数阻止测试失控。画布快速操作时暴露的更新深度错误,也须检查外层状态同步,不能直接归因于滚轮频率。
活动回合快照的初始签名须与初始空数组一致,首次异步返回空数组不能额外换引用。停用、重新启用或切换读取器时应使旧请求失效,避免晚到结果覆盖新快照;测试需控制 Promise 完成时机,不能用“初始数组已为空”当作请求已结束。无原生读取器时窗口只发布一次空状态。
## 2026-09-17 工具 schema 声明的上限与真实校验不一致,会表现成「agent 调不动这个功能」
- **现象**:用户反馈「客户端没法由 agent 调用图片快速编辑功能以及背景音乐生成功能」。查工具目录时两个工具都在(`agc_edit_image``agc_create_or_derive_resource`),图片快速编辑在真实项目日志里还有成功记录;但 agent 侧写一句正常长度的背景音乐描述就失败,而客户端 UI 用同一个提示词却只是被截断加提示。
@@ -5688,8 +5708,8 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/`
- **现象**:在 `apps/ai-game-creator-shell/src-tauri` 跑过 `cargo test` 之后,`npm run ai-game-creator-shell:typecheck` 报 10 条类型错(`src/features/project-workspace/resourceReferences.ts:106-112``string | undefined` 不能赋给 `string | null`;同文件 134 行的对象字面量带 `type: 'message'`,而 `DirectCodexUserMessageItem` 里没有该字段),`npm run ai-game-creator-shell:build` 也死在 `beforeBuildCommand` 的同一条 typecheck 上。
- **原因**crate 里的 ts-rs 导出会按**本机依赖版本**重写 `src/features/project-workspace/generated/DirectCodexUser*.ts`:注释头变成 "This file was generated…"、字符串改双引号、`DirectCodexUserMessageItem` 丢掉 `type: 'message'` 判别字段、并多出一个 `DirectCodexUserMessageEnvelope.ts`。仓库里提交的那份是前端真正依赖的形状(前端按带 `type` 的判别联合写),重写后两边就对不上——错在生成器版本漂移,不在前端。
- **处理(现行口径)**:不要把重写结果当改动提交。跑过 `cargo test` 或构建后`git checkout -- apps/ai-game-creator-shell/src/features/project-workspace/generated`,再删掉多出来的 `DirectCodexUserMessageEnvelope.ts`,然后才做 typecheck / 打包;绑定与前端形状冲突时以**已提交的绑定 + 前端**为基准排查。
- **验证**:恢复提交版本后 `npm run ai-game-creator-shell:typecheck` exit 0`[skill-pack] OK`);保留重写结果时同一条命令 exit 2。release 构建本身还会在 `src/features/ui-editor/types/` 落下 `BindingChange.ts` / `BindingDTO.ts` 两个无人引用的未跟踪文件,属同类生成产物。
- **处理(现行口径)**:不要把重写结果当改动提交。跑过 `cargo test` 或构建后只恢复 `apps/ai-game-creator-shell/src/features/project-workspace/generated/DirectCodexUser*.ts` 的仓库版本,再删掉多出来的 `DirectCodexUserMessageEnvelope.ts`;不要恢复整个 `generated/` 目录,以免误删 DirectThread 的现役绑定。绑定与前端形状冲突时以**已提交的 DirectCodexUser 绑定 + 前端**为基准排查。
- **验证**:恢复仓库版本后 `npm run ai-game-creator-shell:typecheck` exit 0`[skill-pack] OK`);保留重写结果时同一条命令 exit 2。release 构建本身还会在 `src/features/ui-editor/types/` 落下 `BindingChange.ts` / `BindingDTO.ts` 两个无人引用的生成产物;它们不属于前端契约,发现后直接删除,不提交
- **关联**`apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_user_item/`ts-rs 导出源)、`apps/ai-game-creator-shell/src/features/project-workspace/resourceReferences.ts``apps/ai-game-creator-shell/scripts/build-release.mjs``beforeBuildCommand`)。
## 2026-09-16 Node 26 下 vitest 的 jsdom 用例拿不到 window.localStorage
@@ -5698,3 +5718,11 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/`
- **原因(已核对,不是推断)**Node 26 在 `globalThis` 上定义了实验性 `localStorage` 访问器,不传 `--localstorage-file` 时它返回 `undefined`。vitest 0.34 的 jsdom 环境把 jsdom window 的描述符复制到全局时,不会覆盖 `globalThis` 上已存在的键,于是 jsdom 真正的 `Storage` 被 Node 的 `undefined` 顶掉。`appSurface` 之所以不受影响,是因为 `apps/ai-game-creator-shell/tests/appSurface/harness.ts` 自己用 `Object.defineProperty(window, 'localStorage', …)` 挂了内存实现。
- **处理**:跑这类用例时给 Node 加 `--localstorage-file`,例如 `NODE_OPTIONS="--localstorage-file=/tmp/vitest-node-localstorage" npx vitest run <files>``apps/ai-game-creator-shell/tests/clientApi.test.ts``chatPromptPolish.test.tsx` 加这个开关后 26 项全绿。不要改业务代码或往测试里塞假 storage 来绕过。
- **关联**`vitest.config.ts``environment: 'node'` + 用例内 docblock 切 jsdom)、`apps/ai-game-creator-shell/tests/appSurface/harness.ts` 的内存 localStorage 垫片。
## 2026-09-17 AGC 壳首页无限 setState:effect 依赖了每次渲染都换身份的普通函数
- **现象**:dev 客户端停在首页、不点任何东西也会持续刷 `WEBVIEW error webview: Maximum update depth exceeded …`(5 秒涨 ~8.5 KB 日志),对应 WebView2 renderer 工作集涨到 **4.2 GB**、CPU 持续累计(约 0.7–1.5 核);表现上很像"模板库卡片太多/滚动卡",实际与页面内容无关。
- **原因**`WorkspaceLauncher` 里发布"活动项目面板"数据的 effect,依赖数组里带了 `openActiveProject`;它由 `useCallback([openProject, setProjectPath])` 生成,而 `openProject` 来自 `useHomeProjectCreation` 的**普通函数声明**(每次渲染都是新身份)→ `openActiveProject` 每渲染都变 → effect 每渲染重跑 → cleanup/主体调 `setActiveProjectRuns``WindowChrome` 的 state → 标题栏重渲染 → 又一轮。`WindowChrome` 的 context value 当时还是内联对象,进一步放大了连带重渲染。日志里没有组件栈,是靠在 `console.error` 包装里抓 `new Error().stack`(该日志与 setState 同栈)才定位到 `WorkspaceLauncher.tsx``commitHookEffectListUnmount → dispatchSetState`
- **处理(现行口径)**:① 依赖里只放数据,回调走 ref`openActiveProjectRef`)——effect 不再因回调换身份而重跑;② `useDirectActiveTurns` 轮询只在快照内容变化时才 `setActiveTurns`(并给空态做引用稳定),避免每 5 秒换一次数组身份去带动下游 effect;③ `WindowChrome` 的 context value 用 `useMemo` 收口。判断类问题的通行判据:**凡是把"每次渲染新生成的函数/对象"写进 effect 依赖的,一律视为 bug**。
- **验证**:修复后同一台机器、同一路径下 35 秒内新增 `Maximum update depth` **0 条**renderer 工作集 **254 MB**(修复前 4.24.4 GB);`apps/ai-game-creator-shell/tests/directActiveTurns.test.tsx` 断言轮询返回值不变时快照引用不变。
- **关联**`apps/ai-game-creator-shell/src/features/app-shell/WorkspaceLauncher.tsx``apps/ai-game-creator-shell/src/features/agent-runtime/directActiveTurns.ts``apps/ai-game-creator-shell/src/components/WindowChrome.tsx``apps/ai-game-creator-shell/src/features/app-shell/useHomeProjectCreation.ts`
@@ -16,6 +16,7 @@
## 开发中
- 画布卡片类型与信息角标共用 `CanvasCardCornerActions`;菜单收纳共用 `OverflowActions`,宿主决定展示数量和资源命令。AGC 选中菜单前 5 项直显,Web 默认不折叠;浮层 portal 继续接入现有画布关闭与滚轮归属判据。
- 修改范围保持聚焦;优先扩展现有系统、页面、组件、DTO 和脚本,不新建平行入口或业务真相。
- UI 开发优先复用现有公共组件;跨页面或跨端重复的视觉/交互模式应沉淀到 `packages/shared`,由现有页面迁移使用,禁止在业务页复制同类 UI。共享组件只承载通用表现与交互,不下沉领域规则、后端副作用或正式业务状态。
- AGC 当前 Agent 与策划 Agent 的消息层级共用 `packages/shared``AgentMessageContent`:正文使用 `body`,思考、中间输出与工具调用使用 `process`;宿主不按 Agent 类型重新定义过程字号和颜色,错误状态保留语义色。