mu-agent-bridge 1.0.71 → 1.0.78
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -10
- package/dist/src-ts/agent.js +35 -0
- package/dist/src-ts/channel-sessions.js +3 -2
- package/dist/src-ts/cli.js +67 -37
- package/dist/src-ts/control-server.js +12 -30
- package/dist/src-ts/core-updater.js +141 -33
- package/dist/src-ts/daemon-process.js +2 -1
- package/dist/src-ts/dsh-agent.js +31 -13
- package/dist/src-ts/http-agent.js +36 -0
- package/dist/src-ts/plugin-catalog.js +11 -2
- package/dist/src-ts/plugin-manager.js +351 -50
- package/dist/src-ts/plugins/abweb/index.js +1 -1
- package/dist/src-ts/plugins/abweb/public/app.js +73 -5
- package/dist/src-ts/plugins/abweb/public/index.html +15 -0
- package/dist/src-ts/plugins/abweb/public/style.css +4 -0
- package/dist/src-ts/plugins/aun/attachments.js +3 -21
- package/dist/src-ts/plugins/aun/index.js +155 -21
- package/dist/src-ts/plugins/aun/messages.js +4 -1
- package/dist/src-ts/plugins/aun/public/app.js +2 -1
- package/dist/src-ts/plugins/aun/public/index.html +1 -1
- package/dist/src-ts/plugins/cdp-draft.js +5 -2
- package/dist/src-ts/plugins/chatgpt/cdp-client.js +26 -12
- package/dist/src-ts/plugins/chatgpt/index.js +80 -19
- package/dist/src-ts/plugins/claude-cli/index.js +19 -2
- package/dist/src-ts/plugins/codex-cli/index.js +19 -2
- package/dist/src-ts/plugins/desktop-launch.js +44 -3
- package/dist/src-ts/plugins/doubao/cdp-client.js +234 -45
- package/dist/src-ts/plugins/doubao/index.js +152 -8
- package/dist/src-ts/plugins/doubao/native-task.js +134 -0
- package/dist/src-ts/plugins/dsh/cdp-client.js +334 -0
- package/dist/src-ts/plugins/dsh/index.js +172 -21
- package/dist/src-ts/plugins/dsh/session-history.js +31 -3
- package/dist/src-ts/plugins/feishu/index.js +1 -1
- package/dist/src-ts/plugins/local-cli/agent.js +74 -9
- package/dist/src-ts/plugins/personal-assistant/claude-cli-model.js +30 -0
- package/dist/src-ts/plugins/personal-assistant/coordination.js +57 -0
- package/dist/src-ts/plugins/personal-assistant/index.js +263 -0
- package/dist/src-ts/plugins/personal-assistant/model-client.js +181 -0
- package/dist/src-ts/plugins/personal-assistant/owner-context.js +56 -0
- package/dist/src-ts/plugins/personal-assistant/remote-ability.js +176 -0
- package/dist/src-ts/plugins/personal-assistant/session-memory.js +124 -0
- package/dist/src-ts/plugins/personal-assistant/tools.js +167 -0
- package/dist/src-ts/plugins/qwenwork/cdp-client.js +90 -18
- package/dist/src-ts/plugins/qwenwork/index.js +139 -24
- package/dist/src-ts/plugins/workbuddy/cdp-client.js +353 -82
- package/dist/src-ts/plugins/workbuddy/index.js +124 -41
- package/dist/src-ts/plugins/zcode/cdp-client.js +500 -0
- package/dist/src-ts/plugins/zcode/index.js +97 -0
- package/dist/src-ts/session-management.js +132 -14
- package/dist/src-ts/version-manager.js +6 -3
- package/dist/tests-ts/abweb-cli.test.js +10 -2
- package/dist/tests-ts/abweb-model-config.test.js +73 -0
- package/dist/tests-ts/agent.test.js +27 -0
- package/dist/tests-ts/aun-attachments.test.js +19 -17
- package/dist/tests-ts/aun-default-owner-page.test.js +2 -1
- package/dist/tests-ts/aun-messages.test.js +20 -0
- package/dist/tests-ts/aun-plugin.test.js +292 -19
- package/dist/tests-ts/bundled-package.test.js +12 -2
- package/dist/tests-ts/cdp-draft-recovery.test.js +182 -3
- package/dist/tests-ts/channel-sessions.test.js +27 -0
- package/dist/tests-ts/chatgpt-plugin.test.js +118 -0
- package/dist/tests-ts/control-server.test.js +56 -0
- package/dist/tests-ts/core-updater.test.js +215 -46
- package/dist/tests-ts/daemon-process.test.js +126 -1
- package/dist/tests-ts/desktop-launch.test.js +73 -0
- package/dist/tests-ts/doubao-native-task.test.js +237 -0
- package/dist/tests-ts/doubao-plugin.test.js +773 -12
- package/dist/tests-ts/dsh-agent.test.js +157 -8
- package/dist/tests-ts/dsh-cdp.test.js +148 -0
- package/dist/tests-ts/dsh-history.test.js +37 -0
- package/dist/tests-ts/feishu-plugin.test.js +1 -1
- package/dist/tests-ts/http-agent.test.js +18 -0
- package/dist/tests-ts/installer-assets.test.js +1 -1
- package/dist/tests-ts/local-cli.test.js +146 -1
- package/dist/tests-ts/model-provider-cancellation.test.js +152 -0
- package/dist/tests-ts/native-session-management.test.js +8 -7
- package/dist/tests-ts/personal-assistant-coordination.test.js +92 -0
- package/dist/tests-ts/personal-assistant-extensions.test.js +35 -0
- package/dist/tests-ts/personal-assistant-memory.test.js +117 -0
- package/dist/tests-ts/personal-assistant-owner-context.test.js +107 -0
- package/dist/tests-ts/personal-assistant-remote-ability.test.js +123 -0
- package/dist/tests-ts/personal-assistant.test.js +663 -0
- package/dist/tests-ts/platform-integration.test.js +183 -1
- package/dist/tests-ts/plugin-catalog.test.js +12 -2
- package/dist/tests-ts/plugin-manager.test.js +356 -4
- package/dist/tests-ts/plugin-upgrade-deadline.test.js +354 -0
- package/dist/tests-ts/publish-packages.test.js +42 -0
- package/dist/tests-ts/qwenwork-plugin.test.js +135 -1
- package/dist/tests-ts/qwenwork-reconnect.test.js +195 -0
- package/dist/tests-ts/qwenwork-sse.test.js +33 -0
- package/dist/tests-ts/session-access-defaults.test.js +112 -0
- package/dist/tests-ts/session-management.test.js +159 -4
- package/dist/tests-ts/version-manager.test.js +40 -0
- package/dist/tests-ts/workbuddy-cdp-recovery.test.js +311 -0
- package/dist/tests-ts/workbuddy-plugin.test.js +499 -4
- package/dist/tests-ts/zcode-cdp.test.js +894 -0
- package/dist/tests-ts/zcode-plugin.test.js +396 -0
- package/package.json +1 -1
- package/plugins/abweb/dist/src-ts/agent.js +35 -0
- package/plugins/abweb/dist/src-ts/plugins/abweb/index.js +1 -1
- package/plugins/abweb/dist/src-ts/plugins/abweb/public/app.js +73 -5
- package/plugins/abweb/dist/src-ts/plugins/abweb/public/app.ts +57 -5
- package/plugins/abweb/dist/src-ts/plugins/abweb/public/index.html +15 -0
- package/plugins/abweb/dist/src-ts/plugins/abweb/public/style.css +4 -0
- package/plugins/abweb/package.json +1 -1
- package/plugins/abweb/plugin.json +1 -1
- package/plugins/aun/config.schema.json +1 -1
- package/plugins/aun/dist/src-ts/agent.js +35 -0
- package/plugins/aun/dist/src-ts/plugins/aun/attachments.js +3 -21
- package/plugins/aun/dist/src-ts/plugins/aun/index.js +155 -21
- package/plugins/aun/dist/src-ts/plugins/aun/messages.js +4 -1
- package/plugins/aun/dist/src-ts/plugins/aun/public/app.js +2 -1
- package/plugins/aun/dist/src-ts/plugins/aun/public/index.html +1 -1
- package/plugins/aun/package.json +1 -1
- package/plugins/aun/plugin.json +1 -1
- package/plugins/chatgpt/dist/src-ts/agent.js +35 -0
- package/plugins/chatgpt/dist/src-ts/plugins/cdp-draft.js +5 -2
- package/plugins/chatgpt/dist/src-ts/plugins/chatgpt/cdp-client.js +26 -12
- package/plugins/chatgpt/dist/src-ts/plugins/chatgpt/index.js +80 -19
- package/plugins/chatgpt/dist/src-ts/plugins/desktop-launch.js +44 -3
- package/plugins/chatgpt/package.json +1 -1
- package/plugins/chatgpt/plugin.json +1 -1
- package/plugins/claude-cli/dist/src-ts/agent.js +35 -0
- package/plugins/claude-cli/dist/src-ts/plugins/claude-cli/index.js +19 -2
- package/plugins/claude-cli/dist/src-ts/plugins/local-cli/agent.js +74 -9
- package/plugins/claude-cli/package.json +1 -1
- package/plugins/claude-cli/plugin.json +3 -2
- package/plugins/codex-cli/dist/src-ts/agent.js +35 -0
- package/plugins/codex-cli/dist/src-ts/plugins/codex-cli/index.js +19 -2
- package/plugins/codex-cli/dist/src-ts/plugins/local-cli/agent.js +74 -9
- package/plugins/codex-cli/package.json +1 -1
- package/plugins/codex-cli/plugin.json +3 -2
- package/plugins/doubao/config.schema.json +2 -1
- package/plugins/doubao/dist/src-ts/agent.js +35 -0
- package/plugins/doubao/dist/src-ts/plugins/desktop-launch.js +44 -3
- package/plugins/doubao/dist/src-ts/plugins/doubao/cdp-client.js +234 -45
- package/plugins/doubao/dist/src-ts/plugins/doubao/index.js +152 -8
- package/plugins/doubao/dist/src-ts/plugins/doubao/native-task.js +134 -0
- package/plugins/doubao/package.json +1 -1
- package/plugins/doubao/plugin.json +3 -2
- package/plugins/dsh/config.schema.json +5 -2
- package/plugins/dsh/dist/src-ts/agent.js +35 -0
- package/plugins/dsh/dist/src-ts/dsh-agent.js +31 -13
- package/plugins/dsh/dist/src-ts/plugins/desktop-launch.js +44 -3
- package/plugins/dsh/dist/src-ts/plugins/dsh/cdp-client.js +334 -0
- package/plugins/dsh/dist/src-ts/plugins/dsh/index.js +172 -21
- package/plugins/dsh/dist/src-ts/plugins/dsh/session-history.js +31 -3
- package/plugins/dsh/package.json +1 -1
- package/plugins/dsh/plugin.json +3 -2
- package/plugins/feishu/dist/src-ts/agent.js +35 -0
- package/plugins/feishu/dist/src-ts/plugins/feishu/index.js +1 -1
- package/plugins/feishu/package.json +1 -1
- package/plugins/feishu/plugin.json +1 -1
- package/plugins/personal-assistant/config.schema.json +23 -0
- package/plugins/personal-assistant/dist/src-ts/agent.js +69 -0
- package/plugins/personal-assistant/dist/src-ts/attachments.js +62 -0
- package/plugins/personal-assistant/dist/src-ts/plugin.js +13 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/claude-cli-model.js +30 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/coordination.js +57 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/index.js +263 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/model-client.js +181 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/owner-context.js +56 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/remote-ability.js +176 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/session-memory.js +124 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/tools.js +167 -0
- package/plugins/personal-assistant/package.json +39 -0
- package/plugins/personal-assistant/plugin.json +15 -0
- package/plugins/qwenwork/dist/src-ts/agent.js +35 -0
- package/plugins/qwenwork/dist/src-ts/plugins/cdp-draft.js +5 -2
- package/plugins/qwenwork/dist/src-ts/plugins/desktop-launch.js +44 -3
- package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/cdp-client.js +90 -18
- package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/index.js +139 -24
- package/plugins/qwenwork/package.json +1 -1
- package/plugins/qwenwork/plugin.json +3 -2
- package/plugins/workbuddy/config.schema.json +1 -1
- package/plugins/workbuddy/dist/src-ts/agent.js +35 -0
- package/plugins/workbuddy/dist/src-ts/plugins/desktop-launch.js +44 -3
- package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/cdp-client.js +353 -82
- package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/index.js +124 -41
- package/plugins/workbuddy/package.json +1 -1
- package/plugins/workbuddy/plugin.json +3 -2
- package/plugins/zcode/config.schema.json +13 -0
- package/plugins/zcode/dist/src-ts/agent.js +69 -0
- package/plugins/zcode/dist/src-ts/attachments.js +62 -0
- package/plugins/zcode/dist/src-ts/plugin.js +13 -0
- package/plugins/zcode/dist/src-ts/plugins/desktop-launch.js +238 -0
- package/plugins/zcode/dist/src-ts/plugins/workbuddy/cdp-client.js +896 -0
- package/plugins/zcode/dist/src-ts/plugins/workbuddy/cli-client.js +39 -0
- package/plugins/zcode/dist/src-ts/plugins/workbuddy/desktop-launch.js +10 -0
- package/plugins/zcode/dist/src-ts/plugins/workbuddy/index.js +567 -0
- package/plugins/zcode/dist/src-ts/plugins/workbuddy/session-history.js +121 -0
- package/plugins/zcode/dist/src-ts/plugins/zcode/cdp-client.js +500 -0
- package/plugins/zcode/dist/src-ts/plugins/zcode/index.js +97 -0
- package/plugins/zcode/package.json +16 -0
- package/plugins/zcode/plugin.json +11 -0
|
@@ -25,6 +25,7 @@ const desktop_launch_1 = require("./plugins/desktop-launch");
|
|
|
25
25
|
const workbuddy_1 = require("./plugins/workbuddy");
|
|
26
26
|
const logger = { info() { }, warn() { }, error() { } };
|
|
27
27
|
const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
28
|
+
const PLUGIN_UPGRADE_WAIT_MS = 2 * 60 * 60 * 1000;
|
|
28
29
|
class PluginManager {
|
|
29
30
|
options;
|
|
30
31
|
controlUrl;
|
|
@@ -34,6 +35,7 @@ class PluginManager {
|
|
|
34
35
|
root;
|
|
35
36
|
messages = [];
|
|
36
37
|
activeIngress = new Map();
|
|
38
|
+
issuedManagement = new WeakMap();
|
|
37
39
|
trustConfig = defaultPluginTrustConfig();
|
|
38
40
|
trustLoaded = false;
|
|
39
41
|
publisherLookups = new Map();
|
|
@@ -110,7 +112,7 @@ class PluginManager {
|
|
|
110
112
|
if (meta.name !== packageName || !meta.version || (version && meta.version !== version) || !meta.agentBridge?.plugin)
|
|
111
113
|
throw new agent_1.AgentError("npm 包声明无效");
|
|
112
114
|
const manifest = (0, plugin_manifest_1.validateManifest)(JSON.parse(await (0, promises_1.readFile)((0, node_path_1.resolve)(candidate, meta.agentBridge.plugin), "utf8")), candidate);
|
|
113
|
-
if (
|
|
115
|
+
if ((0, plugin_catalog_1.resolvePluginId)(packageName, meta.agentBridge.id) !== manifest.id || manifest.version !== meta.version)
|
|
114
116
|
throw new agent_1.AgentError("npm 包与插件清单不匹配");
|
|
115
117
|
this.validateCandidate(manifest, meta.agentBridge);
|
|
116
118
|
this.log("info", `插件下载完成:${manifest.id}`, { packageName, version: meta.version }, "插件升级");
|
|
@@ -168,7 +170,8 @@ class PluginManager {
|
|
|
168
170
|
return info.packageName ? this.versions.checkPackage(info.packageName, record.version) : Promise.resolve({ packageName: record.id, currentVersion: record.version, state: "latest", checkedAt: new Date().toISOString() });
|
|
169
171
|
})]);
|
|
170
172
|
}
|
|
171
|
-
async upgradePackage(id, packageName
|
|
173
|
+
async upgradePackage(id, packageName, version) {
|
|
174
|
+
packageName ??= this.plugins.get(id)?.record.packageName ?? `agent-bridge-${id}`;
|
|
172
175
|
const pending = this.upgrades.get(id);
|
|
173
176
|
if (pending) {
|
|
174
177
|
if (pending.packageName === packageName && pending.version === version)
|
|
@@ -215,11 +218,25 @@ class PluginManager {
|
|
|
215
218
|
if (!meta.agentBridge?.plugin)
|
|
216
219
|
throw new agent_1.AgentError("npm 包未声明 agentBridge.plugin");
|
|
217
220
|
const manifest = (0, plugin_manifest_1.validateManifest)(JSON.parse(await (0, promises_1.readFile)((0, node_path_1.resolve)(candidate, meta.agentBridge.plugin), "utf8")), candidate);
|
|
218
|
-
if (manifest.id !== id || manifest.version !== meta.version)
|
|
221
|
+
if (manifest.id !== id || (0, plugin_catalog_1.resolvePluginId)(packageName, meta.agentBridge.id) !== id || manifest.version !== meta.version)
|
|
219
222
|
throw new agent_1.AgentError("npm 包插件 ID 或版本不匹配");
|
|
220
223
|
this.validateCandidate(manifest, meta.agentBridge);
|
|
221
|
-
|
|
222
|
-
|
|
224
|
+
const deadline = new AbortController();
|
|
225
|
+
const cancellation = loaded.cancellation;
|
|
226
|
+
const error = new agent_1.AgentOperationError("PLUGIN_UPGRADE_WAIT_TIMEOUT", "等待旧插件任务结束", new Error("升级已等待两小时,强制结束桥接任务并继续升级"));
|
|
227
|
+
this.log("info", `插件升级等待旧任务结束:${id}`, { waitMs: PLUGIN_UPGRADE_WAIT_MS, deadline: new Date(Date.now() + PLUGIN_UPGRADE_WAIT_MS).toISOString() }, "插件升级");
|
|
228
|
+
const timer = setTimeout(() => {
|
|
229
|
+
this.log("warn", `插件升级等待超时,强制结束桥接任务:${id}`, { code: error.code, waitMs: PLUGIN_UPGRADE_WAIT_MS }, "插件升级");
|
|
230
|
+
cancellation?.abort(error);
|
|
231
|
+
deadline.abort(error);
|
|
232
|
+
}, PLUGIN_UPGRADE_WAIT_MS);
|
|
233
|
+
try {
|
|
234
|
+
await this.stop(id, "reload", deadline.signal);
|
|
235
|
+
}
|
|
236
|
+
finally {
|
|
237
|
+
clearTimeout(timer);
|
|
238
|
+
stopped = !loaded.instance;
|
|
239
|
+
}
|
|
223
240
|
if (hadPrefix)
|
|
224
241
|
await (0, promises_1.rename)(prefix, backup);
|
|
225
242
|
await (0, promises_1.rename)(staging, prefix);
|
|
@@ -255,6 +272,39 @@ class PluginManager {
|
|
|
255
272
|
}
|
|
256
273
|
}
|
|
257
274
|
get(id) { return this.plugins.get(id); }
|
|
275
|
+
listModelProviders() {
|
|
276
|
+
return [...this.plugins.values()].filter(({ record }) => record.capabilities.includes("model-provider")).map(({ record, instance }) => {
|
|
277
|
+
const available = record.status === "ready" && typeof instance?.modelProvider?.complete === "function";
|
|
278
|
+
return { id: record.id, displayName: record.displayName, status: record.status, available, reason: available ? "" : record.error ?? (record.status === "ready" ? "插件未实现模型来源接口" : `插件当前状态:${record.status}`) };
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
async completeModelProvider(id, request) {
|
|
282
|
+
const loaded = this.plugins.get(id);
|
|
283
|
+
if (!loaded)
|
|
284
|
+
throw new agent_1.AgentError(`模型来源插件 ${id} 未安装`);
|
|
285
|
+
if (!loaded.record.capabilities.includes("model-provider"))
|
|
286
|
+
throw new agent_1.AgentError(`插件 ${id} 未声明模型来源能力`);
|
|
287
|
+
if (!loaded.instance || loaded.record.status !== "ready" || typeof loaded.instance.modelProvider?.complete !== "function")
|
|
288
|
+
throw new agent_1.AgentError(`模型来源插件“${loaded.record.displayName}”未就绪:${loaded.record.error ?? (loaded.record.status === "ready" ? "插件未实现模型来源接口" : loaded.record.status)}`);
|
|
289
|
+
if (!request || typeof request.prompt !== "string" || !request.prompt.trim() || Buffer.byteLength(request.prompt) > 1024 * 1024)
|
|
290
|
+
throw new agent_1.AgentError("模型来源请求内容为空或超过 1 MiB");
|
|
291
|
+
if (typeof request.sessionId !== "string" || !request.sessionId.trim() || request.sessionId.length > 512)
|
|
292
|
+
throw new agent_1.AgentError("模型来源会话标识无效");
|
|
293
|
+
if (!Number.isInteger(request.timeoutMs) || request.timeoutMs < 1000 || request.timeoutMs > 120_000)
|
|
294
|
+
throw new agent_1.AgentError("模型来源超时时间无效");
|
|
295
|
+
if (!(request.signal instanceof AbortSignal))
|
|
296
|
+
throw new agent_1.AgentError("模型来源请求缺少取消信号");
|
|
297
|
+
// 模型来源只接收最小调用契约,避免把未来扩展字段误传给桌面或 CLI 插件。
|
|
298
|
+
const result = await this.runPluginTask(loaded, request.signal, signal => loaded.instance.modelProvider.complete({
|
|
299
|
+
prompt: request.prompt,
|
|
300
|
+
sessionId: request.sessionId,
|
|
301
|
+
timeoutMs: request.timeoutMs,
|
|
302
|
+
signal,
|
|
303
|
+
}));
|
|
304
|
+
if (!result || typeof result.text !== "string" || !result.text.trim() || Buffer.byteLength(result.text) > 1024 * 1024)
|
|
305
|
+
throw new agent_1.AgentError(`模型来源插件“${loaded.record.displayName}”返回空内容或超大响应`);
|
|
306
|
+
return { text: result.text };
|
|
307
|
+
}
|
|
258
308
|
recentMessages(id) { return this.messages.filter((item) => !id || item.pluginId === id).slice(-200).reverse(); }
|
|
259
309
|
recordMessage(pluginId, direction, text, peer, agentAid) {
|
|
260
310
|
if (direction === "error" && isNoisyAgentError(text))
|
|
@@ -267,8 +317,9 @@ class PluginManager {
|
|
|
267
317
|
if (!/^agent-bridge-[a-z0-9-]+(?:@[0-9][a-zA-Z0-9.+-]*)?$/.test(packageName))
|
|
268
318
|
throw new agent_1.AgentError("仅支持 agent-bridge-* npm 包名");
|
|
269
319
|
const name = packageName.replace(/@.*$/, "");
|
|
270
|
-
|
|
271
|
-
|
|
320
|
+
const version = packageName.slice(name.length + 1);
|
|
321
|
+
if (this.list().some((record) => record.packageName === name))
|
|
322
|
+
throw new agent_1.AgentError("插件已经存在,请使用升级操作");
|
|
272
323
|
const prefix = (0, node_path_1.join)(this.options.stateDir, "npm", name);
|
|
273
324
|
if (await this.existsDir(prefix))
|
|
274
325
|
throw new agent_1.AgentError("插件包已经安装");
|
|
@@ -283,12 +334,16 @@ class PluginManager {
|
|
|
283
334
|
const meta = JSON.parse(await (0, promises_1.readFile)((0, node_path_1.join)(directory, "package.json"), "utf8"));
|
|
284
335
|
if (meta.name !== name || !meta.version || !meta.agentBridge?.plugin)
|
|
285
336
|
throw new agent_1.AgentError("npm 包名称或插件声明无效");
|
|
337
|
+
if (version && meta.version !== version)
|
|
338
|
+
throw new agent_1.AgentError("npm 包版本与请求不匹配");
|
|
286
339
|
const manifestFile = (0, node_path_1.resolve)(directory, meta.agentBridge.plugin);
|
|
287
340
|
if (!manifestFile.startsWith(`${(0, node_path_1.resolve)(directory)}${node_path_1.sep}`))
|
|
288
341
|
throw new agent_1.AgentError("插件清单路径越界");
|
|
289
342
|
const manifest = (0, plugin_manifest_1.validateManifest)(JSON.parse(await (0, promises_1.readFile)(manifestFile, "utf8")), directory);
|
|
290
|
-
if (
|
|
343
|
+
if ((0, plugin_catalog_1.resolvePluginId)(name, meta.agentBridge.id) !== manifest.id || manifest.version !== meta.version || (meta.agentBridge.type && meta.agentBridge.type !== manifest.type))
|
|
291
344
|
throw new agent_1.AgentError("npm 包与插件清单不匹配");
|
|
345
|
+
if (this.plugins.has(manifest.id))
|
|
346
|
+
throw new agent_1.AgentError("插件已经存在;不同 npm 来源不能覆盖,请先显式处理现有插件");
|
|
292
347
|
this.validateCandidate(manifest, meta.agentBridge);
|
|
293
348
|
await this.discoverDirectory(directory, name, manifestFile);
|
|
294
349
|
await this.resolvePendingPublisherLookups();
|
|
@@ -326,7 +381,7 @@ class PluginManager {
|
|
|
326
381
|
}
|
|
327
382
|
if (loaded.instance) {
|
|
328
383
|
try {
|
|
329
|
-
await loaded.instance.deactivate("reload");
|
|
384
|
+
await loaded.instance.deactivate("reload", this.upgrades.has(id) ? { preserveDesktop: true } : undefined);
|
|
330
385
|
}
|
|
331
386
|
catch (error) {
|
|
332
387
|
this.log("warn", `插件恢复前清理失败:${id}`, { error: error instanceof Error ? error.message : String(error) }, "插件生命周期");
|
|
@@ -356,14 +411,12 @@ class PluginManager {
|
|
|
356
411
|
let instance;
|
|
357
412
|
try {
|
|
358
413
|
instance = await this.load(loaded.record);
|
|
414
|
+
const cancellation = loaded.cancellation = new AbortController();
|
|
415
|
+
loaded.tasks = new Set();
|
|
416
|
+
const http = loaded.http = this.createHttpRegistry(id);
|
|
359
417
|
const dataDir = (0, node_path_1.join)(this.options.stateDir, id);
|
|
360
418
|
await (0, promises_1.mkdir)(dataDir, { recursive: true });
|
|
361
|
-
const
|
|
362
|
-
let config = {};
|
|
363
|
-
try {
|
|
364
|
-
config = JSON.parse(await (0, promises_1.readFile)(configPath, "utf8"));
|
|
365
|
-
}
|
|
366
|
-
catch { /* defaults */ }
|
|
419
|
+
const config = await this.config(id);
|
|
367
420
|
const pluginLogger = {
|
|
368
421
|
info: (message, fields) => { this.log("info", message, fields, id); this.options.logger?.info(message, fields); if (fields?.direction && typeof fields.text === "string")
|
|
369
422
|
this.recordMessage(id, fields.direction, fields.text, typeof fields.peer === "string" ? fields.peer : undefined, typeof fields.agentAid === "string" ? fields.agentAid : undefined); },
|
|
@@ -374,15 +427,33 @@ class PluginManager {
|
|
|
374
427
|
dataDir,
|
|
375
428
|
config,
|
|
376
429
|
logger: pluginLogger,
|
|
377
|
-
http
|
|
430
|
+
http,
|
|
431
|
+
modelInputGuard: true,
|
|
378
432
|
callPlugin: async (targetId, request) => {
|
|
379
433
|
if (targetId === id)
|
|
380
434
|
throw new agent_1.AgentError(`插件不能调用自身: ${id}`);
|
|
381
|
-
|
|
435
|
+
if (request.management)
|
|
436
|
+
throw new agent_1.AgentError("插件间调用不能传递管理能力");
|
|
437
|
+
cancellation.signal.throwIfAborted();
|
|
438
|
+
if (loaded.cancellation !== cancellation || !loaded.instance)
|
|
439
|
+
throw new agent_1.AgentError("插件实例已停止,旧调用已失效");
|
|
440
|
+
return this.handle(targetId, { ...request, signal: request.signal ? AbortSignal.any([request.signal, cancellation.signal]) : cancellation.signal });
|
|
441
|
+
},
|
|
442
|
+
callModelProvider: async (targetId, request) => {
|
|
443
|
+
if (targetId === id)
|
|
444
|
+
throw new agent_1.AgentError(`插件不能调用自身: ${id}`);
|
|
445
|
+
cancellation.signal.throwIfAborted();
|
|
446
|
+
if (loaded.cancellation !== cancellation || !loaded.instance)
|
|
447
|
+
throw new agent_1.AgentError("插件实例已停止,旧调用已失效");
|
|
448
|
+
return this.completeModelProvider(targetId, { ...request, signal: AbortSignal.any([request.signal, cancellation.signal]) });
|
|
382
449
|
},
|
|
383
450
|
pluginStatus: (targetId) => this.plugins.get(targetId)?.record.status,
|
|
384
451
|
ingress: {
|
|
385
|
-
publish: async (
|
|
452
|
+
publish: message => this.runPluginTask(loaded, undefined, async (signal) => {
|
|
453
|
+
if (loaded.cancellation !== cancellation || loaded.instance !== instance)
|
|
454
|
+
throw new agent_1.AgentError("通道插件实例已停止,旧调用已失效");
|
|
455
|
+
if (loaded.record.status !== "ready")
|
|
456
|
+
throw new agent_1.AgentError("通道插件正在停止或尚未就绪,不再接收新任务");
|
|
386
457
|
if (message.channel !== id)
|
|
387
458
|
throw new agent_1.AgentError("消息来源与通道插件身份不一致");
|
|
388
459
|
const trustedChannel = loaded.record.type === "channel" && (id === "aun" || id === "feishu");
|
|
@@ -396,7 +467,8 @@ class PluginManager {
|
|
|
396
467
|
throw new agent_1.AgentError("当前通道不支持会话管理");
|
|
397
468
|
const current = await this.sessions.current(message, target);
|
|
398
469
|
const result = await this.sessionManagement.command(message.text, { source: current.source, sessionId: current.logicalSessionId });
|
|
399
|
-
|
|
470
|
+
signal.throwIfAborted();
|
|
471
|
+
await (0, agent_1.abortable)(message.reply(result, signal), signal);
|
|
400
472
|
return;
|
|
401
473
|
}
|
|
402
474
|
if (trustedChannel)
|
|
@@ -407,30 +479,51 @@ class PluginManager {
|
|
|
407
479
|
if (trustedChannel)
|
|
408
480
|
this.activeIngress.set(source, (this.activeIngress.get(source) ?? 0) + 1);
|
|
409
481
|
try {
|
|
410
|
-
const
|
|
411
|
-
const control =
|
|
412
|
-
? (request) => this.controlSession(target, request)
|
|
482
|
+
const agentInstance = this.plugins.get(target)?.instance;
|
|
483
|
+
const control = agentInstance?.sessionOperations?.includes("new")
|
|
484
|
+
? (request) => this.controlSession(target, request, signal)
|
|
413
485
|
: undefined;
|
|
414
486
|
const task = !message.text.trim() && message.attachments?.length ? "请处理收到的附件" : message.text;
|
|
415
487
|
const forwarded = new Set();
|
|
416
488
|
const sent = [];
|
|
417
489
|
const onMessage = async (text) => {
|
|
490
|
+
signal.throwIfAborted();
|
|
418
491
|
if (!text.trim() || forwarded.has(text))
|
|
419
492
|
return;
|
|
420
|
-
await message.reply(text);
|
|
493
|
+
await (0, agent_1.abortable)(message.reply(text, signal), signal);
|
|
494
|
+
signal.throwIfAborted();
|
|
421
495
|
forwarded.add(text);
|
|
422
496
|
sent.push({ text });
|
|
423
497
|
this.recordMessage(target, "reply", text, message.peerId);
|
|
424
498
|
};
|
|
425
|
-
const response = await this.sessions.run(message, target, (sessionId, newSession
|
|
499
|
+
const response = await this.sessions.run(message, target, async (sessionId, newSession, logicalSessionId) => {
|
|
500
|
+
const targetRecord = this.plugins.get(target)?.record;
|
|
501
|
+
const config = targetRecord?.type === "agent" && targetRecord.capabilities.includes("management") ? await this.config(target) : undefined;
|
|
502
|
+
const ability = trustedChannel && id === "aun" && loaded.instance === instance && loaded.record.status === "ready" && Array.isArray(config?.allowedPeers) && (config.allowedPeers.length === 0 || config.allowedPeers.includes("*") || config.allowedPeers.includes(message.peerId))
|
|
503
|
+
? this.createRequestManagement(target, message.peerId, source, logicalSessionId, { signal, onMessage, ...(message.replyAttachments ? { attachmentOutput: true } : {}) }) : undefined;
|
|
504
|
+
signal.throwIfAborted();
|
|
505
|
+
const request = { task, sessionId, signal, onMessage, ...(newSession ? { newSession: true } : {}), ...(message.attachments ? { attachments: message.attachments } : {}), ...(message.replyAttachments ? { attachmentOutput: true } : {}), ...(ability ? { management: ability } : {}) };
|
|
506
|
+
const issued = ability ? this.issuedManagement.get(ability) : undefined;
|
|
507
|
+
if (issued)
|
|
508
|
+
issued.active = true;
|
|
509
|
+
try {
|
|
510
|
+
return await this.handle(target, request);
|
|
511
|
+
}
|
|
512
|
+
finally {
|
|
513
|
+
if (issued)
|
|
514
|
+
issued.active = false;
|
|
515
|
+
}
|
|
516
|
+
}, control);
|
|
517
|
+
signal.throwIfAborted();
|
|
426
518
|
const attachments = response.attachments?.length ? response.attachments : (0, attachments_1.asAttachments)(response.images);
|
|
427
519
|
(0, attachments_1.validateAttachments)(attachments);
|
|
428
520
|
if (attachments.length) {
|
|
429
521
|
if (!message.replyAttachments) {
|
|
430
|
-
await message.reply("当前通道不支持附件回传");
|
|
522
|
+
await (0, agent_1.abortable)(message.reply("当前通道不支持附件回传", signal), signal);
|
|
431
523
|
throw new agent_1.AgentError("当前通道不支持附件回传");
|
|
432
524
|
}
|
|
433
|
-
await message.replyAttachments(attachments, response.text);
|
|
525
|
+
await (0, agent_1.abortable)(message.replyAttachments(attachments, response.text, signal), signal);
|
|
526
|
+
signal.throwIfAborted();
|
|
434
527
|
sent.push({ text: response.text, attachments });
|
|
435
528
|
this.recordMessage(target, "reply", response.text, message.peerId);
|
|
436
529
|
}
|
|
@@ -462,7 +555,7 @@ class PluginManager {
|
|
|
462
555
|
else
|
|
463
556
|
this.activeIngress.delete(source);
|
|
464
557
|
}
|
|
465
|
-
},
|
|
558
|
+
}),
|
|
466
559
|
},
|
|
467
560
|
runtime: { controlUrl: this.controlUrl, ports: {
|
|
468
561
|
qwenwork: Number(process.env.AGENT_BRIDGE_QWEN_CDP_PORT ?? process.env.QWEN_CDP_PORT ?? "9228"),
|
|
@@ -478,13 +571,15 @@ class PluginManager {
|
|
|
478
571
|
catch (error) {
|
|
479
572
|
if (instance) {
|
|
480
573
|
try {
|
|
481
|
-
await instance.deactivate("reload");
|
|
574
|
+
await instance.deactivate("reload", this.upgrades.has(id) ? { preserveDesktop: true } : undefined);
|
|
482
575
|
}
|
|
483
576
|
catch (cleanupError) {
|
|
484
577
|
this.log("warn", `插件启动失败后的清理失败:${id}`, { error: cleanupError instanceof Error ? cleanupError.message : String(cleanupError) }, "插件生命周期");
|
|
485
578
|
}
|
|
486
579
|
this.unregisterHttpRoutes(id);
|
|
487
580
|
}
|
|
581
|
+
loaded.http?.unregister();
|
|
582
|
+
loaded.cancellation?.abort(new agent_1.AgentError("插件启动失败,调用已取消"));
|
|
488
583
|
loaded.instance = undefined;
|
|
489
584
|
loaded.record.status = "failed";
|
|
490
585
|
loaded.record.error = error instanceof Error ? error.message : String(error);
|
|
@@ -492,17 +587,44 @@ class PluginManager {
|
|
|
492
587
|
throw error;
|
|
493
588
|
}
|
|
494
589
|
}
|
|
495
|
-
async stop(id, reason = "stop") {
|
|
590
|
+
async stop(id, reason = "stop", deadline) {
|
|
496
591
|
const loaded = this.plugins.get(id);
|
|
497
592
|
if (!loaded)
|
|
498
593
|
throw new agent_1.AgentError(`unknown plugin: ${id}`);
|
|
499
594
|
if (loaded.instance) {
|
|
500
595
|
loaded.record.status = "stopping";
|
|
596
|
+
const instance = loaded.instance;
|
|
597
|
+
const stopping = Promise.resolve().then(async () => {
|
|
598
|
+
if (deadline)
|
|
599
|
+
await Promise.allSettled([...(loaded.tasks ?? [])]);
|
|
600
|
+
await instance.deactivate(reason, deadline ? { preserveDesktop: true } : undefined);
|
|
601
|
+
});
|
|
501
602
|
try {
|
|
502
|
-
|
|
603
|
+
try {
|
|
604
|
+
await (0, agent_1.abortable)(stopping, deadline);
|
|
605
|
+
}
|
|
606
|
+
catch (error) {
|
|
607
|
+
if (!deadline?.aborted)
|
|
608
|
+
throw error;
|
|
609
|
+
let cleanupTimer;
|
|
610
|
+
try {
|
|
611
|
+
const cleaned = await Promise.race([
|
|
612
|
+
stopping.then(() => true, () => false),
|
|
613
|
+
new Promise(resolve => { cleanupTimer = setTimeout(() => resolve(false), 5_000); }),
|
|
614
|
+
]);
|
|
615
|
+
if (!cleaned)
|
|
616
|
+
this.log("warn", `旧插件未完成清理,已隔离并继续升级:${id}`, { code: "PLUGIN_UPGRADE_CLEANUP_TIMEOUT" }, "插件升级");
|
|
617
|
+
}
|
|
618
|
+
finally {
|
|
619
|
+
clearTimeout(cleanupTimer);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
503
622
|
}
|
|
504
623
|
finally {
|
|
624
|
+
loaded.http?.unregister();
|
|
505
625
|
this.unregisterHttpRoutes(id);
|
|
626
|
+
if (deadline)
|
|
627
|
+
loaded.cancellation?.abort(new agent_1.AgentError("插件已停止,旧实例调用已取消"));
|
|
506
628
|
loaded.instance = undefined;
|
|
507
629
|
}
|
|
508
630
|
}
|
|
@@ -583,13 +705,31 @@ class PluginManager {
|
|
|
583
705
|
}
|
|
584
706
|
}
|
|
585
707
|
async setAid(id, aid) { await this.saveConfig(id, { ...await this.config(id), aid }); }
|
|
586
|
-
async config(id) {
|
|
587
|
-
|
|
588
|
-
|
|
708
|
+
async config(id) {
|
|
709
|
+
const loaded = this.plugins.get(id);
|
|
710
|
+
if (!loaded)
|
|
711
|
+
throw new agent_1.AgentError(`unknown plugin: ${id}`);
|
|
712
|
+
let defaults = {};
|
|
713
|
+
if (loaded.record.manifest.configSchema) {
|
|
714
|
+
try {
|
|
715
|
+
const schema = JSON.parse(await (0, promises_1.readFile)((0, node_path_1.resolve)(loaded.record.path, loaded.record.manifest.configSchema), "utf8"));
|
|
716
|
+
if (schema.default && typeof schema.default === "object" && !Array.isArray(schema.default))
|
|
717
|
+
defaults = schema.default;
|
|
718
|
+
}
|
|
719
|
+
catch { /* 默认声明不可读时保留已有配置 */ }
|
|
720
|
+
}
|
|
721
|
+
try {
|
|
722
|
+
const saved = JSON.parse(await (0, promises_1.readFile)((0, node_path_1.join)(this.options.stateDir, id, "config.json"), "utf8"));
|
|
723
|
+
if (!saved || typeof saved !== "object" || Array.isArray(saved))
|
|
724
|
+
throw new agent_1.AgentError("插件配置必须是对象");
|
|
725
|
+
return { ...defaults, ...saved };
|
|
726
|
+
}
|
|
727
|
+
catch (error) {
|
|
728
|
+
if (error.code === "ENOENT")
|
|
729
|
+
return defaults;
|
|
730
|
+
throw new agent_1.AgentError(`插件 ${id} 的配置无法读取或格式无效`);
|
|
731
|
+
}
|
|
589
732
|
}
|
|
590
|
-
catch {
|
|
591
|
-
return {};
|
|
592
|
-
} }
|
|
593
733
|
async reloadStrategy(record) { if (!record.manifest.configSchema)
|
|
594
734
|
return "reload"; try {
|
|
595
735
|
const schema = JSON.parse(await (0, promises_1.readFile)((0, node_path_1.resolve)(record.path, record.manifest.configSchema), "utf8"));
|
|
@@ -620,9 +760,143 @@ class PluginManager {
|
|
|
620
760
|
...await (0, agent_availability_1.agentAvailability)(plugin.id, await this.config(plugin.id), plugin.status),
|
|
621
761
|
})));
|
|
622
762
|
}
|
|
763
|
+
createRequestManagement(pluginId, peerId, source, logicalSessionId, output) {
|
|
764
|
+
const issued = { pluginId, instance: this.plugins.get(pluginId)?.instance, active: false };
|
|
765
|
+
const cancellation = this.plugins.get(pluginId)?.cancellation;
|
|
766
|
+
const checkActive = () => {
|
|
767
|
+
const current = this.plugins.get(pluginId);
|
|
768
|
+
if (!issued.active || !current?.instance || current.instance !== issued.instance)
|
|
769
|
+
throw new agent_1.AgentError("管理能力请求已结束或已撤销");
|
|
770
|
+
cancellation?.signal.throwIfAborted();
|
|
771
|
+
};
|
|
772
|
+
const allowed = async (agentId) => {
|
|
773
|
+
checkActive();
|
|
774
|
+
const loaded = this.plugins.get(pluginId);
|
|
775
|
+
if (!loaded?.instance || (loaded.record.status !== "ready" && !(loaded.record.status === "stopping" && this.upgrades.has(pluginId))) || !loaded.record.capabilities.includes("management"))
|
|
776
|
+
throw new agent_1.AgentError("当前插件未声明管理能力或未就绪");
|
|
777
|
+
const config = await this.config(pluginId);
|
|
778
|
+
checkActive();
|
|
779
|
+
const peers = config.allowedPeers;
|
|
780
|
+
if (!Array.isArray(peers) || (peers.length > 0 && !peers.includes("*") && !peers.includes(peerId)))
|
|
781
|
+
throw new agent_1.AgentError("当前 AUN 身份无权使用管理能力");
|
|
782
|
+
if (agentId !== undefined) {
|
|
783
|
+
const agents = config.allowedAgents;
|
|
784
|
+
if (!Array.isArray(agents) || (agents.length > 0 && !agents.includes("*") && !agents.includes(agentId)))
|
|
785
|
+
throw new agent_1.AgentError("目标 Agent 不在允许列表中");
|
|
786
|
+
}
|
|
787
|
+
return { loaded, config };
|
|
788
|
+
};
|
|
789
|
+
const management = {
|
|
790
|
+
listAgents: async () => {
|
|
791
|
+
const { config } = await allowed();
|
|
792
|
+
const aun = this.plugins.get("aun");
|
|
793
|
+
let bindings = {};
|
|
794
|
+
let identitiesAvailable = false;
|
|
795
|
+
try {
|
|
796
|
+
const health = aun?.record.status === "ready" ? await aun.instance?.health() : undefined;
|
|
797
|
+
const details = health?.details;
|
|
798
|
+
if (health?.status === "ready" && details && typeof details === "object" && !Array.isArray(details)) {
|
|
799
|
+
const raw = details.bindings;
|
|
800
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
801
|
+
bindings = raw;
|
|
802
|
+
identitiesAvailable = true;
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
catch { /* AUN 状态不可用时只返回 Agent 能力 */ }
|
|
807
|
+
const result = [];
|
|
808
|
+
const agents = this.list().filter(item => item.type === "agent");
|
|
809
|
+
const agentIds = new Set(agents.map(item => item.id));
|
|
810
|
+
for (const record of agents) {
|
|
811
|
+
const agentConfig = await this.config(record.id);
|
|
812
|
+
const current = this.plugins.get(record.id);
|
|
813
|
+
const currentRecord = current?.record ?? record;
|
|
814
|
+
const availability = await (0, agent_availability_1.agentAvailability)(record.id, agentConfig, currentRecord.status);
|
|
815
|
+
const latest = this.plugins.get(record.id);
|
|
816
|
+
const latestRecord = latest?.record ?? currentRecord;
|
|
817
|
+
const aids = identitiesAvailable
|
|
818
|
+
? Object.entries(bindings).filter(([aid, bound]) => /^[a-z0-9][a-z0-9_-]{2,63}\.[a-z0-9-]+(?:\.[a-z0-9-]+)+$/.test(aid) && agentIds.has(bound) && bound === record.id).map(([aid]) => aid).sort()
|
|
819
|
+
: [];
|
|
820
|
+
const callable = Boolean(latest?.instance) && latestRecord.type === "agent" && latestRecord.id !== pluginId && latestRecord.status === "ready" && availability.available && !latestRecord.capabilities.includes("management") && (Array.isArray(config.allowedAgents) && (config.allowedAgents.length === 0 || config.allowedAgents.includes("*") || config.allowedAgents.includes(record.id)));
|
|
821
|
+
result.push({ id: latestRecord.id, displayName: latestRecord.displayName, status: latestRecord.status, ...availability, reason: !identitiesAvailable && !availability.reason ? "AUN 身份数据不可用" : availability.reason, capabilities: [...latestRecord.capabilities], aids, callable });
|
|
822
|
+
}
|
|
823
|
+
await allowed();
|
|
824
|
+
return result;
|
|
825
|
+
},
|
|
826
|
+
callAgent: async (id, task) => {
|
|
827
|
+
const { loaded } = await allowed(id);
|
|
828
|
+
if (typeof task !== "string" || !task.trim() || task.length > 20_000)
|
|
829
|
+
throw new agent_1.AgentError("Agent 调用内容无效");
|
|
830
|
+
const target = this.plugins.get(id);
|
|
831
|
+
if (!target?.instance || target.record.type !== "agent" || target.record.status !== "ready" || target.record.capabilities.includes("management") || id === pluginId)
|
|
832
|
+
throw new agent_1.AgentError("目标 Agent 不可调用");
|
|
833
|
+
if (!(await (0, agent_availability_1.agentAvailability)(id, await this.config(id), target.record.status)).available)
|
|
834
|
+
throw new agent_1.AgentError("目标 Agent 当前不可用");
|
|
835
|
+
await allowed(id);
|
|
836
|
+
// shortcut: 仅 WorkBuddy 已验证未知逻辑会话 ID 可安全建会话,其他插件验证后再扩展。
|
|
837
|
+
const sessionId = id === "workbuddy" && target.instance?.sessionOperations?.includes("new")
|
|
838
|
+
? `management-${(0, node_crypto_1.createHash)("sha256").update(`${logicalSessionId}\0${id}`).digest("hex")}`
|
|
839
|
+
: undefined;
|
|
840
|
+
const signal = output?.signal ? AbortSignal.any([output.signal, cancellation.signal]) : cancellation.signal;
|
|
841
|
+
const response = await this.handle(id, { task, ...output, signal, ...(sessionId ? { sessionId } : {}) });
|
|
842
|
+
if (loaded.instance !== this.plugins.get(pluginId)?.instance)
|
|
843
|
+
throw new agent_1.AgentError("管理插件已重载");
|
|
844
|
+
await allowed(id);
|
|
845
|
+
return response;
|
|
846
|
+
},
|
|
847
|
+
listAunIdentities: async () => {
|
|
848
|
+
await allowed();
|
|
849
|
+
const aun = this.plugins.get("aun")?.instance;
|
|
850
|
+
let result;
|
|
851
|
+
if (aun?.aunManagement)
|
|
852
|
+
result = await aun.aunManagement.list();
|
|
853
|
+
else if (aun?.identity) {
|
|
854
|
+
// 旧 AUN 只有身份列表和健康绑定;兼容只读查询,不开放创建或绑定操作。
|
|
855
|
+
const groups = await aun.identity.list();
|
|
856
|
+
const health = await aun.health();
|
|
857
|
+
const aidPattern = /^[A-Za-z0-9][A-Za-z0-9._-]{2,255}$/;
|
|
858
|
+
const aids = [...new Set([...groups.evolcore, ...groups.muAgentBridge, ...(groups.localMuAgentBridge ?? [])])].filter(aid => aidPattern.test(aid)).sort();
|
|
859
|
+
const raw = health.details?.bindings;
|
|
860
|
+
const bindings = raw && typeof raw === "object" && !Array.isArray(raw)
|
|
861
|
+
? Object.fromEntries(Object.entries(raw).filter(([aid, id]) => aidPattern.test(aid) && typeof id === "string" && /^[a-z0-9][a-z0-9-]{0,63}$/.test(id))) : {};
|
|
862
|
+
result = { aids, bindings };
|
|
863
|
+
}
|
|
864
|
+
else
|
|
865
|
+
throw new agent_1.AgentError("AUN 身份管理不可用");
|
|
866
|
+
await allowed();
|
|
867
|
+
return result;
|
|
868
|
+
},
|
|
869
|
+
createAunIdentity: async (input) => { await allowed(); const aun = this.plugins.get("aun")?.instance; if (!aun?.aunManagement)
|
|
870
|
+
throw new agent_1.AgentError("AUN 身份管理不可用"); const result = await aun.aunManagement.create(input); await allowed(); return result; },
|
|
871
|
+
bindAunIdentity: async (input) => { await allowed(input.agentId); const target = this.plugins.get(input.agentId); if (!target?.instance || target.record.type !== "agent" || target.record.status !== "ready")
|
|
872
|
+
throw new agent_1.AgentError("只能关联已就绪的 Agent 插件"); const aun = this.plugins.get("aun")?.instance; if (!aun?.aunManagement)
|
|
873
|
+
throw new agent_1.AgentError("AUN 身份管理不可用"); const result = await aun.aunManagement.bind(input); await allowed(input.agentId); return result; },
|
|
874
|
+
runSessionCommand: async (command) => { await allowed(); if (typeof command !== "string" || command.length > 2_000 || !/^\/sessions(?:\s|$)/.test(command.trimStart()))
|
|
875
|
+
throw new agent_1.AgentError("仅支持 /sessions 只读命令"); const result = await this.sessionManagement.command(command, { source, sessionId: logicalSessionId }); await allowed(); if (result === undefined)
|
|
876
|
+
throw new agent_1.AgentError("不支持的 /sessions 命令"); return result; },
|
|
877
|
+
summarizeToday: async () => { await allowed(); const result = await this.sessionManagement.summarizeToday({ source, sessionId: logicalSessionId }); await allowed(); return result; },
|
|
878
|
+
};
|
|
879
|
+
Object.freeze(management);
|
|
880
|
+
this.issuedManagement.set(management, issued);
|
|
881
|
+
return management;
|
|
882
|
+
}
|
|
623
883
|
async health(id) { const loaded = this.plugins.get(id); if (!loaded)
|
|
624
884
|
throw new agent_1.AgentError(`unknown plugin: ${id}`); if (!loaded.instance)
|
|
625
885
|
return { status: loaded.record.status }; return loaded.instance.health(); }
|
|
886
|
+
async runPluginTask(loaded, upstream, work) {
|
|
887
|
+
const local = loaded.cancellation.signal;
|
|
888
|
+
const signal = upstream ? AbortSignal.any([upstream, local]) : local;
|
|
889
|
+
signal.throwIfAborted();
|
|
890
|
+
const task = (0, agent_1.abortable)(work(signal), signal);
|
|
891
|
+
const tasks = loaded.tasks;
|
|
892
|
+
tasks.add(task);
|
|
893
|
+
try {
|
|
894
|
+
return await task;
|
|
895
|
+
}
|
|
896
|
+
finally {
|
|
897
|
+
tasks.delete(task);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
626
900
|
async handle(id, request) {
|
|
627
901
|
const loaded = this.plugins.get(id);
|
|
628
902
|
if (!loaded?.instance || loaded.record.status !== "ready") {
|
|
@@ -632,20 +906,32 @@ class PluginManager {
|
|
|
632
906
|
const reason = loaded?.record.error ? `启动错误:${loaded.record.error}` : `当前状态:${loaded?.record.status ?? "未发现"}`;
|
|
633
907
|
throw new agent_1.AgentError(`插件“${name}”无法调用,插件未就绪:${processText}${reason}`);
|
|
634
908
|
}
|
|
909
|
+
if (request.modelResponseSchema !== undefined && !loaded.instance.modelInput)
|
|
910
|
+
throw new agent_1.AgentError("目标插件不支持无工具模型请求");
|
|
911
|
+
if (request.management) {
|
|
912
|
+
const issued = this.issuedManagement.get(request.management);
|
|
913
|
+
if (!issued || !issued.active || issued.pluginId !== id || issued.instance !== loaded.instance)
|
|
914
|
+
throw new agent_1.AgentError("管理能力无效或已撤销");
|
|
915
|
+
}
|
|
635
916
|
(0, attachments_1.validateAttachments)(request.attachments);
|
|
636
917
|
if (request.attachments?.length && !loaded.instance.attachmentInput)
|
|
637
918
|
throw new agent_1.AgentError(`插件 ${id} 不支持接收附件`);
|
|
638
|
-
|
|
919
|
+
const instance = loaded.instance;
|
|
920
|
+
return this.runPluginTask(loaded, request.signal, signal => instance.handle({
|
|
921
|
+
...request, signal,
|
|
922
|
+
...(request.onMessage ? { onMessage: async (text) => { signal.throwIfAborted(); await request.onMessage(text); } } : {}),
|
|
923
|
+
}));
|
|
639
924
|
}
|
|
640
|
-
async controlSession(id, request) {
|
|
925
|
+
async controlSession(id, request, signal) {
|
|
641
926
|
const loaded = this.plugins.get(id);
|
|
642
927
|
if (!loaded?.instance || loaded.record.status !== "ready")
|
|
643
928
|
throw new agent_1.AgentError(`插件 ${id} 未就绪,无法控制会话`);
|
|
644
929
|
if (!loaded.instance.sessionOperations?.includes(request.operation))
|
|
645
930
|
throw new agent_1.AgentError(`插件 ${id} 不支持新建会话`);
|
|
646
|
-
const
|
|
647
|
-
|
|
648
|
-
|
|
931
|
+
const instance = loaded.instance;
|
|
932
|
+
const response = await this.runPluginTask(loaded, signal, async () => instance.controlSession
|
|
933
|
+
? instance.controlSession(request)
|
|
934
|
+
: { text: `${loaded.record.displayName} 已创建新会话`, sessionId: request.sessionId });
|
|
649
935
|
if (typeof response.sessionId !== "string" || !response.sessionId.trim())
|
|
650
936
|
throw new agent_1.AgentError("插件未返回有效的新会话标识,原会话绑定保持不变");
|
|
651
937
|
return response;
|
|
@@ -663,18 +949,25 @@ class PluginManager {
|
|
|
663
949
|
return true;
|
|
664
950
|
}
|
|
665
951
|
createHttpRegistry(pluginId) {
|
|
952
|
+
const disposers = new Set();
|
|
953
|
+
let closed = false;
|
|
954
|
+
const check = () => { if (closed)
|
|
955
|
+
throw new agent_1.AgentError("插件 HTTP 注册表已停止"); };
|
|
956
|
+
const remember = (dispose) => { disposers.add(dispose); return () => { dispose(); disposers.delete(dispose); }; };
|
|
666
957
|
return {
|
|
667
958
|
register: (routeOrMethod, pathOrHandler, maybeHandler) => {
|
|
959
|
+
check();
|
|
668
960
|
if (typeof routeOrMethod === "object")
|
|
669
|
-
return this.registerHttpRoute(pluginId, routeOrMethod.method, routeOrMethod.path, routeOrMethod.handler);
|
|
961
|
+
return remember(this.registerHttpRoute(pluginId, routeOrMethod.method, routeOrMethod.path, routeOrMethod.handler));
|
|
670
962
|
if (typeof pathOrHandler === "string" && maybeHandler)
|
|
671
|
-
return this.registerHttpRoute(pluginId, routeOrMethod, pathOrHandler, maybeHandler);
|
|
963
|
+
return remember(this.registerHttpRoute(pluginId, routeOrMethod, pathOrHandler, maybeHandler));
|
|
672
964
|
if (typeof pathOrHandler === "function")
|
|
673
|
-
return this.registerLegacyRoute(pluginId, routeOrMethod, pathOrHandler);
|
|
965
|
+
return remember(this.registerLegacyRoute(pluginId, routeOrMethod, pathOrHandler));
|
|
674
966
|
throw new agent_1.AgentError("invalid plugin HTTP route");
|
|
675
967
|
},
|
|
676
|
-
registerRoute: (path, handler) => this.registerLegacyRoute(pluginId, path, handler),
|
|
677
|
-
unregister: () =>
|
|
968
|
+
registerRoute: (path, handler) => { check(); return remember(this.registerLegacyRoute(pluginId, path, handler)); },
|
|
969
|
+
unregister: () => { closed = true; for (const dispose of disposers)
|
|
970
|
+
dispose(); disposers.clear(); },
|
|
678
971
|
};
|
|
679
972
|
}
|
|
680
973
|
registerHttpRoute(pluginId, method, path, handler) {
|
|
@@ -686,8 +979,9 @@ class PluginManager {
|
|
|
686
979
|
const existing = this.httpRoutes.get(key);
|
|
687
980
|
if (existing)
|
|
688
981
|
throw new agent_1.AgentError(`HTTP route already registered: ${normalizedMethod} ${normalizedPath}`);
|
|
689
|
-
|
|
690
|
-
|
|
982
|
+
const route = { pluginId, method: normalizedMethod, path: normalizedPath, handler };
|
|
983
|
+
this.httpRoutes.set(key, route);
|
|
984
|
+
return () => { if (this.httpRoutes.get(key) === route)
|
|
691
985
|
this.httpRoutes.delete(key); };
|
|
692
986
|
}
|
|
693
987
|
registerLegacyRoute(pluginId, path, handler) {
|
|
@@ -708,8 +1002,9 @@ class PluginManager {
|
|
|
708
1002
|
response.setHeader(name, value);
|
|
709
1003
|
response.end(result?.body ?? "");
|
|
710
1004
|
};
|
|
711
|
-
|
|
712
|
-
|
|
1005
|
+
const route = { pluginId, method: "*", path: normalizedPath, handler: nativeHandler };
|
|
1006
|
+
this.httpRoutes.set(key, route);
|
|
1007
|
+
return () => { if (this.httpRoutes.get(key) === route)
|
|
713
1008
|
this.httpRoutes.delete(key); };
|
|
714
1009
|
}
|
|
715
1010
|
unregisterHttpRoutes(pluginId) { for (const [key, route] of this.httpRoutes)
|
|
@@ -812,6 +1107,8 @@ class PluginManager {
|
|
|
812
1107
|
try {
|
|
813
1108
|
const packageJson = JSON.parse(await (0, promises_1.readFile)((0, node_path_1.join)(directory, "package.json"), "utf8"));
|
|
814
1109
|
packageName = packageJson.name;
|
|
1110
|
+
if (packageName && (packageName.startsWith("agent-bridge-") || packageJson.agentBridge?.id !== undefined) && (0, plugin_catalog_1.resolvePluginId)(packageName, packageJson.agentBridge?.id) !== manifest.id)
|
|
1111
|
+
throw new agent_1.AgentError("npm 包声明的插件 ID 与清单不一致");
|
|
815
1112
|
coreConstraint = packageJson.agentBridge?.requires?.["mu-agent-bridge"];
|
|
816
1113
|
const declared = packageJson.agentBridge?.requires?.plugins;
|
|
817
1114
|
if (declared && JSON.stringify(declared) !== JSON.stringify(manifest.dependencies ?? {}))
|
|
@@ -824,6 +1121,10 @@ class PluginManager {
|
|
|
824
1121
|
const coreVersion = require((0, node_path_1.resolve)(__dirname, "../../package.json")).version;
|
|
825
1122
|
const error = coreConstraint && !(0, version_manager_1.satisfiesVersion)(coreVersion, coreConstraint) ? `核心版本 ${coreVersion} 不满足插件要求 ${coreConstraint}` : undefined;
|
|
826
1123
|
const record = { id: manifest.id, version: manifest.version, type: manifest.type ?? "other", displayName: manifest.displayName ?? manifest.id, status: error ? "failed" : "discovered", capabilities: manifest.capabilities, manifest, path: directory, ...(packageName ? { packageName } : {}), ...(coreConstraint ? { coreConstraint } : {}), ...(error ? { error } : {}) };
|
|
1124
|
+
if (existing && existing.record.packageName && packageName && existing.record.packageName !== packageName) {
|
|
1125
|
+
this.log("warn", "插件 npm 来源冲突,保留现有来源", { id: manifest.id, existingPackageName: existing.record.packageName, ignoredPackageName: packageName }, "插件发现");
|
|
1126
|
+
return;
|
|
1127
|
+
}
|
|
827
1128
|
if (existing) {
|
|
828
1129
|
const versionOrder = (0, version_manager_1.compareVersions)(record.version, existing.record.version);
|
|
829
1130
|
const candidateManaged = (0, node_path_1.resolve)(directory).startsWith(`${(0, node_path_1.resolve)(this.options.stateDir, "npm")}${node_path_1.sep}`);
|