放开 DirectProject Codex 沙箱权限 #347
@@ -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<std::path::PathBuf> {
|
||||
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<CodexAppServerInner>,
|
||||
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]
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -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 继续只读拒绝。
|
||||
|
||||
@@ -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 强制隔离。
|
||||
|
||||
@@ -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)。
|
||||
|
||||
Reference in New Issue
Block a user