agent-bridge-aun 1.0.20 → 1.0.22

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
@@ -2,10 +2,14 @@
2
2
 
3
3
  `agent-bridge-aun` 为 mu-agent-bridge 提供 AUN 消息通道,支持使用已有 AID 或通过管理 API 注册 AID,并将收到的文本交给指定 Agent 插件处理。
4
4
 
5
- 安装后在 ABWeb 的插件管理中启动 AUN 通道。配置 `aid` 和可选的 `agent_plugin`;默认 Agent 插件为 `qwenwork`。AID 创建接口为 `POST /api/plugins/aun/aid`,请求内容为 `{"aid":"新AID.agentid.cn","owner_aid":"所有者AID.agentid.cn"}`。
5
+ 安装后在 ABWeb 的插件管理中启动 AUN 通道。配置 `aid` 和可选的 `agent_plugin`;默认 Agent 插件为 `qwenwork`。配置字段 `owner_aid` 是可选字符串,用作默认 Owner。AID 创建接口为 `POST /api/plugins/aun/aid`,请求内容为 `{"aid":"新AID.agentid.cn","owner_aid":"所有者AID.agentid.cn"}`。
6
+
7
+ 创建使用 SDK 的兼容注册流程;Owner AID 只保存为本地归属配置,不代表 AUN 协议 Owner 关系已签名或激活。Gateway 信任根使用随插件提供并校验指纹的 AUN 根证书,不从 Gateway 下载未经验证的 CA 链。
8
+
9
+ 核心支持插件命令后,可运行 `mab aun status`、`mab aun list`、`mab aun agents`、`mab aun default-owner`、`mab aun default-owner set <owner-aid>`、`mab aun create <aid> [--owner <owner-aid>] [--agent <plugin-id>]`、`mab aun bind <aid> <plugin-id>` 和 `mab aun unbind <plugin-id>`。`default-owner` 不带参数显示当前配置,`default-owner set <owner-aid>` 校验格式并持久化新的默认值。`create` 明确传入 `--owner` 时优先使用该值;省略时回退到同一配置中的 `owner_aid`,两者都没有时返回错误,不会创建 AID。创建新身份时,实际使用的 Owner 会保存为下次默认值。ABWeb 从同一插件配置读取 `owner_aid` 并预填创建表单,因此 CLI 和 ABWeb 共用默认 Owner。Agent 从当前已就绪的插件动态发现。创建后连接或关联失败时,响应会标明 AID 已创建;先用 `mab aun list` 检查,不要重试创建。运行日志不记录原始参数或 Owner AID;默认 Owner 查询会在终端显示当前值。
6
10
 
7
11
  该插件依赖 `@agentunion/fastaun`,要求 Node.js 22.16 或更高版本。
8
12
 
9
- ABWeb 的 AUN 设置只展示已连接 AID,并按来源分组。插件通过 SDK 密钥库枚举 AID,再逐个建立连接;只有连接成功的身份才会显示。EvolCore 来源身份只用于连接状态检测,不订阅消息,避免重复收信;本工程创建的身份负责消息收发。连接状态检测不调用 `ec agent list/show`。将 EvolCore 身份关联到 Agent 时仍使用既有 `ec agent set/reload` 路由配置,该配置不参与连接状态检测。列表先显示已关联 Agent 的身份,各组按最近成功关联的时间倒序排列,时间相同或旧记录没有时间时按 AID 名称排序。关联时间保存在插件配置的 `binding_times` 中,刷新或重启后保留;旧记录不补造时间,再次关联成功后开始记录。
13
+ ABWeb 的 AUN 设置只展示已连接 AID,并按来源分组。成功注册并记录在插件 `created_aids` 中的 AID 属于本工程;共享密钥库中其余 AID 属于 EvolCore。旧版 `created-aids.json` 记录继续兼容。来源只用于展示,连接和关联由 AUN 插件直接管理。列表先显示已关联 Agent 的身份,各组按最近成功关联的时间倒序排列,时间相同或旧记录没有时间时按 AID 名称排序。关联时间保存在插件配置的 `binding_times` 中,刷新或重启后保留;旧记录不补造时间,再次关联成功后开始记录。
10
14
 
11
15
  AUN 独立包支持核心后台每五分钟检测更新;可信提供者在自动安装策略下会下载新副本并重载通道。
@@ -2,6 +2,7 @@
2
2
  "type": "object",
3
3
  "properties": {
4
4
  "identities": { "type": "array", "items": { "type": "string" } },
5
+ "created_aids": { "type": "array", "items": { "type": "string" } },
5
6
  "bindings": { "type": "object", "additionalProperties": { "type": "string" } },
6
7
  "binding_times": { "type": "object", "additionalProperties": { "type": "string" } },
7
8
  "aid": { "type": "string", "minLength": 1 },
@@ -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-----
@@ -3,19 +3,20 @@ 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.20", type: "channel", displayName: "AUN 通道",
13
+ id: "aun", version: "1.0.22", 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
  };
