mu-agent-bridge 1.0.73 → 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 (152) hide show
  1. package/README.md +12 -8
  2. package/dist/src-ts/cli.js +67 -37
  3. package/dist/src-ts/control-server.js +7 -28
  4. package/dist/src-ts/core-updater.js +141 -33
  5. package/dist/src-ts/daemon-process.js +2 -1
  6. package/dist/src-ts/dsh-agent.js +7 -2
  7. package/dist/src-ts/http-agent.js +36 -0
  8. package/dist/src-ts/plugin-manager.js +185 -58
  9. package/dist/src-ts/plugins/abweb/index.js +1 -1
  10. package/dist/src-ts/plugins/abweb/public/app.js +8 -6
  11. package/dist/src-ts/plugins/abweb/public/index.html +3 -3
  12. package/dist/src-ts/plugins/aun/attachments.js +3 -21
  13. package/dist/src-ts/plugins/aun/index.js +89 -11
  14. package/dist/src-ts/plugins/aun/messages.js +1 -1
  15. package/dist/src-ts/plugins/aun/public/app.js +2 -1
  16. package/dist/src-ts/plugins/aun/public/index.html +1 -1
  17. package/dist/src-ts/plugins/cdp-draft.js +5 -2
  18. package/dist/src-ts/plugins/chatgpt/index.js +3 -2
  19. package/dist/src-ts/plugins/claude-cli/index.js +1 -1
  20. package/dist/src-ts/plugins/desktop-launch.js +44 -3
  21. package/dist/src-ts/plugins/doubao/cdp-client.js +231 -45
  22. package/dist/src-ts/plugins/doubao/index.js +119 -7
  23. package/dist/src-ts/plugins/doubao/native-task.js +134 -0
  24. package/dist/src-ts/plugins/dsh/cdp-client.js +334 -0
  25. package/dist/src-ts/plugins/dsh/index.js +102 -23
  26. package/dist/src-ts/plugins/dsh/session-history.js +31 -3
  27. package/dist/src-ts/plugins/feishu/index.js +1 -1
  28. package/dist/src-ts/plugins/personal-assistant/index.js +29 -8
  29. package/dist/src-ts/plugins/personal-assistant/model-client.js +20 -6
  30. package/dist/src-ts/plugins/personal-assistant/session-memory.js +124 -0
  31. package/dist/src-ts/plugins/personal-assistant/tools.js +4 -3
  32. package/dist/src-ts/plugins/qwenwork/cdp-client.js +43 -7
  33. package/dist/src-ts/plugins/qwenwork/index.js +52 -18
  34. package/dist/src-ts/plugins/workbuddy/cdp-client.js +349 -83
  35. package/dist/src-ts/plugins/workbuddy/index.js +24 -12
  36. package/dist/src-ts/plugins/zcode/cdp-client.js +500 -0
  37. package/dist/src-ts/plugins/zcode/index.js +97 -0
  38. package/dist/src-ts/session-management.js +27 -8
  39. package/dist/src-ts/version-manager.js +6 -3
  40. package/dist/tests-ts/abweb-model-config.test.js +44 -41
  41. package/dist/tests-ts/aun-attachments.test.js +19 -17
  42. package/dist/tests-ts/aun-default-owner-page.test.js +2 -1
  43. package/dist/tests-ts/aun-messages.test.js +14 -0
  44. package/dist/tests-ts/aun-plugin.test.js +207 -19
  45. package/dist/tests-ts/bundled-package.test.js +1 -1
  46. package/dist/tests-ts/cdp-draft-recovery.test.js +182 -3
  47. package/dist/tests-ts/chatgpt-plugin.test.js +19 -0
  48. package/dist/tests-ts/control-server.test.js +20 -0
  49. package/dist/tests-ts/core-updater.test.js +215 -46
  50. package/dist/tests-ts/daemon-process.test.js +126 -1
  51. package/dist/tests-ts/desktop-launch.test.js +73 -0
  52. package/dist/tests-ts/doubao-native-task.test.js +237 -0
  53. package/dist/tests-ts/doubao-plugin.test.js +688 -19
  54. package/dist/tests-ts/dsh-agent.test.js +91 -19
  55. package/dist/tests-ts/dsh-cdp.test.js +148 -0
  56. package/dist/tests-ts/dsh-history.test.js +37 -0
  57. package/dist/tests-ts/feishu-plugin.test.js +1 -1
  58. package/dist/tests-ts/http-agent.test.js +18 -0
  59. package/dist/tests-ts/local-cli.test.js +29 -2
  60. package/dist/tests-ts/model-provider-cancellation.test.js +2 -0
  61. package/dist/tests-ts/native-session-management.test.js +8 -7
  62. package/dist/tests-ts/personal-assistant-coordination.test.js +16 -0
  63. package/dist/tests-ts/personal-assistant-extensions.test.js +7 -1
  64. package/dist/tests-ts/personal-assistant-memory.test.js +117 -0
  65. package/dist/tests-ts/personal-assistant.test.js +277 -3
  66. package/dist/tests-ts/platform-integration.test.js +60 -1
  67. package/dist/tests-ts/plugin-manager.test.js +78 -6
  68. package/dist/tests-ts/plugin-upgrade-deadline.test.js +354 -0
  69. package/dist/tests-ts/publish-packages.test.js +1 -1
  70. package/dist/tests-ts/qwenwork-plugin.test.js +30 -0
  71. package/dist/tests-ts/qwenwork-reconnect.test.js +195 -0
  72. package/dist/tests-ts/qwenwork-sse.test.js +33 -0
  73. package/dist/tests-ts/session-access-defaults.test.js +112 -0
  74. package/dist/tests-ts/session-management.test.js +8 -8
  75. package/dist/tests-ts/version-manager.test.js +40 -0
  76. package/dist/tests-ts/workbuddy-cdp-recovery.test.js +311 -0
  77. package/dist/tests-ts/workbuddy-plugin.test.js +356 -13
  78. package/dist/tests-ts/zcode-cdp.test.js +894 -0
  79. package/dist/tests-ts/zcode-plugin.test.js +396 -0
  80. package/package.json +1 -1
  81. package/plugins/abweb/dist/src-ts/plugins/abweb/index.js +1 -1
  82. package/plugins/abweb/dist/src-ts/plugins/abweb/public/app.js +8 -6
  83. package/plugins/abweb/dist/src-ts/plugins/abweb/public/app.ts +7 -5
  84. package/plugins/abweb/dist/src-ts/plugins/abweb/public/index.html +3 -3
  85. package/plugins/abweb/package.json +1 -1
  86. package/plugins/abweb/plugin.json +1 -1
  87. package/plugins/aun/config.schema.json +1 -1
  88. package/plugins/aun/dist/src-ts/plugins/aun/attachments.js +3 -21
  89. package/plugins/aun/dist/src-ts/plugins/aun/index.js +89 -11
  90. package/plugins/aun/dist/src-ts/plugins/aun/messages.js +1 -1
  91. package/plugins/aun/dist/src-ts/plugins/aun/public/app.js +2 -1
  92. package/plugins/aun/dist/src-ts/plugins/aun/public/index.html +1 -1
  93. package/plugins/aun/package.json +1 -1
  94. package/plugins/aun/plugin.json +1 -1
  95. package/plugins/chatgpt/dist/src-ts/plugins/cdp-draft.js +5 -2
  96. package/plugins/chatgpt/dist/src-ts/plugins/chatgpt/index.js +3 -2
  97. package/plugins/chatgpt/dist/src-ts/plugins/desktop-launch.js +44 -3
  98. package/plugins/claude-cli/dist/src-ts/plugins/claude-cli/index.js +1 -1
  99. package/plugins/claude-cli/package.json +1 -1
  100. package/plugins/claude-cli/plugin.json +1 -1
  101. package/plugins/doubao/config.schema.json +2 -1
  102. package/plugins/doubao/dist/src-ts/plugins/desktop-launch.js +44 -3
  103. package/plugins/doubao/dist/src-ts/plugins/doubao/cdp-client.js +231 -45
  104. package/plugins/doubao/dist/src-ts/plugins/doubao/index.js +119 -7
  105. package/plugins/doubao/dist/src-ts/plugins/doubao/native-task.js +134 -0
  106. package/plugins/doubao/package.json +1 -1
  107. package/plugins/doubao/plugin.json +1 -1
  108. package/plugins/dsh/config.schema.json +5 -2
  109. package/plugins/dsh/dist/src-ts/dsh-agent.js +7 -2
  110. package/plugins/dsh/dist/src-ts/plugins/desktop-launch.js +44 -3
  111. package/plugins/dsh/dist/src-ts/plugins/dsh/cdp-client.js +334 -0
  112. package/plugins/dsh/dist/src-ts/plugins/dsh/index.js +102 -23
  113. package/plugins/dsh/dist/src-ts/plugins/dsh/session-history.js +31 -3
  114. package/plugins/dsh/package.json +1 -1
  115. package/plugins/dsh/plugin.json +1 -1
  116. package/plugins/feishu/dist/src-ts/plugins/feishu/index.js +1 -1
  117. package/plugins/feishu/package.json +1 -1
  118. package/plugins/feishu/plugin.json +1 -1
  119. package/plugins/personal-assistant/config.schema.json +3 -2
  120. package/plugins/personal-assistant/dist/src-ts/attachments.js +62 -0
  121. package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/index.js +29 -8
  122. package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/model-client.js +20 -6
  123. package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/session-memory.js +124 -0
  124. package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/tools.js +4 -3
  125. package/plugins/personal-assistant/package.json +2 -2
  126. package/plugins/personal-assistant/plugin.json +1 -1
  127. package/plugins/qwenwork/dist/src-ts/plugins/cdp-draft.js +5 -2
  128. package/plugins/qwenwork/dist/src-ts/plugins/desktop-launch.js +44 -3
  129. package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/cdp-client.js +43 -7
  130. package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/index.js +52 -18
  131. package/plugins/qwenwork/package.json +1 -1
  132. package/plugins/qwenwork/plugin.json +1 -1
  133. package/plugins/workbuddy/config.schema.json +1 -1
  134. package/plugins/workbuddy/dist/src-ts/plugins/desktop-launch.js +44 -3
  135. package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/cdp-client.js +349 -83
  136. package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/index.js +24 -12
  137. package/plugins/workbuddy/package.json +1 -1
  138. package/plugins/workbuddy/plugin.json +1 -1
  139. package/plugins/zcode/config.schema.json +13 -0
  140. package/plugins/zcode/dist/src-ts/agent.js +69 -0
  141. package/plugins/zcode/dist/src-ts/attachments.js +62 -0
  142. package/plugins/zcode/dist/src-ts/plugin.js +13 -0
  143. package/plugins/zcode/dist/src-ts/plugins/desktop-launch.js +238 -0
  144. package/plugins/zcode/dist/src-ts/plugins/workbuddy/cdp-client.js +896 -0
  145. package/plugins/zcode/dist/src-ts/plugins/workbuddy/cli-client.js +39 -0
  146. package/plugins/zcode/dist/src-ts/plugins/workbuddy/desktop-launch.js +10 -0
  147. package/plugins/zcode/dist/src-ts/plugins/workbuddy/index.js +567 -0
  148. package/plugins/zcode/dist/src-ts/plugins/workbuddy/session-history.js +121 -0
  149. package/plugins/zcode/dist/src-ts/plugins/zcode/cdp-client.js +500 -0
  150. package/plugins/zcode/dist/src-ts/plugins/zcode/index.js +97 -0
  151. package/plugins/zcode/package.json +16 -0
  152. package/plugins/zcode/plugin.json +11 -0
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DoubaoPlugin = exports.doubaoManifest = void 0;
4
+ const node_child_process_1 = require("node:child_process");
4
5
  const promises_1 = require("node:fs/promises");
