收紧平台与编辑器编译范围并分离构建校验

分离 Codex 构建端元数据校验并保留原有测试
限定 Linux 沙箱与进程清理入口,删除无调用的平台占位实现
按实际 feature 收紧编辑器注册并删除冗余宿主包装
保留 Runner 备用端口算法的 Linux 与测试编译入口
同步警告清单、插件契约及定向验证边界
This commit is contained in:
2026-09-23 17:10:39 +00:00
parent b39cd555ae
commit c62a911745
12 changed files with 143 additions and 241 deletions
@@ -1,5 +1,7 @@
#[path = "build_support/codex_bundle.rs"]
mod codex_bundle;
#[path = "build_support/codex_package_metadata.rs"]
mod codex_package_metadata;
#[path = "build_support/frontend_dist_guard.rs"]
mod frontend_dist_guard;
#[path = "build_support/godot_bundle.rs"]
@@ -64,7 +66,7 @@ fn stage_codex_target(manifest_dir: &std::path::Path, target: &str) {
.parent()
.and_then(|apps_dir| apps_dir.parent())
.expect("AI 游戏创作应用必须位于仓库 apps 目录下");
let package = layout.npm_package;
let package = format!("codex-{}", layout.platform);
let source_candidates = [app_root, repo_root]
.into_iter()
.flat_map(|root| {
@@ -99,7 +101,7 @@ fn stage_codex_target(manifest_dir: &std::path::Path, target: &str) {
&fs::read(source.join("codex-package.json")).expect("读取 Codex 原生包元数据失败"),
)
.expect("Codex 原生包元数据无效");
codex_bundle::validate_package_metadata(&metadata, target, layout)
codex_package_metadata::validate_package_metadata(&metadata, target, layout)
.unwrap_or_else(|error| panic!("{error}"));
let target_dir = manifest_dir.join("resources/codex").join(layout.directory);
let notice = target_dir.join("NOTICE.md");
@@ -7,7 +7,6 @@ pub const SCHEMA: &str = "genarrative-codex-sidecar.v2";
#[derive(Clone, Copy, Debug)]
pub struct Layout {
pub platform: &'static str,
pub npm_package: &'static str,
pub directory: &'static str,
pub executable: &'static str,
pub files: &'static [&'static str],
@@ -33,7 +32,6 @@ pub fn for_target(target: &str) -> Option<Layout> {
match target {
"x86_64-pc-windows-msvc" => Some(Layout {
platform: "win32-x64",
npm_package: "codex-win32-x64",
directory: "win-x64",
executable: "bin/codex.exe",
files: WINDOWS_FILES,
@@ -44,11 +42,6 @@ pub fn for_target(target: &str) -> Option<Layout> {
} else {
"darwin-x64"
},
npm_package: if target.starts_with("aarch64") {
"codex-darwin-arm64"
} else {
"codex-darwin-x64"
},
directory: if target.starts_with("aarch64") {
"mac-native/darwin-arm64"
} else {
@@ -61,24 +54,6 @@ pub fn for_target(target: &str) -> Option<Layout> {
}
}
pub fn validate_package_metadata(
metadata: &serde_json::Value,
target: &str,
layout: Layout,
) -> Result<(), String> {
if metadata["layoutVersion"] == 1
&& metadata["version"] == VERSION
&& metadata["target"] == target
&& metadata["entrypoint"] == layout.executable
&& metadata["resourcesDir"] == "codex-resources"
&& metadata["pathDir"] == "codex-path"
{
Ok(())
} else {
Err(format!("Codex 原生包版本、布局或架构不匹配目标 {target}"))
}
}
#[cfg(test)]
mod tests {
use super::*;
@@ -87,13 +62,11 @@ mod tests {
fn platform_layouts_are_explicit_and_preserve_upstream_components() {
let mac = for_target("aarch64-apple-darwin").unwrap();
assert_eq!(mac.platform, "darwin-arm64");
assert_eq!(mac.npm_package, "codex-darwin-arm64");
assert!(mac.files.contains(&"codex-resources/zsh/bin/zsh"));
assert!(mac.files.contains(&"bin/codex-code-mode-host"));
assert!(!mac.files.iter().any(|file| file.ends_with(".exe")));
let intel = for_target("x86_64-apple-darwin").unwrap();
assert_eq!(intel.platform, "darwin-x64");
assert_eq!(intel.npm_package, "codex-darwin-x64");
assert_eq!(mac.directory, "mac-native/darwin-arm64");
assert_eq!(intel.directory, "mac-native/darwin-x64");
assert_ne!(mac.directory, intel.directory);
@@ -107,34 +80,4 @@ mod tests {
assert!(for_target("aarch64-pc-windows-msvc").is_none());
assert!(for_target("x86_64-unknown-linux-gnu").is_none());
}
#[test]
fn metadata_rejects_version_architecture_and_layout_drift() {
let target = "aarch64-apple-darwin";
let layout = for_target(target).unwrap();
let valid = serde_json::json!({
"layoutVersion": 1,
"version": VERSION,
"target": target,
"entrypoint": "bin/codex",
"resourcesDir": "codex-resources",
"pathDir": "codex-path",
});
assert!(validate_package_metadata(&valid, target, layout).is_ok());
for (key, value) in [
("layoutVersion", serde_json::json!(2)),
("version", serde_json::json!("0.0.0")),
("target", serde_json::json!("x86_64-apple-darwin")),
("entrypoint", serde_json::json!("bin/codex.exe")),
("resourcesDir", serde_json::json!("../private")),
("pathDir", serde_json::json!(null)),
] {
let mut invalid = valid.clone();
invalid[key] = value;
assert!(
validate_package_metadata(&invalid, target, layout).is_err(),
"{key}"
);
}
}
}
@@ -0,0 +1,57 @@
//! 随包阶段的原生包元数据校验,不进入运行时生产模块。
use super::codex_bundle::{Layout, VERSION};
pub fn validate_package_metadata(
metadata: &serde_json::Value,
target: &str,
layout: Layout,
) -> Result<(), String> {
if metadata["layoutVersion"] == 1
&& metadata["version"] == VERSION
&& metadata["target"] == target
&& metadata["entrypoint"] == layout.executable
&& metadata["resourcesDir"] == "codex-resources"
&& metadata["pathDir"] == "codex-path"
{
Ok(())
} else {
Err(format!("Codex 原生包版本、布局或架构不匹配目标 {target}"))
}
}
#[cfg(test)]
mod tests {
use super::super::codex_bundle::for_target;
use super::*;
#[test]
fn metadata_rejects_version_architecture_and_layout_drift() {
let target = "aarch64-apple-darwin";
let layout = for_target(target).unwrap();
let valid = serde_json::json!({
"layoutVersion": 1,
"version": VERSION,
"target": target,
"entrypoint": "bin/codex",
"resourcesDir": "codex-resources",
"pathDir": "codex-path",
});
assert!(validate_package_metadata(&valid, target, layout).is_ok());
for (key, value) in [
("layoutVersion", serde_json::json!(2)),
("version", serde_json::json!("0.0.0")),
("target", serde_json::json!("x86_64-apple-darwin")),
("entrypoint", serde_json::json!("bin/codex.exe")),
("resourcesDir", serde_json::json!("../private")),
("pathDir", serde_json::json!(null)),
] {
let mut invalid = valid.clone();
invalid[key] = value;
assert!(
validate_package_metadata(&invalid, target, layout).is_err(),
"{key}"
);
}
}
}
@@ -8,6 +8,11 @@ use tokio::io::{AsyncRead, AsyncReadExt, AsyncWriteExt};
#[path = "../../build_support/codex_bundle.rs"]
pub(crate) mod codex_bundle;
// 复用构建端校验的既有单测,生产运行时只编译共享布局。
#[cfg(test)]
#[path = "../../build_support/codex_package_metadata.rs"]
mod codex_package_metadata;
const GAME_CREATOR_CODEX_CLI_EXECUTABLE: &str = "codex";
const GAME_CREATOR_CODEX_CLI_PROMPT_MAX_BYTES: usize = 4 * 1024 * 1024;
const GAME_CREATOR_CODEX_CLI_STDOUT_MAX_BYTES: usize = 4 * 1024 * 1024;
@@ -1968,60 +1968,14 @@ fn request_unix_project_command_process_group_termination(
Err(format!("请求终止受控进程组失败:{error}"))
}
async fn request_project_command_process_group_termination(
process_id: u32,
) -> Result<&'static str, String> {
#[cfg(unix)]
{
return request_unix_project_command_process_group_termination(process_id);
}
#[cfg(windows)]
{
let system_root = std::env::var_os("SystemRoot")
.ok_or_else(|| "请求终止受控进程组失败:缺少 SystemRoot".to_string())?;
let taskkill = fs::canonicalize(PathBuf::from(&system_root).join("System32/taskkill.exe"))
.map_err(|error| format!("请求终止受控进程组失败:定位 taskkill.exe 失败:{error}"))?;
if !taskkill.is_absolute() || !taskkill.is_file() {
return Err("请求终止受控进程组失败:taskkill.exe 不是绝对普通文件".to_string());
}
let mut command = tokio::process::Command::new(taskkill);
command
.args(["/PID", &process_id.to_string(), "/T", "/F"])
.env_clear()
.env("SystemRoot", &system_root)
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null());
crate::configure_windows_background_tokio_command(&mut command, false);
let status = command
.status()
.await
.map_err(|error| format!("请求终止受控进程组失败:启动 taskkill.exe 失败:{error}"))?;
if !status.success() {
return Err(format!(
"请求终止受控进程组失败:taskkill.exe 退出码 {}",
status
.code()
.map(|code| code.to_string())
.unwrap_or_else(|| "none".to_string())
));
}
return Ok("已请求终止受控进程组");
}
#[cfg(not(any(unix, windows)))]
{
let _ = process_id;
Err("请求终止受控进程组失败:当前平台不支持受控进程组终止".to_string())
}
}
#[cfg(target_os = "linux")]
async fn terminate_project_command_process_group(
child: &mut tokio::process::Child,
) -> Result<String, String> {
let process_id = child
.id()
.ok_or_else(|| "请求终止受控进程组失败:子进程缺少 pid".to_string())?;
let group_result = request_project_command_process_group_termination(process_id).await;
let group_result = request_unix_project_command_process_group_termination(process_id);
let child_kill_error = child.start_kill().err();
let wait_result = child.wait().await;
if let Err(error) = &group_result {
@@ -1,7 +1,10 @@
#[cfg(target_os = "linux")]
use std::ffi::OsStr;
#[cfg(target_os = "linux")]
use std::ffi::OsString;
#[cfg(target_os = "linux")]
use std::fmt;
#[cfg(target_os = "linux")]
use std::path::{Path, PathBuf};
#[cfg(target_os = "linux")]
@@ -48,6 +51,7 @@ impl CommandSandboxMetadata {
}
}
#[cfg(target_os = "linux")]
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct CommandSandboxLaunch {
pub(crate) executable: PathBuf,
@@ -66,12 +70,14 @@ pub(crate) struct StagedCommandSandboxLaunch {
pub(crate) gate: LaunchGate,
}
#[cfg(target_os = "linux")]
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct CommandSandboxError {
message: String,
metadata: CommandSandboxMetadata,
}
#[cfg(target_os = "linux")]
impl CommandSandboxError {
fn new(message: impl Into<String>, metadata: CommandSandboxMetadata) -> Self {
Self {
@@ -79,19 +85,16 @@ impl CommandSandboxError {
metadata,
}
}
#[cfg(not(target_os = "linux"))]
pub(crate) fn metadata(&self) -> &CommandSandboxMetadata {
&self.metadata
}
}
#[cfg(target_os = "linux")]
impl fmt::Display for CommandSandboxError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(&self.message)
}
}
#[cfg(target_os = "linux")]
impl std::error::Error for CommandSandboxError {}
pub(crate) fn command_sandbox_platform_metadata() -> CommandSandboxMetadata {
@@ -107,6 +110,7 @@ pub(crate) fn command_sandbox_platform_metadata() -> CommandSandboxMetadata {
/// Builds a fail-closed launcher for a direct executable plus structured argv.
/// It never falls back to launching the original command on the host.
#[cfg(target_os = "linux")]
pub(crate) fn prepare_command_sandbox_launch(
root: &Path,
executable: &Path,
@@ -114,19 +118,7 @@ pub(crate) fn prepare_command_sandbox_launch(
cwd: &Path,
environment: &[(OsString, OsString)],
) -> Result<CommandSandboxLaunch, CommandSandboxError> {
#[cfg(target_os = "linux")]
{
prepare_linux_command_sandbox_launch(root, executable, arguments, cwd, environment)
}
#[cfg(not(target_os = "linux"))]
{
let _ = (root, executable, arguments, cwd, environment);
let metadata = CommandSandboxMetadata::unsupported_legacy();
Err(CommandSandboxError::new(
"当前平台没有可用的 OS-enforced workspace sandbox;拒绝宿主直通执行",
metadata,
))
}
prepare_linux_command_sandbox_launch(root, executable, arguments, cwd, environment)
}
#[cfg(target_os = "linux")]
@@ -1446,23 +1438,3 @@ print("SANDBOX_OK")
#[cfg(target_os = "linux")]
use linux::prepare_linux_command_sandbox_launch;
#[cfg(all(test, not(target_os = "linux")))]
mod unsupported_tests {
use super::*;
#[test]
fn unsupported_platform_returns_legacy_metadata_without_launcher() {
let error = prepare_command_sandbox_launch(
Path::new("."),
Path::new("tool"),
&[],
Path::new("."),
&[],
)
.expect_err("unsupported platform must fail closed");
assert_eq!(error.metadata().backend, "legacy-host-restricted");
assert_eq!(error.metadata().mode, "fixed-command");
assert_eq!(error.metadata().network, "proxy-only");
}
}
@@ -5,4 +5,6 @@
//! plugin package they belong to under the `plugins/` workspace. This module
//! only re-exports the shared contract so the host stays editor-agnostic.
pub(crate) use editor_adapter_api::{EditorAdapter, EditorConnectionInfo};
pub(crate) use editor_adapter_api::EditorAdapter;
#[cfg(test)]
pub(crate) use editor_adapter_api::EditorConnectionInfo;
@@ -11,16 +11,45 @@ use std::path::PathBuf;
use tauri::Manager;
use crate::plugin_host::PluginHost;
#[cfg(all(
windows,
target_arch = "x86_64",
any(feature = "unity-editor-execute", feature = "godot-editor-execute")
))]
use editor_adapter_api::{EditorAdapter, EditorConnectionInfo};
use serde_json::{json, Value};
#[cfg(any(
test,
all(
windows,
target_arch = "x86_64",
any(feature = "unity-editor-execute", feature = "godot-editor-execute")
)
))]
use serde_json::json;
#[cfg(all(
windows,
target_arch = "x86_64",
any(feature = "unity-editor-execute", feature = "godot-editor-execute")
))]
use serde_json::Value;
use std::path::Path;
mod execution;
pub(crate) use execution::*;
/// GUI 只转发已有 Runner RPC;每个引擎的连接和回执均归同一个 owner。
#[cfg(all(
windows,
target_arch = "x86_64",
any(feature = "unity-editor-execute", feature = "godot-editor-execute")
))]
struct RunnerManagedEditorAdapter(ManagedEditor);
#[cfg(all(
windows,
target_arch = "x86_64",
any(feature = "unity-editor-execute", feature = "godot-editor-execute")
))]
impl EditorAdapter for RunnerManagedEditorAdapter {
fn id(&self) -> &'static str {
self.0.adapter()
@@ -21,7 +21,7 @@ use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use tauri::{Manager, State};
use crate::editor_adapter::{EditorAdapter, EditorConnectionInfo};
use crate::editor_adapter::EditorAdapter;
type EditorRegistry = Arc<Mutex<BTreeMap<String, Box<dyn EditorAdapter>>>>;
type ProjectContext = Arc<Mutex<Option<PathBuf>>>;
@@ -1873,6 +1873,15 @@ impl PluginHost {
Ok(())
}
#[cfg(any(
test,
all(windows, feature = "cocos-editor-execute"),
all(
windows,
target_arch = "x86_64",
any(feature = "unity-editor-execute", feature = "godot-editor-execute")
)
))]
pub(crate) fn register_editor_adapter(
&self,
adapter: Box<dyn EditorAdapter>,
@@ -1891,98 +1900,6 @@ impl PluginHost {
editors.insert(adapter.id().to_string(), adapter);
Ok(())
}
pub(crate) fn detect_editor(
&self,
adapter: String,
project_path: String,
) -> Result<EditorConnectionInfo, String> {
let state = self
.state
.lock()
.map_err(|_| "插件宿主锁已损坏".to_string())?;
let editors = state
.editors
.try_lock()
.map_err(|_| "编辑器注册表锁已损坏".to_string())?;
editors
.get(&adapter)
.ok_or_else(|| format!("未知编辑器适配器:{adapter}"))?
.detect(Path::new(project_path.trim()))
}
pub(crate) fn connect_editor(
&self,
adapter: String,
pid: u32,
project_path: String,
version: String,
) -> Result<EditorConnectionInfo, String> {
let state = self
.state
.lock()
.map_err(|_| "插件宿主锁已损坏".to_string())?;
let mut editors = state
.editors
.try_lock()
.map_err(|_| "编辑器注册表锁已损坏".to_string())?;
editors
.get_mut(&adapter)
.ok_or_else(|| format!("未知编辑器适配器:{adapter}"))?
.connect(pid, Path::new(project_path.trim()), version.trim())
}
pub(crate) fn disconnect_editor(&self, adapter: String) -> Result<(), String> {
let state = self
.state
.lock()
.map_err(|_| "插件宿主锁已损坏".to_string())?;
let mut editors = state
.editors
.try_lock()
.map_err(|_| "编辑器注册表锁已损坏".to_string())?;
if adapter == "godot-editor" {
if !editors.contains_key(&adapter) {
return Err(format!("未知编辑器适配器:{adapter}"));
}
let project = state
.active_project
.lock()
.map_err(|_| "项目上下文锁已损坏".to_string())?
.clone();
drop(editors);
drop(state);
return crate::editor_adapters::disconnect_managed_editor_project(
crate::editor_adapters::ManagedEditor::Godot,
project.as_deref(),
);
}
editors
.get_mut(&adapter)
.ok_or_else(|| format!("未知编辑器适配器:{adapter}"))?
.disconnect();
Ok(())
}
pub(crate) fn translate_editor_rpc(
&self,
adapter: String,
method: String,
params: Value,
) -> Result<Value, String> {
let state = self
.state
.lock()
.map_err(|_| "插件宿主锁已损坏".to_string())?;
let editors = state
.editors
.try_lock()
.map_err(|_| "编辑器注册表锁已损坏".to_string())?;
editors
.get(&adapter)
.ok_or_else(|| format!("未知编辑器适配器:{adapter}"))?
.translate_rpc(&method, params)
}
}
#[tauri::command]
@@ -2078,6 +1995,7 @@ pub(crate) async fn set_agc_plugin_project_path(
#[cfg(test)]
mod tests {
use super::*;
use crate::editor_adapter::EditorConnectionInfo;
#[test]
fn writer_receipt_loss_after_json_write_is_persistently_uncertain() {
@@ -22,6 +22,7 @@ pub(super) fn refresh_external_agent_runner_heartbeat(
write_external_agent_runner_endpoint_atomic(&state.endpoint_path, &endpoint)
}
#[cfg(any(target_os = "linux", test))]
pub(super) fn bind_external_agent_runner_listener_with<T>(
mut fallback_ports: impl FnMut() -> Vec<u16>,
mut bind: impl FnMut(u16) -> io::Result<T>,
@@ -7,7 +7,7 @@
## 1. 基线与范围
首次诊断的基线提交为 `016356e509f11a1a638ce45ed51b9e40ef3e36a2`,诊断时工作树干净。环境为 Windows x64、Rust `1.98.1`、Node `v24.15.0`、npm `12.0.2`。第 2~6 节和附录保留首次诊断快照;后续处理状态及契约核查见第 7~19 节,不能将附录的全部条目视为仍未解决。
首次诊断的基线提交为 `016356e509f11a1a638ce45ed51b9e40ef3e36a2`,诊断时工作树干净。环境为 Windows x64、Rust `1.98.1`、Node `v24.15.0`、npm `12.0.2`。第 2~6 节和附录保留首次诊断快照;后续处理状态及契约核查见第 7~20 节,不能将附录的全部条目视为仍未解决。
AGC Rust 使用默认 features、dev profile237 是本轮编译器诊断数,不代表 237 个独立根因,也不是所有平台、features 和 test targets 的总数。另有 5 条不带常规源码 span 的 ts-rs 宏提示,不计入 237。
@@ -635,6 +635,23 @@ W076 只将 Pending(String) 收窄为 Pending;三处生产返回前继续记
最近一次实测仍为第 14 节的 50 条;第 15~18 节已处理 26 条,本批再处理 9 条,预期剩余 **15 条**11 条平台 / feature / 构建脚本项、4 条暂留契约项),尚未重新编译确认。另有 5 条 ts-rs 提示,不包含在本计数中。
## 20. 平台、feature 与构建脚本编译范围清理(2026-09-23)
本批处理此前归为“其他配置使用”的 11 条诊断。复核后确认,该分类中也混有无调用的包装和平台占位实现,不能整组永久保留。
| 编号 | 实施结论 |
| --- | --- |
| W011/W012 | 共享 `Layout` 删除仅构建端使用的 `npm_package`,构建脚本根据原有 `platform` 生成相同包名。原包元数据校验移入 `build_support/codex_package_metadata.rs`,由 `build.rs` 正式调用;原校验用例随同一实现保留,运行时仅在测试编译中引入该模块。共享版本、平台布局与运行时完整性校验不变。 |
| W097/W098 | Linux 启动失败清理函数限定 Linux,直接调用现役 Unix 进程组终止 helper。删除无其它调用方的跨平台异步转发及其 Windows `taskkill` / 不支持平台分支;共享 Unix helper 仍供其它 Unix 路径使用,Windows 现役 Job Object 终止与回收保持不变。 |
| W101W104 | 沙箱 Launch、Error、相关 impl、准备函数及导入限定 Linux。删除非 Linux 无生产调用的拒绝执行占位、专属错误 metadata getter 及其专属测试;跨平台审计使用的 `CommandSandboxMetadata``command_sandbox_platform_metadata` 保留。Linux bubblewrap 失败关闭逻辑和已有 Linux 测试不变。 |
| W117 | `RunnerManagedEditorAdapter` 的结构、实现与专用导入限定 Windows x64 且启用 Unity/Godot execute feature,与实际构造条件一致。 |
| W129 | 注册方法保留测试及真实 Cocos/Unity/Godot feature 调用;删除四个无人调用的宿主 detect/connect/disconnect/translate 包装。保留 EditorAdapter trait、实现、`host.rpc` 路由和禁用 / 切项目清理。仅测试所需的 `EditorConnectionInfo` 导入及 facade 重导出限定测试编译。 |
| W153 | 备用端口绑定算法限定 `cfg(any(target_os = "linux", test))`,保留 Linux 生产调用和三条跨平台算法测试;其它平台仍使用原有端口 0 绑定。 |
验证:用临时独立 Cargo harness 直接引用当前工作树的两个构建辅助模块,离线运行原有平台布局与包元数据校验 **2 个测试,全部通过**;未编译 AGC 应用或执行其 build.rs。修改 Rust 文件的定向 rustfmt、编码检查、文档索引和 `git diff --check` 通过,另静态核对调用方与 cfg 条件。没有新增测试,也没有将退役业务实现仅为测试而保留;构建端原校验及其用例继续一起维护。
按用户要求未跑全量编译;AGC 默认应用构建、正式 editor features、Linux/macOS 构建及应用单测均未执行。最近一次应用 warning 实测仍为第 14 节的 50 条;第 15~20 节合计处理 46 条,预期剩余 **4 条暂留契约项:W004、W005、W006、W113**,不能将此预期当作重新编译结果。5 条 ts-rs 提示和前端 chunk 体积提示另行处理。
## 附录:237 条编译器诊断位置
以下是诊断的人工可读整理,不保存原始构建日志、本机路径或 target 缓存路径。位置相对 `apps/ai-game-creator-shell/src-tauri/`;行号只对应本节基线,修改后以符号搜索及重新编译为准。每条保留一个主要 span,编号用于本清单内跟踪,不表示独立业务缺陷。生成产物项须回到 `build_support/runtime_prompt_bundle.rs` 处理。
@@ -122,6 +122,8 @@ host.rpc(method, params)
`EditorAdapter` 契约位于通用 crate `server-rs/crates/editor-adapter-api`,只定义 `detect``connect``disconnect``translate_rpc` 和原生 `rpc`。宿主只保存适配器 registry,并把插件声明的适配器名称路由到对应实现;具体编辑器如何查找进程、校验 PID/项目/版本、建立连接和翻译编辑器消息,由插件包自带模块实现。
宿主注册方法只在实际链接编辑器的 feature 或测试编译中存在:Cocos 对应 Windows 的 `cocos-editor-execute`Runner 托管的 Unity/Godot 对应 Windows x64 的各自 execute feature。未启用这些 feature 的默认构建不编译专用适配器及其导入。插件操作统一通过 `host.rpc` 调用适配器的 `rpc`;没有调用方的宿主 detect/connect/disconnect/translate 包装不作为兼容接口保留,trait 方法、项目切换和禁用清理继续按现役合同执行。
宿主源码不包含编辑器专属进程名、注入逻辑或 Tauri 命令。第一个适配器 `cocos-editor``plugins/agc-cocos-editor` 提供:native 模块实现 `EditorAdapter`,由 `editor_adapters.rs` 在启动时按编译期链接注册。新增适配器不会改变 Plugin 生命周期、SDK 或权限协议。
当前 native 适配器仍由宿主在编译期链接(Cargo path 依赖);动态加载插件 native 模块不在本次范围,插件包格式与宿主协议不受此限制。