19
+ const newSessionCommand = /^\/new(?:[ \t]+[^\r\n]+)?$/;
19
20
  async function loadSdk() {
20
21
  const resolver = (0, node_module_1.createRequire)(__filename);
21
22
  const dynamicImport = new Function("url", "return import(url)");
@@ -31,6 +32,25 @@ async function loadSdk() {
31
32
  throw new Error("未找到 @agentunion/fastaun,请确认 AUN 插件依赖已安装");
32
33
  }
33
34
  const aidPattern = /^[a-z0-9][a-z0-9_-]{2,63}\.[a-z0-9-]+(?:\.[a-z0-9-]+)+$/;
35
+ 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";
36
+ const cliCommands = [
37
+ { name: "status", summary: "查看 AUN 通道状态", usage: "mab aun status" },
38
+ { name: "list", summary: "查看已连接 AID 和 Agent 关联", usage: "mab aun list" },
39
+ { name: "agents", summary: "列出当前已就绪的 Agent 插件", usage: "mab aun agents" },
40
+ { name: "default-owner", summary: "查询或设置默认 Owner AID", usage: "mab aun default-owner [set <owner-aid>]", examples: ["mab aun default-owner", "mab aun default-owner set owner.agentid.cn"] },
41
+ { 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"] },
42
+ { name: "bind", summary: "将 AID 关联到 Agent", usage: "mab aun bind <aid> <plugin-id>", examples: ["mab aun bind new.agentid.cn qwenwork"] },
43
+ { name: "unbind", summary: "解除 Agent 与 AID 的关联", usage: "mab aun unbind <plugin-id>", examples: ["mab aun unbind qwenwork"] },
44
+ ];
45
+ class AunCommandError extends Error {
46
+ statusCode;
47
+ data;
48
+ constructor(message, statusCode = 400, data) {
49
+ super(message);
50
+ this.statusCode = statusCode;
51
+ this.data = data;
52
+ }
53
+ }
34
54
  class AunPlugin {
35
55
  options;
36
56
  manifest = manifest;
@@ -40,6 +60,7 @@ class AunPlugin {
40
60
  clients = new Map();
41
61
  identities = new Set();
42
62
  legacyCreatedAids = new Set();
63
+ createdAids = new Set();
43
64
  bindings = {};
44
65
  bindingTimes = {};
45
66
  creating = false;
@@ -57,11 +78,11 @@ class AunPlugin {
57
78
  }
58
79
  identity = {
59
80
  list: async () => {
60
- const connected = [...this.clients.keys()];
81
+ const own = new Set([...this.createdAids, ...this.legacyCreatedAids]);
61
82
  return {
62
- evolcore: connected.filter((aid) => !this.identities.has(aid) && !this.legacyCreatedAids.has(aid)).sort(),
63
- muAgentBridge: connected.filter((aid) => this.identities.has(aid) || this.legacyCreatedAids.has(aid)).sort(),
64
- localMuAgentBridge: [...this.identities].filter((aid) => !this.clients.has(aid)).sort(),
83
+ evolcore: [...this.clients.keys()].filter((aid) => !own.has(aid)).sort(),
84
+ muAgentBridge: [...this.clients.keys()].filter((aid) => own.has(aid)).sort(),
85
+ localMuAgentBridge: [...own].filter((aid) => !this.clients.has(aid)).sort(),
65
86
  };
66
87
  },
67
88
  create: async (aid, owner) => {
@@ -72,11 +93,15 @@ class AunPlugin {
72
93
  this.creating = true;
73
94
  try {
74
95
  if (!this.identities.has(aid)) {
75
- // 创建身份沿用 EvolCore 的 legacy 注册流程。Owner AID 是本地归属配置,
76
- // 只有明确执行 Owner 关系绑定时才进入 aid_register_* 双签流程。
77
- const result = await this.store.register(aid);
78
- if (!result.ok)
79
- throw new Error(redact(result.error?.message ?? result.error?.code ?? "AID 注册失败"));
96
+ // Owner AID 是本地归属配置;身份签发沿用 EvolCore 的兼容注册流程。
97
+ if (!this.listStoreAids().includes(aid)) {
98
+ await this.prepareTrustRoots(aid);
99
+ const result = await this.store.register(aid);
100
+ if (!result.ok)
101
+ throw new Error(redact(result.error?.message ?? result.error?.code ?? "AID 注册失败"));
102
+ this.createdAids.add(aid);
103
+ await this.persist();
104
+ }
80
105
  const loaded = this.store.load(aid);
81
106
  if (!loaded.ok || !loaded.data)
82
107
  throw new Error(loaded.error?.message ?? "注册后未能加载本地身份");
@@ -85,7 +110,7 @@ class AunPlugin {
85
110
  this.config.owner_aid = owner;
86
111
  this.config.aun_path = this.path();
87
112
  await this.persist();
88
- this.context?.logger.info("AUN 身份已创建", { aid, owner });
113
+ this.context?.logger.info("AUN 身份已创建", { aid });
89
114
  }
90
115
  const connectionError = !this.clients.has(aid) ? await this.connectAid(aid) : undefined;
91
116
  return { aid, status: "已创建", connection: this.clients.has(aid) ? "ready" : "degraded", ...(connectionError ? { connectionError } : {}) };
@@ -94,7 +119,7 @@ class AunPlugin {
94
119
  this.creating = false;
95
120
  }
96
121
  },
97
- bind: async (pluginId, aid, port) => {
122
+ bind: async (pluginId, aid, _port) => {
98
123
  if (pluginId === "aun" || pluginId === "abweb")
99
124
  throw new Error("只能关联 Agent 插件");
100
125
  const previous = this.bindings;
@@ -120,15 +145,21 @@ class AunPlugin {
120
145
  this.bindings[aid] = pluginId;
121
146
  }
122
147
  else {
123
- const connected = await this.identity.list();
124
- if (![...connected.evolcore, ...connected.muAgentBridge].includes(aid))
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))
125
156
  throw new Error("AID 当前未连接");
126
- await (0, evolcore_1.configureAidForPlugin)(pluginId, aid, port);
127
157
  for (const [identity, target] of Object.entries(this.bindings))
128
158
  if (target === pluginId) {
129
159
  delete this.bindings[identity];
130
160
  delete this.bindingTimes[identity];
131
161
  }
162
+ this.bindings[aid] = pluginId;
132
163
  }
133
164
  }
134
165
  else {
@@ -155,6 +186,7 @@ class AunPlugin {
155
186
  };
156
187
  async persist() {
157
188
  this.config.identities = [...this.identities].sort();
189
+ this.config.created_aids = [...new Set([...this.createdAids, ...this.legacyCreatedAids])].sort();
158
190
  this.config.bindings = this.bindings;
159
191
  this.config.binding_times = this.bindingTimes;
160
192
  await (0, promises_1.writeFile)((0, node_path_1.join)(this.context.dataDir, "config.json"), `${JSON.stringify(this.config, null, 2)}\n`, { mode: 0o600 });
@@ -163,6 +195,7 @@ class AunPlugin {
163
195
  this.context = context;
164
196
  this.config = { ...context.config };
165
197
  this.legacyCreatedAids.clear();
198
+ this.createdAids.clear();
166
199
  try {
167
200
  const created = JSON.parse(await (0, promises_1.readFile)((0, node_path_1.join)(context.dataDir, "..", "created-aids.json"), "utf8"));
168
201
  if (Array.isArray(created))
@@ -174,6 +207,10 @@ class AunPlugin {
174
207
  if (error.code !== "ENOENT")
175
208
  context.logger.warn("读取旧版身份创建记录失败", { error: safeError(error) });
176
209
  }
210
+ for (const aid of Array.isArray(this.config.created_aids) ? this.config.created_aids : []) {
211
+ if (typeof aid === "string" && aidPattern.test(aid))
212
+ this.createdAids.add(aid);
213
+ }
177
214
  for (const key of ["aid", "owner_aid", "aun_path", "encryption_seed", "root_ca_path", "allow_from", "agent_plugin"]) {
178
215
  if (this.config[key] !== undefined && typeof this.config[key] !== "string")
179
216
  throw new Error(`${key} 必须是字符串`);
@@ -197,6 +234,8 @@ class AunPlugin {
197
234
  ["GET", "/api/plugins/aun/identities", this.listIdentities],
198
235
  ["POST", "/api/plugins/aun/bind", this.bindAgent],
199
236
  ["POST", "/api/plugins/aun/settings", this.saveSettings],
237
+ ["GET", "/api/plugins/aun/commands", this.listCommands],
238
+ ["POST", "/api/plugins/aun/commands", this.runCommand],
200
239
  ...(["", "/app.js", "/style.css"].map((suffix) => ["GET", `/abweb/channels/aun${suffix}`, this.servePage])),
201
240
  ]) {
202
241
  const dispose = context.http.register({ method, path, handler });
@@ -214,13 +253,16 @@ class AunPlugin {
214
253
  this.identities.add(this.aid);
215
254
  for (const aid of this.identities)
216
255
  await this.connectAid(aid);
217
- // AUN 插件直接连接本地密钥库中的所有 AID,不依赖 ec 进程或 CLI。
218
- for (const aid of this.listStoreAids())
256
+ // 密钥库中的其余 AID 也自动连接:不再依赖 ec 或 EvolCore 进程
257
+ for (const aid of this.listStoreAids()) {
258
+ this.identities.add(aid);
219
259
  if (!this.clients.has(aid))
220
260
  await this.connectAid(aid);
261
+ }
221
262
  }
222
263
  async connectAid(aid) {
223
264
  try {
265
+ await this.prepareTrustRoots(aid);
224
266
  const loaded = this.store.load(aid);
225
267
  if (!loaded.ok || !loaded.data)
226
268
  throw new Error(loaded.error?.message ?? "本地密钥库中不存在该 AID");
@@ -272,9 +314,49 @@ class AunPlugin {
272
314
  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
315
  }
274
316
  newStore() {
275
- const rootCaPath = typeof this.config.root_ca_path === "string" && this.config.root_ca_path ? this.config.root_ca_path : (0, node_path_1.join)(this.path(), "CA", "root", "root.crt");
317
+ const rootCaPath = this.rootCaPath();
276
318
  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
319
  }
320
+ rootCaPath() {
321
+ return typeof this.config.root_ca_path === "string" && this.config.root_ca_path
322
+ ? this.config.root_ca_path
323
+ : (0, node_path_1.join)(this.context.dataDir, "CA", "root", "root.crt");
324
+ }
325
+ async prepareTrustRoots(aid) {
326
+ const rootCaPath = this.rootCaPath();
327
+ const customPath = typeof this.config.root_ca_path === "string" && this.config.root_ca_path.trim();
328
+ if ((0, node_fs_1.existsSync)(rootCaPath)) {
329
+ if (customPath)
330
+ return;
331
+ const existing = new node_crypto_1.X509Certificate(await (0, promises_1.readFile)(rootCaPath, "utf8"));
332
+ if (existing.fingerprint256 !== aunRootFingerprint)
333
+ throw new Error("AUN 插件根证书指纹校验失败");
334
+ return;
335
+ }
336
+ if (customPath)
337
+ throw new Error("配置的 AUN 根证书文件不存在");
338
+ try {
339
+ const cert = new node_crypto_1.X509Certificate(await (0, promises_1.readFile)((0, node_path_1.join)(__dirname, "certs", "root.crt"), "utf8"));
340
+ if (cert.fingerprint256 !== aunRootFingerprint)
341
+ throw new Error("内置 AUN 根证书指纹校验失败");
342
+ await (0, promises_1.mkdir)((0, node_path_1.dirname)(rootCaPath), { recursive: true });
343
+ await (0, promises_1.writeFile)(rootCaPath, cert.toString(), { mode: 0o600, flag: "wx" });
344
+ const oldStore = this.store;
345
+ const newStore = this.newStore();
346
+ this.store = newStore;
347
+ try {
348
+ await oldStore?.close();
349
+ }
350
+ catch (error) {
351
+ this.context?.logger.warn("关闭旧 AUN Store 失败", { error: safeError(error) });
352
+ }
353
+ this.context?.logger.info("AUN 固定信任根证书已安装", { aid });
354
+ }
355
+ catch (error) {
356
+ this.context?.logger.warn("AUN Gateway 信任根证书初始化失败", { aid, error: safeError(error) });
357
+ throw new Error(`AUN Gateway 信任根证书初始化失败:${safeError(error)}`);
358
+ }
359
+ }
278
360
  createAid = async (request, response) => {
279
361
  let input;
280
362
  try {
@@ -367,6 +449,277 @@ class AunPlugin {
367
449
  this.json(response, 400, { error: safeError(error) });
368
450
  }
369
451
  };
452
+ listCommands = async (_request, response) => {
453
+ response.setHeader("cache-control", "no-store");
454
+ this.json(response, 200, { protocolVersion: 1, plugin: "aun", version: manifest.version, commands: cliCommands });
455
+ };
456
+ runCommand = async (request, response) => {
457
+ try {
458
+ const chunks = [];
459
+ let size = 0;
460
+ for await (const chunk of request) {
461
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));
462
+ size += buffer.length;
463
+ if (size > 65536)
464
+ throw new AunCommandError("请求内容过大");
465
+ chunks.push(buffer);
466
+ }
467
+ let input;
468
+ try {
469
+ input = JSON.parse(Buffer.concat(chunks).toString("utf8"));
470
+ }
471
+ catch {
472
+ throw new AunCommandError("命令请求 JSON 格式无效");
473
+ }
474
+ if (!input || typeof input !== "object" || input.protocolVersion !== 1 || !Array.isArray(input.argv) || !input.argv.every((arg) => typeof arg === "string")) {
475
+ throw new AunCommandError("命令请求格式无效");
476
+ }
477
+ const result = await this.executeCommand(input.argv);
478
+ this.commandResult(response, 200, true, result.message, result.data);
479
+ }
480
+ catch (error) {
481
+ const failure = error instanceof AunCommandError ? error : new AunCommandError(safeError(error), 502);
482
+ this.commandResult(response, failure.statusCode, false, failure.message, failure.data);
483
+ }
484
+ };
485
+ async executeCommand(argv) {
486
+ const [command, ...args] = argv;
487
+ if (command === "status" && !args.length)
488
+ return { message: this.healthMessage, data: await this.health() };
489
+ if (command === "list" && !args.length) {
490
+ const groups = await this.identity.list();
491
+ const aids = [...new Set([...groups.evolcore, ...groups.muAgentBridge, ...(groups.localMuAgentBridge ?? []), ...this.listStoreAids()])].sort();
492
+ return { message: aids.length ? `AID 列表:\n${aids.join("\n")}` : "暂无本地 AID", data: {
493
+ aids,
494
+ bindings: this.bindings,
495
+ bindingTimes: this.bindingTimes,
496
+ } };
497
+ }
498
+ if (command === "agents" && !args.length) {
499
+ const agents = await this.agents();
500
+ return { message: agents.length ? `当前已就绪的 Agent 插件:\n${agents.map((agent) => `${agent.id}(${agent.displayName})`).join("\n")}` : "暂无已就绪的 Agent 插件", data: agents };
501
+ }
502
+ if (command === "default-owner")
503
+ return this.defaultOwnerCommand(args);
504
+ if (command === "create")
505
+ return this.createCommand(args);
506
+ if (command === "bind" && args.length === 2) {
507
+ const [aid, pluginId] = args;
508
+ if (!aidPattern.test(aid))
509
+ throw new AunCommandError("AID 格式无效");
510
+ this.validatePluginId(pluginId);
511
+ await this.requireAgent(pluginId);
512
+ const replacedAids = (await this.agentBindings(pluginId)).filter((previousAid) => previousAid !== aid);
513
+ try {
514
+ await this.patchAgentAid(pluginId, aid);
515
+ }
516
+ catch (error) {
517
+ const bindingState = await this.bindingState(pluginId).catch(() => undefined);
518
+ throw new AunCommandError(`AID 关联请求失败,已回读当前状态:${safeError(error)}`, error instanceof AunCommandError ? error.statusCode : 502, { aid, agent: pluginId, ...(bindingState ? { bindingState } : { bindingState: "unknown" }) });
519
+ }
520
+ return {
521
+ message: replacedAids.length ? `AID ${replacedAids.join("、")} 已替换为 ${aid} 并关联到 ${pluginId}` : `AID 已关联到 ${pluginId}`,
522
+ data: { aid, agent: pluginId, replacedAids },
523
+ };
524
+ }
525
+ if (command === "unbind" && args.length === 1) {
526
+ const [pluginId] = args;
527
+ this.validatePluginId(pluginId);
528
+ await this.requireAgent(pluginId, false);
529
+ const unboundAids = await this.agentBindings(pluginId);
530
+ try {
531
+ await this.patchAgentAid(pluginId, "");
532
+ }
533
+ catch (error) {
534
+ const bindingState = await this.bindingState(pluginId).catch(() => undefined);
535
+ throw new AunCommandError(`解除关联请求失败,已回读当前状态:${safeError(error)}`, error instanceof AunCommandError ? error.statusCode : 502, { agent: pluginId, ...(bindingState ? { bindingState } : { bindingState: "unknown" }) });
536
+ }
537
+ return { message: `已解除 ${pluginId} 的 AID 关联`, data: { agent: pluginId, unboundAids } };
538
+ }
539
+ if (!["status", "list", "agents", "default-owner", "create", "bind", "unbind"].includes(command ?? ""))
540
+ throw new AunCommandError(`未知 AUN 命令:${command ?? ""}`);
541
+ throw new AunCommandError(`AUN 命令参数无效:${command}`);
542
+ }
543
+ async defaultOwnerCommand(args) {
544
+ const owner = typeof this.config.owner_aid === "string" ? this.config.owner_aid.trim() : "";
545
+ if (!args.length)
546
+ return { message: owner ? `默认 Owner:${owner}` : "未配置默认 Owner", data: { owner_aid: owner || undefined } };
547
+ if (args.length !== 2 || args[0] !== "set")
548
+ throw new AunCommandError("AUN 默认 Owner 命令参数无效");
549
+ const nextOwner = args[1].trim();
550
+ if (!aidPattern.test(nextOwner))
551
+ throw new AunCommandError("Owner AID 格式无效");
552
+ this.config.owner_aid = nextOwner;
553
+ try {
554
+ await this.persist();
555
+ }
556
+ catch (error) {
557
+ this.config.owner_aid = owner;
558
+ throw error;
559
+ }
560
+ return { message: "默认 Owner 已保存", data: { configured: true } };
561
+ }
562
+ async createCommand(args) {
563
+ const aid = args[0] ?? "";
564
+ if (!aidPattern.test(aid))
565
+ throw new AunCommandError("AID 格式无效");
566
+ let owner = "";
567
+ let agent = "";
568
+ for (let index = 1; index < args.length; index++) {
569
+ const option = args[index];
570
+ const value = args[++index];
571
+ if (!value || value.startsWith("--"))
572
+ throw new AunCommandError(`${option} 需要参数`);
573
+ if (option === "--owner" && !owner)
574
+ owner = value;
575
+ else if (option === "--agent" && !agent)
576
+ agent = value;
577
+ else
578
+ throw new AunCommandError(`不支持的创建参数:${option}`);
579
+ }
580
+ if (!owner)
581
+ owner = typeof this.config.owner_aid === "string" ? this.config.owner_aid.trim() : "";
582
+ if (!owner || !aidPattern.test(owner))
583
+ throw new AunCommandError("需要有效的 --owner AID,或先设置默认 Owner");
584
+ if (agent) {
585
+ this.validatePluginId(agent);
586
+ await this.requireAgent(agent);
587
+ }
588
+ let created;
589
+ try {
590
+ created = await this.identity.create(aid, owner);
591
+ }
592
+ catch (error) {
593
+ const exists = this.identities.has(aid) || this.listStoreAids().includes(aid);
594
+ if (exists)
595
+ throw new AunCommandError("AID 可能已创建,但后续连接或保存失败;请先执行 mab aun list 确认,不要重试创建", 502, { created: true, aid });
596
+ throw new AunCommandError(`AID 创建失败:${safeError(error).replaceAll(owner, "[已隐藏]")}`, 502);
597
+ }
598
+ const data = { created: true, aid, connection: created.connection ?? "ready" };
599
+ if (created.connection !== "ready")
600
+ throw new AunCommandError("AID 已创建,但连接失败;请先执行 mab aun list 确认,不要重试创建", 502, { ...data, connectionError: created.connectionError });
601
+ let replacedAids = [];
602
+ if (agent) {
603
+ try {
604
+ replacedAids = (await this.agentBindings(agent)).filter((previousAid) => previousAid !== aid);
605
+ await this.patchAgentAid(agent, aid);
606
+ }
607
+ catch (error) {
608
+ const bindingState = await this.bindingState(agent).catch(() => undefined);
609
+ throw new AunCommandError(`AID 已创建,但 Agent 关联失败,已回读当前状态:${safeError(error)}`, 502, { ...data, agent, ...(bindingState ? { bindingState } : { bindingState: "unknown" }) });
610
+ }
611
+ }
612
+ return {
613
+ message: agent ? (replacedAids.length ? `AID ${replacedAids.join("、")} 已替换为 ${aid} 并关联到 ${agent}` : `AID 已创建并关联到 ${agent}`) : "AID 已创建并连接",
614
+ data: { ...data, ...(agent ? { agent, replacedAids } : {}) },
615
+ };
616
+ }
617
+ async agents(readyOnly = true) {
618
+ const payload = await this.controlRequest("/api/plugins");
619
+ if (!Array.isArray(payload.plugins))
620
+ throw new AunCommandError("控制 API 插件列表格式无效", 502);
621
+ return payload.plugins.filter((plugin) => !!plugin && typeof plugin === "object" && !Array.isArray(plugin))
622
+ .filter((plugin) => plugin.type === "agent" && (!readyOnly || plugin.status === "ready") && typeof plugin.id === "string" && /^[a-z][a-z0-9-]{1,63}$/.test(plugin.id))
623
+ .map((plugin) => ({ id: plugin.id, displayName: typeof plugin.displayName === "string" ? plugin.displayName : plugin.id }))
624
+ .sort((left, right) => left.id.localeCompare(right.id));
625
+ }
626
+ async requireAgent(pluginId, readyOnly = true) {
627
+ if (!(await this.agents(readyOnly)).some((agent) => agent.id === pluginId))
628
+ throw new AunCommandError(readyOnly ? "目标 Agent 不存在或尚未就绪" : "目标插件不存在或不是 Agent 插件");
629
+ }
630
+ validatePluginId(pluginId) {
631
+ if (!/^[a-z][a-z0-9-]{1,63}$/.test(pluginId) || pluginId === "aun" || pluginId === "abweb")
632
+ throw new AunCommandError("Agent 插件 ID 无效");
633
+ }
634
+ async patchAgentAid(pluginId, aid) {
635
+ await this.controlRequest(`/api/plugins/${encodeURIComponent(pluginId)}/aid`, "PATCH", { aid });
636
+ }
637
+ async agentBindings(pluginId) {
638
+ const aun = await this.controlPayload("/api/plugins/aun/identities");
639
+ if (!aun.bindings || typeof aun.bindings !== "object" || Array.isArray(aun.bindings))
640
+ throw new AunCommandError("AUN 身份列表格式无效", 502);
641
+ const bindings = aun.bindings;
642
+ return Object.entries(bindings).filter(([, target]) => target === pluginId).map(([aid]) => aid).sort();
643
+ }
644
+ async bindingState(pluginId) {
645
+ const [aun, agent] = await Promise.all([
646
+ this.agentBindings(pluginId),
647
+ this.controlPayload(`/api/plugins/${encodeURIComponent(pluginId)}/config`),
648
+ ]);
649
+ return {
650
+ aunAids: aun,
651
+ agentAid: typeof agent.aid === "string" ? agent.aid : "",
652
+ };
653
+ }
654
+ async controlRequest(path, method = "GET", body) {
655
+ const result = await this.controlPayload(path, method, body);
656
+ if (result.ok !== true)
657
+ throw new AunCommandError("本机控制 API 响应未确认操作成功", 502);
658
+ return result;
659
+ }
660
+ async controlPayload(path, method = "GET", body) {
661
+ const base = this.controlUrl();
662
+ let response;
663
+ try {
664
+ response = await fetch(new URL(path, base), {
665
+ method,
666
+ headers: { accept: "application/json", ...(body === undefined ? {} : { "content-type": "application/json" }) },
667
+ ...(body === undefined ? {} : { body: JSON.stringify(body) }),
668
+ signal: AbortSignal.timeout(5000),
669
+ redirect: "error",
670
+ });
671
+ }
672
+ catch (error) {
673
+ throw new AunCommandError(`本机控制 API 请求失败:${safeError(error)}`, 502);
674
+ }
675
+ let raw;
676
+ try {
677
+ raw = await readResponse(response, 65536);
678
+ }
679
+ catch (error) {
680
+ if (error instanceof AunCommandError)
681
+ throw error;
682
+ throw new AunCommandError(`本机控制 API 响应读取失败:${safeError(error)}`, 502);
683
+ }
684
+ let payload;
685
+ try {
686
+ payload = JSON.parse(raw);
687
+ }
688
+ catch {
689
+ throw new AunCommandError("本机控制 API 返回的 JSON 无效", 502);
690
+ }
691
+ if (!payload || typeof payload !== "object" || Array.isArray(payload))
692
+ throw new AunCommandError("本机控制 API 响应格式无效", 502);
693
+ const result = payload;
694
+ if (!response.ok) {
695
+ const message = typeof result.error === "string" ? result.error : `HTTP ${response.status}`;
696
+ throw new AunCommandError(`本机控制 API 操作失败:${message}`, response.status >= 400 && response.status < 500 ? response.status : 502);
697
+ }
698
+ return result;
699
+ }
700
+ controlUrl() {
701
+ const controlUrl = this.context?.runtime?.controlUrl;
702
+ if (!controlUrl)
703
+ throw new AunCommandError("运行时未提供 controlUrl,请更新核心服务", 502);
704
+ let url;
705
+ try {
706
+ url = new URL(controlUrl);
707
+ }
708
+ catch {
709
+ throw new AunCommandError("运行时 controlUrl 无效", 502);
710
+ }
711
+ const hostname = url.hostname.replace(/^\[|\]$/g, "");
712
+ if (url.protocol !== "http:" || !["127.0.0.1", "::1"].includes(hostname) || url.username || url.password || (url.pathname !== "/") || url.search || url.hash) {
713
+ throw new AunCommandError("运行时 controlUrl 必须指向本机回环地址", 502);
714
+ }
715
+ return url;
716
+ }
717
+ commandResult(response, status, ok, message, data) {
718
+ response.statusCode = status;
719
+ response.setHeader("content-type", "application/json; charset=utf-8");
720
+ response.setHeader("cache-control", "no-store");
721
+ response.end(JSON.stringify({ ok, message, ...(data === undefined ? {} : { data }) }));
722
+ }
370
723
  servePage = async (request, response) => {
371
724
  const path = new URL(request.url ?? "/", "http://localhost").pathname;
372
725
  const file = path.endsWith("/app.js") ? "app.js" : path.endsWith("/style.css") ? "style.css" : "index.html";
@@ -385,12 +738,14 @@ class AunPlugin {
385
738
  const message = (0, messages_1.normalizeAunMessage)(payload, aid, typeof this.config.allow_from === "string" ? this.config.allow_from : undefined);
386
739
  if (!message || !this.seen.add(message.messageId ? `${aid}:${message.messageId}` : ""))
387
740
  return;
741
+ let replyAttempted = false;
388
742
  const channelMessage = {
389
743
  channel: "aun", recipientId: aid, peerId: message.peerId, messageId: message.messageId, text: message.text, threadId: message.threadId,
390
744
  agentPlugin: this.bindings[aid] || (typeof this.config.agent_plugin === "string" && this.config.agent_plugin.trim() ? this.config.agent_plugin.trim() : "qwenwork"),
391
745
  reply: async (text) => {
392
746
  if (!text.trim())
393
747
  return;
748
+ replyAttempted = true;
394
749
  const payload = { type: "text", text };
395
750
  if (message.threadId)
396
751
  payload.thread_id = message.threadId;
@@ -410,6 +765,14 @@ class AunPlugin {
410
765
  }
411
766
  catch (error) {
412
767
  this.context?.logger.warn("AUN 消息处理失败", { error: safeError(error) });
768
+ if (!replyAttempted && newSessionCommand.test(message.text.trim())) {
769
+ try {
770
+ await channelMessage.reply(`新建会话失败:${safeError(error)}`);
771
+ }
772
+ catch (replyError) {
773
+ this.context?.logger.warn("AUN 错误回复失败", { error: safeError(replyError) });
774
+ }
775
+ }
413
776
  }
414
777
  }
415
778
  isE2eeError(error) {
@@ -428,6 +791,34 @@ exports.default = AunPlugin;
428
791
  function safeError(error) {
429
792
  return redact(error instanceof Error ? error.message : String(error)).slice(0, 240);
430
793
  }
794
+ async function readResponse(response, limit) {
795
+ if (!response.body) {
796
+ const raw = await response.text();
797
+ if (Buffer.byteLength(raw) > limit)
798
+ throw new AunCommandError("响应内容过大", 502);
799
+ return raw;
800
+ }
801
+ const reader = response.body.getReader();
802
+ const chunks = [];
803
+ let size = 0;
804
+ try {
805
+ while (true) {
806
+ const { done, value } = await reader.read();
807
+ if (done)
808
+ break;
809
+ size += value.byteLength;
810
+ if (size > limit) {
811
+ await reader.cancel();
812
+ throw new AunCommandError("响应内容过大", 502);
813
+ }
814
+ chunks.push(Buffer.from(value));
815
+ }
816
+ }
817
+ finally {
818
+ reader.releaseLock();
819
+ }
820
+ return Buffer.concat(chunks).toString("utf8");
821
+ }
431
822
  function redact(message) {
432
823
  return message.replace(/(private[_ -]?key|seed|token|certificate)\s*[:=]\s*[^\s,;]+/gi, "$1=[已隐藏]");
433
824
  }
@@ -37,8 +37,15 @@
37
37
  configForm.elements.encryption_seed.value = "";
38
38
  }
39
39
  list.replaceChildren();
40
- const entries = [["本工程 · 已连接", identities.groups.muAgentBridge || []], ["EvolCore · 已连接", identities.groups.evolcore || []]]
41
- .flatMap(([label, values], group) => values.map((aid) => ({ aid, label, group, bound: identities.bindings?.[aid] || agents.find((agent) => agent.id && agent.configAid === aid)?.id || "", time: Date.parse(identities.bindingTimes?.[aid] || "") || 0 })));
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
+ }
42
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));
43
50
  for (const { aid, label, bound } of entries) {
44
51
  const row = document.createElement("tr");
@@ -49,7 +56,7 @@
49
56
  cell.append(select); row.append(cell); list.append(row);
50
57
  }
51
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); }
52
- show(`${(identities.groups.evolcore || []).length + (identities.groups.muAgentBridge || []).length} 个已连接 AID`);
59
+ show(`${entries.length} 个已连接 AID`);
53
60
  } catch (error) { show(String(error), true); }
54
61
  finally { loading = false; }
55
62
  }
@@ -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 AID 用于本地归属和 Agent 关联;创建身份本身沿用 AUN 的普通注册流程</small></label></div><button type="submit">创建</button></form>
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" placeholder="例如:owner.agentid.cn"><small class="hint">留空时使用已配置的默认 Owner;未配置时需填写。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>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-bridge-aun",
3
- "version": "1.0.20",
3
+ "version": "1.0.22",
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.20",
3
+ "version": "1.0.22",
4
4
  "type": "channel",
5
5
  "displayName": "AUN 通道",
6
6
  "entry": "dist/src-ts/plugins/aun/index.js",
@@ -11,7 +11,8 @@
11
11
  "channel.identity.create",
12
12
  "channel.receive",
13
13
  "channel.send",
14
- "health"
14
+ "health",
15
+ "cli"
15
16
  ],
16
17
  "configSchema": "config.schema.json",
17
18
  "managementPage": "/abweb/channels/aun"