移除 Cocos 和 Unity 插件的工程类型限制

统一插件列表、启动、面板和 Agent 工具的可用性判断,保留开关与平台约束

调整前端自动启动并修复 Cocos 项目切换的旧连接与订阅快照竞态

补充插件宿主、工具目录和前端回归测试,同步插件技术规范与共享决策
This commit is contained in:
kdletters
2026-09-20 10:53:16 +08:00
parent 15774a1c02
commit c1d26f11df
22 changed files with 596 additions and 269 deletions
+6 -4
View File
@@ -3,11 +3,13 @@
Cocos Creator 编辑器桥接插件。用户侧看到的是一个普通 AGC 插件:插件生命周期、UI、
RPC、权限和能力注册全部由通用宿主负责,只有“如何连接 Cocos Creator”属于本插件。
它同时是 AGC 的**内置插件**:随客户端分发、不能卸载。只有当前受控目录被识别为 Cocos
Creator 项目时才会暴露插件、面板和工具;切换到其它项目会立即停止插件实例并隐藏对应能力。
它同时是 AGC 的**内置插件**:随客户端分发、不能卸载。插件列表、启动、面板、RPC 和
Agent 工具暴露不受当前工程类型限制;无当前项目也沿用相同的开关、原生适配器、平台和
feature 规则。切换工程类型不会停止插件或隐藏能力,仍会更新受控项目并失效旧连接。
用户在运行时设置里只能切换“是否可用”,禁用后插件不能启动,`agc_cocos_execute` 与全部
`cocos_*` Agent 工具也会从 Agent 工具列表、工具策略快照和上下文里消失;重新启用后仍需
满足当前项目是 Cocos 的门禁。
`cocos_*` Agent 工具也会从 Agent 工具列表、工具策略快照和后续上下文里消失。
实际编辑器操作仍需当前受控项目对应的真实 Creator 目标,并通过项目路径、PID、版本和
握手校验;工具可见不代表任意目录都能作为 Creator 执行目标。
Cocos Creator 项目目录中的 `extensions/`、`package.json` 插件声明或第三方 MCP 包不属于
AGC Cocos 桥接来源。Agent 处理 Cocos 请求时只使用客户端登记的 `agc-cocos-editor`
+6 -1
View File
@@ -45,6 +45,7 @@ export function createCocosEditorPlugin({
}) {
let nextId = 1;
let activeProjectPath = null;
let projectEpoch = 0;
let disposed = false;
let executionUncertain = false;
let executionPending = false;
@@ -190,6 +191,7 @@ export function createCocosEditorPlugin({
const projectPath = event.payload?.projectPath;
activeProjectPath =
typeof projectPath === 'string' && projectPath ? projectPath : null;
projectEpoch += 1;
}
return;
}
@@ -239,10 +241,13 @@ export function createCocosEditorPlugin({
const panel = await request('host.registerPanel', {
...COCOS_EDITOR_PANEL,
});
const epoch = projectEpoch;
const subscription = await request('host.events.subscribe', {
type: PROJECT_CHANGED_EVENT,
});
activeProjectPath = subscription?.projectPath ?? null;
if (epoch === projectEpoch) {
activeProjectPath = subscription?.projectPath ?? null;
}
return {
command,
capability,
@@ -215,6 +215,58 @@ test('project.changed event updates the cached project path', async () => {
assert.equal(harness.plugin.activeProjectPath, null);
});
for (const [snapshotPath, projectPath] of [
[null, 'C:/New'],
['C:/Old', 'C:/New'],
['C:/Old', null],
]) {
test(`迟到的订阅快照 ${snapshotPath} 不能覆盖新项目事件 ${projectPath}`, async (t) => {
const requests = [];
const plugin = createCocosEditorPlugin({
send(message) {
requests.push(message);
if (!message.method) return;
queueMicrotask(async () => {
if (message.method === 'host.events.subscribe') {
await plugin.handleMessage({
jsonrpc: '2.0',
method: 'host.event',
params: {
type: PROJECT_CHANGED_EVENT,
payload: { projectPath },
},
});
}
await plugin.handleMessage({
jsonrpc: '2.0',
id: message.id,
result:
message.method === 'host.events.subscribe'
? { subscriptionId: 'sub-1', projectPath: snapshotPath }
: { ok: true },
});
});
},
});
t.after(() => plugin.dispose());
await plugin.start();
assert.equal(plugin.activeProjectPath, projectPath);
await plugin.handleMessage({
jsonrpc: '2.0',
id: 500,
method: COCOS_EXECUTE_COMMAND_ID,
params: { code: 'return 2;' },
});
const rpc = requests.find((message) => message.method === 'host.rpc');
if (projectPath) {
assert.equal(rpc.params.params.projectPath, projectPath);
} else {
assert.equal(rpc, undefined);
assert.match(requests.at(-1).error.message, /项目路径/);
}
});
}
test('execute rejects concurrent requests and blocks later requests after uncertainty', async () => {
const harness = createHarness();
await startPlugin(harness);