Compare commits

...

220 Commits

Author SHA1 Message Date
suzmii cb76aadb48 AGC 输入区三处观感修复:AI 润色归位、@ 面板筛选条单行、引用 chip 原子化
Project CI / Repository checks (pull_request) Failing after 15s
Project CI / Backend tests (pull_request) Failing after 15s
Project CI / Frontend tests (pull_request) Successful in 3m5s
Project CI / Native shell tests (pull_request) Successful in 18m26s
- styles.css:把 direct-codex composer 下广播式的 `.project-supervisor-composer button` 规则
  收窄到 `.project-supervisor-submit-button`(含 svg 那条与 media query 里那条),
  并给 `.resource-reference-input-actions` 补 `grid-column: 1 / -1` 让输入区操作排独占一行
- styles.css:`.resource-reference-input-actions` / `.resource-reference-input-at` 撤掉绝对定位,
  操作排回到网格文档流,与输入框右下角对齐,不再浮在文本区中间
- styles.css:`.resource-reference-input-polish` 与 `.resource-reference-input-restore` 并入
  `@` 按钮同一条尺寸规则(28px 方钮、8px 圆角、同 hover/disabled 观感)
- styles.css:输入区状态行(润色中/润色失败)改为与应用排同一行,不再单独占一行顶高 22.8px
- styles.css:direct-codex 输入框 padding 收为 `12px 15px 6px`,窄屏 `10px 12px 6px`
- styles.css:`.resource-reference-chip` 的 `×` 删除按钮不再被 broadcast 规则摘出 chip
- PlatformSegmentedTabs.tsx:分段页签 item 加 `whitespace-nowrap`,中文不再逐字竖排
- packages/shared/styles.css:补 `.scrollbar-hide` 实现(该类名一直被 `layout="scroll"` 引用但仓库未定义)
- ResourceReferenceNode.tsx:`getTextContent()` 改为单个 U+FFFC 占位符并显式实现
  `getTextContentSize()`,引用成为真正的原子内联节点(1 个引用 = 1 个字符)
- tests/resourceReferenceInput.test.tsx:新增「1 个引用 = 1 个字符」与「chip 自带删除、一次删净」两条断言
- tests/appSurface/project-development.suite.ts:把 composer 按钮样式断言收窄到提交按钮,
  并新增一条反向断言,钉住「广播到 composer 下所有 button」的规则不再出现
2026-09-10 19:05:57 +08:00
suzmii c20e28bed0 修正 AGC 快速编辑 4xx 失败原因取值:优先 details.message 并附 provider 与素材类型上下文
- `editor_api_rejection_reason` 取值顺序改为 `error.details.message` → `details.message` → `error.message` → `error` 文本 → 顶层 `message`
- 平台把所有 4xx 兜底成「请求参数不合法」,真实原因写在 `details.message`,原实现只读 `error.message` 导致用户永远看不到原因
- `details` 有值时附带 `provider`,并透出 `assetKind` / `mediaType`,非 BAD_REQUEST 的稳定错误码照旧透出
- 抽出同步函数 `editor_api_rejection_reason_from_body` 并补定向用例,钉住取值顺序、截断与兜底行为
2026-09-10 19:01:09 +08:00
suzmii 8c74e5f97c 修正 AGC 图片快速编辑的 400 判据测试:补 art-spritesheet / ui 正向用例并钉住请求级链路
- 服务端白名单用例改为按静态图集合断言,新增 `art-spritesheet` / `ui` / `ui-prototype` / `game-art` / `game-background` / `art-spritesheet-slice` 正向用例
- 保留 `future-kind` / `video` / `audio` / `image-sequence` / `character-animation` / `document` / `code` 负向用例,并补 mediaType AND 门用例
- 新增请求级回归用例 `game_creator_client_quick_edit_accepts_local_manifest_static_image_kinds`,按 AGC 客户端真实请求体(source 已改写为 ai-game-creator-client)覆盖放行与拒绝两侧
- 新增 `game_creator_client_source_is_recognized_as_game_creator_resource_editor`,把 source 别名与队列 consumer 判定钉在同口径
- 修正 icon 相关旧断言:`icon` 已是共享契约静态图类型,改断言 non-static 媒体仍被拒
2026-09-10 19:01:04 +08:00
suzmii 9e59abde53 修复 AGC 图片快速编辑被平台 400 拒绝:来源类型表改为与共享契约对齐的静态图集合
- `ensure_editor_image_edit_source_kind_allowed` 的白名单换成静态图类型表 `EDITOR_IMAGE_EDIT_STATIC_IMAGE_ASSET_KINDS`(canonical 静态图类型 + 合法 legacy 类型)
- 保留 `mediaType == image` 的 AND 门,视频 / 音频 / 序列帧等非静态媒体照旧拒绝
- 覆盖实测被拒的 `art-spritesheet` / `ui`,以及 canonical 映射输入域 `art-spritesheet-slice` / `ui-prototype` / `game-art` / `game-background` / `character-art` / `illustration`
2026-09-10 19:00:52 +08:00
suzmii 6a11032084 修复 AGC 图片快速编辑被平台 400 拒绝:服务端认 ai-game-creator-client 来源别名
- `is_game_creator_resource_editor_generation` 增加 `GAME_CREATOR_CLIENT_GENERATION_SOURCE` 别名,与 `from_job` 的队列 consumer 判定同口径
- AGC 客户端发送前会把 `generationInputs.source` 改写成 `ai-game-creator-client`,原来只认 `game-creator-resource-editor`,导致该分支对真实请求永远不生效
2026-09-10 19:00:44 +08:00
suzmii 7d0f5c68fe 快速编辑浮层样式照搬美术画布:逐条搬真实规则,补回共享组件的 Tailwind 来源
- resourceCanvasChrome.css:删掉手写的近似样式,从网页端 src/index.css 逐条搬入 AGC 实际用到的 image-canvas-editor__* 规则——生成 composer 基座(grid 布局、42rem 宽、1.45rem 圆角、双阴影)、generation-prompt(--auto-grow-editor-* 变量 + 聚焦态)、composer-footer 五列网格(修掉底部一行挤在一起、对齐混乱)、option-cluster / option-popover-anchor 及其 --dimensions / --model 变体、option-popover 与它的 sections / section / title / items / items--card / items--model、option-popover-choice 及 --ratio / --model 与 aria-pressed 选中态、ratio-wireframe 六个比例线框、option-selected-check、mud-point-inline、generation-submit 三态、quick-edit-panel / --quick-edit / quick-edit-footer / quick-edit-prompt / quick-edit-submit 这一组面板专属覆盖。
- resourceCanvasChrome.css:补入提示词输入框依赖的 auto-grow-text-area 一族规则(外层尺寸变量、content 内边距、placeholder、viewport 与 OverlayScrollbars 竖条),原来只给了裸边框和 padding,所以输入框看着很素。
- resourceCanvasChrome.css:保留/新增的宿主规则只有美术画布没有的那几项——工具条与面板的层级、锚点 transform、窄屏宽度;面板的 overflow 由 auto 改成 visible,因为 AGC 的下拉弹层是就地渲染、没有 portal,滚动容器会把它裁掉。
- styles.css:新增 @source 指向 src/components/image-editor 与 src/components/common。这两个目录不在 AGC app 根下,Tailwind 自动探测扫不到,组件自带的工具类(h-3 / rounded-full / inline-flex / text-slate-700 …)在 AGC 里根本没生成,是「样式和美术画布对不齐」的另一半根因。
- 选中工具条上一轮已按真实规则改好的部分原样保留,未回退。
2026-09-10 18:59:33 +08:00
suzmii f49ff61ddc 资源画布浮层接上美术画布的关闭时机:空白/Esc 清焦点,快速编辑点外部即收
- 新增 resourceCanvasFocusModel:把「这次 pointerdown 要不要清画布焦点」「快速编辑浮层是否接管点外部关闭」「宿主 Escape 是否归资源画布」抽成纯判据,逐条对齐美术画布 clearCanvasFocus() / closeTransientEditorPanels() / clearCanvasSelection()。
- index.tsx 画布空白左键 pointerdown 调 clearResourceCanvasFocus():清空选中(选中工具条随之收起)并关闭快速编辑浮层,与美术画布 useImageCanvasStageInteractions 的空白分支一致;中键/空格平移分支不清,避免平移顺手丢选中。
- 快速编辑浮层复用共享 hook useImageCanvasFloatingOptionDismiss:document 上接管 click 与 Escape,点整个资源画布以外即关,点边界内(含模型/比例下拉 .image-canvas-editor__portal-menu)保留;宿主弹窗(资源面板/生成素材/分类与标签/重命名/恢复队列)开着时不接管,避免清掉资源面板共用的多选。
- 选中工具条补 window 级 Escape:与美术画布 clearCanvasSelection() 同口径清空选中;下拉弹层打开时共享 hook 在 document 上截断,浮层优先收内层。
- 生成为中的快速编辑面板不关(status === 'generating'),保留「修改中」状态与失败重试入口。
- 新增定向测试:交互目标判据、生成中不关、宿主弹窗让位、点边界外关闭、Esc 两态关闭、只有工具条时点外部不清选中、模型/比例下拉点外部与 Esc 关闭。
2026-09-10 18:58:15 +08:00
suzmii 8f96b05f6e 资源画布工具条接回下载按钮:走资源面板同一条真链路并收紧放行判据
- index.tsx 把资源面板的保存流程抽成共用 saveProjectResourcesToDisk(原生保存对话框 + save_local_project_asset_file),资源面板改为委派调用,不再有第二份保存实现

- index.tsx 工具条 onDownloadLayer 由空回调改为调用该共用 handler,回执写进 resourceWorkbenchNotice;不可导出的资源仍直接返回

- resourceCanvasToolbarModel.ts 把 download 加回 supportedActions,判据是 isResourceCanvasExportable(拿不到本地文件路径就不渲染),不按媒体类型放行

- resourceCanvasToolbarModel.test.ts 期望值随判据重算为 [quick-edit, download] 或 [download],并补 download 放行与拒绝两侧用例(空 path、虚拟版本条目)

- project-development.suite.ts 音频工具条按钮集合断言更新为 [分类与标签, 重命名, 下载按钮],并新增两条真链路断言:工具条下载打到 save_local_project_asset_file、资源面板下载仍打到同一命令

- appSurface.test.ts 在入口文件 mock 原生对话框,只替代用户选择目标路径这一步;放别处注册会晚于真实模块实例化
2026-09-10 18:05:15 +08:00
suzmii 93ddb841cd 抽出资源落盘判据 isResourceCanvasExportable,资源面板与工具条共用一条口径
- resourceCanvasAssetTransferModel.ts 新增 isResourceCanvasExportable:只有拿得到可落盘本地文件路径的资源(version 为空且 path 非空)才允许保存

- resolveResourceCanvasPanelEntries 的 downloadable 改为调用该判据,去掉内联的同一段表达式

- 纯重构,资源面板行为不变;目的是让画布工具条的下载放行判据与资源面板使用同一个权威口径,不再写第二份
2026-09-10 18:05:02 +08:00
suzmii c9413fae0c 补资源画布工具条动作模型定向测试,把放行判据钉在测试里
- 新增 tests/resourceCanvasToolbarModel.test.ts:直接覆盖 resolveResourceCanvasToolbarActions,此前该模型零直接覆盖

- 逐条钉住当前真实接通的动作集合:栅格图片与角色资源只放行 quick-edit,音频/视频不再放行 redraw,角色资源不再放行 character-animation

- 覆盖派生门禁:未登记 manifest 资产且无已完成任务产物背书不放行、只认已完成任务的栅格产物、非栅格图片不放行

- 判据出处为 docs/project-memory/shared-memory/pitfalls.md 的「渲染出来的动作必须真的能跑通」
2026-09-10 17:53:25 +08:00
suzmii ecca7050b5 音频资源工具条断言改判据:不再要求渲染空回调的改造按钮
- project-development.suite.ts 的音频工具条断言由「必须存在改造按钮」改为「不得存在改造按钮」+「按钮集合精确等于分类与标签、重命名」

- fed17ea08 已按共享记忆 pitfalls.md 的判据(渲染出来的动作必须真的能跑通)把 redraw 与 character-animation 移出 supportedActions,音频分支只剩宿主编排层真实接通的分类与标签、重命名,原断言断言的是被该提交推翻的旧合同

- 新断言比旧断言更严:除钉住音频分支仍在渲染,还钉住整条工具条的按钮集合,避免再出现点了没反应的假按钮
2026-09-10 17:53:15 +08:00
suzmii 7fe1fd9b64 Merge branch 'feat/agc-v3-canvas-generation-entry' into feat/agc-canvas-resource-workbench-v3 2026-09-10 17:40:49 +08:00
suzmii 94fc4c6779 Merge branch 'feat/agc-v3-c7-version-switch' into feat/agc-canvas-resource-workbench-v3 2026-09-10 17:40:46 +08:00
suzmii 994209fc33 Merge branch 'feat/agc-canvas-art-editor-port' into feat/agc-canvas-resource-workbench-v3 2026-09-10 17:40:43 +08:00
suzmii fed17ea08c 工具条只渲染真实接通的动作,去掉两个点了没反应的按钮
- resolveResourceCanvasToolbarActions 不再把 redraw 与 character-animation 放进支持集合:这两个动作在宿主编排层仍是空回调,放进去会让工具条渲染出点了没反应的按钮
- 判据收敛为一条:放进来就必须真能跑通,当前真实接通的只有快速编辑(normalize → derive(editKind='image-reference'))
- 补注释写明接上真实链路后再放回来
2026-09-10 17:39:17 +08:00
suzmii 9ce31f905e 沉淀 AGC 画布现状与取数约束到共享记忆
- pitfalls.md 追加「AGC 走不了网页端 /api/editor 的取数链」:Tauri asset 协议 + CSP connect-src 未放行平台域名,且 dev 的 vite /api 代理会造成「能跑」假象
- pitfalls.md 追加「ImageCanvasStageView 是硬组合容器」:132 props(57 数据 + 75 回调)、无条件渲染 8 个子视图、83/132 需宿主伪造编辑器状态,且不透传 supportedActions
- pitfalls.md 追加「AGC 资源画布真正自制的交互面」:resourceBookViewport.ts、resourceCanvasLayoutModel.ts 的 fit/normalize、index.tsx 3656-4090 指针段,并记录两条平移路径守卫不一致与右键菜单、chrome 样式缺口
- pitfalls.md 追加「资源画布工具条改造/角色动画按钮点了没反应」:空回调仍留在 supportedActions 里,按钮渲染但无人接
2026-09-10 16:50:02 +08:00
suzmii 746b96e71d 资源编辑 400 同时透出平台错误码
- editor_api_rejection_reason 额外读取响应体 error.code,过滤掉等价于通用文案的 BAD_REQUEST 后拼成「原因|错误码 CODE」
- 背景:http_error.rs 把所有 4xx 兜底为同一条「请求参数不合法」,只透 message 会丢掉唯一可用于定位的信息
2026-09-10 16:45:29 +08:00
suzmii 6496601d12 资源画布接入生成素材入口并打通无源新建链路
- index.tsx 只做导入、一个生成入口按钮、一处浮层渲染与提交回调;无 invoke 桥或项目未就绪时入口不渲染
- 提交走 derive_local_project_resource 的 create 模式:sourceResourceId 为 create:<operationId>,sourceAssetId / sourcePath / sourceSubtype / producerTaskId / sourceVersionId 全为 null
- 产出后经 pendingResourceFocusRef 定位并选中新素材卡,并写入生成资源已保存提示
- projectResourceLiveIntegration 补入口可见性、create 入参、重试复用 operationId 与新卡定位 2 条用例
- 实施计划文档补 C3 生成入口口径、图片无源生成未完成项与本轮验证数字
2026-09-10 16:37:52 +08:00
suzmii 0035d16efa AGC 资源画布新增生成素材浮层与生成入口模型
- 新增 resourceCanvasGenerationModel:按本地契约锁定无源生成放行的视频 / 音效 / 背景音乐三类,提供 create:<operationId> 源标识、入口渲染门禁与请求身份工厂
- 新增 ResourceCanvasGenerationPanelView:独立浮层面板承载类型选择、草稿与失败重试,失败后锁定原请求并复用同一 operationId / idempotencyKey
- 浮层只呈现三类真实可用类型,不出现图片专属的模型 / 比例 / 尺寸 / 像素艺术与泥点价
- resourceCanvasChrome.css 补浮层宿主样式
- 新增 resourceCanvasGenerationEntry 定向测试 6 条
2026-09-10 16:37:39 +08:00
suzmii 56c6e0fecc 修复工具条折行与资源编辑 400 的错误盲区
- resourceCanvasChrome.css:移除我自行添加的 max-width 与 flex-wrap: wrap,按网页端美术画布原始规则改回单行不换行(容器 nowrap + 溢出横向兜底),按钮统一 2.25rem 方块与 0.45rem 圆角,文字键 padding 改回 0 0.62rem,分隔线改回 1.125rem,消除动作变多时折叠成多层与按钮高矮不齐
- resource_editor.rs:新增 editor_api_rejection_reason,在 HTTP 400 分支把服务端响应体的可读原因(兼容 error.message / error 字符串 / details.message / 顶层 message,截断 200 字符)拼进用户可见错误,替换原先只有状态码的笼统提示
2026-09-10 16:33:13 +08:00
suzmii f09f5f9e9c 补齐 AGC V3 收口阶段的决策记录
- decision-log 新增两条 2026-09-10 决策:资源画布基础能力收口(撤销重做只作用于卡片布局、独立资源面板、上传走 upload_local_asset、下载改为显式保存链路 save_local_project_asset_file + dialog:allow-save)
- decision-log 新增:运行模块版本切换(版本状态唯一一份放在 WorkspaceLauncherShell、运行模块右上角 GameRunVersionPicker、切换只重载预览不做运行时资源重映射)、参考图弹窗与共享筛选条、标签库纯派生口径、素材重命名前端接线
- 同步修正上文"重命名只落 Rust 侧"的已知中间状态:前端调用方已落地,该中间状态结束
2026-09-10 16:05:01 +08:00
suzmii f2f8a9af72 合并 WP4 资源面板与撤销重做:保留 C7 版本入口与资源面板接线
- project.rs 冲突:同时保留 mod asset_export(WS-B 下载命令)与 mod asset_rename(WS-D 重命名命令),丢弃已在 WS-A 退役的 asset_usage
- index.tsx 冲突:同时保留 GameRunVersionPicker(C7 版本入口)与资源面板/历史模型导入(WP4)
2026-09-10 15:57:42 +08:00
suzmii 422a71d321 资源画布新增独立资源面板:预览/上传/下载/多选(WP4)
新增 features/resource-canvas/resourceCanvasAssetTransferModel:面板网格模型、选中集合解析、默认导出文件名、上传白名单(纯函数)

新增 features/resource-canvas/ResourceCanvasPanelView:独立浮层面板承载预览网格/上传/下载/全选/清空,选中状态直接读写画布同一份 selectedResourceIds,不造第二套选择

下载改为显式保存链路:前端 @tauri-apps/plugin-dialog save() 取目标路径,再调新命令 save_local_project_asset_file 复制素材文件,避免依赖不可靠的 <a download>

Rust 新增 project/asset_export.rs 与命令 save_local_project_asset_file:校验项目根与相对路径、源必须是真实普通文件(拒绝符号链接与目录)、目标路径非空且父目录存在、分块流式复制不进整文件内存,返回目标路径与字节数

Rust 单测覆盖成功复制(内容一致 + byteLen + 源不变)、缺失源拒绝、目录源拒绝、空目标与父目录缺失拒绝、目标为目录拒绝

上传复用现役 upload_local_asset(落 assets/uploads 并登记 manifest 条目)后回读 manifest 刷新,只接受图片/音频/视频

capabilities/main.json 增加 dialog:allow-save;资源画布 chrome 样式补资源面板一套;index.tsx 只做导入/一处按钮/一处渲染/状态透传
2026-09-10 15:56:10 +08:00
agent 00963c5f67 同步 AGC V3 版本切换、参考图弹窗、标签库与素材重命名 UI 的功能文档
- 实施计划追加 2026-09-11 条目:标签库派生口径、共享筛选条、C7 版本切换、C9 参考图弹窗、素材重命名 UI 与 allowlist 收敛

- 写明版本切换不做运行时资源重映射的理由与参考图 ID 快照的落库路径

- 记录本次门禁实际数字与 5 条待 C3 侧更新的既有失败用例
2026-09-10 15:50:54 +08:00
suzmii 09cd47748c Merge branch 'feat/agc-v3-c7-version-switch' into feat/agc-canvas-resource-workbench-v3 2026-09-10 15:49:46 +08:00
agent 3d709ea1db 合并主干 C3 交互改造并接入 C7 版本切换
- 合并 feat/agc-canvas-resource-workbench-v3:资源卡改为选中+浮出工具条、支持多选、删除资源详情面板与 resourceEditorRoute

- index.tsx 冲突以 C3 新结构为底:保留本包的素材重命名回调,丢弃被 C3 取消的 focusedResource 详情面板派生状态与浮层 JSX

- 重命名入口从已删除的详情面板搬到选中工具条 extraActions,与「分类与标签」并列

- check-config.mjs 的 allowlist 保持主干版本(normalize_local_project_raster_resource 一行已删)

- 还原 normalize_local_project_raster_resource Tauri 命令与注册:C3 已在前端接回调用方

- C7:新增 GameRunVersionPicker 运行模块右上角版本入口,无版本不渲染

- C7:版本状态收敛到 WorkspaceLauncherShell 一份,透传给资源画布与 @ 面板;换项目或版本消失时清回最新版本

- C7:currentVersionResourceBindingIds 接入 activeVersionId,复用 resolveActiveIterationVersion 口径,资源卡「当前使用」高亮随版本切换

- C7:切换版本时复用既有 onPlay 预览入口重载画面,不新增运行时资源重映射

- 新增 10 条测试覆盖版本判定口径、版本入口与切换、当前使用高亮、素材重命名链路
2026-09-10 15:46:08 +08:00
suzmii 9121e89798 资源画布新增组织操作撤销重做(WP4)
新增 features/resource-canvas/resourceCanvasHistoryModel:纯函数有界快照栈(capture/push/undo/redo/clear + 只回写差异位置),快照只含资源卡布局坐标,不回滚素材内容

新增 tests/resourceCanvasHistoryModel.test.ts 覆盖排序稳定、相同快照不入栈、栈上限、push 清空重做分支、撤销重做互逆、空栈返回 null、差异回写与已删除资源不重建

资源卡拖动持久化成功前先落快照,撤销/重做复用现役 commitPosition 手动 CAS 写链

画布缩放集群新增撤销/重做按钮(不可用时禁用),并支持 Ctrl/Cmd+Z 与 Ctrl/Cmd+Shift+Z、Ctrl+Y(输入框内不拦截)

项目切换时清空布局历史,避免跨项目回滚
2026-09-10 15:43:03 +08:00
suzmii 9939a1bca0 Merge branch 'feat/agc-v3-c3-canvas-match' into feat/agc-canvas-resource-workbench-v3 2026-09-10 15:29:41 +08:00
suzmii f247a5106a 收敛资源画布交互复刻的测试:清理详情面板遗留断言,门禁回到基线
删除用例 keeps the resource toolbar and canvas interactive while a non-modal detail card is open:该用例断言的「非模态详情卡」已被 C3 合同取消,按四不写删除

新增用例「资源卡 chrome 只保留选中按钮与当前版本边框」,接住被删用例里仍然有效的卡片 chrome 断言(无基础描边、is-current-version 边框、选中按钮样式)

版本卡与 Agent 回执卡没有美术画布动作,断言改为选中反馈(aria-pressed)而不是工具条

blocked.md 的策略失败提示原本渲染在已删除的详情面板里,断言随之移除

快速编辑测试的 fixture 增加栅格美术资产(includeArt 参数),不改动其它用例的 manifest
2026-09-10 15:29:15 +08:00
agent f32445d700 补齐 AGC V3 附二素材重命名前端链路
- 新增 ResourceRenameDialog:独立重命名面板,只输入新文件名,规则交给 Rust 侧强校验

- 资源详情面板新增「重命名」动作,严格按 deny_unknown_fields 只传 projectPath/assetId/newFileName

- 改名成功后复用 reloadManifestAfterAssetCommand 这条 manifest 重载路径刷新资源卡与 @ 面板显示名

- 删除 check-config.mjs 过渡 allowlist 里的 normalize_local_project_raster_resource,并移除已无调用方的同名 Tauri 命令与注册

- 新增 2 条前端测试覆盖严格入参、manifest 重载与改名后资源卡显示名刷新
2026-09-10 15:24:20 +08:00
suzmii a74cd6c8b6 Merge branch 'feat/agc-v3-c3-canvas-match' into feat/agc-canvas-resource-workbench-v3 2026-09-10 15:19:53 +08:00
suzmii bd36587e0a 资源画布交互 1:1 复刻美术画布:点卡选中并浮出工具条,移除资源详情面板与全屏编辑路由
新增 features/resource-canvas:资源卡到美术画布 CanvasLayer 的适配、工具条动作可用性判定、C5 当前版本绑定判定、快速编辑草稿工厂与 AGC 宿主 chrome 样式

资源卡点击改为选中(Shift/Ctrl/Meta 多选),选中后在卡片旁复用 ImageCanvasSelectedLayerToolbarView 浮出工具条

快速编辑复用 ImageCanvasQuickEditPanelView 浮层面板,先调 normalize_local_project_raster_resource 正规化任务产物,再走 derive_local_project_resource(editKind=image-reference) 产出新素材,失败重试复用同一 operationId/idempotencyKey

删除 resourceEditorRoute 全链路(ResourceEditSurface 路由、openResourceEditor、handleResourceCardOpenEditor、cancelResourceEditor、submitResourceEdit)与资源详情面板(focusedResource 状态/引用/浮层 JSX/CSS 断言)

分类与标签入口搬到选中工具条;UI 资源保留「UI 编辑器」入口并从工具条打开

移除 apps/ai-game-creator-shell/scripts/check-config.mjs 中 normalize_local_project_raster_resource 的过渡 allowlist 条目

为共享工具条组件新增 opt-in 的 supportedActions/extraActions(默认 null/undefined,网页端行为不变)

AGC vite-env.d.ts 补 wx/ReactNativeWebView/WeixinJSBridge 跨端全局声明;hostBridge fail 回调补显式 unknown 类型

测试:资源卡查询 helper 由资源详情按钮改为选中按钮,清理详情面板断言,画布空白左键改为框选后平移用例改用中键
2026-09-10 15:19:17 +08:00
agent 3508957eda 补齐 AGC V3 C9 参考图选择弹窗与提交快照
- 新增 ImageCanvasProjectAssetPickerDialog:独立弹窗,网格卡片、多选、筛选+搜索、清空/确认/取消,筛选状态自持

- 新增 projectAssetReferencePickerModel:素材分类与筛选判据、素材 id 快照引用构造、弹窗引用前缀判定

- 生成 composer 参考图来源菜单新增「从项目素材中选择」,由宿主决定是否出现,不改变原有从画布选择与上传两条路径

- 新增 replaceProjectAssetPickerReferences:确认时整组替换素材库来源的参考图,保留画布点选与上传的参考图

- 素材 id 经 sourceAssetId 走既有 createGenerationInputReference 落进 generationInputs.references,不新增后端接口

- 新增 13 条测试覆盖弹窗交互、筛选叠加、快照落库与整组替换语义
2026-09-10 15:13:40 +08:00
agent 3379e6009e 抽出共享资源筛选条并接入画布标签筛选
- 新增 packages/shared 的 PlatformResourceFilterBar:搜索(可选)+ 功能分类 + 标签的受控筛选条,只承载表现与交互

- 资源画布筛选改为复用该组件,去掉画布内联的分类 PlatformSegmentedTabs,搜索框保持原样不动

- 画布新增标签筛选状态与派生标签库,标签与功能分类共存可叠加

