mu-agent-bridge 1.0.34 → 1.0.39

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 (89) hide show
  1. package/README.md +4 -2
  2. package/dist/src-ts/channel-sessions.js +90 -24
  3. package/dist/src-ts/control-server.js +48 -7
  4. package/dist/src-ts/dsh-agent.js +141 -46
  5. package/dist/src-ts/headless-account.patch.yml +8 -1
  6. package/dist/src-ts/plugin-manager.js +69 -3
  7. package/dist/src-ts/plugins/aun/attachments.js +44 -13
  8. package/dist/src-ts/plugins/aun/index.js +5 -6
  9. package/dist/src-ts/plugins/chatgpt/cdp-client.js +342 -9
  10. package/dist/src-ts/plugins/chatgpt/index.js +127 -7
  11. package/dist/src-ts/plugins/desktop-launch.js +19 -0
  12. package/dist/src-ts/plugins/doubao/attachments.js +42 -0
  13. package/dist/src-ts/plugins/doubao/cdp-client.js +530 -48
  14. package/dist/src-ts/plugins/doubao/index.js +114 -25
  15. package/dist/src-ts/plugins/doubao/session-history.js +123 -0
  16. package/dist/src-ts/plugins/dsh/index.js +18 -2
  17. package/dist/src-ts/plugins/dsh/session-history.js +371 -0
  18. package/dist/src-ts/plugins/qwenwork/cdp-client.js +271 -10
  19. package/dist/src-ts/plugins/qwenwork/index.js +42 -3
  20. package/dist/src-ts/plugins/qwenwork/session-history.js +245 -0
  21. package/dist/src-ts/plugins/workbuddy/cdp-client.js +42 -2
  22. package/dist/src-ts/plugins/workbuddy/index.js +23 -3
  23. package/dist/src-ts/plugins/workbuddy/session-history.js +121 -0
  24. package/dist/src-ts/session-archive.js +509 -0
  25. package/dist/src-ts/session-history.js +2 -0
  26. package/dist/src-ts/session-management.js +665 -0
  27. package/dist/src-ts/session-summary.js +171 -0
  28. package/dist/tests-ts/aun-attachments.test.js +41 -3
  29. package/dist/tests-ts/aun-plugin.test.js +28 -5
  30. package/dist/tests-ts/bundled-package.test.js +3 -1
  31. package/dist/tests-ts/channel-sessions.test.js +255 -2
  32. package/dist/tests-ts/chatgpt-history.test.js +197 -0
  33. package/dist/tests-ts/chatgpt-plugin.test.js +279 -2
  34. package/dist/tests-ts/control-server.test.js +39 -0
  35. package/dist/tests-ts/desktop-launch.test.js +41 -0
  36. package/dist/tests-ts/doubao-history.test.js +172 -0
  37. package/dist/tests-ts/doubao-plugin.test.js +549 -2
  38. package/dist/tests-ts/dsh-agent.test.js +199 -0
  39. package/dist/tests-ts/dsh-history.test.js +165 -0
  40. package/dist/tests-ts/native-session-management.test.js +195 -0
  41. package/dist/tests-ts/plugin-manager.test.js +354 -4
  42. package/dist/tests-ts/qwenwork-history.test.js +339 -0
  43. package/dist/tests-ts/qwenwork-images.test.js +166 -0
  44. package/dist/tests-ts/qwenwork-plugin.test.js +40 -1
  45. package/dist/tests-ts/session-archive.test.js +422 -0
  46. package/dist/tests-ts/session-management.test.js +323 -0
  47. package/dist/tests-ts/session-summary.test.js +199 -0
  48. package/dist/tests-ts/workbuddy-history.test.js +256 -0
  49. package/dist/tests-ts/workbuddy-plugin.test.js +43 -0
  50. package/package.json +1 -1
  51. package/plugins/aun/dist/src-ts/plugins/aun/attachments.js +44 -13
  52. package/plugins/aun/dist/src-ts/plugins/aun/index.js +5 -6
  53. package/plugins/aun/package.json +1 -1
  54. package/plugins/aun/plugin.json +1 -1
  55. package/plugins/chatgpt/dist/src-ts/attachments.js +62 -0
  56. package/plugins/chatgpt/dist/src-ts/plugins/chatgpt/cdp-client.js +342 -9
  57. package/plugins/chatgpt/dist/src-ts/plugins/chatgpt/index.js +127 -7
  58. package/plugins/chatgpt/package.json +1 -1
  59. package/plugins/chatgpt/plugin.json +14 -1
  60. package/plugins/doubao/config.schema.json +3 -0
  61. package/plugins/doubao/dist/src-ts/attachments.js +62 -0
  62. package/plugins/doubao/dist/src-ts/plugins/desktop-launch.js +19 -0
  63. package/plugins/doubao/dist/src-ts/plugins/doubao/attachments.js +42 -0
  64. package/plugins/doubao/dist/src-ts/plugins/doubao/cdp-client.js +530 -48
  65. package/plugins/doubao/dist/src-ts/plugins/doubao/index.js +114 -25
  66. package/plugins/doubao/dist/src-ts/plugins/doubao/session-history.js +123 -0
  67. package/plugins/doubao/package.json +1 -1
  68. package/plugins/doubao/plugin.json +1 -1
  69. package/plugins/dsh/config.schema.json +2 -1
  70. package/plugins/dsh/dist/src-ts/attachments.js +62 -0
  71. package/plugins/dsh/dist/src-ts/dsh-agent.js +141 -46
  72. package/plugins/dsh/dist/src-ts/headless-account.patch.yml +8 -1
  73. package/plugins/dsh/dist/src-ts/plugins/dsh/index.js +18 -2
  74. package/plugins/dsh/dist/src-ts/plugins/dsh/session-history.js +371 -0
  75. package/plugins/dsh/package.json +5 -2
  76. package/plugins/dsh/plugin.json +1 -1
  77. package/plugins/qwenwork/dist/src-ts/attachments.js +62 -0
  78. package/plugins/qwenwork/dist/src-ts/plugins/desktop-launch.js +19 -0
  79. package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/cdp-client.js +271 -10
  80. package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/index.js +42 -3
  81. package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/session-history.js +245 -0
  82. package/plugins/qwenwork/package.json +1 -1
  83. package/plugins/qwenwork/plugin.json +1 -1
  84. package/plugins/workbuddy/config.schema.json +1 -1
  85. package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/cdp-client.js +42 -2
  86. package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/index.js +23 -3
  87. package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/session-history.js +121 -0
  88. package/plugins/workbuddy/package.json +1 -1
  89. package/plugins/workbuddy/plugin.json +1 -1
