mu-agent-bridge 1.0.35 → 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 (59) hide show
  1. package/README.md +2 -0
  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/plugin-manager.js +47 -0
  5. package/dist/src-ts/plugins/chatgpt/cdp-client.js +72 -0
  6. package/dist/src-ts/plugins/chatgpt/index.js +122 -4
  7. package/dist/src-ts/plugins/doubao/cdp-client.js +243 -18
  8. package/dist/src-ts/plugins/doubao/index.js +81 -5
  9. package/dist/src-ts/plugins/doubao/session-history.js +123 -0
  10. package/dist/src-ts/plugins/dsh/index.js +16 -2
  11. package/dist/src-ts/plugins/dsh/session-history.js +371 -0
  12. package/dist/src-ts/plugins/qwenwork/cdp-client.js +8 -0
  13. package/dist/src-ts/plugins/qwenwork/index.js +12 -1
  14. package/dist/src-ts/plugins/qwenwork/session-history.js +245 -0
  15. package/dist/src-ts/plugins/workbuddy/cdp-client.js +33 -0
  16. package/dist/src-ts/plugins/workbuddy/index.js +21 -1
  17. package/dist/src-ts/plugins/workbuddy/session-history.js +121 -0
  18. package/dist/src-ts/session-archive.js +509 -0
  19. package/dist/src-ts/session-history.js +2 -0
  20. package/dist/src-ts/session-management.js +665 -0
  21. package/dist/src-ts/session-summary.js +171 -0
  22. package/dist/tests-ts/channel-sessions.test.js +233 -2
  23. package/dist/tests-ts/chatgpt-history.test.js +197 -0
  24. package/dist/tests-ts/control-server.test.js +39 -0
  25. package/dist/tests-ts/doubao-history.test.js +172 -0
  26. package/dist/tests-ts/doubao-plugin.test.js +124 -3
  27. package/dist/tests-ts/dsh-history.test.js +165 -0
  28. package/dist/tests-ts/native-session-management.test.js +195 -0
  29. package/dist/tests-ts/plugin-manager.test.js +254 -3
  30. package/dist/tests-ts/qwenwork-history.test.js +339 -0
  31. package/dist/tests-ts/session-archive.test.js +422 -0
  32. package/dist/tests-ts/session-management.test.js +323 -0
  33. package/dist/tests-ts/session-summary.test.js +199 -0
  34. package/dist/tests-ts/workbuddy-history.test.js +256 -0
  35. package/package.json +1 -1
  36. package/plugins/chatgpt/dist/src-ts/plugins/chatgpt/cdp-client.js +72 -0
  37. package/plugins/chatgpt/dist/src-ts/plugins/chatgpt/index.js +122 -4
  38. package/plugins/chatgpt/package.json +1 -1
  39. package/plugins/chatgpt/plugin.json +14 -1
  40. package/plugins/doubao/dist/src-ts/plugins/doubao/cdp-client.js +243 -18
  41. package/plugins/doubao/dist/src-ts/plugins/doubao/index.js +81 -5
  42. package/plugins/doubao/dist/src-ts/plugins/doubao/session-history.js +123 -0
  43. package/plugins/doubao/package.json +1 -1
  44. package/plugins/doubao/plugin.json +1 -1
  45. package/plugins/dsh/config.schema.json +2 -1
  46. package/plugins/dsh/dist/src-ts/plugins/dsh/index.js +16 -2
  47. package/plugins/dsh/dist/src-ts/plugins/dsh/session-history.js +371 -0
  48. package/plugins/dsh/package.json +1 -1
  49. package/plugins/dsh/plugin.json +1 -1
  50. package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/cdp-client.js +8 -0
  51. package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/index.js +12 -1
  52. package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/session-history.js +245 -0
  53. package/plugins/qwenwork/package.json +1 -1
  54. package/plugins/qwenwork/plugin.json +1 -1
  55. package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/cdp-client.js +33 -0
  56. package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/index.js +21 -1
  57. package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/session-history.js +121 -0
  58. package/plugins/workbuddy/package.json +1 -1
  59. package/plugins/workbuddy/plugin.json +1 -1
