mu-agent-bridge 1.0.63 → 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.
@@ -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
- if (this.plugins.has(manifest.id))
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.32", type: "channel", displayName: "AUN 通道",
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",
@@ -83,10 +83,11 @@ class AunPlugin {
83
83
  identity = {
84
84
  list: async () => {
85
85
  const own = new Set([...this.createdAids, ...this.legacyCreatedAids]);
86
+ const known = new Set([...this.clients.keys(), ...this.listStoreAids()]);
86
87
  return {
87
- evolcore: [...this.clients.keys()].filter((aid) => !own.has(aid)).sort(),
88
- muAgentBridge: [...this.clients.keys()].filter((aid) => own.has(aid)).sort(),
89
- localMuAgentBridge: [...own].filter((aid) => !this.clients.has(aid)).sort(),
88
+ evolcore: [...known].filter((aid) => !own.has(aid)).sort(),
89
+ muAgentBridge: [...known].filter((aid) => own.has(aid)).sort(),
90
+ localMuAgentBridge: [...own].filter((aid) => !known.has(aid)).sort(),
90
91
  };
91
92
  },
92
93
  create: async (aid, owner) => {
@@ -124,42 +125,29 @@ class AunPlugin {
124
125
  }
125
126
  },
126
127
  bind: async (pluginId, aid, _port) => {
128
+ if (!aid || !aidPattern.test(aid))
129
+ throw new Error("AID 格式无效,必须指定要关联的 AID");
127
130
  if (pluginId === "aun" || pluginId === "abweb")
128
131
  throw new Error("只能关联 Agent 插件");
132
+ if (this.context?.pluginStatus(pluginId) !== "ready")
133
+ throw new Error("目标 Agent 插件未启动");
129
134
  const previous = this.bindings;
130
135
  const previousTimes = this.bindingTimes;
131
- this.bindings = { ...previous };
132
- this.bindingTimes = { ...previousTimes };
136
+ const connection = this.clients.get(aid);
137
+ const hadIdentity = this.identities.has(aid);
138
+ const hadSubscription = Boolean(connection?.subscription);
133
139
  try {
134
- if (aid) {
135
- if (!aidPattern.test(aid))
136
- throw new Error("AID 格式无效");
137
- if (this.context?.pluginStatus(pluginId) !== "ready")
138
- throw new Error("目标 Agent 插件未启动");
139
- if (this.identities.has(aid) || this.legacyCreatedAids.has(aid)) {
140
- if (!this.clients.has(aid))
141
- await this.connectAid(aid);
142
- if (!this.clients.has(aid))
143
- throw new Error("AID 当前未连接");
144
- this.bindings[aid] = pluginId;
145
- }
146
- else {
147
- // 本地身份由插件直接接管,不再通过 ec 配置路由。
148
- const loaded = this.store.load(aid);
149
- if (!loaded.ok || !loaded.data)
150
- throw new Error("AID 不在本地密钥库中,无法关联");
151
- this.identities.add(aid);
152
- if (!this.clients.has(aid))
153
- await this.connectAid(aid);
154
- if (!this.clients.has(aid))
155
- throw new Error("AID 当前未连接");
156
- this.bindings[aid] = pluginId;
157
- }
158
- }
159
- else
160
- throw new Error("必须指定要关联的 AID");
161
- if (aid)
162
- 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);
163
151
  await this.persist();
164
152
  }
165
153
  catch (error) {
@@ -167,6 +155,29 @@ class AunPlugin {
167
155
  this.bindingTimes = previousTimes;
168
156
  this.config.bindings = previous;
169
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
+ }
170
181
  this.context?.logger.error("AUN 身份关联失败", { aid, pluginId, error: safeError(error) });
171
182
  throw error;
172
183
  }
@@ -177,6 +188,8 @@ class AunPlugin {
177
188
  throw new Error("AID 格式无效");
178
189
  if (this.bindings[aid] !== pluginId)
179
190
  throw new Error("指定 AID 当前未关联到该 Agent");
191
+ if ([...this.pendingMessages.keys()].some(key => JSON.parse(key)[0] === aid))
192
+ throw new Error("该 AID 正在处理消息,请等待任务完成后再解绑");
180
193
  const previous = this.bindings;
181
194
  const previousTimes = this.bindingTimes;
182
195
  this.bindings = { ...previous };
@@ -193,6 +206,21 @@ class AunPlugin {
193
206
  this.config.binding_times = previousTimes;
194
207
  throw error;
195
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
+ }
196
224
  this.context?.logger.info("AUN 单个身份关联已解除", { aid, pluginId });
197
225
  },
198
226
  };
@@ -206,6 +234,7 @@ class AunPlugin {
206
234
  async activate(context) {
207
235
  this.context = context;
208
236
  this.config = { ...context.config };
237
+ this.identities.clear();
209
238
  this.legacyCreatedAids.clear();
210
239
  this.createdAids.clear();
211
240
  try {
@@ -265,15 +294,25 @@ class AunPlugin {
265
294
  if (this.aid)
266
295
  this.identities.add(this.aid);
267
296
  this.acceptingMessages = true;
268
- for (const aid of this.identities)
269
- await this.connectAid(aid);
270
- // 密钥库中的其余 AID 也自动连接:不再依赖 ec 或 EvolCore 进程
271
- for (const aid of this.listStoreAids()) {
272
- this.identities.add(aid);
273
- if (!this.clients.has(aid))
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))
274
307
  await this.connectAid(aid);
275
308
  }
276
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
+ }
277
316
  async connectAid(aid) {
278
317
  try {
279
318
  await this.prepareTrustRoots(aid);
@@ -281,7 +320,7 @@ class AunPlugin {
281
320
  if (!loaded.ok || !loaded.data)
282
321
  throw new Error(loaded.error?.message ?? "本地密钥库中不存在该 AID");
283
322
  const client = new this.sdk.AUNClient(loaded.data.aid);
284
- const subscription = this.identities.has(aid) || this.legacyCreatedAids.has(aid)
323
+ const subscription = this.bindings[aid]
285
324
  ? client.on("message.received", (payload) => this.onMessage(payload, aid))
286
325
  : undefined;
287
326
  try {
@@ -945,7 +984,7 @@ class AunPlugin {
945
984
  exports.AunPlugin = AunPlugin;
946
985
  exports.default = AunPlugin;
947
986
  function safeError(error) {
948
- return redact(error instanceof Error ? error.message : String(error)).slice(0, 240);
987
+ return redact(error instanceof Error ? error.message : String(error));
949
988
  }
950
989
  async function readResponse(response, limit) {
951
990
  if (!response.body) {
@@ -10,7 +10,7 @@ const desktop_launch_1 = require("../desktop-launch");
10
10
  const session_history_1 = require("./session-history");
11
11
  exports.qwenworkManifest = {
12
12
  id: "qwenwork",
13
- version: "1.0.23",
13
+ version: "1.0.24",
14
14
  displayName: "千问办公",
15
15
  entry: "dist/index.js",
16
16
  apiVersion: "1",
@@ -215,9 +215,10 @@ class QwenworkPlugin {
215
215
  throw new Error("千问 CDP 已断开,等待空闲后自动恢复");
216
216
  if (request.attachments?.length && (!this.client.attachmentInput || !this.client.complete))
217
217
  throw new Error("千问当前客户端不支持附件输入");
218
+ const timeoutMs = request.timeoutMs ?? asNumber(this.context?.config.timeoutMs, 240_000);
218
219
  const response = this.client.complete
219
- ? await this.client.complete(request.task, request.timeoutMs ?? 240_000, undefined, undefined, request.attachments, request.onMessage)
220
- : { text: await this.complete(request.task, request.timeoutMs ?? 240_000) };
220
+ ? await this.client.complete(request.task, timeoutMs, undefined, undefined, request.attachments, request.onMessage)
221
+ : { text: await this.complete(request.task, timeoutMs) };
221
222
  if ((0, cdp_client_1.isQwenApiUnavailableReply)(response.text))
222
223
  throw new agent_1.AgentOperationError("QWEN_REMOTE_ERROR", "等待千问办公回复", new Error("千问生成接口不可用;桌面未提供具体服务端错误码,CDP 仍连接,未重启或重新提交任务"));
223
224
  this.failures = 0;
@@ -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
- on(_event, handler) { receive = handler; return { unsubscribe() { } }; }
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
- await run(async (payload) => { await handler(payload); }, sent, storage, logs);
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, aids);
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)("共享密钥库中的 AID 自动连接并保留 EvolCore 来源", async () => {
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, storeIds);
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)("默认密钥库指向共享 AUN 目录并自动连接其中全部 AID", async () => {
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
- // 密钥库中的全部 AID 在启动时自动连接,无需 ec 或手动关联
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, 2);
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 已连接;offline-store 在首次 activate 时即自动连接
1141
- strict_1.default.equal(clients.length, 3);
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
- // 密钥库中的 offline-store 已自动连接,绑定只更新关联记录
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
- // 自动连接时 offline-store 先建立连接,bridge 是第二个客户端
1151
- const bridgeClient = clients[1];
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
- // 重启后 identities 已含全部三个 AID,各自恢复连接
1166
- strict_1.default.equal(clients.length, 6);
1207
+ // 重启后只恢复两个显式绑定的身份。
1208
+ strict_1.default.equal(clients.length, 5);
1167
1209
  const health = await plugin.health();
1168
- // 自动连接时 offline-store 先建链,顺序与其余 AID 不同
1169
- strict_1.default.deepEqual([...health.details?.aids].sort(), ["bridge.agentid.pub", "offline-store.agentid.cn", "second.agentid.pub"]);
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"]);
@@ -1342,3 +1384,116 @@ async function withAunAttachmentPlugin(run, publish, config = { allow_from: "pee
1342
1384
  strict_1.default.match(logs.join("\n"), /fs-failed.*AUN_FS_UPLOAD_FAILED|AUN_FS_UPLOAD_FAILED.*fs-failed/);
1343
1385
  }, async (message) => { await message.replyAttachments([{ kind: "file", filename: "报告.pdf", contentType: "application/pdf", data: new Uint8Array([1]) }]); });
1344
1386
  });
1387
+ (0, node_test_1.default)("AUN 长路径错误保留末尾的根因和错误码并继续脱敏", async () => {
1388
+ await withAunAttachmentPlugin(async (receive, sent, _storage, logs) => {
1389
+ await receive({ from: "peer.agentid.cn", message_id: "long-error", payload: { text: "读取文件" } });
1390
+ for (const output of [sent[0].payload.text, logs.join("\n")]) {
1391
+ strict_1.default.match(output, /ENOENT.*产物文件已经删除/);
1392
+ strict_1.default.doesNotMatch(output, /private-token/);
1393
+ }
1394
+ }, async () => { throw new Error(`WORKBUDDY_ARTIFACT_READ_FAILED:读取文件 ${"目录/".repeat(120)};原因:ENOENT:产物文件已经删除;token=private-token`); });
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
+ });
@@ -609,3 +609,17 @@ const cdp_client_1 = require("../src-ts/plugins/qwenwork/cdp-client");
609
609
  client.cdp = async () => { throw Object.assign(new Error("connection reset"), { code: "ECONNRESET" }); };
610
610
  await strict_1.default.rejects(client.complete("测试"), /QWEN_SUBMIT_FAILED.*提交.*ECONNRESET/);
611
611
  });
612
+ (0, node_test_1.default)("千问 AUN 调用采用插件配置的任务等待时间,请求参数仍可覆盖", async () => {
613
+ const timeouts = [];
614
+ const client = { connect: async () => undefined, close() { }, connected: () => true, send: async () => undefined, direct: async () => "回复", complete: async (_text, timeoutMs) => { timeouts.push(timeoutMs); return { text: "已完成" }; } };
615
+ const plugin = new qwenwork_1.QwenworkPlugin({ client });
616
+ try {
617
+ await plugin.activate({ config: { timeoutMs: 900000 }, logger: { info() { }, warn() { }, error() { } }, http: { register() { }, unregister() { } } });
618
+ await plugin.handle({ task: "生成文件" });
619
+ await plugin.handle({ task: "快速检查", timeoutMs: 1000 });
620
+ strict_1.default.deepEqual(timeouts, [900000, 1000]);
621
+ }
622
+ finally {
623
+ await plugin.deactivate("stop");
624
+ }
625
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mu-agent-bridge",
3
- "version": "1.0.63",
3
+ "version": "1.0.65",
4
4
  "description": "将 AUN Agent 连接到本地桌面 AI 客户端的插件平台。",
5
5
  "main": "dist/src-ts/cli.js",
6
6
  "bin": {
@@ -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.32", type: "channel", displayName: "AUN 通道",
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",
@@ -83,10 +83,11 @@ class AunPlugin {
83
83
  identity = {
84
84
  list: async () => {
85
85
  const own = new Set([...this.createdAids, ...this.legacyCreatedAids]);
86
+ const known = new Set([...this.clients.keys(), ...this.listStoreAids()]);
86
87
  return {
87
- evolcore: [...this.clients.keys()].filter((aid) => !own.has(aid)).sort(),
88
- muAgentBridge: [...this.clients.keys()].filter((aid) => own.has(aid)).sort(),
89
- localMuAgentBridge: [...own].filter((aid) => !this.clients.has(aid)).sort(),
88
+ evolcore: [...known].filter((aid) => !own.has(aid)).sort(),
89
+ muAgentBridge: [...known].filter((aid) => own.has(aid)).sort(),
90
+ localMuAgentBridge: [...own].filter((aid) => !known.has(aid)).sort(),
90
91
  };
91
92
  },
92
93
  create: async (aid, owner) => {
@@ -124,42 +125,29 @@ class AunPlugin {
124
125
  }
125
126
  },
126
127
  bind: async (pluginId, aid, _port) => {
128
+ if (!aid || !aidPattern.test(aid))
129
+ throw new Error("AID 格式无效,必须指定要关联的 AID");
127
130
  if (pluginId === "aun" || pluginId === "abweb")
128
131
  throw new Error("只能关联 Agent 插件");
132
+ if (this.context?.pluginStatus(pluginId) !== "ready")
133
+ throw new Error("目标 Agent 插件未启动");
129
134
  const previous = this.bindings;
130
135
  const previousTimes = this.bindingTimes;
131
- this.bindings = { ...previous };
132
- this.bindingTimes = { ...previousTimes };
136
+ const connection = this.clients.get(aid);
137
+ const hadIdentity = this.identities.has(aid);
138
+ const hadSubscription = Boolean(connection?.subscription);
133
139
  try {
134
- if (aid) {
135
- if (!aidPattern.test(aid))
136
- throw new Error("AID 格式无效");
137
- if (this.context?.pluginStatus(pluginId) !== "ready")
138
- throw new Error("目标 Agent 插件未启动");
139
- if (this.identities.has(aid) || this.legacyCreatedAids.has(aid)) {
140
- if (!this.clients.has(aid))
141
- await this.connectAid(aid);
142
- if (!this.clients.has(aid))
143
- throw new Error("AID 当前未连接");
144
- this.bindings[aid] = pluginId;
145
- }
146
- else {
147
- // 本地身份由插件直接接管,不再通过 ec 配置路由。
148
- const loaded = this.store.load(aid);
149
- if (!loaded.ok || !loaded.data)
150
- throw new Error("AID 不在本地密钥库中,无法关联");
151
- this.identities.add(aid);
152
- if (!this.clients.has(aid))
153
- await this.connectAid(aid);
154
- if (!this.clients.has(aid))
155
- throw new Error("AID 当前未连接");
156
- this.bindings[aid] = pluginId;
157
- }
158
- }
159
- else
160
- throw new Error("必须指定要关联的 AID");
161
- if (aid)
162
- 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);
163
151
  await this.persist();
164
152
  }
165
153
  catch (error) {
@@ -167,6 +155,29 @@ class AunPlugin {
167
155
  this.bindingTimes = previousTimes;
168
156
  this.config.bindings = previous;
169
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
+ }
170
181
  this.context?.logger.error("AUN 身份关联失败", { aid, pluginId, error: safeError(error) });
171
182
  throw error;
172
183
  }
@@ -177,6 +188,8 @@ class AunPlugin {
177
188
  throw new Error("AID 格式无效");
178
189
  if (this.bindings[aid] !== pluginId)
179
190
  throw new Error("指定 AID 当前未关联到该 Agent");
191
+ if ([...this.pendingMessages.keys()].some(key => JSON.parse(key)[0] === aid))
192
+ throw new Error("该 AID 正在处理消息,请等待任务完成后再解绑");
180
193
  const previous = this.bindings;
181
194
  const previousTimes = this.bindingTimes;
182
195
  this.bindings = { ...previous };
@@ -193,6 +206,21 @@ class AunPlugin {
193
206
  this.config.binding_times = previousTimes;
194
207
  throw error;
195
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
+ }
196
224
  this.context?.logger.info("AUN 单个身份关联已解除", { aid, pluginId });
197
225
  },
198
226
  };
@@ -206,6 +234,7 @@ class AunPlugin {
206
234
  async activate(context) {
207
235
  this.context = context;
208
236
  this.config = { ...context.config };
237
+ this.identities.clear();
209
238
  this.legacyCreatedAids.clear();
210
239
  this.createdAids.clear();
211
240
  try {
@@ -265,15 +294,25 @@ class AunPlugin {
265
294
  if (this.aid)
266
295
  this.identities.add(this.aid);
267
296
  this.acceptingMessages = true;
268
- for (const aid of this.identities)
269
- await this.connectAid(aid);
270
- // 密钥库中的其余 AID 也自动连接:不再依赖 ec 或 EvolCore 进程
271
- for (const aid of this.listStoreAids()) {
272
- this.identities.add(aid);
273
- if (!this.clients.has(aid))
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))
274
307
  await this.connectAid(aid);
275
308
  }
276
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
+ }
277
316
  async connectAid(aid) {
278
317
  try {
279
318
  await this.prepareTrustRoots(aid);
@@ -281,7 +320,7 @@ class AunPlugin {
281
320
  if (!loaded.ok || !loaded.data)
282
321
  throw new Error(loaded.error?.message ?? "本地密钥库中不存在该 AID");
283
322
  const client = new this.sdk.AUNClient(loaded.data.aid);
284
- const subscription = this.identities.has(aid) || this.legacyCreatedAids.has(aid)
323
+ const subscription = this.bindings[aid]
285
324
  ? client.on("message.received", (payload) => this.onMessage(payload, aid))
286
325
  : undefined;
287
326
  try {
@@ -945,7 +984,7 @@ class AunPlugin {
945
984
  exports.AunPlugin = AunPlugin;
946
985
  exports.default = AunPlugin;
947
986
  function safeError(error) {
948
- return redact(error instanceof Error ? error.message : String(error)).slice(0, 240);
987
+ return redact(error instanceof Error ? error.message : String(error));
949
988
  }
950
989
  async function readResponse(response, limit) {
951
990
  if (!response.body) {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-bridge-aun",
3
- "version": "1.0.32",
3
+ "version": "1.0.34",
4
4
  "description": "mu-agent-bridge 的 AUN 通道插件。",
5
5
  "keywords": [
6
6
  "mu-agent-bridge-plugin",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "aun",
3
- "version": "1.0.32",
3
+ "version": "1.0.34",
4
4
  "type": "channel",
5
5
  "displayName": "AUN 通道",
6
6
  "entry": "dist/src-ts/plugins/aun/index.js",
@@ -10,7 +10,7 @@ const desktop_launch_1 = require("../desktop-launch");
10
10
  const session_history_1 = require("./session-history");
11
11
  exports.qwenworkManifest = {
12
12
  id: "qwenwork",
13
- version: "1.0.23",
13
+ version: "1.0.24",
14
14
  displayName: "千问办公",
15
15
  entry: "dist/index.js",
16
16
  apiVersion: "1",
@@ -215,9 +215,10 @@ class QwenworkPlugin {
215
215
  throw new Error("千问 CDP 已断开,等待空闲后自动恢复");
216
216
  if (request.attachments?.length && (!this.client.attachmentInput || !this.client.complete))
217
217
  throw new Error("千问当前客户端不支持附件输入");
218
+ const timeoutMs = request.timeoutMs ?? asNumber(this.context?.config.timeoutMs, 240_000);
218
219
  const response = this.client.complete
219
- ? await this.client.complete(request.task, request.timeoutMs ?? 240_000, undefined, undefined, request.attachments, request.onMessage)
220
- : { text: await this.complete(request.task, request.timeoutMs ?? 240_000) };
220
+ ? await this.client.complete(request.task, timeoutMs, undefined, undefined, request.attachments, request.onMessage)
221
+ : { text: await this.complete(request.task, timeoutMs) };
221
222
  if ((0, cdp_client_1.isQwenApiUnavailableReply)(response.text))
222
223
  throw new agent_1.AgentOperationError("QWEN_REMOTE_ERROR", "等待千问办公回复", new Error("千问生成接口不可用;桌面未提供具体服务端错误码,CDP 仍连接,未重启或重新提交任务"));
223
224
  this.failures = 0;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-bridge-qwenwork",
3
- "version": "1.0.23",
3
+ "version": "1.0.24",
4
4
  "description": "agent-bridge 的千问办公插件。",
5
5
  "keywords": [
6
6
  "mu-agent-bridge-plugin",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "qwenwork",
3
- "version": "1.0.23",
3
+ "version": "1.0.24",
4
4
  "type": "agent",
5
5
  "displayName": "千问办公",
6
6
  "entry": "dist/src-ts/plugins/qwenwork/index.js",