@@ -41,9 +41,73 @@ const promises_1 = require("node:fs/promises");
41
41
  const node_os_1 = require("node:os");
42
42
  const node_path_1 = require("node:path");
43
43
  const node_test_1 = __importDefault(require("node:test"));
44
+ const node_http_1 = require("node:http");
44
45
  const plugin_manager_1 = require("../src-ts/plugin-manager");
45
46
  const version_manager_1 = require("../src-ts/version-manager");
46
47
  const channel_sessions_1 = require("../src-ts/channel-sessions");
48
+ (0, node_test_1.default)("插件重载同时刷新内部依赖,其他插件的缓存保持不变", async () => {
49
+ const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "mab-reload-dependency-"));
50
+ const manager = new plugin_manager_1.PluginManager({ pluginsDir: (0, node_path_1.join)(root, "plugins"), stateDir: (0, node_path_1.join)(root, "state") });
51
+ try {
52
+ for (const id of ["demo", "other"]) {
53
+ const dir = (0, node_path_1.join)(root, "plugins", id);
54
+ await (0, promises_1.mkdir)(dir, { recursive: true });
55
+ await (0, promises_1.writeFile)((0, node_path_1.join)(dir, "plugin.json"), JSON.stringify({ id, version: "1.0.0", entry: "index.js", apiVersion: "1", capabilities: ["chat"] }));
56
+ await (0, promises_1.writeFile)((0, node_path_1.join)(dir, "client.js"), "module.exports = { text: '旧版' };");
57
+ await (0, promises_1.writeFile)((0, node_path_1.join)(dir, "index.js"), "const client=require('./client');module.exports={activate:async()=>{},deactivate:async()=>{},health:async()=>({status:'ready'}),handle:async()=>({text:client.text})};");
58
+ }
59
+ await manager.discover();
60
+ await manager.start("demo");
61
+ await manager.start("other");
62
+ strict_1.default.equal((await manager.handle("demo", { task: "请求" })).text, "旧版");
63
+ for (const id of ["demo", "other"])
64
+ await (0, promises_1.writeFile)((0, node_path_1.join)(root, "plugins", id, "client.js"), "module.exports = { text: '新版' };");
65
+ await manager.reload("demo");
66
+ strict_1.default.equal((await manager.handle("demo", { task: "请求" })).text, "新版");
67
+ strict_1.default.equal((await manager.handle("other", { task: "请求" })).text, "旧版");
68
+ }
69
+ finally {
70
+ for (const id of ["demo", "other"])
71
+ if (manager.get(id)?.instance)
72
+ await manager.stop(id);
73
+ await (0, promises_1.rm)(root, { recursive: true, force: true });
74
+ }
75
+ });
76
+ (0, node_test_1.default)("相同插件版本的并发升级只下载和重载一次", async () => {
77
+ const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "mab-concurrent-update-"));
78
+ const name = "agent-bridge-demo";
79
+ const manager = new plugin_manager_1.PluginManager({ pluginsDir: (0, node_path_1.join)(root, "plugins"), stateDir: (0, node_path_1.join)(root, "state"),
80
+ versions: new version_manager_1.VersionManager({ stateDir: (0, node_path_1.join)(root, "state"), npm: async () => '"1.0.1"' }), publisherResolver: async () => "m2lihui" });
81
+ let release;
82
+ const gate = new Promise(resolve => { release = resolve; });
83
+ let downloads = 0;
84
+ let results;
85
+ try {
86
+ for (const [dir, version] of [[(0, node_path_1.join)(root, "plugins", "demo"), "1.0.0"], [(0, node_path_1.join)(root, "download", "node_modules", name), "1.0.1"]]) {
87
+ await (0, promises_1.mkdir)(dir, { recursive: true });
88
+ await (0, promises_1.writeFile)((0, node_path_1.join)(dir, "package.json"), JSON.stringify({ name, version, agentBridge: { plugin: "./plugin.json" } }));
89
+ await (0, promises_1.writeFile)((0, node_path_1.join)(dir, "plugin.json"), JSON.stringify({ id: "demo", version, entry: "index.js", apiVersion: "1", capabilities: [] }));
90
+ await (0, promises_1.writeFile)((0, node_path_1.join)(dir, "index.js"), "module.exports={activate:async()=>{},deactivate:async()=>{},health:async()=>({status:'ready'})};");
91
+ }
92
+ await manager.discover();
93
+ await manager.start("demo");
94
+ manager.downloadPackage = async () => { downloads++; await gate; return (0, node_path_1.join)(root, "download"); };
95
+ results = Promise.allSettled([manager.upgradePackage("demo", name, "1.0.1"), manager.upgradePackage("demo", name, "1.0.1")]);
96
+ for (let i = 0; i < 100 && downloads === 0; i++)
97
+ await new Promise(resolve => setTimeout(resolve, 1));
98
+ await new Promise(resolve => setTimeout(resolve, 5));
99
+ strict_1.default.equal(downloads, 1);
100
+ release();
101
+ strict_1.default.ok((await results).every(result => result.status === "fulfilled"));
102
+ }
103
+ finally {
104
+ release();
105
+ await results;
106
+ if (manager.get("demo")?.instance)
107
+ await manager.stop("demo");
108
+ await (0, promises_1.rm)(root, { recursive: true, force: true });
109
+ }
110
+ });
47
111
  (0, node_test_1.default)("随包插件检查独立 npm 更新,升级后重启仍选新副本,失败恢复内置版本", async () => {
48
112
  const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "mab-bundled-update-"));
