mu-agent-bridge 1.0.71 → 1.0.78

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (195) hide show
  1. package/README.md +17 -10
  2. package/dist/src-ts/agent.js +35 -0
  3. package/dist/src-ts/channel-sessions.js +3 -2
  4. package/dist/src-ts/cli.js +67 -37
  5. package/dist/src-ts/control-server.js +12 -30
  6. package/dist/src-ts/core-updater.js +141 -33
  7. package/dist/src-ts/daemon-process.js +2 -1
  8. package/dist/src-ts/dsh-agent.js +31 -13
  9. package/dist/src-ts/http-agent.js +36 -0
  10. package/dist/src-ts/plugin-catalog.js +11 -2
  11. package/dist/src-ts/plugin-manager.js +351 -50
  12. package/dist/src-ts/plugins/abweb/index.js +1 -1
  13. package/dist/src-ts/plugins/abweb/public/app.js +73 -5
  14. package/dist/src-ts/plugins/abweb/public/index.html +15 -0
  15. package/dist/src-ts/plugins/abweb/public/style.css +4 -0
  16. package/dist/src-ts/plugins/aun/attachments.js +3 -21
  17. package/dist/src-ts/plugins/aun/index.js +155 -21
  18. package/dist/src-ts/plugins/aun/messages.js +4 -1
  19. package/dist/src-ts/plugins/aun/public/app.js +2 -1
  20. package/dist/src-ts/plugins/aun/public/index.html +1 -1
  21. package/dist/src-ts/plugins/cdp-draft.js +5 -2
  22. package/dist/src-ts/plugins/chatgpt/cdp-client.js +26 -12
  23. package/dist/src-ts/plugins/chatgpt/index.js +80 -19
  24. package/dist/src-ts/plugins/claude-cli/index.js +19 -2
  25. package/dist/src-ts/plugins/codex-cli/index.js +19 -2
  26. package/dist/src-ts/plugins/desktop-launch.js +44 -3
  27. package/dist/src-ts/plugins/doubao/cdp-client.js +234 -45
  28. package/dist/src-ts/plugins/doubao/index.js +152 -8
  29. package/dist/src-ts/plugins/doubao/native-task.js +134 -0
  30. package/dist/src-ts/plugins/dsh/cdp-client.js +334 -0
  31. package/dist/src-ts/plugins/dsh/index.js +172 -21
  32. package/dist/src-ts/plugins/dsh/session-history.js +31 -3
  33. package/dist/src-ts/plugins/feishu/index.js +1 -1
  34. package/dist/src-ts/plugins/local-cli/agent.js +74 -9
  35. package/dist/src-ts/plugins/personal-assistant/claude-cli-model.js +30 -0
  36. package/dist/src-ts/plugins/personal-assistant/coordination.js +57 -0
  37. package/dist/src-ts/plugins/personal-assistant/index.js +263 -0
  38. package/dist/src-ts/plugins/personal-assistant/model-client.js +181 -0
  39. package/dist/src-ts/plugins/personal-assistant/owner-context.js +56 -0
  40. package/dist/src-ts/plugins/personal-assistant/remote-ability.js +176 -0
  41. package/dist/src-ts/plugins/personal-assistant/session-memory.js +124 -0
  42. package/dist/src-ts/plugins/personal-assistant/tools.js +167 -0
  43. package/dist/src-ts/plugins/qwenwork/cdp-client.js +90 -18
  44. package/dist/src-ts/plugins/qwenwork/index.js +139 -24
  45. package/dist/src-ts/plugins/workbuddy/cdp-client.js +353 -82
  46. package/dist/src-ts/plugins/workbuddy/index.js +124 -41
  47. package/dist/src-ts/plugins/zcode/cdp-client.js +500 -0
  48. package/dist/src-ts/plugins/zcode/index.js +97 -0
  49. package/dist/src-ts/session-management.js +132 -14
  50. package/dist/src-ts/version-manager.js +6 -3
  51. package/dist/tests-ts/abweb-cli.test.js +10 -2
  52. package/dist/tests-ts/abweb-model-config.test.js +73 -0
  53. package/dist/tests-ts/agent.test.js +27 -0
  54. package/dist/tests-ts/aun-attachments.test.js +19 -17
  55. package/dist/tests-ts/aun-default-owner-page.test.js +2 -1
  56. package/dist/tests-ts/aun-messages.test.js +20 -0
  57. package/dist/tests-ts/aun-plugin.test.js +292 -19
  58. package/dist/tests-ts/bundled-package.test.js +12 -2
  59. package/dist/tests-ts/cdp-draft-recovery.test.js +182 -3
  60. package/dist/tests-ts/channel-sessions.test.js +27 -0
  61. package/dist/tests-ts/chatgpt-plugin.test.js +118 -0
  62. package/dist/tests-ts/control-server.test.js +56 -0
  63. package/dist/tests-ts/core-updater.test.js +215 -46
  64. package/dist/tests-ts/daemon-process.test.js +126 -1
  65. package/dist/tests-ts/desktop-launch.test.js +73 -0
  66. package/dist/tests-ts/doubao-native-task.test.js +237 -0
  67. package/dist/tests-ts/doubao-plugin.test.js +773 -12
  68. package/dist/tests-ts/dsh-agent.test.js +157 -8
  69. package/dist/tests-ts/dsh-cdp.test.js +148 -0
  70. package/dist/tests-ts/dsh-history.test.js +37 -0
  71. package/dist/tests-ts/feishu-plugin.test.js +1 -1
  72. package/dist/tests-ts/http-agent.test.js +18 -0
  73. package/dist/tests-ts/installer-assets.test.js +1 -1
  74. package/dist/tests-ts/local-cli.test.js +146 -1
  75. package/dist/tests-ts/model-provider-cancellation.test.js +152 -0
  76. package/dist/tests-ts/native-session-management.test.js +8 -7
  77. package/dist/tests-ts/personal-assistant-coordination.test.js +92 -0
  78. package/dist/tests-ts/personal-assistant-extensions.test.js +35 -0
  79. package/dist/tests-ts/personal-assistant-memory.test.js +117 -0
  80. package/dist/tests-ts/personal-assistant-owner-context.test.js +107 -0
  81. package/dist/tests-ts/personal-assistant-remote-ability.test.js +123 -0
  82. package/dist/tests-ts/personal-assistant.test.js +663 -0
  83. package/dist/tests-ts/platform-integration.test.js +183 -1
  84. package/dist/tests-ts/plugin-catalog.test.js +12 -2
  85. package/dist/tests-ts/plugin-manager.test.js +356 -4
  86. package/dist/tests-ts/plugin-upgrade-deadline.test.js +354 -0
  87. package/dist/tests-ts/publish-packages.test.js +42 -0
  88. package/dist/tests-ts/qwenwork-plugin.test.js +135 -1
  89. package/dist/tests-ts/qwenwork-reconnect.test.js +195 -0
  90. package/dist/tests-ts/qwenwork-sse.test.js +33 -0
  91. package/dist/tests-ts/session-access-defaults.test.js +112 -0
  92. package/dist/tests-ts/session-management.test.js +159 -4
  93. package/dist/tests-ts/version-manager.test.js +40 -0
  94. package/dist/tests-ts/workbuddy-cdp-recovery.test.js +311 -0
  95. package/dist/tests-ts/workbuddy-plugin.test.js +499 -4
  96. package/dist/tests-ts/zcode-cdp.test.js +894 -0
  97. package/dist/tests-ts/zcode-plugin.test.js +396 -0
  98. package/package.json +1 -1
  99. package/plugins/abweb/dist/src-ts/agent.js +35 -0
  100. package/plugins/abweb/dist/src-ts/plugins/abweb/index.js +1 -1
  101. package/plugins/abweb/dist/src-ts/plugins/abweb/public/app.js +73 -5
  102. package/plugins/abweb/dist/src-ts/plugins/abweb/public/app.ts +57 -5
  103. package/plugins/abweb/dist/src-ts/plugins/abweb/public/index.html +15 -0
  104. package/plugins/abweb/dist/src-ts/plugins/abweb/public/style.css +4 -0
  105. package/plugins/abweb/package.json +1 -1
  106. package/plugins/abweb/plugin.json +1 -1
  107. package/plugins/aun/config.schema.json +1 -1
  108. package/plugins/aun/dist/src-ts/agent.js +35 -0
  109. package/plugins/aun/dist/src-ts/plugins/aun/attachments.js +3 -21
  110. package/plugins/aun/dist/src-ts/plugins/aun/index.js +155 -21
  111. package/plugins/aun/dist/src-ts/plugins/aun/messages.js +4 -1
  112. package/plugins/aun/dist/src-ts/plugins/aun/public/app.js +2 -1
  113. package/plugins/aun/dist/src-ts/plugins/aun/public/index.html +1 -1
  114. package/plugins/aun/package.json +1 -1
  115. package/plugins/aun/plugin.json +1 -1
  116. package/plugins/chatgpt/dist/src-ts/agent.js +35 -0
  117. package/plugins/chatgpt/dist/src-ts/plugins/cdp-draft.js +5 -2
  118. package/plugins/chatgpt/dist/src-ts/plugins/chatgpt/cdp-client.js +26 -12
  119. package/plugins/chatgpt/dist/src-ts/plugins/chatgpt/index.js +80 -19
  120. package/plugins/chatgpt/dist/src-ts/plugins/desktop-launch.js +44 -3
  121. package/plugins/chatgpt/package.json +1 -1
  122. package/plugins/chatgpt/plugin.json +1 -1
  123. package/plugins/claude-cli/dist/src-ts/agent.js +35 -0
  124. package/plugins/claude-cli/dist/src-ts/plugins/claude-cli/index.js +19 -2
  125. package/plugins/claude-cli/dist/src-ts/plugins/local-cli/agent.js +74 -9
  126. package/plugins/claude-cli/package.json +1 -1
  127. package/plugins/claude-cli/plugin.json +3 -2
  128. package/plugins/codex-cli/dist/src-ts/agent.js +35 -0
  129. package/plugins/codex-cli/dist/src-ts/plugins/codex-cli/index.js +19 -2
  130. package/plugins/codex-cli/dist/src-ts/plugins/local-cli/agent.js +74 -9
  131. package/plugins/codex-cli/package.json +1 -1
  132. package/plugins/codex-cli/plugin.json +3 -2
  133. package/plugins/doubao/config.schema.json +2 -1
  134. package/plugins/doubao/dist/src-ts/agent.js +35 -0
  135. package/plugins/doubao/dist/src-ts/plugins/desktop-launch.js +44 -3
  136. package/plugins/doubao/dist/src-ts/plugins/doubao/cdp-client.js +234 -45
  137. package/plugins/doubao/dist/src-ts/plugins/doubao/index.js +152 -8
  138. package/plugins/doubao/dist/src-ts/plugins/doubao/native-task.js +134 -0
  139. package/plugins/doubao/package.json +1 -1
  140. package/plugins/doubao/plugin.json +3 -2
  141. package/plugins/dsh/config.schema.json +5 -2
  142. package/plugins/dsh/dist/src-ts/agent.js +35 -0
  143. package/plugins/dsh/dist/src-ts/dsh-agent.js +31 -13
  144. package/plugins/dsh/dist/src-ts/plugins/desktop-launch.js +44 -3
  145. package/plugins/dsh/dist/src-ts/plugins/dsh/cdp-client.js +334 -0
  146. package/plugins/dsh/dist/src-ts/plugins/dsh/index.js +172 -21
  147. package/plugins/dsh/dist/src-ts/plugins/dsh/session-history.js +31 -3
  148. package/plugins/dsh/package.json +1 -1
  149. package/plugins/dsh/plugin.json +3 -2
  150. package/plugins/feishu/dist/src-ts/agent.js +35 -0
  151. package/plugins/feishu/dist/src-ts/plugins/feishu/index.js +1 -1
  152. package/plugins/feishu/package.json +1 -1
  153. package/plugins/feishu/plugin.json +1 -1
  154. package/plugins/personal-assistant/config.schema.json +23 -0
  155. package/plugins/personal-assistant/dist/src-ts/agent.js +69 -0
  156. package/plugins/personal-assistant/dist/src-ts/attachments.js +62 -0
  157. package/plugins/personal-assistant/dist/src-ts/plugin.js +13 -0
  158. package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/claude-cli-model.js +30 -0
  159. package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/coordination.js +57 -0
  160. package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/index.js +263 -0
  161. package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/model-client.js +181 -0
  162. package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/owner-context.js +56 -0
  163. package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/remote-ability.js +176 -0
  164. package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/session-memory.js +124 -0
  165. package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/tools.js +167 -0
  166. package/plugins/personal-assistant/package.json +39 -0
  167. package/plugins/personal-assistant/plugin.json +15 -0
  168. package/plugins/qwenwork/dist/src-ts/agent.js +35 -0
  169. package/plugins/qwenwork/dist/src-ts/plugins/cdp-draft.js +5 -2
  170. package/plugins/qwenwork/dist/src-ts/plugins/desktop-launch.js +44 -3
  171. package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/cdp-client.js +90 -18
  172. package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/index.js +139 -24
  173. package/plugins/qwenwork/package.json +1 -1
  174. package/plugins/qwenwork/plugin.json +3 -2
  175. package/plugins/workbuddy/config.schema.json +1 -1
  176. package/plugins/workbuddy/dist/src-ts/agent.js +35 -0
  177. package/plugins/workbuddy/dist/src-ts/plugins/desktop-launch.js +44 -3
  178. package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/cdp-client.js +353 -82
  179. package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/index.js +124 -41
  180. package/plugins/workbuddy/package.json +1 -1
  181. package/plugins/workbuddy/plugin.json +3 -2
  182. package/plugins/zcode/config.schema.json +13 -0
  183. package/plugins/zcode/dist/src-ts/agent.js +69 -0
  184. package/plugins/zcode/dist/src-ts/attachments.js +62 -0
  185. package/plugins/zcode/dist/src-ts/plugin.js +13 -0
  186. package/plugins/zcode/dist/src-ts/plugins/desktop-launch.js +238 -0
  187. package/plugins/zcode/dist/src-ts/plugins/workbuddy/cdp-client.js +896 -0
  188. package/plugins/zcode/dist/src-ts/plugins/workbuddy/cli-client.js +39 -0
  189. package/plugins/zcode/dist/src-ts/plugins/workbuddy/desktop-launch.js +10 -0
  190. package/plugins/zcode/dist/src-ts/plugins/workbuddy/index.js +567 -0
  191. package/plugins/zcode/dist/src-ts/plugins/workbuddy/session-history.js +121 -0
  192. package/plugins/zcode/dist/src-ts/plugins/zcode/cdp-client.js +500 -0
  193. package/plugins/zcode/dist/src-ts/plugins/zcode/index.js +97 -0
  194. package/plugins/zcode/package.json +16 -0
  195. package/plugins/zcode/plugin.json +11 -0
