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
|
@@ -3,16 +3,16 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.AunPlugin = void 0;
|
|
4
4
|
const promises_1 = require("node:fs/promises");
|
|
5
5
|
const node_fs_1 = require("node:fs");
|
|
6
|
+
const node_crypto_1 = require("node:crypto");
|
|
6
7
|
const node_module_1 = require("node:module");
|
|
7
8
|
const node_path_1 = require("node:path");
|
|
8
9
|
const node_os_1 = require("node:os");
|
|
9
10
|
const node_url_1 = require("node:url");
|
|
10
11
|
const messages_1 = require("./messages");
|
|
11
|
-
const evolcore_1 = require("./evolcore");
|
|
12
12
|
const manifest = {
|
|
13
|
-
id: "aun", version: "1.0.
|
|
13
|
+
id: "aun", version: "1.0.21", type: "channel", displayName: "AUN 通道",
|
|
14
14
|
entry: "dist/src-ts/plugins/aun/index.js", apiVersion: "1", process: "in-process",
|
|
15
|
-
capabilities: ["channel", "channel.identity.create", "channel.receive", "channel.send", "health"],
|
|
15
|
+
capabilities: ["channel", "channel.identity.create", "channel.receive", "channel.send", "health", "cli"],
|
|
16
16
|
configSchema: "config.schema.json",
|
|
17
17
|
managementPage: "/abweb/channels/aun",
|
|
18
18
|
};
|
|
@@ -31,6 +31,24 @@ async function loadSdk() {
|
|
|
31
31
|
throw new Error("未找到 @agentunion/fastaun,请确认 AUN 插件依赖已安装");
|
|
32
32
|
}
|
|
33
33
|
const aidPattern = /^[a-z0-9][a-z0-9_-]{2,63}\.[a-z0-9-]+(?:\.[a-z0-9-]+)+$/;
|
|
34
|
+
const aunRootFingerprint = "DD:09:E9:FD:31:6C:42:DC:7D:F3:5C:98:9C:2C:76:3A:A1:88:D8:4C:9C:C9:FC:D4:2E:A3:10:A2:62:1E:C5:45";
|
|
35
|
+
const cliCommands = [
|
|
36
|
+
{ name: "status", summary: "查看 AUN 通道状态", usage: "mab aun status" },
|
|
37
|
+
{ name: "list", summary: "查看已连接 AID 和 Agent 关联", usage: "mab aun list" },
|
|
38
|
+
{ name: "agents", summary: "列出当前已就绪的 Agent 插件", usage: "mab aun agents" },
|
|
39
|
+
{ name: "create", summary: "创建并连接 AID,可选关联 Agent", usage: "mab aun create <aid> --owner <owner-aid> [--agent <plugin-id>]", examples: ["mab aun create new.agentid.cn --owner owner.agentid.cn --agent qwenwork"] },
|
|
40
|
+
{ name: "bind", summary: "将 AID 关联到 Agent", usage: "mab aun bind <aid> <plugin-id>", examples: ["mab aun bind new.agentid.cn qwenwork"] },
|
|
41
|
+
{ name: "unbind", summary: "解除 Agent 与 AID 的关联", usage: "mab aun unbind <plugin-id>", examples: ["mab aun unbind qwenwork"] },
|
|
42
|
+
];
|
|
43
|
+
class AunCommandError extends Error {
|
|
44
|
+
statusCode;
|
|
45
|
+
data;
|
|
46
|
+
constructor(message, statusCode = 400, data) {
|
|
47
|
+
super(message);
|
|
48
|
+
this.statusCode = statusCode;
|
|
49
|
+
this.data = data;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
34
52
|
class AunPlugin {
|
|
35
53
|
options;
|
|
36
54
|
manifest = manifest;
|
|
@@ -57,10 +75,9 @@ class AunPlugin {
|
|
|
57
75
|
}
|
|
58
76
|
identity = {
|
|
59
77
|
list: async () => {
|
|
60
|
-
const connected = [...this.clients.keys()];
|
|
61
78
|
return {
|
|
62
|
-
evolcore:
|
|
63
|
-
muAgentBridge:
|
|
79
|
+
evolcore: [],
|
|
80
|
+
muAgentBridge: [...this.clients.keys()].sort(),
|
|
64
81
|
localMuAgentBridge: [...this.identities].filter((aid) => !this.clients.has(aid)).sort(),
|
|
65
82
|
};
|
|
66
83
|
},
|
|
@@ -72,11 +89,13 @@ class AunPlugin {
|
|
|
72
89
|
this.creating = true;
|
|
73
90
|
try {
|
|
74
91
|
if (!this.identities.has(aid)) {
|
|
75
|
-
//
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
92
|
+
// Owner AID 是本地归属配置;身份签发沿用 EvolCore 的兼容注册流程。
|
|
93
|
+
if (!this.listStoreAids().includes(aid)) {
|
|
94
|
+
await this.prepareTrustRoots(aid);
|
|
95
|
+
const result = await this.store.register(aid);
|
|
96
|
+
if (!result.ok)
|
|
97
|
+
throw new Error(redact(result.error?.message ?? result.error?.code ?? "AID 注册失败"));
|
|
98
|
+
}
|
|
80
99
|
const loaded = this.store.load(aid);
|
|
81
100
|
if (!loaded.ok || !loaded.data)
|
|
82
101
|
throw new Error(loaded.error?.message ?? "注册后未能加载本地身份");
|
|
@@ -85,17 +104,16 @@ class AunPlugin {
|
|
|
85
104
|
this.config.owner_aid = owner;
|
|
86
105
|
this.config.aun_path = this.path();
|
|
87
106
|
await this.persist();
|
|
88
|
-
this.context?.logger.info("AUN 身份已创建", { aid
|
|
107
|
+
this.context?.logger.info("AUN 身份已创建", { aid });
|
|
89
108
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
return { aid, status: "已创建", connection: this.clients.has(aid) ? "ready" : "degraded" };
|
|
109
|
+
const connectionError = !this.clients.has(aid) ? await this.connectAid(aid) : undefined;
|
|
110
|
+
return { aid, status: "已创建", connection: this.clients.has(aid) ? "ready" : "degraded", ...(connectionError ? { connectionError } : {}) };
|
|
93
111
|
}
|
|
94
112
|
finally {
|
|
95
113
|
this.creating = false;
|
|
96
114
|
}
|
|
97
115
|
},
|
|
98
|
-
bind: async (pluginId, aid,
|
|
116
|
+
bind: async (pluginId, aid, _port) => {
|
|
99
117
|
if (pluginId === "aun" || pluginId === "abweb")
|
|
100
118
|
throw new Error("只能关联 Agent 插件");
|
|
101
119
|
const previous = this.bindings;
|
|
@@ -121,15 +139,21 @@ class AunPlugin {
|
|
|
121
139
|
this.bindings[aid] = pluginId;
|
|
122
140
|
}
|
|
123
141
|
else {
|
|
124
|
-
|
|
125
|
-
|
|
142
|
+
// 本地身份由插件直接接管,不再通过 ec 配置路由。
|
|
143
|
+
const loaded = this.store.load(aid);
|
|
144
|
+
if (!loaded.ok || !loaded.data)
|
|
145
|
+
throw new Error("AID 不在本地密钥库中,无法关联");
|
|
146
|
+
this.identities.add(aid);
|
|
147
|
+
if (!this.clients.has(aid))
|
|
148
|
+
await this.connectAid(aid);
|
|
149
|
+
if (!this.clients.has(aid))
|
|
126
150
|
throw new Error("AID 当前未连接");
|
|
127
|
-
await (0, evolcore_1.configureAidForPlugin)(pluginId, aid, port);
|
|
128
151
|
for (const [identity, target] of Object.entries(this.bindings))
|
|
129
152
|
if (target === pluginId) {
|
|
130
153
|
delete this.bindings[identity];
|
|
131
154
|
delete this.bindingTimes[identity];
|
|
132
155
|
}
|
|
156
|
+
this.bindings[aid] = pluginId;
|
|
133
157
|
}
|
|
134
158
|
}
|
|
135
159
|
else {
|
|
@@ -198,6 +222,8 @@ class AunPlugin {
|
|
|
198
222
|
["GET", "/api/plugins/aun/identities", this.listIdentities],
|
|
199
223
|
["POST", "/api/plugins/aun/bind", this.bindAgent],
|
|
200
224
|
["POST", "/api/plugins/aun/settings", this.saveSettings],
|
|
225
|
+
["GET", "/api/plugins/aun/commands", this.listCommands],
|
|
226
|
+
["POST", "/api/plugins/aun/commands", this.runCommand],
|
|
201
227
|
...(["", "/app.js", "/style.css"].map((suffix) => ["GET", `/abweb/channels/aun${suffix}`, this.servePage])),
|
|
202
228
|
]) {
|
|
203
229
|
const dispose = context.http.register({ method, path, handler });
|
|
@@ -215,13 +241,16 @@ class AunPlugin {
|
|
|
215
241
|
this.identities.add(this.aid);
|
|
216
242
|
for (const aid of this.identities)
|
|
217
243
|
await this.connectAid(aid);
|
|
218
|
-
//
|
|
219
|
-
for (const aid of this.listStoreAids())
|
|
244
|
+
// 密钥库中的其余 AID 也自动连接:不再依赖 ec 或 EvolCore 进程
|
|
245
|
+
for (const aid of this.listStoreAids()) {
|
|
246
|
+
this.identities.add(aid);
|
|
220
247
|
if (!this.clients.has(aid))
|
|
221
248
|
await this.connectAid(aid);
|
|
249
|
+
}
|
|
222
250
|
}
|
|
223
251
|
async connectAid(aid) {
|
|
224
252
|
try {
|
|
253
|
+
await this.prepareTrustRoots(aid);
|
|
225
254
|
const loaded = this.store.load(aid);
|
|
226
255
|
if (!loaded.ok || !loaded.data)
|
|
227
256
|
throw new Error(loaded.error?.message ?? "本地密钥库中不存在该 AID");
|
|
@@ -244,7 +273,8 @@ class AunPlugin {
|
|
|
244
273
|
catch (error) {
|
|
245
274
|
this.status = "degraded";
|
|
246
275
|
this.healthMessage = safeError(error);
|
|
247
|
-
this.context?.logger.warn("AUN 通道连接失败", { error: this.healthMessage });
|
|
276
|
+
this.context?.logger.warn("AUN 通道连接失败", { aid, error: this.healthMessage });
|
|
277
|
+
return this.healthMessage;
|
|
248
278
|
}
|
|
249
279
|
}
|
|
250
280
|
async deactivate(_reason) {
|
|
@@ -272,9 +302,49 @@ class AunPlugin {
|
|
|
272
302
|
return (0, node_path_1.resolve)(process.env.AUN_HOME ?? process.env.EVOLCORE_HOME ?? (0, node_path_1.join)((0, node_os_1.homedir)(), ".evolcore"));
|
|
273
303
|
}
|
|
274
304
|
newStore() {
|
|
275
|
-
const rootCaPath =
|
|
305
|
+
const rootCaPath = this.rootCaPath();
|
|
276
306
|
return new this.sdk.AIDStore({ aunPath: this.path(), encryptionSeed: typeof this.config.encryption_seed === "string" && this.config.encryption_seed ? this.config.encryption_seed : "evol", verifySsl: typeof this.config.verify_ssl === "boolean" ? this.config.verify_ssl : true, rootCaPath: (0, node_fs_1.existsSync)(rootCaPath) ? rootCaPath : undefined });
|
|
277
307
|
}
|
|
308
|
+
rootCaPath() {
|
|
309
|
+
return typeof this.config.root_ca_path === "string" && this.config.root_ca_path
|
|
310
|
+
? this.config.root_ca_path
|
|
311
|
+
: (0, node_path_1.join)(this.context.dataDir, "CA", "root", "root.crt");
|
|
312
|
+
}
|
|
313
|
+
async prepareTrustRoots(aid) {
|
|
314
|
+
const rootCaPath = this.rootCaPath();
|
|
315
|
+
const customPath = typeof this.config.root_ca_path === "string" && this.config.root_ca_path.trim();
|
|
316
|
+
if ((0, node_fs_1.existsSync)(rootCaPath)) {
|
|
317
|
+
if (customPath)
|
|
318
|
+
return;
|
|
319
|
+
const existing = new node_crypto_1.X509Certificate(await (0, promises_1.readFile)(rootCaPath, "utf8"));
|
|
320
|
+
if (existing.fingerprint256 !== aunRootFingerprint)
|
|
321
|
+
throw new Error("AUN 插件根证书指纹校验失败");
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
if (customPath)
|
|
325
|
+
throw new Error("配置的 AUN 根证书文件不存在");
|
|
326
|
+
try {
|
|
327
|
+
const cert = new node_crypto_1.X509Certificate(await (0, promises_1.readFile)((0, node_path_1.join)(__dirname, "certs", "root.crt"), "utf8"));
|
|
328
|
+
if (cert.fingerprint256 !== aunRootFingerprint)
|
|
329
|
+
throw new Error("内置 AUN 根证书指纹校验失败");
|
|
330
|
+
await (0, promises_1.mkdir)((0, node_path_1.dirname)(rootCaPath), { recursive: true });
|
|
331
|
+
await (0, promises_1.writeFile)(rootCaPath, cert.toString(), { mode: 0o600, flag: "wx" });
|
|
332
|
+
const oldStore = this.store;
|
|
333
|
+
const newStore = this.newStore();
|
|
334
|
+
this.store = newStore;
|
|
335
|
+
try {
|
|
336
|
+
await oldStore?.close();
|
|
337
|
+
}
|
|
338
|
+
catch (error) {
|
|
339
|
+
this.context?.logger.warn("关闭旧 AUN Store 失败", { error: safeError(error) });
|
|
340
|
+
}
|
|
341
|
+
this.context?.logger.info("AUN 固定信任根证书已安装", { aid });
|
|
342
|
+
}
|
|
343
|
+
catch (error) {
|
|
344
|
+
this.context?.logger.warn("AUN Gateway 信任根证书初始化失败", { aid, error: safeError(error) });
|
|
345
|
+
throw new Error(`AUN Gateway 信任根证书初始化失败:${safeError(error)}`);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
278
348
|
createAid = async (request, response) => {
|
|
279
349
|
let input;
|
|
280
350
|
try {
|
|
@@ -367,6 +437,254 @@ class AunPlugin {
|
|
|
367
437
|
this.json(response, 400, { error: safeError(error) });
|
|
368
438
|
}
|
|
369
439
|
};
|
|
440
|
+
listCommands = async (_request, response) => {
|
|
441
|
+
response.setHeader("cache-control", "no-store");
|
|
442
|
+
this.json(response, 200, { protocolVersion: 1, plugin: "aun", version: manifest.version, commands: cliCommands });
|
|
443
|
+
};
|
|
444
|
+
runCommand = async (request, response) => {
|
|
445
|
+
try {
|
|
446
|
+
const chunks = [];
|
|
447
|
+
let size = 0;
|
|
448
|
+
for await (const chunk of request) {
|
|
449
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));
|
|
450
|
+
size += buffer.length;
|
|
451
|
+
if (size > 65536)
|
|
452
|
+
throw new AunCommandError("请求内容过大");
|
|
453
|
+
chunks.push(buffer);
|
|
454
|
+
}
|
|
455
|
+
let input;
|
|
456
|
+
try {
|
|
457
|
+
input = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
458
|
+
}
|
|
459
|
+
catch {
|
|
460
|
+
throw new AunCommandError("命令请求 JSON 格式无效");
|
|
461
|
+
}
|
|
462
|
+
if (!input || typeof input !== "object" || input.protocolVersion !== 1 || !Array.isArray(input.argv) || !input.argv.every((arg) => typeof arg === "string")) {
|
|
463
|
+
throw new AunCommandError("命令请求格式无效");
|
|
464
|
+
}
|
|
465
|
+
const result = await this.executeCommand(input.argv);
|
|
466
|
+
this.commandResult(response, 200, true, result.message, result.data);
|
|
467
|
+
}
|
|
468
|
+
catch (error) {
|
|
469
|
+
const failure = error instanceof AunCommandError ? error : new AunCommandError(safeError(error), 502);
|
|
470
|
+
this.commandResult(response, failure.statusCode, false, failure.message, failure.data);
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
async executeCommand(argv) {
|
|
474
|
+
const [command, ...args] = argv;
|
|
475
|
+
if (command === "status" && !args.length)
|
|
476
|
+
return { message: this.healthMessage, data: await this.health() };
|
|
477
|
+
if (command === "list" && !args.length) {
|
|
478
|
+
const groups = await this.identity.list();
|
|
479
|
+
const aids = [...new Set([...groups.evolcore, ...groups.muAgentBridge, ...(groups.localMuAgentBridge ?? []), ...this.listStoreAids()])].sort();
|
|
480
|
+
return { message: aids.length ? `AID 列表:\n${aids.join("\n")}` : "暂无本地 AID", data: {
|
|
481
|
+
aids,
|
|
482
|
+
bindings: this.bindings,
|
|
483
|
+
bindingTimes: this.bindingTimes,
|
|
484
|
+
} };
|
|
485
|
+
}
|
|
486
|
+
if (command === "agents" && !args.length) {
|
|
487
|
+
const agents = await this.agents();
|
|
488
|
+
return { message: agents.length ? `当前已就绪的 Agent 插件:\n${agents.map((agent) => `${agent.id}(${agent.displayName})`).join("\n")}` : "暂无已就绪的 Agent 插件", data: agents };
|
|
489
|
+
}
|
|
490
|
+
if (command === "create")
|
|
491
|
+
return this.createCommand(args);
|
|
492
|
+
if (command === "bind" && args.length === 2) {
|
|
493
|
+
const [aid, pluginId] = args;
|
|
494
|
+
if (!aidPattern.test(aid))
|
|
495
|
+
throw new AunCommandError("AID 格式无效");
|
|
496
|
+
this.validatePluginId(pluginId);
|
|
497
|
+
await this.requireAgent(pluginId);
|
|
498
|
+
const replacedAids = (await this.agentBindings(pluginId)).filter((previousAid) => previousAid !== aid);
|
|
499
|
+
try {
|
|
500
|
+
await this.patchAgentAid(pluginId, aid);
|
|
501
|
+
}
|
|
502
|
+
catch (error) {
|
|
503
|
+
const bindingState = await this.bindingState(pluginId).catch(() => undefined);
|
|
504
|
+
throw new AunCommandError(`AID 关联请求失败,已回读当前状态:${safeError(error)}`, error instanceof AunCommandError ? error.statusCode : 502, { aid, agent: pluginId, ...(bindingState ? { bindingState } : { bindingState: "unknown" }) });
|
|
505
|
+
}
|
|
506
|
+
return {
|
|
507
|
+
message: replacedAids.length ? `AID ${replacedAids.join("、")} 已替换为 ${aid} 并关联到 ${pluginId}` : `AID 已关联到 ${pluginId}`,
|
|
508
|
+
data: { aid, agent: pluginId, replacedAids },
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
if (command === "unbind" && args.length === 1) {
|
|
512
|
+
const [pluginId] = args;
|
|
513
|
+
this.validatePluginId(pluginId);
|
|
514
|
+
await this.requireAgent(pluginId, false);
|
|
515
|
+
const unboundAids = await this.agentBindings(pluginId);
|
|
516
|
+
try {
|
|
517
|
+
await this.patchAgentAid(pluginId, "");
|
|
518
|
+
}
|
|
519
|
+
catch (error) {
|
|
520
|
+
const bindingState = await this.bindingState(pluginId).catch(() => undefined);
|
|
521
|
+
throw new AunCommandError(`解除关联请求失败,已回读当前状态:${safeError(error)}`, error instanceof AunCommandError ? error.statusCode : 502, { agent: pluginId, ...(bindingState ? { bindingState } : { bindingState: "unknown" }) });
|
|
522
|
+
}
|
|
523
|
+
return { message: `已解除 ${pluginId} 的 AID 关联`, data: { agent: pluginId, unboundAids } };
|
|
524
|
+
}
|
|
525
|
+
if (!["status", "list", "agents", "create", "bind", "unbind"].includes(command ?? ""))
|
|
526
|
+
throw new AunCommandError(`未知 AUN 命令:${command ?? ""}`);
|
|
527
|
+
throw new AunCommandError(`AUN 命令参数无效:${command}`);
|
|
528
|
+
}
|
|
529
|
+
async createCommand(args) {
|
|
530
|
+
const aid = args[0] ?? "";
|
|
531
|
+
if (!aidPattern.test(aid))
|
|
532
|
+
throw new AunCommandError("AID 格式无效");
|
|
533
|
+
let owner = "";
|
|
534
|
+
let agent = "";
|
|
535
|
+
for (let index = 1; index < args.length; index++) {
|
|
536
|
+
const option = args[index];
|
|
537
|
+
const value = args[++index];
|
|
538
|
+
if (!value || value.startsWith("--"))
|
|
539
|
+
throw new AunCommandError(`${option} 需要参数`);
|
|
540
|
+
if (option === "--owner" && !owner)
|
|
541
|
+
owner = value;
|
|
542
|
+
else if (option === "--agent" && !agent)
|
|
543
|
+
agent = value;
|
|
544
|
+
else
|
|
545
|
+
throw new AunCommandError(`不支持的创建参数:${option}`);
|
|
546
|
+
}
|
|
547
|
+
if (!owner || !aidPattern.test(owner))
|
|
548
|
+
throw new AunCommandError("需要有效的 --owner AID");
|
|
549
|
+
if (agent) {
|
|
550
|
+
this.validatePluginId(agent);
|
|
551
|
+
await this.requireAgent(agent);
|
|
552
|
+
}
|
|
553
|
+
let created;
|
|
554
|
+
try {
|
|
555
|
+
created = await this.identity.create(aid, owner);
|
|
556
|
+
}
|
|
557
|
+
catch (error) {
|
|
558
|
+
const exists = this.identities.has(aid) || this.listStoreAids().includes(aid);
|
|
559
|
+
if (exists)
|
|
560
|
+
throw new AunCommandError("AID 可能已创建,但后续连接或保存失败;请先执行 mab aun list 确认,不要重试创建", 502, { created: true, aid });
|
|
561
|
+
throw new AunCommandError(`AID 创建失败:${safeError(error).replaceAll(owner, "[已隐藏]")}`, 502);
|
|
562
|
+
}
|
|
563
|
+
const data = { created: true, aid, connection: created.connection ?? "ready" };
|
|
564
|
+
if (created.connection !== "ready")
|
|
565
|
+
throw new AunCommandError("AID 已创建,但连接失败;请先执行 mab aun list 确认,不要重试创建", 502, { ...data, connectionError: created.connectionError });
|
|
566
|
+
let replacedAids = [];
|
|
567
|
+
if (agent) {
|
|
568
|
+
try {
|
|
569
|
+
replacedAids = (await this.agentBindings(agent)).filter((previousAid) => previousAid !== aid);
|
|
570
|
+
await this.patchAgentAid(agent, aid);
|
|
571
|
+
}
|
|
572
|
+
catch (error) {
|
|
573
|
+
const bindingState = await this.bindingState(agent).catch(() => undefined);
|
|
574
|
+
throw new AunCommandError(`AID 已创建,但 Agent 关联失败,已回读当前状态:${safeError(error)}`, 502, { ...data, agent, ...(bindingState ? { bindingState } : { bindingState: "unknown" }) });
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
return {
|
|
578
|
+
message: agent ? (replacedAids.length ? `AID ${replacedAids.join("、")} 已替换为 ${aid} 并关联到 ${agent}` : `AID 已创建并关联到 ${agent}`) : "AID 已创建并连接",
|
|
579
|
+
data: { ...data, ...(agent ? { agent, replacedAids } : {}) },
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
async agents(readyOnly = true) {
|
|
583
|
+
const payload = await this.controlRequest("/api/plugins");
|
|
584
|
+
if (!Array.isArray(payload.plugins))
|
|
585
|
+
throw new AunCommandError("控制 API 插件列表格式无效", 502);
|
|
586
|
+
return payload.plugins.filter((plugin) => !!plugin && typeof plugin === "object" && !Array.isArray(plugin))
|
|
587
|
+
.filter((plugin) => plugin.type === "agent" && (!readyOnly || plugin.status === "ready") && typeof plugin.id === "string" && /^[a-z][a-z0-9-]{1,63}$/.test(plugin.id))
|
|
588
|
+
.map((plugin) => ({ id: plugin.id, displayName: typeof plugin.displayName === "string" ? plugin.displayName : plugin.id }))
|
|
589
|
+
.sort((left, right) => left.id.localeCompare(right.id));
|
|
590
|
+
}
|
|
591
|
+
async requireAgent(pluginId, readyOnly = true) {
|
|
592
|
+
if (!(await this.agents(readyOnly)).some((agent) => agent.id === pluginId))
|
|
593
|
+
throw new AunCommandError(readyOnly ? "目标 Agent 不存在或尚未就绪" : "目标插件不存在或不是 Agent 插件");
|
|
594
|
+
}
|
|
595
|
+
validatePluginId(pluginId) {
|
|
596
|
+
if (!/^[a-z][a-z0-9-]{1,63}$/.test(pluginId) || pluginId === "aun" || pluginId === "abweb")
|
|
597
|
+
throw new AunCommandError("Agent 插件 ID 无效");
|
|
598
|
+
}
|
|
599
|
+
async patchAgentAid(pluginId, aid) {
|
|
600
|
+
await this.controlRequest(`/api/plugins/${encodeURIComponent(pluginId)}/aid`, "PATCH", { aid });
|
|
601
|
+
}
|
|
602
|
+
async agentBindings(pluginId) {
|
|
603
|
+
const aun = await this.controlPayload("/api/plugins/aun/identities");
|
|
604
|
+
if (!aun.bindings || typeof aun.bindings !== "object" || Array.isArray(aun.bindings))
|
|
605
|
+
throw new AunCommandError("AUN 身份列表格式无效", 502);
|
|
606
|
+
const bindings = aun.bindings;
|
|
607
|
+
return Object.entries(bindings).filter(([, target]) => target === pluginId).map(([aid]) => aid).sort();
|
|
608
|
+
}
|
|
609
|
+
async bindingState(pluginId) {
|
|
610
|
+
const [aun, agent] = await Promise.all([
|
|
611
|
+
this.agentBindings(pluginId),
|
|
612
|
+
this.controlPayload(`/api/plugins/${encodeURIComponent(pluginId)}/config`),
|
|
613
|
+
]);
|
|
614
|
+
return {
|
|
615
|
+
aunAids: aun,
|
|
616
|
+
agentAid: typeof agent.aid === "string" ? agent.aid : "",
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
async controlRequest(path, method = "GET", body) {
|
|
620
|
+
const result = await this.controlPayload(path, method, body);
|
|
621
|
+
if (result.ok !== true)
|
|
622
|
+
throw new AunCommandError("本机控制 API 响应未确认操作成功", 502);
|
|
623
|
+
return result;
|
|
624
|
+
}
|
|
625
|
+
async controlPayload(path, method = "GET", body) {
|
|
626
|
+
const base = this.controlUrl();
|
|
627
|
+
let response;
|
|
628
|
+
try {
|
|
629
|
+
response = await fetch(new URL(path, base), {
|
|
630
|
+
method,
|
|
631
|
+
headers: { accept: "application/json", ...(body === undefined ? {} : { "content-type": "application/json" }) },
|
|
632
|
+
...(body === undefined ? {} : { body: JSON.stringify(body) }),
|
|
633
|
+
signal: AbortSignal.timeout(5000),
|
|
634
|
+
redirect: "error",
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
catch (error) {
|
|
638
|
+
throw new AunCommandError(`本机控制 API 请求失败:${safeError(error)}`, 502);
|
|
639
|
+
}
|
|
640
|
+
let raw;
|
|
641
|
+
try {
|
|
642
|
+
raw = await readResponse(response, 65536);
|
|
643
|
+
}
|
|
644
|
+
catch (error) {
|
|
645
|
+
if (error instanceof AunCommandError)
|
|
646
|
+
throw error;
|
|
647
|
+
throw new AunCommandError(`本机控制 API 响应读取失败:${safeError(error)}`, 502);
|
|
648
|
+
}
|
|
649
|
+
let payload;
|
|
650
|
+
try {
|
|
651
|
+
payload = JSON.parse(raw);
|
|
652
|
+
}
|
|
653
|
+
catch {
|
|
654
|
+
throw new AunCommandError("本机控制 API 返回的 JSON 无效", 502);
|
|
655
|
+
}
|
|
656
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
657
|
+
throw new AunCommandError("本机控制 API 响应格式无效", 502);
|
|
658
|
+
const result = payload;
|
|
659
|
+
if (!response.ok) {
|
|
660
|
+
const message = typeof result.error === "string" ? result.error : `HTTP ${response.status}`;
|
|
661
|
+
throw new AunCommandError(`本机控制 API 操作失败:${message}`, response.status >= 400 && response.status < 500 ? response.status : 502);
|
|
662
|
+
}
|
|
663
|
+
return result;
|
|
664
|
+
}
|
|
665
|
+
controlUrl() {
|
|
666
|
+
const controlUrl = this.context?.runtime?.controlUrl;
|
|
667
|
+
if (!controlUrl)
|
|
668
|
+
throw new AunCommandError("运行时未提供 controlUrl,请更新核心服务", 502);
|
|
669
|
+
let url;
|
|
670
|
+
try {
|
|
671
|
+
url = new URL(controlUrl);
|
|
672
|
+
}
|
|
673
|
+
catch {
|
|
674
|
+
throw new AunCommandError("运行时 controlUrl 无效", 502);
|
|
675
|
+
}
|
|
676
|
+
const hostname = url.hostname.replace(/^\[|\]$/g, "");
|
|
677
|
+
if (url.protocol !== "http:" || !["127.0.0.1", "::1"].includes(hostname) || url.username || url.password || (url.pathname !== "/") || url.search || url.hash) {
|
|
678
|
+
throw new AunCommandError("运行时 controlUrl 必须指向本机回环地址", 502);
|
|
679
|
+
}
|
|
680
|
+
return url;
|
|
681
|
+
}
|
|
682
|
+
commandResult(response, status, ok, message, data) {
|
|
683
|
+
response.statusCode = status;
|
|
684
|
+
response.setHeader("content-type", "application/json; charset=utf-8");
|
|
685
|
+
response.setHeader("cache-control", "no-store");
|
|
686
|
+
response.end(JSON.stringify({ ok, message, ...(data === undefined ? {} : { data }) }));
|
|
687
|
+
}
|
|
370
688
|
servePage = async (request, response) => {
|
|
371
689
|
const path = new URL(request.url ?? "/", "http://localhost").pathname;
|
|
372
690
|
const file = path.endsWith("/app.js") ? "app.js" : path.endsWith("/style.css") ? "style.css" : "index.html";
|
|
@@ -428,6 +746,34 @@ exports.default = AunPlugin;
|
|
|
428
746
|
function safeError(error) {
|
|
429
747
|
return redact(error instanceof Error ? error.message : String(error)).slice(0, 240);
|
|
430
748
|
}
|
|
749
|
+
async function readResponse(response, limit) {
|
|
750
|
+
if (!response.body) {
|
|
751
|
+
const raw = await response.text();
|
|
752
|
+
if (Buffer.byteLength(raw) > limit)
|
|
753
|
+
throw new AunCommandError("响应内容过大", 502);
|
|
754
|
+
return raw;
|
|
755
|
+
}
|
|
756
|
+
const reader = response.body.getReader();
|
|
757
|
+
const chunks = [];
|
|
758
|
+
let size = 0;
|
|
759
|
+
try {
|
|
760
|
+
while (true) {
|
|
761
|
+
const { done, value } = await reader.read();
|
|
762
|
+
if (done)
|
|
763
|
+
break;
|
|
764
|
+
size += value.byteLength;
|
|
765
|
+
if (size > limit) {
|
|
766
|
+
await reader.cancel();
|
|
767
|
+
throw new AunCommandError("响应内容过大", 502);
|
|
768
|
+
}
|
|
769
|
+
chunks.push(Buffer.from(value));
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
finally {
|
|
773
|
+
reader.releaseLock();
|
|
774
|
+
}
|
|
775
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
776
|
+
}
|
|
431
777
|
function redact(message) {
|
|
432
778
|
return message.replace(/(private[_ -]?key|seed|token|certificate)\s*[:=]\s*[^\s,;]+/gi, "$1=[已隐藏]");
|
|
433
779
|
}
|
|
@@ -15,6 +15,12 @@
|
|
|
15
15
|
return result;
|
|
16
16
|
}
|
|
17
17
|
function show(message, failed = false) { status.textContent = message; status.className = failed ? "error" : ""; }
|
|
18
|
+
function notify(title, message, failed = false) {
|
|
19
|
+
show(message, failed);
|
|
20
|
+
if (window.parent !== window) window.parent.postMessage({ type: "mab:operation-result", title, message, failed }, window.location.origin);
|
|
21
|
+
else window.alert(`${title}\n${message}`);
|
|
22
|
+
}
|
|
23
|
+
const errorMessage = (error) => error instanceof Error ? error.message : String(error);
|
|
18
24
|
async function load(refreshForm = true) {
|
|
19
25
|
if (loading) return;
|
|
20
26
|
loading = true;
|
|
@@ -31,8 +37,15 @@
|
|
|
31
37
|
configForm.elements.encryption_seed.value = "";
|
|
32
38
|
}
|
|
33
39
|
list.replaceChildren();
|
|
34
|
-
const entries = [
|
|
35
|
-
|
|
40
|
+
const entries = [];
|
|
41
|
+
const seen = new Set();
|
|
42
|
+
for (const [group, label, values] of [[0, "本工程 · 已连接", identities.groups.muAgentBridge || []], [1, "EvolCore · 已连接", identities.groups.evolcore || []]]) {
|
|
43
|
+
for (const aid of values) {
|
|
44
|
+
if (seen.has(aid)) continue;
|
|
45
|
+
seen.add(aid);
|
|
46
|
+
entries.push({ aid, label, group, bound: identities.bindings?.[aid] || agents.find((agent) => agent.id && agent.configAid === aid)?.id || "", time: Date.parse(identities.bindingTimes?.[aid] || "") || 0 });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
36
49
|
entries.sort((a, b) => Number(Boolean(b.bound)) - Number(Boolean(a.bound)) || a.group - b.group || b.time - a.time || a.aid.localeCompare(b.aid));
|
|
37
50
|
for (const { aid, label, bound } of entries) {
|
|
38
51
|
const row = document.createElement("tr");
|
|
@@ -43,22 +56,29 @@
|
|
|
43
56
|
cell.append(select); row.append(cell); list.append(row);
|
|
44
57
|
}
|
|
45
58
|
if (!list.children.length) { const row = document.createElement("tr"); const cell = document.createElement("td"); cell.colSpan = 4; cell.textContent = "暂无已连接 AID"; row.append(cell); list.append(row); }
|
|
46
|
-
show(`${
|
|
59
|
+
show(`${entries.length} 个已连接 AID`);
|
|
47
60
|
} catch (error) { show(String(error), true); }
|
|
48
61
|
finally { loading = false; }
|
|
49
62
|
}
|
|
50
63
|
async function bind(aid, pluginId) {
|
|
51
64
|
try {
|
|
52
65
|
await request(`/api/plugins/${encodeURIComponent(pluginId)}/aid`, "PATCH", { aid });
|
|
53
|
-
await load();
|
|
54
|
-
} catch (error) {
|
|
66
|
+
await load(); notify("关联成功", `${aid} 已关联 ${pluginId}`);
|
|
67
|
+
} catch (error) { notify("关联失败", errorMessage(error), true); }
|
|
55
68
|
}
|
|
56
69
|
createForm.onsubmit = (event) => { event.preventDefault(); const aid = createForm.elements.aid.value.trim(); const owner = createForm.elements.ownerAid.value.trim();
|
|
57
|
-
|
|
70
|
+
show("正在创建 AID 并连接,请稍候……");
|
|
71
|
+
void request("/api/plugins/aun/aid", "POST", { aid, ownerAid: owner }).then((result) => {
|
|
72
|
+
const failed = result.connection === "degraded";
|
|
73
|
+
const message = failed ? `${aid} 已创建,但连接失败:${result.connectionError || "未提供连接失败原因,请查看运行日志"}\n身份已经保存在本地,请勿重复注册。`
|
|
74
|
+
: result.status === "等待 Owner 确认" ? `${aid} 等待 Owner 确认` : `${aid} 已创建并连接`;
|
|
75
|
+
notify(failed ? "AID 创建完成,连接失败" : "AID 创建结果", message, failed);
|
|
76
|
+
return load();
|
|
77
|
+
}).catch((error) => notify("创建 AID 失败", errorMessage(error), true));
|
|
58
78
|
};
|
|
59
79
|
configForm.onsubmit = (event) => { event.preventDefault(); const changes = { agent_plugin: configForm.elements.agent_plugin.value, allow_from: configForm.elements.allow_from.value, aun_path: configForm.elements.aun_path.value };
|
|
60
80
|
if (configForm.elements.encryption_seed.value) changes.encryption_seed = configForm.elements.encryption_seed.value;
|
|
61
|
-
void request("/api/plugins/aun/settings", "POST", changes).then(() => request("/api/plugins/aun/reload", "POST")).then(() => load()).then(() =>
|
|
81
|
+
void request("/api/plugins/aun/settings", "POST", changes).then(() => request("/api/plugins/aun/reload", "POST")).then(() => load()).then(() => notify("保存成功", "配置已保存")).catch((error) => notify("保存配置失败", errorMessage(error), true));
|
|
62
82
|
};
|
|
63
83
|
find("#refresh").onclick = () => void load();
|
|
64
84
|
void load();
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
<header><h1>AUN 身份</h1><button id="refresh" type="button">刷新</button></header>
|
|
7
7
|
<p id="status" role="status"></p>
|
|
8
8
|
<div class="table-scroll"><table><thead><tr><th>AID</th><th>来源</th><th>已关联 Agent</th><th>操作</th></tr></thead><tbody id="identities"></tbody></table></div>
|
|
9
|
-
<form id="create-form"><h2>创建 AID</h2><div class="fields"><label>新 AID<input name="aid" required placeholder="例如:example.agentid.cn"></label><label>Owner AID<input name="ownerAid" required placeholder="例如:owner.agentid.cn(本地归属配置)"><small class="hint">Owner
|
|
9
|
+
<form id="create-form"><h2>创建 AID</h2><div class="fields"><label>新 AID<input name="aid" required placeholder="例如:example.agentid.cn"></label><label>Owner AID<input name="ownerAid" required placeholder="例如:owner.agentid.cn(本地归属配置)"><small class="hint">Owner 保存为本地归属配置;不代表 AUN 协议中的 Owner 关系已激活。</small></label></div><button type="submit">创建</button></form>
|
|
10
10
|
<form id="config-form"><h2>通道配置</h2><div class="fields"><label>默认 Agent<select name="agent_plugin"></select></label><label>允许的来源<input name="allow_from"></label><label>AUN 存储路径<input name="aun_path"></label><label>加密种子<input name="encryption_seed" type="password" autocomplete="new-password" placeholder="留空则不修改"></label></div><button type="submit">保存配置</button></form>
|
|
11
11
|
</main>
|
|
12
12
|
<script src="/abweb/channels/aun/app.js"></script>
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.findWindowsExecutable = findWindowsExecutable;
|
|
4
|
+
exports.findExecutableInTree = findExecutableInTree;
|
|
5
|
+
exports.launchAndWait = launchAndWait;
|
|
6
|
+
exports.waitFor = waitFor;
|
|
7
|
+
const promises_1 = require("node:fs/promises");
|
|
8
|
+
const node_fs_1 = require("node:fs");
|
|
9
|
+
const node_path_1 = require("node:path");
|
|
10
|
+
/** 在 Windows 常见安装目录中查找桌面程序,避免依赖单一盘符。 */
|
|
11
|
+
async function findWindowsExecutable(fileName, configured) {
|
|
12
|
+
if (configured?.trim())
|
|
13
|
+
return (0, node_fs_1.existsSync)((0, node_path_1.resolve)(configured)) ? (0, node_path_1.resolve)(configured) : undefined;
|
|
14
|
+
if (process.platform !== "win32")
|
|
15
|
+
return undefined;
|
|
16
|
+
const pattern = /doubao|豆包|qwen|qwwork|千问/i;
|
|
17
|
+
const roots = [...new Set([
|
|
18
|
+
process.env.LOCALAPPDATA && (0, node_path_1.join)(process.env.LOCALAPPDATA, "Programs"),
|
|
19
|
+
process.env.ProgramFiles,
|
|
20
|
+
process.env["ProgramFiles(x86)"],
|
|
21
|
+
...Array.from({ length: 24 }, (_, index) => `${String.fromCharCode(67 + index)}:\\`),
|
|
22
|
+
].filter((value) => typeof value === "string" && (0, node_fs_1.existsSync)(value)))];
|
|
23
|
+
for (const root of roots) {
|
|
24
|
+
const found = await findExecutableInTree(root, fileName, pattern, 6);
|
|
25
|
+
if (found)
|
|
26
|
+
return found;
|
|
27
|
+
}
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
async function findExecutableInTree(directory, fileName, pattern, depth) {
|
|
31
|
+
return search(directory, fileName, pattern, depth, pattern.test(directory));
|
|
32
|
+
}
|
|
33
|
+
async function search(directory, fileName, pattern, depth, insideProduct) {
|
|
34
|
+
try {
|
|
35
|
+
const entries = await (0, promises_1.readdir)(directory, { withFileTypes: true });
|
|
36
|
+
for (const entry of entries)
|
|
37
|
+
if (entry.isFile() && insideProduct && entry.name.toLowerCase() === fileName.toLowerCase())
|
|
38
|
+
return (0, node_path_1.join)(directory, entry.name);
|
|
39
|
+
if (depth <= 0)
|
|
40
|
+
return undefined;
|
|
41
|
+
for (const entry of entries)
|
|
42
|
+
if (entry.isDirectory() && !entry.name.startsWith(".") && (insideProduct || pattern.test(entry.name) || /^(?:Program Files(?: \(x86\))?|Programs)$/i.test(entry.name))) {
|
|
43
|
+
const found = await search((0, node_path_1.join)(directory, entry.name), fileName, pattern, depth - 1, insideProduct || pattern.test(entry.name));
|
|
44
|
+
if (found)
|
|
45
|
+
return found;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch { /* 无权限或目录在扫描期间消失时继续查找 */ }
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
async function launchAndWait(executable, args, connect, timeoutMs, launch = (file, argv) => (0, node_child_process_1.spawn)(file, argv, { detached: true, stdio: "ignore", windowsHide: false })) {
|
|
52
|
+
const child = launch(executable, args);
|
|
53
|
+
child.unref();
|
|
54
|
+
return waitFor(connect, timeoutMs, 250);
|
|
55
|
+
}
|
|
56
|
+
async function waitFor(action, timeoutMs, intervalMs = 500) {
|
|
57
|
+
const deadline = Date.now() + timeoutMs;
|
|
58
|
+
let last;
|
|
59
|
+
while (Date.now() < deadline) {
|
|
60
|
+
try {
|
|
61
|
+
return await action();
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
last = error;
|
|
65
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
throw last instanceof Error ? last : new Error("桌面程序启动超时");
|
|
69
|
+
}
|
|
70
|
+
const node_child_process_1 = require("node:child_process");
|