49
113
  const stateDir = (0, node_path_1.join)(root, "state");
@@ -514,7 +578,7 @@ const channel_sessions_1 = require("../src-ts/channel-sessions");
514
578
  await (0, promises_1.writeFile)((0, node_path_1.join)(dir, "plugin.json"), JSON.stringify({ id, version: "1.0.0", type: source ? "channel" : "agent", entry: "index.js", apiVersion: "1", capabilities: source ? ["channel"] : ["chat"] }));
515
579
  const code = source
516
580
  ? "module.exports={manifest:require('./plugin.json'),activate:async(c)=>{globalThis.testIngress=c.ingress},deactivate:async()=>{},health:async()=>({status:'ready'}),handle:async()=>({text:''})};"
517
- : "module.exports={manifest:require('./plugin.json'),activate:async()=>{},deactivate:async()=>{},health:async()=>({status:'ready'}),handle:async(r)=>({text:'回复:'+r.task,...(r.task==='画猫'?{images:[{data:Buffer.from([137,80,78,71,13,10,26,10]),filename:'cat.png',contentType:'image/png'}]}:{})})};";
581
+ : "module.exports={manifest:require('./plugin.json'),activate:async()=>{},deactivate:async()=>{},health:async()=>({status:'ready'}),handle:async(r)=>{if(r.task==='中间消息'){await r.onMessage('请补充视频时长');await r.onMessage('请补充视频时长');return {text:'已完成'}}return {text:'回复:'+r.task,...(r.task==='画猫'?{images:[{data:Buffer.from([137,80,78,71,13,10,26,10]),filename:'cat.png',contentType:'image/png'}]}:{})}}};";
518
582
  await (0, promises_1.writeFile)((0, node_path_1.join)(dir, "index.js"), code);
519
583
  }
520
584
  const manager = new plugin_manager_1.PluginManager({ pluginsDir: root, stateDir: (0, node_path_1.join)(root, "state") });
@@ -525,11 +589,14 @@ const channel_sessions_1 = require("../src-ts/channel-sessions");
525
589
  const ingress = globalThis.testIngress;
526
590
  await ingress.publish({ channel: "aun", peerId: "peer.agentid.pub", text: "你好", reply: async (text) => { reply = text; } });
527
591
  strict_1.default.equal(reply, "回复:你好");
592
+ const forwarded = [];
593
+ await ingress.publish({ channel: "aun", peerId: "peer.agentid.pub", text: "中间消息", reply: async (text) => { forwarded.push(text); } });
594
+ strict_1.default.deepEqual(forwarded, ["请补充视频时长", "已完成"]);
528
595
  const images = [];
529
596
  await ingress.publish({ channel: "aun", peerId: "peer.agentid.pub", text: "画猫", reply: async () => { strict_1.default.fail("图片不应作为纯文本丢弃"); }, replyAttachments: async (files, text) => { images.push(...files); strict_1.default.equal(text, "回复:画猫"); } });
530
597
  strict_1.default.equal(images[0].filename, "cat.png");
531
598
  strict_1.default.deepEqual([...images[0].data], [137, 80, 78, 71, 13, 10, 26, 10]);