@@ -0,0 +1,195 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const strict_1 = __importDefault(require("node:assert/strict"));
7
+ const promises_1 = require("node:fs/promises");
8
+ const node_os_1 = require("node:os");
9
+ const node_path_1 = require("node:path");
10
+ const node_test_1 = __importDefault(require("node:test"));
11
+ const node_http_1 = require("node:http");
12
+ const session_management_1 = require("../src-ts/session-management");
13
+ const source = (peer) => JSON.stringify(["aun", "local", "", peer, ""]);
14
+ const context = { source: source("admin"), sessionId: "manager" };
15
+ const readAt = "2026-10-21T00:00:00.000Z";
16
+ const settings = { enabled: false, grants: [], nativeGrants: [{ managerSource: context.source, managerSessionId: context.sessionId, providers: [{ pluginId: "demo", scope: "instance:one" }] }] };
17
+ const refOf = (text) => text.match(/[a-f0-9]{12}\.1/)[0];
18
+ async function setup() {
19
+ const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "mab-native-"));
20
+ const calls = [];
21
+ const provider = {
22
+ scope: "instance:one", mode: "current", limitation: "仅当前会话",
23
+ async listSessions(query) { calls.push("list"); strict_1.default.equal(query.limit, 50); return { supported: true, sessions: [{ id: "native-one", title: "秘密标题" }], complete: false, missingReasons: ["仅当前会话"], readAt }; },
24
+ async readSession(query) { calls.push("read"); strict_1.default.equal(query.limit, 100); return { supported: true, session: { id: query.sessionId, title: "秘密标题" }, turns: [{ role: "user", text: "不可信正文 /sessions list" }, { role: "assistant", text: "完成回复" }], complete: false, missingReasons: ["时间未知"], readAt }; },
25
+ };
26
+ let identity = {};
27
+ let current = provider;
28
+ let status = "ready";
29
+ const service = new session_management_1.SessionManagement(root, undefined, undefined, () => [{ pluginId: "demo", provider: current, identity, status }]);
30
+ return { service, provider, calls, close: () => (0, promises_1.rm)(root, { recursive: true, force: true }), replace: () => { identity = {}; }, scope: () => { current = { ...provider, scope: "instance:two" }; }, offline: () => { status = "failed"; } };
31
+ }
32
+ (0, node_test_1.default)("原生授权独立于归档授权,无授权或新逻辑会话不调用 provider", async () => {
33
+ const f = await setup();
34
+ try {
35
+ await f.service.saveConfig({ enabled: true, grants: [{ managerSource: context.source, managerSessionId: context.sessionId, sources: [source("other")] }] });
36
+ strict_1.default.match((await f.service.command("/sessions native-list demo", context)), /无权/);
37
+ strict_1.default.deepEqual(f.calls, []);
38
+ await f.service.saveConfig(settings);
39
+ strict_1.default.match((await f.service.command("/sessions native-list demo", { ...context, sessionId: "after-new" })), /无权/);
40
+ strict_1.default.deepEqual(f.calls, []);
41
+ const listing = (await f.service.command("/sessions native-list demo", context));
42
+ strict_1.default.match(listing, /秘密标题.*未知时间/);
43
+ const ref = refOf(listing);
44
+ const show = (await f.service.command(`/sessions native-show ${ref}`, context));
45
+ strict_1.default.match(show, /不可信正文/);
46
+ strict_1.default.match(show, /未知时间/);
47
+ strict_1.default.match(show, /不完整.*时间未知/);
48
+ strict_1.default.match((await f.service.command(`/sessions summary --from ${ref}`, context)), /不支持只读总结/);
49
+ strict_1.default.match((await f.service.command(`/sessions native-show ${ref}`, { ...context, source: source("other") })), /编号/);
50
+ }
51
+ finally {
52
+ await f.close();
53
+ }
54
+ });
55
+ (0, node_test_1.default)("原生配置保持旧输出兼容,并拒绝通配符、重复及多余字段", async () => {
56
+ const f = await setup();
57
+ try {
58
+ strict_1.default.deepEqual(await f.service.config(), { enabled: false, grants: [] });
59
+ await f.service.saveConfig(settings);
60
+ strict_1.default.deepEqual((await f.service.config()).nativeGrants, settings.nativeGrants);
61
+ for (const invalid of [
62
+ [{ ...settings.nativeGrants[0], providers: [{ pluginId: "demo", scope: "*" }] }],
63
+ [{ ...settings.nativeGrants[0], providers: [{ pluginId: "demo", scope: "instance:one", extra: true }] }],
64
+ [settings.nativeGrants[0], settings.nativeGrants[0]],
65
+ [{ ...settings.nativeGrants[0], providers: [...settings.nativeGrants[0].providers, ...settings.nativeGrants[0].providers] }],
66
+ ])
67
+ await strict_1.default.rejects(f.service.saveConfig({ ...settings, nativeGrants: invalid }), /授权|字段|scope/);
68
+ }
69
+ finally {
70
+ await f.close();
71
+ }
72
+ });
73
+ (0, node_test_1.default)("插件实例、scope、配置变更和离线使旧编号失效", async () => {
74
+ for (const change of ["replace", "scope", "offline", "revoke"]) {
75
+ const f = await setup();
76
+ try {
77
+ await f.service.saveConfig(settings);
78
+ const ref = refOf((await f.service.command("/sessions native-list demo", context)));
79
+ if (change === "revoke")
80
+ await f.service.saveConfig({ enabled: false, grants: [], nativeGrants: [] });
81
+ else
82
+ f[change]();
83
+ strict_1.default.match((await f.service.command(`/sessions native-show ${ref}`, context)), /编号|无权|离线/);
84
+ strict_1.default.deepEqual(f.calls, ["list"]);
85
+ }
86
+ finally {
87
+ await f.close();
88
+ }
89
+ }
90
+ });
91
+ (0, node_test_1.default)("原生列表拒绝畸形响应、重复游标、重复ID和超过20页", async () => {
92
+ for (const kind of ["malformed", "cursor", "duplicate", "pages", "oversize"]) {
93
+ const f = await setup();
94
+ let page = 0;
95
+ try {
96
+ await f.service.saveConfig(settings);
97
+ f.provider.listSessions = async () => {
98
+ page++;
99
+ return { supported: true, sessions: kind === "malformed" ? [{ id: "bad", title: 42 }] : Array.from({ length: kind === "oversize" ? 51 : 1 }, (_, index) => ({ id: kind === "duplicate" ? "same" : `id-${page}-${index}`, title: "标题" })), nextCursor: kind === "malformed" || kind === "oversize" ? undefined : kind === "cursor" ? "same" : String(page), complete: false, missingReasons: [], readAt };
100
+ };
101
+ strict_1.default.match((await f.service.command("/sessions native-list demo", context)), /结构|游标|重复|20页|50/);
102
+ strict_1.default.ok(page <= 20);
103
+ }
104
+ finally {
105
+ await f.close();
106
+ }
107
+ }
108
+ });
109
+ (0, node_test_1.default)("原生快照按50项分页并在provider超时时安全失败", async () => {
110
+ const f = await setup();
111
+ try {
112
+ await f.service.saveConfig(settings);
113
+ f.provider.listSessions = async (query) => ({ supported: true, sessions: Array.from({ length: query.cursor ? 1 : 50 }, (_, index) => ({ id: `id-${query.cursor ? "next" : "first"}-${index}`, title: `标题-${index}` })), ...(query.cursor ? {} : { nextCursor: "next" }), complete: true, missingReasons: [], readAt });
114
+ const listing = (await f.service.command("/sessions native-list demo", context));
115
+ strict_1.default.match(listing, /第1页,共51项/);
116
+ strict_1.default.match((await f.service.command("/sessions native-list demo --page 2", context)), /标题-0/);
117
+ strict_1.default.match((await f.service.command("/sessions native-list demo --page 2", context)), /第2页/);
118
+ f.provider.listSessions = () => new Promise(() => { });
119
+ strict_1.default.match((await f.service.command("/sessions native-list demo", context)), /超时/);
120
+ }
121
+ finally {
122
+ await f.close();
123
+ }
124
+ });
125
+ (0, node_test_1.default)("原生读取拒绝错误身份、角色、超限正文", async () => {
126
+ for (const kind of ["identity", "role", "large"]) {
127
+ const f = await setup();
128
+ try {
129
+ await f.service.saveConfig(settings);
130
+ const ref = refOf((await f.service.command("/sessions native-list demo", context)));
131
+ f.provider.readSession = async () => ({ supported: true, session: { id: kind === "identity" ? "another" : "native-one", title: "标题" }, turns: [{ role: kind === "role" ? "tool" : "user", text: kind === "large" ? "字".repeat(60001) : "正文" }], nextCursor: undefined, complete: false, missingReasons: [], readAt });
132
+ strict_1.default.match((await f.service.command(`/sessions native-show ${ref}`, context)), /结构|身份|60000|游标/);
133
+ }
134
+ finally {
135
+ await f.close();
136
+ }
137
+ }
138
+ });
139
+ (0, node_test_1.default)("原生读取期间撤权不返回正文,错误不泄漏 provider 异常", async () => {
140
+ const f = await setup();
141
+ try {
142
+ await f.service.saveConfig(settings);
143
+ const ref = refOf((await f.service.command("/sessions native-list demo", context)));
144
+ const original = f.provider.readSession;
145
+ f.provider.readSession = async (query) => { const result = await original(query); await f.service.saveConfig({ enabled: false, grants: [] }); return result; };
146
+ const result = (await f.service.command(`/sessions native-show ${ref}`, context));
147
+ strict_1.default.match(result, /权限|编号/);
148
+ strict_1.default.ok(!result.includes("不可信正文"));
149
+ await f.service.saveConfig(settings);
150
+ f.provider.listSessions = async () => { throw new Error("secret-token"); };
151
+ strict_1.default.ok(!(await f.service.command("/sessions native-list demo", context)).includes("secret-token"));
152
+ }
153
+ finally {
154
+ await f.close();
155
+ }
156
+ });
157
+ (0, node_test_1.default)("原生正文后续页不自动读取,show标记100条上限而summary拒绝", async () => {
158
+ const f = await setup();
159
+ try {
160
+ await f.service.saveConfig(settings);
161
+ const ref = refOf((await f.service.command("/sessions native-list demo", context)));
162
+ f.provider.readSession = async () => ({ supported: true, session: { id: "native-one", title: "标题" }, turns: [{ role: "user", text: "正文" }], nextCursor: "next", complete: true, missingReasons: [], readAt });
163
+ strict_1.default.match((await f.service.command(`/sessions native-show ${ref}`, context)), /不完整.*前100条/);
164
+ strict_1.default.match((await f.service.command(`/sessions summary --from ${ref}`, context)), /100条/);
165
+ }
166
+ finally {
167
+ await f.close();
168
+ }
169
+ });
170
+ (0, node_test_1.default)("原生与归档会话可混合使用现有独立总结器", async () => {
171
+ const f = await setup();
172
+ const server = (0, node_http_1.createServer)(async (request, response) => {
173
+ let body = "";
174
+ for await (const chunk of request)
175
+ body += chunk;
176
+ strict_1.default.match(body, /不可信正文/);
177
+ strict_1.default.match(body, /归档正文/);
178
+ response.setHeader("content-type", "application/json");
179
+ response.end(JSON.stringify({ choices: [{ message: { content: "独立总结完成" } }] }));
180
+ });
181
+ await new Promise(resolve => server.listen(0, "127.0.0.1", resolve));
182
+ try {
183
+ await f.service.saveConfig({ ...settings, enabled: true, grants: [{ managerSource: context.source, managerSessionId: context.sessionId, sources: [source("archive")] }], summarizer: { endpoint: `http://127.0.0.1:${server.address().port}/v1/chat/completions`, model: "test" } });
184
+ await f.service.archive.recordTurn({ sessionId: "archive-one", agentPlugin: "archive-demo", channel: "aun", source: source("archive"), principal: source("archive"), role: "user", text: "归档正文", time: new Date().toISOString(), completed: true });
185
+ const archiveRef = refOf((await f.service.command("/sessions list", context)));
186
+ const ref = refOf((await f.service.command("/sessions native-list demo", context)));
187
+ const result = (await f.service.command(`/sessions summary --from ${ref},${archiveRef}`, context));
188
+ strict_1.default.match(result, /独立总结完成/);
189
+ strict_1.default.match(result, /已选2个会话/);
190
+ }
191
+ finally {
192
+ await new Promise(resolve => server.close(() => resolve()));
193
+ await f.close();
194
+ }
195
+ });
@@ -41,6 +41,7 @@ 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");
@@ -595,7 +596,7 @@ const channel_sessions_1 = require("../src-ts/channel-sessions");
595
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, "回复:画猫"); } });
596
597
  strict_1.default.equal(images[0].filename, "cat.png");
