mu-agent-bridge 1.0.28 → 1.0.30
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 +6 -4
- package/dist/src-ts/cli-commands.js +172 -0
- package/dist/src-ts/cli.js +29 -14
- package/dist/src-ts/control-server.js +1 -1
- package/dist/src-ts/plugin-manager.js +17 -3
- package/dist/src-ts/plugins/abweb/index.js +1 -1
- package/dist/src-ts/plugins/abweb/public/app.js +20 -1
- package/dist/src-ts/plugins/abweb/public/index.html +3 -0
- package/dist/src-ts/plugins/abweb/public/style.css +4 -0
- 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 +369 -23
- package/dist/src-ts/plugins/aun/public/app.js +27 -7
- package/dist/src-ts/plugins/aun/public/index.html +1 -1
- package/dist/src-ts/plugins/desktop-launch.js +70 -0
- package/dist/src-ts/plugins/doubao/cdp-client.js +51 -8
- package/dist/src-ts/plugins/doubao/index.js +24 -5
- package/dist/src-ts/plugins/feishu/index.js +111 -23
- package/dist/src-ts/plugins/qwenwork/index.js +19 -2
- package/dist/tests-ts/abweb-login.test.js +1 -0
- package/dist/tests-ts/aun-plugin.test.js +658 -22
- package/dist/tests-ts/channel-feedback.test.js +99 -0
- package/dist/tests-ts/cli-commands.test.js +305 -0
- package/dist/tests-ts/control-server.test.js +3 -0
- package/dist/tests-ts/desktop-launch.test.js +39 -0
- package/dist/tests-ts/doubao-plugin.test.js +44 -0
- package/dist/tests-ts/feishu-plugin.test.js +187 -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 +37 -0
- package/package.json +1 -1
- package/plugins/abweb/dist/src-ts/plugins/abweb/index.js +1 -1
- package/plugins/abweb/dist/src-ts/plugins/abweb/public/app.js +20 -1
- package/plugins/abweb/dist/src-ts/plugins/abweb/public/app.ts +16 -1
- package/plugins/abweb/dist/src-ts/plugins/abweb/public/index.html +3 -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/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 +369 -23
- package/plugins/aun/dist/src-ts/plugins/aun/public/app.js +27 -7
- 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/package.json +1 -1
- package/plugins/chatgpt/plugin.json +1 -1
- package/plugins/doubao/dist/src-ts/plugins/desktop-launch.js +70 -0
- package/plugins/doubao/dist/src-ts/plugins/doubao/cdp-client.js +51 -8
- 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/package.json +1 -1
- package/plugins/dsh/plugin.json +1 -1
- package/plugins/feishu/dist/src-ts/plugins/feishu/index.js +111 -23
- package/plugins/feishu/package.json +1 -1
- package/plugins/feishu/plugin.json +3 -2
- package/plugins/qwenwork/dist/src-ts/plugins/desktop-launch.js +70 -0
- package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/index.js +19 -2
- package/plugins/qwenwork/package.json +1 -1
- package/plugins/qwenwork/plugin.json +1 -1
- package/plugins/workbuddy/package.json +1 -1
- package/plugins/workbuddy/plugin.json +1 -1
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,4 @@ 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)。
|
|
@@ -0,0 +1,172 @@
|
|
|
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 node_http_1 = require("node:http");
|
|
9
|
+
const MAX_BODY_BYTES = 1024 * 1024;
|
|
10
|
+
const PLUGIN_ID = /^[a-z][a-z0-9-]{1,63}$/;
|
|
11
|
+
const RESERVED_COMMANDS = new Set(["start", "stop", "restart", "status", "pair", "update", "help"]);
|
|
12
|
+
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";
|
|
13
|
+
class CliError extends Error {
|
|
14
|
+
exitCode;
|
|
15
|
+
constructor(message, exitCode = 1) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.exitCode = exitCode;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function resolveLocalControlHost(host) {
|
|
21
|
+
if (host === "0.0.0.0")
|
|
22
|
+
return "127.0.0.1";
|
|
23
|
+
if (host === "::")
|
|
24
|
+
return "::1";
|
|
25
|
+
if (host === "127.0.0.1" || host === "::1" || host === "localhost")
|
|
26
|
+
return host;
|
|
27
|
+
throw new CliError("动态 CLI 仅支持本机 loopback 控制地址;请将 AGENT_BRIDGE_HOST 设置为 127.0.0.1、localhost、::1 或通配地址");
|
|
28
|
+
}
|
|
29
|
+
function controlUrl(host, port) {
|
|
30
|
+
const localHost = resolveLocalControlHost(host);
|
|
31
|
+
return `http://${localHost.includes(":") ? `[${localHost}]` : localHost}:${port}`;
|
|
32
|
+
}
|
|
33
|
+
function object(value) { return value !== null && typeof value === "object" && !Array.isArray(value); }
|
|
34
|
+
function extractOptions(args) {
|
|
35
|
+
const argv = [];
|
|
36
|
+
let json = false;
|
|
37
|
+
let terminated = false;
|
|
38
|
+
for (const argument of args) {
|
|
39
|
+
if (!terminated && argument === "--json") {
|
|
40
|
+
json = true;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (argument === "--")
|
|
44
|
+
terminated = true;
|
|
45
|
+
argv.push(argument);
|
|
46
|
+
}
|
|
47
|
+
return { argv, json };
|
|
48
|
+
}
|
|
49
|
+
function output(result, exitCode, json) {
|
|
50
|
+
if (json)
|
|
51
|
+
return { exitCode, stdout: `${JSON.stringify(result)}\n`, stderr: "" };
|
|
52
|
+
return { exitCode, stdout: exitCode === 0 ? `${result.message}\n` : "", stderr: exitCode === 0 ? "" : `${result.message}\n` };
|
|
53
|
+
}
|
|
54
|
+
async function requestJson(port, path, options, payload) {
|
|
55
|
+
const body = payload === undefined ? undefined : JSON.stringify(payload);
|
|
56
|
+
if (body && Buffer.byteLength(body) > MAX_BODY_BYTES)
|
|
57
|
+
throw new CliError("命令请求超过 1 MiB 上限");
|
|
58
|
+
return new Promise((resolve, reject) => {
|
|
59
|
+
const headers = { accept: "application/json" };
|
|
60
|
+
const token = options.token ?? process.env.AGENT_BRIDGE_TOKEN;
|
|
61
|
+
if (token)
|
|
62
|
+
headers.authorization = `Bearer ${token}`;
|
|
63
|
+
if (body !== undefined) {
|
|
64
|
+
headers["content-type"] = "application/json";
|
|
65
|
+
headers["content-length"] = Buffer.byteLength(body);
|
|
66
|
+
}
|
|
67
|
+
const req = (0, node_http_1.request)({ host: resolveLocalControlHost(options.host ?? "127.0.0.1"), port, path, method: body === undefined ? "GET" : "POST", headers }, (res) => {
|
|
68
|
+
const chunks = [];
|
|
69
|
+
let bytes = 0;
|
|
70
|
+
res.on("data", (chunk) => {
|
|
71
|
+
bytes += chunk.length;
|
|
72
|
+
if (bytes > MAX_BODY_BYTES) {
|
|
73
|
+
req.destroy(new CliError("后台响应超过 1 MiB 上限"));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
chunks.push(chunk);
|
|
77
|
+
});
|
|
78
|
+
res.on("error", reject);
|
|
79
|
+
res.on("end", () => {
|
|
80
|
+
try {
|
|
81
|
+
resolve({ status: res.statusCode ?? 500, value: JSON.parse(Buffer.concat(chunks).toString("utf8")) });
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
reject(new CliError("后台响应不是有效 JSON"));
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
const timer = setTimeout(() => req.destroy(new CliError("连接后台超时;请检查 daemon 状态")), options.timeoutMs ?? 10_000);
|
|
89
|
+
req.on("close", () => clearTimeout(timer));
|
|
90
|
+
req.on("error", (error) => reject(error instanceof CliError ? error : new CliError(`后台无法访问;请先执行 mab start:${error.message}`)));
|
|
91
|
+
req.end(body);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
async function discover(port, options) {
|
|
95
|
+
const { status, value } = await requestJson(port, "/api/plugins", options);
|
|
96
|
+
if (status < 200 || status >= 300)
|
|
97
|
+
throw new CliError(`插件发现失败(HTTP ${status})`);
|
|
98
|
+
if (!object(value) || !Array.isArray(value.plugins) || value.plugins.some((entry) => !object(entry)
|
|
99
|
+
|| typeof entry.id !== "string" || !PLUGIN_ID.test(entry.id) || typeof entry.version !== "string"
|
|
100
|
+
|| typeof entry.displayName !== "string" || typeof entry.status !== "string"
|
|
101
|
+
|| !Array.isArray(entry.capabilities) || entry.capabilities.some((capability) => typeof capability !== "string"))) {
|
|
102
|
+
throw new CliError("后台插件清单格式无效");
|
|
103
|
+
}
|
|
104
|
+
return value.plugins;
|
|
105
|
+
}
|
|
106
|
+
function commandManifest(value, plugin) {
|
|
107
|
+
if (!object(value) || value.protocolVersion !== 1 || value.plugin !== plugin || typeof value.version !== "string"
|
|
108
|
+
|| !Array.isArray(value.commands) || value.commands.some((command) => !object(command)
|
|
109
|
+
|| typeof command.name !== "string" || !/^[a-z][a-z0-9-]*$/.test(command.name)
|
|
110
|
+
|| typeof command.summary !== "string" || !command.summary.trim() || typeof command.usage !== "string" || !command.usage.trim()
|
|
111
|
+
|| (command.examples !== undefined && (!Array.isArray(command.examples) || command.examples.some((example) => typeof example !== "string"))))) {
|
|
112
|
+
throw new CliError("插件命令清单协议无效(需要 protocolVersion: 1)");
|
|
113
|
+
}
|
|
114
|
+
if (new Set(value.commands.map((command) => command.name)).size !== value.commands.length)
|
|
115
|
+
throw new CliError("插件命令清单包含重复命令");
|
|
116
|
+
return value;
|
|
117
|
+
}
|
|
118
|
+
async function runPluginCommand(id, args, port, options = {}) {
|
|
119
|
+
const extracted = extractOptions(args);
|
|
120
|
+
const json = options.json ?? extracted.json;
|
|
121
|
+
try {
|
|
122
|
+
if (!PLUGIN_ID.test(id) || RESERVED_COMMANDS.has(id))
|
|
123
|
+
throw new CliError("插件 ID 无效或使用了核心保留命令", 2);
|
|
124
|
+
const plugin = (await discover(port, options)).find((entry) => entry.id === id);
|
|
125
|
+
if (!plugin)
|
|
126
|
+
throw new CliError(`插件“${id}”未安装;请先到 ABWeb 安装插件`, 2);
|
|
127
|
+
if (!plugin.capabilities.includes("cli"))
|
|
128
|
+
throw new CliError(`插件“${plugin.displayName}”未提供 CLI 命令;请升级对应插件`, 2);
|
|
129
|
+
if (plugin.status !== "ready")
|
|
130
|
+
throw new CliError(`插件“${plugin.displayName}”未就绪(${plugin.status})`, 2);
|
|
131
|
+
const path = `/api/plugins/${id}/commands`;
|
|
132
|
+
const response = await requestJson(port, path, options);
|
|
133
|
+
if (response.status < 200 || response.status >= 300)
|
|
134
|
+
throw new CliError(`插件命令清单读取失败(HTTP ${response.status})`);
|
|
135
|
+
const manifest = commandManifest(response.value, id);
|
|
136
|
+
const argv = extracted.argv;
|
|
137
|
+
if (argv.length === 0 || argv[0] === "help" || argv[0] === "--help") {
|
|
138
|
+
const helpArgs = argv.slice(argv[0] === "help" || argv[0] === "--help" ? 1 : 0);
|
|
139
|
+
if (helpArgs.length > 1)
|
|
140
|
+
throw new CliError(`用法:mab ${id} help [命令]`, 2);
|
|
141
|
+
const selected = helpArgs.length === 0 ? manifest.commands : manifest.commands.filter((command) => command.name === helpArgs[0]);
|
|
142
|
+
if (helpArgs.length > 0 && selected.length === 0)
|
|
143
|
+
throw new CliError(`插件“${id}”没有命令:${helpArgs[0]}`, 2);
|
|
144
|
+
const message = [`${plugin.displayName}(${manifest.version})`, ...selected.flatMap((command) => [
|
|
145
|
+
`${command.name}:${command.summary}`, ` ${command.usage}`, ...(command.examples ?? []).map((example) => ` 示例:${example}`),
|
|
146
|
+
])].join("\n");
|
|
147
|
+
return output({ ok: true, message, data: { ...manifest, commands: selected } }, 0, json);
|
|
148
|
+
}
|
|
149
|
+
const executed = await requestJson(port, path, options, { protocolVersion: 1, argv });
|
|
150
|
+
const result = executed.value;
|
|
151
|
+
if (!object(result) || typeof result.ok !== "boolean" || typeof result.message !== "string"
|
|
152
|
+
|| (executed.status >= 200 && executed.status < 300 ? !result.ok : result.ok)
|
|
153
|
+
|| executed.status < 200 || (executed.status >= 300 && executed.status < 400)) {
|
|
154
|
+
throw new CliError("插件命令响应协议无效");
|
|
155
|
+
}
|
|
156
|
+
const exitCode = executed.status >= 500 ? 1 : executed.status >= 400 ? 2 : 0;
|
|
157
|
+
return output(result, exitCode, json);
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
return output({ ok: false, message: error instanceof Error ? error.message : String(error) }, error instanceof CliError ? error.exitCode : 1, json);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
async function runCoreHelp(port, options = {}) {
|
|
164
|
+
let help = exports.CORE_HELP;
|
|
165
|
+
try {
|
|
166
|
+
const plugins = (await discover(port, options)).filter((plugin) => plugin.capabilities.includes("cli") && !RESERVED_COMMANDS.has(plugin.id));
|
|
167
|
+
if (plugins.length)
|
|
168
|
+
help += `\n插件命令(mab <插件 ID> help 查看详情):\n${plugins.map((plugin) => ` ${plugin.id}:${plugin.displayName}(${plugin.status})`).join("\n")}\n`;
|
|
169
|
+
}
|
|
170
|
+
catch { /* 后台不可达时仍显示核心静态帮助 */ }
|
|
171
|
+
return { exitCode: 0, stdout: help, stderr: "" };
|
|
172
|
+
}
|
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);
|
|
@@ -22,6 +22,7 @@ const logger = { info() { }, warn() { }, error() { } };
|
|
|
22
22
|
const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
23
23
|
class PluginManager {
|
|
24
24
|
options;
|
|
25
|
+
controlUrl;
|
|
25
26
|
plugins = new Map();
|
|
26
27
|
httpRoutes = new Map();
|
|
27
28
|
root;
|
|
@@ -41,6 +42,7 @@ class PluginManager {
|
|
|
41
42
|
this.sessions = new channel_sessions_1.ChannelSessions(options.stateDir);
|
|
42
43
|
}
|
|
43
44
|
log(level, message, fields, source = "mab") { this.logs.append({ time: new Date().toISOString(), level, source, message, ...(fields ? { fields } : {}) }); }
|
|
45
|
+
setControlUrl(url) { this.controlUrl = url; }
|
|
44
46
|
async updatePolicy() { try {
|
|
45
47
|
const value = JSON.parse(await (0, promises_1.readFile)((0, node_path_1.join)(this.options.stateDir, "update-policy.json"), "utf8"));
|
|
46
48
|
return isUpdatePolicy(value) ? value : { mode: "auto-install" };
|
|
@@ -365,7 +367,7 @@ class PluginManager {
|
|
|
365
367
|
}
|
|
366
368
|
},
|
|
367
369
|
},
|
|
368
|
-
runtime: { ports: {
|
|
370
|
+
runtime: { controlUrl: this.controlUrl, ports: {
|
|
369
371
|
qwenwork: Number(process.env.AGENT_BRIDGE_QWEN_CDP_PORT ?? process.env.QWEN_CDP_PORT ?? "9228"),
|
|
370
372
|
doubao: Number(process.env.AGENT_BRIDGE_DOUBAO_CDP_PORT ?? process.env.DOUBAO_CDP_PORT ?? "9227"),
|
|
371
373
|
workbuddy: Number(process.env.AGENT_BRIDGE_WORKBUDDY_CDP_PORT ?? process.env.WORKBUDDY_CDP_PORT ?? "9229"),
|
|
@@ -504,8 +506,17 @@ class PluginManager {
|
|
|
504
506
|
async health(id) { const loaded = this.plugins.get(id); if (!loaded)
|
|
505
507
|
throw new agent_1.AgentError(`unknown plugin: ${id}`); if (!loaded.instance)
|
|
506
508
|
return { status: loaded.record.status }; return loaded.instance.health(); }
|
|
507
|
-
async handle(id, request) {
|
|
508
|
-
|
|
509
|
+
async handle(id, request) {
|
|
510
|
+
const loaded = this.plugins.get(id);
|
|
511
|
+
if (!loaded?.instance || loaded.record.status !== "ready") {
|
|
512
|
+
const name = loaded?.record.displayName ?? id;
|
|
513
|
+
const processName = loaded ? pluginProcessName(loaded.record.id) : undefined;
|
|
514
|
+
const processText = processName ? `目标进程 ${processName} 不可用;` : "";
|
|
515
|
+
const reason = loaded?.record.error ? `启动错误:${loaded.record.error}` : `当前状态:${loaded?.record.status ?? "未发现"}`;
|
|
516
|
+
throw new agent_1.AgentError(`插件“${name}”无法调用,插件未就绪:${processText}${reason}`);
|
|
517
|
+
}
|
|
518
|
+
return loaded.instance.handle(request);
|
|
519
|
+
}
|
|
509
520
|
async dispatchHttp(request, response) {
|
|
510
521
|
const method = (request.method ?? "GET").toUpperCase();
|
|
511
522
|
const pathname = new URL(request.url ?? "/", "http://localhost").pathname;
|
|
@@ -809,6 +820,9 @@ class PluginManager {
|
|
|
809
820
|
}
|
|
810
821
|
exports.PluginManager = PluginManager;
|
|
811
822
|
function isUpdatePolicy(value) { return Boolean(value && typeof value === "object" && ["auto-install", "download-only", "manual"].includes(value.mode)); }
|
|
823
|
+
function pluginProcessName(id) {
|
|
824
|
+
return { qwenwork: "QwenWorkCN.exe", doubao: "Doubao.exe", workbuddy: "WorkBuddy.exe", chatgpt: "ChatGPT/Codex" }[id];
|
|
825
|
+
}
|
|
812
826
|
function defaultPluginTrustConfig() { return { providers: { m2lihui: "allow" }, plugins: {} }; }
|
|
813
827
|
function mergeDefaultPluginTrustConfig(value) { return { providers: { m2lihui: "allow", ...value.providers }, plugins: { ...value.plugins } }; }
|
|
814
828
|
function normalizeProvider(value) { return value.trim().toLowerCase(); }
|
|
@@ -35,6 +35,25 @@
|
|
|
35
35
|
let loading = false;
|
|
36
36
|
const installingPackages = new Set();
|
|
37
37
|
function error(message) { errorNode.textContent = message; errorNode.hidden = !message; }
|
|
38
|
+
function showResult(title, message, failed) {
|
|
39
|
+
node("#operation-result-title").textContent = title;
|
|
40
|
+
const detail = node("#operation-result-message");
|
|
41
|
+
detail.textContent = message;
|
|
42
|
+
detail.className = failed ? "error" : "";
|
|
43
|
+
const dialog = node("#operation-result");
|
|
44
|
+
if (!dialog.open)
|
|
45
|
+
dialog.showModal();
|
|
46
|
+
}
|
|
47
|
+
node("#operation-result-close").onclick = () => node("#operation-result").close();
|
|
48
|
+
window.addEventListener("message", (event) => {
|
|
49
|
+
const frame = channelPanel.querySelector("iframe.channel-frame");
|
|
50
|
+
if (event.origin !== window.location.origin || !frame || event.source !== frame.contentWindow)
|
|
51
|
+
return;
|
|
52
|
+
const result = event.data;
|
|
53
|
+
if (!result || result.type !== "mab:operation-result" || typeof result.title !== "string" || typeof result.message !== "string" || typeof result.failed !== "boolean")
|
|
54
|
+
return;
|
|
55
|
+
showResult(result.title, result.message, result.failed);
|
|
56
|
+
});
|
|
38
57
|
async function loadCatalog() {
|
|
39
58
|
const result = await request(`/api/plugins/catalog?q=${encodeURIComponent(packageSearchNode.value.trim())}`);
|
|
40
59
|
const items = result.plugins ?? [];
|
|
@@ -113,7 +132,7 @@
|
|
|
113
132
|
item.type = "button";
|
|
114
133
|
item.textContent = label;
|
|
115
134
|
item.title = title;
|
|
116
|
-
item.onclick = () => { item.disabled = true; void run().catch((cause) =>
|
|
135
|
+
item.onclick = () => { item.disabled = true; void run().catch((cause) => { const message = cause instanceof Error ? cause.message : String(cause); error(message); showResult(`${title}失败`, message, true); }).finally(() => { item.disabled = false; void load(false); }); };
|
|
117
136
|
return item;
|
|
118
137
|
}
|
|
119
138
|
async function load(refreshCatalog = true) {
|
|
@@ -21,6 +21,9 @@
|
|
|
21
21
|
<div id="logs-panel" role="tabpanel" aria-labelledby="logs-tab" hidden><section><div class="section-heading"><h2>运行日志</h2><button id="logs-refresh" type="button">刷新日志</button></div><div class="table-scroll"><table><thead><tr><th>时间</th><th>级别</th><th>来源</th><th>消息</th><th>详细字段</th></tr></thead><tbody id="logs"></tbody></table></div><p id="logs-empty" class="muted" hidden>暂无运行日志</p><div class="section-heading"><button id="logs-prev" type="button">上一页</button><span id="logs-page" class="muted">第 1 页</span><button id="logs-next" type="button">下一页</button></div></section></div>
|
|
22
22
|
<p id="error" class="error" role="alert" hidden></p>
|
|
23
23
|
</main>
|
|
24
|
+
<dialog id="operation-result" aria-labelledby="operation-result-title" aria-describedby="operation-result-message">
|
|
25
|
+
<h2 id="operation-result-title">操作结果</h2><p id="operation-result-message"></p><button id="operation-result-close" type="button" autofocus>确定</button>
|
|
26
|
+
</dialog>
|
|
24
27
|
<script src="/abweb/app.js"></script>
|
|
25
28
|
</body>
|
|
26
29
|
</html>
|
|
@@ -56,3 +56,7 @@ th { color: #566761; font-weight: 600; background: #edf1ef; }
|
|
|
56
56
|
th:nth-child(1) { width: 145px; } th:nth-child(2) { width: 100px; } th:nth-child(3), th:nth-child(4) { width: 145px; } th:nth-child(5) { width: 62px; } th:nth-child(7) { width: 140px; }
|
|
57
57
|
.message-error td:last-child { color: #a83427; }
|
|
58
58
|
@media (max-width: 620px) { .shell { padding: 16px; } .section-heading { align-items: flex-start; flex-direction: column; } form, form input { width: 100%; } }
|
|
59
|
+
|
|
60
|
+
#operation-result { width: min(520px, calc(100% - 32px)); border: 1px solid #bfc9c5; border-radius: 8px; padding: 24px; }
|
|
61
|
+
#operation-result::backdrop { background: rgb(0 0 0 / 35%); }
|
|
62
|
+
#operation-result-message { white-space: pre-wrap; overflow-wrap: anywhere; margin: 16px 0; }
|
|
@@ -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"],
|