mu-agent-bridge 1.0.73 → 1.0.78

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.
Files changed (152) hide show
  1. package/README.md +12 -8
  2. package/dist/src-ts/cli.js +67 -37
  3. package/dist/src-ts/control-server.js +7 -28
  4. package/dist/src-ts/core-updater.js +141 -33
  5. package/dist/src-ts/daemon-process.js +2 -1
  6. package/dist/src-ts/dsh-agent.js +7 -2
  7. package/dist/src-ts/http-agent.js +36 -0
  8. package/dist/src-ts/plugin-manager.js +185 -58
  9. package/dist/src-ts/plugins/abweb/index.js +1 -1
  10. package/dist/src-ts/plugins/abweb/public/app.js +8 -6
  11. package/dist/src-ts/plugins/abweb/public/index.html +3 -3
  12. package/dist/src-ts/plugins/aun/attachments.js +3 -21
  13. package/dist/src-ts/plugins/aun/index.js +89 -11
  14. package/dist/src-ts/plugins/aun/messages.js +1 -1
  15. package/dist/src-ts/plugins/aun/public/app.js +2 -1
  16. package/dist/src-ts/plugins/aun/public/index.html +1 -1
  17. package/dist/src-ts/plugins/cdp-draft.js +5 -2
  18. package/dist/src-ts/plugins/chatgpt/index.js +3 -2
  19. package/dist/src-ts/plugins/claude-cli/index.js +1 -1
  20. package/dist/src-ts/plugins/desktop-launch.js +44 -3
  21. package/dist/src-ts/plugins/doubao/cdp-client.js +231 -45
  22. package/dist/src-ts/plugins/doubao/index.js +119 -7
  23. package/dist/src-ts/plugins/doubao/native-task.js +134 -0
  24. package/dist/src-ts/plugins/dsh/cdp-client.js +334 -0
  25. package/dist/src-ts/plugins/dsh/index.js +102 -23
  26. package/dist/src-ts/plugins/dsh/session-history.js +31 -3
  27. package/dist/src-ts/plugins/feishu/index.js +1 -1
  28. package/dist/src-ts/plugins/personal-assistant/index.js +29 -8
  29. package/dist/src-ts/plugins/personal-assistant/model-client.js +20 -6
  30. package/dist/src-ts/plugins/personal-assistant/session-memory.js +124 -0
  31. package/dist/src-ts/plugins/personal-assistant/tools.js +4 -3
  32. package/dist/src-ts/plugins/qwenwork/cdp-client.js +43 -7
  33. package/dist/src-ts/plugins/qwenwork/index.js +52 -18
  34. package/dist/src-ts/plugins/workbuddy/cdp-client.js +349 -83
  35. package/dist/src-ts/plugins/workbuddy/index.js +24 -12
  36. package/dist/src-ts/plugins/zcode/cdp-client.js +500 -0
  37. package/dist/src-ts/plugins/zcode/index.js +97 -0
  38. package/dist/src-ts/session-management.js +27 -8
  39. package/dist/src-ts/version-manager.js +6 -3
  40. package/dist/tests-ts/abweb-model-config.test.js +44 -41
  41. package/dist/tests-ts/aun-attachments.test.js +19 -17
  42. package/dist/tests-ts/aun-default-owner-page.test.js +2 -1
  43. package/dist/tests-ts/aun-messages.test.js +14 -0
  44. package/dist/tests-ts/aun-plugin.test.js +207 -19
  45. package/dist/tests-ts/bundled-package.test.js +1 -1
  46. package/dist/tests-ts/cdp-draft-recovery.test.js +182 -3
  47. package/dist/tests-ts/chatgpt-plugin.test.js +19 -0
  48. package/dist/tests-ts/control-server.test.js +20 -0
  49. package/dist/tests-ts/core-updater.test.js +215 -46
  50. package/dist/tests-ts/daemon-process.test.js +126 -1
  51. package/dist/tests-ts/desktop-launch.test.js +73 -0
  52. package/dist/tests-ts/doubao-native-task.test.js +237 -0
  53. package/dist/tests-ts/doubao-plugin.test.js +688 -19
  54. package/dist/tests-ts/dsh-agent.test.js +91 -19
  55. package/dist/tests-ts/dsh-cdp.test.js +148 -0
  56. package/dist/tests-ts/dsh-history.test.js +37 -0
  57. package/dist/tests-ts/feishu-plugin.test.js +1 -1
  58. package/dist/tests-ts/http-agent.test.js +18 -0
  59. package/dist/tests-ts/local-cli.test.js +29 -2
  60. package/dist/tests-ts/model-provider-cancellation.test.js +2 -0
  61. package/dist/tests-ts/native-session-management.test.js +8 -7
  62. package/dist/tests-ts/personal-assistant-coordination.test.js +16 -0
  63. package/dist/tests-ts/personal-assistant-extensions.test.js +7 -1
  64. package/dist/tests-ts/personal-assistant-memory.test.js +117 -0
  65. package/dist/tests-ts/personal-assistant.test.js +277 -3
  66. package/dist/tests-ts/platform-integration.test.js +60 -1
  67. package/dist/tests-ts/plugin-manager.test.js +78 -6
  68. package/dist/tests-ts/plugin-upgrade-deadline.test.js +354 -0
  69. package/dist/tests-ts/publish-packages.test.js +1 -1
  70. package/dist/tests-ts/qwenwork-plugin.test.js +30 -0
  71. package/dist/tests-ts/qwenwork-reconnect.test.js +195 -0
  72. package/dist/tests-ts/qwenwork-sse.test.js +33 -0
  73. package/dist/tests-ts/session-access-defaults.test.js +112 -0
  74. package/dist/tests-ts/session-management.test.js +8 -8
  75. package/dist/tests-ts/version-manager.test.js +40 -0
  76. package/dist/tests-ts/workbuddy-cdp-recovery.test.js +311 -0
  77. package/dist/tests-ts/workbuddy-plugin.test.js +356 -13
  78. package/dist/tests-ts/zcode-cdp.test.js +894 -0
  79. package/dist/tests-ts/zcode-plugin.test.js +396 -0
  80. package/package.json +1 -1
  81. package/plugins/abweb/dist/src-ts/plugins/abweb/index.js +1 -1
  82. package/plugins/abweb/dist/src-ts/plugins/abweb/public/app.js +8 -6
  83. package/plugins/abweb/dist/src-ts/plugins/abweb/public/app.ts +7 -5
  84. package/plugins/abweb/dist/src-ts/plugins/abweb/public/index.html +3 -3
  85. package/plugins/abweb/package.json +1 -1
  86. package/plugins/abweb/plugin.json +1 -1
  87. package/plugins/aun/config.schema.json +1 -1
  88. package/plugins/aun/dist/src-ts/plugins/aun/attachments.js +3 -21
  89. package/plugins/aun/dist/src-ts/plugins/aun/index.js +89 -11
  90. package/plugins/aun/dist/src-ts/plugins/aun/messages.js +1 -1
  91. package/plugins/aun/dist/src-ts/plugins/aun/public/app.js +2 -1
  92. package/plugins/aun/dist/src-ts/plugins/aun/public/index.html +1 -1
  93. package/plugins/aun/package.json +1 -1
  94. package/plugins/aun/plugin.json +1 -1
  95. package/plugins/chatgpt/dist/src-ts/plugins/cdp-draft.js +5 -2
  96. package/plugins/chatgpt/dist/src-ts/plugins/chatgpt/index.js +3 -2
  97. package/plugins/chatgpt/dist/src-ts/plugins/desktop-launch.js +44 -3
  98. package/plugins/claude-cli/dist/src-ts/plugins/claude-cli/index.js +1 -1
  99. package/plugins/claude-cli/package.json +1 -1
  100. package/plugins/claude-cli/plugin.json +1 -1
  101. package/plugins/doubao/config.schema.json +2 -1
  102. package/plugins/doubao/dist/src-ts/plugins/desktop-launch.js +44 -3
  103. package/plugins/doubao/dist/src-ts/plugins/doubao/cdp-client.js +231 -45
  104. package/plugins/doubao/dist/src-ts/plugins/doubao/index.js +119 -7
  105. package/plugins/doubao/dist/src-ts/plugins/doubao/native-task.js +134 -0
  106. package/plugins/doubao/package.json +1 -1
  107. package/plugins/doubao/plugin.json +1 -1
  108. package/plugins/dsh/config.schema.json +5 -2
  109. package/plugins/dsh/dist/src-ts/dsh-agent.js +7 -2
  110. package/plugins/dsh/dist/src-ts/plugins/desktop-launch.js +44 -3
  111. package/plugins/dsh/dist/src-ts/plugins/dsh/cdp-client.js +334 -0
  112. package/plugins/dsh/dist/src-ts/plugins/dsh/index.js +102 -23
  113. package/plugins/dsh/dist/src-ts/plugins/dsh/session-history.js +31 -3
  114. package/plugins/dsh/package.json +1 -1
  115. package/plugins/dsh/plugin.json +1 -1
  116. package/plugins/feishu/dist/src-ts/plugins/feishu/index.js +1 -1
  117. package/plugins/feishu/package.json +1 -1
  118. package/plugins/feishu/plugin.json +1 -1
  119. package/plugins/personal-assistant/config.schema.json +3 -2
  120. package/plugins/personal-assistant/dist/src-ts/attachments.js +62 -0
  121. package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/index.js +29 -8
  122. package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/model-client.js +20 -6
  123. package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/session-memory.js +124 -0
  124. package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/tools.js +4 -3
  125. package/plugins/personal-assistant/package.json +2 -2
  126. package/plugins/personal-assistant/plugin.json +1 -1
  127. package/plugins/qwenwork/dist/src-ts/plugins/cdp-draft.js +5 -2
  128. package/plugins/qwenwork/dist/src-ts/plugins/desktop-launch.js +44 -3
  129. package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/cdp-client.js +43 -7
  130. package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/index.js +52 -18
  131. package/plugins/qwenwork/package.json +1 -1
  132. package/plugins/qwenwork/plugin.json +1 -1
  133. package/plugins/workbuddy/config.schema.json +1 -1
  134. package/plugins/workbuddy/dist/src-ts/plugins/desktop-launch.js +44 -3
  135. package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/cdp-client.js +349 -83
  136. package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/index.js +24 -12
  137. package/plugins/workbuddy/package.json +1 -1
  138. package/plugins/workbuddy/plugin.json +1 -1
  139. package/plugins/zcode/config.schema.json +13 -0
  140. package/plugins/zcode/dist/src-ts/agent.js +69 -0
  141. package/plugins/zcode/dist/src-ts/attachments.js +62 -0
  142. package/plugins/zcode/dist/src-ts/plugin.js +13 -0
  143. package/plugins/zcode/dist/src-ts/plugins/desktop-launch.js +238 -0
  144. package/plugins/zcode/dist/src-ts/plugins/workbuddy/cdp-client.js +896 -0
  145. package/plugins/zcode/dist/src-ts/plugins/workbuddy/cli-client.js +39 -0
  146. package/plugins/zcode/dist/src-ts/plugins/workbuddy/desktop-launch.js +10 -0
  147. package/plugins/zcode/dist/src-ts/plugins/workbuddy/index.js +567 -0
  148. package/plugins/zcode/dist/src-ts/plugins/workbuddy/session-history.js +121 -0
  149. package/plugins/zcode/dist/src-ts/plugins/zcode/cdp-client.js +500 -0
  150. package/plugins/zcode/dist/src-ts/plugins/zcode/index.js +97 -0
  151. package/plugins/zcode/package.json +16 -0
  152. package/plugins/zcode/plugin.json +11 -0
