mu-agent-bridge 1.0.44 → 1.0.46

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
@@ -77,7 +77,7 @@ mab start
77
77
  abweb 插件目录从 npm 搜索带 `mu-agent-bridge-plugin` 关键词的包,按包元数据中的
78
78
  `agentBridge.type` 展示 Agent、Channel 和管理插件;安装后以 `plugin.json` 的类型和能力
79
79
  再次校验,不根据包名推断类型。搜索结果有收录延迟,可在页面直接输入 `agent-bridge-*`
80
- 包名安装。已安装插件按清单能力兼容旧版分类。AUN 通道插件包名为 `agent-bridge-aun`,安装后配置 AID 并启动即可;来信默认交给千问办公,也可通过 `agent_plugin` 指定其他已安装 Agent。`workbuddy` 插件包名为 `agent-bridge-workbuddy`,将 AUN 的 `agent_plugin` 设为 `workbuddy` 即可按对端保持 CodeBuddy 会话。默认问答使用独立的 `@tencent-ai/codebuddy-code` CLI,须单独登录;没有 CLI 时使用 WorkBuddy 桌面端 CDP,并默认自动启动桌面应用。CLI 未登录时不会自动切换到桌面端账号;可设置 `transport` 为 `cdp` 显式使用桌面端。
80
+ 包名安装。已安装插件按清单能力兼容旧版分类。AUN 通道插件包名为 `agent-bridge-aun`,安装后在 AUN 身份页手动关联每个 AID;未关联的 AID 不会自动投递,关联操作只修改指定 AID,不影响其它身份。`workbuddy` 插件包名为 `agent-bridge-workbuddy`,显式关联到 WorkBuddy 后即可按对端保持 CodeBuddy 会话。默认问答使用独立的 `@tencent-ai/codebuddy-code` CLI,须单独登录;没有 CLI 时使用 WorkBuddy 桌面端 CDP,并默认自动启动桌面应用。CLI 未登录时不会自动切换到桌面端账号;可设置 `transport` 为 `cdp` 显式使用桌面端。
81
81
 
82
82
  插件仍在 daemon 的同一个 Node.js 进程内运行。修改插件入口依赖后应重启 daemon,避免模块缓存
83
83
  影响新代码。
@@ -48,6 +48,8 @@ const bridge_1 = require("./bridge");
48
48
  const plugin_manager_1 = require("./plugin-manager");
49
49
  const daemon_process_1 = require("./daemon-process");
50
50
  const cli_commands_1 = require("./cli-commands");
51
+ const core_updater_1 = require("./core-updater");
52
+ const log_store_1 = require("./log-store");
51
53
  const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
52
54
  let activeManager;
