mu-agent-bridge 1.0.64 → 1.0.65
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/dist/src-ts/plugin-manager.js +16 -1
- package/dist/src-ts/plugins/aun/index.js +78 -40
- package/dist/tests-ts/aun-plugin.test.js +165 -19
- package/dist/tests-ts/installer-assets.test.js +2 -0
- package/dist/tests-ts/plugin-manager.test.js +51 -0
- package/package.json +1 -1
- package/plugins/aun/dist/src-ts/plugins/aun/index.js +78 -40
- package/plugins/aun/package.json +1 -1
- package/plugins/aun/plugin.json +1 -1
|
@@ -802,7 +802,8 @@ class PluginManager {
|
|
|
802
802
|
async discoverDirectory(directory, label, manifestFile = (0, node_path_1.join)(directory, "plugin.json")) {
|
|
803
803
|
try {
|
|
804
804
|
const manifest = (0, plugin_manifest_1.validateManifest)(JSON.parse(await (0, promises_1.readFile)(manifestFile, "utf8")), directory);
|
|
805
|
-
|
|
805
|
+
const existing = this.plugins.get(manifest.id);
|
|
806
|
+
if (existing && (existing.instance || (0, node_path_1.resolve)(existing.record.path) === (0, node_path_1.resolve)(directory)))
|
|
806
807
|
return;
|
|
807
808
|
let packageName;
|
|
808
809
|
let coreConstraint;
|
|
@@ -821,6 +822,20 @@ class PluginManager {
|
|
|
821
822
|
const coreVersion = require((0, node_path_1.resolve)(__dirname, "../../package.json")).version;
|
|
822
823
|
const error = coreConstraint && !(0, version_manager_1.satisfiesVersion)(coreVersion, coreConstraint) ? `核心版本 ${coreVersion} 不满足插件要求 ${coreConstraint}` : undefined;
|
|
823
824
|
const record = { id: manifest.id, version: manifest.version, type: manifest.type ?? "other", displayName: manifest.displayName ?? manifest.id, status: error ? "failed" : "discovered", capabilities: manifest.capabilities, manifest, path: directory, ...(packageName ? { packageName } : {}), ...(coreConstraint ? { coreConstraint } : {}), ...(error ? { error } : {}) };
|
|
825
|
+
if (existing) {
|
|
826
|
+
const versionOrder = (0, version_manager_1.compareVersions)(record.version, existing.record.version);
|
|
827
|
+
const candidateManaged = (0, node_path_1.resolve)(directory).startsWith(`${(0, node_path_1.resolve)(this.options.stateDir, "npm")}${node_path_1.sep}`);
|
|
828
|
+
const existingManaged = (0, node_path_1.resolve)(existing.record.path).startsWith(`${(0, node_path_1.resolve)(this.options.stateDir, "npm")}${node_path_1.sep}`);
|
|
829
|
+
// 新版本优先;同版本保留托管包,避免核心内置旧副本遮蔽显式安装包。
|
|
830
|
+
const existingFailed = existing.record.status === "failed";
|
|
831
|
+
const candidateFailed = record.status === "failed";
|
|
832
|
+
const keepExisting = candidateFailed !== existingFailed ? candidateFailed : versionOrder < 0 || (versionOrder === 0 && (!candidateManaged || existingManaged));
|
|
833
|
+
if (keepExisting) {
|
|
834
|
+
this.log("info", "忽略插件候选,保留当前可用版本", { id: manifest.id, ignoredPath: directory, ignoredVersion: record.version, selectedVersion: existing.record.version, reason: record.error }, "插件发现");
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
this.log("info", "插件候选已替换", { id: manifest.id, ignoredPath: existing.record.path, ignoredVersion: existing.record.version, selectedVersion: record.version }, "插件发现");
|
|
838
|
+
}
|
|
824
839
|
this.applyPluginTrust(record);
|
|
825
840
|
this.plugins.set(manifest.id, { record });
|
|
826
841
|
if (packageName && /^agent-bridge-[a-z0-9-]+$/.test(packageName)) {
|
|
@@ -12,7 +12,7 @@ const agent_1 = require("../../agent");
|
|
|
12
12
|
const messages_1 = require("./messages");
|
|
13
13
|
const attachments_1 = require("./attachments");
|
|
14
14
|
const manifest = {
|
|
15
|
-
id: "aun", version: "1.0.
|
|
15
|
+
id: "aun", version: "1.0.34", type: "channel", displayName: "AUN 通道",
|
|
16
16
|
entry: "dist/src-ts/plugins/aun/index.js", apiVersion: "1", process: "in-process",
|
|
17
17
|
capabilities: ["channel", "channel.identity.create", "channel.receive", "channel.send", "health", "cli"],
|
|
18
18
|
configSchema: "config.schema.json",
|
|
@@ -86,7 +86,7 @@ class AunPlugin {
|
|
|
86
86
|
const known = new Set([...this.clients.keys(), ...this.listStoreAids()]);
|
|
87
87
|
return {
|
|
88
88
|
evolcore: [...known].filter((aid) => !own.has(aid)).sort(),
|
|
89
|
-
muAgentBridge: [...
|
|
89
|
+
muAgentBridge: [...known].filter((aid) => own.has(aid)).sort(),
|
|
90
90
|
localMuAgentBridge: [...own].filter((aid) => !known.has(aid)).sort(),
|
|
91
91
|
};
|
|
92
92
|
},
|
|
@@ -125,42 +125,29 @@ class AunPlugin {
|
|
|
125
125
|
}
|
|
126
126
|
},
|
|
127
127
|
bind: async (pluginId, aid, _port) => {
|
|
128
|
+
if (!aid || !aidPattern.test(aid))
|
|
129
|
+
throw new Error("AID 格式无效,必须指定要关联的 AID");
|
|
128
130
|
if (pluginId === "aun" || pluginId === "abweb")
|
|
129
131
|
throw new Error("只能关联 Agent 插件");
|
|
132
|
+
if (this.context?.pluginStatus(pluginId) !== "ready")
|
|
133
|
+
throw new Error("目标 Agent 插件未启动");
|
|
130
134
|
const previous = this.bindings;
|
|
131
135
|
const previousTimes = this.bindingTimes;
|
|
132
|
-
|
|
133
|
-
|
|
136
|
+
const connection = this.clients.get(aid);
|
|
137
|
+
const hadIdentity = this.identities.has(aid);
|
|
138
|
+
const hadSubscription = Boolean(connection?.subscription);
|
|
134
139
|
try {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
}
|
|
147
|
-
else {
|
|
148
|
-
// 本地身份由插件直接接管,不再通过 ec 配置路由。
|
|
149
|
-
const loaded = this.store.load(aid);
|
|
150
|
-
if (!loaded.ok || !loaded.data)
|
|
151
|
-
throw new Error("AID 不在本地密钥库中,无法关联");
|
|
152
|
-
this.identities.add(aid);
|
|
153
|
-
if (!this.clients.has(aid))
|
|
154
|
-
await this.connectAid(aid);
|
|
155
|
-
if (!this.clients.has(aid))
|
|
156
|
-
throw new Error("AID 当前未连接");
|
|
157
|
-
this.bindings[aid] = pluginId;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
else
|
|
161
|
-
throw new Error("必须指定要关联的 AID");
|
|
162
|
-
if (aid)
|
|
163
|
-
this.bindingTimes[aid] = new Date().toISOString();
|
|
140
|
+
const loaded = this.store.load(aid);
|
|
141
|
+
if (!loaded.ok || !loaded.data)
|
|
142
|
+
throw new Error("AID 不在本地密钥库中,无法关联");
|
|
143
|
+
this.identities.add(aid);
|
|
144
|
+
if (!connection)
|
|
145
|
+
await this.connectAid(aid);
|
|
146
|
+
if (!this.clients.has(aid))
|
|
147
|
+
throw new Error("AID 当前未连接");
|
|
148
|
+
this.bindings = { ...previous, [aid]: pluginId };
|
|
149
|
+
this.bindingTimes = { ...previousTimes, [aid]: new Date().toISOString() };
|
|
150
|
+
this.subscribeAid(aid);
|
|
164
151
|
await this.persist();
|
|
165
152
|
}
|
|
166
153
|
catch (error) {
|
|
@@ -168,6 +155,29 @@ class AunPlugin {
|
|
|
168
155
|
this.bindingTimes = previousTimes;
|
|
169
156
|
this.config.bindings = previous;
|
|
170
157
|
this.config.binding_times = previousTimes;
|
|
158
|
+
if (!hadIdentity)
|
|
159
|
+
this.identities.delete(aid);
|
|
160
|
+
this.config.identities = [...this.identities].sort();
|
|
161
|
+
if (!hadSubscription) {
|
|
162
|
+
this.clients.get(aid)?.subscription?.unsubscribe();
|
|
163
|
+
if (connection)
|
|
164
|
+
connection.subscription = undefined;
|
|
165
|
+
}
|
|
166
|
+
if (!connection) {
|
|
167
|
+
try {
|
|
168
|
+
await this.clients.get(aid)?.client.close();
|
|
169
|
+
}
|
|
170
|
+
catch (closeError) {
|
|
171
|
+
this.context?.logger.warn("AUN 关联回滚关闭连接失败", { aid, error: safeError(closeError) });
|
|
172
|
+
}
|
|
173
|
+
finally {
|
|
174
|
+
this.clients.delete(aid);
|
|
175
|
+
if (!this.clients.size) {
|
|
176
|
+
this.status = "degraded";
|
|
177
|
+
this.healthMessage = "暂无已连接 AID";
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
171
181
|
this.context?.logger.error("AUN 身份关联失败", { aid, pluginId, error: safeError(error) });
|
|
172
182
|
throw error;
|
|
173
183
|
}
|
|
@@ -178,6 +188,8 @@ class AunPlugin {
|
|
|
178
188
|
throw new Error("AID 格式无效");
|
|
179
189
|
if (this.bindings[aid] !== pluginId)
|
|
180
190
|
throw new Error("指定 AID 当前未关联到该 Agent");
|
|
191
|
+
if ([...this.pendingMessages.keys()].some(key => JSON.parse(key)[0] === aid))
|
|
192
|
+
throw new Error("该 AID 正在处理消息,请等待任务完成后再解绑");
|
|
181
193
|
const previous = this.bindings;
|
|
182
194
|
const previousTimes = this.bindingTimes;
|
|
183
195
|
this.bindings = { ...previous };
|
|
@@ -194,6 +206,21 @@ class AunPlugin {
|
|
|
194
206
|
this.config.binding_times = previousTimes;
|
|
195
207
|
throw error;
|
|
196
208
|
}
|
|
209
|
+
const connection = this.clients.get(aid);
|
|
210
|
+
connection?.subscription?.unsubscribe();
|
|
211
|
+
try {
|
|
212
|
+
await connection?.client.close();
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
this.context?.logger.warn("AUN 解绑已完成,关闭连接失败", { aid, pluginId, error: safeError(error) });
|
|
216
|
+
}
|
|
217
|
+
finally {
|
|
218
|
+
this.clients.delete(aid);
|
|
219
|
+
if (!this.clients.size) {
|
|
220
|
+
this.status = "degraded";
|
|
221
|
+
this.healthMessage = "暂无已连接 AID";
|
|
222
|
+
}
|
|
223
|
+
}
|
|
197
224
|
this.context?.logger.info("AUN 单个身份关联已解除", { aid, pluginId });
|
|
198
225
|
},
|
|
199
226
|
};
|
|
@@ -207,6 +234,7 @@ class AunPlugin {
|
|
|
207
234
|
async activate(context) {
|
|
208
235
|
this.context = context;
|
|
209
236
|
this.config = { ...context.config };
|
|
237
|
+
this.identities.clear();
|
|
210
238
|
this.legacyCreatedAids.clear();
|
|
211
239
|
this.createdAids.clear();
|
|
212
240
|
try {
|
|
@@ -266,15 +294,25 @@ class AunPlugin {
|
|
|
266
294
|
if (this.aid)
|
|
267
295
|
this.identities.add(this.aid);
|
|
268
296
|
this.acceptingMessages = true;
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
297
|
+
// 只订阅明确关联 Agent 的身份,避免接管 EvolCore 或其它 AID 的消息。
|
|
298
|
+
for (const aid of Object.keys(this.bindings)) {
|
|
299
|
+
const target = this.bindings[aid];
|
|
300
|
+
if (!target || !context.pluginStatus(target)) {
|
|
301
|
+
context.logger.warn("AUN 跳过目标插件不存在的旧绑定,未连接身份", { aid, agentPlugin: target });
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
if (context.pluginStatus(target) !== "ready")
|
|
305
|
+
context.logger.warn("AUN 恢复已有显式绑定,目标插件尚未就绪;关联保持不变", { aid, agentPlugin: target, status: context.pluginStatus(target) });
|
|
306
|
+
if (aidPattern.test(aid))
|
|
275
307
|
await this.connectAid(aid);
|
|
276
308
|
}
|
|
277
309
|
}
|
|
310
|
+
subscribeAid(aid) {
|
|
311
|
+
const connection = this.clients.get(aid);
|
|
312
|
+
if (!connection || connection.subscription || !this.bindings[aid])
|
|
313
|
+
return;
|
|
314
|
+
connection.subscription = connection.client.on("message.received", (payload) => this.onMessage(payload, aid));
|
|
315
|
+
}
|
|
278
316
|
async connectAid(aid) {
|
|
279
317
|
try {
|
|
280
318
|
await this.prepareTrustRoots(aid);
|
|
@@ -282,7 +320,7 @@ class AunPlugin {
|
|
|
282
320
|
if (!loaded.ok || !loaded.data)
|
|
283
321
|
throw new Error(loaded.error?.message ?? "本地密钥库中不存在该 AID");
|
|
284
322
|
const client = new this.sdk.AUNClient(loaded.data.aid);
|
|
285
|
-
const subscription = this.
|
|
323
|
+
const subscription = this.bindings[aid]
|
|
286
324
|
? client.on("message.received", (payload) => this.onMessage(payload, aid))
|
|
287
325
|
: undefined;
|
|
288
326
|
try {
|
|
@@ -86,9 +86,14 @@ async function runAunCommand(routes, response, argv) {
|
|
|
86
86
|
close() { }
|
|
87
87
|
},
|
|
88
88
|
AUNClient: class {
|
|
89
|
+
aid;
|
|
89
90
|
async connect() { }
|
|
90
91
|
async close() { }
|
|
91
|
-
|
|
92
|
+
constructor(aid) {
|
|
93
|
+
this.aid = aid;
|
|
94
|
+
}
|
|
95
|
+
on(_event, handler) { if (this.aid === "selected.agentid.cn")
|
|
96
|
+
receive = handler; return { unsubscribe() { } }; }
|
|
92
97
|
async call(_method, params) { sent.push(params); }
|
|
93
98
|
},
|
|
94
99
|
};
|
|
@@ -142,7 +147,10 @@ async function withAunAttachmentPlugin(run, publish, config = { allow_from: "pee
|
|
|
142
147
|
const plugin = new aun_1.AunPlugin({ sdk, ingress: { publish } });
|
|
143
148
|
try {
|
|
144
149
|
await plugin.activate({ dataDir: root, config, logger, callPlugin: async () => ({ text: "" }), pluginStatus: () => "ready", http: { register() { }, unregister() { } } });
|
|
145
|
-
|
|
150
|
+
if (!Object.keys(config.bindings ?? {}).length)
|
|
151
|
+
strict_1.default.equal(handler, undefined);
|
|
152
|
+
await run(async (payload) => { if (handler)
|
|
153
|
+
await handler(payload); }, sent, storage, logs, plugin);
|
|
146
154
|
}
|
|
147
155
|
finally {
|
|
148
156
|
await plugin.deactivate("stop");
|
|
@@ -361,7 +369,7 @@ async function withAunAttachmentPlugin(run, publish, config = { allow_from: "pee
|
|
|
361
369
|
await plugin.activate({ dataDir: root, config: { identities: [aids[0]] }, logger: { info() { }, warn() { }, error() { } }, callPlugin: async () => ({ text: "" }), pluginStatus: () => "ready", http: {
|
|
362
370
|
register(route) { routes.set(`${route.method} ${route.path}`, route.handler); return () => undefined; }, unregister() { },
|
|
363
371
|
} });
|
|
364
|
-
strict_1.default.deepEqual(connects,
|
|
372
|
+
strict_1.default.deepEqual(connects, []);
|
|
365
373
|
for (const aid of aids) {
|
|
366
374
|
const result = await runAunCommand(routes, commandResponse(), ["create", aid, "--owner", "owner.agentid.cn"]);
|
|
367
375
|
strict_1.default.equal(result.statusCode, 200);
|
|
@@ -937,7 +945,7 @@ async function withAunAttachmentPlugin(run, publish, config = { allow_from: "pee
|
|
|
937
945
|
const plugin = new aun_1.AunPlugin({ sdk, ingress: { publish: async () => undefined } });
|
|
938
946
|
try {
|
|
939
947
|
process.env.EVOLCORE_HOME = aunPath;
|
|
940
|
-
await plugin.activate({ dataDir, config: {}, logger: { info() { }, warn() { }, error() { } }, callPlugin: async () => ({ text: "" }), pluginStatus: () => "ready", http: { register() { }, unregister() { } } });
|
|
948
|
+
await plugin.activate({ dataDir, config: { bindings: { "existing.agentid.cn": "workbuddy" } }, logger: { info() { }, warn() { }, error() { } }, callPlugin: async () => ({ text: "" }), pluginStatus: () => "ready", http: { register() { }, unregister() { } } });
|
|
941
949
|
strict_1.default.deepEqual(connects, ["existing.agentid.cn"]);
|
|
942
950
|
strict_1.default.deepEqual(subscriptions, ["existing.agentid.cn"]);
|
|
943
951
|
strict_1.default.deepEqual(await plugin.identity.list(), { evolcore: ["existing.agentid.cn"], muAgentBridge: [], localMuAgentBridge: [] });
|
|
@@ -991,7 +999,41 @@ async function withAunAttachmentPlugin(run, publish, config = { allow_from: "pee
|
|
|
991
999
|
await (0, promises_1.rm)(root, { recursive: true, force: true });
|
|
992
1000
|
}
|
|
993
1001
|
});
|
|
994
|
-
(0, node_test_1.default)("
|
|
1002
|
+
(0, node_test_1.default)("AUN 启动只连接显式绑定身份,旧 identities 不接管共享密钥库", async () => {
|
|
1003
|
+
const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "aun-explicit-only-"));
|
|
1004
|
+
const connected = [];
|
|
1005
|
+
const closed = [];
|
|
1006
|
+
const sdk = {
|
|
1007
|
+
AIDStore: class {
|
|
1008
|
+
list() { return { ok: true, data: { identities: ["selected.agentid.cn", "external.agentid.cn"].map(aid => ({ aid })) } }; }
|
|
1009
|
+
load(aid) { return { ok: true, data: { aid } }; }
|
|
1010
|
+
close() { }
|
|
1011
|
+
},
|
|
1012
|
+
AUNClient: class {
|
|
1013
|
+
aid;
|
|
1014
|
+
constructor(aid) {
|
|
1015
|
+
this.aid = aid;
|
|
1016
|
+
}
|
|
1017
|
+
on() { return { unsubscribe() { } }; }
|
|
1018
|
+
async connect() { connected.push(this.aid); }
|
|
1019
|
+
async close() { closed.push(this.aid); }
|
|
1020
|
+
},
|
|
1021
|
+
};
|
|
1022
|
+
const plugin = new aun_1.AunPlugin({ sdk, ingress: { publish: async () => undefined } });
|
|
1023
|
+
try {
|
|
1024
|
+
await plugin.activate({ dataDir: root, config: { aid: "external.agentid.cn", identities: ["external.agentid.cn"], bindings: { "selected.agentid.cn": "workbuddy" } }, logger: { info() { }, warn() { }, error() { } }, callPlugin: async () => ({ text: "" }), pluginStatus: () => "ready", http: { register() { }, unregister() { } } });
|
|
1025
|
+
strict_1.default.deepEqual(connected, ["selected.agentid.cn"]);
|
|
1026
|
+
strict_1.default.ok((await plugin.identity.list()).evolcore.includes("external.agentid.cn"));
|
|
1027
|
+
await plugin.identity.unbind("workbuddy", "selected.agentid.cn", 26107);
|
|
1028
|
+
strict_1.default.deepEqual(closed, ["selected.agentid.cn"]);
|
|
1029
|
+
strict_1.default.deepEqual((await plugin.health()).details?.bindings, {});
|
|
1030
|
+
}
|
|
1031
|
+
finally {
|
|
1032
|
+
await plugin.deactivate("stop");
|
|
1033
|
+
await (0, promises_1.rm)(root, { recursive: true, force: true });
|
|
1034
|
+
}
|
|
1035
|
+
});
|
|
1036
|
+
(0, node_test_1.default)("共享密钥库中的 AID 仅列出并保留 EvolCore 来源", async () => {
|
|
995
1037
|
const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "aun-origin-"));
|
|
996
1038
|
const dataDir = (0, node_path_1.join)(root, "aun");
|
|
997
1039
|
await (0, promises_1.mkdir)(dataDir);
|
|
@@ -1046,14 +1088,14 @@ async function withAunAttachmentPlugin(run, publish, config = { allow_from: "pee
|
|
|
1046
1088
|
try {
|
|
1047
1089
|
await plugin.activate({ dataDir, config: { created_aids: ["created.agentid.cn"] }, logger: { info() { }, warn() { }, error() { } }, callPlugin: async () => ({ text: "" }), pluginStatus: () => "ready", http: { register() { }, unregister() { } } });
|
|
1048
1090
|
strict_1.default.deepEqual(await plugin.identity.list(), { evolcore: ["shared.agentid.cn"], muAgentBridge: ["created.agentid.cn", "legacy.agentid.cn"], localMuAgentBridge: [] });
|
|
1049
|
-
strict_1.default.deepEqual(subscriptions,
|
|
1091
|
+
strict_1.default.deepEqual(subscriptions, []);
|
|
1050
1092
|
await plugin.deactivate("stop");
|
|
1051
1093
|
}
|
|
1052
1094
|
finally {
|
|
1053
1095
|
await (0, promises_1.rm)(root, { recursive: true, force: true });
|
|
1054
1096
|
}
|
|
1055
1097
|
});
|
|
1056
|
-
(0, node_test_1.default)("
|
|
1098
|
+
(0, node_test_1.default)("默认共享 AUN 密钥库可列出全部 AID 但不自动连接", async () => {
|
|
1057
1099
|
const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "aun-keystore-"));
|
|
1058
1100
|
const dataDir = (0, node_path_1.join)(root, "aun");
|
|
1059
1101
|
await (0, promises_1.mkdir)(dataDir);
|
|
@@ -1078,9 +1120,9 @@ async function withAunAttachmentPlugin(run, publish, config = { allow_from: "pee
|
|
|
1078
1120
|
try {
|
|
1079
1121
|
await plugin.activate({ dataDir, config: {}, logger: { info() { }, warn() { }, error() { } }, callPlugin: async () => ({ text: "" }), pluginStatus: () => "ready", http: { register() { }, unregister() { } } });
|
|
1080
1122
|
strict_1.default.equal(plugin.path(), root);
|
|
1081
|
-
//
|
|
1123
|
+
// 共享密钥库中的身份可见,但未手动绑定时不建立连接。
|
|
1082
1124
|
strict_1.default.deepEqual((await plugin.identity.list()).evolcore, ["devwin.agentid.cn", "qwenwork.agentid.cn"]);
|
|
1083
|
-
strict_1.default.equal(clients.length,
|
|
1125
|
+
strict_1.default.equal(clients.length, 0);
|
|
1084
1126
|
}
|
|
1085
1127
|
finally {
|
|
1086
1128
|
delete process.env.AUN_HOME;
|
|
@@ -1137,18 +1179,18 @@ async function withAunAttachmentPlugin(run, publish, config = { allow_from: "pee
|
|
|
1137
1179
|
const second = (async function* () { yield Buffer.from('{"aid":"second.agentid.pub","owner_aid":"owner.agentid.pub"}'); })();
|
|
1138
1180
|
await route.handler(second, response);
|
|
1139
1181
|
strict_1.default.equal(response.statusCode, 201);
|
|
1140
|
-
// bridge、second
|
|
1141
|
-
strict_1.default.equal(clients.length,
|
|
1182
|
+
// 只有显式创建的 bridge、second 建立连接,尚未订阅消息。
|
|
1183
|
+
strict_1.default.equal(clients.length, 2);
|
|
1142
1184
|
// 关联不再调用 ec:AID 在本地密钥库即可直接绑定
|
|
1143
1185
|
await plugin.identity.bind("dsh", "bridge.agentid.pub", 26107);
|
|
1144
1186
|
const boundAt = JSON.parse(await (0, promises_1.readFile)((0, node_path_1.join)(root, "config.json"), "utf8")).binding_times["bridge.agentid.pub"];
|
|
1145
1187
|
strict_1.default.ok(Number.isFinite(Date.parse(boundAt)));
|
|
1146
|
-
//
|
|
1188
|
+
// 首次绑定共享身份时才建立连接并订阅消息。
|
|
1147
1189
|
await plugin.identity.bind("doubao", "offline-store.agentid.cn", 26107);
|
|
1148
1190
|
strict_1.default.ok(JSON.parse(await (0, promises_1.readFile)((0, node_path_1.join)(root, "config.json"), "utf8")).bindings["offline-store.agentid.cn"]);
|
|
1149
1191
|
strict_1.default.equal(clients.length, 3);
|
|
1150
|
-
//
|
|
1151
|
-
const bridgeClient = clients[
|
|
1192
|
+
// bridge 是本次显式创建的第一个客户端。
|
|
1193
|
+
const bridgeClient = clients[0];
|
|
1152
1194
|
await bridgeClient.emit({ from: "peer.agentid.pub", message_id: "m1", payload: { text: "你好" } });
|
|
1153
1195
|
strict_1.default.equal(received[0].agentPlugin, "dsh");
|
|
1154
1196
|
await received[0].reply("收到");
|
|
@@ -1162,11 +1204,11 @@ async function withAunAttachmentPlugin(run, publish, config = { allow_from: "pee
|
|
|
1162
1204
|
strict_1.default.equal(calls.filter((call) => call === "client.close").length, 3);
|
|
1163
1205
|
strict_1.default.equal(calls.at(-1), "store.close");
|
|
1164
1206
|
await plugin.activate({ dataDir: root, config: JSON.parse(await (0, promises_1.readFile)((0, node_path_1.join)(root, "config.json"), "utf8")), logger: { info() { }, warn() { }, error() { } }, callPlugin: async () => ({ text: "" }), pluginStatus: () => "ready", http: { register() { }, unregister() { } } });
|
|
1165
|
-
//
|
|
1166
|
-
strict_1.default.equal(clients.length,
|
|
1207
|
+
// 重启后只恢复两个显式绑定的身份。
|
|
1208
|
+
strict_1.default.equal(clients.length, 5);
|
|
1167
1209
|
const health = await plugin.health();
|
|
1168
|
-
//
|
|
1169
|
-
strict_1.default.deepEqual([...health.details?.aids].sort(), ["bridge.agentid.pub", "offline-store.agentid.cn"
|
|
1210
|
+
// 未绑定的 second 不恢复连接。
|
|
1211
|
+
strict_1.default.deepEqual([...health.details?.aids].sort(), ["bridge.agentid.pub", "offline-store.agentid.cn"]);
|
|
1170
1212
|
strict_1.default.deepEqual(health.details?.bindings, { "bridge.agentid.pub": "dsh", "offline-store.agentid.cn": "doubao" });
|
|
1171
1213
|
await routes.find((entry) => entry.path === "/api/plugins/aun/identities").handler({}, response);
|
|
1172
1214
|
strict_1.default.equal(response.body.bindingTimes["bridge.agentid.pub"], boundAt);
|
|
@@ -1306,7 +1348,7 @@ async function withAunAttachmentPlugin(run, publish, config = { allow_from: "pee
|
|
|
1306
1348
|
const plugin = new aun_1.AunPlugin({ sdk, ingress: { publish: async () => undefined } });
|
|
1307
1349
|
try {
|
|
1308
1350
|
globalThis.fetch = (async () => { fetchCalls++; throw new Error("不应从 Gateway 下载信任根"); });
|
|
1309
|
-
await plugin.activate({ dataDir, config: { aid: "previous.agentid.cn", identities: ["previous.agentid.cn"], aun_path: aunPath }, logger: { info() { }, warn() { }, error() { } }, callPlugin: async () => ({ text: "" }), pluginStatus: () => "ready", http: { register() { }, unregister() { } } });
|
|
1351
|
+
await plugin.activate({ dataDir, config: { aid: "previous.agentid.cn", identities: ["previous.agentid.cn"], bindings: { "previous.agentid.cn": "workbuddy" }, aun_path: aunPath }, logger: { info() { }, warn() { }, error() { } }, callPlugin: async () => ({ text: "" }), pluginStatus: () => "ready", http: { register() { }, unregister() { } } });
|
|
1310
1352
|
strict_1.default.equal(storeOptions.length, 2);
|
|
1311
1353
|
strict_1.default.equal(storeOptions[1].rootCaPath, (0, node_path_1.join)(dataDir, "CA", "root", "root.crt"));
|
|
1312
1354
|
strict_1.default.deepEqual(events, ["store.close", "client.connect"]);
|
|
@@ -1351,3 +1393,107 @@ async function withAunAttachmentPlugin(run, publish, config = { allow_from: "pee
|
|
|
1351
1393
|
}
|
|
1352
1394
|
}, async () => { throw new Error(`WORKBUDDY_ARTIFACT_READ_FAILED:读取文件 ${"目录/".repeat(120)};原因:ENOENT:产物文件已经删除;token=private-token`); });
|
|
1353
1395
|
});
|
|
1396
|
+
(0, node_test_1.default)("AUN 绑定写盘失败清理新连接,解绑关闭失败仍删除连接并更新健康状态", async () => {
|
|
1397
|
+
for (const existingConnection of [false, true]) {
|
|
1398
|
+
const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "aun-binding-cleanup-"));
|
|
1399
|
+
const aid = "cleanup.agentid.cn";
|
|
1400
|
+
let closed = 0;
|
|
1401
|
+
let subscriptions = 0;
|
|
1402
|
+
let closeFails = false;
|
|
1403
|
+
const warnings = [];
|
|
1404
|
+
const sdk = {
|
|
1405
|
+
AIDStore: class {
|
|
1406
|
+
list() { return { ok: true, data: { identities: [{ aid }] } }; }
|
|
1407
|
+
load() { return { ok: true, data: { aid } }; }
|
|
1408
|
+
close() { }
|
|
1409
|
+
},
|
|
1410
|
+
AUNClient: class {
|
|
1411
|
+
on() { subscriptions++; return { unsubscribe() { subscriptions--; } }; }
|
|
1412
|
+
async connect() { }
|
|
1413
|
+
async close() { closed++; if (closeFails)
|
|
1414
|
+
throw new Error("关闭失败"); }
|
|
1415
|
+
},
|
|
1416
|
+
};
|
|
1417
|
+
const plugin = new aun_1.AunPlugin({ sdk, ingress: { publish: async () => undefined } });
|
|
1418
|
+
try {
|
|
1419
|
+
await plugin.activate({ dataDir: root, config: {}, logger: { info() { }, warn(message, fields) { warnings.push(JSON.stringify([message, fields])); }, error() { } }, callPlugin: async () => ({ text: "" }), pluginStatus: () => "ready", http: { register() { }, unregister() { } } });
|
|
1420
|
+
if (existingConnection)
|
|
1421
|
+
await plugin.identity.create(aid, "owner.agentid.cn");
|
|
1422
|
+
await (0, promises_1.rm)((0, node_path_1.join)(root, "config.json"), { force: true });
|
|
1423
|
+
await (0, promises_1.mkdir)((0, node_path_1.join)(root, "config.json"));
|
|
1424
|
+
await strict_1.default.rejects(plugin.identity.bind("workbuddy", aid, 26107));
|
|
1425
|
+
strict_1.default.deepEqual((await plugin.health()).details?.bindings, {});
|
|
1426
|
+
strict_1.default.deepEqual((await plugin.health()).details?.aids, existingConnection ? [aid] : []);
|
|
1427
|
+
strict_1.default.equal(subscriptions, 0);
|
|
1428
|
+
strict_1.default.equal(closed, existingConnection ? 0 : 1);
|
|
1429
|
+
await (0, promises_1.rm)((0, node_path_1.join)(root, "config.json"), { recursive: true });
|
|
1430
|
+
await plugin.identity.bind("workbuddy", aid, 26107);
|
|
1431
|
+
strict_1.default.equal(subscriptions, 1);
|
|
1432
|
+
closeFails = true;
|
|
1433
|
+
await plugin.identity.unbind("workbuddy", aid, 26107);
|
|
1434
|
+
strict_1.default.match(warnings.join("\n"), /AUN.*关闭.*cleanup.agentid.cn.*关闭失败/);
|
|
1435
|
+
const health = await plugin.health();
|
|
1436
|
+
strict_1.default.deepEqual(health.details?.aids, []);
|
|
1437
|
+
strict_1.default.deepEqual(health.details?.bindings, {});
|
|
1438
|
+
strict_1.default.equal(health.details?.connected, false);
|
|
1439
|
+
strict_1.default.equal(health.message, "暂无已连接 AID");
|
|
1440
|
+
strict_1.default.equal(subscriptions, 0);
|
|
1441
|
+
}
|
|
1442
|
+
finally {
|
|
1443
|
+
await plugin.deactivate("stop");
|
|
1444
|
+
await (0, promises_1.rm)(root, { recursive: true, force: true });
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
});
|
|
1448
|
+
(0, node_test_1.default)("AUN 旧绑定目标不存在时保留配置并跳过连接,记录具体身份", async () => {
|
|
1449
|
+
const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "aun-missing-target-"));
|
|
1450
|
+
const logs = [];
|
|
1451
|
+
let connects = 0;
|
|
1452
|
+
const sdk = {
|
|
1453
|
+
AIDStore: class {
|
|
1454
|
+
list() { return { ok: true, data: { identities: [{ aid: "external.agentid.cn" }] } }; }
|
|
1455
|
+
close() { }
|
|
1456
|
+
},
|
|
1457
|
+
AUNClient: class {
|
|
1458
|
+
async connect() { connects++; }
|
|
1459
|
+
},
|
|
1460
|
+
};
|
|
1461
|
+
const plugin = new aun_1.AunPlugin({ sdk, ingress: { publish: async () => undefined } });
|
|
1462
|
+
try {
|
|
1463
|
+
await plugin.activate({ dataDir: root, config: { bindings: { "external.agentid.cn": "qwenwork" } }, logger: { info() { }, warn(message, fields) { logs.push(JSON.stringify([message, fields])); }, error() { } }, callPlugin: async () => ({ text: "" }), pluginStatus: () => undefined, http: { register() { }, unregister() { } } });
|
|
1464
|
+
strict_1.default.equal(connects, 0);
|
|
1465
|
+
strict_1.default.deepEqual((await plugin.health()).details?.bindings, { "external.agentid.cn": "qwenwork" });
|
|
1466
|
+
strict_1.default.match(logs.join("\n"), /external.agentid.cn.*qwenwork/);
|
|
1467
|
+
}
|
|
1468
|
+
finally {
|
|
1469
|
+
await plugin.deactivate("stop");
|
|
1470
|
+
await (0, promises_1.rm)(root, { recursive: true, force: true });
|
|
1471
|
+
}
|
|
1472
|
+
});
|
|
1473
|
+
(0, node_test_1.default)("AUN 有在途消息时拒绝解绑,最终回复完成后才能解除关联", async () => {
|
|
1474
|
+
let started;
|
|
1475
|
+
let release;
|
|
1476
|
+
const taskStarted = new Promise(resolve => { started = resolve; });
|
|
1477
|
+
const taskGate = new Promise(resolve => { release = resolve; });
|
|
1478
|
+
await withAunAttachmentPlugin(async (receive, sent, _storage, _logs, plugin) => {
|
|
1479
|
+
const task = receive({ from: "peer.agentid.cn", message_id: "unbind-busy", payload: { text: "执行任务" } });
|
|
1480
|
+
try {
|
|
1481
|
+
await taskStarted;
|
|
1482
|
+
await strict_1.default.rejects(plugin.identity.unbind("qwenwork", "local.agentid.cn", 26107), /正在处理消息.*完成后.*解绑/);
|
|
1483
|
+
strict_1.default.deepEqual((await plugin.health()).details?.bindings, { "local.agentid.cn": "qwenwork" });
|
|
1484
|
+
strict_1.default.equal((await plugin.health()).details?.connected, true);
|
|
1485
|
+
}
|
|
1486
|
+
finally {
|
|
1487
|
+
release();
|
|
1488
|
+
await task;
|
|
1489
|
+
}
|
|
1490
|
+
strict_1.default.equal(sent.at(-1).payload.text, "任务最终回复");
|
|
1491
|
+
await plugin.identity.unbind("qwenwork", "local.agentid.cn", 26107);
|
|
1492
|
+
strict_1.default.deepEqual((await plugin.health()).details?.bindings, {});
|
|
1493
|
+
strict_1.default.equal((await plugin.health()).details?.connected, false);
|
|
1494
|
+
}, async (message) => {
|
|
1495
|
+
started();
|
|
1496
|
+
await taskGate;
|
|
1497
|
+
await message.reply("任务最终回复");
|
|
1498
|
+
});
|
|
1499
|
+
});
|
|
@@ -67,6 +67,8 @@ const node_util_1 = require("node:util");
|
|
|
67
67
|
strict_1.default.match(setup, /TopMost = \$true/);
|
|
68
68
|
strict_1.default.match(setup, /BringToFront\(\)/);
|
|
69
69
|
strict_1.default.match(setup, /Invoke-AunCommand @\('list'\)/);
|
|
70
|
+
strict_1.default.match(setup, /自动解除未安装千问办公的残留绑定/);
|
|
71
|
+
strict_1.default.match(setup, /Invoke-AunCommand @\('unbind', \[string\]\$binding\.Name\)/);
|
|
70
72
|
for (const id of ["qwenwork", "doubao", "workbuddy", "dsh"])
|
|
71
73
|
strict_1.default.ok(setup.includes(id), "缺少 Agent " + id);
|
|
72
74
|
strict_1.default.match(setup, /choices\[0\]\.message\.content/);
|
|
@@ -45,6 +45,35 @@ const node_http_1 = require("node:http");
|
|
|
45
45
|
const plugin_manager_1 = require("../src-ts/plugin-manager");
|
|
46
46
|
const version_manager_1 = require("../src-ts/version-manager");
|
|
47
47
|
const channel_sessions_1 = require("../src-ts/channel-sessions");
|
|
48
|
+
(0, node_test_1.default)("启动时内置新版替代状态目录旧插件,保留较新及同版托管插件", async () => {
|
|
49
|
+
for (const [bundled, managed, expectedManaged] of [["1.0.31", "1.0.29", false], ["1.0.31", "1.0.32", true], ["1.0.31", "1.0.31", true], ["1.0.10", "1.0.9", false], ["1.0.9", "1.0.10", true]]) {
|
|
50
|
+
const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "mab-plugin-precedence-"));
|
|
51
|
+
const bundledDir = (0, node_path_1.join)(root, "plugins", "aun");
|
|
52
|
+
const managedDir = (0, node_path_1.join)(root, "state", "npm", "agent-bridge-aun", "node_modules", "agent-bridge-aun");
|
|
53
|
+
try {
|
|
54
|
+
for (const [dir, version] of [[bundledDir, bundled], [managedDir, managed]]) {
|
|
55
|
+
await (0, promises_1.mkdir)(dir, { recursive: true });
|
|
56
|
+
await (0, promises_1.writeFile)((0, node_path_1.join)(dir, "package.json"), JSON.stringify({ name: "agent-bridge-aun", version, agentBridge: { plugin: "./plugin.json" } }));
|
|
57
|
+
await (0, promises_1.writeFile)((0, node_path_1.join)(dir, "plugin.json"), JSON.stringify({ id: "aun", version, entry: "index.js", apiVersion: "1", capabilities: [] }));
|
|
58
|
+
await (0, promises_1.writeFile)((0, node_path_1.join)(dir, "index.js"), "module.exports={activate:async()=>{},deactivate:async()=>{},health:async()=>({status:'ready'})};");
|
|
59
|
+
}
|
|
60
|
+
const manager = new plugin_manager_1.PluginManager({ pluginsDir: (0, node_path_1.join)(root, "plugins"), stateDir: (0, node_path_1.join)(root, "state"), publisherResolver: async () => "m2lihui" });
|
|
61
|
+
await manager.discover();
|
|
62
|
+
strict_1.default.equal(manager.list()[0].path, expectedManaged ? managedDir : bundledDir);
|
|
63
|
+
strict_1.default.equal(manager.list()[0].version, expectedManaged ? managed : bundled);
|
|
64
|
+
strict_1.default.equal(manager.packageInfo("aun").managed, expectedManaged);
|
|
65
|
+
await manager.start("aun");
|
|
66
|
+
const instance = manager.get("aun").instance;
|
|
67
|
+
await manager.discover();
|
|
68
|
+
strict_1.default.equal(manager.get("aun").instance, instance);
|
|
69
|
+
strict_1.default.equal(manager.get("aun").record.status, "ready");
|
|
70
|
+
await manager.stop("aun", "stop");
|
|
71
|
+
}
|
|
72
|
+
finally {
|
|
73
|
+
await (0, promises_1.rm)(root, { recursive: true, force: true });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
});
|
|
48
77
|
(0, node_test_1.default)("后台桌面重启先停止插件再启动,拒绝无关插件", { skip: process.platform === "win32" }, async () => {
|
|
49
78
|
const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "mab-desktop-restart-"));
|
|
50
79
|
const manager = new plugin_manager_1.PluginManager({ pluginsDir: (0, node_path_1.join)(root, "plugins"), stateDir: (0, node_path_1.join)(root, "state") });
|
|
@@ -1157,3 +1186,25 @@ const channel_sessions_1 = require("../src-ts/channel-sessions");
|
|
|
1157
1186
|
await (0, promises_1.rm)(root, { recursive: true, force: true });
|
|
1158
1187
|
}
|
|
1159
1188
|
});
|
|
1189
|
+
(0, node_test_1.default)("不兼容的新托管候选不遮蔽可用内置插件", async () => {
|
|
1190
|
+
for (const managedFirst of [true, false]) {
|
|
1191
|
+
const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "mab-compatible-choice-"));
|
|
1192
|
+
const manager = new plugin_manager_1.PluginManager({ pluginsDir: (0, node_path_1.join)(root, "plugins"), stateDir: (0, node_path_1.join)(root, "state"), publisherResolver: async () => "m2lihui" });
|
|
1193
|
+
try {
|
|
1194
|
+
const bundled = (0, node_path_1.join)(root, "plugins", "demo");
|
|
1195
|
+
const managed = (0, node_path_1.join)(root, "state", "npm", "agent-bridge-demo", "node_modules", "agent-bridge-demo");
|
|
1196
|
+
for (const [dir, version, requires] of [[bundled, "1.0.0", "*"], [managed, "2.0.0", ">=999.0.0"]]) {
|
|
1197
|
+
await (0, promises_1.mkdir)(dir, { recursive: true });
|
|
1198
|
+
await (0, promises_1.writeFile)((0, node_path_1.join)(dir, "plugin.json"), JSON.stringify({ id: "demo", version, entry: "index.js", apiVersion: "1", capabilities: [] }));
|
|
1199
|
+
await (0, promises_1.writeFile)((0, node_path_1.join)(dir, "package.json"), JSON.stringify({ name: "agent-bridge-demo", agentBridge: { requires: { "mu-agent-bridge": requires } } }));
|
|
1200
|
+
}
|
|
1201
|
+
for (const dir of managedFirst ? [managed, bundled] : [bundled, managed])
|
|
1202
|
+
await manager.discoverDirectory(dir, "demo");
|
|
1203
|
+
strict_1.default.equal(manager.get("demo").record.path, bundled);
|
|
1204
|
+
strict_1.default.equal(manager.get("demo").record.status, "discovered");
|
|
1205
|
+
}
|
|
1206
|
+
finally {
|
|
1207
|
+
await (0, promises_1.rm)(root, { recursive: true, force: true });
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
});
|
package/package.json
CHANGED
|
@@ -12,7 +12,7 @@ const agent_1 = require("../../agent");
|
|
|
12
12
|
const messages_1 = require("./messages");
|
|
13
13
|
const attachments_1 = require("./attachments");
|
|
14
14
|
const manifest = {
|
|
15
|
-
id: "aun", version: "1.0.
|
|
15
|
+
id: "aun", version: "1.0.34", type: "channel", displayName: "AUN 通道",
|
|
16
16
|
entry: "dist/src-ts/plugins/aun/index.js", apiVersion: "1", process: "in-process",
|
|
17
17
|
capabilities: ["channel", "channel.identity.create", "channel.receive", "channel.send", "health", "cli"],
|
|
18
18
|
configSchema: "config.schema.json",
|
|
@@ -86,7 +86,7 @@ class AunPlugin {
|
|
|
86
86
|
const known = new Set([...this.clients.keys(), ...this.listStoreAids()]);
|
|
87
87
|
return {
|
|
88
88
|
evolcore: [...known].filter((aid) => !own.has(aid)).sort(),
|
|
89
|
-
muAgentBridge: [...
|
|
89
|
+
muAgentBridge: [...known].filter((aid) => own.has(aid)).sort(),
|
|
90
90
|
localMuAgentBridge: [...own].filter((aid) => !known.has(aid)).sort(),
|
|
91
91
|
};
|
|
92
92
|
},
|
|
@@ -125,42 +125,29 @@ class AunPlugin {
|
|
|
125
125
|
}
|
|
126
126
|
},
|
|
127
127
|
bind: async (pluginId, aid, _port) => {
|
|
128
|
+
if (!aid || !aidPattern.test(aid))
|
|
129
|
+
throw new Error("AID 格式无效,必须指定要关联的 AID");
|
|
128
130
|
if (pluginId === "aun" || pluginId === "abweb")
|
|
129
131
|
throw new Error("只能关联 Agent 插件");
|
|
132
|
+
if (this.context?.pluginStatus(pluginId) !== "ready")
|
|
133
|
+
throw new Error("目标 Agent 插件未启动");
|
|
130
134
|
const previous = this.bindings;
|
|
131
135
|
const previousTimes = this.bindingTimes;
|
|
132
|
-
|
|
133
|
-
|
|
136
|
+
const connection = this.clients.get(aid);
|
|
137
|
+
const hadIdentity = this.identities.has(aid);
|
|
138
|
+
const hadSubscription = Boolean(connection?.subscription);
|
|
134
139
|
try {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
}
|
|
147
|
-
else {
|
|
148
|
-
// 本地身份由插件直接接管,不再通过 ec 配置路由。
|
|
149
|
-
const loaded = this.store.load(aid);
|
|
150
|
-
if (!loaded.ok || !loaded.data)
|
|
151
|
-
throw new Error("AID 不在本地密钥库中,无法关联");
|
|
152
|
-
this.identities.add(aid);
|
|
153
|
-
if (!this.clients.has(aid))
|
|
154
|
-
await this.connectAid(aid);
|
|
155
|
-
if (!this.clients.has(aid))
|
|
156
|
-
throw new Error("AID 当前未连接");
|
|
157
|
-
this.bindings[aid] = pluginId;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
else
|
|
161
|
-
throw new Error("必须指定要关联的 AID");
|
|
162
|
-
if (aid)
|
|
163
|
-
this.bindingTimes[aid] = new Date().toISOString();
|
|
140
|
+
const loaded = this.store.load(aid);
|
|
141
|
+
if (!loaded.ok || !loaded.data)
|
|
142
|
+
throw new Error("AID 不在本地密钥库中,无法关联");
|
|
143
|
+
this.identities.add(aid);
|
|
144
|
+
if (!connection)
|
|
145
|
+
await this.connectAid(aid);
|
|
146
|
+
if (!this.clients.has(aid))
|
|
147
|
+
throw new Error("AID 当前未连接");
|
|
148
|
+
this.bindings = { ...previous, [aid]: pluginId };
|
|
149
|
+
this.bindingTimes = { ...previousTimes, [aid]: new Date().toISOString() };
|
|
150
|
+
this.subscribeAid(aid);
|
|
164
151
|
await this.persist();
|
|
165
152
|
}
|
|
166
153
|
catch (error) {
|
|
@@ -168,6 +155,29 @@ class AunPlugin {
|
|
|
168
155
|
this.bindingTimes = previousTimes;
|
|
169
156
|
this.config.bindings = previous;
|
|
170
157
|
this.config.binding_times = previousTimes;
|
|
158
|
+
if (!hadIdentity)
|
|
159
|
+
this.identities.delete(aid);
|
|
160
|
+
this.config.identities = [...this.identities].sort();
|
|
161
|
+
if (!hadSubscription) {
|
|
162
|
+
this.clients.get(aid)?.subscription?.unsubscribe();
|
|
163
|
+
if (connection)
|
|
164
|
+
connection.subscription = undefined;
|
|
165
|
+
}
|
|
166
|
+
if (!connection) {
|
|
167
|
+
try {
|
|
168
|
+
await this.clients.get(aid)?.client.close();
|
|
169
|
+
}
|
|
170
|
+
catch (closeError) {
|
|
171
|
+
this.context?.logger.warn("AUN 关联回滚关闭连接失败", { aid, error: safeError(closeError) });
|
|
172
|
+
}
|
|
173
|
+
finally {
|
|
174
|
+
this.clients.delete(aid);
|
|
175
|
+
if (!this.clients.size) {
|
|
176
|
+
this.status = "degraded";
|
|
177
|
+
this.healthMessage = "暂无已连接 AID";
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
171
181
|
this.context?.logger.error("AUN 身份关联失败", { aid, pluginId, error: safeError(error) });
|
|
172
182
|
throw error;
|
|
173
183
|
}
|
|
@@ -178,6 +188,8 @@ class AunPlugin {
|
|
|
178
188
|
throw new Error("AID 格式无效");
|
|
179
189
|
if (this.bindings[aid] !== pluginId)
|
|
180
190
|
throw new Error("指定 AID 当前未关联到该 Agent");
|
|
191
|
+
if ([...this.pendingMessages.keys()].some(key => JSON.parse(key)[0] === aid))
|
|
192
|
+
throw new Error("该 AID 正在处理消息,请等待任务完成后再解绑");
|
|
181
193
|
const previous = this.bindings;
|
|
182
194
|
const previousTimes = this.bindingTimes;
|
|
183
195
|
this.bindings = { ...previous };
|
|
@@ -194,6 +206,21 @@ class AunPlugin {
|
|
|
194
206
|
this.config.binding_times = previousTimes;
|
|
195
207
|
throw error;
|
|
196
208
|
}
|
|
209
|
+
const connection = this.clients.get(aid);
|
|
210
|
+
connection?.subscription?.unsubscribe();
|
|
211
|
+
try {
|
|
212
|
+
await connection?.client.close();
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
this.context?.logger.warn("AUN 解绑已完成,关闭连接失败", { aid, pluginId, error: safeError(error) });
|
|
216
|
+
}
|
|
217
|
+
finally {
|
|
218
|
+
this.clients.delete(aid);
|
|
219
|
+
if (!this.clients.size) {
|
|
220
|
+
this.status = "degraded";
|
|
221
|
+
this.healthMessage = "暂无已连接 AID";
|
|
222
|
+
}
|
|
223
|
+
}
|
|
197
224
|
this.context?.logger.info("AUN 单个身份关联已解除", { aid, pluginId });
|
|
198
225
|
},
|
|
199
226
|
};
|
|
@@ -207,6 +234,7 @@ class AunPlugin {
|
|
|
207
234
|
async activate(context) {
|
|
208
235
|
this.context = context;
|
|
209
236
|
this.config = { ...context.config };
|
|
237
|
+
this.identities.clear();
|
|
210
238
|
this.legacyCreatedAids.clear();
|
|
211
239
|
this.createdAids.clear();
|
|
212
240
|
try {
|
|
@@ -266,15 +294,25 @@ class AunPlugin {
|
|
|
266
294
|
if (this.aid)
|
|
267
295
|
this.identities.add(this.aid);
|
|
268
296
|
this.acceptingMessages = true;
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
297
|
+
// 只订阅明确关联 Agent 的身份,避免接管 EvolCore 或其它 AID 的消息。
|
|
298
|
+
for (const aid of Object.keys(this.bindings)) {
|
|
299
|
+
const target = this.bindings[aid];
|
|
300
|
+
if (!target || !context.pluginStatus(target)) {
|
|
301
|
+
context.logger.warn("AUN 跳过目标插件不存在的旧绑定,未连接身份", { aid, agentPlugin: target });
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
if (context.pluginStatus(target) !== "ready")
|
|
305
|
+
context.logger.warn("AUN 恢复已有显式绑定,目标插件尚未就绪;关联保持不变", { aid, agentPlugin: target, status: context.pluginStatus(target) });
|
|
306
|
+
if (aidPattern.test(aid))
|
|
275
307
|
await this.connectAid(aid);
|
|
276
308
|
}
|
|
277
309
|
}
|
|
310
|
+
subscribeAid(aid) {
|
|
311
|
+
const connection = this.clients.get(aid);
|
|
312
|
+
if (!connection || connection.subscription || !this.bindings[aid])
|
|
313
|
+
return;
|
|
314
|
+
connection.subscription = connection.client.on("message.received", (payload) => this.onMessage(payload, aid));
|
|
315
|
+
}
|
|
278
316
|
async connectAid(aid) {
|
|
279
317
|
try {
|
|
280
318
|
await this.prepareTrustRoots(aid);
|
|
@@ -282,7 +320,7 @@ class AunPlugin {
|
|
|
282
320
|
if (!loaded.ok || !loaded.data)
|
|
283
321
|
throw new Error(loaded.error?.message ?? "本地密钥库中不存在该 AID");
|
|
284
322
|
const client = new this.sdk.AUNClient(loaded.data.aid);
|
|
285
|
-
const subscription = this.
|
|
323
|
+
const subscription = this.bindings[aid]
|
|
286
324
|
? client.on("message.received", (payload) => this.onMessage(payload, aid))
|
|
287
325
|
: undefined;
|
|
288
326
|
try {
|
package/plugins/aun/package.json
CHANGED