@@ -615,6 +615,41 @@ async function withMappedNpmPackage(run) {
615
615
  await (0, promises_1.rm)(root, { recursive: true, force: true });
616
616
  }
617
617
  });
618
+ (0, node_test_1.default)("插件整体默认配置用于启动并保留用户显式限制", async () => {
619
+ const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "mab-config-defaults-"));
620
+ const manager = new plugin_manager_1.PluginManager({ pluginsDir: (0, node_path_1.join)(root, "plugins"), stateDir: (0, node_path_1.join)(root, "state") });
621
+ try {
622
+ for (const id of ["with-defaults", "without-defaults"]) {
623
+ const dir = (0, node_path_1.join)(root, "plugins", id);
624
+ await (0, promises_1.mkdir)(dir, { recursive: true });
625
+ await (0, promises_1.writeFile)((0, node_path_1.join)(dir, "plugin.json"), JSON.stringify({ id, version: "1.0.0", entry: "index.js", configSchema: "config.schema.json", apiVersion: "1", capabilities: ["chat"] }));
626
+ await (0, promises_1.writeFile)((0, node_path_1.join)(dir, "config.schema.json"), JSON.stringify({ type: "object", properties: { allowedPeers: { type: "array", items: { type: "string" }, default: ["*"] }, allowedAgents: { type: "array", items: { type: "string" } }, aid: { type: "string" } }, ...(id === "with-defaults" ? { default: { allowedPeers: ["*"], allowedAgents: ["workbuddy"] } } : {}) }));
627
+ await (0, promises_1.writeFile)((0, node_path_1.join)(dir, "index.js"), "module.exports=class{async activate(c){this.config=c.config}async deactivate(){}async health(){return {status:'ready'}}async handle(){return {text:JSON.stringify(this.config)}}};");
628
+ }
629
+ await manager.discover();
630
+ const defaults = { allowedPeers: ["*"], allowedAgents: ["workbuddy"] };
631
+ strict_1.default.deepEqual(await manager.config("with-defaults"), defaults);
632
+ await manager.start("with-defaults");
633
+ strict_1.default.deepEqual(JSON.parse((await manager.handle("with-defaults", { task: "查看配置" })).text), defaults);
634
+ await manager.saveConfig("with-defaults", { aid: "assistant.agentid.cn" });
635
+ strict_1.default.deepEqual(await manager.config("with-defaults"), { ...defaults, aid: "assistant.agentid.cn" });
636
+ await manager.saveConfig("with-defaults", { allowedPeers: [], allowedAgents: [] });
637
+ strict_1.default.deepEqual(await manager.config("with-defaults"), { allowedPeers: [], allowedAgents: [], aid: "assistant.agentid.cn" });
638
+ await manager.reload("with-defaults");
639
+ strict_1.default.deepEqual(JSON.parse((await manager.handle("with-defaults", { task: "查看限制" })).text).allowedPeers, []);
640
+ strict_1.default.deepEqual(await manager.config("without-defaults"), {});
641
+ for (const value of ["{损坏", "null", "[]", '"无效配置"']) {
642
+ await (0, promises_1.writeFile)((0, node_path_1.join)(root, "state", "with-defaults", "config.json"), value);
643
+ await strict_1.default.rejects(manager.config("with-defaults"), /配置.*无法读取|配置.*无效/);
644
+ }
645
+ }
646
+ finally {
647
+ if (manager.get("with-defaults")?.instance)
648
+ await manager.stop("with-defaults");
649
+ await manager.logs.flush();
650
+ await (0, promises_1.rm)(root, { recursive: true, force: true });
651
+ }
652
+ });
618
653
  (0, node_test_1.default)("插件配置按 schema 重载并拒绝无效值", async () => {
619
654
  const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "agent-bridge-config-"));
