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.
- package/README.md +4 -2
- package/dist/src-ts/channel-sessions.js +90 -24
- package/dist/src-ts/control-server.js +48 -7
- package/dist/src-ts/dsh-agent.js +141 -46
- package/dist/src-ts/headless-account.patch.yml +8 -1
- package/dist/src-ts/plugin-manager.js +69 -3
- package/dist/src-ts/plugins/aun/attachments.js +44 -13
- package/dist/src-ts/plugins/aun/index.js +5 -6
- package/dist/src-ts/plugins/chatgpt/cdp-client.js +342 -9
- package/dist/src-ts/plugins/chatgpt/index.js +127 -7
- package/dist/src-ts/plugins/desktop-launch.js +19 -0
- package/dist/src-ts/plugins/doubao/attachments.js +42 -0
- package/dist/src-ts/plugins/doubao/cdp-client.js +530 -48
- package/dist/src-ts/plugins/doubao/index.js +114 -25
- package/dist/src-ts/plugins/doubao/session-history.js +123 -0
- package/dist/src-ts/plugins/dsh/index.js +18 -2
- package/dist/src-ts/plugins/dsh/session-history.js +371 -0
- package/dist/src-ts/plugins/qwenwork/cdp-client.js +271 -10
- package/dist/src-ts/plugins/qwenwork/index.js +42 -3
- package/dist/src-ts/plugins/qwenwork/session-history.js +245 -0
- package/dist/src-ts/plugins/workbuddy/cdp-client.js +42 -2
- package/dist/src-ts/plugins/workbuddy/index.js +23 -3
- package/dist/src-ts/plugins/workbuddy/session-history.js +121 -0
- package/dist/src-ts/session-archive.js +509 -0
- package/dist/src-ts/session-history.js +2 -0
- package/dist/src-ts/session-management.js +665 -0
- package/dist/src-ts/session-summary.js +171 -0
- package/dist/tests-ts/aun-attachments.test.js +41 -3
- package/dist/tests-ts/aun-plugin.test.js +28 -5
- package/dist/tests-ts/bundled-package.test.js +3 -1
- package/dist/tests-ts/channel-sessions.test.js +255 -2
- package/dist/tests-ts/chatgpt-history.test.js +197 -0
- package/dist/tests-ts/chatgpt-plugin.test.js +279 -2
- package/dist/tests-ts/control-server.test.js +39 -0
- package/dist/tests-ts/desktop-launch.test.js +41 -0
- package/dist/tests-ts/doubao-history.test.js +172 -0
- package/dist/tests-ts/doubao-plugin.test.js +549 -2
- package/dist/tests-ts/dsh-agent.test.js +199 -0
- package/dist/tests-ts/dsh-history.test.js +165 -0
- package/dist/tests-ts/native-session-management.test.js +195 -0
- package/dist/tests-ts/plugin-manager.test.js +354 -4
- package/dist/tests-ts/qwenwork-history.test.js +339 -0
- package/dist/tests-ts/qwenwork-images.test.js +166 -0
- package/dist/tests-ts/qwenwork-plugin.test.js +40 -1
- package/dist/tests-ts/session-archive.test.js +422 -0
- package/dist/tests-ts/session-management.test.js +323 -0
- package/dist/tests-ts/session-summary.test.js +199 -0
- package/dist/tests-ts/workbuddy-history.test.js +256 -0
- package/dist/tests-ts/workbuddy-plugin.test.js +43 -0
- package/package.json +1 -1
- package/plugins/aun/dist/src-ts/plugins/aun/attachments.js +44 -13
- package/plugins/aun/dist/src-ts/plugins/aun/index.js +5 -6
- package/plugins/aun/package.json +1 -1
- package/plugins/aun/plugin.json +1 -1
- package/plugins/chatgpt/dist/src-ts/attachments.js +62 -0
- package/plugins/chatgpt/dist/src-ts/plugins/chatgpt/cdp-client.js +342 -9
- package/plugins/chatgpt/dist/src-ts/plugins/chatgpt/index.js +127 -7
- package/plugins/chatgpt/package.json +1 -1
- package/plugins/chatgpt/plugin.json +14 -1
- package/plugins/doubao/config.schema.json +3 -0
- package/plugins/doubao/dist/src-ts/attachments.js +62 -0
- package/plugins/doubao/dist/src-ts/plugins/desktop-launch.js +19 -0
- package/plugins/doubao/dist/src-ts/plugins/doubao/attachments.js +42 -0
- package/plugins/doubao/dist/src-ts/plugins/doubao/cdp-client.js +530 -48
- package/plugins/doubao/dist/src-ts/plugins/doubao/index.js +114 -25
- package/plugins/doubao/dist/src-ts/plugins/doubao/session-history.js +123 -0
- package/plugins/doubao/package.json +1 -1
- package/plugins/doubao/plugin.json +1 -1
- package/plugins/dsh/config.schema.json +2 -1
- package/plugins/dsh/dist/src-ts/attachments.js +62 -0
- package/plugins/dsh/dist/src-ts/dsh-agent.js +141 -46
- package/plugins/dsh/dist/src-ts/headless-account.patch.yml +8 -1
- package/plugins/dsh/dist/src-ts/plugins/dsh/index.js +18 -2
- package/plugins/dsh/dist/src-ts/plugins/dsh/session-history.js +371 -0
- package/plugins/dsh/package.json +5 -2
- package/plugins/dsh/plugin.json +1 -1
- package/plugins/qwenwork/dist/src-ts/attachments.js +62 -0
- package/plugins/qwenwork/dist/src-ts/plugins/desktop-launch.js +19 -0
- package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/cdp-client.js +271 -10
- package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/index.js +42 -3
- package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/session-history.js +245 -0
- package/plugins/qwenwork/package.json +1 -1
- package/plugins/qwenwork/plugin.json +1 -1
- package/plugins/workbuddy/config.schema.json +1 -1
- package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/cdp-client.js +42 -2
- package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/index.js +23 -3
- package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/session-history.js +121 -0
- package/plugins/workbuddy/package.json +1 -1
- 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: "
|
|
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: "
|
|
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: "
|
|
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
|
+
});
|