新增 AGC Cocos 直连模块

新增独立 crate server-rs/crates/cocos-editor-bridge:Creator 主进程发现与项目/PID 身份校验、named pipe 命令协议、可选 Windows DLL 注入,默认 feature 全部关闭。
新增 Creator 主进程内 payload:在 \\.\pipe\genarrative-cocos-editor-{pid} 上串行提供 ping/status/execute,execute 只接受有界 JavaScript 函数体,结果不确定时返回 ExecutionUncertain 并禁止自动重放。
AGC 新增 cocos_editor 模块与 prepare_cocos_editor_injection、inject_cocos_editor、ping_cocos_editor、status_cocos_editor、execute_cocos_editor_code 五条命令,注入只加载随包资源目录中的 DLL。
AGC 新增 cocos-editor、cocos-editor-execute、cocos-editor-injection 三层 feature 与资源映射,Windows 构建把 bridge DLL 暂存到 resources/cocos-editor-bridge,并改为按 Cargo profile 目录查找产物 DLL。
Runtime 新增 cocos.editor.execute:同步工具策略快照、并行账本、隔离 Agent 拒绝表、工具描述与动作审计,审计只记录代码长度与 SHA-256。
DirectProject 的 agc_tools 新增 agc_cocos_execute,只接收 code,复用项目写锁和当前项目权限,结果不确定时阻断后续调用。
共享契约新增 cocos.editor.execute(confirm 权限),当前分支命令契约从 64 条更新为 65 条。
新增技术方案文档与决策记录,并在文档索引登记;resources/cocos-editor-bridge 仅提交占位与 .gitignore,生成的 DLL 不入库。
移植到 codex/agc-agent-plugins 的适配:命令契约长度、Tauri 未调用命令 allowlist 与 Windows 资源清单校验随当前分支基线同步更新。
This commit is contained in:
2026-09-10 14:37:41 +08:00
parent 7df526868c
commit 9d2ad69a75
34 changed files with 2391 additions and 11 deletions
@@ -126,6 +126,11 @@ const allowedUncalledTauriCommands = [
'call_agc_plugin',
'read_agc_plugin_panel',
'set_agc_plugin_project_path',
'prepare_cocos_editor_injection',
'ping_cocos_editor',
'status_cocos_editor',
'execute_cocos_editor_code',
'inject_cocos_editor',
];
const sourceExtensions = new Set([
'.json',
@@ -1294,7 +1299,7 @@ if (tauriConfig.identifier !== 'world.genarrative.ai-game-creator') {
throw new Error('AI game creator shell identifier drifted');
}
const expectedBundledCodexResources = {
const expectedBundledWindowsResources = {
'resources/codex/win-x64/bin/codex.exe': 'codex/win-x64/bin/codex.exe',
'resources/codex/win-x64/bin/codex-code-mode-host.exe':
'codex/win-x64/bin/codex-code-mode-host.exe',
@@ -1308,6 +1313,7 @@ const expectedBundledCodexResources = {
'codex/win-x64/codex-package.json',
'resources/codex/win-x64/NOTICE.md': 'codex/win-x64/NOTICE.md',
'resources/codex/win-x64/manifest.json': 'codex/win-x64/manifest.json',
'resources/cocos-editor-bridge': 'cocos-editor-bridge',
};
if (tauriConfig.bundle?.resources !== undefined) {
throw new Error(
@@ -1316,8 +1322,8 @@ if (tauriConfig.bundle?.resources !== undefined) {
}
assert.deepEqual(
windowsTauriConfig.bundle?.resources,
expectedBundledCodexResources,
'AI game creator shell Windows Tauri config must bundle the complete pinned Codex resource set',
expectedBundledWindowsResources,
'AI game creator shell Windows Tauri config must bundle the complete pinned Codex resource set and Cocos bridge payload directory',
);
if (windowsTauriConfig.bundle?.useLocalToolsDir !== true) {
throw new Error(
+12
View File
@@ -733,6 +733,17 @@ dependencies = [
"error-code",
]
[[package]]
name = "cocos-editor-bridge"
version = "0.1.0"
dependencies = [
"cc",
"serde",
"serde_json",
"sha2",
"windows-sys 0.61.2",
]
[[package]]
name = "combine"
version = "4.6.7"
@@ -1709,6 +1720,7 @@ dependencies = [
"axum",
"base64 0.22.1",
"chromiumoxide",
"cocos-editor-bridge",
"futures",
"getrandom 0.3.4",
"http",
@@ -6,6 +6,9 @@ publish = false
[features]
default = []
cocos-editor = ["cocos-editor-bridge/process-discovery"]
cocos-editor-execute = ["cocos-editor", "cocos-editor-bridge/windows-transport"]
cocos-editor-injection = ["cocos-editor-execute", "cocos-editor-bridge/windows-injection"]
[build-dependencies]
serde = { version = "1", features = ["derive"] }
@@ -19,6 +22,7 @@ ts-rs = "12.0.1"
typed_floats = { version = "1.0.7", features = ["serde"] }
nalgebra = { version = "0.35.0", features = ["serde-serialize"] }
agent-runtime-core = { path = "../../../server-rs/crates/agent-runtime-core" }
cocos-editor-bridge = { path = "../../../server-rs/crates/cocos-editor-bridge", default-features = false }
base64 = "0.22"
axum = "0.8"
chromiumoxide = "0.9.1"
@@ -182,6 +182,7 @@ fn main() {
);
let manifest_path = manifest_dir.join("prompts/runtime/manifest.json");
stage_bundled_codex_cli(&manifest_dir);
stage_cocos_editor_payload(&manifest_dir);
let compiled = runtime_prompt_bundle::compile_manifest(&manifest_path)
.unwrap_or_else(|error| panic!("Prompt Bundle 编译失败:{error}"));
validate_seed_task_catalog(&compiled);
@@ -203,3 +204,32 @@ fn main() {
}
tauri_build::build()
}
#[cfg(windows)]
fn stage_cocos_editor_payload(manifest_dir: &std::path::Path) {
if std::env::var_os("CARGO_FEATURE_COCOS_EDITOR_INJECTION").is_none() {
return;
}
let out_dir = std::path::PathBuf::from(std::env::var_os("OUT_DIR").expect("OUT_DIR"));
let profile_dir = out_dir
.ancestors()
.find(|path| path.file_name().is_some_and(|name| name == "build"))
.and_then(|build_dir| build_dir.parent())
.expect("AGC Cargo profile directory not found");
let candidates = [
profile_dir.join("deps/cocos_editor_bridge.dll"),
profile_dir.join("cocos_editor_bridge.dll"),
];
let source = candidates
.iter()
.find(|path| path.is_file())
.unwrap_or_else(|| panic!("Cocos bridge native payload 未构建:{}", candidates.iter().map(|p| p.display().to_string()).collect::<Vec<_>>().join("")));
let destination = manifest_dir.join("resources/cocos-editor-bridge/cocos-editor-bridge.dll");
std::fs::create_dir_all(destination.parent().expect("payload resource parent"))
.expect("创建 Cocos bridge 资源目录失败");
std::fs::copy(source, &destination).expect("复制 Cocos bridge native payload 失败");
println!("cargo:rerun-if-changed={}", source.display());
}
#[cfg(not(windows))]
fn stage_cocos_editor_payload(_manifest_dir: &std::path::Path) {}
@@ -54,6 +54,8 @@ struct DirectToolBridgeState {
regeneration_gate: tokio::sync::Mutex<()>,
resource_generation_gate: tokio::sync::Mutex<()>,
image_generation_gate: tokio::sync::Mutex<()>,
#[cfg(all(windows, feature = "cocos-editor-execute"))]
cocos_execute_uncertain: tokio::sync::Mutex<bool>,
}
#[derive(Default)]
@@ -687,6 +689,8 @@ fn direct_tool_bridge_state_with_search(
regeneration_gate: tokio::sync::Mutex::new(()),
resource_generation_gate: tokio::sync::Mutex::new(()),
image_generation_gate: tokio::sync::Mutex::new(()),
#[cfg(all(windows, feature = "cocos-editor-execute"))]
cocos_execute_uncertain: tokio::sync::Mutex::new(false),
})
}
@@ -2295,6 +2299,67 @@ async fn bridge_web_search_at(root: &Path, arguments: &Value, search_url: &str)
}
}
#[cfg(all(windows, feature = "cocos-editor-execute"))]
async fn bridge_cocos_execute(state: &DirectToolBridgeState, arguments: &Value) -> Value {
let prepared = (|| {
bridge_reject_unknown_fields(arguments, &["code"])?;
enforce_project_permission_policy(&state.root, "cocos.editor.execute")?;
let code = arguments.get("code").and_then(Value::as_str)
.ok_or_else(|| "code 必须是 JavaScript 函数体".to_string())?;
cocos_editor_bridge::validate_execute_code(code).map_err(|error| error.to_string())?;
Ok::<_, String>(code.to_string())
})();
let code = match prepared {
Ok(code) => code,
Err(error) => return bridge_tool_result(
redact_agent_runtime_error(&state.root, &error, 480), Vec::new(), true,
),
};
let mut uncertain = state.cocos_execute_uncertain.lock().await;
if *uncertain {
return bridge_tool_result(json!({
"status": "needs-reconciliation", "retryAllowed": false,
"message": "先前 Cocos execute 结果待核对,当前 bridge 不再发送执行命令"
}).to_string(), Vec::new(), true);
}
let root = state.root.clone();
let result = tokio::task::spawn_blocking(move || {
let _lock = acquire_project_write_lock(&root, "direct-cocos.execute")
.map_err(cocos_editor_bridge::BridgeError::InvalidInput)?;
cocos_editor_bridge::execute_cocos_editor_code_for_project(
root.to_string_lossy().as_ref(), &code, cocos_editor_bridge::DEFAULT_COMMAND_TIMEOUT_MS,
)
}).await;
match result {
Ok(Ok(response)) => {
let is_error = !response.ok;
let text = json!({
"status": if response.ok { "completed" } else { "failed" },
"requestId": response.request_id,
"result": response.result,
"error": response.error,
}).to_string();
bridge_tool_result(redact_agent_runtime_project_paths(&state.root, &text, 32_000), Vec::new(), is_error)
}
failed => {
let (is_uncertain, error) = match failed {
Ok(Err(error)) => (
matches!(&error, cocos_editor_bridge::BridgeError::ExecutionUncertain(_)),
error.to_string(),
),
Err(_) => (true, "Cocos execute worker 退出,执行结果需要核对".to_string()),
Ok(Ok(_)) => unreachable!(),
};
*uncertain = is_uncertain;
bridge_tool_result(json!({
"status": if is_uncertain { "needs-reconciliation" } else { "failed" },
"retryAllowed": !is_uncertain,
"message": redact_agent_runtime_error(&state.root, &error, 480),
}).to_string(), Vec::new(), true)
}
}
}
async fn handle_direct_tool_bridge(
State(state): State<Arc<DirectToolBridgeState>>,
Json(request): Json<DirectToolBridgeRequest>,
@@ -2308,6 +2373,8 @@ async fn handle_direct_tool_bridge(
}
"agc_list_project_files" => bridge_list_project_files(&state.root, &request.arguments),
"agc_write_file" => bridge_write_file(&state.root, &request.arguments),
#[cfg(all(windows, feature = "cocos-editor-execute"))]
"agc_cocos_execute" => bridge_cocos_execute(&state, &request.arguments).await,
"agc_list_account_assets" => bridge_list_account_assets(&state, &request.arguments).await,
"agc_import_account_assets" => {
bridge_import_account_assets(&state, &request.arguments).await
@@ -431,6 +431,17 @@ fn direct_tools_mcp_specs_for(controlled_web_search: bool) -> Value {
}),
];
let mut tools = tools;
#[cfg(all(windows, feature = "cocos-editor-execute"))]
tools.push(json!({
"name": "agc_cocos_execute",
"description": "在当前项目已连接的 Cocos Creator 主进程执行 JavaScript 函数体,支持 await 和 return。宿主绑定项目和目标进程,只提交 code。结果待核对或超时后禁止自动重发;使用 Editor.Message 调用 Creator API。",
"inputSchema": {
"type": "object",
"properties": { "code": { "type": "string", "minLength": 1, "maxLength": cocos_editor_bridge::MAX_EXECUTE_CODE_BYTES } },
"required": ["code"],
"additionalProperties": false
}
}));
if controlled_web_search {
tools.push(json!({
"name": "agc_web_search",
@@ -503,6 +514,19 @@ fn validate_write_file_arguments(arguments: &Value) -> Result<(), String> {
normalize_relative_path(&path).map(|_| ())
}
#[cfg(all(windows, feature = "cocos-editor-execute"))]
async fn call_agc_cocos_execute(arguments: &Value) -> Value {
let validated = validate_tool_object_fields(arguments, &["code"]).and_then(|()| {
let code = arguments.get("code").and_then(Value::as_str)
.ok_or_else(|| "code 必须是 JavaScript 函数体".to_string())?;
cocos_editor_bridge::validate_execute_code(code).map_err(|error| error.to_string())
});
if let Err(error) = validated {
return mcp_tool_result(error, Vec::new(), true);
}
call_client_tool_bridge("agc_cocos_execute", arguments).await
}
fn mcp_success(id: Value, result: Value) -> Value {
json!({ "jsonrpc": "2.0", "id": id, "result": result })
}
@@ -1575,6 +1599,8 @@ async fn handle_direct_tools_mcp_request(root: &Path, request: Value) -> Option<
"conversation.read" => external_mcp_conversation_read(root, &arguments),
"agc_read_skill_resource" => call_agc_read_skill_resource(&arguments),
"agc_write_file" => call_agc_write_file(&arguments).await,
#[cfg(all(windows, feature = "cocos-editor-execute"))]
"agc_cocos_execute" => call_agc_cocos_execute(&arguments).await,
"taonier_prepare_game_art" => call_taonier_prepare_game_art(&arguments).await,
"agc_generate_image" => call_agc_generate_image(&arguments).await,
"agc_edit_image" => call_agc_edit_image(&arguments).await,
@@ -1869,6 +1895,9 @@ mod tests {
"agc_remove_background",
"agc_browser_playtest",
]
.into_iter()
.chain(cfg!(all(windows, feature = "cocos-editor-execute")).then_some("agc_cocos_execute"))
.collect::<Vec<_>>()
);
let serialized = specs.to_string();
assert!(!serialized.contains("agc_web_search"));
@@ -1606,6 +1606,17 @@ pub(crate) fn agent_runtime_tool_action_input_summary(
.unwrap_or(160)
),
"command.run_limited" => format!("commandId={}", text(&["commandId", "command_id", "id"])),
"cocos.editor.execute" => format!(
"codeChars={} · codeSha256={:x}",
chars(&["code"]),
Sha256::digest(
input
.get("code")
.and_then(serde_json::Value::as_str)
.unwrap_or_default()
.as_bytes()
)
),
"preview.validate" => {
let viewports = input
.get("viewports")
@@ -362,6 +362,16 @@ pub(crate) async fn execute_game_creator_agent_runtime_tool_action_with_pending_
observe_agent_runtime_limited_command(root, agent_id, run_id, &action.input)
}
"preview.start" => observe_agent_runtime_preview_start(root, agent_id, run_id),
"cocos.editor.execute" => observe_agent_runtime_project_snapshot_with_lock(
root,
agent_id,
run_id,
action,
&action_fingerprint,
pending_action,
true,
|| observe_agent_runtime_cocos_editor_execute(root, action, pending_action),
),
"preview.validate" => {
observe_agent_runtime_preview_validate(
root,
@@ -95,6 +95,8 @@ pub(in crate::agent) fn game_creator_agent_runtime_tool_command_id(
"command.stdin" => Some("command.stdin"),
"command.terminate" => Some("command.terminate"),
"command.run_limited" => Some("command.run_limited"),
#[cfg(feature = "cocos-editor-execute")]
"cocos.editor.execute" => Some("cocos.editor.execute"),
"preview.start" => Some("preview.start"),
"preview.validate" => Some("preview.validate"),
"image.inspect" => Some("image.inspect"),
@@ -17,7 +17,7 @@ mod canvas_asset_kind_contract_tests {
}
pub(crate) fn agent_runtime_executable_tools() -> Vec<&'static str> {
vec![
let tools = vec![
GAME_CREATOR_USER_INPUT_REQUEST_TOOL,
"memory.read",
"memory.write",
@@ -63,7 +63,11 @@ pub(crate) fn agent_runtime_executable_tools() -> Vec<&'static str> {
"agent.schedule_ready",
"agent.action_history",
"agent.run_status",
]
];
tools
.into_iter()
.chain(cfg!(feature = "cocos-editor-execute").then_some("cocos.editor.execute"))
.collect()
}
pub(crate) fn agent_runtime_native_executable_tools() -> Vec<&'static str> {
@@ -2,6 +2,7 @@ use super::*;
mod action_history;
mod command_ops;
mod cocos_editor;
mod context;
mod delegation;
mod delivery;
@@ -21,6 +22,7 @@ mod ui_workflow;
pub(in crate::agent) use action_history::*;
pub(in crate::agent) use command_ops::*;
pub(in crate::agent) use cocos_editor::*;
pub(in crate::agent) use context::*;
pub(in crate::agent) use delegation::*;
pub(in crate::agent) use delivery::*;
@@ -0,0 +1,109 @@
use super::*;
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
struct CocosEditorExecuteInput {
code: String,
}
pub(in crate::agent) fn observe_agent_runtime_cocos_editor_execute(
root: &Path,
action: &AgentRuntimeToolAction,
pending_action: Option<&AgentRuntimePendingToolAction>,
) -> AgentRuntimeToolObservation {
let input = match serde_json::from_value::<CocosEditorExecuteInput>(action.input.clone()) {
Ok(input) => input,
Err(error) => {
return AgentRuntimeToolObservation {
tool: "cocos.editor.execute".to_string(),
status: "failed".to_string(),
summary: sanitize_agent_runtime_text(
&format!("cocos.editor.execute 输入无效:{error}"),
240,
),
detail: None,
};
}
};
if pending_action.is_none() {
return AgentRuntimeToolObservation {
tool: "cocos.editor.execute".to_string(),
status: "failed".to_string(),
summary: "cocos.editor.execute 必须绑定 durable pending action".to_string(),
detail: None,
};
}
let response = match cocos_editor_bridge::execute_cocos_editor_code_for_project(
root.to_string_lossy().as_ref(),
&input.code,
cocos_editor_bridge::DEFAULT_COMMAND_TIMEOUT_MS,
) {
Ok(response) => response,
Err(error) => {
let uncertain = matches!(
&error,
cocos_editor_bridge::BridgeError::ExecutionUncertain(_)
);
return AgentRuntimeToolObservation {
tool: "cocos.editor.execute".to_string(),
status: if uncertain {
AGENT_RUNTIME_TOOL_OBSERVATION_STATUS_NEEDS_RECONCILIATION
} else {
"failed"
}
.to_string(),
summary: redact_agent_runtime_project_paths(root, &error.to_string(), 240),
detail: Some(
serde_json::json!({
"codeChars": input.code.chars().count(),
"error": redact_agent_runtime_project_paths(root, &error.to_string(), 500),
})
.to_string(),
),
};
}
};
let ok = response.ok;
let detail = serde_json::json!({
"processId": response.process_id,
"requestId": response.request_id,
"ok": ok,
"result": response.result.clone(),
"error": response.error.clone(),
"codeChars": input.code.chars().count(),
})
.to_string();
AgentRuntimeToolObservation {
tool: "cocos.editor.execute".to_string(),
status: if ok { "ok" } else { "failed" }.to_string(),
summary: if ok {
format!(
"已在 Cocos Creator 执行 {} 字符代码",
input.code.chars().count()
)
} else {
"Cocos Creator execute 返回失败".to_string()
},
detail: Some(detail),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn execute_requires_durable_pending_action_before_touching_editor() {
let action = AgentRuntimeToolAction {
tool: "cocos.editor.execute".to_string(),
reason: Some("test".to_string()),
input: serde_json::json!({"code": "return 1 + 1;"}),
};
let observation =
observe_agent_runtime_cocos_editor_execute(Path::new("C:\\cocos"), &action, None);
assert_eq!(observation.status, "failed");
assert!(observation
.summary
.contains("必须绑定 durable pending action"));
}
}
@@ -1027,6 +1027,9 @@ fn runtime_tool_description(tool: &str) -> &'static str {
"ui.workflow.run" => {
"先用 discover 从受控 game/ui-pages.json 或页面声明标记自动发现全部功能页面,再把已登记 ui-prototype 与每个页面的设计图桥接成独立 UI JSON State;可同时载入已登记图片、图标和项目字体,执行 Provider 结构识别、多树合并与分批组件绑定、回读阶段,并且只有所有页面已绑定且已应用到 game/ 后才允许 finalize。项目根目录由 Runtime 注入,模型不得传入宿主路径。"
}
"cocos.editor.execute" => {
"在当前项目对应的已打开 Cocos Creator 编辑器中执行一段有界代码;Runtime 自动绑定唯一匹配的 Creator 主进程,代码与结果都通过注入 payload 的本机 bridge 返回。"
}
"blackboard.write" => "向项目级共享黑板追加稳定结论。",
"agent.message" => "向一个目标 Agent 写入定向上下文消息。",
"agent.delegate" => {
@@ -1216,6 +1219,14 @@ fn runtime_tool_input_schema(tool: &str) -> Value {
}
}),
"command.exec" | "command.start" => command_start_input_schema(),
"cocos.editor.execute" => json!({
"type": "object",
"required": ["code"],
"additionalProperties": false,
"properties": {
"code": { "type": "string", "minLength": 1, "maxLength": 131072 }
}
}),
"command.output_read" => json!({
"type": "object", "required": ["actionId", "startLine", "maxLines"], "additionalProperties": false,
"properties": {
@@ -0,0 +1,172 @@
#[cfg(feature = "cocos-editor-injection")]
use cocos_editor_bridge::CocosEditorInjectionRequest;
use cocos_editor_bridge::{
CocosEditorCommandResponse, CocosEditorInjectionResult, CocosEditorProcess,
};
use serde::Deserialize;
#[cfg(feature = "cocos-editor-injection")]
use tauri::Manager;
#[cfg(feature = "cocos-editor-injection")]
const BUNDLED_COCOS_BRIDGE_PAYLOAD: &str = "cocos-editor-bridge/cocos-editor-bridge.dll";
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub(crate) struct CocosEditorTargetRequest {
process_id: u32,
project_path: String,
}
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub(crate) struct CocosEditorCommandInput {
process_id: u32,
project_path: String,
#[serde(default = "default_command_timeout_ms")]
timeout_ms: u32,
}
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub(crate) struct CocosEditorInjectionInput {
process_id: u32,
project_path: String,
#[serde(default = "default_injection_timeout_ms")]
timeout_ms: u32,
}
fn default_command_timeout_ms() -> u32 {
cocos_editor_bridge::DEFAULT_COMMAND_TIMEOUT_MS
}
fn default_injection_timeout_ms() -> u32 {
cocos_editor_bridge::DEFAULT_INJECTION_TIMEOUT_MS
}
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub(crate) struct CocosEditorExecuteInput {
process_id: u32,
project_path: String,
code: String,
#[serde(default = "default_command_timeout_ms")]
timeout_ms: u32,
}
#[tauri::command]
pub(crate) fn prepare_cocos_editor_injection(
request: CocosEditorTargetRequest,
) -> Result<CocosEditorProcess, String> {
#[cfg(feature = "cocos-editor")]
{
return cocos_editor_bridge::validate_injection_target(
request.process_id,
&request.project_path,
)
.map_err(|error| error.to_string());
}
#[cfg(not(feature = "cocos-editor"))]
{
let _ = (request.process_id, request.project_path);
Err("Cocos Editor bridge feature 未启用".to_string())
}
}
#[tauri::command]
pub(crate) fn ping_cocos_editor(
request: CocosEditorCommandInput,
) -> Result<CocosEditorCommandResponse, String> {
#[cfg(feature = "cocos-editor-execute")]
{
return cocos_editor_bridge::ping_cocos_editor(
request.process_id,
&request.project_path,
request.timeout_ms,
)
.map_err(|error| error.to_string());
}
#[cfg(not(feature = "cocos-editor-execute"))]
{
let _ = (request.process_id, request.project_path, request.timeout_ms);
Err("Cocos Editor execute feature 未启用".to_string())
}
}
#[tauri::command]
pub(crate) fn status_cocos_editor(
request: CocosEditorCommandInput,
) -> Result<CocosEditorCommandResponse, String> {
#[cfg(feature = "cocos-editor-execute")]
{
return cocos_editor_bridge::status_cocos_editor(
request.process_id,
&request.project_path,
request.timeout_ms,
)
.map_err(|error| error.to_string());
}
#[cfg(not(feature = "cocos-editor-execute"))]
{
let _ = (request.process_id, request.project_path, request.timeout_ms);
Err("Cocos Editor execute feature 未启用".to_string())
}
}
#[tauri::command]
pub(crate) fn execute_cocos_editor_code(
request: CocosEditorExecuteInput,
) -> Result<CocosEditorCommandResponse, String> {
#[cfg(feature = "cocos-editor-execute")]
{
return cocos_editor_bridge::execute_cocos_editor_code(
request.process_id,
&request.project_path,
&request.code,
request.timeout_ms,
)
.map_err(|error| error.to_string());
}
#[cfg(not(feature = "cocos-editor-execute"))]
{
let _ = (
request.process_id,
request.project_path,
request.code,
request.timeout_ms,
);
Err("Cocos Editor execute feature 未启用".to_string())
}
}
#[tauri::command]
pub(crate) fn inject_cocos_editor(
app: tauri::AppHandle,
input: CocosEditorInjectionInput,
) -> Result<CocosEditorInjectionResult, String> {
#[cfg(feature = "cocos-editor-injection")]
{
let payload = app
.path()
.resource_dir()
.map_err(|error| format!("解析 AGC 资源目录失败:{error}"))?
.join(BUNDLED_COCOS_BRIDGE_PAYLOAD);
if !payload.is_file() {
return Err(format!(
"AGC 未随包提供 Cocos bridge payload{}",
BUNDLED_COCOS_BRIDGE_PAYLOAD
));
}
let request = CocosEditorInjectionRequest {
process_id: input.process_id,
project_path: input.project_path,
bridge_dll_path: payload.to_string_lossy().into_owned(),
timeout_ms: input.timeout_ms,
};
return cocos_editor_bridge::inject_bridge_dll(&request).map_err(|error| error.to_string());
}
#[cfg(not(feature = "cocos-editor-injection"))]
{
let _ = app;
let _ = (input.process_id, input.project_path, input.timeout_ms);
Err("Cocos Editor injection feature 未启用;当前构建只支持目标预检".to_string())
}
}
@@ -43,6 +43,7 @@ pub(crate) const ISOLATED_AGENT_UNSCOPED_DENIED_COMMAND_IDS: &[&str] = &[
"command.exec",
"command.start",
"command.stdin",
"cocos.editor.execute",
"preview.start",
"agent.delegate",
"agent.spawn_isolated",
@@ -62,6 +63,7 @@ pub(crate) const ISOLATED_AGENT_UNSCOPED_DENIED_TOOLS: &[&str] = &[
"command.exec",
"command.start",
"command.stdin",
"cocos.editor.execute",
"preview.start",
"agent.delegate",
"agent.spawn_isolated",
@@ -251,6 +251,7 @@ mod command_exec;
mod command_output;
mod command_sandbox;
mod command_sandbox_trampoline;
mod cocos_editor;
mod commands;
mod config;
mod context_compaction;
@@ -293,6 +294,7 @@ use collaboration::*;
use command_exec::*;
use command_output::*;
use command_sandbox::*;
use cocos_editor::*;
use commands::*;
use config::*;
use context_compaction::*;
@@ -2728,7 +2730,12 @@ fn main() {
read_diagnostic_logs,
report_client_error,
get_pending_error_reports,
ack_error_reports
ack_error_reports,
prepare_cocos_editor_injection,
ping_cocos_editor,
status_cocos_editor,
execute_cocos_editor_code,
inject_cocos_editor,
])
.build(tauri_context);
let app = match app {
@@ -11,7 +11,8 @@
"resources/codex/win-x64/codex-resources/codex-windows-sandbox-setup.exe": "codex/win-x64/codex-resources/codex-windows-sandbox-setup.exe",
"resources/codex/win-x64/codex-package.json": "codex/win-x64/codex-package.json",
"resources/codex/win-x64/NOTICE.md": "codex/win-x64/NOTICE.md",
"resources/codex/win-x64/manifest.json": "codex/win-x64/manifest.json"
"resources/codex/win-x64/manifest.json": "codex/win-x64/manifest.json",
"resources/cocos-editor-bridge": "cocos-editor-bridge"
}
}
}
+1
View File
@@ -26,6 +26,7 @@
- [DirectProject Codex 原始历史与异常恢复](./technical/【技术方案】DirectProject%20Codex原始历史与异常恢复-2026-09-04.md):原始 Responses item 持久化、线程注入与异常回合收尾。
- [DirectProject 客户端 Skill 与 MCP 扩展导入方案](./technical/【技术方案】DirectProject客户端Skill与MCP扩展导入方案-2026-08-31.md):客户端扩展导入、按独立 Skill/MCP 拆分、命名、启用和启动时注入边界。
- [AGC 通用插件宿主与编辑器适配](./technical/【技术方案】AGC通用插件宿主与编辑器适配-2026-09-09.md):通用插件宿主、SDK、权限审计、UI 挂载和 Cocos 编辑器适配边界。
- [AGC Cocos Creator 编辑器桥接模块](<./technical/【技术方案】AGC Cocos Creator 编辑器桥接模块-2026-09-09.md>):独立 crate、feature 开关、目标校验与 Windows 注入边界。
- [AGC 客户端更新检查与下载](./technical/【技术方案】AGC客户端更新检查与下载-2026-08-31.md):启动版本检测、OSS 清单格式和下载约定。
- [DirectProject 本轮附件路径映射](./technical/【技术方案】DirectProject本轮附件路径映射-2026-08-31.md):Direct 首轮只映射附件原名与项目相对路径,不灌正文、不区别 GDD。
- [Direct 回合行为审计账本](./technical/【技术方案】Direct回合行为审计账本-2026-08-31.md)Direct GUI 回合把 native 读 / MCP / 写文件落成项目内有界时间线,用于判断有没有打开本轮附件。
@@ -8203,3 +8203,11 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在
- 用户侧 AGC Plugin 按 OpenAI Agent Plugins 组合模型吸收现有 Skill/MCP:统一 catalog、来源和审计,但 Skill 仍由 Codex 原生读取、MCP 仍由 MCP transport 启动。新增通用 `plugin_host``@genarrative/agc-plugin-sdk`;扫描、manifest 校验、Runtime Plugin 子进程启停/热重载、行分隔 JSON-RPC、UI/Capability 注册、权限和审计统一由宿主负责。
- 插件 manifest 使用 Agent Plugins 根目录 `plugin.json` 和标准 schema,兼容 `.codex-plugin/plugin.json`AGC Runtime 字段放在 `extensions.world.genarrative.agc`,来源和父/子项统一保存到既有 `extensions` 索引。入口和面板资源只能是插件目录内普通文件;权限采用白名单,进程继承最小系统环境,不接收客户端凭据。
- 目标编辑器只实现 `EditorAdapter` 的查找、PID/项目/版本校验、连接和请求转换;本次仅保留通用 registry 和 trait,不随标准 Plugin 核心内置具体编辑器适配器。
## 2026-09-09 AGC Cocos Creator 编辑器桥接独立 crate
- Cocos Creator bridge 核心位于 `server-rs/crates/cocos-editor-bridge`,与 Tauri、Agent Runtime 和服务端解耦;默认 feature 关闭,桌面宿主按需启用 `process-discovery``windows-injection`
- 进程发现只用于把 CocosCreator 主进程 PID 与 `--project` 和 Creator 版本绑定,排除 Electron 子进程;AGC 默认不加载该能力。
- 注入仅加载随 AGC 资源目录提供的 DLL,结果先标记 `injected-unverified`,必须由 payload 完成握手后才可开放有限 Cocos 操作;Runtime 的 execute 代码有界并受确认策略保护,不开放未受限 eval 或项目扩展自动写入。
- Runtime 第一阶段只广告 `cocos.editor.execute`,代码长度有界、默认走确认策略,项目根和目标 PID 不交给模型;`ping/status` 先作为宿主命令保留,不扩大全局 Agent 工具面。
- DirectProject 的 `agc_tools` 对应入口是 `agc_cocos_execute`,同样只接收 code,并沿用当前项目权限。2026-09-10 已通过临时真实 Creator 3.8.8 验证 Node 的 Windows 调试 handler 激活 Inspector、注入 bootstrap、pipe execute 及关闭 Inspector 后继续执行;此路线尚未替换当前 native DLL 源码。现有 `RequestInterrupt` 回调不能调用 JavaScript,不能把 DLL 加载和窗口线程钩子当作可用握手。执行发送后的未知结果禁止自动重放,Direct bridge 会阻断后续 execute。详细步骤、版本/fuse 和端口边界见 Cocos bridge 技术方案。
@@ -0,0 +1,95 @@
# AGC Cocos Creator 编辑器桥接模块
## 目标
目标是在用户已打开 Cocos Creator 项目时,由 AGC 识别正确的 Creator 主进程,并在进程内注入随包 JavaScript bootstrap;用户不需要在 Cocos 项目中手动安装扩展。桥接核心独立于 Tauri,位于 `server-rs/crates/cocos-editor-bridge`。2026-09-10 已验证通过 Node 自带的运行中 Inspector 激活入口完成引导,具体见本文“Inspector 注入调研”;这条路径尚未替换当前 crate 的 DLL 实现。
## 边界
Cocos Creator 3.x 是 Electron/Node 编辑器,不能复用 Unity Mono 的 CoreCLR/Roslyn 进程内调用方式。Core crate 只负责:
1. 读取 Creator 主进程的 PID、父 PID、可执行文件和 `--project` 参数;Electron renderer、GPU、utility、crashpad 子进程被排除。
2. 对 PID、项目目录和 payload 做同一目标校验。项目目录必须是绝对路径、可解析目录并包含 `package.json`Creator 版本只从 `package.json.creator.version` 读取。
3. 在启用 `windows-injection` feature 时,通过 `OpenProcess``VirtualAllocEx``WriteProcessMemory``CreateRemoteThread(LoadLibraryW)` 加载受信任 DLL。
4. 提供 `ping/status/execute` 协议、Windows pipe 客户端和 `payload/bootstrap.cjs`。native payload 只在具备受支持的 Node/V8 上下文调度时尝试送入 Creator 主进程并调用 `install(Editor)`,不写入项目扩展目录。
当前 DLL 实现仍只返回 `injected-unverified`,不能作为可交付的注入入口。它在 `windows-injection` 下构建 C++ payload 并尝试通过窗口钩子调用 V8;其中 `RequestInterrupt → run_bootstrap → Script::Run` 违反 V8 的中断回调约束,不能因编译或 DLL 加载成功而认定安全可用。`HandleScope` 的存储大小和 C++ Local/MaybeLocal 的调用约定也不能通过裸指针替代来推断。后续接入采用下述 Inspector 引导,不再依赖这条未经验证的 native 路径。
## Feature 开关
crate 默认不启用任何宿主集成:
```toml
cocos-editor-bridge = { path = ".../server-rs/crates/cocos-editor-bridge", default-features = false, features = ["process-discovery"] }
```
- `process-discovery`:启用 Windows Creator 主进程发现;不加载 Windows 注入 API。
- `windows-transport`:在已注入 payload 后启用本机 named pipe 的 `ping/status/execute` 命令传输。
- `windows-injection`:隐含启用 `windows-transport`,并启用 Windows native DLL 注入实现。
AGC 或其它桌面宿主应将 `windows-injection` 作为单独的发行构建开关,服务端和非桌面构建保持 `default-features = false`
当前 AGC Tauri adapter 只暴露 `prepare_cocos_editor_injection`(确认前预检)、`inject_cocos_editor`(确认后注入)、`ping_cocos_editor``status_cocos_editor``execute_cocos_editor_code`。Runtime 只广告一个 `cocos.editor.execute` 工具,代码输入使用当前项目根,目标 PID 由 crate 内部唯一匹配;默认命令权限为 confirm,具体运行档沿用已有 Runtime 策略。进程发现留在 crate 内部作为目标校验步骤,不建立客户端扫描服务或独立发现入口。默认 AGC 构建不启用 Cocos 集成;桌面构建需显式传 `--features cocos-editor`,命令执行需传 `--features cocos-editor-execute`,注入构建再传 `--features cocos-editor-injection`。注入命令不接收 DLL 路径,只加载资源目录中的 `cocos-editor-bridge/cocos-editor-bridge.dll`,避免把 Tauri command 变成任意 DLL 注入器。
## 第一阶段命令协议
DirectProject 的现役 `agc_tools` 目录通过 Windows `cocos-editor-execute` feature 注册 `agc_cocos_execute`,参数只有 `code`。客户端在 blocking worker 内调用 crate,保留项目锁和现有项目权限;当前 bridge 出现执行结果不确定后拒绝后续 execute。旧 Runtime 的对应工具名为 `cocos.editor.execute`,继续使用它已有的 pending action、权限和恢复语义。
注入 payload 在目标 Creator 主进程内监听 `\\.\pipe\genarrative-cocos-editor-{pid}`,使用换行分隔的 JSON。crate 只生成三种操作:
```json
{"schemaVersion":"game-creator-cocos-editor-bridge.v1","requestId":"cocos-42-...","processId":42,"projectPath":"C:\\demo","command":{"op":"ping"}}
{"schemaVersion":"game-creator-cocos-editor-bridge.v1","requestId":"cocos-42-...","processId":42,"projectPath":"C:\\demo","command":{"op":"status"}}
{"schemaVersion":"game-creator-cocos-editor-bridge.v1","requestId":"cocos-42-...","processId":42,"projectPath":"C:\\demo","command":{"op":"execute","code":"return Editor.Project.path"}}
```
回执必须回传相同的 `schemaVersion/requestId/processId``execute.code` 是支持 `await``return` 的 JavaScript 函数体,上限为 128 KiB,单次回执上限为 2 MiBbootstrap 通过 `AsyncFunction('Editor', 'require', code)` 在 Creator 主进程事件循环串行执行,并返回可序列化 JSON。它使用 Creator/Node 的现有权限,不是代码沙箱;AGC 不维护额外的 Cocos 业务 API 名单。
Rust 客户端在写入前通过 `GetNamedPipeServerProcessId` 验证 pipe 属于目标 PID,读写使用 overlapped I/O 和 deadline。execute 开始写入后遇到断线、超时或无可信回执,返回 `ExecutionUncertain`Runtime 进入 `needs-reconciliation`,不得自动重放。客户端超时不等于 JavaScript 已取消,bootstrap 保持同一串行队列直到原执行结束;同步死循环仍可能阻塞 Creator,需要真实集成阶段提供运行时中断方案。
## 安全与失败关闭
- PowerShell 查询脚本为固定常量,用户输入不拼接进 shell。
- 目标 PID 必须是没有 Electron `--type` 参数的 `CocosCreator.exe`,且其 `--project` 与请求目录规范化后相同;不能仅凭进程名注入。
- bridge DLL 必须是绝对路径、普通文件、非符号链接、`.dll` 扩展名且大小不超过 64 MiB。AGC adapter 还必须把路径限制在签名/随包资源目录;core 不接受任意下载 URL。
- 注入超时不会释放仍可能被远程线程使用的内存,并返回人工核对错误,避免在不确定状态下破坏目标进程。
- 非 Windows、feature 未启用、目标不存在、身份不匹配或 payload 不合规均直接失败,不启动 Cocos、不关闭 Cocos、不修改项目文件。
## 后续接入
下一步是在独立 crate 内实现 Inspector 引导并替换 DLL 装载入口,保留 feature 开关、项目/PID 绑定和 `ping/status/execute` 三条命令。进程发现仍只是内部目标校验,不增加客户端扫描服务。隔离验证脚本使用 Node 的 `_debugProcess`;正式 Rust 实现可直接调用同一组 Win32 API,无需附带额外 Node 运行时。源码接入、默认/启用 feature 编译和 AGC 发行包验收仍未完成。
当前验证:crate 全 feature 单元测试、Node bootstrap fixture、Rust 到 Node bootstrap 的真实命名管道回环(含 execute 超时不确定结果)通过;AGC feature 编译使用临时工作树与 Codex 资源替身,只属于编译检查,不代表发布包或真实 Creator 验收。当前工作区完整 AGC 检查受缺失内置 Codex CLI 阻断,TypeScript 契约测试受缺失 Vitest 阻断。
## Inspector 注入调研(2026-09-10
### 入口与执行链
Node 20.15.1 的 Windows 调试信号初始化会创建 `node-debug-handler-{pid}` 命名共享内存,其中存放目标进程自身 `StartIoThreadProc` 的函数地址。`process._debugProcess(pid)` 的内部实现是 `OpenProcess → OpenFileMappingW(FILE_MAP_READ) → MapViewOfFile → CreateRemoteThread(目标 handler)`。handler 通过 Node 自己的 libuv/interrupt 调度启动 Inspector,宿主无需写入 DLL、修改机器指令或猜测 V8 对象布局。
接入次序:
1. 校验 Creator 主进程、项目身份、位数和调试入口是否可用;共享内存给出的入口应与目标进程已加载的可执行映像匹配,不能仅信任一个带 PID 的对象名。
2. 如已有目标 Inspector,复用且记录原有状态;否则激活目标 handler。只连接属于该 PID 的回环监听端口,随后再次核对 `process.pid``Editor.Project.path` 和运行版本。已有进程通常使用默认调试端口;端口被其它进程占用时应失败,不连接其它目标。AGC 自己启动的 Creator 可预设 `--inspect-port=127.0.0.1:0`,该参数本身不启用 Inspector。
3. 使用 Inspector `Runtime.evaluate` 在 Node 主上下文求值 CommonJS 包装器,把 `require` 显式传给 bootstrap,并调用 `install(Editor)`。不增加业务工具列表。
4. 等待 bootstrap 的 pipe 就绪,按既有身份合同验证 `ping`,再开放 `execute`
5. 如 Inspector 是本次引导开启的,先断开 Inspector WebSocket,再从 pipe 执行 `require('node:inspector').close()`;已有用户调试会话保持原状态。之后持续命令全部走 pipe。
### 已验证与限制
- 使用本机 Creator 3.8.8 的真实 GUI 主进程和由官方 Empty(3D) 模板创建的临时项目,未安装项目扩展。运行时为 Electron 31.3.1、Node 20.15.1、V8 12.6.228.28-electron.0。
- 在进程启动后激活 Inspector,注入当前 `payload/bootstrap.cjs`,实际 `ping``execute` 返回目标 PID、真实项目路径和 Creator 版本。
- 编辑器就绪后,`Editor.Message.request('scene', 'query-node-tree')` 成功返回 `null`:空白项目未打开场景。这证明消息调用链可用,不代表已验证非空场景的读取或修改。
- 关闭 Inspector 后,pipe 的 `execute` 仍成功;验证结束正常退出临时 Creator。用户原有 Creator 仅做共享内存入口和监听端口的只读检查。
- 本次真实 Electron Inspector 对 `awaitPromise: true` 曾返回 `Promise was collected`。验证改为同步 evaluate 启动安装、保存就绪状态并轮询;异步业务代码由 pipe 内的 Node 队列执行。不要把单次 Inspector 异常当作 bootstrap 未执行并自动重放。
- Inspector 的命令行 API `require` 在求值之外不保证可访问;异步闭包须捕获或显式传入该函数,不能假设 `globalThis.require` 存在。
- 本机二进制的 `EnableNodeCliInspectArguments` fuse 开启,当前已打开的 Creator 也存在调试 handler。其它版本或 fuse 被关闭的发行包不能推断支持;入口缺失应失败,不能修改 fuse 或回退到未验证的函数打补丁。
### 上游依据
- [Node 20.15.1Windows DebugProcess](https://github.com/nodejs/node/blob/v20.15.1/src/node_process_methods.cc#L377-L448)
- [Node 20.15.1:注册目标调试 handler](https://github.com/nodejs/node/blob/v20.15.1/src/inspector_agent.cc#L143-L197)
- [Electron 31.3.1NodeBindings 与 Inspector fuse](https://github.com/electron/electron/blob/v31.3.1/shell/common/node_bindings.cc)
- [Electron Inspector fuse 说明](https://github.com/electron/electron/blob/v31.3.1/docs/tutorial/fuses.md#nodecliinspect)
- [V8 12.6RequestInterrupt 的回调禁止重入 isolate](https://github.com/v8/v8/blob/12.6.228/include/v8-isolate.h)
- [Node inspector.close:等待现有连接关闭后停用 Inspector](https://github.com/nodejs/node/blob/v20.15.1/doc/api/inspector.md#inspectorclose)
@@ -183,7 +183,7 @@ Supervisor 认领该回执后,由父 run 自己为每个原 delivery 逐一创
## 目标
在 Genarrative 内建设独立桌面 App:普通用户通过项目开发工作台中的陶泥儿对话、资源画布、运行状态和确认操作,让平台生成保存在本地的可运行 Web 游戏原型,并通过本地 HTTP server 预览;主窗口提供运行时配置入口,用于保存发布版 AppData / Tauri 配置目录里的 LLM 配置及受控开发者 External Editor 配置,设置弹窗同时提供独立“关于”页并显示从客户端构建版本注入的版本号。普通客户素材画布使用平台登录态调用内部编辑器 API,不展示或要求填写画板 Base URL / API Key。任务明细、原始文件、命令日志和专业 Agent 调试控制只通过显式开发调试入口查看,不随普通客户端启动额外打开窗口。v1 的生成闭环仍以 Web 小游戏为主,同时允许用户打开已有 Godot 项目:用户选择的目录始终作为工作区根,`.agent/`、Session、Runtime、文件工具和外围资料都留在该根;客户端检查根目录及一层直接子目录中的普通文件 `project.godot`,将唯一命中的实际目录以工作区相对 `godotProjectRoot` 记录到 manifest。Agent 使用标准运行档继续修改,不创建 `game/``assets/``memory/``exports/` 平行目录;本期不扩展 Unity、Godot 内嵌预览、云同步或插件市场。
在 Genarrative 内建设独立桌面 App:普通用户通过项目开发工作台中的陶泥儿对话、资源画布、运行状态和确认操作,让平台生成保存在本地的可运行 Web 游戏原型,并通过本地 HTTP server 预览;主窗口提供运行时配置入口,用于保存发布版 AppData / Tauri 配置目录里的 LLM 配置及受控开发者 External Editor 配置,设置弹窗同时提供独立“关于”页并显示从客户端构建版本注入的版本号。普通客户素材画布使用平台登录态调用内部编辑器 API,不展示或要求填写画板 Base URL / API Key。任务明细、原始文件、命令日志和专业 Agent 调试控制只通过显式开发调试入口查看,不随普通客户端启动额外打开窗口。v1 的生成闭环仍以 Web 小游戏为主,同时允许用户打开已有 Godot 项目:用户选择的目录始终作为工作区根,`.agent/`、Session、Runtime、文件工具和外围资料都留在该根;客户端检查根目录及一层直接子目录中的普通文件 `project.godot`,将唯一命中的实际目录以工作区相对 `godotProjectRoot` 记录到 manifest。Agent 使用标准运行档继续修改,不创建 `game/``assets/``memory/``exports/` 平行目录;本期不扩展 Unity、Godot 内嵌预览、云同步或插件市场。新增的 Cocos Creator bridge 核心独立为 `server-rs/crates/cocos-editor-bridge`AGC 仅通过 feature 转发桌面进程发现、受控 execute 和 Windows 注入能力;它不改变服务端路线,也不把原始 pipe、句柄或未绑定项目身份的代码执行面暴露给 Agent。
## 技术选择
@@ -19,7 +19,7 @@ describe('AI 游戏创作 App 共享契约', () => {
it('keeps command permissions explicit', () => {
const commandIds = GAME_CREATION_APP_COMMANDS.map((command) => command.id);
expect(GAME_CREATION_APP_COMMANDS).toHaveLength(64);
expect(GAME_CREATION_APP_COMMANDS).toHaveLength(65);
expect(commandIds).toContain('project.git_inspect');
expect(commandIds).toContain('project.git_commit');
expect(commandIds).toContain('project.patchset');
@@ -29,6 +29,7 @@ describe('AI 游戏创作 App 共享契约', () => {
expect(commandIds).toContain('command.poll');
expect(commandIds).toContain('command.stdin');
expect(commandIds).toContain('command.terminate');
expect(commandIds).toContain('cocos.editor.execute');
expect(commandIds).toContain('mcp.call');
expect(commandIds.indexOf('command.exec')).toBe(
commandIds.indexOf('command.run_limited') + 1,
@@ -67,6 +67,7 @@ export const GAME_CREATION_APP_COMMANDS = [
{ id: 'command.poll', permission: 'auto' },
{ id: 'command.stdin', permission: 'confirm' },
{ id: 'command.terminate', permission: 'confirm' },
{ id: 'cocos.editor.execute', permission: 'confirm' },
{ id: 'canvas.project_open', permission: 'confirm' },
{ id: 'canvas.project_sync', permission: 'confirm' },
{ id: 'canvas.asset_import', permission: 'confirm' },

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