Compare commits

..

40 Commits

Author SHA1 Message Date
menghao 6dca066a87 Merge branch 'master' into codex/game-agent-run
Project CI / Repository checks (pull_request) Successful in 1m6s
Project CI / Frontend tests (pull_request) Successful in 3m15s
Project CI / Backend tests (pull_request) Successful in 4m11s
Project CI / Native shell tests (pull_request) Successful in 16m43s
2026-08-07 19:53:30 +08:00
menghao 02ae3d3cad 合并最新主线并解决共享记忆冲突
Project CI / Backend tests (pull_request) Failing after 8s
Project CI / Repository checks (pull_request) Failing after 7s
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Successful in 3m0s
同步 origin/master 的 CI 与生成结果原子提交改动

保留双方 pitfalls 排障记录
2026-08-07 19:50:14 +08:00
menghao d04339ad7f 修复自主完成合同测试竞态
Project CI / Repository checks (pull_request) Successful in 1m23s
Project CI / Native shell tests (pull_request) Successful in 12m15s
Project CI / Frontend tests (pull_request) Successful in 3m45s
Project CI / Backend tests (pull_request) Successful in 4m12s
隔离 readiness 状态投影,避免测试重复调度 preview-playtest child
补充 Runtime 终态投影测试的竞态避坑说明
2026-08-07 13:46:16 +08:00
menghao 6fe52a92bb 修复合并后的仓库检查失败
Project CI / Repository checks (pull_request) Successful in 1m8s
Project CI / Frontend tests (pull_request) Successful in 3m2s
Project CI / Backend tests (pull_request) Successful in 3m42s
Project CI / Native shell tests (pull_request) Failing after 9m28s
删除重复维护页原子替换函数并恢复符号链接防护
清理开发栈未使用变量与资源投影正则告警
统一相关测试导入与开发栈导出顺序
2026-08-07 12:44:29 +08:00
menghao 75e3f1d041 合并远端主线并解决游戏创作冲突
Project CI / Repository checks (pull_request) Failing after 33s
Project CI / Backend tests (pull_request) Successful in 3m45s
Project CI / Native shell tests (pull_request) Successful in 13m59s
Project CI / Frontend tests (pull_request) Successful in 3m1s
融合主线 Runtime、安全修复与提示词资源
保留可运行版本、资源替换和数值微调能力
修复资源画布布局、焦点与依赖关系融合
补齐 manifest 并发保护与 Tetris 场景兼容
同步前端测试、Rust 回归测试与权威文档
2026-08-07 12:15:12 +08:00
menghao 8a4b71bf8e 修复资源画布溢出与模板资源投影
Project CI / Backend tests (pull_request) Failing after 10s
Project CI / Repository checks (pull_request) Failing after 10s
Project CI / Frontend tests (pull_request) Successful in 3m3s
Project CI / Native shell tests (pull_request) Successful in 10m23s
闭合资源画布横向收缩链并增加内部滚动条

过滤任务产物通配模板避免生成无效资源卡

补充布局与资源投影回归测试

同步工作台文档与共享排障记忆
2026-08-07 10:53:41 +08:00
menghao 53e37ea361 支持App Run并行开发并修复资源画布
Project CI / Repository checks (pull_request) Failing after 10s
Project CI / Backend tests (pull_request) Failing after 10s
Project CI / Frontend tests (pull_request) Successful in 3m28s
Project CI / Native shell tests (pull_request) Failing after 9m27s
新增App Run独立开发profile并隔离端口、数据库、Tauri身份与AppData
常驻资源排列切换并避免布局状态挤压操作入口
显式扩展资源画布横向滚动范围并收敛依赖图可见连线
补充启动器、资源布局、界面回归测试与项目文档
2026-08-07 10:13:25 +08:00
menghao dc7c7dc6cc 完成P7验收修复与CI稳定性加固
Project CI / Repository checks (pull_request) Failing after 10s
Project CI / Backend tests (pull_request) Failing after 11s
Project CI / Frontend tests (pull_request) Failing after 18s
Project CI / Native shell tests (pull_request) Failing after 7m24s
修正工作台前端 lint 与正式测试切片初始化依赖
补齐可调参数完成态测试夹具并稳定生成恢复 HTTP 读取
修正 macOS Mach-O release 产物识别与原生壳门禁
同步阶段七验收文档与长期排障记忆
2026-08-05 10:00:09 +08:00
menghao 3ec3a09380 完成游戏工作台资源替换与数值微调
实现可信资源规格校验与不可变资源替换子版本
新增登记参数读取、暂停态写入和会话失效链路
补齐前后端合同、生成产物约束与回归测试
同步工作台 PRD、技术方案和共享决策记录
2026-08-04 18:38:41 +08:00
menghao 752505547d 实现运行工作台正式测试切片与悬停信息
Project CI / Frontend tests (pull_request) Failing after 23s
Project CI / Repository checks (pull_request) Failing after 1m4s
Project CI / Backend tests (pull_request) Successful in 4m59s
Project CI / Native shell tests (pull_request) Successful in 13m17s
新增正式测试切片合同、状态机与上一项/播放暂停/下一项控制
新增可信悬停资源解析与资源管理画布定位
扩展 Tauri 版本登记、运行 Bridge 和 Agent 生成合同
补齐 TypeScript、Rust 与 AppSurface 回归测试
同步工作台 PRD、技术方案与共享决策记录
保持 P2 资源替换、参数写入和下一版本能力未接线
2026-08-04 17:06:24 +08:00
menghao 203ce5b9e7 实现游戏运行会话与桥接协议
Project CI / Backend tests (pull_request) Successful in 3m49s
Project CI / Native shell tests (pull_request) Failing after 8m26s
Project CI / Frontend tests (pull_request) Failing after 25s
Project CI / Repository checks (pull_request) Failing after 54s
冻结 Host/Game Bridge 双向消息合同与安全校验
为 PreviewRegistry 增加预览身份并注入宿主 bootstrap
接入 iframe 一次性会话、状态投影和暂停继续控制
补充协议、AppSurface、Rust 生命周期与共享合同测试
同步更新工作台 PRD、技术方案和共享决策记录
2026-08-04 16:16:04 +08:00
menghao d1a47c0fe7 实现游戏工作台P0/P1可运行版本能力
Project CI / Frontend tests (pull_request) Successful in 3m43s
Project CI / Repository checks (pull_request) Failing after 1m27s
Project CI / Backend tests (pull_request) Successful in 4m26s
Project CI / Native shell tests (pull_request) Failing after 7m40s
冻结工作台运行版本、会话、参数、资源与消息跨端合同
新增可信 Runtime 验证后的不可变可运行版本登记与快照校验
支持 Tauri 客户端选择、切换并运行历史版本快照
收紧普通预览授权边界并同步资源高亮与明确错误展示
补充前后端测试、PRD、技术方案和项目决策记录
2026-08-04 15:21:55 +08:00
menghao 2e0ef02fda 修复CI依赖准备顺序
Project CI / Repository checks (pull_request) Successful in 1m1s
Project CI / Frontend tests (pull_request) Successful in 3m7s
Project CI / Backend tests (pull_request) Successful in 3m48s
Project CI / Native shell tests (pull_request) Successful in 10m55s
修复依赖图测试import排序门禁

在后端构建门禁前预取锁定Cargo依赖

新增Backend-CI执行顺序回归测试

同步更新Gitea-CI开发运维约定
2026-08-04 12:00:04 +08:00
menghao ab16c87c5d 调整资源依赖视图连线规则
Project CI / Repository checks (pull_request) Failing after 48s
Project CI / Backend tests (pull_request) Failing after 29s
Project CI / Frontend tests (pull_request) Successful in 2m54s
Project CI / Native shell tests (pull_request) Successful in 10m33s
限制任务虚线仅在同资源类型分区内渲染
调亮精确资源引用橙线并保持持续展示
取消资源点击后的依赖高亮与无关连线弱化
补充依赖图单元测试与工作台集成测试
同步更新项目开发工作台 PRD
2026-08-04 11:20:58 +08:00
menghao 6d8c7ae496 修复前端异步读取卸载竞态
Project CI / Repository checks (pull_request) Successful in 55s
Project CI / Backend tests (pull_request) Successful in 4m13s
Project CI / Native shell tests (pull_request) Successful in 11m16s
Project CI / Frontend tests (pull_request) Successful in 2m29s
未登录画布不再触发受保护的钱包刷新
充值中心读取在换代与卸载时中止并失效
补充卸载取消回归测试与共享排障经验
2026-08-04 10:38:28 +08:00
menghao 80bcb4ba0e 完成资源管理阶段七验收
Project CI / Repository checks (pull_request) Successful in 1m1s
Project CI / Backend tests (pull_request) Successful in 3m43s
Project CI / Native shell tests (pull_request) Successful in 10m47s
Project CI / Frontend tests (pull_request) Failing after 1m41s
补齐视频与资源读取失败空态测试
完成全量前后端和 Rust CI 验收
修复 macOS/BSD 跨平台门禁与测试隔离
更新阶段七验收与共享项目记忆
2026-08-04 10:06:12 +08:00
menghao df4e61a208 合并最新主分支
同步 Runtime、外部 API 与统一弹窗等主分支更新

解决开发栈进程探活、Runtime continuation 与启动测试冲突

保留资源依赖图排障记忆并验证冲突路径
2026-08-03 19:21:12 +08:00
menghao fef53b634e 完成正式项目版本阶段六
Project CI / Repository checks (pull_request) Failing after 43s
Project CI / Frontend tests (pull_request) Failing after 2m6s
Project CI / Backend tests (pull_request) Successful in 3m37s
Project CI / Native shell tests (pull_request) Successful in 11m49s
扩展 manifest 正式迭代版本合同并强制历史记录追加不可变
接入版本卡、父子关系与绑定资源高亮
补齐共享契约、Tauri 存储与工作台回归测试
同步工作台 PRD、技术方案与项目记忆
2026-08-03 19:04:01 +08:00
menghao ba996aad80 完成资源依赖排列阶段五
Project CI / Frontend tests (pull_request) Failing after 21s
Project CI / Repository checks (pull_request) Successful in 1m1s
Project CI / Backend tests (pull_request) Successful in 3m54s
Project CI / Native shell tests (pull_request) Successful in 11m49s
计算资源引用 SCC 与任务深度下限
拆分 producer assignment 和布局深度合同
补齐依赖布局回归测试
同步工作台 PRD、技术方案与项目记忆
2026-08-03 17:51:59 +08:00
menghao d374f3292a 完成资源聚焦阶段四
Project CI / Repository checks (pull_request) Successful in 1m16s
Project CI / Frontend tests (pull_request) Failing after 2m37s
Project CI / Backend tests (pull_request) Successful in 3m29s
Project CI / Native shell tests (pull_request) Successful in 11m21s
新增项目文档与媒体资源安全读取命令
补齐Markdown、扩展美术媒体与音频聚焦展示
增加安全边界与工作台回归测试
同步PRD、技术方案与共享决策
2026-08-03 17:20:14 +08:00
menghao 2c65878b60 完成资源分类投影与中央聚焦
Project CI / Repository checks (pull_request) Failing after 49s
Project CI / Frontend tests (pull_request) Failing after 2m16s
Project CI / Backend tests (pull_request) Successful in 3m55s
Project CI / Native shell tests (pull_request) Successful in 11m17s
收口文档、版本、美术和音频资源分类
将资源详情改为中央主视窗聚焦并恢复列表上下文
补充资源身份、聚焦状态和依赖图回归测试
同步工作台PRD、技术方案和项目决策记录
2026-08-03 16:39:43 +08:00
menghao 7339bb5da1 暂缓资源卡拖动并统一资源管理合同
Project CI / Repository checks (pull_request) Failing after 10s
Project CI / Backend tests (pull_request) Failing after 10s
Project CI / Frontend tests (pull_request) Failing after 2m18s
Project CI / Native shell tests (pull_request) Successful in 11m37s
移除资源卡 Pointer 拖动入口并保留点击详情
改写交互与布局测试,覆盖指针不改坐标且不提交 CAS
同步工作台 PRD、技术方案和项目决策记录
2026-08-03 15:35:54 +08:00
menghao c24e3010f8 Merge remote-tracking branch 'origin/master' into codex/dependency-graph
Project CI / Repository checks (pull_request) Successful in 1m3s
Project CI / Frontend tests (pull_request) Failing after 2m7s
Project CI / Backend tests (pull_request) Successful in 3m25s
Project CI / Native shell tests (pull_request) Successful in 10m41s
2026-08-03 14:24:46 +08:00
menghao d8064eff49 同步自主首批任务图测试夹具
Project CI / Repository checks (pull_request) Failing after 11s
Project CI / Backend tests (pull_request) Failing after 11s
Project CI / Frontend tests (pull_request) Successful in 2m46s
Project CI / Native shell tests (pull_request) Successful in 10m54s
补齐可信 Supervisor 根任务与三 Leader 首批合同测试数据

