mu-agent-bridge 1.0.71 → 1.0.73
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 +9 -6
- package/dist/src-ts/agent.js +35 -0
- package/dist/src-ts/channel-sessions.js +3 -2
- package/dist/src-ts/control-server.js +5 -2
- package/dist/src-ts/dsh-agent.js +25 -12
- package/dist/src-ts/plugin-catalog.js +11 -2
- package/dist/src-ts/plugin-manager.js +183 -9
- package/dist/src-ts/plugins/abweb/index.js +1 -1
- package/dist/src-ts/plugins/abweb/public/app.js +68 -2
- 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/index.js +66 -10
- package/dist/src-ts/plugins/aun/messages.js +3 -0
- package/dist/src-ts/plugins/chatgpt/cdp-client.js +26 -12
- package/dist/src-ts/plugins/chatgpt/index.js +77 -17
- 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/doubao/cdp-client.js +5 -2
- package/dist/src-ts/plugins/doubao/index.js +36 -4
- package/dist/src-ts/plugins/dsh/index.js +78 -6
- 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 +242 -0
- package/dist/src-ts/plugins/personal-assistant/model-client.js +167 -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/tools.js +166 -0
- package/dist/src-ts/plugins/qwenwork/cdp-client.js +47 -11
- package/dist/src-ts/plugins/qwenwork/index.js +89 -8
- package/dist/src-ts/plugins/workbuddy/cdp-client.js +9 -4
- package/dist/src-ts/plugins/workbuddy/index.js +104 -33
- package/dist/src-ts/session-management.js +105 -6
- package/dist/tests-ts/abweb-cli.test.js +10 -2
- package/dist/tests-ts/abweb-model-config.test.js +70 -0
- package/dist/tests-ts/agent.test.js +27 -0
- package/dist/tests-ts/aun-messages.test.js +6 -0
- package/dist/tests-ts/aun-plugin.test.js +85 -0
- package/dist/tests-ts/bundled-package.test.js +12 -2
- package/dist/tests-ts/channel-sessions.test.js +27 -0
- package/dist/tests-ts/chatgpt-plugin.test.js +99 -0
- package/dist/tests-ts/control-server.test.js +36 -0
- package/dist/tests-ts/doubao-plugin.test.js +92 -0
- package/dist/tests-ts/dsh-agent.test.js +77 -0
- package/dist/tests-ts/feishu-plugin.test.js +1 -1
- package/dist/tests-ts/installer-assets.test.js +1 -1
- package/dist/tests-ts/local-cli.test.js +119 -1
- package/dist/tests-ts/model-provider-cancellation.test.js +150 -0
- package/dist/tests-ts/personal-assistant-coordination.test.js +76 -0
- package/dist/tests-ts/personal-assistant-extensions.test.js +29 -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 +389 -0
- package/dist/tests-ts/platform-integration.test.js +124 -1
- package/dist/tests-ts/plugin-catalog.test.js +12 -2
- package/dist/tests-ts/plugin-manager.test.js +280 -0
- package/dist/tests-ts/publish-packages.test.js +42 -0
- package/dist/tests-ts/qwenwork-plugin.test.js +105 -1
- package/dist/tests-ts/session-management.test.js +155 -0
- package/dist/tests-ts/workbuddy-plugin.test.js +154 -2
- 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 +68 -2
- package/plugins/abweb/dist/src-ts/plugins/abweb/public/app.ts +53 -3
- 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/dist/src-ts/agent.js +35 -0
- package/plugins/aun/dist/src-ts/plugins/aun/index.js +66 -10
- package/plugins/aun/dist/src-ts/plugins/aun/messages.js +3 -0
- package/plugins/chatgpt/dist/src-ts/agent.js +35 -0
- package/plugins/chatgpt/dist/src-ts/plugins/chatgpt/cdp-client.js +26 -12
- package/plugins/chatgpt/dist/src-ts/plugins/chatgpt/index.js +77 -17
- 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/dist/src-ts/agent.js +35 -0
- package/plugins/doubao/dist/src-ts/plugins/doubao/cdp-client.js +5 -2
- package/plugins/doubao/dist/src-ts/plugins/doubao/index.js +36 -4
- package/plugins/doubao/package.json +1 -1
- package/plugins/doubao/plugin.json +3 -2
- package/plugins/dsh/dist/src-ts/agent.js +35 -0
- package/plugins/dsh/dist/src-ts/dsh-agent.js +25 -12
- package/plugins/dsh/dist/src-ts/plugins/dsh/index.js +78 -6
- 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/personal-assistant/config.schema.json +22 -0
- package/plugins/personal-assistant/dist/src-ts/agent.js +69 -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 +242 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/model-client.js +167 -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/tools.js +166 -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/qwenwork/cdp-client.js +47 -11
- package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/index.js +89 -8
- package/plugins/qwenwork/package.json +1 -1
- package/plugins/qwenwork/plugin.json +3 -2
- package/plugins/workbuddy/dist/src-ts/agent.js +35 -0
- package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/cdp-client.js +9 -4
- package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/index.js +104 -33
- package/plugins/workbuddy/package.json +1 -1
- package/plugins/workbuddy/plugin.json +3 -2
package/README.md
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
# mu-agent-bridge 动态插件平台
|
|
2
2
|
|
|
3
3
|
运行时使用 TypeScript/Node.js,实现一个可动态发现、启动、停止、重载和安装插件的 daemon。
|
|
4
|
-
当前插件包括千问办公 `qwenwork`、DeepSeek Harness `dsh`、豆包 `doubao`、基于 CodeBuddy CLI 的 `workbuddy`、远程 ChatGPT/Codex CDP 插件 `chatgpt`,以及插件化管理后台
|
|
5
|
-
`abweb`。插件之间独立失败,管理 API 和 OpenAI 兼容接口由同一个本地控制服务提供。
|
|
4
|
+
当前插件包括千问办公 `qwenwork`、DeepSeek Harness `dsh`、豆包 `doubao`、基于 CodeBuddy CLI 的 `workbuddy`、远程 ChatGPT/Codex CDP 插件 `chatgpt`、个人助理管理 Agent `personal-assistant`,以及插件化管理后台 `abweb`。插件之间独立失败,管理 API 和 OpenAI 兼容接口由同一个本地控制服务提供。
|
|
6
5
|
|
|
7
6
|
## 快速开始
|
|
8
7
|
|
|
@@ -48,14 +47,14 @@ abweb 的“配置码”栏;配对码 10 分钟有效,只能使用一次,
|
|
|
48
47
|
|
|
49
48
|
## npm 插件
|
|
50
49
|
|
|
51
|
-
默认包 `mu-agent-bridge` 已内置 abweb、AUN、千问办公、DSH、豆包、飞书、WorkBuddy
|
|
50
|
+
默认包 `mu-agent-bridge` 已内置 abweb、AUN、千问办公、DSH、豆包、飞书、WorkBuddy、ChatGPT/Codex、Codex CLI、Claude CLI 和个人助理共十一个插件,直接安装即可使用:
|
|
52
51
|
|
|
53
52
|
```powershell
|
|
54
53
|
npm install -g mu-agent-bridge@latest
|
|
55
54
|
mab start
|
|
56
55
|
```
|
|
57
56
|
|
|
58
|
-
|
|
57
|
+
十一个插件会从核心包的 `plugins/` 目录自动发现。插件仍分别发布为独立 npm 包,可用于单独测试、升级或替换版本。安装与发布说明见
|
|
59
58
|
[`docs/npm-publishing.md`](docs/npm-publishing.md)。
|
|
60
59
|
|
|
61
60
|
`mab start` 启动时会查询核心包和托管插件的 npm 最新版本,检查状态保存于 mab 状态目录的
|
|
@@ -77,12 +76,16 @@ mab start
|
|
|
77
76
|
abweb 插件目录从 npm 搜索带 `mu-agent-bridge-plugin` 关键词的包,按包元数据中的
|
|
78
77
|
`agentBridge.type` 展示 Agent、Channel 和管理插件;安装后以 `plugin.json` 的类型和能力
|
|
79
78
|
再次校验,不根据包名推断类型。搜索结果有收录延迟,可在页面直接输入 `agent-bridge-*`
|
|
80
|
-
包名安装。已安装插件按清单能力兼容旧版分类。AUN 通道插件包名为 `agent-bridge-aun`,安装后在 AUN 身份页手动关联每个 AID;未关联的 AID 不会自动投递,关联操作只修改指定 AID
|
|
79
|
+
包名安装。已安装插件按清单能力兼容旧版分类。AUN 通道插件包名为 `agent-bridge-aun`,安装后在 AUN 身份页手动关联每个 AID;未关联的 AID 不会自动投递,关联操作只修改指定 AID。
|
|
80
|
+
|
|
81
|
+
个人助理的独立 npm 包名为 `agent-bridge-pa`,运行时 ID、源码和数据目录仍用 `personal-assistant`。在 ABWeb 的“已安装插件 → 个人助理 → 配置模型”选择已安装来源:五个桌面 Agent、Codex CLI、Claude CLI,或 OpenAI 兼容接口;列表由后台动态发现。来源未就绪时显示原因并保留原配置。专用 AUN AID 仍关联到 `personal-assistant`,Peer 和可调用 Agent 仍受原白名单限制。详见[个人助理使用说明](packages/personal-assistant/README.md)。
|
|
82
|
+
|
|
83
|
+
`workbuddy` 插件包名为 `agent-bridge-workbuddy`,显式关联后按对端保持会话。默认问答使用独立的 `@tencent-ai/codebuddy-code` CLI,须单独登录;没有 CLI 时使用桌面端 CDP。CLI 未登录时不会自动切换到桌面账号。个人助理选择 WorkBuddy 桌面模型来源时请设置 `transport: "cdp"`。
|
|
81
84
|
|
|
82
85
|
插件仍在 daemon 的同一个 Node.js 进程内运行。修改插件入口依赖后应重启 daemon,避免模块缓存
|
|
83
86
|
影响新代码。
|
|
84
87
|
|
|
85
|
-
npm
|
|
88
|
+
npm 尚未发布时,核心包内置的十一个默认插件仍会出现在插件列表。abweb 的“安装本地插件”
|
|
86
89
|
可填写 **Windows 主机上**包含 `plugin.json` 的目录路径,例如编译后的
|
|
87
90
|
`C:\path\to\agent_bridge\packages\dsh`;安装后可在下拉框中启动。已内置的千问办公无需再次安装。
|
|
88
91
|
“安装 npm 插件”只适用于包已经公开发布的情况。npm 发布令牌仅供发布脚本向 npm 官网认证,
|
package/dist/src-ts/agent.js
CHANGED
|
@@ -1,7 +1,42 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.AgentOperationError = exports.AgentError = void 0;
|
|
4
|
+
exports.abortable = abortable;
|
|
4
5
|
exports.errorDetails = errorDetails;
|
|
6
|
+
async function abortable(promise, signal) {
|
|
7
|
+
if (!signal)
|
|
8
|
+
return promise;
|
|
9
|
+
if (signal.aborted) {
|
|
10
|
+
// promise 已在调用方启动,先观察其拒绝,避免预取消时产生未处理拒绝。
|
|
11
|
+
void promise.catch(() => undefined);
|
|
12
|
+
signal.throwIfAborted();
|
|
13
|
+
}
|
|
14
|
+
return new Promise((resolve, reject) => {
|
|
15
|
+
let settled = false;
|
|
16
|
+
const cleanup = () => signal.removeEventListener("abort", abort);
|
|
17
|
+
const abort = () => {
|
|
18
|
+
if (settled)
|
|
19
|
+
return;
|
|
20
|
+
settled = true;
|
|
21
|
+
cleanup();
|
|
22
|
+
reject(signal.reason);
|
|
23
|
+
};
|
|
24
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
25
|
+
promise.then((value) => {
|
|
26
|
+
if (settled)
|
|
27
|
+
return;
|
|
28
|
+
settled = true;
|
|
29
|
+
cleanup();
|
|
30
|
+
resolve(value);
|
|
31
|
+
}, (error) => {
|
|
32
|
+
if (settled)
|
|
33
|
+
return;
|
|
34
|
+
settled = true;
|
|
35
|
+
cleanup();
|
|
36
|
+
reject(error);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
}
|
|
5
40
|
class AgentError extends Error {
|
|
6
41
|
constructor(message, options) {
|
|
7
42
|
super(message, options);
|
|
@@ -136,12 +136,13 @@ class ChannelSessions {
|
|
|
136
136
|
const active = this.sessions[this.active[source]];
|
|
137
137
|
const entry = !command && active?.agentPlugin === agentPlugin ? active : undefined;
|
|
138
138
|
const sessionId = entry?.sessionId ?? (0, node_crypto_1.randomUUID)();
|
|
139
|
+
const logicalSessionId = entry?.logicalSessionId ?? (0, node_crypto_1.randomUUID)();
|
|
139
140
|
const response = command
|
|
140
141
|
? await control({ operation: "new", sessionId, ...(command[1]?.trim() ? { name: command[1].trim() } : {}) })
|
|
141
|
-
: await call(sessionId, !entry || entry.newSession === true);
|
|
142
|
+
: await call(sessionId, !entry || entry.newSession === true, logicalSessionId);
|
|
142
143
|
if (command && (typeof response.sessionId !== "string" || !response.sessionId.trim()))
|
|
143
144
|
throw new Error("插件未返回有效的新会话标识,原会话绑定保持不变");
|
|
144
|
-
const next = { source, logicalSessionId
|
|
145
|
+
const next = { source, logicalSessionId, agentPlugin, sessionId: response.sessionId || sessionId, updatedAt: new Date().toISOString(), ...(command ? { newSession: true } : {}) };
|
|
145
146
|
await this.save(next);
|
|
146
147
|
return command ? response : { ...response, source, logicalSessionId: next.logicalSessionId, agentPlugin, request: message, response };
|
|
147
148
|
});
|
|
@@ -183,7 +183,9 @@ class ControlServer {
|
|
|
183
183
|
if (method === "GET" && url.pathname === "/api/health")
|
|
184
184
|
return this.json(res, 200, { status: "ok", plugins: this.options.manager.list().length });
|
|
185
185
|
if (method === "GET" && url.pathname === "/api/versions")
|
|
186
|
-
return this.json(res, 200, { core: this.options.manager.versions.get("mu-agent-bridge") ?? null, plugins: this.options.manager.list().map((plugin) => ({ ...plugin, versionStatus: this.options.manager.versions.get(`agent-bridge-${plugin.id}`) ?? null })) });
|
|
186
|
+
return this.json(res, 200, { core: this.options.manager.versions.get("mu-agent-bridge") ?? null, plugins: this.options.manager.list().map((plugin) => ({ ...plugin, versionStatus: this.options.manager.versions.get(plugin.packageName ?? `agent-bridge-${plugin.id}`) ?? null })) });
|
|
187
|
+
if (method === "GET" && url.pathname === "/api/model-providers")
|
|
188
|
+
return this.json(res, 200, { providers: this.options.manager.listModelProviders() });
|
|
187
189
|
if (method === "GET" && url.pathname === "/api/update-policy")
|
|
188
190
|
return this.json(res, 200, await this.options.manager.updatePolicy());
|
|
189
191
|
if (method === "PATCH" && url.pathname === "/api/update-policy")
|
|
@@ -283,7 +285,8 @@ class ControlServer {
|
|
|
283
285
|
}
|
|
284
286
|
if (method === "POST" && parts[3] === "download") {
|
|
285
287
|
const body = await this.body(req);
|
|
286
|
-
|
|
288
|
+
const packageName = body.packageName ?? this.options.manager.packageInfo(id).packageName ?? `agent-bridge-${id}`;
|
|
289
|
+
return this.json(res, 200, { ok: true, path: await this.options.manager.downloadPackage(packageName, body.version) });
|
|
287
290
|
}
|
|
288
291
|
if (method === "DELETE" && parts.length === 3) {
|
|
289
292
|
await this.options.manager.uninstall(id);
|
package/dist/src-ts/dsh-agent.js
CHANGED
|
@@ -72,9 +72,10 @@ class DshAgent {
|
|
|
72
72
|
this.spawnProcess = options.spawn ?? node_child_process_1.spawn;
|
|
73
73
|
this.defaultTimeoutMs = options.defaultTimeoutMs ?? 300_000;
|
|
74
74
|
}
|
|
75
|
-
async send(request) {
|
|
75
|
+
async send(request, modelRequest = false) {
|
|
76
76
|
if (!request.task.trim())
|
|
77
77
|
throw new agent_1.AgentError("DSH task must not be empty");
|
|
78
|
+
request.signal?.throwIfAborted();
|
|
78
79
|
(0, attachments_1.validateAttachments)(request.attachments);
|
|
79
80
|
const paths = this.paths ?? await (0, dsh_paths_1.resolveDshPaths)(this.options.patchPath);
|
|
80
81
|
const workDirectory = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "agent-bridge-dsh-"));
|
|
@@ -87,7 +88,7 @@ class DshAgent {
|
|
|
87
88
|
const inputPaths = (request.attachments ?? []).map((item, index) => ({ path: (0, node_path_1.join)(workDirectory, `${index}-${(0, attachments_1.attachmentFilename)(item.filename)}`), returnPath: (0, node_path_1.join)(outputDirectory, `${index}-${(0, attachments_1.attachmentFilename)(item.filename)}`), filename: item.filename, contentType: item.contentType }));
|
|
88
89
|
for (const [index, item] of (request.attachments ?? []).entries())
|
|
89
90
|
await (0, promises_1.writeFile)(inputPaths[index].returnPath, item.data, { mode: 0o600 });
|
|
90
|
-
const task = `${request.task}\n\n请使用真实文件工具读取输入附件;生成的回传文件必须写入 outputDirectory 的直接子文件,并调用 present 工具声明文件。原附件已准备 returnPath 副本,原样回传时直接 present 此路径,无需 shell 复制。图片或视频无法理解时请明确说明,仍可原样回传。\n附件传输:\n${JSON.stringify({ inputs: inputPaths, outputDirectory })}`;
|
|
91
|
+
const task = modelRequest ? request.task : `${request.task}\n\n请使用真实文件工具读取输入附件;生成的回传文件必须写入 outputDirectory 的直接子文件,并调用 present 工具声明文件。原附件已准备 returnPath 副本,原样回传时直接 present 此路径,无需 shell 复制。图片或视频无法理解时请明确说明,仍可原样回传。\n附件传输:\n${JSON.stringify({ inputs: inputPaths, outputDirectory })}`;
|
|
91
92
|
const args = ["--expose-internals", paths.cli, "--profile", "headless", "--patch", paths.patch, "--json"];
|
|
92
93
|
if (request.sessionId && !request.newSession)
|
|
93
94
|
args.push("--session-id", request.sessionId);
|
|
@@ -129,18 +130,30 @@ class DshAgent {
|
|
|
129
130
|
});
|
|
130
131
|
child.stderr.on("data", (chunk) => { errors += chunk; });
|
|
131
132
|
await new Promise((resolve, reject) => {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
reject(new agent_1.AgentError(`DSH task timed out after ${request.timeoutMs ?? this.defaultTimeoutMs}ms`));
|
|
135
|
-
}, request.timeoutMs ?? this.defaultTimeoutMs);
|
|
136
|
-
child.once("error", (error) => { clearTimeout(timer); reject(new agent_1.AgentError("failed to start DSH", { cause: error })); });
|
|
137
|
-
child.once("close", (code) => {
|
|
133
|
+
let timedOut = false;
|
|
134
|
+
const finish = (action) => {
|
|
138
135
|
clearTimeout(timer);
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
136
|
+
request.signal?.removeEventListener("abort", abort);
|
|
137
|
+
action();
|
|
138
|
+
};
|
|
139
|
+
const abort = () => { child.kill(); };
|
|
140
|
+
const timer = setTimeout(() => { timedOut = true; child.kill(); }, request.timeoutMs ?? this.defaultTimeoutMs);
|
|
141
|
+
child.once("error", (error) => finish(() => reject(new agent_1.AgentError("failed to start DSH", { cause: error }))));
|
|
142
|
+
child.once("close", (code) => {
|
|
143
|
+
finish(() => {
|
|
144
|
+
if (request.signal?.aborted)
|
|
145
|
+
reject(request.signal.reason instanceof Error ? request.signal.reason : new agent_1.AgentError("DSH task canceled"));
|
|
146
|
+
else if (timedOut)
|
|
147
|
+
reject(new agent_1.AgentError(`DSH task timed out after ${request.timeoutMs ?? this.defaultTimeoutMs}ms`));
|
|
148
|
+
else if (code !== 0 && !finalText)
|
|
149
|
+
reject(new agent_1.AgentError(errors.trim() || `DSH exited with code ${code}`));
|
|
150
|
+
else
|
|
151
|
+
resolve();
|
|
152
|
+
});
|
|
143
153
|
});
|
|
154
|
+
request.signal?.addEventListener("abort", abort, { once: true });
|
|
155
|
+
if (request.signal?.aborted)
|
|
156
|
+
abort();
|
|
144
157
|
child.stdin.end(task, "utf8");
|
|
145
158
|
});
|
|
146
159
|
if (request.newSession && !sessionId)
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolvePluginId = resolvePluginId;
|
|
3
4
|
exports.searchPluginCatalog = searchPluginCatalog;
|
|
4
5
|
const registry = "https://registry.npmjs.org";
|
|
5
6
|
const pluginName = /^agent-bridge-[a-z0-9-]+$/;
|
|
7
|
+
const pluginId = /^[a-z][a-z0-9-]{1,63}$/;
|
|
8
|
+
function resolvePluginId(packageName, declaredId) {
|
|
9
|
+
if (!pluginName.test(packageName))
|
|
10
|
+
return;
|
|
11
|
+
const id = declaredId === undefined ? packageName.slice("agent-bridge-".length) : declaredId;
|
|
12
|
+
return typeof id === "string" && pluginId.test(id) ? id : undefined;
|
|
13
|
+
}
|
|
6
14
|
async function getJson(url) {
|
|
7
15
|
const response = await fetch(url, { signal: AbortSignal.timeout(10_000) });
|
|
8
16
|
if (!response.ok)
|
|
@@ -24,9 +32,10 @@ async function searchPluginCatalog(query = "", get = getJson) {
|
|
|
24
32
|
const version = metadata?.["dist-tags"]?.latest;
|
|
25
33
|
const pkg = metadata?.versions?.[version];
|
|
26
34
|
const type = pkg?.agentBridge?.type;
|
|
27
|
-
|
|
35
|
+
const id = resolvePluginId(name, pkg?.agentBridge?.id);
|
|
36
|
+
if (!id || !pkg?.agentBridge?.plugin || !["agent", "channel", "management"].includes(type))
|
|
28
37
|
return;
|
|
29
|
-
return { name, version, description: typeof entry.package.description === "string" ? entry.package.description : "", type };
|
|
38
|
+
return { name, id, version, description: typeof entry.package.description === "string" ? entry.package.description : "", type };
|
|
30
39
|
}
|
|
31
40
|
catch {
|
|
32
41
|
return;
|
|
@@ -34,6 +34,7 @@ class PluginManager {
|
|
|
34
34
|
root;
|
|
35
35
|
messages = [];
|
|
36
36
|
activeIngress = new Map();
|
|
37
|
+
issuedManagement = new WeakMap();
|
|
37
38
|
trustConfig = defaultPluginTrustConfig();
|
|
38
39
|
trustLoaded = false;
|
|
39
40
|
publisherLookups = new Map();
|
|
@@ -110,7 +111,7 @@ class PluginManager {
|
|
|
110
111
|
if (meta.name !== packageName || !meta.version || (version && meta.version !== version) || !meta.agentBridge?.plugin)
|
|
111
112
|
throw new agent_1.AgentError("npm 包声明无效");
|
|
112
113
|
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 (
|
|
114
|
+
if ((0, plugin_catalog_1.resolvePluginId)(packageName, meta.agentBridge.id) !== manifest.id || manifest.version !== meta.version)
|
|
114
115
|
throw new agent_1.AgentError("npm 包与插件清单不匹配");
|
|
115
116
|
this.validateCandidate(manifest, meta.agentBridge);
|
|
116
117
|
this.log("info", `插件下载完成:${manifest.id}`, { packageName, version: meta.version }, "插件升级");
|
|
@@ -168,7 +169,8 @@ class PluginManager {
|
|
|
168
169
|
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
170
|
})]);
|
|
170
171
|
}
|
|
171
|
-
async upgradePackage(id, packageName
|
|
172
|
+
async upgradePackage(id, packageName, version) {
|
|
173
|
+
packageName ??= this.plugins.get(id)?.record.packageName ?? `agent-bridge-${id}`;
|
|
172
174
|
const pending = this.upgrades.get(id);
|
|
173
175
|
if (pending) {
|
|
174
176
|
if (pending.packageName === packageName && pending.version === version)
|
|
@@ -215,7 +217,7 @@ class PluginManager {
|
|
|
215
217
|
if (!meta.agentBridge?.plugin)
|
|
216
218
|
throw new agent_1.AgentError("npm 包未声明 agentBridge.plugin");
|
|
217
219
|
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)
|
|
220
|
+
if (manifest.id !== id || (0, plugin_catalog_1.resolvePluginId)(packageName, meta.agentBridge.id) !== id || manifest.version !== meta.version)
|
|
219
221
|
throw new agent_1.AgentError("npm 包插件 ID 或版本不匹配");
|
|
220
222
|
this.validateCandidate(manifest, meta.agentBridge);
|
|
221
223
|
await this.stop(id, "reload");
|
|
@@ -255,6 +257,39 @@ class PluginManager {
|
|
|
255
257
|
}
|
|
256
258
|
}
|
|
257
259
|
get(id) { return this.plugins.get(id); }
|
|
260
|
+
listModelProviders() {
|
|
261
|
+
return [...this.plugins.values()].filter(({ record }) => record.capabilities.includes("model-provider")).map(({ record, instance }) => {
|
|
262
|
+
const available = record.status === "ready" && typeof instance?.modelProvider?.complete === "function";
|
|
263
|
+
return { id: record.id, displayName: record.displayName, status: record.status, available, reason: available ? "" : record.error ?? (record.status === "ready" ? "插件未实现模型来源接口" : `插件当前状态:${record.status}`) };
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
async completeModelProvider(id, request) {
|
|
267
|
+
const loaded = this.plugins.get(id);
|
|
268
|
+
if (!loaded)
|
|
269
|
+
throw new agent_1.AgentError(`模型来源插件 ${id} 未安装`);
|
|
270
|
+
if (!loaded.record.capabilities.includes("model-provider"))
|
|
271
|
+
throw new agent_1.AgentError(`插件 ${id} 未声明模型来源能力`);
|
|
272
|
+
if (!loaded.instance || loaded.record.status !== "ready" || typeof loaded.instance.modelProvider?.complete !== "function")
|
|
273
|
+
throw new agent_1.AgentError(`模型来源插件“${loaded.record.displayName}”未就绪:${loaded.record.error ?? (loaded.record.status === "ready" ? "插件未实现模型来源接口" : loaded.record.status)}`);
|
|
274
|
+
if (!request || typeof request.prompt !== "string" || !request.prompt.trim() || Buffer.byteLength(request.prompt) > 1024 * 1024)
|
|
275
|
+
throw new agent_1.AgentError("模型来源请求内容为空或超过 1 MiB");
|
|
276
|
+
if (typeof request.sessionId !== "string" || !request.sessionId.trim() || request.sessionId.length > 512)
|
|
277
|
+
throw new agent_1.AgentError("模型来源会话标识无效");
|
|
278
|
+
if (!Number.isInteger(request.timeoutMs) || request.timeoutMs < 1000 || request.timeoutMs > 120_000)
|
|
279
|
+
throw new agent_1.AgentError("模型来源超时时间无效");
|
|
280
|
+
if (!(request.signal instanceof AbortSignal))
|
|
281
|
+
throw new agent_1.AgentError("模型来源请求缺少取消信号");
|
|
282
|
+
// 模型来源只接收最小调用契约,避免把未来扩展字段误传给桌面或 CLI 插件。
|
|
283
|
+
const result = await loaded.instance.modelProvider.complete({
|
|
284
|
+
prompt: request.prompt,
|
|
285
|
+
sessionId: request.sessionId,
|
|
286
|
+
timeoutMs: request.timeoutMs,
|
|
287
|
+
signal: request.signal,
|
|
288
|
+
});
|
|
289
|
+
if (!result || typeof result.text !== "string" || !result.text.trim() || Buffer.byteLength(result.text) > 1024 * 1024)
|
|
290
|
+
throw new agent_1.AgentError(`模型来源插件“${loaded.record.displayName}”返回空内容或超大响应`);
|
|
291
|
+
return { text: result.text };
|
|
292
|
+
}
|
|
258
293
|
recentMessages(id) { return this.messages.filter((item) => !id || item.pluginId === id).slice(-200).reverse(); }
|
|
259
294
|
recordMessage(pluginId, direction, text, peer, agentAid) {
|
|
260
295
|
if (direction === "error" && isNoisyAgentError(text))
|
|
@@ -267,8 +302,9 @@ class PluginManager {
|
|
|
267
302
|
if (!/^agent-bridge-[a-z0-9-]+(?:@[0-9][a-zA-Z0-9.+-]*)?$/.test(packageName))
|
|
268
303
|
throw new agent_1.AgentError("仅支持 agent-bridge-* npm 包名");
|
|
269
304
|
const name = packageName.replace(/@.*$/, "");
|
|
270
|
-
|
|
271
|
-
|
|
305
|
+
const version = packageName.slice(name.length + 1);
|
|
306
|
+
if (this.list().some((record) => record.packageName === name))
|
|
307
|
+
throw new agent_1.AgentError("插件已经存在,请使用升级操作");
|
|
272
308
|
const prefix = (0, node_path_1.join)(this.options.stateDir, "npm", name);
|
|
273
309
|
if (await this.existsDir(prefix))
|
|
274
310
|
throw new agent_1.AgentError("插件包已经安装");
|
|
@@ -283,12 +319,16 @@ class PluginManager {
|
|
|
283
319
|
const meta = JSON.parse(await (0, promises_1.readFile)((0, node_path_1.join)(directory, "package.json"), "utf8"));
|
|
284
320
|
if (meta.name !== name || !meta.version || !meta.agentBridge?.plugin)
|
|
285
321
|
throw new agent_1.AgentError("npm 包名称或插件声明无效");
|
|
322
|
+
if (version && meta.version !== version)
|
|
323
|
+
throw new agent_1.AgentError("npm 包版本与请求不匹配");
|
|
286
324
|
const manifestFile = (0, node_path_1.resolve)(directory, meta.agentBridge.plugin);
|
|
287
325
|
if (!manifestFile.startsWith(`${(0, node_path_1.resolve)(directory)}${node_path_1.sep}`))
|
|
288
326
|
throw new agent_1.AgentError("插件清单路径越界");
|
|
289
327
|
const manifest = (0, plugin_manifest_1.validateManifest)(JSON.parse(await (0, promises_1.readFile)(manifestFile, "utf8")), directory);
|
|
290
|
-
if (
|
|
328
|
+
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
329
|
throw new agent_1.AgentError("npm 包与插件清单不匹配");
|
|
330
|
+
if (this.plugins.has(manifest.id))
|
|
331
|
+
throw new agent_1.AgentError("插件已经存在;不同 npm 来源不能覆盖,请先显式处理现有插件");
|
|
292
332
|
this.validateCandidate(manifest, meta.agentBridge);
|
|
293
333
|
await this.discoverDirectory(directory, name, manifestFile);
|
|
294
334
|
await this.resolvePendingPublisherLookups();
|
|
@@ -375,11 +415,19 @@ class PluginManager {
|
|
|
375
415
|
config,
|
|
376
416
|
logger: pluginLogger,
|
|
377
417
|
http: this.createHttpRegistry(id),
|
|
418
|
+
modelInputGuard: true,
|
|
378
419
|
callPlugin: async (targetId, request) => {
|
|
379
420
|
if (targetId === id)
|
|
380
421
|
throw new agent_1.AgentError(`插件不能调用自身: ${id}`);
|
|
422
|
+
if (request.management)
|
|
423
|
+
throw new agent_1.AgentError("插件间调用不能传递管理能力");
|
|
381
424
|
return this.handle(targetId, request);
|
|
382
425
|
},
|
|
426
|
+
callModelProvider: async (targetId, request) => {
|
|
427
|
+
if (targetId === id)
|
|
428
|
+
throw new agent_1.AgentError(`插件不能调用自身: ${id}`);
|
|
429
|
+
return this.completeModelProvider(targetId, request);
|
|
430
|
+
},
|
|
383
431
|
pluginStatus: (targetId) => this.plugins.get(targetId)?.record.status,
|
|
384
432
|
ingress: {
|
|
385
433
|
publish: async (message) => {
|
|
@@ -407,8 +455,8 @@ class PluginManager {
|
|
|
407
455
|
if (trustedChannel)
|
|
408
456
|
this.activeIngress.set(source, (this.activeIngress.get(source) ?? 0) + 1);
|
|
409
457
|
try {
|
|
410
|
-
const
|
|
411
|
-
const control =
|
|
458
|
+
const agentInstance = this.plugins.get(target)?.instance;
|
|
459
|
+
const control = agentInstance?.sessionOperations?.includes("new")
|
|
412
460
|
? (request) => this.controlSession(target, request)
|
|
413
461
|
: undefined;
|
|
414
462
|
const task = !message.text.trim() && message.attachments?.length ? "请处理收到的附件" : message.text;
|
|
@@ -422,7 +470,23 @@ class PluginManager {
|
|
|
422
470
|
sent.push({ text });
|
|
423
471
|
this.recordMessage(target, "reply", text, message.peerId);
|
|
424
472
|
};
|
|
425
|
-
const response = await this.sessions.run(message, target, (sessionId, newSession
|
|
473
|
+
const response = await this.sessions.run(message, target, async (sessionId, newSession, logicalSessionId) => {
|
|
474
|
+
const targetRecord = this.plugins.get(target)?.record;
|
|
475
|
+
const config = targetRecord?.type === "agent" && targetRecord.capabilities.includes("management") ? await this.config(target) : undefined;
|
|
476
|
+
const ability = trustedChannel && id === "aun" && loaded.instance === instance && loaded.record.status === "ready" && Array.isArray(config?.allowedPeers) && (config.allowedPeers.includes("*") || config.allowedPeers.includes(message.peerId))
|
|
477
|
+
? this.createRequestManagement(target, message.peerId, source, logicalSessionId) : undefined;
|
|
478
|
+
const request = { task, sessionId, onMessage, ...(newSession ? { newSession: true } : {}), ...(message.attachments ? { attachments: message.attachments } : {}), ...(ability ? { management: ability } : {}) };
|
|
479
|
+
const issued = ability ? this.issuedManagement.get(ability) : undefined;
|
|
480
|
+
if (issued)
|
|
481
|
+
issued.active = true;
|
|
482
|
+
try {
|
|
483
|
+
return await this.handle(target, request);
|
|
484
|
+
}
|
|
485
|
+
finally {
|
|
486
|
+
if (issued)
|
|
487
|
+
issued.active = false;
|
|
488
|
+
}
|
|
489
|
+
}, control);
|
|
426
490
|
const attachments = response.attachments?.length ? response.attachments : (0, attachments_1.asAttachments)(response.images);
|
|
427
491
|
(0, attachments_1.validateAttachments)(attachments);
|
|
428
492
|
if (attachments.length) {
|
|
@@ -620,6 +684,103 @@ class PluginManager {
|
|
|
620
684
|
...await (0, agent_availability_1.agentAvailability)(plugin.id, await this.config(plugin.id), plugin.status),
|
|
621
685
|
})));
|
|
622
686
|
}
|
|
687
|
+
createRequestManagement(pluginId, peerId, source, logicalSessionId) {
|
|
688
|
+
const issued = { pluginId, instance: this.plugins.get(pluginId)?.instance, active: false };
|
|
689
|
+
const checkActive = () => {
|
|
690
|
+
const current = this.plugins.get(pluginId);
|
|
691
|
+
if (!issued.active || !current?.instance || current.instance !== issued.instance)
|
|
692
|
+
throw new agent_1.AgentError("管理能力请求已结束或已撤销");
|
|
693
|
+
};
|
|
694
|
+
const allowed = async (agentId) => {
|
|
695
|
+
checkActive();
|
|
696
|
+
const loaded = this.plugins.get(pluginId);
|
|
697
|
+
if (!loaded?.instance || loaded.record.status !== "ready" || !loaded.record.capabilities.includes("management"))
|
|
698
|
+
throw new agent_1.AgentError("当前插件未声明管理能力或未就绪");
|
|
699
|
+
const config = await this.config(pluginId);
|
|
700
|
+
checkActive();
|
|
701
|
+
const peers = Array.isArray(config.allowedPeers) ? config.allowedPeers : [];
|
|
702
|
+
if (!peers.includes("*") && !peers.includes(peerId))
|
|
703
|
+
throw new agent_1.AgentError("当前 AUN 身份无权使用管理能力");
|
|
704
|
+
if (agentId !== undefined) {
|
|
705
|
+
const agents = Array.isArray(config.allowedAgents) ? config.allowedAgents : [];
|
|
706
|
+
if (!agents.includes(agentId))
|
|
707
|
+
throw new agent_1.AgentError("目标 Agent 不在允许列表中");
|
|
708
|
+
}
|
|
709
|
+
return { loaded, config };
|
|
710
|
+
};
|
|
711
|
+
const management = {
|
|
712
|
+
listAgents: async () => {
|
|
713
|
+
const { config } = await allowed();
|
|
714
|
+
const aun = this.plugins.get("aun");
|
|
715
|
+
let bindings = {};
|
|
716
|
+
let identitiesAvailable = false;
|
|
717
|
+
try {
|
|
718
|
+
const health = aun?.record.status === "ready" ? await aun.instance?.health() : undefined;
|
|
719
|
+
const details = health?.details;
|
|
720
|
+
if (health?.status === "ready" && details && typeof details === "object" && !Array.isArray(details)) {
|
|
721
|
+
const raw = details.bindings;
|
|
722
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
723
|
+
bindings = raw;
|
|
724
|
+
identitiesAvailable = true;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
catch { /* AUN 状态不可用时只返回 Agent 能力 */ }
|
|
729
|
+
const result = [];
|
|
730
|
+
const agents = this.list().filter(item => item.type === "agent");
|
|
731
|
+
const agentIds = new Set(agents.map(item => item.id));
|
|
732
|
+
for (const record of agents) {
|
|
733
|
+
const agentConfig = await this.config(record.id);
|
|
734
|
+
const current = this.plugins.get(record.id);
|
|
735
|
+
const currentRecord = current?.record ?? record;
|
|
736
|
+
const availability = await (0, agent_availability_1.agentAvailability)(record.id, agentConfig, currentRecord.status);
|
|
737
|
+
const latest = this.plugins.get(record.id);
|
|
738
|
+
const latestRecord = latest?.record ?? currentRecord;
|
|
739
|
+
const aids = identitiesAvailable
|
|
740
|
+
? 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()
|
|
741
|
+
: [];
|
|
742
|
+
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.includes(record.id));
|
|
743
|
+
result.push({ id: latestRecord.id, displayName: latestRecord.displayName, status: latestRecord.status, ...availability, reason: !identitiesAvailable && !availability.reason ? "AUN 身份数据不可用" : availability.reason, capabilities: [...latestRecord.capabilities], aids, callable });
|
|
744
|
+
}
|
|
745
|
+
await allowed();
|
|
746
|
+
return result;
|
|
747
|
+
},
|
|
748
|
+
callAgent: async (id, task) => {
|
|
749
|
+
const { loaded } = await allowed(id);
|
|
750
|
+
if (typeof task !== "string" || !task.trim() || task.length > 20_000)
|
|
751
|
+
throw new agent_1.AgentError("Agent 调用内容无效");
|
|
752
|
+
const target = this.plugins.get(id);
|
|
753
|
+
if (!target?.instance || target.record.type !== "agent" || target.record.status !== "ready" || target.record.capabilities.includes("management") || id === pluginId)
|
|
754
|
+
throw new agent_1.AgentError("目标 Agent 不可调用");
|
|
755
|
+
if (!(await (0, agent_availability_1.agentAvailability)(id, await this.config(id), target.record.status)).available)
|
|
756
|
+
throw new agent_1.AgentError("目标 Agent 当前不可用");
|
|
757
|
+
await allowed(id);
|
|
758
|
+
// shortcut: 仅 WorkBuddy 已验证未知逻辑会话 ID 可安全建会话,其他插件验证后再扩展。
|
|
759
|
+
const sessionId = id === "workbuddy" && target.instance?.sessionOperations?.includes("new")
|
|
760
|
+
? `management-${(0, node_crypto_1.createHash)("sha256").update(`${logicalSessionId}\0${id}`).digest("hex")}`
|
|
761
|
+
: undefined;
|
|
762
|
+
const response = await this.handle(id, { task, ...(sessionId ? { sessionId } : {}) });
|
|
763
|
+
if (loaded.instance !== this.plugins.get(pluginId)?.instance)
|
|
764
|
+
throw new agent_1.AgentError("管理插件已重载");
|
|
765
|
+
await allowed(id);
|
|
766
|
+
return response;
|
|
767
|
+
},
|
|
768
|
+
listAunIdentities: async () => { await allowed(); const aun = this.plugins.get("aun")?.instance; if (!aun?.aunManagement)
|
|
769
|
+
throw new agent_1.AgentError("AUN 身份管理不可用"); const result = await aun.aunManagement.list(); await allowed(); return result; },
|
|
770
|
+
createAunIdentity: async (input) => { await allowed(); const aun = this.plugins.get("aun")?.instance; if (!aun?.aunManagement)
|
|
771
|
+
throw new agent_1.AgentError("AUN 身份管理不可用"); const result = await aun.aunManagement.create(input); await allowed(); return result; },
|
|
772
|
+
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")
|
|
773
|
+
throw new agent_1.AgentError("只能关联已就绪的 Agent 插件"); const aun = this.plugins.get("aun")?.instance; if (!aun?.aunManagement)
|
|
774
|
+
throw new agent_1.AgentError("AUN 身份管理不可用"); const result = await aun.aunManagement.bind(input); await allowed(input.agentId); return result; },
|
|
775
|
+
runSessionCommand: async (command) => { await allowed(); if (typeof command !== "string" || command.length > 2_000 || !/^\/sessions(?:\s|$)/.test(command.trimStart()))
|
|
776
|
+
throw new agent_1.AgentError("仅支持 /sessions 只读命令"); const result = await this.sessionManagement.command(command, { source, sessionId: logicalSessionId }); await allowed(); if (result === undefined)
|
|
777
|
+
throw new agent_1.AgentError("不支持的 /sessions 命令"); return result; },
|
|
778
|
+
summarizeToday: async () => { await allowed(); const result = await this.sessionManagement.summarizeToday({ source, sessionId: logicalSessionId }); await allowed(); return result; },
|
|
779
|
+
};
|
|
780
|
+
Object.freeze(management);
|
|
781
|
+
this.issuedManagement.set(management, issued);
|
|
782
|
+
return management;
|
|
783
|
+
}
|
|
623
784
|
async health(id) { const loaded = this.plugins.get(id); if (!loaded)
|
|
624
785
|
throw new agent_1.AgentError(`unknown plugin: ${id}`); if (!loaded.instance)
|
|
625
786
|
return { status: loaded.record.status }; return loaded.instance.health(); }
|
|
@@ -632,6 +793,13 @@ class PluginManager {
|
|
|
632
793
|
const reason = loaded?.record.error ? `启动错误:${loaded.record.error}` : `当前状态:${loaded?.record.status ?? "未发现"}`;
|
|
633
794
|
throw new agent_1.AgentError(`插件“${name}”无法调用,插件未就绪:${processText}${reason}`);
|
|
634
795
|
}
|
|
796
|
+
if (request.modelResponseSchema !== undefined && !loaded.instance.modelInput)
|
|
797
|
+
throw new agent_1.AgentError("目标插件不支持无工具模型请求");
|
|
798
|
+
if (request.management) {
|
|
799
|
+
const issued = this.issuedManagement.get(request.management);
|
|
800
|
+
if (!issued || !issued.active || issued.pluginId !== id || issued.instance !== loaded.instance)
|
|
801
|
+
throw new agent_1.AgentError("管理能力无效或已撤销");
|
|
802
|
+
}
|
|
635
803
|
(0, attachments_1.validateAttachments)(request.attachments);
|
|
636
804
|
if (request.attachments?.length && !loaded.instance.attachmentInput)
|
|
637
805
|
throw new agent_1.AgentError(`插件 ${id} 不支持接收附件`);
|
|
@@ -812,6 +980,8 @@ class PluginManager {
|
|
|
812
980
|
try {
|
|
813
981
|
const packageJson = JSON.parse(await (0, promises_1.readFile)((0, node_path_1.join)(directory, "package.json"), "utf8"));
|
|
814
982
|
packageName = packageJson.name;
|
|
983
|
+
if (packageName && (packageName.startsWith("agent-bridge-") || packageJson.agentBridge?.id !== undefined) && (0, plugin_catalog_1.resolvePluginId)(packageName, packageJson.agentBridge?.id) !== manifest.id)
|
|
984
|
+
throw new agent_1.AgentError("npm 包声明的插件 ID 与清单不一致");
|
|
815
985
|
coreConstraint = packageJson.agentBridge?.requires?.["mu-agent-bridge"];
|
|
816
986
|
const declared = packageJson.agentBridge?.requires?.plugins;
|
|
817
987
|
if (declared && JSON.stringify(declared) !== JSON.stringify(manifest.dependencies ?? {}))
|
|
@@ -824,6 +994,10 @@ class PluginManager {
|
|
|
824
994
|
const coreVersion = require((0, node_path_1.resolve)(__dirname, "../../package.json")).version;
|
|
825
995
|
const error = coreConstraint && !(0, version_manager_1.satisfiesVersion)(coreVersion, coreConstraint) ? `核心版本 ${coreVersion} 不满足插件要求 ${coreConstraint}` : undefined;
|
|
826
996
|
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 } : {}) };
|
|
997
|
+
if (existing && existing.record.packageName && packageName && existing.record.packageName !== packageName) {
|
|
998
|
+
this.log("warn", "插件 npm 来源冲突,保留现有来源", { id: manifest.id, existingPackageName: existing.record.packageName, ignoredPackageName: packageName }, "插件发现");
|
|
999
|
+
return;
|
|
1000
|
+
}
|
|
827
1001
|
if (existing) {
|
|
828
1002
|
const versionOrder = (0, version_manager_1.compareVersions)(record.version, existing.record.version);
|
|
829
1003
|
const candidateManaged = (0, node_path_1.resolve)(directory).startsWith(`${(0, node_path_1.resolve)(this.options.stateDir, "npm")}${node_path_1.sep}`);
|