620
655
  try {
@@ -857,13 +892,13 @@ async function withMappedNpmPackage(run) {
857
892
  const pendingRequest = ingress.publish({ channel: "aun", peerId: "admin.agentid.cn", text: "撤权竞态", agentPlugin: "personal-assistant", reply: async () => undefined });
858
893
  await managementReady;
859
894
  // 直接更新配置以验证逐次授权检查,不依赖配置保存触发的插件重载。
860
- await (0, promises_1.writeFile)((0, node_path_1.join)(root, "state", "personal-assistant", "config.json"), JSON.stringify({ allowedPeers: [], allowedAgents: ["target"] }));
895
+ await (0, promises_1.writeFile)((0, node_path_1.join)(root, "state", "personal-assistant", "config.json"), JSON.stringify({ allowedPeers: ["other.agentid.cn"], allowedAgents: ["target"] }));
861
896
  globalThis.managementContinue();
862
897
  globalThis.managementContinue();
863
898
  await pendingRequest;
864
899
  strict_1.default.match(globalThis.managementDenied, /无权/);
865
900
  delete globalThis.managementPause;
866
- await manager.saveConfig("personal-assistant", { allowedPeers: [], allowedAgents: ["target"] });
901
+ await manager.saveConfig("personal-assistant", { allowedPeers: ["other.agentid.cn"], allowedAgents: ["target"] });
867
902
  let unauthorizedReply = "";
868
903
  await ingress.publish({ channel: "aun", peerId: "admin.agentid.cn", text: "再次请求", agentPlugin: "personal-assistant", reply: async (text) => { unauthorizedReply = text; } });
869
904
  strict_1.default.equal(unauthorizedReply, "untrusted");
@@ -1009,9 +1044,11 @@ async function withMappedNpmPackage(run) {
1009
1044
  await globalThis.attachmentIngress.publish(message);
1010
1045
  strict_1.default.equal(globalThis.attachmentRequest.task, "原文");
1011
1046
  strict_1.default.deepEqual(globalThis.attachmentRequest.attachments, [item]);
1047
+ strict_1.default.equal(globalThis.attachmentRequest.attachmentOutput, true);
1012
1048
  strict_1.default.deepEqual(received, { items: [{ ...png, kind: "image" }], text: "生成结果" });
1013
1049
  let failureText = "";
1014
1050
  await strict_1.default.rejects(() => globalThis.attachmentIngress.publish({ ...message, replyAttachments: undefined, reply: async (text) => { failureText = text; } }), /不支持附件回传/);
1051
+ strict_1.default.equal(globalThis.attachmentRequest.attachmentOutput, undefined);
1015
1052
  strict_1.default.match(failureText, /不支持附件回传/);
1016
1053
  }
1017
1054
  finally {
@@ -1248,7 +1285,7 @@ async function withMappedNpmPackage(run) {
1248
1285
  await (0, promises_1.rm)(root, { recursive: true, force: true });
1249
1286
  }
1250
1287
  });
1251
- (0, node_test_1.default)("归档只信任真实通道,管理命令不会执行 Agent 或归档产物", async () => {
1288
+ (0, node_test_1.default)("限制模式下归档只信任真实通道,管理命令不会执行 Agent 或归档产物", async () => {
1252
1289
  const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "manager-session-history-"));
1253
1290
  const manager = new plugin_manager_1.PluginManager({ pluginsDir: root, stateDir: (0, node_path_1.join)(root, "state") });
1254
1291
  let summaryRequest = "";
@@ -1279,7 +1316,7 @@ async function withMappedNpmPackage(run) {
1279
1316
  await strict_1.default.rejects(globalThis.historyIngress_fake.publish(message("伪造")), /通道|来源/);
1280
1317
  await ingress.publish(message("默认不归档"));
1281
1318
  strict_1.default.equal((await manager.sessionManagement.archive.listSessions()).sessions.length, 0);
1282
- await manager.sessionManagement.saveConfig({ enabled: true, grants: [] });
1319
+ await manager.sessionManagement.saveConfig({ enabled: true, allowFrom: ["history-admin.agentid.cn"], grants: [] });
1283
1320
  await ingress.publish(message("正文", "peer", "same-message"));
1284
1321
  await ingress.publish(message("正文", "peer", "same-message"));
1285
1322
  const archived = (await manager.sessionManagement.archive.listSessions()).sessions;
@@ -1294,7 +1331,7 @@ async function withMappedNpmPackage(run) {
1294
1331
  await ingress.publish(message("最近 4 小时有哪些话题", "admin"));
1295
1332
  strict_1.default.match(replies.at(-1), /无权/);
1296
1333
  strict_1.default.equal(globalThis.historyCalls, before);
1297
- await manager.sessionManagement.saveConfig({ enabled: true, grants: [{ managerSource: admin.source, managerSessionId: admin.logicalSessionId, sources: [archived[0].source] }], summarizer: { endpoint: `http://127.0.0.1:${summaryServer.address().port}/v1/chat/completions`, model: "read-only" } });
1334
+ await manager.sessionManagement.saveConfig({ enabled: true, allowFrom: ["history-admin.agentid.cn"], grants: [{ managerSource: admin.source, managerSessionId: admin.logicalSessionId, sources: [archived[0].source] }], summarizer: { endpoint: `http://127.0.0.1:${summaryServer.address().port}/v1/chat/completions`, model: "read-only" } });
1298
1335
  await ingress.publish(message("/sessions list", "admin"));
1299
1336
  const ref = replies.at(-1).split("\n")[0].split(" ")[0];
1300
1337
  await ingress.publish(message(`/sessions summary --from ${ref} 比较内容`, "admin"));
@@ -1308,7 +1345,7 @@ async function withMappedNpmPackage(run) {
1308
1345
  await ingress.publish(message("/sessions list", "admin"));
1309
1346
  strict_1.default.ok(!replies.at(-1).includes("agent2"));
1310
1347
  const sources = (await manager.sessionManagement.archive.listSessions()).sessions.map(item => item.source);
1311
- await manager.sessionManagement.saveConfig({ enabled: true, grants: [{ managerSource: admin.source, managerSessionId: admin.logicalSessionId, sources }], summarizer: { endpoint: `http://127.0.0.1:${summaryServer.address().port}/v1/chat/completions`, model: "read-only" } });
1348
+ await manager.sessionManagement.saveConfig({ enabled: true, allowFrom: ["history-admin.agentid.cn"], grants: [{ managerSource: admin.source, managerSessionId: admin.logicalSessionId, sources }], summarizer: { endpoint: `http://127.0.0.1:${summaryServer.address().port}/v1/chat/completions`, model: "read-only" } });
1312
1349
  await ingress.publish(message("/sessions list", "admin"));
1313
1350
  const refs = replies.at(-1).split("\n").filter(line => /^[a-f0-9]{12}\./.test(line)).map(line => line.split(" ")[0]);
1314
1351
  strict_1.default.equal(refs.length, 2);
@@ -1523,3 +1560,38 @@ async function withMappedNpmPackage(run) {
1523
1560
  }
1524
1561
  }
1525
1562
  });
1563
+ (0, node_test_1.default)("个人助理兼容旧 AUN 的只读身份查询,保留绑定并转交附件回传能力", async () => {
1564
+ const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "mab-pa-legacy-aun-"));
1565
+ const manager = new plugin_manager_1.PluginManager({ pluginsDir: root, stateDir: (0, node_path_1.join)(root, "state") });
1566
+ try {
1567
+ for (const [id, type, capabilities, code] of [
1568
+ ["aun", "channel", ["channel"], "activate:async(c)=>{globalThis.legacyPaIngress=c.ingress},identity:{list:async()=>({evolcore:['worker.agentid.cn'],muAgentBridge:['pa.agentid.cn'],localMuAgentBridge:['local.agentid.cn']})},health:async()=>({status:'ready',details:{bindings:{'worker.agentid.cn':'workbuddy'}}})"],
1569
+ ["personal-assistant", "agent", ["chat", "management"], "handle:async(r)=>{globalThis.legacyPaIdentities=await r.management.listAunIdentities();return r.management.callAgent('workbuddy',r.task)}"],
1570
+ ["workbuddy", "agent", ["chat"], "sessionOperations:['new'],handle:async(r)=>{globalThis.legacyPaTarget=r;await r.onMessage('执行中');return {text:'完成'}}"],
1571
+ ]) {
1572
+ const dir = (0, node_path_1.join)(root, id);
1573
+ await (0, promises_1.mkdir)(dir, { recursive: true });
1574
+ await (0, promises_1.writeFile)((0, node_path_1.join)(dir, "plugin.json"), JSON.stringify({ id, type, capabilities, version: "1.0.0", entry: "index.js", apiVersion: "1" }));
1575
+ await (0, promises_1.writeFile)((0, node_path_1.join)(dir, "index.js"), `module.exports={manifest:require('./plugin.json'),activate:async()=>{},deactivate:async()=>{},health:async()=>({status:'ready'}),${code}};`);
1576
+ }
1577
+ await manager.discover();
1578
+ await manager.saveConfig("workbuddy", { executablePath: process.execPath });
1579
+ await manager.saveConfig("personal-assistant", { allowedPeers: ["*"], allowedAgents: ["workbuddy"] });
1580
+ for (const id of ["workbuddy", "personal-assistant", "aun"])
1581
+ await manager.start(id);
1582
+ const replies = [];
1583
+ await globalThis.legacyPaIngress.publish({ channel: "aun", peerId: "owner.agentid.cn", agentPlugin: "personal-assistant", text: "生成图片", reply: async (s) => { replies.push(s); }, replyAttachments: async () => { } });
1584
+ strict_1.default.deepEqual(globalThis.legacyPaIdentities, { aids: ["local.agentid.cn", "pa.agentid.cn", "worker.agentid.cn"], bindings: { "worker.agentid.cn": "workbuddy" } });
1585
+ strict_1.default.equal(globalThis.legacyPaTarget.attachmentOutput, true);
1586
+ strict_1.default.equal(globalThis.legacyPaTarget.management, undefined);
1587
+ strict_1.default.deepEqual(replies, ["执行中", "完成"]);
1588
+ }
1589
+ finally {
1590
+ for (const id of ["aun", "personal-assistant", "workbuddy"])
1591
+ if (manager.get(id)?.instance)
1592
+ await manager.stop(id);
1593
+ for (const key of ["legacyPaIngress", "legacyPaIdentities", "legacyPaTarget"])
1594
+ delete globalThis[key];
1595
+ await (0, promises_1.rm)(root, { recursive: true, force: true });
1596
+ }
1597
+ });
@@ -0,0 +1,354 @@
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 node_crypto_1 = require("node:crypto");
8
+ const promises_1 = require("node:fs/promises");
9
+ const node_os_1 = require("node:os");
10
+ const node_path_1 = require("node:path");
11
+ const node_test_1 = __importDefault(require("node:test"));
12
+ const agent_1 = require("../src-ts/agent");
13
+ const plugin_manager_1 = require("../src-ts/plugin-manager");
14
+ const version_manager_1 = require("../src-ts/version-manager");
15
+ const TWO_HOURS = 2 * 60 * 60 * 1000;
16
+ const flush = () => new Promise(resolve => setImmediate(resolve));
17
+ async function until(check) {
18
+ for (let i = 0; i < 2_000; i++) {
19
+ if (check())
20
+ return;
21
+ await flush();
22
+ }
23
+ strict_1.default.ok(check(), "等待预期状态超时");
24
+ }
25
+ function deferred() {
26
+ let resolve;
27
+ const promise = new Promise(done => { resolve = done; });
28
+ return { promise, resolve };
29
+ }
30
+ function cancelled(task, pattern = /PLUGIN_UPGRADE_WAIT_TIMEOUT/) {
31
+ const checked = strict_1.default.rejects(task, pattern);
32
+ void checked.catch(() => undefined);
33
+ return checked;
34
+ }
35
+ async function fixture(t, run) {
36
+ const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "mab-upgrade-deadline-"));
37
+ const key = `upgrade-${(0, node_crypto_1.randomUUID)()}`;
38
+ const gate = deferred();
39
+ const cleanup = deferred();
40
+ const state = { contexts: {}, signals: [], stopping: [], cleanupOptions: [], fail: false, holdCleanup: false, ignoreSignal: false };
41
+ state.run = async (request) => {
42
+ state.signals.push(request.signal);
43
+ state.request = request;
44
+ await (state.ignoreSignal ? gate.promise : (0, agent_1.abortable)(gate.promise, request.signal));
45
+ await request.onMessage?.("旧任务的迟到回复");
46
+ return { text: "旧任务完成" };
47
+ };
48
+ globalThis[key] = state;
49
+ const stateDir = (0, node_path_1.join)(root, "state");
50
+ await (0, promises_1.mkdir)((0, node_path_1.join)(stateDir, "npm"), { recursive: true });
51
+ const manager = new plugin_manager_1.PluginManager({ pluginsDir: (0, node_path_1.join)(root, "plugins"), stateDir,
52
+ versions: new version_manager_1.VersionManager({ stateDir, npm: async () => '"1.0.1"' }), publisherResolver: async () => "m2lihui" });
53
+ const pending = [];
54
+ const add = async (id, type = "agent") => {
55
+ for (const [dir, version] of [[(0, node_path_1.join)(root, "plugins", id), "1.0.0"], [(0, node_path_1.join)(stateDir, "downloads", `agent-bridge-${id}`, "1.0.1", "node_modules", `agent-bridge-${id}`), "1.0.1"]]) {
56
+ await (0, promises_1.mkdir)(dir, { recursive: true });
57
+ await (0, promises_1.writeFile)((0, node_path_1.join)(dir, "package.json"), JSON.stringify({ name: `agent-bridge-${id}`, version, agentBridge: { plugin: "./plugin.json" } }));
58
+ await (0, promises_1.writeFile)((0, node_path_1.join)(dir, "plugin.json"), JSON.stringify({ id, type, version, entry: "index.js", apiVersion: "1", capabilities: type === "channel" ? ["channel"] : id === "personal-assistant" ? ["chat", "management"] : ["chat", "model-provider"] }));
59
+ await (0, promises_1.writeFile)((0, node_path_1.join)(dir, "index.js"), `
60
+ const state=globalThis[${JSON.stringify(key)}];
61
+ module.exports=class {
62
+ constructor(){this.pending=new Set();this.modelProvider={complete:r=>this.handle({task:r.prompt,...r})}}
63
+ get sessionOperations(){return ['new']}
64
+ async controlSession(r){return {...await this.handle({task:'/new'}),sessionId:r.sessionId}}
65
+ async activate(c){this.context=c;state.contexts[${JSON.stringify(id)}]=${JSON.stringify(type)}==='channel'?{...c,ingress:{publish:async m=>{const task=c.ingress.publish(m);this.pending.add(task);try{return await task}finally{this.pending.delete(task)}}}}:c;this.dispose=c.http.register('GET','/${id}/deadline',(_q,r)=>r.end('${version}'));if('${version}'==='1.0.1'&&state.fail)throw Error('新版启动失败')}
66
+ async deactivate(reason,options){state.stopping.push('${id}@${version}');state.cleanupOptions.push(options);await Promise.allSettled([...this.pending]);if('${version}'==='1.0.0'&&state.failCleanup){state.failCleanup=false;throw Error('旧插件清理失败')}if('${version}'==='1.0.0'&&state.holdCleanup)await state.cleanup;this.dispose();this.context.http.unregister()}
67
+ async health(){return {status:'ready'}}
68
+ async handle(r){if('${version}'!=='1.0.0')return {text:'新版完成'};const task=state.run(r);this.pending.add(task);try{return await task}finally{this.pending.delete(task)}}
69
+ };`);
70
+ }
71
+ await manager.discover();
72
+ await manager.start(id);
73
+ };
74
+ state.cleanup = cleanup.promise;
75
+ const upgrade = (id = "demo") => {
76
+ const promise = manager.upgradePackage(id, `agent-bridge-${id}`, "1.0.1");
77
+ pending.push(promise);
78
+ void promise.catch(() => undefined);
79
+ return promise;
80
+ };
81
+ try {
82
+ await add("demo");
83
+ t.mock.timers.enable({ apis: ["setTimeout"] });
84
+ await run({ manager, gate, cleanup, state, upgrade, add });
85
+ }
86
+ finally {
87
+ gate.resolve();
88
+ cleanup.resolve();
89
+ t.mock.timers.reset();
90
+ await Promise.allSettled(pending);
91
+ for (const plugin of manager.list())
92
+ if (manager.get(plugin.id)?.instance)
93
+ await manager.stop(plugin.id);
94
+ await manager.logs.flush();
95
+ delete globalThis[key];
96
+ await (0, promises_1.rm)(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 10 });
97
+ }
98
+ }
99
+ (0, node_test_1.default)("插件升级等待满两小时才取消旧任务并继续安装", async (t) => {
100
+ await fixture(t, async ({ manager, state, upgrade }) => {
101
+ const task = manager.handle("demo", { task: "长任务" });
102
+ const failure = cancelled(task, /PLUGIN_UPGRADE_WAIT_TIMEOUT.*两小时/);
103
+ await until(() => state.signals.length === 1);
104
+ const updated = upgrade();
105
+ await until(() => manager.get("demo").record.status === "stopping");
106
+ t.mock.timers.tick(TWO_HOURS - 1);
107
+ await flush();
108
+ strict_1.default.equal(Boolean(state.signals[0]?.aborted), false);
109
+ strict_1.default.equal(manager.get("demo").record.version, "1.0.0");
110
+ strict_1.default.deepEqual(state.stopping, [], "两小时内不能提前调用可能取消任务的 deactivate");
111
+ t.mock.timers.tick(1);
112
+ await flush();
113
+ strict_1.default.equal(state.signals[0]?.aborted, true);
114
+ await failure;
115
+ await updated;
116
+ strict_1.default.deepEqual(state.cleanupOptions, [{ preserveDesktop: true }]);
117
+ strict_1.default.equal((await manager.handle("demo", { task: "新任务" })).text, "新版完成");
118
+ await manager.logs.flush();
119
+ const logs = (await manager.logs.page()).logs;
120
+ strict_1.default.ok(logs.some(log => log.fields?.waitMs === TWO_HOURS && typeof log.fields?.deadline === "string"));
121
+ strict_1.default.ok(logs.some(log => log.fields?.code === "PLUGIN_UPGRADE_WAIT_TIMEOUT"));
122
+ });
123
+ });
124
+ (0, node_test_1.default)("旧任务及时完成则正常升级且清除强制取消定时器", async (t) => {
125
+ await fixture(t, async ({ manager, state, gate, upgrade }) => {
126
+ const task = manager.handle("demo", { task: "短任务" });
127
+ await until(() => state.signals.length === 1);
128
+ const updated = upgrade();
129
+ await until(() => manager.get("demo").record.status === "stopping");
130
+ gate.resolve();
131
+ strict_1.default.equal((await task).text, "旧任务完成");
132
+ await updated;
133
+ t.mock.timers.tick(TWO_HOURS);
134
+ await flush();
135
+ await manager.logs.flush();
136
+ strict_1.default.ok(!(await manager.logs.page()).logs.some(log => log.fields?.code === "PLUGIN_UPGRADE_WAIT_TIMEOUT"));
137
+ strict_1.default.equal((await manager.handle("demo", { task: "新任务" })).text, "新版完成");
138
+ });
139
+ });
140
+ (0, node_test_1.default)("不响应取消的旧任务不能继续阻塞升级、发送迟到回复或注销新版路由", async (t) => {
141
+ await fixture(t, async ({ manager, state, cleanup, gate, upgrade, add }) => {
142
+ await add("other");
143
+ state.ignoreSignal = true;
144
+ state.holdCleanup = true;
145
+ const replies = [];
146
+ const task = manager.handle("demo", { task: "挂起任务", onMessage: async (text) => { replies.push(text); } });
147
+ const failure = cancelled(task);
148
+ await until(() => state.signals.length === 1);
149
+ const oldContext = state.contexts.demo;
150
+ const updated = upgrade();
151
+ await until(() => manager.get("demo").record.status === "stopping");
152
+ t.mock.timers.tick(TWO_HOURS);
153
+ await flush();
154
+ strict_1.default.equal(state.signals[0]?.aborted, true);
155
+ t.mock.timers.tick(5_000);
156
+ await flush();
157
+ await failure;
158
+ await updated;
159
+ let reply = "";
160
+ const readRoute = () => manager.dispatchHttp({ method: "GET", url: "/demo/deadline" }, { end: (text) => { reply = text; } });
161
+ strict_1.default.equal(await readRoute(), true);
162
+ strict_1.default.equal(reply, "1.0.1");
163
+ gate.resolve();
164
+ cleanup.resolve();
165
+ await flush();
166
+ await flush();
167
+ strict_1.default.deepEqual(replies, []);
168
+ strict_1.default.equal(await readRoute(), true);
169
+ strict_1.default.equal(reply, "1.0.1");
170
+ strict_1.default.throws(() => oldContext.http.register("GET", "/stale", () => undefined), /停止|取消|结束/);
171
+ await strict_1.default.rejects(oldContext.callPlugin("other", { task: "过期调用" }), /停止|取消|结束|PLUGIN_UPGRADE_WAIT_TIMEOUT/);
172
+ });
173
+ });
174
+ (0, node_test_1.default)("模型来源任务也在插件升级等待两小时后取消", async (t) => {
175
+ await fixture(t, async ({ manager, state, upgrade }) => {
176
+ const task = manager.completeModelProvider("demo", { prompt: "模型任务", sessionId: "model-session", timeoutMs: 120_000, signal: new AbortController().signal });
177
+ const failure = cancelled(task);
178
+ await until(() => state.signals.length === 1);
179
+ const updated = upgrade();
180
+ await until(() => manager.get("demo").record.status === "stopping");
181
+ t.mock.timers.tick(TWO_HOURS);
182
+ await flush();
183
+ strict_1.default.equal(state.signals[0]?.aborted, true);
184
+ await failure;
185
+ await updated;
186
+ });
187
+ });
188
+ (0, node_test_1.default)("通道升级期限取消来源任务并传递到目标 Agent,保留原会话记录", async (t) => {
189
+ await fixture(t, async ({ manager, state, add, upgrade }) => {
190
+ await add("aun", "channel");
191
+ const source = state.contexts.aun;
192
+ const task = source.ingress.publish({ channel: "aun", peerId: "peer.agentid.cn", agentPlugin: "demo", text: "长任务", reply: async () => undefined });
193
+ const failure = cancelled(task);
194
+ await until(() => state.signals.length === 1);
195
+ const updated = upgrade("aun");
196
+ await until(() => manager.get("aun").record.status === "stopping");
197
+ t.mock.timers.tick(TWO_HOURS);
198
+ await flush();
199
+ strict_1.default.equal(state.signals[0]?.aborted, true);
200
+ await failure;
201
+ await updated;
202
+ strict_1.default.equal(manager.get("demo").record.status, "ready");
203
+ strict_1.default.equal(manager.sessions.isBusy('["aun","","","peer.agentid.cn",""]'), false);
204
+ });
205
+ });
206
+ (0, node_test_1.default)("插件间调用随来源插件的升级期限取消,不取消无关插件实例", async (t) => {
207
+ await fixture(t, async ({ manager, state, add, upgrade }) => {
208
+ await add("parent");
209
+ const original = state.run;
210
+ state.run = (request) => request.task === "父任务"
211
+ ? state.contexts.parent.callPlugin("demo", { task: "子任务" })
212
+ : original(request);
213
+ const task = manager.handle("parent", { task: "父任务" });
214
+ const failure = cancelled(task);
215
+ await until(() => state.signals.length === 1);
216
+ const updated = upgrade("parent");
217
+ await until(() => manager.get("parent").record.status === "stopping");
218
+ t.mock.timers.tick(TWO_HOURS);
219
+ await flush();
220
+ strict_1.default.equal(state.signals[0]?.aborted, true);
221
+ await failure;
222
+ await updated;
223
+ strict_1.default.equal(manager.get("demo").record.status, "ready");
224
+ });
225
+ });
226
+ (0, node_test_1.default)("强制取消后新版启动失败能回滚,下一次升级重新等待两小时", async (t) => {
227
+ await fixture(t, async ({ manager, state, add, upgrade }) => {
228
+ state.fail = true;
229
+ const first = cancelled(manager.handle("demo", { task: "第一项任务" }));
230
+ await until(() => state.signals.length === 1);
231
+ const updated = upgrade();
232
+ const rollback = strict_1.default.rejects(updated, /新版启动失败/);
233
+ await until(() => manager.get("demo").record.status === "stopping");
234
+ t.mock.timers.tick(TWO_HOURS);
235
+ await flush();
236
+ await first;
237
+ await rollback;
238
+ strict_1.default.equal(manager.get("demo").record.version, "1.0.0");
239
+ strict_1.default.equal(manager.get("demo").record.status, "ready");
240
+ strict_1.default.deepEqual(state.cleanupOptions, [{ preserveDesktop: true }, { preserveDesktop: true }]);
241
+ state.fail = false;
242
+ await add("demo");
243
+ const second = cancelled(manager.handle("demo", { task: "第二项任务" }));
244
+ await until(() => state.signals.length === 2);
245
+ strict_1.default.equal(state.signals[1].aborted, false);
246
+ const retried = upgrade();
247
+ await until(() => manager.get("demo").record.status === "stopping");
248
+ t.mock.timers.tick(TWO_HOURS - 1);
249
+ await flush();
250
+ strict_1.default.equal(state.signals[1].aborted, false);
251
+ t.mock.timers.tick(1);
252
+ await flush();
253
+ await second;
254
+ await retried;
255
+ strict_1.default.equal(manager.get("demo").record.version, "1.0.1");
256
+ });
257
+ });
258
+ (0, node_test_1.default)("旧插件清理失败时恢复旧版本并清除升级期限", async (t) => {
259
+ await fixture(t, async ({ manager, state, upgrade }) => {
260
+ state.failCleanup = true;
261
+ await strict_1.default.rejects(upgrade(), /旧插件清理失败/);
262
+ strict_1.default.equal(manager.get("demo").record.version, "1.0.0");
263
+ strict_1.default.equal(manager.get("demo").record.status, "ready");
264
+ t.mock.timers.tick(TWO_HOURS);
265
+ await flush();
266
+ await manager.logs.flush();
267
+ strict_1.default.ok(!(await manager.logs.page()).logs.some(log => log.fields?.code === "PLUGIN_UPGRADE_WAIT_TIMEOUT"));
268
+ });
269
+ });
270
+ (0, node_test_1.default)("普通停止不套用升级的两小时强制取消期限", async (t) => {
271
+ await fixture(t, async ({ manager, state, gate }) => {
272
+ const task = manager.handle("demo", { task: "普通任务" });
273
+ await until(() => state.signals.length === 1);
274
+ const stopping = manager.stop("demo");
275
+ await until(() => state.stopping.length === 1);
276
+ t.mock.timers.tick(TWO_HOURS + 5_000);
277
+ await flush();
278
+ strict_1.default.equal(state.signals[0].aborted, false);
279
+ gate.resolve();
280
+ await task;
281
+ await stopping;
282
+ strict_1.default.deepEqual(state.cleanupOptions, [undefined]);
283
+ strict_1.default.equal(manager.get("demo").record.status, "disabled");
284
+ });
285
+ });
286
+ (0, node_test_1.default)("个人助理升级期限传递到管理能力启动的子 Agent 任务", async (t) => {
287
+ await fixture(t, async ({ manager, state, add, upgrade }) => {
288
+ await add("personal-assistant");
289
+ await add("aun", "channel");
290
+ await manager.saveConfig("personal-assistant", { allowedPeers: ["*"], allowedAgents: ["demo"] });
291
+ const original = state.run;
292
+ state.run = (request) => request.task === "助理任务"
293
+ ? request.management.callAgent("demo", "子任务")
294
+ : original(request);
295
+ const failure = cancelled(state.contexts.aun.ingress.publish({ channel: "aun", peerId: "peer.agentid.cn", agentPlugin: "personal-assistant", text: "助理任务", reply: async () => undefined }));
296
+ await until(() => state.signals.length === 1);
297
+ const updated = upgrade("personal-assistant");
298
+ await until(() => manager.get("personal-assistant").record.status === "stopping");
299
+ t.mock.timers.tick(TWO_HOURS);
300
+ await flush();
301
+ strict_1.default.equal(state.signals[0]?.aborted, true, "助理取消不能留下仍在执行的桥接子任务");
302
+ await failure;
303
+ await updated;
304
+ strict_1.default.equal(manager.get("demo").record.status, "ready");
305
+ });
306
+ });
307
+ (0, node_test_1.default)("挂起的新建会话操作同样受插件升级期限约束", async (t) => {
308
+ await fixture(t, async ({ manager, state, upgrade }) => {
309
+ let rejected = false;
310
+ const task = manager.controlSession("demo", { operation: "new", sessionId: "new-session" }).catch(error => { rejected = true; throw error; });
311
+ const failure = cancelled(task);
312
+ await until(() => state.signals.length === 1);
313
+ const updated = upgrade();
314
+ await until(() => manager.get("demo").record.status === "stopping");
315
+ t.mock.timers.tick(TWO_HOURS);
316
+ await flush();
317
+ strict_1.default.equal(rejected, true);
318
+ t.mock.timers.tick(5_000);
319
+ await flush();
320
+ await failure;
321
+ await updated;
322
+ strict_1.default.equal(manager.get("demo").record.version, "1.0.1");
323
+ });
324
+ });
325
+ for (const delivery of ["文本", "附件"])
326
+ (0, node_test_1.default)(`升级取消挂起的${delivery}回传后释放来源忙碌状态并记录具体错误`, async (t) => {
327
+ await fixture(t, async ({ manager, state, add, gate, upgrade }) => {
328
+ await add("aun", "channel");
329
+ await manager.sessionManagement.saveConfig({ enabled: true, grants: [] });
330
+ state.run = async () => ({ text: "回传结果", ...(delivery === "附件" ? { attachments: [{ kind: "file", data: new Uint8Array([42]), filename: "结果.txt", contentType: "text/plain" }] } : {}) });
331
+ let delivering = false;
332
+ const deliver = async () => { delivering = true; await gate.promise; };
333
+ const message = { channel: "aun", peerId: "peer.agentid.cn", agentPlugin: "demo", text: "生成文件", reply: deliver, replyAttachments: deliver };
334
+ const failure = cancelled(state.contexts.aun.ingress.publish(message));
335
+ await until(() => delivering);
336
+ const binding = await manager.sessions.current(message, "demo");
337
+ const updated = upgrade("aun");
338
+ await until(() => manager.get("aun").record.status === "stopping");
339
+ t.mock.timers.tick(TWO_HOURS);
340
+ await flush();
341
+ t.mock.timers.tick(5_000);
342
+ await flush();
343
+ await failure;
344
+ await updated;
345
+ const sessions = await manager.sessionManagement.command("/sessions list", { source: binding.source, sessionId: "另一个管理会话" });
346
+ strict_1.default.match(sessions, new RegExp(binding.logicalSessionId));
347
+ strict_1.default.doesNotMatch(sessions, /正在运行/);
348
+ strict_1.default.ok(manager.recentMessages().some(item => item.direction === "error" && item.text.includes("PLUGIN_UPGRADE_WAIT_TIMEOUT")));
349
+ gate.resolve();
350
+ await flush();
351
+ strict_1.default.ok(!manager.recentMessages().some(item => item.direction === "reply"));
352
+ strict_1.default.equal((await manager.sessionManagement.archive.snapshotSessions()).sessions.length, 0);
353
+ });
354
+ });
@@ -16,7 +16,7 @@ const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
16
16
  const bin = (0, node_path_1.join)(root, "bin");