更新视觉失败和 Ready 任务调度断言以匹配最新任务图

修复 Provider 重试恢复响应仍使用旧底层 Agent 合同的问题
2026-08-03 14:21:30 +08:00
menghao bda0d0d398 修复Linux进程树探活与Runtime默认栈溢出
Project CI / Repository checks (pull_request) Successful in 1m7s
Project CI / Frontend tests (pull_request) Successful in 3m0s
Project CI / Backend tests (pull_request) Successful in 3m32s
Project CI / Native shell tests (pull_request) Failing after 7m38s
Linux进程组探活忽略已终止但未回收的zombie成员

pending恢复与后续队列统一跨越装箱的Tokio任务边界

补充Tauri生命周期和Runtime默认栈回归测试与文档
2026-08-03 13:34:25 +08:00
menghao b95da30721 Merge branch 'master' into codex/dependency-graph
Project CI / Repository checks (pull_request) Successful in 54s
Project CI / Frontend tests (pull_request) Failing after 2m9s
Project CI / Backend tests (pull_request) Successful in 3m46s
Project CI / Native shell tests (pull_request) Failing after 3m4s
2026-08-03 11:55:21 +08:00
menghao 7f038490f1 修复运行时恢复路径栈溢出
Project CI / Repository checks (pull_request) Failing after 7s
Project CI / Backend tests (pull_request) Failing after 10s
Project CI / Frontend tests (pull_request) Successful in 2m51s
Project CI / Native shell tests (pull_request) Failing after 7m51s
为 pending continuation 与后台主循环增加可取消的 Tokio 任务边界
保留 durable batch 恢复防重和父任务取消语义
补充运行时技术约束与共享排障记录
2026-08-03 11:53:59 +08:00
menghao 05c608214e 修复资源依赖图布局初始化与拖动性能
Project CI / Repository checks (pull_request) Successful in 1m0s
Project CI / Frontend tests (pull_request) Successful in 3m4s
Project CI / Backend tests (pull_request) Successful in 3m29s
Project CI / Native shell tests (pull_request) Failing after 5m34s
将任务依赖深度迁移到 Rust SCC 压缩图并返回稳定结果

为 dependency 模式增加图加载屏障并重派生自动布局

通过 DOM 热路径和局部 SVG 缓存避免拖动全量 React 重渲染

补充 4096 资源、异步初始化及布局保持回归测试

同步工作台 PRD、技术方案与共享项目记忆
2026-08-03 11:16:02 +08:00
menghao 37f4a63112 合并最新master并解决工作台冲突
Project CI / Repository checks (pull_request) Successful in 1m17s
Project CI / Frontend tests (pull_request) Successful in 3m13s
Project CI / Backend tests (pull_request) Successful in 3m45s
Project CI / Native shell tests (pull_request) Failing after 5m39s
合并 master 最新游戏聊天前端 Lint 修复。

保留依赖图分支的 Hook 稳定化逻辑并统一注释格式。
2026-08-03 10:35:49 +08:00
menghao fe34eee052 合并最新master并解决工作台冲突
合并game-chat五分钟首版与平台美术硬门更新
解决App响应流Hook的等价内容冲突
稳定新增消息追加回调并整理合并后的导入顺序
通过前端完整测试、Lint、类型检查和Rust tests编译
2026-08-03 10:30:48 +08:00
menghao cced839da5 修复合并后的前端Lint门禁
Project CI / Repository checks (pull_request) Successful in 55s
Project CI / Frontend tests (pull_request) Successful in 2m55s
Project CI / Native shell tests (pull_request) Successful in 11m45s
Project CI / Backend tests (pull_request) Successful in 3m54s
稳定项目总控响应流更新回调并补齐Effect依赖
调整Agent Runtime模型测试的导入顺序
通过完整ESLint、类型检查和AppSurface回归测试
2026-07-31 19:31:24 +08:00
menghao e77d187497 合并最新master并解决资源图冲突
Project CI / Repository checks (pull_request) Failing after 47s
Project CI / Frontend tests (pull_request) Successful in 3m21s
Project CI / Backend tests (pull_request) Successful in 3m49s
Project CI / Native shell tests (pull_request) Successful in 11m42s
合并远端master的运行收口、聊天输出与画布交互更新
保留资源依赖图只读命令注册和Rust测试隔离配置
解决main.rs入口附近的内容冲突
完成依赖图、退出保护及完整前端回归验证
2026-07-31 19:19:05 +08:00
menghao 29dce59b35 将资源依赖图计算迁移到Rust
Project CI / Repository checks (pull_request) Failing after 10s
Project CI / Backend tests (pull_request) Failing after 7s
Project CI / Frontend tests (pull_request) Successful in 2m50s
Project CI / Native shell tests (pull_request) Successful in 11m33s
新增只读资源依赖图模型,完成过滤、去重、环检测和任务流聚合
前端SVG仅负责几何渲染、搜索联动、选中高亮和拖动预览
补充资源图、覆盖层和工作台回归测试
同步更新工作台PRD、技术方案与项目记忆
2026-07-31 19:09:57 +08:00
menghao f582ecf032 优化资源依赖图连线外观
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 3m5s
Project CI / Native shell tests (pull_request) Successful in 10m26s
将任务流分支改为平滑贝塞尔曲线
调整两类连线的线宽、虚线和箭头样式
补充连线几何测试与相关文档
2026-07-31 18:36:21 +08:00
menghao a431ab47cd 合并最新 master 并解决依赖图文档冲突
Project CI / Frontend tests (pull_request) Successful in 3m7s
Project CI / Backend tests (pull_request) Successful in 3m37s
Project CI / Native shell tests (pull_request) Successful in 12m20s
Project CI / Repository checks (pull_request) Successful in 59s
保留资源依赖图的纯前端 SVG 决策记录
保留主分支新增的抠图元数据与 OpenAPI 决策记录
同步 master 最新代码并保持依赖图与后端测试修复
2026-07-31 16:50:59 +08:00
menghao 11140b9fb6 修复后端源码约束测试
Project CI / Native shell tests (pull_request) Successful in 11m56s
Project CI / Repository checks (pull_request) Successful in 59s
Project CI / Frontend tests (pull_request) Successful in 3m35s
Project CI / Backend tests (pull_request) Successful in 3m49s
同步 UI 素材切片持久化失败断言至现有 warning 映射函数
保持生产逻辑和持久层不变
2026-07-31 16:31:09 +08:00
menghao 246fd1d9d6 同步最新 master 修复 CI 基线
Project CI / Repository checks (pull_request) Successful in 1m15s
Project CI / Frontend tests (pull_request) Successful in 3m23s
Project CI / Native shell tests (pull_request) Successful in 11m51s
Project CI / Backend tests (pull_request) Failing after 2m47s
合并画板素材上传后的私有图片预览修复。

确保 PR 分支包含当前 master 基础提交。
2026-07-31 16:05:05 +08:00
menghao 68ea9bdff3 合并最新 master 并解决资源依赖图冲突
Project CI / Repository checks (pull_request) Failing after 7s
Project CI / Frontend tests (pull_request) Failing after 2m19s
Project CI / Backend tests (pull_request) Failing after 8s
Project CI / Native shell tests (pull_request) Successful in 12m34s
同步 mentor 已合入 master 的最新工程改动。

保留资源依赖图决策记录并整合 master 的新增决策。

# Conflicts:
#	docs/project-memory/shared-memory/decision-log.md
2026-07-31 15:54:40 +08:00
menghao 0b6be155dd 合并最新 AI 游戏创作 App 分支
Project CI / Frontend tests (pull_request) Successful in 3m18s
Project CI / Repository checks (pull_request) Failing after 22s
Project CI / Backend tests (pull_request) Failing after 22s
Project CI / Native shell tests (pull_request) Failing after 22s
2026-07-31 15:34:36 +08:00
menghao 8531a9af3e 实现资源依赖关系图层
Project CI / Frontend tests (pull_request) Successful in 2m40s
Project CI / Native shell tests (pull_request) Successful in 10m40s
Project CI / Repository checks (pull_request) Failing after 7s
Project CI / Backend tests (pull_request) Failing after 7s
新增资源依赖图模型、去重、环检测与上下游查询。

使用原生 SVG 渲染资源引用和聚合任务流,并接入搜索、高亮、拖动及生命周期清理。

修复资源自引用连线被卡片遮挡的问题。

