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
@@ -43,12 +43,15 @@ const promises_1 = require("node:fs/promises");
43
43
  const node_os_1 = require("node:os");
44
44
  const node_path_1 = require("node:path");
45
45
  const node_test_1 = __importDefault(require("node:test"));
46
+ const node_util_1 = require("node:util");
46
47
  const daemon_process_1 = require("../src-ts/daemon-process");
47
48
  const cli = "C:\\Users\\molian\\AppData\\Roaming\\npm\\node_modules\\mu-agent-bridge\\dist\\src-ts\\cli.js";
48
49
  (0, node_test_1.default)("识别同一安装的全部旧 daemon,兼容 Windows 路径与引号", () => {
49
50
  strict_1.default.equal((0, daemon_process_1.isDaemonCommand)(`"C:\\Program Files\\nodejs\\node.exe" ${cli.replaceAll("\\", "/")} start`, cli), true);
50
51
  strict_1.default.equal((0, daemon_process_1.isDaemonCommand)(`node.exe "${cli}" --serve`, cli), true);
51
52
  strict_1.default.equal((0, daemon_process_1.isDaemonCommand)(`node.exe "${cli.replaceAll("\\", "\\\\")}" start`, cli), true);
53
+ const spacedCli = cli.replace("molian", "测试 目录");
54
+ strict_1.default.equal((0, daemon_process_1.isDaemonCommand)(["node", spacedCli, "start"], spacedCli), true);
52
55
  });
