agent-bridge-aun 1.0.0 → 1.0.3

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
@@ -5,3 +5,5 @@
5
5
  安装后在 ABWeb 的插件管理中启动 AUN 通道。配置 `aid` 和可选的 `agent_plugin`;默认 Agent 插件为 `qwenwork`。AID 创建接口为 `POST /api/plugins/aun/aid`,请求内容为 `{"aid":"新AID.agentid.cn","owner_aid":"所有者AID.agentid.cn"}`。
6
6
 
7
7
  该插件依赖 `@agentunion/fastaun`,要求 Node.js 22.16 或更高版本。
8
+
9
+ ABWeb 的 AUN 设置仅展示已连接 AID。列表先显示已关联 Agent 的身份,同类中本工程身份在 EvolCore 身份前面;各组按最近成功关联的时间倒序排列,时间相同或旧记录没有时间时按 AID 名称排序。关联时间保存在插件配置的 `binding_times` 中,刷新或重启后保留;旧记录不补造时间,再次关联成功后开始记录。
@@ -1,6 +1,9 @@
1
1
  {
2
2
  "type": "object",
3
3
  "properties": {
4
+ "identities": { "type": "array", "items": { "type": "string" } },
5
+ "bindings": { "type": "object", "additionalProperties": { "type": "string" } },
6
+ "binding_times": { "type": "object", "additionalProperties": { "type": "string" } },
4
7
  "aid": { "type": "string", "minLength": 1 },
5
8
  "owner_aid": { "type": "string", "minLength": 1 },
6
9
  "agent_plugin": { "type": "string", "minLength": 1, "default": "qwenwork" },
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.listExistingAids = listExistingAids;
4
+ const promises_1 = require("node:fs/promises");
5
+ const node_os_1 = require("node:os");
6
+ const node_path_1 = require("node:path");
7
+ async function listExistingAids() {
8
+ const root = (0, node_path_1.resolve)(process.env.EVOLCORE_HOME ?? (0, node_path_1.join)((0, node_os_1.homedir)(), ".evolcore"), "AIDs");
9
+ let names;
10
+ try {
11
+ names = await (0, promises_1.readdir)(root);
12
+ }
13
+ catch {
14
+ return [];
15
+ }
16
+ const available = await Promise.all(names.filter((name) => /^[a-zA-Z0-9.-]+$/.test(name)).map(async (name) => {
17
+ try {
18
+ await (0, promises_1.access)((0, node_path_1.join)(root, name, "private", "key.json"));
19
+ return name;
20
+ }
21
+ catch {
22
+ return undefined;
23
+ }
24
+ }));
25
+ return available.filter((name) => Boolean(name)).sort();
26
+ }
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runEc = runEc;
4
+ exports.configureAidForPlugin = configureAidForPlugin;
5
+ exports.listConnectedAids = listConnectedAids;
6
+ const node_child_process_1 = require("node:child_process");
7
+ const node_util_1 = require("node:util");
8
+ const local_aids_1 = require("../../local-aids");
9
+ const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
10
+ async function runEc(args) {
11
+ try {
12
+ const { stdout } = await execFileAsync("ec", args, { shell: process.platform === "win32", timeout: 15_000, maxBuffer: 1024 * 1024 });
13
+ return JSON.parse(stdout);
14
+ }
15
+ catch (error) {
16
+ const stdout = error.stdout;
17
+ if (stdout) {
18
+ try {
19
+ return JSON.parse(stdout);
20
+ }
21
+ catch { /* 保留原始命令错误 */ }
22
+ }
23
+ throw error;
24
+ }
25
+ }
26
+ async function configureAidForPlugin(pluginId, aid, port, run = runEc) {
27
+ if (!aid)
28
+ return;
29
+ if (!/^[a-z0-9_-]+$/.test(pluginId))
30
+ throw new Error("插件 ID 格式无效");
31
+ const path = pluginId === "dsh" ? "/dsh/v1" : pluginId === "qwenwork" ? "/v1" : `/api/agents/${pluginId}/v1`;
32
+ for (const [key, value] of [
33
+ ["baseagents.ecagent.baseUrl", `http://127.0.0.1:${port}${path}`],
34
+ ["baseagents.ecagent.apiKey", "mab-local"],
35
+ ["baseagents.ecagent.model", pluginId],
36
+ ["active_baseagent", "ecagent"],
37
+ ]) {
38
+ const result = await run(["agent", "set", aid, key, value, "--format", "json"]);
39
+ if (!result?.ok)
40
+ throw new Error(`配置 AID ${aid} 失败:${key};${result?.code ?? ""} ${result?.error ?? "未知错误"}`);
41
+ }
42
+ const result = await run(["agent", "reload", aid, "--format", "json"]);
43
+ if (!result?.ok)
44
+ throw new Error(`重载 AID ${aid} 失败:${result?.code ?? ""} ${result?.error ?? "未知错误"}`);
45
+ }
46
+ async function listConnectedAids(run = runEc) {
47
+ const result = await run(["agent", "list", "--format", "json"]);
48
+ if (!result.ok || !Array.isArray(result.agents))
49
+ throw new Error("读取 EvolCore Agent 列表失败");
50
+ const candidates = result.agents.filter((agent) => agent.status === "running" && typeof agent.aid === "string" && /^[a-zA-Z0-9.-]+$/.test(agent.aid) && agent.channels?.some((channel) => channel.startsWith("aun#")));
51
+ const details = await Promise.all(candidates.map((agent) => run(["agent", "show", agent.aid, "--format", "json"]).catch(() => null)));
52
+ const local = new Set(await (0, local_aids_1.listExistingAids)());
53
+ return details.filter((detail) => detail?.ok && detail.status === "running" && detail.connection?.status === "connected" && local.has(detail.aid)).map((detail) => detail.aid).sort();
54
+ }
@@ -6,11 +6,14 @@ const node_module_1 = require("node:module");
6
6
  const node_path_1 = require("node:path");
7
7
  const node_url_1 = require("node:url");
8
8
  const messages_1 = require("./messages");
9
+ const evolcore_1 = require("./evolcore");
10
+ const local_aids_1 = require("../../local-aids");
9
11
  const manifest = {
10
- id: "aun", version: "1.0.0", type: "channel", displayName: "AUN 通道",
12
+ id: "aun", version: "1.0.3", type: "channel", displayName: "AUN 通道",
11
13
  entry: "dist/src-ts/plugins/aun/index.js", apiVersion: "1", process: "in-process",
12
14
  capabilities: ["channel", "channel.identity.create", "channel.receive", "channel.send", "health"],
13
15
  configSchema: "config.schema.json",
16
+ managementPage: "/abweb/channels/aun",
14
17
  };
15
18
  async function loadSdk() {
16
19
  const resolver = (0, node_module_1.createRequire)(__filename);
@@ -28,23 +31,152 @@ async function loadSdk() {
28
31
  }
29
32
  const aidPattern = /^[a-z0-9][a-z0-9_-]{2,63}\.[a-z0-9-]+(?:\.[a-z0-9-]+)+$/;
30
33
  class AunPlugin {
34
+ options;
31
35
  manifest = manifest;
32
36
  context;
33
37
  sdk;
34
38
  store;
35
- client;
36
- subscription;
37
- unregister;
39
+ clients = new Map();
40
+ identities = new Set();
41
+ legacyCreatedAids = new Set();
42
+ bindings = {};
43
+ bindingTimes = {};
44
+ creating = false;
45
+ unregister = [];
38
46
  ingress;
39
47
  aid = "";
40
48
  config = {};
41
49
  status = "degraded";
42
50
  healthMessage = "尚未配置 AID";
43
51
  seen = new messages_1.DuplicateTracker();
44
- constructor(options = {}) { this.sdk = options.sdk; this.ingress = options.ingress; }
52
+ constructor(options = {}) {
53
+ this.options = options;
54
+ this.sdk = options.sdk;
55
+ this.ingress = options.ingress;
56
+ }
57
+ identity = {
58
+ list: async () => {
59
+ const connected = await (this.options.connectedAids ?? evolcore_1.listConnectedAids)().catch((error) => {
60
+ this.context?.logger.warn("查询 EvolCore 已连接身份失败", { error: safeError(error) });
61
+ return [];
62
+ });
63
+ return {
64
+ evolcore: connected.filter((aid) => !this.identities.has(aid) && !this.legacyCreatedAids.has(aid)),
65
+ muAgentBridge: [...new Set([...this.clients.keys(), ...connected.filter((aid) => this.legacyCreatedAids.has(aid))])].sort(),
66
+ localMuAgentBridge: [...this.identities].filter((aid) => !this.clients.has(aid)).sort(),
67
+ };
68
+ },
69
+ create: async (aid, owner) => {
70
+ if (!aidPattern.test(aid) || !aidPattern.test(owner))
71
+ throw new Error("需要有效的 AID 和 Owner AID");
72
+ if (this.creating)
73
+ throw new Error("正在创建身份,请稍后再试");
74
+ this.creating = true;
75
+ try {
76
+ if (!this.identities.has(aid)) {
77
+ const result = await this.store.register({ aid, type: "agent", owner });
78
+ if (!result.ok)
79
+ throw new Error(redact(result.error?.message ?? result.error?.code ?? "AID 注册失败"));
80
+ const ownership = result.data?.ownership_status ?? result.data?.state ?? result.data?.operation_state;
81
+ if (ownership && ownership !== "active")
82
+ return { aid, status: "等待 Owner 确认", operation_id: result.data?.operation_id };
83
+ const loaded = this.store.load(aid);
84
+ if (!loaded.ok || !loaded.data)
85
+ throw new Error(loaded.error?.message ?? "注册后未能加载本地身份");
86
+ this.identities.add(aid);
87
+ this.config.aid = this.aid = aid;
88
+ this.config.owner_aid = owner;
89
+ this.config.aun_path = this.path();
90
+ await this.persist();
91
+ this.context?.logger.info("AUN 身份已创建", { aid, owner });
92
+ }
93
+ if (!this.clients.has(aid))
94
+ await this.connectAid(aid);
95
+ return { aid, status: "已创建", connection: this.clients.has(aid) ? "ready" : "degraded" };
96
+ }
97
+ finally {
98
+ this.creating = false;
99
+ }
100
+ },
101
+ bind: async (pluginId, aid, port) => {
102
+ if (pluginId === "aun" || pluginId === "abweb")
103
+ throw new Error("只能关联 Agent 插件");
104
+ const previous = this.bindings;
105
+ const previousTimes = this.bindingTimes;
106
+ this.bindings = { ...previous };
107
+ this.bindingTimes = { ...previousTimes };
108
+ try {
109
+ if (aid) {
110
+ if (!aidPattern.test(aid))
111
+ throw new Error("AID 格式无效");
112
+ if (this.context?.pluginStatus(pluginId) !== "ready")
113
+ throw new Error("目标 Agent 插件未启动");
114
+ if (this.identities.has(aid)) {
115
+ if (!this.clients.has(aid))
116
+ await this.connectAid(aid);
117
+ if (!this.clients.has(aid))
118
+ throw new Error("AID 当前未连接");
119
+ for (const [identity, target] of Object.entries(this.bindings))
120
+ if (target === pluginId) {
121
+ delete this.bindings[identity];
122
+ delete this.bindingTimes[identity];
123
+ }
124
+ this.bindings[aid] = pluginId;
125
+ }
126
+ else {
127
+ if (!(await (0, evolcore_1.listConnectedAids)()).includes(aid))
128
+ throw new Error("AID 当前未连接");
129
+ await (0, evolcore_1.configureAidForPlugin)(pluginId, aid, port);
130
+ for (const [identity, target] of Object.entries(this.bindings))
131
+ if (target === pluginId) {
132
+ delete this.bindings[identity];
133
+ delete this.bindingTimes[identity];
134
+ }
135
+ }
136
+ }
137
+ else {
138
+ for (const [identity, target] of Object.entries(this.bindings))
139
+ if (target === pluginId) {
140
+ delete this.bindings[identity];
141
+ delete this.bindingTimes[identity];
142
+ }
143
+ }
144
+ if (aid)
145
+ this.bindingTimes[aid] = new Date().toISOString();
146
+ await this.persist();
147
+ }
148
+ catch (error) {
149
+ this.bindings = previous;
150
+ this.bindingTimes = previousTimes;
151
+ this.config.bindings = previous;
152
+ this.config.binding_times = previousTimes;
153
+ this.context?.logger.error("AUN 身份关联失败", { aid, pluginId, error: safeError(error) });
154
+ throw error;
155
+ }
156
+ this.context?.logger.info("AUN 身份关联已更新", { aid, pluginId });
157
+ },
158
+ };
159
+ async persist() {
160
+ this.config.identities = [...this.identities].sort();
161
+ this.config.bindings = this.bindings;
162
+ this.config.binding_times = this.bindingTimes;
163
+ await (0, promises_1.writeFile)((0, node_path_1.join)(this.context.dataDir, "config.json"), `${JSON.stringify(this.config, null, 2)}\n`, { mode: 0o600 });
164
+ }
45
165
  async activate(context) {
46
166
  this.context = context;
47
167
  this.config = { ...context.config };
168
+ this.legacyCreatedAids.clear();
169
+ try {
170
+ const created = JSON.parse(await (0, promises_1.readFile)((0, node_path_1.join)(context.dataDir, "..", "created-aids.json"), "utf8"));
171
+ if (Array.isArray(created))
172
+ for (const aid of created)
173
+ if (typeof aid === "string" && aidPattern.test(aid))
174
+ this.legacyCreatedAids.add(aid);
175
+ }
176
+ catch (error) {
177
+ if (error.code !== "ENOENT")
178
+ context.logger.warn("读取旧版身份创建记录失败", { error: safeError(error) });
179
+ }
48
180
  for (const key of ["aid", "owner_aid", "aun_path", "encryption_seed", "root_ca_path", "allow_from", "agent_plugin"]) {
49
181
  if (this.config[key] !== undefined && typeof this.config[key] !== "string")
50
182
  throw new Error(`${key} 必须是字符串`);
@@ -63,21 +195,45 @@ class AunPlugin {
63
195
  this.sdk ??= await loadSdk();
64
196
  await (0, promises_1.mkdir)(this.path(), { recursive: true });
65
197
  this.store = this.newStore();
66
- const disposer = context.http.register({ method: "POST", path: "/api/plugins/aun/aid", handler: this.createAid });
67
- if (typeof disposer === "function")
68
- this.unregister = disposer;
198
+ for (const [method, path, handler] of [
199
+ ["POST", "/api/plugins/aun/aid", this.createAid],
200
+ ["GET", "/api/plugins/aun/identities", this.listIdentities],
201
+ ["POST", "/api/plugins/aun/bind", this.bindAgent],
202
+ ["POST", "/api/plugins/aun/settings", this.saveSettings],
203
+ ...(["", "/app.js", "/style.css"].map((suffix) => ["GET", `/abweb/channels/aun${suffix}`, this.servePage])),
204
+ ]) {
205
+ const dispose = context.http.register({ method, path, handler });
206
+ if (typeof dispose === "function")
207
+ this.unregister.push(dispose);
208
+ }
69
209
  this.aid = typeof this.config.aid === "string" ? this.config.aid.trim() : "";
210
+ this.bindings = this.config.bindings && typeof this.config.bindings === "object" && !Array.isArray(this.config.bindings) ? { ...this.config.bindings } : {};
211
+ this.bindingTimes = this.config.binding_times && typeof this.config.binding_times === "object" && !Array.isArray(this.config.binding_times) ? { ...this.config.binding_times } : {};
212
+ for (const aid of Array.isArray(this.config.identities) ? this.config.identities : []) {
213
+ if (typeof aid === "string" && aidPattern.test(aid))
214
+ this.identities.add(aid);
215
+ }
70
216
  if (this.aid)
71
- await this.connectAid(this.aid);
217
+ this.identities.add(this.aid);
218
+ for (const aid of this.identities)
219
+ await this.connectAid(aid);
72
220
  }
73
221
  async connectAid(aid) {
74
222
  try {
75
223
  const loaded = this.store.load(aid);
76
224
  if (!loaded.ok || !loaded.data)
77
225
  throw new Error(loaded.error?.message ?? "本地密钥库中不存在该 AID");
78
- this.client = new this.sdk.AUNClient(loaded.data.aid);
79
- this.subscription = this.client.on("message.received", (payload) => this.onMessage(payload));
80
- await this.client.connect();
226
+ const client = new this.sdk.AUNClient(loaded.data.aid);
227
+ const subscription = client.on("message.received", (payload) => this.onMessage(payload, aid));
228
+ try {
229
+ await client.connect();
230
+ }
231
+ catch (error) {
232
+ subscription.unsubscribe();
233
+ await client.close();
234
+ throw error;
235
+ }
236
+ this.clients.set(aid, { client, subscription });
81
237
  this.status = "ready";
82
238
  this.healthMessage = "已连接";
83
239
  }
@@ -85,23 +241,24 @@ class AunPlugin {
85
241
  this.status = "degraded";
86
242
  this.healthMessage = safeError(error);
87
243
  this.context?.logger.warn("AUN 通道连接失败", { error: this.healthMessage });
88
- await this.closeClient();
89
244
  }
90
245
  }
91
246
  async deactivate(_reason) {
92
247
  this.status = "degraded";
93
- this.subscription?.unsubscribe();
94
- this.subscription = undefined;
95
- await this.closeClient();
248
+ for (const { client, subscription } of this.clients.values()) {
249
+ subscription.unsubscribe();
250
+ await client.close();
251
+ }
252
+ this.clients.clear();
96
253
  await this.store?.close();
97
254
  this.store = undefined;
98
- this.unregister?.();
99
- this.unregister = undefined;
255
+ for (const dispose of this.unregister.splice(0))
256
+ dispose();
100
257
  this.context?.http.unregister("aun");
101
258
  this.context = undefined;
102
259
  }
103
260
  async health() {
104
- return { status: this.status, message: this.healthMessage, details: { aid: this.aid || undefined, connected: this.status === "ready" } };
261
+ return { status: this.clients.size ? "ready" : this.status, message: this.healthMessage, details: { aid: this.aid || undefined, connected: this.clients.size > 0, aids: [...this.clients.keys()], identities: [...this.identities], bindings: this.bindings } };
105
262
  }
106
263
  async handle(_request) { throw new Error("AUN 是入站通道插件"); }
107
264
  path() {
@@ -128,45 +285,96 @@ class AunPlugin {
128
285
  const aid = input && typeof input === "object" && typeof input.aid === "string" ? input.aid.trim() : "";
129
286
  if (!aidPattern.test(aid))
130
287
  return this.json(response, 400, { error: "AID 格式无效" });
131
- if (this.aid && aid !== this.aid)
132
- return this.json(response, 409, { error: "创建其他身份前请先停止当前 AID" });
133
- if (aid === this.aid)
134
- return this.json(response, 200, { aid, status: "existing", connection: this.status });
135
288
  try {
136
289
  const body = input;
137
- const owner = typeof body.owner_aid === "string" && body.owner_aid.trim()
138
- ? body.owner_aid.trim()
290
+ const requestedOwner = body.owner_aid ?? body.ownerAid;
291
+ const owner = typeof requestedOwner === "string" && requestedOwner.trim()
292
+ ? requestedOwner.trim()
139
293
  : typeof this.config.owner_aid === "string" ? this.config.owner_aid.trim() : "";
140
294
  if (!owner)
141
295
  return this.json(response, 400, { error: "创建 AID 时必须提供 Owner AID" });
142
296
  if (!aidPattern.test(owner))
143
297
  return this.json(response, 400, { error: "Owner AID 格式无效" });
144
- const result = await this.store.register({ aid, type: "agent", owner });
145
- if (!result.ok)
146
- return this.json(response, 502, { error: result.error?.code ?? "注册失败", message: redact(result.error?.message ?? "AID 注册失败") });
147
- if (result.data?.ownership_status !== "active")
148
- return this.json(response, 202, { aid, owner, status: "等待 Owner 确认", operation_id: result.data?.operation_id });
149
- this.config.aid = aid;
150
- this.config.owner_aid = owner;
151
- this.config.aun_path = this.path();
152
- await (0, promises_1.mkdir)(this.context.dataDir, { recursive: true });
153
- await (0, promises_1.writeFile)((0, node_path_1.join)(this.context.dataDir, "config.json"), `${JSON.stringify(this.config, null, 2)}\n`, { mode: 0o600 });
154
- this.aid = aid;
155
- await this.connectAid(aid);
156
- return this.json(response, 201, { aid, status: "已创建", connection: this.status });
298
+ const result = await this.identity.create(aid, owner);
299
+ return this.json(response, result.status === "等待 Owner 确认" ? 202 : 201, result);
157
300
  }
158
301
  catch (error) {
159
302
  this.context?.logger.warn("AUN AID 注册失败", { error: safeError(error) });
160
303
  return this.json(response, 502, { error: safeError(error) });
161
304
  }
162
305
  };
163
- async onMessage(payload) {
164
- const message = (0, messages_1.normalizeAunMessage)(payload, this.aid, typeof this.config.allow_from === "string" ? this.config.allow_from : undefined);
165
- if (!message || !this.seen.add(message.messageId ?? ""))
306
+ listIdentities = async (_request, response) => {
307
+ const groups = await this.identity.list();
308
+ const connected = new Set([...groups.evolcore, ...groups.muAgentBridge]);
309
+ const localAids = (await (0, local_aids_1.listExistingAids)()).filter((aid) => !connected.has(aid));
310
+ this.json(response, 200, { groups, localAids, bindings: this.bindings, bindingTimes: this.bindingTimes });
311
+ };
312
+ saveSettings = async (request, response) => {
313
+ try {
314
+ let raw = "";
315
+ for await (const chunk of request) {
316
+ raw += chunk;
317
+ if (raw.length > 65536)
318
+ throw new Error("请求内容过大");
319
+ }
320
+ const body = JSON.parse(raw || "{}");
321
+ for (const key of ["agent_plugin", "allow_from", "aun_path"])
322
+ if (typeof body[key] !== "string")
323
+ return this.json(response, 400, { error: `${key} 必须是字符串` });
324
+ if (body.encryption_seed !== undefined && typeof body.encryption_seed !== "string")
325
+ return this.json(response, 400, { error: "encryption_seed 必须是字符串" });
326
+ this.config = { ...this.config, agent_plugin: body.agent_plugin, allow_from: body.allow_from, aun_path: body.aun_path, ...(body.encryption_seed ? { encryption_seed: body.encryption_seed } : {}) };
327
+ await this.persist();
328
+ this.context?.logger.info("AUN 通道配置已保存");
329
+ this.json(response, 200, { ok: true });
330
+ }
331
+ catch (error) {
332
+ this.context?.logger.warn("AUN 通道配置失败", { error: safeError(error) });
333
+ this.json(response, 400, { error: safeError(error) });
334
+ }
335
+ };
336
+ bindAgent = async (request, response) => {
337
+ try {
338
+ let raw = "";
339
+ for await (const chunk of request) {
340
+ raw += chunk;
341
+ if (raw.length > 65536)
342
+ throw new Error("请求内容过大");
343
+ }
344
+ const body = JSON.parse(raw || "{}");
345
+ const aid = typeof body.aid === "string" ? body.aid.trim() : "";
346
+ const pluginId = typeof body.pluginId === "string" ? body.pluginId.trim() : "";
347
+ const port = Number(body.port);
348
+ if (!aidPattern.test(aid) || !/^[a-z][a-z0-9-]{1,63}$/.test(pluginId) || !Number.isInteger(port) || port < 1 || port > 65535)
349
+ return this.json(response, 400, { error: "关联参数无效" });
350
+ await this.identity.bind(pluginId, aid, port);
351
+ this.json(response, 200, { ok: true });
352
+ }
353
+ catch (error) {
354
+ this.json(response, 400, { error: safeError(error) });
355
+ }
356
+ };
357
+ servePage = async (request, response) => {
358
+ const path = new URL(request.url ?? "/", "http://localhost").pathname;
359
+ const file = path.endsWith("/app.js") ? "app.js" : path.endsWith("/style.css") ? "style.css" : "index.html";
360
+ try {
361
+ const content = await (0, promises_1.readFile)((0, node_path_1.join)(__dirname, "public", file));
362
+ response.statusCode = 200;
363
+ response.setHeader("content-type", file.endsWith(".js") ? "text/javascript; charset=utf-8" : file.endsWith(".css") ? "text/css; charset=utf-8" : "text/html; charset=utf-8");
364
+ response.setHeader("cache-control", "no-store");
365
+ response.end(content);
366
+ }
367
+ catch {
368
+ this.json(response, 503, { error: "AUN 设置页面资源不可用" });
369
+ }
370
+ };
371
+ async onMessage(payload, aid) {
372
+ const message = (0, messages_1.normalizeAunMessage)(payload, aid, typeof this.config.allow_from === "string" ? this.config.allow_from : undefined);
373
+ if (!message || !this.seen.add(message.messageId ? `${aid}:${message.messageId}` : ""))
166
374
  return;
167
375
  const channelMessage = {
168
376
  channel: "aun", peerId: message.peerId, messageId: message.messageId, text: message.text, threadId: message.threadId,
169
- agentPlugin: typeof this.config.agent_plugin === "string" && this.config.agent_plugin.trim() ? this.config.agent_plugin.trim() : "qwenwork",
377
+ agentPlugin: this.bindings[aid] || (typeof this.config.agent_plugin === "string" && this.config.agent_plugin.trim() ? this.config.agent_plugin.trim() : "qwenwork"),
170
378
  reply: async (text) => {
171
379
  if (!text.trim())
172
380
  return;
@@ -175,12 +383,12 @@ class AunPlugin {
175
383
  payload.thread_id = message.threadId;
176
384
  const params = { to: message.peerId, payload, encrypt: true };
177
385
  try {
178
- await this.client.call("message.send", params);
386
+ await this.clients.get(aid).client.call("message.send", params);
179
387
  }
180
388
  catch (error) {
181
389
  if (!this.isE2eeError(error))
182
390
  throw error;
183
- await this.client.call("message.send", { ...params, encrypt: false });
391
+ await this.clients.get(aid).client.call("message.send", { ...params, encrypt: false });
184
392
  }
185
393
  },
186
394
  };
@@ -196,12 +404,6 @@ class AunPlugin {
196
404
  return [sdk.E2EEError, sdk.E2EEDecryptFailedError, sdk.E2EEDegradedError].some((type) => !!type && error instanceof type)
197
405
  || (error instanceof Error && /^(E2EE|E2EE[A-Za-z]+Error)/.test(error.name));
198
406
  }
199
- async closeClient() {
200
- const client = this.client;
201
- this.client = undefined;
202
- if (client)
203
- await client.close();
204
- }
205
407
  json(response, status, value) {
206
408
  response.statusCode = status;
207
409
  response.setHeader("content-type", "application/json; charset=utf-8");
@@ -0,0 +1,66 @@
1
+ (() => {
2
+ const find = (selector) => document.querySelector(selector);
3
+ const status = find("#status");
4
+ const list = find("#identities");
5
+ const createForm = find("#create-form");
6
+ const configForm = find("#config-form");
7
+ let agents = [];
8
+ let loading = false;
9
+
10
+ async function request(path, method = "GET", body) {
11
+ const token = localStorage.getItem("mab-login-token");
12
+ const response = await fetch(path, { method, headers: { ...(token ? { authorization: `Bearer ${token}` } : {}), ...(body ? { "content-type": "application/json" } : {}) }, ...(body ? { body: JSON.stringify(body) } : {}) });
13
+ const result = await response.json();
14
+ if (!response.ok) throw new Error(result.error || `请求失败:${response.status}`);
15
+ return result;
16
+ }
17
+ function show(message, failed = false) { status.textContent = message; status.className = failed ? "error" : ""; }
18
+ async function load(refreshForm = true) {
19
+ if (loading) return;
20
+ loading = true;
21
+ try {
22
+ const [identities, plugins, config] = await Promise.all([request("/api/plugins/aun/identities"), request("/api/plugins"), request("/api/plugins/aun/config")]);
23
+ agents = (plugins.plugins || []).filter((plugin) => plugin.type === "agent");
24
+ if (refreshForm) createForm.elements.ownerAid.value = config.owner_aid || "";
25
+ await Promise.all(agents.map(async (agent) => { agent.configAid = (await request(`/api/plugins/${encodeURIComponent(agent.id)}/config`)).aid || ""; }));
26
+ const target = configForm.elements.agent_plugin;
27
+ if (refreshForm) {
28
+ target.replaceChildren(new Option("默认 Agent", ""), ...agents.map((agent) => new Option(agent.displayName || agent.id, agent.id)));
29
+ target.value = config.agent_plugin || "";
30
+ for (const key of ["allow_from", "aun_path"]) configForm.elements[key].value = config[key] || "";
31
+ configForm.elements.encryption_seed.value = "";
32
+ }
33
+ list.replaceChildren();
34
+ const entries = [["本工程 · 已连接", identities.groups.muAgentBridge || []], ["EvolCore · 已连接", identities.groups.evolcore || []]]
35
+ .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 })));
36
+ 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
+ for (const { aid, label, bound } of entries) {
38
+ const row = document.createElement("tr");
39
+ for (const value of [aid, label, bound || "未关联"]) { const cell = document.createElement("td"); cell.textContent = value; row.append(cell); }
40
+ const cell = document.createElement("td"); const select = document.createElement("select"); select.setAttribute("aria-label", `${aid} 关联 Agent`);
41
+ select.append(new Option("选择 Agent", ""), ...agents.map((agent) => new Option(agent.displayName || agent.id, agent.id)));
42
+ select.value = bound; select.onchange = () => { if (select.value) void bind(aid, select.value); };
43
+ cell.append(select); row.append(cell); list.append(row);
44
+ }
45
+ 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(`${(identities.groups.evolcore || []).length + (identities.groups.muAgentBridge || []).length} 个已连接 AID`);
47
+ } catch (error) { show(String(error), true); }
48
+ finally { loading = false; }
49
+ }
50
+ async function bind(aid, pluginId) {
51
+ try {
52
+ await request(`/api/plugins/${encodeURIComponent(pluginId)}/aid`, "PATCH", { aid });
53
+ await load(); show(`${aid} 已关联 ${pluginId}`);
54
+ } catch (error) { show(`关联失败:${String(error)}`, true); }
55
+ }
56
+ createForm.onsubmit = (event) => { event.preventDefault(); const aid = createForm.elements.aid.value.trim(); const owner = createForm.elements.ownerAid.value.trim();
57
+ void request("/api/plugins/aun/aid", "POST", { aid, ownerAid: owner }).then((result) => load().then(() => show(result.status === "等待 Owner 确认" ? `${aid} 等待 Owner 确认` : `${aid} 已创建`))).catch((error) => show(String(error), true));
58
+ };
59
+ 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
+ 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(() => show("配置已保存")).catch((error) => show(String(error), true));
62
+ };
63
+ find("#refresh").onclick = () => void load();
64
+ void load();
65
+ setInterval(() => { if (!document.hidden) void load(false); }, 10_000);
66
+ })();
@@ -0,0 +1,14 @@
1
+ <!doctype html>
2
+ <html lang="zh-CN">
3
+ <head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><link rel="stylesheet" href="/abweb/channels/aun/style.css"><title>AUN 设置</title></head>
4
+ <body>
5
+ <main>
6
+ <header><h1>AUN 身份</h1><button id="refresh" type="button">刷新</button></header>
7
+ <p id="status" role="status"></p>
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="new.agentid.cn"></label><label>Owner AID<input name="ownerAid" required></label></div><button type="submit">创建</button></form>
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
+ </main>
12
+ <script src="/abweb/channels/aun/app.js"></script>
13
+ </body>
14
+ </html>
@@ -0,0 +1,21 @@
1
+ :root { font-family: "Segoe UI", "Microsoft YaHei", sans-serif; color: #182027; background: #fff; }
2
+ * { box-sizing: border-box; }
3
+ body { margin: 0; }
4
+ main { padding: 8px 2px 20px; }
5
+ header { display: flex; align-items: center; justify-content: space-between; }
6
+ h1 { font-size: 17px; margin: 0; }
7
+ h2 { font-size: 15px; margin: 0 0 12px; }
8
+ p { min-height: 20px; color: #54665d; font-size: 13px; }
9
+ form { padding: 18px 0; border-top: 1px solid #d9dfdc; }
10
+ .fields { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; margin-bottom: 12px; }
11
+ label { display: grid; gap: 5px; font-size: 13px; }
12
+ input, select, button { min-height: 34px; padding: 6px 9px; border: 1px solid #bfc9c5; border-radius: 5px; background: white; color: inherit; font: inherit; }
13
+ button { cursor: pointer; }
14
+ button:hover { background: #e5f2ea; }
15
+ .table-scroll { overflow-x: auto; margin: 10px 0 20px; }
16
+ table { width: 100%; min-width: 650px; border-collapse: collapse; font-size: 13px; }
17
+ th, td { padding: 9px 8px; text-align: left; border-bottom: 1px solid #dce3df; }
18
+ th { background: #edf1ef; color: #566761; }
19
+ td select { max-width: 190px; }
20
+ .error { color: #a83427; }
21
+ @media (max-width: 620px) { .fields { grid-template-columns: 1fr; } }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-bridge-aun",
3
- "version": "1.0.0",
3
+ "version": "1.0.3",
4
4
  "description": "mu-agent-bridge 的 AUN 通道插件。",
5
5
  "keywords": ["mu-agent-bridge-plugin", "mu-agent-bridge-channel"],
6
6
  "repository": { "type": "git", "url": "https://codeup.aliyun.com/6808da56fb3da14b899afd54/agent_bridge.git", "directory": "packages/aun" },
package/plugin.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "id": "aun",
3
- "version": "1.0.0",
3
+ "version": "1.0.3",
4
4
  "type": "channel",
5
5
  "displayName": "AUN 通道",
6
6
  "entry": "dist/src-ts/plugins/aun/index.js",
7
7
  "apiVersion": "1",
8
8
  "process": "in-process",
9
9
  "capabilities": ["channel", "channel.identity.create", "channel.receive", "channel.send", "health"],
10
- "configSchema": "config.schema.json"
10
+ "configSchema": "config.schema.json",
11
+ "managementPage": "/abweb/channels/aun"
11
12
  }