合并主线并解决界面编辑器冲突
Project CI / Repository checks (pull_request) Successful in 2m44s
Project CI / Frontend tests (pull_request) Successful in 3m25s
Project CI / Backend tests (pull_request) Failing after 4m23s
Project CI / Native shell tests (pull_request) Successful in 17m48s

合并主线的 AGC Runtime、UI 工作流与开发脚本变更

保留现行 Prompt Runtime 结构并整合设计基础提示词契约断言

适配 CanvasWorld 逆缩放渲染并修正预览缩放回归测试

同步项目文档、共享契约与已退役策划资源清理
This commit is contained in:
2026-09-12 11:38:50 +08:00
179 changed files with 12751 additions and 35721 deletions
@@ -5,10 +5,12 @@
- 后台 owner 在“AGC 模型”维护列表;每项包含稳定 `id`、必填 `alias`、服务端 `modelId`、`enabled`。默认项必须启用。标识唯一,别名唯一,列表最多 32 项。
- 配置保存到私有 `agc_model_catalog` 单例表,使用 revision 乐观锁,重启及多 api-server 实例共享同一事实。缺少配置时使用初始目录,高质量对应 `gpt-6-astra`,快速对应 `gpt-5.6-luna`。
- `GET/PUT /admin/api/agc-models` 仅 owner 可用,返回完整配置;PUT 携带上次读取的 revision,冲突拒绝覆盖。
- `GET /api/llm/models` 返回启用项的 `id/displayName` 和 `defaultModelId`,不返回实际模型名、Router 目录、凭据或能力原始数据。
- `GET /api/llm/models` 返回启用项的 `id/displayName`、`defaultModelId` 和目录 `revision`,不返回实际模型名、Router 目录、凭据或能力原始数据。
- 客户端缓存最近 `revision`,在项目切换 / 对话表面挂载 / 下拉展开 / 窗口聚焦时条件刷新:`revision` 未变化不更新界面,同一时刻只保留一个在途请求,刷新失败保留上一次有效目录与本地选择。发起对话前用同一份快照校验所选模型仍启用,已停用或删除则回退默认模型并提示。
- AGC Responses 请求的 `model` 是稳定目录标识。服务端按当前目录映射实际模型名;未知、停用项拒绝,不回退其它模型。旧客户端无 AGC 标记时使用后台默认项。
- 输入框右下角选择模型,只显示别名;选择保存到客户端配置 `selectedModelId`,从下一次请求生效。加载失败或选项停用时禁用提交并允许刷新,不显示实际 ID 作为兜底文案。
- 首页聊天框架的右下角同样提供模型选择入口(与项目对话右侧一致)。首页入口按需加载模型目录(首次展开才请求),选择仅影响后续创建/发送的轮次,不阻塞「开启创作」,因此模型目录不可用时仍可创建项目并使用后台默认项。
- 输入框右下角选择模型,只显示别名;选择保存到客户端配置 `selectedModelId` 与 `selectedModelIsDefault`(当前选择是否来自平台默认项),从下一次请求生效。加载失败或选项停用时禁用提交并允许刷新,不显示实际 ID 作为兜底文案。
- `selectedModelIsDefault` 为真表示选择由平台默认项驱动(首次进入、默认项变化、所选模型失效回退),后台默认项变化时客户端跟随切换并提示;用户手动选择后置为假,不再被默认项变化覆盖。
- 首页聊天框架的右下角同样提供模型选择入口(与项目对话右侧一致)。首页入口与项目对话共用同一份目录缓存、挂载即加载(失败时沿用上一次成功目录),选择仅影响后续创建/发送的轮次,不阻塞「开启创作」,因此模型目录不可用时仍可创建项目并使用后台默认项。
- 项目右侧对话的模型选择器在对话进行中保持可交互:切换模型只写回客户端配置并作用于下一轮,当前回合不受影响;发送按钮仍由 `controlBusy` / `modelReady` 把关。
- 设置页恢复到布局改版前的官方代理版本,不包含模型管理或模型选择,保留配置安全清理和官方代理锁定。
@@ -16,4 +18,5 @@
- 目录领域校验、未知/停用模型拒绝、客户端响应不包含实际模型名。
- 后台鉴权、持久化 revision 冲突处理;客户端选择保存后重新读取,设置保存不覆盖选择。
- 目录 `revision` 条件刷新与并发触发去重、发送前回退默认模型、刷新失败可恢复。
- AGC/admin-web 类型检查与定向测试、编码检查、Rust 定向检查、schema 一致性与 diff 检查。
@@ -1,5 +1,19 @@
# AI 游戏创作智能体 App 实施计划
## 图片生成恢复与测试边界
已有持久生成账本的 Provider 待执行动作恢复时,若动作省略了旧视觉 Agent 自动补齐的参数,只在 Agent、动作身份、生成种类和冻结提示词均匹配旧合同后补齐缺省参数;显式参数不得被覆盖。新请求继续按当前自由图片合同执行,不能重新引入固定视觉产物门禁。恢复复用原 operation 与幂等账本,不因默认值变化重复提交已受理请求。
单 HTML 测试须在项目初始化前准备 HTML;npm 项目的预览与导出测试须准备构建目录。图片测试按现行数量和布局合同验证资源、透明度、引用和持久恢复,不继续要求固定四切片。
本地 Provider smoke 在启动 Agent 前准备已有的 `game/index.html`,按 JSON Generator 的单 HTML 项目合同验证生成、资源引用与浏览器预览。Agent 子进程失败时,诊断必须包含退出码、终止信号及有长度上限的 stderr/stdout 尾部,避免编译 warning 淹没实际错误。定位此阶段失败时单独运行 `npm run ai-game-creator-shell:agent-run:smoke`。
## 常用设置职责
常用设置负责运行参数的读取、编辑和保存,配置读写独立于账号权限诊断。账号权限由登录会话与实际智能服务请求链路处理,设置面板只维护配置草稿与读写反馈。
保存配置复用写入前读取的高优先级本地覆盖内容:常用设置同步覆盖文件中已有的对应配置项,并保留当前模型 ID 和默认模型标记;模型选择仅同步 `selectedModelId` 与 `selectedModelIsDefault`;无冲突时不写覆盖文件。所有内容先完成序列化,多文件写入前保存原始内容,任一写入失败时逆序恢复已变更文件,回滚失败须明确报告。各文件沿用现有原子写入,不提供断电或进程崩溃下的多文件事务保证。全部成功后直接返回规范化配置,不执行保存后回读或外部诊断;单文件保存保持原路径。
## 2026-09-08 Web 游戏 npm 与 Phaser 4 产物合同
新建 Web 游戏使用 npm 工程:默认 `game/package.json` 声明 Phaser 4.2.1 和 Vite 构建工具,源码使用 `import Phaser from 'phaser'`,`package-lock.json` 由 npm 维护。默认脚手架文件位于 `game/`,包含 `index.html`、`game.js`、`style.css`、`vite.config.js` 和 npm 配置/锁文件;已有根目录 npm 工程沿用原根,可按需求拆分模块并添加任意其它 npm 依赖,不设置包名白名单。客户端不手工分发 Phaser bundle,不用 import map 模拟 package 导入。
@@ -195,7 +209,7 @@ Supervisor 认领该回执后,由父 run 自己为每个原 delivery 逐一创
- Windows AppData 安全迁移:首次创建客户端 AppData 时必须以进程 `TokenUser` SID 显式设置 owner,并写入当前用户私有 DACL,不能把可能为 Administrators 的 `TokenOwner` 当作用户身份。发现历史目录 owner 不属于当前 `TokenUser` 时,不在原目录上放宽权限,而是拒绝 reparse point / junction / symlink 后,将旧目录原子重命名到同级唯一 `.owner-mismatch-backup-*` 备份,再新建并验证当前用户 owner 与私有 DACL;迁移或备份失败必须失败关闭,不覆盖旧配置。
- Windows 私有文件初始化:父目录已归当前 `TokenUser` 后,新建 `.agent/.manifest.json.lock`、`agent-runner.lock`、endpoint 临时文件、project-owner 诊断临时文件与 real-E2E 私有文件的 owner 仍可能采用 token 默认 owner `Administrators`。manifest 固定锁和 Runner 固定 stale lock 只有在 Windows 不共享独占句柄已取得、且句柄确认普通文件、非 reparse point、链接数为一时才允许初始化或修复为当前 `TokenUser`,随后必须再次复核句柄并按既有 owner/DACL 门禁验证;其它临时文件只允许在本进程 `create_new` 成功且仍持有同一独占句柄时初始化 `TokenUser` owner / DACL,再写入、原子安装并严格复核,初始化失败必须清理刚创建的文件。既有 durable endpoint / diagnostic 读取不得自动接管;活锁不得截断,只有 sharing / lock violation `32/33` 表示占用,access denied 等其它错误立即返回。父进程观察到 Runner 子进程退出后立即返回错误,不等待完整 30 秒 deadline。
- Windows ACL 提权边界:自定义 `--config-dir` 的启动前置检查必须把 `managed / user-selected` scope 一并传入提权子进程,不能依赖父进程内存中的配置目录覆盖;native picker 返回的文件或项目目录在同一进程登记短时授权,后续导入 / 项目操作只对登记路径(目录可覆盖其后代)允许 `user-selected` 自动提权,直接伪造 IPC 绝对路径不得获得该能力。项目文件列表 / 索引递归逐项拒绝 symlink 与 Windows reparse point,并在 metadata / read 前先完成 ACL 准备。
- Windows ACL 提权边界:自定义 `--config-dir` 的启动前置检查必须把 `managed / user-selected` scope 一并传入提权子进程,不能依赖父进程内存中的配置目录覆盖;native picker 返回的文件或项目目录在同一进程登记短时授权,后续导入 / 项目操作只对登记路径(目录可覆盖其后代)允许 `user-selected` 自动提权,直接伪造 IPC 绝对路径不得获得该能力。项目文件列表 / 索引递归逐项拒绝 symlink 与 Windows reparse point,并在 metadata / read 前先完成 ACL 准备。本进程刚创建的普通文件或目录只在当前进程收紧 owner / 私有 DACL,不因继承 ACE 自动 UAC;UAC 只修复允许范围内、owner 不属于当前用户的已有对象。提权 `Start-Process -ArgumentList` 必须是一条按 Windows 命令行规则加引号的字符串,不能把带空格路径拆成多个 argv。
- 启动恢复和续跑边界:本条取代上一条中“只有 accepted 才可恢复”的窄口径。若进程在 Supervisor 用户消息已持久、accepted 未持久之间崩溃,只读 preflight 可以把该 `preparing` 识别为可恢复,但不改写 task/conversation;真实 resume 持有 Agent 锁后必须先幂等补写 accepted,再提升为 `pending / queued`。用户消息或 accepted conversation 已落盘而辅助审计失败时,以 conversation 为公开真相继续入队,不留下“已接收但永不执行”的任务;根终态首次公开写入的瞬时失败必须在终态投影后用相同 message ID 重试。receipt / isolated-join 等带 parent 的 Supervisor continuation 不再另写 Session 终态,只保留单一后端公开事件;`runtime-task-*` 与 `runtime-public-status-*` 共享同 run 的不透明关联摘要,秒级时间戳下多个连续任务必须按实际 run 对应的 `user -> accepted -> terminal` 顺序交错展示。
- ready-task 启动活性:`background_task.queued`、`autonomous_ready_task.scheduled`、Runner heartbeat 或执行锁已移交都不等于 child 已启动。实际持有执行权的 Runner 必须在释放项目写锁后同步写入 child 的 running task、`turn.started` 与 started journal,再把已启动 state 和 per-Agent 执行锁交给已确认开始轮询的独立 execution worker;同步启动或 worker 接管失败时,要在仍持有执行锁期间依次把 child 和 manifest Graph 节点明确落为 failed,再释放锁并让 parent 收到调度错误。`autonomous_ready_task.scheduled` 只作诊断审计,其写入失败不能阻断 durable child 启动;external client 只 wake Runner,不在客户端抢占执行。Supervisor 进度卡通过 durable `startedAt`(旧 Run 从完整 task journal 恢复,最新 task-record fallback 保持 0)显示真实持续时间,并以父 Run 与当前关联专业 Agent 的最大事件时间计算运行态活跃度:运行超过 5 分钟无新事件时显示“运行中 · 疑似停滞”和静默时长;等待用户、等待确认、Provider retry、视觉资产、进程会话、pausing 与 paused 不误报。父 Run terminal 后,持续时间冻结在父 Run 自身最后活动,不随 child 晚到收口事件增长。消息时间统一校验为 JavaScript 可表示的 Date;越界值显示“时间未知”且不写无效 `datetime`。实时回复只显示 response stream 自己的 `updatedAt`,缺失时同样显示“时间未知”,不能借用其它 Runtime 活动时间或随前端时钟漂移。该提示只提供可观测性,不改变 Runtime/manifest 正式状态。
- ready-task 对账取消续跑:未知工具结果仍停在 `needs-reconciliation` 且禁止自动重放;人工核对后显式取消原 child,保留 cancel tombstone,旧 child 和旧父 Run 按真实终态收口。若随后创建同 Session、同 Supervisor source、同有效任务语义的 continuation,新完成合同只对同时具有历史 `failed / needs-reconciliation`、最终 `cancelled` 和 durable tombstone 的 ready-task,把当前 manifest 对应 failed 节点恢复为 pending,并由 scheduler 创建全新 child Run。manifest 的读取、failed 筛选、每任务一次的 child journal 索引、证据重验和写回必须位于同一项目写锁域;较新的无 child 根 Run 只有在 durable journal 精确表明为旧 failed Graph 在进入调度前即失败时才能跨过,scheduler 自身失败必须阻断借用更老 tombstone。普通失败、无 tombstone、不同 source/Session/任务语义或证据冲突均保持失败关闭;不得复活旧 pending action、补造 observation 或把取消任务标成 completed。
@@ -568,6 +582,7 @@ game-project/
- 中间主视窗提供 `资源管理 / 运行` 切换。`code-prototype` 任务完成前运行入口保持视觉不可用,但仍可点击查看“当前无可运行版本”,不能使用会阻断说明交互的原生 `disabled` 或 `aria-disabled`;完成后才允许进入运行表现层。切回资源管理只修改前端展示态,不伪造后端预览暂停结果。
- 资源管理从当前 `GameCreationAppManifest`(包含可选 `versions`)、合法 Agent 文本回执和已导入附件派生资源,固定按文档、项目版本、美术资源、音乐音效资源分区;未知任务产物不再兜底为版本,任务声明中的未登记音频也不冒充正式音频。`按依赖 / 按类型` 使用各自前端排列,dependency 模式额外绘制当前 manifest 与资源投影可证明的依赖关系。排列与图层都不写回 manifest,不能推断或伪造缺失依赖。
- 资源卡支持点击聚焦、搜索和类型筛选。2026-07-28 起完成两套二维坐标与本地 CAS sidecar;2026-07-31 起 dependency 模式增加不持久化的原生 SVG 关系图层。2026-08-03 mentor 决定暂缓资源卡拖动,当前卡片不挂载 Pointer Down / Move / Up / Cancel 拖动入口,只允许自动布局和点击聚焦。聚焦态替换中央主视窗内容,保留左侧导航、右侧对话和底部 Agent 状态栏,退出后恢复搜索、布局模式、滚动位置与选中资源;不提供工具栏、工具侧边栏或可拖动标题栏。2026-08-10 起聚焦态以资源元数据、Rust 权威深度、同类型上下游 / 任务流和版本信息为首屏;美术图片与视频只保留卡内本体,不在详情重复放大。安全文档正文与按意图读取的音频控制位于元数据之后;美术编辑、音频编辑 / 替换、版本替换或运行模块仍不在本阶段。
- `@genarrative/image-canvas-react` 的 `CanvasWorld` 接收逻辑 `viewport`,`viewport.scale` 表示最终视觉比例;渲染细节由共享包封装。调用方测试将预期逻辑 `{ x, y, scale }` 传入公开 helper `canvasViewportToWorldTransform`,再比较实际 world 的 `style.transform`;不自行换算渲染值,不以缩放按钮文案替代 viewport 断言。world 继续使用共享默认正方形 `CANVAS_WORLD_SIZE = 12000`,不新增矩形尺寸 API;资源页的 `navigationBounds` 只用于布局、fit、关系线 geometry 和数据属性,不能再设置 world DOM 宽高。非缩放描边只应用于资源依赖关系线等几何 overlay,Lucide 等静态卡片图标保持自身正常缩放,避免被统一 SVG 规则压窄;普通位图不强制 `pixelated` 插值。依赖线、卡片拖拽、平移和滚轮锚点继续使用同一逻辑 viewport 坐标。
#### 资源管理串行改造:本体卡、分区缩放与依赖聚类
@@ -1315,3 +1330,28 @@ DirectProject 使用 `approvalPolicy=never`,避免每次原生调用再经过
- `/api/llm/responses` 与 `/api/llm/chat/completions` 的正式请求体上限为 `32 MiB`。两个路由必须显式配置 Axum `DefaultBodyLimit::max(LLM_REQUEST_MAX_BODY_BYTES)`;不能依赖 handler 内的 `Bytes / Json` 后置检查,否则 Axum 默认 `2 MiB` 会先拒绝 Direct Codex 携带图片工具结果的大上下文请求。超过 `32 MiB` 仍返回 `413 PAYLOAD_TOO_LARGE`。
- Codex app-server 的 failed turn 需要把上游 / 连接层 HTTP 413、`PAYLOAD_TOO_LARGE` 和 provider proxy 的 `provider request too large` 映射为稳定分类 `codex-app-server-error:request-too-large`;用户可见文案固定为“模型请求体过大,请减少参考图或上下文后重试”,不得落入 `other` 或泛化成权限 / 安全策略错误。
## 2026-09-09 项目写锁残留回收与启动诊断
- `.agent/project.lock` 新增 `processStartedAt`(持有进程启动时间,Unix 秒)。PID 仍存活时必须先核对启动身份:身份不一致即判定 PID 复用,可直接回收;旧锁没有该字段时退回“进程启动时间晚于锁 `createdAt` 加 5 秒容差”的推断,锁创建时间未知时不做该推断。崩溃停在 `create_new` 与落盘 payload 之间的空锁 / 坏锁宽限期从 600 秒收紧到 30 秒;无法判定持有者是否存活、或 mtime 不可读时保持保守(前者 600 秒、后者不回收),活持有者仍然不回收。
- 回收判据与删除必须基于同一次读到的锁文件快照:payload 只解析一次,删除前重新核对字节,只有内容仍是判定时的内容才 unlink;文件已消失或被替换时重试 `create_new`,不把并发回收当成错误。
- 启动诊断日志改为 `StartupLogSlot`:优先用已经生效的配置目录(含 `--config-dir`),否则退到平台配置根(Windows APPDATA、macOS Application Support、其它平台 `XDG_CONFIG_HOME` / `~/.config`),成功后再切换到真实配置目录。`startup.*.failed` 与 `show_startup_error_dialog` 不再是死分支;日志路径未知时同样给出用户可见提示。Windows 启动失败恢复系统消息框并附诊断日志路径,其它平台写 stderr,同一进程只提示一次。
- 边界与验证:残留的 `agent-runner.lock` / `agent-runner.gui-owner.lock` 是 OS 独占句柄锁,进程退出即释放,文件本身不阻塞下次启动;真正阻塞启动的是仍有活进程持锁。验证覆盖 `project_lock_recovery` 11 条(死 PID、非法进程号、空锁宽限、PID 复用时间推断、PID 复用身份不一致、身份一致不抢锁、旧格式活持有者不抢锁、新鲜空锁不抢锁、存活未知保守回收、mtime 未知保守回收、并发替换或已消失时不删除)、`diagnostic_log` 7 条,以及真实二进制双实例:第二个实例写入 `startup.runner.owner-lock.failed` 并弹出可见提示。
## 2026-09-10 Direct 写通道项目锁等待、持锁方可诊断与权限分类
- `agc_write_file` 是用户直接触发、失败即整轮无法落盘的项目写入通道,原先却用零等待 `acquire_project_write_lock`:任何重叠都在 24-42ms 内被判成“项目正在被其他写操作占用”,而 `file.write / file.patch / file.delete` 等入口用的是约 10 秒有界等待。现统一为 `acquire_game_creator_agent_runtime_project_write_lock_with_wait`:短暂重叠排队等成功,只有预算耗尽才报出带持锁方身份的错误;同一轮并行写多个文件按同一把锁串行。这是 2026-07-22 同一形状修复在 Direct 通道上的补齐,与 2026-08-13 一节“这些结果统一投影为争用并进入既有有界等待”的口径一致。**失败耗时是判据**:几十毫秒说明该入口没等,不是锁没释放。
- 这条等待是**同步轮询**(2_000 × 5ms,最多约 10 秒),而 `handle_direct_tool_bridge` 是 async handler:直接在 handler 里跑完整条写路径会占住一个 tokio worker,争用窗口内同一轮并行写多个文件时会有多个 worker 被占,而这条 bridge 与只读端点、UI 命令共享同一个 runtime——Issue #318 现场“只读工具全部正常”这条诊断特征会在争用窗口内失效。因此写路径经 `bridge_write_file_in_blocking_pool` 走 `tokio::task::spawn_blocking`(仓库既有模式,如 `codex_app_server.rs` 的 DirectProject 历史落盘),等待语义与错误文案不变;定向用例用默认 `current_thread` runtime 加心跳任务锁住“等待期间 runtime 仍在推进”。
- 争用错误必须带持锁方身份才可行动:`项目正在被其他写操作占用:<锁路径>(持锁方 commandId=<命令> pid=<进程> createdAt=<创建时间> ownerIsSelf=<是否本进程>)`。锁文件处于 delete-pending 或尚未写完时读不到身份,也必须显式表达成“不可读”,不得默认成“没有持锁方”。前缀逐字不变:`project_gates.rs`、`provider_recovery.rs`、`planning_session_v2.rs`、`direct_runtime.rs` 和前端 `App.tsx` 都按它把争用识别成可等待的瞬时状态;这句话已是 `crate::project::PROJECT_WRITE_LOCK_CONTENTION_PREFIX` 单一真源,四个站点不再各自手写中文。
- `create_new` 的失败必须分三类处置,不能再共用一句文案:可重试(目标已存在、Windows `sharing violation(32)` / `lock violation(33)` / `ACCESS_DENIED(5)`)进入有界等待;明确判定不是争用的权限 / ACL 拒绝(Unix `EACCES`)失败关闭且文案不含争用前缀;其它 I/O 错误原样上报。**重试性只能由错误码决定,不能用 `path.exists()` 这类一次 metadata 观察决定**:目标被删除时目录项先消失、删除挂起随后才结束,`create_new` 会在这个拆链窗口里返回 `ACCESS_DENIED(5)`,而 `exists()` 往往已经报 false(本机实测 6 万次建锁 / 删锁竞争里 396-538 例命中该组合)。按“目标不存在”当场判成权限拒绝,等待层就会立刻失败关闭——正是本次要消灭的“毫秒级直接失败”,只是换成更误导的 ACL 文案。平台判据以 `project_write_lock_open_failure_for(platform, error)` 保留、平台由参数传入而不是 `#[cfg]`:CI 只有 Linux runner,Windows 分支必须在 Linux 上也能断言。
- Windows 上真实 ACL 拒绝与删除拆链窗口在错误码上不可区分,所以终态改判放到**等待预算耗尽之后**:`ProjectWriteLockFailure::exhausted_projection(waited)` 只在“真的等过预算 + 目标此刻仍不存在 + 错误码是 `ACCESS_DENIED(5)`”三个条件同时成立时才投影成权限拒绝;单次试探(`max_attempts == 1`,例如 hydrate 的 `try_acquire_...`)没有等待证据,保持争用语义。代价是 Windows 上真实 ACL 拒绝会先等满等待窗口(约 10 秒)才报权限错误;Unix 的 `EACCES` 立即判定、不等待。
- 重试与否改由**类型**决定,不再解析错误文案:`acquire_project_write_lock_failure` 返回 `ProjectWriteLockFailure::{Retryable, Terminal}`,有界等待按 `is_retryable()` 分流,`acquire_project_write_lock` 只是它的文案包装。零等待入口前缀不变,只在“错误码不可区分且目标此刻不存在”时补一句“可能是删除挂起、删除拆链窗口或权限 / ACL 拒绝”,把两种处置都交给调用方,而不是替它猜一个。
- 复用 2026-09-09 的回收机制,不新增第二套:`ProjectWriteLockSnapshot` 补 `commandId` 与 `describe_holder()`,争用错误、`project.write_lock.reclaim_stale`、`project.write_lock.wait_exhausted` 三处共用同一份身份描述。“活持有者始终不回收”的判据不变。
- 等待预算耗尽时按 `project.write_lock.wait_exhausted` 记录 `commandId`、尝试次数、等待毫秒数、`projection=`(contention / permission_denied)与持锁方身份,Unix 上明确判定的权限拒绝按 `project.write_lock.permission_denied` 记录;争用不在零等待入口里逐次记账,避免有界等待的上千次重试淹没日志。这条日志正是 Issue #318 现场缺的“谁在持锁、是不是自己人”。**这条日志与终态改判都只在真的等过(`max_attempts > 1`)时发生**:单次试探(hydrate 的 `try_acquire_*`)不写 `wait_exhausted`(`waitedMs≈0` 会让“耗尽”失去意义,而 hydrate 每次状态变化都会撞一次锁,写成日志就是噪声),也不做终态改判。
- 定向验收覆盖:同进程重叠写等待后成功、同一轮并行写多个文件、有界等待不占 runtime worker(`current_thread` + 心跳任务)、活外部进程持锁(错误带 `ownerIsSelf=false` 且锁文件不被回收)、ACL 拒绝不投影成争用,外加两条平台无关判据用例(重试性只由错误码决定、终态改判三条件)——后两条让 Linux CI 也能盯住 Windows 分支。对应 `project_lock_recovery`、`direct_tool_bridge` 与 `project/write_lock` 定向测试;`tests/project_tools.rs` 既有的 `runtime_project_write_lock_waits_for_delete_pending_target` 继续覆盖“带句柄的 delete-pending 必须等到成功”。
- 仍待收口(后续事项):① 其余仍用零等待取锁的入口(`command.exec / project.verify / memory / conversation / task / checkpoint / 预览 / UI 编辑器 / 资源编辑器 / Tauri 命令`)本批不改,遇到同类争用仍会立刻失败;零等待入口无法区分“拆链窗口 / ACL 拒绝”,因此在前缀不变的前提下补一句“锁文件此刻不存在,可能是删除挂起、删除拆链窗口或权限 / ACL 拒绝”。② 锁策略已按“单一职责”收口到 `project/write_lock.rs`(887 行:取锁、等待分类、持锁方诊断、残留回收),`project/filesystem.rs` 回到项目文件 IO(680 行);仍待收口的是 Direct 锁用例,它们还留在 `direct_tool_bridge.rs`(3135 行,锁用例与桥实现混在一起),后续移到 `tests/project_lock_recovery.rs` 或独立测试文件。③ 行为级 Windows 用例(delete-pending 等)仍只在 Windows 本地执行,CI 没有 Windows runner;关键判据已参数化到 Linux 可覆盖,行为级覆盖仍需本地执行或后续补 runner。
## 2026-09-11 AGC 登录态自动续期
- AGC 前端请求客户端配套后端的鉴权 API(包括 `/api/llm/models`)收到 `401` 时,共享进行中的 refresh 请求;确认当前用户并安装 Rust / Runner 会话后,用新 access token 最多重试原请求一次。`403` 权限拒绝不触发续期;续期失败保留原鉴权错误,账号切换或登出后不重发旧请求。
- DirectProject 的 Rust/app-server 对话调用返回鉴权失效时,前端先刷新客户端平台会话并重新提交同一 `clientTurnId`;平台会话代次变化后由 app-server pool 使用新 access token 建立连接,避免长时间运行后必须重新登录。
@@ -1,6 +1,7 @@
# 立项策划 Agent(Fast GDD)技术方案
- 日期:2026-08-10
- 状态:**已退役**。本文描述的 V1 策划链路(`project-supervisor-plan` 根 Run、`project-planning` 子 Agent、`plan.submit_gdd` 工具、Fast GDD 审批门禁与恢复机制)已由策划会话 Runtime V2 取代,源码已于 2026-09 按四不写原则整体删除;现行方案见 `【技术方案】策划会话RuntimeV2接入与旧链路退役-2026-09-03.md`。本文仅作为历史推导记录保留。
- 适用范围:AI 游戏创作独立 App、Project Supervisor、Agent Runtime、本地项目策划 sidecar 与后续完整构建准入
> 当前口径(2026-08-30):以本文件中标注的 D11 / 最新修订和当前 `apps/ai-game-creator-shell` 实现为准。D6~D9 等被明确标注为作废或被取代的段落仅保留推导背景,不得作为现行拓扑、入口或 Runtime 真相;产品入口与 DirectProject 总体口径见 `docs/README.md` 和 App 实施计划。
@@ -9,7 +10,7 @@
当前普通完整构建会从简短需求直接进入 `autonomous-game-build`,用户在消耗完整构建成本前没有正式确认玩法方向、MVP 范围和原型验证项的环节。现有完整构建中的 `design-director` 是只读协调任务,`design-foundation` 又会自行补齐玩法定位;用户意图与后续实现之间缺少可版本化、可审批、可恢复的策划基线。
本方案新增“立项策划”阶段:用户给出一句需求后,由 Project Supervisor 顶层 root run 通过 `agent.delegate` 发起一个独立 `agentId` 的静态委派子 Agent(`agentId=project-planning`;2026-08-13 起取代原“下游工作流节点由 manifest ready-task 调度器启动”的表述,见第 1.1 节「D11 新拓扑」与 D11),在最多 3 轮决策卡内形成 Fast GDD(该 3 轮上限依赖 WP1——静态委派澄清轮次与返工深度拆分——先落地,见第 1.1 节;落地前实际上限仍是 1 轮);Runtime 校验并提交不可变版本,用户通过审批卡批准、修改或退回。只有不可变 GDD 与对应 approve receipt 同时有效时,后续完整构建才能取得 `approvedGddRef`。
本方案新增“立项策划”阶段:用户给出一句需求后,由 Project Supervisor 顶层 root run 通过 `agent.delegate` 发起一个独立 `agentId` 的静态委派子 Agent(`agentId=project-planning`;2026-08-13 起取代原“下游工作流节点由 manifest ready-task 调度器启动”的表述,见第 1.1 节「D11 新拓扑」与 D11),在最多 3 轮决策卡内形成 Fast GDD(该 3 轮上限依赖 WP1——静态委派澄清轮次与返工深度拆分——先落地,见第 1.1 节;落地前实际上限仍是 1 轮);Runtime 校验并提交不可变版本,用户通过审批卡批准、修改或退回。审批修改后的 continuation 沿用普通 Planning V2 回合协议,Provider 可以继续问询,也可以直接提交新的完整 GDD;既有问询计数和已确认问答继承,不因修改重置。只有不可变 GDD 与对应 approve receipt 同时有效时,后续完整构建才能取得 `approvedGddRef`。
目标:
@@ -389,7 +390,7 @@ Runtime 注入并强校验以下精确结构:
### 5.1 对话循环
- 最多 3 轮主动追问,每轮只问 1 个主要决定。
- 最多 3 轮主动追问,每轮只问 1 个主要决定;审批修改后的 continuation 仍沿用同一问询预算,可以继续问询或直接提交新的完整 GDD,问询计数不重置。
- 建议顺序:核心行为与本局目标 → 重玩动力 → 制作边界与 MVP。
- 满足任一条件即出稿:用户明确说“直接出稿”;已经完成第 3 轮;剩余问题不影响首个可玩闭环;Runtime 注入 240 秒 Agent 活跃时间软提示。
- `accumulatedAgentMillis` 只累计 Provider 活跃区间,不包含等待用户、等待审批、进程休眠或应用关闭时间。
@@ -790,7 +791,7 @@ input 是当前 GDD 的完整快照,不要求与 source session 的 `decisions
| title | 1~80 scalar |
| genre.primary / fusion | primary 1~40;fusion 为 `null` 或 1~40 |
| artStyle | visualType 1~80;keywords 3~5 个且去重,每项 1~32;其余各 1~400 |
| oneLiner | 45~90 scalar |
| oneLiner | Runtime 实际接受 10~160 scalar;模型 schema 提示 25~90 scalar(有意不对称,不是缺陷或 bug) |
| pillars | 2~4 条;name 1~40,其余文本各 1~240;name 唯一 |
| coreLoop | 4~8 步,每步 1~120 |
| targetUsers | 三个主文本各 1~240;referenceGames 0~5 项,每项 1~80 |
@@ -1895,7 +1896,7 @@ M0 文档 PR 本身最低验证:Markdown 结构与三张 Mermaid 图可解析
| 现役 pending wire | `apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver.rs:26-27`、`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/provider_action_batch.rs:3-46,219-270`、`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/pending_confirmation_ledger.rs:290-520` | 保持 `game-creator-pending-action.v5`;M1 另建 planning pending,不升级全局 wire |
| submit 专用提交/恢复分支 | `apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/main_loop.rs`、`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/pending_recovery.rs`、`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/recovery_scan.rs`、`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/provider_action_batch.rs` | **2026-08-14 按 M1B-2 收口**:在普通 dispatch 前处理 Runtime-owned submit;提交后终止策划子 run,并保留 generic v5 standalone pending + v4 batch anchors,不复用 `WaitingForUserInput`,不创建 planning pending。审批等待属于 M1C-1 |
| JSON sidecar | `apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/json_sidecar.rs:44-104,122-250` | 现有 writer 可覆盖;不可变文件必须新增 no-replace helper |
| 项目锁 | `apps/ai-game-creator-shell/src-tauri/src/project/filesystem.rs:85-138`、`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/project_gates.rs:1467-1505` | 所有 planning mutation 在同一项目锁内重读事实 |
| 项目锁 | `apps/ai-game-creator-shell/src-tauri/src/project/write_lock.rs`、`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/project_gates.rs:1467-1505` | 所有 planning mutation 在同一项目锁内重读事实 |
| completion blocker | `apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_approval.rs`、`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/project_gates.rs`、`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/main_loop.rs` | **M1C-1 隔离 worktree 已补齐**:仅对 exact `project-supervisor-plan` standard 顶层根 Run 生效,读取 GDD lineage、approval pending/receipt、generic submit anchors、terminal observation、decision audit、planning session 与 recovery,且只读不创建 pending;现役 collaboration blocker 对策划子 Agent 仍不适用 |
| plan 根 run 子 Agent 创建面 | `apps/ai-game-creator-shell/src-tauri/src/agent/runtime_tools/delegation.rs`(`observe_agent_runtime_agent_delegate` / `observe_agent_runtime_agent_spawn_isolated`)、`agent/runtime_protocol/run_configuration.rs`(`validate_project_supervisor_plan_root_binding_at`)、`agent/prompt.rs`(`game_creator_project_supervisor_tool_plan_prompt`) | **2026-08-14 `M1A-4` 已落地**:plan 根 run 只能委派 `project-planning`,`agent.spawn_isolated` 一律拒,两条通道共用 typed `kind=plan-root-child-target-unsupported`;plan source 下不拼 `supervisorIntro` 与 `$visualContract`。**已知残留(有意保留,见 decision-log 2026-08-14 `M1A-4` 条)**:`$base` 的 `$isolatedAgentTemplates` 段仍会向 plan 根 run 列出全部专业角色名——那是 `agent.spawn_isolated` 的模板目录,因执行层硬拒而成为死文本;因此**不得**写「plan 根 run 上下文不出现其它 Agent 名」这类验收句 |
| plan retry | `apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/lifecycle_control.rs`(`resolve_game_creator_agent_runtime_retry_configuration_at`)、`runtime_driver.rs`(`supervisor_plan_root_identity_holds_at`) | **2026-08-13 `M1A-3` 已落地 source 保源**:`task.source == project-supervisor-plan` 时先走强判据,通过则保留该 source,失败 `kind=plan-root-retry-identity-unsupported`、不降级。gui/cli 仍走 `agent-background-task`。plan-session revision / `gddId` / 按 `gdd-approval` kind 禁 retry 仍属后续包(现役已拒 `waiting-*`) |
@@ -176,7 +176,9 @@ error=unable to locate image at `<project>/memory/README.md`: (os error 2)
---
## 4. 缺陷 4:普通 action 批次带 plan update 时预检自相矛盾(已修)
## 4. 缺陷 4:普通 action 批次带 plan update 时预检自相矛盾(已修,2026-09 随策划 V1 退役删除相关代码)
> 本节引用的 `plan.submit_gdd` 特判、`provider_batch_plan_update` 与 `planning_session_binding` 字段属于已退役的策划 V1 链路,2026-09 已按四不写原则整体删除;回归测试 `supervisor_collaboration_batch_keeps_plan_update_off_members` 保留并收敛为「非 planning 批次的 plan update 不落入成员」这一不变量。
换成 `gpt-5.6-terra` 后 LLM 链路可用,但首次复现曾在第二步「委派 project-planning 产出 Fast GDD」失败。这一条与 Provider 无关,是当时实现引入、现已修复的回归。