@@ -10,13 +10,14 @@ const node_path_1 = require("node:path");
10
10
  const node_test_1 = __importDefault(require("node:test"));
11
11
  const codex_cli_1 = __importDefault(require("../src-ts/plugins/codex-cli"));
12
12
  const claude_cli_1 = __importDefault(require("../src-ts/plugins/claude-cli"));
13
+ const personal_assistant_1 = require("../src-ts/plugins/personal-assistant");
13
14
  const agent_1 = require("../src-ts/plugins/local-cli/agent");
14
15
  const plugin_manifest_1 = require("../src-ts/plugin-manifest");
15
16
  const fixture = String.raw `
16
17
  const fs = require('node:fs'); const {randomUUID}=require('node:crypto');
17
18
  const args=process.argv.slice(2);
18
19
  if(args.includes('--version')) { console.log('1.0.0'); process.exit(0); }
19
- if(args.includes('--help')) { console.log('exec resume --json --print --output-format --resume'); process.exit(0); }
20
+ if(args.includes('--help')) { console.log('exec resume --json --print --output-format --resume --json-schema --tools --strict-mcp-config --mcp-config --no-session-persistence --settings --setting-sources --output-schema --ephemeral --sandbox --disable --config --ignore-rules'); process.exit(0); }
20
21
  let input=''; process.stdin.setEncoding('utf8'); process.stdin.on('data',x=>input+=x);
21
22
  process.stdin.on('end',()=>{
22
23
  fs.appendFileSync('calls.jsonl',JSON.stringify({args,input})+'\n');
@@ -25,6 +26,10 @@ process.stdin.on('end',()=>{
25
26
  if(input==='permission') {console.error('permission denied EACCES'); process.exit(9);}
26
27
  if(input==='structured-error') { if(args[0]==='exec') console.log(JSON.stringify({type:'turn.failed',error:{message:'E_AUTH_42 登录失败'}})); else console.log(JSON.stringify({type:'result',is_error:true,errors:['E_AUTH_42 登录失败']})); return; }
27
28
  if(input==='malformed') {console.log('{}'); return;}
29
+ let decision={text:'模型回复'};
30
+ try { const prompt=JSON.parse(input); if(prompt.messages) decision=prompt.messages.at(-1).role==='tool' ? {role:'assistant',content:'已查询千问办公',tool_calls:[]} : {role:'assistant',content:null,tool_calls:[{id:'list-1',type:'function',function:{name:'agents_list',arguments:'{}'}}]}; } catch {}
31
+ if(args.includes('--json-schema')) {console.log(JSON.stringify({type:'result',session_id:randomUUID(),is_error:false,result:'',...(input==='missing-structured'?{}:{structured_output:decision})}));return;}
32
+ if(args.includes('--output-schema')) { const id=randomUUID(); console.log(JSON.stringify({type:'thread.started',thread_id:id})); console.log(JSON.stringify({type:'item.completed',item:{type:'agent_message',text:JSON.stringify(decision)}})); console.log(JSON.stringify({type:'turn.completed'})); return; }
28
33
  const codex=args[0]==='exec'; const resume=codex?args.indexOf('resume'):args.indexOf('--resume');
29
34
  let id=resume>=0?args[resume+1]:randomUUID();
30
35
  if(input==='wrong-session') id=randomUUID();
@@ -44,6 +49,30 @@ async function setup(t) {
44
49
  return { root, context };
45
50
  }
46
51
  for (const [name, Constructor] of [["Codex", codex_cli_1.default], ["Claude", claude_cli_1.default]]) {
52
+ (0, node_test_1.default)(`个人助理通过 ${name} 真实插件入口完成模型决策和工具回合`, async (t) => {
53
+ const { root, context } = await setup(t);
54
+ const cli = new Constructor();
55
+ const assistant = new personal_assistant_1.PersonalAssistantPlugin();
56
+ await cli.activate(context);
57
+ let queried = 0;
58
+ try {
59
+ await assistant.activate({ ...context, config: { modelProvider: "plugin", modelPlugin: cli.manifest.id, allowedPeers: [], allowedAgents: [] }, callModelProvider: async (id, request) => {
60
+ strict_1.default.equal(id, cli.manifest.id);
61
+ return cli.modelProvider.complete(request);
62
+ } });
63
+ const result = await assistant.handle({ task: "请列出 Agent", sessionId: "private-thread", management: { listAgents: async () => { queried++; return [{ id: "qwenwork", displayName: "千问办公" }]; } } });
64
+ strict_1.default.equal(result.text, "已查询千问办公");
65
+ strict_1.default.equal(queried, 1);
66
+ const calls = (await (0, promises_1.readFile)((0, node_path_1.join)(root, "calls.jsonl"), "utf8")).trim().split("\n").map(line => JSON.parse(line));
67
+ strict_1.default.equal(calls.length, 2);
68
+ strict_1.default.match(JSON.parse(calls[1].input).messages.at(-1).content, /千问办公/);
69
+ await strict_1.default.rejects((0, promises_1.readFile)((0, node_path_1.join)(context.dataDir, "cli-sessions.json")), /ENOENT/);
70
+ }
71
+ finally {
72
+ await assistant.deactivate("stop");
73
+ await cli.deactivate("stop");
74
+ }
75
+ });
47
76
  (0, node_test_1.default)(`${name} 隔离双会话、精确恢复、重载和新建,不覆盖模型及权限`, async (t) => {
48
77
  const { root, context } = await setup(t);
49
78
  let plugin = new Constructor();
@@ -108,6 +137,122 @@ for (const [name, Constructor] of [["Codex", codex_cli_1.default], ["Claude", cl
108
137
  await strict_1.default.rejects(plugin.handle({ task: "recall" }), /未启动|停止/);
109
138
  });
110
139
  }
140
+ (0, node_test_1.default)("Claude 无工具模型请求使用默认模型及结构化输出,不保存聊天映射", async (t) => {
141
+ const { root, context } = await setup(t);
142
+ const plugin = new claude_cli_1.default();
143
+ await plugin.activate(context);
144
+ try {
145
+ const modelResponseSchema = { type: "object", properties: { text: { type: "string" } }, required: ["text"], additionalProperties: false };
146
+ const response = await plugin.handle({ task: "模型请求", modelResponseSchema });
147
+ strict_1.default.deepEqual(JSON.parse(response.text), { text: "模型回复" });
148
+ strict_1.default.equal(response.sessionId, undefined);
149
+ const call = JSON.parse((await (0, promises_1.readFile)((0, node_path_1.join)(root, "calls.jsonl"), "utf8")).trim());
150
+ strict_1.default.equal(call.args[call.args.indexOf("--tools") + 1], "");
151
+ strict_1.default.ok(call.args.includes("--strict-mcp-config"));
152
+ strict_1.default.deepEqual(JSON.parse(call.args[call.args.indexOf("--mcp-config") + 1]), { mcpServers: {} });
153
+ strict_1.default.deepEqual(JSON.parse(call.args[call.args.indexOf("--settings") + 1]), { disableAllHooks: true });
154
+ strict_1.default.ok(call.args.includes("--no-session-persistence"));
155
+ strict_1.default.ok(!call.args.includes("--model"));
156
+ strict_1.default.deepEqual(JSON.parse(call.args[call.args.indexOf("--json-schema") + 1]), modelResponseSchema);
157
+ await strict_1.default.rejects((0, promises_1.readFile)((0, node_path_1.join)(context.dataDir, "cli-sessions.json")), /ENOENT/);
158
+ await strict_1.default.rejects(plugin.handle({ task: "missing-structured", modelResponseSchema }), /结构化/);
159
+ const controller = new AbortController();
160
+ const pending = strict_1.default.rejects(plugin.handle({ task: "hang", modelResponseSchema, signal: controller.signal }), /取消/);
161
+ setTimeout(() => controller.abort(), 100);
162
+ await pending;
163
+ }
164
+ finally {
165
+ await plugin.deactivate("stop");
166
+ }
167
+ });
168
+ (0, node_test_1.default)("Claude 独立模型来源解析 prompt Schema,不保存管理会话", async (t) => {
169
+ const { root, context } = await setup(t);
170
+ const plugin = new claude_cli_1.default();
171
+ strict_1.default.equal(plugin.modelProvider, undefined);
172
+ await plugin.activate(context);
173
+ try {
174
+ const responseSchema = { type: "object", properties: { text: { type: "string" } }, required: ["text"], additionalProperties: false };
175
+ const provider = plugin.modelProvider;
176
+ strict_1.default.ok(provider);
177
+ plugin.handle = async () => { throw new Error("模型来源不能委托普通 handle"); };
178
+ const response = await provider.complete({
179
+ prompt: JSON.stringify({ instructions: "只返回结构化结果", responseSchema }),
180
+ sessionId: "pa-model",
181
+ timeoutMs: 2_000,
182
+ signal: new AbortController().signal,
183
+ });
184
+ strict_1.default.deepEqual(JSON.parse(response.text), { text: "模型回复" });
185
+ await strict_1.default.rejects((0, promises_1.readFile)((0, node_path_1.join)(context.dataDir, "cli-sessions.json")), /ENOENT/);
186
+ await strict_1.default.rejects(provider.complete({ prompt: JSON.stringify({ instructions: "缺少 Schema" }), sessionId: "pa-model", timeoutMs: 2_000, signal: new AbortController().signal }), /Schema/);
187
+ const call = JSON.parse((await (0, promises_1.readFile)((0, node_path_1.join)(root, "calls.jsonl"), "utf8")).trim());
188
+ strict_1.default.equal(call.args[call.args.indexOf("--tools") + 1], "");
189
+ strict_1.default.ok(call.args.includes("--strict-mcp-config"));
190
+ strict_1.default.deepEqual(JSON.parse(call.args[call.args.indexOf("--mcp-config") + 1]), { mcpServers: {} });
191
+ strict_1.default.deepEqual(JSON.parse(call.args[call.args.indexOf("--settings") + 1]), { disableAllHooks: true });
192
+ strict_1.default.deepEqual(JSON.parse(call.args[call.args.indexOf("--json-schema") + 1]), responseSchema);
193
+ strict_1.default.ok(call.args.includes("--no-session-persistence"));
194
+ strict_1.default.ok(!call.args.includes("--model"));
195
+ }
196
+ finally {
197
+ await plugin.deactivate("stop");
198
+ }
199
+ strict_1.default.equal(plugin.modelProvider, undefined);
200
+ });
201
+ (0, node_test_1.default)("Codex CLI 提供隔离模型来源,且普通对话仍可用", async (t) => {
202
+ const { root, context } = await setup(t);
203
+ const plugin = new codex_cli_1.default();
204
+ strict_1.default.ok(plugin.manifest.capabilities.includes("model-provider"));
205
+ strict_1.default.equal(plugin.modelProvider, undefined);
206
+ await plugin.activate(context);
207
+ try {
208
+ strict_1.default.equal(plugin.modelInput, true);
209
+ const responseSchema = { type: "object", properties: { text: { type: "string" } }, required: ["text"], additionalProperties: false };
210
+ const provider = plugin.modelProvider;
211
+ strict_1.default.ok(provider);
212
+ const response = await provider.complete({
213
+ prompt: JSON.stringify({ instructions: "只返回结构化结果", responseSchema }),
214
+ sessionId: "pa-codex-model",
215
+ timeoutMs: 2_000,
216
+ signal: new AbortController().signal,
217
+ });
218
+ strict_1.default.deepEqual(JSON.parse(response.text), { text: "模型回复" });
219
+ const call = JSON.parse((await (0, promises_1.readFile)((0, node_path_1.join)(root, "calls.jsonl"), "utf8")).trim());
220
+ strict_1.default.equal(call.args[0], "exec");
221
+ strict_1.default.ok(call.args.includes("--ephemeral"));
222
+ strict_1.default.deepEqual(call.args.slice(call.args.indexOf("--sandbox"), call.args.indexOf("--sandbox") + 2), ["--sandbox", "read-only"]);
223
+ for (const feature of ["shell_tool", "apps", "plugins", "hooks"]) {
224
+ const index = call.args.indexOf(feature);
225
+ strict_1.default.ok(index > 0);
226
+ strict_1.default.equal(call.args[index - 1], "--disable");
227
+ }
228
+ strict_1.default.ok(call.args.includes("--ignore-rules"));
229
+ strict_1.default.ok(call.args.includes("mcp_servers={}"));
230
+ strict_1.default.ok(call.args.includes("--output-schema"));
231
+ strict_1.default.ok(!call.args.includes("resume"));
232
+ strict_1.default.ok(!call.args.includes("--model"));
233
+ await strict_1.default.rejects((0, promises_1.readFile)((0, node_path_1.join)(context.dataDir, "cli-sessions.json")), /ENOENT/);
234
+ strict_1.default.equal((await plugin.handle({ task: "普通 Codex 请求" })).text, "普通 Codex 请求");
235
+ const calls = (await (0, promises_1.readFile)((0, node_path_1.join)(root, "calls.jsonl"), "utf8")).trim().split("\n").map(line => JSON.parse(line));
236
+ strict_1.default.ok(calls.some(entry => entry.args[0] === "exec" && entry.args.includes("--json")));
237
+ }
238
+ finally {
239
+ await plugin.deactivate("stop");
240
+ }
241
+ });
242
+ (0, node_test_1.default)("CLI 模型失败保留退出码但不记录外部错误夹带的提示正文", async (t) => {
243
+ const { context } = await setup(t);
244
+ const records = [];
245
+ const plugin = new codex_cli_1.default();
246
+ await plugin.activate({ ...context, logger: { info() { }, warn() { }, error: (_message, fields) => records.push(fields) } });
247
+ plugin.run = async () => { throw new Error("codex CLI 退出码 5:请检查 CLI 配置及网络;外部回显的私密用户提示"); };
248
+ try {
249
+ await strict_1.default.rejects(plugin.modelProvider.complete({ prompt: JSON.stringify({ responseSchema: { type: "object" } }), sessionId: "model", timeoutMs: 1000, signal: new AbortController().signal }), error => /退出码 5/.test(String(error)) && !/私密用户提示/.test(String(error)));
250
+ strict_1.default.doesNotMatch(JSON.stringify(records), /私密用户提示/);
251
+ }
252
+ finally {
253
+ await plugin.deactivate("stop");
254
+ }
255
+ });
111
256
  (0, node_test_1.default)("配置无效和 CLI 未安装时不冒充就绪", async (t) => {
112
257
  const { context } = await setup(t);
113
258
  const plugin = new codex_cli_1.default();
@@ -0,0 +1,152 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const strict_1 = __importDefault(require("node:assert/strict"));
7
+ const node_test_1 = __importDefault(require("node:test"));
8
+ const cdp_client_1 = require("../src-ts/plugins/qwenwork/cdp-client");
9
+ const cdp_client_2 = require("../src-ts/plugins/chatgpt/cdp-client");
10
+ const cdp_client_3 = require("../src-ts/plugins/workbuddy/cdp-client");
11
+ const cdp_client_4 = require("../src-ts/plugins/doubao/cdp-client");
12
+ const flush = () => new Promise(resolve => setImmediate(resolve));
13
+ (0, node_test_1.default)("千问取消后保留生成中的任务,确认本轮空回复结束才收尾", async (t) => {
14
+ t.mock.timers.enable({ apis: ["setTimeout", "Date"] });
15
+ const client = new cdp_client_1.QwenCdpClient();
16
+ const controller = new AbortController();
17
+ let submitted = false;
18
+ let generating = false;
19
+ let error;
20
+ client.ensureVisible = client.recoverDraft = client.waitFor = async () => undefined;
21
+ client.readArtifacts = async () => [];
22
+ client.cdp = async () => { submitted = true; };
23
+ client.close = () => strict_1.default.fail("取消不应关闭桌面连接");
24
+ client.evaluate = async (expression) => {
25
+ if (expression === "location.href")
26
+ return "file:///index.html?chat=model-chat";
27
+ if (expression.startsWith("JSON.stringify(["))
28
+ return JSON.stringify(submitted ? [{ id: "current", text: "" }] : []);
29
+ if (expression.startsWith("Boolean(document.querySelector('button"))
30
+ return generating;
31
+ return true;
32
+ };
33
+ const running = client.complete("模型提示", 10, controller.signal).catch((value) => { error = value; });
34
+ await flush();
35
+ controller.abort();
36
+ for (let index = 0; index < 2; index++) {
37
+ t.mock.timers.tick(2000);
38
+ await flush();
39
+ }
40
+ strict_1.default.equal(error, undefined, "尚未观察到本轮生成时不能猜测任务结束");
41
+ generating = true;
42
+ t.mock.timers.tick(2000);
43
+ await flush();
44
+ strict_1.default.equal(error, undefined);
45
+ generating = false;
46
+ t.mock.timers.tick(2000);
47
+ await flush();
48
+ strict_1.default.equal(error, undefined, "单次未生成状态不能释放任务");
49
+ t.mock.timers.tick(2000);
50
+ await flush();
51
+ strict_1.default.match(String(error), /QWEN_REQUEST_CANCELLED.*已取消.*本轮.*结束/);
52
+ await running;
53
+ });
54
+ (0, node_test_1.default)("ChatGPT 取消后等待本轮生成结束,空回复不永久占用原任务", async (t) => {
55
+ t.mock.timers.enable({ apis: ["setTimeout", "Date"] });
56
+ const client = new cdp_client_2.ChatgptCdpClient();
57
+ const controller = new AbortController();
58
+ let active = false;
59
+ let error;
60
+ client.waitFor = client.waitForGeneration = client.recoverDraft = async () => undefined;
61
+ client.activeSession = async () => ({ id: "model", host: "local", title: "模型会话" });
62
+ client.cdp = async () => undefined;
63
+ client.close = () => strict_1.default.fail("取消不应关闭桌面连接");
64
+ client.evaluate = async (expression) => {
65
+ if (expression.includes("findLast((item, index)"))
66
+ return JSON.stringify({ submitted: true, active, reply: "", hasArtifacts: false, artifacts: [{ conversationId: "model", messageId: "current" }] });
67
+ if (expression.startsWith("JSON.stringify("))
68
+ return "[]";
69
+ if (expression.endsWith(".length"))
70
+ return 0;
71
+ return true;
72
+ };
73
+ const running = client.direct("模型提示", 10, undefined, controller.signal).catch((value) => { error = value; });
74
+ await flush();
75
+ controller.abort();
76
+ for (let index = 0; index < 2; index++) {
77
+ t.mock.timers.tick(800);
78
+ await flush();
79
+ }
80
+ strict_1.default.equal(error, undefined, "消息已提交但尚未开始生成时不能释放任务");
81
+ active = true;
82
+ t.mock.timers.tick(800);
83
+ await flush();
84
+ strict_1.default.equal(error, undefined);
85
+ active = false;
86
+ t.mock.timers.tick(800);
87
+ await flush();
88
+ strict_1.default.equal(error, undefined, "单次停止状态不能释放任务");
89
+ t.mock.timers.tick(800);
90
+ await flush();
91
+ strict_1.default.match(String(error), /ChatGPT.*已取消.*本轮.*结束/);
92
+ await running;
93
+ });
94
+ (0, node_test_1.default)("WorkBuddy 取消后等待本轮完成标记,空回复也能结束等待", async (t) => {
95
+ t.mock.timers.enable({ apis: ["setTimeout", "Date"] });
96
+ const client = new cdp_client_3.WorkbuddyCdpClient();
97
+ const controller = new AbortController();
98
+ let finished = false;
99
+ let error;
100
+ client.evaluate = async () => JSON.stringify([{ id: "current", text: "", finished, stopped: false }]);
101
+ client.readInteraction = async () => undefined;
102
+ client.readArtifacts = async () => [];
103
+ client.close = () => strict_1.default.fail("取消不应关闭桌面连接");
104
+ const running = client.waitForReply(new Set(), 10, undefined, undefined, "model", undefined, controller.signal).catch((value) => { error = value; });
105
+ controller.abort();
106
+ t.mock.timers.tick(1000);
107
+ await flush();
108
+ strict_1.default.equal(error, undefined);
109
+ finished = true;
110
+ t.mock.timers.tick(1000);
111
+ await flush();
112
+ strict_1.default.match(String(error), /WORKBUDDY_REQUEST_CANCELLED.*已取消.*本轮.*结束/);
113
+ await running;
114
+ });
115
+ (0, node_test_1.default)("豆包取消后的空 SSE 终态释放队列,下一轮不会接到前轮结果", async (t) => {
116
+ t.mock.timers.enable({ apis: ["setTimeout", "Date"] });
117
+ const client = new cdp_client_4.DoubaoCdpClient({ timeoutMs: 10 });
118
+ const controller = new AbortController();
119
+ let requests = 0;
120
+ let finished = false;
121
+ let error;
122
+ client.connect = async () => undefined;
123
+ client.close = () => strict_1.default.fail("取消不应关闭桌面连接");
124
+ client.evaluate = async (expression) => {
125
+ if (expression === "window.__mab_doubao_progress")
126
+ return undefined;
127
+ if (expression !== "window.__mab_doubao_result") {
128
+ requests++;
129
+ return "started";
130
+ }
131
+ if (requests === 1)
132
+ return finished ? JSON.stringify({ ok: true, reply: "", pending: false, conversationId: "123" }) : null;
133
+ return JSON.stringify({ ok: true, reply: "只属于下一轮的回复", conversationId: "456" });
134
+ };
135
+ const first = client.complete("取消提示", controller.signal, undefined, "").catch((value) => { error = value; });
136
+ await flush();
137
+ const second = client.complete("新请求", undefined, undefined, "");
138
+ controller.abort();
139
+ t.mock.timers.tick(1000);
140
+ await flush();
141
+ strict_1.default.equal(error, undefined);
142
+ strict_1.default.equal(requests, 1, "页面请求未结束时,下一轮不得提交");
143
+ finished = true;
144
+ t.mock.timers.tick(1000);
145
+ await flush();
146
+ strict_1.default.match(String(error), /豆包.*已取消.*本轮.*结束/);
147
+ strict_1.default.equal(requests, 2, "已确认空结果后应释放原队列");
148
+ t.mock.timers.tick(1000);
149
+ await flush();
150
+ strict_1.default.deepEqual(await second, { text: "只属于下一轮的回复", sessionId: "456" });
151
+ await first;
152
+ });
@@ -13,7 +13,7 @@ const session_management_1 = require("../src-ts/session-management");
13
13
  const source = (peer) => JSON.stringify(["aun", "local", "", peer, ""]);
14
14
  const context = { source: source("admin"), sessionId: "manager" };
15
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" }] }] };
16
+ const settings = { enabled: false, allowFrom: ["history-admin.agentid.cn"], grants: [], nativeGrants: [{ managerSource: context.source, managerSessionId: context.sessionId, providers: [{ pluginId: "demo", scope: "instance:one" }] }] };
17
17
  const refOf = (text) => text.match(/[a-f0-9]{12}\.1/)[0];
18
18
  async function setup() {
19
19
  const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "mab-native-"));
@@ -29,10 +29,10 @@ async function setup() {
29
29
  const service = new session_management_1.SessionManagement(root, undefined, undefined, () => [{ pluginId: "demo", provider: current, identity, status }]);
30
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
31
  }
32
- (0, node_test_1.default)("原生授权独立于归档授权,无授权或新逻辑会话不调用 provider", async () => {
32
+ (0, node_test_1.default)("限制模式下原生授权独立于归档授权,无授权或新逻辑会话不调用 provider", async () => {
33
33
  const f = await setup();
34
34
  try {
35
- await f.service.saveConfig({ enabled: true, grants: [{ managerSource: context.source, managerSessionId: context.sessionId, sources: [source("other")] }] });
35
+ await f.service.saveConfig({ enabled: true, allowFrom: ["history-admin.agentid.cn"], grants: [{ managerSource: context.source, managerSessionId: context.sessionId, sources: [source("other")] }] });
36
36
  strict_1.default.match((await f.service.command("/sessions native-list demo", context)), /无权/);
37
37
  strict_1.default.deepEqual(f.calls, []);
38
38
  await f.service.saveConfig(settings);
@@ -52,10 +52,10 @@ async function setup() {
52
52
  await f.close();
53
53
  }
54
54
  });
55
- (0, node_test_1.default)("原生配置保持旧输出兼容,并拒绝通配符、重复及多余字段", async () => {
55
+ (0, node_test_1.default)("原生精确授权保持兼容,并拒绝 scope 通配符、重复及多余字段", async () => {
56
56
  const f = await setup();
57
57
  try {
58
- strict_1.default.deepEqual(await f.service.config(), { enabled: false, grants: [] });
58
+ strict_1.default.deepEqual(await f.service.config(), { enabled: false, allowFrom: [], grants: [] });
59
59
  await f.service.saveConfig(settings);
60
60
  strict_1.default.deepEqual((await f.service.config()).nativeGrants, settings.nativeGrants);
61
61
  for (const invalid of [
@@ -77,7 +77,7 @@ async function setup() {
77
77
  await f.service.saveConfig(settings);
78
78
  const ref = refOf((await f.service.command("/sessions native-list demo", context)));
79
79
  if (change === "revoke")
80
- await f.service.saveConfig({ enabled: false, grants: [], nativeGrants: [] });
80
+ await f.service.saveConfig({ enabled: false, allowFrom: ["history-admin.agentid.cn"], grants: [], nativeGrants: [] });
81
81
  else
82
82
  f[change]();
83
83
  strict_1.default.match((await f.service.command(`/sessions native-show ${ref}`, context)), /编号|无权|离线/);
@@ -142,10 +142,11 @@ async function setup() {
142
142
  await f.service.saveConfig(settings);
143
143
  const ref = refOf((await f.service.command("/sessions native-list demo", context)));
144
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; };
145
+ f.provider.readSession = async (query) => { const result = await original(query); await f.service.saveConfig({ enabled: false, allowFrom: ["history-admin.agentid.cn"], grants: [] }); return result; };
146
146
  const result = (await f.service.command(`/sessions native-show ${ref}`, context));
147
147
  strict_1.default.match(result, /权限|编号/);
148
148
  strict_1.default.ok(!result.includes("不可信正文"));
149
+ strict_1.default.match((await f.service.command("/sessions native-list demo", context)), /无权/);
149
150
  await f.service.saveConfig(settings);
150
151
  f.provider.listSessions = async () => { throw new Error("secret-token"); };
151
152
  strict_1.default.ok(!(await f.service.command("/sessions native-list demo", context)).includes("secret-token"));
@@ -0,0 +1,92 @@
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 = require("node:test");
11
+ const coordination_1 = require("../src-ts/plugins/personal-assistant/coordination");
12
+ const tools_1 = require("../src-ts/plugins/personal-assistant/tools");
13
+ async function fixture(responses) {
14
+ const dir = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "pa-coordination-"));
15
+ const logPath = (0, node_path_1.join)(dir, "args.jsonl");
16
+ const ecPath = (0, node_path_1.join)(dir, "ec");
17
+ const responder = `const fs=require('node:fs');const args=process.argv.slice(2);fs.appendFileSync(${JSON.stringify(logPath)},JSON.stringify(args)+'\\n');const value=(${responses.toString()})(args);process.stdout.write(JSON.stringify(value));`;
18
+ await (0, promises_1.writeFile)(ecPath, `#!/usr/bin/env node\n${responder}\n`);
19
+ await (0, promises_1.chmod)(ecPath, 0o755);
20
+ return {
21
+ ecPath,
22
+ coordinator: (0, coordination_1.createCoordinator)({ ecPath, selfAid: "self.agentid.cn", allowedAgents: [{ id: "worker", aids: ["worker.agentid.cn"] }] }),
23
+ args: async () => { try {
24
+ return (await (0, promises_1.readFile)(logPath, "utf8")).trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
25
+ }
26
+ catch {
27
+ return [];
28
+ } },
29
+ cleanup: () => (0, promises_1.rm)(dir, { recursive: true, force: true }),
30
+ };
31
+ }
32
+ (0, node_test_1.test)("Agent 名单留空可协调派活,但仍校验核心的可调用状态和真实 AID 绑定", async () => {
33
+ const value = await fixture(args => args[0] === "thread" ? { thread_id: "thread-all" } : { ok: true });
34
+ const management = { listAgents: async () => [{ id: "worker", aids: ["worker.agentid.cn"], callable: true }] };
35
+ try {
36
+ const runtime = { selfAid: "self.agentid.cn", ecPath: value.ecPath };
37
+ await strict_1.default.rejects((0, tools_1.executeTool)("coordination_dispatch", { agentId: "worker", aid: "other.agentid.cn", task: "派活" }, management, [], runtime), /不可派活/);
38
+ const result = await (0, tools_1.executeTool)("coordination_dispatch", { agentId: "worker", aid: "worker.agentid.cn", task: "派活" }, management, [], runtime);
39
+ strict_1.default.deepEqual(result, { status: "pending", agentId: "worker", aid: "worker.agentid.cn", threadId: "thread-all" });
40
+ strict_1.default.equal((await value.args()).length, 2);
41
+ }
42
+ finally {
43
+ await value.cleanup();
44
+ }
45
+ });
46
+ (0, node_test_1.test)("派活只允许白名单内匹配的 Agent 与 AID", async () => {
47
+ const value = await fixture(() => ({ thread_id: "thread-1" }));
48
+ try {
49
+ await strict_1.default.rejects(value.coordinator.dispatch({ agentId: "other", aid: "worker.agentid.cn", task: "工作" }), /白名单/);
50
+ await strict_1.default.rejects(value.coordinator.dispatch({ agentId: "worker", aid: "other.agentid.cn", task: "工作" }), /白名单/);
51
+ await strict_1.default.rejects(value.coordinator.dispatch({ agentId: "worker; touch /tmp/no", aid: "worker.agentid.cn", task: "工作" }), /白名单/);
52
+ strict_1.default.deepEqual(await value.args(), []);
53
+ }
54
+ finally {
55
+ await value.cleanup();
56
+ }
57
+ });
58
+ (0, node_test_1.test)("创建 thread 后用 argv 派活,特殊字符作为普通任务文本传入", async () => {
59
+ const value = await fixture((args) => args[0] === "thread" ? { ok: true, thread_id: "thread-1" } : { ok: true, status: "completed" });
60
+ try {
61
+ const task = "检查 x; touch /tmp/no && $(whoami)";
62
+ const dispatched = await value.coordinator.dispatch({ agentId: "worker", aid: "worker.agentid.cn", task });
63
+ strict_1.default.deepEqual(dispatched, { status: "pending", agentId: "worker", aid: "worker.agentid.cn", threadId: "thread-1" });
64
+ strict_1.default.deepEqual(await value.args(), [
65
+ ["thread", "create", "worker.agentid.cn", "--name", "个人助理派活-worker", "--format", "json"],
66
+ ["msg", "send", "self.agentid.cn", "worker.agentid.cn", "--text", task, "--thread", "thread-1", "--return", "required", "--format", "json"],
67
+ ]);
68
+ }
69
+ finally {
70
+ await value.cleanup();
71
+ }
72
+ });
73
+ (0, node_test_1.test)("thread history 未明确标记完成时保持 pending,且不重发任务", async () => {
74
+ const value = await fixture((args) => {
75
+ if (args[0] === "thread" && args[1] === "create")
76
+ return { thread_id: "thread-1" };
77
+ if (args[0] === "msg")
78
+ return { ok: true, status: "completed" };
79
+ return { messages: [{ from: "worker.agentid.cn", text: "仍在处理" }] };
80
+ });
81
+ try {
82
+ await value.coordinator.dispatch({ agentId: "worker", aid: "worker.agentid.cn", task: "执行任务" });
83
+ const result = await value.coordinator.getStatus({ aid: "worker.agentid.cn", threadId: "thread-1" });
84
+ strict_1.default.deepEqual(result, { status: "pending", aid: "worker.agentid.cn", threadId: "thread-1", history: { messages: [{ from: "worker.agentid.cn", text: "仍在处理" }] } });
85
+ const calls = await value.args();
86
+ strict_1.default.equal(calls.filter((args) => args[0] === "msg" && args[1] === "send").length, 1);
87
+ strict_1.default.deepEqual(calls.at(-1), ["thread", "history", "thread-1", "--format", "json"]);
88
+ }
89
+ finally {
90
+ await value.cleanup();
91
+ }
92
+ });
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const strict_1 = __importDefault(require("node:assert/strict"));
7
+ const node_test_1 = require("node:test");
8
+ const tools_1 = require("../src-ts/plugins/personal-assistant/tools");
9
+ const management = {
10
+ listAgents: async () => [{ id: "worker", displayName: "工作 Agent", status: "ready", available: true, reason: "", capabilities: ["chat"], aids: ["worker.agentid.cn"], callable: true }],
11
+ callAgent: async () => ({ text: "" }), listAunIdentities: async () => ({}), createAunIdentity: async () => ({}), bindAunIdentity: async () => ({}),
12
+ runSessionCommand: async () => "", summarizeToday: async () => ({ supported: false, text: "", sources: [], complete: false }),
13
+ };
14
+ (0, node_test_1.test)("扩展工具只向配置的 Provider 访问远程 Ability", async () => {
15
+ let calls = 0;
16
+ const remoteAbility = { discover: async () => { calls++; return []; }, call: async () => { calls++; return { status: "completed" }; }, getRequest: async () => { calls++; return { status: "pending" }; } };
17
+ await strict_1.default.rejects((0, tools_1.executeTool)("ability_discover", { providerAid: "other.agentid.cn" }, management, [], { allowedProviders: ["provider.agentid.cn"], remoteAbility: remoteAbility }));
18
+ strict_1.default.deepEqual(await (0, tools_1.executeTool)("ability_discover", { providerAid: "provider.agentid.cn" }, management, [], { allowedProviders: ["provider.agentid.cn"], remoteAbility: remoteAbility }), []);
19
+ strict_1.default.equal(calls, 1);
20
+ });
21
+ (0, node_test_1.test)("协调派活要求核心可用状态、Agent 白名单和绑定 AID 同时匹配", async () => {
22
+ const args = { agentId: "worker", aid: "worker.agentid.cn", task: "执行工作" };
23
+ await strict_1.default.rejects((0, tools_1.executeTool)("coordination_dispatch", args, management, ["other"], { selfAid: "assistant.agentid.cn" }), /白名单/);
24
+ await strict_1.default.rejects((0, tools_1.executeTool)("coordination_dispatch", { ...args, aid: "other.agentid.cn" }, management, ["worker"], { selfAid: "assistant.agentid.cn" }), /不可派活/);
25
+ strict_1.default.deepEqual((0, tools_1.toolDefinitions)().map((tool) => tool.function.name).slice(-6), ["ability_discover", "ability_call", "ability_request_get", "coordination_dispatch", "coordination_status", "owner_context_read"]);
26
+ });
27
+ (0, node_test_1.test)("thread 状态查询只允许当前插件进程已派发的 thread", async () => {
28
+ await strict_1.default.rejects((0, tools_1.executeTool)("coordination_status", { aid: "worker.agentid.cn", threadId: "thread-1" }, management, [], { selfAid: "assistant.agentid.cn", threads: new Map() }), /不属于/);
29
+ });
30
+ (0, node_test_1.test)("Agent 名单留空允许列举和调用,填写名单后限制目标", async () => {
31
+ const agents = await (0, tools_1.executeTool)("agents_list", {}, management, []);
32
+ strict_1.default.equal(agents[0].callable, true);
33
+ strict_1.default.deepEqual(await (0, tools_1.executeTool)("agents_call", { id: "worker", task: "测试" }, management, []), { text: "" });
34
+ await strict_1.default.rejects((0, tools_1.executeTool)("agents_call", { id: "worker", task: "测试" }, management, ["other"]), /白名单/);
35
+ });
@@ -0,0 +1,117 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const strict_1 = __importDefault(require("node:assert/strict"));
7
+ const node_test_1 = require("node:test");
8
+ const promises_1 = require("node:fs/promises");
9
+ const node_os_1 = require("node:os");
10
+ const node_path_1 = require("node:path");
11
+ const session_memory_1 = require("../src-ts/plugins/personal-assistant/session-memory");
12
+ const turn = (content) => [{ role: "user", content }, { role: "assistant", content: `回答:${content}` }];
13
+ const toolTurn = [
14
+ { role: "user", content: "调用 WorkBuddy 记住 PA_TOOL" },
15
+ { role: "assistant", content: null, tool_calls: [{ id: "call-1", type: "function", function: { name: "agents_call", arguments: '{"id":"workbuddy","task":"记住 PA_TOOL"}' } }] },
16
+ { role: "tool", tool_call_id: "call-1", name: "agents_call", content: '{"text":"已记住 PA_TOOL"}' },
17
+ { role: "assistant", content: "WorkBuddy 已记住" },
18
+ ];
19
+ (0, node_test_1.test)("会话历史裁剪保留完整轮次和工具调用配对", async () => {
20
+ const dataDir = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "pa-memory-trim-"));
21
+ try {
22
+ const memory = new session_memory_1.PersonalAssistantSessionMemory(dataDir);
23
+ await memory.save("thread", [...toolTurn, ...Array.from({ length: 19 }, (_, i) => turn(String(i))).flat()]);
24
+ const result = await memory.load("thread");
25
+ strict_1.default.equal(result.length, 38);
26
+ strict_1.default.equal(result[0].role, "user");
27
+ strict_1.default.equal(result.some(message => message.role === "tool"), false);
28
+ await memory.save("tool-thread", toolTurn);
29
+ strict_1.default.deepEqual(await new session_memory_1.PersonalAssistantSessionMemory(dataDir).load("tool-thread"), toolTurn);
30
+ }
31
+ finally {
32
+ await (0, promises_1.rm)(dataDir, { recursive: true, force: true });
33
+ }
34
+ });
35
+ (0, node_test_1.test)("并发首次读取和保存不遗漏不同会话,文件保持私有", async () => {
36
+ const dataDir = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "pa-memory-concurrent-"));
37
+ try {
38
+ await (0, promises_1.writeFile)((0, node_path_1.join)(dataDir, "model-sessions.json"), JSON.stringify({ old: turn("旧记录") }));
39
+ const memory = new session_memory_1.PersonalAssistantSessionMemory(dataDir);
40
+ strict_1.default.deepEqual(await Promise.all([memory.load("old"), memory.load("old")]), [turn("旧记录"), turn("旧记录")]);
41
+ await Promise.all(Array.from({ length: 10 }, (_, i) => memory.save(`thread-${i}`, turn(`内容-${i}`))));
42
+ const restored = new session_memory_1.PersonalAssistantSessionMemory(dataDir);
43
+ for (let i = 0; i < 10; i++)
44
+ strict_1.default.deepEqual(await restored.load(`thread-${i}`), turn(`内容-${i}`));
45
+ if (process.platform !== "win32")
46
+ strict_1.default.equal((await (0, promises_1.stat)((0, node_path_1.join)(dataDir, "model-sessions.json"))).mode & 0o777, 0o600);
47
+ }
48
+ finally {
49
+ await (0, promises_1.rm)(dataDir, { recursive: true, force: true });
50
+ }
51
+ });
52
+ (0, node_test_1.test)("损坏历史不回显正文或把多余字段带入模型", async () => {
53
+ const dataDir = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "pa-memory-invalid-"));
54
+ const warnings = [];
55
+ try {
56
+ const file = (0, node_path_1.join)(dataDir, "model-sessions.json");
57
+ await (0, promises_1.writeFile)(file, '{"private-token-secret-value": malformed');
58
+ const memory = new session_memory_1.PersonalAssistantSessionMemory(dataDir, (...args) => warnings.push(args));
59
+ strict_1.default.deepEqual(await memory.load("old"), []);
60
+ strict_1.default.equal(warnings.length, 1);
61
+ strict_1.default.doesNotMatch(JSON.stringify(warnings), /private-token-secret-value/);
62
+ await (0, promises_1.writeFile)(file, JSON.stringify({ good: turn("可用"), bad: [{ role: "user", content: "测试", secret: "private-token-secret-value" }, { role: "tool", content: "孤立工具结果", tool_call_id: "missing" }] }));
63
+ const restored = new session_memory_1.PersonalAssistantSessionMemory(dataDir);
64
+ strict_1.default.deepEqual(await restored.load("good"), turn("可用"));
65
+ strict_1.default.deepEqual(await restored.load("bad"), []);
66
+ }
67
+ finally {
68
+ await (0, promises_1.rm)(dataDir, { recursive: true, force: true });
69
+ }
70
+ });
71
+ (0, node_test_1.test)("历史条数与字符数超限时丢弃完整旧轮次,容量超限淘汰最旧会话", async () => {
72
+ const dataDir = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "pa-memory-limit-"));
73
+ try {
74
+ const memory = new session_memory_1.PersonalAssistantSessionMemory(dataDir);
75
+ await memory.save("large", turn("字".repeat(121_000)));
76
+ strict_1.default.ok(JSON.stringify(await memory.load("large")).length <= 120_000);
77
+ for (let i = 0; i < 130; i++)
78
+ await memory.save(`s-${i}`, turn(String(i)));
79
+ const raw = JSON.parse(await (0, promises_1.readFile)((0, node_path_1.join)(dataDir, "model-sessions.json"), "utf8"));
80
+ strict_1.default.equal(Object.keys(raw).length, 128);
81
+ strict_1.default.deepEqual(await memory.load("s-0"), []);
82
+ strict_1.default.deepEqual(await memory.load("s-129"), turn("129"));
83
+ }
84
+ finally {
85
+ await (0, promises_1.rm)(dataDir, { recursive: true, force: true });
86
+ }
87
+ });
88
+ (0, node_test_1.test)("历史写盘失败保留内存上下文并记录安全提示", async () => {
89
+ const dataDir = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "pa-memory-write-failure-"));
90
+ const warnings = [];
91
+ try {
92
+ const blocked = (0, node_path_1.join)(dataDir, "不可写目录");
93
+ await (0, promises_1.writeFile)(blocked, "阻挡目录创建");
94
+ const memory = new session_memory_1.PersonalAssistantSessionMemory(blocked, (...args) => warnings.push(args));
95
+ await memory.save("thread", turn("有效回复"));
96
+ strict_1.default.deepEqual(await memory.load("thread"), turn("有效回复"));
97
+ strict_1.default.ok(JSON.stringify(warnings).includes("保存失败"));
98
+ strict_1.default.doesNotMatch(JSON.stringify(warnings), /有效回复|不可写目录/);
99
+ }
100
+ finally {
101
+ await (0, promises_1.rm)(dataDir, { recursive: true, force: true });
102
+ }
103
+ });
104
+ (0, node_test_1.test)("格式错误的新历史不会删除先前可用的内存记录", async () => {
105
+ const dataDir = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "pa-memory-invalid-save-"));
106
+ const warnings = [];
107
+ try {
108
+ const memory = new session_memory_1.PersonalAssistantSessionMemory(dataDir, (...args) => warnings.push(args));
109
+ await memory.save("thread", turn("先前回复"));
110
+ await memory.save("thread", [{ role: "tool", content: "孤立结果", tool_call_id: "missing" }]);
111
+ strict_1.default.deepEqual(await memory.load("thread"), turn("先前回复"));
112
+ strict_1.default.ok(JSON.stringify(warnings).includes("格式无效"));
113
+ }
114
+ finally {
115
+ await (0, promises_1.rm)(dataDir, { recursive: true, force: true });
116
+ }
117
+ });