53
56
  (0, node_test_1.default)("清理进程时保留 CLI 操作、其他安装及普通 Node 程序", () => {
54
57
  for (const args of ["restart", "stop", "update", "status"])
@@ -68,7 +71,7 @@ const cli = "C:\\Users\\molian\\AppData\\Roaming\\npm\\node_modules\\mu-agent-br
68
71
  await (0, promises_1.mkdir)(plugin, { recursive: true });
69
72
  await (0, promises_1.writeFile)((0, node_path_1.join)(plugin, "plugin.json"), JSON.stringify({ id: "aun", version: "1.0.0", type: "channel", entry: "index.cjs", apiVersion: "1", capabilities: [] }));
70
73
  await (0, promises_1.writeFile)((0, node_path_1.join)(plugin, "index.cjs"), "module.exports={activate:async()=>{},deactivate:async()=>{},health:async()=>({status:'ready'})};");
71
- await (0, promises_1.writeFile)(preload, `require(${JSON.stringify((0, node_path_1.resolve)(__dirname, "../src-ts/plugin-manager.js"))}).PluginManager.prototype.checkPackageUpdates=async()=>{console.log('UPDATE_BLOCKED');return new Promise(()=>{});};`);
74
+ await (0, promises_1.writeFile)(preload, `require(${JSON.stringify((0, node_path_1.resolve)(__dirname, "../src-ts/core-updater.js"))}).updateCoreAtStartup=async()=>false;require(${JSON.stringify((0, node_path_1.resolve)(__dirname, "../src-ts/plugin-manager.js"))}).PluginManager.prototype.checkPackageUpdates=async()=>{console.log('UPDATE_BLOCKED');return new Promise(()=>{});};`);
72
75
  child = (0, node_child_process_1.spawn)(process.execPath, ["--require", preload, (0, node_path_1.resolve)(__dirname, "../src-ts/cli.js"), "start"], {
73
76
  env: { ...process.env, AGENT_BRIDGE_STATE_DIR: state, AGENT_BRIDGE_PLUGINS_DIR: (0, node_path_1.join)(root, "plugins"), AGENT_BRIDGE_HOST: "127.0.0.1", AGENT_BRIDGE_PORT: "0", AGENT_BRIDGE_TOKEN: "" },
74
77
  stdio: ["ignore", "pipe", "pipe"],
@@ -118,3 +121,125 @@ const cli = "C:\\Users\\molian\\AppData\\Roaming\\npm\\node_modules\\mu-agent-br
118
121
  await (0, promises_1.rm)(root, { recursive: true, force: true });
119
122
  }
120
123
  });
124
+ (0, node_test_1.default)("核心升级等待旧后台慢停,且只认可新进程的健康状态", { skip: process.platform === "win32", timeout: 15_000 }, async () => {
125
+ const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "mab-update-restart-"));
126
+ const state = (0, node_path_1.join)(root, "state");
127
+ const addressFile = (0, node_path_1.join)(state, "daemon-address.json");
128
+ const stoppedFile = (0, node_path_1.join)(root, "old-stopped");
129
+ const preload = (0, node_path_1.join)(root, "update.cjs");
130
+ let old;
131
+ let newPid;
132
+ try {
133
+ await (0, promises_1.mkdir)(state);
134
+ const plugin = (0, node_path_1.join)(root, "slow-plugins", "aun");
135
+ await (0, promises_1.mkdir)(plugin, { recursive: true });
136
+ await (0, promises_1.writeFile)((0, node_path_1.join)(plugin, "plugin.json"), JSON.stringify({ id: "aun", version: "1.0.0", type: "channel", entry: "index.cjs", apiVersion: "1", capabilities: [] }));
137
+ await (0, promises_1.writeFile)((0, node_path_1.join)(plugin, "index.cjs"), `module.exports={activate:async()=>{},deactivate:async()=>{await new Promise(done=>setTimeout(done,1200));require('node:fs').writeFileSync(${JSON.stringify(stoppedFile)},'stopped');},health:async()=>({status:'ready'})};`);
138
+ const oldPreload = (0, node_path_1.join)(root, "old.cjs");
139
+ await (0, promises_1.writeFile)(oldPreload, `require(${JSON.stringify((0, node_path_1.resolve)(__dirname, "../src-ts/plugin-manager.js"))}).PluginManager.prototype.checkPackageUpdates=async()=>{console.log('READY');return [];};`);
140
+ old = (0, node_child_process_1.spawn)(process.execPath, [(0, node_path_1.resolve)(__dirname, "../src-ts/cli.js"), "start"], {
141
+ env: { ...process.env, NODE_OPTIONS: `--require=${oldPreload}`, AGENT_BRIDGE_CORE_UPDATE_CHILD: "1", AGENT_BRIDGE_STATE_DIR: state, AGENT_BRIDGE_PLUGINS_DIR: (0, node_path_1.join)(root, "slow-plugins"), AGENT_BRIDGE_HOST: "127.0.0.1", AGENT_BRIDGE_PORT: "0", AGENT_BRIDGE_TOKEN: "" },
142
+ stdio: ["ignore", "pipe", "pipe"],
143
+ });
144
+ let output = "";
145
+ await new Promise(done => old.stdout.on("data", chunk => { output += chunk; if (output.includes("READY"))
146
+ done(); }));
147
+ const previous = JSON.parse(await (0, promises_1.readFile)(addressFile, "utf8"));
148
+ await (0, promises_1.writeFile)(preload, `
149
+ const assert=require('node:assert/strict');
150
+ const fs=require('node:fs/promises');
151
+ const readFile=fs.readFile;
152
+ let maskPid=false;
153
+ fs.readFile=async function(path,...args){
154
+ const data=await readFile(path,...args);
155
+ return maskPid && String(path)===${JSON.stringify(addressFile)} ? JSON.stringify({...JSON.parse(data),pid:process.pid}) : data;
156
+ };
157
+ require(${JSON.stringify((0, node_path_1.resolve)(__dirname, "../src-ts/plugin-manager.js"))}).PluginManager.prototype.checkPackageUpdates=async()=>[];
158
+ if(process.env.MAB_TEST_HOLD_DISCOVERY==='1'){
159
+ require(${JSON.stringify((0, node_path_1.resolve)(__dirname, "../src-ts/plugin-manager.js"))}).PluginManager.prototype.discover=async()=>{
160
+ await fs.writeFile(${JSON.stringify((0, node_path_1.join)(root, "hanging-pid"))},String(process.pid));
161
+ setInterval(()=>{},1000);return new Promise(()=>{});
162
+ };
163
+ }
164
+ require(${JSON.stringify((0, node_path_1.resolve)(__dirname, "../src-ts/core-updater.js"))}).updateCoreAtStartup=async options=>{
165
+ await options.stopDaemon();
166
+ assert.equal(await readFile(${JSON.stringify(stoppedFile)},'utf8'),'stopped','停止操作必须等旧后台释放端口');
167
+ await options.startDaemon();
168
+ for(let attempt=0;attempt<100;attempt++){
169
+ try { await readFile(${JSON.stringify(addressFile)},'utf8');break; } catch { await new Promise(done=>setTimeout(done,20)); }
170
+ }
171
+ maskPid=true;
172
+ let settled=false;
173
+ const health=options.waitForHealth().then(value=>{settled=true;return value;});
174
+ await new Promise(done=>setTimeout(done,200));
175
+ try { assert.equal(settled,false,'其他进程的健康响应不能判定升级成功'); }
176
+ finally { maskPid=false; }
177
+ assert.equal(await health,true);
178
+ await options.stopDaemon();
179
+ process.env.MAB_TEST_HOLD_DISCOVERY='1';
180
+ const hanging=await options.startDaemon();
181
+ for(let attempt=0;attempt<100;attempt++){
182
+ try { await readFile(${JSON.stringify((0, node_path_1.join)(root, "hanging-pid"))},'utf8');break; } catch { await new Promise(done=>setTimeout(done,20)); }
183
+ }
184
+ await assert.rejects(readFile(${JSON.stringify(addressFile)},'utf8'),{code:'ENOENT'});
185
+ await options.stopDaemon(hanging.pid);
186
+ assert.throws(()=>process.kill(hanging.pid,0),{code:'ESRCH'},'未登记的新进程也必须在回滚前停止');
187
+ delete process.env.MAB_TEST_HOLD_DISCOVERY;
188
+ await options.startDaemon();
189
+ assert.equal(await options.waitForHealth(),true);
190
+ console.log('UPDATER_HEALTH=true');
191
+ return true;
192
+ };
193
+ `);
194
+ const { stdout } = await (0, node_util_1.promisify)(node_child_process_1.execFile)(process.execPath, [(0, node_path_1.resolve)(__dirname, "../src-ts/cli.js"), "update"], {
195
+ env: { ...process.env, NODE_OPTIONS: `--require=${preload}`, AGENT_BRIDGE_STATE_DIR: state, AGENT_BRIDGE_PLUGINS_DIR: (0, node_path_1.join)(root, "plugins"), AGENT_BRIDGE_HOST: "127.0.0.1", AGENT_BRIDGE_PORT: String(previous.port), AGENT_BRIDGE_TOKEN: "" },
196
+ timeout: 10_000,
197
+ });
198
+ strict_1.default.match(stdout, /UPDATER_HEALTH=true/);
199
+ const address = JSON.parse(await (0, promises_1.readFile)(addressFile, "utf8"));
200
+ newPid = address.pid;
201
+ strict_1.default.notEqual(newPid, old.pid);
202
+ strict_1.default.equal(address.port, previous.port);
203
+ strict_1.default.equal((await fetch(`http://127.0.0.1:${address.port}/api/health`, { signal: AbortSignal.timeout(2000) })).status, 200);
204
+ }
205
+ finally {
206
+ try {
207
+ newPid ??= JSON.parse(await (0, promises_1.readFile)(addressFile, "utf8")).pid;
208
+ }
209
+ catch { /* 新后台可能未启动 */ }
210
+ if (newPid && newPid !== old?.pid) {
211
+ try {
212
+ process.kill(newPid, "SIGKILL");
213
+ }
214
+ catch { /* 已退出 */ }
215
+ }
216
+ if (old?.pid && old.exitCode === null && old.signalCode === null) {
217
+ const exited = (0, node_events_1.once)(old, "exit");
218
+ old.kill("SIGKILL");
219
+ await exited;
220
+ }
221
+ await (0, promises_1.rm)(root, { recursive: true, force: true });
222
+ }
223
+ });
224
+ (0, node_test_1.default)("旧地址文件的 PID 指向无关进程时只清理地址,不停止该进程", { skip: process.platform === "win32", timeout: 10_000 }, async () => {
225
+ const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "mab-stale-pid-"));
226
+ const unrelated = (0, node_child_process_1.spawn)(process.execPath, ["-e", "setInterval(()=>{},1000);console.log('READY');"], { stdio: ["ignore", "pipe", "pipe"] });
227
+ try {
228
+ await (0, node_events_1.once)(unrelated.stdout, "data");
229
+ const addressFile = (0, node_path_1.join)(root, "daemon-address.json");
230
+ await (0, promises_1.writeFile)(addressFile, JSON.stringify({ pid: unrelated.pid, port: 26107 }));
231
+ await (0, node_util_1.promisify)(node_child_process_1.execFile)(process.execPath, [(0, node_path_1.resolve)(__dirname, "../src-ts/cli.js"), "stop"], {
232
+ env: { ...process.env, AGENT_BRIDGE_STATE_DIR: root }, timeout: 5000,
233
+ });
234
+ strict_1.default.doesNotThrow(() => process.kill(unrelated.pid, 0), "无关进程应继续运行");
235
+ await strict_1.default.rejects((0, promises_1.readFile)(addressFile), { code: "ENOENT" });
236
+ }
237
+ finally {
238
+ if (unrelated.exitCode === null && unrelated.signalCode === null) {
239
+ const exited = (0, node_events_1.once)(unrelated, "exit");
240
+ unrelated.kill("SIGKILL");
241
+ await exited;
242
+ }
243
+ await (0, promises_1.rm)(root, { recursive: true, force: true });
244
+ }
245
+ });
@@ -59,6 +59,31 @@ const desktop_launch_1 = require("../src-ts/plugins/desktop-launch");
59
59
  await (0, promises_1.rm)(root, { recursive: true, force: true });
