mu-agent-bridge 1.0.29 → 1.0.31
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 +22 -4
- package/dist/installer/mu-agent-bridge.msi +0 -0
- package/dist/src-ts/agent-availability.js +14 -0
- package/dist/src-ts/channel-sessions.js +28 -5
- package/dist/src-ts/cli-commands.js +176 -0
- package/dist/src-ts/cli.js +29 -14
- package/dist/src-ts/control-server.js +14 -3
- package/dist/src-ts/daemon-process.js +3 -2
- package/dist/src-ts/dsh-agent.js +4 -2
- package/dist/src-ts/plugin-manager.js +53 -6
- package/dist/src-ts/plugins/aun/certs/root.crt +13 -0
- package/dist/src-ts/plugins/aun/evolcore.js +2 -1
- package/dist/src-ts/plugins/aun/index.js +411 -20
- package/dist/src-ts/plugins/aun/public/app.js +10 -3
- package/dist/src-ts/plugins/aun/public/index.html +1 -1
- package/dist/src-ts/plugins/chatgpt/cdp-client.js +31 -2
- package/dist/src-ts/plugins/chatgpt/index.js +37 -1
- package/dist/src-ts/plugins/chatgpt/session-store.js +6 -2
- package/dist/src-ts/plugins/desktop-launch.js +81 -0
- package/dist/src-ts/plugins/doubao/cdp-client.js +80 -11
- package/dist/src-ts/plugins/doubao/index.js +24 -5
- package/dist/src-ts/plugins/dsh/index.js +1 -0
- package/dist/src-ts/plugins/feishu/index.js +138 -25
- package/dist/src-ts/plugins/feishu/public/app.js +20 -8
- package/dist/src-ts/plugins/feishu/public/index.html +1 -1
- package/dist/src-ts/plugins/qwenwork/cdp-client.js +105 -31
- package/dist/src-ts/plugins/qwenwork/index.js +218 -8
- package/dist/src-ts/plugins/qwenwork/openai-adapter.js +12 -2
- package/dist/src-ts/plugins/workbuddy/cdp-client.js +10 -4
- package/dist/src-ts/plugins/workbuddy/index.js +139 -7
- package/dist/src-ts/secret-input.js +47 -0
- package/dist/tests-ts/agent-availability.test.js +27 -0
- package/dist/tests-ts/aun-default-owner-page.test.js +81 -0
- package/dist/tests-ts/aun-plugin.test.js +794 -19
- package/dist/tests-ts/channel-sessions.test.js +189 -0
- package/dist/tests-ts/chatgpt-plugin.test.js +104 -0
- package/dist/tests-ts/cli-commands.test.js +363 -0
- package/dist/tests-ts/control-server.test.js +54 -0
- package/dist/tests-ts/daemon-process.test.js +1 -0
- package/dist/tests-ts/desktop-launch.test.js +79 -0
- package/dist/tests-ts/doubao-plugin.test.js +54 -0
- package/dist/tests-ts/dsh-agent.test.js +25 -0
- package/dist/tests-ts/feishu-page.test.js +10 -3
- package/dist/tests-ts/feishu-plugin.test.js +248 -0
- package/dist/tests-ts/installer-assets.test.js +84 -0
- package/dist/tests-ts/platform-integration.test.js +1 -1
- package/dist/tests-ts/plugin-cli-integration.test.js +111 -0
- package/dist/tests-ts/plugin-manager.test.js +172 -2
- package/dist/tests-ts/qwenwork-plugin.test.js +423 -0
- package/dist/tests-ts/qwenwork-sse.test.js +46 -0
- package/dist/tests-ts/workbuddy-plugin.test.js +278 -3
- package/package.json +2 -1
- package/plugins/abweb/package.json +1 -1
- package/plugins/abweb/plugin.json +1 -1
- package/plugins/aun/config.schema.json +1 -0
- package/plugins/aun/dist/src-ts/plugins/aun/certs/root.crt +13 -0
- package/plugins/aun/dist/src-ts/plugins/aun/evolcore.js +2 -1
- package/plugins/aun/dist/src-ts/plugins/aun/index.js +411 -20
- package/plugins/aun/dist/src-ts/plugins/aun/public/app.js +10 -3
- 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 +3 -2
- package/plugins/chatgpt/dist/src-ts/plugins/chatgpt/cdp-client.js +31 -2
- package/plugins/chatgpt/dist/src-ts/plugins/chatgpt/index.js +37 -1
- package/plugins/chatgpt/dist/src-ts/plugins/chatgpt/session-store.js +6 -2
- package/plugins/chatgpt/package.json +1 -1
- package/plugins/chatgpt/plugin.json +1 -1
- package/plugins/doubao/dist/src-ts/plugins/desktop-launch.js +81 -0
- package/plugins/doubao/dist/src-ts/plugins/doubao/cdp-client.js +80 -11
- package/plugins/doubao/dist/src-ts/plugins/doubao/index.js +24 -5
- package/plugins/doubao/package.json +1 -1
- package/plugins/doubao/plugin.json +1 -1
- package/plugins/dsh/dist/src-ts/dsh-agent.js +4 -2
- package/plugins/dsh/dist/src-ts/plugins/dsh/index.js +1 -0
- package/plugins/dsh/dist/src-ts/plugins/qwenwork/openai-adapter.js +12 -2
- package/plugins/dsh/package.json +1 -1
- package/plugins/dsh/plugin.json +1 -1
- package/plugins/feishu/dist/src-ts/plugins/feishu/index.js +138 -25
- package/plugins/feishu/dist/src-ts/plugins/feishu/public/app.js +20 -8
- package/plugins/feishu/dist/src-ts/plugins/feishu/public/index.html +1 -1
- package/plugins/feishu/package.json +1 -1
- package/plugins/feishu/plugin.json +3 -2
- package/plugins/qwenwork/config.schema.json +3 -0
- package/plugins/qwenwork/dist/src-ts/plugins/desktop-launch.js +81 -0
- package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/cdp-client.js +105 -31
- package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/index.js +218 -8
- package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/openai-adapter.js +12 -2
- package/plugins/qwenwork/package.json +1 -1
- package/plugins/qwenwork/plugin.json +2 -2
- package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/cdp-client.js +10 -4
- package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/index.js +139 -7
- package/plugins/workbuddy/package.json +1 -1
- package/plugins/workbuddy/plugin.json +3 -3
package/README.md
CHANGED
|
@@ -22,10 +22,12 @@ OpenAI 兼容 API 为 `/v1/models` 和 `/v1/chat/completions`。管理页面位
|
|
|
22
22
|
`http://<Windows 主机 IP>:26107/abweb/`。页面使用一次性配置码配对后管理插件;请只在可信网络使用。
|
|
23
23
|
如果 26107 被旧的 mab 进程占用,可执行 `mab restart`;Windows 会扫描并结束同一 CLI 安装路径的全部旧 daemon,并重新启动。`mab stop` 只停止 daemon,
|
|
24
24
|
`mab update` 会结束旧 daemon、安装最新核心包并启动新版本。不会把自己的旧进程误当成新的 daemon,也不会因此默认改用临时端口。
|
|
25
|
-
已有 AID 的连接状态由 AUN
|
|
26
|
-
|
|
25
|
+
已有 AID 的连接状态由 AUN 插件查询,只有当前已连接的身份才能关联 Agent。
|
|
26
|
+
本机 AID 来自 AUN 插件自身的密钥库,不依赖 `ec` 命令,`ec stop` 后仍会显示。
|
|
27
|
+
未配置 `aun_path` 时密钥库默认使用共享 AUN 目录(`AUN_HOME`/`EVOLCORE_HOME` 或
|
|
28
|
+
`~/.evolcore`),已注册的 AID 无需迁移;插件启动时自动连接密钥库中的全部 AID。
|
|
27
29
|
创建新 AID 必须填写 Owner AID,并安装、启动 AUN 插件;创建与通信由该插件完成。
|
|
28
|
-
|
|
30
|
+
已连接身份保存在 AUN 插件自身的密钥库和配置中。关联 AID 到 Agent 时,由 AUN 插件直接保持连接并转发来信和回复,不再执行 `ec agent set/reload`。
|
|
29
31
|
其他 Agent 插件可关联 AUN 插件自己创建的身份,由通道直接转发来信和回复。
|
|
30
32
|
Windows 可运行 `start-mab.ps1` 注册并启动登录时自动运行的计划任务;令牌保存在
|
|
31
33
|
`%USERPROFILE%\.mab\token.txt`,页面输入该令牌即可使用管理 API。
|
|
@@ -139,4 +141,20 @@ Windows 路径时,可设置 `DSH_INSTALL_DIR`。
|
|
|
139
141
|
|
|
140
142
|
## AUN 身份管理
|
|
141
143
|
|
|
142
|
-
创建、连接、关联和 AUN 通信统一由独立 `agent-bridge-aun`
|
|
144
|
+
创建、连接、关联和 AUN 通信统一由独立 `agent-bridge-aun` 插件完成。安装并启动 AUN 后,在 ABWeb 的“通道 → AUN”创建或选择身份,再将已连接 AID 关联到已启动的 Agent;用 `mab status` 查看状态。Owner AID 仅作本地归属配置,不代表 AUN Owner 关系已激活。完整步骤见 [AUN 插件与 AID 身份管理](docs/aun-plugin-identities.md);给 Codex、Claude Code 直接执行的 CLI 说明见 [使用说明](使用说明.md)。
|
|
145
|
+
|
|
146
|
+
### 飞书凭据与办公 Agent 关联
|
|
147
|
+
|
|
148
|
+
在 ABWeb 的“飞书通道 → 手动配置凭据”中填写 App ID、App Secret,并选择“关联办公 Agent”。Secret 使用密码框显示;已保存的密钥保持掩码,不通过状态接口返回。WorkBuddy、千问办公、豆包根据运行后台的机器上是否存在可执行文件检测,文件缺失或插件未就绪的选项置灰。自定义安装位置请在对应插件配置中填写 `executablePath`。
|
|
149
|
+
|
|
150
|
+
也可以使用 CLI,Secret 在命令执行后隐藏输入,不写在命令参数中:
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
mab feishu configure cli_你的应用ID --agent workbuddy
|
|
154
|
+
mab feishu agents
|
|
155
|
+
mab feishu list
|
|
156
|
+
mab feishu bind cli_你的应用ID workbuddy
|
|
157
|
+
mab feishu unbind cli_你的应用ID
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
`configure` 保存凭据及可选关联后,需要在 ABWeb 重载飞书插件以连接机器人。非交互终端请使用网页密码框;不要把 Secret 写进命令行、脚本或仓库。
|
|
Binary file
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.agentAvailability = agentAvailability;
|
|
4
|
+
const desktop_launch_1 = require("./plugins/desktop-launch");
|
|
5
|
+
const workbuddy_1 = require("./plugins/workbuddy");
|
|
6
|
+
async function agentAvailability(id, config, status) {
|
|
7
|
+
const configured = typeof config.executablePath === "string" ? config.executablePath : undefined;
|
|
8
|
+
const executable = id === "workbuddy" ? (0, workbuddy_1.findExecutable)(configured)
|
|
9
|
+
: id === "qwenwork" ? await (0, desktop_launch_1.findWindowsExecutable)("QwenWorkCN.exe", configured)
|
|
10
|
+
: id === "doubao" ? await (0, desktop_launch_1.findWindowsExecutable)("Doubao.exe", configured) : true;
|
|
11
|
+
if (!executable)
|
|
12
|
+
return { available: false, reason: "未找到可执行文件" };
|
|
13
|
+
return { available: status === "ready", reason: status === "ready" ? "" : "插件未就绪" };
|
|
14
|
+
}
|
|
@@ -4,6 +4,7 @@ exports.ChannelSessions = void 0;
|
|
|
4
4
|
const node_crypto_1 = require("node:crypto");
|
|
5
5
|
const promises_1 = require("node:fs/promises");
|
|
6
6
|
const node_path_1 = require("node:path");
|
|
7
|
+
const unsupportedCommands = /^(?:\/(?:s|session|slist|name|rename|del|status|stop|pause|resume|compact|fork|rewind|rw|repair|renew))(?:[ \t]+[^\r\n]*)?$/;
|
|
7
8
|
class ChannelSessions {
|
|
8
9
|
directory;
|
|
9
10
|
sessions = {};
|
|
@@ -21,7 +22,7 @@ class ChannelSessions {
|
|
|
21
22
|
throw new Error("通道会话文件格式无效");
|
|
22
23
|
for (const [key, value] of Object.entries(data)) {
|
|
23
24
|
const entry = value;
|
|
24
|
-
if (!entry || typeof entry.sessionId !== "string" || typeof entry.source !== "string" || typeof entry.agentPlugin !== "string" || typeof entry.updatedAt !== "string")
|
|
25
|
+
if (!entry || typeof entry.sessionId !== "string" || typeof entry.source !== "string" || typeof entry.agentPlugin !== "string" || typeof entry.updatedAt !== "string" || (entry.newSession !== undefined && typeof entry.newSession !== "boolean"))
|
|
25
26
|
throw new Error("通道会话记录无效");
|
|
26
27
|
this.sessions[key] = entry;
|
|
27
28
|
}
|
|
@@ -34,15 +35,37 @@ class ChannelSessions {
|
|
|
34
35
|
await this.loaded;
|
|
35
36
|
}
|
|
36
37
|
async list() { await this.load(); return Object.values(this.sessions).sort((a, b) => b.updatedAt.localeCompare(a.updatedAt)); }
|
|
37
|
-
async run(message, agentPlugin, call) {
|
|
38
|
+
async run(message, agentPlugin, call, control) {
|
|
38
39
|
const source = JSON.stringify([message.channel, message.recipientId || "", message.chatId || "", message.peerId, message.threadId || ""]);
|
|
40
|
+
const command = message.text?.trim().match(/^\/new(?:[ \t]+([^\r\n]+))?$/);
|
|
41
|
+
const unsupported = message.text?.trim().match(unsupportedCommands);
|
|
42
|
+
if (unsupported)
|
|
43
|
+
return { text: `会话命令 ${unsupported[0].split(/[ \t]/, 1)[0]} 本期暂不支持` };
|
|
44
|
+
if (/^\/clear(?:[ \t]+[^\r\n]*)?$/.test(message.text?.trim() ?? ""))
|
|
45
|
+
return { text: "/clear 已移除,请使用 /new 新建会话" };
|
|
46
|
+
if (message.text?.trim() === "/help")
|
|
47
|
+
return { text: [
|
|
48
|
+
control ? "/new [名称]:新建并切换会话" : "/new [名称]:当前 Agent 插件不支持新建会话",
|
|
49
|
+
"话题中不支持 /new;/renew 及其他会话命令当前暂不支持",
|
|
50
|
+
"/clear 已移除,请使用 /new",
|
|
51
|
+
].join("\n") };
|
|
52
|
+
if (command && message.threadId)
|
|
53
|
+
return { text: "话题中不支持 /new,请使用 /renew 轮换话题会话(当前尚未支持)" };
|
|
54
|
+
if (command && !control)
|
|
55
|
+
return { text: "当前 Agent 插件不支持新建会话" };
|
|
56
|
+
if (command && this.pending.has(source))
|
|
57
|
+
throw new Error("当前会话正在执行任务,请等待完成后再新建会话");
|
|
39
58
|
const previous = this.pending.get(source) ?? Promise.resolve();
|
|
40
59
|
const turn = previous.catch(() => { }).then(async () => {
|
|
41
60
|
await this.load();
|
|
42
61
|
const entry = this.sessions[source];
|
|
43
|
-
const sessionId = entry?.agentPlugin === agentPlugin ? entry.sessionId : (0, node_crypto_1.randomUUID)();
|
|
44
|
-
const response =
|
|
45
|
-
|
|
62
|
+
const sessionId = !command && entry?.agentPlugin === agentPlugin ? entry.sessionId : (0, node_crypto_1.randomUUID)();
|
|
63
|
+
const response = command
|
|
64
|
+
? await control({ operation: "new", sessionId, ...(command[1]?.trim() ? { name: command[1].trim() } : {}) })
|
|
65
|
+
: await call(sessionId, !entry || entry.agentPlugin !== agentPlugin || entry.newSession === true);
|
|
66
|
+
if (command && (typeof response.sessionId !== "string" || !response.sessionId.trim()))
|
|
67
|
+
throw new Error("插件未返回有效的新会话标识,原会话绑定保持不变");
|
|
68
|
+
const next = { source, agentPlugin, sessionId: response.sessionId || sessionId, updatedAt: new Date().toISOString(), ...(command ? { newSession: true } : {}) };
|
|
46
69
|
const save = this.writes.catch(() => { }).then(async () => {
|
|
47
70
|
const snapshot = { ...this.sessions, [source]: next };
|
|
48
71
|
await (0, promises_1.mkdir)(this.directory, { recursive: true });
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CORE_HELP = void 0;
|
|
4
|
+
exports.resolveLocalControlHost = resolveLocalControlHost;
|
|
5
|
+
exports.controlUrl = controlUrl;
|
|
6
|
+
exports.runPluginCommand = runPluginCommand;
|
|
7
|
+
exports.runCoreHelp = runCoreHelp;
|
|
8
|
+
const secret_input_1 = require("./secret-input");
|
|
9
|
+
const node_http_1 = require("node:http");
|
|
10
|
+
const MAX_BODY_BYTES = 1024 * 1024;
|
|
11
|
+
const PLUGIN_ID = /^[a-z][a-z0-9-]{1,63}$/;
|
|
12
|
+
const RESERVED_COMMANDS = new Set(["start", "stop", "restart", "status", "pair", "update", "help"]);
|
|
13
|
+
exports.CORE_HELP = "用法:mab --help\n mab start | stop | restart | update | status | pair\n mab --self-test <任务> | --serve\n mab <插件 ID> help [命令]\n mab <插件 ID> <命令> [参数...] [--json]\n";
|
|
14
|
+
class CliError extends Error {
|
|
15
|
+
exitCode;
|
|
16
|
+
constructor(message, exitCode = 1) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.exitCode = exitCode;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function resolveLocalControlHost(host) {
|
|
22
|
+
if (host === "0.0.0.0")
|
|
23
|
+
return "127.0.0.1";
|
|
24
|
+
if (host === "::")
|
|
25
|
+
return "::1";
|
|
26
|
+
if (host === "127.0.0.1" || host === "::1" || host === "localhost")
|
|
27
|
+
return host;
|
|
28
|
+
throw new CliError("动态 CLI 仅支持本机 loopback 控制地址;请将 AGENT_BRIDGE_HOST 设置为 127.0.0.1、localhost、::1 或通配地址");
|
|
29
|
+
}
|
|
30
|
+
function controlUrl(host, port) {
|
|
31
|
+
const localHost = resolveLocalControlHost(host);
|
|
32
|
+
return `http://${localHost.includes(":") ? `[${localHost}]` : localHost}:${port}`;
|
|
33
|
+
}
|
|
34
|
+
function object(value) { return value !== null && typeof value === "object" && !Array.isArray(value); }
|
|
35
|
+
function extractOptions(args) {
|
|
36
|
+
const argv = [];
|
|
37
|
+
let json = false;
|
|
38
|
+
let terminated = false;
|
|
39
|
+
for (const argument of args) {
|
|
40
|
+
if (!terminated && argument === "--json") {
|
|
41
|
+
json = true;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (argument === "--")
|
|
45
|
+
terminated = true;
|
|
46
|
+
argv.push(argument);
|
|
47
|
+
}
|
|
48
|
+
return { argv, json };
|
|
49
|
+
}
|
|
50
|
+
function output(result, exitCode, json) {
|
|
51
|
+
if (json)
|
|
52
|
+
return { exitCode, stdout: `${JSON.stringify(result)}\n`, stderr: "" };
|
|
53
|
+
return { exitCode, stdout: exitCode === 0 ? `${result.message}\n` : "", stderr: exitCode === 0 ? "" : `${result.message}\n` };
|
|
54
|
+
}
|
|
55
|
+
async function requestJson(port, path, options, payload) {
|
|
56
|
+
const body = payload === undefined ? undefined : JSON.stringify(payload);
|
|
57
|
+
if (body && Buffer.byteLength(body) > MAX_BODY_BYTES)
|
|
58
|
+
throw new CliError("命令请求超过 1 MiB 上限");
|
|
59
|
+
return new Promise((resolve, reject) => {
|
|
60
|
+
const headers = { accept: "application/json" };
|
|
61
|
+
const token = options.token ?? process.env.AGENT_BRIDGE_TOKEN;
|
|
62
|
+
if (token)
|
|
63
|
+
headers.authorization = `Bearer ${token}`;
|
|
64
|
+
if (body !== undefined) {
|
|
65
|
+
headers["content-type"] = "application/json";
|
|
66
|
+
headers["content-length"] = Buffer.byteLength(body);
|
|
67
|
+
}
|
|
68
|
+
const req = (0, node_http_1.request)({ host: resolveLocalControlHost(options.host ?? "127.0.0.1"), port, path, method: body === undefined ? "GET" : "POST", headers }, (res) => {
|
|
69
|
+
const chunks = [];
|
|
70
|
+
let bytes = 0;
|
|
71
|
+
res.on("data", (chunk) => {
|
|
72
|
+
bytes += chunk.length;
|
|
73
|
+
if (bytes > MAX_BODY_BYTES) {
|
|
74
|
+
req.destroy(new CliError("后台响应超过 1 MiB 上限"));
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
chunks.push(chunk);
|
|
78
|
+
});
|
|
79
|
+
res.on("error", reject);
|
|
80
|
+
res.on("end", () => {
|
|
81
|
+
try {
|
|
82
|
+
resolve({ status: res.statusCode ?? 500, value: JSON.parse(Buffer.concat(chunks).toString("utf8")) });
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
reject(new CliError("后台响应不是有效 JSON"));
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
const timer = setTimeout(() => req.destroy(new CliError("连接后台超时;请检查 daemon 状态")), options.timeoutMs ?? 10_000);
|
|
90
|
+
req.on("close", () => clearTimeout(timer));
|
|
91
|
+
req.on("error", (error) => reject(error instanceof CliError ? error : new CliError(`后台无法访问;请先执行 mab start:${error.message}`)));
|
|
92
|
+
req.end(body);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
async function discover(port, options) {
|
|
96
|
+
const { status, value } = await requestJson(port, "/api/plugins", options);
|
|
97
|
+
if (status < 200 || status >= 300)
|
|
98
|
+
throw new CliError(`插件发现失败(HTTP ${status})`);
|
|
99
|
+
if (!object(value) || !Array.isArray(value.plugins) || value.plugins.some((entry) => !object(entry)
|
|
100
|
+
|| typeof entry.id !== "string" || !PLUGIN_ID.test(entry.id) || typeof entry.version !== "string"
|
|
101
|
+
|| typeof entry.displayName !== "string" || typeof entry.status !== "string"
|
|
102
|
+
|| !Array.isArray(entry.capabilities) || entry.capabilities.some((capability) => typeof capability !== "string"))) {
|
|
103
|
+
throw new CliError("后台插件清单格式无效");
|
|
104
|
+
}
|
|
105
|
+
return value.plugins;
|
|
106
|
+
}
|
|
107
|
+
function commandManifest(value, plugin) {
|
|
108
|
+
if (!object(value) || value.protocolVersion !== 1 || value.plugin !== plugin || typeof value.version !== "string"
|
|
109
|
+
|| !Array.isArray(value.commands) || value.commands.some((command) => !object(command)
|
|
110
|
+
|| typeof command.name !== "string" || !/^[a-z][a-z0-9-]*$/.test(command.name)
|
|
111
|
+
|| typeof command.summary !== "string" || !command.summary.trim() || typeof command.usage !== "string" || !command.usage.trim()
|
|
112
|
+
|| (command.secretPrompt !== undefined && (typeof command.secretPrompt !== "string" || !command.secretPrompt.trim()))
|
|
113
|
+
|| (command.examples !== undefined && (!Array.isArray(command.examples) || command.examples.some((example) => typeof example !== "string"))))) {
|
|
114
|
+
throw new CliError("插件命令清单协议无效(需要 protocolVersion: 1)");
|
|
115
|
+
}
|
|
116
|
+
if (new Set(value.commands.map((command) => command.name)).size !== value.commands.length)
|
|
117
|
+
throw new CliError("插件命令清单包含重复命令");
|
|
118
|
+
return value;
|
|
119
|
+
}
|
|
120
|
+
async function runPluginCommand(id, args, port, options = {}) {
|
|
121
|
+
const extracted = extractOptions(args);
|
|
122
|
+
const json = options.json ?? extracted.json;
|
|
123
|
+
try {
|
|
124
|
+
if (!PLUGIN_ID.test(id) || RESERVED_COMMANDS.has(id))
|
|
125
|
+
throw new CliError("插件 ID 无效或使用了核心保留命令", 2);
|
|
126
|
+
const plugin = (await discover(port, options)).find((entry) => entry.id === id);
|
|
127
|
+
if (!plugin)
|
|
128
|
+
throw new CliError(`插件“${id}”未安装;请先到 ABWeb 安装插件`, 2);
|
|
129
|
+
if (!plugin.capabilities.includes("cli"))
|
|
130
|
+
throw new CliError(`插件“${plugin.displayName}”未提供 CLI 命令;请升级对应插件`, 2);
|
|
131
|
+
if (!["ready", "degraded"].includes(plugin.status))
|
|
132
|
+
throw new CliError(`插件“${plugin.displayName}”未就绪(${plugin.status})`, 2);
|
|
133
|
+
const path = `/api/plugins/${id}/commands`;
|
|
134
|
+
const response = await requestJson(port, path, options);
|
|
135
|
+
if (response.status < 200 || response.status >= 300)
|
|
136
|
+
throw new CliError(`插件命令清单读取失败(HTTP ${response.status})`);
|
|
137
|
+
const manifest = commandManifest(response.value, id);
|
|
138
|
+
const argv = extracted.argv;
|
|
139
|
+
if (argv.length === 0 || argv[0] === "help" || argv[0] === "--help") {
|
|
140
|
+
const helpArgs = argv.slice(argv[0] === "help" || argv[0] === "--help" ? 1 : 0);
|
|
141
|
+
if (helpArgs.length > 1)
|
|
142
|
+
throw new CliError(`用法:mab ${id} help [命令]`, 2);
|
|
143
|
+
const selected = helpArgs.length === 0 ? manifest.commands : manifest.commands.filter((command) => command.name === helpArgs[0]);
|
|
144
|
+
if (helpArgs.length > 0 && selected.length === 0)
|
|
145
|
+
throw new CliError(`插件“${id}”没有命令:${helpArgs[0]}`, 2);
|
|
146
|
+
const message = [`${plugin.displayName}(${manifest.version})`, ...selected.flatMap((command) => [
|
|
147
|
+
`${command.name}:${command.summary}`, ` ${command.usage}`, ...(command.examples ?? []).map((example) => ` 示例:${example}`),
|
|
148
|
+
])].join("\n");
|
|
149
|
+
return output({ ok: true, message, data: { ...manifest, commands: selected } }, 0, json);
|
|
150
|
+
}
|
|
151
|
+
const prompt = manifest.commands.find((command) => command.name === argv[0])?.secretPrompt;
|
|
152
|
+
const secret = prompt ? await (options.readSecret ?? secret_input_1.readSecret)(prompt) : undefined;
|
|
153
|
+
const executed = await requestJson(port, path, options, { protocolVersion: 1, argv, ...(secret === undefined ? {} : { secret }) });
|
|
154
|
+
const result = executed.value;
|
|
155
|
+
if (!object(result) || typeof result.ok !== "boolean" || typeof result.message !== "string"
|
|
156
|
+
|| (executed.status >= 200 && executed.status < 300 ? !result.ok : result.ok)
|
|
157
|
+
|| executed.status < 200 || (executed.status >= 300 && executed.status < 400)) {
|
|
158
|
+
throw new CliError("插件命令响应协议无效");
|
|
159
|
+
}
|
|
160
|
+
const exitCode = executed.status >= 500 ? 1 : executed.status >= 400 ? 2 : 0;
|
|
161
|
+
return output(result, exitCode, json);
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
return output({ ok: false, message: error instanceof Error ? error.message : String(error) }, error instanceof CliError ? error.exitCode : 1, json);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
async function runCoreHelp(port, options = {}) {
|
|
168
|
+
let help = exports.CORE_HELP;
|
|
169
|
+
try {
|
|
170
|
+
const plugins = (await discover(port, options)).filter((plugin) => plugin.capabilities.includes("cli") && !RESERVED_COMMANDS.has(plugin.id));
|
|
171
|
+
if (plugins.length)
|
|
172
|
+
help += `\n插件命令(mab <插件 ID> help 查看详情):\n${plugins.map((plugin) => ` ${plugin.id}:${plugin.displayName}(${plugin.status})`).join("\n")}\n`;
|
|
173
|
+
}
|
|
174
|
+
catch { /* 后台不可达时仍显示核心静态帮助 */ }
|
|
175
|
+
return { exitCode: 0, stdout: help, stderr: "" };
|
|
176
|
+
}
|
package/dist/src-ts/cli.js
CHANGED
|
@@ -47,6 +47,7 @@ const registry_1 = require("./registry");
|
|
|
47
47
|
const bridge_1 = require("./bridge");
|
|
48
48
|
const plugin_manager_1 = require("./plugin-manager");
|
|
49
49
|
const daemon_process_1 = require("./daemon-process");
|
|
50
|
+
const cli_commands_1 = require("./cli-commands");
|
|
50
51
|
const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
51
52
|
let activeManager;
|
|
52
53
|
const logger = {
|
|
@@ -72,19 +73,19 @@ async function selfTest(task) {
|
|
|
72
73
|
console.error(`session=${response.sessionId}`);
|
|
73
74
|
return 0;
|
|
74
75
|
}
|
|
75
|
-
async function
|
|
76
|
+
async function daemonAddress() {
|
|
76
77
|
try {
|
|
77
78
|
const address = JSON.parse(await (0, promises_1.readFile)((0, node_path_1.resolve)(stateDirectory(), "daemon-address.json"), "utf8"));
|
|
78
79
|
if (Number.isInteger(address.port) && address.port > 0)
|
|
79
|
-
return address.port;
|
|
80
|
+
return { host: typeof address.host === "string" ? address.host : "127.0.0.1", port: address.port };
|
|
80
81
|
}
|
|
81
82
|
catch { /* 后台未写入地址时使用配置端口 */ }
|
|
82
|
-
return Number(process.env.AGENT_BRIDGE_PORT ?? "26107");
|
|
83
|
+
return { host: process.env.AGENT_BRIDGE_HOST ?? "127.0.0.1", port: Number(process.env.AGENT_BRIDGE_PORT ?? "26107") };
|
|
83
84
|
}
|
|
84
85
|
async function pair() {
|
|
85
|
-
const port = await
|
|
86
|
+
const { host, port } = await daemonAddress();
|
|
86
87
|
const result = await new Promise((resolve, reject) => {
|
|
87
|
-
const req = (0, node_http_1.request)({ host:
|
|
88
|
+
const req = (0, node_http_1.request)({ host: (0, cli_commands_1.resolveLocalControlHost)(host), port, path: "/api/pair-code", method: "GET" }, (res) => {
|
|
88
89
|
let body = "";
|
|
89
90
|
res.setEncoding("utf8");
|
|
90
91
|
res.on("data", (chunk) => body += chunk);
|
|
@@ -181,9 +182,9 @@ async function restart() {
|
|
|
181
182
|
return 0;
|
|
182
183
|
}
|
|
183
184
|
async function status() {
|
|
184
|
-
const port = await
|
|
185
|
+
const { host, port } = await daemonAddress();
|
|
185
186
|
const get = (path) => new Promise((resolve, reject) => {
|
|
186
|
-
const req = (0, node_http_1.request)({ host:
|
|
187
|
+
const req = (0, node_http_1.request)({ host: (0, cli_commands_1.resolveLocalControlHost)(host), port, path, method: "GET" }, (res) => {
|
|
187
188
|
let body = "";
|
|
188
189
|
res.setEncoding("utf8");
|
|
189
190
|
res.on("data", (chunk) => body += chunk);
|
|
@@ -243,6 +244,12 @@ async function serve() {
|
|
|
243
244
|
const fallbackPorts = (process.env.AGENT_BRIDGE_FALLBACK_PORTS ?? "").split(",").map((value) => Number(value.trim())).filter((value) => Number.isInteger(value) && value >= 0);
|
|
244
245
|
const server = new control_server_1.ControlServer({ manager, host, port, fallbackPorts, token: process.env.AGENT_BRIDGE_TOKEN, sessionFile: (0, node_path_1.resolve)(stateDirectory(), "paired-sessions.json") });
|
|
245
246
|
const address = await server.start();
|
|
247
|
+
try {
|
|
248
|
+
manager.setControlUrl((0, cli_commands_1.controlUrl)(address.host, address.port));
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
logger.warn("当前监听地址仅提供网络 API,插件动态 CLI 需要本机 loopback 或通配监听地址");
|
|
252
|
+
}
|
|
246
253
|
const addressFile = (0, node_path_1.resolve)(stateDirectory(), "daemon-address.json");
|
|
247
254
|
await (0, promises_1.mkdir)(stateDirectory(), { recursive: true });
|
|
248
255
|
await (0, promises_1.writeFile)(addressFile, JSON.stringify({ host: address.host, port: address.port, pid: process.pid }));
|
|
@@ -324,7 +331,7 @@ async function checkAndUpgradePlugins(manager) {
|
|
|
324
331
|
}
|
|
325
332
|
async function main() {
|
|
326
333
|
const args = process.argv.slice(2);
|
|
327
|
-
if (args
|
|
334
|
+
if (args[0] === "pair")
|
|
328
335
|
return pair();
|
|
329
336
|
if (args[0] === "status")
|
|
330
337
|
return status();
|
|
@@ -334,15 +341,23 @@ async function main() {
|
|
|
334
341
|
return restart();
|
|
335
342
|
if (args[0] === "update")
|
|
336
343
|
return update();
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
if (args.includes("--serve") || args.includes("start")) {
|
|
344
|
+
if (args[0] === "--self-test")
|
|
345
|
+
return selfTest(args[1] ?? "hi");
|
|
346
|
+
if (args[0] === "--serve" || args[0] === "start") {
|
|
341
347
|
await stopDaemon(false);
|
|
342
348
|
return serve();
|
|
343
349
|
}
|
|
344
|
-
|
|
345
|
-
|
|
350
|
+
if (!args[0]) {
|
|
351
|
+
process.stderr.write(cli_commands_1.CORE_HELP);
|
|
352
|
+
return 2;
|
|
353
|
+
}
|
|
354
|
+
const address = await daemonAddress();
|
|
355
|
+
const result = args[0] === "--help" || args[0] === "help"
|
|
356
|
+
? await (0, cli_commands_1.runCoreHelp)(address.port, { host: address.host, timeoutMs: 1000 })
|
|
357
|
+
: await (0, cli_commands_1.runPluginCommand)(args[0], args.slice(1), address.port, { host: address.host });
|
|
358
|
+
process.stdout.write(result.stdout);
|
|
359
|
+
process.stderr.write(result.stderr);
|
|
360
|
+
return result.exitCode;
|
|
346
361
|
}
|
|
347
362
|
main().then((code) => { process.exitCode = code; }).catch((error) => {
|
|
348
363
|
console.error(error instanceof Error ? error.stack ?? error.message : error);
|
|
@@ -54,7 +54,7 @@ class ControlServer {
|
|
|
54
54
|
reject(new Error("后台监听地址无效"));
|
|
55
55
|
return;
|
|
56
56
|
}
|
|
57
|
-
resolve({ host, port: info.port });
|
|
57
|
+
resolve({ host: info.address, port: info.port });
|
|
58
58
|
};
|
|
59
59
|
this.server.once("error", onError);
|
|
60
60
|
this.server.once("listening", onListening);
|
|
@@ -132,12 +132,21 @@ class ControlServer {
|
|
|
132
132
|
return this.json(res, 200, { ok: true, token, expiresAt });
|
|
133
133
|
}
|
|
134
134
|
const authorization = req.headers.authorization;
|
|
135
|
-
const authorized = authorization === `Bearer ${this.options.token}` || (authorization?.startsWith("Bearer ") && (this.pairedTokens.get((0, node_crypto_1.createHash)("sha256").update(authorization.slice(7)).digest("hex")) ?? 0) > Date.now());
|
|
135
|
+
const authorized = (Boolean(this.options.token) && authorization === `Bearer ${this.options.token}`) || (authorization?.startsWith("Bearer ") && (this.pairedTokens.get((0, node_crypto_1.createHash)("sha256").update(authorization.slice(7)).digest("hex")) ?? 0) > Date.now());
|
|
136
136
|
if (this.options.token && !isLoopback(req.socket.remoteAddress) && !authorized && !(method === "GET" && url.pathname.startsWith("/abweb")))
|
|
137
137
|
return this.json(res, 401, { error: "unauthorized" });
|
|
138
138
|
if (parts[0] === "api" && parts[1] === "agents" && parts[2] && parts[3] === "v1") {
|
|
139
139
|
const id = decodeURIComponent(parts[2]);
|
|
140
|
-
|
|
140
|
+
if (req.headers["x-evolcore-session-id"] !== undefined && !authorized && !isLoopback(req.socket.remoteAddress))
|
|
141
|
+
return this.json(res, 401, { error: "会话身份仅允许已认证或本机调用方使用" });
|
|
142
|
+
const adapter = new openai_adapter_1.OpenAIAdapter(async (task, _signal, context) => {
|
|
143
|
+
if (!context)
|
|
144
|
+
return (await this.options.manager.handle(id, { task })).text;
|
|
145
|
+
// 客户端标识只在认证调用方或本机信任范围内生效,不能直接恢复其他入口的会话。
|
|
146
|
+
const caller = authorized ? (0, node_crypto_1.createHash)("sha256").update(authorization).digest("hex") : "loopback";
|
|
147
|
+
const sessionId = `openai:${(0, node_crypto_1.createHash)("sha256").update(JSON.stringify([caller, id, context.agentAid ?? "", context.peerId ?? "", context.sessionId])).digest("hex")}`;
|
|
148
|
+
return (await this.options.manager.handle(id, { task, sessionId })).text;
|
|
149
|
+
}, { model: id, prefix: `/api/agents/${encodeURIComponent(id)}/v1`, onExchange: (direction, text, peer, aid) => this.options.manager.recordMessage(id, direction, text, peer, aid) });
|
|
141
150
|
await adapter.handle(req, res);
|
|
142
151
|
return;
|
|
143
152
|
}
|
|
@@ -212,6 +221,8 @@ class ControlServer {
|
|
|
212
221
|
const result = await identity.create(body.aid, body.ownerAid);
|
|
213
222
|
return this.json(res, result.status === "等待 Owner 确认" ? 202 : result.status === "existing" ? 200 : 201, result);
|
|
214
223
|
}
|
|
224
|
+
if (method === "GET" && url.pathname === "/api/agents/availability")
|
|
225
|
+
return this.json(res, 200, { agents: await this.options.manager.availableAgents() });
|
|
215
226
|
if (method === "GET" && url.pathname === "/api/plugins")
|
|
216
227
|
return this.json(res, 200, { ok: true, plugins: this.options.manager.list() });
|
|
217
228
|
if (method === "GET" && url.pathname === "/api/plugins/catalog")
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.isDaemonCommand = isDaemonCommand;
|
|
4
4
|
/** 只识别直接运行本安装 CLI 的后台进程,避免旧地址文件中的 PID 被其他程序复用。 */
|
|
5
5
|
function isDaemonCommand(commandLine, cli) {
|
|
6
|
-
const
|
|
7
|
-
|
|
6
|
+
const normalize = (value) => value.replace(/[\\/]+/g, "/").toLowerCase();
|
|
7
|
+
const args = [...commandLine.matchAll(/"([^"]*)"|(\S+)/g)].map((match) => normalize(match[1] ?? match[2]));
|
|
8
|
+
return args[1] === normalize(cli) && ["start", "--serve"].includes(args[2]);
|
|
8
9
|
}
|
package/dist/src-ts/dsh-agent.js
CHANGED
|
@@ -18,7 +18,7 @@ class DshAgent {
|
|
|
18
18
|
if (!request.task.trim())
|
|
19
19
|
throw new agent_1.AgentError("DSH task must not be empty");
|
|
20
20
|
const args = ["--expose-internals", this.paths.cli, "--profile", "headless", "--patch", this.paths.patch, "--json"];
|
|
21
|
-
if (request.sessionId)
|
|
21
|
+
if (request.sessionId && !request.newSession)
|
|
22
22
|
args.push("--session-id", request.sessionId);
|
|
23
23
|
args.push("-");
|
|
24
24
|
const child = this.spawnProcess(this.paths.executable, args, {
|
|
@@ -29,7 +29,7 @@ class DshAgent {
|
|
|
29
29
|
child.stdin.end(request.task, "utf8");
|
|
30
30
|
let output = "";
|
|
31
31
|
let errors = "";
|
|
32
|
-
let sessionId = request.sessionId;
|
|
32
|
+
let sessionId = request.newSession ? undefined : request.sessionId;
|
|
33
33
|
let finalText = "";
|
|
34
34
|
child.stdout.setEncoding?.("utf8");
|
|
35
35
|
child.stderr.setEncoding?.("utf8");
|
|
@@ -64,6 +64,8 @@ class DshAgent {
|
|
|
64
64
|
});
|
|
65
65
|
if (!finalText)
|
|
66
66
|
throw new agent_1.AgentError("DSH returned no final text");
|
|
67
|
+
if (request.newSession && !sessionId)
|
|
68
|
+
throw new agent_1.AgentError("DSH 未返回真实新会话标识,无法确认后续续聊目标");
|
|
67
69
|
return { text: finalText, sessionId };
|
|
68
70
|
}
|
|
69
71
|
}
|
|
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.PluginManager = void 0;
|
|
7
|
+
const agent_availability_1 = require("./agent-availability");
|
|
7
8
|
const promises_1 = require("node:fs/promises");
|
|
8
9
|
const node_fs_1 = require("node:fs");
|
|
9
10
|
const node_module_1 = require("node:module");
|
|
@@ -22,6 +23,7 @@ const logger = { info() { }, warn() { }, error() { } };
|
|
|
22
23
|
const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
23
24
|
class PluginManager {
|
|
24
25
|
options;
|
|
26
|
+
controlUrl;
|
|
25
27
|
plugins = new Map();
|
|
26
28
|
httpRoutes = new Map();
|
|
27
29
|
root;
|
|
@@ -41,6 +43,7 @@ class PluginManager {
|
|
|
41
43
|
this.sessions = new channel_sessions_1.ChannelSessions(options.stateDir);
|
|
42
44
|
}
|
|
43
45
|
log(level, message, fields, source = "mab") { this.logs.append({ time: new Date().toISOString(), level, source, message, ...(fields ? { fields } : {}) }); }
|
|
46
|
+
setControlUrl(url) { this.controlUrl = url; }
|
|
44
47
|
async updatePolicy() { try {
|
|
45
48
|
const value = JSON.parse(await (0, promises_1.readFile)((0, node_path_1.join)(this.options.stateDir, "update-policy.json"), "utf8"));
|
|
46
49
|
return isUpdatePolicy(value) ? value : { mode: "auto-install" };
|
|
@@ -230,6 +233,8 @@ class PluginManager {
|
|
|
230
233
|
get(id) { return this.plugins.get(id); }
|
|
231
234
|
recentMessages(id) { return this.messages.filter((item) => !id || item.pluginId === id).slice(-200).reverse(); }
|
|
232
235
|
recordMessage(pluginId, direction, text, peer, agentAid) {
|
|
236
|
+
if (direction === "error" && isNoisyAgentError(text))
|
|
237
|
+
return;
|
|
233
238
|
this.messages.push({ pluginId, direction, text: text.slice(0, 10000), time: new Date().toISOString(), ...(peer ? { peer } : {}), ...(agentAid ? { agentAid } : {}) });
|
|
234
239
|
if (this.messages.length > 200)
|
|
235
240
|
this.messages.shift();
|
|
@@ -355,7 +360,11 @@ class PluginManager {
|
|
|
355
360
|
const target = message.agentPlugin || "qwenwork";
|
|
356
361
|
this.recordMessage(target, "request", message.text, message.peerId);
|
|
357
362
|
try {
|
|
358
|
-
const
|
|
363
|
+
const instance = this.plugins.get(target)?.instance;
|
|
364
|
+
const control = instance?.sessionOperations?.includes("new")
|
|
365
|
+
? (request) => this.controlSession(target, request)
|
|
366
|
+
: undefined;
|
|
367
|
+
const response = await this.sessions.run(message, target, (sessionId, newSession) => this.handle(target, { task: message.text, sessionId, ...(newSession ? { newSession: true } : {}) }), control);
|
|
359
368
|
await message.reply(response.text);
|
|
360
369
|
this.recordMessage(target, "reply", response.text, message.peerId);
|
|
361
370
|
}
|
|
@@ -365,7 +374,7 @@ class PluginManager {
|
|
|
365
374
|
}
|
|
366
375
|
},
|
|
367
376
|
},
|
|
368
|
-
runtime: { ports: {
|
|
377
|
+
runtime: { controlUrl: this.controlUrl, ports: {
|
|
369
378
|
qwenwork: Number(process.env.AGENT_BRIDGE_QWEN_CDP_PORT ?? process.env.QWEN_CDP_PORT ?? "9228"),
|
|
370
379
|
doubao: Number(process.env.AGENT_BRIDGE_DOUBAO_CDP_PORT ?? process.env.DOUBAO_CDP_PORT ?? "9227"),
|
|
371
380
|
workbuddy: Number(process.env.AGENT_BRIDGE_WORKBUDDY_CDP_PORT ?? process.env.WORKBUDDY_CDP_PORT ?? "9229"),
|
|
@@ -501,11 +510,39 @@ class PluginManager {
|
|
|
501
510
|
throw error;
|
|
502
511
|
throw new agent_1.AgentError(`插件配置校验失败:${error instanceof Error ? error.message : String(error)}`);
|
|
503
512
|
} }
|
|
513
|
+
async availableAgents() {
|
|
514
|
+
return Promise.all(this.list().filter((plugin) => plugin.type === "agent").map(async (plugin) => ({
|
|
515
|
+
id: plugin.id, displayName: plugin.displayName, status: plugin.status,
|
|
516
|
+
...await (0, agent_availability_1.agentAvailability)(plugin.id, await this.config(plugin.id), plugin.status),
|
|
517
|
+
})));
|
|
518
|
+
}
|
|
504
519
|
async health(id) { const loaded = this.plugins.get(id); if (!loaded)
|
|
505
520
|
throw new agent_1.AgentError(`unknown plugin: ${id}`); if (!loaded.instance)
|
|
506
521
|
return { status: loaded.record.status }; return loaded.instance.health(); }
|
|
507
|
-
async handle(id, request) {
|
|
508
|
-
|
|
522
|
+
async handle(id, request) {
|
|
523
|
+
const loaded = this.plugins.get(id);
|
|
524
|
+
if (!loaded?.instance || loaded.record.status !== "ready") {
|
|
525
|
+
const name = loaded?.record.displayName ?? id;
|
|
526
|
+
const processName = loaded ? pluginProcessName(loaded.record.id) : undefined;
|
|
527
|
+
const processText = processName ? `目标进程 ${processName} 不可用;` : "";
|
|
528
|
+
const reason = loaded?.record.error ? `启动错误:${loaded.record.error}` : `当前状态:${loaded?.record.status ?? "未发现"}`;
|
|
529
|
+
throw new agent_1.AgentError(`插件“${name}”无法调用,插件未就绪:${processText}${reason}`);
|
|
530
|
+
}
|
|
531
|
+
return loaded.instance.handle(request);
|
|
532
|
+
}
|
|
533
|
+
async controlSession(id, request) {
|
|
534
|
+
const loaded = this.plugins.get(id);
|
|
535
|
+
if (!loaded?.instance || loaded.record.status !== "ready")
|
|
536
|
+
throw new agent_1.AgentError(`插件 ${id} 未就绪,无法控制会话`);
|
|
537
|
+
if (!loaded.instance.sessionOperations?.includes(request.operation))
|
|
538
|
+
throw new agent_1.AgentError(`插件 ${id} 不支持新建会话`);
|
|
539
|
+
const response = loaded.instance.controlSession
|
|
540
|
+
? await loaded.instance.controlSession(request)
|
|
541
|
+
: { text: `${loaded.record.displayName} 已创建新会话`, sessionId: request.sessionId };
|
|
542
|
+
if (typeof response.sessionId !== "string" || !response.sessionId.trim())
|
|
543
|
+
throw new agent_1.AgentError("插件未返回有效的新会话标识,原会话绑定保持不变");
|
|
544
|
+
return response;
|
|
545
|
+
}
|
|
509
546
|
async dispatchHttp(request, response) {
|
|
510
547
|
const method = (request.method ?? "GET").toUpperCase();
|
|
511
548
|
const pathname = new URL(request.url ?? "/", "http://localhost").pathname;
|
|
@@ -576,10 +613,13 @@ class PluginManager {
|
|
|
576
613
|
const packagedFile = (0, node_path_1.join)(record.path, record.manifest.entry);
|
|
577
614
|
const developmentFile = (0, node_path_1.resolve)(__dirname, "plugins", record.id, (0, node_path_1.basename)(record.manifest.entry));
|
|
578
615
|
const file = (0, node_fs_1.existsSync)(packagedFile) ? packagedFile : developmentFile;
|
|
579
|
-
//
|
|
616
|
+
// 同时刷新插件内部依赖,避免升级后仍运行旧版 CDP 客户端。
|
|
580
617
|
const req = (0, node_module_1.createRequire)(file);
|
|
581
618
|
const resolved = req.resolve(file);
|
|
582
|
-
|
|
619
|
+
const moduleRoot = (0, node_fs_1.existsSync)(packagedFile) ? (0, node_path_1.resolve)(record.path) : (0, node_path_1.dirname)(file);
|
|
620
|
+
for (const cached of Object.keys(req.cache))
|
|
621
|
+
if (cached === resolved || cached.startsWith(`${moduleRoot}${node_path_1.sep}`))
|
|
622
|
+
delete req.cache[cached];
|
|
583
623
|
const mod = req(resolved);
|
|
584
624
|
let value = mod.default ?? mod.plugin ?? mod;
|
|
585
625
|
// Entries may export a ready instance, a factory, or a class. Supporting all
|
|
@@ -808,7 +848,14 @@ class PluginManager {
|
|
|
808
848
|
} }
|
|
809
849
|
}
|
|
810
850
|
exports.PluginManager = PluginManager;
|
|
851
|
+
function isNoisyAgentError(text) {
|
|
852
|
+
const value = text.trim();
|
|
853
|
+
return /^(?:千问生成接口不可用(?:,已触发后台恢复)?|千问办公 UI 超时未返回回复|(?:API|接口)\s*(?:暂时)?不可用[。.!!]?)$/i.test(value);
|
|
854
|
+
}
|
|
811
855
|
function isUpdatePolicy(value) { return Boolean(value && typeof value === "object" && ["auto-install", "download-only", "manual"].includes(value.mode)); }
|
|
856
|
+
function pluginProcessName(id) {
|
|
857
|
+
return { qwenwork: "QwenWorkCN.exe", doubao: "Doubao.exe", workbuddy: "WorkBuddy.exe", chatgpt: "ChatGPT/Codex" }[id];
|
|
858
|
+
}
|
|
812
859
|
function defaultPluginTrustConfig() { return { providers: { m2lihui: "allow" }, plugins: {} }; }
|
|
813
860
|
function mergeDefaultPluginTrustConfig(value) { return { providers: { m2lihui: "allow", ...value.providers }, plugins: { ...value.plugins } }; }
|
|
814
861
|
function normalizeProvider(value) { return value.trim().toLowerCase(); }
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
|
2
|
+
MIIB/TCCAYOgAwIBAgIUI/WLfQXFR9KpvBlhnvJFIAtylNQwCgYIKoZIzj0EAwMw
|
|
3
|
+
KzEUMBIGA1UEAwwLQVVOIFJvb3QgQ0ExEzARBgNVBAoMCkFnZW50VW5pb24wIBcN
|
|
4
|
+
MjYwMzE3MDE1NDUyWhgPMjA1NjAzMDkwMTU5NTJaMCsxFDASBgNVBAMMC0FVTiBS
|
|
5
|
+
b290IENBMRMwEQYDVQQKDApBZ2VudFVuaW9uMHYwEAYHKoZIzj0CAQYFK4EEACID
|
|
6
|
+
YgAEbuQMkmsHjhtwsgoFhfgC4514k2IMQVECNeBtxiwiEe5OTY0D7MvqwtEzz67r
|
|
7
|
+
1jo2OXOuA+BajSW/ZOw89HH2zrWIshw2qVo5WP4ELnrKk/dipfiJBm9IrJXsO0kN
|
|
8
|
+
ope4o2YwZDASBgNVHRMBAf8ECDAGAQH/AgECMA4GA1UdDwEB/wQEAwIBhjAdBgNV
|
|
9
|
+
HQ4EFgQULmOUMJCtK8VatDpoo/iigMIu3hwwHwYDVR0jBBgwFoAULmOUMJCtK8Va
|
|
10
|
+
tDpoo/iigMIu3hwwCgYIKoZIzj0EAwMDaAAwZQIwPcby4jt32sKANZhUuFaOcWiy
|
|
11
|
+
V8rgbF0pF+ROrlg+9JdQ3yRyGWTU2VRNyzd+FlNbAjEAgVKYzN6mpxnVfjk8dHYH
|
|
12
|
+
vh6o6LY9oqw8rdZ/2Q1k1eUSaFxykTZQ9ipR84GIGXrc
|
|
13
|
+
-----END CERTIFICATE-----
|
|
@@ -26,7 +26,8 @@ async function configureAidForPlugin(pluginId, aid, port, run = runEc) {
|
|
|
26
26
|
return;
|
|
27
27
|
if (!/^[a-z0-9_-]+$/.test(pluginId))
|
|
28
28
|
throw new Error("插件 ID 格式无效");
|
|
29
|
-
|
|
29
|
+
// 通过 daemon 的统一插件代理访问所有 Agent;否则 qwenwork 的裸 /v1 路由在有插件 HTTP 注册表时不存在。
|
|
30
|
+
const path = pluginId === "dsh" ? "/dsh/v1" : `/api/agents/${pluginId}/v1`;
|
|
30
31
|
for (const [key, value] of [
|
|
31
32
|
["baseagents.ecagent.baseUrl", `http://127.0.0.1:${port}${path}`],
|
|
32
33
|
["baseagents.ecagent.apiKey", "mab-local"],
|