From 5ae8f35aaa8fb532e489a11fa7a8eda228617344 Mon Sep 17 00:00:00 2001 From: kdletters Date: Mon, 14 Sep 2026 12:49:58 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=BE=E5=BC=80=20DirectProject=20Codex=20?= =?UTF-8?q?=E6=B2=99=E7=AE=B1=E6=9D=83=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DirectProject 使用 danger-full-access sandbox 移除 Codex 文件审批根白名单与路径提示约束 同步更新 AGC 技术方案和项目决策记录 --- .../src-tauri/src/agent/codex_app_server.rs | 307 ++++-------------- .../src-tauri/src/agent/direct_runtime.rs | 2 +- .../shared-memory/decision-log.md | 8 + ...案】AI游戏创作Agent Runtime V1.1-2026-07-12.md | 2 +- ...案】AI游戏创作智能体App实施计划-2026-06-24.md | 10 +- 5 files changed, 79 insertions(+), 250 deletions(-) diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server.rs index 1d0ecf233..19f473cca 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server.rs @@ -1378,15 +1378,19 @@ fn codex_app_server_thread_start_params( base_instructions: String, use_model_provider: bool, ) -> serde_json::Value { - // DirectProject is an autonomous Codex session. The app-server sandbox - // remains the hard write boundary; approval prompts are not a second - // harness that can stall a turn. DirectHome/ToolHost stay passive. + // DirectProject is an autonomous Codex session with explicit full OS + // access; approval prompts are not a second harness that can stall a turn. + // DirectHome/ToolHost stay passive. let approval_policy = "never"; let mut params = serde_json::json!({ "model": model, "cwd": workspace_path, "approvalPolicy": approval_policy, - "sandbox": if workspace_mode.allows_workspace_writes() { "workspace-write" } else { "read-only" }, + "sandbox": if workspace_mode == CodexAppServerWorkspaceMode::DirectProject { + "danger-full-access" + } else { + "read-only" + }, "ephemeral": true, "baseInstructions": base_instructions }); @@ -1404,7 +1408,6 @@ fn codex_app_server_turn_start_params( thread_id: &str, input: serde_json::Value, model: &str, - workspace_path: &std::path::Path, workspace_mode: CodexAppServerWorkspaceMode, client_user_message_id: Option<&str>, ) -> serde_json::Value { @@ -1415,14 +1418,12 @@ fn codex_app_server_turn_start_params( "model": model, "approvalPolicy": approval_policy, }); - if workspace_mode.allows_workspace_writes() { - // npm install/build must resolve project dependencies. Network access - // is enabled only for DirectProject; writableRoots keeps the file-write - // boundary at the real game workspace. + if workspace_mode == CodexAppServerWorkspaceMode::DirectProject { + // DirectProject is an explicitly user-selected local Codex session. + // Give the native Codex tools the full OS sandbox profile so they are + // not narrowed by a project-root writableRoots allowlist. params["sandboxPolicy"] = serde_json::json!({ - "type": "workspaceWrite", - "writableRoots": [workspace_path], - "networkAccess": true + "type": "dangerFullAccess" }); } if let Some(client_user_message_id) = client_user_message_id @@ -1436,40 +1437,27 @@ fn codex_app_server_turn_start_params( } fn game_creator_codex_app_server_interaction_response( - workspace_path: &std::path::Path, workspace_mode: CodexAppServerWorkspaceMode, id: u64, - method: &str, - requested_grant_root: Option<&str>, + _method: &str, + _requested_grant_root: Option<&str>, ) -> serde_json::Value { - let direct_workspace = workspace_mode.allows_workspace_writes(); - let file_change_within_workspace = game_creator_codex_file_change_request_is_allowed( - workspace_path, - method, - requested_grant_root, - ); - if direct_workspace && file_change_within_workspace { - serde_json::json!({ + if workspace_mode == CodexAppServerWorkspaceMode::DirectProject { + // Full-access DirectProject sessions do not use a file-root allowlist + // or a second approval gate. The declared sandbox policy is the only + // capability boundary for native Codex operations. + return serde_json::json!({ "id": id, "result": { "decision": "accept" } - }) - } else if direct_workspace && method == "item/fileChange/requestApproval" { - serde_json::json!({ - "id": id, - "error": { - "code": -32602, - "message": "Genarrative AGC 只允许当前项目工作区的文件变更审批" - } - }) - } else { - serde_json::json!({ - "id": id, - "error": { - "code": -32601, - "message": "Genarrative AGC 拒绝 app-server 的交互、审批与工具请求" - } - }) + }); } + serde_json::json!({ + "id": id, + "error": { + "code": -32601, + "message": "Genarrative AGC 拒绝 app-server 的交互、审批与工具请求" + } + }) } #[cfg(test)] @@ -2734,7 +2722,6 @@ impl CodexAppServerConnection { &thread_id, input, model, - &self.inner.workspace_path, self.inner.workspace_mode, direct_client_turn_id, ); @@ -3293,7 +3280,6 @@ async fn read_game_creator_codex_app_server_stdout( ); } let response = game_creator_codex_app_server_interaction_response( - &inner.workspace_path, inner.workspace_mode, id, method, @@ -3635,82 +3621,6 @@ async fn read_game_creator_codex_app_server_stderr( } } -#[cfg(windows)] -fn game_creator_codex_workspace_path_key(path: &std::path::Path) -> String { - let value = path.to_string_lossy(); - value - .strip_prefix("\\\\?\\") - .unwrap_or(value.as_ref()) - .replace('/', "\\") - .trim_end_matches('\\') - .to_ascii_lowercase() -} - -fn game_creator_codex_grant_root_is_within_workspace( - workspace: &std::path::Path, - grant_root: &str, -) -> bool { - fn canonicalize_with_missing_tail(path: &std::path::Path) -> Option { - if !path.is_absolute() - || path - .components() - .any(|component| matches!(component, std::path::Component::ParentDir)) - { - return None; - } - let mut existing = path.to_path_buf(); - let mut missing_tail = Vec::new(); - while !existing.exists() { - missing_tail.push(existing.file_name()?.to_os_string()); - if !existing.pop() { - return None; - } - } - let mut normalized = existing.canonicalize().ok()?; - for component in missing_tail.iter().rev() { - normalized.push(component); - } - Some(normalized) - } - - let workspace = workspace - .canonicalize() - .unwrap_or_else(|_| workspace.to_path_buf()); - let Some(grant_root) = canonicalize_with_missing_tail(std::path::Path::new(grant_root)) else { - return false; - }; - #[cfg(windows)] - { - let workspace_key = game_creator_codex_workspace_path_key(&workspace); - let grant_key = game_creator_codex_workspace_path_key(&grant_root); - grant_key == workspace_key - || grant_key - .strip_prefix(&workspace_key) - .is_some_and(|suffix| suffix.starts_with('\\')) - } - #[cfg(not(windows))] - { - grant_root == workspace || grant_root.starts_with(&workspace) - } -} - -fn game_creator_codex_file_change_request_is_allowed( - workspace: &std::path::Path, - method: &str, - grant_root: Option<&str>, -) -> bool { - if method != "item/fileChange/requestApproval" { - return false; - } - // `grantRoot: null` means the already-declared turn sandbox root. It is - // valid only for file changes; it must never authorize another capability - // or a broader permission request. - grant_root.is_none() - || grant_root.is_some_and(|grant_root| { - game_creator_codex_grant_root_is_within_workspace(workspace, grant_root) - }) -} - async fn fail_game_creator_codex_app_server_connection( inner: &Weak, error: String, @@ -4481,7 +4391,6 @@ mod tests { "home-thread", serde_json::json!([{ "type": "text", "text": "你好" }]), "fixture-model", - workspace, CodexAppServerWorkspaceMode::DirectHome, None, ); @@ -4495,7 +4404,6 @@ mod tests { "item/permissions/requestApproval", ] { let response = game_creator_codex_app_server_interaction_response( - workspace, CodexAppServerWorkspaceMode::DirectHome, 7, method, @@ -4513,14 +4421,10 @@ mod tests { } #[test] - fn direct_project_protocol_and_interactions_expose_only_the_real_game_workspace() { + fn direct_project_protocol_uses_full_access_without_a_root_allowlist() { let temp = tempfile::tempdir().expect("temp dir"); let project_root = temp.path().join("project"); - let assets = project_root.join("assets"); - let agent = project_root.join(".agent"); std::fs::create_dir_all(&project_root).expect("project root"); - std::fs::create_dir(&assets).expect("assets directory"); - std::fs::create_dir(&agent).expect("agent directory"); let workspace = resolve_direct_codex_game_workspace(&project_root).expect("resolve project workspace"); assert_eq!( @@ -4536,83 +4440,40 @@ mod tests { true, ); assert_eq!(thread["cwd"], serde_json::json!(workspace)); - assert_eq!(thread["sandbox"], "workspace-write"); + assert_eq!(thread["sandbox"], "danger-full-access"); let turn = codex_app_server_turn_start_params( "project-thread", serde_json::json!([{ "type": "text", "text": "修复游戏" }]), "fixture-model", - &workspace, CodexAppServerWorkspaceMode::DirectProject, Some("direct-turn-0001"), ); assert_eq!(turn["clientUserMessageId"], "direct-turn-0001"); assert_eq!( - turn.pointer("/sandboxPolicy/writableRoots/0"), - Some(&serde_json::json!(workspace)) - ); - assert_eq!( - turn.pointer("/sandboxPolicy/networkAccess"), - Some(&serde_json::json!(true)) - ); - let authority_paths = [ - turn.get("cwd"), - turn.pointer("/sandboxPolicy/writableRoots/0"), - ]; - assert!( - authority_paths - .iter() - .flatten() - .all(|value| value.as_str() == Some(workspace.to_string_lossy().as_ref())), - "writable params must be exactly the project workspace" + turn.pointer("/sandboxPolicy/type"), + Some(&serde_json::json!("dangerFullAccess")) ); + assert!(turn.pointer("/sandboxPolicy/writableRoots").is_none()); + assert!(turn.pointer("/sandboxPolicy/networkAccess").is_none()); - let workspace_string = workspace.to_string_lossy().into_owned(); - for allowed_root in [None, Some(workspace_string.as_str())] { + for (id, method) in [ + (9, "item/fileChange/requestApproval"), + (10, "item/commandExecution/requestApproval"), + (11, "item/permissions/requestApproval"), + (12, "item/tool/call"), + ] { let response = game_creator_codex_app_server_interaction_response( - &workspace, CodexAppServerWorkspaceMode::DirectProject, - 9, - "item/fileChange/requestApproval", - allowed_root, + id, + method, + Some("C:\\outside-project"), ); assert_eq!( response.pointer("/result/decision"), Some(&serde_json::json!("accept")) ); } - for forbidden_root in [assets, agent] { - let forbidden_root = forbidden_root.to_string_lossy().into_owned(); - let response = game_creator_codex_app_server_interaction_response( - &workspace, - CodexAppServerWorkspaceMode::DirectProject, - 10, - "item/fileChange/requestApproval", - Some(&forbidden_root), - ); - assert_eq!( - response.pointer("/result/decision"), - Some(&serde_json::json!("accept")), - "project-root children must stay writable: {forbidden_root}" - ); - } - for method in [ - "item/commandExecution/requestApproval", - "item/permissions/requestApproval", - "item/tool/call", - ] { - for requested_root in [None, Some(workspace_string.as_str())] { - let response = game_creator_codex_app_server_interaction_response( - &workspace, - CodexAppServerWorkspaceMode::DirectProject, - 11, - method, - requested_root, - ); - assert!(response.get("error").is_some()); - assert!(response.get("result").is_none()); - } - } } #[test] @@ -4630,26 +4491,6 @@ mod tests { assert!(resolve_direct_codex_game_workspace(&file_root).is_err()); } - #[cfg(all(unix, not(target_os = "macos")))] - #[test] - fn direct_project_grant_root_comparison_remains_case_sensitive() { - let temp = tempfile::tempdir().expect("temp dir"); - let project_root = temp.path().join("Project"); - let child = project_root.join("assets"); - let different_case = temp.path().join("project").join("assets"); - std::fs::create_dir_all(&child).expect("child directory"); - std::fs::create_dir_all(&different_case).expect("different-case directory"); - - assert!(game_creator_codex_grant_root_is_within_workspace( - &project_root, - project_root.to_string_lossy().as_ref() - )); - assert!(!game_creator_codex_grant_root_is_within_workspace( - &project_root, - different_case.to_string_lossy().as_ref() - )); - } - #[cfg(unix)] #[test] fn direct_project_rejects_a_non_directory_workspace() { @@ -5246,51 +5087,25 @@ while IFS= read -r line; do :; done } #[test] - fn direct_file_change_approval_is_limited_to_workspace() { - let temp = tempfile::tempdir().expect("temp dir"); - let workspace = temp.path().join("demo"); - let child = workspace.join("assets"); - let sibling = temp.path().join("demolition"); - std::fs::create_dir_all(&child).expect("workspace child"); - std::fs::create_dir(&sibling).expect("sibling"); - assert!(game_creator_codex_file_change_request_is_allowed( - &workspace, + fn direct_project_interactions_accept_full_access_without_a_root_allowlist() { + for method in [ "item/fileChange/requestApproval", - None - )); - assert!(game_creator_codex_grant_root_is_within_workspace( - &workspace, - workspace.to_string_lossy().as_ref() - )); - assert!(game_creator_codex_grant_root_is_within_workspace( - &workspace, - child.to_string_lossy().as_ref() - )); - assert!(!game_creator_codex_grant_root_is_within_workspace( - &workspace, - sibling.to_string_lossy().as_ref() - )); - assert!(!game_creator_codex_file_change_request_is_allowed( - &workspace, - "item/fileChange/requestApproval", - Some(sibling.to_string_lossy().as_ref()) - )); - assert!(!game_creator_codex_grant_root_is_within_workspace( - &workspace, - sibling.join("missing").to_string_lossy().as_ref() - )); - assert!(game_creator_codex_grant_root_is_within_workspace( - &workspace, - workspace.join("missing").to_string_lossy().as_ref() - )); - assert!(!game_creator_codex_grant_root_is_within_workspace( - &workspace, - workspace - .join("..") - .join("outside") - .to_string_lossy() - .as_ref() - )); + "item/commandExecution/requestApproval", + "item/permissions/requestApproval", + "item/tool/call", + ] { + let response = game_creator_codex_app_server_interaction_response( + CodexAppServerWorkspaceMode::DirectProject, + 1, + method, + Some("C:\\outside-project"), + ); + assert_eq!( + response.pointer("/result/decision"), + Some(&serde_json::json!("accept")) + ); + assert!(response.get("error").is_none()); + } } #[test] diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_runtime.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_runtime.rs index 0766ab0d3..59954591b 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_runtime.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_runtime.rs @@ -10,7 +10,7 @@ const MAX_DIRECT_SYSTEM_PROMPT_CHARS: usize = 16 * 1024; const MIN_DIRECT_CLIENT_TURN_ID_CHARS: usize = 6; const MAX_DIRECT_CLIENT_TURN_ID_CHARS: usize = 160; const DIRECT_TAONIER_IDENTITY_GUIDANCE: &str = "对外身份合同:你是“陶泥儿”,是 Genarrative 的游戏创作助手。用户询问你是谁、你的名称或能力时,以陶泥儿的身份回答;不要把 Codex、ChatGPT、OpenAI、模型、通用 AI 助手或内部执行智能体当作自己的名称或对外身份。Codex app-server 仅是客户端内部执行技术;只有用户明确询问底层实现时才可如实说明,同时仍以陶泥儿自称。"; -const DIRECT_AGC_ENGINEERING_GUIDANCE: &str = "AGC 工程合同:当前 cwd 是用户选择的项目目录。先读取当前 cwd 下适用的 AGENTS.md、README 或项目说明并识别实际引擎与工程结构。用户明确指定 Cocos、Unity、Godot 或其它编辑器/引擎,而当前目录不具备对应工程结构时,必须先说明不匹配并提出澄清;在澄清前不得把请求改写成 Phaser/Web 实现,也不得写文件、安装依赖、构建或试玩。仅当用户确认继续当前工程或提供了匹配的项目目录后才执行。识别为 Cocos Creator 项目时,优先使用 `agc_cocos_execute` 或 Cocos 插件的 `cocos.editor.execute` 在已打开的 Creator 编辑器中操作;不要创建 Phaser 文件,不要把 Cocos 请求改写成 Web 工程。新 Web 游戏使用 npm + Vite,Phaser 固定为 4.2.1,在 `game.js` 或模块中使用 `import Phaser from 'phaser'`;可以按需使用其它 npm 依赖,不得复制 Phaser bundle、使用 import map 或 CDN。简单修改只完成用户明确要求的范围;安装依赖、构建和试玩是后续操作,除非用户明确要求或它们是完成该项不可替代的最小验证,否则不得擅自扩展任务。源码使用 cwd 相对路径,例如 `index.html`、`style.css`、`game.js`、`package.json`、`package-lock.json`、`assets/hero.png`;依赖安装与构建使用项目自己的 npm scripts。原生文件工具、patch 和命令参数使用 cwd 相对路径,例如 `index.html`、`style.css`、`game.js`、`assets/hero.png`;如果 Codex 原生文件修改不可用,可以按需用客户端 `agc_write_file` 把文本写入项目相对路径。调用 `agc_write_file` 时,content 必须是目标文件的完整原始 UTF-8 正文;不得把 command.exec 的 Exit code、Wall time、Output 包装、终端日志或解释文字一起复制进 content,命令结果只能用于判断,不能当作文件正文。`../`、绝对路径、`.agent/`、`.git/`、密钥文件和 Runtime 控制面属于客户端边界,不能请求扩权或直接改写。DirectProject 提供 Codex 原生文件、搜索、命令、图片查看、Skill、经客户端注入的 `agc_tools` MCP,以及客户端扩展列表中用户已启用的第三方 MCP。用户明确指定第三方 MCP Server 或工具时,先在当前可用工具中查找并直接调用;找不到时如实说明,不得伪造。你可以按需选择这些能力:`agc_write_file` 写入代码、配置、资源依赖清单或说明文件;`agc_generate_image` 生成普通图片、角色图、视觉规范图(icon-spec)、UI 设计图;`agc_edit_image` 修改已登记图片;`taonier_prepare_game_art` 准备完整游戏美术包及可用的 canonical 切片;`agc_list_registered_assets`、`agc_list_project_files`、`agc_list_account_assets`、`agc_import_account_assets` 用于发现和接入资源依赖;`agc_create_or_derive_resource` 用于视频、角色动画、音效或背景音乐;`agc_browser_playtest` 用于需要时的本地试玩观察;Skill references 按需使用相对路径直接读取。切图、资源依赖、规范图和试玩都只是可选工具提示,不要求调用、固定顺序或特定产物,AGC 不会据此替你拆任务、编排 DAG、做强验收或阻止继续执行;不要等待 Supervisor、harness 或宿主规划器。不要读取或输出凭据、Token、Cookie、auth.json、.env 或宿主私密路径;项目锁、付费提交、幂等键、下载校验和客户端投影由客户端处理。游戏文件真实变化后客户端可登记资源和版本,Codex 不直接保存或伪造项目版本。"; +const DIRECT_AGC_ENGINEERING_GUIDANCE: &str = "AGC 工程合同:当前 cwd 是用户选择的项目目录。先读取当前 cwd 下适用的 AGENTS.md、README 或项目说明并识别实际引擎与工程结构。用户明确指定 Cocos、Unity、Godot 或其它编辑器/引擎,而当前目录不具备对应工程结构时,必须先说明不匹配并提出澄清;在澄清前不得把请求改写成 Phaser/Web 实现,也不得写文件、安装依赖、构建或试玩。仅当用户确认继续当前工程或提供了匹配的项目目录后才执行。识别为 Cocos Creator 项目时,优先使用 `agc_cocos_execute` 或 Cocos 插件的 `cocos.editor.execute` 在已打开的 Creator 编辑器中操作;不要创建 Phaser 文件,不要把 Cocos 请求改写成 Web 工程。新 Web 游戏使用 npm + Vite,Phaser 固定为 4.2.1,在 `game.js` 或模块中使用 `import Phaser from 'phaser'`;可以按需使用其它 npm 依赖,不得复制 Phaser bundle、使用 import map 或 CDN。简单修改只完成用户明确要求的范围;安装依赖、构建和试玩是后续操作,除非用户明确要求或它们是完成该项不可替代的最小验证,否则不得擅自扩展任务。源码使用 cwd 相对路径,例如 `index.html`、`style.css`、`game.js`、`package.json`、`package-lock.json`、`assets/hero.png`;依赖安装与构建使用项目自己的 npm scripts。原生文件工具、patch 和命令参数可以使用 DirectProject Codex app-server 声明的完整访问权限;优先使用 cwd 相对路径,例如 `index.html`、`style.css`、`game.js`、`assets/hero.png`,便于用户理解和审计,但不再把项目路径、`.agent/`、`.git/` 或其它目录做成 Codex 原生能力白名单。若 Codex 原生文件修改不可用,可以按需用客户端 `agc_write_file` 把文本写入项目相对路径;调用 `agc_write_file` 时,content 必须是目标文件的完整原始 UTF-8 正文,不得把 command.exec 的 Exit code、Wall time、Output 包装、终端日志或解释文字一起复制进 content,命令结果只能用于判断,不能当作文件正文。凭据、Token、Cookie、auth.json、`.env` 和 Runtime 私有控制面仍不得主动输出到对话、工具参数或日志。DirectProject 提供 Codex 原生文件、搜索、命令、图片查看、Skill、经客户端注入的 `agc_tools` MCP,以及客户端扩展列表中用户已启用的第三方 MCP。用户明确指定第三方 MCP Server 或工具时,先在当前可用工具中查找并直接调用;找不到时如实说明,不得伪造。你可以按需选择这些能力:`agc_write_file` 写入代码、配置、资源依赖清单或说明文件;`agc_generate_image` 生成普通图片、角色图、视觉规范图(icon-spec)、UI 设计图;`agc_edit_image` 修改已登记图片;`taonier_prepare_game_art` 准备完整游戏美术包及可用的 canonical 切片;`agc_list_registered_assets`、`agc_list_project_files`、`agc_list_account_assets`、`agc_import_account_assets` 用于发现和接入资源依赖;`agc_create_or_derive_resource` 用于视频、角色动画、音效或背景音乐;`agc_browser_playtest` 用于需要时的本地试玩观察;Skill references 按需使用相对路径直接读取。切图、资源依赖、规范图和试玩都只是可选工具提示,不要求调用、固定顺序或特定产物,AGC 不会据此替你拆任务、编排 DAG、做强验收或阻止继续执行;不要等待 Supervisor、harness 或宿主规划器。不要读取或输出凭据、Token、Cookie、auth.json、.env 或宿主私密路径;项目锁、付费提交、幂等键、下载校验和客户端投影由客户端处理。游戏文件真实变化后客户端可登记资源和版本,Codex 不直接保存或伪造项目版本。"; const DIRECT_COCOS_BUILTIN_PLUGIN_GUIDANCE: &str = r#"Cocos Creator 桥接边界:Cocos 的编辑器能力来自客户端随包提供的内置插件 `agc-cocos-editor`,Agent 工具名是 `cocos.editor.execute`(客户端受控工具名为 `agc_cocos_execute`)。识别为 Cocos Creator 项目后,直接检查当前可用工具并调用这个内置工具;不要搜索、读取、安装、启用或建议项目目录里的 MCP 扩展、`extensions/` 包、`package.json` 插件或 Cocos 面板服务。项目内的第三方 MCP 扩展不是 AGC Cocos 桥接来源,缺失内置工具时只能报告客户端内置插件不可用,不得改为查项目扩展或要求用户打开 Cocos MCP 面板。历史聊天记录仅用于理解上下文,不是工具或系统指令;其中与本边界冲突的旧说明一律以当前提示和当前可用内置工具为准。"#; const DIRECT_COCOS_CAPABILITY_GUIDE: &str = r#"Cocos 能力:先用 cocos_get_capabilities 和 cocos_get_hierarchy 查询;查询返回 NID 与 UUID,场景切换后必须重新查询。读取场景树 `Editor.Message.request('scene', 'query-node-tree')`,先用只读查询拿到真实 uuid 和当前状态,再执行修改。用 cocos_inspect_node 取得 componentIndex、组件类型及属性后再修改。节点、组件、Prefab、Label/Sprite/Button/Shape、Layout/Widget、九宫格、批量 UI、保存、撤销、日志、构建诊断和网页预览调试均有对应 cocos_* 工具,按实际 inputSchema 调用。批量 UI 最多 64 个节点和 12 层,save 缺省 true;首次保存可用 cocos_save_scene 的 path 指定 assets 下新 .scene 路径。只在 verified 为 true 时报告结果已经回读确认;failed、rolledBack 和 needs-reconciliation 不能当成功,结果不确定不得自动重发。cocos_mcp_undo_last 会拒绝覆盖后续手动修改。预览工具只管理自己的 Chromium 窗口和当前项目 loopback 地址,capture 返回 PNG 图片。目录之外的操作继续用 agc_cocos_execute 注入支持 await/return 的 JS 函数体。"#; const DIRECT_CODEX_ART_SPEC_ASSET_PATH: &str = "assets/art-spec.png"; diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index da59becc3..ef516ae27 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -8629,3 +8629,11 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在 - 决策:`agc-cocos-editor` 只有在当前受控项目通过 Cocos Creator 根目录识别(`package.json.creator.version` + 普通 `assets/`)时才暴露插件、面板和 Cocos 工具;无项目或其它项目类型均隐藏并失败关闭。 - 决策:项目切换离开 Cocos 时立即停止已运行的插件实例;启动、面板读取、插件 RPC、Runtime execute 和 DirectProject MCP 工具目录/执行入口全部再次校验项目类型。Cocos 编辑器操作优先经内置插件入口,禁止回退到项目 `extensions/`、`package.json` 插件或第三方 MCP。 - 验证:新增 builtin/plugin host 项目级门禁测试,Direct MCP fixture 补最小 Cocos 工程结构;Rust 定向测试、显式 `cocos-editor-execute` feature 编译、编码检查和 `git diff --check` 已执行。 + + +## 2026-09-14 DirectProject Codex 取消路径白名单并启用完整 sandbox + +- 背景:DirectProject 原先以 `workspaceWrite(writableRoots=[项目根])` 和 `item/fileChange/requestApproval` 的项目根校验限制 Codex 原生文件与命令能力,系统提示词还把 `.agent/`、`.git/`、项目外路径列为不可访问边界。 +- 决策:DirectProject app-server thread 改为 `sandbox="danger-full-access"`,turn 改为 `sandboxPolicy.type="dangerFullAccess"`,不再发送 `writableRoots` 或 workspace 网络开关,原生命令网络随完整 sandbox 开放;app-server 交互请求不再按 grant root 做白名单裁剪,直接项目会话统一接受文件变更、命令执行和权限请求。首页只读对话、AGC `agc_tools` 业务授权、Provider 凭据隔离、Runtime 审计和客户端受控文件工具合同继续保留。 +- 提示词同步:DirectProject 不再把路径范围描述成 Codex 原生能力禁区,但仍禁止主动输出 Token、Cookie、auth.json、`.env` 和 Runtime 私有控制面。 +- 验证:Rust 定向单测覆盖 `danger-full-access` / `dangerFullAccess`、无 `writableRoots`、外部 grant root 仍接受,以及 DirectHome 继续只读拒绝。 diff --git a/docs/technical/【技术方案】AI游戏创作Agent Runtime V1.1-2026-07-12.md b/docs/technical/【技术方案】AI游戏创作Agent Runtime V1.1-2026-07-12.md index 06e155fd2..39a9f1760 100644 --- a/docs/technical/【技术方案】AI游戏创作Agent Runtime V1.1-2026-07-12.md +++ b/docs/technical/【技术方案】AI游戏创作Agent Runtime V1.1-2026-07-12.md @@ -1761,4 +1761,4 @@ V1.54 的公共编排层可以在运行前构造动态 DAG,但 LLM 在执行 本文中 V1.1/V1.52 关于 app-server 全局关闭 native shell、network、browser、plugin 和 multi-agent 的表述继续适用于 ToolHost/DirectHome 与 legacy Runtime;不再作为 DirectProject 的现行实现。DirectProject 恢复原生文件/搜索/命令、图片查看和 Skill,始终注入审核后的 `agc_tools` MCP,并可在启动时从客户端扩展仓库接入用户已启用的独立第三方 MCP 配置;第三方配置不进入全局 Codex home,不开启完整 Plugin Runtime。平台美术、资源投影、浏览器试玩、受控搜索、付费副作用和 durable delegation 仍必须走 AGC 权威链路。 -DirectProject 的写入根固定为真实 `game/`,审批策略为 `never`,原生命令网络保持关闭,联网资料继续走受控 `agc_web_search`;shell 使用 Codex `shell_environment_policy` 的 glob 排除 API key、proxy、loopback bridge 和受控开关。配置了 AGC LLM Key 或可解析的 `OPENAI_API_KEY` 登录态时,真实 provider 凭据只由 AGC 本地 provider proxy 持有,Codex 仅获得连接级随机代理令牌;无法安全代理的 OAuth `auth.json` 继续关闭 native shell/unified exec。Codex 原生子 Agent、Apps、插件、hooks、图片生成、Goals、Workspace Dependencies、Tool Suggestion 以及未接入 AGC 证据链的浏览器/电脑控制保持关闭。系统提示词只传入最小身份、工作区、Skill 索引和副作用边界,不再批量注入源码快照或 Skill 正文。sandbox writableRoots 不提供 deny-read;`.agent` 与 `../assets` 的不可读约束仍需通过 prompt/Skill 行为合同和真实 smoke 验证,不能误称为 OS 强制隔离。 +DirectProject 的历史写入根规则由 2026-09-14 覆盖:现使用 `danger-full-access` sandbox,取消 `workspaceWrite(writableRoots=...)` 与文件变更批准根白名单;项目根继续作为 cwd、连接池和审计身份根。审批策略为 `never`,原生命令网络随完整 sandbox 开放;联网资料仍可走受控 `agc_web_search`;shell 使用 Codex `shell_environment_policy` 的 glob 排除 API key、proxy、loopback bridge 和受控开关。配置了 AGC LLM Key 或可解析的 `OPENAI_API_KEY` 登录态时,真实 provider 凭据只由 AGC 本地 provider proxy 持有,Codex 仅获得连接级随机代理令牌;无法安全代理的 OAuth `auth.json` 继续关闭 native shell/unified exec。Codex 原生子 Agent、Apps、插件、hooks、图片生成、Goals、Workspace Dependencies、Tool Suggestion 以及未接入 AGC 证据链的浏览器/电脑控制保持关闭。系统提示词只传入最小身份、工作区、Skill 索引和副作用边界,不再批量注入源码快照或 Skill 正文。sandbox writableRoots 不提供 deny-read;`.agent` 与 `../assets` 的不可读约束仍需通过 prompt/Skill 行为合同和真实 smoke 验证,不能误称为 OS 强制隔离。 diff --git a/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md b/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md index fd13ced6e..f8e9416ec 100644 --- a/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md +++ b/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md @@ -1271,9 +1271,15 @@ game-project/ ## DirectProject 工具权限现行覆盖(2026-08-24) -本文早期关于“DirectProject 关闭通用 shell、原生网络和主动工具”的描述属于迁移前基线,现由以下覆盖规则取代:DirectProject 仅在真实 `game/` cwd 与 `workspaceWrite(writableRoots=[game])` 内恢复 Codex 原生文件/搜索/命令、图片查看和 Skill;其余 ToolHost/DirectHome 合同不变。客户端审核的 `agc_tools` MCP 继续承担平台美术、资源登记、去背景、浏览器试玩和受控搜索,并保留项目锁、幂等账本、下载校验、恢复与投影权威。 +本文早期关于“DirectProject 关闭通用 shell、原生网络和主动工具”的描述属于迁移前基线;2026-09-14 起,DirectProject 的 Codex sandbox 与审批规则由下方“完整访问覆盖”取代。其余 ToolHost/DirectHome 合同不变。客户端审核的 `agc_tools` MCP 继续承担平台美术、资源登记、去背景、浏览器试玩和受控搜索,并保留项目锁、幂等账本、下载校验、恢复与投影权威。 -DirectProject 使用 `approvalPolicy=never`,避免每次原生调用再经过泛化 ToolHost 包装;原生命令网络保持关闭,联网资料继续走受控 `agc_web_search`。多 Agent、Apps、完整插件 Runtime、hooks、Goals、Workspace Dependencies、Tool Suggestion 和原生浏览器/电脑控制仍关闭,避免绕过 AGC durable delegation、浏览器证据和副作用审计;图片生成通过客户端审核的 `agc_tools.agc_generate_image` 暴露普通单图、角色图、视觉规范图和 UI 设计图,完整游戏美术包继续使用 `agc_tools.taonier_prepare_game_art`,两者都复用同一客户端登录态、幂等账本、下载校验和 manifest/revision 投影,不开放 Codex 原生 image tool。app-server 使用隔离 `CODEX_HOME`:内置 `agc_tools` 由客户端启动参数注入,用户在客户端扩展列表启用的独立第三方 MCP 以原生配置写入该次隔离 home;全局 Codex MCP、禁用项、Plugin hooks/apps 和其它插件能力不进入 DirectProject。第三方项固定非 required,配置或启动失败只记录该项,不替换 `agc_tools`;provider session token、工具桥地址和受控搜索标记不得通过第三方 MCP 的环境转发字段泄露。配置了 AGC LLM Key 或可解析的 `OPENAI_API_KEY` 登录态时,真实 provider 凭据只由 AGC 本地 provider proxy 持有,Codex 仅使用连接级随机代理令牌;无法安全代理的 OAuth `auth.json` 继续关闭 native shell/unified exec。`agc_tools` 的平台授权由 AGC 客户端当前登录会话和受控后端完成,普通客户端不得把 DirectProject 请求改成外部 API Key 请求;401/403 只投影为客户端登录或权限异常,不向用户索要凭据或暴露内部 URL。shell 子进程采用 `shell_environment_policy` core 继承及 secret/proxy/bridge 排除,provider key 和桥接凭据不得进入命令环境。系统提示词不再预注入项目源码快照或 Skill 正文,Codex 按需读取当前 cwd 文件。 +## DirectProject Codex 完整访问覆盖(2026-09-14) + +DirectProject 现明确采用 Codex app-server 的 `danger-full-access` sandbox:thread 使用 `sandbox="danger-full-access"`,turn 使用 `sandboxPolicy.type="dangerFullAccess"`,不再发送 `workspaceWrite`、`writableRoots` 或项目根文件批准白名单。DirectProject 收到 app-server 的文件变更、命令执行和权限请求时直接接受,Codex 原生能力不再按项目路径做二次白名单裁剪;用户选择的项目目录仍作为 cwd 和 AGC 业务身份根,用于连接池、审计与客户端受控 MCP 的项目绑定。 + +这项覆盖只改变 Codex 原生 app-server 的 sandbox 与审批边界:首页只读对话、AGC `agc_tools` MCP 的业务授权、Provider 凭据隔离、Runtime 审计与客户端 `agc_write_file` 的产品契约继续有效。系统提示词不再把 `.agent/`、`.git/`、项目外路径等描述为 Codex 原生能力禁区,但仍要求不要把 Token、Cookie、auth.json、`.env` 或 Runtime 私有控制面主动输出到对话、工具参数和日志。 + +DirectProject 使用 `approvalPolicy=never`,避免每次原生调用再经过泛化 ToolHost 包装;原生命令网络随完整 sandbox 开放;联网资料仍可走受控 `agc_web_search`。多 Agent、Apps、完整插件 Runtime、hooks、Goals、Workspace Dependencies、Tool Suggestion 和原生浏览器/电脑控制仍关闭,避免绕过 AGC durable delegation、浏览器证据和副作用审计;图片生成通过客户端审核的 `agc_tools.agc_generate_image` 暴露普通单图、角色图、视觉规范图和 UI 设计图,完整游戏美术包继续使用 `agc_tools.taonier_prepare_game_art`,两者都复用同一客户端登录态、幂等账本、下载校验和 manifest/revision 投影,不开放 Codex 原生 image tool。app-server 使用隔离 `CODEX_HOME`:内置 `agc_tools` 由客户端启动参数注入,用户在客户端扩展列表启用的独立第三方 MCP 以原生配置写入该次隔离 home;全局 Codex MCP、禁用项、Plugin hooks/apps 和其它插件能力不进入 DirectProject。第三方项固定非 required,配置或启动失败只记录该项,不替换 `agc_tools`;provider session token、工具桥地址和受控搜索标记不得通过第三方 MCP 的环境转发字段泄露。配置了 AGC LLM Key 或可解析的 `OPENAI_API_KEY` 登录态时,真实 provider 凭据只由 AGC 本地 provider proxy 持有,Codex 仅使用连接级随机代理令牌;无法安全代理的 OAuth `auth.json` 继续关闭 native shell/unified exec。`agc_tools` 的平台授权由 AGC 客户端当前登录会话和受控后端完成,普通客户端不得把 DirectProject 请求改成外部 API Key 请求;401/403 只投影为客户端登录或权限异常,不向用户索要凭据或暴露内部 URL。shell 子进程采用 `shell_environment_policy` core 继承及 secret/proxy/bridge 排除,provider key 和桥接凭据不得进入命令环境。系统提示词不再预注入项目源码快照或 Skill 正文,Codex 按需读取当前 cwd 文件。 ## 2026-08-24 AGC UI 原型桥接与自主 UI workflow - 2026-08-24 起,`ui-prototype` 与 UI 编辑器的 `UI` JSON 资源明确分离。设计图生成后必须由白名单 `ui.workflow.run` 按页面执行 `prepare → recognize → status → finalize`:为每个功能页面创建并关联 `UI` JSON,载入页面设计图和已登记图片/图标/字体,调用 UI Editor 的 provider-backed 结构识别、多树合并与分批组件绑定,持久化 State/revision,写入 `game/` 应用标记,并把 `reference-ready → structure-ready → merge-ready → binding-ready → application-ready → completed` 各阶段的 `generationKind` 和 manifest revision 投影给客户端。Provider 未配置、请求失败、工具调用缺失、结果不匹配、未知字体引用、未产出可渲染组件或仍有待审节点时保留最近真实阶段并返回 blocker,不得使用 deterministic seed 冒充完成。工作台点击 `ui-prototype` 时通过 `ensure_ui_design_resource_for_prototype` 幂等补齐关联资源;工作流完成后自动打开首个页面的 UI 编辑器 `visual-binding` 最终阶段,交给用户检查和手动调整。UI 编辑器独立的语义建议请求也必须复用统一 LLM 传输选择,`llm.stream=true` 时发送 `stream=true` 并聚合完整工具调用后再校验结果。只生成图片、登记空 JSON 或进入普通图片画布均不构成 UI 工作流完成,详见 [`【技术方案】UI工作流资源桥接与Runtime执行-2026-08-24.md`](../【技术方案】UI工作流资源桥接与Runtime执行-2026-08-24.md)。 -- 2.52.0