60
60
  }
61
61
  });
62
+ (0, node_test_1.default)("豆包自动发现支持 LOCALAPPDATA 下的用户安装目录", async () => {
63
+ const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "desktop-localappdata-"));
64
+ const platform = Object.getOwnPropertyDescriptor(process, "platform");
65
+ const previousLocalAppData = process.env.LOCALAPPDATA;
66
+ try {
67
+ const application = (0, node_path_1.join)(root, "Doubao", "Application");
68
+ const executable = (0, node_path_1.join)(application, "app", "Doubao.exe");
69
+ await (0, promises_1.mkdir)((0, node_path_1.join)(application, "app"), { recursive: true });
70
+ await (0, promises_1.writeFile)((0, node_path_1.join)(application, "Doubao.exe"), "");
71
+ await (0, promises_1.writeFile)(executable, "");
72
+ process.env.LOCALAPPDATA = root;
73
+ Object.defineProperty(process, "platform", { value: "win32" });
74
+ const unregistered = async () => ({ stdout: "" });
75
+ strict_1.default.equal(await (0, desktop_launch_1.findWindowsExecutable)("Doubao.exe", undefined, unregistered), executable);
76
+ strict_1.default.equal(await (0, desktop_launch_1.findWindowsExecutable)("Doubao.exe", (0, node_path_1.join)(root, "missing", "Doubao.exe"), unregistered), executable);
77
+ }
78
+ finally {
79
+ Object.defineProperty(process, "platform", platform);
80
+ if (previousLocalAppData === undefined)
81
+ delete process.env.LOCALAPPDATA;
82
+ else
83
+ process.env.LOCALAPPDATA = previousLocalAppData;
84
+ await (0, promises_1.rm)(root, { recursive: true, force: true });
85
+ }
86
+ });
62
87
  (0, node_test_1.default)("桌面程序发现优先选择较新的版本目录", async () => {
63
88
  const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "desktop-launch-versions-"));