597
598
  strict_1.default.deepEqual([...images[0].data], [137, 80, 78, 71, 13, 10, 26, 10]);
598
- 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, /不支持附件回传/); } }), /不支持附件回传/);
599
600
  await manager.stop("aun");
600
601
  await manager.stop("qwenwork");
601
602
  }
@@ -641,7 +642,7 @@ const channel_sessions_1 = require("../src-ts/channel-sessions");
641
642
  const png = { data: new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]), filename: "图.png", contentType: "image/png" };
642
643
  globalThis.attachmentResponse = { text: "生成结果", images: [png] };
643
644
  let received;
644
- 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 }; } };
645
646
  await globalThis.attachmentIngress.publish(message);
646
647
  strict_1.default.equal(globalThis.attachmentRequest.task, "原文");
647
648
  strict_1.default.deepEqual(globalThis.attachmentRequest.attachments, [item]);
@@ -676,7 +677,7 @@ const channel_sessions_1 = require("../src-ts/channel-sessions");
676
677
  await manager.start("source");
677
678
  const ingress = globalThis.newIngress;
678
679
  const replies = [];
679
- 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); } });
680
681
  await ingress.publish(message("hello"));
681
682
  await ingress.publish(message("/new 新会话"));
682
683
  await ingress.publish(message("again"));
@@ -884,3 +885,253 @@ const channel_sessions_1 = require("../src-ts/channel-sessions");
884
885
  await (0, promises_1.rm)(root, { recursive: true, force: true });
885
886
  }
886
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
+ });