532
- await strict_1.default.rejects(ingress.publish({ channel: "feishu", peerId: "peer", text: "画猫", reply: async (text) => { strict_1.default.match(text, /不支持附件回传/); } }), /不支持附件回传/);
599
+ await strict_1.default.rejects(ingress.publish({ channel: "aun", peerId: "peer", text: "画猫", reply: async (text) => { strict_1.default.match(text, /不支持附件回传/); } }), /不支持附件回传/);
533
600
  await manager.stop("aun");
534
601
  await manager.stop("qwenwork");
535
602
  }
@@ -575,7 +642,7 @@ const channel_sessions_1 = require("../src-ts/channel-sessions");
575
642
  const png = { data: new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]), filename: "图.png", contentType: "image/png" };
576
643
  globalThis.attachmentResponse = { text: "生成结果", images: [png] };
577
644
  let received;
578
- const message = { channel: "aun", peerId: "peer", text: "原文", attachments: [item], agentPlugin: "agent", reply: async () => { throw new Error("不应退回纯文本"); }, replyAttachments: async (items, text) => { received = { items, text }; } };
645
+ const message = { channel: "source", peerId: "peer", text: "原文", attachments: [item], agentPlugin: "agent", reply: async () => { throw new Error("不应退回纯文本"); }, replyAttachments: async (items, text) => { received = { items, text }; } };
579
646
  await globalThis.attachmentIngress.publish(message);
580
647
  strict_1.default.equal(globalThis.attachmentRequest.task, "原文");
581
648
  strict_1.default.deepEqual(globalThis.attachmentRequest.attachments, [item]);
@@ -610,7 +677,7 @@ const channel_sessions_1 = require("../src-ts/channel-sessions");
610
677
  await manager.start("source");
611
678
  const ingress = globalThis.newIngress;
612
679
  const replies = [];
613
- const message = (text) => ({ channel: "aun", peerId: "peer", text, agentPlugin: "agent", reply: async (value) => { replies.push(value); } });
680
+ const message = (text) => ({ channel: "source", peerId: "peer", text, agentPlugin: "agent", reply: async (value) => { replies.push(value); } });
614
681
  await ingress.publish(message("hello"));
615
682
  await ingress.publish(message("/new 新会话"));
616
683
  await ingress.publish(message("again"));
@@ -635,6 +702,39 @@ const channel_sessions_1 = require("../src-ts/channel-sessions");
635
702
  await (0, promises_1.rm)(root, { recursive: true, force: true });
636
703
  }
637
704
  });
705
+ (0, node_test_1.default)("DSH 式插件由通用层创建新会话并把后续消息绑定到新逻辑 ID", async () => {
706
+ const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "agent-bridge-generic-new-"));
707
+ try {
708
+ const agent = (0, node_path_1.join)(root, "dsh");
709
+ await (0, promises_1.mkdir)(agent, { recursive: true });
710
+ await (0, promises_1.writeFile)((0, node_path_1.join)(agent, "plugin.json"), JSON.stringify({ id: "dsh", version: "1.0.0", type: "agent", entry: "index.js", apiVersion: "1", capabilities: ["chat"] }));
711
+ await (0, promises_1.writeFile)((0, node_path_1.join)(agent, "index.js"), `let count=0;const known=new Set();module.exports={manifest:require('./plugin.json'),sessionOperations:['new'],activate:async()=>{},deactivate:async()=>{},health:async()=>({status:'ready'}),handle:async(r)=>{const id=r.newSession?'backend-'+(++count):r.sessionId;if(!r.newSession&&!known.has(id))throw Error('后端会话不存在');known.add(id);return {text:id,sessionId:id}}};`);
712
+ const channel = (0, node_path_1.join)(root, "aun");
713
+ await (0, promises_1.mkdir)(channel, { recursive: true });
714
+ await (0, promises_1.writeFile)((0, node_path_1.join)(channel, "plugin.json"), JSON.stringify({ id: "aun", version: "1.0.0", type: "channel", entry: "index.js", apiVersion: "1", capabilities: ["channel"] }));
715
+ await (0, promises_1.writeFile)((0, node_path_1.join)(channel, "index.js"), "module.exports={manifest:require('./plugin.json'),activate:async(c)=>{globalThis.genericNewIngress=c.ingress},deactivate:async()=>{},health:async()=>({status:'ready'}),handle:async()=>({text:''})};");
716
+ const manager = new plugin_manager_1.PluginManager({ pluginsDir: root, stateDir: (0, node_path_1.join)(root, "state") });
717
+ await manager.discover();
718
+ await manager.start("dsh");
719
+ await manager.start("aun");
720
+ const replies = [];
721
+ const ingress = globalThis.genericNewIngress;
722
+ const message = (text) => ({ channel: "aun", peerId: "peer", text, agentPlugin: "dsh", reply: async (value) => { replies.push(value); } });
723
+ await ingress.publish(message("before"));
724
+ const previous = replies[0];
725
+ await ingress.publish(message("/new"));
726
+ await ingress.publish(message("after"));
727
+ strict_1.default.equal(replies[1], "dsh 已创建新会话");
728
+ strict_1.default.notEqual(replies[2], previous);
729
+ strict_1.default.equal(replies[2], (await manager.sessions.list())[0].sessionId);
730
+ await manager.stop("aun");
731
+ await manager.stop("dsh");
732
+ }
733
+ finally {
734
+ delete globalThis.genericNewIngress;
735
+ await (0, promises_1.rm)(root, { recursive: true, force: true });
736
+ }
737
+ });
638
738
  (0, node_test_1.default)("插件控制协议拒绝非字符串或空白的新会话标识", async () => {
639
739
  const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "agent-bridge-invalid-new-"));
