@@ -1,5 +1,9 @@
# AI 游戏创作智能体 App 实施计划
## 当前策划入口与退役边界
策划 V1、策划会话 Runtime V2 均已删除,当前“做方案”只使用独立 Design Agent,现行合同见[策划 Agent 生产迁移与工作区浏览 ](./【技术方案】策划Agent生产迁移与工作区浏览-2026-09-10.md )。旧 V1/V2 Runtime、命令、会话、审批卡、身份白名单和专属测试不作为兼容或恢复目标;历史方案中的 lifecycle v3、planning binding 等要求不能作为孤立代码的保留依据。共享能力按现役调用判断,不因名称相似删除当前 Design Agent 或通用 Runtime。
## 2026-09-23 Direct 宿主继续请求输入修复
- 首次模型请求使用原始结构化用户输入,保留 Skill 提及及其它引用;未提供结构化输入时沿用请求正文与图片转换。
@@ -356,7 +360,7 @@ DirectProject 的生图、素材处理、构建和试玩可能跨越短生命周
DirectProject 工作区只恢复自身对话,不按专业 Agent 默认任务占位行批量读取旧会话或生成专业 Agent 文本回执。专业 Agent 结果加载 effect 必须以当前 Runtime 模式为边界,并在模式切换时清空旧结果。仍供开发入口使用的 `read_local_conversation` 在 blocking worker 内完整执行权限校验、会话目录解析和历史读取,避免文件访问或锁等待阻塞 Tauri 窗口线程。
项目打开链路的目录检查、manifest 读取、 项目 revision 读取和 Planning V2 hydrate 也必须通过 blocking worker 执行;它们可能碰到项目写锁,不能在 Tauri 窗口线程同步等待。
项目打开链路的目录检查、manifest 读取和 项目 revision 读取也必须通过 blocking worker 执行;它们可能碰到项目写锁,不能在 Tauri 窗口线程同步等待。Planning V2 hydrate 已随旧策划 Runtime 删除,不再属于现役打开链路。
DirectProject 自身的 `read_direct_project_conversation` 也必须在 blocking worker 中执行权限校验、JSONL 历史解析和消息投影,不能因为它只读取一份项目历史就保留同步 Tauri command。
@@ -1529,7 +1533,7 @@ DirectProject 使用 `approvalPolicy=never`,避免每次原生调用再经过
- `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` 单一真源,四个站点不再各自手写中文 。
- 争用错误必须带持锁方身份才可行动:`项目正在被其他写操作占用:<锁路径>(持锁方 commandId=<命令> pid=<进程> createdAt=<创建时间> ownerIsSelf=<是否本进程>) ` 。锁文件处于 delete-pending 或尚未写完时读不到身份,也必须显式表达成“不可读”,不得默认成“没有持锁方”。现役调用方通过 `crate::project::PROJECT_WRITE_LOCK_CONTENTION_PREFIX` 单一真源识别争用,不各自手写中文;已删除的 `planning_session_v2.rs` 不再列为现役调用点 。
- `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 拒绝”,把两种处置都交给调用方,而不是替它猜一个。