5
6
  const node_path_1 = require("node:path");
6
7
  const agent_1 = require("../../agent");
@@ -11,7 +12,7 @@ const desktop_launch_1 = require("../desktop-launch");
11
12
  const node_os_1 = require("node:os");
12
13
  exports.doubaoManifest = {
13
14
  id: "doubao",
14
- version: "1.0.24",
15
+ version: "1.0.35",
15
16
  displayName: "豆包",
16
17
  entry: "dist/index.js",
17
18
  apiVersion: "1",
@@ -19,6 +20,95 @@ exports.doubaoManifest = {
19
20
  capabilities: ["chat", "health", "model-provider"],
20
21
  configSchema: "config.schema.json",
21
22
  };
23
+ const doubaoLoginEntries = [
24
+ "Local State",
25
+ "saman_app_state",
26
+ "saman_shell_db_storage",
27
+ "saman_shell_persistent_storage_v1",
28
+ "Default/Network",
29
+ "Default/Local Storage",
30
+ "Default/IndexedDB",
31
+ "Default/Session Storage",
32
+ "Default/WebStorage",
33
+ "Default/DoubaoStorage",
34
+ "Default/Preferences",
35
+ "Default/Secure Preferences",
36
+ "Default/Remote_State",
37
+ "Default/Remote_State_Aha_Chromium",
38
+ ];
39
+ function runRobocopy(args) {
40
+ return new Promise((resolve, reject) => (0, node_child_process_1.execFile)("robocopy.exe", args, { windowsHide: true }, error => {
41
+ if (!error)
42
+ return resolve();
43
+ const rawCode = error.code;
44
+ const code = typeof rawCode === "number" ? rawCode : Number(rawCode);
45
+ if (Number.isFinite(code) && code <= 7)
46
+ resolve();
47
+ else
48
+ reject(error);
49
+ }));
50
+ }
51
+ async function copyWithWindowsBackup(from, staged) {
52
+ const sourceInfo = await (0, promises_1.stat)(from);
53
+ const robocopyTarget = `${staged}.robocopy`;
54
+ await (0, promises_1.rm)(robocopyTarget, { recursive: true, force: true });
55
+ await (0, promises_1.mkdir)(robocopyTarget, { recursive: true });
56
+ const args = [
57
+ sourceInfo.isDirectory() ? from : (0, node_path_1.dirname)(from),
58
+ robocopyTarget,
59
+ sourceInfo.isDirectory() ? "*" : (0, node_path_1.basename)(from),
60
+ ...(sourceInfo.isDirectory() ? ["/E"] : []),
61
+ "/B", "/COPY:DAT", "/DCOPY:DAT", "/R:0", "/W:0", "/NFL", "/NDL", "/NJH", "/NJS", "/NP", "/XF", "LOCK", "LOG", "LOG.old", "lockfile",
62
+ ];
63
+ try {
64
+ await runRobocopy(args);
65
+ const copied = sourceInfo.isDirectory() ? robocopyTarget : (0, node_path_1.join)(robocopyTarget, (0, node_path_1.basename)(from));
66
+ await (0, promises_1.rm)(staged, { recursive: true, force: true });
67
+ await (0, promises_1.rename)(copied, staged);
68
+ }
69
+ finally {
70
+ await (0, promises_1.rm)(robocopyTarget, { recursive: true, force: true }).catch(() => undefined);
71
+ }
72
+ }
73
+ async function copyDoubaoLoginState(target) {
74
+ const source = (0, node_path_1.join)(process.env.LOCALAPPDATA ?? (0, node_path_1.join)((0, node_os_1.homedir)(), "AppData", "Local"), "Doubao", "User Data");
75
+ await (0, promises_1.mkdir)(target, { recursive: true });
76
+ for (const entry of doubaoLoginEntries) {
77
+ const from = (0, node_path_1.join)(source, entry);
78
+ const to = (0, node_path_1.join)(target, entry);
79
+ const staged = `${to}.mab-copy`;
80
+ try {
81
+ await (0, promises_1.rm)(staged, { recursive: true, force: true });
82
+ await (0, promises_1.mkdir)((0, node_path_1.dirname)(staged), { recursive: true });
83
+ try {
84
+ await (0, promises_1.cp)(from, staged, {
85
+ recursive: true,
86
+ force: true,
87
+ filter: value => !new Set(["LOCK", "LOG", "LOG.old", "lockfile"]).has(value.split(/[\\/]/).at(-1) ?? ""),
88
+ });
89
+ }
90
+ catch (error) {
91
+ if (error.code !== "EBUSY")
92
+ throw error;
93
+ try {
94
+ await copyWithWindowsBackup(from, staged);
95
+ }
96
+ catch {
97
+ await (0, promises_1.rm)(staged, { recursive: true, force: true }).catch(() => undefined);
98
+ continue;
99
+ }
100
+ }
101
+ await (0, promises_1.rm)(to, { recursive: true, force: true });
102
+ await (0, promises_1.rename)(staged, to);
103
+ }
104
+ catch (error) {
105
+ await (0, promises_1.rm)(staged, { recursive: true, force: true }).catch(() => undefined);
106
+ const code = error.code;
107
+ if (code !== "ENOENT" && code !== "EBUSY")
108
+ throw error;
109
+ }
110
+ }
111
+ }
22
112
  class DoubaoPlugin {
23
113
  injectedClient;
24
114
  manifest = exports.doubaoManifest;
@@ -63,7 +153,14 @@ class DoubaoPlugin {
63
153
  conversationId: typeof context.config.conversationId === "string" ? context.config.conversationId : undefined,
64
154
  botId: typeof context.config.botId === "string" ? context.config.botId : undefined,
65
155
  pluginList: Array.isArray(context.config.pluginList) ? context.config.pluginList.filter((value) => typeof value === "string") : undefined,
156
+ mode: context.config.mode === "chat" ? "chat" : "local-computer",
66
157
  timeoutMs: typeof context.config.timeoutMs === "number" ? context.config.timeoutMs : undefined,
158
+ logger: { warn: message => context.logger?.warn?.(message) },
159
+ recoverCdp: async () => {
160
+ if (!this.reconnect)
161
+ throw new Error("豆包插件尚未配置 CDP 恢复流程");
162
+ await this.reconnect();
163
+ },
67
164
  });
68
165
  this.sessionHistory = new session_history_1.DoubaoSessionHistoryProvider({
69
166
  scope: `doubao:${(0, plugin_1.resolveRuntimePort)(context.config, context.runtime, "doubao", "DOUBAO_CDP_PORT", 9227)}`,
@@ -76,17 +173,30 @@ class DoubaoPlugin {
76
173
  }
77
174
  catch (first) {
78
175
  if (context.config.autoStart !== false && !this.injectedClient && process.platform === "win32") {
176
+ if (!(0, agent_1.errorDetails)(first).includes("ECONNREFUSED"))
177
+ throw first;
79
178
  const executable = await (0, desktop_launch_1.findWindowsExecutable)("Doubao.exe", typeof context.config.executablePath === "string" ? context.config.executablePath : undefined);
80
179
  if (!executable)
81
180
  throw new Error("未找到 Doubao.exe,请配置 executablePath");
82
181
  const port = (0, plugin_1.resolveRuntimePort)(context.config, context.runtime, "doubao", "DOUBAO_CDP_PORT", 9227);
83
182
  context.logger?.warn?.(`正在恢复豆包:${executable},等待 CDP ${port}`);
84
183
  try {
85
- await (0, desktop_launch_1.restartOwnedAndWait)(this.ownedProcess, executable, [`--remote-debugging-port=${port}`], (0, node_path_1.join)(this.dataDir ?? (0, node_path_1.join)((0, node_os_1.homedir)(), ".agent-bridge", "doubao"), "desktop-profile"), () => this.client.connect(), typeof context.config.startupTimeoutMs === "number" ? context.config.startupTimeoutMs : 30_000, child => { this.ownedProcess = child; });
184
+ // 只重启桥接持有的进程;人工实例占用默认目录时使用下方独立目录。
185
+ await (0, desktop_launch_1.restartOwnedAndWait)(this.ownedProcess, executable, [`--remote-debugging-port=${port}`], undefined, () => this.client.connect(), typeof context.config.startupTimeoutMs === "number" ? context.config.startupTimeoutMs : 30_000, child => { this.ownedProcess = child; });
86
186
  return;
87
187
  }
88
188
  catch (error) {
89
189
  first = new Error(`已启动豆包,但 CDP ${port} 未就绪:${error instanceof Error ? error.message : String(error)}`);
190
+ const profile = (0, node_path_1.join)(this.dataDir ?? (0, node_path_1.join)((0, node_os_1.homedir)(), ".agent-bridge", "doubao"), "desktop-profile");
191
+ try {
192
+ await copyDoubaoLoginState(profile);
193
+ context.logger?.warn?.(`默认豆包用户目录不可用,改用登录状态副本:${profile}`);
194
+ await (0, desktop_launch_1.restartOwnedAndWait)(this.ownedProcess, executable, [`--remote-debugging-port=${port}`], profile, () => this.client.connect(), typeof context.config.startupTimeoutMs === "number" ? context.config.startupTimeoutMs : 30_000, child => { this.ownedProcess = child; });
195
+ return;
196
+ }
197
+ catch (fallbackError) {
198
+ first = new Error(`已启动豆包,但 CDP ${port} 未就绪:${fallbackError instanceof Error ? fallbackError.message : String(fallbackError)}`);
199
+ }
90
200
  }
91
201
  }
92
202
  throw first;
@@ -106,13 +216,14 @@ class DoubaoPlugin {
106
216
  throw error;
107
217
  }
108
218
  }
109
- async deactivate() {
219
+ async deactivate(_reason = "stop", options) {
110
220
  await this.queue;
111
221
  this.disposer?.();
112
222
  this.disposer = undefined;
113
223
  this.reconnect = undefined;
114
224
  await this.client?.close();
115
- await (0, desktop_launch_1.terminateOwnedProcess)(this.ownedProcess).catch(() => undefined);
225
+ if (!options?.preserveDesktop)
226
+ await (0, desktop_launch_1.terminateOwnedProcess)(this.ownedProcess).catch(() => undefined);
116
227
  this.ownedProcess = undefined;
117
228
  this.client = undefined;
118
229
  this.state = "stopped";
@@ -142,7 +253,7 @@ class DoubaoPlugin {
142
253
  if (this.state !== "ready" || !this.client)
143
254
  throw new Error("豆包插件未就绪");
144
255
  if (!this.client.connected())
145
- await this.reconnect?.();
256
+ await this.reconnect();
146
257
  }
147
258
  async handle(request) {
148
259
  return this.enqueue(async () => {
@@ -169,9 +280,10 @@ class DoubaoPlugin {
169
280
  const run = this.enqueue(async () => {
170
281
  request.signal.throwIfAborted();
171
282
  stage = "连接豆包模型(connect)";
283
+ await this.ensureConnected();
172
284
  const client = this.client;
173
- if (this.state !== "ready" || !client?.connected() || !client.complete)
174
- throw new Error("豆包 CDP 未连接,模型请求不会启动或重启桌面");
285
+ if (!client?.complete)
286
+ throw new Error("豆包客户端不支持模型调用");
175
287
  stage = "等待豆包模型生成(generate)";
176
288
  // 空服务端标识会原子创建独立会话,无需切换用户桌面页面。
177
289
  const response = await client.complete(request.prompt, request.signal, undefined, this.bindings.get(request.sessionId) ?? "");
@@ -0,0 +1,134 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.nativeTaskScript = void 0;
4
+ // shortcut: 依赖桌面端原生模块导出,豆包升级接口后需重新校验;接口缺失时拒绝发送。
5
+ exports.nativeTaskScript = String.raw `(body, readTexts, readArtifacts, getRequestUrl) => {
6
+ window.__mab_doubao_controller?.abort();
7
+ const controller = new AbortController();
8
+ window.__mab_doubao_controller = controller;
9
+ window.__mab_doubao_read_artifacts = undefined;
10
+ window.__mab_doubao_request_id = body.option.unique_key;
11
+ let progressSequence = 0;
12
+ window.__mab_doubao_progress = { requestId: body.option.unique_key, sequence: progressSequence };
13
+ window.__mab_doubao_result = null;
14
+ const finish = result => {
15
+ if (window.__mab_doubao_controller === controller) window.__mab_doubao_result = JSON.stringify(result);
16
+ };
17
+ (async () => {
18
+ window.__mab_doubao_request_url = getRequestUrl().href;
19
+ const chunks = window['@flow-web/desktop:stable'];
20
+ if (!Array.isArray(chunks)) throw Error('豆包本地电脑原生接口不可用,请检查桌面端版本');
21
+ let requireNative;
22
+ chunks.push([['mab_task_' + body.option.unique_key], {}, value => { requireNative = value; }]);
23
+ const factories = new Map(chunks.flatMap(chunk => Object.entries(chunk[1] || {})));
24
+ const load = (marker, extra = '') => {
25
+ for (const [id, factory] of factories) if (String(factory).includes(marker) && String(factory).includes(extra)) return requireNative(id);
26
+ throw Error('豆包本地电脑接口缺失:' + marker);
27
+ };
28
+ const named = name => load(name + ':()=>')[name];
29
+ const only = (module, predicate) => {
30
+ const matches = Object.values(module).filter(value => typeof value === 'function' && predicate(value));
31
+ if (matches.length !== 1) throw Error('豆包本地电脑原生接口不兼容');
32
+ return matches[0];
33
+ };
34
+ const environment = await named('sandboxEnvironmentController').query();
35
+ if (!environment.isReady || !environment.environmentId) throw Error('豆包本地电脑环境未就绪,请在当前桌面登录态中检查本地电脑功能');
36
+ const getStore = only(load('chat-ioc-manager: cannot bind empty store'), value => String(value).includes('it is async store'));
37
+ const state = getStore('chatInputStore').getState();
38
+ const agentMode = named('AgentMode').MOA;
39
+ const mode = state.modeSelectConfigs?.menu_item_list?.find(item => Number(item.agent_mode) === agentMode);
40
+ if (!mode) throw Error('当前豆包登录态未提供任务模式,不能使用本地电脑');
41
+ const modeId = state.modeSelectConfigsV2?.mode_list?.item_list?.find(item => Number(item.agent_mode) === agentMode)?.mode_id || mode.default_mode;
42
+ if (!modeId) throw Error('豆包本地电脑任务模式标识缺失');
43
+ const getAuthorization = only(load('new WeakMap', 'sandboxAuthType:'), value => value.length === 0);
44
+ const authorization = getAuthorization();
45
+ if (!authorization || typeof authorization.sandboxAuthType !== 'number') throw Error('无法读取豆包本地电脑授权设置');
46
+ const getService = only(load('chat-ioc-manager: cannot bind empty service'), value => value.constructor.name === 'AsyncFunction');
47
+ const service = await getService('chatIMService');
48
+ const messageService = await service?.getMessageService();
49
+ if (!messageService?.sendMessage) throw Error('豆包本地电脑消息服务未就绪');
50
+ const scope = body.client_meta.conversation_id || body.client_meta.local_conversation_id;
51
+ const taskApi = load('buildGeneralAgentCompletionTaskParam:()=>');
52
+ const clientOption = await taskApi.buildGeneralAgentCompletionTaskParam(scope);
53
+ if (!clientOption.sandbox_id || !clientOption.workspace) throw Error('豆包本地电脑工作区或运行环境不可用');
54
+ const workspace = clientOption.workspace;
55
+ const runtimeType = taskApi.GeneralAgentRuntimeType.LocalPC;
56
+ const modify = only(load('general_agent_conversation_modify_params'), value => value.constructor.name === 'AsyncFunction');
57
+ const changes = await modify({ completionTaskParam: clientOption, messageConversationId: scope, primaryWorkspace: clientOption.workspace, runtimeType, sandboxAuthType: authorization.sandboxAuthType });
58
+ const general = { ...changes, runtime_type: runtimeType, server_do_compare: true, client_option: {
59
+ ...clientOption, sandbox_id: undefined, sandbox_env_id: clientOption.sandbox_id,
60
+ shared_folder_path: [clientOption.workspace], sandbox_auth_type: authorization.sandboxAuthType,
61
+ } };
62
+ const connector = await named('getFeishuConnectorEnabledForConversation')(scope);
63
+ const connectorInfo = only(load('connector_info_list', 'wrapped:!0'), value => {
64
+ const source = String(value); return source.includes('connector_id:') && source.includes('switch:') && source.includes('connector_vendor:');
65
+ });
66
+ const extra = {
67
+ agent_mode: String(agentMode), use_deep_think: String(mode.menu_type ?? mode.item_id), mode_id: String(modeId),
68
+ model_item_key: mode.model_item_key, model_extra_params: JSON.stringify(mode.model_extra_params || {}),
69
+ reasoning_effort: String(mode.reasoning_effort_config?.default_level ?? body.option.model_config.reasoning_effort),
70
+ general_task_param: JSON.stringify(general), mention_plugin_list: body.option.aggregate_params.mention_plugin_list,
71
+ ...(connector ? { connector_info_list: JSON.stringify([connectorInfo(connector.enabled, connector.id)]) } : {}),
72
+ ...(!body.client_meta.conversation_id ? {
73
+ conversation_init_option: JSON.stringify({ need_ack_conversation: true }),
74
+ conversation_init_ext: JSON.stringify({ mode_id: String(modeId), agent_task_param: JSON.stringify(changes.agent_task_param) }),
75
+ } : {}),
76
+ };
77
+ const create = named('createSendMessage');
78
+ const messages = body.messages.map(item => create({ conversationContext: {
79
+ conversation_id: body.client_meta.conversation_id || undefined,
80
+ local_conversation_id: body.client_meta.conversation_id ? undefined : scope, bot_id: body.client_meta.bot_id,
81
+ }, message: { content_blocks_v2: item.content_block, unique_id: item.local_message_id, ext: extra } }));
82
+ controller.signal.throwIfAborted();
83
+ const handle = messageService.sendMessage({ messages, sessionId: body.option.unique_key, session: { reportParams: { is_local_computer_on: 1 } }, sendMode: 'normal' });
84
+ controller.signal.addEventListener('abort', () => { Promise.resolve().then(() => handle.cancel()).catch(() => {}); }, { once: true });
85
+ const localIds = new Set(body.messages.map(item => item.local_message_id)), replies = new Map();
86
+ const questionIds = new Set(), messageIds = new Set();
87
+ let expectVideo = false, conversationId = body.client_meta.conversation_id;
88
+ const collect = message => {
89
+ const statuses = Object.values(message.final_status || {});
90
+ if (statuses.some(status => ['Broken', 'CancelError', 'Cancelled', 'Canceled'].includes(status))) throw Error('豆包本地电脑任务已中止');
91
+ if (message.error_details?.has_error || statuses.some(status => ['Error', 'PanicError', 'ExpectError'].includes(status))) throw Error(message.error_details?.error_msg || '豆包本地电脑任务执行失败');
92
+ if (message.ext?.has_video_gen === '1' || message.ext?.has_video_gen === 1) expectVideo = true;
93
+ if (message.message_id) {
94
+ messageIds.add(message.message_id);
95
+ replies.set(message.message_id, message.content_blocks_v2 || message.content_blocks || message.content_block || []);
96
+ }
97
+ };
98
+ for await (const message of handle.subscribe()) {
99
+ if (window.__mab_doubao_controller === controller) window.__mab_doubao_progress = { requestId: body.option.unique_key, sequence: ++progressSequence };
100
+ if (message.conversation_id) conversationId = message.conversation_id;
101
+ if (localIds.has(message.local_message_id) || localIds.has(message.unique_id)) {
102
+ if (message.error_details?.has_error) throw Error('豆包本地电脑任务发送失败');
103
+ if (message.message_id) questionIds.add(message.message_id);
104
+ continue;
105
+ }
106
+ if (message.reply_id) questionIds.add(message.reply_id);
107
+ collect(message);
108
+ }
109
+ const mediaSnapshot = () => {
110
+ const media = readArtifacts([...replies.values()], workspace);
111
+ return { ...media, pending: media.pending || (expectVideo && !media.artifacts.some(item => item.kind === 'video')) };
112
+ };
113
+ // 异步媒体按本轮引用读取原生消息列表,不依赖人工当前打开的会话。
114
+ if (window.__mab_doubao_controller !== controller) throw Error('豆包本地电脑原任务上下文已变化');
115
+ window.__mab_doubao_read_artifacts = async requestId => {
116
+ if (requestId !== body.option.unique_key || window.__mab_doubao_controller !== controller) throw Error('豆包本地电脑原任务上下文已变化');
117
+ controller.signal.throwIfAborted();
118
+ if (typeof messageService.fetchAndUpdateMessageList !== 'function') throw Error('豆包本地电脑异步产物读取接口不可用');
119
+ const page = await messageService.fetchAndUpdateMessageList({ conversationId, options: { limit: 50 } });
120
+ for (const message of page?.messages || []) {
121
+ if (message.conversation_id !== conversationId) continue;
122
+ const reference = message.ext?.reference_info;
123
+ const match = typeof reference === 'string' ? reference.match(/"referenced_message_id"\s*:\s*(?:"([^"\n]+)"|(\d+))/) : null;
124
+ const referenceId = match?.[1] || match?.[2];
125
+ if (messageIds.has(message.message_id) || questionIds.has(message.reply_id) || questionIds.has(referenceId)) collect(message);
126
+ }
127
+ return mediaSnapshot();
128
+ };
129
+ const media = mediaSnapshot();
130
+ const reply = readTexts([...replies.values()].map(blocks => blocks.filter(block => !block.parent_id && block.control_info?.is_visible !== false))).join('\n').trim();
131
+ if (!reply && !media.artifacts.length && !media.pending) throw Error('豆包本地电脑任务未返回结果');
132
+ finish({ ok: true, reply, conversationId, ...media, requestId: body.option.unique_key, questionIds: [...questionIds], messageIds: [...messageIds] });
133
+ })().catch(error => { finish({ failed: true, requestId: body.option.unique_key, reason: error?.message || String(error) }); });
134
+ }`;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-bridge-doubao",
3
- "version": "1.0.24",
3
+ "version": "1.0.35",
4
4
  "description": "agent-bridge 的豆包插件。",
5
5
  "keywords": [
6
6
  "mu-agent-bridge-plugin",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "doubao",
3
- "version": "1.0.24",
3
+ "version": "1.0.35",
4
4
  "type": "agent",
5
5
  "displayName": "豆包",
6
6
  "entry": "dist/src-ts/plugins/doubao/index.js",
@@ -1,9 +1,12 @@
1
1
  {
2
2
  "type": "object",
3
3
  "properties": {
4
- "executablePath": { "type": "string", "description": "DeepSeek Harness 客户端路径;升级后自动重新检测" },
4
+ "executablePath": { "type": "string", "description": "旧配置兼容字段;CDP 插件不启动客户端" },
5
5
  "aid": { "type": "string", "description": "复用的 EvolCore 已有 AID" },
6
- "patchPath": { "type": "string", "description": "DeepSeek Harness patch 文件路径" },
6
+ "patchPath": { "type": "string", "description": "旧配置兼容字段;CDP 桌面请求不读取" },
7
+ "cdpPort": { "type": "integer", "minimum": 1, "maximum": 65535, "default": 9226, "description": "DSH 桌面调试端口" },
8
+ "timeoutMs": { "type": "integer", "minimum": 1000, "default": 240000, "description": "等待 DSH 桌面回复的超时毫秒数" },
9
+ "startupTimeoutMs": { "type": "integer", "minimum": 1000, "default": 10000, "description": "连接 DSH 桌面调试端口的超时毫秒数" },
7
10
  "sessionsRoot": { "type": "string", "description": "DSH 官方持久化会话目录;默认使用 DSH_HOME/sessions 或用户目录下的 .dsh/sessions" }
8
11
  },
9
12
  "additionalProperties": false
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DshAgent = void 0;
4
+ exports.readPresentedFiles = readPresentedFiles;
4
5
  const node_child_process_1 = require("node:child_process");
5
6
  const node_crypto_1 = require("node:crypto");
6
7
  const node_fs_1 = require("node:fs");
@@ -19,7 +20,7 @@ function digest(data) { return (0, node_crypto_1.createHash)("sha256").update(da
19
20
  function outputContentType(filename, data) {
20
21
  return (0, attachments_1.detectImageContentType)(data) ?? outputMimeTypes[(0, node_path_1.extname)(filename).toLowerCase()] ?? "application/octet-stream";
21
22
  }
22
- async function readPresentedFiles(directory, paths, cwd, inputs, returnPaths) {
23
+ async function readPresentedFiles(directory, paths, cwd, inputs = [], returnPaths = []) {
23
24
  if (paths.length > attachments_1.MAX_ATTACHMENTS)
24
25
  throw new Error("单条消息最多包含 8 个附件");
25
26
  const result = [];
@@ -88,7 +89,11 @@ class DshAgent {
88
89
  const inputPaths = (request.attachments ?? []).map((item, index) => ({ path: (0, node_path_1.join)(workDirectory, `${index}-${(0, attachments_1.attachmentFilename)(item.filename)}`), returnPath: (0, node_path_1.join)(outputDirectory, `${index}-${(0, attachments_1.attachmentFilename)(item.filename)}`), filename: item.filename, contentType: item.contentType }));
89
90
  for (const [index, item] of (request.attachments ?? []).entries())
90
91
  await (0, promises_1.writeFile)(inputPaths[index].returnPath, item.data, { mode: 0o600 });
91
- const task = modelRequest ? request.task : `${request.task}\n\n请使用真实文件工具读取输入附件;生成的回传文件必须写入 outputDirectory 的直接子文件,并调用 present 工具声明文件。原附件已准备 returnPath 副本,原样回传时直接 present 此路径,无需 shell 复制。图片或视频无法理解时请明确说明,仍可原样回传。\n附件传输:\n${JSON.stringify({ inputs: inputPaths, outputDirectory })}`;
92
+ const task = modelRequest ? request.task : inputPaths.length
93
+ ? `${request.task}\n\n请读取输入附件;如需回传文件,将文件写入 outputDirectory 的直接子文件并调用 present。原附件可直接使用 returnPath。\n附件传输:\n${JSON.stringify({ inputs: inputPaths, outputDirectory })}`
94
+ : request.attachmentOutput
95
+ ? `${request.task}\n\n如需回传文件,将文件写入 outputDirectory 的直接子文件并调用 present。\n附件传输:\n${JSON.stringify({ outputDirectory })}`
96
+ : request.task;
92
97
  const args = ["--expose-internals", paths.cli, "--profile", "headless", "--patch", paths.patch, "--json"];
93
98
  if (request.sessionId && !request.newSession)
94
99
  args.push("--session-id", request.sessionId);
@@ -16,8 +16,8 @@ const node_path_1 = require("node:path");
16
16
  const node_util_1 = require("node:util");
17
17
  const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
18
18
  const versionDirectory = /^(?:app[-_ ]?)?v?\d+(?:[.-]\d+)+$/i;
19
- /** 在 Windows 常见安装目录中查找桌面程序,避免依赖单一盘符。 */
20
- async function findWindowsExecutable(fileName, configured) {
19
+ /** 先从 Windows 注册信息和开始菜单解析,失败时再查常见安装目录。 */
20
+ async function findWindowsExecutable(fileName, configured, run = execFileAsync) {
21
21
  if (configured?.trim()) {
22
22
  const configuredPath = (0, node_path_1.resolve)(configured);
23
23
  if (fileName.toLowerCase() === "doubao.exe") {
@@ -42,12 +42,16 @@ async function findWindowsExecutable(fileName, configured) {
42
42
  }
43
43
  if (process.platform !== "win32")
44
44
  return undefined;
45
- const pattern = /doubao|豆包|qwen|qwwork|千问|deepseek|harness|dsh/i;
45
+ const registered = await findRegisteredWindowsExecutable(fileName, run);
46
+ if (registered)
47
+ return registered;
48
+ const pattern = /doubao|豆包|qwen|qwwork|千问|deepseek|harness|dsh|zcode/i;
46
49
  const roots = [...new Set([
47
50
  process.env.DSH_INSTALL_DIR,
48
51
  "D:\\Programs\\DeepSeek Harness",
49
52
  "D:\\DSH",
50
53
  process.env.LOCALAPPDATA && (0, node_path_1.join)(process.env.LOCALAPPDATA, "Programs"),
54
+ process.env.LOCALAPPDATA,
51
55
  process.env.ProgramFiles,
52
56
  process.env["ProgramFiles(x86)"],
53
57
  ...Array.from({ length: 24 }, (_, index) => `${String.fromCharCode(67 + index)}:\\`),
@@ -59,6 +63,36 @@ async function findWindowsExecutable(fileName, configured) {
59
63
  }
60
64
  return undefined;
61
65
  }
66
+ async function findRegisteredWindowsExecutable(fileName, run) {
67
+ const target = fileName.replace(/'/g, "''");
68
+ const script = `$ErrorActionPreference='SilentlyContinue';[Console]::OutputEncoding=[Text.Encoding]::UTF8;$target='${target}'
69
+ function Emit-Path($value){if(-not $value){return};$value=[Environment]::ExpandEnvironmentVariables(([string]$value).Trim());if($value.StartsWith('"')){$end=$value.IndexOf('"',1);if($end -gt 1){$value=$value.Substring(1,$end-1)}}elseif($value -match '^(\S+?\.exe)(?:\s|$)'){$value=$Matches[1]};if($value -match '(?i)\.exe$'){[Console]::WriteLine($value)}}
70
+ $keys=@("HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\$target","HKLM:\\Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\$target","HKLM:\\Software\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\App Paths\\$target","HKCU:\\Software\\Classes\\Applications\\$target\\shell\\open\\command","HKLM:\\Software\\Classes\\Applications\\$target\\shell\\open\\command")
71
+ foreach($key in $keys){$item=Get-Item $key -ErrorAction SilentlyContinue;if($item){Emit-Path $item.GetValue('')}}
72
+ $dirs=@();if($env:APPDATA){$dirs+=[IO.Path]::Combine($env:APPDATA,'Microsoft\\Windows\\Start Menu\\Programs')};if($env:ProgramData){$dirs+=[IO.Path]::Combine($env:ProgramData,'Microsoft\\Windows\\Start Menu\\Programs')};$shell=New-Object -ComObject WScript.Shell
73
+ foreach($dir in $dirs){Get-ChildItem $dir -Filter '*.lnk' -File -Recurse -ErrorAction SilentlyContinue|ForEach-Object{$link=$shell.CreateShortcut($_.FullName);if($link.TargetPath -and [IO.Path]::GetFileName($link.TargetPath) -ieq $target){Emit-Path $link.TargetPath}}}`;
74
+ try {
75
+ const { stdout } = await run("powershell.exe", ["-NoProfile", "-NonInteractive", "-EncodedCommand", Buffer.from(script, "utf16le").toString("base64")], { timeout: 10_000, windowsHide: true });
76
+ for (const candidate of stdout.split(/\r?\n/).map(value => value.trim()).filter(Boolean)) {
77
+ const found = await resolveRegisteredCandidate(fileName, candidate);
78
+ if (found)
79
+ return found;
80
+ }
81
+ }
82
+ catch { /* 注册表或开始菜单不可读时继续使用目录兜底 */ }
83
+ return undefined;
84
+ }
85
+ async function resolveRegisteredCandidate(fileName, candidate) {
86
+ const candidatePath = (0, node_path_1.resolve)(candidate.replace(/^"|"$/g, ""));
87
+ if (fileName.toLowerCase() === "doubao.exe" && (0, node_path_1.basename)((0, node_path_1.dirname)(candidatePath)).toLowerCase() === "application") {
88
+ const client = (0, node_path_1.join)((0, node_path_1.dirname)(candidatePath), "app", fileName);
89
+ if ((0, node_fs_1.existsSync)(client))
90
+ return client;
91
+ }
92
+ if ((0, node_fs_1.existsSync)(candidatePath) && (0, node_path_1.basename)(candidatePath).toLowerCase() === fileName.toLowerCase())
93
+ return candidatePath;
94
+ return findExecutableInTree((0, node_path_1.dirname)(candidatePath), fileName, /^/, 3);
95
+ }
62
96
  async function findExecutableInTree(directory, fileName, pattern, depth) {
63
97
  return search(directory, fileName, pattern, depth, pattern.test(directory));
64
98
  }
@@ -104,9 +138,16 @@ async function launchManagedAndWait(executable, args, profileDirectory, connect,
104
138
  if (profileDirectory)
105
139
  await (0, promises_1.mkdir)(profileDirectory, { recursive: true });
106
140
  const child = launch(executable, profileDirectory ? [...args, `--user-data-dir=${profileDirectory}`] : args);
141
+ // 先等待 spawn,避免异步 error 终止 daemon 或在启动失败后继续轮询。
142
+ const events = child;
143
+ const started = typeof events.once === "function" ? new Promise((resolve, reject) => {
144
+ events.once("error", reject);
145
+ events.once("spawn", resolve);
146
+ }) : Promise.resolve();
107
147
  child.unref();
108
148
  onProcess(child);
109
149
  try {
150
+ await started;
110
151
  return await waitFor(connect, timeoutMs, 250);
111
152
  }
112
153
  catch (error) {