Compare commits

...

211 Commits

Author SHA1 Message Date
kdletters fafe6b63cd 修复AGC模型请求与对话登录态自动续期 (#329)
Project CI / Repository checks (push) Successful in 2m40s
Project CI / Frontend tests (push) Successful in 3m23s
Project CI / Backend tests (push) Successful in 5m46s
Project CI / Native shell tests (push) Successful in 18m30s
## 变更说明

- AGC 客户端配套后端 API 在 401 时自动刷新登录态并重试一次,覆盖模型目录请求。
- DirectProject 对话鉴权失效时刷新平台会话、同步 Rust/Runner 凭据并重试同一 clientTurnId。
- 403 权限拒绝不触发续期;账号切换后不重发旧请求。
- 补充并发续期、失败保留原错误、权限边界和 Direct 对话重试测试。

## 验证

- npm run test -- apps/ai-game-creator-shell/tests/appSurface.test.ts apps/ai-game-creator-shell/tests/clientApi.test.ts apps/ai-game-creator-shell/tests/clientHttp.test.ts apps/ai-game-creator-shell/tests/conversationModelSelect.test.tsx
- npm run agc:typecheck
- npm run check:encoding
- git diff --check

Reviewed-on: #329
Co-authored-by: kdletters <kdletters@qq.com>
Co-committed-by: kdletters <kdletters@qq.com>
2026-09-11 18:09:48 +08:00
lhk229 11c8cbdf67 Merge pull request '修复 AGC Direct 写通道项目锁零等待与持锁方不可诊断' (#320) from fix/issue-318-direct-write-lock-wait into master
Project CI / Repository checks (push) Successful in 3m7s
Project CI / Frontend tests (push) Successful in 3m41s
Project CI / Backend tests (push) Successful in 5m48s
Project CI / Native shell tests (push) Successful in 17m34s
Reviewed-on: https://git.genarrative.world/git/GenarrativeAI/Genarrative/pulls/320
2026-09-10 21:43:37 +08:00
suzmii ff42fff61c 按评审意见把写锁等待挪出 runtime worker 并收紧 wait_exhausted 记账
Project CI / Repository checks (pull_request) Successful in 3m18s
Project CI / Frontend tests (pull_request) Successful in 4m3s
Project CI / Backend tests (pull_request) Successful in 7m25s
Project CI / Native shell tests (pull_request) Successful in 19m35s
- agc_write_file 写路径改经 bridge_write_file_in_blocking_pool 走 tokio::task::spawn_blocking:有界等待是同步轮询(最多约 10 秒),直接在 async handler 里跑会占住 tokio worker,争用窗口内同一轮并行写多个文件时会波及共享同一 runtime 的只读端点与 UI 命令
- 新增用例用默认 current_thread runtime 加心跳任务锁住该性质;把 handler 临时改回同步直调时该用例按预期失败,确认有区分度
- project.write_lock.wait_exhausted 与终态改判一起改为只在真的等过(max_attempts > 1)时发生:hydrate 的单次试探不再写 waitedMs 近似 0 的“耗尽”日志
- 技术方案、decision-log、pitfalls 同步这两条,并补“同步有界等待不能直接跑在 async handler 里”的排障经验
2026-09-10 21:38:48 +08:00
lhk229 e8c5d2e247 Merge branch 'master' into fix/issue-318-direct-write-lock-wait
Project CI / Repository checks (pull_request) Successful in 2m58s
Project CI / Frontend tests (pull_request) Successful in 3m45s
Project CI / Backend tests (pull_request) Successful in 7m5s
Project CI / Native shell tests (pull_request) Successful in 17m13s
2026-09-10 21:13:23 +08:00
lhk229 ab86efa421 Merge pull request '停止追踪误提交的本地文档' (#323) from rm/local-docs into master
Project CI / Native shell tests (push) Has been cancelled
Project CI / Frontend tests (push) Has been cancelled
Project CI / Backend tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Reviewed-on: https://git.genarrative.world/git/GenarrativeAI/Genarrative/pulls/323
2026-09-10 21:12:38 +08:00
lhk229 a12d18c1cd 停止追踪误提交的本地文档
Project CI / Repository checks (pull_request) Successful in 2m30s
Project CI / Frontend tests (pull_request) Successful in 3m19s
Project CI / Backend tests (pull_request) Successful in 6m6s
Project CI / Native shell tests (pull_request) Successful in 18m2s
从仓库索引移除 local-docs 下的 11 份本地过程文档
保留当前工作区本地文件,不新增或修改忽略规则
2026-09-10 13:00:26 +00:00
suzmii 71e9ad3133 修复锁失败终态判据漏传平台导致 CI 把权限改判成争用
Project CI / Repository checks (pull_request) Successful in 6m17s
Project CI / Frontend tests (pull_request) Successful in 8m23s
Project CI / Backend tests (pull_request) Successful in 12m7s
Project CI / Native shell tests (pull_request) Successful in 25m6s
- project_write_lock_permission_is_ambiguous 改为按平台加原始错误码判定(Windows 的 ACCESS_DENIED(5));只看 ErrorKind 在 Linux 上会得出相反结论:errno 5 在 Windows 是 ACCESS_DENIED、在 Linux 是 EIO
- ProjectWriteLockFailure::Retryable 携带 platform,终态改判与争用文案都用同一次分类的平台,不再依赖宿主 errno 语义
- 终态投影用例显式用 Windows 平台构造失败并补 Unix 反例,两个平台上结论一致;CI 首次推送正是在此失败(2358 passed / 1 failed,left contention / right permission_denied)
- pitfalls 补充“判据的每一环都要带平台”的排障经验
2026-09-10 20:46:10 +08:00
suzmii 06f738dc99 将项目写锁从 project/filesystem.rs 纯搬移到 project/write_lock.rs
Project CI / Repository checks (pull_request) Successful in 2m37s
Project CI / Frontend tests (pull_request) Successful in 3m30s
Project CI / Backend tests (pull_request) Successful in 6m36s
Project CI / Native shell tests (pull_request) Failing after 13m57s
- 新建 project/write_lock.rs:取锁、等待分类、持锁方诊断、残留回收与 4 条锁用例整体搬移,逻辑不变
- project/filesystem.rs 只保留项目文件 IO(1533 → 680 行),锁相关常量、结构、进程判据与用例全部移出
- project.rs 注册 mod write_lock 并 pub(crate) use write_lock::*,crate::project:: 与 crate:: 既有路径不变
- windows_metadata_is_reparse_point 提为 pub(crate),供 write_lock 复用同一条 reparse point 判据
- agent_db.rs 与 checkpoint.rs 的 PROJECT_FILE_FLAG_OPEN_REPARSE_POINT 导入路径改为 super::write_lock
- 同步修正技术方案、Fast GDD 技术方案、decision-log、pitfalls 中指向锁实现的文件路径,并把“拆锁”从后续事项改为已完成
2026-09-10 20:12:28 +08:00
suzmii 8c639e5d13 修复项目写锁重试判据用一次元数据观察误判瞬时争用
- 项目写锁分类改为只按错误码判定重试性,不再用 path.exists() 决定“要不要等”:真机 6 万次建锁/删锁竞争实测 396-538 例命中“ACCESS_DENIED(5) + 目标不可见”,旧判据会让等待层立刻失败关闭,把毫秒级竞争换成更误导的 ACL 文案
- 新增 ProjectWriteLockFailure(Retryable / Terminal)与 acquire_project_write_lock_failure,有界等待改按类型分流,acquire_project_write_lock 退化为它的文案包装
- Windows 的 ACCESS_DENIED(5) 终态改判移到等待预算耗尽之后:只有真的等过预算且目标此刻仍不存在时才投影成权限拒绝,单次试探保持争用语义
- 锁分类判据改为平台参数传入(project_write_lock_open_failure_for),Linux CI 可覆盖 Windows 分支;替换原先只在 Windows 本地执行的分类用例
- 零等待入口在错误码不可区分时补一句“可能是删除挂起、删除拆链窗口或权限 / ACL 拒绝”,争用前缀逐字不变,调用方既有重试语义不受影响
- PROJECT_WRITE_LOCK_CONTENTION_PREFIX 收口 provider_recovery.rs、planning_session_v2.rs、direct_runtime.rs 三处手写文案
- project.write_lock.wait_exhausted 日志补 projection= 分类,attempts 记为实际尝试次数
- 同步更新技术方案、decision-log、pitfalls,并修正 ownerIsSelf 只比 PID 的表述
2026-09-10 20:07:28 +08:00
suzmii 59cab3d9fd Merge remote-tracking branch 'origin/master' into fix/issue-318-write-lock-classification 2026-09-10 19:58:05 +08:00
lhk229 89447ed432 按评审意见强化回退模板读取通道回归测试
Project CI / Repository checks (pull_request) Successful in 2m47s
Project CI / Frontend tests (pull_request) Successful in 3m44s
Project CI / Backend tests (pull_request) Successful in 7m1s
Project CI / Native shell tests (pull_request) Successful in 18m11s
Project CI / Backend tests (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
Project CI / Frontend tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
- tests/configuration.rs 回退模板测试改用与模板无关的 runtime dir,分类断言覆盖按路径归属而非 runtime dir 为 None 的恒真分支,并补充 runtime dir 内路径的 managed 正向断言
- tests/mod.rs 移除不再使用的 clear_test_runtime_config_dir 辅助
2026-09-10 09:46:48 +00:00
lhk229 3ebfcc0c2f 修正新增配置读取回归测试的 Rust 格式
Project CI / Repository checks (pull_request) Successful in 2m16s
Project CI / Frontend tests (pull_request) Successful in 3m8s
Project CI / Backend tests (pull_request) Successful in 7m2s
Project CI / Native shell tests (pull_request) Successful in 18m27s
- tests/configuration.rs 按 cargo fmt 调整两个 assert! 宏换行
2026-09-10 09:03:19 +00:00
lhk229 9b5d1fe107 修复仓库回退配置模板被读取通道私有化 ACL 锁定
Project CI / Frontend tests (pull_request) Successful in 3m30s
Project CI / Backend tests (pull_request) Successful in 6m27s
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
- config.rs 新增 game_creator_config_path_is_runtime_managed 判定,read_game_creator_config_file 按路径归属分流:AppData 托管目录内的真实凭据维持私有加固读取,仓库旁边的回退模板与 local 覆盖改用 open_project_snapshot_regular_file 非变异快照通道
- 根因:开发 CLI 无 AppHandle 时回退读取 worktree 内 git 跟踪模板,私有读通道在 Windows 上无条件收紧 DACL 为仅当前进程用户,导致其他账号与 cargo include_str! 全部 Access Denied
- tests/mod.rs 新增 clear_test_runtime_config_dir 辅助
- tests/configuration.rs 新增两个回归测试锁定快照 / 私有两条读取通道的分流
- pitfalls.md 记录该排障经验与读取通道副作用白名单教训
2026-09-10 08:57:31 +00:00
suzmii 4ecab19429 修正 CI 权限用例在 root 容器下的前提并补平台无关的分类判据
Project CI / Repository checks (pull_request) Successful in 3m5s
Project CI / Frontend tests (pull_request) Successful in 4m2s
Project CI / Backend tests (pull_request) Successful in 6m51s
Project CI / Native shell tests (pull_request) Successful in 19m16s
- project_write_lock_does_not_project_permission_denial_as_contention 不再用 expect_err 断言“只读目录必须挡住取锁”:CI 容器以 root 运行,0o500 不生效,取锁会正常成功;此时跳过端到端前提
- 新增平台无关用例 project_write_lock_classifies_by_whether_the_target_exists:目标存在才是争用、目标不存在却创建失败是权限拒绝、NotFound 归其它
- 让权限分类判据在不依赖 ACL 环境的条件下也有回归护栏,避免只靠会被 root 绕过的端到端用例
2026-09-10 16:52:30 +08:00
suzmii 81c2389132 修复 AGC Direct 写通道项目锁零等待与持锁方不可诊断
Project CI / Repository checks (pull_request) Successful in 3m9s
Project CI / Frontend tests (pull_request) Successful in 4m7s
Project CI / Backend tests (pull_request) Successful in 6m54s
Project CI / Native shell tests (pull_request) Failing after 14m11s
- Direct 写路径(agc_write_file)改用统一的有界等待,与 file.write / file.patch / file.delete 同语义,同一轮并行写多个文件按同一把锁串行,不再在 24-42ms 内把重叠判成"项目正在被其他写操作占用"
- create_new 失败拆成争用 / 权限拒绝 / 其它三类:锁文件不存在却仍创建失败不再投影成争用;sharing violation(32) 与 lock violation(33) 恒定归争用
- 争用错误与等待日志带上持锁方身份(commandId / pid / createdAt / ownerIsSelf),锁文件处于删除挂起或未写完时显式表达成"身份不可读"
- ProjectWriteLockSnapshot 补 commandId 与 describe_holder(),沿用既有快照 + 字节 CAS 回收机制,不新增第二套回收判据
- 有界等待预算耗尽记 project.write_lock.wait_exhausted(含等待毫秒数),权限拒绝记 project.write_lock.permission_denied,争用不在零等待入口里逐次记账
- 新增同进程重叠写等待、同轮并行写、活外部进程持锁带身份、权限拒绝分类、ACL 拒绝不投影成争用五条回归用例
- 同步 decision-log、pitfalls 与技术方案文档
2026-09-10 16:30:22 +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 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 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 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 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
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
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
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
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 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
suzmii a0e6a83691 修复 AGC 模型选择入口、交互与默认模型回退 (#299)
Project CI / Backend tests (push) Failing after 7m10s
Project CI / Repository checks (push) Successful in 2m19s
Project CI / Frontend tests (push) Successful in 3m32s
Project CI / Native shell tests (push) Failing after 19m48s
### 背景

AGC 后台已支持维护 LLM 模型目录(`agc_model_catalog`),客户端此前只在项目右侧对话浮出模型选择,且存在几个体验问题:

1. 首页聊天框架缺少模型选择入口;
2. 首次进入项目时,右侧模型选择器「点不动」(被 disabled);
3. 模型选择下拉失去焦点不自动收起;
4. 默认模型缺少可视标识;
5. 已保存的选择被后台停用/移除后,客户端显示「请选择模型」,未自动回退默认模型;首页因懒加载在打开前也一直显示「选择模型」。

### 改动内容(完整变更集)

- **首页聊天框架新增模型选择入口**,复用项目右侧对话的 `ConversationModelSelect`;上传按钮在左,模型选择器与发送按钮在右(同一组,模型选择器紧挨发送按钮)。
- **修复首次进入项目时右侧模型选择器点不动**:选择器不再因目录加载(`busy`)或对话进行中(`controlBusy`)被禁用;切换模型只写回客户端配置并作用于后续轮次,发送仍由 `controlBusy` / `modelReady` 把关。
- **模型下拉支持点击外部 / Esc 自动收起**:给组件容器挂 ref,监听 `document` 的 `mousedown` 与 `keydown`,点击容器外或按 Esc 即收起(仅在展开时挂监听)。
- **默认模型增加「默认」标识**:组件记录 `defaultModelId`,在下拉选项中给默认模型显示小标签;亮暗主题自适应。
- **模型选择始终回退默认模型**:已保存选择仍可用时沿用;被停用/移除或从未选择时回退到后台默认模型并写回配置;只有默认模型本身不可用才走错误分流。首页由「懒加载」改为「挂载即加载」,触发按钮直接落在默认模型上,不再出现「选择模型」空态。
- **清理**:移除组件中已无调用方的 `lazy` 加载路径。
- **文档**:同步更新 `【技术方案】AGC后台模型别名与对话选择-2026-09-05.md`(首页入口、对话进行中可切换模型、失焦收起等行为)。

### 关键交互/行为说明

- 触发按钮永远显示模型名(默认或当前选择),不再出现「选择模型」占位,除非默认模型不可用。
- 模型下拉的加载中、刷新、选中态沿用原有交互;默认模型有「默认」标签,便于识别。
- 首页模型选择器不改动「开启创作」门控:目录不可用时仍可创建项目并走后台默认项。

### 验证

- `npm run test -- apps/ai-game-creator-shell/tests/conversationModelSelect.test.tsx` -> 7/7 通过
- `npm run test -- apps/ai-game-creator-shell/tests/appSurface.test.ts` -> 386/386 通过
- `npm run ai-game-creator-shell:typecheck` 通过
- `npm run check:encoding`(4309 文件)通过
- `git diff --check` 通过

### 影响范围与注意事项

- 变更集中在 AGC 客户端(`apps/ai-game-creator-shell`),不涉及 SpacetimeDB schema / 后端接口 / 契约。
- `GET /api/llm/models` 语义不变(仍只返回启用项 + 默认项),本次不改后端。
- 模型下拉在窄屏的换行/遮挡,以及「默认」标签对比度,建议在真机确认一下观感(单测只覆盖逻辑)。

### 关联

- 无关联 Issue;本 PR 独立提交。

---------

Co-authored-by: 段舒康 <kdletters@qq.com>
Reviewed-on: #299
Co-authored-by: 董羽秦 <suzmii@qq.com>
Co-committed-by: 董羽秦 <suzmii@qq.com>
2026-09-08 22:05:15 +08:00
k88936 d3f5d0a355 feat/AGC codex的工具调用 持久化处理 (#282)
Project CI / Repository checks (push) Successful in 2m7s
Project CI / Frontend tests (push) Successful in 2m46s
Project CI / Backend tests (push) Failing after 3m53s
Project CI / Native shell tests (push) Failing after 3m50s
重构directproject的thread持久化,  保存response items用来恢复thread

修改了DirectProject的对话jsonl格式,  不兼容不迁移. (测试需要手动清理旧对话jsonl文件)

AGC显式写入user msg, 对codex 返回的user msg忽略
清理原来的上下文滑动窗口, 因为codex内部会自动compact

工具调用后重启,继续对话:
![shotmd-1788579616.jpg](/attachments/8350c924-9498-47cd-b3da-8be6b79696f9)

close #249
close #277

---------

Co-authored-by: 段舒康 <kdletters@qq.com>
Reviewed-on: #282
Co-authored-by: 王德宇 <kvtodev@outlook.com>
Co-committed-by: 王德宇 <kvtodev@outlook.com>
2026-09-08 22:04:37 +08:00
k88936 01e057d047 Feat/AGC对话渲染markdown (#281)
Project CI / Frontend tests (push) Successful in 3m37s
Project CI / Backend tests (push) Failing after 4m13s
Project CI / Native shell tests (push) Failing after 4m11s
Project CI / Repository checks (push) Successful in 3m24s
before:
 ![image.png](/attachments/ca6f46f7-dcbd-4132-bad2-cf5c83c49096)

after:
 ![shotmd-1788500615.jpg](/attachments/719539a1-9dfb-412a-a9b9-298547b1791c)
![shotmd-1788500598.jpg](/attachments/4656815f-caca-4325-bca3-b29fe5742b6e)

考虑到在AGC场景下不是很有理由, 暂不实现链接, 图片, 内嵌html

---------

Co-authored-by: 段舒康 <kdletters@qq.com>
Reviewed-on: #281
Co-authored-by: 王德宇 <kvtodev@outlook.com>
Co-committed-by: 王德宇 <kvtodev@outlook.com>
2026-09-08 22:03:37 +08:00
kdletters 1f904d28e9 AGC 客户端 MCP 能力暴露 (#274)
Project CI / Frontend tests (push) Successful in 4m2s
Project CI / Repository checks (push) Successful in 2m16s
Project CI / Backend tests (push) Successful in 10m14s
Project CI / Native shell tests (push) Failing after 19m54s
## 目标
保留现有客户端对话与 Codex app-server 链路,把客户端自身受控业务能力通过 MCP 暴露给 Codex。

## 范围
- 客户端会话、项目文件、资源、画布、生成、预览等稳定能力
- 审核 Skill 的索引与按需指导资源
- 复用现有账号、项目路径、权限、计费、幂等、锁和恢复边界

## 明确不做
- 不替换客户端对话入口或 Codex app-server
- 不让客户端替 Codex 判断高层意图、完成状态或规划
- 不暴露任意 Tauri command、shell、凭据、内部 URL、数据库和管理能力

当前 PR 先建立独立分支与审查边界,后续提交实现与定向验证。

Reviewed-on: #274
2026-09-08 22:01:29 +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
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 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
kdletters 1498247e9f AGC产物迁移npm与Phaser4
Project CI / Repository checks (push) Successful in 2m54s
Project CI / Native shell tests (push) Failing after 16m56s
Project CI / Frontend tests (push) Successful in 3m4s
Project CI / Backend tests (push) Successful in 8m10s
新增 npm + Vite + Phaser 4.2.1 游戏工程脚手架

允许 DirectProject 在项目边界内安装依赖并执行构建

让预览、静态检查、投影和试玩导出统一使用 dist 产物

补充 Phaser Skill、技术方案、项目记忆和定向测试
2026-09-08 15:11:36 +08:00
suzmii 456cc42dd3 优化泥点流水的素材消耗原因展示 (#302)
Project CI / Repository checks (push) Successful in 1m59s
Project CI / Frontend tests (push) Successful in 3m7s
Project CI / Backend tests (push) Successful in 6m13s
Project CI / Native shell tests (push) Successful in 19m24s
Close #296

## 问题

钱包流水目前只把素材类扣费暴露为 `asset_operation_consume`,共享账单组件统一显示“资产操作消耗”,无法说明本次泥点实际用于哪类素材生成。

## 落地方案

- 在既有钱包流水 metadata 中记录服务端确定的 `assetKind`,不修改 SpacetimeDB schema。
- `api-server` 只将内部素材类型白名单映射为用户可见 `reason`,不暴露原始 metadata、资源 ID 或任务 ID。
- 共享钱包流水契约与组件优先展示具体原因;历史、未知或空 metadata 继续回退为“资产操作消耗”。
- 主站、图片画布与 AGC 继续复用同一共享账单组件。

## 当前进度

- [x] 补充权威后端数据契约
- [x] 写入素材操作类型 metadata
- [x] 扩展钱包流水公开 DTO 与安全映射
- [x] 更新共享账单展示与回归测试
- [x] 完成定向验证与边界检查

## 用户可见映射

- 图片、图标图集、美术规范、UI 设计、发布素材:生成美术素材
- 图片修改:编辑美术素材
- UI 素材提取:提取美术素材
- 角色动画:生成角色动画
- 视频:生成视频素材
- 音效:生成音效素材
- 背景音乐:生成背景音乐
- 历史、未知、空 metadata:资产操作消耗

## 验证

- `cargo fmt --all -- --check`
- `cargo test -p api-server profile_wallet_ledger_reason_only_exposes_known_asset_operations`
- `cargo test -p api-server worker_billing_context_freezes_charge_and_preserves_job_metadata`
- `cargo test -p shared-contracts profile_wallet_ledger_response_uses_camel_case_fields`
- `npx vitest run packages/shared/src/components/PlatformProfileWalletLedgerModal/index.test.tsx`
- `npm run typecheck`
- `npx eslint packages/shared/src/components/PlatformProfileWalletLedgerModal/model.ts packages/shared/src/components/PlatformProfileWalletLedgerModal/index.test.tsx packages/shared/src/contracts/runtime.ts`
- `npm run check:encoding`
- `npm run check:spacetime-schema`
- `git diff --check`

以上均通过;Rust 仅输出仓库已有 dead-code warnings。

## 未验证边界

- 未执行真实登录、生成并读取新钱包流水的端到端联调。本机 `8082/3101` 已由另一套本地栈占用,为避免当前分支挂接同一数据库并带起 worker 处理现有队列,本轮未启动 `npm run dev:api-server`。
- 四项远端 CI 已全部通过,PR 已转为 Ready for review。

## 不变项

- 不修改历史流水数据。
- 不调整泥点价格、扣费顺序、幂等 ledger、失败退款或余额结算。
- 不修改 SpacetimeDB schema。

---------

Co-authored-by: 段舒康 <kdletters@qq.com>
Reviewed-on: http://192.168.35.82/git/GenarrativeAI/Genarrative/pulls/302
Co-authored-by: Suzumiya <suzmii@qq.com>
Co-committed-by: Suzumiya <suzmii@qq.com>
2026-09-08 14:56:16 +08:00
k88936 4db6d28603 UI编辑器一批细节优化 (#278)
Project CI / Repository checks (push) Successful in 3m6s
Project CI / Frontend tests (push) Successful in 3m44s
Project CI / Backend tests (push) Successful in 8m16s
Project CI / Native shell tests (push) Successful in 20m48s
* 提示词:完善组件建议,增加容器背景与节点大小一致的实现
* 支持Delete删除节点 ctrl + -缩放
* 步骤结束弹窗提醒
![shotmd-1788599068.jpg](/attachments/99a1e4aa-d778-41bf-9dda-0df703723407)
![shotmd-1788605840.jpg](/attachments/31ab883d-32bc-467b-9308-616b83387a79)

* UI 树快捷删除
![shotmd-1788605655.jpg](/attachments/3d451201-3f68-4eb6-b558-864ecf0cb5a3)

* zoom滑动条样式

before:
![image.png](/attachments/e4a04304-e6eb-4da6-9352-99e093fee42e)
after:
![shotmd-1788606577.jpg](/attachments/6cdc377c-f2a3-4a7e-b78e-772a3fdb13b7)

Reviewed-on: http://192.168.35.82/git/GenarrativeAI/Genarrative/pulls/278
Co-authored-by: 王德宇 <kvtodev@outlook.com>
Co-committed-by: 王德宇 <kvtodev@outlook.com>
2026-09-08 11:56:36 +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
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 e15e3b455f 修复AGC大上下文请求体限制 (#295)
Project CI / Repository checks (push) Successful in 3m12s
Project CI / Frontend tests (push) Successful in 3m21s
Project CI / Backend tests (push) Successful in 7m42s
Project CI / Native shell tests (push) Successful in 20m14s
Project CI / Frontend tests (pull_request) Successful in 50m43s
Project CI / Repository checks (pull_request) Successful in 20m21s
Project CI / Backend tests (pull_request) Successful in 6m18s
Project CI / Native shell tests (pull_request) Successful in 18m19s
## 背景与问题

AGC(AI 游戏创作智能体 App)Direct Codex 模式在携带图片工具结果等大上下文调用 LLM 时,`/api/llm/responses` 与 `/api/llm/chat/completions` 会命中 Axum 默认 **2 MiB** 请求体上限,直接被 413 拒绝,大上下文任务无法执行;同时 Codex app-server 的 failed turn 未把上游 413 映射为稳定错误分类,前端只能显示笼统的“其他错误”,无法引导用户处理。

## 改动内容

**1. api-server(server-rs)**
- 新增常量 `LLM_REQUEST_MAX_BODY_BYTES = 32 MiB`,作为两个 LLM 代理路由的正式请求体上限;
- 两个路由显式配置 `DefaultBodyLimit::max(32 MiB)`,避免 Axum 默认 2 MiB 提前拒绝;handler 内保留超限检查,超过 32 MiB 仍返回 `413 PAYLOAD_TOO_LARGE`;
- 补充回归测试:>2 MiB 大上下文请求不再命中默认限制;超过 32 MiB 仍返回 413。

**2. AGC 壳(apps/ai-game-creator-shell)**
- `codex_app_server` 错误映射:上游/连接层 HTTP 413、`PAYLOAD_TOO_LARGE`、`provider request too large` 等统一映射为稳定分类 `codex-app-server-error:request-too-large`,不再落入 `other` 或误判为权限/安全策略错误;
- 前端新增用户可见文案“模型请求体过大,请减少参考图或上下文后重试”,并补充单测断言。

**3. 文档**
- 【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md 补充“2026-09-06 AGC LLM 代理请求体合同”:明确 32 MiB 上限、必须显式配置 `DefaultBodyLimit`、413 映射规则与用户文案。

## 验证

- `cargo test -p api-server llm_routes_accept_large_context_bodies_beyond_axum_default` 通过
- `cargo test -p api-server llm_responses_rejects_bodies_above_explicit_limit` 通过
- `cargo test ... ai-game-creator-shell ... codex_app_server_failed_turn_maps_request_too_large_details` 通过
- `npm run test -- apps/ai-game-creator-shell/tests/agentRuntimeModel.test.ts`(27 通过)

全量 workspace 测试与 CI 门禁待推送后由 CI 覆盖。

Reviewed-on: #295
Reviewed-by: 段舒康 <kdletters@qq.com>
Co-authored-by: 董羽秦 <suzmii@qq.com>
Co-committed-by: 董羽秦 <suzmii@qq.com>
2026-09-07 17:05:42 +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
k88936 964290c641 优化错误报告 (#286)
Project CI / Native shell tests (push) Successful in 21m3s
Project CI / Repository checks (push) Successful in 2m37s
Project CI / Frontend tests (push) Successful in 2m41s
Project CI / Backend tests (push) Successful in 7m2s
样式

before:
![shotmd-1788605870.jpg](/attachments/0470de00-944f-466e-a339-cf9e6cc0b780)
![shotmd-1788605755.jpg](/attachments/3e7be68e-31c5-41ba-9f8b-6dc98ec08868)

after:
![shotmd-1788607435.jpg](/attachments/165c878e-5c1c-4517-b7d5-74ce97a1a99b)
![shotmd-1788608364.jpg](/attachments/4dd20baf-879c-4aa8-ab51-bcc588d48dbc)

逻辑:
每次打开报告modal会再次查询队列中的错误
<video src="attachments/6d9d9bd7-5b7b-4a05-a7c3-ca64b6fb77f3" title="2026-09-05 19-46-02.mp4" controls></video>

---------

Co-authored-by: 段舒康 <kdletters@qq.com>
Reviewed-on: http://192.168.35.82/git/GenarrativeAI/Genarrative/pulls/286
Reviewed-by: 段舒康 <kdletters@qq.com>
Co-authored-by: 王德宇 <kvtodev@outlook.com>
Co-committed-by: 王德宇 <kvtodev@outlook.com>
2026-09-07 15:17:21 +08: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 60fbee2353 修复设置页重新出现连接与工具 (#287)
Project CI / Repository checks (pull_request) Failing after 10s
Project CI / Frontend tests (pull_request) Successful in 3m3s
Project CI / Backend tests (pull_request) Failing after 26s
Project CI / Native shell tests (pull_request) Successful in 20m54s
Project CI / Repository checks (push) Successful in 4m37s
Project CI / Frontend tests (push) Successful in 5m31s
Project CI / Backend tests (push) Successful in 8m38s
Project CI / Native shell tests (push) Successful in 20m0s
移除运行时配置中的连接与工具入口及 External Editor 字段

更新 AppSurface 回归断言,确保普通客户端不展示该入口

Reviewed-on: http://192.168.35.82/git/GenarrativeAI/Genarrative/pulls/287
Reviewed-by: 段舒康 <kdletters@qq.com>
Co-authored-by: Linghong <ink29535@proton.me>
Co-committed-by: Linghong <ink29535@proton.me>
2026-09-06 19:47:33 +08: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
kdletters 2975062c69 Merge pull request 'AGC 官方 LLM Router 账号链路与流式联网输出' (#242) from feat/agc-llm-router-official-chain into master
Project CI / Repository checks (push) Successful in 3m22s
Project CI / Frontend tests (push) Successful in 3m16s
Project CI / Backend tests (push) Successful in 6m29s
Project CI / Native shell tests (push) Successful in 18m25s
Reviewed-on: http://192.168.35.82/git/GenarrativeAI/Genarrative/pulls/242
2026-09-06 16:49:13 +08: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
suzmii 6166094a3c 修复AGC配置测试字段
Project CI / Repository checks (pull_request) Successful in 2m46s
Project CI / Frontend tests (pull_request) Successful in 2m55s
Project CI / Backend tests (pull_request) Successful in 7m6s
Project CI / Native shell tests (pull_request) Successful in 18m3s
同步配置测试夹具使用 selected_model_id

移除已删除的 model_profiles 与 selected_model_profile_id 字段
2026-09-06 01:16:48 +08:00
suzmii 736a1b6ac6 接入 LLM Router 累计额度结算
Project CI / Repository checks (pull_request) Successful in 2m36s
Project CI / Frontend tests (pull_request) Successful in 3m15s
Project CI / Backend tests (pull_request) Successful in 7m35s
Project CI / Native shell tests (pull_request) Failing after 7m43s
按 Router used_quota 累计值与首次基线结算泥点
新增原子 checkpoint 事务及 llm_router_consume 钱包流水
同步额度查询校验、前端展示、生成绑定和技术文档
2026-09-06 00:36:31 +08: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
suzmii 78f547d26d Merge remote-tracking branch 'origin/master' into feat/agc-llm-router-official-chain
Project CI / Repository checks (pull_request) Successful in 2m38s
Project CI / Frontend tests (pull_request) Successful in 3m46s
Project CI / Backend tests (pull_request) Successful in 6m35s
Project CI / Native shell tests (pull_request) Failing after 8m14s
# Conflicts:
#	apps/ai-game-creator-shell/tests/appSurface/runtime-settings.suite.ts
2026-09-05 20:08:43 +08:00
suzmii 0b9fb40108 Merge remote-tracking branch 'origin/master' into feat/agc-llm-router-official-chain
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:
#	apps/ai-game-creator-shell/scripts/check-config.mjs
#	apps/ai-game-creator-shell/src/features/runtime-config/RuntimeConfigDialog.tsx
#	apps/ai-game-creator-shell/tests/appSurface/runtime-settings.suite.ts
2026-09-05 20:03:25 +08:00
kdletters c4f344678f 收窄设置页删除改动
Project CI / Repository checks (push) Successful in 2m36s
Project CI / Frontend tests (push) Successful in 3m4s
Project CI / Backend tests (push) Successful in 8m42s
Project CI / Native shell tests (push) Successful in 20m38s
移除多余的页面回归门禁
清理已删除入口的冗余测试断言
2026-09-05 20:01:02 +08: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
suzmii 34e3f70409 补充服务启动配置校验并优化模型菜单
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
启动时校验 LLM Router 地址、模型和相关密钥配置

将刷新操作移入模型展开菜单并修复菜单样式覆盖

更新启动运维文档与定向测试
2026-09-05 19:48:27 +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
suzmii 4f3f0f24ff 接入AGC后台模型目录与对话模型选择
新增后台 AGC 模型目录、别名、启停和默认项管理

客户端设置页恢复原状,对话框右下角按别名选择模型

服务端按稳定模型标识映射并校验实际模型白名单

修复 AGC 配套后端端口漂移、启动等待和 SpacetimeDB 版本检查

补充迁移、文档、启动与模型选择测试
2026-09-05 19:10:49 +08: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
kdletters 524e2f0f4f 删除连接与工具设置页
Project CI / Repository checks (push) Successful in 2m34s
Project CI / Frontend tests (push) Successful in 3m3s
Project CI / Backend tests (push) Successful in 6m53s
Project CI / Native shell tests (push) Successful in 17m53s
移除运行设置中的连接与工具导航及页面内容
更新运行设置测试以校验该入口已删除
同步配置门禁,防止已删除页面重新出现
2026-09-05 17:39:05 +08: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
lhk229 250360a40a 放宽GDD一句话概念校验范围
删除决策首项必须为 confirmed 且 round=0 的阻断校验

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

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

修复 Planning V2 系统提示词参数占位符
2026-09-05 05:00:32 +00: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
suzmii 76f0c8e58e Merge branch 'master' into feat/agc-llm-router-official-chain
Project CI / Repository checks (pull_request) Successful in 3m39s
Project CI / Frontend tests (pull_request) Successful in 4m0s
Project CI / Backend tests (pull_request) Successful in 8m3s
Project CI / Native shell tests (pull_request) Successful in 19m58s
2026-09-04 19:07:08 +08: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
k88936 f9e1e5d2a7 Fix/修复ui editor选择逻辑 (#270)
Project CI / Backend tests (push) Successful in 7m35s
Project CI / Repository checks (push) Successful in 3m2s
Project CI / Frontend tests (push) Successful in 3m24s
Project CI / Native shell tests (push) Successful in 18m52s
close #266
当前实现: 按下就重新选择

实际上应该: 松开而且没有发生拖动才重新选择

before:
![shotmd-1788407266-compressed.webp](/attachments/251eccf6-ae02-4ae6-b9d2-4eb1fbf67df3)

after:
![shotmd-1788418953-compressed.webp](/attachments/5c05b506-ae08-4cb1-83c8-8da8e1590a7f)

Reviewed-on: http://192.168.35.82/git/GenarrativeAI/Genarrative/pulls/270
Co-authored-by: 王德宇 <kvtodev@outlook.com>
Co-committed-by: 王德宇 <kvtodev@outlook.com>
2026-09-04 16:30:43 +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 87c0cbd06d 修复 Provider 代理错误外发 AGC 主站客户端标记
Project CI / Repository checks (pull_request) Successful in 3m13s
Project CI / Frontend tests (pull_request) Successful in 3m34s
Project CI / Backend tests (pull_request) Successful in 7m43s
Project CI / Native shell tests (pull_request) Successful in 19m55s
仅 PlatformSession 的 /api/llm 主站路由携带 x-genarrative-client 标记

通用 Provider 与第三方凭据桥接不再自动附加该标记

补充主站路由代理的标记回归测试
2026-09-04 15:52:47 +08:00
lhk229 45568797f9 将策划 V2 既有阻断校验回灌给 Provider
把当前 question/GDD 硬校验契约写入 V2 system prompt,不扩大校验范围或新增门禁

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

同步 Runtime V2 技术方案和项目决策记录
2026-09-04 07:40:36 +00:00
suzmii 13639d9f8c 修复 AGC 运行时配置模型方案测试断言
Project CI / Repository checks (pull_request) Successful in 4m23s
Project CI / Frontend tests (pull_request) Successful in 5m11s
Project CI / Backend tests (pull_request) Successful in 7m25s
Project CI / Native shell tests (pull_request) Failing after 15m15s
将推理档断言迁移到模型方案管理面板的推理档位选择器

补充持久化测试 fixture 的 modelProfiles 与选中方案字段

同步保存回写断言中的模型方案结构
2026-09-04 15:19:01 +08:00
suzmii 020f7d7a7a 修复 Router 凭据并发一致性问题
Project CI / Repository checks (pull_request) Failing after 8m28s
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
移除 Router Key 进程内 TTL 缓存,改为每次读取权威账号状态并即时解密当前密文

Router 账号写入改为同主键原地更新,避免 delete+insert 读取缺口

新增 owner/route 映射冲突校验,防止并发写入插入重复账号行

同步更新后端数据契约文档中的凭据读取语义
2026-09-04 14:08:16 +08:00
suzmii aa47fa833d Merge remote-tracking branch 'origin/master' into feat/agc-llm-router-official-chain 2026-09-04 13:54:16 +08:00
suzmii 4bbef74859 feat: AGC 支持模型方案选择
新增 Router 模型目录接口并返回可用模型列表

客户端配置新增模型方案与当前选中方案字段

设置页新增模型方案卡片和管理弹层,支持方案名称、模型和推理强度

自定义下拉菜单圆角、遮罩层级与窗口边界定位,并隐藏滚动条

AGC 官方路由保留所选模型并携带客户端标记,由服务端校验后转发

补齐配置结构变更后的既有测试初始化字段
2026-09-04 13:52:00 +08:00
lhk229 8d13e868bd AGC 更新提示增加 X 关闭按钮 (#276)
Project CI / Frontend tests (push) Successful in 8m49s
Project CI / Repository checks (push) Successful in 8m50s
Project CI / Backend tests (push) Successful in 10m3s
Project CI / Native shell tests (push) Failing after 20m52s
## 变更内容
- 为 AGC 更新提示增加可访问的 X 关闭按钮。
- 复用现有 dismiss 逻辑,手动关闭后立即移除提示。
- 增加关闭按钮的 hover、禁用和图标样式。

## 验证
- npm --prefix apps/ai-game-creator-shell run typecheck
- npm exec vitest run apps/ai-game-creator-shell/tests/appUpdate.test.ts
- npm run check:encoding
- git diff --check

Reviewed-on: http://192.168.35.82/git/GenarrativeAI/Genarrative/pulls/276
Reviewed-by: 段舒康 <kdletters@qq.com>
Co-authored-by: 孔令弘 <ink29535@proton.me>
Co-committed-by: 孔令弘 <ink29535@proton.me>
2026-09-04 11:24:21 +08:00
suzmii 938c37ca1b 修复 AGC 本地 Provider Smoke 路由
Project CI / Repository checks (pull_request) Successful in 3m54s
Project CI / Frontend tests (pull_request) Successful in 5m1s
Project CI / Backend tests (pull_request) Successful in 8m26s
Project CI / Native shell tests (pull_request) Successful in 18m51s
允许显式 Debug E2E 开关使用 loopback Provider

为 smoke 子进程透传环境并启用 Debug E2E 开关
2026-09-03 23:27:54 +08:00
suzmii d0d3466a56 修复 Direct MCP 路径文案泄露
Project CI / Repository checks (pull_request) Successful in 4m24s
Project CI / Frontend tests (pull_request) Successful in 4m46s
Project CI / Backend tests (pull_request) Successful in 7m44s
Project CI / Native shell tests (pull_request) Failing after 13m40s
平台无关拒绝盘符、绝对路径和上级目录

避免未审核路径出现在 Direct Codex 活动文案中
2026-09-03 21:42:39 +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
suzmii 75da77dcb4 Merge remote-tracking branch 'origin/master' into feat/agc-llm-router-official-chain
Project CI / Repository checks (pull_request) Successful in 2m31s
Project CI / Backend tests (pull_request) Successful in 8m6s
Project CI / Frontend tests (pull_request) Successful in 3m57s
Project CI / Native shell tests (pull_request) Has been cancelled
2026-09-03 20:07:51 +08:00
suzmii 703b90938b 合并 master 并保留 Router 账号链路整改
合并 origin/master 最新 UI 编辑器与 AGC 运行时变更

保留 external_api_key 恢复为 master 原始结构

保留 LLM Router 凭据统一存储于 llm_router_account 的整改

解决 App.tsx 与 appSurface 测试冲突并完成定向验证
2026-09-03 20:05:50 +08:00
k88936 f5f94111ca Feat/图片绑定选择器改用带预览的模态框 (#269)
Project CI / Native shell tests (push) Successful in 20m12s
Project CI / Repository checks (push) Successful in 2m42s
Project CI / Frontend tests (push) Successful in 3m39s
Project CI / Backend tests (push) Successful in 7m18s
Project CI / Backend tests (pull_request) Failing after 9s
Project CI / Repository checks (pull_request) Failing after 9s
Project CI / Native shell tests (pull_request) Failing after 32m52s
Project CI / Frontend tests (pull_request) Failing after 44m27s
before:
![image.png](/attachments/5ae7df1f-cf57-4712-8038-f65ae83e2ffa)

after:
![shotmd-1788417438.jpg](/attachments/676e653d-2561-4eca-b28f-9725d3b8af02)

close #268

---------

Co-authored-by: 段舒康 <kdletters@qq.com>
Reviewed-on: http://192.168.35.82/git/GenarrativeAI/Genarrative/pulls/269
Co-authored-by: 王德宇 <kvtodev@outlook.com>
Co-committed-by: 王德宇 <kvtodev@outlook.com>
2026-09-03 20:02:48 +08:00
suzmii cbc73f111c 恢复外部 API Key 表并统一 Router 账号存储
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
恢复 external_api_key 表及客户端绑定为 master 原始结构

将 LLM Router 凭据、状态、撤销和缓存统一收敛到 llm_router_account

移除后台与契约中的 purpose 字段及 Router 专用撤销 procedure

同步更新架构文档并补齐相关测试契约
2026-09-03 19:43:10 +08: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
k88936 4cd53688e9 Feat/优化UI编辑器inspector offset编辑部分UI (#265)
Project CI / Repository checks (push) Successful in 2m21s
Project CI / Frontend tests (push) Successful in 3m37s
Project CI / Backend tests (push) Successful in 6m21s
Project CI / Native shell tests (push) Successful in 19m48s
before:
![image.png](/attachments/e0344964-55c6-4ba1-9191-a79d81f32b2e)
after:
![shotmd-1788414839.jpg](/attachments/14c8a723-b2df-48d5-9773-7f576bcef873)
close #267

---------

Co-authored-by: 段舒康 <kdletters@qq.com>
Reviewed-on: http://192.168.35.82/git/GenarrativeAI/Genarrative/pulls/265
Co-authored-by: 王德宇 <kvtodev@outlook.com>
Co-committed-by: 王德宇 <kvtodev@outlook.com>
2026-09-03 19:36:18 +08: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
kdletters cb7bc50ef9 Merge pull request 'UI编辑器撤销回退功能' (#261) from feat/ui-editor-edit-history into master
Project CI / Backend tests (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Project CI / Frontend tests (push) Has been cancelled
Reviewed-on: http://192.168.35.82/git/GenarrativeAI/Genarrative/pulls/261
2026-09-03 19:31:25 +08:00
kdletters b7641f94b7 Merge branch 'master' into feat/ui-editor-edit-history
Project CI / Repository checks (pull_request) Successful in 2m40s
Project CI / Frontend tests (pull_request) Successful in 3m6s
Project CI / Backend tests (pull_request) Successful in 6m40s
Project CI / Native shell tests (pull_request) Successful in 20m27s
2026-09-03 19:30:43 +08:00
k88936 520b9344ef Fix/AGC上下文丢失问题 (#247)
Project CI / Frontend tests (push) Successful in 3m27s
Project CI / Backend tests (push) Successful in 7m20s
Project CI / Repository checks (push) Successful in 3m1s
Project CI / Native shell tests (push) Has been cancelled
权衡之后选择附加历史消息构造一个巨大的prompt
before:
```
1. user:  aabb
2. assistant: bbaa 这里codex崩溃了或者重启上下文丢失
```
after:
重启之后发送新消息abab
 codex看到:
```
user:  aabb
assistant: bbaa
这里崩溃了!
user: abab
```

移除了原来的"会话最后只有用户消息就自动重新发送用户消息"功能, 因为存在冲突: 现在的实现是每次用户手动发送新消息才做以上的步骤

before and after:
![shotmd-1788345830.jpg](/attachments/28549e4d-9913-459a-bae2-a195b8ee9e04)

Close #249

---------

Co-authored-by: 段舒康 <kdletters@qq.com>
Reviewed-on: http://192.168.35.82/git/GenarrativeAI/Genarrative/pulls/247
Co-authored-by: 王德宇 <kvtodev@outlook.com>
Co-committed-by: 王德宇 <kvtodev@outlook.com>
2026-09-03 19:30:21 +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
suzmii 270aa38ccb Merge branch 'master' into feat/agc-llm-router-official-chain
Project CI / Repository checks (pull_request) Successful in 3m32s
Project CI / Frontend tests (pull_request) Successful in 3m40s
Project CI / Backend tests (pull_request) Successful in 7m25s
Project CI / Native shell tests (pull_request) Failing after 13m42s
2026-09-03 19:04:22 +08:00
suzmii 6d41dc2f89 切换 LLM Router 凭据到专用账号表
Project CI / Backend tests (pull_request) Failing after 15s
Project CI / Repository checks (pull_request) Failing after 16s
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
- 将 Router 账号、API Key 元数据和密文统一保存到 llm_router_account

- 增加 Router API Key 的进程内缓存并清理旧 external_api_key 兼容路径

- 更新 SpacetimeDB 绑定、后端架构文档和决策记录
2026-09-03 19:03:40 +08: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
k88936 51b7d51d01 Merge branch 'master' into feat/ui-editor-edit-history
Project CI / Backend tests (pull_request) Successful in 7m2s
Project CI / Repository checks (pull_request) Successful in 3m8s
Project CI / Frontend tests (pull_request) Successful in 3m9s
Project CI / Native shell tests (pull_request) Successful in 21m38s
2026-09-03 16:08:23 +08:00
suzmii c12d744d91 合并master并保留双侧最新决策记录
Project CI / Frontend tests (pull_request) Successful in 3m16s
Project CI / Repository checks (pull_request) Successful in 2m37s
Project CI / Backend tests (pull_request) Successful in 7m14s
Project CI / Native shell tests (pull_request) Failing after 21m2s
合入主站 AGC 请求头归属与项目命名链路改动。
保留本分支 Direct 过程卡决策记录。
保留主站登录归属、workspace 边界与 Native shell 决策记录。
2026-09-03 16:04:04 +08:00
suzmii e34d797336 修复 AGC 确定性 E2E 与 master 合并后的运行链路
Project CI / Backend tests (pull_request) Failing after 11s
Project CI / Frontend tests (pull_request) Successful in 2m59s
Project CI / Repository checks (pull_request) Failing after 21s
Project CI / Native shell tests (pull_request) Failing after 16m26s
合并 master 后 isolated AppData 配置改为私有副本,避免 Windows 安全校验拒绝硬链接。
只读 runner 状态命令显式配置 config dir,避免误读为 runner 未启用。
为 debug 构建增加仅 E2E 显式开启的确定性 Provider 路由开关,并补充回归测试。
兼容 Windows 对私有副本权限位的校验差异。
2026-09-03 15:59:19 +08: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
k88936 0c0cad7fa4 Merge branch 'master' into feat/ui-editor-edit-history
Project CI / Repository checks (pull_request) Failing after 1m57s
Project CI / Frontend tests (pull_request) Successful in 4m9s
Project CI / Backend tests (pull_request) Successful in 6m17s
Project CI / Native shell tests (pull_request) Failing after 20m35s
2026-09-03 14:44:36 +08:00
k88936 9fd160efc5 合并批量组件绑定历史记录
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 3m7s
Project CI / Native shell tests (pull_request) Successful in 22m4s
批处理中的前置绑定批次跳过历史,最后一批使用非跳过替换统一形成撤销边界

为跳过后记录历史的状态基线补充撤销回归测试
2026-09-03 14:42:38 +08:00
suzmii f86a1320f6 Merge remote-tracking branch 'origin/master' into feat/agc-llm-router-official-chain 2026-09-03 14:41:44 +08:00
suzmii 9ae3aba092 清理已退役工具测试并补充 Router 账号文档
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 3m26s
Project CI / Native shell tests (pull_request) Has been cancelled
删除已移除 Skill 引用工具的遗留专用测试

补充 llm_router_account 表结构与用途说明
2026-09-03 14:35:22 +08:00
suzmii 8aaa3f5c7b 修复运行时配置诊断阻塞窗口
Project CI / Repository checks (pull_request) Failing after 15s
Project CI / Backend tests (pull_request) Failing after 15s
Project CI / Native shell tests (pull_request) Failing after 11m21s
Project CI / Frontend tests (pull_request) Successful in 11m46s
将 Codex CLI 与 app-server 探测移入 Tokio 阻塞线程池

保持 GameAgent 运行时配置读写流程不变

增加异步 LLM 配置诊断回归测试并补充技术方案约束
2026-09-03 14:24:40 +08:00
suzmii 75845bd7e9 Merge remote-tracking branch 'origin/feat/agc-llm-router-official-chain' into feat/agc-llm-router-official-chain
Project CI / Repository checks (pull_request) Failing after 9s
Project CI / Frontend tests (pull_request) Successful in 3m2s
Project CI / Backend tests (pull_request) Failing after 12s
Project CI / Native shell tests (pull_request) Failing after 7m30s
2026-09-03 13:42:19 +08:00
suzmii 4f79a275a8 美化 AGC 客户端设置页
收敛账号与角色协作展示文案

增加账号状态检测动画和状态卡片样式

将流式输出与联网检索整理为单列

更新设置页回归测试与配置护栏
2026-09-03 13:39:32 +08:00
k88936 e4680d6f70 统一节点变换实时预览
Project CI / Frontend tests (pull_request) Successful in 3m17s
Project CI / Backend tests (pull_request) Failing after 11s
Project CI / Repository checks (pull_request) Failing after 12s
Project CI / Native shell tests (pull_request) Successful in 19m52s
预览拖动和缩放时同步解析保持子节点页面矩形的变换

将检查器的子节点保持选项传入预览交互并补充回归测试
2026-09-03 13:38:19 +08:00
k88936 79d3e5e6de 保留颜色草稿卸载时的修改
组件卸载前提交仍未关闭的颜色草稿,避免切换面板或删除组件导致颜色丢失
2026-09-03 13:17:15 +08:00
k88936 08caa1f29f 防止资源比较循环递归溢出
为 sameResource 增加对象对访问跟踪,遇到循环结构时安全终止递归
2026-09-03 13:15:49 +08:00
k88936 6b332e8c12 阻止快捷键重复触发历史操作
忽略 keydown 重复事件,避免长按撤销或重做键耗尽历史记录
2026-09-03 13:15:17 +08:00
k88936 cb86ab8be9 减少编辑器历史快照复制
提交历史时复用已克隆的 nextState,避免每次编辑产生冗余深拷贝
2026-09-03 13:15:01 +08:00
k88936 071998fee9 提取编辑器历史上限常量
为撤销历史容量 100 增加命名常量,便于理解和调节内存权衡
2026-09-03 13:14:43 +08:00
k88936 d60d93a327 简化预览变换查找
单次读取节点预览变换,移除重复 Map 查找和非空断言
2026-09-03 13:14:18 +08:00
k88936 e75450357d 优化预览变换回退分配
复用空的预览变换映射,避免节点递归渲染重复分配
2026-09-03 13:14:00 +08:00
k88936 d6f0c2642f 提交颜色选择器结束态草稿
Project CI / Repository checks (pull_request) Failing after 19s
Project CI / Backend tests (pull_request) Failing after 19s
Project CI / Frontend tests (pull_request) Successful in 2m41s
Project CI / Native shell tests (pull_request) Successful in 14m54s
使用 RgbaColorPicker 的 onChangeEnd 处理指针与键盘结束\n关闭颜色弹层时提交仍存在的草稿
2026-09-03 12:17:36 +08:00
k88936 074ee615ae 更新节点拖拽提交测试
断言 pointermove 仅更新预览\n断言 pointerup 才提交一次节点变换
2026-09-03 12:17:25 +08:00
k88936 d44c924f5e 简化 UI 编辑器历史提交边界
移除撤销事务 API 与额外事务状态

让绑定每个 batch 独立提交一条撤销记录

同步更新规格和独立提交测试
2026-09-03 12:14:21 +08:00
k88936 b07c8a5e25 优化编辑器状态相等性检查
用可提前退出的递归比较替代完整状态序列化\n减少每次编辑提交的无谓 JSON 字符串构造
2026-09-03 12:00:36 +08:00
k88936 37c465dbc3 稳定编辑器快捷键监听
按稳定的撤销重做回调绑定全局监听\n尊重已被其他处理器阻止的键盘事件
2026-09-03 11:54:59 +08:00
k88936 95f5904b92 放宽编辑器快捷键焦点限制
仅将文本输入控件视为可编辑目标\n保持工具栏按钮聚焦时仍可使用撤销重做快捷键
2026-09-03 11:54:27 +08:00
k88936 e616e2c899 清理透明度输入的颜色草稿
透明度修改提交前显式清除颜色草稿\n避免无效更新后残留未提交颜色
2026-09-03 11:51:58 +08:00
k88936 f79ccccf44 稳定预览手势清理回调
通过 ref 读取最新预览回调\n避免回调身份变化时重复取消进行中的手势
2026-09-03 11:51:34 +08:00
k88936 093a2ace76 优化预览变换状态更新
仅在预览变换实际变化时创建新 Map\n避免取消不存在手势时触发预览树重渲染
2026-09-03 11:51:07 +08:00
suzmii 1c4a7bd221 Merge branch 'master' into feat/agc-llm-router-official-chain
Project CI / Repository checks (pull_request) Successful in 2m49s
Project CI / Frontend tests (pull_request) Successful in 3m36s
Project CI / Backend tests (pull_request) Failing after 2m51s
Project CI / Native shell tests (pull_request) Failing after 6m42s
2026-09-03 11:42:37 +08:00
suzmii ac0f6bb783 Revert "恢复 Router provisioning 固定密钥方案"
Project CI / Repository checks (pull_request) Failing after 13s
Project CI / Backend tests (pull_request) Failing after 9s
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
This reverts commit 9a06945e44.
2026-09-03 11:22:29 +08:00
suzmii 9a06945e44 恢复 Router provisioning 固定密钥方案
Project CI / Repository checks (pull_request) Failing after 11s
Project CI / Frontend tests (pull_request) Successful in 3m4s
Project CI / Backend tests (pull_request) Failing after 9s
Project CI / Native shell tests (pull_request) Failing after 10m8s
恢复 api-server 内置 provisioning secret,保持现有账号派生兼容

移除 provisioning secret 的环境变量和示例配置入口

补充阶段性取舍而非设计缺陷的代码注释

同步 Router 账号链路架构文档
2026-09-03 11:10:32 +08:00
k88936 90623f136d 登记 UI 编辑器撤销重做规范
将撤销重做规格加入文档总览入口
2026-09-03 11:08:52 +08:00
k88936 dc0ea0eda0 接入 UI 编辑器撤销重做入口
在编辑器头部增加撤销与重做按钮及禁用态

增加桌面 Cmd/Ctrl 快捷键并保留文本控件原生行为
2026-09-03 11:07:19 +08:00
k88936 0544e78996 修正清空编辑器历史边界
清空编辑器时重置撤销与重做栈

补充清空操作不会进入历史的测试
2026-09-03 11:06:47 +08:00
k88936 e379459820 降低 UI 编辑器颜色选择器提交频率
颜色拖动期间使用本地草稿预览

指针释放时一次提交最终颜色 State
2026-09-03 11:05:58 +08:00
k88936 15caf1cafc 收敛 UI 编辑器锁与历史事务
新增统一的加锁并包裹历史事务操作

将 AI 建议、识别、合并和批量绑定改用统一入口
2026-09-03 11:05:04 +08:00
k88936 f79724cb17 实现 UI 编辑器 State 撤销重做核心
在统一 State 提交入口增加 100 条快照历史与 no-op 过滤

提供撤销、重做、事务分组及加载重置历史 API

补充 State 历史、事务回滚和加载清理测试
2026-09-03 11:01:21 +08:00
k88936 c519f65ac2 补充 UI 编辑器撤销重做规范
明确撤销与重做的 State 范围、事务边界和生命周期

明确连续控件与节点变换的低频提交规则

明确资产文件、副作用、快捷键和验收标准
2026-09-03 10:58:54 +08:00
k88936 8a18b1b181 调整 UI 编辑器节点变换为松开时提交
Project CI / Repository checks (pull_request) Failing after 16s
Project CI / Frontend tests (pull_request) Failing after 2m3s
Project CI / Backend tests (pull_request) Failing after 20s
Project CI / Native shell tests (pull_request) Failing after 4m30s
拖动和缩放期间仅更新预览临时变换

指针松开时提交最终变换,取消操作不写入 State

补充 UI 编辑器拖动变换提交边界文档
2026-09-03 10:51:52 +08:00
suzmii 6bea5bb2b1 合并 master 并整合 Router 安全修复
Project CI / Native shell tests (pull_request) Failing after 6m32s
Project CI / Repository checks (pull_request) Successful in 3m5s
Project CI / Backend tests (pull_request) Failing after 3m16s
Project CI / Frontend tests (pull_request) Successful in 3m44s
合入 origin/master 最新提交 a465e481e

保留后台错误报告、AGC 受控搜索与 Router 计费安全修复

同步 SpacetimeDB migration、技能文档和配置契约
2026-09-03 10:28:20 +08:00
suzmii 4b3d27e3d3 修复 AGC Router 计费与登录安全问题
绑定 LLM 幂等键与请求指纹,阻止跨请求免扣费

放宽登录对 Router 控制面的依赖并迁移 provisioning secret

加强 AGC 配置文件校验与 SSE UTF-8 处理

同步环境配置、架构文档和回归测试
2026-09-03 10:24:36 +08:00
suzmii beae1dfe5f 过程卡工具文案改为用户语义
- MCP 工具按写入、浏览、读取素材、导入、生成图片等用户动作显示文案
- 写入工具与文件变更统一显示正在写入文件和项目相对路径
- 命令显示具体命令,验证类命令显示正在验证游戏
- 未知工具只显示正在调用工具,不暴露内部工具名和未审核参数
- 工具详情限制路径长度并拒绝绝对路径与上跳路径
- stream=false 时继续保留新的具体工具执行文案
- 补充工具映射、安全边界、命令分类与过程卡心跳回归
- 决策记录同步工具语义文案规则
2026-09-02 19:44:57 +08:00
suzmii 73f5c94de4 合并master并保留双侧决策记录
合并 origin/master 的 UI 编辑器、GDD 审批与前端修复。

保留本分支 LLM Router、Direct 过程卡与私有路径相关实现和决策记录。

解决 decision-log 文档冲突,完整保留双方新增决策条目。
2026-09-02 19:31:12 +08:00
suzmii 5008691975 过程卡思考阶段显示正在思考并保留具体执行命令
Project CI / Native shell tests (pull_request) Failing after 12m28s
Project CI / Repository checks (pull_request) Failing after 9s
Project CI / Frontend tests (pull_request) Successful in 3m31s
Project CI / Backend tests (pull_request) Failing after 9s
- preparing 活动词文案由正在理解需求改为正在思考中
- Codex 计划与推理通知只收敛为 thinking 活动,不再把原始推理正文送入 UI
- 推理期 thinking 活动按 1.2 秒限流,避免重复事件刷屏
- command-exec 等执行细节不再依赖回复流开关,stream=false 也保留具体命令
- 同一活动的心跳事件不再用通用文案覆盖正在执行的具体命令
- 补充 Rust 纯函数单测与 AppSurface 思考态、命令心跳回归
- 决策记录同步 thinking 与执行细节展示规则
2026-09-02 18:24:14 +08:00
suzmii c668964d2f 优化 Direct 流式回复展示
将生成中的累计正文直接显示在助手会话气泡中。

过程卡只保留阶段与执行信息,避免工具事件覆盖正文。

展开详情的滚动条轨道与角落改为透明。

补充流式、失败清理与正式消息接管的界面回归验证。
2026-09-02 17:50:17 +08:00
suzmii 8f7017f6c0 Direct 过程卡改为按回合阶段状态驱动并移除合成打字机
Project CI / Frontend tests (pull_request) Successful in 3m20s
Project CI / Repository checks (pull_request) Failing after 14s
Project CI / Backend tests (pull_request) Failing after 11s
Project CI / Native shell tests (pull_request) Failing after 13m20s
- DirectProject observer 只把真实回复增量标记 streaming,工具中间文本与活动一律 running
- 删除最终回复的合成打字机回放,改由真实事件驱动流式展示
- App 事件投影新增回合状态与 processKey,小字统一补充正在前缀
- 过程卡标题只由回合状态决定,展开状态在同一回合内保持
- 失败与接受态文案改为当前阶段描述,旧直接活动词映射移除
- AppSurface 回归覆盖接受态、展开保持、流式标题与失败态断言
- 决策记录与 Direct 审计账本同步新的状态口径
2026-09-02 16:49:42 +08:00
suzmii bbf9500bea 合并最新主线并保留官方路由锁定
Project CI / Frontend tests (pull_request) Failing after 3m0s
Project CI / Repository checks (pull_request) Failing after 3m1s
Project CI / Backend tests (pull_request) Failing after 3m52s
Project CI / Native shell tests (pull_request) Failing after 5m55s
合并客户端扩展、应用更新、格式化门禁和后台表查询等主线改动。

运行时设置页保留官方账号服务与安全元数据,同时接入主线扩展管理与更新能力。

DirectProject 文档合并主线第三方 MCP 支持与官方路由、受控搜索边界。

修复合并后的后台 API Key 详情 Eye 图标导入。
2026-09-02 15:34:30 +08:00
suzmii a30f03a38c 零泥点用户禁止发起 LLM 对话
Project CI / Backend tests (pull_request) Failing after 12s
Project CI / Repository checks (pull_request) Failing after 12s
Project CI / Frontend tests (pull_request) Failing after 6m53s
Project CI / Native shell tests (pull_request) Failing after 9m17s
Responses 与 Chat 兼容入口在解析 Router 凭据前读取用户钱包余额。

余额为 0 时直接返回 409 MUD_POINTS_INSUFFICIENT,不创建、续期或访问 Router 账号。

余额读取失败时失败关闭并提示稍后重试。

补充零余额前置门禁回归测试与架构决策记录。
2026-09-02 15:22:51 +08:00
suzmii 44e1e03f2e 格式化 AGC Rust 模块
Project CI / Frontend tests (pull_request) Failing after 2m13s
Project CI / Native shell tests (pull_request) Failing after 4m39s
Project CI / Repository checks (pull_request) Failing after 13s
Project CI / Backend tests (pull_request) Failing after 11s
按当前 rustfmt 规则整理 Agent Runtime、配置、预览、会话与相关测试的代码布局。

本提交不改变运行逻辑,保持 AGC Rust 工作区格式门禁可通过。
2026-09-02 14:57:18 +08:00
suzmii b414f2f5f8 完善 Router 共享账号与订阅契约
Router 固定 Token 展示名改为 agc_auto_generate,并同步后台安全查询测试。

文档记录跨独立数据库的稳定账号恢复、default 分组、订阅续期和 best-effort 扣费规则。

调整共享官方 Router 的非生产启动告警,并保留错误 code 构造能力。
2026-09-02 14:56:19 +08:00
suzmii 1c6c499a11 登录返回前同步认证投影
新增 create_auth_session_and_sync,统一密码、手机号和微信登录会话的投影同步时机。

避免登录接口签发的新 access token 在下一次请求被当前或其它节点的 SpacetimeDB 校验拒绝。

补充登录投影同步回归测试。
2026-09-02 14:55:52 +08:00
suzmii e9591c0543 锁定 AGC 官方路由并强化美术素材使用
真实 Debug 和 Release 构建都强制通过 API Server 使用官方 LLM Router,仅 Rust 测试构建保留显式 loopback fixture。

清理配置读取中的旧 Provider 凭据入口,并补充配置锁定回归。

code-prototype 任务提示要求实际读取并渲染四类平台美术切片,防止只引用整图或路径。
2026-09-02 14:55:23 +08:00
suzmii 479094ef89 取消 DirectProject 成功回复脱敏
DirectProject 流式文本和最终回复只移除 thinking 包装,成功正文按用户项目内容原样展示。

删除路径、链接和凭据占位符替换以及回复长度截断逻辑。

同步删除最终回复必须脱敏的文档要求,并更新回归测试验证项目内容保持原样。
2026-09-02 14:43:54 +08:00
suzmii 529a1a0a77 格式化 AGC Agent 既有代码
修正 generation 与 runtime_state 两个文件的 rustfmt 布局,使工作区格式门禁可通过。
2026-09-02 14:27:08 +08:00
suzmii 2e15289264 补齐 AGC 直连活动状态与泥点错误映射
Codex app-server 将文件、命令、验证、搜索等 item/started 事件映射为具体活动标题和安全短详情。

泥点不足错误映射为稳定 409 上游错误并在 DirectProject 显示明确充值引导,不再落到 other。

官方平台会话保持 Provider Proxy 链路,测试专用 API Key/AuthBridge 逻辑仅保留在 cfg(test)。Codex 子进程清理代理环境,确保 loopback SSE 不被系统代理劫持。

补充活动投影、错误映射、流式配置和敏感内容脱敏回归测试。
2026-09-02 14:26:00 +08:00
suzmii 69c11c3285 调整 LLM 泥点后置扣费与执行状态展示
LLM Router 计费改为每开始 10000 token 扣 1 点且至少 1 点。

钱包在同一事务内按可消费余额扣光并记录赠送差额,成功响应不再因余额不足中断。

Responses 流式终态延后到结算后发送,泥点不足映射为稳定错误。

执行状态改为具体工具标题,详情默认单行折叠并增加扫过高亮动画。

补充计费公式、幂等扣费与状态展示回归测试。
2026-09-02 14:17:27 +08:00
suzmii 700d7940a7 修复 Router 分组与账号凭据契约
- 固定 Router 用户为 taonier、Token 为 default 并确保订阅

- 复用本地账号 Key,禁止无管理员令牌绕过订阅校验

- LLM 转发只读取服务端已验证的 Router 凭据并保持请求流式参数
2026-09-02 12:06:31 +08:00
suzmii c0694ceedd Merge remote-tracking branch 'origin/master' into feat/hide-llm-router-and-stream-transport
Project CI / Repository checks (pull_request) Failing after 8s
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
# Conflicts:
#	apps/ai-game-creator-shell/src-tauri/src/config.rs
2026-09-01 12:15:24 +08:00
suzmii c2354f855f 收敛 AGC 官方 LLM Router 账号链路
新增 llm_router_account 表、procedures 与客户端绑定
api-server 改为 New API 管理员流程签发独立 Router 账号
Router 成功后执行幂等后置泥点扣费
生产外锁定非生产 Router 控制面为 loopback
AGC 正式模式清除手工 Provider 凭据并锁定官方代理
AGC 状态面只返回账号凭据与官方路由安全元数据
后台数据库表查询适配账号凭据状态展示
补充 AGC 与后端测试、环境示例和架构文档
修正 dev-stack 状态路径按仓库脚本位置解析
2026-09-01 12:03:07 +08:00
suzmii 26e4eab6b6 对齐主线ACL文件边界
将AGC Rust源码中的ACL专用实现恢复为主线版本

移除本分支内的Windows ACL提权命令与私有路径调用

保留Router、联网搜索和流式传输改动在后续stash中继续处理
2026-08-31 13:09:29 +08:00
suzmii c3c572dbac 合并最新 Agent 编排基线
合并 origin/master 的 Agent Runtime 编排依赖与测试门禁

恢复 platform-agent 的任务图校验与动态编排实现

为后续 AGC Router 流程调试提供可启动的最新基线
2026-08-31 11:54:18 +08:00
suzmii 7132ce543e 修正Responses流式结算事件顺序
延迟透传上游 [DONE],确保扣费失败时客户端先收到 error 事件

补充 SSE done 标记边界解析回归测试
2026-08-30 16:18:19 +08:00
suzmii 9f832ee96a 完善AGC Router代理与后置计费
修正无 provisioning 时仅使用服务端 fallback Key,禁止生成 Router 不认识的随机 Key

补齐 AGC 凭据元数据脱敏、失效复用保护与 SpacetimeDB 字段映射

让 Responses 与 Chat 流式调用在成功后按 usage 幂等扣除泥点

同步 schema 迁移、环境变量示例、后端架构和决策文档
2026-08-30 16:14:10 +08:00
suzmii e2689b4be5 调整配置默认值回归断言
移除已不再存在的前端 Agent 推理映射源码断言

保留 Rust 侧规范 Agent 默认映射和配置模板验证
2026-08-30 14:43:06 +08:00
suzmii ddb668710e 补回联网搜索测试依赖导入
保留 DirectToolBridge 测试路由使用的 Query 和 get
2026-08-30 14:36:41 +08:00
suzmii b27df8dfab 修正配置迁移测试可见性
开放 AGC 配置迁移测试所需的 crate 内函数

清理受控联网搜索合并后的未使用导入
2026-08-30 14:35:24 +08:00
suzmii 8d6b923607 合并AGC流式输出与受控联网搜索
保留官方路由锁定、账号凭据状态和 Windows 私有路径安全边界

合入 DirectProject 流式可见文本投影与受控联网搜索

同步配置 schema 迁移、状态输出和回归测试
2026-08-30 14:32:35 +08:00
suzmii 68dfd2c24f 补齐AGC流式输出与受控联网搜索
增加 DirectProject 流式可见文本投影和敏感内容过滤

接入受控 web search 工具及参数、网络边界测试

补充配置迁移和运行时回归覆盖
2026-08-30 14:22:03 +08:00
suzmii 93477f2b1b 重整AGC官方路由与安全文件链路
记录当前 AGC 客户端、Tauri、API Server、后台查询和契约改动

保留后续按新 Router 后端方案重构的检查点
2026-08-30 14:21:18 +08:00
368 changed files with 34828 additions and 38851 deletions
+5
View File
@@ -8,6 +8,11 @@ LLM_BASE_URL="https://api.vectorengine.cn/v1"
# but it should not be relied on by browser code. # but it should not be relied on by browser code.
LLM_API_KEY="" LLM_API_KEY=""
# Router account provisioning secret (server-side only). Prefer the protected
# file form in production; never expose either value to clients or commit it.
GENARRATIVE_LLM_ROUTER_PROVISIONING_SECRET=""
GENARRATIVE_LLM_ROUTER_PROVISIONING_SECRET_FILE=""
# Optional frontend override for the local proxy path. # Optional frontend override for the local proxy path.
VITE_LLM_PROXY_BASE_URL="/api/llm" VITE_LLM_PROXY_BASE_URL="/api/llm"
+50
View File
@@ -26,6 +26,8 @@ import type {
AdminErrorReportDetail, AdminErrorReportDetail,
AdminErrorReportEntry, AdminErrorReportEntry,
AdminErrorReportListResponse, AdminErrorReportListResponse,
AdminExternalApiKeyListQuery,
AdminExternalApiKeyListResponse,
AdminFeatureGateConfigResponse, AdminFeatureGateConfigResponse,
AdminLoginResponse, AdminLoginResponse,
AdminMeResponse, AdminMeResponse,
@@ -249,6 +251,16 @@ export function getAdminDatabaseTableRows(
); );
} }
export function getAdminExternalApiKeys(
token: string,
query: AdminExternalApiKeyListQuery = {},
) {
return request<AdminExternalApiKeyListResponse>(
`/admin/api/external-api-keys${buildExternalApiKeyQuery(query)}`,
{ token },
);
}
export function debugAdminHttp(token: string, payload: AdminDebugHttpRequest) { export function debugAdminHttp(token: string, payload: AdminDebugHttpRequest) {
return request<AdminDebugHttpResponse>('/admin/api/debug/http', { return request<AdminDebugHttpResponse>('/admin/api/debug/http', {
method: 'POST', method: 'POST',
@@ -928,6 +940,28 @@ function buildDatabaseTableRowsQuery(query: AdminDatabaseTableRowsQuery) {
return queryString ? `?${queryString}` : ''; return queryString ? `?${queryString}` : '';
} }
function buildExternalApiKeyQuery(query: AdminExternalApiKeyListQuery) {
const params = new URLSearchParams();
appendQueryParam(params, 'ownerUserId', query.ownerUserId);
appendQueryParam(params, 'publicUserCode', query.publicUserCode);
appendQueryParam(params, 'keyId', query.keyId);
appendQueryParam(params, 'name', query.name);
appendQueryParam(params, 'keyPrefix', query.keyPrefix);
appendQueryParam(params, 'createdAfter', query.createdAfter);
appendQueryParam(params, 'createdBefore', query.createdBefore);
appendQueryParam(params, 'status', query.status);
if (typeof query.limit === 'number' && Number.isFinite(query.limit)) {
params.set('limit', String(Math.floor(query.limit)));
}
if (typeof query.offset === 'number' && Number.isFinite(query.offset)) {
params.set('offset', String(Math.floor(query.offset)));
}
appendQueryParam(params, 'sortColumn', query.sortColumn);
appendQueryParam(params, 'sortDirection', query.sortDirection);
const queryString = params.toString();
return queryString ? `?${queryString}` : '';
}
function buildEditorAssetListQuery(query: AdminEditorAssetListQuery) { function buildEditorAssetListQuery(query: AdminEditorAssetListQuery) {
const params = new URLSearchParams(); const params = new URLSearchParams();
appendQueryParam(params, 'cursor', query.cursor); appendQueryParam(params, 'cursor', query.cursor);
@@ -1038,3 +1072,19 @@ function buildAdminApiError(
function isRecord(value: unknown): value is Record<string, unknown> { function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null && !Array.isArray(value); return typeof value === 'object' && value !== null && !Array.isArray(value);
} }
export function getAgcModelCatalog(token: string) {
return request<import('./adminApiTypes').AdminAgcModelCatalog>(
'/admin/api/agc-models',
{ token },
);
}
export function saveAgcModelCatalog(
token: string,
body: import('./adminApiTypes').AdminAgcModelCatalog,
) {
return request<import('./adminApiTypes').AdminAgcModelCatalog>(
'/admin/api/agc-models',
{ token, method: 'PUT', body },
);
}
+57
View File
@@ -275,6 +275,51 @@ export interface AdminDatabaseTableStatPayload {
errorMessage: string | null; errorMessage: string | null;
} }
export interface AdminExternalApiKeyListQuery {
ownerUserId?: string;
publicUserCode?: string;
keyId?: string;
name?: string;
keyPrefix?: string;
createdAfter?: string;
createdBefore?: string;
status?: 'active' | 'revoked';
limit?: number;
offset?: number;
sortColumn?:
| 'keyId'
| 'ownerUserId'
| 'name'
| 'keyPrefix'
| 'createdAt'
| 'lastUsedAt'
| 'updatedAt';
sortDirection?: 'asc' | 'desc';
}
export interface AdminExternalApiKeyPayload {
keyId: string;
ownerUserId: string;
name: string;
keyPrefix: string;
scopes: string[];
createdAt: string;
lastUsedAt: string | null;
revokedAt: string | null;
updatedAt: string;
status: 'active' | 'revoked';
}
export interface AdminExternalApiKeyListResponse {
keys: AdminExternalApiKeyPayload[];
total: number;
limit: number;
offset: number;
scannedCount: number;
scanLimit: number;
scanLimitReached: boolean;
}
export interface AdminDebugHeaderInput { export interface AdminDebugHeaderInput {
name: string; name: string;
value: string; value: string;
@@ -953,3 +998,15 @@ export interface AdminRechargeRefundActionResponse {
export interface AdminWalletRestrictionResponse { export interface AdminWalletRestrictionResponse {
wallet: AdminProfileWalletPayload; wallet: AdminProfileWalletPayload;
} }
export interface AdminAgcModel {
id: string;
alias: string;
modelId: string;
enabled: boolean;
}
export interface AdminAgcModelCatalog {
revision: number;
defaultModelId: string;
models: AdminAgcModel[];
}
+4
View File
@@ -18,6 +18,7 @@ import {
setStoredAdminToken, setStoredAdminToken,
} from '../auth/adminAuthStore'; } from '../auth/adminAuthStore';
import { AdminAccountsPage } from '../pages/AdminAccountsPage'; import { AdminAccountsPage } from '../pages/AdminAccountsPage';
import { AdminAgcModelsPage } from '../pages/AdminAgcModelsPage';
import { AdminDashboardPage } from '../pages/AdminDashboardPage'; import { AdminDashboardPage } from '../pages/AdminDashboardPage';
import { AdminDatabaseTablesPage } from '../pages/AdminDatabaseTablesPage'; import { AdminDatabaseTablesPage } from '../pages/AdminDatabaseTablesPage';
import { AdminDebugHttpPage } from '../pages/AdminDebugHttpPage'; import { AdminDebugHttpPage } from '../pages/AdminDebugHttpPage';
@@ -289,6 +290,9 @@ export function AdminApp() {
onUnauthorized={handleUnauthorized} onUnauthorized={handleUnauthorized}
/> />
) : null} ) : null}
{activeRouteId === 'agc-models' ? (
<AdminAgcModelsPage token={token} onUnauthorized={handleUnauthorized} />
) : null}
{activeRouteId === 'editor-showcase' ? ( {activeRouteId === 'editor-showcase' ? (
<AdminEditorShowcaseReviewPage <AdminEditorShowcaseReviewPage
token={token} token={token}
+1
View File
@@ -50,6 +50,7 @@ const routeIcons = {
'editor-showcase': Star, 'editor-showcase': Star,
'editor-assets': Images, 'editor-assets': Images,
accounts: Users, accounts: Users,
'agc-models': ListChecks,
} satisfies Record<AdminRouteId, typeof LayoutDashboard>; } satisfies Record<AdminRouteId, typeof LayoutDashboard>;
export function AdminShell({ export function AdminShell({
+6 -1
View File
@@ -16,9 +16,13 @@ export type AdminRouteId =
| 'editor-generation-pricing' | 'editor-generation-pricing'
| 'editor-showcase' | 'editor-showcase'
| 'editor-assets' | 'editor-assets'
| 'agc-models'
| 'accounts'; | 'accounts';
export type AdminTabPermission = Exclude<AdminRouteId, 'accounts'>; export type AdminTabPermission = Exclude<
AdminRouteId,
'accounts' | 'agc-models'
>;
/** 后台导航项定义,hash 是浏览器地址栏和移动底栏共用入口。 */ /** 后台导航项定义,hash 是浏览器地址栏和移动底栏共用入口。 */
export interface AdminRouteDefinition { export interface AdminRouteDefinition {
@@ -47,6 +51,7 @@ export const adminRoutes: AdminRouteDefinition[] = [
label: '模型定价', label: '模型定价',
hash: '#editor-generation-pricing', hash: '#editor-generation-pricing',
}, },
{ id: 'agc-models', label: 'AGC 模型', hash: '#agc-models', ownerOnly: true },
{ id: 'editor-showcase', label: '精选审核', hash: '#editor-showcase' }, { id: 'editor-showcase', label: '精选审核', hash: '#editor-showcase' },
{ id: 'editor-assets', label: '素材查询', hash: '#editor-assets' }, { id: 'editor-assets', label: '素材查询', hash: '#editor-assets' },
{ id: 'accounts', label: '账号管理', hash: '#accounts', ownerOnly: true }, { id: 'accounts', label: '账号管理', hash: '#accounts', ownerOnly: true },
@@ -0,0 +1,56 @@
// @vitest-environment jsdom
import {
cleanup,
fireEvent,
render,
screen,
waitFor,
} from '@testing-library/react';
import { afterEach, expect, test, vi } from 'vitest';
import { getAgcModelCatalog, saveAgcModelCatalog } from '../api/adminApiClient';
import { AdminAgcModelsPage } from './AdminAgcModelsPage';
vi.mock('../api/adminApiClient', () => ({
getAgcModelCatalog: vi.fn(),
saveAgcModelCatalog: vi.fn(),
isAdminApiError: vi.fn(() => false),
formatAdminApiError: vi.fn(() => '保存失败'),
}));
vi.mock('../components/useAdminWriteConfirm', () => ({
useAdminWriteConfirm: () => ({
confirmWrite: async () => true,
confirmDialog: null,
}),
}));
afterEach(cleanup);
test('edits alias and upstream model without changing the stable identifier or revision', async () => {
const catalog = {
revision: 3,
defaultModelId: 'quality',
models: [
{ id: 'quality', alias: '高质量', modelId: 'gpt-6-astra', enabled: true },
],
};
vi.mocked(getAgcModelCatalog).mockResolvedValue(catalog);
vi.mocked(saveAgcModelCatalog).mockImplementation(async (_, input) => ({
...input,
revision: 4,
}));
render(<AdminAgcModelsPage token="test" onUnauthorized={vi.fn()} />);
await screen.findByDisplayValue('gpt-6-astra');
fireEvent.change(screen.getByLabelText('模型 1 别名'), {
target: { value: '精细创作' },
});
fireEvent.click(screen.getByRole('button', { name: '保存' }));
await waitFor(() =>
expect(saveAgcModelCatalog).toHaveBeenCalledWith('test', {
...catalog,
models: [{ ...catalog.models[0], alias: '精细创作' }],
}),
);
await waitFor(() => {
expect(screen.getAllByText('已保存').length).toBeGreaterThanOrEqual(2);
});
});
@@ -0,0 +1,255 @@
import { CircleHelp, Plus, RefreshCcw, Save, Trash2 } from 'lucide-react';
import { useEffect, useState } from 'react';
import { getAgcModelCatalog, saveAgcModelCatalog } from '../api/adminApiClient';
import type { AdminAgcModel, AdminAgcModelCatalog } from '../api/adminApiTypes';
import { useAdminWriteConfirm } from '../components/useAdminWriteConfirm';
import { handlePageError } from './pageUtils';
export function AdminAgcModelsPage({
token,
onUnauthorized,
}: {
token: string;
onUnauthorized: (message?: string) => void;
}) {
const [catalog, setCatalog] = useState<AdminAgcModelCatalog | null>(null);
const [busy, setBusy] = useState(false);
const [error, setError] = useState('');
const [saved, setSaved] = useState(false);
const { confirmWrite, confirmDialog } = useAdminWriteConfirm();
async function refresh() {
if (!token) return;
setBusy(true);
setError('');
setSaved(false);
try {
setCatalog(await getAgcModelCatalog(token));
} catch (error) {
handlePageError(error, onUnauthorized, setError);
} finally {
setBusy(false);
}
}
useEffect(() => {
void refresh();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [token]);
function update(id: string, patch: Partial<AdminAgcModel>) {
setSaved(false);
setCatalog(
(current) =>
current && {
...current,
models: current.models.map((model) =>
model.id === id ? { ...model, ...patch } : model,
),
},
);
}
async function save() {
if (!catalog || busy) return;
if (
!(await confirmWrite({
action: '保存 AGC 模型目录',
target: `${catalog.models.length} 个模型`,
}))
)
return;
setBusy(true);
setError('');
setSaved(false);
try {
setCatalog(await saveAgcModelCatalog(token, catalog));
setSaved(true);
} catch (error) {
handlePageError(error, onUnauthorized, setError);
} finally {
setBusy(false);
}
}
return (
<section className="admin-page admin-page-wide admin-agc-models">
<div className="admin-page-heading">
<div>
<h2>AGC </h2>
<p></p>
</div>
<span className="admin-agc-models-revision">
v{catalog?.revision ?? '-'}
</span>
</div>
<div className="admin-agc-models-summary">
<div>
<span></span>
<strong>
{catalog?.models.filter((model) => model.enabled).length ?? 0}
</strong>
</div>
<div>
<span></span>
<strong>
{catalog
? (catalog.models.find(
(model) => model.id === catalog.defaultModelId,
)?.alias ?? '未设置')
: '未设置'}
</strong>
</div>
<div>
<span></span>
<strong>{busy ? '处理中' : saved ? '已保存' : '待修改'}</strong>
</div>
</div>
<section className="admin-panel admin-agc-models-panel">
<div className="admin-panel-heading">
<div>
<h3></h3>
<span></span>
</div>
<CircleHelp size={17} aria-label="模型目录帮助" />
</div>
<div className="admin-agc-models-toolbar">
<button
type="button"
title="重新读取"
aria-label="重新读取模型"
disabled={busy}
onClick={() => void refresh()}
>
<RefreshCcw size={16} />
</button>
<button
type="button"
title="添加模型"
aria-label="添加模型"
disabled={busy || !catalog || catalog.models.length >= 32}
onClick={() => {
setSaved(false);
setCatalog(
(current) =>
current && {
...current,
models: [
...current.models,
{
id: crypto.randomUUID(),
alias: '',
modelId: '',
enabled: true,
},
],
},
);
}}
>
<Plus size={16} />
</button>
<button
type="button"
disabled={busy || !catalog}
onClick={() => void save()}
>
<Save size={16} />
</button>
</div>
{error ? <p role="alert">{error}</p> : null}
{saved ? <p role="status"></p> : null}
{busy ? <p role="status"></p> : null}
<div className="admin-table-wrap admin-agc-models-table">
<table className="admin-table admin-agc-models-table-grid">
<thead>
<tr>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
{catalog?.models.map((model, index) => (
<tr key={model.id}>
<td>
<input
aria-label={`模型 ${index + 1} 别名`}
maxLength={40}
required
value={model.alias}
disabled={busy}
onChange={(e) =>
update(model.id, { alias: e.target.value })
}
/>
</td>
<td>
<input
aria-label={`模型 ${index + 1} 实际模型名`}
maxLength={200}
required
value={model.modelId}
disabled={busy}
onChange={(e) =>
update(model.id, { modelId: e.target.value })
}
/>
</td>
<td>
<input
aria-label={`模型 ${index + 1} 启用`}
type="checkbox"
checked={model.enabled}
disabled={busy || model.id === catalog.defaultModelId}
onChange={(e) =>
update(model.id, { enabled: e.target.checked })
}
/>
</td>
<td>
<input
aria-label={`模型 ${index + 1} 默认`}
name="agc-default-model"
type="radio"
checked={model.id === catalog.defaultModelId}
disabled={busy || !model.enabled}
onChange={() => {
setSaved(false);
setCatalog({ ...catalog, defaultModelId: model.id });
}}
/>
</td>
<td>
<button
type="button"
title="删除模型"
aria-label={`删除模型 ${index + 1}`}
disabled={busy || model.id === catalog.defaultModelId}
onClick={() => {
setSaved(false);
setCatalog({
...catalog,
models: catalog.models.filter(
(candidate) => candidate.id !== model.id,
),
});
}}
>
<Trash2 size={16} />
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
</section>
{confirmDialog}
</section>
);
}
@@ -7,6 +7,7 @@ import { beforeEach, expect, test, vi } from 'vitest';
import { import {
getAdminDatabaseTableRows, getAdminDatabaseTableRows,
getAdminDatabaseTables, getAdminDatabaseTables,
getAdminExternalApiKeys,
} from '../api/adminApiClient'; } from '../api/adminApiClient';
import type { AdminDatabaseTableRowsResponse } from '../api/adminApiTypes'; import type { AdminDatabaseTableRowsResponse } from '../api/adminApiTypes';
import { import {
@@ -20,6 +21,7 @@ vi.mock('../api/adminApiClient', () => ({
), ),
getAdminDatabaseTableRows: vi.fn(), getAdminDatabaseTableRows: vi.fn(),
getAdminDatabaseTables: vi.fn(), getAdminDatabaseTables: vi.fn(),
getAdminExternalApiKeys: vi.fn(),
isAdminApiError: vi.fn(() => false), isAdminApiError: vi.fn(() => false),
})); }));
@@ -74,6 +76,7 @@ const referralRows = [
beforeEach(() => { beforeEach(() => {
vi.clearAllMocks(); vi.clearAllMocks();
window.location.hash = '#tables?table=profile_referral_relation'; window.location.hash = '#tables?table=profile_referral_relation';
vi.mocked(getAdminExternalApiKeys).mockReset();
vi.mocked(getAdminDatabaseTables).mockResolvedValue({ vi.mocked(getAdminDatabaseTables).mockResolvedValue({
fetchErrors: [], fetchErrors: [],
tables: ['profile_referral_relation'], tables: ['profile_referral_relation'],
@@ -92,6 +95,55 @@ beforeEach(() => {
}); });
}); });
test('external_api_key 使用专用安全查询且详情不展示原始 JSON', async () => {
const user = userEvent.setup();
window.location.hash = '#tables?table=external_api_key';
vi.mocked(getAdminDatabaseTables).mockResolvedValue({
fetchErrors: [],
tables: ['external_api_key'],
});
vi.mocked(getAdminExternalApiKeys).mockResolvedValue({
keys: [
{
keyId: 'external-api-key-1',
ownerUserId: 'user-1',
name: 'agc_auto_generate',
keyPrefix: 'tnr_sk_fixture',
scopes: ['llm:responses'],
createdAt: '2026-08-29T00:00:00Z',
lastUsedAt: null,
revokedAt: null,
updatedAt: '2026-08-29T00:00:00Z',
status: 'active',
},
],
total: 1,
limit: 100,
offset: 0,
scannedCount: 1,
scanLimit: 5000,
scanLimitReached: false,
});
render(
<AdminDatabaseTablesPage token="admin-token" onUnauthorized={vi.fn()} />,
);
await user.type(screen.getByPlaceholderText('精确 ownerUserId'), 'user-1');
await user.click(screen.getByRole('button', { name: '安全查询' }));
await waitFor(() => {
expect(getAdminExternalApiKeys).toHaveBeenLastCalledWith(
'admin-token',
expect.objectContaining({ ownerUserId: 'user-1' }),
);
});
expect(await screen.findByText('tnr_sk_fixture')).toBeTruthy();
await user.click(screen.getByRole('button', { name: '详情' }));
expect(screen.getByRole('dialog')).toBeTruthy();
expect(screen.queryByText('复制 JSON')).toBeNull();
expect(screen.queryByText('key_hash')).toBeNull();
});
test('后台表查询页通过页面级固定栏翻页并提示扫描结果可能不完整', async () => { test('后台表查询页通过页面级固定栏翻页并提示扫描结果可能不完整', async () => {
const user = userEvent.setup(); const user = userEvent.setup();
vi.mocked(getAdminDatabaseTableRows).mockResolvedValue({ vi.mocked(getAdminDatabaseTableRows).mockResolvedValue({
File diff suppressed because it is too large Load Diff
@@ -88,10 +88,12 @@ export function AdminErrorReportsPage({ token, onUnauthorized }: Props) {
setStatus(''); setStatus('');
try { try {
const updated = await updateAdminErrorReport(token, selected.batchId, { const updated = await updateAdminErrorReport(token, selected.batchId, {
status: nextStatus, status: nextStatus,
note: selected.note, note: selected.note,
}); });
setSelected((current) => (current ? { ...current, ...updated } : current)); setSelected((current) =>
current ? { ...current, ...updated } : current,
);
await loadRef.current(); await loadRef.current();
} catch (error) { } catch (error) {
if (isAdminApiError(error) && error.status === 401) onUnauthorized(); if (isAdminApiError(error) && error.status === 401) onUnauthorized();
@@ -244,7 +246,9 @@ export function AdminErrorReportsPage({ token, onUnauthorized }: Props) {
value={selected.note ?? ''} value={selected.note ?? ''}
onChange={(event) => onChange={(event) =>
setSelected((current) => setSelected((current) =>
current ? { ...current, note: event.target.value } : current, current
? { ...current, note: event.target.value }
: current,
) )
} }
maxLength={2000} maxLength={2000}
+137 -2
View File
@@ -3093,5 +3093,140 @@ button:disabled {
background: var(--admin-surface, #fff); background: var(--admin-surface, #fff);
} }
.admin-detail-modal__panel header, .admin-detail-modal__panel header,
.admin-detail-modal__actions { display: flex; align-items: center; justify-content: space-between; gap: 12px; } .admin-detail-modal__actions {
.admin-detail-modal__panel pre { max-height: 360px; overflow: auto; white-space: pre-wrap; background: #f8fafc; padding: 12px; border-radius: 8px; } display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.admin-detail-modal__panel pre {
max-height: 360px;
overflow: auto;
white-space: pre-wrap;
background: #f8fafc;
padding: 12px;
border-radius: 8px;
}
.admin-agc-models {
min-width: 0;
}
.admin-agc-models-revision {
padding: 6px 10px;
border: 1px solid #e7d9cc;
border-radius: 999px;
background: #fffaf6;
color: #9a8170;
font-size: 12px;
font-variant-numeric: tabular-nums;
}
.admin-agc-models-summary {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 12px;
}
.admin-agc-models-summary > div {
display: grid;
gap: 6px;
padding: 16px 18px;
border: 1px solid #eadfd6;
border-radius: 10px;
background: #fffdfa;
}
.admin-agc-models-summary span,
.admin-agc-models-panel > .admin-panel-heading span {
color: #9a8170;
font-size: 12px;
}
.admin-agc-models-summary strong {
overflow: hidden;
color: #3d2a20;
font-size: 20px;
text-overflow: ellipsis;
white-space: nowrap;
}
.admin-agc-models-panel {
border: 1px solid #eadfd6;
border-radius: 10px;
background: #fffdfa;
box-shadow: 0 10px 30px rgb(78 48 28 / 6%);
}
.admin-agc-models-panel > .admin-panel-heading > div {
display: grid;
gap: 4px;
}
.admin-agc-models-panel > .admin-panel-heading > svg {
color: #b9947a;
}
.admin-agc-models-toolbar {
display: flex;
justify-content: flex-end;
gap: 8px;
padding: 4px 0 8px;
}
.admin-agc-models-toolbar button,
.admin-agc-models-table-grid button {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
min-height: 34px;
padding: 0 11px;
border: 1px solid #e2d3c7;
border-radius: 8px;
background: #fffaf6;
color: #684d3d;
font-size: 12px;
font-weight: 700;
cursor: pointer;
}
.admin-agc-models-toolbar button:hover,
.admin-agc-models-toolbar button:focus-visible,
.admin-agc-models-table-grid button:hover,
.admin-agc-models-table-grid button:focus-visible {
border-color: #c99d80;
background: #fff;
outline: none;
}
.admin-agc-models-toolbar button:last-child {
border-color: #a96442;
background: #a96442;
color: #fff;
}
.admin-agc-models-table-grid {
min-width: 720px;
}
.admin-agc-models-table-grid th {
padding-top: 12px;
padding-bottom: 12px;
background: #fcf7f2;
}
.admin-agc-models-table-grid td {
padding-top: 14px;
padding-bottom: 14px;
}
.admin-agc-models-table-grid td input:not([type]) {
width: 100%;
min-width: 180px;
box-sizing: border-box;
padding: 9px 10px;
border: 1px solid #e1d3c8;
border-radius: 7px;
background: #fff;
color: #3d2a20;
}
.admin-agc-models-table-grid td input:not([type]):focus-visible {
border-color: #b97854;
outline: none;
box-shadow: 0 0 0 3px rgb(185 120 84 / 14%);
}
.admin-agc-models-table-grid td:has(input[type='checkbox']),
.admin-agc-models-table-grid td:has(input[type='radio']) {
width: 72px;
text-align: center;
vertical-align: middle;
}
@media (max-width: 680px) {
.admin-agc-models-summary {
grid-template-columns: 1fr;
}
}
@@ -1,13 +1,14 @@
{ {
"schemaVersion": "game-creator-config.v2",
"agentMode": "codex_app_server", "agentMode": "codex_app_server",
"llm": { "llm": {
"apiKey": "", "apiKey": "",
"baseUrl": "https://dev.genarrative.world/gpt/v1", "baseUrl": "https://dev.genarrative.world/gpt/v1",
"model": "gpt-5.6-sol", "model": "gpt-6-astra",
"apiKind": "openai_responses", "apiKind": "openai_responses",
"reasoningEffort": "max", "reasoningEffort": "max",
"stream": true, "stream": true,
"webSearchEnabled": false, "webSearchEnabled": true,
"contextWindowTokens": 128000, "contextWindowTokens": 128000,
"autoCompactTokenLimit": 64000, "autoCompactTokenLimit": 64000,
"toolOutputTokenLimit": 12000, "toolOutputTokenLimit": 12000,
@@ -15,8 +16,5 @@
"maxRetries": 2, "maxRetries": 2,
"retryBackoffMs": 500 "retryBackoffMs": 500
}, },
"agentLlm": {}, "agentLlm": {}
"planning": {
"capabilityEnabled": true
}
} }
+1 -2
View File
@@ -19,8 +19,6 @@
"config": "node scripts/game-creator-config-wizard.mjs", "config": "node scripts/game-creator-config-wizard.mjs",
"test:chat": "node scripts/agent-swarm-test-chat.mjs --task \"制作一个可直接试玩的原创植物塔防小游戏:玩家选择并放置原创守卫阻挡敌人,完成波次后可以进入下一关并重新开始。主题、单位名称与视觉语言必须原创,不使用任何现有游戏角色、单位名、Logo 或受保护视觉语言。请自主完成正式产物、静态检查和双视口试玩验证。\" --no-open", "test:chat": "node scripts/agent-swarm-test-chat.mjs --task \"制作一个可直接试玩的原创植物塔防小游戏:玩家选择并放置原创守卫阻挡敌人,完成波次后可以进入下一关并重新开始。主题、单位名称与视觉语言必须原创,不使用任何现有游戏角色、单位名、Logo 或受保护视觉语言。请自主完成正式产物、静态检查和双视口试玩验证。\" --no-open",
"test:chat:manual": "node scripts/agent-swarm-test-chat.mjs", "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": "node scripts/run-cli-with-config.mjs --agent-run",
"agent-run:smoke": "node scripts/smoke-agent-run-local-provider.mjs", "agent-run:smoke": "node scripts/smoke-agent-run-local-provider.mjs",
"agent-runtime:real-e2e": "node scripts/agent-runtime-real-e2e.mjs", "agent-runtime:real-e2e": "node scripts/agent-runtime-real-e2e.mjs",
@@ -53,6 +51,7 @@
"focus-trap-react": "^12.0.3", "focus-trap-react": "^12.0.3",
"lexical": "^0.47.0", "lexical": "^0.47.0",
"lucide-react": "^0.546.0", "lucide-react": "^0.546.0",
"phaser": "^4.2.1",
"react": "^19.0.0", "react": "^19.0.0",
"react-arborist": "^3.16.0", "react-arborist": "^3.16.0",
"react-colorful": "^5.8.0", "react-colorful": "^5.8.0",
@@ -1948,6 +1948,7 @@ async function runE2e(options) {
...process.env, ...process.env,
NO_COLOR: '1', NO_COLOR: '1',
[platformSessionFixtureEnv]: fixturePath, [platformSessionFixtureEnv]: fixturePath,
GENARRATIVE_AGC_DEBUG_PROVIDER_E2E: '1',
}), }),
); );
childReport = parseChildReport(childResult); childReport = parseChildReport(childResult);
@@ -773,7 +773,8 @@ export async function prepareIsolatedSuiteAppData({
isScopedAgentsSuite() || isScopedAgentsSuite() ||
isProjectSkillSuite() || isProjectSkillSuite() ||
isParallelReadSuite() || isParallelReadSuite() ||
isSupervisorSwarmSuite() isSupervisorSwarmSuite() ||
isSupervisorAutonomousPlayableLaneDefenseSuite()
? 'private-copy' ? 'private-copy'
: 'hardlink'; : 'hardlink';
try { try {
@@ -796,7 +797,7 @@ export async function prepareIsolatedSuiteAppData({
storageMode === 'private-copy' && storageMode === 'private-copy' &&
(linkedMetadata.dev !== source.metadata.dev || (linkedMetadata.dev !== source.metadata.dev ||
linkedMetadata.ino !== source.metadata.ino) && linkedMetadata.ino !== source.metadata.ino) &&
(linkedMetadata.mode & 0o077) === 0; (process.platform === 'win32' || (linkedMetadata.mode & 0o077) === 0);
const hardlinkValid = const hardlinkValid =
storageMode === 'hardlink' && storageMode === 'hardlink' &&
linkedMetadata.dev === source.metadata.dev && linkedMetadata.dev === source.metadata.dev &&
@@ -1976,7 +1977,7 @@ export async function verifyIsolatedSuiteConfigLinksUnchanged() {
linkedMetadata.ino === link.linkedIno && linkedMetadata.ino === link.linkedIno &&
(linkedMetadata.dev !== sourceMetadata.dev || (linkedMetadata.dev !== sourceMetadata.dev ||
linkedMetadata.ino !== sourceMetadata.ino) && linkedMetadata.ino !== sourceMetadata.ino) &&
(linkedMetadata.mode & 0o077) === 0 (process.platform === 'win32' || (linkedMetadata.mode & 0o077) === 0)
: linkedMetadata.dev === link.dev && linkedMetadata.ino === link.ino; : linkedMetadata.dev === link.dev && linkedMetadata.ino === link.ino;
const sourceMetadataStable = const sourceMetadataStable =
sourceMetadata.mode === link.sourceMode && sourceMetadata.mode === link.sourceMode &&
@@ -36,8 +36,6 @@ export const ungeneratedGameEntryMarker =
'还没有生成游戏。回到聊天输入创意并确认生成后'; '还没有生成游戏。回到聊天输入创意并确认生成后';
export const defaultRealSwarmTestTask = export const defaultRealSwarmTestTask =
'制作一个可直接试玩的原创植物塔防小游戏:玩家选择并放置原创守卫阻挡敌人,完成波次后可以进入下一关并重新开始。主题、单位名称与视觉语言必须原创,不使用任何现有游戏角色、单位名、Logo 或受保护视觉语言。请自主完成正式产物、静态检查和双视口试玩验证。'; '制作一个可直接试玩的原创植物塔防小游戏:玩家选择并放置原创守卫阻挡敌人,完成波次后可以进入下一关并重新开始。主题、单位名称与视觉语言必须原创,不使用任何现有游戏角色、单位名、Logo 或受保护视觉语言。请自主完成正式产物、静态检查和双视口试玩验证。';
export const defaultRealSwarmPlanTask =
'我想做一款原创横版像素解谜小游戏,主角是一个能操控自己影子的小机器人,影子可以变成平台和开关。请完成立项策划并给出 Fast GDD。主题、角色名与视觉语言必须原创,不使用任何现有游戏角色、名称、Logo 或受保护视觉语言。';
export const swarmTurnReportPrefix = '[turn.report] '; export const swarmTurnReportPrefix = '[turn.report] ';
export const swarmTurnReportSchema = 'game-creator-swarm-turn-report.v1'; export const swarmTurnReportSchema = 'game-creator-swarm-turn-report.v1';
@@ -140,15 +138,10 @@ export const usage = `用法:
--keep-project 保留自动创建的一次性项目 --keep-project 保留自动创建的一次性项目
--no-open 手工模式启动预览但不自动打开浏览器 --no-open 手工模式启动预览但不自动打开浏览器
--task <需求> 通过 manual 入口非交互提交自定义需求 --task <需求> 通过 manual 入口非交互提交自定义需求
--plan 走「做方案」立项策划入口,不做游戏,不做产物验收和试玩 --timeout-minutes <分钟> 设置本次执行期限;自动任务默认 50 分钟,手工模式默认不限时
--timeout-minutes <分钟> 设置本次执行期限;自动任务默认 50 分钟,--plan 默认 6 分钟,手工模式默认不限时
--dry-run 只检查目录发现和项目准备,不启动 LLM --dry-run 只检查目录发现和项目准备,不启动 LLM
-h, --help 显示帮助 -h, --help 显示帮助
`;
环境变量:
AGC_PLAN_GDD_DECISION 审批卡自动应答动作,默认 approverevise/reject 必须
同时用 AGC_PLAN_GDD_COMMENT 给出真实修改意见
AGC_PLAN_GDD_COMMENT revise/reject 的意见原文`;
function readOptionValue(args, index, option) { function readOptionValue(args, index, option) {
const value = args[index + 1]?.trim(); const value = args[index + 1]?.trim();
@@ -177,7 +170,6 @@ export function parseSwarmTestArguments(args) {
keepProject: false, keepProject: false,
openBrowser: true, openBrowser: true,
task: null, task: null,
plan: false,
timeoutMinutes: null, timeoutMinutes: null,
dryRun: false, dryRun: false,
help: false, help: false,
@@ -202,8 +194,6 @@ export function parseSwarmTestArguments(args) {
if (task.length > 4_000) throw new Error('--task 不能超过 4000 字符'); if (task.length > 4_000) throw new Error('--task 不能超过 4000 字符');
options.task = task; options.task = task;
index += 1; index += 1;
} else if (argument === '--plan') {
options.plan = true;
} else if (argument === '--timeout-minutes') { } else if (argument === '--timeout-minutes') {
if (options.timeoutMinutes !== null) { if (options.timeoutMinutes !== null) {
throw new Error('--timeout-minutes 只能指定一次'); throw new Error('--timeout-minutes 只能指定一次');
@@ -222,16 +212,11 @@ export function parseSwarmTestArguments(args) {
} }
export function shouldStartPersistentPreview(options) { export function shouldStartPersistentPreview(options) {
// 立项策划链路只出 GDD,没有可试玩产物,任何模式都不该起预览。 return !options.task;
return !options.task && !options.plan;
} }
export function resolveSwarmTestTimeoutMs(options) { export function resolveSwarmTestTimeoutMs(options) {
// 立项策划的设计目标是五分钟出方案,给一分钟余量;再久就是卡住了,早失败 const minutes = options.timeoutMinutes ?? (options.task ? 50 : null);
// 比让 harness 空等更有用。做游戏那条链路的 50 分钟不变。
const planMinutes = options.plan ? 6 : null;
const minutes =
options.timeoutMinutes ?? planMinutes ?? (options.task ? 50 : null);
return minutes === null ? null : minutes * 60_000; return minutes === null ? null : minutes * 60_000;
} }
@@ -979,25 +964,12 @@ export function swarmAutoPilotShouldCloseInput(output, promptsAfterSubmit) {
return swarmAutoPilotSitsAtPrompt(output) && promptsAfterSubmit >= 1; 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( async function runTaskCargo(
cliArguments, cliArguments,
task, task,
setActiveChild, setActiveChild,
timeoutMs, timeoutMs,
autoPilot = false, autoPilot = false,
onPlanGddApprovalWait = null,
) { ) {
const child = spawnChild(cargoCommand, buildCargoCliArguments(cliArguments), { const child = spawnChild(cargoCommand, buildCargoCliArguments(cliArguments), {
stdio: ['pipe', 'pipe', 'inherit'], stdio: ['pipe', 'pipe', 'inherit'],
@@ -1009,25 +981,6 @@ async function runTaskCargo(
let taskSubmitted = false; let taskSubmitted = false;
let promptsSeen = 0; let promptsSeen = 0;
let sittingAtPrompt = false; 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.setEncoding('utf8');
child.stdout.on('data', (chunk) => { child.stdout.on('data', (chunk) => {
process.stdout.write(chunk); process.stdout.write(chunk);
@@ -1040,13 +993,6 @@ async function runTaskCargo(
reportLines.push(normalizedLine); reportLines.push(normalizedLine);
settled = true; settled = true;
} }
if (
onPlanGddApprovalWait &&
!planGddApprovalStarted &&
swarmOutputAwaitsPlanGddApproval(normalizedLine)
) {
startPlanGddApproval();
}
} }
if (!autoPilot || child.stdin.writableEnded) return; if (!autoPilot || child.stdin.writableEnded) return;
const atPrompt = swarmAutoPilotSitsAtPrompt(pendingLine); const atPrompt = swarmAutoPilotSitsAtPrompt(pendingLine);
@@ -1085,12 +1031,9 @@ async function runTaskCargo(
if (normalizedPendingLine.startsWith(swarmTurnReportPrefix)) { if (normalizedPendingLine.startsWith(swarmTurnReportPrefix)) {
reportLines.push(normalizedPendingLine); reportLines.push(normalizedPendingLine);
} }
await planGddApprovalPromise;
if (planGddApprovalError) throw planGddApprovalError;
return { return {
...result, ...result,
turnReportOutput: reportLines.join('\n'), turnReportOutput: reportLines.join('\n'),
planGddApproval,
}; };
} finally { } finally {
setActiveChild(null); setActiveChild(null);
@@ -1780,196 +1723,6 @@ export async function validateSwarmProjectArtifacts(projectPath, options) {
return inspection; 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) { export async function hasConfiguredEditorApiKey(configDir) {
let configured = false; let configured = false;
for (const fileName of [configFileName, localConfigFileName]) { for (const fileName of [configFileName, localConfigFileName]) {
@@ -2122,13 +1875,7 @@ export async function runSwarmTestChat(options) {
const setActiveChild = (child) => { const setActiveChild = (child) => {
activeChild = child; activeChild = child;
}; };
// GDD 审批要和 swarm CLI 并发跑,两者不能共用 activeChild 这一个槽位:审批子进程
// 结束时的 setActiveChild(null) 会把 CLI 从槽里抹掉,Ctrl-C 就杀不到它了。
const concurrentChildren = new Set(); const concurrentChildren = new Set();
const setConcurrentChild = (child) => {
if (child) concurrentChildren.add(child);
else concurrentChildren.clear();
};
const stopRequested = () => receivedSignal !== null; const stopRequested = () => receivedSignal !== null;
const handleSignal = (signal) => { const handleSignal = (signal) => {
const repeatedSignal = receivedSignal !== null; const repeatedSignal = receivedSignal !== null;
@@ -2206,11 +1953,10 @@ export async function runSwarmTestChat(options) {
); );
} }
console.log('LLM 配置已就绪。'); console.log('LLM 配置已就绪。');
const requirementNoun = options.plan ? '立项策划需求' : '游戏需求';
console.log( console.log(
options.task options.task
? `已提交一条非交互${requirementNoun},正在等待 Swarm 自主完成。\n` ? '已提交一条非交互游戏需求,正在等待 Swarm 自主完成。\n'
: `输入一条${requirementNoun}并回车;提交后按 Ctrl+D,让 Swarm 自主完成。\n`, : '输入一条游戏需求并回车;提交后按 Ctrl+D,让 Swarm 自主完成。\n',
); );
phase = 'chat'; phase = 'chat';
@@ -2220,8 +1966,7 @@ export async function runSwarmTestChat(options) {
runtimeConfig.path, runtimeConfig.path,
'--swarm-chat', '--swarm-chat',
'--init', '--init',
// 做方案链路只能跑 standard 档,后端对 plan + autonomous 是硬否决。 '--autonomous-game-build',
options.plan ? '--plan' : '--autonomous-game-build',
project.path, project.path,
]; ];
let chat; let chat;
@@ -2234,15 +1979,6 @@ export async function runSwarmTestChat(options) {
timeoutDeadline === null timeoutDeadline === null
? null ? null
: Math.max(1, timeoutDeadline - Date.now()), : Math.max(1, timeoutDeadline - Date.now()),
options.plan,
options.plan
? () =>
settlePlanGddApproval(
project.path,
runtimeConfig.path,
setConcurrentChild,
)
: null,
) )
: await runInteractiveCargo(chatArguments, setActiveChild); : await runInteractiveCargo(chatArguments, setActiveChild);
} catch (error) { } catch (error) {
@@ -2258,32 +1994,6 @@ export async function runSwarmTestChat(options) {
if (options.task) { if (options.task) {
turnReport = parseSettledSwarmTurnReport(chat.turnReportOutput); 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( const requireEditorImages = await hasConfiguredEditorApiKey(
runtimeConfig.path, runtimeConfig.path,
); );
@@ -107,12 +107,16 @@ const rustSharedContractSource = fs.readFileSync(
'utf8', 'utf8',
); );
const allowedUncalledTauriCommands = [ const allowedUncalledTauriCommands = [
'append_direct_project_conversation_message',
'chat_with_game_creator_agent', 'chat_with_game_creator_agent',
'check_ui_editor_font_glyph_coverage', 'check_ui_editor_font_glyph_coverage',
'create_ui_design_resource', 'create_ui_design_resource',
'open_game_creator_launcher_window', 'open_game_creator_launcher_window',
'open_game_creator_workspace_window', 'open_game_creator_workspace_window',
'read_direct_project_conversation',
'stop_local_game_preview_if_matches', 'stop_local_game_preview_if_matches',
'start_game_creator_external_mcp',
'stop_game_creator_external_mcp',
]; ];
const sourceExtensions = new Set([ const sourceExtensions = new Set([
'.json', '.json',
@@ -1701,10 +1705,16 @@ if (
runtimeConfigSetupStart === -1 || runtimeConfigSetupStart === -1 ||
runtimeConfigSetupEnd === -1 || runtimeConfigSetupEnd === -1 ||
!runtimeConfigSetupSource.includes('sanitize_diagnostic_message(') || !runtimeConfigSetupSource.includes('sanitize_diagnostic_message(') ||
!runtimeConfigSetupSource.includes('append_bounded_diagnostic_line(') || !runtimeConfigSetupSource.includes('setup_log.fail(') ||
!runtimeConfigSetupSource.includes( !runtimeConfigSetupSource.includes(
'startup.appdata.configure.failed details={details}', '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( throw new Error(
'AI game creator setup must configure the runtime AppData directory and log sanitized setup failures', 'AI game creator setup must configure the runtime AppData directory and log sanitized setup failures',
@@ -1753,9 +1763,8 @@ for (const snippet of [
"'read_game_creator_app_config'", "'read_game_creator_app_config'",
"'write_game_creator_app_config'", "'write_game_creator_app_config'",
'aria-label="运行时配置"', 'aria-label="运行时配置"',
'LLM API Key', '陶泥儿智能创作(固定)',
'External Editor Base URL', '官方账号服务(固定)',
'External Editor API Key',
'runtime_config.save', 'runtime_config.save',
"'/run:运行自检,启动本地 HTTP 预览并载入客户端运行视图'", "'/run:运行自检,启动本地 HTTP 预览并载入客户端运行视图'",
"'activate_local_game_preview'", "'activate_local_game_preview'",
@@ -26,6 +26,7 @@ const repositoryRoot = path.resolve(appRoot, '..', '..');
const cargoManifestPath = path.join(appRoot, 'src-tauri', 'Cargo.toml'); const cargoManifestPath = path.join(appRoot, 'src-tauri', 'Cargo.toml');
const defaultConfigPath = path.join(appRoot, configFileName); const defaultConfigPath = path.join(appRoot, configFileName);
const localConfigFileName = 'game-creator.config.local.json'; const localConfigFileName = 'game-creator.config.local.json';
const gameCreatorConfigSchemaVersion = 'game-creator-config.v2';
const cargoCommand = process.platform === 'win32' ? 'cargo.exe' : 'cargo'; const cargoCommand = process.platform === 'win32' ? 'cargo.exe' : 'cargo';
const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm'; const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
@@ -211,6 +212,7 @@ export function buildGameCreatorWizardConfig(existingConfig, llmInput) {
} }
return { return {
...source, ...source,
schemaVersion: gameCreatorConfigSchemaVersion,
agentMode: 'provider', agentMode: 'provider',
llm: { llm: {
...previousLlm, ...previousLlm,
@@ -7,6 +7,7 @@ import path from 'node:path';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
const appRoot = fileURLToPath(new URL('..', import.meta.url)); const appRoot = fileURLToPath(new URL('..', import.meta.url));
const inheritedChildEnvironment = { ...globalThis['process']['env'] };
const localConfigPath = path.join(appRoot, 'game-creator.config.local.json'); const localConfigPath = path.join(appRoot, 'game-creator.config.local.json');
const projectRoot = path.join( const projectRoot = path.join(
os.tmpdir(), os.tmpdir(),
@@ -671,6 +672,11 @@ function runAgent() {
{ {
cwd: appRoot, cwd: appRoot,
stdio: ['pipe', 'pipe', 'pipe'], stdio: ['pipe', 'pipe', 'pipe'],
env: {
...inheritedChildEnvironment,
// 该 smoke 只使用一次性 loopback Provider;生产路由仍保持锁定。
GENARRATIVE_AGC_DEBUG_PROVIDER_E2E: '1',
},
}, },
); );
let stdout = ''; let stdout = '';
@@ -716,7 +722,7 @@ function runAgent() {
stderr += chunk.toString(); stderr += chunk.toString();
}); });
child.on('error', reject); child.on('error', reject);
child.on('close', (code) => { child.on('close', (code, signal) => {
const output = `${stdout}${stderr}`; const output = `${stdout}${stderr}`;
if (previewReadError) { if (previewReadError) {
reject(previewReadError); reject(previewReadError);
@@ -734,13 +740,24 @@ function runAgent() {
previewDom, previewDom,
}); });
} else { } 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() { 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, 'assets/uploads'), { recursive: true });
await fs.mkdir(path.join(projectRoot, '.agent'), { recursive: true }); await fs.mkdir(path.join(projectRoot, '.agent'), { recursive: true });
await seedConversationContext(); 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 { existsSync, readdirSync, readFileSync } from 'node:fs';
import http from 'node:http'; import http from 'node:http';
import net from 'node:net'; import net from 'node:net';
import { resolve } from 'node:path'; import { resolve } from 'node:path';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
import {
normalizeWindowsPath,
parseWindowsProcessSnapshot,
stopWindowsProcessTree,
stopWindowsWorktreeProcesses,
} from '../../../scripts/dev-windows-process.mjs';
import { import {
agcVitePortEnvKey, agcVitePortEnvKey,
readAgcDevEndpoint, readAgcDevEndpoint,
@@ -15,6 +21,10 @@ import {
const appRoot = fileURLToPath(new URL('..', import.meta.url)); const appRoot = fileURLToPath(new URL('..', import.meta.url));
const repoRoot = resolve(appRoot, '../..'); const repoRoot = resolve(appRoot, '../..');
const devStackStatePath = resolve(repoRoot, '.app/dev-stack.json'); const devStackStatePath = resolve(repoRoot, '.app/dev-stack.json');
const apiServerExePath = resolve(
repoRoot,
'server-rs/target/debug/api-server.exe',
);
const defaultApiTarget = const defaultApiTarget =
process.env.RUST_SERVER_TARGET || 'http://127.0.0.1:8082'; process.env.RUST_SERVER_TARGET || 'http://127.0.0.1:8082';
const backendDatabase = 'genarrative-game-creator-dev'; const backendDatabase = 'genarrative-game-creator-dev';
@@ -127,19 +137,217 @@ function readBackendTargets({ requireAgcBackend = false } = {}) {
}); });
} }
function readBackendServiceFailure(
state,
{
expectedDatabase = backendDatabase,
expectedSpacetimeDataDir = backendSpacetimeDataDir,
} = {},
) {
const targets = resolveBackendTargetsFromState(state, {
requireAgcBackend: true,
expectedDatabase,
expectedSpacetimeDataDir,
});
if (!targets.hasMatchingBackend) {
return null;
}
for (const serviceName of ['spacetime', 'api-server', 'bgfilter-worker']) {
const service = state?.services?.[serviceName];
if (service?.status !== 'failed') {
continue;
}
return {
serviceName,
failure: service.signal
? `signal=${service.signal}`
: `code=${service.exitCode ?? 1}`,
};
}
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({ async function isBackendReady({
state = readJson(devStackStatePath), state = readJson(devStackStatePath),
isReady = isHttpReady, isReady = isHttpReady,
verifyOwnership = verifyAgcBackendOwnership,
onOwnershipRejected = null,
} = {}) { } = {}) {
const { apiUrl, spacetimeUrl, bgfilterWorkerUrl, hasMatchingBackend } = const { apiUrl, spacetimeUrl, bgfilterWorkerUrl, hasMatchingBackend } =
resolveBackendTargetsFromState(state, { resolveBackendTargetsFromState(state, {
requireAgcBackend: true, 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 ( return (
hasMatchingBackend &&
Boolean(apiUrl) &&
Boolean(spacetimeUrl) &&
Boolean(bgfilterWorkerUrl) &&
(await isReady(`${apiUrl}/healthz`)) && (await isReady(`${apiUrl}/healthz`)) &&
(await isReady(`${spacetimeUrl}/v1/ping`)) && (await isReady(`${spacetimeUrl}/v1/ping`)) &&
(await isReady(`${bgfilterWorkerUrl}/readyz`)) (await isReady(`${bgfilterWorkerUrl}/readyz`))
@@ -452,14 +660,18 @@ async function terminateChildTree(
return { stopped: true, forced: false }; return { stopped: true, forced: false };
} }
const result = await taskkillImpl(child.pid); const result = await taskkillImpl(child.pid);
return { const taskkillStopped =
stopped: !result?.timedOut &&
!result?.timedOut && !result?.error &&
!result?.error && [0, 128].includes(result?.code ?? 0);
[0, 128].includes(result?.code ?? 0), if (taskkillStopped) {
forced: true, return { stopped: true, forced: true, result };
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; const processGroupId = childLifecycles.get(child)?.processGroupId;
@@ -505,11 +717,43 @@ async function terminateChildTree(
return { stopped, forced: true }; return { stopped, forced: true };
} }
async function waitForBackendReady(backendChild, timeoutMs = 600_000) { async function waitForBackendReady(
backendChild,
timeoutMs = 600_000,
{
checkBackendReady = (onOwnershipRejected) =>
isBackendReady({ onOwnershipRejected }),
readState = () => readJson(devStackStatePath),
resolveTargets = readBackendTargets,
} = {},
) {
const initialStateUpdatedAt = readState()?.updatedAt ?? '';
const startedAt = Date.now(); const startedAt = Date.now();
let lastOwnershipReason = '';
while (Date.now() - startedAt < timeoutMs) { while (Date.now() - startedAt < timeoutMs) {
if (await isBackendReady()) { if (
return readBackendTargets(); 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();
if ((state?.updatedAt ?? '') !== initialStateUpdatedAt) {
const serviceFailure = readBackendServiceFailure(state);
if (serviceFailure) {
throw new Error(
`配套后端启动失败: ${serviceFailure.serviceName} ${serviceFailure.failure}`,
);
}
} }
const failure = readChildFailure(backendChild); const failure = readChildFailure(backendChild);
if (failure) { if (failure) {
@@ -525,7 +769,14 @@ async function waitForBackendReady(backendChild, timeoutMs = 600_000) {
async function ensureBackend({ async function ensureBackend({
onBackendChild = () => {}, onBackendChild = () => {},
checkBackendReady = isBackendReady, checkBackendReady = () =>
isBackendReady({
onOwnershipRejected(ownership) {
console.warn(
`[ai-game-creator-shell] 端口上的配套后端不属于当前工作树(${ownership.reason}: ${formatOwnerLabel(ownership.apiOwner ?? ownership.spacetimeOwner ?? ownership.workerOwner)}),改为启动本工作树自己的后端。`,
);
},
}),
resolveTargets = readBackendTargets, resolveTargets = readBackendTargets,
spawnBackend = () => spawnBackend = () =>
spawnChild( spawnChild(
@@ -540,6 +791,7 @@ async function ensureBackend({
backendDatabase, backendDatabase,
'--spacetime-data-dir', '--spacetime-data-dir',
backendSpacetimeDataDir, backendSpacetimeDataDir,
'--preserve-database',
'--no-interactive', '--no-interactive',
], ],
{ cwd: appRoot }, { cwd: appRoot },
@@ -604,15 +856,33 @@ async function startVite(apiTarget, endpoint = readAgcDevEndpoint()) {
async function main() { async function main() {
let backendChild = null; let backendChild = null;
let startedBackend = false;
let viteChild = null; let viteChild = null;
let shutdownSignal = ''; let shutdownSignal = '';
const signalHandlers = new Map(); 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']) { for (const signal of ['SIGINT', 'SIGTERM']) {
const handler = () => { const handler = () => {
shutdownSignal = signal; shutdownSignal = signal;
stopChild(viteChild, signal); stopChild(viteChild, signal);
stopChild(backendChild, signal); stopChild(backendChild, signal);
// 立刻清扫,避免外层 taskkill /F 抢在 finally 之前把本进程杀掉。
sweepStartedBackend();
}; };
signalHandlers.set(signal, handler); signalHandlers.set(signal, handler);
process.on(signal, handler); process.on(signal, handler);
@@ -631,6 +901,7 @@ async function main() {
}, },
}); });
backendChild = backend.backendChild; backendChild = backend.backendChild;
startedBackend = Boolean(backendChild);
if (shutdownSignal) { if (shutdownSignal) {
throw new Error(`启动期收到 ${shutdownSignal},已停止配套后端`); throw new Error(`启动期收到 ${shutdownSignal},已停止配套后端`);
} }
@@ -664,6 +935,7 @@ async function main() {
terminateChildTree(viteChild), terminateChildTree(viteChild),
terminateChildTree(backendChild), terminateChildTree(backendChild),
]); ]);
sweepStartedBackend();
for (const [signal, handler] of signalHandlers) { for (const [signal, handler] of signalHandlers) {
process.off(signal, handler); process.off(signal, handler);
} }
@@ -680,17 +952,25 @@ function isDirectModuleExecution() {
export { export {
ensureBackend, ensureBackend,
formatChildFailure, formatChildFailure,
formatOwnerLabel,
isAiGameCreatorServer,
isBackendReady, isBackendReady,
isDirectModuleExecution, isDirectModuleExecution,
isProcessGroupAlive, isProcessGroupAlive,
isWorktreeApiServerOwner,
isWorktreeSpacetimeOwner,
preflightExistingVite, preflightExistingVite,
readBackendServiceFailure,
readChildFailure, readChildFailure,
readExistingViteServer,
readLinuxProcessGroupAlive, readLinuxProcessGroupAlive,
readWindowsPortOwnerIdentities,
resolveBackendTargetsFromState, resolveBackendTargetsFromState,
runWindowsTaskkill, runWindowsTaskkill,
spawnChild, spawnChild,
stopChild, stopChild,
terminateChildTree, terminateChildTree,
verifyAgcBackendOwnership,
waitForBackendReady, waitForBackendReady,
waitForChildTermination, waitForChildTermination,
}; };
@@ -7,7 +7,10 @@ import {
withAgcDevEndpointEnv, withAgcDevEndpointEnv,
} from './dev-port.mjs'; } from './dev-port.mjs';
import { import {
isAiGameCreatorServer,
preflightExistingVite, preflightExistingVite,
readChildFailure,
readExistingViteServer,
spawnChild, spawnChild,
stopChild, stopChild,
terminateChildTree, terminateChildTree,
@@ -20,7 +23,9 @@ const tauriCliPath = resolve(repoRoot, 'node_modules/@tauri-apps/cli/tauri.js');
function buildTauriArguments(argv, devUrl = readAgcDevEndpoint().url) { function buildTauriArguments(argv, devUrl = readAgcDevEndpoint().url) {
const args = [...argv]; const args = [...argv];
const configOverride = JSON.stringify({ build: { devUrl } }); const configOverride = JSON.stringify({
build: { devUrl, beforeDevCommand: '' },
});
const separatorIndex = args.indexOf('--'); const separatorIndex = args.indexOf('--');
if (separatorIndex < 0) { if (separatorIndex < 0) {
return ['dev', ...args, '--config', configOverride]; return ['dev', ...args, '--config', configOverride];
@@ -51,6 +56,7 @@ async function runTauriDev(
{ {
resolveDevEndpoint = resolveAgcDevEndpoint, resolveDevEndpoint = resolveAgcDevEndpoint,
preflight = preflightExistingVite, preflight = preflightExistingVite,
prepareFrontend = prepareFrontendDev,
spawnCli = spawnTauriCli, spawnCli = spawnTauriCli,
waitForCli = waitForChildTermination, waitForCli = waitForChildTermination,
terminateTree = terminateChildTree, terminateTree = terminateChildTree,
@@ -59,10 +65,9 @@ async function runTauriDev(
const endpoint = await resolveDevEndpoint(); const endpoint = await resolveDevEndpoint();
await preflight({ endpoint }); await preflight({ endpoint });
const tauriArguments = buildTauriArguments(argv, endpoint.url); let child = null;
const child = spawnCli(tauriArguments, { let frontendChild = null;
env: withAgcDevEndpointEnv(endpoint), const preparationAbort = new AbortController();
});
let resolveShutdown; let resolveShutdown;
let shutdownSignal = ''; let shutdownSignal = '';
let repeatedSignal = false; let repeatedSignal = false;
@@ -76,21 +81,47 @@ async function runTauriDev(
if (!shutdownSignal) { if (!shutdownSignal) {
shutdownSignal = signal; shutdownSignal = signal;
stopChild(child, 'SIGTERM'); stopChild(child, 'SIGTERM');
stopChild(frontendChild, 'SIGTERM');
preparationAbort.abort();
resolveShutdown(signal); resolveShutdown(signal);
return; return;
} }
repeatedSignal = true; repeatedSignal = true;
stopChild(child, 'SIGKILL'); stopChild(child, 'SIGKILL');
stopChild(frontendChild, 'SIGKILL');
}; };
signalHandlers.set(signal, handler); signalHandlers.set(signal, handler);
process.on(signal, handler); process.on(signal, handler);
} }
try { try {
const preparation = prepareFrontend(endpoint, {
signal: preparationAbort.signal,
onChild(frontend) {
frontendChild = frontend;
},
});
const prepared = await Promise.race([
preparation.then(() => true),
shutdownRequested.then(() => false),
]);
if (!prepared || shutdownSignal) return 1;
const tauriArguments = buildTauriArguments(argv, endpoint.url);
child = spawnCli(tauriArguments, {
env: withAgcDevEndpointEnv(endpoint),
});
const childResult = waitForCli(child); const childResult = waitForCli(child);
const outcome = await Promise.race([ const outcome = await Promise.race([
childResult.then((failure) => ({ type: 'exit', failure })), childResult.then((failure) => ({ type: 'exit', failure })),
shutdownRequested.then((signal) => ({ type: 'signal', signal })), shutdownRequested.then((signal) => ({ type: 'signal', signal })),
...(frontendChild
? [
waitForChildTermination(frontendChild).then((failure) => ({
type: 'frontend-exit',
failure,
})),
]
: []),
]); ]);
const cleanup = await terminateTree(child, { const cleanup = await terminateTree(child, {
gracefulTimeoutMs: repeatedSignal ? 0 : 2500, gracefulTimeoutMs: repeatedSignal ? 0 : 2500,
@@ -105,15 +136,51 @@ async function runTauriDev(
if (outcome.type === 'signal') { if (outcome.type === 'signal') {
return 1; return 1;
} }
if (outcome.type === 'frontend-exit') return 1;
const { failure } = outcome; const { failure } = outcome;
return failure.type === 'error' || failure.signal ? 1 : (failure.code ?? 0); return failure.type === 'error' || failure.signal ? 1 : (failure.code ?? 0);
} finally { } finally {
for (const [signal, handler] of signalHandlers) { for (const [signal, handler] of signalHandlers) {
process.off(signal, handler); process.off(signal, handler);
} }
preparationAbort.abort();
if (frontendChild) {
const cleanup = await terminateTree(frontendChild);
if (!cleanup.stopped) {
console.error('[ai-game-creator-shell] 配套开发服务未能完全停止。');
}
}
} }
} }
async function prepareFrontendDev(endpoint, { onChild, signal }) {
const frontend = spawnChild(
process.platform === 'win32' ? 'npm.cmd' : 'npm',
['run', 'agc:serve'],
{ cwd: repoRoot, env: withAgcDevEndpointEnv(endpoint) },
);
onChild(frontend);
console.log(
'[ai-game-creator-shell] 正在准备前端与配套后端,完成后启动 Tauri',
);
const deadline = Date.now() + 660_000;
while (Date.now() < deadline) {
signal.throwIfAborted();
const failure = readChildFailure(frontend);
if (failure) {
throw new Error(
`配套开发服务退出,前端未就绪:${failure.error?.message ?? failure.signal ?? failure.code}`,
);
}
if (isAiGameCreatorServer(await readExistingViteServer(endpoint))) return;
await Promise.race([
new Promise((resolveWait) => setTimeout(resolveWait, 1000)),
waitForChildTermination(frontend),
]);
}
throw new Error(`等待前端与配套后端就绪超时:${endpoint.url}`);
}
function isDirectModuleExecution() { function isDirectModuleExecution() {
return Boolean( return Boolean(
process.argv[1] && process.argv[1] &&
@@ -43,7 +43,6 @@ struct PromptCompositions {
/// 而是一份独立的完整清单:plan 根的工具面只有 7 个原生工具,专业组、 /// 而是一份独立的完整清单:plan 根的工具面只有 7 个原生工具,专业组、
/// isolated child、任务图与视觉产物合同在这条链路上全部不可执行,逐段 /// isolated child、任务图与视觉产物合同在这条链路上全部不可执行,逐段
/// 减法会把「plan 根到底看到什么」摊在两个函数的四个否定分支里。 /// 减法会把「plan 根到底看到什么」摊在两个函数的四个否定分支里。
supervisor_plan: Vec<String>,
supervisor_chat: SupervisorChatComposition, supervisor_chat: SupervisorChatComposition,
} }
@@ -99,10 +98,6 @@ struct ProviderFragments {
#[serde(rename_all = "camelCase", deny_unknown_fields)] #[serde(rename_all = "camelCase", deny_unknown_fields)]
struct AgentCatalog { struct AgentCatalog {
supervisor: AgentGroup, supervisor: AgentGroup,
/// 立项策划子 Agent。与 `supervisor` 平级、**不进 `groups`**`specialist_nodes`
/// 只从 `groups[].roles[]` 派生,因此它不参与 `build.rs` 与种子 DAG 的一致性
/// 校验,「做游戏」的 16 任务 DAG 一行不动。详见技术方案第 3.1 节。
planning: AgentGroup,
groups: Vec<AgentGroup>, groups: Vec<AgentGroup>,
} }
@@ -231,12 +226,6 @@ pub fn compile_manifest(manifest_path: &Path) -> Result<CompiledPromptBundle, St
&sections, &sections,
&["$base", "$visualContract"], &["$base", "$visualContract"],
)?; )?;
validate_composition(
"supervisorPlan",
&manifest.compositions.supervisor_plan,
&sections,
&["$header"],
)?;
validate_section_reference( validate_section_reference(
&manifest.compositions.supervisor_chat.identity, &manifest.compositions.supervisor_chat.identity,
&sections, &sections,
@@ -299,7 +288,6 @@ pub fn compile_manifest(manifest_path: &Path) -> Result<CompiledPromptBundle, St
.supervisor .supervisor
.roles .roles
.iter() .iter()
.chain(manifest.agent_catalog.planning.roles.iter())
.chain( .chain(
manifest manifest
.agent_catalog .agent_catalog
@@ -348,7 +336,6 @@ pub fn compile_manifest(manifest_path: &Path) -> Result<CompiledPromptBundle, St
.runtime .runtime
.iter() .iter()
.chain(manifest.compositions.supervisor.iter()) .chain(manifest.compositions.supervisor.iter())
.chain(manifest.compositions.supervisor_plan.iter())
.filter(|item| !item.starts_with('$')) .filter(|item| !item.starts_with('$'))
.cloned() .cloned()
.collect::<BTreeSet<_>>(); .collect::<BTreeSet<_>>();
@@ -421,14 +408,6 @@ fn validate_section_ownership(manifest: &PromptBundleManifest) -> Result<(), Str
{ {
register("composition supervisor", section); 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.identity", identity);
register( register(
"composition supervisorChat.finalReply", "composition supervisorChat.finalReply",
@@ -457,19 +436,9 @@ fn validate_section_ownership(manifest: &PromptBundleManifest) -> Result<(), Str
"composition supervisor", "composition supervisor",
"composition supervisorChat.identity", "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 { for (section, section_owners) in owners {
if section_owners.len() > 1 if section_owners.len() > 1
&& !(section == identity && section_owners == allowed_identity_owners) && !(section == identity && section_owners == allowed_identity_owners)
&& section_owners != allowed_plan_runtime_owners
&& section_owners != allowed_plan_supervisor_owners
{ {
return Err(format!( return Err(format!(
"Prompt section 跨语义所有者复用:{section} -> {section_owners:?}" "Prompt section 跨语义所有者复用:{section} -> {section_owners:?}"
@@ -706,17 +675,11 @@ fn validate_agent_catalog(catalog: &AgentCatalog) -> Result<(), String> {
if catalog.supervisor.roles.len() != 1 { if catalog.supervisor.roles.len() != 1 {
return Err("agentCatalog.supervisor 必须且只能包含一个 role".to_string()); 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() { if catalog.groups.is_empty() {
return Err("agentCatalog.groups 不能为空".to_string()); return Err("agentCatalog.groups 不能为空".to_string());
} }
let mut group_brief_names = BTreeSet::new(); let mut group_brief_names = BTreeSet::new();
for group in std::iter::once(&catalog.supervisor) for group in std::iter::once(&catalog.supervisor).chain(catalog.groups.iter()) {
.chain(std::iter::once(&catalog.planning))
.chain(catalog.groups.iter())
{
if !group_brief_names.insert(group.brief_path_name.as_str()) { if !group_brief_names.insert(group.brief_path_name.as_str()) {
return Err(format!( return Err(format!(
"agent group briefPathName 重复:{}", "agent group briefPathName 重复:{}",
@@ -724,10 +687,7 @@ fn validate_agent_catalog(catalog: &AgentCatalog) -> Result<(), String> {
)); ));
} }
} }
let mut generated_names = BTreeSet::from([ let mut generated_names = BTreeSet::from(["PROJECT_SUPERVISOR".to_string()]);
"PROJECT_SUPERVISOR".to_string(),
"PROJECT_PLANNING".to_string(),
]);
for group in &catalog.groups { for group in &catalog.groups {
let generated = rust_identifier(&group.id); let generated = rust_identifier(&group.id);
if !generated if !generated
@@ -753,12 +713,6 @@ fn validate_agent_catalog(catalog: &AgentCatalog) -> Result<(), String> {
&mut task_ids, &mut task_ids,
&mut tool_ids, &mut tool_ids,
)?; )?;
validate_agent_group(
&catalog.planning,
&mut group_ids,
&mut task_ids,
&mut tool_ids,
)?;
for group in &catalog.groups { for group in &catalog.groups {
validate_agent_group(group, &mut group_ids, &mut task_ids, &mut tool_ids)?; 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", "RUNTIME_PROMPT_SUPERVISOR_COMPOSITION",
&manifest.compositions.supervisor, &manifest.compositions.supervisor,
)); ));
output.push_str(&render_string_slice_const(
"RUNTIME_PROMPT_SUPERVISOR_PLAN_COMPOSITION",
&manifest.compositions.supervisor_plan,
));
output.push_str(&format!( output.push_str(&format!(
"pub(crate) const RUNTIME_PROMPT_SUPERVISOR_CHAT_COMPOSITION: &[&str] = &[{}, {}];\n", "pub(crate) const RUNTIME_PROMPT_SUPERVISOR_CHAT_COMPOSITION: &[&str] = &[{}, {}];\n",
rust_literal(&manifest.compositions.supervisor_chat.identity), 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", "static PROJECT_SUPERVISOR_AGENT_DEFINITION: AgentGroupDefinition = {};\n",
render_group_value(&catalog.supervisor, "&PROJECT_SUPERVISOR_AGENT_ROLES") 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 { for group in &catalog.groups {
let roles_name = format!("{}_AGENT_ROLES", rust_identifier(&group.id)); let roles_name = format!("{}_AGENT_ROLES", rust_identifier(&group.id));
output.push_str(&render_role_array(&roles_name, &group.roles)); output.push_str(&render_role_array(&roles_name, &group.roles));
@@ -23,11 +23,7 @@
"supervisorVisualWithEditor": "supervisor/visual-contract-with-editor.md", "supervisorVisualWithEditor": "supervisor/visual-contract-with-editor.md",
"supervisorPlaybook": "supervisor/playbook.md", "supervisorPlaybook": "supervisor/playbook.md",
"supervisorClaimGate": "supervisor/claim-gate.md", "supervisorClaimGate": "supervisor/claim-gate.md",
"supervisorRepair": "supervisor/repair.md", "supervisorRepair": "supervisor/repair.md"
"projectPlanningRoleBrief": "roles/project-planning.md",
"planCommon": "plan/common.md",
"planSupervisorIdentity": "plan/supervisor-identity.md",
"planSupervisorPlaybook": "plan/supervisor-playbook.md"
}, },
"compositions": { "compositions": {
"runtime": [ "runtime": [
@@ -47,14 +43,6 @@
"supervisorClaimGate", "supervisorClaimGate",
"supervisorRepair" "supervisorRepair"
], ],
"supervisorPlan": [
"$header",
"planCommon",
"isolatedAgentContract",
"planSupervisorIdentity",
"planSupervisorPlaybook",
"supervisorRepair"
],
"supervisorChat": { "supervisorChat": {
"identity": "supervisorIdentityContract", "identity": "supervisorIdentityContract",
"finalReply": "supervisorFinalReplyContract" "finalReply": "supervisorFinalReplyContract"
@@ -70,12 +58,7 @@
"editorUnavailable": "supervisorVisualWithoutEditor" "editorUnavailable": "supervisorVisualWithoutEditor"
} }
}, },
"roleOverlays": [ "roleOverlays": [],
{
"agentId": "project-planning",
"sections": ["projectPlanningRoleBrief"]
}
],
"providerFragments": { "providerFragments": {
"isolatedToolContract": "providerIsolatedToolContract", "isolatedToolContract": "providerIsolatedToolContract",
"autonomousRunProfile": "providerAutonomousRunProfile", "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": [ "groups": [
{ {
"id": "design", "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,不要泄露密钥。

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