agent-bridge-aun 1.0.33 → 1.0.35
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 +2 -0
- package/dist/src-ts/plugins/aun/evolcore.js +93 -17
- package/dist/src-ts/plugins/aun/index.js +87 -40
- package/package.json +1 -1
- package/plugin.json +1 -1
package/README.md
CHANGED
|
@@ -15,3 +15,5 @@
|
|
|
15
15
|
ABWeb 的 AUN 设置只展示已连接 AID,并按来源分组。成功注册并记录在插件 `created_aids` 中的 AID 属于本工程;共享密钥库中其余 AID 属于 EvolCore。旧版 `created-aids.json` 记录继续兼容。来源只用于展示,连接和关联由 AUN 插件直接管理。列表先显示已关联 Agent 的身份,各组按最近成功关联的时间倒序排列,时间相同或旧记录没有时间时按 AID 名称排序。关联时间保存在插件配置的 `binding_times` 中,刷新或重启后保留;旧记录不补造时间,再次关联成功后开始记录。
|
|
16
16
|
|
|
17
17
|
AUN 独立包支持核心后台每五分钟检测更新;可信提供者在自动安装策略下会下载新副本并重载通道。
|
|
18
|
+
|
|
19
|
+
显式关联已有 EvolCore Agent 的 AID 时,先执行 `ec agent disable <aid>`,再配置 `ecagent` 路由并重载。`ec` 不存在或命令失败时,只将所选 AID 的 JSON 配置更新为 `enabled=false`、`active_baseagent=ecagent` 和对应路由;其它 AID 保持原值。仍有 EvolCore 运行实例时,必须确认重载成功才完成关联,避免原 Claude 与桌面 Agent 同时回复。纯密钥库身份不调用 `ec`,解绑不自动启用原 Agent。
|
|
@@ -4,10 +4,14 @@ exports.runEc = runEc;
|
|
|
4
4
|
exports.configureAidForPlugin = configureAidForPlugin;
|
|
5
5
|
const node_child_process_1 = require("node:child_process");
|
|
6
6
|
const node_util_1 = require("node:util");
|
|
7
|
+
const promises_1 = require("node:fs/promises");
|
|
8
|
+
const node_crypto_1 = require("node:crypto");
|
|
9
|
+
const node_os_1 = require("node:os");
|
|
10
|
+
const node_path_1 = require("node:path");
|
|
7
11
|
const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
8
|
-
async function runEc(args) {
|
|
12
|
+
async function runEc(args, home) {
|
|
9
13
|
try {
|
|
10
|
-
const { stdout } = await execFileAsync("ec", args, { shell: process.platform === "win32", timeout:
|
|
14
|
+
const { stdout } = await execFileAsync("ec", args, { shell: process.platform === "win32", timeout: 60_000, env: { ...process.env, ...(home ? { EVOLCORE_HOME: home } : {}) }, windowsHide: true, maxBuffer: 1024 * 1024 });
|
|
11
15
|
return JSON.parse(stdout);
|
|
12
16
|
}
|
|
13
17
|
catch (error) {
|
|
@@ -21,24 +25,96 @@ async function runEc(args) {
|
|
|
21
25
|
throw error;
|
|
22
26
|
}
|
|
23
27
|
}
|
|
24
|
-
async function
|
|
25
|
-
|
|
26
|
-
|
|
28
|
+
async function ecIsRunning(home) {
|
|
29
|
+
const records = [];
|
|
30
|
+
try {
|
|
31
|
+
records.push(Number(await (0, promises_1.readFile)((0, node_path_1.join)(home, "daemon.pid"), "utf8")));
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
if (error.code !== "ENOENT")
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
const directory = (0, node_path_1.join)(home, "data", "instance");
|
|
38
|
+
let names;
|
|
39
|
+
try {
|
|
40
|
+
names = await (0, promises_1.readdir)(directory);
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
if (error.code !== "ENOENT")
|
|
44
|
+
throw error;
|
|
45
|
+
names = [];
|
|
46
|
+
}
|
|
47
|
+
for (const name of names.filter(name => /^main-\d+\.json$/.test(name))) {
|
|
48
|
+
records.push(JSON.parse(await (0, promises_1.readFile)((0, node_path_1.join)(directory, name), "utf8")).pid);
|
|
49
|
+
}
|
|
50
|
+
return records.some(pid => {
|
|
51
|
+
if (!Number.isSafeInteger(pid) || pid <= 0)
|
|
52
|
+
return false;
|
|
53
|
+
try {
|
|
54
|
+
process.kill(pid, 0);
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
if (error.code === "ESRCH")
|
|
59
|
+
return false;
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
async function configureAidForPlugin(pluginId, aid, port, run = runEc, home = process.env.EVOLCORE_HOME ?? (0, node_path_1.join)((0, node_os_1.homedir)(), ".evolcore")) {
|
|
65
|
+
if (!/^[a-z0-9][a-z0-9_.-]+$/i.test(aid) || !aid.includes("."))
|
|
66
|
+
throw new Error("AID 格式无效");
|
|
27
67
|
if (!/^[a-z0-9_-]+$/.test(pluginId))
|
|
28
68
|
throw new Error("插件 ID 格式无效");
|
|
29
|
-
|
|
69
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535)
|
|
70
|
+
throw new Error("后台端口无效");
|
|
30
71
|
const path = pluginId === "dsh" ? "/dsh/v1" : `/api/agents/${pluginId}/v1`;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
["
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const
|
|
72
|
+
const ecagent = { baseUrl: `http://127.0.0.1:${port}${path}`, apiKey: "mab-local", model: pluginId };
|
|
73
|
+
try {
|
|
74
|
+
// MAB 直接接收 AUN 消息,先停用该 AID 的 EvolCore Agent,避免双重回复。
|
|
75
|
+
const disabled = await run(["agent", "disable", aid, "--format", "json"], home);
|
|
76
|
+
if (!disabled?.ok)
|
|
77
|
+
throw new Error(`停用原 Agent 失败:${disabled?.code ?? ""} ${disabled?.error ?? "未知错误"}`);
|
|
78
|
+
for (const [key, value] of [
|
|
79
|
+
...Object.entries(ecagent).map(([key, value]) => [`baseagents.ecagent.${key}`, value]),
|
|
80
|
+
["active_baseagent", "ecagent"],
|
|
81
|
+
]) {
|
|
82
|
+
const result = await run(["agent", "set", aid, key, value, "--format", "json"], home);
|
|
83
|
+
if (!result?.ok)
|
|
84
|
+
throw new Error(`配置 ${key} 失败:${result?.code ?? ""} ${result?.error ?? "未知错误"}`);
|
|
85
|
+
}
|
|
86
|
+
const result = await run(["agent", "reload", aid, "--format", "json"], home);
|
|
38
87
|
if (!result?.ok)
|
|
39
|
-
throw new Error(
|
|
88
|
+
throw new Error(`重载失败:${result?.code ?? ""} ${result?.error ?? "未知错误"}`);
|
|
89
|
+
return "ec";
|
|
90
|
+
}
|
|
91
|
+
catch (commandError) {
|
|
92
|
+
const file = (0, node_path_1.join)(home, "agents", aid, "config.json");
|
|
93
|
+
const config = JSON.parse(await (0, promises_1.readFile)(file, "utf8"));
|
|
94
|
+
if (!config || typeof config !== "object" || Array.isArray(config) || config.baseagents && (typeof config.baseagents !== "object" || Array.isArray(config.baseagents)))
|
|
95
|
+
throw new Error(`AID ${aid} 原配置格式无效,未覆盖文件`);
|
|
96
|
+
config.enabled = false;
|
|
97
|
+
config.active_baseagent = "ecagent";
|
|
98
|
+
config.baseagents = { ...config.baseagents, ecagent: { ...config.baseagents?.ecagent, ...ecagent } };
|
|
99
|
+
const temporary = `${file}.mab-${(0, node_crypto_1.randomUUID)()}.tmp`;
|
|
100
|
+
try {
|
|
101
|
+
await (0, promises_1.writeFile)(temporary, `${JSON.stringify(config, null, 2)}\n`, { mode: 0o600 });
|
|
102
|
+
await (0, promises_1.rename)(temporary, file);
|
|
103
|
+
}
|
|
104
|
+
finally {
|
|
105
|
+
await (0, promises_1.rm)(temporary, { force: true });
|
|
106
|
+
}
|
|
107
|
+
// 写盘不能关闭运行中的旧 Agent;存在活跃实例时必须确认重载成功。
|
|
108
|
+
if (await ecIsRunning(home)) {
|
|
109
|
+
try {
|
|
110
|
+
const result = await run(["agent", "reload", aid, "--format", "json"], home);
|
|
111
|
+
if (!result?.ok)
|
|
112
|
+
throw new Error(result?.error ?? "重载失败");
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
throw new AggregateError([commandError, error], `AID ${aid} 配置已保存,但 EvolCore 仍在运行,原 Agent 未确认停用:${error instanceof Error ? error.message : error}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return "json";
|
|
40
119
|
}
|
|
41
|
-
const result = await run(["agent", "reload", aid, "--format", "json"]);
|
|
42
|
-
if (!result?.ok)
|
|
43
|
-
throw new Error(`重载 AID ${aid} 失败:${result?.code ?? ""} ${result?.error ?? "未知错误"}`);
|
|
44
120
|
}
|
|
@@ -9,10 +9,11 @@ const node_path_1 = require("node:path");
|
|
|
9
9
|
const node_os_1 = require("node:os");
|
|
10
10
|
const node_url_1 = require("node:url");
|
|
11
11
|
const agent_1 = require("../../agent");
|
|
12
|
+
const evolcore_1 = require("./evolcore");
|
|
12
13
|
const messages_1 = require("./messages");
|
|
13
14
|
const attachments_1 = require("./attachments");
|
|
14
15
|
const manifest = {
|
|
15
|
-
id: "aun", version: "1.0.
|
|
16
|
+
id: "aun", version: "1.0.35", type: "channel", displayName: "AUN 通道",
|
|
16
17
|
entry: "dist/src-ts/plugins/aun/index.js", apiVersion: "1", process: "in-process",
|
|
17
18
|
capabilities: ["channel", "channel.identity.create", "channel.receive", "channel.send", "health", "cli"],
|
|
18
19
|
configSchema: "config.schema.json",
|
|
@@ -86,7 +87,7 @@ class AunPlugin {
|
|
|
86
87
|
const known = new Set([...this.clients.keys(), ...this.listStoreAids()]);
|
|
87
88
|
return {
|
|
88
89
|
evolcore: [...known].filter((aid) => !own.has(aid)).sort(),
|
|
89
|
-
muAgentBridge: [...
|
|
90
|
+
muAgentBridge: [...known].filter((aid) => own.has(aid)).sort(),
|
|
90
91
|
localMuAgentBridge: [...own].filter((aid) => !known.has(aid)).sort(),
|
|
91
92
|
};
|
|
92
93
|
},
|
|
@@ -124,43 +125,38 @@ class AunPlugin {
|
|
|
124
125
|
this.creating = false;
|
|
125
126
|
}
|
|
126
127
|
},
|
|
127
|
-
bind: async (pluginId, aid,
|
|
128
|
+
bind: async (pluginId, aid, port) => {
|
|
129
|
+
if (!aid || !aidPattern.test(aid))
|
|
130
|
+
throw new Error("AID 格式无效,必须指定要关联的 AID");
|
|
128
131
|
if (pluginId === "aun" || pluginId === "abweb")
|
|
129
132
|
throw new Error("只能关联 Agent 插件");
|
|
133
|
+
if (this.context?.pluginStatus(pluginId) !== "ready")
|
|
134
|
+
throw new Error("目标 Agent 插件未启动");
|
|
130
135
|
const previous = this.bindings;
|
|
131
136
|
const previousTimes = this.bindingTimes;
|
|
132
|
-
|
|
133
|
-
|
|
137
|
+
const connection = this.clients.get(aid);
|
|
138
|
+
const hadIdentity = this.identities.has(aid);
|
|
139
|
+
const hadSubscription = Boolean(connection?.subscription);
|
|
134
140
|
try {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
this.bindings[aid] = pluginId;
|
|
146
|
-
}
|
|
147
|
-
else {
|
|
148
|
-
// 本地身份由插件直接接管,不再通过 ec 配置路由。
|
|
149
|
-
const loaded = this.store.load(aid);
|
|
150
|
-
if (!loaded.ok || !loaded.data)
|
|
151
|
-
throw new Error("AID 不在本地密钥库中,无法关联");
|
|
152
|
-
this.identities.add(aid);
|
|
153
|
-
if (!this.clients.has(aid))
|
|
154
|
-
await this.connectAid(aid);
|
|
155
|
-
if (!this.clients.has(aid))
|
|
156
|
-
throw new Error("AID 当前未连接");
|
|
157
|
-
this.bindings[aid] = pluginId;
|
|
158
|
-
}
|
|
141
|
+
const loaded = this.store.load(aid);
|
|
142
|
+
if (!loaded.ok || !loaded.data)
|
|
143
|
+
throw new Error("AID 不在本地密钥库中,无法关联");
|
|
144
|
+
// 仅接管用户本次选中的 EvolCore Agent;纯密钥库身份不调用 ec。
|
|
145
|
+
if ((0, node_fs_1.existsSync)((0, node_path_1.join)(this.path(), "agents", aid, "config.json"))) {
|
|
146
|
+
const method = await (0, evolcore_1.configureAidForPlugin)(pluginId, aid, port, this.options.runEc ?? evolcore_1.runEc, this.path());
|
|
147
|
+
if (method === "json")
|
|
148
|
+
this.context?.logger.warn("ec 命令不可用或失败,已通过 JSON 停用原 Agent 并配置 ecagent", { aid, pluginId });
|
|
149
|
+
else
|
|
150
|
+
this.context?.logger.info("已通过 ec 命令停用原 Agent 并切换为 ecagent", { aid, pluginId });
|
|
159
151
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
152
|
+
this.identities.add(aid);
|
|
153
|
+
if (!connection)
|
|
154
|
+
await this.connectAid(aid);
|
|
155
|
+
if (!this.clients.has(aid))
|
|
156
|
+
throw new Error("AID 当前未连接");
|
|
157
|
+
this.bindings = { ...previous, [aid]: pluginId };
|
|
158
|
+
this.bindingTimes = { ...previousTimes, [aid]: new Date().toISOString() };
|
|
159
|
+
this.subscribeAid(aid);
|
|
164
160
|
await this.persist();
|
|
165
161
|
}
|
|
166
162
|
catch (error) {
|
|
@@ -168,6 +164,29 @@ class AunPlugin {
|
|
|
168
164
|
this.bindingTimes = previousTimes;
|
|
169
165
|
this.config.bindings = previous;
|
|
170
166
|
this.config.binding_times = previousTimes;
|
|
167
|
+
if (!hadIdentity)
|
|
168
|
+
this.identities.delete(aid);
|
|
169
|
+
this.config.identities = [...this.identities].sort();
|
|
170
|
+
if (!hadSubscription) {
|
|
171
|
+
this.clients.get(aid)?.subscription?.unsubscribe();
|
|
172
|
+
if (connection)
|
|
173
|
+
connection.subscription = undefined;
|
|
174
|
+
}
|
|
175
|
+
if (!connection) {
|
|
176
|
+
try {
|
|
177
|
+
await this.clients.get(aid)?.client.close();
|
|
178
|
+
}
|
|
179
|
+
catch (closeError) {
|
|
180
|
+
this.context?.logger.warn("AUN 关联回滚关闭连接失败", { aid, error: safeError(closeError) });
|
|
181
|
+
}
|
|
182
|
+
finally {
|
|
183
|
+
this.clients.delete(aid);
|
|
184
|
+
if (!this.clients.size) {
|
|
185
|
+
this.status = "degraded";
|
|
186
|
+
this.healthMessage = "暂无已连接 AID";
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
171
190
|
this.context?.logger.error("AUN 身份关联失败", { aid, pluginId, error: safeError(error) });
|
|
172
191
|
throw error;
|
|
173
192
|
}
|
|
@@ -178,6 +197,8 @@ class AunPlugin {
|
|
|
178
197
|
throw new Error("AID 格式无效");
|
|
179
198
|
if (this.bindings[aid] !== pluginId)
|
|
180
199
|
throw new Error("指定 AID 当前未关联到该 Agent");
|
|
200
|
+
if ([...this.pendingMessages.keys()].some(key => JSON.parse(key)[0] === aid))
|
|
201
|
+
throw new Error("该 AID 正在处理消息,请等待任务完成后再解绑");
|
|
181
202
|
const previous = this.bindings;
|
|
182
203
|
const previousTimes = this.bindingTimes;
|
|
183
204
|
this.bindings = { ...previous };
|
|
@@ -194,6 +215,21 @@ class AunPlugin {
|
|
|
194
215
|
this.config.binding_times = previousTimes;
|
|
195
216
|
throw error;
|
|
196
217
|
}
|
|
218
|
+
const connection = this.clients.get(aid);
|
|
219
|
+
connection?.subscription?.unsubscribe();
|
|
220
|
+
try {
|
|
221
|
+
await connection?.client.close();
|
|
222
|
+
}
|
|
223
|
+
catch (error) {
|
|
224
|
+
this.context?.logger.warn("AUN 解绑已完成,关闭连接失败", { aid, pluginId, error: safeError(error) });
|
|
225
|
+
}
|
|
226
|
+
finally {
|
|
227
|
+
this.clients.delete(aid);
|
|
228
|
+
if (!this.clients.size) {
|
|
229
|
+
this.status = "degraded";
|
|
230
|
+
this.healthMessage = "暂无已连接 AID";
|
|
231
|
+
}
|
|
232
|
+
}
|
|
197
233
|
this.context?.logger.info("AUN 单个身份关联已解除", { aid, pluginId });
|
|
198
234
|
},
|
|
199
235
|
};
|
|
@@ -207,6 +243,7 @@ class AunPlugin {
|
|
|
207
243
|
async activate(context) {
|
|
208
244
|
this.context = context;
|
|
209
245
|
this.config = { ...context.config };
|
|
246
|
+
this.identities.clear();
|
|
210
247
|
this.legacyCreatedAids.clear();
|
|
211
248
|
this.createdAids.clear();
|
|
212
249
|
try {
|
|
@@ -266,15 +303,25 @@ class AunPlugin {
|
|
|
266
303
|
if (this.aid)
|
|
267
304
|
this.identities.add(this.aid);
|
|
268
305
|
this.acceptingMessages = true;
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
306
|
+
// 只订阅明确关联 Agent 的身份,避免接管 EvolCore 或其它 AID 的消息。
|
|
307
|
+
for (const aid of Object.keys(this.bindings)) {
|
|
308
|
+
const target = this.bindings[aid];
|
|
309
|
+
if (!target || !context.pluginStatus(target)) {
|
|
310
|
+
context.logger.warn("AUN 跳过目标插件不存在的旧绑定,未连接身份", { aid, agentPlugin: target });
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
if (context.pluginStatus(target) !== "ready")
|
|
314
|
+
context.logger.warn("AUN 恢复已有显式绑定,目标插件尚未就绪;关联保持不变", { aid, agentPlugin: target, status: context.pluginStatus(target) });
|
|
315
|
+
if (aidPattern.test(aid))
|
|
275
316
|
await this.connectAid(aid);
|
|
276
317
|
}
|
|
277
318
|
}
|
|
319
|
+
subscribeAid(aid) {
|
|
320
|
+
const connection = this.clients.get(aid);
|
|
321
|
+
if (!connection || connection.subscription || !this.bindings[aid])
|
|
322
|
+
return;
|
|
323
|
+
connection.subscription = connection.client.on("message.received", (payload) => this.onMessage(payload, aid));
|
|
324
|
+
}
|
|
278
325
|
async connectAid(aid) {
|
|
279
326
|
try {
|
|
280
327
|
await this.prepareTrustRoots(aid);
|
|
@@ -282,7 +329,7 @@ class AunPlugin {
|
|
|
282
329
|
if (!loaded.ok || !loaded.data)
|
|
283
330
|
throw new Error(loaded.error?.message ?? "本地密钥库中不存在该 AID");
|
|
284
331
|
const client = new this.sdk.AUNClient(loaded.data.aid);
|
|
285
|
-
const subscription = this.
|
|
332
|
+
const subscription = this.bindings[aid]
|
|
286
333
|
? client.on("message.received", (payload) => this.onMessage(payload, aid))
|
|
287
334
|
: undefined;
|
|
288
335
|
try {
|
package/package.json
CHANGED