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 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: 15_000, maxBuffer: 1024 * 1024 });
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 configureAidForPlugin(pluginId, aid, port, run = runEc) {
25
- if (!aid)
26
- return;
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
- // 通过 daemon 的统一插件代理访问所有 Agent;否则 qwenwork 的裸 /v1 路由在有插件 HTTP 注册表时不存在。
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
- for (const [key, value] of [
32
- ["baseagents.ecagent.baseUrl", `http://127.0.0.1:${port}${path}`],
33
- ["baseagents.ecagent.apiKey", "mab-local"],
34
- ["baseagents.ecagent.model", pluginId],
35
- ["active_baseagent", "ecagent"],
36
- ]) {
37
- const result = await run(["agent", "set", aid, key, value, "--format", "json"]);
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(`配置 AID ${aid} 失败:${key};${result?.code ?? ""} ${result?.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.33", type: "channel", displayName: "AUN 通道",
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: [...this.clients.keys()].filter((aid) => own.has(aid)).sort(),
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, _port) => {
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
- this.bindings = { ...previous };
133
- this.bindingTimes = { ...previousTimes };
137
+ const connection = this.clients.get(aid);
138
+ const hadIdentity = this.identities.has(aid);
139
+ const hadSubscription = Boolean(connection?.subscription);
134
140
  try {
135
- if (aid) {
136
- if (!aidPattern.test(aid))
137
- throw new Error("AID 格式无效");
138
- if (this.context?.pluginStatus(pluginId) !== "ready")
139
- throw new Error("目标 Agent 插件未启动");
140
- if (this.identities.has(aid) || this.legacyCreatedAids.has(aid)) {
141
- if (!this.clients.has(aid))
142
- await this.connectAid(aid);
143
- if (!this.clients.has(aid))
144
- throw new Error("AID 当前未连接");
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
- else
161
- throw new Error("必须指定要关联的 AID");
162
- if (aid)
163
- this.bindingTimes[aid] = new Date().toISOString();
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
- for (const aid of this.identities)
270
- await this.connectAid(aid);
271
- // 密钥库中的其余 AID 也自动连接:不再依赖 ec 或 EvolCore 进程
272
- for (const aid of this.listStoreAids()) {
273
- this.identities.add(aid);
274
- if (!this.clients.has(aid))
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.identities.has(aid) || this.legacyCreatedAids.has(aid)
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-bridge-aun",
3
- "version": "1.0.33",
3
+ "version": "1.0.35",
4
4
  "description": "mu-agent-bridge 的 AUN 通道插件。",
5
5
  "keywords": [
6
6
  "mu-agent-bridge-plugin",
package/plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "aun",
3
- "version": "1.0.33",
3
+ "version": "1.0.35",
4
4
  "type": "channel",
5
5
  "displayName": "AUN 通道",
6
6
  "entry": "dist/src-ts/plugins/aun/index.js",