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
@@ -13,13 +13,30 @@ const evolcore_1 = require("./evolcore");
13
13
  const messages_1 = require("./messages");
14
14
  const attachments_1 = require("./attachments");
15
15
  const manifest = {
16
- id: "aun", version: "1.0.35", type: "channel", displayName: "AUN 通道",
16
+ id: "aun", version: "1.0.41", type: "channel", displayName: "AUN 通道",
17
17
  entry: "dist/src-ts/plugins/aun/index.js", apiVersion: "1", process: "in-process",
18
18
  capabilities: ["channel", "channel.identity.create", "channel.receive", "channel.send", "health", "cli"],
19
19
  configSchema: "config.schema.json",
20
20
  managementPage: "/abweb/channels/aun",
21
21
  };
22
22
  const newSessionCommand = /^\/new(?:[ \t]+[^\r\n]+)?$/;
23
+ const successfulSendStatuses = new Set(["accepted", "sent", "delivered", "duplicate"]);
24
+ function attachmentDownloadLinks(refs) {
25
+ return refs.flatMap((ref) => typeof ref.url === "string" && ref.url ? [ref.url]
26
+ : typeof ref.owner_aid === "string" && typeof ref.object_key === "string" ? [`https://${ref.owner_aid}/${ref.object_key}`] : []);
27
+ }
28
+ function withAttachmentDownloadLinks(text, kind, refs) {
29
+ const links = attachmentDownloadLinks(refs);
30
+ if (!links.length)
31
+ return text;
32
+ return `${text || "附件已生成。"}\n${kind === "file" ? "文件" : "附件"}下载链接:\n${links.join("\n")}`;
33
+ }
34
+ function withFailureDownloadLinks(text, refs) {
35
+ const links = attachmentDownloadLinks(refs);
36
+ if (!links.length || text?.includes("下载链接"))
37
+ return text;
38
+ return `${text || "附件已生成。"}\n附件下载链接(消息未确认时):\n${links.join("\n")}`;
39
+ }
23
40
  async function loadSdk() {
24
41
  const resolver = (0, node_module_1.createRequire)(__filename);
25
42
  const dynamicImport = new Function("url", "return import(url)");
@@ -942,7 +959,8 @@ class AunPlugin {
942
959
  channel: "aun", recipientId: aid, peerId: message.peerId, messageId: message.messageId, text: message.text, threadId: message.threadId,
943
960
  ...(attachments.length ? { attachments } : {}),
944
961
  agentPlugin,
945
- reply: async (text) => {
962
+ reply: async (text, signal) => {
963
+ signal?.throwIfAborted();
946
964
  if (!text.trim())
947
965
  return;
948
966
  try {
@@ -954,17 +972,21 @@ class AunPlugin {
954
972
  throw new agent_1.AgentOperationError("AUN_TEXT_SEND_FAILED", "发送 AUN 文本消息(结果未确认,不自动重发)", error);
955
973
  }
956
974
  },
957
- replyAttachments: async (items, text) => {
975
+ replyAttachments: async (items, text, signal) => {
976
+ signal?.throwIfAborted();
958
977
  if (!items.length)
959
978
  return;
960
979
  let sent = 0;
961
980
  let groupsCount = 0;
962
981
  let sending = false;
982
+ let fallbackText;
983
+ let fallbackLinks = [];
963
984
  try {
964
985
  if (!client?.storage)
965
986
  throw new Error("AUN SDK 不支持附件存储接口");
966
987
  const refs = uploadedRefs && uploadedItems === items ? uploadedRefs : await (0, attachments_1.uploadAunAttachments)({ storage: client.storage }, aid, message.peerId, items);
967
- this.context?.logger.info("AUN FS 文件上传与授权完成", { ...trace, stage: "上传并授权附件", files: items.map(item => ({ filename: item.filename, sizeBytes: item.data.byteLength })) });
988
+ signal?.throwIfAborted();
989
+ this.context?.logger.info("AUN FS 标准附件上传完成", { ...trace, stage: "上传附件", files: items.map(item => ({ filename: item.filename, sizeBytes: item.data.byteLength })) });
968
990
  if (!uploadedRefs || uploadedItems !== items) {
969
991
  uploadedItems = items;
970
992
  uploadedRefs = refs;
@@ -978,25 +1000,47 @@ class AunPlugin {
978
1000
  }
979
1001
  groupsCount = groups.size;
980
1002
  for (const [kind, group] of groups) {
981
- const payload = { type: kind, text: text || (kind === "image" ? "图片已生成。" : kind === "video" ? "视频已生成。" : "文件已生成。"), attachments: group };
1003
+ signal?.throwIfAborted();
1004
+ const payloadText = withAttachmentDownloadLinks(text || (kind === "image" ? "图片已生成。" : kind === "video" ? "视频已生成。" : "文件已生成。"), kind, group);
1005
+ // AUN 网页会隐藏带普通正文的单图卡片,图片说明需另发文本。
1006
+ const payload = { type: kind, ...(kind === "image" ? {} : { text: payloadText }), attachments: group };
982
1007
  if (message.threadId)
983
1008
  payload.thread_id = message.threadId;
984
1009
  if (message.messageId)
985
1010
  payload.ref_message_id = message.messageId;
1011
+ fallbackText = payloadText;
1012
+ fallbackLinks = attachmentDownloadLinks(group);
986
1013
  sending = true;
987
1014
  // message.send 失败时可能已送达,不自动重试或降级明文,避免重复发送和泄露引用。
988
- await client.call("message.send", { to: message.peerId, payload, encrypt: true });
1015
+ const sendResult = await this.callAunWithRecovery(aid, { to: message.peerId, payload, encrypt: true });
1016
+ if (typeof sendResult?.status !== "string" || !successfulSendStatuses.has(sendResult.status))
1017
+ throw new Error(`AUN 附件发送状态未确认:${sendResult?.status === undefined ? "未返回状态" : String(sendResult.status)}`);
989
1018
  this.context?.logger.info("AUN 附件消息已发送", { ...trace, stage: "发送附件消息", kind, count: group.length });
990
1019
  sending = false;
991
1020
  sent++;
1021
+ if (kind === "image" && payloadText) {
1022
+ try {
1023
+ signal?.throwIfAborted();
1024
+ const { attachments: _attachments, ...caption } = payload;
1025
+ const result = await this.callAunWithRecovery(aid, { to: message.peerId, payload: { ...caption, type: "text", text: payloadText }, encrypt: true });
1026
+ if (typeof result?.status !== "string" || !successfulSendStatuses.has(result.status))
1027
+ throw new Error(`AUN 图片说明发送状态未确认:${result?.status === undefined ? "未返回状态" : String(result.status)}`);
1028
+ this.context?.logger.info("AUN 图片说明已发送", { ...trace, stage: "发送图片说明" });
1029
+ }
1030
+ catch (error) {
1031
+ throw new agent_1.AgentOperationError("AUN_ATTACHMENT_CAPTION_SEND_FAILED", "图片已发送,发送 AUN 图片说明(结果未确认,不自动重发)", error);
1032
+ }
1033
+ }
992
1034
  }
993
1035
  }
994
1036
  catch (error) {
1037
+ signal?.throwIfAborted();
995
1038
  deliveryFailed = true;
996
1039
  const failure = sending ? new agent_1.AgentOperationError("AUN_ATTACHMENT_SEND_FAILED", "发送 AUN 附件消息(FS 上传已完成)", error) : error;
997
1040
  const progress = sending ? `(已确认发送 ${sent}/${groupsCount} 组,当前组结果未确认,请勿重复发送)` : "";
1041
+ const fallback = fallbackText && fallbackLinks.length ? `\n${withFailureDownloadLinks(fallbackText, fallbackLinks.map((url) => ({ url })))}` : "";
998
1042
  try {
999
- await this.sendText(aid, message.peerId, `附件回传失败${progress}:${safeError((0, agent_1.errorDetails)(failure))}`, message.threadId);
1043
+ await this.sendText(aid, message.peerId, `附件回传失败${progress}:${safeError((0, agent_1.errorDetails)(failure))}${fallback}`, message.threadId, true);
1000
1044
  }
1001
1045
  catch { /* 尽力反馈 */ }
1002
1046
  throw failure;
@@ -1021,18 +1065,44 @@ class AunPlugin {
1021
1065
  }
1022
1066
  }
1023
1067
  }
1024
- async sendText(aid, peerId, text, threadId) {
1068
+ async sendText(aid, peerId, text, threadId, encryptedOnly = false) {
1025
1069
  const payload = { type: "text", text };
1026
1070
  if (threadId)
1027
1071
  payload.thread_id = threadId;
1028
1072
  const params = { to: peerId, payload, encrypt: true };
1029
1073
  try {
1030
- await this.clients.get(aid).client.call("message.send", params);
1074
+ await this.callAunWithRecovery(aid, params);
1031
1075
  }
1032
1076
  catch (error) {
1033
- if (!this.isE2eeError(error))
1077
+ if (encryptedOnly || !this.isE2eeError(error))
1034
1078
  throw error;
1035
- await this.clients.get(aid).client.call("message.send", { ...params, encrypt: false });
1079
+ await this.callAunWithRecovery(aid, { ...params, encrypt: false });
1080
+ }
1081
+ }
1082
+ async callAunWithRecovery(aid, params) {
1083
+ const connection = this.clients.get(aid);
1084
+ if (!connection)
1085
+ throw new agent_1.AgentOperationError("AUN_CONNECTION_MISSING", "查找 AUN 身份连接", new Error(`AID ${aid} 当前没有已连接的客户端`));
1086
+ try {
1087
+ return await connection.client.call("message.send", params);
1088
+ }
1089
+ catch (error) {
1090
+ if (!isAunWriteNotStarted(error))
1091
+ throw error;
1092
+ this.context?.logger.warn("AUN 发送前连接已断开,正在恢复 AUN 到 Peer 的连接并重试一次", { aid, peer: params.to, stage: "恢复 AUN 连接", error: safeError((0, agent_1.errorDetails)(error)) });
1093
+ try {
1094
+ connection.reconnecting ??= connection.client.connect({ auto_reconnect: true }).finally(() => { connection.reconnecting = undefined; });
1095
+ await connection.reconnecting;
1096
+ }
1097
+ catch (cause) {
1098
+ throw new agent_1.AgentOperationError("AUN_RECONNECT_FAILED", "恢复 AUN 到 Peer 的连接(消息确认未写出)", cause);
1099
+ }
1100
+ try {
1101
+ return await connection.client.call("message.send", params);
1102
+ }
1103
+ catch (cause) {
1104
+ throw new agent_1.AgentOperationError("AUN_SEND_FAILED", "AUN 重连后发送消息(仅重试一次)", cause);
1105
+ }
1036
1106
  }
1037
1107
  }
1038
1108
  isE2eeError(error) {
@@ -1051,6 +1121,14 @@ exports.default = AunPlugin;
1051
1121
  function safeError(error) {
1052
1122
  return redact(error instanceof Error ? error.message : String(error));
1053
1123
  }
1124
+ function isAunWriteNotStarted(error) {
1125
+ for (let current = error; current && typeof current === "object"; current = current.cause) {
1126
+ const value = current;
1127
+ if (value.name === "ConnectionError" && typeof value.message === "string" && /^(?:client is not connected|transport not connected|send cancelled before write:|transport closed$)/i.test(value.message))
1128
+ return true;
1129
+ }
1130
+ return false;
1131
+ }
1054
1132
  async function readResponse(response, limit) {
1055
1133
  if (!response.body) {
1056
1134
  const raw = await response.text();
@@ -10,7 +10,7 @@ function normalizeAunMessage(payload, selfAid, allowFrom) {
10
10
  const data = payload;
11
11
  const peerId = typeof data.from === "string" ? data.from.trim() : "";
12
12
  const rules = (allowFrom ?? "").split(",").map((value) => value.trim()).filter(Boolean);
13
- if (!peerId || peerId === selfAid || (rules.length > 0 && !rules.includes(peerId)))
13
+ if (!peerId || peerId === selfAid || (rules.length > 0 && !rules.includes("*") && !rules.includes(peerId)))
14
14
  return undefined;
15
15
  const body = data.payload && typeof data.payload === "object" ? data.payload : {};
16
16
  // 对端的系统事件只作通知,转为任务回复会导致两个 Agent 循环互答。
@@ -29,7 +29,8 @@
29
29
  agents = (plugins.plugins || []).filter((plugin) => plugin.type === "agent");
30
30
  if (refreshForm) createForm.elements.ownerAid.value = config.owner_aid || "";
31
31
  if (refreshForm) {
32
- for (const key of ["allow_from", "aun_path"]) configForm.elements[key].value = config[key] || "";
32
+ configForm.elements.allow_from.value = config.allow_from?.split(",").some(value => value.trim() === "*") ? "" : config.allow_from || "";
33
+ configForm.elements.aun_path.value = config.aun_path || "";
33
34
  configForm.elements.encryption_seed.value = "";
34
35
  }
35
36
  list.replaceChildren();
@@ -7,7 +7,7 @@
7
7
  <p id="status" role="status"></p>
8
8
  <div class="table-scroll"><table><thead><tr><th>AID</th><th>来源</th><th>已关联 Agent</th><th>操作</th></tr></thead><tbody id="identities"></tbody></table></div>
9
9
  <form id="create-form"><h2>创建 AID</h2><div class="fields"><label>新 AID<input name="aid" required placeholder="例如:example.agentid.cn"></label><label>Owner AID<input name="ownerAid" placeholder="例如:owner.agentid.cn"><small class="hint">留空时使用已配置的默认 Owner;未配置时需填写。Owner 保存为本地归属配置;不代表 AUN 协议中的 Owner 关系已激活。</small></label></div><button type="submit">创建</button></form>
10
- <form id="config-form"><h2>通道配置</h2><div class="fields"><label>允许的来源<input name="allow_from"></label><label>AUN 存储路径<input name="aun_path"></label><label>加密种子<input name="encryption_seed" type="password" autocomplete="new-password" placeholder="留空则不修改"></label></div><button type="submit">保存配置</button></form>
10
+ <form id="config-form"><h2>通道配置</h2><div class="fields"><label>允许的来源<input name="allow_from" placeholder="留空接受所有 Peer,填写 AID 后限制"></label><label>AUN 存储路径<input name="aun_path"></label><label>加密种子<input name="encryption_seed" type="password" autocomplete="new-password" placeholder="留空则不修改"></label></div><button type="submit">保存配置</button></form>
11
11
  </main>
12
12
  <script src="/abweb/channels/aun/app.js"></script>
13
13
  </body>
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-bridge-aun",
3
- "version": "1.0.35",
3
+ "version": "1.0.41",
4
4
  "description": "mu-agent-bridge 的 AUN 通道插件。",
5
5
  "keywords": [
6
6
  "mu-agent-bridge-plugin",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "aun",
3
- "version": "1.0.35",
3
+ "version": "1.0.41",
4
4
  "type": "channel",
5
5
  "displayName": "AUN 通道",
6
6
  "entry": "dist/src-ts/plugins/aun/index.js",
@@ -5,7 +5,7 @@ const promises_1 = require("node:fs/promises");
5
5
  const node_os_1 = require("node:os");
6
6
  const node_path_1 = require("node:path");
7
7
  /** 空闲时先备份旧草稿,再通过编辑器原生删除事件清理,避免只修改 DOM。 */
8
- async function recoverCdpDraft(evaluate, editorSelector, stopSelector, dataDir = (0, node_path_1.join)((0, node_os_1.tmpdir)(), "agent-bridge-draft-recovery")) {
8
+ async function recoverCdpDraft(evaluate, editorSelector, stopSelector, dataDir = (0, node_path_1.join)((0, node_os_1.tmpdir)(), "agent-bridge-draft-recovery"), expected) {
9
9
  const state = await evaluate(`(() => ({
10
10
  text: document.querySelector(${JSON.stringify(editorSelector)})?.innerText ?? null,
11
11
  generating: Boolean(document.querySelector(${JSON.stringify(stopSelector)})),
@@ -15,13 +15,16 @@ async function recoverCdpDraft(evaluate, editorSelector, stopSelector, dataDir =
15
15
  throw new Error("桌面端正在生成回复,不能清理输入或切换会话");
16
16
  if (state.text === null)
17
17
  throw new Error("桌面端输入框未就绪");
18
+ if (expected && (state.text !== expected.text || state.url !== expected.url))
19
+ return;
18
20
  if (!state.text?.trim())
19
21
  return;
20
22
  await (0, promises_1.mkdir)(dataDir, { recursive: true, mode: 0o700 });
21
23
  await (0, promises_1.appendFile)((0, node_path_1.join)(dataDir, "draft-recovery.jsonl"), JSON.stringify({ time: new Date().toISOString(), url: state.url, text: state.text }) + "\n", { encoding: "utf8", mode: 0o600 });
22
24
  const cleared = await evaluate(`(() => {
23
25
  const editor = document.querySelector(${JSON.stringify(editorSelector)});
24
- const unchanged = () => editor && editor.innerText === ${JSON.stringify(state.text)}
26
+ const unchanged = () => editor && document.querySelector(${JSON.stringify(editorSelector)}) === editor
27
+ && editor.innerText === ${JSON.stringify(state.text)} && (document.URL ?? '') === ${JSON.stringify(state.url)}
25
28
  && !document.querySelector(${JSON.stringify(stopSelector)});
26
29
  if (!unchanged()) return false;
27
30
  editor.focus();
@@ -138,11 +138,12 @@ class ChatgptPlugin {
138
138
  throw connectionError;
139
139
  }
140
140
  }
141
- async deactivate() {
141
+ async deactivate(_reason = "stop", options) {
142
142
  await this.queue;
143
143
  await this.store?.drain();
144
144
  this.client?.close();
145
- await (0, desktop_launch_1.terminateOwnedProcess)(this.ownedProcess).catch(() => undefined);
145
+ if (!options?.preserveDesktop)
146
+ await (0, desktop_launch_1.terminateOwnedProcess)(this.ownedProcess).catch(() => undefined);
146
147
  this.ownedProcess = undefined;
147
148
  this.reconnect = undefined;
148
149
  this.client = undefined;
@@ -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) {
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ClaudeCliPlugin = exports.manifest = void 0;
4
4
  const agent_1 = require("../local-cli/agent");
5
5
  exports.manifest = {
6
- id: "claude-cli", version: "1.0.2", displayName: "Claude Code CLI", type: "agent", executionMode: "cli",
6
+ id: "claude-cli", version: "1.0.3", displayName: "Claude Code CLI", type: "agent", executionMode: "cli",
7
7
  entry: "dist/src-ts/plugins/claude-cli/index.js", apiVersion: "1", process: "in-process",
8
8
  capabilities: ["chat", "health", "model-provider"], configSchema: "config.schema.json",
9
9
  };
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-bridge-claude-cli",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "mu-agent-bridge 的本地 Claude Code CLI 独立插件,沿用 CLI 默认模型与登录。",
5
5
  "keywords": [
6
6
  "mu-agent-bridge-plugin",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "claude-cli",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "type": "agent",
5
5
  "executionMode": "cli",
6
6
  "displayName": "Claude Code CLI",
@@ -8,8 +8,9 @@
8
8
  "startupTimeoutMs": { "type": "integer", "minimum": 1000, "default": 30000, "description": "等待豆包启动的超时时间(毫秒)" },
9
9
  "conversationId": { "type": "string", "description": "豆包会话 ID" },
10
10
  "botId": { "type": "string", "description": "豆包 bot ID" },
11
+ "mode": { "type": "string", "enum": ["local-computer", "chat"], "default": "local-computer", "description": "默认使用本地电脑任务模式,继承当前桌面登录态的工作区和授权;chat 为纯聊天" },
11
12
  "pluginList": { "type": "array", "items": { "type": "string" }, "description": "请求时显式启用的豆包伙伴标识,例如飞书伙伴" },
12
- "timeoutMs": { "type": "integer", "minimum": 1000, "description": "等待回复超时时间(毫秒)" },
13
+ "timeoutMs": { "type": "integer", "minimum": 1000, "description": "等待回复超时时间(毫秒),默认 180000;本地电脑模式按本轮新流事件续期" },
13
14
  "token": { "type": "string", "secret": true }
14
15
  },
15
16
  "additionalProperties": false
@@ -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) {