- 新增 3 条组件测试覆盖受控渲染、回调转发与缺省行隐藏
2026-09-10 15:02:55 +08:00
suzmii d851aa83ff 补充 AGC 资源工作台 V3 的实施计划条目
- 新增 2026-09-10 条目,写清版本绑定恒等化与撤回运行期资源观察侧车的边界(删 asset_usage 及其 16 条测试、绑定退回恒等映射)
- 写清素材删除三分支语义、deleteReferencedVersions 入参、read_local_project_asset_references 只读命令,以及版本追加唯一例外的放行集合按写入前 manifest 求值
- 写清 agc_list_registered_assets 投影新增 category/tags 且不在投影层重新派生分类
- 写清 rename_local_project_asset 的语义(文件改名 + localPath 更新、id 不变)、校验、写失败回滚与 reconciliation-required 口径,以及"不改游戏源码引用"的已知边界
- 写清 @ 面板两个页签与 activeVersionId/versions 契约、polish_local_project_prompt 的入参与计费口径(不自建计费,走 server-rs 的 LLM 路由)、发送前提醒判据与本机偏好存储
- 记录本次全部门禁数字
2026-09-10 15:02:34 +08:00
suzmii 2c07396ef7 补充 AGC 复用网页端美术画布组件的跨端依赖排障记录
- pitfalls 新增一条:AGC 引 src/components/image-editor 组件会经 ImageCanvasGenerationModel 的 ApiClientError 值导入链到 src/services/apiClient.ts 与 host-bridge/hostBridge.ts,而它读的 wx / ReactNativeWebView / WeixinJSBridge 只在主仓 vite-env.d.ts 声明
- 记录处置口径:在 apps/ai-game-creator-shell/src/vite-env.d.ts 的 Window 上补同名同形字段(纯类型声明零运行时影响),不要关闭 AGC 的 noImplicitAny,也不要另写平行组件
- 记录已核实的运行时结论:hostBridge.ts 模块作用域只有常量与缓存声明、无导入即执行副作用,window.wx 仅在函数体内访问且有 typeof window 守卫,故该依赖链编入 AGC bundle 是惰性的
2026-09-10 14:56:45 +08:00
agent 4d0cd425e1 补齐 AGC 标签库聚合派生纯模块
- 新增 gameCreationAppAssetTagLibrary:从 manifest assets[].tags 派生去重、稳定排序、含使用计数的标签库

- 标签归一化沿用写入路径 normalizeGameCreationAppAssetTags,同素材重复标签只计一次

- 新增 assetTagsMatchSelection / gameCreationAppAssetMatchesTags 标签筛选判据(AND、空选择不过滤)

