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
@@ -50,6 +50,38 @@ const dsh_1 = require("../src-ts/plugins/dsh");
50
50
  strict_1.default.deepEqual(plugin.sessionOperations, ["new"]);
51
51
  strict_1.default.equal(plugin.attachmentInput, true);
52
52
  });
53
+ (0, node_test_1.default)("DSH 插件通过桌面 CDP 发送并返回真实会话标识", async () => {
54
+ const calls = [];
55
+ const client = {
56
+ connect: async () => { calls.push("connect"); },
57
+ close: () => { calls.push("close"); },
58
+ connected: () => true,
59
+ activeSession: async () => "",
60
+ createSession: async () => { calls.push("new"); return "dsh-session-7"; },
61
+ activateSession: async (id) => { calls.push(`select:${id}`); },
62
+ complete: async (request) => { calls.push(`send:${request.task}`); return { text: "桌面回复" }; },
63
+ };
64
+ const plugin = Reflect.construct(dsh_1.DshPlugin, [{ client }]);
65
+ await plugin.activate({ config: {} });
66
+ const response = await plugin.handle({ task: "CDP 历史可见性测试", sessionId: "logical-id", newSession: true });
67
+ strict_1.default.deepEqual(calls, ["connect", "new", "send:CDP 历史可见性测试"]);
68
+ strict_1.default.deepEqual(response, { text: "桌面回复", sessionId: "dsh-session-7" });
69
+ strict_1.default.deepEqual(await plugin.health(), { status: "ready", details: { transport: "cdp", port: 9226, connected: true } });
70
+ await plugin.deactivate();
71
+ });
72
+ (0, node_test_1.default)("DSH 插件不能在 CDP 断开时回退到后台 CLI", async () => {
73
+ const calls = [];
74
+ const client = {
75
+ connect: async () => { throw new Error("DSH CDP 端口不可达"); },
76
+ close: () => { calls.push("close"); },
77
+ connected: () => false,
78
+ };
79
+ const plugin = Reflect.construct(dsh_1.DshPlugin, [{ client }]);
80
+ await strict_1.default.rejects(plugin.activate({ config: {} }), /DSH CDP 端口不可达/);
81
+ strict_1.default.equal((await plugin.health()).status, "failed");
82
+ strict_1.default.deepEqual((await plugin.health()).details, { transport: "cdp", port: 9226, connected: false });
83
+ strict_1.default.deepEqual(calls, ["close"]);
84
+ });
53
85
  function fakeSpawn(_file, _args, _options) {
54
86
  const child = new node_events_1.EventEmitter();
55
87
  child.stdin = { end: (task) => { child.task = task; queueMicrotask(() => { child.stdout.emit("data", `{"type":"session","sessionId":"session-1"}\n`); child.stdout.emit("data", `{"type":"final","text":"hello from model"}\n`); child.emit("close", 0); }); } };
@@ -62,6 +94,30 @@ function fakeSpawn(_file, _args, _options) {
62
94
  const agent = new dsh_agent_1.DshAgent({ patchPath: "patch.yml", paths: { executable: "dsh.exe", cli: "cli.js", patch: "patch.yml" }, spawn: fakeSpawn });
63
95
  strict_1.default.deepEqual(await agent.send({ task: "hi" }), { text: "hello from model", sessionId: "session-1" });
64
96
  });