64
89
  try {
@@ -131,6 +156,11 @@ const desktop_launch_1 = require("../src-ts/plugins/desktop-launch");
131
156
  await (0, promises_1.rm)(root, { recursive: true, force: true });
132
157
  }
133
158
  });
159
+ (0, node_test_1.default)("管理式启动把 spawn 异步错误转成可捕获异常并清理归属", async () => {
160
+ let owned;
161
+ await strict_1.default.rejects(() => Promise.resolve().then(() => __importStar(require("../src-ts/plugins/desktop-launch"))).then(({ launchManagedAndWait }) => launchManagedAndWait((0, node_path_1.join)((0, node_os_1.tmpdir)(), `missing-desktop-${Date.now()}.exe`), [], undefined, async () => { throw new Error("仍在连接"); }, 400, process => { owned = process?.pid; })), error => error.code === "ENOENT");
162
+ strict_1.default.equal(owned, undefined);
163
+ });
134
164
  (0, node_test_1.default)("使用客户端原有用户目录时,管理式启动不覆盖用户数据目录", async () => {
135
165
  const { launchManagedAndWait } = await Promise.resolve().then(() => __importStar(require("../src-ts/plugins/desktop-launch")));
136
166
  let args = [];
@@ -230,3 +260,46 @@ const desktop_launch_1 = require("../src-ts/plugins/desktop-launch");
230
260
  await (0, promises_1.rm)(root, { recursive: true, force: true });
231
261
  }
232
262
  });