53
55
  const logger = {
@@ -170,6 +172,41 @@ async function startDaemon() {
170
172
  const child = (0, node_child_process_1.spawn)(process.execPath, [(0, node_path_1.resolve)(__dirname, "cli.js"), "start"], { detached: true, stdio: "ignore", windowsHide: true });
171
173
  child.unref();
172
174
  }
175
+ async function startUpdatedDaemon() {
176
+ let child;
177
+ const env = { ...process.env, AGENT_BRIDGE_CORE_UPDATE_CHILD: "1" };
178
+ if (process.platform === "win32" && !env.AGENT_BRIDGE_TOKEN) {
179
+ try {
180
+ env.AGENT_BRIDGE_TOKEN = (await (0, promises_1.readFile)((0, node_path_1.resolve)(process.env.USERPROFILE ?? (0, node_os_1.homedir)(), ".mab", "token.txt"), "utf8")).trim();
181
+ env.AGENT_BRIDGE_HOST ??= "0.0.0.0";
182
+ env.AGENT_BRIDGE_PORT ??= "26107";
183
+ }
184
+ catch { /* 当前 Windows 安装未使用 mab 启动器 */ }
185
+ }
186
+ child = (0, node_child_process_1.spawn)(process.execPath, [(0, node_path_1.resolve)(__dirname, "cli.js"), "start"], { detached: true, stdio: "ignore", windowsHide: true, env });
187
+ let launchFailed = false;
188
+ child?.once("error", () => { launchFailed = true; });
189
+ child?.unref();
190
+ const healthy = async () => {
191
+ for (let attempt = 0; attempt < 30; attempt++) {
192
+ if (launchFailed || child && (child.exitCode !== null || child.signalCode !== null))
193
+ return false;
194
+ const address = await daemonAddress();
195
+ try {
196
+ const response = await fetch(`http://127.0.0.1:${address.port}/api/health`, {
197
+ headers: process.env.AGENT_BRIDGE_TOKEN ? { authorization: `Bearer ${process.env.AGENT_BRIDGE_TOKEN}` } : undefined,
198
+ signal: AbortSignal.timeout(1000),
199
+ });
200
+ if (response.ok && (await response.json()).status === "ok")
201
+ return true;
202
+ }
203
+ catch { /* daemon 尚未就绪 */ }
204
+ await new Promise((done) => setTimeout(done, 1000));
205
+ }
206
+ return false;
207
+ };
208
+ return { healthy };
209
+ }
173
210
  async function stop() {
174
211
  await stopDaemon();
175
212
  console.log("旧 daemon 已停止");
@@ -344,6 +381,24 @@ async function main() {
344
381
  if (args[0] === "--self-test")
345
382
  return selfTest(args[1] ?? "hi");
346
383
  if (args[0] === "--serve" || args[0] === "start") {
384
+ if (process.env.AGENT_BRIDGE_CORE_UPDATE_CHILD === "1") {
385
+ delete process.env.AGENT_BRIDGE_CORE_UPDATE_CHILD;
386
+ return serve();
387
+ }
388
+ const logs = new log_store_1.LogStore((0, node_path_1.resolve)(stateDirectory(), "logs"));
389
+ let launched;
390
+ const updated = await (0, core_updater_1.updateCoreAtStartup)({
391
+ packageRoot: (0, node_path_1.resolve)(__dirname, "..", ".."),
392
+ stateDir: stateDirectory(),
393
+ stopDaemon: () => stopDaemon(false),
394
+ startDaemon: async () => { launched = await startUpdatedDaemon(); },
395
+ waitForHealth: async () => launched?.healthy() ?? false,
396
+ log: (level, message, fields) => logs.append({ time: new Date().toISOString(), level, source: "核心升级", message, fields }),
397
+ });
398
+ if (updated) {
399
+ await logs.flush();
400
+ return 0;
401
+ }
347
402
  await stopDaemon(false);
348
403
  return serve();
349
404
  }
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.updateCoreAtStartup = updateCoreAtStartup;
4
+ const promises_1 = require("node:fs/promises");
5
+ const node_path_1 = require("node:path");
6
+ const node_child_process_1 = require("node:child_process");
7
+ const node_util_1 = require("node:util");
8
+ const version_manager_1 = require("./version-manager");
9
+ const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
10
+ function normalizePath(value) {
11
+ const path = (0, node_path_1.resolve)(value).replace(/[\\/]+$/, "");
12
+ return process.platform === "win32" ? path.toLowerCase() : path;
13
+ }
14
+ async function sameDirectory(left, right) {
15
+ try {
16
+ return normalizePath(await (0, promises_1.realpath)(left)) === normalizePath(await (0, promises_1.realpath)(right));
17
+ }
18
+ catch {
19
+ return normalizePath(left) === normalizePath(right);
20
+ }
21
+ }
22
+ async function runNpm(args) {
23
+ const command = process.platform === "win32" ? "npm.cmd" : "npm";
24
+ const { stdout } = await execFileAsync(command, args, { timeout: 180_000, maxBuffer: 4 * 1024 * 1024, ...(process.platform === "win32" ? { shell: true } : {}) });
25
+ return stdout.trim();
26
+ }
27
+ async function updateCoreAtStartup(options) {
28
+ const npm = options.npm ?? runNpm;
29
+ try {
30
+ const globalRoot = options.globalRoot ?? await npm(["root", "-g"]);
31
+ const packageRoot = (0, node_path_1.resolve)(options.packageRoot);
32
+ if (!await sameDirectory(packageRoot, (0, node_path_1.join)(globalRoot, "mu-agent-bridge")))
33
+ return false;
34
+ const metadata = JSON.parse(await (0, promises_1.readFile)((0, node_path_1.join)(packageRoot, "package.json"), "utf8"));
35
+ if (metadata.name !== "mu-agent-bridge" || !metadata.version)
36
+ throw new Error("当前全局核心包信息无效");
37
+ const rawVersion = await npm(["view", "mu-agent-bridge", "version", "--json"]);
38
+ const latestVersion = (() => { try {
39
+ return String(JSON.parse(rawVersion));
40
+ }
41
+ catch {
42
+ return rawVersion.replace(/^['\"]|['\"]$/g, "");
43
+ } })();
44
+ if ((0, version_manager_1.compareVersions)(latestVersion, metadata.version) <= 0)
45
+ return false;
46
+ options.log?.("info", "发现 daemon 新版本,开始自动升级", { currentVersion: metadata.version, latestVersion });
47
+ await (0, promises_1.mkdir)(options.stateDir, { recursive: true });
48
+ const backupRoot = await (0, promises_1.mkdtemp)((0, node_path_1.join)(options.stateDir, "mab-core-update-"));
49
+ const backup = (0, node_path_1.join)(backupRoot, "package");
50
+ await (0, promises_1.cp)(packageRoot, backup, { recursive: true });
51
+ try {
52
+ await options.stopDaemon();
53
+ await npm(["install", "-g", `mu-agent-bridge@${latestVersion}`, "--no-fund", "--no-audit"]);
54
+ const installed = JSON.parse(await (0, promises_1.readFile)((0, node_path_1.join)(packageRoot, "package.json"), "utf8"));
55
+ if (installed.name !== "mu-agent-bridge" || installed.version !== latestVersion)
56
+ throw new Error(`核心安装结果不匹配:${installed.version ?? "未知版本"}`);
57
+ await options.startDaemon();
58
+ if (!await options.waitForHealth())
59
+ throw new Error("新 daemon 健康检查失败");
60
+ options.log?.("info", "daemon 自动升级成功", { version: latestVersion });
61
+ await (0, promises_1.rm)(backupRoot, { recursive: true, force: true });
62
+ return true;
63
+ }
64
+ catch (error) {
65
+ await options.stopDaemon().catch(() => undefined);
66
+ await (0, promises_1.rm)(packageRoot, { recursive: true, force: true });
67
+ await (0, promises_1.cp)(backup, packageRoot, { recursive: true });
68
+ options.log?.("error", "daemon 自动升级失败,已恢复旧版本", { error: error instanceof Error ? error.message : String(error), version: metadata.version });
69
+ await (0, promises_1.rm)(backupRoot, { recursive: true, force: true });
70
+ return false;
71
+ }
72
+ }
73
+ catch (error) {
74
+ options.log?.("warn", "daemon 自动升级检查失败,继续使用当前版本", { error: error instanceof Error ? error.message : String(error) });
75
+ return false;
76
+ }
77
+ }
@@ -74,6 +74,37 @@ async function runAunCommand(routes, response, argv) {
74
74
  await (0, promises_1.rm)(root, { recursive: true, force: true });
75
75
  }
76
76
  });
77
+ (0, node_test_1.default)("AUN 绑定目标未就绪时不调用插件且不改动其它 AID", async () => {
78
+ const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "aun-unready-binding-"));
79
+ let receive;
80
+ const sent = [];
81
+ let published = false;
82
+ const sdk = {
83
+ AIDStore: class {
84
+ list() { return { ok: true, data: { identities: [{ aid: "selected.agentid.cn" }, { aid: "other.agentid.cn" }] } }; }
85
+ load(aid) { return { ok: true, data: { aid } }; }
86
+ close() { }
87
+ },
88
+ AUNClient: class {
89
+ async connect() { }
90
+ async close() { }
91
+ on(_event, handler) { receive = handler; return { unsubscribe() { } }; }
92
+ async call(_method, params) { sent.push(params); }
93
+ },
94
+ };
95
+ const plugin = new aun_1.AunPlugin({ sdk, ingress: { publish: async () => { published = true; } } });
96
+ try {
97
+ await plugin.activate({ dataDir: root, config: { bindings: { "selected.agentid.cn": "qwenwork", "other.agentid.cn": "workbuddy" } }, logger: { info() { }, warn() { }, error() { } }, callPlugin: async () => ({ text: "" }), pluginStatus: () => "discovered", http: { register() { }, unregister() { } } });
98
+ await receive({ from: "peer.agentid.cn", message_id: "unready-1", payload: { text: "不要转发" } });
99
+ strict_1.default.equal(published, false);
100
+ strict_1.default.deepEqual(sent, [{ to: "peer.agentid.cn", payload: { type: "text", text: "绑定的 Agent 插件 qwenwork 尚未就绪,消息未转发;AID 关联保持不变。" }, encrypt: true }]);
101
+ strict_1.default.deepEqual((await plugin.health()).details?.bindings, { "selected.agentid.cn": "qwenwork", "other.agentid.cn": "workbuddy" });
102
+ }
103
+ finally {
104
+ await plugin.deactivate("stop");
105
+ await (0, promises_1.rm)(root, { recursive: true, force: true });
106
+ }
107
+ });
77
108
  async function withAunAttachmentPlugin(run, publish, config = { allow_from: "peer.agentid.cn", bindings: { "local.agentid.cn": "qwenwork" } }) {
78
109
  const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "aun-attachments-plugin-"));
