Compare commits

..

4 Commits

Author SHA1 Message Date
lhk229 1ce2c10107 删除客户端每回合四项媒体资源请求上限 (#427)
Project CI / AI game creator shell Rust shard 3/4 (push) Successful in 7m10s
Project CI / AI game creator shell Rust shard 1/4 (push) Successful in 7m28s
Project CI / AI game creator shell Rust shard 4/4 (push) Successful in 7m32s
Project CI / AI game creator shell Rust shard 2/4 (push) Successful in 7m48s
Project CI / AI game creator shell Rust smoke (push) Successful in 2m1s
Project CI / AI game creator shell Rust crates (push) Successful in 1m58s
Project CI / Repository checks (push) Successful in 5m46s
Project CI / Frontend tests (push) Successful in 7m8s
Project CI / Backend tests (push) Successful in 9m19s
Project CI / Native shell tests (push) Successful in 9m42s
Project CI / AI game creator shell web tests (push) Successful in 5m5s
- direct_tool_bridge.rs 删除 DIRECT_TOOL_BRIDGE_MAX_RESOURCE_CALLS_PER_TURN 常量、回合授权状态的 resource_request_ids 计数 map 与 resource_request_ids() 方法
- agc_create_or_derive_resource(含 agc_edit_image 委托)与 agc_remove_background 统一按回合身份与请求指纹确定性派生 operation/idempotency id,同指纹重试复用与 pending 对账语义不变
- 付费提交串行仍由 resource_generation_gate 互斥保证,成本控制交由服务端计费与泥点余额兜底
- 决策记录新增 2026-09-19 删除上限条目,2026-09-18 抠图豁免条目标记为仅作追溯
- 实施计划文档同步改为「不同请求串行提交,不设每回合计数上限」

Reviewed-on: #427
Co-authored-by: Linghong <ink29535@proton.me>
Co-committed-by: Linghong <ink29535@proton.me>
2026-09-19 01:44:48 +08:00
lhk229 ea141d9f17 修复持久目标模式切换测试的异步竞态
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust smoke (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 1/4 (push) Successful in 5m11s
Project CI / AI game creator shell Rust shard 3/4 (push) Successful in 5m4s
Project CI / AI game creator shell Rust shard 2/4 (push) Successful in 5m50s
Project CI / AI game creator shell Rust smoke (push) Successful in 1m59s
Project CI / AI game creator shell Rust crates (push) Successful in 2m28s
Project CI / AI game creator shell Rust shard 4/4 (push) Successful in 4m37s
Project CI / Backend tests (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
Project CI / Frontend tests (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
- developer-agent-window.suite.ts 在切换目标模式前等待按钮脱离 disabled,避免读取历史链路在途时点击落空导致 aria-pressed 断言偶发失败
2026-09-19 01:21:05 +08:00
lhk229 769740a46d Merge pull request '取消抠图工具的每回合四项媒体请求限制' (#426) from codex/remove-bg-turn-limit into master
Project CI / AI game creator shell Rust shard 3/4 (push) Successful in 6m56s
Project CI / AI game creator shell Rust shard 1/4 (push) Successful in 7m15s
Project CI / AI game creator shell Rust shard 2/4 (push) Successful in 7m24s
Project CI / AI game creator shell Rust shard 4/4 (push) Successful in 7m21s
Project CI / AI game creator shell Rust smoke (push) Successful in 2m25s
Project CI / AI game creator shell Rust crates (push) Successful in 2m55s
Project CI / Repository checks (push) Successful in 15m5s
Project CI / Backend tests (push) Successful in 18m40s
Project CI / Native shell tests (push) Successful in 20m39s
Project CI / Frontend tests (push) Failing after 19m35s
Project CI / AI game creator shell web tests (push) Successful in 13m6s
Reviewed-on: #426
2026-09-19 00:29:51 +08:00
lhk229 b472b9eec9 取消抠图工具的每回合四项媒体请求限制
Project CI / AI game creator shell Rust smoke (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Has been cancelled
agc_remove_background 不再经过 resource_request_ids 付费计数,直接按回合身份与请求指纹确定性派生 id

抠图请求不再挤占付费 create/derive 的四项额度,幂等复用与 pending 对账语义不变

create_or_derive(含 agc_edit_image 委托)路径逐字节不变

决策记录补充 2026-09-18 抠图豁免条目

实施计划文档同步注明抠图不占四项额度
2026-09-19 00:14:01 +08:00
113 changed files with 251 additions and 8638 deletions
+4 -22
View File
@@ -221,7 +221,8 @@ jobs:
- name: Run AI game creator shell agent-run smoke
run: npm run check:native-shells:agc-rust-smoke
# AGC 壳依赖的共享 / 平台和编辑器插件 crate 各自预热独立 manifest,再运行对应测试。
# AGC 壳依赖的共享 / 平台 crate 测试用的是 server-rs workspace 与两个无锁独立 crate
# 的 manifest,属另一套依赖图,因此单独一个 job 预热、单独跑。
ai-game-creator-shell-rust-crates:
name: AI game creator shell Rust crates
runs-on: genarrative-ci
@@ -262,15 +263,13 @@ jobs:
# `cargo test --manifest-path` 单独跑这两个 crate。不在这里预热的话,这两条测试
# 会在测试阶段自己 `Updating crates.io index`crates.io 一抖动整条 job 就红
# (见 #327 / PR #316 run 1950)。
# Cocos 插件也使用独立且未提交的锁文件,一并预热。
# 这些 crate 都没有提交 Cargo.lock,所以这里只能做不带锁标志的 fetch:
# 两个 crate 都没有提交 Cargo.lock,所以这里只能做不带锁标志的 fetch:
# 加锁标志会因为缺少锁文件直接失败。生成的 Cargo.lock 落在两个 crate 目录内,
# 已被各自的 .gitignore 忽略,只留在容器里;随后的测试阶段因此能用锁定版本
# 解析,不再触碰 registry index。
for manifest_path in \
server-rs/crates/agent-runtime-core/Cargo.toml \
server-rs/crates/agent-runtime-orchestration/Cargo.toml \
plugins/agc-cocos-editor/native/cocos-editor-bridge/Cargo.toml; do
server-rs/crates/agent-runtime-orchestration/Cargo.toml; do
for attempt in $(seq 1 5); do
if cargo fetch \
--target x86_64-unknown-linux-gnu \
@@ -285,23 +284,6 @@ jobs:
done
done
- name: Prepare Unity plugin Rust dependencies
shell: bash
run: |
set -euo pipefail
for attempt in $(seq 1 5); do
if cargo fetch --locked \
--target x86_64-unknown-linux-gnu \
--manifest-path plugins/agc-unity-editor/native/unity-editor-bridge/Cargo.toml; then
break
fi
if [[ "${attempt}" -eq 5 ]]; then
echo 'Unity plugin Cargo dependency fetch failed after 5 attempts.' >&2
exit 1
fi
sleep $((attempt * 2))
done
- name: Run AI game creator shell shared crate gates
run: npm run check:native-shells:agc-rust-crates
-4
View File
@@ -48,10 +48,6 @@ temp*build*/
/apps/ai-game-creator-shell/src-tauri/resources/codex/mac-native/manifest.json
/apps/ai-game-creator-shell/src-tauri/resources/codex/mac-native/NOTICE.md
/plugins/agc-cocos-editor/native/payload/
/plugins/agc-unity-editor/dotnet/**/bin/
/plugins/agc-unity-editor/dotnet/**/obj/
/plugins/agc-unity-editor/dotnet/publish/
/plugins/agc-unity-editor/dotnet/native-build/
/apps/ai-game-creator-shell/logs/
/apps/ai-game-creator-shell/.llm-drafts/
/apps/ai-game-creator-shell/game-creator.config.local.json
@@ -105,7 +105,6 @@ export const agcReleasePathPatterns = [
'packages/',
'server-rs/crates/',
'plugins/agc-cocos-editor/',
'plugins/agc-unity-editor/',
'apps/desktop-shell/src-tauri/icons/',
'package.json',
'package-lock.json',
@@ -344,11 +344,7 @@ test('Windows remains the default and explicit Windows overrides macOS environme
context,
{
spawn: (_binary, command) => {
assert.ok(
command.includes(
'--features=cocos-editor-execute,unity-editor-execute',
),
);
assert.ok(command.includes('--features=cocos-editor-execute'));
assert.ok(command.includes('user-config.json'));
const configIndex = command.lastIndexOf('--config');
const config = JSON.parse(
@@ -19,6 +19,6 @@ export function withDefaultCargoFeatures(argv, features) {
export function defaultEditorFeatures(target) {
return target === 'win32' || target.includes('windows')
? ['cocos-editor-execute', 'unity-editor-execute']
? ['cocos-editor-execute']
: [];
}
@@ -9,7 +9,7 @@ test('Windows release includes the same editor feature as development', () => {
buildTauriBuildArguments([], 'x86_64-pc-windows-msvc', 'win32'),
[
'build',
'--features=cocos-editor-execute,unity-editor-execute',
'--features=cocos-editor-execute',
'--target',
'x86_64-pc-windows-msvc',
],
@@ -143,6 +143,7 @@ const allowedUncalledTauriCommands = [
'reload_agc_plugin',
'call_agc_plugin',
'read_agc_plugin_panel',
'set_agc_plugin_project_path',
'set_agc_plugin_enabled',
];
const sourceExtensions = new Set([
@@ -1,10 +1,7 @@
import { resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import {
defaultEditorFeatures,
withDefaultCargoFeatures,
} from './cargo-features.mjs';
import { withDefaultCargoFeatures } from './cargo-features.mjs';
import {
readAgcDevEndpoint,
resolveAgcDevEndpoint,
@@ -50,8 +47,9 @@ function buildTauriArguments(argv, devUrl = readAgcDevEndpoint().url) {
];
}
// 开发和发行构建使用同一平台编辑器 feature 集合。
// 可用 AGC_DEV_CARGO_FEATURES(逗号分隔)覆盖,传空串即关闭。
// `agc_cocos_execute` 与 Cocos 编辑器适配器只在 `cocos-editor-execute` feature
// 注册。开发构建默认在 Windows 打开它,否则 Agent 的工具清单里根本没有该工具,
// 只能退化成改写脚本。可用 AGC_DEV_CARGO_FEATURES(逗号分隔)覆盖,传空串即关闭。
function readDevCargoFeatures(env = process.env) {
const override = env.AGC_DEV_CARGO_FEATURES;
if (override !== undefined) {
@@ -60,7 +58,7 @@ function readDevCargoFeatures(env = process.env) {
.map((value) => value.trim())
.filter(Boolean);
}
return defaultEditorFeatures(process.platform);
return process.platform === 'win32' ? ['cocos-editor-execute'] : [];
}
function withDevCargoFeatures(argv, features = readDevCargoFeatures()) {
-11
View File
@@ -1793,7 +1793,6 @@ dependencies = [
"ttf-parser",
"typed_floats",
"unicode-normalization",
"unity-editor-bridge",
"url",
"uuid",
"windows-sys 0.61.2",
@@ -6408,16 +6407,6 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
[[package]]
name = "unity-editor-bridge"
version = "0.1.0"
dependencies = [
"editor-adapter-api",
"serde",
"serde_json",
"windows-sys 0.61.2",
]
[[package]]
name = "unsafe-libyaml"
version = "0.2.11"
@@ -12,7 +12,6 @@ template-library-fixtures = []
cocos-editor = ["cocos-editor-bridge/process-discovery"]
cocos-editor-execute = ["cocos-editor", "cocos-editor-bridge/windows-bootstrap"]
cocos-editor-injection = ["cocos-editor-execute", "cocos-editor-bridge/windows-injection"]
unity-editor-execute = []
[build-dependencies]
serde = { version = "1", features = ["derive"] }
@@ -28,7 +27,6 @@ nalgebra = { version = "0.35.0", features = ["serde-serialize"] }
agent-runtime-core = { path = "../../../server-rs/crates/agent-runtime-core" }
cocos-editor-bridge = { path = "../../../plugins/agc-cocos-editor/native/cocos-editor-bridge", default-features = false }
editor-adapter-api = { path = "../../../server-rs/crates/editor-adapter-api" }
unity-editor-bridge = { path = "../../../plugins/agc-unity-editor/native/unity-editor-bridge" }
base64 = "0.22"
axum = "0.8"
chromiumoxide = "0.9.1"
+1 -109
View File
@@ -196,7 +196,6 @@ fn main() {
);
let manifest_path = manifest_dir.join("prompts/runtime/manifest.json");
stage_bundled_codex_cli(&manifest_dir);
prepare_unity_editor_helper(&manifest_dir);
stage_plugin_workspace(&manifest_dir);
stage_cocos_editor_payload(&manifest_dir);
let compiled = runtime_prompt_bundle::compile_manifest(&manifest_path)
@@ -267,107 +266,6 @@ fn stage_cocos_editor_payload(manifest_dir: &std::path::Path) {
#[cfg(not(windows))]
fn stage_cocos_editor_payload(_manifest_dir: &std::path::Path) {}
/// Unity helper 是插件的随包运行文件。内容指纹避免每次 Cargo 检查都重新发布 .NET。
fn prepare_unity_editor_helper(manifest_dir: &std::path::Path) {
println!("cargo:rerun-if-env-changed=CARGO_FEATURE_UNITY_EDITOR_EXECUTE");
let target = env::var("TARGET").expect("Cargo TARGET");
if env::var_os("CARGO_FEATURE_UNITY_EDITOR_EXECUTE").is_none()
|| target != "x86_64-pc-windows-msvc"
{
return;
}
let root = manifest_dir.join("../../../plugins/agc-unity-editor/dotnet");
let mut sources = Vec::new();
collect_unity_helper_sources(&root, &mut sources);
sources.sort();
let mut fingerprint = Sha256::new();
for source in &sources {
println!("cargo:rerun-if-changed={}", source.display());
fingerprint.update(
source
.strip_prefix(&root)
.expect("helper source")
.to_string_lossy()
.as_bytes(),
);
fingerprint.update([0]);
fingerprint.update(fs::read(source).expect("读取 Unity helper 源文件失败"));
}
let fingerprint = format!("{:x}", fingerprint.finalize());
let publish = root.join("publish/win-x64");
let executable = publish.join("Agc.Unity.Attach.exe");
let stamp = publish.join(".agc-source.sha256");
println!("cargo:rerun-if-changed={}", executable.display());
if unity_helper_publish_complete(&publish)
&& fs::read_to_string(&stamp).ok().as_deref() == Some(&fingerprint)
{
return;
}
assert!(
cfg!(windows),
"构建 Unity 插件 helper 需要 Windows .NET 10 与 x64 C++ 工具链"
);
let status = std::process::Command::new("powershell.exe")
.args([
"-NoProfile",
"-NonInteractive",
"-ExecutionPolicy",
"Bypass",
"-File",
])
.arg(root.join("build.ps1"))
.current_dir(&root)
.status()
.expect("无法启动 Unity helper 构建脚本");
assert!(
status.success() && unity_helper_publish_complete(&publish),
"Unity helper 构建失败或缺少运行文件/许可"
);
fs::write(stamp, fingerprint).expect("写入 Unity helper 构建指纹失败");
}
fn unity_helper_publish_complete(publish: &std::path::Path) -> bool {
[
"Agc.Unity.Attach.exe",
"NOTICE",
"THIRD-PARTY-NOTICES.txt",
"licenses/DotCraft-Apache-2.0.txt",
"licenses/Roslyn-MIT.txt",
"licenses/upstream.json",
"licenses/dotnet-LICENSE.TXT",
"licenses/dotnet-THIRD-PARTY-NOTICES.TXT",
"licenses/microsoft.codeanalysis.common-ThirdPartyNotices.rtf",
"licenses/microsoft.codeanalysis.csharp-ThirdPartyNotices.rtf",
]
.iter()
.all(|name| {
fs::symlink_metadata(publish.join(name)).is_ok_and(|metadata| {
metadata.is_file() && !metadata.file_type().is_symlink() && metadata.len() > 0
})
})
}
fn collect_unity_helper_sources(root: &std::path::Path, sources: &mut Vec<PathBuf>) {
for entry in fs::read_dir(root)
.expect("Unity helper 源码目录缺失")
.flatten()
{
let kind = entry.file_type().expect("读取 Unity helper 源文件类型失败");
assert!(!kind.is_symlink(), "Unity helper 源码不允许符号链接");
let name = entry.file_name();
if kind.is_dir() {
if !matches!(
name.to_str(),
Some("bin" | "obj" | "publish" | "native-build")
) {
collect_unity_helper_sources(&entry.path(), sources);
}
} else if kind.is_file() {
sources.push(entry.path());
}
}
}
/// 把 `plugins/` 工作区里的插件包随包映射到应用资源目录。
///
/// 只复制插件运行需要的清单、入口、面板和 native payload,不复制 native 源码、
@@ -415,15 +313,9 @@ fn stage_plugin_workspace(manifest_dir: &std::path::Path) {
for relative in [
std::path::PathBuf::from("src"),
std::path::PathBuf::from("panels"),
std::path::PathBuf::from("skills"),
std::path::PathBuf::from("native/payload"),
std::path::PathBuf::from("dotnet/publish/win-x64"),
] {
if (relative == std::path::Path::new("native/payload") && !target.contains("windows"))
|| (relative == std::path::Path::new("dotnet/publish/win-x64")
&& (target != "x86_64-pc-windows-msvc"
|| env::var_os("CARGO_FEATURE_UNITY_EDITOR_EXECUTE").is_none()))
{
if relative == std::path::Path::new("native/payload") && !target.contains("windows") {
continue;
}
copy_plugin_tree(&plugin_root.join(&relative), &destination.join(&relative));
File diff suppressed because one or more lines are too long
@@ -27,7 +27,6 @@ const DIRECT_TOOL_BRIDGE_SEARCH_URL: &str = "https://www.bing.com/search?format=
const DIRECT_TOOL_BRIDGE_MAX_RESOURCE_NAME_CHARS: usize = 120;
const DIRECT_TOOL_BRIDGE_MAX_RESOURCE_KIND_CHARS: usize = 80;
const DIRECT_TOOL_BRIDGE_MAX_RESOURCE_PAGE_SIZE: usize = 100;
const DIRECT_TOOL_BRIDGE_MAX_RESOURCE_CALLS_PER_TURN: usize = 4;
const DIRECT_TOOL_BRIDGE_MAX_ACCOUNT_ASSET_ID_CHARS: usize = 512;
const DIRECT_TOOL_BRIDGE_MAX_LOCAL_ASSET_PATH_CHARS: usize = 512;
@@ -68,7 +67,6 @@ struct DirectToolBridgeActiveTurnAuthorization {
turn_id: String,
brief_sha256: Option<String>,
completed_result: Option<Value>,
resource_request_ids: BTreeMap<String, (String, String)>,
}
enum DirectToolBridgeRegenerationCall {
@@ -207,7 +205,6 @@ impl DirectToolBridgeState {
turn_id: turn_id.clone(),
brief_sha256: None,
completed_result: None,
resource_request_ids: BTreeMap::new(),
});
Ok(DirectToolBridgeTurnGuard {
state: Arc::clone(self),
@@ -637,30 +634,17 @@ impl DirectToolBridgeState {
Ok(())
}
fn resource_request_ids(&self, request_fingerprint: &str) -> Result<(String, String), String> {
let mut authorization = self
/// 取当前回合身份,媒体资源请求按回合身份与请求指纹确定性派生 operation/idempotency id。
fn active_resource_turn_id(&self) -> Result<String, String> {
let authorization = self
.turn_authorization
.lock()
.map_err(|_| "AGC 工具桥回合授权状态不可用".to_string())?;
let active = authorization
authorization
.active
.as_mut()
.ok_or_else(|| "当前没有客户端签发的资源生成回合身份".to_string())?;
if let Some(ids) = active.resource_request_ids.get(request_fingerprint) {
return Ok(ids.clone());
}
if active.resource_request_ids.len() >= DIRECT_TOOL_BRIDGE_MAX_RESOURCE_CALLS_PER_TURN {
return Err("单个用户回合最多只能创建四项媒体资源请求".to_string());
}
let operation_id =
direct_resource_request_uuid(&active.turn_id, "operation", request_fingerprint);
let idempotency_key =
direct_resource_request_uuid(&active.turn_id, "idempotency", request_fingerprint);
active.resource_request_ids.insert(
request_fingerprint.to_string(),
(operation_id.clone(), idempotency_key.clone()),
);
Ok((operation_id, idempotency_key))
.as_ref()
.map(|active| active.turn_id.clone())
.ok_or_else(|| "当前没有客户端签发的资源生成回合身份".to_string())
}
}
@@ -1890,8 +1874,11 @@ async fn bridge_create_or_derive_resource(
))
.await?
} else {
let (operation_id, idempotency_key) =
state.resource_request_ids(&request_fingerprint)?;
let turn_id = state.active_resource_turn_id()?;
let operation_id =
direct_resource_request_uuid(&turn_id, "operation", &request_fingerprint);
let idempotency_key =
direct_resource_request_uuid(&turn_id, "idempotency", &request_fingerprint);
let revision = read_game_creator_agent_runtime_project_revision(&state.root)?.revision;
let source_resource_id = source_asset
.as_ref()
@@ -2001,7 +1988,11 @@ async fn bridge_remove_background(state: &DirectToolBridgeState, arguments: &Val
})
.await?
} else {
let (operation_id, idempotency_key) = state.resource_request_ids(&fingerprint)?;
// id 按回合身份与请求指纹确定性派生,同指纹重试与 pending 对账语义不变。
let turn_id = state.active_resource_turn_id()?;
let operation_id = direct_resource_request_uuid(&turn_id, "operation", &fingerprint);
let idempotency_key =
direct_resource_request_uuid(&turn_id, "idempotency", &fingerprint);
let revision = read_game_creator_agent_runtime_project_revision(&state.root)?.revision;
let request = DeriveLocalProjectResourceInput {
project_path: state.root.to_string_lossy().into_owned(),
@@ -2592,51 +2583,6 @@ async fn bridge_web_search_at(root: &Path, arguments: &Value, search_url: &str)
}
}
#[cfg(all(windows, target_arch = "x86_64", feature = "unity-editor-execute"))]
async fn bridge_unity_execute(state: &DirectToolBridgeState, arguments: &Value) -> Value {
let prepared = (|| {
if !crate::builtin_plugins::unity_editor_agent_tool_available_for_project(&state.root) {
return Err("当前项目不是 Unity 项目或 Unity 插件不可用".to_string());
}
enforce_project_permission_policy(&state.root, "unity.editor.execute")?;
bridge_reject_unknown_fields(arguments, &["code"])?;
let code = arguments
.get("code")
.and_then(Value::as_str)
.ok_or_else(|| "code 必须是 C# 代码".to_string())?;
if code.trim().is_empty() || code.len() > 131072 || code.contains('\0') {
return Err("code 不能为空、包含 NUL 或超过 128 KiB".to_string());
}
Ok(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 root = state.root.clone();
let result = tokio::task::spawn_blocking(move || {
if !crate::builtin_plugins::unity_editor_agent_tool_available_for_project(&root) {
return Err("当前 Unity 插件不可用".to_string());
}
crate::editor_adapters::execute_unity_editor_code(&root, &code)
})
.await;
match result {
Ok(Ok(response)) => {
let failed = response["ok"] != true || response["status"] != "completed";
bridge_tool_result(redact_agent_runtime_error(&state.root, &response.to_string(), 32_000), Vec::new(), failed)
}
Ok(Err(error)) => bridge_tool_result(redact_agent_runtime_error(&state.root, &error, 480), Vec::new(), true),
Err(_) => bridge_tool_result(json!({"ok":false,"status":"needs-reconciliation","retryAllowed":false,"error":"Unity 执行任务异常,请人工核对结果"}).to_string(), Vec::new(), true),
}
}
#[cfg(all(windows, feature = "cocos-editor-execute"))]
async fn bridge_cocos_execute(state: &DirectToolBridgeState, arguments: &Value) -> Value {
bridge_cocos_call(state, arguments, None).await
@@ -2648,9 +2594,6 @@ async fn bridge_cocos_call(
arguments: &Value,
operation: Option<&str>,
) -> Value {
if !crate::builtin_plugins::is_enabled(crate::builtin_plugins::AGC_COCOS_EDITOR_PLUGIN_ID) {
return bridge_tool_result("Cocos 编辑器插件已禁用".to_string(), Vec::new(), true);
}
if !crate::builtin_plugins::cocos_editor_agent_tool_available_for_project(&state.root) {
return bridge_tool_result(
"当前项目不是 Cocos Creator 项目或 Cocos 插件不可用,agc_cocos_execute 不可用"
@@ -2837,8 +2780,6 @@ async fn handle_direct_tool_bridge(
"agc_list_project_files" => bridge_list_project_files(&state.root, &request.arguments),
#[cfg(all(windows, feature = "cocos-editor-execute"))]
"agc_cocos_execute" => bridge_cocos_execute(&state, &request.arguments).await,
#[cfg(all(windows, target_arch = "x86_64", feature = "unity-editor-execute"))]
"agc_unity_execute" => bridge_unity_execute(&state, &request.arguments).await,
#[cfg(all(windows, feature = "cocos-editor-execute"))]
operation if cocos_editor_bridge::is_cocos_operation(operation) => {
bridge_cocos_call(&state, &request.arguments, Some(operation)).await
@@ -75,14 +75,7 @@ pub(crate) fn run_direct_tools_mcp_if_requested(args: &[String]) -> Option<i32>
async fn direct_tools_mcp_specs() -> Value {
let mut cocos_editor_available = false;
let mut unity_editor_available = false;
if cfg!(all(windows, feature = "cocos-editor-execute"))
|| cfg!(all(
windows,
target_arch = "x86_64",
feature = "unity-editor-execute"
))
{
if cfg!(all(windows, feature = "cocos-editor-execute")) {
// 每次 tools/list 询问绑定的宿主;失败时不广告可选插件工具。
if let Ok(result) = tokio::time::timeout(
std::time::Duration::from_secs(5),
@@ -103,22 +96,10 @@ async fn direct_tools_mcp_specs() -> Value {
.iter()
.any(|tool| tool == crate::builtin_plugins::AGC_COCOS_EDITOR_TOOL_NAME)
});
unity_editor_available = availability
.as_ref()
.and_then(|v| v["tools"].as_array())
.is_some_and(|tools| {
tools
.iter()
.any(|tool| tool == crate::builtin_plugins::AGC_UNITY_EDITOR_TOOL_NAME)
});
}
}
}
direct_tools_mcp_specs_for_plugins(
controlled_web_search_enabled(),
cocos_editor_available,
unity_editor_available,
)
direct_tools_mcp_specs_for(controlled_web_search_enabled(), cocos_editor_available)
}
/// 工具 kind(wire 值)对应的提示词上限。
@@ -147,16 +128,7 @@ fn resource_tool_prompt_schema_max_chars() -> usize {
.unwrap_or(0)
}
#[cfg(test)]
fn direct_tools_mcp_specs_for(controlled_web_search: bool, cocos_editor_available: bool) -> Value {
direct_tools_mcp_specs_for_plugins(controlled_web_search, cocos_editor_available, false)
}
fn direct_tools_mcp_specs_for_plugins(
controlled_web_search: bool,
_cocos_editor_available: bool,
_unity_editor_available: bool,
) -> Value {
fn direct_tools_mcp_specs_for(controlled_web_search: bool, _cocos_editor_available: bool) -> Value {
let tools = vec![
json!({
"name": "client.session.info",
@@ -596,14 +568,6 @@ fn direct_tools_mcp_specs_for_plugins(
.cloned(),
);
}
#[cfg(all(windows, target_arch = "x86_64", feature = "unity-editor-execute"))]
if _unity_editor_available {
tools.push(json!({
"name": "agc_unity_execute",
"description": "在当前项目已打开的 Windows x64 Unity Mono Editor 执行 C#,可使用 return 返回值。仅提交 code;宿主绑定项目及进程。needs-reconciliation 或超时后禁止自动重发。",
"inputSchema": {"type":"object", "properties":{"code":{"type":"string", "minLength":1, "maxLength":131072}}, "required":["code"], "additionalProperties":false}
}));
}
if controlled_web_search {
tools.push(json!({
"name": "agc_web_search",
@@ -691,24 +655,6 @@ async fn call_agc_cocos_execute(arguments: &Value) -> Value {
call_client_tool_bridge("agc_cocos_execute", arguments).await
}
#[cfg(all(windows, target_arch = "x86_64", feature = "unity-editor-execute"))]
async fn call_agc_unity_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 必须是 C# 代码".to_string())?;
if code.trim().is_empty() || code.len() > 131072 || code.contains('\0') {
return Err("code 不能为空、包含 NUL 或超过 128 KiB".to_string());
}
Ok(())
});
if let Err(error) = validated {
return mcp_tool_result(error, Vec::new(), true);
}
call_client_tool_bridge("agc_unity_execute", arguments).await
}
fn mcp_success(id: Value, result: Value) -> Value {
json!({ "jsonrpc": "2.0", "id": id, "result": result })
}
@@ -1811,8 +1757,6 @@ async fn handle_direct_tools_mcp_request(root: &Path, request: Value) -> Option<
"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,
#[cfg(all(windows, target_arch = "x86_64", feature = "unity-editor-execute"))]
"agc_unity_execute" => call_agc_unity_execute(&arguments).await,
#[cfg(all(windows, feature = "cocos-editor-execute"))]
operation if cocos_editor_bridge::is_cocos_operation(operation) => {
call_client_tool_bridge(operation, &arguments).await
@@ -1620,7 +1620,7 @@ 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" | "unity.editor.execute" => format!(
"cocos.editor.execute" => format!(
"codeChars={} · codeSha256={:x}",
chars(&["code"]),
Sha256::digest(
@@ -383,16 +383,6 @@ pub(crate) async fn execute_game_creator_agent_runtime_tool_action_with_pending_
true,
|| observe_agent_runtime_cocos_editor_execute(root, action, pending_action),
),
"unity.editor.execute" => observe_agent_runtime_project_snapshot_with_lock(
root,
agent_id,
run_id,
action,
&action_fingerprint,
pending_action,
true,
|| observe_agent_runtime_unity_editor_execute(root, action, pending_action),
),
"preview.validate" => {
observe_agent_runtime_preview_validate(
root,
@@ -97,7 +97,6 @@ pub(in crate::agent) fn game_creator_agent_runtime_tool_command_id(
"command.run_limited" => Some("command.run_limited"),
#[cfg(feature = "cocos-editor-execute")]
"cocos.editor.execute" => Some("cocos.editor.execute"),
"unity.editor.execute" => Some("unity.editor.execute"),
"preview.start" => Some("preview.start"),
"preview.validate" => Some("preview.validate"),
"image.inspect" => Some("image.inspect"),
@@ -247,10 +247,7 @@ fn build_game_creator_agent_background_tool_plan_request_at(
let relaxed_prompt = format!(
"你正在执行一个自主游戏构建任务。请按自己的判断规划并直接调用当前广告的原生工具完成目标;任务可以与其它 Agent 并行,依赖只作为参考,不要等待或索要平台资产/验收回执。已有观察只代表已发生的事实,完成后直接调用 respond_to_user。\n\n运行上下文:\n{context}\n\n任务:\n{effective_task}\n\n已有观察:\n{observations_json}"
);
let mut function_tools =
crate::agent_native_tools::build_agent_runtime_native_function_tools_for_project(
root, agent_id,
)?;
let mut function_tools = build_agent_runtime_native_function_tools_for_agent(agent_id)?;
remove_relaxed_autonomous_platform_validation_tools(&mut function_tools)?;
// Platform-backed generation remains an optional capability. A
// relaxed run may proceed with all ordinary project tools when no
@@ -482,11 +479,9 @@ fn build_game_creator_agent_background_tool_plan_request_at(
.with_api_kind(api_kind)
.with_max_output_tokens(AGENT_RUNTIME_TOOL_PLAN_MAX_OUTPUT_TOKENS)
.with_response_text_verbosity(platform_llm::LlmResponseTextVerbosity::Low)
.with_function_tools(
crate::agent_native_tools::build_agent_runtime_native_function_tools_for_project(
root, agent_id,
)?,
)
.with_function_tools(build_agent_runtime_native_function_tools_for_agent(
agent_id,
)?)
.with_tool_choice(platform_llm::LlmToolChoice::Required);
if runtime_owner_artifact_validation_available {
remove_autonomous_owner_manual_verification_tools(&mut request.function_tools)?;
@@ -969,7 +969,7 @@ pub(in crate::agent) async fn request_game_creator_agent_background_tool_plan_at
|| force_autonomous_pre_mutation
{
request.function_tools =
crate::agent_native_tools::build_agent_runtime_native_function_tools_for_project(root, agent_id)?;
build_agent_runtime_native_function_tools_for_agent(agent_id)?;
if runtime_owner_artifact_validation_available {
remove_autonomous_owner_manual_verification_tools(
&mut request.function_tools,
@@ -70,9 +70,6 @@ pub(crate) fn agent_runtime_executable_tools() -> Vec<&'static str> {
if crate::builtin_plugins::cocos_editor_agent_tool_available() {
tools.push(crate::builtin_plugins::AGC_COCOS_EDITOR_TOOL_NAME);
}
if crate::builtin_plugins::unity_editor_agent_tool_available() {
tools.push(crate::builtin_plugins::AGC_UNITY_EDITOR_TOOL_NAME);
}
tools
}
@@ -157,11 +154,6 @@ pub(in crate::agent) fn agent_runtime_tool_policy_snapshot_at(
let mut confirm_tools = Vec::new();
let mut denied_tools = Vec::new();
for tool in agent_runtime_executable_tools() {
if tool == crate::builtin_plugins::AGC_UNITY_EDITOR_TOOL_NAME
&& !crate::builtin_plugins::unity_editor_agent_tool_available_for_project(root)
{
continue;
}
if isolated && ISOLATED_AGENT_UNSCOPED_DENIED_TOOLS.contains(&tool) {
denied_tools.push(tool.to_string());
continue;
@@ -204,10 +196,6 @@ pub(in crate::agent) fn agent_runtime_tool_policy_snapshot_at(
run_profile_binding_fingerprint: String::new(),
allowed_tools: agent_runtime_executable_tools()
.into_iter()
.filter(|tool| {
*tool != crate::builtin_plugins::AGC_UNITY_EDITOR_TOOL_NAME
|| crate::builtin_plugins::unity_editor_agent_tool_available_for_project(root)
})
.map(str::to_string)
.collect(),
auto_tools,
@@ -11,7 +11,6 @@ mod goal_contract;
mod helpers;
mod isolated_joins;
mod media;
mod unity_editor;
pub(in crate::agent) use media::design_foundation_ui_page_output_path_is_valid;
mod policy;
mod preview;
@@ -39,7 +38,6 @@ pub(in crate::agent) use project_ops::*;
pub(in crate::agent) use run_status::*;
pub(in crate::agent) use task_ops::*;
pub(in crate::agent) use ui_workflow::*;
pub(in crate::agent) use unity_editor::*;
#[cfg(test)]
pub(crate) use delegation::observe_agent_runtime_agent_delegate_at_locked;
@@ -1,79 +0,0 @@
use super::*;
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
struct UnityEditorExecuteInput {
code: String,
}
pub(in crate::agent) fn observe_agent_runtime_unity_editor_execute(
root: &Path,
action: &AgentRuntimeToolAction,
pending_action: Option<&AgentRuntimePendingToolAction>,
) -> AgentRuntimeToolObservation {
let execution = (|| {
let input: UnityEditorExecuteInput = serde_json::from_value(action.input.clone())
.map_err(|error| format!("unity.editor.execute 输入无效:{error}"))?;
if pending_action.is_none() {
return Err("unity.editor.execute 必须绑定 durable pending action".to_string());
}
if !crate::builtin_plugins::unity_editor_agent_tool_available_for_project(root) {
return Err("当前项目不是 Unity 项目或 Unity 插件不可用".to_string());
}
crate::editor_adapters::execute_unity_editor_code(root, &input.code)
})();
match execution {
Ok(response) => {
let status = match response["status"].as_str() {
Some("completed") if response["ok"] == true => "ok",
Some("needs-reconciliation") => {
AGENT_RUNTIME_TOOL_OBSERVATION_STATUS_NEEDS_RECONCILIATION
}
_ => "failed",
};
AgentRuntimeToolObservation {
tool: "unity.editor.execute".to_string(),
status: status.to_string(),
summary: match status {
"ok" => "Unity 编辑器已返回执行成功回执",
"needs-reconciliation" => "Unity 执行结果待人工核对,禁止自动重发",
_ => "Unity 编辑器执行失败",
}
.to_string(),
detail: Some(redact_agent_runtime_project_paths(
root,
&response.to_string(),
32_000,
)),
}
}
Err(error) => AgentRuntimeToolObservation {
tool: "unity.editor.execute".to_string(),
status: "failed".to_string(),
summary: redact_agent_runtime_project_paths(root, &error, 480),
detail: None,
},
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn unity_execute_requires_pending_action_and_rejects_project_override() {
for input in [
serde_json::json!({"code":"return 2;"}),
serde_json::json!({"code":"return 2;", "projectPath":"C:/other"}),
] {
let action = AgentRuntimeToolAction {
tool: "unity.editor.execute".to_string(),
reason: None,
input,
};
let observation =
observe_agent_runtime_unity_editor_execute(Path::new("C:/unity"), &action, None);
assert_eq!(observation.status, "failed");
}
}
}
@@ -253,13 +253,18 @@ fn build_agent_runtime_native_capability_registry(
fn agent_runtime_native_capability_registry() -> Result<&'static CapabilityRegistry<String>, String>
{
// 两个独立开关产生四份目录,使用同一快照选缓存并构建。
static REGISTRIES: [OnceLock<Result<CapabilityRegistry<String>, String>>; 4] =
[const { OnceLock::new() }; 4];
// 内置插件开关会改变工具目录,因此按“可用 / 不可用”各缓存一份:切换后立即
// 生效,又不需要每次调用都重建 registry。
static ENABLED_REGISTRY: OnceLock<Result<CapabilityRegistry<String>, String>> = OnceLock::new();
static DISABLED_REGISTRY: OnceLock<Result<CapabilityRegistry<String>, String>> =
OnceLock::new();
// 缓存选择与构建消费同一份快照,避免开关变化污染另一份永久缓存。
let tools = agent_runtime_native_executable_tools();
let index = usize::from(tools.contains(&crate::builtin_plugins::AGC_COCOS_EDITOR_TOOL_NAME))
| (usize::from(tools.contains(&crate::builtin_plugins::AGC_UNITY_EDITOR_TOOL_NAME)) << 1);
let cache = &REGISTRIES[index];
let cache = if tools.contains(&crate::builtin_plugins::AGC_COCOS_EDITOR_TOOL_NAME) {
&ENABLED_REGISTRY
} else {
&DISABLED_REGISTRY
};
cache
.get_or_init(|| build_agent_runtime_native_capability_registry(tools))
.as_ref()
@@ -306,18 +311,6 @@ pub(crate) fn build_agent_runtime_native_function_tools_for_agent(
Ok(functions)
}
pub(crate) fn build_agent_runtime_native_function_tools_for_project(
root: &std::path::Path,
agent_id: &str,
) -> Result<Vec<LlmFunctionTool>, String> {
let mut tools = build_agent_runtime_native_function_tools_for_agent(agent_id)?;
if !crate::builtin_plugins::unity_editor_agent_tool_available_for_project(root) {
let name = native_runtime_function_name_for_tool("unity.editor.execute");
tools.retain(|tool| tool.name != name);
}
Ok(tools)
}
pub(crate) fn agent_runtime_native_tool_allowed_for_agent(tool: &str) -> bool {
agent_runtime_native_capability_registry()
.ok()
@@ -1051,7 +1044,6 @@ fn runtime_tool_description(tool: &str) -> &'static str {
"cocos.editor.execute" => {
"在当前项目对应的已打开 Cocos Creator 编辑器中执行一段有界代码;Runtime 自动绑定唯一匹配的 Creator 主进程,代码与结果都通过注入 payload 的本机 bridge 返回。"
}
"unity.editor.execute" => "在当前 Unity 项目已打开的 Windows x64 Mono Editor 中执行 C#。仅提交 code,宿主绑定项目身份;结果待核对时禁止自动重发。",
"blackboard.write" => "向项目级共享黑板追加稳定结论。",
"agent.message" => "向一个目标 Agent 写入定向上下文消息。",
"agent.delegate" => {
@@ -1249,7 +1241,7 @@ fn runtime_tool_input_schema(tool: &str) -> Value {
}
}),
"command.exec" | "command.start" => command_start_input_schema(),
"cocos.editor.execute" | "unity.editor.execute" => json!({
"cocos.editor.execute" => json!({
"type": "object",
"required": ["code"],
"additionalProperties": false,
@@ -17,8 +17,6 @@ pub(crate) const AGC_COCOS_EDITOR_PLUGIN_ID: &str = "agc-cocos-editor";
/// 该插件在 Agent 侧对应的 Runtime 工具名。
pub(crate) const AGC_COCOS_EDITOR_TOOL_NAME: &str = "cocos.editor.execute";
pub(crate) const AGC_UNITY_EDITOR_PLUGIN_ID: &str = "agc-unity-editor";
pub(crate) const AGC_UNITY_EDITOR_TOOL_NAME: &str = "unity.editor.execute";
const STATE_FILE_NAME: &str = "builtin-plugins.json";
const STATE_SCHEMA_VERSION: &str = "agc.builtin-plugins.v1";
@@ -26,34 +24,31 @@ const STATE_SCHEMA_VERSION: &str = "agc.builtin-plugins.v1";
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum BuiltinPlugin {
CocosEditor,
UnityEditor,
}
impl BuiltinPlugin {
pub(crate) fn id(self) -> &'static str {
match self {
Self::CocosEditor => AGC_COCOS_EDITOR_PLUGIN_ID,
Self::UnityEditor => AGC_UNITY_EDITOR_PLUGIN_ID,
}
}
/// 未持久化任何开关时的默认状态。
fn default_enabled(self) -> bool {
match self {
Self::CocosEditor | Self::UnityEditor => true,
Self::CocosEditor => true,
}
}
/// 该插件是否向 Agent 暴露 Runtime 工具。
fn exposes_agent_tools(self) -> bool {
match self {
Self::CocosEditor | Self::UnityEditor => true,
Self::CocosEditor => true,
}
}
}
pub(crate) const BUILTIN_PLUGINS: &[BuiltinPlugin] =
&[BuiltinPlugin::CocosEditor, BuiltinPlugin::UnityEditor];
pub(crate) const BUILTIN_PLUGINS: &[BuiltinPlugin] = &[BuiltinPlugin::CocosEditor];
pub(crate) fn builtin_plugin(id: &str) -> Option<BuiltinPlugin> {
BUILTIN_PLUGINS
@@ -242,16 +237,7 @@ fn persist(guard: &BuiltinPluginState) -> Result<(), String> {
/// Agent 工具面是否可用:编译期 feature 打开且用户没有禁用该内置插件。
pub(crate) fn agent_tool_available(plugin: BuiltinPlugin) -> bool {
plugin.exposes_agent_tools()
&& match plugin {
BuiltinPlugin::CocosEditor => cfg!(all(windows, feature = "cocos-editor-execute")),
BuiltinPlugin::UnityEditor => {
cfg!(all(
windows,
target_arch = "x86_64",
feature = "unity-editor-execute"
)) && unity_editor_bridge::is_supported_platform()
}
}
&& cfg!(all(windows, feature = "cocos-editor-execute"))
&& is_enabled(plugin.id())
}
@@ -271,7 +257,6 @@ pub(crate) fn cocos_editor_agent_tool_available_for_project(root: &Path) -> bool
}
pub(crate) fn available_agent_tools() -> Vec<&'static str> {
let mut available = Vec::new();
if cocos_editor_agent_tool_available() {
let mut tools = vec![AGC_COCOS_EDITOR_TOOL_NAME];
tools.extend(
@@ -279,39 +264,19 @@ pub(crate) fn available_agent_tools() -> Vec<&'static str> {
.iter()
.filter_map(|tool| tool["name"].as_str()),
);
available.extend(tools);
tools
} else {
Vec::new()
}
if unity_editor_agent_tool_available() {
available.push(AGC_UNITY_EDITOR_TOOL_NAME);
}
available
}
/// Project-scoped variant used by the isolated DirectProject MCP bridge.
/// Without a project root the safe result is an empty Cocos tool set.
pub(crate) fn available_agent_tools_for_project(root: &Path) -> Vec<&'static str> {
if !cocos_editor_agent_tool_available_for_project(root) {
return Vec::new();
}
available_agent_tools()
.into_iter()
.filter(|tool| {
if *tool == AGC_UNITY_EDITOR_TOOL_NAME {
unity_editor_agent_tool_available_for_project(root)
} else {
cocos_editor_agent_tool_available_for_project(root)
}
})
.collect()
}
pub(crate) fn unity_editor_agent_tool_available() -> bool {
agent_tool_available(BuiltinPlugin::UnityEditor)
}
pub(crate) fn unity_editor_agent_tool_available_for_project(root: &Path) -> bool {
unity_editor_agent_tool_available()
&& crate::project::discover_local_unity_project_root(root)
.ok()
.flatten()
.is_some()
}
#[cfg(test)]
@@ -329,47 +294,6 @@ mod tests {
.unwrap_or_else(|error| error.into_inner())
}
#[test]
fn unity_tool_visibility_requires_project_platform_and_independent_toggle() {
let _guard = test_lock();
let config = tempdir().unwrap();
initialize(config.path()).unwrap();
let project = tempdir().unwrap();
for directory in ["Assets", "Packages", "ProjectSettings"] {
fs::create_dir(project.path().join(directory)).unwrap();
}
fs::write(
project.path().join("ProjectSettings/ProjectVersion.txt"),
"m_EditorVersion: 6000.0.1f1",
)
.unwrap();
let supported = cfg!(all(
windows,
target_arch = "x86_64",
feature = "unity-editor-execute"
));
assert_eq!(
available_agent_tools_for_project(project.path()).contains(&AGC_UNITY_EDITOR_TOOL_NAME),
supported
);
assert!(
!available_agent_tools_for_project(config.path()).contains(&AGC_UNITY_EDITOR_TOOL_NAME)
);
set_enabled(AGC_COCOS_EDITOR_PLUGIN_ID, false).unwrap();
assert_eq!(
unity_editor_agent_tool_available_for_project(project.path()),
supported
);
set_enabled(AGC_UNITY_EDITOR_PLUGIN_ID, false).unwrap();
assert!(!unity_editor_agent_tool_available_for_project(
project.path()
));
set_enabled(AGC_COCOS_EDITOR_PLUGIN_ID, true).unwrap();
assert!(!unity_editor_agent_tool_available_for_project(
project.path()
));
}
#[test]
fn builtin_plugins_default_to_enabled_and_reject_unknown_ids() {
let _guard = test_lock();
@@ -676,21 +676,6 @@ pub(crate) fn import_local_cocos_project(
import_local_cocos_project_at(root, project_id.trim(), name.trim())
}
#[tauri::command]
pub(crate) fn import_local_unity_project(
project_path: String,
project_id: String,
name: String,
) -> Result<InitLocalProjectResult, String> {
let root = Path::new(project_path.trim());
enforce_project_permission_policy(root, "project.create")?;
if discover_local_unity_project_root(root)?.is_none() {
return Err("所选目录不是有效的 Unity 项目".to_string());
}
let _lock = acquire_project_write_lock(root, "project.create")?;
import_local_unity_project_at(root, project_id.trim(), name.trim())
}
#[tauri::command]
pub(crate) fn is_local_project_directory_non_empty(project_path: String) -> Result<bool, String> {
let root = Path::new(project_path.trim());
@@ -752,7 +737,6 @@ pub(crate) fn inspect_local_project_directory_sync(
let recent_run_trace = recent_game_creator_run_trace(root);
let godot_project_root = discover_local_godot_project_root(root)?;
let cocos_project_root = discover_local_cocos_project_root(root)?;
let unity_project_root = discover_local_unity_project_root(root)?;
Ok(LocalProjectDirectoryStatus {
project_path: root.to_string_lossy().into_owned(),
exists: root.exists(),
@@ -762,8 +746,6 @@ pub(crate) fn inspect_local_project_directory_sync(
godot_project_root,
is_cocos_project: cocos_project_root.is_some(),
cocos_project_root,
is_unity_project: unity_project_root.is_some(),
unity_project_root,
project_name: game_creator_project_name(root),
modified_at: project_directory_modified_at(root),
manifest_error: game_creator_project_manifest_error(root),

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