97
+ (0, node_test_1.default)("DSH 纯文字请求不追加附件处理说明", async () => {
98
+ let task = "";
99
+ const spawn = (file, args, options) => {
100
+ const child = fakeSpawn(file, args, options);
101
+ const end = child.stdin.end;
102
+ child.stdin.end = (value) => { task = value; end(value); };
103
+ return child;
104
+ };
105
+ await new dsh_agent_1.DshAgent({ patchPath: "patch.yml", paths: { executable: "dsh.exe", cli: "cli.js", patch: "patch.yml" }, spawn }).send({ task: "只回答这一句" });
106
+ strict_1.default.equal(task, "只回答这一句");
107
+ });
108
+ (0, node_test_1.default)("DSH 为支持附件回复的通道提供回传目录但不声明空输入", async () => {
109
+ let task = "";
110
+ const spawn = (file, args, options) => {
111
+ const child = fakeSpawn(file, args, options);
112
+ const end = child.stdin.end;
113
+ child.stdin.end = (value) => { task = value; end(value); };
114
+ return child;
115
+ };
116
+ await new dsh_agent_1.DshAgent({ patchPath: "patch.yml", paths: { executable: "dsh.exe", cli: "cli.js", patch: "patch.yml" }, spawn }).send({ task: "生成报告", attachmentOutput: true });
117
+ strict_1.default.match(task, /附件传输:\n\{"outputDirectory":"/);
118
+ strict_1.default.doesNotMatch(task, /"inputs":\[\]/);
119
+ strict_1.default.match(task, /写入.*outputDirectory.*present/);
120
+ });
65
121
  (0, node_test_1.default)("DSH agent includes a session id when continuing", async () => {
66
122
  let captured = [];
67
123
  const spawn = (file, args, options) => { captured = args; return fakeSpawn(file, args, options); };
@@ -77,6 +133,18 @@ function fakeSpawn(_file, _args, _options) {
77
133
  strict_1.default.equal(captured.includes("--session-id"), false);
78
134
  strict_1.default.equal(response.sessionId, "session-1");
79
135
  });
136
+ (0, node_test_1.default)("DSH 模型入口只发送决策提示,不追加普通附件任务指令", async () => {
137
+ let input = "";
138
+ const spawn = (file, args, options) => {
139
+ const child = fakeSpawn(file, args, options);
140
+ const end = child.stdin.end;
141
+ child.stdin.end = (task) => { input = task; end(task); };
142
+ return child;
143
+ };
144
+ const agent = new dsh_agent_1.DshAgent({ patchPath: "patch.yml", paths: { executable: "dsh.exe", cli: "cli.js", patch: "patch.yml" }, spawn });
145
+ await agent.send({ task: "只返回结构化决策" }, true);
146
+ strict_1.default.equal(input, "只返回结构化决策");
147
+ });
80
148
  (0, node_test_1.default)("DSH 新会话缺少真实 ID 时拒绝用逻辑 ID 冒充创建成功", async () => {
81
149
  const spawn = () => {
82
150
  const child = fakeSpawn("", [], {});
@@ -89,6 +157,25 @@ function fakeSpawn(_file, _args, _options) {
89
157
  const agent = new dsh_agent_1.DshAgent({ patchPath: "patch.yml", paths: { executable: "dsh.exe", cli: "cli.js", patch: "patch.yml" }, spawn });
90
158
  await strict_1.default.rejects(agent.send({ task: "首条消息", sessionId: "logical-id", newSession: true }), /未返回真实新会话标识/);
91
159
  });
160
+ (0, node_test_1.default)("DSH 取消时只终止本次启动的 CLI 子进程", async () => {
161
+ let child;
162
+ let killed = 0;
163
+ const spawn = () => {
164
+ child = new node_events_1.EventEmitter();
165
+ child.stdin = { end: () => undefined };
166
+ child.stdout = new node_events_1.EventEmitter();
167
+ child.stderr = new node_events_1.EventEmitter();
168
+ child.kill = () => { killed++; queueMicrotask(() => child.emit("close", null)); return true; };
169
+ return child;
170
+ };
171
+ const agent = new dsh_agent_1.DshAgent({ patchPath: "patch.yml", paths: { executable: "dsh.exe", cli: "cli.js", patch: "patch.yml" }, spawn, defaultTimeoutMs: 20 });
172
+ const controller = new AbortController();
173
+ const task = agent.send({ task: "长任务", signal: controller.signal });
174
+ await new Promise((resolve) => setImmediate(resolve));
175
+ controller.abort(new Error("模型请求已取消"));
176
+ await strict_1.default.rejects(task, /模型请求已取消/);
177
+ strict_1.default.equal(killed, 1);
178
+ });
92
179
  (0, node_test_1.default)("DSH 附件先落盘供真实工具读取,成功 present 后回传原字节与 MIME 并清理临时目录", async () => {
93
180
  const { readFile, copyFile, access } = await Promise.resolve().then(() => __importStar(require("node:fs/promises")));
94
181
  const { join } = await Promise.resolve().then(() => __importStar(require("node:path")));
@@ -178,13 +265,14 @@ function presentSpawn(prepare, status = "completed", text = "已完成") {
178
265
  const withoutPresent = await new dsh_agent_1.DshAgent({ patchPath: "patch.yml", paths: { executable: "dsh.exe", cli: "cli.js", patch: "patch.yml" }, spawn: fakeSpawn }).send({ task: "只读取", attachments: [input] });
179
266
  strict_1.default.equal(withoutPresent.attachments, undefined);
180
267
  });
181
- (0, node_test_1.default)("DSH 无输入附件时仍可生成并回传文件", async () => {
268
+ (0, node_test_1.default)("DSH 为支持附件回复的通道生成并回传无输入文件", async () => {
182
269
  const spawn = presentSpawn(async (block) => {
270
+ strict_1.default.equal(block.inputs, undefined);
183
271
  const path = (0, node_path_1.join)(block.outputDirectory, "result.txt");
184
272
  await (await Promise.resolve().then(() => __importStar(require("node:fs/promises")))).writeFile(path, "生成文件");
185
273
  return [path];
186
274
  });
187
- const response = await new dsh_agent_1.DshAgent({ patchPath: "patch.yml", paths: { executable: "dsh.exe", cli: "cli.js", patch: "patch.yml" }, spawn }).send({ task: "创建文本文件并回传" });
275
+ const response = await new dsh_agent_1.DshAgent({ patchPath: "patch.yml", paths: { executable: "dsh.exe", cli: "cli.js", patch: "patch.yml" }, spawn }).send({ task: "创建文本文件并回传", attachmentOutput: true });
188
276
  strict_1.default.equal(Buffer.from(response.attachments[0].data).toString(), "生成文件");
189
277
  });
190
278
  (0, node_test_1.default)("DSH 允许只有成功交付附件的空正文回复", async () => {
@@ -193,7 +281,7 @@ function presentSpawn(prepare, status = "completed", text = "已完成") {
193
281
  await (await Promise.resolve().then(() => __importStar(require("node:fs/promises")))).writeFile(path, "附件正文");
194
282
  return [path];
195
283
  }, "completed", "");
196
- const response = await new dsh_agent_1.DshAgent({ patchPath: "patch.yml", paths: { executable: "dsh.exe", cli: "cli.js", patch: "patch.yml" }, spawn }).send({ task: "只回传文件" });
284
+ const response = await new dsh_agent_1.DshAgent({ patchPath: "patch.yml", paths: { executable: "dsh.exe", cli: "cli.js", patch: "patch.yml" }, spawn }).send({ task: "只回传文件", attachmentOutput: true });
197
285
  strict_1.default.equal(response.text, "");
198
286
  strict_1.default.equal(response.attachments?.length, 1);
199
287
  });
@@ -216,6 +304,7 @@ function presentSpawn(prepare, status = "completed", text = "已完成") {
216
304
  }
217
305
  });
218
306
  (0, node_test_1.default)("DSH 拒绝回传超量和超大附件", async () => {
307
+ const input = { kind: "file", data: Buffer.from("输入"), filename: "输入.txt", contentType: "text/plain" };
219
308
  for (const limit of ["count", "size"]) {
220
309
  const spawn = presentSpawn(async (block) => {
221
310
  const path = (0, node_path_1.join)(block.outputDirectory, "large.txt");
@@ -225,7 +314,7 @@ function presentSpawn(prepare, status = "completed", text = "已完成") {
225
314
  await truncate(path, 32 * 1024 * 1024 + 1);
226
315
  return limit === "count" ? Array.from({ length: 9 }, (_, index) => (0, node_path_1.join)(block.outputDirectory, `${index}.txt`)) : [path];
227
316
  });
228
- await strict_1.default.rejects(new dsh_agent_1.DshAgent({ patchPath: "patch.yml", paths: { executable: "dsh.exe", cli: "cli.js", patch: "patch.yml" }, spawn }).send({ task: "回传文件" }), /最多|大小/);
317
+ await strict_1.default.rejects(new dsh_agent_1.DshAgent({ patchPath: "patch.yml", paths: { executable: "dsh.exe", cli: "cli.js", patch: "patch.yml" }, spawn }).send({ task: "回传文件", attachments: [input] }), /最多|大小/);
229
318
  }
230
319
  });
231
320
  (0, node_test_1.default)("DSH 使用发现的真实安装路径构建 CLI 路径,启动前拒绝不存在的程序", async () => {
@@ -237,10 +326,6 @@ function presentSpawn(prepare, status = "completed", text = "已完成") {
237
326
  const paths = await (0, dsh_paths_1.resolveDshPaths)("patch.yml", executable);
238
327
  strict_1.default.equal(paths.executable, executable);
239
328
  strict_1.default.equal(paths.cli, (0, node_path_1.join)(root, "resources", "app.asar", "dsh", "node_modules", "@deepseek-ai", "dsh-desktop-host", "lib", "cli.js"));
240
- const plugin = new dsh_1.DshPlugin();
241
- await plugin.activate({ config: { executablePath: executable } });
242
- strict_1.default.equal(plugin.agent.paths.executable, executable);
243
- await plugin.deactivate();
244
329
  process.env.DSH_INSTALL_DIR = root;
245
330
  strict_1.default.equal((await (0, dsh_paths_1.resolveDshPaths)("patch.yml")).executable, executable);
246
331
  await (0, promises_1.rm)(executable);
@@ -255,3 +340,67 @@ function presentSpawn(prepare, status = "completed", text = "已完成") {
255
340
  await (0, promises_1.rm)(root, { recursive: true, force: true });
256
341
  }
257
342
  });
343
+ (0, node_test_1.default)("DSH 模型排队取消立即返回,桌面请求完成前不启动模型 CLI", async () => {
344
+ let release;
345
+ const sent = [];
346
+ const plugin = new dsh_1.DshPlugin({ client: {
347
+ connect: async () => undefined, close() { }, connected: () => true,
348
+ activeSession: async () => "desktop-session", createSession: async () => "desktop-session", activateSession: async () => undefined,
349
+ complete: async (request) => { sent.push(request); if (request.task === "用户任务")
350
+ await new Promise(resolve => { release = resolve; }); return { text: "回复" }; },
351
+ } });
352
+ await plugin.activate({ config: {} });
353
+ plugin.agent = { send: async () => { throw new Error("排队取消后不应调用模型 CLI"); } };
354
+ const running = plugin.handle({ task: "用户任务" });
355
+ await new Promise(resolve => setImmediate(resolve));
356
+ const controller = new AbortController();
357
+ let error;
358
+ const model = plugin.modelProvider.complete({ prompt: "不应发送", sessionId: "pa-queued", timeoutMs: 5000, signal: controller.signal }).catch((value) => { error = value; });
359
+ controller.abort(new Error("排队取消"));
360
+ await new Promise(resolve => setImmediate(resolve));
361
+ const canceledImmediately = Boolean(error);
362
+ release();
363
+ await Promise.all([running, model]);
364
+ await plugin.handle({ task: "用户续聊" });
365
+ await plugin.deactivate();
366
+ strict_1.default.equal(canceledImmediately, true);
367
+ strict_1.default.match(error.stage, /queue/);
368
+ strict_1.default.deepEqual(sent, [{ task: "用户任务" }, { task: "用户续聊" }]);
369
+ });
370
+ (0, node_test_1.default)("DSH 模型会话在重载后恢复,普通桌面任务不调用 CLI 或写入模型映射", async () => {
371
+ const directory = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "dsh-model-session-"));
372
+ const executablePath = (0, node_path_1.join)(directory, "DeepSeek Harness.exe");
373
+ await (0, promises_1.writeFile)(executablePath, "");
374
+ const requests = [];
375
+ const desktopRequests = [];
376
+ const { mock } = await Promise.resolve().then(() => __importStar(require("node:test")));
377
+ const stub = mock.method(dsh_agent_1.DshAgent.prototype, "send", async (request, modelRequest) => {
378
+ strict_1.default.equal(modelRequest, true);
379
+ requests.push(request);
380
+ return { text: "模型回复", sessionId: "native-model-session" };
381
+ });
382
+ try {
383
+ for (let round = 0; round < 2; round++) {
384
+ const plugin = new dsh_1.DshPlugin({ client: {
385
+ connect: async () => undefined, close() { }, connected: () => true,
386
+ activeSession: async () => "ordinary-session", createSession: async () => "ordinary-session", activateSession: async () => undefined,
387
+ complete: async (request) => { desktopRequests.push(request.task); return { text: "桌面回复" }; },
388
+ } });
389
+ await plugin.activate({ config: { executablePath }, dataDir: directory });
390
+ await plugin.handle({ task: "普通用户任务", sessionId: "ordinary-session" });
391
+ strict_1.default.equal(plugin.agent, undefined);
392
+ await plugin.modelProvider.complete({ prompt: "模型提示", sessionId: "pa-logical", timeoutMs: 1000, signal: new AbortController().signal });
393
+ await plugin.deactivate();
394
+ }
395
+ strict_1.default.deepEqual(desktopRequests, ["普通用户任务", "普通用户任务"]);
396
+ strict_1.default.equal(requests.length, 2);
397
+ strict_1.default.equal(requests[0].newSession, true);
398
+ strict_1.default.equal(requests[1].sessionId, "native-model-session");
399
+ strict_1.default.equal(requests[1].newSession, undefined);
400
+ strict_1.default.deepEqual(JSON.parse(await (0, promises_1.readFile)((0, node_path_1.join)(directory, "model-provider-sessions.json"), "utf8")), { "pa-logical": "native-model-session" });
401
+ }
402
+ finally {
403
+ stub.mock.restore();
404
+ await (0, promises_1.rm)(directory, { recursive: true, force: true });
405
+ }
406
+ });
@@ -0,0 +1,148 @@
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/dsh/cdp-client");
9
+ class FakeWebSocket {
10
+ readyState = 0;
11
+ onopen;
12
+ onclose;
13
+ onerror;
14
+ onmessage;
15
+ sent = [];
16
+ state = { sessionId: "dsh-session-1", editorText: "", conversationText: "已有历史", busy: false, turnCount: 1, blank: false, pendingAttachments: [] };
17
+ flowNodes = [
18
+ { kind: "user", text: "已有历史\n12:00", userText: "已有历史" },
19
+ { kind: "assistant-step", part: "response", text: "旧回复" },
20
+ { kind: "turn-tail", text: "12:00" },
21
+ ];
22
+ messages = ["已有历史"];
23
+ pendingAttachmentLabel = "待发送附件";
24
+ inserted = "";
25
+ sessionRows = ["dsh-session-1", "dsh-session-2"];
26
+ constructor() { queueMicrotask(() => { this.readyState = 1; this.onopen?.(); }); }
27
+ send(raw) {
28
+ const request = JSON.parse(raw);
29
+ this.sent.push(request);
30
+ if (request.method === "Input.insertText") {
31
+ this.inserted = String(request.params.text);
32
+ this.state.editorText = this.inserted;
33
+ }
34
+ if (request.method === "Input.dispatchKeyEvent" && request.params.type === "keyDown" && request.params.key === "Enter") {
35
+ this.messages.push(this.inserted, "桌面模型回复");
36
+ this.flowNodes.push({ kind: "user", text: `${this.inserted}\n13:15`, userText: this.inserted }, { kind: "turn-process", text: "已完成\n已完成,用时 1 秒" }, { kind: "assistant-step", part: "reasoning", text: "内部思考" }, { kind: "assistant-step", part: "response", text: "桌面模型回复" }, { kind: "turn-tail", text: "13:15" });
37
+ this.state.conversationText = `${this.messages.join("\n\n")}\n13:15\n已完成,用时 1 秒\n13:15`;
38
+ this.state.editorText = "";
39
+ this.state.turnCount++;
40
+ }
41
+ if (request.method === "DOM.setFileInputFiles") {
42
+ this.state.pendingAttachments = request.params.files.map(path => path.replaceAll("\\", "/").split("/").at(-1));
43
+ }
44
+ const expression = String(request.params.expression);
45
+ const value = request.method === "Runtime.evaluate"
46
+ ? expression.includes("[data-conversation-content]")
47
+ ? JSON.stringify({ ...this.state, conversationText: expression.includes('[data-chat-flow-kind="user"]')
48
+ ? this.flowNodes.filter(node => (node.kind === "user" || node.kind === "assistant-step") && node.part !== "reasoning").map(node => node.kind === "user" && expression.includes("conversation.user.text.node") ? node.userText : node.text).join("\n\n")
49
+ : this.flowNodes.map(node => node.text).join("\n\n"), pendingAttachments: expression.includes(`aria-label*=\"${this.pendingAttachmentLabel}\"`) ? this.state.pendingAttachments : [] })
50
+ : expression.includes("const id =")
51
+ ? Function("document", `return ${expression}`)({ querySelectorAll: () => this.sessionRows.map(id => ({
52
+ getAttribute: (key) => key === "data-row-key" ? `session:${id}` : null,
53
+ matches: (selector) => selector.includes('[role="treeitem"]'),
54
+ closest: () => null,
55
+ click: () => { this.state.sessionId = id; },
56
+ })) })
57
+ : true
58
+ : undefined;
59
+ const result = request.method === "Runtime.evaluate" ? { result: { value } }
60
+ : request.method === "DOM.getDocument" ? { root: { nodeId: 1 } }
61
+ : request.method === "DOM.querySelector" ? { nodeId: 2 }
62
+ : {};
63
+ queueMicrotask(() => this.onmessage?.({ data: JSON.stringify({ id: request.id, result }) }));
64
+ }
65
+ close() { this.readyState = 3; this.onclose?.(); }
66
+ }
67
+ (0, node_test_1.default)("DSH CDP 只连接 DSH 会话页,并将用户原文提交到可见历史", async () => {
68
+ let socket;
69
+ const client = new cdp_client_1.DshCdpClient({
70
+ port: 9226,
71
+ fetchImpl: async () => new Response(JSON.stringify([{ type: "page", title: "DeepSeek Harness", url: "dsh-app://app/", webSocketDebuggerUrl: "ws://127.0.0.1:9226/devtools/page/dsh" }])),
72
+ webSocketFactory: () => (socket = new FakeWebSocket()),
73
+ });
74
+ await client.connect();
75
+ strict_1.default.equal(await client.activeSession(), "dsh-session-1");
76
+ const request = { task: "请只回答:CDP历史可见" };
77
+ const result = await client.complete(request);
78
+ strict_1.default.equal(socket?.inserted, request.task);
79
+ strict_1.default.equal(result.text, "桌面模型回复");
80
+ strict_1.default.ok(socket?.sent.some(item => item.method === "Input.dispatchKeyEvent" && item.params.key === "Enter"));
81
+ client.close();
82
+ });
83
+ (0, node_test_1.default)("DSH CDP 通过桌面文件控件上传真实附件", async () => {
84
+ let socket;
85
+ const client = new cdp_client_1.DshCdpClient({
86
+ port: 9226,
87
+ timeoutMs: 3_000,
88
+ fetchImpl: async () => new Response(JSON.stringify([{ type: "page", url: "dsh-app://app/", webSocketDebuggerUrl: "ws://127.0.0.1:9226/devtools/page/dsh" }])),
89
+ webSocketFactory: () => (socket = new FakeWebSocket()),
90
+ });
91
+ await client.connect();
92
+ const result = await client.complete({
93
+ task: "请分析这个文件",
94
+ attachments: [{ kind: "file", filename: "说明.txt", contentType: "text/plain", data: Buffer.from("真实文件内容") }],
95
+ });
96
+ strict_1.default.deepEqual(socket?.state.pendingAttachments, ["说明.txt"]);
97
+ strict_1.default.match(socket?.inserted ?? "", /请读取本条已上传附件:说明\.txt/);
98
+ strict_1.default.ok(socket?.sent.some(item => item.method === "DOM.setFileInputFiles"));
99
+ strict_1.default.equal(result.text, "桌面模型回复");
100
+ client.close();
101
+ });
102
+ (0, node_test_1.default)("DSH CDP 不连接其他 Electron 页面", async () => {
103
+ const client = new cdp_client_1.DshCdpClient({
104
+ port: 9226,
105
+ fetchImpl: async () => new Response(JSON.stringify([{ type: "page", title: "设置", url: "dsh-app://shell/update-dialog.html", webSocketDebuggerUrl: "ws://127.0.0.1:9226/devtools/page/other" }])),
106
+ webSocketFactory: () => { throw new Error("不能连接非 DSH 会话页面"); },
107
+ });
108
+ await strict_1.default.rejects(client.connect(), /未找到 DSH 桌面会话页/);
109
+ });
110
+ (0, node_test_1.default)("DSH CDP 点击侧栏 treeitem 恢复已有会话,并保护当前草稿和任务", async () => {
111
+ let socket;
112
+ const client = new cdp_client_1.DshCdpClient({
113
+ timeoutMs: 100,
114
+ fetchImpl: async () => new Response(JSON.stringify([{ type: "page", url: "dsh-app://app/", webSocketDebuggerUrl: "ws://127.0.0.1:9226/devtools/page/dsh" }])),
115
+ webSocketFactory: () => (socket = new FakeWebSocket()),
116
+ });
117
+ try {
118
+ await client.connect();
119
+ socket.state.editorText = "用户未发送的草稿";
120
+ await strict_1.default.rejects(client.activateSession("dsh-session-2"), /未发送内容或附件/);
121
+ strict_1.default.equal(await client.activeSession(), "dsh-session-1");
122
+ socket.state.editorText = "";
123
+ socket.state.busy = true;
124
+ await strict_1.default.rejects(client.activateSession("dsh-session-2"), /正在生成回复/);
125
+ socket.state.busy = false;
126
+ await client.activateSession("dsh-session-2");
127
+ strict_1.default.equal(await client.activeSession(), "dsh-session-2");
128
+ await strict_1.default.rejects(client.activateSession("不存在的会话"), /未找到可切换的会话/);
129
+ }
130
+ finally {
131
+ client.close();
132
+ }
133
+ });
134
+ (0, node_test_1.default)("DSH 新建允许桌面复用已确认的空白会话,拒绝复用历史会话", async () => {
135
+ let socket;
136
+ const client = new cdp_client_1.DshCdpClient({
137
+ timeoutMs: 100,
138
+ fetchImpl: async () => new Response(JSON.stringify([{ type: "page", url: "dsh-app://app/", webSocketDebuggerUrl: "ws://127.0.0.1:9226/devtools/page/dsh" }])),
139
+ webSocketFactory: () => (socket = new FakeWebSocket()),
140
+ });
141
+ await client.connect();
142
+ await strict_1.default.rejects(client.createSession(), /新会话状态未确认/);
143
+ socket.state.blank = true;
144
+ socket.state.turnCount = 0;
145
+ socket.state.conversationText = "探索未至之境";
146
+ strict_1.default.equal(await client.createSession(), "dsh-session-1");
147
+ client.close();
148
+ });
@@ -56,6 +56,43 @@ const event = (seq, type, data, time = "2026-10-21T02:00:00.000Z") => ({ seq, ty
56
56
  await f.close();
57
57
  }
58
58
  });
59
+ (0, node_test_1.default)("DSH 日志返回工作目录、事件序号和本轮 present 文件", async () => {
60
+ const f = await fixture();
61
+ try {
62
+ await f.session("delivery", [
63
+ event(0, "user/message", { content: [{ type: "text", text: "旧请求" }] }),
64
+ event(1, "deliverables/presented", { turn: 1, callId: "present-1", files: [{ path: "report.txt" }] }),
65
+ ]);
66
+ const state = await f.provider.readDeliveryState("session-delivery");
67
+ strict_1.default.deepEqual(state, { cwd: "C:\\project", sequence: 1, presented: [{ sequence: 1, paths: ["report.txt"] }] });
68
+ }
69
+ finally {
70
+ await f.close();
71
+ }
72
+ });
73
+ (0, node_test_1.default)("DSH 文件任务的作业生命周期事件不阻断附件与正文读取,未知事件仍拒绝交付", async () => {
74
+ const f = await fixture();
75
+ try {
76
+ const job = { jobId: "pwsh-test", owner: "session-jobs", kind: "pwsh", label: "不应回传的命令", startedAt: 1792512000000 };
77
+ const events = [
78
+ event(0, "user/message", { content: [{ type: "text", text: "生成文件" }] }),
79
+ event(1, "job/registered", job),
80
+ event(2, "job/settled", { ...job, finishedAt: 1792512001000, status: "completed", cause: "producer", producerReported: true, detail: "exit code: 0" }),
81
+ event(3, "deliverables/presented", { turn: 1, callId: "present-test", files: [{ path: "report.txt" }] }),
82
+ event(4, "assistant/message", { message: { content: [{ type: "text", text: "文件已生成" }] } }),
83
+ ];
84
+ await f.session("jobs", events);
85
+ strict_1.default.deepEqual(await f.provider.readDeliveryState("session-jobs"), { cwd: "C:\\project", sequence: 4, presented: [{ sequence: 3, paths: ["report.txt"] }] });
86
+ const body = await f.provider.readSession({ sessionId: "session-jobs", limit: 10 });
87
+ strict_1.default.equal(body.supported && body.complete, true);
88
+ strict_1.default.deepEqual(body.supported && body.turns.map(turn => turn.text), ["生成文件", "文件已生成"]);
89
+ await f.session("jobs", [...events, event(5, "job/future-event", {})]);
90
+ await strict_1.default.rejects(f.provider.readDeliveryState("session-jobs"), /未知且不可安全忽略.*job\/future-event/);
91
+ }
92
+ finally {
93
+ await f.close();
94
+ }
95
+ });
59
96
  (0, node_test_1.default)("DSH 遇到未知事件、未知代际或越界游标时显式报告不完整/拒绝", async () => {
60
97
  const f = await fixture();
61
98
  try {
@@ -231,7 +231,7 @@ const feishu_1 = require("../src-ts/plugins/feishu");
231
231
  const manifest = JSON.parse(response.body);
232
232
  strict_1.default.deepEqual(manifest.commands.map((command) => command.name), ["status", "list", "agents", "configure", "bind", "unbind"]);
233
233
  strict_1.default.equal(manifest.protocolVersion, 1);
234
- strict_1.default.equal(manifest.version, "1.0.5");
234
+ strict_1.default.equal(manifest.version, plugin.manifest.version);
235
235
  const run = async (...argv) => {
236
236
  response.statusCode = 0;
237
237
  await postCommands(node_stream_1.Readable.from([JSON.stringify({ protocolVersion: 1, argv })]), response);
@@ -0,0 +1,18 @@
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 http_agent_1 = require("../src-ts/http-agent");
9
+ (0, node_test_1.default)("HTTP 验收 Agent 解析 OpenAI 兼容响应", async () => {
10
+ const original = globalThis.fetch;
11
+ globalThis.fetch = (async () => new Response(JSON.stringify({ choices: [{ message: { content: "reply" } }] }), { status: 200 }));
12
+ try {
13
+ strict_1.default.deepEqual(await new http_agent_1.HttpAgent("http://127.0.0.1:1").send({ task: "hi" }), { text: "reply" });
14
+ }
15
+ finally {
16
+ globalThis.fetch = original;
17
+ }
18
+ });
@@ -48,7 +48,7 @@ const node_util_1 = require("node:util");
48
48
  strict_1.default.match(setup, /Get-Command npm\.cmd/);
49
49
  strict_1.default.match(setup, /\$npmCommand\.Source prefix --global/);
50
50
  strict_1.default.match(setup, /@\(\$nodeDirectory, \$npmDirectory, \$npmPrefix\)/);
51
- strict_1.default.match(setup, /mu-agent-bridge@latest --engine-strict/);
51
+ strict_1.default.match(setup, /mu-agent-bridge@\{1\} --engine-strict/);
52
52
  strict_1.default.match(setup, /--prefer-online/);
53
53
  strict_1.default.match(setup, /npm 安装遇到版本尚未同步或文件占用.*重试/);
54
54
  strict_1.default.doesNotMatch(setup, /mu-agent-bridge@\d+\.\d+\.\d+/);