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
@@ -17,7 +17,7 @@ const session_history_1 = require("./session-history");
17
17
  const desktop_launch_1 = require("../desktop-launch");
18
18
  const desktop_launch_2 = require("./desktop-launch");
19
19
  exports.workbuddyManifest = {
20
- id: "workbuddy", version: "1.1.34", displayName: "WorkBuddy", type: "agent",
20
+ id: "workbuddy", version: "1.1.35", displayName: "WorkBuddy", type: "agent",
21
21
  entry: "dist/index.js", apiVersion: "1", process: "in-process",
22
22
  capabilities: ["chat", "health", "model-provider"], configSchema: "config.schema.json",
23
23
  };
@@ -52,7 +52,8 @@ class WorkbuddyPlugin {
52
52
  cliPath;
53
53
  model;
54
54
  transport = "auto";
55
- timeoutMs = 3_600_000;
55
+ replyTimeoutLimitMs;
56
+ timeoutMs = cdp_client_1.WORKBUDDY_MAX_WAIT_MS;
56
57
  queue = Promise.resolve();
57
58
  pending = 0;
58
59
  activeSessionId;
@@ -76,6 +77,7 @@ class WorkbuddyPlugin {
76
77
  sessionHistory;
77
78
  constructor(options = {}) {
78
79
  this.options = options;
80
+ this.replyTimeoutLimitMs = options.replyTimeoutLimitMs ?? cdp_client_1.WORKBUDDY_MAX_WAIT_MS;
79
81
  }
80
82
  get attachmentInput() { return Boolean(this.client?.attachmentInput && this.client.complete && this.transport !== "cli" && (!this.cliPath || this.transport === "cdp")); }
81
83
  async activate(context) {
@@ -83,7 +85,7 @@ class WorkbuddyPlugin {
83
85
  if (config.transport !== undefined && !["auto", "cli", "cdp"].includes(String(config.transport)))
84
86
  throw new Error("transport 只能是 auto、cli 或 cdp");
85
87
  this.transport = config.transport ?? "auto";
86
- this.timeoutMs = typeof config.timeoutMs === "number" && config.timeoutMs > 0 ? config.timeoutMs : 3_600_000;
88
+ this.timeoutMs = Math.min(typeof config.timeoutMs === "number" && config.timeoutMs > 0 ? config.timeoutMs : 3_600_000, this.replyTimeoutLimitMs);
87
89
  this.model = typeof config.model === "string" && config.model.trim() ? config.model : undefined;
88
90
  this.cliPath = (0, cli_client_1.findCli)(typeof config.cliPath === "string" ? config.cliPath : undefined);
89
91
  this.failure = undefined;
@@ -237,18 +239,23 @@ class WorkbuddyPlugin {
237
239
  return { status: "ready", details: { transport: "cdp" } };
238
240
  if (this.transport !== "cdp" && this.cliPath && (0, node_fs_1.existsSync)(this.cliPath))
239
241
  return { status: "ready", details: { transport: "cli" } };
240
- return { status: "failed", message: this.failure ?? "WorkBuddy 未连接", details: { transport: this.transport } };
242
+ return { status: "failed", message: this.failure ?? "WorkBuddy CDP 未连接;桌面升级后请在后台使用“重启桌面”恢复连接", details: { transport: this.transport } };
241
243
  }
242
244
  async handle(request) {
243
245
  if (!request.task.trim())
244
246
  throw new Error("消息内容不能为空");
245
247
  (0, attachments_1.validateAttachments)(request.attachments);
246
248
  const run = this.enqueue(async () => {
249
+ request.signal?.throwIfAborted();
247
250
  try {
248
- return await this.complete(request);
249
- }
250
- finally {
251
+ const response = await this.complete(request);
251
252
  await this.synchronizeSession();
253
+ return response;
254
+ }
255
+ catch (error) {
256
+ // 失败时桌面可能已切换,保留原绑定并要求重新校验或恢复。
257
+ this.desktopSessionValid = false;
258
+ throw error;
252
259
  }
253
260
  });
254
261
  const response = await run;
@@ -257,7 +264,12 @@ class WorkbuddyPlugin {
257
264
  async synchronizeSession() {
258
265
  if (this.activeSessionId && this.desktopSessionValid && this.client?.conversationId) {
259
266
  try {
260
- this.activeDesktopConversationId = await this.client.conversationId();
267
+ const conversationId = await this.client.conversationId();
268
+ if (this.activeDesktopConversationId && conversationId !== this.activeDesktopConversationId) {
269
+ this.desktopSessionValid = false;
270
+ throw new Error("WorkBuddy 桌面任务已切换,已保留原会话绑定");
271
+ }
272
+ this.activeDesktopConversationId = conversationId;
261
273
  if (this.activeDesktopConversationId)
262
274
  this.sessionBindings.set(this.activeSessionId, { conversationId: this.activeDesktopConversationId, expert: this.activeExpert });
263
275
  await this.persistKnownSessions();
@@ -337,8 +349,8 @@ class WorkbuddyPlugin {
337
349
  async complete(request, modelRequest = false) {
338
350
  let stage = "连接 WorkBuddy 桌面";
339
351
  try {
340
- const timeout = request.timeoutMs ?? this.timeoutMs;
341
- const expert = modelRequest ? undefined : extractExpertQuery(request.task);
352
+ const timeout = Math.min(request.timeoutMs ?? this.timeoutMs, this.replyTimeoutLimitMs);
353
+ const expert = modelRequest || this.options.expertSelection === false ? undefined : extractExpertQuery(request.task);
342
354
  const usesCli = Boolean(this.cliPath && this.transport !== "cdp" && !expert);
343
355
  const hasAttachments = Array.isArray(request.attachments) && request.attachments.length > 0;
344
356
  if (hasAttachments && !this.attachmentInput)
@@ -372,7 +384,7 @@ class WorkbuddyPlugin {
372
384
  this.expertSession = true;
373
385
  try {
374
386
  if (this.client.complete)
375
- return await this.client.complete({ ...request, timeoutMs: timeout }, select, false);
387
+ return await this.client.complete({ ...request, timeoutMs: timeout }, select, false, request.sessionId ? this.activeDesktopConversationId ?? null : undefined);
376
388
  return { text: await this.client.direct(request.task, timeout, select) };
377
389
  }
378
390
  catch (error) {
@@ -392,7 +404,7 @@ class WorkbuddyPlugin {
392
404
  this.activeExpert = null;
393
405
  try {
394
406
  if (this.client.complete)
395
- return await this.client.complete({ ...request, timeoutMs: timeout }, undefined, reset);
407
+ return await this.client.complete({ ...request, timeoutMs: timeout }, undefined, reset, request.sessionId ? this.activeDesktopConversationId ?? null : undefined);
396
408
  return { text: await this.client.direct(request.task, timeout, undefined, reset) };
397
409
  }
398
410
  catch (error) {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-bridge-workbuddy",
3
- "version": "1.1.34",
3
+ "version": "1.1.35",
4
4
  "description": "mu-agent-bridge 的 WorkBuddy 桌面端插件。",
5
5
  "keywords": [
6
6
  "mu-agent-bridge-plugin",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "workbuddy",
3
- "version": "1.1.34",
3
+ "version": "1.1.35",
4
4
  "type": "agent",
5
5
  "displayName": "WorkBuddy",
6
6
  "entry": "dist/src-ts/plugins/workbuddy/index.js",
@@ -0,0 +1,13 @@
1
+ {
2
+ "type": "object",
3
+ "properties": {
4
+ "aid": { "type": "string", "description": "关联的 AID" },
5
+ "autoStart": { "type": "boolean", "default": true, "description": "CDP 端口拒绝连接时自动发现并启动 ZCode" },
6
+ "executablePath": { "type": "string", "description": "可选的 ZCode.exe 路径,留空时从注册表、开始菜单和安装目录自动发现" },
7
+ "startupArgs": { "type": "array", "items": { "type": "string" }, "default": [], "description": "附加启动参数;CDP 地址和端口由插件自动传入" },
8
+ "startupTimeoutMs": { "type": "integer", "minimum": 1, "default": 30000, "description": "启动后等待 CDP 就绪的时间(毫秒)" },
9
+ "cdpPort": { "type": "integer", "minimum": 1, "maximum": 65535, "default": 9231, "description": "ZCode CDP 端口" },
10
+ "timeoutMs": { "type": "integer", "minimum": 1, "default": 3600000, "description": "单次回复等待时间(毫秒)" }
11
+ },
12
+ "additionalProperties": false
13
+ }
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AgentOperationError = exports.AgentError = void 0;
4
+ exports.abortable = abortable;
5
+ exports.errorDetails = errorDetails;
6
+ async function abortable(promise, signal) {
7
+ if (!signal)
8
+ return promise;
9
+ if (signal.aborted) {
10
+ // promise 已在调用方启动,先观察其拒绝,避免预取消时产生未处理拒绝。
11
+ void promise.catch(() => undefined);
12
+ signal.throwIfAborted();
13
+ }
14
+ return new Promise((resolve, reject) => {
15
+ let settled = false;
16
+ const cleanup = () => signal.removeEventListener("abort", abort);
17
+ const abort = () => {
18
+ if (settled)
19
+ return;
20
+ settled = true;
21
+ cleanup();
22
+ reject(signal.reason);
23
+ };
24
+ signal.addEventListener("abort", abort, { once: true });
25
+ promise.then((value) => {
26
+ if (settled)
27
+ return;
28
+ settled = true;
29
+ cleanup();
30
+ resolve(value);
31
+ }, (error) => {
32
+ if (settled)
33
+ return;
34
+ settled = true;
35
+ cleanup();
36
+ reject(error);
37
+ });
38
+ });
39
+ }
40
+ class AgentError extends Error {
41
+ constructor(message, options) {
42
+ super(message, options);
43
+ this.name = "AgentError";
44
+ }
45
+ }
46
+ exports.AgentError = AgentError;
47
+ /** 保留底层错误码,避免只留下 fetch failed 或超时这类表层提示。 */
48
+ function errorDetails(error) {
49
+ const parts = [];
50
+ const seen = new Set();
51
+ for (let current = error; current && !seen.has(current) && seen.size < 5;) {
52
+ seen.add(current);
53
+ const value = current;
54
+ const message = typeof value.message === "string" ? value.message : String(current);
55
+ parts.push(`${message}${value.code ? `(${String(value.code)})` : ""}`);
56
+ current = value.cause;
57
+ }
58
+ return parts.join(";原因:") || String(error);
59
+ }
60
+ class AgentOperationError extends AgentError {
61
+ code;
62
+ stage;
63
+ constructor(code, stage, cause) {
64
+ super(`${code}:${stage}失败:${errorDetails(cause)}`, { cause });
65
+ this.code = code;
66
+ this.stage = stage;
67
+ }
68
+ }
69
+ exports.AgentOperationError = AgentOperationError;
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MAX_TOTAL_ATTACHMENT_BYTES = exports.MAX_ATTACHMENTS = exports.MAX_ATTACHMENT_BYTES = void 0;
4
+ exports.detectImageContentType = detectImageContentType;
5
+ exports.attachmentFilename = attachmentFilename;
6
+ exports.validateAttachment = validateAttachment;
7
+ exports.validateAttachments = validateAttachments;
8
+ exports.asAttachments = asAttachments;
9
+ exports.MAX_ATTACHMENT_BYTES = 32 * 1024 * 1024;
10
+ exports.MAX_ATTACHMENTS = 8;
11
+ exports.MAX_TOTAL_ATTACHMENT_BYTES = 64 * 1024 * 1024;
12
+ function detectImageContentType(data) {
13
+ if (data.length >= 8 && data.slice(0, 8).every((value, index) => value === [137, 80, 78, 71, 13, 10, 26, 10][index]))
14
+ return "image/png";
15
+ if (data.length >= 3 && data[0] === 0xff && data[1] === 0xd8 && data[2] === 0xff)
16
+ return "image/jpeg";
17
+ if (data.length >= 12 && String.fromCharCode(...data.slice(0, 4)) === "RIFF" && String.fromCharCode(...data.slice(8, 12)) === "WEBP")
18
+ return "image/webp";
19
+ return undefined;
20
+ }
21
+ function attachmentFilename(filename) {
22
+ if (typeof filename !== "string" || /[\u0000-\u001f\u007f]/.test(filename))
23
+ throw new Error("附件文件名无效");
24
+ const name = filename.split(/[\\/]/).pop() ?? "";
25
+ if (!name.trim() || name === "." || name === "..")
26
+ throw new Error("附件文件名无效");
27
+ return name;
28
+ }
29
+ function validateAttachment(item) {
30
+ if (!item || !(item.data instanceof Uint8Array) || item.data.byteLength === 0)
31
+ throw new Error("附件内容不能为空");
32
+ if (item.data.byteLength > exports.MAX_ATTACHMENT_BYTES)
33
+ throw new Error("单个附件不能超过 32 MiB");
34
+ attachmentFilename(item.filename);
35
+ if (item.kind !== "image" && item.kind !== "file")
36
+ throw new Error("附件类型无效");
37
+ if (typeof item.contentType !== "string" || !/^[A-Za-z0-9!#$&^_.+-]+\/[A-Za-z0-9!#$&^_.+-]+$/.test(item.contentType))
38
+ throw new Error("附件 MIME 类型无效");
39
+ if (item.kind === "image") {
40
+ const detected = detectImageContentType(item.data);
41
+ if (!detected || detected !== item.contentType.toLowerCase())
42
+ throw new Error("图片内容与 MIME 类型不一致");
43
+ }
44
+ }
45
+ function validateAttachments(items) {
46
+ if (!items)
47
+ return;
48
+ if (!Array.isArray(items))
49
+ throw new Error("附件列表无效");
50
+ if (items.length > exports.MAX_ATTACHMENTS)
51
+ throw new Error("单条消息最多包含 8 个附件");
52
+ let total = 0;
53
+ for (const item of items) {
54
+ validateAttachment(item);
55
+ total += item.data.byteLength;
56
+ }
57
+ if (total > exports.MAX_TOTAL_ATTACHMENT_BYTES)
58
+ throw new Error("单条消息附件总大小不能超过 64 MiB");
59
+ }
60
+ function asAttachments(images) {
61
+ return (images ?? []).map((image) => ({ ...image, kind: "image" }));
62
+ }
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveRuntimePort = resolveRuntimePort;
4
+ function resolveRuntimePort(config, runtime, pluginId, envName, fallback) {
5
+ const injected = runtime?.ports?.[pluginId];
6
+ if (typeof injected === "number" && Number.isInteger(injected) && injected > 0)
7
+ return injected;
8
+ const configured = config.cdpPort;
9
+ if (typeof configured === "number" && Number.isInteger(configured) && configured > 0)
10
+ return configured;
11
+ const environment = Number(process.env[envName] ?? "");
12
+ return Number.isInteger(environment) && environment > 0 ? environment : fallback;
13
+ }
@@ -0,0 +1,238 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.findWindowsExecutable = findWindowsExecutable;
4
+ exports.findExecutableInTree = findExecutableInTree;
5
+ exports.launchAndWait = launchAndWait;
6
+ exports.launchManagedAndWait = launchManagedAndWait;
7
+ exports.restartOwnedAndWait = restartOwnedAndWait;
8
+ exports.terminateWindowsDesktop = terminateWindowsDesktop;
9
+ exports.terminateOwnedProcess = terminateOwnedProcess;
10
+ exports.findChatgptExecutable = findChatgptExecutable;
11
+ exports.waitFor = waitFor;
12
+ const node_child_process_1 = require("node:child_process");
13
+ const promises_1 = require("node:fs/promises");
14
+ const node_fs_1 = require("node:fs");
15
+ const node_path_1 = require("node:path");
16
+ const node_util_1 = require("node:util");
17
+ const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
18
+ const versionDirectory = /^(?:app[-_ ]?)?v?\d+(?:[.-]\d+)+$/i;
19
+ /** 先从 Windows 注册信息和开始菜单解析,失败时再查常见安装目录。 */
20
+ async function findWindowsExecutable(fileName, configured, run = execFileAsync) {
21
+ if (configured?.trim()) {
22
+ const configuredPath = (0, node_path_1.resolve)(configured);
23
+ if (fileName.toLowerCase() === "doubao.exe") {
24
+ let root = (0, node_path_1.dirname)(configuredPath);
25
+ while (/^(?:app|launcher|doubao_update)$/i.test((0, node_path_1.basename)(root)) || versionDirectory.test((0, node_path_1.basename)(root)))
26
+ root = (0, node_path_1.dirname)(root);
27
+ const updated = (0, node_path_1.join)(root, "Doubao_update", fileName);
28
+ if ((0, node_fs_1.existsSync)(updated))
29
+ return updated;
30
+ const latest = await findExecutableInTree(root, fileName, /^/, 3);
31
+ if (latest)
32
+ return latest;
33
+ }
34
+ // 千问升级会保留或删除旧版本目录,从同一安装位置重新定位新版。
35
+ if (fileName.toLowerCase() === "qwenworkcn.exe" && /^v?\d+(?:[.-]\d+)+$/i.test((0, node_path_1.basename)((0, node_path_1.dirname)(configuredPath)))) {
36
+ const latest = await findExecutableInTree((0, node_path_1.dirname)((0, node_path_1.dirname)(configuredPath)), fileName, /^/, 1);
37
+ if (latest)
38
+ return latest;
39
+ }
40
+ if ((0, node_fs_1.existsSync)(configuredPath))
41
+ return configuredPath;
42
+ }
43
+ if (process.platform !== "win32")
44
+ return undefined;
45
+ const registered = await findRegisteredWindowsExecutable(fileName, run);
46
+ if (registered)
47
+ return registered;
48
+ const pattern = /doubao|豆包|qwen|qwwork|千问|deepseek|harness|dsh|zcode/i;
49
+ const roots = [...new Set([
50
+ process.env.DSH_INSTALL_DIR,
51
+ "D:\\Programs\\DeepSeek Harness",
52
+ "D:\\DSH",
53
+ process.env.LOCALAPPDATA && (0, node_path_1.join)(process.env.LOCALAPPDATA, "Programs"),
54
+ process.env.LOCALAPPDATA,
55
+ process.env.ProgramFiles,
56
+ process.env["ProgramFiles(x86)"],
57
+ ...Array.from({ length: 24 }, (_, index) => `${String.fromCharCode(67 + index)}:\\`),
58
+ ].filter((value) => typeof value === "string" && (0, node_fs_1.existsSync)(value)))];
59
+ for (const root of roots) {
60
+ const found = await findExecutableInTree(root, fileName, pattern, 6);
61
+ if (found)
62
+ return found;
63
+ }
64
+ return undefined;
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
+ }
96
+ async function findExecutableInTree(directory, fileName, pattern, depth) {
97
+ return search(directory, fileName, pattern, depth, pattern.test(directory));
98
+ }
99
+ async function search(directory, fileName, pattern, depth, insideProduct) {
100
+ try {
101
+ const entries = await (0, promises_1.readdir)(directory, { withFileTypes: true });
102
+ // 豆包上层文件是启动器,优先查找新版目录或 app 中的实际程序。
103
+ if (insideProduct && depth > 0 && fileName.toLowerCase() === "doubao.exe") {
104
+ const versions = entries.filter(entry => entry.isDirectory() && (versionDirectory.test(entry.name) || /^(?:app|doubao_update)$/i.test(entry.name)));
105
+ versions.sort((left, right) => {
106
+ const rank = (name) => /^doubao_update$/i.test(name) ? 2 : /^app$/i.test(name) ? 0 : 1;
107
+ return rank(right.name) - rank(left.name) || right.name.replace(/^\D+/, "").localeCompare(left.name.replace(/^\D+/, ""), "en", { numeric: true });
108
+ });
109
+ for (const entry of versions) {
110
+ const found = await search((0, node_path_1.join)(directory, entry.name), fileName, pattern, depth - 1, true);
111
+ if (found)
112
+ return found;
113
+ }
114
+ }
115
+ for (const entry of entries)
116
+ if (entry.isFile() && insideProduct && entry.name.toLowerCase() === fileName.toLowerCase() && !(fileName.toLowerCase() === "doubao.exe" && /^(?:launcher|doubao)$/i.test((0, node_path_1.basename)(directory))))
117
+ return (0, node_path_1.join)(directory, entry.name);
118
+ if (depth <= 0)
119
+ return undefined;
120
+ // 按数字比较版本目录,避免 1.2.5 排在 1.2.10 之前。
121
+ entries.sort((left, right) => right.name.localeCompare(left.name, "en", { numeric: true }));
122
+ for (const entry of entries)
123
+ if (entry.isDirectory() && !entry.name.startsWith(".") && (insideProduct || pattern.test(entry.name) || /^(?:Program Files(?: \(x86\))?|Programs)$/i.test(entry.name))) {
124
+ const found = await search((0, node_path_1.join)(directory, entry.name), fileName, pattern, depth - 1, insideProduct || pattern.test(entry.name));
125
+ if (found)
126
+ return found;
127
+ }
128
+ }
129
+ catch { /* 无权限或目录在扫描期间消失时继续查找 */ }
130
+ return undefined;
131
+ }
132
+ async function launchAndWait(executable, args, connect, timeoutMs, launch = (file, argv) => (0, node_child_process_1.spawn)(file, argv, { detached: true, stdio: "ignore", windowsHide: false })) {
133
+ const child = launch(executable, args);
134
+ child.unref();
135
+ return waitFor(connect, timeoutMs, 250);
136
+ }
137
+ async function launchManagedAndWait(executable, args, profileDirectory, connect, timeoutMs, onProcess, launch = (file, argv) => (0, node_child_process_1.spawn)(file, argv, { detached: true, stdio: "ignore", windowsHide: false })) {
138
+ if (profileDirectory)
139
+ await (0, promises_1.mkdir)(profileDirectory, { recursive: true });
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();
147
+ child.unref();
148
+ onProcess(child);
149
+ try {
150
+ await started;
151
+ return await waitFor(connect, timeoutMs, 250);
152
+ }
153
+ catch (error) {
154
+ await terminateOwnedProcess(child).catch(() => undefined);
155
+ onProcess(undefined);
156
+ throw error;
157
+ }
158
+ }
159
+ async function restartOwnedAndWait(child, executable, args, profileDirectory, connect, timeoutMs, onProcess) {
160
+ await terminateOwnedProcess(child);
161
+ onProcess(undefined);
162
+ return launchManagedAndWait(executable, args, profileDirectory, connect, timeoutMs, onProcess);
163
+ }
164
+ /** 仅在用户确认重启后清理同一用户、同一安装路径的桌面进程。 */
165
+ async function terminateWindowsDesktop(executable) {
166
+ if (process.platform !== "win32")
167
+ return [];
168
+ const script = `
169
+ $ErrorActionPreference = 'Stop'
170
+ $target = '${(0, node_path_1.resolve)(executable).replace(/'/g, "''")}'
171
+ $sid = [Security.Principal.WindowsIdentity]::GetCurrent().User.Value
172
+ $processes = @(Get-CimInstance Win32_Process | Where-Object { $_.ExecutablePath -ieq $target } | Where-Object { (Invoke-CimMethod -InputObject $_ -MethodName GetOwnerSid).Sid -eq $sid })
173
+ $ids = @($processes.ProcessId)
174
+ $roots = @($processes | Where-Object { $ids -notcontains $_.ParentProcessId })
175
+ foreach ($item in $roots) {
176
+ $current = Get-CimInstance Win32_Process -Filter "ProcessId=$($item.ProcessId)"
177
+ if (-not $current -or $current.CreationDate -ne $item.CreationDate) { continue }
178
+ & taskkill.exe /PID $item.ProcessId /T /F | Out-Null
179
+ if ($LASTEXITCODE -ne 0 -and (Get-Process -Id $item.ProcessId -ErrorAction SilentlyContinue)) { throw "无法结束桌面进程 $($item.ProcessId)" }
180
+ $remaining = Get-Process -Id $item.ProcessId -ErrorAction SilentlyContinue
181
+ if ($remaining -and -not $remaining.WaitForExit(10000)) { throw "等待桌面进程退出超时 $($item.ProcessId)" }
182
+ }
183
+ ConvertTo-Json -Compress -InputObject @($roots | ForEach-Object { [int]$_.ProcessId })
184
+ `;
185
+ const { stdout } = await execFileAsync("powershell.exe", ["-NoProfile", "-NonInteractive", "-EncodedCommand", Buffer.from(script, "utf16le").toString("base64")], { timeout: 30_000, windowsHide: true });
186
+ return JSON.parse(stdout.trim());
187
+ }
188
+ async function terminateOwnedProcess(child, platform = process.platform, run = async (file, args) => execFileAsync(file, args, { timeout: 10_000 })) {
189
+ const pid = child?.pid;
190
+ if (!Number.isSafeInteger(pid) || !pid || child?.exitCode !== null && child?.exitCode !== undefined || child?.signalCode)
191
+ return;
192
+ if (platform === "win32") {
193
+ try {
194
+ await run("taskkill.exe", ["/PID", String(pid), "/T", "/F"]);
195
+ }
196
+ catch (error) {
197
+ if (!child.exitCode && !child.signalCode && error.code !== "ESRCH")
198
+ throw error;
199
+ }
200
+ }
201
+ else {
202
+ try {
203
+ process.kill(pid, "SIGTERM");
204
+ }
205
+ catch (error) {
206
+ if (error.code !== "ESRCH")
207
+ throw error;
208
+ }
209
+ }
210
+ }
211
+ async function findChatgptExecutable(configured) {
212
+ if (configured?.trim() && (0, node_fs_1.existsSync)((0, node_path_1.resolve)(configured)))
213
+ return (0, node_path_1.resolve)(configured);
214
+ if (process.platform !== "win32")
215
+ return undefined;
216
+ const script = "$candidates = foreach ($package in (Get-AppxPackage | Where-Object { $_.Name -like 'OpenAI.Codex*' })) { $appDir = Join-Path $package.InstallLocation 'app'; $path = Join-Path $appDir 'ChatGPT.exe'; if (-not (Test-Path -LiteralPath $path)) { $path = Get-ChildItem -LiteralPath $appDir -Filter 'ChatGPT*.exe' -File -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty FullName }; if ($path) { [pscustomobject]@{ Version = [version]$package.Version; Executable = $path } } }; $candidate = $candidates | Sort-Object Version -Descending | Select-Object -First 1; if ($candidate) { $candidate.Executable }";
217
+ try {
218
+ const { stdout } = await execFileAsync("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", script], { timeout: 10_000 });
219
+ return stdout.trim().split(/\r?\n/).at(-1)?.trim() || undefined;
220
+ }
221
+ catch {
222
+ return undefined;
223
+ }
224
+ }
225
+ async function waitFor(action, timeoutMs, intervalMs = 500) {
226
+ const deadline = Date.now() + timeoutMs;
227
+ let last;
228
+ while (Date.now() < deadline) {
229
+ try {
230
+ return await action();
231
+ }
232
+ catch (error) {
233
+ last = error;
234
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
235
+ }
236
+ }
237
+ throw last instanceof Error ? last : new Error("桌面程序启动超时");
238
+ }