agent-bridge-aun 0.0.0-stage → 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
@@ -1,3 +1,9 @@
1
- # Temporary Holding Version
1
+ # AUN 通道插件
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ `agent-bridge-aun` 为 mu-agent-bridge 提供 AUN 消息通道,支持使用已有 AID 或通过管理 API 注册 AID,并将收到的文本交给指定 Agent 插件处理。
4
+
5
+ 安装后在 ABWeb 的插件管理中启动 AUN 通道。配置 `aid` 和可选的 `agent_plugin`;默认 Agent 插件为 `qwenwork`。AID 创建接口为 `POST /api/plugins/aun/aid`,请求内容为 `{"aid":"新AID.agentid.cn","owner_aid":"所有者AID.agentid.cn"}`。
6
+
7
+ 该插件依赖 `@agentunion/fastaun`,要求 Node.js 22.16 或更高版本。
8
+
9
+ ABWeb 的 AUN 设置仅展示已连接 AID。列表先显示已关联 Agent 的身份,同类中本工程身份在 EvolCore 身份前面;各组按最近成功关联的时间倒序排列,时间相同或旧记录没有时间时按 AID 名称排序。关联时间保存在插件配置的 `binding_times` 中,刷新或重启后保留;旧记录不补造时间,再次关联成功后开始记录。
@@ -0,0 +1,18 @@
1
+ {
2
+ "type": "object",
3
+ "properties": {
4
+ "identities": { "type": "array", "items": { "type": "string" } },
5
+ "bindings": { "type": "object", "additionalProperties": { "type": "string" } },
6
+ "binding_times": { "type": "object", "additionalProperties": { "type": "string" } },
7
+ "aid": { "type": "string", "minLength": 1 },
8
+ "owner_aid": { "type": "string", "minLength": 1 },
9
+ "agent_plugin": { "type": "string", "minLength": 1, "default": "qwenwork" },
10
+ "aun_path": { "type": "string", "minLength": 1 },
11
+ "encryption_seed": { "type": "string" },
12
+ "verify_ssl": { "type": "boolean", "default": true },
13
+ "root_ca_path": { "type": "string" },
14
+ "allow_from": { "type": "string" },
15
+ "delivery_mode": { "enum": ["fanout", "queue"] }
16
+ },
17
+ "additionalProperties": false
18
+ }
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AgentError = void 0;
4
+ class AgentError extends Error {
5
+ constructor(message, options) {
6
+ super(message, options);
7
+ this.name = "AgentError";
8
+ }
9
+ }
10
+ exports.AgentError = AgentError;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -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
+ }
@@ -0,0 +1,420 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AunPlugin = void 0;
4
+ const promises_1 = require("node:fs/promises");
5
+ const node_module_1 = require("node:module");
6
+ const node_path_1 = require("node:path");
7
+ const node_url_1 = require("node:url");
8
+ const messages_1 = require("./messages");
9
+ const evolcore_1 = require("./evolcore");
10
+ const local_aids_1 = require("../../local-aids");
11
+ const manifest = {
12
+ id: "aun", version: "1.0.3", type: "channel", displayName: "AUN 通道",
13
+ entry: "dist/src-ts/plugins/aun/index.js", apiVersion: "1", process: "in-process",
14
+ capabilities: ["channel", "channel.identity.create", "channel.receive", "channel.send", "health"],
15
+ configSchema: "config.schema.json",
16
+ managementPage: "/abweb/channels/aun",
17
+ };
18
+ async function loadSdk() {
19
+ const resolver = (0, node_module_1.createRequire)(__filename);
20
+ const dynamicImport = new Function("url", "return import(url)");
21
+ for (const modules of resolver.resolve.paths("@agentunion/fastaun") ?? []) {
22
+ const packageRoot = (0, node_path_1.join)(modules, "@agentunion", "fastaun");
23
+ try {
24
+ const metadata = JSON.parse(await (0, promises_1.readFile)((0, node_path_1.join)(packageRoot, "package.json"), "utf8"));
25
+ const entry = typeof metadata.exports?.["."] === "object" ? metadata.exports["."]?.import : metadata.exports?.["."];
26
+ return await dynamicImport((0, node_url_1.pathToFileURL)((0, node_path_1.join)(packageRoot, entry ?? metadata.main ?? "index.js")).href);
27
+ }
28
+ catch { /* 继续查找上一级 node_modules */ }
29
+ }
30
+ throw new Error("未找到 @agentunion/fastaun,请确认 AUN 插件依赖已安装");
31
+ }
32
+ const aidPattern = /^[a-z0-9][a-z0-9_-]{2,63}\.[a-z0-9-]+(?:\.[a-z0-9-]+)+$/;
33
+ class AunPlugin {
34
+ options;
35
+ manifest = manifest;
36
+ context;
37
+ sdk;
38
+ store;
39
+ clients = new Map();
40
+ identities = new Set();
41
+ legacyCreatedAids = new Set();
42
+ bindings = {};
43
+ bindingTimes = {};
44
+ creating = false;
45
+ unregister = [];
46
+ ingress;
47
+ aid = "";
48
+ config = {};
49
+ status = "degraded";
50
+ healthMessage = "尚未配置 AID";
51
+ seen = new messages_1.DuplicateTracker();
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
+ }
165
+ async activate(context) {
166
+ this.context = context;
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
+ }
180
+ for (const key of ["aid", "owner_aid", "aun_path", "encryption_seed", "root_ca_path", "allow_from", "agent_plugin"]) {
181
+ if (this.config[key] !== undefined && typeof this.config[key] !== "string")
182
+ throw new Error(`${key} 必须是字符串`);
183
+ }
184
+ if (this.config.verify_ssl !== undefined && typeof this.config.verify_ssl !== "boolean")
185
+ throw new Error("verify_ssl 必须是布尔值");
186
+ if (this.config.delivery_mode !== undefined && !["fanout", "queue"].includes(String(this.config.delivery_mode)))
187
+ throw new Error("delivery_mode 只能是 fanout 或 queue");
188
+ if (this.config.aid && !aidPattern.test(String(this.config.aid)))
189
+ throw new Error("AID 格式无效");
190
+ if (this.config.owner_aid && !aidPattern.test(String(this.config.owner_aid)))
191
+ throw new Error("Owner AID 格式无效");
192
+ this.ingress ??= context.ingress;
193
+ if (!this.ingress)
194
+ throw new Error("运行时未提供通道消息入口");
195
+ this.sdk ??= await loadSdk();
196
+ await (0, promises_1.mkdir)(this.path(), { recursive: true });
197
+ this.store = this.newStore();
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
+ }
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
+ }
216
+ if (this.aid)
217
+ this.identities.add(this.aid);
218
+ for (const aid of this.identities)
219
+ await this.connectAid(aid);
220
+ }
221
+ async connectAid(aid) {
222
+ try {
223
+ const loaded = this.store.load(aid);
224
+ if (!loaded.ok || !loaded.data)
225
+ throw new Error(loaded.error?.message ?? "本地密钥库中不存在该 AID");
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 });
237
+ this.status = "ready";
238
+ this.healthMessage = "已连接";
239
+ }
240
+ catch (error) {
241
+ this.status = "degraded";
242
+ this.healthMessage = safeError(error);
243
+ this.context?.logger.warn("AUN 通道连接失败", { error: this.healthMessage });
244
+ }
245
+ }
246
+ async deactivate(_reason) {
247
+ this.status = "degraded";
248
+ for (const { client, subscription } of this.clients.values()) {
249
+ subscription.unsubscribe();
250
+ await client.close();
251
+ }
252
+ this.clients.clear();
253
+ await this.store?.close();
254
+ this.store = undefined;
255
+ for (const dispose of this.unregister.splice(0))
256
+ dispose();
257
+ this.context?.http.unregister("aun");
258
+ this.context = undefined;
259
+ }
260
+ async health() {
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 } };
262
+ }
263
+ async handle(_request) { throw new Error("AUN 是入站通道插件"); }
264
+ path() {
265
+ const configured = this.config.aun_path;
266
+ return typeof configured === "string" && configured.trim() ? configured : (0, node_path_1.join)(this.context.dataDir, "aun");
267
+ }
268
+ newStore() {
269
+ return new this.sdk.AIDStore({ aunPath: this.path(), encryptionSeed: typeof this.config.encryption_seed === "string" ? this.config.encryption_seed : "", verifySsl: typeof this.config.verify_ssl === "boolean" ? this.config.verify_ssl : true, rootCaPath: this.config.root_ca_path });
270
+ }
271
+ createAid = async (request, response) => {
272
+ let input;
273
+ try {
274
+ let body = "";
275
+ for await (const chunk of request) {
276
+ body += Buffer.isBuffer(chunk) ? chunk.toString("utf8") : String(chunk);
277
+ if (body.length > 65536)
278
+ throw new Error("请求内容过大");
279
+ }
280
+ input = JSON.parse(body);
281
+ }
282
+ catch (error) {
283
+ return this.json(response, 400, { error: error instanceof Error && error.message === "请求内容过大" ? error.message : "JSON 请求格式无效" });
284
+ }
285
+ const aid = input && typeof input === "object" && typeof input.aid === "string" ? input.aid.trim() : "";
286
+ if (!aidPattern.test(aid))
287
+ return this.json(response, 400, { error: "AID 格式无效" });
288
+ try {
289
+ const body = input;
290
+ const requestedOwner = body.owner_aid ?? body.ownerAid;
291
+ const owner = typeof requestedOwner === "string" && requestedOwner.trim()
292
+ ? requestedOwner.trim()
293
+ : typeof this.config.owner_aid === "string" ? this.config.owner_aid.trim() : "";
294
+ if (!owner)
295
+ return this.json(response, 400, { error: "创建 AID 时必须提供 Owner AID" });
296
+ if (!aidPattern.test(owner))
297
+ return this.json(response, 400, { error: "Owner AID 格式无效" });
298
+ const result = await this.identity.create(aid, owner);
299
+ return this.json(response, result.status === "等待 Owner 确认" ? 202 : 201, result);
300
+ }
301
+ catch (error) {
302
+ this.context?.logger.warn("AUN AID 注册失败", { error: safeError(error) });
303
+ return this.json(response, 502, { error: safeError(error) });
304
+ }
305
+ };
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}` : ""))
374
+ return;
375
+ const channelMessage = {
376
+ channel: "aun", peerId: message.peerId, messageId: message.messageId, text: message.text, threadId: message.threadId,
377
+ agentPlugin: this.bindings[aid] || (typeof this.config.agent_plugin === "string" && this.config.agent_plugin.trim() ? this.config.agent_plugin.trim() : "qwenwork"),
378
+ reply: async (text) => {
379
+ if (!text.trim())
380
+ return;
381
+ const payload = { type: "text", text };
382
+ if (message.threadId)
383
+ payload.thread_id = message.threadId;
384
+ const params = { to: message.peerId, payload, encrypt: true };
385
+ try {
386
+ await this.clients.get(aid).client.call("message.send", params);
387
+ }
388
+ catch (error) {
389
+ if (!this.isE2eeError(error))
390
+ throw error;
391
+ await this.clients.get(aid).client.call("message.send", { ...params, encrypt: false });
392
+ }
393
+ },
394
+ };
395
+ try {
396
+ await this.ingress.publish(channelMessage);
397
+ }
398
+ catch (error) {
399
+ this.context?.logger.warn("AUN 消息处理失败", { error: safeError(error) });
400
+ }
401
+ }
402
+ isE2eeError(error) {
403
+ const sdk = this.sdk;
404
+ return [sdk.E2EEError, sdk.E2EEDecryptFailedError, sdk.E2EEDegradedError].some((type) => !!type && error instanceof type)
405
+ || (error instanceof Error && /^(E2EE|E2EE[A-Za-z]+Error)/.test(error.name));
406
+ }
407
+ json(response, status, value) {
408
+ response.statusCode = status;
409
+ response.setHeader("content-type", "application/json; charset=utf-8");
410
+ response.end(JSON.stringify(value));
411
+ }
412
+ }
413
+ exports.AunPlugin = AunPlugin;
414
+ exports.default = AunPlugin;
415
+ function safeError(error) {
416
+ return redact(error instanceof Error ? error.message : String(error)).slice(0, 240);
417
+ }
418
+ function redact(message) {
419
+ return message.replace(/(private[_ -]?key|seed|token|certificate)\s*[:=]\s*[^\s,;]+/gi, "$1=[已隐藏]");
420
+ }
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DuplicateTracker = void 0;
4
+ exports.normalizeAunMessage = normalizeAunMessage;
5
+ function normalizeAunMessage(payload, selfAid, allowFrom) {
6
+ if (!payload || typeof payload !== "object")
7
+ return undefined;
8
+ const data = payload;
9
+ const peerId = typeof data.from === "string" ? data.from.trim() : "";
10
+ const rules = (allowFrom ?? "").split(",").map((value) => value.trim()).filter(Boolean);
11
+ if (!peerId || peerId === selfAid || (rules.length > 0 && !rules.includes(peerId)))
12
+ return undefined;
13
+ const body = data.payload && typeof data.payload === "object" ? data.payload : {};
14
+ const text = typeof data.payload === "string" ? data.payload : typeof body.text === "string" ? body.text : "";
15
+ if (!text.trim())
16
+ return undefined;
17
+ return {
18
+ peerId,
19
+ ...(typeof data.message_id === "string" ? { messageId: data.message_id } : {}),
20
+ text,
21
+ ...(typeof body.thread_id === "string" ? { threadId: body.thread_id } : {}),
22
+ };
23
+ }
24
+ class DuplicateTracker {
25
+ limit;
26
+ ttlMs;
27
+ ids = new Map();
28
+ constructor(limit = 10000, ttlMs = 5 * 60 * 1000) {
29
+ this.limit = limit;
30
+ this.ttlMs = ttlMs;
31
+ }
32
+ add(id, now = Date.now()) {
33
+ if (!id)
34
+ return true;
35
+ const seen = this.ids.get(id);
36
+ if (seen !== undefined && now - seen < this.ttlMs)
37
+ return false;
38
+ this.ids.delete(id);
39
+ this.ids.set(id, now);
40
+ while (this.ids.size > this.limit)
41
+ this.ids.delete(this.ids.keys().next().value);
42
+ return true;
43
+ }
44
+ }
45
+ exports.DuplicateTracker = DuplicateTracker;
@@ -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,13 @@
1
1
  {
2
2
  "name": "agent-bridge-aun",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.3",
4
+ "description": "mu-agent-bridge 的 AUN 通道插件。",
5
+ "keywords": ["mu-agent-bridge-plugin", "mu-agent-bridge-channel"],
6
+ "repository": { "type": "git", "url": "https://codeup.aliyun.com/6808da56fb3da14b899afd54/agent_bridge.git", "directory": "packages/aun" },
7
+ "main": "dist/src-ts/plugins/aun/index.js",
8
+ "files": ["dist/", "plugin.json", "config.schema.json", "README.md"],
9
+ "agentBridge": { "plugin": "./plugin.json", "type": "channel" },
10
+ "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" },
11
+ "engines": { "node": ">=22.16.0" },
12
+ "dependencies": { "@agentunion/fastaun": "0.5.18" }
13
+ }
package/plugin.json ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "id": "aun",
3
+ "version": "1.0.3",
4
+ "type": "channel",
5
+ "displayName": "AUN 通道",
6
+ "entry": "dist/src-ts/plugins/aun/index.js",
7
+ "apiVersion": "1",
8
+ "process": "in-process",
9
+ "capabilities": ["channel", "channel.identity.create", "channel.receive", "channel.send", "health"],
10
+ "configSchema": "config.schema.json",
11
+ "managementPage": "/abweb/channels/aun"
12
+ }