补充前端回归测试并同步工作台 PRD、技术方案和决策记录。
2026-07-31 15:13:18 +08:00
352 changed files with 17059 additions and 43441 deletions
@@ -17,7 +17,7 @@ Prefer `scripts/genarrative_external_api.py` for runnable REST calls. It uses on
2. Before the first generation in a new conversation, obtain a canvas name unless an existing `projectId` and `assetFolderId` were supplied. Create or reuse a project and a same-name asset-library folder. Retain `canvasName`, `projectId`, `assetFolderId`, and the current art spec.
3. Normalize art requests into a reusable spec. Ask only for missing values that block the selected operation. Reuse the spec until the user changes its style, subject family, palette, format, or constraints.
4. Infer the operation from the user's intent. Do not ask the user to select an API unless two operations would produce materially different artifacts.
5. If a reference exists only as a local file, upload and confirm it first. Pass the stable returned `objectKey` to operations that accept object references; never substitute a temporary signed URL. For an icon-spritesheet primary spec, additionally create a project resource or asset record with `assetKind="icon-spec"`, then pass the returned resource or asset ID as `referenceId`.
5. If a reference exists only as a local file, upload and confirm it first. Pass the stable returned `objectKey` to generation; never substitute a temporary signed URL.
6. For generation endpoints that support the fields, include `projectId`, `assetFolderId`, an asset label, and `canvasCompletion` so the result enters both the canvas and its same-name library folder.
7. Treat every generation POST as asynchronous. Send one stable `Idempotency-Key` per logical request, retain the returned `operationId`, and poll the returned `statusUrl` or `GET /api/external/v1/generations/{operationId}` according to `pollAfterMs`.
8. Consume `result` only after `status=completed`. On `failed`, surface the safe error. On a client timeout or lost response, retain the operation/key; do not create a replacement request.
@@ -51,11 +51,11 @@ Every generation row requires a stable `Idempotency-Key` header and returns HTTP
| --- | --- | --- | --- |
| Image generation | `/api/external/v1/editor/images/generations` | `prompt` | `kind`, `style`, `model`, `aspectRatio`, `imageSize`, `size`, `referenceImageSrcs`, `projectId`, `assetFolderId`, `assetLabel`, `canvasCompletion`, `generationInputs` |
| Image edit/redraw | `/api/external/v1/editor/images/edits` | `prompt`, `sourceImageSrc` | `referenceImageSrcs`, `model`, `size`, `projectId`, `assetFolderId`, `assetLabel`, `sourceResourceId`, `targetLayerId`, `canvasCompletion` |
| Icon spritesheet | `/api/external/v1/editor/icon-spritesheets/generations` | `referenceId`, `iconDescriptions` | `style`, `referenceImageSrcs`, `screenColor`, `model`, `aspectRatio`, `imageSize`, `projectId`, `assetFolderId`, `assetLabel`, `canvasCompletion` |
| Icon spritesheet | `/api/external/v1/editor/icon-spritesheets/generations` | `referenceImageSrc`, `iconDescriptions` | `style`, `referenceImageSrcs`, `screenColor`, `model`, `aspectRatio`, `imageSize`, `projectId`, `assetFolderId`, `assetLabel`, `canvasCompletion` |
| UI asset extraction | `/api/external/v1/editor/ui-designs/assets/extractions` | `sourceImageSrc`, `aspectRatio`, `imageSize` | `screenColor`, `model`, `referenceImageSrcs`, `projectId`, `assetFolderId`, `spritesheetLabel`, `canvasCompletion` |
| Character animation | `/api/external/v1/editor/character-animations/generations` | `sourceLayerId`, `sourceImageSrc`, `sourceWidth`, `sourceHeight`, `promptText`, `resolution`, `ratio`, `frameCount`, `durationSeconds`, `model` | `projectId`, `sourceResourceId`, `assetFolderId`, `assetLabel`, `canvasCompletion` |
| Video generation | `/api/external/v1/editor/videos/generations` | `prompt`, `model`, `aspectRatio`, `durationSeconds`, `resolution`, `mode`, `sound` | `referenceImageSrcs`, `referenceVideoSrcs`, `referenceAudioSrcs`, `webSearchEnabled`, `projectId`, `assetFolderId`, `assetLabel`, `canvasCompletion` |
| Sound effect | `/api/external/v1/editor/audios/sound-effects/generations` | `prompt` | `model`, `duration`, `loop`, `projectId`, `assetFolderId`, `assetLabel`, `canvasCompletion`, `generationInputs` |
| Sound effect | `/api/external/v1/editor/audios/sound-effects/generations` | `prompt`, `duration` | `model`, `projectId`, `assetFolderId`, `assetLabel`, `canvasCompletion`, `generationInputs` |
| Background music | `/api/external/v1/editor/audios/background-music/generations` | `gptDescriptionPrompt`, `makeInstrumental` | `projectId`, `assetFolderId`, `assetLabel`, `canvasCompletion`, `generationInputs` |
Poll all eight through:
@@ -77,27 +77,24 @@ Supply the `operationId` returned by submission. Poll no faster than `pollAfterM
## Reference Field Mapping
After confirming a local upload, pass its stable `objectKey` into operations that accept object references:
After confirming a local upload, pass its stable `objectKey` into:
| Target capability | Field |
| --- | --- |
| Image generation | `referenceImageSrcs` |
| Image edit/redraw | `sourceImageSrc`; additional references in `referenceImageSrcs` |
| Icon spritesheet | Register the primary spec as an `assetKind="icon-spec"` project resource or asset, then pass its returned ID as `referenceId`; additional style references remain in `referenceImageSrcs` |
| Icon spritesheet | `referenceImageSrc`; additional style references in `referenceImageSrcs` |
| UI design extraction | `sourceImageSrc`; additional references in `referenceImageSrcs` |
| Character animation | `sourceImageSrc` |
| Video with image references | `referenceImageSrcs` |
Use video/audio reference arrays only with models that support them. Do not pass an expiring signed read URL as a generation reference.
The icon-spritesheet primary `referenceId` is intentionally stricter than ordinary image references: it accepts only a current-owner project resource ID or asset ID whose authoritative `assetKind` is `icon-spec`. It does not accept an `objectKey`, URL, Data URL, or Blob URL.
## Common Values
Use OpenAPI as the final authority; these common values are a routing aid:
- Image `kind`: `spec`, `character`, `quick-edit`, `ui-design`, `publication-material`; ordinary image generation may omit it.
- External v1 currently has no structured game-scene generation operation. Do not send `kind: "scene"` or `assetKind: "scene"` through generic image generation; the server rejects both before queueing.
- Image `model`: `gpt-image-2`, `gemini-3.1-flash-image-preview`, `nanobanana2`, `nano-banana`.
- Image `aspectRatio`: `1:1`, `2:3`, `3:2`, `9:16`, `16:9`.
- Image `imageSize`: `0.5K`, `1K`, `2K`.
@@ -105,7 +102,6 @@ Use OpenAPI as the final authority; these common values are a routing aid:
- Video `aspectRatio`: `16:9`, `9:16`, `1:1`, `4:3`, `3:4`, `21:9`.
- Video `resolution`: `480p`, `720p`, `1080p`; `mode`: `std`; `sound`: `on` or `off`.
- Character animation uses `model: "seedance2.0-fast"`; `resolution`: `480p` or `720p`; `frameCount`: `32`, `40`, or `48`; `durationSeconds`: `4`, `5`, or `6`; `ratio`: `same`, `1:1`, `4:3`, `16:9`, `9:16`, or `3:4`.
- Sound effect uses canonical model `eleven_text_to_sound_v2`; omit `duration` or send `null` for automatic duration, otherwise send a finite `0.5-30` number. `loop` defaults to `false` and remains independent from Prompt text.
- UI extraction uses `aspectRatio: "1:1"`; use `imageSize: "1K"` for normal/small extraction and `2K` for dense designs.
Do not hard-code this list as a replacement client schema. In particular, the top-level image `style` field is intentionally extensible; see `requests-and-outputs.md` for its fallback behavior.
@@ -78,7 +78,7 @@ Keep the existing autonomous-build task graph. Do not add a parallel task system
1. `art-director` generates `assets/art-spec.png` with image generation, `kind: "spec"`, then registers it as `assetKind: "icon-spec"`. This image is the authoritative visual spec; `generationInputs.artSpec` is supporting structured context.
2. `design-foundation` generates `assets/ui-prototype.png` with `kind: "ui-design"`, using the registered art-spec resource ID in `referenceImageSrcs`.
3. `art-asset-plan` generates transparent `assets/art-spritesheet.png` through icon spritesheet generation, using the same registered art-spec resource ID as `referenceId` plus concrete `iconDescriptions`.
3. `art-asset-plan` generates transparent `assets/art-spritesheet.png` through icon spritesheet generation, using the same registered art-spec resource ID as `referenceImageSrc` plus concrete `iconDescriptions`.
For a playable Canvas game, do not stop at generation. Make `code-prototype` depend on `art-asset-plan` and consume the persisted `iconImageSrcs` slices for core players, blocks or targets, scene obstacles, and feedback. For the four-category game-chat contract, require exactly four slices before registering the local runtime sheet; both fewer and extra components fail closed. Treat `art-spec.png` as reference-only. A full-sheet `<img>`, CSS background, path-only mention, guessed equal-grid crop, or code-drawn replacement for core entities is not runtime asset use. If slicing produces `sliceWarning`, keep the complete transparent sheet as a valid editor artifact, but fail the playable game asset gate until real slice files or verified atlas coordinates exist; never invent coordinates or replace the icon-spritesheet route with ordinary image generation.
@@ -104,8 +104,6 @@ For the lower-level asset/resource creation endpoints, `generationInputs` is rep
## Art Spec and Image Request
Generic External v1 image generation does not expose the main-site structured game-scene contract. `kind: "scene"` and `assetKind: "scene"` are both invalid and return HTTP `400` before any generation job is queued. Do not replace the structured scene fields and server-owned prompt assembly with a generic image prompt.
Carry the current art spec in `generationInputs.artSpec` and reflect important constraints in the prompt:
```json
@@ -169,8 +167,6 @@ client.generate_image(
)
```
Icon spritesheet generation has a stricter primary-spec contract. After upload confirmation, create a project resource or asset record with `assetKind: "icon-spec"`, retain its returned `resourceId` or `assetId`, and pass that ID as `referenceId`. The primary spec does not accept the uploaded `objectKey` directly; only additional style references may continue to use stable object keys in `referenceImageSrcs`.
For character animation from a local-only source, use actual dimensions and a stable synthetic layer ID:
```json
@@ -197,7 +193,6 @@ Do not guess dimensions or pass a temporary signed read URL. See `authentication
The completed `result` may contain stable artifact fields such as:
- `objectKey`, media type, dimensions, or task ID.
- Sound-effect `durationSeconds` is the probed MP3 duration and `loop` is the frozen request boolean; neither is inferred from Prompt text.
- `resource`, `resourceId`, or equivalent canvas reference.
- `asset`, `assetId`, or equivalent library reference.
- `spritesheetResource`, `spritesheetAsset`, and stable spritesheet metadata.
@@ -560,13 +560,10 @@ class GenarrativeExternalClient:
def generate_icon_spritesheet(
self,
reference_id: str,
reference_image_src: str,
icon_descriptions: list[str],
**fields: Any,
) -> Any:
reference_id = normalize_optional_text(reference_id)
if not reference_id:
raise GenarrativeApiError("reference_id must be a registered icon-spec resource or asset ID")
descriptions = [item.strip() for item in icon_descriptions if item.strip()]
if not descriptions:
raise GenarrativeApiError("icon_descriptions must contain at least one non-empty item")
@@ -577,9 +574,9 @@ class GenarrativeExternalClient:
return self.submit_and_wait_generation(
"/api/external/v1/editor/icon-spritesheets/generations",
{
**fields,
"referenceId": reference_id,
"referenceImageSrc": reference_image_src,
"iconDescriptions": descriptions,
**fields,
},
idempotency_key=idempotency_key,
)
@@ -652,19 +649,13 @@ class GenarrativeExternalClient:
idempotency_key=idempotency_key,
)
def generate_sound_effect(
self,
prompt: str,
duration: float | None = None,
loop: bool = False,
**fields: Any,
) -> Any:
def generate_sound_effect(self, prompt: str, duration: int, **fields: Any) -> Any:
self._apply_canvas_session_fields(fields, prompt, 360, 120)
prompt = self._apply_art_spec(fields, prompt)
idempotency_key = fields.pop("idempotencyKey", None)
return self.submit_and_wait_generation(
"/api/external/v1/editor/audios/sound-effects/generations",
{"prompt": prompt, "duration": duration, "loop": loop, **fields},
{"prompt": prompt, "duration": duration, **fields},
idempotency_key=idempotency_key,
)
@@ -775,11 +766,9 @@ def _self_test() -> None:
["蛇头向上", "蛇身直线", "转角", "尾部", "四类食物"],
canvasSession=session,
assetLabel="贪吃蛇透明图集",
referenceId="must-not-override-explicit-reference",
iconDescriptions=["不得覆盖显式图标描述"],
)
assert calls[0]["path"] == "/api/external/v1/editor/icon-spritesheets/generations"
assert calls[0]["body"]["referenceId"] == "editor-resource-spec"
assert calls[0]["body"]["referenceImageSrc"] == "editor-resource-spec"
assert calls[0]["body"]["screenColor"] == "auto"
assert calls[0]["body"]["iconDescriptions"][0] == "蛇头向上"
assert calls[1]["path"] == "/api/external/v1/generations/task-operation-demo"
-5
View File
@@ -130,11 +130,6 @@ VECTOR_ENGINE_BASE_URL="https://api.vectorengine.cn"
VECTOR_ENGINE_API_KEY=""
VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS="1000000"
# ElevenLabs editor sound-effect generation is server-side only.
ELEVENLABS_BASE_URL="https://api.elevenlabs.io"
ELEVENLABS_API_KEY=""
ELEVENLABS_REQUEST_TIMEOUT_MS="180000"
# 阿里云 OSS 配置。
# Rust `server-rs` 的 `api-server` 会优先从 `.env` / `.env.local` 读取这些变量,
# 用于签发浏览器 PostObject 直传票据,并保持 `/generated-*` 旧路径习惯。
-2
View File
@@ -46,7 +46,6 @@ module.exports = {
{
files: [
'apps/admin-web/src/pages/*.tsx',
'src/components/platform-entry/PlatformActiveMobileWelcomeDialog.tsx',
'src/components/platform-entry/PlatformMobileHomeWelcomeDialog.tsx',
],
rules: {
@@ -73,7 +72,6 @@ module.exports = {
'src/components/image-editor/**/*.{ts,tsx}',
'src/components/project/**/*.{ts,tsx}',
'src/components/platform-entry/PlatformActiveProfileView*.tsx',
'src/components/platform-entry/PlatformActiveMobileWelcomeDialog*.tsx',
'src/components/platform-entry/PlatformEntryActiveFlowShell*.tsx',
'src/components/platform-entry/PlatformEntryFlowShell.tsx',
'src/components/platform-entry/PlatformProfileApiKeysModal.tsx',
@@ -2,7 +2,7 @@
name: genarrative-dev-stack-port-routing
short_description: 修改 Genarrative 本地 dev 启动端口、代理目标、端口冲突处理时使用。
description: 在 Genarrative 中修改 npm run dev / dev:spacetime / dev:api-server / dev:bgfilter-worker / dev:web / dev:admin-web 的本地启动端口、端口可用性探测、端口漂移、SpacetimeDB publish server、Rust 进程环境变量、Vite 代理目标和后台 admin-web 启动串联时使用。
version: 1.2.0
version: 1.1.0
author: Hermes Agent
license: MIT
metadata:
@@ -17,7 +17,7 @@ metadata:
## 适用场景
- 修改 `scripts/dev.mjs``scripts/dev-utils.mjs``scripts/dev-stack-port-utils.mjs` 或 AI 游戏创作客户端 dev 启动器
- 修改 `scripts/dev.mjs``scripts/dev-utils.mjs``scripts/dev-stack-port-utils.mjs`
- 处理 `3000``3101``3102``8082` 等端口被占用导致本地开发栈启动失败。
- 排查 Vite 代理仍指向旧 api-server 端口、前端打开了旧 dev server、后台代理错配。
- 调整 SpacetimeDB standalone、publish、Rust `api-server`、主站 Vite、后台 Vite 的启动顺序。
@@ -32,11 +32,10 @@ metadata:
3. SpacetimeDB standalone`3101`,健康检查为 `http://127.0.0.1:<spacetime-port>/v1/ping`
4. 后台 Vite`3102`,后台地址为 `http://127.0.0.1:<admin-web-port>/admin/`
5. 独立 BgFilter worker`8083`,就绪检查为 `http://127.0.0.1:<bgfilter-worker-port>/readyz`
6. AI 游戏创作 Vite:非 Linux 兼容首选 `3080`Linux 使用当前用户端口段的 `start + 5`
端口不可用时,脚本会从优先端口开始向后寻找可用端口。后续流程必须以解析后的实际端口为准,不能继续使用默认端口。
Linux 多用户并发开发时,`GENARRATIVE_DEV_PORT_RANGE``--port-range` 会先向系统级注册表 `/var/tmp/genarrative-dev-port-ranges/registry.json` 申请一个端口段,再把该段映射为 `web = start``api = start + 1``spacetime = start + 2``adminWeb = start + 3``bgfilterWorker = start + 4``agcVite = start + 5`。注册表锁文件是 `/var/tmp/genarrative-dev-port-ranges/registry.lock`,可通过 `GENARRATIVE_DEV_PORT_RANGE_REGISTRY_DIR` 覆盖目录。自动分配从 `10000-10099` 起,每次占用 100 个端口块,后续块按 `10100-10199``10200-10299` 递增;当前口径是“一个用户固定占用一个段,后续启动继续复用这段并在段内漂移”;该注册表只在 Linux 上生效;Windows 继续沿用原有统一端口探测和漂移逻辑,不读系统级注册表。
Linux 多用户并发开发时,`GENARRATIVE_DEV_PORT_RANGE``--port-range` 会先向系统级注册表 `/var/tmp/genarrative-dev-port-ranges/registry.json` 申请一个端口段,再把该段映射为 `web = start``api = start + 1``spacetime = start + 2``adminWeb = start + 3``bgfilterWorker = start + 4`。注册表锁文件是 `/var/tmp/genarrative-dev-port-ranges/registry.lock`,可通过 `GENARRATIVE_DEV_PORT_RANGE_REGISTRY_DIR` 覆盖目录。自动分配从 `10000-10099` 起,每次占用 100 个端口块,后续块按 `10100-10199``10200-10299` 递增;当前口径是“一个用户固定占用一个段,后续启动继续复用这段并在段内漂移”;该注册表只在 Linux 上生效;Windows 继续沿用原有统一端口探测和漂移逻辑,不读系统级注册表。
## 实现入口
@@ -52,12 +51,9 @@ Linux 多用户并发开发时,`GENARRATIVE_DEV_PORT_RANGE` 或 `--port-range`
- `scripts/dev.mjs`
- 解析 CLI 参数后统一计算 client host、端口、`SPACETIME_SERVER``RUST_SERVER_TARGET`
- 完整栈按 SpacetimeDB、publish、BgFilter worker readiness、api-server readiness、主站 Vite、后台 Vite 顺序启动。
- Linux 下会先申请系统级端口段并映射成六个预留槽位;主 dev 栈使用前五个,AGC Vite 使用 `start + 5`自动分配从 `10000-10099`Windows 的主 dev 栈和 AGC 则各自沿用统一端口探测与漂移逻辑。
- Linux 下会先申请系统级端口段并把它映射成五个 dev 端口;自动分配从 `10000-10099`Windows 则把第五个服务纳入原有统一参数解析与端口漂移逻辑。
- 完整栈和 `dev:api-server` 把两个 Rust 进程作为同一重启单元,先全部停止,再先启动 BgFilter worker、后启动 api-server;不要为同一份 Rust 源码创建两个并发 `cargo` watcher。
- 单模块命令复用同一套参数和 env 解析。
- `apps/ai-game-creator-shell/scripts/dev-port.mjs`
- 复用系统级用户端口段,解析 AGC Vite 的 `start + 5` 首选槽位。
- 把最终端口通过 `GENARRATIVE_AGC_VITE_PORT` 同步给 `beforeDevCommand` 和配套后端端口解析器,通过 Tauri CLI `--config` 同步 `build.devUrl`,并通过 Vite CLI `--port` 同步 `strictPort` 监听。
## 必须保持的传递链路
@@ -73,7 +69,6 @@ Linux 多用户并发开发时,`GENARRATIVE_DEV_PORT_RANGE` 或 `--port-range`
8. 后台 Vite`ADMIN_API_TARGET``GENARRATIVE_API_TARGET``GENARRATIVE_API_PORT``--port=${ADMIN_WEB_PORT}`
9. 控制台日志:`[dev:ports]``[dev] web/admin web/api-server/bgfilter-worker/spacetime` 必须显示最终实际地址。
10. Linux 端口段注册:`[dev] port-range:``[dev] port-range-registry:` 只在 Linux 输出,Windows 不应依赖系统级注册表。
11. AI 游戏创作客户端:外层启动器解析最终 AGC Vite 端口后,通过 Tauri CLI `--config` 覆盖 `build.devUrl`,把同一 `GENARRATIVE_AGC_VITE_PORT` 传给 `beforeDevCommand` 与配套后端端口解析器,并用 Vite CLI `--port` 启动严格监听;后端端口漂移必须跳过该预留端口。
如果只改了其中一段,通常会出现:浏览器打开的前端可用,但 `/api/*` 代理到旧端口;后台页面可用但后台 API 失败;SpacetimeDB 启动在新端口但 publish 仍发往旧端口。
@@ -133,7 +128,6 @@ node scripts/dev-stack-port-utils.mjs resolve-dev-stack spacetime:127.0.0.1:0 ap
- [ ] `npm run dev` 的 SpacetimeDB、publish、api-server、主站 Vite、后台 Vite 都使用实际端口。
- [ ] BgFilter worker 在 api-server 前 ready,父子共享实际 base URL / TokenRust watch 只触发一次组合重启。
- [ ] `npm run dev:web` 在主站端口不可用时能切换到可用端口。
- [ ] `npm run agc` / `npm run agc:game-chat` 在 Linux 使用用户段 `start + 5`Tauri、Vite、marker 和预检使用同一最终端口。
- [ ] 文档同步更新 `docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
- [ ] 长期踩坑同步更新 `docs/project-memory/shared-memory/pitfalls.md`
- [ ] 修改中文文件后运行 `npm run check:encoding`
+1
View File
@@ -5,6 +5,7 @@
"type": "module",
"scripts": {
"dev": "node scripts/start-tauri-dev.mjs",
"dev:app-run": "node scripts/start-tauri-dev.mjs --app-run",
"game-chat": "node scripts/start-tauri-dev.mjs --game-chat",
"dev-server": "node scripts/start-dev-server.mjs",
"dev-stack": "node scripts/start-dev-stack.mjs",
@@ -27,6 +27,12 @@ const tauriConfig = JSON.parse(
'utf8',
),
);
const appRunTauriConfig = JSON.parse(
fs.readFileSync(
new URL('../src-tauri/tauri.app-run-dev.conf.json', import.meta.url),
'utf8',
),
);
const gameChatReleaseTauriConfig = JSON.parse(
fs.readFileSync(
new URL('../src-tauri/tauri.game-chat-release.conf.json', import.meta.url),
@@ -62,14 +68,6 @@ const viteConfigSource = fs.readFileSync(
new URL('../vite.config.ts', import.meta.url),
'utf8',
);
const devPortSource = fs.readFileSync(
new URL('../scripts/dev-port.mjs', import.meta.url),
'utf8',
);
const startTauriDevSource = fs.readFileSync(
new URL('../scripts/start-tauri-dev.mjs', import.meta.url),
'utf8',
);
const appSource = [
readSourceTree(new URL('../src/', import.meta.url), '.ts'),
readSourceTree(new URL('../src/', import.meta.url), '.tsx'),
@@ -1225,7 +1223,7 @@ if (
if (tauriConfig.build?.devUrl !== 'http://127.0.0.1:3080/') {
throw new Error(
'AI game creator shell Tauri config must retain the non-launcher fallback devUrl',
'AI game creator shell Tauri devUrl must stay on the fixed Vite dev port',
);
}
@@ -1235,41 +1233,12 @@ if (!viteConfigSource.includes("host: '127.0.0.1'")) {
);
}
if (
!viteConfigSource.includes('port: 3080') ||
!viteConfigSource.includes('port: server.config.server.port')
) {
if (!viteConfigSource.includes('port: 3080')) {
throw new Error(
'AI game creator shell Vite config must retain its fallback and report the actual CLI-selected port',
'AI game creator shell Vite dev port must match Tauri devUrl',
);
}
for (const snippet of [
'mapDevPortsToPortRange',
'agcVitePort',
'resolveAgcDevEndpoint',
'GENARRATIVE_AGC_VITE_PORT',
]) {
if (!devPortSource.includes(snippet)) {
throw new Error(
`AI game creator shell dev port resolver drifted: ${snippet}`,
);
}
}
for (const snippet of [
'resolveAgcDevEndpoint',
'withAgcDevEndpointEnv',
"'--config'",
'configOverride',
]) {
if (!startTauriDevSource.includes(snippet)) {
throw new Error(
`AI game creator shell Tauri dev port injection drifted: ${snippet}`,
);
}
}
if (!viteConfigSource.includes('strictPort: true')) {
throw new Error(
'AI game creator shell Vite dev server must not drift away from Tauri devUrl',
@@ -1290,7 +1259,7 @@ if (
)
) {
throw new Error(
'AI game creator shell beforeDevCommand must start the selected Vite dev server',
'AI game creator shell beforeDevCommand must reuse or start the fixed Vite dev server',
);
}
@@ -1300,6 +1269,33 @@ if (packageConfig.scripts?.dev !== 'node scripts/start-tauri-dev.mjs') {
);
}
if (
packageConfig.scripts?.['dev:app-run'] !==
'node scripts/start-tauri-dev.mjs --app-run' ||
rootPackageConfig.scripts?.['agc:app-run'] !==
'npm --prefix apps/ai-game-creator-shell run dev:app-run'
) {
throw new Error(
'AI game creator app-run dev profile must use the managed Tauri dev launcher',
);
}
const appRunWindow = appRunTauriConfig.app?.windows?.[0];
if (
appRunTauriConfig.productName !==
'Genarrative AI Game Creator App Run' ||
appRunTauriConfig.identifier !==
'world.genarrative.ai-game-creator.app-run' ||
appRunTauriConfig.identifier === tauriConfig.identifier ||
appRunTauriConfig.build?.devUrl !== 'http://127.0.0.1:3081/' ||
appRunWindow?.label !== 'client' ||
appRunWindow?.title !== 'AI 游戏创作 · App Run'
) {
throw new Error(
'AI game creator app-run profile must keep an independent identity, title, and Vite port',
);
}
if (
packageConfig.scripts?.['game-chat'] !==
'node scripts/start-tauri-dev.mjs --game-chat'
@@ -1500,15 +1496,12 @@ if (
}
for (const snippet of [
'resolveAgcDevEndpoint',
'withAgcDevEndpointEnv',
'const port = 3080',
"response.body.includes('<title>AI 游戏创作</title>')",
'function isPortListening()',
'cannot be safely reused',
'reuse existing Vite dev server',
'non-HTTP or unrecognized server',
"'--config'",
"'vite.config.ts'",
"'--port'",
"'--config', 'vite.config.ts'",
]) {
if (!devServerSource.includes(snippet)) {
throw new Error(
@@ -1,111 +0,0 @@
import {
findAvailablePort,
formatPortDecision,
mapDevPortsToPortRange,
normalizePort,
reserveLinuxDevPortRange,
} from '../../../scripts/dev-stack-port-utils.mjs';
const agcDevHost = '127.0.0.1';
const legacyAgcDevPort = 3080;
const agcVitePortEnvKey = 'GENARRATIVE_AGC_VITE_PORT';
function readConfiguredAgcDevPort(env = process.env) {
const rawPort = String(env[agcVitePortEnvKey] ?? '').trim();
if (!rawPort) {
return null;
}
const port = normalizePort(rawPort, -1);
if (port < 1024) {
throw new Error(`${agcVitePortEnvKey} 必须是 1024-65535 的有效端口`);
}
return port;
}
function createAgcDevEndpoint(port, portRange = null) {
const url = `http://${agcDevHost}:${port}/`;
return {
host: agcDevHost,
port,
url,
markerUrl: `${url}__agc_dev_server.json`,
portRange,
};
}
function readAgcDevEndpoint(env = process.env) {
return createAgcDevEndpoint(
readConfiguredAgcDevPort(env) ?? legacyAgcDevPort,
);
}
async function resolveAgcDevEndpoint({
env = process.env,
platform = process.platform,
strictConfigured = false,
reservePortRange = reserveLinuxDevPortRange,
findPort = findAvailablePort,
} = {}) {
const configuredPort = readConfiguredAgcDevPort(env);
let portRange = null;
let preferredPort = configuredPort ?? legacyAgcDevPort;
if (platform === 'linux') {
const allocation = await reservePortRange({ env });
if (!allocation?.range) {
throw new Error('无法取得当前 Linux 用户的 dev 端口段');
}
portRange = allocation.range;
const mappedAgcVitePort = mapDevPortsToPortRange(portRange)?.agcVitePort;
if (!Number.isInteger(mappedAgcVitePort)) {
throw new Error(
`当前 Linux dev 端口段 ${portRange.label} 缺少 AGC Vite 槽位;请先迁移为至少 6 个端口且不与其它用户重叠的端口段`,
);
}
if (configuredPort != null && configuredPort < mappedAgcVitePort) {
throw new Error(
`${agcVitePortEnvKey} ${configuredPort} 占用了 Linux dev 端口段 ${portRange.label} 的前五个服务槽位`,
);
}
preferredPort = configuredPort ?? mappedAgcVitePort;
}
const port = await findPort({
host: agcDevHost,
preferredPort,
portRange,
strict: strictConfigured && configuredPort != null,
});
console.log(
formatPortDecision({
name: 'ai-game-creator-shell',
host: agcDevHost,
preferredPort,
resolvedPort: port,
}),
);
if (portRange) {
console.log(`[ai-game-creator-shell] dev port-range: ${portRange.label}`);
}
return createAgcDevEndpoint(port, portRange);
}
function withAgcDevEndpointEnv(endpoint, env = process.env) {
return {
...env,
[agcVitePortEnvKey]: String(endpoint.port),
};
}
export {
agcDevHost,
agcVitePortEnvKey,
createAgcDevEndpoint,
legacyAgcDevPort,
readAgcDevEndpoint,
readConfiguredAgcDevPort,
resolveAgcDevEndpoint,
withAgcDevEndpointEnv,
};
@@ -3,11 +3,10 @@ import http from 'node:http';
import net from 'node:net';
import { fileURLToPath } from 'node:url';
import { resolveAgcDevEndpoint, withAgcDevEndpointEnv } from './dev-port.mjs';
const appRoot = fileURLToPath(new URL('..', import.meta.url));
const endpoint = await resolveAgcDevEndpoint();
const { host, port, url: devUrl } = endpoint;
const host = '127.0.0.1';
const port = 3080;
const devUrl = `http://${host}:${port}/`;
function readExistingServer() {
return new Promise((resolve) => {
@@ -70,10 +69,8 @@ function isPortListening() {
const existing = await readExistingServer();
if (existing) {
if (isAiGameCreatorServer(existing)) {
console.error(
`[ai-game-creator-shell] ${devUrl} is already running but cannot be safely reused. Stop that process before starting the dev server.`,
);
process.exit(1);
console.log(`[ai-game-creator-shell] reuse existing Vite dev server ${devUrl}`);
process.exit(0);
}
console.error(
`[ai-game-creator-shell] ${devUrl} is already in use by another server. Stop that process before starting Tauri dev.`,
@@ -91,20 +88,9 @@ if (await isPortListening()) {
const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm';
const child = spawn(
npm,
[
'--prefix',
'../..',
'exec',
'vite',
'--',
'--config',
'vite.config.ts',
'--port',
String(endpoint.port),
],
['--prefix', '../..', 'exec', 'vite', '--', '--config', 'vite.config.ts'],
{
cwd: appRoot,
env: withAgcDevEndpointEnv(endpoint),
stdio: 'inherit',
// Node 18.20+/20+/24 on Windows rejects spawning .cmd (npm.cmd) without a shell (EINVAL).
shell: true,
@@ -5,23 +5,58 @@ import net from 'node:net';
import { resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import {
agcVitePortEnvKey,
readAgcDevEndpoint,
resolveAgcDevEndpoint,
withAgcDevEndpointEnv,
} from './dev-port.mjs';
const appRoot = fileURLToPath(new URL('..', import.meta.url));
const repoRoot = resolve(appRoot, '../..');
const devStackStatePath = resolve(repoRoot, '.app/dev-stack.json');
const devProfileName = process.env.GENARRATIVE_AGC_DEV_PROFILE || 'default';
function resolveDevStackProfile(name = 'default') {
switch (name) {
case 'default':
return {
name,
viteHost: '127.0.0.1',
vitePort: 3080,
apiPort: 8082,
bgfilterWorkerPort: 8083,
spacetimePort: 3101,
backendDatabase: 'genarrative-game-creator-dev',
backendSpacetimeDataDir: resolve(
repoRoot,
'server-rs/.spacetimedb/ai-game-creator/data',
),
};
case 'app-run':
return {
name,
viteHost: '127.0.0.1',
vitePort: 3081,
apiPort: 8084,
bgfilterWorkerPort: 8085,
spacetimePort: 3103,
backendDatabase: 'genarrative-game-creator-app-run-dev',
backendSpacetimeDataDir: resolve(
repoRoot,
'server-rs/.spacetimedb/ai-game-creator-app-run/data',
),
};
default:
throw new Error(`未知 AI 游戏创作开发 profile: ${name}`);
}
}
const devProfile = resolveDevStackProfile(devProfileName);
const {
viteHost,
vitePort,
apiPort,
spacetimePort,
backendDatabase,
backendSpacetimeDataDir,
} = devProfile;
const viteUrl = `http://${viteHost}:${vitePort}/`;
const defaultApiTarget =
process.env.RUST_SERVER_TARGET || 'http://127.0.0.1:8082';
const backendDatabase = 'genarrative-game-creator-dev';
const backendSpacetimeDataDir = resolve(
repoRoot,
'server-rs/.spacetimedb/ai-game-creator/data',
);
process.env.RUST_SERVER_TARGET || `http://127.0.0.1:${apiPort}`;
const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm';
const childLifecycles = new WeakMap();
@@ -75,6 +110,7 @@ function resolveBackendTargetsFromState(
expectedDatabase = backendDatabase,
expectedSpacetimeDataDir = backendSpacetimeDataDir,
fallbackApiTarget = defaultApiTarget,
fallbackSpacetimeTarget = `http://127.0.0.1:${spacetimePort}`,
} = {},
) {
const apiServer = state?.services?.['api-server'];
@@ -103,7 +139,7 @@ function resolveBackendTargetsFromState(
? spacetime.url
: requireAgcBackend
? ''
: 'http://127.0.0.1:3101';
: fallbackSpacetimeTarget;
return {
apiUrl,
spacetimeUrl,
@@ -134,13 +170,24 @@ async function isBackendReady() {
);
}
async function readExistingViteServer(endpoint = readAgcDevEndpoint()) {
return httpGetText(endpoint.url);
function resolveViteProfileUrls(profile = devProfile) {
const profileViteUrl = `http://${profile.viteHost}:${profile.vitePort}/`;
return {
viteUrl: profileViteUrl,
viteMarkerUrl: `${profileViteUrl}__agc_dev_server.json`,
};
}
function isVitePortListening(endpoint = readAgcDevEndpoint()) {
async function readExistingViteServer(profile = devProfile) {
return httpGetText(resolveViteProfileUrls(profile).viteUrl);
}
function isVitePortListening(profile = devProfile) {
return new Promise((resolveRequest) => {
const socket = net.connect({ host: endpoint.host, port: endpoint.port });
const socket = net.connect({
host: profile.viteHost,
port: profile.vitePort,
});
socket.once('connect', () => {
socket.destroy();
resolveRequest(true);
@@ -163,8 +210,11 @@ function isAiGameCreatorServer(response) {
);
}
async function readExistingViteMarker(endpoint = readAgcDevEndpoint()) {
const response = await httpGetText(endpoint.markerUrl, 2000);
async function readExistingViteMarker(profile = devProfile) {
const response = await httpGetText(
resolveViteProfileUrls(profile).viteMarkerUrl,
2000,
);
if (!response || response.statusCode !== 200) {
return null;
}
@@ -176,16 +226,17 @@ async function readExistingViteMarker(endpoint = readAgcDevEndpoint()) {
}
async function preflightExistingVite({
endpoint = readAgcDevEndpoint(),
readServer = readExistingViteServer,
portListening = isVitePortListening,
readMarker = readExistingViteMarker,
profile = devProfile,
readServer = () => readExistingViteServer(profile),
portListening = () => isVitePortListening(profile),
readMarker = () => readExistingViteMarker(profile),
} = {}) {
const existing = await readServer(endpoint);
const { viteUrl: profileViteUrl } = resolveViteProfileUrls(profile);
const existing = await readServer();
if (!existing) {
if (await portListening(endpoint)) {
if (await portListening()) {
throw new Error(
`${endpoint.url} is already in use by a non-HTTP or unrecognized server. Stop it before starting Tauri dev.`,
`${profileViteUrl} is already in use by a non-HTTP or unrecognized server. Stop it before starting Tauri dev.`,
);
}
return { status: 'available', apiTarget: '' };
@@ -193,11 +244,11 @@ async function preflightExistingVite({
if (!isAiGameCreatorServer(existing)) {
throw new Error(
`${endpoint.url} is already in use by another server. Stop it before starting Tauri dev.`,
`${profileViteUrl} is already in use by another server. Stop it before starting Tauri dev.`,
);
}
const marker = await readMarker(endpoint);
const marker = await readMarker();
const markerApiTarget =
marker?.schemaVersion === 1 &&
marker?.app === 'ai-game-creator-shell' &&
@@ -206,7 +257,7 @@ async function preflightExistingVite({
: '';
const actualTarget = markerApiTarget || 'unknown';
throw new Error(
`${endpoint.url} is already running with API target ${actualTarget}. Its owning worktree cannot be proven, so it will not be reused. Stop that Vite dev server before starting Tauri dev.`,
`${profileViteUrl} is already running with API target ${actualTarget}. Its owning worktree cannot be proven, so it will not be reused. Stop that Vite dev server before starting Tauri dev.`,
);
}
@@ -511,27 +562,50 @@ async function waitForBackendReady(backendChild, timeoutMs = 600_000) {
throw new Error('等待配套后端和数据库启动超时');
}
function buildBackendStartArguments(profile = devProfile) {
return [
'--prefix',
'../..',
'run',
'agc:backend',
'--',
'--database',
profile.backendDatabase,
'--spacetime-data-dir',
profile.backendSpacetimeDataDir,
'--api-port',
String(profile.apiPort),
'--bgfilter-worker-port',
String(profile.bgfilterWorkerPort),
'--spacetime-port',
String(profile.spacetimePort),
'--no-interactive',
];
}
function buildViteStartArguments(profile = devProfile) {
return [
'--prefix',
'../..',
'exec',
'vite',
'--',
'--config',
'vite.config.ts',
'--host',
profile.viteHost,
'--port',
String(profile.vitePort),
'--strictPort',
];
}
async function ensureBackend({
onBackendChild = () => {},
checkBackendReady = isBackendReady,
resolveTargets = readBackendTargets,
spawnBackend = () =>
spawnChild(
npm,
[
'--prefix',
'../..',
'run',
'agc:backend',
'--',
'--database',
backendDatabase,
'--spacetime-data-dir',
backendSpacetimeDataDir,
'--no-interactive',
],
{ cwd: appRoot },
),
spawnChild(npm, buildBackendStartArguments(), { cwd: appRoot }),
waitUntilReady = waitForBackendReady,
} = {}) {
if (await checkBackendReady()) {
@@ -553,7 +627,7 @@ async function ensureBackend({
}
}
async function startVite(apiTarget, endpoint = readAgcDevEndpoint()) {
async function startVite(apiTarget) {
const { apiUrl } = readBackendTargets();
if (apiUrl !== apiTarget) {
throw new Error(
@@ -561,33 +635,19 @@ async function startVite(apiTarget, endpoint = readAgcDevEndpoint()) {
);
}
const existing = await readExistingViteServer(endpoint);
const existing = await readExistingViteServer();
if (existing) {
if (isAiGameCreatorServer(existing)) {
throw new Error(
`${endpoint.url} is already running and cannot be safely reused. Stop it before starting Tauri dev.`,
`${viteUrl} is already running and cannot be safely reused. Stop it before starting Tauri dev.`,
);
}
throw new Error(
`${endpoint.url} is already in use by another server. Stop it before starting Tauri dev.`,
`${viteUrl} is already in use by another server. Stop it before starting Tauri dev.`,
);
}
return spawnChild(
npm,
[
'--prefix',
'../..',
'exec',
'vite',
'--',
'--config',
'vite.config.ts',
'--port',
String(endpoint.port),
],
{ cwd: appRoot, env: withAgcDevEndpointEnv(endpoint) },
);
return spawnChild(npm, buildViteStartArguments(), { cwd: appRoot });
}
async function main() {
@@ -607,9 +667,10 @@ async function main() {
}
try {
const endpoint = await resolveAgcDevEndpoint({ strictConfigured: true });
process.env[agcVitePortEnvKey] = String(endpoint.port);
await preflightExistingVite({ endpoint });
console.log(
`[ai-game-creator-shell] dev profile ${devProfile.name}: vite=${viteUrl} api=${apiPort} spacetime=${spacetimePort}`,
);
await preflightExistingVite();
const backend = await ensureBackend({
onBackendChild(child) {
backendChild = child;
@@ -623,7 +684,7 @@ async function main() {
throw new Error(`启动期收到 ${shutdownSignal},已停止配套后端`);
}
viteChild = await startVite(backend.targets.apiUrl, endpoint);
viteChild = await startVite(backend.targets.apiUrl);
if (shutdownSignal) {
stopChild(viteChild, shutdownSignal);
throw new Error(`启动期收到 ${shutdownSignal},已停止前端服务`);
@@ -666,6 +727,8 @@ function isDirectModuleExecution() {
}
export {
buildBackendStartArguments,
buildViteStartArguments,
ensureBackend,
formatChildFailure,
isDirectModuleExecution,
@@ -674,6 +737,7 @@ export {
readChildFailure,
readLinuxProcessGroupAlive,
resolveBackendTargetsFromState,
resolveDevStackProfile,
runWindowsTaskkill,
spawnChild,
stopChild,
@@ -1,13 +1,9 @@
import { resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import {
readAgcDevEndpoint,
resolveAgcDevEndpoint,
withAgcDevEndpointEnv,
} from './dev-port.mjs';
import {
preflightExistingVite,
resolveDevStackProfile,
spawnChild,
stopChild,
terminateChildTree,
@@ -20,44 +16,43 @@ const tauriCliPath = resolve(repoRoot, 'node_modules/@tauri-apps/cli/tauri.js');
function parseLauncherArguments(argv) {
const args = [...argv];
const appRun = args[0] === '--app-run';
if (appRun) {
args.shift();
}
const gameChat = args[0] === '--game-chat';
if (gameChat) {
args.shift();
}
return { gameChat, args };
if (appRun && gameChat) {
throw new Error('app-run profile 不能与 game-chat 入口同时使用');
}
return { appRun, gameChat, args };
}
function buildTauriArguments(argv, devUrl = readAgcDevEndpoint().url) {
const { gameChat, args } = parseLauncherArguments(argv);
const configOverride = JSON.stringify({ build: { devUrl } });
if (gameChat) {
function buildTauriArguments(argv) {
const { appRun, gameChat, args } = parseLauncherArguments(argv);
if (appRun) {
return [
'dev',
'--config',
configOverride,
'--',
'--',
'--game-chat',
'src-tauri/tauri.app-run-dev.conf.json',
...args,
];
}
const separatorIndex = args.indexOf('--');
if (separatorIndex < 0) {
return ['dev', ...args, '--config', configOverride];
if (gameChat) {
return ['dev', '--', '--', '--game-chat', ...args];
}
return [
'dev',
...args.slice(0, separatorIndex),
'--config',
configOverride,
...args.slice(separatorIndex),
];
return ['dev', ...args];
}
function spawnTauriCli(argv, { env = process.env } = {}) {
function spawnTauriCli(argv, { profileName = 'default' } = {}) {
return spawnChild(process.execPath, [tauriCliPath, ...argv], {
cwd: appRoot,
env,
env: {
...process.env,
GENARRATIVE_AGC_DEV_PROFILE: profileName,
},
shell: false,
});
}
@@ -65,20 +60,18 @@ function spawnTauriCli(argv, { env = process.env } = {}) {
async function runTauriDev(
argv = process.argv.slice(2),
{
resolveDevEndpoint = resolveAgcDevEndpoint,
preflight = preflightExistingVite,
spawnCli = spawnTauriCli,
waitForCli = waitForChildTermination,
terminateTree = terminateChildTree,
} = {},
) {
const endpoint = await resolveDevEndpoint();
await preflight({ endpoint });
const { appRun } = parseLauncherArguments(argv);
const profile = resolveDevStackProfile(appRun ? 'app-run' : 'default');
await preflight({ profile });
const tauriArguments = buildTauriArguments(argv, endpoint.url);
const child = spawnCli(tauriArguments, {
env: withAgcDevEndpointEnv(endpoint),
});
const tauriArguments = buildTauriArguments(argv);
const child = spawnCli(tauriArguments, { profileName: profile.name });
let resolveShutdown;
let shutdownSignal = '';
let repeatedSignal = false;
@@ -1,7 +1,7 @@
{
"schemaVersion": 1,
"id": "genarrative.agent-runtime",
"version": "2026-08-07.2",
"version": "2026-08-06.1",
"sections": {
"common": "common.md",
"isolatedTemplateCatalogIntro": "isolated-template-catalog-intro.md",
@@ -9,6 +9,7 @@
"platformDefault": "platform/default.md",
"platformLinux": "platform/linux.md",
"codePrototypeGameChat": "roles/code-prototype-game-chat.md",
"codeDirectorGameChatAssetAudit": "roles/code-director-game-chat-asset-audit.md",
"projectSupervisorGameChatRouting": "supervisor/game-chat-routing.md",
"providerIsolatedToolContract": "provider/isolated-tool-contract.md",
"providerAutonomousRunProfile": "provider/autonomous-run-profile.md",
@@ -66,6 +67,11 @@
"rootSourceKind": "supervisorGameChat",
"sections": ["projectSupervisorGameChatRouting"]
},
{
"agentId": "code-director",
"rootSourceKind": "supervisorGameChat",
"sections": ["codeDirectorGameChatAssetAudit"]
},
{
"agentId": "code-prototype",
"rootSourceKind": "supervisorGameChat",
@@ -1 +1 @@
片段只适用于普通 GUI / CLI 的完整 `autonomous-game-build` manifest DAG;不得用于持久 source 为 `project-supervisor-game-chat` 的单主 route,后者不得用此修复建立固定首批委派。普通 DAG 的本次修复原生工具目录只保留 agent.delegate。必须在同一响应一次性建立完整首批合同,且只允许以下三个非 repair 委派,各出现一次:design-director 与 code-director 的 task 或 acceptanceCriteria 必须显式声明只读且不得修改项目,expectedArtifacts 必须为 []art-director 必须是非只读规范图生成任务,expectedArtifacts 必须包含 assets/art-spec.png。三者都必须提供非空 task、1-8 条 acceptanceCriteria,并设置 repairOfDelegationId=null、runId=null。不得委派 code-prototype、quality-review、design-foundation、art-asset-plan 或其它底层 Agent,不得调用 agent.spawn_isolated,不得更新计划、读取、搜索、查询状态、修改项目或返回最终回复。不要解释,不要 markdown,不要代码围栏。
本次修复原生工具目录只保留 agent.delegate。必须在同一响应一次性建立完整首批合同,且只允许以下三个非 repair 委派,各出现一次:design-director 与 code-director 的 task 或 acceptanceCriteria 必须显式声明只读且不得修改项目,expectedArtifacts 必须为 []art-director 必须是非只读规范图生成任务,expectedArtifacts 必须包含 assets/art-spec.png。三者都必须提供非空 task、1-8 条 acceptanceCriteria,并设置 repairOfDelegationId=null、runId=null。不得委派 code-prototype、quality-review、design-foundation、art-asset-plan 或其它底层 Agent,不得调用 agent.spawn_isolated,不得更新计划、读取、搜索、查询状态、修改项目或返回最终回复。不要解释,不要 markdown,不要代码围栏。
@@ -1 +1 @@
本片段只适用于普通 GUI / CLI 的完整 `autonomous-game-build` manifest DAG,不适用于持久 source 为 `project-supervisor-game-chat` 的单主 route。普通 DAG 中,正式 manifest 任务图是唯一首轮专业执行链不得在 manifest 之前另行创建 code-prototype、quality-review、art-director、design-foundation 或 art-asset-plan 的首批 agent.delegate;这些角色会由 Runtime 按 manifest 依赖顺序调度。没有待认领的显式返工合同时也不得额外委派。game-chat 则由其专用路由提示决定:Supervisor 持久化意图后只启动 code-prototype,只有该主 Agent 的 asset.list 审计证实真实缺口时才可委派受限美术 child。请直接推进/观察适用于当前 root source 的任务路径;Runtime 会在你尝试收束时调度 ready task,并在任务图完成前阻止最终交付。
autonomous-game-build 的正式 manifest 任务图是唯一首轮专业执行链不得在 manifest 之前另行创建 code-prototype、quality-review、art-director、design-foundation 或 art-asset-plan 的首批 agent.delegate;这些角色会由 Runtime 按 manifest 依赖顺序调度。没有待认领的显式返工合同时也不得额外委派。请直接推进/观察 manifestRuntime 会在你尝试收束时调度 ready task,并在任务图完成前阻止最终交付。
@@ -0,0 +1 @@
game-chat 的 code-director 是程序侧资产审计节点。第一步必须调用 `asset.list` 核对当前正式资产、Canvas 登记和可复用状态;不得生成图片、修改游戏或只凭用户措辞猜测缺口。取得 asset.list observation 后,本轮必须调用 `agent.route_manifest` 提交结构化结果:Supervisor 选择 audit-existing-first 且没有缺口时使用 `strategy=use-existing-art, missingAssetSlots=[]`;存在缺口时使用 `strategy=generate-missing-art`missingAssetSlots 只能精确列出 `art-spec``core-spritesheet` 中实际缺失项;Supervisor 选择 regenerate-art 时使用 `strategy=regenerate-art, missingAssetSlots=["art-spec","core-spritesheet"]`。Runtime 会复核权威资产状态,拒绝遗漏、虚构、过期或重复缺口。路由成功后再交付只读审计结论;美术补齐后由同一根 Run 恢复 code-prototype。
@@ -1,5 +1,3 @@
game-chat 使用单主素材审计快车道。你是唯一的 `code-prototype` 主 Agent;必须先保护既有游戏语义和已完成产物。若本轮尚未取得成功的 `asset.list` observation,第一步必须调用 `asset.list` 核对当前正式资产、Canvas 登记和可复用状态;不得仅凭用户措辞、关键词或旧摘要判断已有素材是否可用。审计后必须用 `agent.route_manifest` 持久化审计结论:完整覆盖使用 `use-existing-art, missingAssetSlots=[]`,真实缺口使用 `generate-missing-art` 且只列出实际缺失槽位;不得把 Supervisor 的用户 intent 改写成重生成路线。审计证明核心规范图、透明 spritesheet、切片清单或四类切片存在真实缺口时,才可一次委派对应的 `art-director``art-asset-plan` 补齐;任务必须精确说明缺失项和验收产物,child 只能写 `assets/**`,不得修改 `game/**`、删除/补丁程序文件或替你接入游戏。一次最多保留一个活跃美术委派,收到并认领其回执后继续同一 Run;不得在美术已齐或仅凭“UI 没有用素材”等描述时生成、委派或扣费。Supervisor 持久化的整体重做意图也只是上下文,不能改变这条审计门或授权整套美术强制重生成
game-chat 使用素材完整快车道。必须先保护既有游戏语义和已完成产物:如果 observation 尚未包含 game/index.html 的当前正文与摘要,本响应第一步必须file.read(path=game/index.html),不得猜测入口仍是初始化占位。只有文件缺失或正文与初始化页面一致、且当前 run 尚未写入项目时,才允许用一次 file.write 生成完整、自包含、可运行的 game/index.html;检测到非占位入口、已有可玩实现、当前 revision 已验证,或当前 run 已发生修改时,禁止整文件 file.write,必须保留原玩法,优先执行静态检查/试玩,确需修复时只能依据已读取的精确原文做最小 file.patch。不得把“继续”、continue 或其它纯续跑词当作游戏主题;缺少可继承的具体目标时必须失败关闭,不得另造新玩法
在成功审计并取得可用素材、或已认领必要美术回执后,才读取游戏入口:如果 observation 尚未包含 game/index.html 的当前正文与摘要,本响应下一步必须调用 file.read(path=game/index.html),不得猜测入口仍是初始化占位。只有文件缺失或正文与初始化页面一致、且当前 run 尚未写入项目时,才允许用一次 file.write 生成完整、自包含、可运行的 game/index.html;检测到非占位入口、已有可玩实现、当前 revision 已验证,或当前 run 已发生修改时,禁止整文件 file.write,必须保留原玩法,优先执行静态检查/试玩,确需修复时只能依据已读取的精确原文做最小 file.patch。不得把“继续”、continue 或其它纯续跑词当作游戏主题;缺少可继承的具体目标时必须失败关闭,不得另造新玩法
HTML 必须满足固定试玩合同,包含真实 Canvas 游戏循环、键盘与触控输入、开始、主要操作、重开、胜负状态和移动端布局。assets/art-spec.png 只能作为视觉规范与派生参考,不得在运行时加载、铺作背景或裁切实体。必须等待已登记且透明有效的 assets/art-spritesheet.png 与 assets/art-spritesheet-slices/manifest.json,从切片清单按 player、blocks-and-targets、obstacles-and-scene、feedback-effects 四种 usage 加载四个不同的独立透明素材,并在活动 Canvas 中通过 drawImage 绘制对应核心玩家、方块/目标、障碍/场景和反馈;不得猜测整张图集是等分网格,不得把整张图集作为 img、CSS background 或完整 drawImage 展示,也不得以纯代码几何替代核心实体。图集、四类切片或其可见使用任一缺失时不得交付。完成最后一处项目写入后,由你在同一 Run 执行 `game.static_smoke`,再执行 desktop 与 mobile `preview.validate`;不得把接入、检查或试玩交回固定验证节点。首次实现或最小修复后不要继续扩写功能。
HTML 必须满足固定试玩合同,包含真实 Canvas 游戏循环、键盘与触控输入、开始、主要操作、重开、胜负状态和移动端布局。assets/art-spec.png 只能作为视觉规范与派生参考,不得在运行时加载、铺作背景或裁切实体。必须等待已登记且透明有效的 assets/art-spritesheet.png 与 assets/art-spritesheet-slices/manifest.json,从切片清单按 player、blocks-and-targets、obstacles-and-scene、feedback-effects 四种 usage 加载四个不同的独立透明素材,并在活动 Canvas 中通过 drawImage 绘制对应核心玩家、方块/目标、障碍/场景和反馈;不得猜测整张图集是等分网格,不得把整张图集作为 img、CSS background 或完整 drawImage 展示,也不得以纯代码几何替代核心实体。图集、四类切片或其可见使用任一缺失时不得交付。首次实现或最小修复后不要继续扩写功能;Runtime 会自动执行静态自检并在通过后立即试玩
@@ -1,3 +1,3 @@
game-chat 的固定关键词和资产探测只作为 `advisoryOnly=true` 的补充上下文,不能直接选择、重置或跳过 manifest 节点。当前根 Run 尚无工作流决策时,你必须先自行理解用户真正要做的事,并把本轮唯一动作设为 `agent.route_manifest``strategy=audit-existing-first, missingAssetSlots=[]` 是固定执行安全策略;`intentSummary` 必须由你概括用户意图,例如“把已有美术资源接入当前游戏”或“按用户要求刷新整体视觉方向”,不得照抄固定信号代替理解。这个动作只记录意图,不代表生成许可,也不能把整体重做解释成整套美术的强制重生成。不得根据关键词自行声称已有资产完整,也不得在该结构化决策前委派或调度美术 Agent。
game-chat 的固定关键词和资产探测只作为 `advisoryOnly=true` 的补充上下文,不能直接选择、重置或跳过 manifest 节点。当前根 Run 尚无工作流决策时,你必须先理解用户目标,并把本轮唯一动作设为 `agent.route_manifest`通常选择 `strategy=audit-existing-first, missingAssetSlots=[]`,让程序侧先核对现有游戏真正需要且已经可用的正式美术;只有用户明确要求整体重做或替换成全新美术时才选择 `strategy=regenerate-art, missingAssetSlots=[]`。不得根据关键词自行声称已有资产完整,也不得在该结构化决策前委派或调度美术 Agent。
这一步只持久化用户意图和单主路径,不审计资产、不代替 `code-prototype` 判断缺口,也不得创建 `design-director``code-director``art-director``art-asset-plan`、试玩或其它固定首波节点。持久路由后 Runtime 只启动同一根 Run 的 `code-prototype`。它先以 `asset.list` 取得权威资产、Canvas 登记和可复用状态;只有该审计证明 `art-spec` 或核心 spritesheet 确实缺失,才可由该主 Agent 向对应美术角色发起一次受限的 durable 委派。美术 child 仅可写 `assets/**`,回执由同一 `code-prototype` 认领后恢复其原 Run 接入、静态检查和桌面/移动试玩。完整覆盖时不得生成、委派或扣费;整体重做意图同样必须经过这次审计,不能绕过资产复用或授权整套美术重生成。Runtime 负责校验根/父子身份、路径、Canvas 登记、合同指纹缺口一致性和写入范围,但不替你解释用户意图或生成美术
程序侧审计会在同一根 Run 中提交覆盖合同:现有正式资源覆盖完整时直接开放 code-prototype 接入;存在明确缺口时只开放缺口对应的美术 owner,补齐后再恢复 code-prototype。Runtime 负责校验路径、Canvas 登记、合同指纹缺口一致性,但不替你解释用户意图。
@@ -1240,13 +1240,13 @@ pub(in crate::agent) async fn request_platform_art_asset_with_runtime_options_at
.then(|| canonical_art_spec_reference_at(root, &canvas_context.project_id))
.transpose()?;
let (endpoint, request_body) = if is_canonical_art_spritesheet {
let reference_id = canonical_reference
let reference_image_src = canonical_reference
.as_deref()
.ok_or_else(|| "透明美术图集缺少规范图引用".to_string())?;
(
"/api/external/v1/editor/icon-spritesheets/generations",
serde_json::json!({
"referenceId": reference_id,
"referenceImageSrc": reference_image_src,
"iconDescriptions": canonical_art_spritesheet_icon_descriptions(&generation_prompt),
"screenColor": "auto",
"aspectRatio": options.aspect_ratio,
@@ -5710,10 +5710,22 @@ mod canvas_generation_tests {
stream
.set_read_timeout(Some(Duration::from_secs(10)))
.expect("set request read timeout");
let deadline = std::time::Instant::now() + Duration::from_secs(10);
let mut bytes = Vec::new();
let mut buffer = [0_u8; 4096];
loop {
let read = stream.read(&mut buffer).expect("read request bytes");
let read = match stream.read(&mut buffer) {
Ok(read) => read,
Err(error)
if matches!(
error.kind(),
std::io::ErrorKind::WouldBlock | std::io::ErrorKind::TimedOut
) && std::time::Instant::now() < deadline =>
{
continue;
}
Err(error) => panic!("read request bytes: {error}"),
};
if read == 0 {
break;
}
@@ -344,10 +344,11 @@ pub(super) fn platform_art_generation_runtime_request_snapshot(
(generation_kind, reference_resource_ids)
}
"/api/external/v1/editor/icon-spritesheets/generations" => {
let reference_resource_id = json_string_field(&request_body, "referenceId")
.or_else(|| json_string_field(&request_body, "referenceImageSrc"))
let reference_resource_id = json_string_field(&request_body, "referenceImageSrc")
.filter(|value| !value.trim().is_empty())
.ok_or_else(|| "External Editor 图集生成账本请求缺少 referenceId".to_string())?;
.ok_or_else(|| {
"External Editor 图集生成账本请求缺少 referenceImageSrc".to_string()
})?;
("icon-spritesheet".to_string(), vec![reference_resource_id])
}
_ => return Err("External Editor 生成账本 endpoint 不受支持".to_string()),
@@ -867,35 +868,6 @@ mod external_generation_state_tests {
)
.expect("prepare generation ledger");
assert!(created);
let mut spritesheet_state = prepared.clone();
spritesheet_state.endpoint =
"/api/external/v1/editor/icon-spritesheets/generations".to_string();
spritesheet_state.request_body_json = serde_json::json!({
"referenceId": "resource-icon-spec",
"iconDescriptions": ["玩家主体"],
"projectId": "canvas-project",
"assetFolderId": "asset-folder"
})
.to_string();
assert_eq!(
platform_art_generation_runtime_request_snapshot(&spritesheet_state)
.expect("parse current icon spritesheet ledger")
.reference_resource_ids,
vec!["resource-icon-spec"]
);
spritesheet_state.request_body_json = serde_json::json!({
"referenceImageSrc": "legacy-resource-icon-spec",
"iconDescriptions": ["玩家主体"],
"projectId": "canvas-project",
"assetFolderId": "asset-folder"
})
.to_string();
assert_eq!(
platform_art_generation_runtime_request_snapshot(&spritesheet_state)
.expect("parse already persisted legacy icon spritesheet ledger")
.reference_resource_ids,
vec!["legacy-resource-icon-spec"]
);
let mut invalid_key = prepared.clone();
invalid_key.idempotency_key = "invalid key".to_string();
assert!(
@@ -519,7 +519,7 @@ fn game_creator_art_asset_plan_tool_plan_prompt(
);
}
format!(
"{prompt}\n\n你负责首版美术素材实际生成。资产清单和美术计划只是中间结果;最终必须调用 canvas.asset_generate 生成并登记 assets/art-spritesheet.png,固定使用 1:1、1K、assetKind=art-spritesheet、assetLabel=游戏首版核心美术素材、replaceExisting=false,并写入可解析的 assets/manifest.art.json。调用前必须用 asset.list 确认 assets/art-spec.png 已登记为当前项目的 icon-spec 画布资源,并依据当前任务、game/game_design.md 与 manifest 逐项说明真实需要的玩家主体及朝向/状态、目标或收集物、障碍/场景元素和反馈特效,由 Runtime 形成 iconDescriptions;不得假设为塔防或加入合同中不存在的单位、敌人、波次、卡牌。Runtime 固定以规范图的权威 resourceId 作为 referenceId,调用 POST /api/external/v1/editor/icon-spritesheets/generations,并用 screenColor=auto 完成透明后处理;不得把 UI 原型、Data URL、Blob URL、本地路径或结构化 JSON 冒充规范图引用,不得回退普通生图或 UI extraction。缺少规范图时必须等待 art-director 依赖并如实阻塞。成功后回读 observation 与 asset.list,核对服务端返回的透明 spritesheet、真实 alpha、warning 和 sliceWarning。warning.code=postprocess-failed-source-preserved 时没有透明图集,不得登记、验收或自动重试;仅 sliceWarning 时可保留完整透明图集,但不得声称独立切片已生成。已有有效同路径资产时不得重复生成或扣费;只有带 repairOfDelegationId 的唯一返工轮可 replaceExisting=true 原位替换。不得运行 game.static_smoke 或 preview.validate,也不得编辑 game/index.html。图片生成未配置、待确认、失败或透明证据不足时不得提交最终回复。"
"{prompt}\n\n你负责首版美术素材实际生成。资产清单和美术计划只是中间结果;最终必须调用 canvas.asset_generate 生成并登记 assets/art-spritesheet.png,固定使用 1:1、1K、assetKind=art-spritesheet、assetLabel=游戏首版核心美术素材、replaceExisting=false,并写入可解析的 assets/manifest.art.json。调用前必须用 asset.list 确认 assets/art-spec.png 已登记为当前项目的 icon-spec 画布资源,并依据当前任务、game/game_design.md 与 manifest 逐项说明真实需要的玩家主体及朝向/状态、目标或收集物、障碍/场景元素和反馈特效,由 Runtime 形成 iconDescriptions;不得假设为塔防或加入合同中不存在的单位、敌人、波次、卡牌。Runtime 固定以规范图的权威 resourceId 作为 referenceImageSrc,调用 POST /api/external/v1/editor/icon-spritesheets/generations,并用 screenColor=auto 完成透明后处理;不得把 UI 原型、Data URL、Blob URL、本地路径或结构化 JSON 冒充规范图引用,不得回退普通生图或 UI extraction。缺少规范图时必须等待 art-director 依赖并如实阻塞。成功后回读 observation 与 asset.list,核对服务端返回的透明 spritesheet、真实 alpha、warning 和 sliceWarning。warning.code=postprocess-failed-source-preserved 时没有透明图集,不得登记、验收或自动重试;仅 sliceWarning 时可保留完整透明图集,但不得声称独立切片已生成。已有有效同路径资产时不得重复生成或扣费;只有带 repairOfDelegationId 的唯一返工轮可 replaceExisting=true 原位替换。不得运行 game.static_smoke 或 preview.validate,也不得编辑 game/index.html。图片生成未配置、待确认、失败或透明证据不足时不得提交最终回复。"
)
}
@@ -685,7 +685,7 @@ mod tests {
#[test]
fn runtime_prompt_bundle_manifest_covers_all_embedded_sections() {
assert_eq!(RUNTIME_PROMPT_BUNDLE_ID, "genarrative.agent-runtime");
assert_eq!(RUNTIME_PROMPT_BUNDLE_VERSION, "2026-08-07.2");
assert_eq!(RUNTIME_PROMPT_BUNDLE_VERSION, "2026-08-06.1");
assert_eq!(
RUNTIME_PROMPT_RUNTIME_COMPOSITION,
&[
@@ -707,6 +707,7 @@ mod tests {
"isolatedAgentContract",
"platformDefault",
"platformLinux",
"codeDirectorGameChatAssetAudit",
"codePrototypeGameChat",
"projectSupervisorGameChatRouting",
"providerIsolatedToolContract",
@@ -739,25 +740,24 @@ mod tests {
Some(AGENT_RUNTIME_SUPERVISOR_GAME_CHAT_SOURCE),
);
assert!(supervisor.contains("固定关键词和资产探测只作为"));
assert!(supervisor.contains("必须先自行理解用户真正要做的事"));
assert!(supervisor.contains("intentSummary"));
assert!(supervisor.contains("必须先理解用户目标"));
assert!(supervisor.contains("agent.route_manifest"));
assert!(!supervisor.contains("第一步必须调用 `asset.list`"));
let code_prototype = game_creator_agent_runtime_role_overlay_prompt(
"code-prototype",
let code_director = game_creator_agent_runtime_role_overlay_prompt(
"code-director",
Some(AGENT_RUNTIME_SUPERVISOR_GAME_CHAT_SOURCE),
);
assert!(code_prototype.contains("第一步必须调用 `asset.list`"));
assert!(code_prototype.contains("不得仅凭用户措辞"));
assert!(code_prototype.contains("child 只能写 `assets/**`"));
assert!(code_director.contains("第一步必须调用 `asset.list`"));
assert!(code_director.contains("missingAssetSlots"));
assert!(code_director.contains("只凭用户措辞猜测缺口"));
assert!(game_creator_agent_runtime_role_overlay_prompt(
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
None,
)
.is_empty());
assert!(game_creator_agent_runtime_role_overlay_prompt("code-prototype", None).is_empty());
assert!(game_creator_agent_runtime_role_overlay_prompt("code-director", None).is_empty());
}
#[test]
@@ -1224,7 +1224,7 @@ mod tests {
assert!(with_canvas.contains("由 Runtime 形成 iconDescriptions"));
assert!(with_canvas.contains("玩家主体及朝向/状态"));
assert!(with_canvas.contains("不得假设为塔防"));
assert!(with_canvas.contains("权威 resourceId 作为 referenceId"));
assert!(with_canvas.contains("权威 resourceId 作为 referenceImageSrc"));
assert!(with_canvas.contains("POST /api/external/v1/editor/icon-spritesheets/generations"));
assert!(with_canvas.contains("不得回退普通生图或 UI extraction"));
assert!(with_canvas.contains("screenColor=auto"));
@@ -61,15 +61,6 @@ pub(crate) async fn execute_game_creator_agent_runtime_tool_action_with_pending_
if let Some(blocker) = supervisor_orchestrator_mutation_block_at(root, agent_id, run_id, tool) {
return blocker;
}
if let Some(blocker) = game_chat_delegated_art_agent_input_mutation_block(
root,
agent_id,
run_id,
tool,
&action.input,
) {
return blocker;
}
if let Some(blocker) =
supervisor_orchestrator_mcp_mutation_block_at(root, agent_id, run_id, action).await
{
@@ -629,11 +629,6 @@ pub(crate) fn validate_agent_runtime_autonomous_plan_liveness(
.actions
.iter()
.any(|action| action.tool.trim() == "agent.delegate");
let has_code_asset_route = agent_id == "code-prototype"
&& plan
.actions
.iter()
.any(|action| action.tool.trim() == "agent.route_manifest");
if agent_id == GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID {
if let Some(failed_playtest_revision) = verification_gate.failed_playtest_revision {
if project_revision < failed_playtest_revision {
@@ -770,7 +765,6 @@ pub(crate) fn validate_agent_runtime_autonomous_plan_liveness(
|| mutation_revision.is_some()
|| !plan.response.trim().is_empty()
|| has_mutation
|| has_code_asset_route
{
return Ok(());
}
@@ -819,10 +813,9 @@ pub(in crate::agent) fn validate_agent_runtime_autonomous_read_only_delivery_pla
| "command.output_read"
| "command.poll"
| "image.inspect" => false,
"agent.route_manifest" => agent_id != "code-prototype",
"preview.validate" => agent_id != "preview-playtest" && agent_id != "code-prototype",
"preview.validate" => agent_id != "preview-playtest",
"command.run_limited" => {
agent_id != "preview-readiness" && agent_id != "code-prototype"
agent_id != "preview-readiness"
|| action
.input
.get("commandId")
@@ -1778,14 +1771,6 @@ mod tests {
serde_json::json!({"commandId": "game.static_smoke"}),
);
let preview = plan_for("preview.validate", serde_json::json!({}));
let route_manifest = plan_for(
"agent.route_manifest",
serde_json::json!({
"strategy": "use-existing-art",
"intentSummary": null,
"missingAssetSlots": [],
}),
);
assert!(validate_agent_runtime_autonomous_read_only_delivery_plan(
"preview-readiness",
@@ -1799,38 +1784,6 @@ mod tests {
&preview,
)
.is_ok());
assert!(validate_agent_runtime_autonomous_read_only_delivery_plan(
"code-prototype",
true,
&route_manifest,
)
.is_ok());
let verification_gate = AgentRuntimeVerificationGate {
schema_version: "test".to_string(),
project_id: "test".to_string(),
agent_id: "code-prototype".to_string(),
run_id: "test".to_string(),
requires_verification: false,
mutation_revision: None,
verified_revision: None,
last_mutation_tool: None,
last_verification_tool: None,
last_verification_status: None,
static_smoke_verified_revision: None,
failed_playtest_revision: None,
updated_at: 0,
};
assert!(validate_agent_runtime_autonomous_plan_liveness(
"code-prototype",
AGENT_RUNTIME_AUTONOMOUS_PRE_MUTATION_LOOP_LIMIT + 1,
0,
&verification_gate,
&[],
&route_manifest,
false,
false,
)
.is_ok());
for (agent_id, plan) in [
("quality-review", &smoke),
("quality-review", &preview),

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