17
17
  const packageRoot = (0, node_path_1.join)(root, "packages", "personal-assistant");
18
18
  try {
19
- const ids = ["qwenwork", "abweb", "dsh", "doubao", "aun", "workbuddy", "feishu", "chatgpt", "codex-cli", "claude-cli", "personal-assistant"];
19
+ const ids = ["qwenwork", "abweb", "dsh", "doubao", "aun", "workbuddy", "zcode", "feishu", "chatgpt", "codex-cli", "claude-cli", "personal-assistant"];
20
20
  await (0, promises_1.writeFile)((0, node_path_1.join)(root, "package.json"), JSON.stringify({ name: "mu-agent-bridge", version: "1.0.0" }));
21
21
  for (const id of ids) {
22
22
  const directory = (0, node_path_1.join)(root, "packages", id);
@@ -251,6 +251,36 @@ const cdp_client_1 = require("../src-ts/plugins/qwenwork/cdp-client");
251
251
  await plugin.deactivate("stop");
252
252
  strict_1.default.equal((await plugin.health()).status, "stopped");
253
253
  });
254
+ (0, node_test_1.default)("千问办公仅在重载时请求结束桌面进程", async () => {
255
+ let restarts = 0;
256
+ const client = {
257
+ connect: async () => undefined,
258
+ close: () => undefined,
259
+ connected: () => true,
260
+ send: async () => undefined,
261
+ direct: async () => "回复",
262
+ };
263
+ const plugin = new qwenwork_1.QwenworkPlugin({ client, httpPort: 0, restartDesktop: async () => { restarts++; } });
264
+ await plugin.activate({ config: {}, dataDir: "." });
265
+ await plugin.deactivate("reload");
266
+ strict_1.default.equal(restarts, 1);
267
+ await plugin.activate({ config: {}, dataDir: "." });
268
+ await plugin.deactivate("stop");
269
+ strict_1.default.equal(restarts, 1);
270
+ });
271
+ (0, node_test_1.default)("千问办公升级清理保留桌面进程", async () => {
272
+ let restarts = 0;
273
+ let closed = 0;
274
+ const plugin = new qwenwork_1.QwenworkPlugin({
275
+ httpPort: 0, restartDesktop: async () => { restarts++; },
276
+ client: { connect: async () => undefined, close: () => { closed++; }, connected: () => true, send: async () => undefined, direct: async () => "回复" },
277
+ });
278
+ await plugin.activate({ config: {}, dataDir: "." });
279
+ await plugin.deactivate("reload", { preserveDesktop: true });
280
+ strict_1.default.equal(restarts, 0);
281
+ strict_1.default.equal(closed, 1);
282
+ strict_1.default.equal((await plugin.health()).status, "stopped");
283
+ });
254
284
  (0, node_test_1.default)("千问办公 /new 调用真实新任务并返回逻辑会话标识", async () => {
255
285
  let created = 0;
256
286
  const plugin = new qwenwork_1.QwenworkPlugin({