640
740
  try {
@@ -785,3 +885,253 @@ const channel_sessions_1 = require("../src-ts/channel-sessions");
785
885
  await (0, promises_1.rm)(root, { recursive: true, force: true });
786
886
  }
787
887
  });
888
+ (0, node_test_1.default)("归档只信任真实通道,管理命令不会执行 Agent 或归档产物", async () => {
889
+ const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "manager-session-history-"));
890
+ const manager = new plugin_manager_1.PluginManager({ pluginsDir: root, stateDir: (0, node_path_1.join)(root, "state") });
891
+ let summaryRequest = "";
892
+ const summaryServer = (0, node_http_1.createServer)(async (request, response) => {
893
+ for await (const chunk of request)
894
+ summaryRequest += chunk;
895
+ response.setHeader("content-type", "application/json");
896
+ response.end(JSON.stringify({ choices: [{ message: { content: "只读摘要结果" } }] }));
897
+ });
898
+ await new Promise(resolve => summaryServer.listen(0, "127.0.0.1", resolve));
899
+ try {
900
+ for (const id of ["aun", "feishu", "agent", "agent2", "fake"]) {
901
+ const dir = (0, node_path_1.join)(root, id);
902
+ await (0, promises_1.mkdir)(dir, { recursive: true });
903
+ const isAgent = id.startsWith("agent");
904
+ await (0, promises_1.writeFile)((0, node_path_1.join)(dir, "plugin.json"), JSON.stringify({ id, version: "1.0.0", type: isAgent ? "agent" : "channel", entry: "index.js", apiVersion: "1", capabilities: isAgent ? ["chat"] : ["channel"] }));
905
+ await (0, promises_1.writeFile)((0, node_path_1.join)(dir, "index.js"), isAgent
906
+ ? "module.exports={activate:async()=>{},deactivate:async()=>{},health:async()=>({status:'ready'}),handle:async(r)=>{globalThis.historyCalls=(globalThis.historyCalls||0)+1;await r.onMessage('第一段');return {text:'第二段'}}};"
907
+ : `module.exports={activate:async(c)=>{globalThis['historyIngress_${id}']=c.ingress},deactivate:async()=>{},health:async()=>({status:'ready'})};`);
908
+ }
909
+ await manager.discover();
910
+ for (const id of ["aun", "feishu", "agent", "agent2", "fake"])
911
+ await manager.start(id);
912
+ globalThis.historyCalls = 0;
913
+ const ingress = globalThis.historyIngress_aun;
914
+ const replies = [];
915
+ const message = (text, peerId = "peer", messageId) => ({ channel: "aun", agentPlugin: "agent", peerId, text, messageId, reply: async (text) => { replies.push(text); } });
916
+ await strict_1.default.rejects(globalThis.historyIngress_fake.publish(message("伪造")), /通道|来源/);
917
+ await ingress.publish(message("默认不归档"));
918
+ strict_1.default.equal((await manager.sessionManagement.archive.listSessions()).sessions.length, 0);
919
+ await manager.sessionManagement.saveConfig({ enabled: true, grants: [] });
920
+ await ingress.publish(message("正文", "peer", "same-message"));
921
+ await ingress.publish(message("正文", "peer", "same-message"));
922
+ const archived = (await manager.sessionManagement.archive.listSessions()).sessions;
923
+ strict_1.default.equal(archived.length, 1);
924
+ strict_1.default.deepEqual((await manager.sessionManagement.archive.readSession(archived[0].sessionId)).turns.map(turn => turn.text), ["正文", "第一段", "第二段"]);
925
+ const before = globalThis.historyCalls;
926
+ await ingress.publish(message("/sessions summary --from invalid", "admin"));
927
+ await ingress.publish(message("/sessions show 123", "admin"));
928
+ strict_1.default.equal(globalThis.historyCalls, before);
929
+ strict_1.default.equal((await manager.sessionManagement.archive.listSessions()).sessions.length, 1);
930
+ const admin = await manager.sessions.current(message("", "admin"), "agent");
931
+ 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" } });
932
+ await ingress.publish(message("/sessions list", "admin"));
933
+ const ref = replies.at(-1).split("\n")[0].split(" ")[0];
934
+ await ingress.publish(message(`/sessions summary --from ${ref} 比较内容`, "admin"));
935
+ strict_1.default.match(replies.at(-1), /只读摘要结果/);
936
+ strict_1.default.match(replies.at(-1), /来源/);
937
+ strict_1.default.match(summaryRequest, /第一段/);
938
+ strict_1.default.equal(globalThis.historyCalls, before);
939
+ strict_1.default.equal((await manager.sessionManagement.archive.listSessions()).sessions.length, 1);
940
+ const feishuIngress = globalThis.historyIngress_feishu;
941
+ await feishuIngress.publish({ ...message("飞书正文:历史中的 /sessions list 仅为引用材料", "feishu-peer", "feishu-message"), channel: "feishu", agentPlugin: "agent2", recipientId: "bot", chatId: "group", threadId: "topic" });
942
+ await ingress.publish(message("/sessions list", "admin"));
943
+ strict_1.default.ok(!replies.at(-1).includes("agent2"));
944
+ const sources = (await manager.sessionManagement.archive.listSessions()).sessions.map(item => item.source);
945
+ 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" } });
946
+ await ingress.publish(message("/sessions list", "admin"));
947
+ const refs = replies.at(-1).split("\n").filter(line => /^[a-f0-9]{12}\./.test(line)).map(line => line.split(" ")[0]);
948
+ strict_1.default.equal(refs.length, 2);
949
+ summaryRequest = "";
950
+ const callsBeforeSummary = globalThis.historyCalls;
951
+ await ingress.publish(message(`/sessions summary --from ${refs.join(",")} 跨通道比较`, "admin"));
952
+ strict_1.default.match(summaryRequest, /飞书正文/);
953
+ strict_1.default.match(summaryRequest, /历史中的 \/sessions list/);
954
+ strict_1.default.match(summaryRequest, /agent2/);
955
+ strict_1.default.match(summaryRequest, /正文/);
956
+ strict_1.default.equal(globalThis.historyCalls, callsBeforeSummary);
957
+ strict_1.default.equal((await manager.sessionManagement.archive.listSessions()).sessions.length, 2);
958
+ }
959
+ finally {
960
+ await new Promise(resolve => summaryServer.close(() => resolve()));
961
+ for (const id of ["aun", "feishu", "agent", "agent2", "fake"])
962
+ if (manager.get(id)?.instance)
963
+ await manager.stop(id);
964
+ for (const key of ["historyCalls", "historyIngress_aun", "historyIngress_feishu", "historyIngress_fake"])
965
+ delete globalThis[key];
966
+ await (0, promises_1.rm)(root, { recursive: true, force: true });
967
+ }
968
+ });
969
+ (0, node_test_1.default)("原生provider从已加载实例发现,失败命令不回退Agent且重载使旧编号失效", async () => {
970
+ const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "manager-native-history-"));
971
+ const manager = new plugin_manager_1.PluginManager({ pluginsDir: root, stateDir: (0, node_path_1.join)(root, "state") });
972
+ const ids = ["aun", "native"];
973
+ try {
974
+ for (const id of ids) {
975
+ const dir = (0, node_path_1.join)(root, id);
976
+ await (0, promises_1.mkdir)(dir, { recursive: true });
977
+ await (0, promises_1.writeFile)((0, node_path_1.join)(dir, "plugin.json"), JSON.stringify({ id, version: "1.0.0", type: id === "aun" ? "channel" : "agent", entry: "index.js", apiVersion: "1", capabilities: id === "aun" ? ["channel"] : ["chat"] }));
978
+ await (0, promises_1.writeFile)((0, node_path_1.join)(dir, "index.js"), id === "aun"
979
+ ? "module.exports={activate:async(c)=>{globalThis.nativeIngress=c.ingress},deactivate:async()=>{},health:async()=>({status:'ready'})};"
980
+ : `module.exports={sessionHistory:{scope:'native:local',mode:'current',limitation:'仅当前会话',listSessions:async()=>{if(globalThis.nativeFailure)throw new Error('secret');return {supported:true,sessions:[{id:'one',title:'原生标题'}],complete:false,missingReasons:['仅当前会话'],readAt:new Date().toISOString()}},readSession:async()=>({supported:true,session:{id:'one',title:'原生标题'},turns:[{role:'user',text:'原生正文'}],complete:false,missingReasons:['时间未知'],readAt:new Date().toISOString()})},activate:async()=>{},deactivate:async()=>{},health:async()=>({status:'ready'}),handle:async()=>{globalThis.nativeHandleCalls=(globalThis.nativeHandleCalls||0)+1;return {text:'普通回复'}}};`);
981
+ }
982
+ await manager.discover();
983
+ for (const id of ids)
984
+ await manager.start(id);
985
+ strict_1.default.deepEqual(manager.sessionManagement.providers(), [{ pluginId: "native", scope: "native:local", mode: "current", limitation: "仅当前会话", status: "ready" }]);
986
+ const replies = [];
987
+ const message = (text) => ({ channel: "aun", agentPlugin: "native", peerId: "admin", text, reply: async (text) => { replies.push(text); } });
988
+ const current = await manager.sessions.current(message(""), "native");
989
+ await manager.sessionManagement.saveConfig({ enabled: false, grants: [], nativeGrants: [{ managerSource: current.source, managerSessionId: current.logicalSessionId, providers: [{ pluginId: "native", scope: "native:local" }] }] });
990
+ const ingress = globalThis.nativeIngress;
991
+ await ingress.publish(message("/sessions native-list native"));
992
+ const ref = replies.at(-1).match(/[a-f0-9]{12}\.1/)[0];
993
+ await ingress.publish(message(`/sessions native-show ${ref}`));
994
+ strict_1.default.match(replies.at(-1), /原生正文/);
995
+ await manager.reload("native");
996
+ await ingress.publish(message(`/sessions native-show ${ref}`));
997
+ strict_1.default.match(replies.at(-1), /编号失效/);
998
+ globalThis.nativeFailure = true;
999
+ await ingress.publish(message("/sessions native-list native"));
1000
+ strict_1.default.ok(!replies.at(-1).includes("secret"));
1001
+ strict_1.default.equal(globalThis.nativeHandleCalls, undefined);
1002
+ strict_1.default.equal((await manager.sessionManagement.archive.listSessions()).sessions.length, 0);
1003
+ }
1004
+ finally {
1005
+ for (const id of ids)
1006
+ if (manager.get(id)?.instance)
1007
+ await manager.stop(id);
1008
+ for (const key of ["nativeIngress", "nativeFailure", "nativeHandleCalls"])
1009
+ delete globalThis[key];
1010
+ await (0, promises_1.rm)(root, { recursive: true, force: true });
1011
+ }
1012
+ });
1013
+ (0, node_test_1.default)("按来源删除归档后,在途通道响应不会把旧轮次写回", async () => {
1014
+ const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "manager-archive-delete-race-"));
1015
+ const manager = new plugin_manager_1.PluginManager({ pluginsDir: root, stateDir: (0, node_path_1.join)(root, "state") });
1016
+ let release;
1017
+ let entered;
1018
+ const gate = new Promise(resolve => { release = resolve; });
1019
+ const started = new Promise(resolve => { entered = resolve; });
1020
+ try {
1021
+ for (const id of ["aun", "agent"]) {
1022
+ const dir = (0, node_path_1.join)(root, id);
1023
+ await (0, promises_1.mkdir)(dir, { recursive: true });
1024
+ await (0, promises_1.writeFile)((0, node_path_1.join)(dir, "plugin.json"), JSON.stringify({ id, version: "1.0.0", type: id === "aun" ? "channel" : "agent", entry: "index.js", apiVersion: "1", capabilities: id === "aun" ? ["channel"] : ["chat"] }));
1025
+ await (0, promises_1.writeFile)((0, node_path_1.join)(dir, "index.js"), id === "aun"
1026
+ ? "module.exports={activate:async(c)=>{globalThis.deleteRaceIngress=c.ingress},deactivate:async()=>{},health:async()=>({status:'ready'})};"
1027
+ : "module.exports={activate:async()=>{},deactivate:async()=>{},health:async()=>({status:'ready'}),handle:async()=>{globalThis.deleteRaceEntered();await globalThis.deleteRaceGate;return {text:'响应完成'}}};");
1028
+ }
1029
+ await manager.discover();
1030
+ await manager.start("aun");
1031
+ await manager.start("agent");
1032
+ globalThis.deleteRaceEntered = entered;
1033
+ globalThis.deleteRaceGate = gate;
1034
+ await manager.sessionManagement.saveConfig({ enabled: true, grants: [] });
1035
+ const replies = [];
1036
+ const message = { channel: "aun", agentPlugin: "agent", peerId: "peer", text: "在途消息", reply: async (text) => { replies.push(text); } };
1037
+ const ingress = globalThis.deleteRaceIngress;
1038
+ const task = ingress.publish(message);
1039
+ await started;
1040
+ const binding = await manager.sessions.current(message, "agent");
1041
+ await manager.sessionManagement.deleteSource(binding.source);
1042
+ release();
1043
+ await task;
1044
+ strict_1.default.deepEqual((await manager.sessionManagement.archive.listSessions()).sessions, []);
1045
+ strict_1.default.deepEqual(replies, ["响应完成"]);
1046
+ }
1047
+ finally {
1048
+ release();
1049
+ for (const id of ["aun", "agent"])
1050
+ if (manager.get(id)?.instance)
1051
+ await manager.stop(id);
1052
+ for (const key of ["deleteRaceIngress", "deleteRaceEntered", "deleteRaceGate"])
1053
+ delete globalThis[key];
1054
+ await (0, promises_1.rm)(root, { recursive: true, force: true });
1055
+ }
1056
+ });
1057
+ (0, node_test_1.default)("清空归档后,在途通道响应不会重新写入", async () => {
1058
+ const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "manager-archive-clear-race-"));
1059
+ const manager = new plugin_manager_1.PluginManager({ pluginsDir: root, stateDir: (0, node_path_1.join)(root, "state") });
1060
+ let release;
1061
+ let entered;
1062
+ const gate = new Promise(resolve => { release = resolve; });
1063
+ const started = new Promise(resolve => { entered = resolve; });
1064
+ try {
1065
+ for (const id of ["aun", "agent"]) {
1066
+ const dir = (0, node_path_1.join)(root, id);
1067
+ await (0, promises_1.mkdir)(dir, { recursive: true });
1068
+ await (0, promises_1.writeFile)((0, node_path_1.join)(dir, "plugin.json"), JSON.stringify({ id, version: "1.0.0", type: id === "aun" ? "channel" : "agent", entry: "index.js", apiVersion: "1", capabilities: id === "aun" ? ["channel"] : ["chat"] }));
1069
+ await (0, promises_1.writeFile)((0, node_path_1.join)(dir, "index.js"), id === "aun"
1070
+ ? "module.exports={activate:async(c)=>{globalThis.clearRaceIngress=c.ingress},deactivate:async()=>{},health:async()=>({status:'ready'})};"
1071
+ : "module.exports={activate:async()=>{},deactivate:async()=>{},health:async()=>({status:'ready'}),handle:async()=>{globalThis.clearRaceEntered();await globalThis.clearRaceGate;return {text:'完成'}}};");
1072
+ }
1073
+ await manager.discover();
1074
+ await manager.start("aun");
1075
+ await manager.start("agent");
1076
+ globalThis.clearRaceEntered = entered;
1077
+ globalThis.clearRaceGate = gate;
1078
+ await manager.sessionManagement.saveConfig({ enabled: true, grants: [] });
1079
+ const message = { channel: "aun", agentPlugin: "agent", peerId: "peer", text: "在途", reply: async () => { } };
1080
+ const task = globalThis.clearRaceIngress.publish(message);
1081
+ await started;
1082
+ await manager.sessionManagement.clear();
1083
+ release();
1084
+ await task;
1085
+ strict_1.default.deepEqual((await manager.sessionManagement.archive.listSessions()).sessions, []);
1086
+ }
1087
+ finally {
1088
+ release();
1089
+ for (const id of ["aun", "agent"])
1090
+ if (manager.get(id)?.instance)
1091
+ await manager.stop(id);
1092
+ for (const key of ["clearRaceIngress", "clearRaceEntered", "clearRaceGate"])
1093
+ delete globalThis[key];
1094
+ await (0, promises_1.rm)(root, { recursive: true, force: true });
1095
+ }
1096
+ });
1097
+ (0, node_test_1.default)("删除单个会话归档后,在途通道响应不会重建该会话", async () => {
1098
+ const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "manager-archive-session-delete-race-"));
1099
+ const manager = new plugin_manager_1.PluginManager({ pluginsDir: root, stateDir: (0, node_path_1.join)(root, "state") });
1100
+ let release;
1101
+ let entered;
1102
+ const gate = new Promise(resolve => { release = resolve; });
1103
+ const started = new Promise(resolve => { entered = resolve; });
1104
+ try {
1105
+ for (const id of ["aun", "agent"]) {
1106
+ const dir = (0, node_path_1.join)(root, id);
1107
+ await (0, promises_1.mkdir)(dir, { recursive: true });
1108
+ await (0, promises_1.writeFile)((0, node_path_1.join)(dir, "plugin.json"), JSON.stringify({ id, version: "1.0.0", type: id === "aun" ? "channel" : "agent", entry: "index.js", apiVersion: "1", capabilities: id === "aun" ? ["channel"] : ["chat"] }));
1109
+ await (0, promises_1.writeFile)((0, node_path_1.join)(dir, "index.js"), id === "aun"
1110
+ ? "module.exports={activate:async(c)=>{globalThis.sessionDeleteRaceIngress=c.ingress},deactivate:async()=>{},health:async()=>({status:'ready'})};"
1111
+ : "module.exports={activate:async()=>{},deactivate:async()=>{},health:async()=>({status:'ready'}),handle:async()=>{globalThis.sessionDeleteRaceEntered();await globalThis.sessionDeleteRaceGate;return {text:'完成'}}};");
1112
+ }
1113
+ await manager.discover();
1114
+ await manager.start("aun");
1115
+ await manager.start("agent");
1116
+ globalThis.sessionDeleteRaceEntered = entered;
1117
+ globalThis.sessionDeleteRaceGate = gate;
1118
+ await manager.sessionManagement.saveConfig({ enabled: true, grants: [] });
1119
+ const message = { channel: "aun", agentPlugin: "agent", peerId: "peer", text: "删除前收到的消息", reply: async () => { } };
1120
+ const task = globalThis.sessionDeleteRaceIngress.publish(message);
1121
+ await started;
1122
+ const binding = await manager.sessions.current(message, "agent");
1123
+ await manager.sessionManagement.deleteSession(binding.logicalSessionId);
1124
+ release();
1125
+ await task;
1126
+ strict_1.default.deepEqual((await manager.sessionManagement.archive.listSessions()).sessions, []);
1127
+ }
1128
+ finally {
1129
+ release();
1130
+ for (const id of ["aun", "agent"])
1131
+ if (manager.get(id)?.instance)
1132
+ await manager.stop(id);
1133
+ for (const key of ["sessionDeleteRaceIngress", "sessionDeleteRaceEntered", "sessionDeleteRaceGate"])
1134
+ delete globalThis[key];
1135
+ await (0, promises_1.rm)(root, { recursive: true, force: true });
1136
+ }
1137
+ });