263
+ (0, node_test_1.default)("豆包优先使用注册表或开始菜单路径,并从启动器定位实际客户端", async () => {
264
+ const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "doubao-system-registration-"));
265
+ const platform = Object.getOwnPropertyDescriptor(process, "platform");
266
+ try {
267
+ const launcher = (0, node_path_1.join)(root, "Application", "Doubao.exe");
268
+ const client = (0, node_path_1.join)(root, "Application", "app", "Doubao.exe");
269
+ await (0, promises_1.mkdir)((0, node_path_1.join)(root, "Application", "app"), { recursive: true });
270
+ await (0, promises_1.writeFile)(launcher, "");
271
+ await (0, promises_1.writeFile)(client, "");
272
+ Object.defineProperty(process, "platform", { value: "win32" });
273
+ const run = async (_file, _args) => ({ stdout: `${launcher}\r\n` });
274
+ strict_1.default.equal(await (0, desktop_launch_1.findWindowsExecutable)("Doubao.exe", undefined, run), client);
275
+ }
276
+ finally {
277
+ Object.defineProperty(process, "platform", platform);
278
+ await (0, promises_1.rm)(root, { recursive: true, force: true });
279
+ }
280
+ });
281
+ (0, node_test_1.default)("ZCode 从安装注册信息或开始菜单发现自定义路径,失效时查当前用户目录", async () => {
282
+ const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "zcode-discovery-"));
283
+ const platform = Object.getOwnPropertyDescriptor(process, "platform");
284
+ const previous = process.env.LOCALAPPDATA;
285
+ try {
286
+ const registered = (0, node_path_1.join)(root, "自定义应用", "ZCode.exe");
287
+ const fallback = (0, node_path_1.join)(root, "Programs", "ZCode", "ZCode.exe");
288
+ await (0, promises_1.mkdir)((0, node_path_1.join)(root, "自定义应用"), { recursive: true });
289
+ await (0, promises_1.mkdir)((0, node_path_1.join)(root, "Programs", "ZCode"), { recursive: true });
290
+ await (0, promises_1.writeFile)(registered, "");
291
+ await (0, promises_1.writeFile)(fallback, "");
292
+ Object.defineProperty(process, "platform", { value: "win32" });
293
+ process.env.LOCALAPPDATA = root;
294
+ strict_1.default.equal(await (0, desktop_launch_1.findWindowsExecutable)("ZCode.exe", undefined, async () => ({ stdout: registered })), registered);
295
+ strict_1.default.equal(await (0, desktop_launch_1.findWindowsExecutable)("ZCode.exe", undefined, async () => ({ stdout: (0, node_path_1.join)(root, "已卸载", "ZCode.exe") })), fallback);
296
+ }
297
+ finally {
298
+ Object.defineProperty(process, "platform", platform);
299
+ if (previous === undefined)
300
+ delete process.env.LOCALAPPDATA;
301
+ else
302
+ process.env.LOCALAPPDATA = previous;
303
+ await (0, promises_1.rm)(root, { recursive: true, force: true });
304
+ }
305
+ });
@@ -0,0 +1,237 @@
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 node_test_1 = __importDefault(require("node:test"));
7
+ const strict_1 = __importDefault(require("node:assert/strict"));
8
+ const node_vm_1 = require("node:vm");
9
+ const cdp_client_1 = require("../src-ts/plugins/doubao/cdp-client");
10
+ function nativePage(options = {}) {
11
+ const sent = [];
12
+ const modules = {
13
+ enums: { AgentMode: { MOA: 17, Chat: 21 } },
14
+ environment: { sandboxEnvironmentController: { query: async () => ({ isReady: options.ready !== false, environmentId: "native-env" }) } },
15
+ task: { GeneralAgentRuntimeType: { LocalPC: 27 }, buildGeneralAgentCompletionTaskParam: async (chatId) => ({ sandbox_id: "native-env", workspace: `workspace:${chatId}`, client_env_id: "native-client" }) },
16
+ store: { get: function get() { /* it is async store */ return { getState: () => ({ modeSelectConfigs: { menu_item_list: options.mode === false ? [] : [{ agent_mode: 17, menu_type: 9, model_item_key: "native-model", default_mode: "native-mode" }] } }) }; } },
17
+ auth: { snapshot: () => options.authorization === false ? undefined : ({ mode: "ask-risky", sandboxAuthType: 41 }) },
18
+ modify: { build: async (value) => ({ agent_task_param: { runtime_type: value.runtimeType, workspace: value.completionTaskParam.workspace, sandbox_auth_type: value.sandboxAuthType }, need_modify_conversation: true }) },
19
+ connector: { getFeishuConnectorEnabledForConversation: async () => ({ enabled: true, id: "native-lark" }) },
20
+ connectorInfo: { build: function build(enabled, id) { return { connector_id: id, switch: enabled ? 2 : 1, connector_vendor: 2 }; } },
21
+ message: { createSendMessage: (value) => ({ ...value.message, ...value.conversationContext, local_message_id: value.message.unique_id }) },
22
+ service: { get: async function get() {
23
+ return { getMessageService: async () => ({ sendMessage(value) {
24
+ sent.push(value);
25
+ return { cancel() { }, subscribe: () => options.messages ? options.messages(value) : (async function* () {
26
+ yield { ...value.messages[0], message_id: "question", conversation_id: "real-conversation" };
27
+ yield { message_id: "answer", conversation_id: "real-conversation", reply_id: "question", content_blocks_v2: [{ content: { text_block: { text: "命令已执行" } } }], ...(options.failure ? { error_details: { has_error: true, error_msg: "执行失败" } } : {}) };
28
+ })() };
29
+ }, fetchAndUpdateMessageList: async (request) => {
30
+ strict_1.default.equal(typeof request.conversationId, "string");
31
+ strict_1.default.equal(request.options.limit, 50);
32
+ return { messages: options.historyMessages || [] };
33
+ } }) };
34
+ } },
35
+ };
36
+ const markers = {
37
+ enums: "AgentMode:()=>", environment: "sandboxEnvironmentController:()=>", task: "buildGeneralAgentCompletionTaskParam:()=>", store: "chat-ioc-manager: cannot bind empty store", auth: "new WeakMap sandboxAuthType:", modify: "general_agent_conversation_modify_params", connector: "getFeishuConnectorEnabledForConversation:()=>", connectorInfo: "connector_info_list connector_vendor wrapped:!0", message: "createSendMessage:()=>", service: "chat-ioc-manager: cannot bind empty service",
38
+ };
39
+ const factories = Object.fromEntries(Object.keys(modules).map(id => {
40
+ const factory = () => { };
41
+ factory.toString = () => markers[id];
42
+ return [id, factory];
43
+ }));
44
+ const chunks = [[[], factories]];
45
+ chunks.push = (chunk) => { chunk[2]((id) => modules[id]); return 1; };
46
+ const window = { "@flow-web/desktop:stable": chunks };
47
+ return { sent, window };
48
+ }
49
+ (0, node_test_1.default)("豆包客户端默认使用本地电脑,纯聊天需要显式选择", () => {
50
+ strict_1.default.equal(new cdp_client_1.DoubaoCdpClient().options.mode, "local-computer");
51
+ strict_1.default.equal(new cdp_client_1.DoubaoCdpClient({ mode: "chat" }).options.mode, "chat");
52
+ });
53
+ (0, node_test_1.default)("豆包本地电脑生成图片持续输出时可跨过三分钟等待并返回图片附件", async (t) => {
54
+ t.mock.timers.enable({ apis: ["setTimeout", "Date"] });
55
+ const imageUrl = "https://p1-flow-imagex-sign.byteimg.com/long-image.png";
56
+ const png = Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+jL1sAAAAASUVORK5CYII=", "base64");
57
+ const page = nativePage({ messages: async function* (value) {
58
+ yield { ...value.messages[0], message_id: "question", conversation_id: "123" };
59
+ for (let i = 1; i <= 4; i++) {
60
+ await new Promise(resolve => setTimeout(resolve, 60_000));
61
+ yield { message_id: "answer", conversation_id: "123", content_blocks_v2: [{ content: { text_block: { text: `正在生成第 ${i} 步` } } }] };
62
+ }
63
+ yield { message_id: "answer", conversation_id: "123", content_blocks_v2: [
64
+ { content: { text_block: { text: "图片已生成" } } },
65
+ { block_type: 10084, is_finish: true, content: { rich_media_layout_block: { media: [{ type: 6, creation: { type: 1, image: { status: 2, image_ori: { url: imageUrl } } } }] } } },
66
+ ] };
67
+ } });
68
+ const client = new cdp_client_1.DoubaoCdpClient({ fetchImpl: async (input) => {
69
+ strict_1.default.equal(String(input), imageUrl);
70
+ return new Response(png, { headers: { "content-type": "image/png" } });
71
+ } });
72
+ client.connect = async () => { };
73
+ client.evaluate = async (expression) => (0, node_vm_1.runInNewContext)(expression, { window: page.window, Map, Set, JSON, Date, Error, AbortController, URL, performance: { getEntriesByType: () => [] }, navigator: { userAgent: "" } });
74
+ const running = client.complete("生成图片", undefined, undefined, "");
75
+ void running.catch(() => undefined);
76
+ for (let i = 0; i < 242; i++) {
77
+ await new Promise(setImmediate);
78
+ t.mock.timers.tick(1_000);
79
+ }
80
+ const result = await running;
81
+ strict_1.default.equal(result.text, "图片已生成");
82
+ strict_1.default.equal(result.images.length, 1);
83
+ strict_1.default.deepEqual(Buffer.from(result.images[0].data), png);
84
+ strict_1.default.equal(page.sent.length, 1, "持续等待不能重新提交生成任务");
85
+ });
86
+ function start(page, conversationId) {
87
+ (0, node_vm_1.runInNewContext)((0, cdp_client_1.buildLocalComputerInjection)((0, cdp_client_1.requestBody)("请执行命令", conversationId, "bot", ["feishu"], [], "local-computer")), { window: page.window, Map, Set, JSON, Date, Error, AbortController, URL, performance: { getEntriesByType: () => [] }, navigator: { userAgent: "" } });
88
+ }
89
+ async function execute(page, conversationId = "peer-conversation") {
90
+ start(page, conversationId);
91
+ for (let i = 0; i < 100 && !page.window.__mab_doubao_result; i++)
92
+ await new Promise(resolve => setImmediate(resolve));
93
+ strict_1.default.ok(page.window.__mab_doubao_result, "原生任务必须结束并返回结果");
94
+ return JSON.parse(page.window.__mab_doubao_result);
95
+ }
96
+ (0, node_test_1.default)("豆包本地电脑请求使用原生模式、工作区、授权和伙伴,并通过原生发送链返回结果", async () => {
97
+ const page = nativePage();
98
+ const result = await execute(page);
99
+ strict_1.default.equal(result.ok, true);
100
+ strict_1.default.equal(result.reply, "命令已执行");
101
+ strict_1.default.equal(result.conversationId, "real-conversation");
102
+ strict_1.default.equal(page.window.__mab_doubao_progress.requestId, result.requestId);
103
+ strict_1.default.equal(page.window.__mab_doubao_progress.sequence, 2);
104
+ strict_1.default.equal(new URL(page.window.__mab_doubao_request_url).origin, "https://www.doubao.com");
105
+ strict_1.default.equal(page.window.__mab_doubao_controller.signal.aborted, false);
106
+ strict_1.default.equal(page.sent.length, 1);
107
+ const message = page.sent[0].messages[0];
108
+ strict_1.default.equal(message.conversation_id, "peer-conversation");
109
+ strict_1.default.equal(message.ext.agent_mode, "17");
110
+ strict_1.default.equal(message.ext.use_deep_think, "9");
111
+ const general = JSON.parse(message.ext.general_task_param);
112
+ strict_1.default.equal(general.runtime_type, 27);
113
+ strict_1.default.equal(general.client_option.workspace, "workspace:peer-conversation");
114
+ strict_1.default.equal(general.client_option.sandbox_env_id, "native-env");
115
+ strict_1.default.equal(general.client_option.sandbox_auth_type, 41);
116
+ strict_1.default.equal(general.agent_task_param.workspace, "workspace:peer-conversation");
117
+ strict_1.default.equal(JSON.parse(message.ext.connector_info_list)[0].connector_id, "native-lark");
118
+ strict_1.default.deepEqual(JSON.parse(message.ext.mention_plugin_list), ["feishu"]);
119
+ });
120
+ (0, node_test_1.default)("豆包未开通任务模式、环境未就绪或缺少授权时拒绝发送,不能降为纯聊天", async () => {
121
+ for (const options of [{ ready: false }, { mode: false }, { authorization: false }]) {
122
+ const page = nativePage(options);
123
+ const result = await execute(page);
124
+ strict_1.default.equal(result.failed, true);
125
+ strict_1.default.equal(page.sent.length, 0);
126
+ strict_1.default.match(result.reason, /本地电脑|任务模式|授权/);
127
+ }
128
+ });
129
+ (0, node_test_1.default)("豆包新会话沿用独立临时会话标识,初始化本地电脑参数", async () => {
130
+ const page = nativePage();
131
+ const result = await execute(page, "");
132
+ strict_1.default.equal(result.ok, true);
133
+ const message = page.sent[0].messages[0];
134
+ strict_1.default.equal(message.conversation_id, undefined);
135
+ strict_1.default.match(message.local_conversation_id, /^local_/);
136
+ strict_1.default.ok(JSON.parse(message.ext.conversation_init_ext).agent_task_param);
137
+ });
138
+ (0, node_test_1.default)("豆包原生发送失败必须报错,不能将收到的中间文字当作成功", async () => {
139
+ const page = nativePage({ failure: true });
140
+ strict_1.default.equal((await execute(page)).failed, true);
141
+ });
142
+ (0, node_test_1.default)("豆包本地电脑中止终态不能伪装成成功", async () => {
143
+ const page = nativePage({ messages: async function* () {
144
+ yield { message_id: "answer", conversation_id: "broken", content_blocks_v2: [{ content: { text_block: { text: "只完成第一步" } } }], final_status: { session: "Broken" } };
145
+ } });
146
+ const result = await execute(page, "broken");
147
+ strict_1.default.equal(result.failed, true);
148
+ strict_1.default.match(result.reason, /中止/);
149
+ });
150
+ (0, node_test_1.default)("豆包本地电脑等待工具返回的异步视频并通过原生消息列表读取", async () => {
151
+ const page = nativePage({
152
+ messages: async function* (value) {
153
+ yield { ...value.messages[0], message_id: "question", conversation_id: "video-conversation" };
154
+ yield { message_id: "answer", conversation_id: "video-conversation", content_blocks_v2: [{ content: { text_block: { text: "已提交视频" } } }], ext: { has_video_gen: "1" } };
155
+ },
156
+ historyMessages: [{ message_id: "video-message", conversation_id: "video-conversation", reply_id: "question", content_blocks_v2: [{ block_type: 2074, is_finish: true, content: { creation_block: { creations: [{ video: { status: 3, vid: "async-video" } }] } } }] }],
157
+ });
158
+ const result = await execute(page);
159
+ strict_1.default.equal(result.pending, true);
160
+ const media = await page.window.__mab_doubao_read_artifacts(page.window.__mab_doubao_request_id);
161
+ strict_1.default.equal(media.pending, false);
162
+ strict_1.default.equal(media.artifacts[0].vid, "async-video");
163
+ });
164
+ (0, node_test_1.default)("豆包本地电脑旧请求晚结束时不覆盖其他 peer 的正文与会话", async () => {
165
+ let release;
166
+ const gate = new Promise(resolve => { release = resolve; });
167
+ const page = nativePage({ messages: async function* (value) {
168
+ const id = value.messages[0].conversation_id;
169
+ if (id === "first")
170
+ await gate;
171
+ yield { message_id: id, conversation_id: id, content_blocks_v2: [{ content: { text_block: { text: id } } }] };
172
+ } });
173
+ start(page, "first");
174
+ for (let i = 0; i < 100 && page.sent.length === 0; i++)
175
+ await new Promise(resolve => setImmediate(resolve));
176
+ strict_1.default.equal((await execute(page, "second")).reply, "second");
177
+ release();
178
+ await new Promise(resolve => setImmediate(resolve));
179
+ strict_1.default.equal(JSON.parse(page.window.__mab_doubao_result).reply, "second");
180
+ strict_1.default.equal(JSON.parse(page.window.__mab_doubao_result).conversationId, "second");
181
+ });
182
+ (0, node_test_1.default)("豆包旧请求迟到时不能覆盖新请求的异步产物读取器", async () => {
183
+ let release;
184
+ const gate = new Promise(resolve => { release = resolve; });
185
+ const page = nativePage({
186
+ messages: async function* (value) {
187
+ const id = value.messages[0].conversation_id;
188
+ if (id === "first")
189
+ await gate;
190
+ yield { message_id: id, conversation_id: id, reply_id: "question-" + id, content_blocks_v2: [{ content: { text_block: { text: "已提交" } } }], ext: { has_video_gen: "1" } };
191
+ },
192
+ historyMessages: [{ message_id: "second-video", conversation_id: "second", reply_id: "question-second", content_blocks_v2: [{ block_type: 2074, is_finish: true, content: { creation_block: { creations: [{ video: { status: 3, vid: "second-video" } }] } } }] }],
193
+ });
194
+ start(page, "first");
195
+ for (let i = 0; i < 100 && page.sent.length === 0; i++)
196
+ await new Promise(resolve => setImmediate(resolve));
197
+ start(page, "second");
198
+ for (let i = 0; i < 100 && !page.window.__mab_doubao_result; i++)
199
+ await new Promise(resolve => setImmediate(resolve));
200
+ release();
201
+ await new Promise(resolve => setImmediate(resolve));
202
+ const media = await page.window.__mab_doubao_read_artifacts(page.window.__mab_doubao_request_id);
203
+ strict_1.default.equal(media.artifacts[0].vid, "second-video");
204
+ });
205
+ (0, node_test_1.default)("豆包默认客户端通过原生发送链解析文件 URI、视频 VID 和图片", async () => {
206
+ const png = Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+jL1sAAAAASUVORK5CYII=", "base64");
207
+ const video = Buffer.from([0, 0, 0, 12, 102, 116, 121, 112, 0, 0, 0, 0]);
208
+ const imageUrl = "https://p1-flow-imagex-sign.byteimg.com/image.png";
209
+ const videoUrl = "https://v1-vdl.doubao.com/video.mp4";
210
+ const fileUrl = "https://p1-flow-sign.byteimg.com/file.txt";
211
+ const page = nativePage({ messages: async function* () {
212
+ yield { message_id: "answer", conversation_id: "media-conversation", content_blocks_v2: [
213
+ { block_type: 2074, is_finish: true, content: { creation_block: { creations: [
214
+ { image: { status: 2, image_ori: { url: imageUrl } } }, { video: { status: 3, vid: "native-video" } },
215
+ ] } } },
216
+ { block_type: 10020, is_finish: true, content: { file_block: { name: "结果.txt", uri: "native-file" } } },
217
+ ] };
218
+ } });
219
+ const urls = [];
220
+ const context = { window: page.window, Map, Set, JSON, Date, Error, AbortController, URL, performance: { getEntriesByType: () => [] }, navigator: { userAgent: "" }, fetch: async (url, options) => {
221
+ strict_1.default.equal(options.signal.aborted, false);
222
+ urls.push(new URL(url).pathname);
223
+ return new Response(JSON.stringify({ code: 0, data: url.includes("get_play_info") ? { play_infos: [{ main: videoUrl }] } : { file_urls: [{ uri: "native-file", main_url: fileUrl }] } }));
224
+ } };
225
+ const client = new cdp_client_1.DoubaoCdpClient({ timeoutMs: 5000, fetchImpl: async (input) => {
226
+ const url = String(input);
227
+ return new Response(url === imageUrl ? png : url === videoUrl ? video : Buffer.from("文件内容"), { headers: { "content-type": url === imageUrl ? "image/png" : url === videoUrl ? "video/mp4" : "text/plain" } });
228
+ } });
229
+ client.connect = async () => { };
230
+ client.evaluate = async (expression) => (0, node_vm_1.runInNewContext)(expression, context);
231
+ const result = await client.complete("生成产物", undefined, undefined, "");
232
+ strict_1.default.equal(result.sessionId, "media-conversation");
233
+ strict_1.default.equal(result.attachments.length, 3);
234
+ strict_1.default.equal(result.images.length, 1);
235
+ strict_1.default.equal(result.attachments[2].filename, "结果.txt");
236
+ strict_1.default.deepEqual(urls, ["/samantha/video/get_play_info", "/alice/message/get_file_url"]);
237
+ });