79
110
  let handler;
@@ -405,6 +436,8 @@ async function withAunAttachmentPlugin(run, publish, config = { allow_from: "pee
405
436
  const plugin = new aun_1.AunPlugin({ sdk, ingress: { publish: async () => undefined } });
406
437
  try {
407
438
  await plugin.activate({ dataDir: root, config: { bindings: { [aids[0]]: "qwenwork", [aids[1]]: "qwenwork" }, binding_times: { [aids[0]]: "old-1", [aids[1]]: "old-2" } }, logger: { info() { }, warn() { }, error() { } }, callPlugin: async () => ({ text: "" }), pluginStatus: () => "ready", http: { register() { }, unregister() { } } });
439
+ await plugin.identity.bind("qwenwork", aids[0], 26107);
440
+ strict_1.default.deepEqual((await plugin.health()).details?.bindings, { [aids[0]]: "qwenwork", [aids[1]]: "qwenwork" });
408
441
  await plugin.identity.bind("workbuddy", aids[0], 26107);
409
442
  strict_1.default.deepEqual((await plugin.health()).details?.bindings, { [aids[0]]: "workbuddy", [aids[1]]: "qwenwork" });
410
443
  await plugin.identity.unbind("workbuddy", aids[0], 26107);
@@ -462,13 +495,13 @@ async function withAunAttachmentPlugin(run, publish, config = { allow_from: "pee
462
495
  strict_1.default.match(bound.body.message, /AID 不在本地密钥库中/);
463
496
  strict_1.default.deepEqual(bound.body.data, { aid: "missing.agentid.cn", agent: "qwenwork", bindingState: { aunAids: [], agentAid: "old.agentid.cn" } });
464
497
  const created = await runAunCommand(routes, commandResponse(), ["create", "new.agentid.cn", "--owner", "owner.agentid.cn", "--agent", "qwenwork"]);
465
- strict_1.default.equal(created.statusCode, 502);
498
+ strict_1.default.equal(created.statusCode, 200);
466
499
  strict_1.default.equal(created.body.data.created, true);
467
- strict_1.default.deepEqual(created.body.data.bindingState, { aunAids: ["new.agentid.cn"], agentAid: "old.agentid.cn" });
468
- const unbound = await runAunCommand(routes, commandResponse(), ["unbind", "qwenwork"]);
469
- strict_1.default.equal(unbound.statusCode, 400);
470
- strict_1.default.equal(unbound.body.ok, false);
471
- strict_1.default.deepEqual(unbound.body.data.bindingState, { aunAids: [], agentAid: "old.agentid.cn" });
500
+ strict_1.default.equal(created.body.data.agent, "qwenwork");
501
+ strict_1.default.equal((await plugin.health()).details?.bindings?.["new.agentid.cn"], "qwenwork");
502
+ const unbound = await runAunCommand(routes, commandResponse(), ["unbind", "new.agentid.cn"]);
503
+ strict_1.default.equal(unbound.statusCode, 200);
504
+ strict_1.default.deepEqual((await plugin.health()).details?.bindings, {});
472
505
  }
473
506
  finally {
474
507
  await server.stop();
@@ -636,8 +669,13 @@ async function withAunAttachmentPlugin(run, publish, config = { allow_from: "pee
636
669
  return new Response(JSON.stringify({ ok: true, bindings: currentAid ? { [currentAid]: "qwenwork" } : {} }), { status: 200 });
637
670
  if (url.endsWith("/config"))
638
671
  return new Response(JSON.stringify({ aid: currentAid }), { status: 200 });
639
- if (init?.method === "PATCH")
672
+ if (init?.method === "PATCH") {
640
673
  currentAid = JSON.parse(String(init.body)).aid;
674
+ if (currentAid)
675
+ plugin.bindings[currentAid] = "qwenwork";
676
+ }
677
+ if (init?.method === "POST" && url.endsWith("/api/plugins/aun/unbind"))
678
+ delete plugin.bindings[JSON.parse(String(init.body)).aid];
641
679
  return new Response(JSON.stringify({ ok: true }), { status: 200 });
642
680
  });
643
681
  await plugin.activate({ dataDir: root, config: {}, logger: { info(message, fields) { logs.push(JSON.stringify([message, fields])); }, warn(message, fields) { logs.push(JSON.stringify([message, fields])); }, error(message, fields) { logs.push(JSON.stringify([message, fields])); } }, callPlugin: async () => ({ text: "" }), pluginStatus: () => "ready", http: {
@@ -650,19 +688,18 @@ async function withAunAttachmentPlugin(run, publish, config = { allow_from: "pee
650
688
  const created = await runAunCommand(routes, commandResponse(), ["create", "new.agentid.cn", "--owner", "owner.agentid.cn", "--agent", "qwenwork"]);
651
689
  strict_1.default.equal(created.body.ok, true);
652
690
  strict_1.default.equal(created.body.data.created, true);
653
- strict_1.default.deepEqual(created.body.data.replacedAids, ["old.agentid.cn"]);
654
- strict_1.default.match(created.body.message, /old\.agentid\.cn.*new\.agentid\.cn/);
691
+ strict_1.default.equal(created.body.data.agent, "qwenwork");
692
+ strict_1.default.doesNotMatch(created.body.message, /old\.agentid\.cn/);
655
693
  const bound = await runAunCommand(routes, commandResponse(), ["bind", "new.agentid.cn", "qwenwork"]);
656
694
  strict_1.default.equal(bound.body.ok, true);
657
- strict_1.default.deepEqual(bound.body.data.replacedAids, []);
658
- const unbound = await runAunCommand(routes, commandResponse(), ["unbind", "qwenwork"]);
695
+ strict_1.default.deepEqual(bound.body.data, { aid: "new.agentid.cn", agent: "qwenwork" });
696
+ const unbound = await runAunCommand(routes, commandResponse(), ["unbind", "new.agentid.cn"]);
659
697
  strict_1.default.equal(unbound.body.ok, true);
660
- strict_1.default.deepEqual(unbound.body.data.unboundAids, ["new.agentid.cn"]);
698
+ strict_1.default.deepEqual(unbound.body.data, { aid: "new.agentid.cn", agent: "qwenwork" });
661
699
  const patches = requests.filter((request) => request.method === "PATCH");
662
700
  strict_1.default.deepEqual(patches.map(({ url, body }) => [url, JSON.parse(body)]), [
663
701
  ["http://127.0.0.1:3021/api/plugins/qwenwork/aid", { aid: "new.agentid.cn" }],
664
702
  ["http://127.0.0.1:3021/api/plugins/qwenwork/aid", { aid: "new.agentid.cn" }],
665
- ["http://127.0.0.1:3021/api/plugins/qwenwork/aid", { aid: "" }],
666
703
  ]);
667
704
  strict_1.default.ok(requests.every(({ url }) => new URL(url).hostname === "127.0.0.1"));
668
705
  strict_1.default.ok(requests.every(({ signal }) => signal instanceof AbortSignal));
@@ -730,10 +767,11 @@ async function withAunAttachmentPlugin(run, publish, config = { allow_from: "pee
730
767
  const failedBind = await runAunCommand(routes, commandResponse(), ["bind", "existing.agentid.cn", "qwenwork"]);
731
768
  strict_1.default.equal(failedBind.statusCode, 502);
732
769
  strict_1.default.deepEqual(failedBind.body.data.bindingState, { aunAids: ["new.agentid.cn"], agentAid: "old.agentid.cn" });
733
- const failedUnbind = await runAunCommand(routes, commandResponse(), ["unbind", "qwenwork"]);
734
- strict_1.default.equal(failedUnbind.statusCode, 502);
735
- strict_1.default.deepEqual(failedUnbind.body.data.bindingState, { aunAids: ["new.agentid.cn"], agentAid: "old.agentid.cn" });
736
- strict_1.default.equal(patchCalls, 3);
770
+ plugin.bindings["new.agentid.cn"] = "qwenwork";
771
+ const unbound = await runAunCommand(routes, commandResponse(), ["unbind", "new.agentid.cn"]);
772
+ strict_1.default.equal(unbound.statusCode, 200);
773
+ strict_1.default.deepEqual((await plugin.health()).details?.bindings, {});
774
+ strict_1.default.equal(patchCalls, 2, "解绑 AUN 映射不得改写 Agent 全局配置");
737
775
  const malformed = await runAunCommand(routes, commandResponse(), ["unknown"]);
738
776
  strict_1.default.equal(malformed.statusCode, 400);
739
777
  strict_1.default.equal(malformed.body.ok, false);
@@ -768,7 +806,7 @@ async function withAunAttachmentPlugin(run, publish, config = { allow_from: "pee
768
806
  const plugin = new aun_1.AunPlugin({ sdk, ingress: { publish: async () => undefined } });
769
807
  try {
770
808
  await plugin.activate({ dataDir: root, config: {}, logger: { info() { }, warn() { }, error() { } }, callPlugin: async () => ({ text: "" }), pluginStatus: () => "ready", http: { register(route) { routes.set(`${route.method} ${route.path}`, route.handler); return () => undefined; }, unregister() { } } });
771
- for (const argv of [["agents"], ["bind", "valid.agentid.cn", "qwenwork"], ["unbind", "qwenwork"]]) {
809
+ for (const argv of [["agents"], ["bind", "valid.agentid.cn", "qwenwork"]]) {
772
810
  const result = await runAunCommand(routes, commandResponse(), argv);
773
811
  strict_1.default.equal(result.statusCode, 502);
774
812
  strict_1.default.equal(result.body.ok, false);
@@ -852,7 +890,7 @@ async function withAunAttachmentPlugin(run, publish, config = { allow_from: "pee
852
890
  fetch: async (path) => ({ ok: true, json: async () => path.endsWith("/identities") ? identities : path === "/api/plugins" ? { plugins: agents } : path.includes("qwenwork") ? { aid: "external.agentid.cn" } : {} }),
853
891
  });
854
892
  await new Promise((resolve) => setImmediate(resolve));
855
- strict_1.default.deepEqual(elements.get("#identities").children.map((row) => row.children[0].textContent), ["new.agentid.cn", "old.agentid.cn", "unknown.agentid.cn", "external.agentid.cn", "offline-order.agentid.cn"]);
893
+ strict_1.default.deepEqual(elements.get("#identities").children.map((row) => row.children[0].textContent), ["new.agentid.cn", "old.agentid.cn", "unknown.agentid.cn", "offline-order.agentid.cn", "external.agentid.cn"]);
856
894
  });
857
895
  (0, node_test_1.default)("AUN 页面跨来源重复 AID 只显示本工程归属", async () => {
858
896
  const node = () => ({ children: [], elements: new Proxy({}, { get: () => ({ value: "", replaceChildren() { } }) }), append(...items) { this.children.push(...items); }, replaceChildren(...items) { this.children = items; }, setAttribute() { } });
@@ -281,7 +281,9 @@ const plugin_manager_1 = require("../src-ts/plugin-manager");
281
281
  const bound = await fetch(`${base}/api/plugins/dsh/aid`, { method: "PATCH", headers: { "content-type": "application/json" }, body: JSON.stringify({ aid: "new.agentid.cn" }) });
282
282
  strict_1.default.equal(bound.status, 200);
283
283
  strict_1.default.deepEqual(calls[1], ["dsh", "new.agentid.cn", address.port]);
284
- strict_1.default.deepEqual(calls[2], ["dsh", "new.agentid.cn"]);
284
+ strict_1.default.equal(calls.length, 2, "AUN 关联不得改写 Agent 插件的全局 AID 配置");
285
+ const broadUnbind = await fetch(`${base}/api/plugins/dsh/aid`, { method: "PATCH", headers: { "content-type": "application/json" }, body: JSON.stringify({ aid: "" }) });
286
+ strict_1.default.equal(broadUnbind.status, 400, "解绑必须指定 AID");
285
287
  }
286
288
  finally {
287
289
  await server.stop();
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const strict_1 = __importDefault(require("node:assert/strict"));
7
+ const promises_1 = require("node:fs/promises");
8
+ const node_os_1 = require("node:os");
9
+ const node_path_1 = require("node:path");
10
+ const node_test_1 = __importDefault(require("node:test"));
11
+ const core_updater_1 = require("../src-ts/core-updater");
12
+ async function fixture() {
13
+ const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "mab-core-update-"));
14
+ const packageRoot = (0, node_path_1.join)(root, "node_modules", "mu-agent-bridge");
15
+ const globalRoot = (0, node_path_1.join)(root, "node_modules");
16
+ await (0, promises_1.mkdir)((0, node_path_1.join)(packageRoot, "dist", "src-ts"), { recursive: true });
17
+ await (0, promises_1.writeFile)((0, node_path_1.join)(packageRoot, "package.json"), JSON.stringify({ name: "mu-agent-bridge", version: "1.0.0" }));
18
+ await (0, promises_1.writeFile)((0, node_path_1.join)(packageRoot, "dist", "src-ts", "cli.js"), "old");
19
+ return { root, packageRoot, globalRoot };
20
+ }
21
+ (0, node_test_1.default)("mab start 在核心有新版本时安装、启动并等待健康检查", async () => {
22
+ const value = await fixture();
23
+ const events = [];
24
+ try {
25
+ const updated = await (0, core_updater_1.updateCoreAtStartup)({
26
+ packageRoot: value.packageRoot,
27
+ globalRoot: value.globalRoot,
28
+ stateDir: (0, node_path_1.join)(value.root, "state"),
29
+ npm: async (args) => {
30
+ events.push(args.join(" "));
31
+ if (args[0] === "view")
32
+ return '"1.1.0"';
33
+ await (0, promises_1.writeFile)((0, node_path_1.join)(value.packageRoot, "package.json"), JSON.stringify({ name: "mu-agent-bridge", version: "1.1.0" }));
34
+ await (0, promises_1.writeFile)((0, node_path_1.join)(value.packageRoot, "dist", "src-ts", "cli.js"), "new");
35
+ return "";
36
+ },
37
+ stopDaemon: async () => { events.push("stop"); },
38
+ startDaemon: async () => { events.push("start"); },
39
+ waitForHealth: async () => true,
40
+ });
41
+ strict_1.default.equal(updated, true);
42
+ strict_1.default.equal(JSON.parse(await (0, promises_1.readFile)((0, node_path_1.join)(value.packageRoot, "package.json"), "utf8")).version, "1.1.0");
43
+ strict_1.default.deepEqual(events, ["view mu-agent-bridge version --json", "stop", "install -g mu-agent-bridge@1.1.0 --no-fund --no-audit", "start"]);
44
+ }
45
+ finally {
46
+ await (0, promises_1.rm)(value.root, { recursive: true, force: true });
47
+ }
48
+ });
49
+ (0, node_test_1.default)("新 daemon 健康检查失败时恢复旧核心包并继续旧进程", async () => {
50
+ const value = await fixture();
51
+ try {
52
+ const updated = await (0, core_updater_1.updateCoreAtStartup)({
53
+ packageRoot: value.packageRoot,
54
+ globalRoot: value.globalRoot,
55
+ stateDir: (0, node_path_1.join)(value.root, "state"),
56
+ npm: async (args) => {
57
+ if (args[0] === "view")
58
+ return '"1.1.0"';
59
+ await (0, promises_1.writeFile)((0, node_path_1.join)(value.packageRoot, "package.json"), JSON.stringify({ name: "mu-agent-bridge", version: "1.1.0" }));
60
+ return "";
61
+ },
62
+ stopDaemon: async () => undefined,
63
+ startDaemon: async () => undefined,
64
+ waitForHealth: async () => false,
65
+ });
66
+ strict_1.default.equal(updated, false);
67
+ strict_1.default.equal(JSON.parse(await (0, promises_1.readFile)((0, node_path_1.join)(value.packageRoot, "package.json"), "utf8")).version, "1.0.0");
68
+ }
69
+ finally {
70
+ await (0, promises_1.rm)(value.root, { recursive: true, force: true });
71
+ }
72
+ });
73
+ (0, node_test_1.default)("核心已是最新或不是 npm 全局安装时不停止 daemon", async () => {
74
+ const value = await fixture();
75
+ let stops = 0;
76
+ try {
77
+ const current = await (0, core_updater_1.updateCoreAtStartup)({ packageRoot: value.packageRoot, globalRoot: value.globalRoot, stateDir: (0, node_path_1.join)(value.root, "state"), npm: async () => '"1.0.0"', stopDaemon: async () => { stops++; }, startDaemon: async () => undefined, waitForHealth: async () => true });
78
+ const local = await (0, core_updater_1.updateCoreAtStartup)({ packageRoot: value.packageRoot, globalRoot: value.root, stateDir: (0, node_path_1.join)(value.root, "state"), npm: async () => '"2.0.0"', stopDaemon: async () => { stops++; }, startDaemon: async () => undefined, waitForHealth: async () => true });
79
+ strict_1.default.equal(current, false);
80
+ strict_1.default.equal(local, false);
81
+ strict_1.default.equal(stops, 0);
82
+ }
83
+ finally {
84
+ await (0, promises_1.rm)(value.root, { recursive: true, force: true });
85
+ }
86
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mu-agent-bridge",
3
- "version": "1.0.44",
3
+ "version": "1.0.46",
4
4
  "description": "将 AUN Agent 连接到本地桌面 AI 客户端的插件平台。",
5
5
  "main": "dist/src-ts/cli.js",
6
6
  "bin": {