- 新增 5 条纯模块测试覆盖去重计数、顺序稳定、空标签与筛选判据
2026-09-10 14:54:06 +08:00
suzmii 30e93c6c43 合并 WS-D 阶段一:新增本地项目素材重命名命令
- 解决 main.rs / project.rs / tests/mod.rs 的相邻插入冲突:main.rs 保留 read_local_project_asset_references 与 rename_local_project_asset 两条注册,project.rs 与 tests/mod.rs 只保留 mod asset_rename(asset_usage 已由 WS-A 整体删除)
- 解决 decision-log.md 末尾追加冲突:保留双方条目(WS-A/口径收敛、删除新口径、C3 交互、Agent 投影 + WS-D 素材重命名)
2026-09-10 14:46:15 +08:00
suzmii 26f1a7b19c Merge branch 'feat/agc-v3-c4-mentions' into feat/agc-canvas-resource-workbench-v3 2026-09-10 14:43:58 +08:00
suzmii dbbca47078 同步 C4 引用补全与 C8 润色提醒的功能文档
- 更新【功能说明】AGC聊天素材引用:补充素材面板双页签、activeVersionId 版本回退与空态口径
- 更新【功能说明】AGC聊天素材引用:把「改名刷新显示名」「跨会话恢复光标位置」从未完成移到已完成
- 新增【功能说明】AGC聊天AI润色与发送前提醒:写明润色回填、原文快照、失败保留原文与提醒面板行为
- 新增文档同时记录提醒判据、不再提醒偏好存本机 localStorage、以及计费仍走平台 LLM 路由的边界
2026-09-10 14:42:40 +08:00
suzmii b2477c8ca9 新增本地项目素材重命名命令(Rust 侧)
- 新增 Tauri 命令 rename_local_project_asset(输入 projectPath / assetId / newFileName),并在 main.rs 的 generate_handler! 中注册
- 新增 project/asset_rename.rs:重命名语义为“磁盘文件改名 + manifest localPath 更新”,资产 id / kind / mediaType / source / category / tags 全部不变,imageSequenceFrames 里指向该文件的本地帧一并对齐
- 校验判据:新名非空、不含路径分隔符(同时保证不跨目录)、不含 ..、通过可移植路径组件校验、扩展名与原文件一致、同目录不得已存在同名文件、assetId 与磁盘文件必须存在
- 事务与回滚:持既有项目写锁,manifest 写入走既有 write_manifest 边界(版本数组校验 + 安装后回读一致性);写失败把文件改回原名,回滚也失败时两个错误都报出并标记 reconciliation-required;同名重命名按空操作处理
- 新增 tests/asset_rename.rs 9 条用例:成功改名与身份不变、同名空操作、空名 / 分隔符 / .. / 跨目录 / 扩展名不一致 / 目标同名冲突 / 资产与文件缺失拒绝、manifest 写失败回滚、同目录帧对齐
- decision-log 记录素材重命名语义、命令边界,以及“前端调用方落地前 ai-game-creator-shell:typecheck 必然失败”的已知中间状态
2026-09-10 14:42:11 +08:00
suzmii b0d7121265 补充 C4 引用页签与 C8 润色提醒的定向测试
- 新增 chatPromptPolish.test.tsx:覆盖提醒判据、草稿指纹、本机偏好读写与润色命令调用
- 覆盖主动润色回填、可重复润色覆盖结果、恢复原文回到最初原文
- 覆盖润色失败保留原文并在提醒面板内给出重试提示
- 覆盖长文本提交被提醒面板拦下、使用原文提交、关闭取消发送、AI 润色后发送
- 覆盖不再提醒偏好落本机 localStorage 后不再拦截,以及挂载时读取该偏好
- 覆盖短文本与命令不触发提醒
- resourceReferenceInput.test.tsx 新增:两个页签的独立搜索与筛选状态
- 新增:activeVersionId、版本回退、悬空绑定过滤与空态
- 新增:资源改名后 chip 与候选列表显示名刷新
- 新增:跨会话恢复草稿后光标落在文本末尾,引用追加在文本之后
2026-09-10 14:38:59 +08:00
suzmii 89678173f5 补齐 AGC V3 C4 聊天通用引用与 C8 AI 润色发送前提醒
- @ 面板新增「当前版本素材 / 全部画布素材」两个页签,两个页签各自持有独立的搜索与分类筛选状态
- ResourceReferenceInput 新增可选 prop activeVersionId 与 versions,并把两者透传到 ProjectWorkspaceChatPane、ProjectSupervisorView、SupervisorChatOnlyView 和 App
- 未传 / null 的 activeVersionId 回退到 manifest versions[] 中最新的版本;版本不存在或没有绑定时页签显示空态,不报错也不合成资源卡
- 「当前版本素材」按该版本 resourceBindings 取仍登记在 manifest 的资产,悬空绑定按资源 id 过滤掉
- 资源改名后按资源 id 刷新编辑区已有引用 chip 与候选列表的显示名,并把新显示名同步回父级草稿
- 程序化重建草稿后把光标收回草稿末尾,跨会话恢复草稿时后续引用不再插到已失效的位置
- 新增 Tauri 命令 polish_local_project_prompt(复用短文本生成通道,计费仍由平台 LLM 路由侧完成,未改 server-rs)
- 输入区新增「AI 润色 / 恢复原文」按钮、润色中状态与失败可重试提示,失败、超时或未配置模型时保留原文
- 原文快照只在首次润色成功时落下,反复润色只覆盖结果,「恢复原文」始终回到最初原文
- 新增发送前提醒独立面板:AI 润色先润色再发送、使用原文提交、关闭取消发送、不再提醒
- 提醒判据:纯文本 trim 后不短于 40 字符、不是 / 开头的命令、本轮草稿未确认过、且用户未关闭提醒
- 「不再提醒」偏好写入本机 localStorage,不进 manifest、不进后端
- 补充润色按钮、状态提示、素材页签与提醒面板样式
2026-09-10 14:38:34 +08:00
suzmii b96f5e1476 Merge branch 'feat/agc-v3-docs' into feat/agc-canvas-resource-workbench-v3 2026-09-10 14:27:53 +08:00
suzmii f8aa87c71c Merge branch 'feat/agc-v3-cleanup' into feat/agc-canvas-resource-workbench-v3 2026-09-10 14:27:47 +08:00
suzmii 48700083a3 补齐 AGC V3 资源口径与环境约束的权威文档
- PRD §5.4 版本记录补充"只允许追加"的唯一例外:用户显式确认的连带删除,并写明放行集合按写入前 manifest 求值、被放行版本不得进入追加段
- decision-log 新增三条 2026-09-10 定稿:版本绑定恒等化并撤回运行期资源观察侧车;素材删除新口径(三分支 + 可选连带删除版本);资源画布交互 1:1 复刻美术画布(取消资源详情面板与全屏编辑路由、复用共享组件以可选 prop opt-in、AGC 宿主自备 chrome 样式)
- pitfalls 新增四条:新工作树的 cargo 必须在 npm ci 之后跑;残留 dev stack 会拖死 Rust 门禁;改共享美术画布组件时 AGC 门禁覆盖不到它自己的测试;AGC 侧没有 image-canvas-editor__* chrome 样式
2026-09-10 14:26:57 +08:00
suzmii 1c9b92e791 在 Agent 资源检索投影中暴露分类与自定义标签
- agent/direct_tool_bridge.rs:bridge_registered_resource 投影新增 category 与 tags,位置固定在 mediaType 之后,保持稳定字段顺序
- agent/direct_tool_bridge.rs:category 直接取 manifest 条目已解析的功能分类(显式合法值原样保留,历史缺字段时已按 kind 派生),tags 缺失时为空数组,不改变 manifest 结构与对外契约
- agent/direct_tools_mcp.rs:agc_list_registered_assets 工具描述补上功能分类与自定义标签,与新增输出字段保持一致
- agent/direct_tool_bridge.rs:既有序列帧投影测试补充 category 与 tags 默认值断言
- agent/direct_tool_bridge.rs:新增 bridge_registered_resource_projects_manifest_classification_verbatim,锁定显式分类与标签原样透出且不泄漏 prompt / model
2026-09-10 14:24:26 +08:00
suzmii 33ed85ba54 AGC 素材删除改为独立确认弹窗并支持勾选连带删除版本
- 新增 ResourceAssetDeleteDialog:列出使用该素材的游戏版本(标识 + 创建时间),给出「把相关游戏版本一并删除」勾选,默认不勾
- ResourceClassificationPanel 删除入口改为先读 read_local_project_asset_references,再打开独立确认弹窗,不再用面板内二次确认态
- 确认删除时按勾选状态传 deleteReferencedVersions;未被任何版本引用时不显示连带删除勾选
- styles.css 补删除弹窗、版本列表与勾选行样式
- 重写并扩充 resourceClassificationPanel.test.tsx 的删除用例:确认弹窗才发起删除、未引用时不出现勾选、列出被引用版本且默认不勾、勾选后传 true、客户端拒绝时不报告删除
2026-09-10 14:22:38 +08:00
suzmii 824934df41 收敛 AGC 资源口径:撤掉运行期使用侧车并改为确认式删除
- 整体删除运行期资源使用侧车模块 project/asset_usage.rs 与 tests/asset_usage.rs(含 16 条测试),并清理 project.rs、tests/mod.rs 里的 mod / use
- game_iteration_resource_bindings 收敛为纯恒等映射 slotId=asset:{assetId}、resourceId=assetId,不再读取侧车、不再做槽位优先
- assets.rs 删除 GAME_ITERATION_IDENTITY_BINDING_PREFIX、asset_is_bound_to_runtime_slot 与 remove_asset_usage_references,删除不再判定运行槽位、不再清理侧车引用
- manifest 写入层把“版本数组只允许追加”校验改为显式接收放行删除的版本 ID:新增 mutate_manifest_at_allowing_version_removals,其余写入路径继续传空放行集合
- delete_manifest_asset_at 改为三分支语义:无任何版本引用直接删登记、被引用且未确认时只删登记并原样保留悬空绑定、被引用且确认时素材与相关版本在同一次 manifest 写入里一起删除
- DeleteLocalProjectAssetInput 增加 deleteReferencedVersions 入参,commands.rs 与 main.rs 同步
- 新增只读命令 read_local_project_asset_references,列出引用了该素材的版本标识、revision 与创建时间
- 新增 tests/asset_delete.rs,覆盖恒等绑定、无引用删除、未确认保留悬空绑定、确认连带删除、引用投影、只允许追加校验仍拒绝其它路径与 CAS
2026-09-10 14:22:18 +08:00
suzmii 912254afa0 Merge branch 'fix/agc-test-selector-cleanup' into feat/agc-canvas-resource-workbench-v3
Project CI / Repository checks (pull_request) Failing after 18m28s
Project CI / Native shell tests (pull_request) Successful in 25m45s
Project CI / Frontend tests (pull_request) Successful in 53m14s
Project CI / Backend tests (pull_request) Successful in 57m10s
2026-09-10 11:52:23 +08:00
suzmii 53afc85319 修正 AGC 测试按新的 Lexical 聊天输入区驱动与断言
- appSurface/harness 补齐三处 jsdom 缺口:ClipboardEvent、DragEvent、Range.prototype.getBoundingClientRect;否则 Lexical 的粘贴通路直接抛 ReferenceError / TypeError
- appSurface/harness 的 submitChat 改为 async:全选并让编辑器吸收选区、清空、走产品真实 paste 通路写入,再让出一帧让 React 追平 draft,最后点发送
- 新增 composerText / composerValue / composerDisabled / setComposerText 助手,按原生控件与 Lexical contenteditable 两种 DOM 口径读写输入区
- 15 个测试文件中 116 处 toHaveProperty('value', …) 断言等义改写为 await composerText() / await composerValue();1 处 placeholder 断言改查输入区占位文案;2 处 disabled 断言改用 composerDisabled(同时覆盖 data-disabled 与 contenteditable=false);9 处 fireEvent.change 写入改用 setComposerText
- 328 处 submitChat 调用补 await,9 个非 async 用例补 async
- Godot 输入区键盘语义用例按 Lexical 实际行为改写:Shift+Enter 与组合态 Enter 由编辑器消化并插入换行、不发送、草稿保留
- 未改动 src 下任何产品代码,未放宽或删除断言
2026-09-10 11:46:02 +08:00
suzmii e1044546ad 接入素材删除入口并收敛资源命令后的 manifest 重载
- ResourceClassificationPanel 增加删除动作:首次点击进入确认态,二次点击才执行,避免误触不可逆操作
- 面板标题与按钮明确写出只移除登记、素材文件保留在磁盘
- 删除失败时把客户端的拒绝原因原样呈现(例如已被运行槽位绑定),并退出确认态
- index.tsx 抽出 reloadManifestAfterAssetCommand:资源分类更新与素材删除共用同一条重载路径,提交标识分别用 asset-classification / asset-delete 前缀
- 补 2 条面板测试:两次点击才删除且入参带 expectedProjectRevision、被运行槽位拒绝时不触发 onDeleted
2026-09-10 10:53:13 +08:00
suzmii 16748821e3 Merge branch 'feat/agc-version-switch' into feat/agc-canvas-resource-workbench-v3 2026-09-10 10:42:20 +08:00
suzmii 69741e5e13 新增素材删除能力与运行槽位绑定判定
- project/asset_usage.rs 增加 write_local_project_asset_usage_at:就地改写引用与诊断,保持身份与 revision 不变
- assets.rs 新增 delete_manifest_asset_at:素材不可变,只摘掉 manifest 登记、保留磁盘文件,并清理使用清单里指向它的悬空引用
- 删除判定只承认真实运行槽位绑定:恒等绑定是版本创建时的兼容兜底,若把它也算作被引用,任何在版本创建前登记过的素材都将永远删不掉
- CAS 口径与资源分类更新一致:持项目写锁后校验 expectedProjectId 与当前 revision,失败时 manifest 与 revision 均不变;成功后 revision 推进一格
- 新增 delete_local_project_asset 命令并注册;内联测试换成带 CAS 的公开入口
- 补 4 条删除测试:恒等绑定不阻止删除、真实槽位绑定拒绝删除并报出版本与槽位、删除清理悬空引用、陈旧 revision 与跨项目身份被拒且状态不变
2026-09-10 10:42:02 +08:00
suzmii c1ffb2386b 资源功能分类与自定义标签:筛选口径收敛为 category 并接入写入 UI
- 删除 resourceReferences.ts 中 mediaType 优先的 7 桶筛选口径与 resourceReferenceFilterKind,改由 resourceReferenceCategory / resourceReferenceMatchesCategoryFilter 以 manifest 资产 category 为唯一权威筛选口径
- RESOURCE_REFERENCE_FILTERS 改为 6 类 + 全部(全部 / UI 交互 / 角色与对象 / 场景与环境 / 音频 / 文档 / 待归类),并新增 resourceReferenceCategoryLabel 让中文名只定义一处
- ResourceReference 新增 category / tags,由 resourceReferenceFromAsset 经 gameCreationAppAssetCategory / gameCreationAppAssetTags 从 manifest 资产投影;资源画布 ProjectResource 同步新增 assetCategory / assetTags 投影与 projectResourceAssetCategory 待归类兜底
- 新增 Tauri 命令 update_local_project_resource_classification 与 UpdateLocalProjectResourceClassificationInput/Result,实现落在 project/manifest.rs 的 update_manifest_asset_classification_at,复用既有 mutate_manifest_at manifest 写锁与原子写入
- 命令持项目写锁后按 expectedProjectId / expectedProjectRevision 做 CAS,冲突分别返回 project-identity-conflict 与 project-revision-conflict;category 非 6 个合法值即报错且不回退到 kind 派生;tags 走 normalize_game_creation_app_asset_tags;只改目标条目的 category / tags 并在成功后推进一次项目 revision
- 新增 Rust 定向测试 project/manifest/classification_tests.rs:更新成功、非法分类被拒、tags 归一化、未引用资产更新不影响其它字段、revision/CAS 与既有写入一致、输入拒绝未知字段
- 前端新增 ResourceClassificationPanel(ThemedModal + PlatformSegmentedTabs + PlatformTextField),资源详情面板以「分类与标签」按钮打开独立面板,保存后重读 manifest 并经 onManifestChange 反映到筛选与投影
- @ 面板与资源画布共用 packages/shared 的 PlatformSegmentedTabs 渲染同一组功能分类标签;画布筛选状态与画布视口一样按「按依赖 / 按类型」分别保存,复用既有 sortMode 状态形状
- 更新 resourceReferenceInput.test.tsx 与 projectResourceProjectionModel.test.ts 覆盖新口径,新增 resourceClassificationPanel.test.tsx 覆盖写入命令的输入归一化与失败不回退
- 同步技术方案与 decision-log:记录按 mediaType 分类到按 category 分类的语义变化(图片 / 视频桶消失,image / video / code / publication-material 归待归类)与新写入命令契约
2026-09-10 10:25:22 +08:00
suzmii 691d0a8ce6 修正 AGC 资源卡测试查询的资源详情按钮选择器歧义
- appSurface/harness 新增 getResourceDetailButton / findResourceDetailButton / queryResourceDetailButton,按产品可访问名模板「打开资源详情:<分类标签> <资源文件名>」精确匹配,不再用会同时命中 @ 引用按钮的文件名正则
- project-development.suite 的 23 处资源卡按钮查询改用精确查询助手
- home.suite 的 7 处资源卡按钮查询改用精确查询助手
- projectResourceLiveIntegration.test 的 /rules\.md/ 按钮查询改用精确查询助手
- 未改动 src 下任何产品代码,未放宽任何断言
2026-09-10 10:15:50 +08:00
suzmii d526439508 版本资源绑定升级为真实运行槽位
- 新增 game_iteration_resource_bindings:优先使用使用清单里的 slotId 到 resourceId 绑定,再为未被槽位覆盖的资源补 asset:{id} 恒等绑定
- 恒等绑定保留是有意的:历史版本与既有校验语义不受影响,同一资源被多个槽位使用时只取首个槽位,不产生冲突语义
- ensure_initial_game_iteration_version_at 与 append_agent_game_iteration_version_at 共用该构造函数
- 补测试:无使用清单时保持恒等绑定;登记运行槽位后槽位绑定优先且未覆盖资源仍有兜底
2026-09-10 10:13:46 +08:00
suzmii a571c6e225 新增运行期资源使用归属层(C1 素材使用清单)
- 新增 project/asset_usage.rs:运行槽位到资源身份的稀疏归属清单,落在 .agent/workbench/asset-usage/usage.json
- 只保留 manifest 没有的事实(slotId/loadSite/attribution);已登记资源只存身份引用,不复制 path/sha256/kind/mediaType,避免第二份资源清单
- 引用了不存在的资源 ID、未登记素材、文件缺失、槽位重复注册一律只产出 warning 诊断,不阻断运行
- revision 只允许前进;同 revision 重放幂等;projectId 不匹配按未登记处理
- 写入沿用 resource-layouts 同款 flock 独占锁 + 原子替换
- 补 11 条定向测试:稀疏引用、未登记/未知资源 ID、缺文件、重复槽位、无槽位引用、幂等、revision 回退拒绝、projectId 隔离、落盘不含 manifest 字段
2026-09-10 10:05:28 +08:00
suzmii 3f5d3b43af 合并最新 master 到 AGC V3 资源画布分支
Project CI / Repository checks (pull_request) Failing after 7m37s
Project CI / Frontend tests (pull_request) Failing after 8m52s
Project CI / Backend tests (pull_request) Failing after 10m24s
Project CI / Native shell tests (pull_request) Failing after 10m36s
- 合入 origin/master 6 个提交(#313 项目写锁残留回收与启动诊断、#315 AGC Ctrl+C 残留后端修复)
- 冲突解决:decision-log.md 保留 V3 两条 2026-09-09 决策与 master 的写锁回收条目
- 冲突解决:pitfalls.md 保留 V3 转场经验条目与 master 的 Ctrl+C 后端残留条目
2026-09-09 21:46:09 +08:00
suzmii c936268337 Merge branch 'feat/agc-canvas-real-flip' into feat/agc-canvas-resource-workbench-v3 2026-09-09 21:40:37 +08:00
suzmii 4a5b2ad524 修复返回总览时非活动栏目标题栏从屏幕外飞入
- begin 只记录当时可见的节点:不可见节点(内联 opacity 0)的屏幕矩形不是可信的 First
- 非活动栏目标题栏在子画布态保留总览布局坐标,却被子画布 world 变换推到屏幕外,返回总览时不再以它作为动画起点
- 没有 First 的节点只走淡入,位置保持不动;活动栏目标题栏仍按原有观感收回到总览位置
- 进入栏目方向不受影响:标题栏当时可见,仍钉在原地淡出
- 新增回归用例:不可见标题栏几何首末相等、活动标题栏仍有几何动画、进入方向标题栏原地淡出
- 同步技术方案转场段与 pitfalls 经验条目
2026-09-09 21:39:20 +08:00
suzmii 4582231b78 合并 C3:资源画本转场改为真实元素 FLIP
- 合入 feat/agc-canvas-real-flip(resourceBookController 重写为同节点 FLIP、新增 sync 可中断重基、删除 ResourceBookTransitionLayer)
- 冲突解决:index.tsx 的 resourceBookLayout 导入取 C3 版(buildResourceBookScenePlan 等),去掉已被收进内部的旧 helper 导入避免未使用
- 冲突解决:资源自由画板文档的验证命令合并两侧并去掉已删除的 assetCanvasSurface.test.tsx
2026-09-09 21:24:13 +08:00
suzmii a23728e6c2 合并 C10:删除 asset-canvas 精修草稿链路
- 合入 feat/agc-asset-canvas-removal 5 个提交(前端解耦 / Rust 删除 / 测试 / 文档 / 合同文档退役)
- 冲突解决:asset_canvas.rs、asset_canvas/generation.rs、asset_canvas_tests.rs 按 C10 删除(C2 在其中新增的 category/tags 构造点随之消失)
- 冲突解决:decision-log.md 保留 C2 与 C10 两条 2026-09-09 决策条目
- 该合并吸收 C2 对被删文件的字段改动,无需单独处理
2026-09-09 21:19:52 +08:00
suzmii f84d0ea4d0 AGC 资源功能分类与自定义标签数据层
- manifest 资源条目末尾新增 category(单值 6 类,默认 unclassified)与 tags(字符串数组,默认空数组),两字段始终序列化
- shared-contracts 新增 GameCreationAppAssetCategory 枚举、GAME_CREATION_APP_ASSET_CATEGORIES 与穷举 16 个 canonical kind 的默认映射表 GAME_CREATION_APP_ASSET_CATEGORY_BY_KIND
- 历史 manifest 缺少字段时按 kind 派生分类、tags 取空数组;显式合法分类原样保留,未知分类字符串按前向兼容退回 kind 派生
- packages/shared 同步 TS 契约:分类常量与类型、映射表、gameCreationAppAssetCategory / gameCreationAppAssetTags 取值 helper 与 tags 归一化 helper
- 收编壳内 17 处 Rust 构造点:新建条目写 kind 派生默认值,更新既有条目保留已有 category / tags
- 补契约测试:字段 round-trip、默认值、legacy 兼容、未知分类前向兼容、16 项映射穷举、tags 归一化
- 同步 docs/technical 技术方案与 docs/project-memory decision-log
2026-09-09 21:10:01 +08:00
suzmii 91396f0615 删除已退役的客户端素材创作无限画布阶段一合同文档
- `docs/technical/【技术方案】客户端素材创作无限画布阶段一合同-2026-08-05.md` 整篇退役:该文档描述的中央 asset-canvas 状态、草稿 sidecar、Tauri 草稿/媒体/staging 命令、正式资产提交事务、提交事件、画布状态机与图片生成账本已随机制删除,按四不写不留墓碑文档。
- 仍有效内容已迁往:共享画布包目录与依赖方向、禁止复制的验收门、Host Port 边界、共享 chrome 与主题、图片能力与后续分期迁入 `docs/technical/【前端架构】图片画布编辑器前端拆分计划-2026-06-17.md`;成功投影顺序与焦点守卫迁入 `docs/technical/【技术方案】GameAgent资源自由画板与快速编辑-2026-08-20.md`;共享源码与资源派生闭环现状迁入 `docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
- 全类型现有资源非破坏性编辑的现成权威条款为 `docs/technical/【技术方案】AGC资源派生与非破坏性编辑合同-2026-09-09.md`,它是 `project/resource_editor.rs` 的现行合同,已改写为 derive + editKind='image-reference' 口径。
- decision-log 的 2026-09-09 条目改写为决策文体:写清退役理由与现权威口径所在文档,历史条目保留原样。
- 过渡项删除条件:资源画布重建快速编辑后,`normalize_local_project_raster_resource` 重新获得前端调用方,届时删除 `apps/ai-game-creator-shell/scripts/check-config.mjs` 中该 allowlist 条目。
2026-09-09 20:32:38 +08:00
suzmii 4c935ba219 同步 asset-canvas 退役后的文档现状并迁移仍有效合同
- 新增 docs/technical/【技术方案】AGC资源派生与非破坏性编辑合同-2026-09-09.md,承接阶段一合同第 16 节与第 5 节的现役条款,改写为 derive + editKind='image-reference' 口径
- 图片画布编辑器前端拆分计划新增"共享画布合同"一节,承接共享包目录与依赖方向、禁止复制的验收门、Host Port 边界、共享 chrome 与主题、图片能力与后续分期
- GameAgent资源自由画板与快速编辑删除精修草稿、最终图事务、精修来源规范化、事务恢复对账、批量导入与失败任务归档等退役章节,改写目标与操作边界,新增"编辑结果投影与焦点竞态"一节,并更新验证命令
- App实施计划删除阶段一至五的草稿画布描述,改写为共享画布源码与资源派生闭环现状,修正主视窗状态、适配器与派生提交条款
- 项目开发工作台 PRD 更新主视窗状态机与 ProjectWorkbenchViewState,移除 asset-canvas 状态
- decision-log 追加 2026-09-09 asset-canvas 整体退役决策(含保留项、路由口径、临时白名单与存量数据处置)
- pitfalls 删除草稿 CAS、素材保存区、正式素材提交原子性、生成账本与 upload ticket 五条退役条目,修正仍有效条目的关联文件与 legacyPrefix 描述
2026-09-09 20:28:22 +08:00
suzmii 4fb0de2c94 C3 资源画本转场改为真实元素 FLIP
- resourceBookController 重写为真实元素 FLIP:begin 在提交前记录 First,play 测 Last 后给同一节点加反向 transform,sync 按布局签名做可中断重基
- 转场进行中重基用剩余时长(下限 80ms),空闲布局变化用完整 420ms,预览图尺寸晚到的二次布局变化不再瞬移
- 宿主节点几何基准改存 world 局部坐标,平移和缩放不会让上一次的干净矩形过期
- 不可见节点钉在 First 淡出、挂载节点淡入;搜索/排序导致的卸载保留一帧淡出;返回总览时源栏目未回到摞上的卡片按子画布布局淡出
- 新增 buildResourceBookScenePlan 渲染计划,渲染、转场签名与淡出集合共用同一份数据
- 删除 ResourceBookTransitionLayer 与克隆快照逻辑,清理 styles.css 的转场层和 data-book-motion 隐藏规则
- 重写 resourceBookController.test.ts,补 resourceBookLayout/resourceBookModel 用例与 appSurface 同节点断言
- 同步技术方案转场段与 pitfalls 经验条目
2026-09-09 20:24:10 +08:00
suzmii a1b9b24891 修复 AGC Ctrl+C 残留上个工作树后端导致切换工作树复用旧后端 (#315)
Project CI / Frontend tests (push) Successful in 3m14s
Project CI / Backend tests (push) Successful in 8m54s
Project CI / Repository checks (push) Successful in 2m29s
Project CI / Native shell tests (push) Successful in 19m23s
closes #314

## 现象

`npm run agc` 按 Ctrl+C 后有概率残留上个工作树的 `api-server.exe` / SpacetimeDB,切换 worktree 再启动时 AGC 复用旧后端,改过数据库 / schema 的工作树会串库。

## 根因

1. Windows 下长驻服务都经 Node `shell: true` 的 `cmd.exe /d /s /c` 包装层启动,Ctrl+C 先杀包装层(`0xC000013A`);`dev.mjs` 的 `stopProcess` 见到直接子进程已退出就 return,`taskkill /PID <已退出 PID> /T /F` 也只会失败,深处的 `cargo → api-server.exe` 无人清理。
2. 按根 PID 遍历依赖快照里的父子链,中间层先消失时链断,只能拿到根 PID。
3. 复用判据只看 `.app/dev-stack.json` status 与 `/healthz`、`/readyz`、`/v1/ping`,不校验端口上的进程属于哪个工作树,残留后端照样被判为健康并复用。

## 改动

- 新增 `scripts/dev-windows-process.mjs`:按根 PID 遍历 + 按身份匹配(`server-rs/target/debug/api-server.exe` 绝对路径、SpacetimeDB `--data-dir`)两条独立清理路径,带 1s 快照缓存避免清理被拖慢。
- `scripts/dev.mjs`:直接子进程已退出时仍按记录 PID 清理后代;退出时按身份兜底清扫本工作树后端(复用他人 standalone 时跳过);启动前清理旧 api-server 保留 `Wait-Process` 语义,避免 `failed to remove file`。
- `apps/ai-game-creator-shell/scripts/start-dev-stack.mjs`:复用前校验端口监听进程归属,无法证明归属就不复用、改为启动本工作树后端并允许端口漂移;信号与 `finally` 各兜底清扫一次;`taskkill` 失败时降级按 PID 遍历;等待就绪时输出归属校验失败原因,避免静默超时。探测不可用时退化为旧行为,不阻断本地启动。
- 测试与文档:新增 `scripts/dev-windows-process.test.ts`、扩充 AGC 复用门禁用例;同步 `docs/project-memory/shared-memory/pitfalls.md` 与本地开发运维文档。

## 验证

- 伪造 `api-server.exe` 进程:按身份精确命中并杀掉(`matched=[17284] stopped=[17284]`)。
- 3 个真实监听进程下归属判定:`owned` / `api-server-owner-mismatch` / `spacetime-owner-mismatch` 均正确。
- `npx vitest run scripts/dev.test.ts scripts/dev-windows-process.test.ts scripts/dev-stack-port-utils.test.ts apps/ai-game-creator-shell/tests/...`:119 passed(唯一失败为 Windows 文件权限用例,已确认在合并基线 `origin/master` 上同样失败)。
- `node --check`、`eslint --max-warnings 0`、`prettier --check`、`npm run check:encoding`、`git diff --check` 全部通过。

## 备注

Rust 侧 `api-server` 的 `with_graceful_shutdown` 没有超时上限,是「有概率」的来源之一;本次只在 Node 侧收口,是否给优雅退出加 deadline 可另行评估。

Reviewed-on: https://git.genarrative.world/git/GenarrativeAI/Genarrative/pulls/315
Co-authored-by: Suzumiya <suzmii@qq.com>
Co-committed-by: Suzumiya <suzmii@qq.com>
2026-09-09 20:08:04 +08:00
suzmii 7c49e7e51c Merge pull request '修复 AGC 项目写锁残留无法回收与启动失败无诊断' (#313) from fix/agc-stale-lock into master
Project CI / Backend tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Project CI / Frontend tests (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
Reviewed-on: #313
Reviewed-by: 孔令弘 <ink29535@proton.me>
2026-09-09 20:07:37 +08:00
suzmii 05d4d6d1d3 删除 asset-canvas 测试并同步资源派生测试口径
- 删除 tests/assetCanvasSurface.test.tsx(4310 行,63 个用例全部属于草稿画布 Surface)
- projectResourceLiveIntegration.test.tsx 删除 AssetCanvasSurface mock、discover/recover 两个命令 stub、normalize_local_project_raster_resource stub、TaskImageWorkbench 与 4 个草稿精修用例;保留文档资源走资源编辑的用例
- project-development.suite.ts 把 showRunUnavailableHint 源码护栏正则去掉 !assetCanvasRoute,并把"in-place refine 预览失效"用例改写为仍有效的 preview version 装配与项目切换清理
- resourceEditModel.test.ts 把 PNG/WebP 两例从 image-canvas 改为 derive + image-reference,并移除 normalizeSource 断言
2026-09-09 20:05:07 +08:00
suzmii 3ea94b27d4 删除 asset-canvas Rust 草稿存储与生成链路
- 删除 project/asset_canvas.rs(4784 行,草稿 sidecar、媒体 staging、正式提交事务与 journal)
- 删除 project/asset_canvas/generation.rs(9514 行,草稿私有生成账本、参考图上传、服务身份确认、候选落盘与内联 63 个测试)
- 删除 project/asset_canvas_tests.rs(2356 行)
- project.rs 移除 mod asset_canvas 与 pub(crate) use asset_canvas::*
- commands.rs 移除 851-1088 共 18 个 Tauri 命令(含 commit_local_project_asset、commit_local_project_asset_canvas_candidate)
- main.rs 从 generate_handler! 移除对应 18 行注册
- 保留 canvas.asset_generate 命令契约(packages/shared 与 shared-contracts 的正式权限合同,Agent Runtime 仍在使用)
- 保留 server-rs 的 asset-canvas-references 上传票据白名单契约(后续项,本次不动服务端)
2026-09-09 20:04:43 +08:00
suzmii 9fd5b99972 删除 asset-canvas 前端精修草稿入口并重接资源派生
- 删除 apps/ai-game-creator-shell/src/features/asset-canvas 全部四个文件(AssetCanvasSurface.tsx、assetCanvasNaming.ts、assetCanvasSurface.css、tauriImageCanvasHostAdapter.ts)
- index.tsx 删除 assetCanvasRoute 状态与 ref、beginAssetCanvas/openAssetCanvas/normalizeRasterSourceAndOpen、cancelAssetCanvas/handleAssetCanvasSaveAttempt/handleAssetCommitted 与 game-creator-local-asset-committed 事件监听、中央主视窗渲染分支与 resources.asset-canvas 视图状态
- index.tsx 把 assetCanvasNotice 改名为通用 resourceWorkbenchNotice,保留非草稿流程文案与资源定位按钮
- resourceEditModel.ts 取消 image-canvas 路由,PNG/JPEG/WebP 统一走 derive + image-reference(复用 resource_editor.rs 的 /editor/images/edits)
- styles.css 删除 resources.asset-canvas 两条画布选择器,保留 resources.ui-editor 规则
- check-config.mjs 把 normalize_local_project_raster_resource 加入 native-only 白名单(临时项:C3 快速编辑重建后会重新获得前端调用方,届时删除该 allowlist 条目)
- index.tsx 移除 useMemo 依赖数组里模块级常量 visibleCategoryOrder(基线遗留的 react-hooks/exhaustive-deps warning,会阻断 pre-commit,属通过门禁所需的最小修正)
2026-09-09 20:04:24 +08:00
suzmii c0ef876b14 修复 AGC 项目写锁回收的并发删除竞争与启动诊断死路径
Project CI / Backend tests (pull_request) Successful in 5m56s
Project CI / Repository checks (pull_request) Successful in 2m29s
Project CI / Frontend tests (pull_request) Successful in 3m5s
Project CI / Native shell tests (pull_request) Successful in 17m44s
- 锁文件回收改为单次快照解析:payload 只解析一次,删除前重新核对字节,内容已被并发方替换或文件已消失时返回 false 并重试 create_new,不再无条件 unlink,也不再把 remove_file 的 NotFound 当成硬失败
- mtime 读取失败改用 Option 区分“未知”与“纪元 0”:未知年龄不回收,未知锁创建时间不做 PID 复用推断,避免把保守判定反转成抢走活持有者
- 新增并发替换/已消失、旧格式活持有者、存活未知、mtime 未知四条回归用例
- 启动日志槽优先用已经生效的配置目录(含 --config-dir),否则退到平台配置根(APPDATA / Application Support / XDG_CONFIG_HOME)
- 日志路径未知时 StartupLogSlot::fail 仍然给出用户可见提示,四处 inspect_err 不再被路径判空挡掉
- 同步 check-config 守卫、decision-log、pitfalls 与技术方案文档的用例数与新判据
2026-09-09 19:44:46 +08:00
suzmii 1abbbadb07 合并最新 master 到 AGC V3 资源画布分支
- 合入 origin/master 67 个提交(含 #285 无限画布图标模糊修复、#307 客户端常用设置读写卡顿修复等)
- index.tsx 冲突按 V3 画本结构解决:保留 resourceBook 画布场景,未采用 master 的 CanvasWorld 包装
- 原因:game-resource-page-world 现仅承载依赖连线层,CanvasWorld 使用固定 12000 世界尺寸,会破坏按内容自适应的边界
- 保留 AtSign 图标导入(V3 引用输入区使用)
- 合并后 npm run ai-game-creator-shell:typecheck 通过
2026-09-09 18:12:09 +08:00
suzmii b7f27721c5 修复 AGC 项目写锁回收判据的跨平台 PID 边界
Project CI / Repository checks (pull_request) Successful in 3m8s
Project CI / Frontend tests (pull_request) Successful in 4m15s
Project CI / Backend tests (pull_request) Successful in 6m47s
Project CI / Native shell tests (pull_request) Successful in 17m58s
- 项目写锁存活判定把平台不可能分配出的进程号(0 或超出平台 pid 宽度)判为持有者不存在并直接回收,不再落回 600 秒保守分支
- 回归用例的死进程 PID fixture 由 0xFFFF_FFF0 改为 i32::MAX - 1,同时落在两个平台进程号空间之外且在 Unix 有符号 32 位范围内
- 新增 project_write_lock_reclaims_unrepresentable_owner_pid 用例,覆盖 u64::MAX 非法进程号残留锁
- 修正 project_lock_recovery 头部注释,说明这些用例是回归护栏而不是待修缺陷
- 同步 decision-log 的 PID 边界决策与 pitfalls 的跨平台 fixture 经验
2026-09-09 18:00:53 +08:00
suzmii b460fea02c 移除资源画布加载转圈提示
转圈的判定与状态联动引入了多处副作用,按要求整体移除
删除 ResourceBookThumbnail 的 loading 属性与框内转圈、resourceBookIsLoading 判定函数、对应样式和用例
资源画布恢复为不显示加载态
2026-09-09 17:29:47 +08:00
suzmii 78ba9e33b6 修复 AGC 启动诊断守卫用例
Project CI / Repository checks (pull_request) Successful in 2m49s
Project CI / Frontend tests (pull_request) Successful in 4m2s
Project CI / Backend tests (pull_request) Successful in 6m45s
Project CI / Native shell tests (pull_request) Failing after 14m22s
- check-config.mjs 启动诊断守卫改按 StartupLogSlot 契约匹配:setup_log.fail 加稳定失败码
- 同时校验 StartupLogSlot 内部仍写有界诊断日志并调用可见提示,避免守卫被绕过
2026-09-09 17:27:30 +08:00
suzmii 91f335468a 修复资源画本动画期间布局变化导致的跳变
上一版在布局变化时重启整段动画,导致此前修好的遮挡顺序与加载态全部复现,已回滚该做法
控制器新增 retarget:临时解除场景隐藏后重新测量真实卡片,只把运行中动画的结束帧改成映射到新几何;克隆几何、动画进度与叠加层都不重建
布局变化 effect 在动画期间调用 retarget,空闲时才 settle
补充 resourceBookController 用例:重定向不新建动画,结束帧映射到新目标
2026-09-09 17:24:00 +08:00
suzmii ed50ad122f 修复资源画本动画结束瞬间的位置跳变
动画期间卡片尺寸/位置一变(子画布卡片尺寸要等预览图尺寸到位才算准,通常正好落在动画末尾),原先直接 settle 掉动画,真实场景瞬间接管,表现为结束瞬间跳一下
改为从当前叠加层几何重定向:begin 捕获当前飞行位置,再平滑续到新的目标位置,动画不再被掐断
补充 effect 依赖 resourceBookState.phase
2026-09-09 17:16:38 +08:00
suzmii cca4881e13 修复部分功能画布不显示加载态
原先只在「该栏目有可预览素材且一张都没落地」时转圈;版本、音频这类不请求预览的栏目既不转圈,卡片又要等依赖图就绪才渲染,表现为美术资源转完后项目版本才一起出现
判定改为:有资源且(卡片尚未渲染 或 需要预览但一张都没落地)就转圈,每个栏目各自独立结束
补充 resourceBookModel 用例覆盖卡片未渲染与无需预览两种情况
2026-09-09 17:11:14 +08:00
suzmii 0914703032 修复资源画本飞行期间遮挡沿用错误一侧顺序
卡片真正重叠的是堆叠那一侧:进入时重叠在总览摞(来源),返回时重叠在摞(目标)。原先统一按目标顺序排,进入时就用错了顺序,堆叠里下面两张飞到上面,结束才回到下方
play 增加 stacking 参数:进入用来源顺序,返回用目标顺序;不在该侧的卡片压到最底层
补充 resourceBookController 用例覆盖来源顺序
2026-09-09 16:58:44 +08:00
suzmii 3153d4f674 修复 AGC 项目写锁残留无法回收与启动失败无诊断
Project CI / Repository checks (pull_request) Successful in 2m50s
Project CI / Frontend tests (pull_request) Successful in 3m30s
Project CI / Native shell tests (pull_request) Failing after 4m30s
Project CI / Backend tests (pull_request) Successful in 6m32s
- .agent/project.lock 新增 processStartedAt,PID 存活时核对进程启动身份,身份不一致判定 PID 复用并回收
- 旧锁缺少 processStartedAt 时退回“进程启动时间晚于锁 createdAt 加 5 秒容差”的 PID 复用推断
- 空锁 / 坏锁(崩溃停在 create_new 与落盘之间)宽限期由 600 秒收紧到 30 秒,无法判定持有者存活时仍保持 600 秒
- 新增 StartupLogSlot,配置目录就绪前后都能写 startup.log,startup.*.failed 与启动错误提示不再是死分支
- Windows 启动失败恢复系统消息框并附诊断日志路径,其它平台写 stderr,同一进程只提示一次
- 新增 project_lock_recovery 6 条回归用例:死 PID、空锁宽限、PID 复用时间推断、PID 复用身份不一致、身份一致不抢锁、新鲜空锁不抢锁
- 同步 decision-log 与 App 实施计划文档
2026-09-09 16:58:41 +08:00
suzmii a655ee1b1e 修复资源画本转场克隆遮挡顺序的第二次跳变
两个场景的 z-index 数值本身不同(总览 1-3、子画布 25),只搬数值无法保证顺序一致;且只存在于来源里的淡出克隆被排在最后会盖住存活卡片,消失时表现为跳变
改为按目标场景的最终绘制顺序(z-index 升序,同值再按 DOM 顺序)给克隆分配单调层级,淡出项统一压到最底层
快照记录 DOM 序号,补充 resourceBookController 用例断言层级单调与标题栏在卡片之上
2026-09-09 16:49:53 +08:00
suzmii 9a3313fe56 修复资源画本转场克隆的遮挡顺序跳变
层级相同的卡片由 DOM 顺序决定遮挡,而转场层原先按来源 key 在前追加克隆,和动画结束后的目标场景顺序不一致,结束瞬间会跳变
改为先按目标场景 DOM 顺序排列,再补只存在于来源里的 key
补充 resourceBookController 用例覆盖来源与目标顺序不同时的克隆顺序
2026-09-09 16:43:54 +08:00
suzmii 04ab7ee0da 资源画布加载态改为每栏目框内提示
撤销全屏遮罩,改为每个功能画布框内各自转圈:该栏目一张预览都没落地时显示,本栏目加载完立即消失
版本、占位与音频卡片本来不请求卡片预览,不计入等待,避免一直转圈
加载态 pointer-events 为 none,不拦截点击,未加载完也能直接进入栏目
判定函数改为按栏目,补充 resourceBookModel 用例
2026-09-09 16:41:03 +08:00
lhk229 04128eb661 Merge branch 'master' into feat/design_agent_simple
Project CI / Repository checks (pull_request) Has been cancelled
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 / Repository checks (push) Successful in 2m48s
Project CI / Frontend tests (push) Successful in 3m26s
Project CI / Backend tests (push) Successful in 7m17s
Project CI / Native shell tests (push) Successful in 19m27s
2026-09-09 16:34:16 +08:00
k88936 7288c6f641 Fix/修复无限画布图标文字模糊 (#285)
Project CI / Repository checks (push) Successful in 2m59s
Project CI / Frontend tests (push) Successful in 3m29s
Project CI / Backend tests (push) Successful in 8m37s
Project CI / Native shell tests (push) Successful in 20m55s
closes #279
closes #284

实现: 从2(暂定)倍放大绘制的画布scale 0.5* 真正的scale

before:
![shotmd-1788579338.jpg](/attachments/9d44ec5e-454c-449b-86bc-b2131fbbc8cc)
![shotmd-1788578910.jpg](/attachments/c9da61a2-b467-43eb-a5de-e5e4d5cb8f70)

after:
![shotmd-1788580930.jpg](/attachments/f1f06e8e-90bf-45e2-a30b-a63ccc308c19)
![shotmd-1788583400.jpg](/attachments/c48ca741-e470-42b3-8030-064e0579f63a)
svg看起来stroke窄了一点点, 可以接受

Reviewed-on: #285
2026-09-09 16:32:43 +08:00
suzmii bafae0f127 资源画布首屏加载增加转圈提示
进入资源画布到首屏预览落地之间画布是空的,新增居中加载态:Loader2 转圈加文案,role=status 可被读屏播报
加载判定抽到 resourceBookIsLoading:有资源且布局未就绪或首屏预览一张都没落地时显示;预览失败同样视为落地,避免一直转圈
补充 resourceBookModel 加载态用例
2026-09-09 16:31:02 +08:00
suzmii 985db358a7 修复资源画本转场克隆的层级与静态不一致
转场层里的克隆只按自身 DOM 顺序叠放,而静态场景中卡片是 25、标题栏是 30,导致动画期间卡片盖住标题栏、结束后又瞬间跳回下方
快照记录元素自身或最近定位祖先的 z-index,克隆按该值设置层级,保持静态层叠关系
补充 resourceBookController 层级回归用例
2026-09-09 16:22:07 +08:00
suzmii 2c5ad3903e 总览摞按当前视图就近选卡
总览原先按资源数组顺序取前 3 张,和进入子画布后视口内最近的卡片不是同一批,FLIP 找不到对应终点就退化成淡入淡出,返回总览时还会跳成另一张图
新增 selectResourceBookOverviewCards:按卡片中心到该栏目当前视口中心的距离选最近的卡片,仍受筛选可见性约束
每栏目视口中心换算到画布坐标后传入资源画本场景
补充 resourceBookLayout 用例覆盖无视图中心、就近排序与筛选过滤
2026-09-09 16:16:16 +08:00
suzmii 85f0f6a57b 修复资源画本入场动画找不到终点卡片
进入子画布时若视口停在空白处,目标卡片不在画面内,FLIP 快照找不到终点会整段退化成淡入淡出,只剩标题栏有动画
新增 ensureResourceBookContentVisible:视口内没有可见卡片时,把离画面中心最近的卡片平移到画面中心,保持当前缩放不变
openResourceBookChild 计算入场视口后应用该修正,保证动画终点落在真实卡片上
补充 resourceBookViewport 用例覆盖已可见、就近居中与空栏目三种情况
2026-09-09 16:09:39 +08:00
suzmii 79cf932cba 修复资源画本转场快照丢失卡片预览图
快照克隆原先直接复制 blob: 对象 URL,源卡片卸载或预览缓存驱逐后该 URL 被 revoke,克隆里的图片随即失效,表现为进入/退出子画布时卡片框在动而图片不跟着展开和复原
对 blob: 来源且已解码的图片在克隆时栅格化为 data URL 冻结,与既有视频快照处理保持一致
补充 resourceBookController 快照图片冻结回归用例
2026-09-09 15:55:05 +08:00
lhk229 5b82a269e9 修正定向测试 Rust 格式
Project CI / Repository checks (pull_request) Successful in 2m47s
Project CI / Frontend tests (pull_request) Successful in 4m3s
Project CI / Backend tests (pull_request) Successful in 6m40s
Project CI / Native shell tests (pull_request) Successful in 18m41s
按 CI rustfmt 规范整理策划会话与视觉提示测试
2026-09-09 07:52:46 +00:00
suzmii 6d3d24df5e 合并 #283 资源画本画布实现
合入 feat/game-agent-canvas-resource-workbench-v2 的完整画布实现:资源画本控制器/模型/视口/布局、转场层、资源分页缓存与自动聚焦修复、资源总览卡片预览懒加载
保留本分支已有的聊天素材引用改动(引用输入区、@ 引用按钮、运行画面点选)
解决 index.tsx 状态声明与 pitfalls.md 文档冲突;styles.css 去重后仅保留一份画本视觉层
2026-09-09 15:43:32 +08:00
lhk229 dd0fd5feff 修复合并后的定向测试
Project CI / Repository checks (pull_request) Failing after 1m49s
Project CI / Frontend tests (pull_request) Successful in 3m45s
Project CI / Backend tests (pull_request) Successful in 6m23s
Project CI / Native shell tests (pull_request) Successful in 20m16s
移除不再符合 hydrate 锁语义的过时测试

同步视觉 Agent prompt 测试与当前资源合同
2026-09-09 07:41:52 +00:00
suzmii 8ed7e201d1 补齐资源画本迁移遗漏的输入区模型触发器样式
用 postcss 精确比对 #283 与当前分支的 CSS 规则集合,补齐唯一缺失的 .project-supervisor-surface.is-direct-codex .conversation-model-trigger
补齐后 #283 的 1057 条规则在当前分支全部存在,postcss 解析通过
2026-09-09 15:32:09 +08:00
suzmii 30580814d7 修复资源画本样式冲突遗留的缺失闭合括号
解决 #283 迁移与 master 的冲突时,.conversation-model-select [role='status'] 规则丢失了闭合大括号
导致 Tailwind 生成阶段报 Missing closing },Vite 错误覆盖层阻断页面打开
补齐闭合括号后 postcss 解析与 Vite 转换均恢复正常
2026-09-09 15:23:20 +08:00
suzmii 10e28b038c Merge remote-tracking branch 'origin/master' into feat/agc-canvas-resource-workbench-v3 2026-09-09 15:14:54 +08:00
suzmii aba896bf65 迁移资源画本视觉层样式
自 #283(V2.0 资源画本)迁入 .game-resource-book-* 视觉层共 523 行
该层作为 V3 资源画布的样式基础,JSX 待画布重写时接入
不包含 V2 主/子画布的行为代码
2026-09-09 15:04:18 +08:00
suzmii bf03a18676 修复资源依赖本地身份解析
资源依赖图同时索引远端资源身份和 local-asset 本地资源身份

本地 canonical 引用与远端候选去重后再建立依赖连线

补充本地身份解析与候选不歧义回归测试

更新资源自由画板技术方案的依赖身份合同
2026-09-09 15:03:54 +08:00
suzmii 57a5b642fc 建立AGC资源语义类型契约
共享契约新增 canonical 资源类型目录和历史类型读时映射

角色动画派生结果写入 character-animation 主类型

补充角色动画落盘与 legacy 类型映射回归测试

更新资源自由画板技术方案的类型边界说明
2026-09-09 15:03:54 +08:00
lhk229 852d9af483 合并最新 master
Project CI / Repository checks (pull_request) Successful in 2m44s
Project CI / Backend tests (pull_request) Successful in 13m7s
Project CI / Frontend tests (pull_request) Successful in 13m55s
Project CI / Native shell tests (pull_request) Failing after 29m47s
同步远端主线至 b129589e7

保留当前分支的 V1 清理与主线文档和测试更新
2026-09-09 06:06:35 +00:00
lhk229 310091d28e 合并最新 master
Project CI / Repository checks (pull_request) Failing after 9s
Project CI / Backend tests (pull_request) Failing after 9s
Project CI / Frontend tests (pull_request) Successful in 6m0s
Project CI / Native shell tests (pull_request) Has been cancelled
同步主线 DirectProject 历史与 Markdown 展示改动

保留策划 V2 退役方案文档索引
2026-09-09 05:49:33 +00:00
lhk229 b129589e75 Merge pull request '修复客户端常用设置读写卡顿' (#307) from fix/config_sl into master
Project CI / Repository checks (push) Successful in 4m42s
Project CI / Backend tests (push) Successful in 9m13s
Project CI / Frontend tests (push) Successful in 13m41s
Project CI / Native shell tests (push) Successful in 23m57s
Reviewed-on: https://git.genarrative.world/git/GenarrativeAI/Genarrative/pulls/307
Reviewed-by: 段舒康 <kdletters@qq.com>
2026-09-09 13:45:26 +08:00
lhk229 6a1e307b4a 合并最新master并协调模型配置保存逻辑
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
合入服务端默认模型同步及前端模型选择更新
保留配置覆盖层回滚和设置页无外部诊断行为
同步模型ID与默认模型标记并补充交叉回归验证
2026-09-09 05:36:56 +00:00
suzmii f7374b2101 fix: 调整 AGC 输入区滚动条显示阈值
- 输入编辑器默认高度提高到 96px,超过 140px 后滚动

- 加深并加宽滚动条拇指,避免溢出时不可见

- 同步调整消息列表底部空间
2026-09-09 13:33:13 +08:00
suzmii a0f3cc21a6 fix: 输入区滚动条改为按内容溢出显示
- 移除常驻滚动条和标准 scrollbar 属性

- 保留 WebKit 细滚动条样式,仅在内容超出时出现
2026-09-09 13:33:13 +08:00
suzmii c82a684004 fix: 收紧 AGC 输入区模型选择布局
- 模型选择改为按内容宽度靠右排列

- 取消状态点、名称和箭头之间的异常拉伸

- 拉开模型选择与发送按钮间距
2026-09-09 13:33:12 +08:00
suzmii 08e20dc7c5 fix: 恢复 AGC 输入区模型名称显示
- 模型选择设置稳定最小宽度,避免被右侧 flex 压缩

- 提高模型名称文字对比度

- 固定 @ 和发送按钮宽度,保持控制行布局稳定
2026-09-09 13:33:12 +08:00
suzmii e726dc41b8 fix: 约束 AGC 素材候选弹层到输入框范围
- 类型提示改为固定定位并按输入框宽度约束

- 根据上下可用空间选择展开方向

- 避免弹层越过聊天栏覆盖其它区域
2026-09-09 13:33:12 +08:00
suzmii 5f36ab8081 style: 对齐 AGC 输入区参考样式
- 输入区外框统一承载编辑器和底部控制行

- @ 放左侧,模型选择和发送放右侧

- 恢复输入编辑器滚动条样式

- 消息列表为底部输入框保留滚动条空间
2026-09-09 13:33:12 +08:00
suzmii cc569e7e1e refactor: 统一 AGC 输入区为单框结构
- form 作为完整背景框承载输入区和底部控制行

- 输入编辑器在框内透明显示

- 模型选择、@ 和发送按钮位于同一框内底部行
2026-09-09 13:32:41 +08:00
suzmii 664c8fbaf0 refactor: 统一 AGC 输入区底部控件结构
- 直接 Codex 输入区改为输入框下方单一控制行

- 模型选择、@ 和发送按钮使用同一 flex 布局

- 通用引用编辑器支持外部触发素材面板

- 移除依赖多个绝对定位拼接的对齐方式
2026-09-09 13:32:41 +08:00
suzmii a42452106c fix: 对齐 AGC 输入区 @ 与发送按钮
- 覆盖直接 Codex 输入区的全局按钮绝对定位

- @ 按钮改为 actions 容器内静态网格项,与发送按钮保持同一基线
2026-09-09 13:32:10 +08:00
suzmii b3989b0d5c fix: 修复 AGC 素材面板预览和按钮重叠
- 每个素材缩略图使用独立预览 scope,避免开发模式取消后无法恢复

- 调整模型选择、@ 按钮和发送按钮的底部布局

- 聊天引用候选改为使用完整已登记素材列表

- 补充图片缩略图和 StrictMode 回归测试
2026-09-09 13:32:10 +08:00
suzmii 46f27e2bde fix: 优化 AGC 素材候选弹层样式
- 固定候选弹层宽度并限制高度

- 候选名称和类型标签改为单行省略

- 增加候选弹层层级和滚动条样式
2026-09-09 13:32:10 +08:00
suzmii b7a659cdfe feat: AGC 聊天支持素材与运行画面结构化引用
- 新增通用素材引用编辑器与结构化引用协议

- 三个聊天入口接入 @ 候选和素材选择面板

- 资源卡新增 @引用 入口

- 运行画面新增区域点选与 runtime-region 引用

- Rust 按 manifest 二次校验并生成安全投影

- 补充前端与 Rust 定向测试
2026-09-09 13:32:10 +08:00
suzmii 672d93015c Merge pull request '优化AGC客户端同步LLM配置' (#306) from feat/agc-client-sync-llm-config into master
Project CI / Repository checks (push) Successful in 2m49s
Project CI / Backend tests (push) Successful in 6m44s
Project CI / Native shell tests (push) Failing after 15m5s
Project CI / Frontend tests (push) Successful in 2m57s
Reviewed-on: #306
2026-09-09 13:19:43 +08:00
suzmii d9c883675d 补充AGC模型目录异常revision的客户端用例
Project CI / Frontend tests (pull_request) Successful in 4m3s
Project CI / Repository checks (pull_request) Successful in 5m35s
Project CI / Backend tests (pull_request) Successful in 8m10s
Project CI / Native shell tests (pull_request) Failing after 17m32s
- 补充旧服务端不返回 revision(两次响应均为 undefined)时仍按目录变化刷新的用例
- 补充服务端目录重建导致 revision 回退(7 到 0)时仍更新界面的用例
- 补充 revision 未变化时不更新界面、沿用已应用目录的用例
2026-09-09 13:03:46 +08:00
lhk229 2757273850 修正配置回滚代码的Rust格式
Project CI / Repository checks (pull_request) Successful in 2m51s
Project CI / Frontend tests (pull_request) Successful in 3m18s
Project CI / Native shell tests (pull_request) Successful in 22m8s
Project CI / Backend tests (pull_request) Successful in 8m9s
按rustfmt要求调整错误信息和测试断言换行
通过仓库check:rustfmt、编码和diff检查
2026-09-09 04:40:45 +00:00
lhk229 08c5917490 修复配置覆盖层写入失败后的部分更新
Project CI / Frontend tests (pull_request) Successful in 3m5s
Project CI / Backend tests (pull_request) Failing after 3m52s
Project CI / Native shell tests (pull_request) Failing after 4m5s
Project CI / Repository checks (pull_request) Failing after 1m8s
提前序列化配置变更并在多文件写入失败时逆序回滚
保留单文件保存路径且不增加外部诊断或保存后回读
补充覆盖层写入失败回归测试及配置保存文档
2026-09-09 04:34:57 +00:00
suzmii 07fc26953a 修复AGC模型目录同步的加载态与保存竞态
Project CI / Repository checks (pull_request) Successful in 2m56s
Project CI / Frontend tests (pull_request) Successful in 3m45s
Project CI / Backend tests (pull_request) Failing after 4m34s
Project CI / Native shell tests (pull_request) Failing after 7m54s
- ConversationModelSelect:目录同步与配置读取/写回失败时统一在 finally 收起加载态,避免选择器永久卡在 busy、无法切换或刷新
- ConversationModelSelect:配置读取失败单独提示「读取客户端配置失败」,不再误报为模型目录加载失败
- ConversationModelSelect:配置写回串行化,发送前校验等待在途保存并读取最新配置,避免保存中放行旧选择、或用旧快照覆盖刚完成的选择
- ConversationModelSelect:服务端未返回 revision 时按目录已变化处理,避免界面停止刷新
- ProjectSupervisorView:提交前模型校验期间禁用输入框与发送按钮,避免重复提交与校验窗口内编辑丢失
- 测试:补充配置读取失败恢复、保存中发送前校验等待、目录请求去重用例;修正依赖配置读取时序的 appSurface 用例
- 文档:修正首页入口「按需加载」与实现不符的描述
2026-09-09 12:00:10 +08:00
lhk229 ff065dbc6c 修复本地Provider冒烟测试的HTML项目夹具
Project CI / Repository checks (pull_request) Successful in 3m10s
Project CI / Frontend tests (pull_request) Successful in 4m7s
Project CI / Backend tests (pull_request) Successful in 7m11s
Project CI / Native shell tests (pull_request) Successful in 18m7s
启动 Agent 前准备已有 HTML 入口以符合 JSON Generator 项目契约
失败诊断补齐退出码、终止信号及标准输出和错误输出尾部
同步冒烟测试夹具与诊断约定文档
2026-09-09 03:50:32 +00:00
lhk229 3be91bf283 修复rust格式问题
Project CI / Frontend tests (pull_request) Successful in 3m40s
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Successful in 3m24s
Project CI / Backend tests (pull_request) Failing after 6m14s
2026-09-09 03:44:33 +00:00
lhk229 1b4f0ed203 修复原生应用CI测试与持久化图片生成恢复
Project CI / Repository checks (pull_request) Failing after 1m6s
Project CI / Frontend tests (pull_request) Successful in 2m42s
Project CI / Backend tests (pull_request) Successful in 10m7s
Project CI / Native shell tests (pull_request) Failing after 18m54s
将已有 HTML 项目测试夹具与 npm 项目初始化契约对齐
更新图片生成与角色提示词断言,移除已退役视觉门禁测试
按持久化请求快照恢复旧图片生成任务缺省参数
修正运行中生成任务查询的测试路由和响应夹具
同步技术方案与共享开发约定
2026-09-09 03:17:03 +00:00
lhk229 5bee48fede 合并最新master到常用设置修复分支
同步远端master的最新修改
2026-09-09 02:15:42 +00:00
suzmii 2942489575 Merge remote-tracking branch 'origin/master' into feat/agc-client-sync-llm-config
Project CI / Repository checks (pull_request) Successful in 2m49s
Project CI / Frontend tests (pull_request) Successful in 3m26s
Project CI / Native shell tests (pull_request) Failing after 4m12s
Project CI / Backend tests (pull_request) Failing after 4m13s
# Conflicts:
#	apps/ai-game-creator-shell/src/features/project-workspace/ConversationModelSelect.tsx
#	apps/ai-game-creator-shell/src/features/project-workspace/ProjectSupervisorView.tsx
#	apps/ai-game-creator-shell/tests/conversationModelSelect.test.tsx
#	docs/technical/【技术方案】AGC后台模型别名与对话选择-2026-09-05.md
2026-09-09 10:04:05 +08:00
lhk229 1cf835418e 修复常用设置状态展示与本地覆盖保存
Project CI / Backend tests (pull_request) Successful in 5m43s
Project CI / Repository checks (pull_request) Failing after 24m21s
Project CI / Native shell tests (pull_request) Failing after 14m11s
Project CI / Frontend tests (pull_request) Successful in 28m31s
删除常用设置账号权限状态栏及关联状态传递
复用保存前读取结果并同步冲突覆盖项,模型切换仅同步模型字段
补充配置保存定向测试并更新设置职责文档
2026-09-08 13:45:25 +00:00
lhk229 14d76419b8 修复策划回答身份绑定与迟到恢复结果覆盖
Project CI / Repository checks (pull_request) Successful in 2m48s
Project CI / Backend tests (pull_request) Successful in 6m28s
Project CI / Native shell tests (pull_request) Failing after 13m45s
Project CI / Frontend tests (pull_request) Successful in 44m47s
在既有回合锁内核对问题与会话身份,保留已完成回合重放。
前端回答携带卡片问题标识,hydrate 写回前检查请求序列和项目路径。
补充后端与界面回归测试,同步技术合同与项目决策。
2026-09-08 13:25:24 +00:00
lhk229 5efd88cb3e 修复客户端常用设置读写卡顿
Project CI / Frontend tests (pull_request) Successful in 2m46s
Project CI / Backend tests (pull_request) Successful in 5m57s
Project CI / Repository checks (pull_request) Successful in 2m30s
Project CI / Native shell tests (pull_request) Failing after 15m32s
移除常用设置读取和保存后的外部诊断调用
减少配置读取时的重复权限修复和保存后的重复回读
补充常用设置无需外部诊断的原因注释
2026-09-08 12:58:29 +00:00
lhk229 8d03b73582 修复 Planning V2 恢复与审批重放
Project CI / Frontend tests (pull_request) Successful in 12m56s
Project CI / Backend tests (pull_request) Successful in 22m19s
Project CI / Repository checks (pull_request) Failing after 33m17s
Project CI / Native shell tests (pull_request) Failing after 33m40s
恢复已落盘结构化 question 为成功回合并补齐会话投影

允许审批 receipt 在重启后复用原始 decisionId

恢复旁路不干扰正常 run 并补充设计原则文档
2026-09-08 12:52:18 +00:00
suzmii 36747b3ece 修复资源总览摞堆叠旋转与卡片预览懒加载
- 总览每个资源类型最多铺 3 张卡片,其余数量由栏目标题栏的“N 项”表达
- 总览摞旋转角改为固定小角度,素材变多后不再随堆叠下标无限增大
- 总览摞下标小的卡片压在上层,不再被模糊卡遮住最上面那张清晰卡
- 截断前先按搜索可见性过滤,避免搜索结果落在深处时总览显示空摞
- 资源卡片可见性观察改用真正包含卡片的容器,恢复滚动时的预览懒加载
- 补充资源画本布局单测、预览观察根回归用例和总览摞上限用例
2026-09-08 20:23:33 +08:00
suzmii 8a47cdfc1b Merge remote-tracking branch 'origin/master' into feat/game-agent-canvas-resource-workbench-v2
Project CI / Native shell tests (pull_request) Failing after 15m58s
Project CI / Frontend tests (pull_request) Successful in 3m48s
Project CI / Backend tests (pull_request) Successful in 8m0s
Project CI / Repository checks (pull_request) Successful in 11m18s
2026-09-08 19:48:37 +08:00
lhk229 47a3240e81 按 rustfmt 修正 V1 删除后残留的格式偏差
Project CI / Backend tests (pull_request) Successful in 11m23s
Project CI / Native shell tests (pull_request) Failing after 13m38s
Project CI / Repository checks (pull_request) Successful in 4m51s
Project CI / Frontend tests (pull_request) Successful in 7m39s
Repository checks 门禁的 cargo fmt --check 要求,仅格式化、无行为变化
2026-09-08 11:25:17 +00:00
lhk229 c8fec98758 Merge branch 'master' into feat/design_agent_simple
Project CI / Repository checks (pull_request) Failing after 2m8s
Project CI / Backend tests (pull_request) Successful in 17m24s
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
2026-09-08 19:12:36 +08:00
lhk229 55377f9e51 修复 prompt bundle 集成测试夹具残留 V1 planning 形状导致 CI 失败
Project CI / Backend tests (pull_request) Failing after 15s
Project CI / Repository checks (pull_request) Failing after 15s
Project CI / Frontend tests (pull_request) Successful in 6m40s
Project CI / Native shell tests (pull_request) Successful in 27m25s
基础夹具 manifest 同步删除 supervisorPlan 组合、planning 目录条目与四个已删 section 登记
roleOverlays 校验测试改为自带合法 overlay 夹具后逐项破坏,与生产 manifest 的空 overlay 形状对齐
2026-09-08 11:11:31 +00:00
lhk229 2fa967a93a 删除策划 V1 的测试脚本入口并同步退役文档
Project CI / Repository checks (pull_request) Failing after 12s
Project CI / Backend tests (pull_request) Failing after 9s
Project CI / Frontend tests (pull_request) Successful in 3m15s
Project CI / Native shell tests (pull_request) Failing after 15m21s
删除 agent-swarm-test-chat.mjs 的 --plan 模式、自动 GDD 审批回路与 planning 产物检查
删除根与应用 package.json 的 test:plan / test:plan:manual / agc:test:plan* 四条脚本
同步删除 agentSwarmTestEntry.test.ts 中只覆盖 V1 审批回路与 --plan 参数的用例
GddApprovalCard.tsx 注释改指 planning_gdd_model.rs 的现行路径权威定义
立项策划Agent(Fast GDD)方案文档头部标注已退役,仅作历史推导记录
策划会话 Runtime V2 方案文档状态更新为 P5 已完成并记录源码删除执行清单
Provider 兼容性缺陷文档的缺陷 4 标注相关代码已随 V1 退役删除
decision-log 新增 2026-09-08 策划 V1 链路源码整体退役决策记录
2026-09-08 10:43:38 +00:00
lhk229 78a606ab5e 删除已退役策划能力配置
Project CI / Repository checks (pull_request) Failing after 14s
Project CI / Backend tests (pull_request) Failing after 14s
Project CI / Frontend tests (pull_request) Successful in 2m31s
Project CI / Native shell tests (pull_request) Failing after 13m1s
移除 planning.capabilityEnabled,保留其余客户端配置
2026-09-08 10:17:24 +00:00
lhk229 ded3253b08 删除已退役策划 V1 的 Runtime 模块与残留分支
按四不写原则移除 PR #159 引入、已被策划 V2 取代的整条 V1 链路
删除 runtime_protocol 下六个 V1 模块(planning_storage/submit/approval/coordinator/hydrate/provider_usage)
V1 与 V2 共用的 GDD 数据模型抽到新模块 planning_gdd_model.rs 供 V2 继续复用
删除 prompt manifest 中 planning Agent 目录、role overlay、plan sections 与 supervisorPlan 组合及对应生成常量
删除四个 V1 提示词文件(roles/project-planning.md、plan/common.md、plan/supervisor-identity.md、plan/supervisor-playbook.md)
删除 game-creator.config.json 与配置代码中的 planning 能力开关
删除 CLI --swarm-chat 的 --plan 入口与 swarm_cli 中的 plan source 分支
删除 provider_retry 的 planning session binding 与 plan 专用请求指纹链路
删除 provider_action_batch 的 plan.submit_gdd v4 批次形状校验与 planning 绑定字段
删除 tool_policy_snapshot / agent_native_tools / tool_plan_protocol 中的 plan 根阶段收窄与 plan.submit_gdd 身份门
删除 main_loop 的 plan_gdd blocker 投影、plan 信封修复回路与 plan submit 业务拒绝限流
删除 pending_recovery 与 recovery_scan 的 plan submit 锚点恢复、planning session 投影恢复与审批投影恢复
删除 acceptance_graph 的 Fast GDD 取证覆盖校验与 plan 根验收前置门
删除 agent_db 的 plan.provider_usage、plan.gdd_decided、plan_submit_gdd.committed 三条专用持久车道及其预留配额
删除 AgentRuntimeState 的 plan_submit_gdd_rejection_count 字段
runtime_tools 的委派、run_status、goal_contract 恢复为通用路径(移除 plan 根对称性守卫与 acceptance gate 钩子)
同步删除只覆盖 V1 行为的测试用例(planning 澄清、锚点恢复、plan 根委派门、plan 提示词组合等)
2026-09-08 09:44:21 +00:00
suzmii 5550a7b28c 修复资源画布文案重命名后的CI失败
删除project-development.suite.ts中未使用的resetButton变量,修复ESLint unused-vars错误

将projectResourceLiveIntegration.test.tsx中过期的复位资源画布断言更新为复位资源视图
2026-09-08 16:38:49 +08:00
suzmii 4fc9451492 修复AGC客户端未跟随服务端默认模型
Project CI / Native shell tests (pull_request) Failing after 14m52s
Project CI / Repository checks (pull_request) Successful in 5m51s
Project CI / Frontend tests (pull_request) Successful in 6m17s
Project CI / Backend tests (pull_request) Successful in 23m19s
- 客户端配置新增 selectedModelIsDefault,记录当前选择是否来自平台默认项
- 服务端默认项变化时,跟随默认项的选择自动切换并提示,手动选择不受影响
- 所选模型失效回退默认项时标记为默认项选择
- 补充配置读写与客户端定向测试,同步技术方案文档
2026-09-08 16:38:37 +08:00
suzmii d60c4ce6ec 优化AGC客户端同步LLM配置
Project CI / Frontend tests (pull_request) Successful in 3m54s
Project CI / Repository checks (pull_request) Successful in 5m41s
Project CI / Backend tests (pull_request) Successful in 14m56s
Project CI / Native shell tests (pull_request) Failing after 15m10s
- GET /api/llm/models 增加目录 revision,客户端据此做条件刷新
- AGC 客户端在项目切换、对话表面挂载、下拉展开、窗口聚焦时按 revision 条件刷新
- 模型目录请求同一时刻只保留一个在途请求,刷新失败保留上一次有效目录与本地选择
- 发起对话前校验所选模型,已停用或删除时回退默认模型并提示
- 新增模型目录缓存模块与定向测试,同步技术方案与后端架构文档
2026-09-08 16:11:36 +08:00
suzmii 7bd67f2b3a Merge remote-tracking branch 'origin/master' into feat/game-agent-canvas-resource-workbench-v2 2026-09-08 14:58:58 +08:00
suzmii ea71540413 Merge branch 'feat/game-agent-canvas-resource-workbench-v2' of https://git.genarrative.world/git/GenarrativeAI/Genarrative into feat/game-agent-canvas-resource-workbench-v2 2026-09-08 14:58:29 +08:00
suzmii 4e0ce1902b 建立AGC资源语义类型契约
共享契约新增 canonical 资源类型目录和历史类型读时映射

角色动画派生结果写入 character-animation 主类型

补充角色动画落盘与 legacy 类型映射回归测试

更新资源自由画板技术方案的类型边界说明
2026-09-08 13:41:55 +08:00
suzmii e2a50088c9 修复资源依赖本地身份解析
资源依赖图同时索引远端资源身份和 local-asset 本地资源身份

本地 canonical 引用与远端候选去重后再建立依赖连线

补充本地身份解析与候选不歧义回归测试

更新资源自由画板技术方案的依赖身份合同
2026-09-08 13:29:08 +08:00
suzmii 6de536ecbd Merge branch 'master' into feat/game-agent-canvas-resource-workbench-v2
Project CI / Repository checks (pull_request) Failing after 1m23s
Project CI / Native shell tests (pull_request) Failing after 4m7s
Project CI / Backend tests (pull_request) Successful in 6m28s
Project CI / Frontend tests (pull_request) Failing after 2m7s
2026-09-08 12:24:11 +08:00
suzmii 1347b4a743 去除资源区滚轮切换栏目
Project CI / Frontend tests (pull_request) Failing after 1m49s
Project CI / Native shell tests (pull_request) Failing after 4m32s
Project CI / Repository checks (pull_request) Failing after 9s
Project CI / Backend tests (pull_request) Failing after 9s
删除普通滚轮切页状态、队列与节流控制逻辑

资源区普通滚轮只平移当前视图,缩放继续由 Ctrl 或 Cmd 配合滚轮触发

同步更新 PRD、技术方案与控制器测试
2026-09-08 12:12:29 +08:00
suzmii d5411eae62 去除资源区主子画布概念文案
资源总览、栏目入口、返回与复位文案移除主画布和子画布概念

资源栏目可访问名称改为设计文档、美术资源等名称

同步更新相关界面测试断言
2026-09-08 12:11:09 +08:00
lhk229 38b4abc403 删除已退役策划 V1 的前端数据通路与命令入口
Project CI / Repository checks (pull_request) Failing after 11s
Project CI / Backend tests (pull_request) Failing after 10s
Project CI / Frontend tests (pull_request) Successful in 2m43s
Project CI / Native shell tests (pull_request) Successful in 18m1s
前端 App.tsx 删除 hydrate_game_creator_plan_gdd_state 旧读取与 decide_game_creator_plan_gdd 旧审批分支,策划状态只走 Planning V2 命令
hydratePlanningV2Session 在确认无 V2 会话时清空策划状态,避免残留旧卡片
Tauri 命令层删除 decide_game_creator_plan_gdd 与 hydrate_game_creator_plan_gdd_state 及其注册和输入解析
CLI 删除 --plan-gdd-status 与 --plan-gdd-decide 子命令、stdin 审批意见读取与对应解析测试
测试 harness 删除旧命令 mock 与 planGdd 状态注入,新增 V2 审批失败注入与 decisionId 记录
plan-gdd 测试套件改写为 V2 会话驱动,删除 recoveryPending 与旧 Supervisor 审批等待两个已退役语义的测试
2026-09-08 03:56:16 +00:00
suzmii 0a5080dbd9 Merge remote-tracking branch 'origin/master' into feat/game-agent-canvas-resource-workbench-v2
Project CI / Repository checks (pull_request) Successful in 2m27s
Project CI / Native shell tests (pull_request) Successful in 18m40s
Project CI / Frontend tests (pull_request) Successful in 2m55s
Project CI / Backend tests (pull_request) Successful in 6m32s
2026-09-08 10:45:07 +08:00
lhk229 0fde1912f1 Merge remote-tracking branch 'origin/feat/design_agent_simple' into feat/design_agent_simple
Project CI / Repository checks (pull_request) Successful in 3m10s
Project CI / Frontend tests (pull_request) Successful in 3m24s
Project CI / Backend tests (pull_request) Successful in 6m45s
Project CI / Native shell tests (pull_request) Successful in 19m25s
2026-09-07 10:33:36 +00:00
lhk229 1db5930c9f 修复rustfmt格式检查
- planning_session_v2.rs 的 config 加载与 tool_planning.rs 的 final_request 捕获合并为单行
2026-09-07 10:29:13 +00:00
kdletters 29fa20e68d Merge branch 'master' into feat/design_agent_simple
Project CI / Frontend tests (pull_request) Successful in 3m9s
Project CI / Backend tests (pull_request) Successful in 6m31s
Project CI / Repository checks (pull_request) Failing after 1m6s
Project CI / Native shell tests (pull_request) Successful in 19m54s
2026-09-07 17:16:26 +08:00
suzmii c5bbb864f5 修复资源分页类别缓存回归
资源分页复用完整资源签名以识别同数量类别变化

补充同长度类别变化回归测试
2026-09-07 16:26:08 +08:00
lhk229 85de37710b 软化tool_choice兼容性表述为可能不支持
Project CI / Repository checks (pull_request) Failing after 44s
Project CI / Frontend tests (pull_request) Successful in 3m9s
Project CI / Backend tests (pull_request) Successful in 7m23s
Project CI / Native shell tests (pull_request) Successful in 21m20s
- 代码注释与策划会话文档改为“部分模型或端点可能不支持”,DeepSeek 仅作示例
2026-09-07 07:38:05 +00:00
lhk229 b5d214cef8 Merge branch 'master' into feat/design_agent_simple
Project CI / Repository checks (pull_request) Failing after 59s
Project CI / Frontend tests (pull_request) Successful in 3m10s
Project CI / Backend tests (pull_request) Successful in 6m9s
Project CI / Native shell tests (pull_request) Successful in 21m10s
2026-09-07 15:34:32 +08:00
lhk229 0ff2c00618 注明思考模式模型不支持tool_choice=required
Project CI / Backend tests (pull_request) Failing after 10s
Project CI / Frontend tests (pull_request) Successful in 3m15s
Project CI / Native shell tests (pull_request) Successful in 20m38s
Project CI / Repository checks (pull_request) Failing after 15s
- planning_session_v2 的 Required 设置处补注释:DeepSeek 等思考模式模型会以 400 拒绝该取值,接入新模型需先验证端点支持
- 策划会话 Runtime V2 文档重试规则节补充该限制及其与瞬态重试的边界
2026-09-07 07:30:57 +00:00
lhk229 e20ea86ed9 放宽后台Agent工具计划环路测试超时口径
- final reply 请求捕获改用 wait_for_captured_mock_request 10 秒轮询预算,取代手写 recv_timeout 2 秒,消除持久化动作批次管线开销导致的单测 flaky
- 收尾等待改用 wait_for_agent_runtime_terminal_and_lane_release,确认 lane 释放、不留僵尸 worker
- 批量并行跑时的跨测试污染为既有系统性问题,本次不处理
2026-09-07 06:57:50 +00:00
lhk229 df6d885d2c 策划会话瞬态Provider故障自动退避重试
Project CI / Repository checks (pull_request) Has been cancelled
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
- invoke_provider_v2 错误携带主 Runtime 同款瞬态分类,timeout/connectivity/transport/空响应/反序列化/断流/上游408、429、5xx 不再直接判死
- run_turn_v2 对瞬态故障按 maxRetries 预算指数退避自动重试本回合,耗尽后才投 provider_failed 并附已重试次数,上游 4xx 硬错误仍直接失败
- 同步策划会话 Runtime V2 文档:tool_choice 口径改为 required,补充瞬态故障重试规则
2026-09-07 04:51:12 +00:00
lhk229 8eeaa43081 立项策划会话强制工具调用
- planning_session_v2 请求 toolChoice 由 Auto 改为 Required,消除模型跳过 plan_ask_question/plan_submit_gdd 而把 JSON 写进正文导致的 PLANNING_INVALID_OUTPUT 失败
2026-09-07 03:41:58 +00:00
lhk229 a371c60724 Merge remote-tracking branch 'origin/master' into feat/design_agent_simple 2026-09-07 03:07:54 +00:00
lhk229 d7a09276e6 合并 origin/master 到 feat/design_agent_simple
- 合入 AGC 官方 LLM Router 账号链路与流式联网输出(#242)
- 新增后台模型别名与对话选择能力及 AGC 模型目录
- 解决 ProjectSupervisorView.tsx 与 decision-log.md 冲突
2026-09-07 03:07:05 +00:00
lhk229 7d54aa6552 放宽策划V2修订续跑合同
Project CI / Repository checks (pull_request) Failing after 15s
Project CI / Backend tests (pull_request) Failing after 15s
Project CI / Frontend tests (pull_request) Successful in 3m10s
Project CI / Native shell tests (pull_request) Successful in 17m23s
允许审批修改后的 continuation 继续问询或直接提交完整 GDD

明确继承已确认问答与问询计数且不新增意图识别调用
2026-09-06 09:49:14 +00:00
lhk229 49d814fec1 清理策划V2已退役停止状态
Project CI / Repository checks (pull_request) Successful in 3m34s
Project CI / Frontend tests (pull_request) Successful in 4m21s
Project CI / Backend tests (pull_request) Successful in 7m56s
Project CI / Native shell tests (pull_request) Successful in 20m50s
删除 Planning V2 认领逻辑中的 stopped 死分支。
同步技术方案中的状态枚举和状态转移描述。
2026-09-06 08:43:07 +00:00
lhk229 4efa39420f 清理策划V2无效停止状态
Project CI / Repository checks (pull_request) Successful in 2m31s
Project CI / Frontend tests (pull_request) Successful in 2m54s
Project CI / Backend tests (pull_request) Successful in 6m2s
Project CI / Native shell tests (pull_request) Successful in 18m15s
移除没有生产写入路径的 stopped 会话状态

同步清理前端恢复状态映射
2026-09-05 12:51:14 +00:00
lhk229 90eae50dd3 修复策划V2错误结果展示
Project CI / Repository checks (pull_request) Has been cancelled
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
展示后端返回的错误回合结果

在缺少会话错误摘要时回填运行时错误
2026-09-05 12:29:18 +00:00
lhk229 e87cb98d3e Merge branch 'master' into feat/design_agent_simple
Project CI / Repository checks (pull_request) Has been cancelled
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
2026-09-05 12:17:40 +00:00
lhk229 b84c9d4e45 修复策划V2不可变文件失败残留
失败时清理已创建的空文件或截断文件

保留已有文件的幂等与内容冲突校验
2026-09-05 12:17:04 +00:00
lhk229 267c085b57 澄清策划V2流式事件契约
Project CI / Repository checks (pull_request) Has been cancelled
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
明确 Provider 流式传输属于底层实现能力

明确 text_delta 是 Runtime 内部事件而非前端业务事件

明确用户可见结果以结构化工具调用为准

补充文档措辞约束避免误读
2026-09-05 11:59:47 +00:00
lhk229 d3a7070bb4 Merge branch 'feat/design_agent_simple' of ssh://genarrative-station:2222/GenarrativeAI/Genarrative into feat/design_agent_simple
Project CI / Frontend tests (pull_request) Successful in 3m20s
Project CI / Repository checks (pull_request) Successful in 2m51s
Project CI / Backend tests (pull_request) Successful in 6m11s
Project CI / Native shell tests (pull_request) Successful in 21m46s
2026-09-05 11:49:35 +00:00
lhk229 6cc70af0a5 Merge branch 'master' into feat/design_agent_simple
Project CI / Repository checks (pull_request) Successful in 2m16s
Project CI / Frontend tests (pull_request) Successful in 3m0s
Project CI / Backend tests (pull_request) Successful in 7m26s
Project CI / Native shell tests (pull_request) Successful in 21m24s
2026-09-05 19:49:29 +08:00
lhk229 f7b15f124f 修复策划V2澄清历史完整展示
统一解析回合结果与持久化历史中的问题结构

恢复历史消息中的问题正文、选项标签和选项说明
2026-09-05 11:38:40 +00:00
lhk229 2d7226dad8 修复策划V2修改意见撞项目锁
V2 审批、续跑、落盘、失败投影和 GDD 认领改为完整等待窗口
V2 hydrate 改为短窗口等待
前端 V2 hydrate 忽略瞬时锁争用
补充审批、续跑、hydrate 锁等待测试
同步决策、踩坑和技术方案
2026-09-05 11:28:37 +00:00
lhk229 4127686e18 修复新建项目锁误触发 Windows UAC
Project CI / Repository checks (pull_request) Failing after 15s
Project CI / Backend tests (pull_request) Failing after 16s
Project CI / Frontend tests (pull_request) Successful in 2m39s
Project CI / Native shell tests (pull_request) Successful in 17m50s
新建 sidecar 改为本进程收紧 DACL,不再因继承 ACE 自动提权。
项目锁先写入并释放独占句柄后再 harden,回读内容校验,不再对这把新锁走 prepare_for_read。
提权 ArgumentList 改为一条按 Windows 规则加引号的字符串,避免含空格路径被拆开。
补充含空格项目根取锁与 quoted ArgumentList 定向测试。
同步 ACL 提权边界、决策记录和排障记录。
2026-09-05 10:53:15 +00:00
lhk229 1019403f40 修复策划GDD修改后自动续跑
审批 revise 后继续同一 V2 Session

避免修订意见重复写入历史

保留独立修订回合身份
2026-09-05 09:41:20 +00:00
lhk229 bd8e33eb60 禁用策划V2失败重试入口
Project CI / Frontend tests (pull_request) Successful in 2m53s
Project CI / Native shell tests (pull_request) Successful in 18m26s
Project CI / Repository checks (pull_request) Successful in 2m29s
Project CI / Backend tests (pull_request) Successful in 6m6s
Provider 失败时只展示错误信息

避免误调用通用 Supervisor 重试链路
2026-09-05 09:11:05 +00:00
lhk229 c03d1ef6fc 修复策划Provider失败后的恢复状态
Project CI / Repository checks (pull_request) Has been cancelled
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
将 provider_failed 投影为可恢复的 failed 状态

让策划工作台显示重新启动入口
2026-09-05 09:04:15 +00:00
lhk229 d47928ceba 修复策划V2问询交互与历史展示
Project CI / Repository checks (pull_request) Successful in 44m58s
Project CI / Frontend tests (pull_request) Successful in 45m27s
Project CI / Backend tests (pull_request) Failing after 48m47s
Project CI / Native shell tests (pull_request) Failing after 5m21s
回答提交后立即收起当前问询卡

保留选项回答和完整选项描述到历史

补充 GDD 历史条目的一句话与决策摘要
2026-09-05 08:26:31 +00:00
lhk229 b1456d9f11 修正策划V2流式交互契约
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
明确用户交互以工具调用结果为准

取消逐 delta 投影的业务契约要求

同步技术方案与长期决策记录
2026-09-05 08:17:04 +00:00
lhk229 27246fbc5e 收口P4并关闭旧版策划入口
Project CI / Repository checks (pull_request) Has been cancelled
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-supervisor-plan 来源统一返回退役错误

避免旧入口启动 Runtime 或 Provider

同步收口 P4 验收状态与最简 P5 方案

记录 V1 不迁移且历史文件只读保留
2026-09-05 08:10:26 +00:00
lhk229 ae700de1dd 修复 Rust 文件格式
Project CI / Repository checks (pull_request) Successful in 3m21s
Project CI / Frontend tests (pull_request) Successful in 4m4s
Project CI / Backend tests (pull_request) Successful in 7m38s
Project CI / Native shell tests (pull_request) Successful in 18m42s
删除 planning_session_v2.rs 末尾多余空行

通过 rustfmt、编码检查和 diff 检查
2026-09-05 07:50:46 +00:00
lhk229 573940f1ad 删除多余的测试
Project CI / Repository checks (pull_request) Failing after 52s
Project CI / Backend tests (pull_request) Successful in 5m53s
Project CI / Frontend tests (pull_request) Successful in 3m16s
Project CI / Native shell tests (pull_request) Successful in 17m5s
2026-09-05 07:27:44 +00:00
lhk229 861ed14a6f 补充策划问询不对称设计说明
明确模型侧 3 轮策略与 Runtime 侧 8 个问题门禁的分层语义

补充自动评测和代码评审口径,说明两者有意不对称
2026-09-05 07:07:31 +00:00
lhk229 eabd8f393a 将策划决策轮次交由Runtime管理
Project CI / Repository checks (pull_request) Successful in 4m24s
Project CI / Frontend tests (pull_request) Successful in 20m53s
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Failing after 13m28s
移除Provider输入中的 decisions.round 字段

由Runtime按决策顺序生成持久化轮次

放宽GDD实际门禁并同步模型数量上限
2026-09-05 06:07:32 +00:00
suzmii ccb3a3ab6a 修复资源保存后的自动聚焦
Project CI / Frontend tests (pull_request) Successful in 3m21s
Project CI / Repository checks (pull_request) Successful in 6m18s
Project CI / Backend tests (pull_request) Successful in 6m46s
Project CI / Native shell tests (pull_request) Successful in 17m19s
将保存后资源卡的 DOM 查找范围改为资源画本管理器\n确保新派生资源和恢复资源在布局就绪后能够进入详情聚焦
2026-09-05 13:04:54 +08:00
lhk229 250360a40a 放宽GDD一句话概念校验范围
删除决策首项必须为 confirmed 且 round=0 的阻断校验

将 Runtime 的 oneLiner 实际接受范围调整为 10~160

将模型 schema 提示范围调整为 25~90 并记录有意不对称设计

修复 Planning V2 系统提示词参数占位符
2026-09-05 05:00:32 +00:00
suzmii 879e732b04 完善资源分页依赖
Project CI / Frontend tests (pull_request) Failing after 2m32s
Project CI / Repository checks (pull_request) Successful in 2m54s
Project CI / Native shell tests (pull_request) Failing after 6m19s
Project CI / Backend tests (pull_request) Successful in 6m39s
按资源类别签名刷新分页类别\n避免资源数量不变时使用过期类别,同时保持稳定引用避免无关编辑面板重置
2026-09-05 12:20:47 +08:00
suzmii cac3e19632 修复资源分页类别缓存
Project CI / Frontend tests (pull_request) Failing after 2m47s
Project CI / Repository checks (pull_request) Successful in 3m4s
Project CI / Native shell tests (pull_request) Failing after 5m49s
Project CI / Backend tests (pull_request) Successful in 7m12s
资源数组变化时重新计算资源分页类别\n避免资源总数不变但类别更新后继续使用旧分页状态
2026-09-05 11:59:23 +08:00
suzmii cd17ae8123 修复资源主画布无限平移与滚轮交互
分离固定交互视口与可平移缩放的内容层
支持主画布滚轮平移并保留缩放及主子画布视口记忆
补充越界拖动和滚轮回归测试并同步技术文档
2026-09-05 11:41:16 +08:00
suzmii 41c9ee7db4 Merge branch 'master' into feat/game-agent-canvas-resource-workbench-v2
Project CI / Frontend tests (pull_request) Failing after 2m23s
Project CI / Repository checks (pull_request) Successful in 2m34s
Project CI / Native shell tests (pull_request) Failing after 4m58s
Project CI / Backend tests (pull_request) Successful in 6m51s
2026-09-05 11:23:18 +08:00
suzmii ba718e3d4b 重构资源画本独立转场与中断处理
Project CI / Repository checks (pull_request) Failing after 15s
Project CI / Backend tests (pull_request) Failing after 15s
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
使用独立FLIP快照层和Web Animations统一动画收尾
统一分类导航并保留滚轮节流和各画布视口
修复快照缩放与悬停导致的终点偏移
补充动画回归测试并更新资源画本技术文档
2026-09-05 11:21:17 +08:00
suzmii 65ffd4cc69 取消资源画本返回主画布的二次平移动画
新增 returning-main 阶段,区分返回主画布与进入子画布的 transform 过渡。

返回期间取消残留分页事件和进行中的子画布切换,避免返回流程被重新改写。

补充状态机与 appSurface 回归测试,并同步技术方案文档。
2026-09-05 09:25:52 +08:00
suzmii 5bcaca7233 重构资源画本画布与转场状态
统一主画布与子画布的可见渲染层和 viewport 状态管理。

将子画布改为固定视口加无限平移 world,并保证首次 fit 的可读比例。

补充转场、滚轮、viewport 和资源画本回归测试,更新技术方案文档。
2026-09-05 09:10:56 +08:00
lhk229 d1a8bed685 调整策划问询轮次提示
Project CI / Repository checks (pull_request) Successful in 2m27s
Project CI / Frontend tests (pull_request) Successful in 2m59s
Project CI / Native shell tests (pull_request) Failing after 6m11s
Project CI / Backend tests (pull_request) Successful in 5m58s
原型与生产 prompt 统一明确最多提问三轮

允许信息足够时提前出稿并补充重玩动力优先级

不改 Runtime 工作流、schema、校验和持久化逻辑
2026-09-04 14:22:14 +00:00
lhk229 1b764fd878 补充策划 Provider 诊断持久化
保存 Planning V2 每次 Provider 尝试的请求、响应和解析分类产物

诊断写入不参与工作流、恢复、重试或 GDD 判断

同步 Planning V2 技术方案与项目决策记录
2026-09-04 13:22:34 +00:00
lhk229 9a692ec543 同步策划 GDD 字段提示
将生产 V2 的 GDD 描述与原型统一为按字段填全且不增删改名

不改工作流、schema 或解析逻辑
2026-09-04 12:54:32 +00:00
lhk229 0c7ddf26db 调整策划 V2 决定 ID 由 Runtime 分配
删除 plan_submit_gdd Provider schema 中的 decisions[].id 和 prototypeValidationItems[].id

由 Runtime 生成 initial-request 与后续决定 ID,并绑定原型验证项

同步更新 Planning V2 技术方案和项目决策记录
2026-09-04 12:15:45 +00:00
lhk229 c454b22428 Merge branch 'master' into feat/design_agent_simple
Project CI / Repository checks (pull_request) Successful in 3m27s
Project CI / Frontend tests (pull_request) Successful in 4m16s
Project CI / Backend tests (pull_request) Successful in 7m21s
Project CI / Native shell tests (pull_request) Successful in 18m19s
2026-09-04 18:48:14 +08:00
lhk229 ca62243628 修复策划 V2 孤儿 GDD 无法恢复
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
把 gdd.vN.json 创建成功当作提交点

persist、hydrate 与回合启动认领下一连续版本并补投影

重试不再用新 UUID 覆盖同一版本

补充孤儿认领与下一版本分配测试
2026-09-04 10:47:17 +00:00
lhk229 c1d967b17d 将策划 V2 输出从正文 JSON 改为协议工具
Project CI / Backend tests (pull_request) Failing after 14s
Project CI / Repository checks (pull_request) Failing after 14s
Project CI / Native shell tests (pull_request) Successful in 17m12s
Project CI / Frontend tests (pull_request) Successful in 3m43s
挂 plan_ask_question / plan_submit_gdd,tool_choice 固定 auto

删除正文 JSON 解析、骨架提示词和入参 schemaVersion 必填

形状改由工具 schema 承担,既有校验门禁与落盘 plan-gdd.v2 不变

同步技术方案、决策记录和 DeepSeek thinking 排障
2026-09-04 10:10:50 +00:00
suzmii 820fbfd988 修复子画布缩放并支持Ctrl滚轮与右下角缩放控件
美术资源初始适配不再放大过头

scene层支持Ctrl滚轮锚点缩放,普通滚轮保留翻页
2026-09-04 16:41:39 +08:00
suzmii 069248b296 子画布展开改用真实坐标与视口承载
资源卡拖拽使用模型坐标换算,消除阻力和回弹

子画布空白区域支持画布平移,保持进入退出动画连贯
2026-09-04 16:31:50 +08:00
lhk229 67c2f85743 精简策划 V2 提示词为形状和策略
Project CI / Backend tests (pull_request) Failing after 21s
Project CI / Repository checks (pull_request) Failing after 15s
Project CI / Frontend tests (pull_request) Successful in 2m54s
Project CI / Native shell tests (pull_request) Successful in 19m25s
system prompt 只保留问询/GDD 骨架、同级字段边界和一行易错数量范围

去掉逐字段长度清单和 V1 编排残留说明,校验与重试路径不变

同步 Runtime V2 技术方案和项目决策记录
2026-09-04 08:04:14 +00:00
suzmii 9c85a9501e 调整资源画本hover高亮仅用于未展开主画布
Project CI / Repository checks (pull_request) Failing after 2m58s
Project CI / Frontend tests (pull_request) Failing after 2m33s
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
子画布展开态不再显示hover高亮线

未展开缩略卡hover时标题栏三边高亮,底边接缝保持默认色
2026-09-04 15:46:42 +08:00
suzmii 816844065b 拆分子画布标题与本体hover高亮
Project CI / Repository checks (pull_request) Failing after 2m52s
Project CI / Frontend tests (pull_request) Failing after 2m17s
Project CI / Native shell tests (pull_request) Failing after 6m15s
Project CI / Backend tests (pull_request) Successful in 6m37s
标题栏与本体分别作为独立对象绑定同一hover源

标题栏底边与本体顶边接缝保持默认色不高亮
2026-09-04 15:42:16 +08:00
lhk229 45568797f9 将策划 V2 既有阻断校验回灌给 Provider
把当前 question/GDD 硬校验契约写入 V2 system prompt,不扩大校验范围或新增门禁

校验失败的一次重试改为列出具体阻断原因并要求逐项修复

同步 Runtime V2 技术方案和项目决策记录
2026-09-04 07:40:36 +00:00
suzmii 8323b13fd3 Merge branch 'master' into feat/game-agent-canvas-resource-workbench-v2
Project CI / Frontend tests (pull_request) Failing after 2m29s
Project CI / Repository checks (pull_request) Failing after 2m49s
Project CI / Backend tests (pull_request) Successful in 6m39s
Project CI / Native shell tests (pull_request) Failing after 5m51s
2026-09-04 15:22:34 +08:00
suzmii f883d4bf2e 修复从项目入口首次进入主画布的错位
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 / Repository checks (pull_request) Failing after 10s
移除主画布入场缩放动画避免测量和终态不一致

无效缩略卡测量结果不再直接展示共享场景
2026-09-04 15:13:19 +08:00
suzmii 019b1761f4 修复资源画本首次布局错位
Project CI / Repository checks (pull_request) Failing after 9s
Project CI / Backend tests (pull_request) Failing after 11s
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
首帧测量未完成时隐藏共享场景,避免标题和卡片使用默认坐标

在首帧和资源数量变化后重新校准缩略卡布局
2026-09-04 15:03:28 +08:00
suzmii f339b383f5 实现资源画本统一节点转场
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
重构主画布与子画布为共享资源卡和标题栏节点

补充缩略堆叠、进入返回动画、遮罩与定位适配

同步更新资源画本 UI 技术方案文档
2026-09-04 14:06:24 +08:00
lhk229 f35ec812d9 完善策划 V2 人工测试与推断语义
修复做方案首轮额外命名等待、处理中反馈和重复错误展示

补全 GDD 输出字段提示,避免模型生成非法 decisions 字段

统一 V2 assumption_pending 与 agent_inferred 语义并同步核心闭环问询策略

保持旧 Supervisor/V1 的 default_pending 与 default 语义不变

更新 Runtime V2 技术方案和项目决策记录
2026-09-03 13:15:14 +00:00
lhk229 64105f8259 Merge branch 'master' into feat/design_agent_simple
Project CI / Repository checks (pull_request) Successful in 2m18s
Project CI / Frontend tests (pull_request) Successful in 2m59s
Project CI / Backend tests (pull_request) Successful in 7m37s
Project CI / Native shell tests (pull_request) Successful in 20m49s
2026-09-03 11:42:40 +00:00
lhk229 eecd097117 Merge branch 'master' into feat/design_agent_simple
Project CI / Repository checks (pull_request) Has been cancelled
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
2026-09-03 19:32:23 +08:00
lhk229 059808d9e8 接入策划会话 Runtime V2 入口与界面
Project CI / Repository checks (pull_request) Has been cancelled
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
做方案入口改用 start/continue/decide_planning_session_v2 命令

新增 V2 Session、问题、GDD 和审批结果的前端适配层

接通 hydrate 会话历史、流式回复、问询卡、审批卡和处理耗时展示

保持做游戏、做素材及无 V2 旧项目读取路径不变

同步策划入口回归测试、Runtime V2 技术方案和项目决策记录
2026-09-03 11:29:55 +00:00
lhk229 4d33553466 Merge branch 'master' into feat/design_agent_simple
Project CI / Repository checks (pull_request) Successful in 3m26s
Project CI / Frontend tests (pull_request) Successful in 4m1s
Project CI / Backend tests (pull_request) Successful in 6m33s
Project CI / Native shell tests (pull_request) Failing after 3m40s
2026-09-03 10:14:51 +00:00
lhk229 12da570d28 策划 Agent Runtime V2 完成 P2 产物闭环
新增单 Agent GDD 策略、版本产物和审批命令

补齐问询计数、上下文进度和输出校验边界

注册 Planning V2 Tauri 命令并纳入私有产物路径

更新 Runtime V2 技术方案与项目决策记录
2026-09-03 10:04:48 +00:00
lhk229 d70f635eae Merge remote-tracking branch 'origin/master' into feat/design_agent_simple
Project CI / Repository checks (pull_request) Has been cancelled
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
# Conflicts:
#	docs/project-memory/shared-memory/decision-log.md
2026-09-03 08:41:57 +00:00
lhk229 e38edfb446 冻结策划会话 Runtime V2 P0 合同
Project CI / Repository checks (pull_request) Has been cancelled
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
固定 Session、消息、回合结果、GDD 产物和审批记录的 V2 schema。

固定四个 V2 command、输入归一化和 questionLimit=8 语义。

固定 Provider adapter、ContextBuilder、能力快照及未来 MCP/Skill 扩展边界。

固定旧 Supervisor 链路 legacy-cutover 封存和未完成会话强制失败规则。

标记 P0 完成并同步项目决策记录。
2026-09-03 07:25:19 +00:00
lhk229 044c8cfadf 补充策划会话 Runtime V2 接入与旧链路退役方案
新增单 Agent PlanningSessionRuntime V2 的目标架构、状态、持久化和未来 MCP/Skill 兼容边界。

补充 P0-P5 阶段任务拆分、阶段目标、依赖和验收条件。

补充问询上限、Provider 失败、审批、恢复和旧链路强制退役的 BDD 场景。

明确 V2 切换时未完成旧 Supervisor 会话统一投影为 legacy_retired 失败,历史文件只读保留。

同步 docs README、文档地图和项目决策记录。
2026-09-03 07:05:11 +00:00
283 changed files with 33010 additions and 65171 deletions
@@ -16,8 +16,5 @@
"maxRetries": 2,
"retryBackoffMs": 500
},
"agentLlm": {},
"planning": {
"capabilityEnabled": true
}
"agentLlm": {}
}
-2
View File
@@ -19,8 +19,6 @@
"config": "node scripts/game-creator-config-wizard.mjs",
"test:chat": "node scripts/agent-swarm-test-chat.mjs --task \"制作一个可直接试玩的原创植物塔防小游戏:玩家选择并放置原创守卫阻挡敌人,完成波次后可以进入下一关并重新开始。主题、单位名称与视觉语言必须原创,不使用任何现有游戏角色、单位名、Logo 或受保护视觉语言。请自主完成正式产物、静态检查和双视口试玩验证。\" --no-open",
"test:chat:manual": "node scripts/agent-swarm-test-chat.mjs",
"test:plan": "node scripts/agent-swarm-test-chat.mjs --plan --task \"我想做一款原创横版像素解谜小游戏,主角是一个能操控自己影子的小机器人,影子可以变成平台和开关。请完成立项策划并给出 Fast GDD。主题、角色名与视觉语言必须原创,不使用任何现有游戏角色、名称、Logo 或受保护视觉语言。\"",
"test:plan:manual": "node scripts/agent-swarm-test-chat.mjs --plan",
"agent-run": "node scripts/run-cli-with-config.mjs --agent-run",
"agent-run:smoke": "node scripts/smoke-agent-run-local-provider.mjs",
"agent-runtime:real-e2e": "node scripts/agent-runtime-real-e2e.mjs",
@@ -36,8 +36,6 @@ export const ungeneratedGameEntryMarker =
'还没有生成游戏。回到聊天输入创意并确认生成后';
export const defaultRealSwarmTestTask =
'制作一个可直接试玩的原创植物塔防小游戏:玩家选择并放置原创守卫阻挡敌人,完成波次后可以进入下一关并重新开始。主题、单位名称与视觉语言必须原创,不使用任何现有游戏角色、单位名、Logo 或受保护视觉语言。请自主完成正式产物、静态检查和双视口试玩验证。';
export const defaultRealSwarmPlanTask =
'我想做一款原创横版像素解谜小游戏,主角是一个能操控自己影子的小机器人,影子可以变成平台和开关。请完成立项策划并给出 Fast GDD。主题、角色名与视觉语言必须原创,不使用任何现有游戏角色、名称、Logo 或受保护视觉语言。';
export const swarmTurnReportPrefix = '[turn.report] ';
export const swarmTurnReportSchema = 'game-creator-swarm-turn-report.v1';
@@ -140,15 +138,10 @@ export const usage = `用法:
--keep-project 保留自动创建的一次性项目
--no-open 手工模式启动预览但不自动打开浏览器
--task <需求> 通过 manual 入口非交互提交自定义需求
--plan 走「做方案」立项策划入口,不做游戏,不做产物验收和试玩
--timeout-minutes <分钟> 设置本次执行期限;自动任务默认 50 分钟,--plan 默认 6 分钟,手工模式默认不限时
--timeout-minutes <分钟> 设置本次执行期限;自动任务默认 50 分钟,手工模式默认不限时
--dry-run 只检查目录发现和项目准备,不启动 LLM
-h, --help 显示帮助
环境变量:
AGC_PLAN_GDD_DECISION 审批卡自动应答动作,默认 approverevise/reject 必须
同时用 AGC_PLAN_GDD_COMMENT 给出真实修改意见
AGC_PLAN_GDD_COMMENT revise/reject 的意见原文`;
`;
function readOptionValue(args, index, option) {
const value = args[index + 1]?.trim();
@@ -177,7 +170,6 @@ export function parseSwarmTestArguments(args) {
keepProject: false,
openBrowser: true,
task: null,
plan: false,
timeoutMinutes: null,
dryRun: false,
help: false,
@@ -202,8 +194,6 @@ export function parseSwarmTestArguments(args) {
if (task.length > 4_000) throw new Error('--task 不能超过 4000 字符');
options.task = task;
index += 1;
} else if (argument === '--plan') {
options.plan = true;
} else if (argument === '--timeout-minutes') {
if (options.timeoutMinutes !== null) {
throw new Error('--timeout-minutes 只能指定一次');
@@ -222,16 +212,11 @@ export function parseSwarmTestArguments(args) {
}
export function shouldStartPersistentPreview(options) {
// 立项策划链路只出 GDD,没有可试玩产物,任何模式都不该起预览。
return !options.task && !options.plan;
return !options.task;
}
export function resolveSwarmTestTimeoutMs(options) {
// 立项策划的设计目标是五分钟出方案,给一分钟余量;再久就是卡住了,早失败
// 比让 harness 空等更有用。做游戏那条链路的 50 分钟不变。
const planMinutes = options.plan ? 6 : null;
const minutes =
options.timeoutMinutes ?? planMinutes ?? (options.task ? 50 : null);
const minutes = options.timeoutMinutes ?? (options.task ? 50 : null);
return minutes === null ? null : minutes * 60_000;
}
@@ -979,25 +964,12 @@ export function swarmAutoPilotShouldCloseInput(output, promptsAfterSubmit) {
return swarmAutoPilotSitsAtPrompt(output) && promptsAfterSubmit >= 1;
}
// GDD 审批位不能等 CLI 退出之后再处理:Run 停在这里时状态是 waiting-for-user-input
// 而 swarm CLI 恰好把这个状态算作「本轮还在跑」,turn 永远不 settleCLI 也就永远
// 不退出。所以审批必须在 CLI 还活着的时候并发做完,让 Run 自己继续跑到收束。
// 这一句是 PlanGddCompletionBlockerKind::AwaitingApprovalDecision 专有的投影文案,
// 另外三个 blocked 子状态都不会打出它;即便认错了,真正的判据也是随后那次
// --plan-gdd-status,没有待决定审批时不会有任何写入。
const planGddApprovalWaitPattern = /等待 Fast GDD 审批决定/u;
export function swarmOutputAwaitsPlanGddApproval(line) {
return planGddApprovalWaitPattern.test(line);
}
async function runTaskCargo(
cliArguments,
task,
setActiveChild,
timeoutMs,
autoPilot = false,
onPlanGddApprovalWait = null,
) {
const child = spawnChild(cargoCommand, buildCargoCliArguments(cliArguments), {
stdio: ['pipe', 'pipe', 'inherit'],
@@ -1009,25 +981,6 @@ async function runTaskCargo(
let taskSubmitted = false;
let promptsSeen = 0;
let sittingAtPrompt = false;
let planGddApproval = null;
let planGddApprovalError = null;
let planGddApprovalStarted = false;
let planGddApprovalPromise = null;
const startPlanGddApproval = () => {
planGddApprovalStarted = true;
console.log(
`[自动审批] 检测到 Fast GDD 审批位,正在提交 ${resolvePlanGddAutoDecision().action}`,
);
planGddApprovalPromise = onPlanGddApprovalWait()
.then((value) => {
planGddApproval = value;
})
.catch((error) => {
planGddApprovalError = error;
// 审批没成的话 Run 会一直停在等待位,干等到超时只会把真正的原因埋掉。
void terminateChildTree(child).catch(() => {});
});
};
child.stdout.setEncoding('utf8');
child.stdout.on('data', (chunk) => {
process.stdout.write(chunk);
@@ -1040,13 +993,6 @@ async function runTaskCargo(
reportLines.push(normalizedLine);
settled = true;
}
if (
onPlanGddApprovalWait &&
!planGddApprovalStarted &&
swarmOutputAwaitsPlanGddApproval(normalizedLine)
) {
startPlanGddApproval();
}
}
if (!autoPilot || child.stdin.writableEnded) return;
const atPrompt = swarmAutoPilotSitsAtPrompt(pendingLine);
@@ -1085,12 +1031,9 @@ async function runTaskCargo(
if (normalizedPendingLine.startsWith(swarmTurnReportPrefix)) {
reportLines.push(normalizedPendingLine);
}
await planGddApprovalPromise;
if (planGddApprovalError) throw planGddApprovalError;
return {
...result,
turnReportOutput: reportLines.join('\n'),
planGddApproval,
};
} finally {
setActiveChild(null);
@@ -1780,196 +1723,6 @@ export async function validateSwarmProjectArtifacts(projectPath, options) {
return inspection;
}
// 这四条路径的权威定义都在 Rust 侧 `planning_storage.rs``PLAN_SESSION_PATH`、
// `PLAN_GDD_INDEX_PATH`、`PLAN_STORAGE_ROOT`、`PLAN_FAST_GDD_PATH`)。跨语言没有共享
// 常量的通道,改路径时要连同 `GddApprovalCard.tsx` 一起动。
export const planningOutputPaths = [
'.agent/planning/session.json',
'.agent/planning/index.json',
'.agent/planning/pending.json',
'game/fast_gdd.md',
];
export async function inspectPlanningOutputs(projectPath) {
const outputs = [];
for (const relativePath of planningOutputPaths) {
const absolutePath = path.join(projectPath, ...relativePath.split('/'));
const metadata = await lstat(absolutePath).catch((error) => {
if (error?.code === 'ENOENT') return null;
throw error;
});
outputs.push({
path: relativePath,
exists: Boolean(metadata?.isFile()),
bytes: metadata?.isFile() ? metadata.size : 0,
});
}
return outputs;
}
async function reportPlanningOutputs(projectPath) {
const outputs = await inspectPlanningOutputs(projectPath);
console.log('\n立项策划产物:');
for (const output of outputs) {
console.log(
output.exists
? ` [有] ${output.path}${output.bytes} 字节)`
: ` [无] ${output.path}`,
);
}
}
const planGddApprovalTimeoutMs = 60_000;
export const planGddStatusOutputPrefix = 'planGddStateJson=';
export const planGddDecisionOutputPrefix = 'planGddDecisionJson=';
function parsePrefixedJsonLine(output, prefix, label) {
const line = output
.split('\n')
.map((value) => (value.endsWith('\r') ? value.slice(0, -1) : value))
.find((value) => value.startsWith(prefix));
if (!line) throw new Error(`${label}缺少 ${prefix} 输出`);
try {
return JSON.parse(line.slice(prefix.length));
} catch (error) {
throw new Error(`解析${label}失败:${error.message}`);
}
}
export function parsePlanGddStatusOutput(output) {
return parsePrefixedJsonLine(
output,
planGddStatusOutputPrefix,
'Fast GDD 审批状态',
);
}
export function parsePlanGddDecisionOutput(output) {
return parsePrefixedJsonLine(
output,
planGddDecisionOutputPrefix,
'Fast GDD 审批回执',
);
}
// 审批卡是这条链路唯一的人类判据,所以自动应答默认只投 approve,且只在投影确实有
// 一张待决定审批时出手。revise/reject 需要一段真实的修改意见,让机器编一段等于把
// 判据换成噪声——所以那两条分支只在跑的人自己用 AGC_PLAN_GDD_COMMENT 给出意见时
// 才走。手工调 --plan-gdd-decide 也能达到同样效果,但那要求 plan 根 run 仍然活着,
// 而它恰好是本进程持有的 CLI 子进程。
export function planGddAutoApprovalIsPending(state) {
return Boolean(state?.pendingApproval);
}
export function resolvePlanGddAutoDecision(env = process.env) {
const action = (env.AGC_PLAN_GDD_DECISION ?? 'approve').trim();
if (!['approve', 'revise', 'reject'].includes(action)) {
throw new Error('AGC_PLAN_GDD_DECISION 只能是 approve / revise / reject');
}
const comment = (env.AGC_PLAN_GDD_COMMENT ?? '').trim();
if (action === 'approve') return { action, comment: null };
if (!comment) {
throw new Error(
`${action} 必须同时设 AGC_PLAN_GDD_COMMENT 提供真实修改意见`,
);
}
return { action, comment };
}
async function settlePlanGddApproval(
projectPath,
runtimeConfigPath,
setActiveChild,
) {
const readStatus = async () => {
const result = await runCapturedCargo(
['--config-dir', runtimeConfigPath, '--plan-gdd-status', projectPath],
setActiveChild,
{
timeoutMs: planGddApprovalTimeoutMs,
label: 'Fast GDD 审批状态查询',
},
);
if (result.code !== 0 || result.signal) {
throw new Error(
`读取 Fast GDD 审批状态失败:${result.stderr.trim() || result.stdout.trim()}`,
);
}
return parsePlanGddStatusOutput(result.stdout);
};
const before = await readStatus();
if (!planGddAutoApprovalIsPending(before)) {
return { decided: false, state: before };
}
const { action, comment } = resolvePlanGddAutoDecision();
const decision = await runCapturedCargo(
[
'--config-dir',
runtimeConfigPath,
'--plan-gdd-decide',
projectPath,
action,
...(comment === null ? [] : ['--stdin']),
],
setActiveChild,
{
timeoutMs: planGddApprovalTimeoutMs,
label: 'Fast GDD 审批决定',
stdin: comment,
},
);
if (decision.code !== 0 || decision.signal) {
throw new Error(
`提交 Fast GDD 审批决定失败:${decision.stderr.trim() || decision.stdout.trim()}`,
);
}
const receipt = parsePlanGddDecisionOutput(decision.stdout);
// 回执落盘和唤醒后台任务是两件事:decide 命令把唤醒失败降级成 recoveryPending
// 于是审批已经生效、Run 却仍停在 waiting-for-user-input。实测就是这样——只有
// 补一次 --agent-resume 才会重新起 turn。这是仓库自己给这个状态定义的恢复动作。
let recovered = false;
if (receipt.recoveryPending) {
const resume = await runCapturedCargo(
['--config-dir', runtimeConfigPath, '--agent-resume', projectPath],
setActiveChild,
{
timeoutMs: planGddApprovalTimeoutMs,
label: 'Fast GDD 审批后恢复后台任务',
},
);
if (resume.code !== 0 || resume.signal) {
throw new Error(
`审批已提交但恢复后台任务失败:${resume.stderr.trim() || resume.stdout.trim()}`,
);
}
recovered = true;
}
return { decided: true, receipt, recovered, state: await readStatus() };
}
async function reportPlanGddApproval(approval) {
const { state } = approval;
console.log('\nFast GDD 审批:');
if (!approval.decided) {
console.log(` [无待决定审批] 当前投影状态=${state.state}`);
return;
}
console.log(
` [已决定 ${approval.receipt.decisionRef.action}] outcome=${approval.receipt.outcome} v${approval.receipt.decisionRef.version} 投影状态=${state.state}`,
);
if (approval.recovered) {
console.log(
' [已恢复] 审批回执的 recoveryPending 由一次 --agent-resume 收口',
);
}
if (state.session) {
console.log(
` 澄清轮次=${state.session.clarificationRound} 返工深度=${state.session.repairDepth} phase=${state.session.phase}`,
);
}
}
export async function hasConfiguredEditorApiKey(configDir) {
let configured = false;
for (const fileName of [configFileName, localConfigFileName]) {
@@ -2122,13 +1875,7 @@ export async function runSwarmTestChat(options) {
const setActiveChild = (child) => {
activeChild = child;
};
// GDD 审批要和 swarm CLI 并发跑,两者不能共用 activeChild 这一个槽位:审批子进程
// 结束时的 setActiveChild(null) 会把 CLI 从槽里抹掉,Ctrl-C 就杀不到它了。
const concurrentChildren = new Set();
const setConcurrentChild = (child) => {
if (child) concurrentChildren.add(child);
else concurrentChildren.clear();
};
const stopRequested = () => receivedSignal !== null;
const handleSignal = (signal) => {
const repeatedSignal = receivedSignal !== null;
@@ -2206,11 +1953,10 @@ export async function runSwarmTestChat(options) {
);
}
console.log('LLM 配置已就绪。');
const requirementNoun = options.plan ? '立项策划需求' : '游戏需求';
console.log(
options.task
? `已提交一条非交互${requirementNoun},正在等待 Swarm 自主完成。\n`
: `输入一条${requirementNoun}并回车;提交后按 Ctrl+D,让 Swarm 自主完成。\n`,
? '已提交一条非交互游戏需求,正在等待 Swarm 自主完成。\n'
: '输入一条游戏需求并回车;提交后按 Ctrl+D,让 Swarm 自主完成。\n',
);
phase = 'chat';
@@ -2220,8 +1966,7 @@ export async function runSwarmTestChat(options) {
runtimeConfig.path,
'--swarm-chat',
'--init',
// 做方案链路只能跑 standard 档,后端对 plan + autonomous 是硬否决。
options.plan ? '--plan' : '--autonomous-game-build',
'--autonomous-game-build',
project.path,
];
let chat;
@@ -2234,15 +1979,6 @@ export async function runSwarmTestChat(options) {
timeoutDeadline === null
? null
: Math.max(1, timeoutDeadline - Date.now()),
options.plan,
options.plan
? () =>
settlePlanGddApproval(
project.path,
runtimeConfig.path,
setConcurrentChild,
)
: null,
)
: await runInteractiveCargo(chatArguments, setActiveChild);
} catch (error) {
@@ -2258,32 +1994,6 @@ export async function runSwarmTestChat(options) {
if (options.task) {
turnReport = parseSettledSwarmTurnReport(chat.turnReportOutput);
}
if (options.plan) {
// 立项策划不出游戏产物,正式验收在 GDD 审批卡上;这里只报告落盘情况,
// 是否收束已经由 CLI 的退出码判过了。
// 自动任务档的审批已经在 CLI 运行期间并发做完了;手工档(人自己敲 Ctrl+D
// 退出)没有那次触发,退出后补一次,没有待决定审批时它是只读的。
phase = 'plan-approval';
const approval =
chat.planGddApproval ??
(await settlePlanGddApproval(
project.path,
runtimeConfig.path,
setConcurrentChild,
));
if (receivedSignal) break session;
phase = 'plan-report';
await reportPlanGddApproval(approval);
await reportPlanningOutputs(project.path);
phase = 'complete';
console.log(
approval.decided
? '\n立项策划链路已收束:Fast GDD 已批准,策划产物见上方清单。'
: '\n立项策划链路已收束:Run 正常结束但没有待决定审批,策划产物见上方清单。',
);
break session;
}
phase = 'artifact-validation';
const requireEditorImages = await hasConfiguredEditorApiKey(
runtimeConfig.path,
);
@@ -1705,10 +1705,16 @@ if (
runtimeConfigSetupStart === -1 ||
runtimeConfigSetupEnd === -1 ||
!runtimeConfigSetupSource.includes('sanitize_diagnostic_message(') ||
!runtimeConfigSetupSource.includes('append_bounded_diagnostic_line(') ||
!runtimeConfigSetupSource.includes('setup_log.fail(') ||
!runtimeConfigSetupSource.includes(
'startup.appdata.configure.failed details={details}',
)
) ||
!tauriHandlerSource.includes('impl StartupLogSlot {') ||
!tauriHandlerSource.includes('append_bounded_diagnostic_line(&path, line)') ||
!tauriHandlerSource.includes(
'self.append(line);\n show_startup_error_dialog(self.path().as_deref());',
) ||
!tauriHandlerSource.includes('early_startup_log_path(')
) {
throw new Error(
'AI game creator setup must configure the runtime AppData directory and log sanitized setup failures',
@@ -722,7 +722,7 @@ function runAgent() {
stderr += chunk.toString();
});
child.on('error', reject);
child.on('close', (code) => {
child.on('close', (code, signal) => {
const output = `${stdout}${stderr}`;
if (previewReadError) {
reject(previewReadError);
@@ -740,13 +740,24 @@ function runAgent() {
previewDom,
});
} else {
reject(new Error(output || `agent run exited with ${code}`));
reject(
new Error(
`agent run failed: exitCode=${code}, signal=${signal ?? 'none'}\n` +
`stderr tail (last 8000 characters):\n${stderr.slice(-8000)}\n` +
`stdout tail (last 4000 characters):\n${stdout.slice(-4000)}`,
),
);
}
});
});
}
async function seedLocalAsset() {
await fs.mkdir(path.join(projectRoot, 'game'), { recursive: true });
await fs.writeFile(
path.join(projectRoot, 'game/index.html'),
'<!doctype html><html lang="zh-CN"><meta charset="UTF-8"><body>还没有生成游戏</body></html>',
);
await fs.mkdir(path.join(projectRoot, 'assets/uploads'), { recursive: true });
await fs.mkdir(path.join(projectRoot, '.agent'), { recursive: true });
await seedConversationContext();
@@ -1,10 +1,16 @@
import { spawn } from 'node:child_process';
import { spawn, spawnSync } from 'node:child_process';
import { existsSync, readdirSync, readFileSync } from 'node:fs';
import http from 'node:http';
import net from 'node:net';
import { resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import {
normalizeWindowsPath,
parseWindowsProcessSnapshot,
stopWindowsProcessTree,
stopWindowsWorktreeProcesses,
} from '../../../scripts/dev-windows-process.mjs';
import {
agcVitePortEnvKey,
readAgcDevEndpoint,
@@ -15,6 +21,10 @@ import {
const appRoot = fileURLToPath(new URL('..', import.meta.url));
const repoRoot = resolve(appRoot, '../..');
const devStackStatePath = resolve(repoRoot, '.app/dev-stack.json');
const apiServerExePath = resolve(
repoRoot,
'server-rs/target/debug/api-server.exe',
);
const defaultApiTarget =
process.env.RUST_SERVER_TARGET || 'http://127.0.0.1:8082';
const backendDatabase = 'genarrative-game-creator-dev';
@@ -160,19 +170,184 @@ function readBackendServiceFailure(
return null;
}
function urlPort(url) {
try {
const port = Number(new URL(url).port);
return Number.isInteger(port) && port > 0 ? port : 0;
} catch {
return 0;
}
}
// 读取端口当前真正的监听进程身份。返回 null 表示探测本身不可用(例如缺少
// Get-NetTCPConnection),此时调用方必须退化为旧行为,不能让本地启动直接失败。
function readWindowsPortOwnerIdentities(
ports,
{ spawnImpl = spawnSync, env = process.env } = {},
) {
const uniquePorts = [...new Set(ports.filter((port) => port > 0))];
if (uniquePorts.length === 0) {
return null;
}
const command = [
'$ErrorActionPreference = "SilentlyContinue"',
'$ports = ($env:GENARRATIVE_QUERY_PORTS -split ",") | Where-Object { $_ }',
'$result = @()',
'foreach ($port in $ports) {',
' $connection = Get-NetTCPConnection -State Listen -LocalPort ([int]$port) -ErrorAction SilentlyContinue | Select-Object -First 1',
' if (-not $connection) { continue }',
' $owner = Get-CimInstance Win32_Process -Filter ("ProcessId=" + $connection.OwningProcess) -ErrorAction SilentlyContinue',
' $result += [pscustomobject]@{ port = [int]$port; processId = [int]$connection.OwningProcess; name = $owner.Name; executablePath = $owner.ExecutablePath; commandLine = $owner.CommandLine }',
'}',
'ConvertTo-Json -InputObject @($result) -Compress',
].join('\n');
const result = spawnImpl(
'powershell.exe',
['-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', command],
{
encoding: 'utf8',
env: { ...env, GENARRATIVE_QUERY_PORTS: uniquePorts.join(',') },
maxBuffer: 8 * 1024 * 1024,
},
);
if (result?.error || result?.status !== 0) {
return null;
}
const owners = new Map();
for (const entry of parseWindowsProcessSnapshot(result.stdout)) {
const port = Number(entry?.port);
if (Number.isInteger(port) && port > 0) {
owners.set(port, entry);
}
}
return owners;
}
function isWorktreeApiServerOwner(
owner,
{ expectedExePath = apiServerExePath } = {},
) {
if (!owner) {
return false;
}
const expected = normalizeWindowsPath(expectedExePath);
const actual = normalizeWindowsPath(owner.executablePath);
return Boolean(expected) && actual === expected;
}
function isWorktreeSpacetimeOwner(
owner,
{ expectedDataDir = backendSpacetimeDataDir } = {},
) {
if (!owner) {
return false;
}
const expected = normalizeWindowsPath(expectedDataDir);
if (!expected) {
return false;
}
const name = String(owner.name ?? '').toLowerCase();
if (!name.startsWith('spacetime')) {
return false;
}
return normalizeWindowsPath(owner.commandLine).includes(expected);
}
// 端口健康不代表后端属于当前工作树:上个工作树 Ctrl+C 残留的 api-server 仍会
// 应答 /healthz。复用前必须证明端口上的进程就是本工作树的可执行文件与数据目录。
function verifyAgcBackendOwnership({
apiUrl,
spacetimeUrl,
bgfilterWorkerUrl,
platform = process.platform,
expectedExePath = apiServerExePath,
expectedDataDir = backendSpacetimeDataDir,
readPortOwners = readWindowsPortOwnerIdentities,
} = {}) {
if (platform !== 'win32') {
return { ok: true, reason: 'platform-unsupported', owners: new Map() };
}
const ports = [
urlPort(apiUrl),
urlPort(bgfilterWorkerUrl),
urlPort(spacetimeUrl),
];
const owners = readPortOwners(ports);
if (!owners) {
return { ok: true, reason: 'owner-probe-unavailable', owners: new Map() };
}
const apiOwner = owners.get(urlPort(apiUrl));
if (!isWorktreeApiServerOwner(apiOwner, { expectedExePath })) {
return { ok: false, reason: 'api-server-owner-mismatch', owners, apiOwner };
}
const workerOwner = owners.get(urlPort(bgfilterWorkerUrl));
if (!isWorktreeApiServerOwner(workerOwner, { expectedExePath })) {
return {
ok: false,
reason: 'bgfilter-worker-owner-mismatch',
owners,
workerOwner,
};
}
const spacetimeOwner = owners.get(urlPort(spacetimeUrl));
if (!isWorktreeSpacetimeOwner(spacetimeOwner, { expectedDataDir })) {
return {
ok: false,
reason: 'spacetime-owner-mismatch',
owners,
spacetimeOwner,
};
}
return { ok: true, reason: 'owned', owners };
}
function formatOwnerLabel(owner) {
if (!owner) {
return '未知进程';
}
const pid = Number(owner.processId);
const label = owner.executablePath || owner.commandLine || owner.name || '';
return `${Number.isInteger(pid) ? `pid=${pid} ` : ''}${String(label).trim()}`.trim();
}
async function isBackendReady({
state = readJson(devStackStatePath),
isReady = isHttpReady,
verifyOwnership = verifyAgcBackendOwnership,
onOwnershipRejected = null,
} = {}) {
const { apiUrl, spacetimeUrl, bgfilterWorkerUrl, hasMatchingBackend } =
resolveBackendTargetsFromState(state, {
requireAgcBackend: true,
});
if (!hasMatchingBackend || !apiUrl || !spacetimeUrl || !bgfilterWorkerUrl) {
return false;
}
const ownership = await verifyOwnership({
apiUrl,
spacetimeUrl,
bgfilterWorkerUrl,
});
if (!ownership?.ok) {
onOwnershipRejected?.(ownership);
return false;
}
if (ownership.reason === 'owner-probe-unavailable') {
console.warn(
'[ai-game-creator-shell] 无法读取端口监听进程归属,本次按旧行为复用配套后端。',
);
}
return (
hasMatchingBackend &&
Boolean(apiUrl) &&
Boolean(spacetimeUrl) &&
Boolean(bgfilterWorkerUrl) &&
(await isReady(`${apiUrl}/healthz`)) &&
(await isReady(`${spacetimeUrl}/v1/ping`)) &&
(await isReady(`${bgfilterWorkerUrl}/readyz`))
@@ -485,14 +660,18 @@ async function terminateChildTree(
return { stopped: true, forced: false };
}
const result = await taskkillImpl(child.pid);
return {
stopped:
!result?.timedOut &&
!result?.error &&
[0, 128].includes(result?.code ?? 0),
forced: true,
result,
};
const taskkillStopped =
!result?.timedOut &&
!result?.error &&
[0, 128].includes(result?.code ?? 0);
if (taskkillStopped) {
return { stopped: true, forced: true, result };
}
// 包装层(cmd.exe / npm.cmd)先被 Ctrl+C 杀掉时 taskkill 拿不到活着的 PID
// 这里继续按记录下来的根 PID 遍历,尽量收掉更深的后端进程。
const treeStopped = stopWindowsProcessTree(child.pid);
return { stopped: treeStopped.length > 0, forced: true, result };
}
const processGroupId = childLifecycles.get(child)?.processGroupId;
@@ -542,15 +721,29 @@ async function waitForBackendReady(
backendChild,
timeoutMs = 600_000,
{
checkBackendReady = isBackendReady,
checkBackendReady = (onOwnershipRejected) =>
isBackendReady({ onOwnershipRejected }),
readState = () => readJson(devStackStatePath),
resolveTargets = readBackendTargets,
} = {},
) {
const initialStateUpdatedAt = readState()?.updatedAt ?? '';
const startedAt = Date.now();
let lastOwnershipReason = '';
while (Date.now() - startedAt < timeoutMs) {
if (await checkBackendReady()) {
if (
await checkBackendReady((ownership) => {
if (ownership.reason === lastOwnershipReason) {
return;
}
lastOwnershipReason = ownership.reason;
// 本次自己拉起的后端如果归属校验一直不通过,必须把原因打出来,
// 否则只会表现为等待 600 秒后超时。
console.warn(
`[ai-game-creator-shell] 等待配套后端就绪时归属校验未通过(${ownership.reason}: ${formatOwnerLabel(ownership.apiOwner ?? ownership.spacetimeOwner ?? ownership.workerOwner)})。`,
);
})
) {
return resolveTargets();
}
const state = readState();
@@ -576,7 +769,14 @@ async function waitForBackendReady(
async function ensureBackend({
onBackendChild = () => {},
checkBackendReady = isBackendReady,
checkBackendReady = () =>
isBackendReady({
onOwnershipRejected(ownership) {
console.warn(
`[ai-game-creator-shell] 端口上的配套后端不属于当前工作树(${ownership.reason}: ${formatOwnerLabel(ownership.apiOwner ?? ownership.spacetimeOwner ?? ownership.workerOwner)}),改为启动本工作树自己的后端。`,
);
},
}),
resolveTargets = readBackendTargets,
spawnBackend = () =>
spawnChild(
@@ -656,15 +856,33 @@ async function startVite(apiTarget, endpoint = readAgcDevEndpoint()) {
async function main() {
let backendChild = null;
let startedBackend = false;
let viteChild = null;
let shutdownSignal = '';
const signalHandlers = new Map();
// 只有本次会话真正拉起过配套后端时才做兜底清扫:复用别人后端时不能连带
// 杀掉对方的进程。dev.mjs 的清理依赖它的 shell 包装层仍然活着,而 Ctrl+C
// 往往先杀掉包装层,所以这里必须按本工作树 api-server.exe 的身份再收一次。
const sweepStartedBackend = () => {
if (!startedBackend || process.platform !== 'win32') {
return;
}
const stopped = stopWindowsWorktreeProcesses({ apiServerExePath });
if (stopped.length > 0) {
console.log(
`[ai-game-creator-shell] 已清理残留后端进程: ${stopped.join(', ')}`,
);
}
};
for (const signal of ['SIGINT', 'SIGTERM']) {
const handler = () => {
shutdownSignal = signal;
stopChild(viteChild, signal);
stopChild(backendChild, signal);
// 立刻清扫,避免外层 taskkill /F 抢在 finally 之前把本进程杀掉。
sweepStartedBackend();
};
signalHandlers.set(signal, handler);
process.on(signal, handler);
@@ -683,6 +901,7 @@ async function main() {
},
});
backendChild = backend.backendChild;
startedBackend = Boolean(backendChild);
if (shutdownSignal) {
throw new Error(`启动期收到 ${shutdownSignal},已停止配套后端`);
}
@@ -716,6 +935,7 @@ async function main() {
terminateChildTree(viteChild),
terminateChildTree(backendChild),
]);
sweepStartedBackend();
for (const [signal, handler] of signalHandlers) {
process.off(signal, handler);
}
@@ -732,20 +952,25 @@ function isDirectModuleExecution() {
export {
ensureBackend,
formatChildFailure,
formatOwnerLabel,
isAiGameCreatorServer,
isBackendReady,
isDirectModuleExecution,
isProcessGroupAlive,
isWorktreeApiServerOwner,
isWorktreeSpacetimeOwner,
preflightExistingVite,
readBackendServiceFailure,
readChildFailure,
readExistingViteServer,
readLinuxProcessGroupAlive,
readWindowsPortOwnerIdentities,
resolveBackendTargetsFromState,
runWindowsTaskkill,
spawnChild,
stopChild,
terminateChildTree,
verifyAgcBackendOwnership,
waitForBackendReady,
waitForChildTermination,
};
@@ -43,7 +43,6 @@ struct PromptCompositions {
/// 而是一份独立的完整清单:plan 根的工具面只有 7 个原生工具,专业组、
/// isolated child、任务图与视觉产物合同在这条链路上全部不可执行,逐段
/// 减法会把「plan 根到底看到什么」摊在两个函数的四个否定分支里。
supervisor_plan: Vec<String>,
supervisor_chat: SupervisorChatComposition,
}
@@ -99,10 +98,6 @@ struct ProviderFragments {
#[serde(rename_all = "camelCase", deny_unknown_fields)]
struct AgentCatalog {
supervisor: AgentGroup,
/// 立项策划子 Agent。与 `supervisor` 平级、**不进 `groups`**`specialist_nodes`
/// 只从 `groups[].roles[]` 派生,因此它不参与 `build.rs` 与种子 DAG 的一致性
/// 校验,「做游戏」的 16 任务 DAG 一行不动。详见技术方案第 3.1 节。
planning: AgentGroup,
groups: Vec<AgentGroup>,
}
@@ -231,12 +226,6 @@ pub fn compile_manifest(manifest_path: &Path) -> Result<CompiledPromptBundle, St
&sections,
&["$base", "$visualContract"],
)?;
validate_composition(
"supervisorPlan",
&manifest.compositions.supervisor_plan,
&sections,
&["$header"],
)?;
validate_section_reference(
&manifest.compositions.supervisor_chat.identity,
&sections,
@@ -299,7 +288,6 @@ pub fn compile_manifest(manifest_path: &Path) -> Result<CompiledPromptBundle, St
.supervisor
.roles
.iter()
.chain(manifest.agent_catalog.planning.roles.iter())
.chain(
manifest
.agent_catalog
@@ -348,7 +336,6 @@ pub fn compile_manifest(manifest_path: &Path) -> Result<CompiledPromptBundle, St
.runtime
.iter()
.chain(manifest.compositions.supervisor.iter())
.chain(manifest.compositions.supervisor_plan.iter())
.filter(|item| !item.starts_with('$'))
.cloned()
.collect::<BTreeSet<_>>();
@@ -421,14 +408,6 @@ fn validate_section_ownership(manifest: &PromptBundleManifest) -> Result<(), Str
{
register("composition supervisor", section);
}
for section in manifest
.compositions
.supervisor_plan
.iter()
.filter(|section| !section.starts_with('$'))
{
register("composition supervisorPlan", section);
}
register("composition supervisorChat.identity", identity);
register(
"composition supervisorChat.finalReply",
@@ -457,19 +436,9 @@ fn validate_section_ownership(manifest: &PromptBundleManifest) -> Result<(), Str
"composition supervisor",
"composition supervisorChat.identity",
]);
// plan 根 composition 是 Supervisor system prompt 的第二条 lane,不是另一种
// 语义面。它按设计复用 runtime lane 的 `isolatedAgentContract``agent.delegate`
// 的 expectedArtifacts/writeScopes 合同)和 supervisor lane 的 `supervisorRepair`
// (返工必须逐字继承原合同)。除这两个方向外,跨所有者复用仍然是错误。
let allowed_plan_runtime_owners =
BTreeSet::from(["composition runtime", "composition supervisorPlan"]);
let allowed_plan_supervisor_owners =
BTreeSet::from(["composition supervisor", "composition supervisorPlan"]);
for (section, section_owners) in owners {
if section_owners.len() > 1
&& !(section == identity && section_owners == allowed_identity_owners)
&& section_owners != allowed_plan_runtime_owners
&& section_owners != allowed_plan_supervisor_owners
{
return Err(format!(
"Prompt section 跨语义所有者复用:{section} -> {section_owners:?}"
@@ -706,17 +675,11 @@ fn validate_agent_catalog(catalog: &AgentCatalog) -> Result<(), String> {
if catalog.supervisor.roles.len() != 1 {
return Err("agentCatalog.supervisor 必须且只能包含一个 role".to_string());
}
if catalog.planning.roles.len() != 1 {
return Err("agentCatalog.planning 必须且只能包含一个 role".to_string());
}
if catalog.groups.is_empty() {
return Err("agentCatalog.groups 不能为空".to_string());
}
let mut group_brief_names = BTreeSet::new();
for group in std::iter::once(&catalog.supervisor)
.chain(std::iter::once(&catalog.planning))
.chain(catalog.groups.iter())
{
for group in std::iter::once(&catalog.supervisor).chain(catalog.groups.iter()) {
if !group_brief_names.insert(group.brief_path_name.as_str()) {
return Err(format!(
"agent group briefPathName 重复:{}",
@@ -724,10 +687,7 @@ fn validate_agent_catalog(catalog: &AgentCatalog) -> Result<(), String> {
));
}
}
let mut generated_names = BTreeSet::from([
"PROJECT_SUPERVISOR".to_string(),
"PROJECT_PLANNING".to_string(),
]);
let mut generated_names = BTreeSet::from(["PROJECT_SUPERVISOR".to_string()]);
for group in &catalog.groups {
let generated = rust_identifier(&group.id);
if !generated
@@ -753,12 +713,6 @@ fn validate_agent_catalog(catalog: &AgentCatalog) -> Result<(), String> {
&mut task_ids,
&mut tool_ids,
)?;
validate_agent_group(
&catalog.planning,
&mut group_ids,
&mut task_ids,
&mut tool_ids,
)?;
for group in &catalog.groups {
validate_agent_group(group, &mut group_ids, &mut task_ids, &mut tool_ids)?;
}
@@ -920,10 +874,6 @@ fn render_rust(manifest: &PromptBundleManifest, sections: &BTreeMap<String, Stri
"RUNTIME_PROMPT_SUPERVISOR_COMPOSITION",
&manifest.compositions.supervisor,
));
output.push_str(&render_string_slice_const(
"RUNTIME_PROMPT_SUPERVISOR_PLAN_COMPOSITION",
&manifest.compositions.supervisor_plan,
));
output.push_str(&format!(
"pub(crate) const RUNTIME_PROMPT_SUPERVISOR_CHAT_COMPOSITION: &[&str] = &[{}, {}];\n",
rust_literal(&manifest.compositions.supervisor_chat.identity),
@@ -1012,26 +962,6 @@ fn render_agent_catalog(catalog: &AgentCatalog) -> String {
"static PROJECT_SUPERVISOR_AGENT_DEFINITION: AgentGroupDefinition = {};\n",
render_group_value(&catalog.supervisor, "&PROJECT_SUPERVISOR_AGENT_ROLES")
));
let planning_role = &catalog.planning.roles[0];
output.push_str(&format!(
"pub(crate) const GAME_CREATOR_PROJECT_PLANNING_AGENT_ID: &str = {};\n",
rust_literal(&planning_role.task_id)
));
output.push_str(&format!(
"pub(crate) const GAME_CREATOR_PROJECT_PLANNING_MEMORY_PATH: &str = {};\n",
rust_literal(&format!(
"memory/agents/{}",
catalog.planning.brief_path_name
))
));
output.push_str(&render_role_array(
"PROJECT_PLANNING_AGENT_ROLES",
&catalog.planning.roles,
));
output.push_str(&format!(
"static PROJECT_PLANNING_AGENT_DEFINITION: AgentGroupDefinition = {};\n",
render_group_value(&catalog.planning, "&PROJECT_PLANNING_AGENT_ROLES")
));
for group in &catalog.groups {
let roles_name = format!("{}_AGENT_ROLES", rust_identifier(&group.id));
output.push_str(&render_role_array(&roles_name, &group.roles));
@@ -1,7 +1,7 @@
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "main",
"description": "AI 游戏创作主窗口允许读取系统剪贴板图片,用于粘贴素材附件。",
"description": "AI 游戏创作主窗口允许读取系统剪贴板图片,用于粘贴素材附件;允许弹出原生打开/保存对话框用于素材上传与导出。",
"windows": ["client"],
"permissions": [
"clipboard-manager:allow-read-image",
@@ -21,6 +21,7 @@
]
},
"opener:default",
"dialog:allow-open"
"dialog:allow-open",
"dialog:allow-save"
]
}
@@ -0,0 +1 @@
你是游戏创作需求的润色助手。把用户给创作 Agent 的一段需求改写成更清晰、可执行的中文需求。要求:保留用户的原始意图、玩法、美术方向、数值与限制条件,不得新增或删除需求点,不得替用户做决定,不得写成方案书或任务清单。只输出润色后的需求正文,不要解释、不要引言、不要 Markdown 标记、不要引号包裹、不要重复用户原文;无法润色时原样输出用户输入。
@@ -23,11 +23,7 @@
"supervisorVisualWithEditor": "supervisor/visual-contract-with-editor.md",
"supervisorPlaybook": "supervisor/playbook.md",
"supervisorClaimGate": "supervisor/claim-gate.md",
"supervisorRepair": "supervisor/repair.md",
"projectPlanningRoleBrief": "roles/project-planning.md",
"planCommon": "plan/common.md",
"planSupervisorIdentity": "plan/supervisor-identity.md",
"planSupervisorPlaybook": "plan/supervisor-playbook.md"
"supervisorRepair": "supervisor/repair.md"
},
"compositions": {
"runtime": [
@@ -47,14 +43,6 @@
"supervisorClaimGate",
"supervisorRepair"
],
"supervisorPlan": [
"$header",
"planCommon",
"isolatedAgentContract",
"planSupervisorIdentity",
"planSupervisorPlaybook",
"supervisorRepair"
],
"supervisorChat": {
"identity": "supervisorIdentityContract",
"finalReply": "supervisorFinalReplyContract"
@@ -70,12 +58,7 @@
"editorUnavailable": "supervisorVisualWithoutEditor"
}
},
"roleOverlays": [
{
"agentId": "project-planning",
"sections": ["projectPlanningRoleBrief"]
}
],
"roleOverlays": [],
"providerFragments": {
"isolatedToolContract": "providerIsolatedToolContract",
"autonomousRunProfile": "providerAutonomousRunProfile",
@@ -102,21 +85,6 @@
}
]
},
"planning": {
"id": "planning",
"label": "立项策划",
"role": "Project Planning",
"briefPathName": "project-planning.md",
"roles": [
{
"id": "project-planning",
"role": "Project Planning",
"taskId": "project-planning",
"toolId": "agent.runtime.project-planning",
"briefPathName": "project-planning.md"
}
]
},
"groups": [
{
"id": "design",
@@ -1,7 +0,0 @@
用户只描述玩法类型、机制或相似体验时,不代表授权复刻现有游戏。所有专业 Agent 必须创建原创标题、阵营、资源、单位名称、角色造型、界面术语和视觉语言;禁止沿用、翻译或近似改写现有游戏的专有角色、单位名、Logo、贴图、标志性布局与受保护视觉语言。除非用户明确提供有权使用的项目内素材,否则不得把 Sunflower、Peashooter、向日葵、豌豆射手、僵尸等知名塔防元素写入策划、记忆、代码、图片提示或正式产物。
静态委派协议:新 agent.delegate 必须提交 1-8 条 acceptanceCriteria、0-16 个精确项目内非私有 expectedArtifacts,以及 nullable repairOfDelegationId/runId/continuationOfDelegationId/questionsSha256/answersSha256,普通委派后三项传 null。专业 Agent 收到的 task 会携带完整合同。Supervisor 认领回执后必须区分 evidence-ready、needs-user-input 与 needs-repair;前者仍需语义验收,needs-repair 不能作为成功。专业 Agent 若缺少会实质改变结果的用户事实,不能调用 user.input_request,必须以最终回复首行 `AGC_NEEDS_USER_INPUT_V1`,下一行短 JSON `{"questions":[...]}` 返回 1-3 个结构化问题;Runtime 会把它作为内部回执交给 Supervisor。Supervisor 对每个原 delivery 逐一用现有 user.input_request 提问,收齐对应答案后最多创建一次 continuation 委派,并同时提交 continuationOfDelegationId、questionsSha256、answersSha256Runtime 会自动派生稳定 continuation identity,不得把多个 delivery 的问题或答案混入同一 continuation。
每 6 轮只是一次进度 checkpoint 与停滞检测,不是上下文压缩或 run 的终止上限;只要 observation 出现新的独立进展,就在同一 run 继续下一窗口,只有窗口没有新进展时才按停滞处理。真正的上下文压缩仅由 token 阈值或显式 compact 触发。
必须直接调用与当前请求广告的工具一一对应的动作函数,或在本阶段确实无事可做时调用 respond_to_user。本 run 不维护结构化计划,也没有 update_agent_plan 可调;工具结果会由 Runtime 作为 observation 返回,不要假装工具已执行,不要把动作或回复放进普通文本,不要 markdown,不要泄露密钥。
@@ -1,7 +0,0 @@
你是 Genarrative AI 游戏创作桌面 App 的 Project Supervisor。当前 run 是立项策划根 run(`source=project-supervisor-plan`),你是用户在本条链路里唯一的对话对象。
你不生产策划内容。本链路的全部策划工作——提问、取舍、撰写 GDD——都由 `project-planning` 子 Agent 完成。你只有四件事:冻结目标合同;发起与续跑对 `project-planning` 的委派;代子 Agent 向用户提问并把答案原样转达回去;在子 Agent 提交 GDD 后完成取证,把审批交给用户。
你不做的事:不自己提策划问题(`user.input_request` 只能用于转达子 Agent 的问题信封);不自己撰写、补写或改写 GDD 正文、决定台账与原型验证项;不替用户做产品决定;不写文件、不跑命令、不做预览、不生成素材、不查询任务图、不调度 ready 任务;不委派 `project-planning` 以外的任何 Agent,也不创建 isolated child 或启动构建。
`project-planning` 的消息和回执只是原目标的证据,不能替换原目标。contractStatus=evidence-ready 只代表客观证据齐全,你仍须按 acceptanceCriteria 逐条完成语义验收;needs-repair 不得忽略,同一原委派最多发起一轮显式返工。GDD 最终是否通过由用户在审批卡上决定,不由你代答。
@@ -1,22 +0,0 @@
【固定动作顺序,不得跳步】
1. 本 run 第一轮只调用一次 `agent.goal_contract` 冻结目标合同:outcome 概括用户原话意图,`preferences` 必须传空数组,`acceptanceNodes` 提交 Runtime 指定的固定单节点。这一轮不做任何其它调用。
2. 冻结后立即用一次 `agent.delegate` 把任务委派给 `project-planning``expectedArtifacts``game/fast_gdd.md``repairOfDelegationId``runId``continuationOfDelegationId``questionsSha256``answersSha256` 全传 null。已有委派尚未收束时不要重复委派。
3. 等待子 Agent 期间不得调用 `respond_to_user`。Runtime 会通过 delegate 完成屏障保持同一父 run,回执到达后再继续。
4. 子 Agent 以问询信封退出时,决策卡由 Runtime 直接按信封原文呈现给用户,**不需要你调用任何工具**——你根本不会在那一刻被恢复。用户答完之后你才会拿到答案,届时为该原 delivery 创建且仅创建一次 continuation 委派,`continuationOfDelegationId``repairOfDelegationId` 都指向该原 delivery。`questionsSha256``answersSha256``acceptanceCriteria``expectedArtifacts` 四个全传 null——Runtime 会从该原 delivery 补齐权威指纹和原委派合同,你不要自己抄。子 Agent 在 continuation 里**再次**以信封退出时,对那条新 delivery 重复同一动作:「仅创建一次」约束的是单条 delivery,不是整条链,澄清预算未用尽时这个循环继续。Runtime 会在委派 task 末尾写明已用轮次与上限,不需要你自己数,也不要替它宣布预算已尽。
5. 回执 contractStatus=evidence-ready 且 GDD 已提交时,用 `file.read` 从第 1 行读到 `game/fast_gdd.md` 末尾取证,每次都传 `maxLines: 240`(上限),尽量一页读完;确实需要第二页时从上一页的下一行开始,不要重复读同一段。每次 `file.read` 的 observation 末尾都带着 `sourceAgentId` / `sourceRunId` / `sourceActionId` 三个字段,把它们原样抄成 evidence 的 `{agentId, runId, actionId}`,用一次 `agent.acceptance_update` 一并提交即可——evidence 是按这三个字段整体查回执的,回忆错任何一个都会被判成"缺少持久动作回执"。不要为了取这些字段再去查动作历史。取证完成前审批卡不会出现。
6. 用户在审批卡上选择修改或退回时,直接创建返工委派:`repairOfDelegationId` 指向原 delegationId`runId``acceptanceCriteria``expectedArtifacts` 都传 null——Runtime 会从原 delivery 继承权威合同,不需要先 `agent.run_status` 去取再手抄。把用户原话完整附在 task 里。「同一原委派只能返工一次」约束的是单条 delivery,不是整条链:用户看过新稿再点一次修改,就对那条新 delivery 重复同一动作,这个循环没有次数上限——`repair_depth` 防的是 runaway agent,而每一轮修订都由用户亲手触发,人本身就是循环边界。不要替 Runtime 宣布「这是最后一次修改机会」,也不要因此把多条意见攒到一轮里改完。用户通过后只做一句简短收尾。
【转达的规则】
- 把用户答案回灌给 `project-planning` 时,逐条列出全部已确认决定,每条格式为 `[已确认] 第N轮问的是:{question 原文} 候选项:{option1.label} {option2.label} {option3.label} → 用户答:{原文}`。**问题原文和三个选项标签必须带上**:`{header}` 只写到「第N轮·当前要决定:{主题}」这一层,答案落在选项上;子 Agent 每轮都是全新 run,除了这段正文什么都看不到,只给它主题和答案,「类似B」「B · 沙盒里程碑成长」这类答案就无从解读,它只能把同一件事再问一遍。用户答案原文一字不改、不归纳、不拆分、不搬轮次;任务长度接近上限时压缩你自己的说明文字和选项描述,绝不压缩用户答案、问题原文和选项标签。
- 策划链路的澄清信封**恰好一题**,不是通用静态委派协议里的 1-3 题:`project-planning` 每轮只提一个主要决定,Runtime 也只接受一题,多于一题会在出卡时被拒。委派 task 里不要写“1-3 个结构化问题”。
- 上一条格式里的三个选项标签就是决策卡上的 A、B 和“需要原型验证”,必须原样转述、一个都不能省;B 是用户确认的 `confirmed/user_option`,不能转成默认建议。用户后续自由填写推翻了更早的决定时,你只负责把两轮答案的原文都原样带到,并说明后者更晚;怎么记进决定台账由 `project-planning` 判断,不要替它裁定哪条作废。
【委派合同的边界】
委派 `project-planning` 时,acceptanceCriteria 只写产物形状、覆盖范围与红线(例如必须交付 `game/fast_gdd.md`、必须原创、必须只定义一个 MVP 闭环),**不得替用户预先裁定产品取舍**。用户没有指定的玩法规则、数值、关卡量级、美术方向和目标人群,一律留给策划子 Agent 按其 3 轮问询预算决定是提问还是按默认建议填写;不要写“未指定的标注为立项假设”“自行假设后继续”这类指令,那会把问询预算作废。平台事实(自包含 Web、desktop/mobile 双视口、keyboard/touch 双输入、本地 HTTP 预览)由 Runtime 固定注入,属于已定事实,不得要求标为待定、建议或开放项。
**本轮指令三选一。** 委派任务正文里,除了用户原始意图和已确认答案原文,你只能再写一句“本轮该做什么”,且必须是下面三个之一:**继续澄清**(默认,不附加任何前置条件)、**直接出稿**(仅当用户明确要求跳过问询)、**按意见修订**(仅审批返回修改或退回时)。不要自己描述“什么情况下才该提问”“若缺少会实质改变结果的事实则……”“否则直接提交完整 GDD”——那不在这三项里。提问预算怎么花,由 `project-planning` 按 Runtime 注入的判据决定。
不要向用户暴露内部 task/event、工具计划、动态 child ID 或调试状态。
@@ -1,33 +0,0 @@
你是“立项策划 Agent”(`agentId=project-planning`),由 Project Supervisor 通过静态 `agent.delegate` 委派。你的工作是把一句用户需求收敛成可审批的 MVP Fast GDD;你只负责玩法澄清、原型验证建议和最小 GDD,不负责完整游戏构建。
## 身份与边界
- 当前 run 固定为 `source=agent-delegate``profile=standard`,父 Agent 是 `project-supervisor`。不得伪造、改写或猜测这些 Runtime 身份。
- 你不能委派或调度其他 Agent,不能创建 isolated child,不能调用 MCP、命令、进程、预览、画布、素材生成、写入/补丁/删除工具,也不能改变项目版本或审批事实。
- 你的原生工具目录只应包含 `file.read``file.list` 以及 Runtime 协议控制函数 `update_agent_plan``respond_to_user``user.input_request` 不属于你的工具目录。若需要用户决定,必须以终态信封首行 `AGC_NEEDS_USER_INPUT_V1` 退出本轮,下一行给出严格 JSON 信封 `{"questions":[{ ... }]}`,交由 Supervisor 转发。`questions` 恰好一个元素;元素字段只能是 `id``header``question``options` 四个,多写任何字段(例如 `answerFormat`)或省掉 `questions` 外壳都会被 Runtime 拒收,整条委派随即作废。`id` 是唯一 snake_case(小写字母开头,只含小写字母、数字、下划线);`header` 是决策卡标题,写成 `第N轮·当前要决定:<主题>`,单行且不超过 60 字符;`question` 是决策卡正文,单行且不超过 400 字符;`options` 恰好 3 个 `{"label": ..., "description": ...}`,依次是 A、B、逐字“需要原型验证”(详见下文决策卡一段),label 单行不超过 60 字符、description 单行不超过 240 字符。不要另起一行写答题说明或把选项复述进 `question`,作答方式由 Runtime 自己呈现。
- 只有 Runtime 广告并允许 `plan.submit_gdd` 时才可提交 GDD;不要假设未广告的工具存在,也不要把 GDD、审批或下游构建写进普通文本。
## 目标与轮次
- 最多进行 3 轮关键澄清;每轮是新 run、同一 session。你看得到自己的历史,但用户答案以 Supervisor 委派任务中的转述为准,缺失信息不能臆造。
- **默认先澄清。** 出稿只有四个触发器,除此之外每轮都先做下面的字段差距检测再决定问不问:①任务正文出现“直接出稿”这四个字;②已完成第 3 轮澄清(任务正文写明的已用轮次已达上限);③剩余空白都能由默认建议覆盖,且不影响首个可玩闭环;④收到 Runtime 的活跃预算或超时提示。任务正文能改变流程的只有第 ① 条——它写的其它说明属于内容,不是出稿触发器。既定事实(用户答案、已确认决定)仍以任务正文为准。
- 每轮提问前逐项对照 `plan-submit-gdd-input.v1``game` 字段做差距检测:用户明确提供的 = `confirmed`;有依据可推断的 = 按下面的默认建议填写并标 `default_pending`;无从判断的 = 空白。提问名额只花在**空白或存疑、且影响首个可玩闭环**的决定上;有默认建议兜底的字段优先用默认建议而不是提问——「有默认」不等于「不能问」,那条默认明显可能是错的、且选错就做不出首个可玩闭环时,它就是一个该问的存疑项。`title``oneLiner``mvpSystems``creatorTips` 由你生成并标 `default_pending`,不作为提问对象;`platformFacts` 禁问。
- **默认建议**(一律 `answerSource=default``round=0`;只用于缩短对话,不覆盖用户明确输入):`targetUsers.sessionLength` 缺 → 1020 分钟一局;`artStyle` 缺 → `visualType` 风格化、轮廓清楚,`keywords` 取自已确认的核心行为,`mvpArtBoundary` 写明 MVP 用占位资产、资产可复用;缺成长时 → 1 条成长线和 2~3 个选择;缺探索时 → 1 条主路线加 1 个有意义的岔路;缺构建时 → 高风险输出和稳健防御两种方向。清单之外的字段没有默认值兜底——`genre.fusion``targetUsers.coreUsers` / `preferences` / `referenceGames``outOfScope` 缺失时都算空白,该不该花一轮问它们由上面的判据决定,不要自己拍一个值填掉就当它已经定了。**`pillars``coreLoop` 没有默认建议**:它们就是首个可玩闭环本身,空白时属于该问的空白,不得用默认值填掉。
- 优先顺序:核心行为与本局目标 → 重玩动力 → 制作边界与 MVP。每轮最多问一个主要决定。**已确认决定关掉的那条轴不得重问。** 任务正文里每条 `[已确认]` 都带着当轮的问题原文和三个选项标签,先照它判断哪些轴已经关闭,本轮的问题必须落在另一条还没关闭的轴上。把已确认答案换个说法再问一遍——例如用户已经选定“自由经营、靠成就和攒钱升级推进”,你又拿“短周期经营目标 vs 沙盒里程碑成长”去问——是白烧一轮预算。所有轴都已关闭时按出稿触发器③直接出稿。
- 决策卡的 header 写成“第N轮·当前要决定:<主题>”,最多 60 字符。N 是 Runtime 从委派谱系派生的当前轮号,写错会被 Runtime 拒收:首轮恒为 1;之后每次续跑的任务正文都会写明已用轮次与上限,本轮该用的 N 就是“已用轮次 + 1”。`<主题>` 是这一轮真正要定的那件事本身(例如“塔的构筑方式”“每局变化来源”),一句话说完、不带状态标记——它会原样落进决定台账的 `topic`,也是你下一轮辨认哪些轴已经关掉的唯一线索,写成“关键决定”这类空话等于把它作废。正文只问尚未由平台事实或 MVP 规则排除的真实产品取舍,并说明为什么现在问;每张卡固定提供三个选项:A 是你的推荐方案(label 以 `A ·``A:``A``A-` 开头并写明推荐、好处和代价),B 是形状不同且真实可行的平行备选(label 以 `B ·``B:``B``B-` 开头并写明后果和代价),第三项逐字为“需要原型验证”,description 必须给出 30~90 分钟微型原型、试玩对象、观察信号和通过标准。自由输入按用户原话处理。
## 低幻觉与 GDD 约束
- 用户描述玩法类型、机制或“像某款游戏”时,不代表授权复刻该游戏。游戏名称、世界观、角色与单位名、阵营、资源、界面术语和视觉语言必须原创;不得沿用、翻译或近似改写现有游戏的专有名称、Logo、标志性布局与受保护视觉语言,也不得把它们写进 GDD 正文、决定台账或原型验证项。用户提到的相似作品只能作为抽象品类参考,`targetUsers.referenceGames` 同样不得填入受保护名称。你的工具面窄,但内容红线不因此放宽——GDD 是整条产线的上游。
- 决定台账记录当前 GDD 的决定快照。澄清阶段的 A、B 或自由填写得到的用户决定标 `confirmed`,选择“需要原型验证”标 `prototype_pending`;未提问、由你按默认建议填写的字段标 `default_pending``answerSource=default``round=0`。审批阶段的用户修改意见是本轮最高优先级:由该意见新增或改写的决定使用 `answerSource=user_revision``round=0`,并按当前意见重新填写 `topic``state``answerSummary`
- 以当前 GDD 为基线,仅修改用户审批意见明确涉及的内容,以及为保持内部一致性所必需同步调整的派生内容。未被意见涉及的内容保持不变;如果意见与过去决定冲突,以最新意见为准。不要把用户未要求的其它方向自行扩展进本轮修订。提交时仍须提供完整 GDD 快照,但完整快照不代表可以任意重写未涉及内容。
- `prototypeValidationItems` 是必填字段(没有就传空数组),与 `prototype_pending` 决定**一一对应**:每条 `prototype_pending` 决定必须有一个同 id 的验证项,每个验证项也必须对应一条 `prototype_pending` 决定,最多 3 项。除了用户亲选“需要原型验证”之外,你自己也可以主动标:手感、节奏、可读性、难度曲线这类你没问过、但选错就做不出首个可玩闭环的判断,标 `prototype_pending``answerSource=default``round=0`)比标 `default_pending` 诚实——那不是一个默认值,是一个没人验证过的假设。每项写清 30~90 分钟微型原型做什么、让谁试玩、观察什么信号、什么算通过。
- 不得编造具体游戏的机制、数值、销量、人群规模、团队规模或来源。写 `targetUsers` 时按已确认的类型与核心行为描述典型玩家即可。
- 只定义一个完整可玩闭环。MVP 不含多人、商城、服务器、开放世界、赛季、复杂社交、完整剧情或全量内容,除非用户明确改变范围。
- GDD 至少覆盖:游戏名称与类型、一句话描述、2~4 条游戏支柱、核心循环、目标用户、美术方向、3~6 个最小 MVP 系统、先做/暂缓/验证/扩展条件、决定状态和审批请求。不要把 Runtime 注入的身份、时间、指纹、审批 receipt 或平台事实当作 Provider 输入字段。
- 平台事实由 Runtime 固定注入为自包含 Web、desktop/mobile 双视口、keyboard/touch 双输入、本地 HTTP 预览;不得修改、删减或向用户询问。
## 输出纪律
- 澄清模式只返回 `AGC_NEEDS_USER_INPUT_V1` 终态信封,不再调用其他函数;成稿模式只在 `plan.submit_gdd` 被广告时调用它并等待 Runtime 校验;收到 revise/reject observation 后按同一 GDD 谱系修订,收到 approve 后只做简短收尾。
- 必须直接调用当前请求广告的原生函数;不要输出 JSON、代码围栏或内部思考过程,不要假装已经写入文件、完成审批或启动构建。
@@ -16,6 +16,7 @@ mod direct_codex_attachments;
mod direct_codex_audit;
mod direct_project_history;
mod direct_project_turn_history;
mod direct_codex_references;
mod direct_runtime;
mod direct_tool_bridge;
mod direct_tools_mcp;
@@ -42,6 +43,7 @@ pub(crate) use direct_codex_attachments::*;
pub(crate) use direct_codex_audit::*;
pub(crate) use direct_project_history::*;
pub(crate) use direct_project_turn_history::*;
pub(crate) use direct_codex_references::*;
pub(crate) use direct_runtime::*;
pub(crate) use direct_tool_bridge::*;
pub(crate) use direct_tools_mcp::*;
@@ -3777,8 +3777,6 @@ pub(crate) async fn direct_game_creator_codex_chat_at_with_optional_observer(
request_slot: "direct-chat".to_string(),
web_search_enabled: config.llm.web_search_enabled,
allow_idle_context_compaction: false,
// direct-codex 不是立项策划链路,没有 planning session 可绑定。
planning_session_binding: None,
};
let api_kind =
parse_game_creator_llm_api_kind(&config.llm.api_kind).map_err(|error| error.to_string())?;
@@ -3845,8 +3843,6 @@ pub(crate) async fn direct_game_creator_home_codex_chat(
request_slot: "direct-home-chat".to_string(),
web_search_enabled: config.llm.web_search_enabled,
allow_idle_context_compaction: false,
// 直连 Codex 的首页对话不属于任何立项策划 session。
planning_session_binding: None,
};
let api_kind =
parse_game_creator_llm_api_kind(&config.llm.api_kind).map_err(|error| error.to_string())?;
@@ -4180,7 +4176,6 @@ mod tests {
request_slot: "slot-1".to_string(),
web_search_enabled: false,
allow_idle_context_compaction: false,
planning_session_binding: None,
}
}
@@ -0,0 +1,328 @@
use super::*;
pub(crate) const MAX_DIRECT_CODEX_REFERENCES: usize = 32;
const MAX_DIRECT_CODEX_REFERENCE_ID_CHARS: usize = 200;
const MAX_DIRECT_CODEX_REFERENCE_LABEL_CHARS: usize = 160;
const MAX_DIRECT_CODEX_REFERENCE_SOURCE_CHARS: usize = 32;
const MAX_DIRECT_CODEX_REFERENCE_ELEMENT_CHARS: usize = 80;
const MAX_DIRECT_CODEX_REFERENCE_TEXT_CHARS: usize = 240;
const DIRECT_CODEX_REFERENCE_HEADER: &str =
"[本轮用户引用素材:以下均为当前项目已确认的安全引用。请使用稳定资源 ID 和项目相对路径读取,不要读取或输出其它路径。]";
#[derive(Clone, Debug, serde::Deserialize)]
#[serde(rename_all = "camelCase", tag = "type")]
pub(crate) enum DirectCodexTurnReference {
#[serde(rename = "resource")]
Resource(DirectCodexResourceReference),
#[serde(rename = "runtime-region")]
RuntimeRegion(DirectCodexRuntimeRegionReference),
}
#[derive(Clone, Debug, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct DirectCodexResourceReference {
pub(crate) resource_id: String,
#[serde(default)]
pub(crate) label: Option<String>,
#[serde(default)]
pub(crate) source: Option<String>,
}
#[derive(Clone, Debug, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct DirectCodexRuntimeRegionReference {
#[serde(default)]
pub(crate) label: Option<String>,
#[serde(default)]
pub(crate) run_id: Option<String>,
#[serde(default)]
pub(crate) version_id: Option<String>,
#[serde(default)]
pub(crate) element_tag: Option<String>,
#[serde(default)]
pub(crate) element_role: Option<String>,
#[serde(default)]
pub(crate) text: Option<String>,
#[serde(default)]
pub(crate) width: Option<f64>,
#[serde(default)]
pub(crate) height: Option<f64>,
#[serde(default)]
pub(crate) resource_ids: Vec<String>,
}
fn sanitize_reference_text(value: &str, max_chars: usize) -> Option<String> {
let sanitized = value
.trim()
.chars()
.filter(|character| !character.is_control())
.take(max_chars)
.collect::<String>();
if sanitized.is_empty() {
None
} else {
Some(sanitized)
}
}
fn sanitize_reference_label(value: Option<&str>) -> Option<String> {
value.and_then(|value| sanitize_reference_text(value, MAX_DIRECT_CODEX_REFERENCE_LABEL_CHARS))
}
fn sanitize_reference_source(value: Option<&str>) -> Option<String> {
let value = sanitize_reference_text(value?, MAX_DIRECT_CODEX_REFERENCE_SOURCE_CHARS)?;
if value
.chars()
.all(|character| character.is_ascii_alphanumeric() || matches!(character, '-' | '_' | '.'))
{
Some(value)
} else {
None
}
}
fn sanitize_reference_element(value: Option<&str>) -> Option<String> {
let value = sanitize_reference_text(value?, MAX_DIRECT_CODEX_REFERENCE_ELEMENT_CHARS)?;
if value
.chars()
.all(|character| character.is_ascii_alphanumeric() || matches!(character, '-' | '_' | '.'))
{
Some(value)
} else {
None
}
}
fn sanitize_reference_dimension(value: Option<f64>) -> Option<u32> {
value
.filter(|value| value.is_finite() && *value > 0.0)
.map(|value| value.round().clamp(1.0, 100_000.0) as u32)
}
fn asset_display_label(asset: &GameCreationAppAssetManifestEntry) -> String {
let basename = asset
.local_path
.rsplit(['/', '\\'])
.next()
.unwrap_or(&asset.id);
let without_extension = basename
.rsplit_once('.')
.map(|(name, _)| name)
.unwrap_or(basename)
.trim();
if without_extension.is_empty() {
asset.id.clone()
} else {
without_extension
.chars()
.filter(|character| !character.is_control())
.take(MAX_DIRECT_CODEX_REFERENCE_LABEL_CHARS)
.collect()
}
}
fn validate_resource_reference_id(value: &str) -> Result<String, String> {
let resource_id = value.trim();
if resource_id.is_empty()
|| resource_id.chars().count() > MAX_DIRECT_CODEX_REFERENCE_ID_CHARS
|| resource_id.chars().any(char::is_control)
{
return Err("引用的素材 ID 无效,请移除后重新选择".to_string());
}
Ok(resource_id.to_string())
}
fn render_resource_reference_line(
manifest: &GameCreationAppManifest,
reference: &DirectCodexResourceReference,
) -> Result<String, String> {
let resource_id = validate_resource_reference_id(&reference.resource_id)?;
let asset = manifest
.assets
.iter()
.find(|asset| asset.id == resource_id)
.ok_or_else(|| "引用的素材已不存在,请移除后重新选择".to_string())?;
let local_path = sanitize_attachment_local_path(&asset.local_path)
.ok_or_else(|| "引用的素材路径无效,请移除后重新选择".to_string())?;
let label = sanitize_reference_label(reference.label.as_deref())
.unwrap_or_else(|| asset_display_label(asset));
let source = sanitize_reference_source(reference.source.as_deref())
.unwrap_or_else(|| "unknown".to_string());
Ok(format!(
"- 素材 ID{resource_id};名称:{label};类型:{};媒体类型:{};项目路径:{local_path};来源:{source}",
asset.kind, asset.media_type
))
}
fn render_runtime_region_reference_line(
manifest: &GameCreationAppManifest,
reference: &DirectCodexRuntimeRegionReference,
) -> Result<String, String> {
let label = sanitize_reference_label(reference.label.as_deref())
.unwrap_or_else(|| "运行画面区域".to_string());
let run_id = sanitize_reference_source(reference.run_id.as_deref());
let version_id = sanitize_reference_source(reference.version_id.as_deref());
let element_tag = sanitize_reference_element(reference.element_tag.as_deref());
let element_role = sanitize_reference_element(reference.element_role.as_deref());
let text = reference
.text
.as_deref()
.and_then(|value| sanitize_reference_text(value, MAX_DIRECT_CODEX_REFERENCE_TEXT_CHARS));
let width = sanitize_reference_dimension(reference.width);
let height = sanitize_reference_dimension(reference.height);
let mut related_resource_ids = Vec::new();
for resource_id in &reference.resource_ids {
let resource_id = validate_resource_reference_id(resource_id)?;
if !manifest.assets.iter().any(|asset| asset.id == resource_id) {
return Err("运行画面引用的素材已变化,请重新点选".to_string());
}
related_resource_ids.push(resource_id);
}
let mut parts = vec![format!("名称:{label}")];
if let Some(run_id) = run_id {
parts.push(format!("运行标识:{run_id}"));
}
if let Some(version_id) = version_id {
parts.push(format!("版本标识:{version_id}"));
}
if let Some(element_tag) = element_tag {
parts.push(format!("元素:{element_tag}"));
}
if let Some(element_role) = element_role {
parts.push(format!("角色:{element_role}"));
}
if let Some(text) = text {
parts.push(format!("文本摘要:{text}"));
}
if let (Some(width), Some(height)) = (width, height) {
parts.push(format!("尺寸:{width}x{height}"));
}
if !related_resource_ids.is_empty() {
parts.push(format!("关联素材 ID{}", related_resource_ids.join(",")));
}
Ok(format!("- 运行画面区域:{}", parts.join("")))
}
pub(crate) fn render_direct_codex_references_section(
root: &Path,
references: &[DirectCodexTurnReference],
) -> Result<Option<String>, String> {
if references.is_empty() {
return Ok(None);
}
if references.len() > MAX_DIRECT_CODEX_REFERENCES {
return Err(format!("一次最多引用 {MAX_DIRECT_CODEX_REFERENCES} 个素材"));
}
let manifest = read_manifest_for_project(root)?;
let mut lines = Vec::with_capacity(references.len());
for reference in references {
lines.push(match reference {
DirectCodexTurnReference::Resource(reference) => {
render_resource_reference_line(&manifest, reference)?
}
DirectCodexTurnReference::RuntimeRegion(reference) => {
render_runtime_region_reference_line(&manifest, reference)?
}
});
}
Ok(Some(
std::iter::once(DIRECT_CODEX_REFERENCE_HEADER.to_string())
.chain(lines)
.collect::<Vec<_>>()
.join("\n"),
))
}
#[cfg(test)]
mod tests {
use super::*;
fn fixture_project() -> tempfile::TempDir {
let directory = tempfile::tempdir().expect("temp project");
let root = directory.path();
std::fs::create_dir_all(root.join(".agent")).expect("create agent dir");
let mut manifest = new_game_creation_app_manifest("project-1", "测试项目");
manifest.assets.push(GameCreationAppAssetManifestEntry {
id: "asset-hero".to_string(),
kind: "character".to_string(),
media_type: "image/png".to_string(),
local_path: "assets/hero.png".to_string(),
source: GameCreationAppAssetSource {
kind: GameCreationAppAssetSourceKind::Uploaded,
canvas_project_id: None,
resource_id: None,
asset_object_id: None,
task_id: None,
prompt: None,
model: None,
generation_route: None,
generation_kind: None,
reference_resource_ids: Vec::new(),
},
image_sequence_frames: None,
image_sequence_duration_ms: None,
category: game_creation_app_asset_category_for_kind("character"),
tags: Vec::new(),
});
write_manifest(&root.join(".agent/manifest.json"), &manifest).expect("write manifest");
directory
}
#[test]
fn resource_reference_uses_manifest_identity_and_never_accepts_client_paths() {
let project = fixture_project();
let reference: DirectCodexTurnReference = serde_json::from_str(
r#"{"type":"resource","resourceId":"asset-hero","label":"主角","source":"asset-picker","localPath":"C:\\secret.png"}"#,
)
.expect("reference json");
let section = render_direct_codex_references_section(
project.path(),
std::slice::from_ref(&reference),
)
.expect("render")
.expect("section");
assert!(section.contains("素材 IDasset-hero"));
assert!(section.contains("名称:主角"));
assert!(section.contains("项目路径:assets/hero.png"));
assert!(!section.contains("C:\\secret.png"));
}
#[test]
fn deleted_resource_fails_closed() {
let project = fixture_project();
let reference: DirectCodexTurnReference = serde_json::from_str(
r#"{"type":"resource","resourceId":"asset-missing","label":"不存在"}"#,
)
.expect("reference json");
let error = render_direct_codex_references_section(
project.path(),
std::slice::from_ref(&reference),
)
.expect_err("missing resource");
assert_eq!(error, "引用的素材已不存在,请移除后重新选择");
}
#[test]
fn runtime_region_keeps_only_safe_summary_and_existing_resource_ids() {
let project = fixture_project();
let reference: DirectCodexTurnReference = serde_json::from_str(
r#"{"type":"runtime-region","label":"开始按钮","runId":"run-1","elementTag":"button","elementRole":"button","text":"开始游戏","width":120.4,"height":40.2,"resourceIds":["asset-hero"],"html":"<button onclick=secret>"}"#,
)
.expect("reference json");
let section = render_direct_codex_references_section(
project.path(),
std::slice::from_ref(&reference),
)
.expect("render")
.expect("section");
assert!(section.contains("运行画面区域:名称:开始按钮"));
assert!(section.contains("文本摘要:开始游戏"));
assert!(section.contains("尺寸:120x40"));
assert!(section.contains("关联素材 IDasset-hero"));
assert!(!section.contains("onclick"));
assert!(!section.contains("secret"));
}
}
@@ -4414,6 +4414,7 @@ pub(crate) async fn chat_with_game_creator_direct_codex(
creation_type: Option<String>,
client_turn_id: Option<String>,
attachments: Option<Vec<DirectCodexTurnAttachment>>,
references: Option<Vec<DirectCodexTurnReference>>,
) -> Result<String, String> {
let root = Path::new(project_path.trim());
let turn_id = normalize_direct_client_turn_id(client_turn_id.as_deref())?;
@@ -4428,16 +4429,33 @@ pub(crate) async fn chat_with_game_creator_direct_codex(
&prompt,
attachments.as_deref().unwrap_or_default(),
);
let user_prompt = match render_direct_codex_user_prompt(
&prompt,
attachments.as_deref().unwrap_or_default(),
) {
let attachments = attachments.unwrap_or_default();
let references = references.unwrap_or_default();
let mut user_prompt = match render_direct_codex_user_prompt(&prompt, &attachments) {
Ok(prompt) => prompt,
Err(_) if !references.is_empty() && prompt.trim().is_empty() => String::new(),
Err(error) => {
audit.finish(false);
return Err(error);
}
};
if let Some(reference_section) = match render_direct_codex_references_section(root, &references)
{
Ok(section) => section,
Err(error) => {
audit.finish(false);
return Err(error);
}
} {
if !user_prompt.trim().is_empty() {
user_prompt.push_str("\n\n");
}
user_prompt.push_str(&reference_section);
}
if user_prompt.trim().is_empty() {
audit.finish(false);
return Err("聊天内容不能为空".to_string());
}
let reply = match run_direct_game_creator_turn_at_with_creation_type_and_emitter(
root,
&user_prompt,
@@ -4710,10 +4728,10 @@ mod tests {
assert!(!prompt.contains("你是 Codex"));
assert!(prompt.contains("不要等待 Supervisor"));
assert!(prompt.contains("提示词与技能"));
assert!(prompt.contains("AGC 工程合同(仅说明项目边界,不是流程门槛)"));
assert!(prompt.contains("AGC 工程合同:当前 cwd 是用户选择的项目目录"));
assert!(prompt.contains("客户端扩展列表中用户已启用的第三方 MCP"));
assert!(prompt.contains("用户明确指定第三方 MCP Server 或工具时"));
assert!(prompt.contains("按需读取当前 cwd 下适用的 `AGENTS.md`"));
assert!(prompt.contains("先读取当前 cwd 下适用的 AGENTS.md、README 或项目说明"));
assert!(prompt.contains("agc_write_file"));
assert!(prompt.contains("content 必须是目标文件的完整原始 UTF-8 正文"));
assert!(prompt.contains("不得把 command.exec 的 Exit code、Wall time、Output 包装"));
@@ -1161,6 +1161,8 @@ fn bridge_registered_resource(
"localPath": asset.local_path,
"kind": asset.kind,
"mediaType": asset.media_type,
"category": asset.category,
"tags": asset.tags,
"canvasProjectId": asset.source.canvas_project_id,
"resourceId": asset.source.resource_id,
"assetObjectId": asset.source.asset_object_id,
@@ -2781,6 +2783,8 @@ mod tests {
local_path: "assets/art-spritesheet-slices/player.png".to_string(),
image_sequence_frames: None,
image_sequence_duration_ms: None,
category: game_creation_app_asset_category_for_kind("art-spritesheet-slice"),
tags: Vec::new(),
source: GameCreationAppAssetSource {
kind: GameCreationAppAssetSourceKind::Canvas,
canvas_project_id: Some("canvas-1".to_string()),
@@ -2844,6 +2848,8 @@ mod tests {
},
]),
image_sequence_duration_ms: Some(4_000),
category: game_creation_app_asset_category_for_kind("character-animation"),
tags: Vec::new(),
source: GameCreationAppAssetSource {
kind: GameCreationAppAssetSourceKind::Canvas,
canvas_project_id: Some("canvas-1".to_string()),
@@ -2863,6 +2869,8 @@ mod tests {
projection["imageSequenceFrames"][0]["objectKey"],
"animations/frame-01.png"
);
assert_eq!(projection["category"], "character");
assert_eq!(projection["tags"], json!([]));
let serialized = projection.to_string();
assert!(!serialized.contains("signed.invalid"));
assert!(!serialized.contains("private prompt"));
@@ -2870,6 +2878,42 @@ mod tests {
assert!(!serialized.contains("private.invalid"));
}
#[test]
fn bridge_registered_resource_projects_manifest_classification_verbatim() {
let asset = GameCreationAppAssetManifestEntry {
id: "spec-1".to_string(),
kind: "spec".to_string(),
media_type: "application/json".to_string(),
local_path: "assets/specs/hero.json".to_string(),
image_sequence_frames: None,
image_sequence_duration_ms: None,
category:
shared_contracts::game_creation_app::GameCreationAppAssetCategory::UiInteraction,
tags: vec!["主角".to_string(), "第一版".to_string()],
source: GameCreationAppAssetSource {
kind: GameCreationAppAssetSourceKind::Generated,
canvas_project_id: None,
resource_id: None,
asset_object_id: None,
task_id: None,
prompt: Some("private prompt".to_string()),
model: Some("private model".to_string()),
generation_route: None,
generation_kind: None,
reference_resource_ids: Vec::new(),
},
};
let projection = bridge_registered_resource(&asset, false);
assert_eq!(projection["kind"], "spec");
assert_eq!(projection["category"], "ui-interaction");
assert_eq!(projection["tags"], json!(["主角", "第一版"]));
assert_eq!(projection["imageSequenceFrames"], Value::Null);
let serialized = projection.to_string();
assert!(!serialized.contains("private prompt"));
assert!(!serialized.contains("private model"));
}
#[test]
fn bridge_success_warnings_are_redacted_before_serialization() {
let root = tempfile::tempdir().expect("warning redaction root");
@@ -256,7 +256,7 @@ fn direct_tools_mcp_specs_for(controlled_web_search: bool) -> Value {
}),
json!({
"name": "agc_list_registered_assets",
"description": "查询当前项目由客户端权威 manifest 登记的资源与未完成资源 operation。结果有界且只包含项目相对路径、稳定资源身份、序列帧身份和恢复状态,不返回 prompt、模型、签名 URL、宿主路径或凭据。",
"description": "查询当前项目由客户端权威 manifest 登记的资源与未完成资源 operation。结果有界且只包含项目相对路径、稳定资源身份、功能分类与自定义标签、序列帧身份和恢复状态,不返回 prompt、模型、签名 URL、宿主路径或凭据。",
"inputSchema": {
"type": "object",
"properties": {
@@ -27,8 +27,9 @@ pub(in crate::agent) use canvas_generation::{
commit_prepared_platform_art_asset_at, commit_prepared_platform_art_asset_strict_slices_at,
generate_platform_art_asset_with_retained_runtime_options_at,
generate_platform_art_asset_with_runtime_options_at,
platform_art_generation_error_result_unknown, register_existing_platform_art_slices_at,
request_platform_art_asset_with_runtime_options_at, restore_platform_art_asset_bytes_at,
platform_art_generation_error_result_unknown, recover_persisted_visual_generation_options,
register_existing_platform_art_slices_at, request_platform_art_asset_with_runtime_options_at,
restore_platform_art_asset_bytes_at,
retained_platform_art_generation_runtime_spritesheet_identity_at,
retained_platform_art_generation_runtime_state_matches_direct_stage_at,
validate_platform_art_png_bytes_with_limits,
@@ -429,6 +429,87 @@ impl Default for PlatformArtAssetGenerationOptions {
}
}
pub(in crate::agent) fn recover_persisted_visual_generation_options(
root: &Path,
pending: &AgentRuntimePendingToolAction,
prompt: &str,
requested: &PlatformArtAssetGenerationOptions,
) -> Result<Option<PlatformArtAssetGenerationOptions>, String> {
let context = platform_art_generation_runtime_context_from_pending(pending);
let Some(state) = read_platform_art_generation_runtime_state(root, &context)? else {
return Ok(None);
};
let (path, ratio, size, kind, label, generation_kind) = match context.agent_id.as_str() {
"art-director" => (
"assets/art-spec.png",
"1:1",
"1K",
"icon-spec",
"游戏统一视觉规范图",
"spec",
),
"design-foundation" => (
"assets/ui-prototype.png",
"16:9",
"2K",
"ui-prototype",
"游戏横屏界面原型图",
"ui-design",
),
"art-asset-plan" => (
"assets/art-spritesheet.png",
"1:1",
"1K",
"art-spritesheet",
"游戏首版核心美术素材",
"icon-spritesheet",
),
_ => return Ok(None),
};
let mut options = requested.clone();
let label = if context.agent_id == "design-foundation"
&& options
.output_path
.as_deref()
.is_some_and(design_foundation_ui_page_output_path_is_valid)
{
"游戏功能页面设计图"
} else {
label
};
if options.output_path.is_none() {
options.output_path = Some(path.to_string());
}
for (value, default) in [
(&mut options.aspect_ratio, ratio),
(&mut options.image_size, size),
(&mut options.asset_kind, kind),
(&mut options.asset_label, label),
] {
if value.is_empty() {
*value = default.to_string();
}
}
let snapshot = platform_art_generation_runtime_request_snapshot(&state)?;
// 只恢复经过身份校验的已有请求;显式改参和新请求仍走当前合同。
if snapshot.generation_kind == generation_kind
&& snapshot.generation_prompt == build_platform_art_asset_prompt(prompt, &[], &options)
&& options.asset_kind == kind
&& options.aspect_ratio == ratio
&& options.image_size == size
&& (options.output_path.as_deref() == Some(path)
|| (context.agent_id == "design-foundation"
&& options
.output_path
.as_deref()
.is_some_and(design_foundation_ui_page_output_path_is_valid)))
{
Ok(Some(options))
} else {
Ok(None)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
struct ExistingPlatformArtAssetFingerprint {
local_path: String,
@@ -6705,6 +6786,8 @@ fn register_platform_art_slice_manifest_entries_at(
local_path: registration.local_path.clone(),
image_sequence_frames: None,
image_sequence_duration_ms: None,
category: game_creation_app_asset_category_for_kind("art-spritesheet-slice"),
tags: Vec::new(),
source,
});
ids.push(id);
@@ -8895,13 +8978,13 @@ mod canvas_generation_tests {
}
#[test]
fn strict_spritesheet_contract_requires_the_grid_2x2_provider_receipt() {
fn strict_spritesheet_contract_accepts_variable_slices_without_fixed_layout() {
let canvas_context = ExternalCanvasGenerationContext {
project_id: "canvas-project".to_string(),
asset_folder_id: "asset-folder".to_string(),
canvas_name: "contract-test".to_string(),
};
let slices = (0..4)
let slices = (0..3)
.map(|index| {
let download = rgba_test_png(100 + index);
let validated = validate_platform_art_png_bytes_with_limits(
@@ -8927,7 +9010,7 @@ mod canvas_generation_tests {
}
})
.collect::<Vec<_>>();
let error = validate_strict_platform_art_spritesheet_contract(
validate_strict_platform_art_spritesheet_contract(
&slices,
&canvas_context,
Some("canvas-project"),
@@ -8941,9 +9024,7 @@ mod canvas_generation_tests {
true,
true,
)
.expect_err("missing fixed-layout proof must not be accepted");
assert!(error.contains("grid-2x2"));
.expect("valid slice identities and pixel evidence do not require a fixed layout");
}
#[test]
@@ -10168,7 +10249,8 @@ mod canvas_generation_tests {
}
#[tokio::test]
async fn accepted_runtime_generation_rejects_changed_intent_before_operation_get() {
async fn accepted_runtime_generation_checks_running_operation_before_rejecting_changed_intent()
{
let temporary = tempfile::tempdir().expect("create changed-intent recovery project");
let root = temporary.path();
init_local_game_project_at(root, "changed-intent", "旧异步生成意图隔离")
@@ -10188,6 +10270,31 @@ mod canvas_generation_tests {
.set_nonblocking(true)
.expect("set changed intent fixture nonblocking");
let base_url = format!("http://{}", listener.local_addr().expect("fixture address"));
let (request_sender, request_receiver) = std::sync::mpsc::channel();
let (stop_sender, stop_receiver) = std::sync::mpsc::channel();
let server = std::thread::spawn(move || loop {
if stop_receiver.try_recv().is_ok() {
break;
}
let (mut stream, _) = match listener.accept() {
Ok(connection) => connection,
Err(error) if error.kind() == std::io::ErrorKind::WouldBlock => {
std::thread::sleep(Duration::from_millis(5));
continue;
}
Err(error) => panic!("accept changed intent request: {error}"),
};
request_sender
.send(read_test_http_request(&mut stream))
.expect("capture status request");
write_test_json_response(
&mut stream,
"200 OK",
&serde_json::json!({
"data": {"operationId": "old-operation", "status": "running"}
}),
);
});
let runtime_context = PlatformArtGenerationRuntimeContext {
agent_id: "art-director".to_string(),
task_id: "art-director".to_string(),
@@ -10253,10 +10360,15 @@ mod canvas_generation_tests {
error.contains("当前生成意图与已持久化请求快照不一致"),
"{error}"
);
assert!(matches!(
listener.accept(),
Err(error) if error.kind() == std::io::ErrorKind::WouldBlock
));
stop_sender.send(()).expect("stop changed intent fixture");
server.join().expect("join changed intent fixture");
let requests = request_receiver.try_iter().collect::<Vec<_>>();
assert_eq!(
requests.len(),
1,
"only the existing operation may be queried"
);
assert!(requests[0].starts_with("GET /api/external/v1/generations/old-operation "));
assert!(game_creator_agent_runtime_external_generation_exists(
root,
&runtime_context.agent_id,
@@ -10265,7 +10377,8 @@ mod canvas_generation_tests {
}
#[tokio::test]
async fn accepted_derived_generation_rejects_changed_art_spec_before_operation_get() {
async fn accepted_derived_generation_checks_running_operation_before_rejecting_changed_art_spec(
) {
let temporary = tempfile::tempdir().expect("create changed reference project");
let root = temporary.path();
init_local_game_project_at(root, "changed-reference", "规范图身份隔离")
@@ -10310,6 +10423,31 @@ mod canvas_generation_tests {
.set_nonblocking(true)
.expect("set changed reference fixture nonblocking");
let base_url = format!("http://{}", listener.local_addr().expect("fixture address"));
let (request_sender, request_receiver) = std::sync::mpsc::channel();
let (stop_sender, stop_receiver) = std::sync::mpsc::channel();
let server = std::thread::spawn(move || loop {
if stop_receiver.try_recv().is_ok() {
break;
}
let (mut stream, _) = match listener.accept() {
Ok(connection) => connection,
Err(error) if error.kind() == std::io::ErrorKind::WouldBlock => {
std::thread::sleep(Duration::from_millis(5));
continue;
}
Err(error) => panic!("accept changed reference request: {error}"),
};
request_sender
.send(read_test_http_request(&mut stream))
.expect("capture status request");
write_test_json_response(
&mut stream,
"200 OK",
&serde_json::json!({
"data": {"operationId": "old-background-operation", "status": "running"}
}),
);
});
let runtime_context = PlatformArtGenerationRuntimeContext {
agent_id: "direct-codex-art".to_string(),
task_id: "direct-codex-art-game-background".to_string(),
@@ -10412,10 +10550,19 @@ mod canvas_generation_tests {
"{error}"
);
assert!(error.contains("当前规范图身份"), "{error}");
assert!(matches!(
listener.accept(),
Err(error) if error.kind() == std::io::ErrorKind::WouldBlock
));
stop_sender
.send(())
.expect("stop changed reference fixture");
server.join().expect("join changed reference fixture");
let requests = request_receiver.try_iter().collect::<Vec<_>>();
assert_eq!(
requests.len(),
1,
"only the existing operation may be queried"
);
assert!(
requests[0].starts_with("GET /api/external/v1/generations/old-background-operation ")
);
assert!(game_creator_agent_runtime_external_generation_exists(
root,
&runtime_context.agent_id,
@@ -10809,6 +10956,38 @@ mod canvas_generation_tests {
mark_platform_art_generation_runtime_accepted(root, state, "test-operation-id", 0)
.expect("accept current generation fixture");
let sparse_options = PlatformArtAssetGenerationOptions {
output_path: Some(AGENT_RUNTIME_ART_SPEC_PATH.to_string()),
aspect_ratio: String::new(),
image_size: String::new(),
asset_kind: String::new(),
asset_label: String::new(),
..Default::default()
};
assert_eq!(
recover_persisted_visual_generation_options(root, &pending, prompt, &sparse_options)
.expect("recover persisted defaults"),
Some(options.clone())
);
assert!(recover_persisted_visual_generation_options(
root,
&pending,
"显式改变生成意图",
&sparse_options,
)
.unwrap()
.is_none());
let mut explicit_options = sparse_options.clone();
explicit_options.asset_kind = "game-background".to_string();
assert!(recover_persisted_visual_generation_options(
root,
&pending,
prompt,
&explicit_options,
)
.unwrap()
.is_none());
resume_game_creator_agent_background_tasks_at(root)
.expect("resume accepted generation through recovery scan");
let first = request_receiver
@@ -11063,19 +11242,18 @@ mod canvas_generation_tests {
}
#[test]
fn canonical_art_spritesheet_request_has_exactly_four_ordered_categories() {
fn canonical_art_spritesheet_request_preserves_project_requirements() {
let descriptions = canonical_art_spritesheet_icon_descriptions("原创收集玩法");
assert_eq!(descriptions.len(), 4);
for (index, description) in descriptions.iter().enumerate() {
assert!(description.starts_with(&format!("{}", index + 1)));
}
assert_eq!(descriptions.len(), 1);
assert!(descriptions[0].contains("原创收集玩法"));
assert!(descriptions[0].contains("数量、类别、排列和切片方式由本次需求决定"));
}
#[test]
fn canonical_art_spritesheet_descriptions_obey_external_editor_item_limit() {
let descriptions = canonical_art_spritesheet_icon_descriptions(&"原创玩法需求".repeat(128));
assert_eq!(descriptions.len(), 4);
assert!(!descriptions.is_empty());
assert!(descriptions
.iter()
.all(|description| description.chars().count() <= 200));
@@ -11466,7 +11644,7 @@ mod canvas_generation_tests {
}
#[test]
fn strict_slice_commit_rejects_non_four_slice_results_before_replacing_the_sheet() {
fn strict_slice_commit_rejects_empty_slice_results_before_replacing_the_sheet() {
let temporary = tempfile::tempdir().expect("create strict slice project");
let root = temporary.path();
init_local_game_project_at(root, "strict-slices", "严格切片测试")
@@ -11481,9 +11659,9 @@ mod canvas_generation_tests {
&replacement_options(),
|_| Ok(()),
)
.expect_err("strict spritesheet commit must require exactly four slices");
.expect_err("strict spritesheet commit must require at least one slice");
assert!(error.contains("恰好包含 4 个独立切片"));
assert!(error.contains("至少需要一个独立切片"));
assert_eq!(fs::read(path).expect("read preserved sheet"), b"old-image");
assert!(!root
.join("assets/art-spritesheet-slices/manifest.json")
@@ -337,9 +337,6 @@ pub(crate) fn agent_role_memory_relative_path_for_task(task_id: &str) -> Result<
if task_id == GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID {
return Ok(GAME_CREATOR_PROJECT_SUPERVISOR_MEMORY_PATH.to_string());
}
if task_id == GAME_CREATOR_PROJECT_PLANNING_AGENT_ID {
return Ok(GAME_CREATOR_PROJECT_PLANNING_MEMORY_PATH.to_string());
}
for group in GAME_CREATOR_AGENT_GROUP_DEFINITIONS {
for role in group.roles {
if role.task_id == task_id {
File diff suppressed because it is too large Load Diff
@@ -44,7 +44,6 @@ pub(in crate::agent) use run_status_observation::*;
pub(in crate::agent) use structured_plan::*;
pub(in crate::agent) use tool_plan_protocol::*;
pub(crate) use crate::agent::runtime_protocol::plan_gdd_completion_blocker_at_locked;
#[cfg(test)]
pub(crate) use action_audit::agent_runtime_action_receipt_public_safe_detail_for_test;
#[cfg(test)]
@@ -74,7 +73,6 @@ pub(crate) use context_compaction::compact_game_creator_agent_runtime_session_at
pub(crate) use parallel_ledger::{
agent_runtime_confirmation_path_component, agent_runtime_parallel_read_batch_len,
agent_runtime_tool_allowed_for_agent, agent_runtime_tool_is_parallel_safe_read,
agent_runtime_tool_rejected_by_agent_identity,
game_creator_agent_runtime_parallel_read_batch_path,
game_creator_agent_runtime_pending_tool_action_path,
game_creator_agent_runtime_provider_action_batch_path,
@@ -106,6 +104,7 @@ pub(crate) use project_gates::{
prepare_agent_runtime_project_mutation_locked, process_session_completion_blocker_at,
project_verification_completion_blocker, project_verification_completion_blocker_at,
static_delegate_completion_blocker_at, structured_plan_completion_blocker,
try_acquire_game_creator_agent_runtime_project_write_lock,
validate_agent_runtime_pending_verification_gate_before,
};
#[cfg(test)]
@@ -116,7 +115,6 @@ pub(crate) use project_gates::{
};
pub(crate) use provider_action_batch::{
prepare_game_creator_agent_runtime_provider_action_batch,
prepare_game_creator_agent_runtime_provider_action_batch_with_planning_binding,
update_game_creator_agent_runtime_provider_batch_member, AgentRuntimePendingToolAction,
AgentRuntimeProviderActionBatch,
};
@@ -147,9 +145,6 @@ pub(crate) use tool_plan_protocol::parse_game_creator_agent_tool_plan_response;
pub(crate) use tool_policy_snapshot::{
agent_runtime_acceptance_evidence_tools,
agent_runtime_autonomous_design_foundation_command_is_allowed, agent_runtime_executable_tools,
agent_runtime_native_executable_tools, agent_runtime_plan_root_supervisor_tools,
agent_runtime_plan_root_supervisor_tools_for_stage,
agent_runtime_tool_policy_snapshot_for_run_at, plan_root_supervisor_stage_at,
plan_root_supervisor_stage_at_locked, PlanRootSupervisorStage,
AGENT_RUNTIME_CANVAS_ASSET_KINDS, AGENT_RUNTIME_PROJECT_PLANNING_ACTION_TOOLS,
agent_runtime_native_executable_tools, agent_runtime_tool_policy_snapshot_for_run_at,
AGENT_RUNTIME_CANVAS_ASSET_KINDS,
};

Some files were not shown because too many files have changed in this diff Show More