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,15 +1,21 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.doubaoHistoryReadPageScript = exports.doubaoHistoryPageScript = exports.DoubaoCdpClient = exports.currentResponseArtifactsScript = exports.responseArtifactsScript = exports.responseImagesScript = exports.responseTextScript = void 0;
3
+ exports.doubaoRequestUrlScript = exports.doubaoHistoryReadPageScript = exports.doubaoHistoryPageScript = exports.DoubaoCdpClient = exports.currentResponseArtifactsScript = exports.responseArtifactsScript = exports.responseImagesScript = exports.responseTextScript = void 0;
4
4
  exports.doubaoStreamErrorMessage = doubaoStreamErrorMessage;
5
5
  exports.requestBody = requestBody;
6
6
  exports.mergeDoubaoReply = mergeDoubaoReply;
7
7
  exports.cleanDoubaoReply = cleanDoubaoReply;
8
+ exports.buildLocalComputerInjection = buildLocalComputerInjection;
8
9
  exports.downloadDoubaoArtifact = downloadDoubaoArtifact;
9
10
  exports.downloadDoubaoImage = downloadDoubaoImage;
10
11
  const node_crypto_1 = require("node:crypto");
12
+ const node_fs_1 = require("node:fs");
13
+ const promises_1 = require("node:fs/promises");
14
+ const node_path_1 = require("node:path");
15
+ const agent_1 = require("../../agent");
11
16
  const attachments_1 = require("../../attachments");
12
17
  const attachments_2 = require("./attachments");
18
+ const native_task_1 = require("./native-task");
13
19
  function doubaoStreamErrorMessage(data) {
14
20
  let code = "";
15
21
  let message = "";
@@ -38,12 +44,20 @@ function doubaoStreamErrorMessage(data) {
38
44
  return `豆包平台返回流式错误${code ? `(错误码 ${code})` : ""}`;
39
45
  }
40
46
  const defaults = { port: Number(process.env.DOUBAO_CDP_PORT ?? "9227"), conversationId: process.env.DOUBAO_CONVERSATION_ID ?? "", botId: process.env.DOUBAO_BOT_ID ?? "7338286299411103781", timeoutMs: Number(process.env.DOUBAO_TIMEOUT_MS ?? "180000") };
47
+ const MAX_PREALLOCATION_ATTEMPTS = 3;
48
+ const CHAT_AGENT_MODE = 2;
49
+ const LOCAL_COMPUTER_AGENT_MODE = 1;
41
50
  function sleep(ms, signal) {
42
51
  if (signal?.aborted)
43
52
  return Promise.reject(new Error("请求已取消"));
44
- return new Promise((resolve, reject) => { const timer = setTimeout(resolve, ms); signal?.addEventListener("abort", () => { clearTimeout(timer); reject(new Error("请求已取消")); }, { once: true }); });
53
+ return new Promise((resolve, reject) => {
54
+ const abort = () => { clearTimeout(timer); reject(new Error("请求已取消")); };
55
+ const timer = setTimeout(() => { signal?.removeEventListener("abort", abort); resolve(); }, ms);
56
+ signal?.addEventListener("abort", abort, { once: true });
57
+ });
45
58
  }
46
- function requestBody(text, conversationId, botId, pluginList = [], attachments = []) {
59
+ function requestBody(text, conversationId, botId, pluginList = [], attachments = [], mode = "chat") {
60
+ const agentMode = mode === "local-computer" ? LOCAL_COMPUTER_AGENT_MODE : CHAT_AGENT_MODE;
47
61
  const fresh = !conversationId;
48
62
  const general = fresh ? { server_do_compare: true } : { runtime_type: 0, agent_task_param: {}, agent_task_param_change: { runtime_changed: false, device_changed: false, sandbox_auth_type_changed: false }, project_id: "", need_modify_conversation: false };
49
63
  const block = { block_type: 10000, content: { text_block: { text, icon_url: "", icon_url_dark: "", summary: "" }, pc_event_block: "" }, block_id: (0, node_crypto_1.randomUUID)(), parent_id: "", meta_info: [], append_fields: [] };
@@ -56,7 +70,7 @@ function requestBody(text, conversationId, botId, pluginList = [], attachments =
56
70
  parse_state: item.type === "image" ? 0 : 1, review_state: 1, upload_status: 1, progress: 100, src: "",
57
71
  })) }, pc_event_block: "" } }], message_status: 0 });
58
72
  const localConversationId = `local_${Date.now()}${Math.floor(Math.random() * 1000).toString().padStart(3, "0")}`;
59
- return JSON.stringify({ client_meta: { ...(fresh ? { local_conversation_id: localConversationId } : {}), conversation_id: conversationId, bot_id: botId, last_section_id: "", last_message_index: null, local_permissions: [{ permission_name: "ACCESS_COARSE_LOCATION", status: 1 }, { permission_name: "ACCESS_FINE_LOCATION", status: 1 }, { permission_name: "ACCESS_BACKGROUND_LOCATION", status: 1 }] }, messages, option: { send_message_scene: "", create_time_ms: Date.now(), collect_id: "", is_audio: false, answer_with_suggest: false, agent_mode: 2, tts_switch: false, need_deep_think: 0, click_clear_context: false, from_suggest: false, is_regen: false, is_replace: false, is_from_click_option: false, is_from_click_softlink: false, disable_sse_cache: false, select_text_action: "", is_select_text: false, resend_for_regen: false, scene_type: 0, unique_key: (0, node_crypto_1.randomUUID)(), start_seq: 0, need_create_conversation: fresh, ...(fresh ? { conversation_init_option: { need_ack_conversation: true }, conversation_init_ext: { model_item_key: "0", reasoning_effort: "3", mode_id: "1" } } : {}), regen_query_id: [], edit_query_id: [], regen_instruction: "", no_replace_for_regen: false, message_from: 0, shared_app_name: "", shared_app_id: "", sse_recv_event_options: { support_chunk_delta: true }, support_lazy_fetch_stream: true, is_ai_playground: false, is_old_user: true, general_task_param: general, recovery_option: { is_recovery: false, req_create_time_sec: Math.floor(Date.now() / 1000), append_sse_event_scene: 0 }, message_storage_type: 0, related_deleted_message_ids: {}, connector_info_list: [], model_config: { model_item_key: "0", model_extra_params: {}, reasoning_effort: fresh ? 3 : 5 }, aggregate_params: { mention_skill_list: "[]", mention_plugin_list: JSON.stringify(pluginList), mention_ext: "[{}]", conversation_mode: fresh ? "1" : "", mode_id: "1", model_item_key: "0", agent_mode: "2", reasoning_effort: fresh ? "3" : "5", provider_id: "" }, ...(fresh ? { conversation_mode: 1 } : {}) }, user_context: [], ext: { ...(fresh ? { sub_conv_firstmet_type: "1", conversation_init_option: JSON.stringify({ need_ack_conversation: true }) } : {}), agent_mode: "2", use_deep_think: "0", general_task_param: JSON.stringify(general), collection_id: "", is_finish: "1", commerce_credit_config_enable: "0" } });
73
+ return JSON.stringify({ client_meta: { ...(fresh ? { local_conversation_id: localConversationId } : {}), conversation_id: conversationId, bot_id: botId, last_section_id: "", last_message_index: null, local_permissions: [{ permission_name: "ACCESS_COARSE_LOCATION", status: 1 }, { permission_name: "ACCESS_FINE_LOCATION", status: 1 }, { permission_name: "ACCESS_BACKGROUND_LOCATION", status: 1 }] }, messages, option: { send_message_scene: "", create_time_ms: Date.now(), collect_id: "", is_audio: false, answer_with_suggest: false, agent_mode: agentMode, tts_switch: false, need_deep_think: 0, click_clear_context: false, from_suggest: false, is_regen: false, is_replace: false, is_from_click_option: false, is_from_click_softlink: false, disable_sse_cache: false, select_text_action: "", is_select_text: false, resend_for_regen: false, scene_type: 0, unique_key: (0, node_crypto_1.randomUUID)(), start_seq: 0, need_create_conversation: fresh, ...(fresh ? { conversation_init_option: { need_ack_conversation: true }, conversation_init_ext: { model_item_key: "0", reasoning_effort: "3", mode_id: "1" } } : {}), regen_query_id: [], edit_query_id: [], regen_instruction: "", no_replace_for_regen: false, message_from: 0, shared_app_name: "", shared_app_id: "", sse_recv_event_options: { support_chunk_delta: true }, support_lazy_fetch_stream: true, is_ai_playground: false, is_old_user: true, general_task_param: general, recovery_option: { is_recovery: false, req_create_time_sec: Math.floor(Date.now() / 1000), append_sse_event_scene: 0 }, message_storage_type: 0, related_deleted_message_ids: {}, connector_info_list: [], model_config: { model_item_key: "0", model_extra_params: {}, reasoning_effort: fresh ? 3 : 5 }, aggregate_params: { mention_skill_list: "[]", mention_plugin_list: JSON.stringify(pluginList), mention_ext: "[{}]", conversation_mode: fresh ? "1" : "", mode_id: "1", model_item_key: "0", agent_mode: String(agentMode), reasoning_effort: fresh ? "3" : "5", provider_id: "" }, ...(fresh ? { conversation_mode: 1 } : {}) }, user_context: [], ext: { ...(fresh ? { sub_conv_firstmet_type: "1", conversation_init_option: JSON.stringify({ need_ack_conversation: true }) } : {}), agent_mode: String(agentMode), use_deep_think: "0", general_task_param: JSON.stringify(general), collection_id: "", is_finish: "1", commerce_credit_config_enable: "0" } });
60
74
  }
61
75
  exports.responseTextScript = `(value) => { const texts = []; const visit = (item) => { if (Array.isArray(item)) { for (const child of item) visit(child); return; } if (!item || typeof item !== 'object') return; if (item.text_block && typeof item.text_block.text === 'string') texts.push(item.text_block.text); for (const child of Object.values(item)) visit(child); }; visit(value); return texts; }`;
62
76
  function mergeDoubaoReply(current, next) {
@@ -95,16 +109,17 @@ exports.responseImagesScript = `(value) => {
95
109
  };
96
110
  visit(value); return [...urls];
97
111
  }`;
98
- // 只读取平台原生产物块,不将正文链接、封面或本地路径当作附件。
99
- exports.responseArtifactsScript = `(value) => {
112
+ // 只读取平台原生产物块;本地文件必须来自当前任务工作区。
113
+ exports.responseArtifactsScript = `(value, workspace) => {
100
114
  const artifacts = [], seen = new Set(); let pending = false, failed = false;
101
115
  const add = (item) => { const key = JSON.stringify(item); if (!seen.has(key)) { seen.add(key); artifacts.push(item); } };
102
116
  const visit = (item) => {
103
117
  if (Array.isArray(item)) { for (const child of item) visit(child); return; }
104
118
  if (!item || typeof item !== 'object') return;
105
119
  const type = Number(item.block_type), content = item.content || item.content_obj;
106
- if (type === 2074) {
107
- for (const creation of (content?.creation_block || content)?.creations || []) {
120
+ if (type === 2074 || type === 10084) {
121
+ const creations = type === 10084 ? (content?.rich_media_layout_block?.media || []).map(media => media?.creation).filter(Boolean) : (content?.creation_block || content)?.creations || [];
122
+ for (const creation of creations) {
108
123
  const image = creation.image, video = creation.video;
109
124
  if (image?.status === 2 && item.is_finish === true && image.image_ori?.url) add({ kind: 'image', url: image.image_ori.url });
110
125
  if (image && (image.status === 1 || item.is_finish === false)) pending = true;
@@ -121,10 +136,20 @@ exports.responseArtifactsScript = `(value) => {
121
136
  else pending = true;
122
137
  }
123
138
  }
139
+ if (type === 10019) {
140
+ const file = content?.file_operation_block;
141
+ if ((file?.file_type === 'file' || file?.file_type === 'diff') && typeof file.path === 'string' && file.path && typeof workspace === 'string' && workspace) {
142
+ if (item.is_finish === true) add({ kind: 'file', filename: file.path.split(/[\\/]/).pop(), path: file.path, workspace });
143
+ else pending = true;
144
+ }
145
+ }
124
146
  for (const child of Object.values(item)) visit(child);
125
147
  };
126
148
  visit(value); return { artifacts, pending, ...(failed ? { failed: true } : {}) };
127
149
  }`;
150
+ function buildLocalComputerInjection(body) {
151
+ return `(${native_task_1.nativeTaskScript})(${body}, ${exports.responseTextScript}, ${exports.responseArtifactsScript}, ${exports.doubaoRequestUrlScript})`;
152
+ }
128
153
  // 异步生成会另发消息,必须通过本轮问题引用关联,不能拿当前页面最近的任意媒体。
129
154
  exports.currentResponseArtifactsScript = String.raw `(conversationId, questionIds, messageIds) => {
130
155
  if (location.pathname !== '/chat/' + conversationId) return { artifacts: [], pending: true };
@@ -147,6 +172,37 @@ exports.currentResponseArtifactsScript = String.raw `(conversationId, questionId
147
172
  }`;
148
173
  async function downloadDoubaoArtifact(item, fetchImpl = fetch, signal, budget = attachments_1.MAX_ATTACHMENT_BYTES) {
149
174
  const label = item.kind === "image" ? "图片" : item.kind === "video" ? "视频" : "文件";
175
+ const limit = Math.min(attachments_1.MAX_ATTACHMENT_BYTES, budget);
176
+ if (limit <= 0)
177
+ throw new Error("豆包附件总大小过大(上限 64 MiB)");
178
+ if (item.path) {
179
+ if (!item.workspace)
180
+ throw new Error("豆包本地产物缺少任务工作区");
181
+ const workspace = await (0, promises_1.realpath)(item.workspace);
182
+ const candidate = (0, node_path_1.isAbsolute)(item.path) ? item.path : (0, node_path_1.resolve)(workspace, item.path);
183
+ const filePath = await (0, promises_1.realpath)(candidate);
184
+ const relativePath = (0, node_path_1.relative)(workspace, filePath);
185
+ if (!relativePath || relativePath === ".." || relativePath.startsWith(`..${node_path_1.sep}`) || (0, node_path_1.isAbsolute)(relativePath))
186
+ throw new Error("豆包文件不在任务工作区内");
187
+ const info = await (0, promises_1.stat)(filePath);
188
+ if (!info.isFile())
189
+ throw new Error("豆包本地产物不是普通文件");
190
+ if (info.size > limit)
191
+ throw new Error("豆包文件过大(单项上限 32 MiB,总计上限 64 MiB)");
192
+ const chunks = [];
193
+ let size = 0;
194
+ for await (const chunk of (0, node_fs_1.createReadStream)(filePath)) {
195
+ size += chunk.byteLength;
196
+ if (size > limit)
197
+ throw new Error("豆包文件过大(单项上限 32 MiB,总计上限 64 MiB)");
198
+ chunks.push(chunk);
199
+ }
200
+ if (!size || size !== info.size)
201
+ throw new Error("豆包本地产物读取不完整");
202
+ const attachment = { kind: "file", data: Buffer.concat(chunks), filename: (0, attachments_1.attachmentFilename)(item.filename ?? filePath.split(/[\\/]/).pop() ?? ""), contentType: "application/octet-stream" };
203
+ (0, attachments_1.validateAttachments)([attachment]);
204
+ return attachment;
205
+ }
150
206
  let url;
151
207
  try {
152
208
  url = new URL(item.url ?? "");
@@ -160,9 +216,6 @@ async function downloadDoubaoArtifact(item, fetchImpl = fetch, signal, budget =
160
216
  if (url.protocol !== "https:" || !trusted || url.username || url.password || (url.port && url.port !== "443"))
161
217
  throw new Error(`豆包${label}地址无效`);
162
218
  const filename = item.kind === "file" ? (0, attachments_1.attachmentFilename)(item.filename ?? "") : "";
163
- const limit = Math.min(attachments_1.MAX_ATTACHMENT_BYTES, budget);
164
- if (limit <= 0)
165
- throw new Error("豆包附件总大小过大(上限 64 MiB)");
166
219
  const response = await fetchImpl(url.href, { redirect: "error", signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(60_000)]) : AbortSignal.timeout(60_000) });
167
220
  if (!response.ok)
168
221
  throw new Error(`下载豆包${label} HTTP ${response.status}`);
@@ -220,7 +273,7 @@ class DoubaoCdpClient {
220
273
  closeController = new AbortController();
221
274
  pending = new Map();
222
275
  options;
223
- constructor(options = {}) { this.options = { port: options.port ?? defaults.port, conversationId: options.conversationId ?? defaults.conversationId, botId: options.botId ?? defaults.botId, pluginList: options.pluginList ?? [], timeoutMs: options.timeoutMs ?? defaults.timeoutMs, fetchImpl: options.fetchImpl }; this.conversationId = this.options.conversationId; }
276
+ constructor(options = {}) { this.options = { port: options.port ?? defaults.port, conversationId: options.conversationId ?? defaults.conversationId, botId: options.botId ?? defaults.botId, pluginList: options.pluginList ?? [], mode: options.mode ?? "local-computer", timeoutMs: options.timeoutMs ?? defaults.timeoutMs, fetchImpl: options.fetchImpl, logger: options.logger, recoverCdp: options.recoverCdp }; this.conversationId = this.options.conversationId; }
224
277
  connected() { return this.socket?.readyState === 1; }
225
278
  async connect() {
226
279
  if (this.closeController.signal.aborted)
@@ -229,18 +282,34 @@ class DoubaoCdpClient {
229
282
  return;
230
283
  if (this.connecting)
231
284
  return this.connecting;
232
- const opening = this.openConnection();
285
+ const opening = (async () => {
286
+ for (let attempt = 1;; attempt++) {
287
+ try {
288
+ await this.openConnection();
289
+ return;
290
+ }
291
+ catch (error) {
292
+ if (attempt >= 3 || !isRecoverableCdpError(error) || this.closeController.signal.aborted)
293
+ throw error;
294
+ this.options.logger?.warn(`豆包 CDP ${this.options.port} 建连失败,准备第 ${attempt + 1}/3 次重试:${(0, agent_1.errorDetails)(error)}`);
295
+ await sleep(1000, this.closeController.signal);
296
+ }
297
+ }
298
+ })();
233
299
  this.connecting = opening;
234
300
  try {
235
301
  await opening;
236
302
  }
303
+ catch (error) {
304
+ throw new agent_1.AgentOperationError("DOUBAO_CDP_UNAVAILABLE", `连接豆包 CDP(端口 ${this.options.port})`, error);
305
+ }
237
306
  finally {
238
307
  if (this.connecting === opening)
239
308
  this.connecting = undefined;
240
309
  }
241
310
  }
242
311
  async openConnection() {
243
- const response = await (this.options.fetchImpl ?? fetch)(`http://127.0.0.1:${this.options.port}/json`);
312
+ const response = await (this.options.fetchImpl ?? fetch)(`http://127.0.0.1:${this.options.port}/json`, { signal: AbortSignal.any([this.closeController.signal, AbortSignal.timeout(Math.min(this.options.timeoutMs, 10_000))]) });
244
313
  if (!response.ok)
245
314
  throw new Error(`豆包 CDP /json HTTP ${response.status}`);
246
315
  const targets = await response.json();
@@ -254,7 +323,9 @@ class DoubaoCdpClient {
254
323
  this.socket = socket;
255
324
  socket.onmessage = event => this.onMessage(String(event.data));
256
325
  await new Promise((resolve, reject) => {
326
+ const timer = setTimeout(() => { disconnect(new Error("豆包 CDP WebSocket 连接超时")); socket.close(); }, Math.min(this.options.timeoutMs, 10_000));
257
327
  const disconnect = (error) => {
328
+ clearTimeout(timer);
258
329
  if (this.socket === socket) {
259
330
  this.socket = undefined;
260
331
  for (const pending of this.pending.values())
@@ -263,7 +334,7 @@ class DoubaoCdpClient {
263
334
  }
264
335
  reject(error);
265
336
  };
266
- socket.onopen = () => resolve();
337
+ socket.onopen = () => { clearTimeout(timer); resolve(); };
267
338
  socket.onclose = () => disconnect(new Error("豆包 CDP 已断开"));
268
339
  socket.onerror = () => { disconnect(new Error("豆包 CDP WebSocket 连接失败")); socket.close(); };
269
340
  });
@@ -353,32 +424,130 @@ class DoubaoCdpClient {
353
424
  throw new Error("请求已取消");
354
425
  const target = conversationId ?? this.conversationId;
355
426
  const uploaded = attachments?.length ? await this.uploadAttachments(attachments, requestSignal) : [];
356
- await this.evaluate(buildInjection(requestBody(text, target, this.options.botId, this.options.pluginList, uploaded)));
357
- const deadline = Date.now() + this.options.timeoutMs;
358
- while (Date.now() < deadline || signal?.aborted) {
427
+ const body = requestBody(text, target, this.options.botId, this.options.pluginList, uploaded, this.options.mode);
428
+ let attempts = 1;
429
+ const requestId = JSON.parse(body).option.unique_key;
430
+ let deadline = Date.now() + this.options.timeoutMs;
431
+ let progressSequence = 0;
432
+ let recovered = false;
433
+ const recover = async (error) => {
434
+ this.options.logger?.warn(`豆包 CDP ${this.options.port} 任务连接断开,重连并读取原任务 ${requestId},不会重复提交:${(0, agent_1.errorDetails)(error)}`);
435
+ // Runtime.evaluate 超时不一定触发 WebSocket close;先丢弃失效 socket,避免 connect() 误判为已连接。
436
+ const staleSocket = this.socket;
437
+ if (staleSocket && this.connected()) {
438
+ this.socket = undefined;
439
+ for (const pending of this.pending.values())
440
+ pending.reject(new Error("豆包 CDP 已断开,正在恢复连接"));
441
+ this.pending.clear();
442
+ try {
443
+ staleSocket.close();
444
+ }
445
+ catch { /* 旧连接关闭失败时继续重新发现页面 */ }
446
+ }
447
+ try {
448
+ await this.connect();
449
+ }
450
+ catch (cause) {
451
+ if (!this.options.recoverCdp || !(0, agent_1.errorDetails)(cause).includes("ECONNREFUSED"))
452
+ throw new agent_1.AgentOperationError("DOUBAO_CDP_UNAVAILABLE", "恢复豆包 CDP(原任务结果未确认)", cause);
453
+ this.options.logger?.warn(`豆包 CDP ${this.options.port} 在读取原任务时拒绝连接,调用受控桌面恢复:${(0, agent_1.errorDetails)(cause)}`);
454
+ try {
455
+ await this.options.recoverCdp();
456
+ if (!this.connected())
457
+ await this.connect();
458
+ }
459
+ catch (recoveryError) {
460
+ throw new agent_1.AgentOperationError("DOUBAO_CDP_UNAVAILABLE", "受控恢复豆包桌面进程后的 CDP 重连(原任务结果未确认)", recoveryError);
461
+ }
462
+ }
463
+ let restored;
464
+ try {
465
+ restored = await this.evaluate("window.__mab_doubao_request_id");
466
+ }
467
+ catch (cause) {
468
+ throw new agent_1.AgentOperationError("DOUBAO_RESULT_UNKNOWN", "确认豆包原任务(未重复提交)", cause);
469
+ }
470
+ if (restored !== requestId)
471
+ throw new agent_1.AgentOperationError("DOUBAO_RESULT_UNKNOWN", "恢复豆包原任务", new Error("原页面上下文或任务标识已丢失,结果未知;保留原会话,未重复提交"));
472
+ };
473
+ const submit = async () => {
474
+ try {
475
+ await this.evaluate(this.options.mode === "chat" ? buildInjection(body) : buildLocalComputerInjection(body));
476
+ }
477
+ catch (error) {
478
+ if (isRecoverableCdpError(error)) {
479
+ await recover(error);
480
+ recovered = true;
481
+ }
482
+ else
483
+ throw new agent_1.AgentOperationError("DOUBAO_SUBMIT_UNKNOWN", "通过 CDP 提交豆包任务(接收状态未知,未重复提交)", error);
484
+ }
485
+ };
486
+ await submit();
487
+ while (Date.now() < deadline || signal?.aborted || recovered) {
359
488
  // 上游取消只结束等待,页面请求结束前继续占用原队列。
360
- await sleep(1000, this.closeController.signal);
361
- const result = await this.evaluate("window.__mab_doubao_result");
489
+ // 重连耗时可能跨过期限,只额外读取一次,避免漏掉断线期间完成的原任务。
490
+ const finalRead = recovered && Date.now() >= deadline;
491
+ if (recovered)
492
+ recovered = false;
493
+ else
494
+ await sleep(1000, this.closeController.signal);
495
+ let result;
496
+ let progress;
497
+ try {
498
+ result = await this.evaluate("window.__mab_doubao_result");
499
+ if (typeof result !== "string" && this.options.mode === "local-computer")
500
+ progress = await this.evaluate("window.__mab_doubao_progress");
501
+ }
502
+ catch (error) {
503
+ if (!isRecoverableCdpError(error))
504
+ throw new agent_1.AgentOperationError("DOUBAO_CDP_READ_FAILED", "通过 CDP 读取豆包任务结果", error);
505
+ if (finalRead && !signal?.aborted)
506
+ break;
507
+ await recover(error);
508
+ recovered = true;
509
+ continue;
510
+ }
511
+ // 只按本轮新流事件续期,反复读取同一快照不能延长等待。
512
+ if (progress?.requestId === requestId && typeof progress.sequence === "number" && Number.isSafeInteger(progress.sequence) && progress.sequence > progressSequence) {
513
+ progressSequence = progress.sequence;
514
+ deadline = Date.now() + this.options.timeoutMs;
515
+ }
362
516
  if (typeof result !== "string")
363
517
  continue;
364
518
  const parsed = JSON.parse(result);
365
- if (parsed.failed)
366
- throw new Error("豆包媒体生成失败");
519
+ if (parsed.requestId && parsed.requestId !== requestId)
520
+ continue;
521
+ if (parsed.failed) {
522
+ const failure = parsed.reason ?? `豆包任务失败${parsed.errorCode ? `(错误码 ${parsed.errorCode})` : ""}`;
523
+ if (signal?.aborted)
524
+ throw new agent_1.AgentOperationError("DOUBAO_REQUEST_CANCELLED", "等待豆包平台响应", new Error(`${failure};请求已取消,不再重试`));
525
+ const retryable = parsed.errorCode === "718013001" && parsed.retryable === true && parsed.acknowledged === false && parsed.progressed === false;
526
+ if (retryable && attempts < MAX_PREALLOCATION_ATTEMPTS && Date.now() < deadline) {
527
+ attempts++;
528
+ this.options.logger?.warn(`豆包平台 718013001 未接收任务,复用原会话和请求 ${requestId},第 ${attempts}/${MAX_PREALLOCATION_ATTEMPTS} 次尝试`);
529
+ await submit();
530
+ continue;
531
+ }
532
+ throw new agent_1.AgentOperationError(parsed.errorCode ? "DOUBAO_PLATFORM_ERROR" : "DOUBAO_STREAM_FAILED", "等待豆包平台流式响应(CDP 提交已完成)", new Error(`${failure}${retryable ? `;已尝试 ${attempts} 次,保留原会话` : ""}`));
533
+ }
367
534
  if (!parsed.ok)
368
535
  throw new Error(`豆包请求失败: ${parsed.reason ?? "未知错误"}`);
369
536
  let artifacts = parsed.artifacts ?? [];
370
537
  if (parsed.pending || parsed.expectVideo) {
371
- const current = await this.evaluate(`(${exports.currentResponseArtifactsScript})(${JSON.stringify(parsed.conversationId || target)}, ${JSON.stringify(parsed.questionIds ?? [])}, ${JSON.stringify(parsed.messageIds ?? [])})`);
538
+ const current = await this.evaluate(this.options.mode === "local-computer"
539
+ ? `window.__mab_doubao_read_artifacts(${JSON.stringify(requestId)})`
540
+ : `(${exports.currentResponseArtifactsScript})(${JSON.stringify(parsed.conversationId || target)}, ${JSON.stringify(parsed.questionIds ?? [])}, ${JSON.stringify(parsed.messageIds ?? [])})`, true);
372
541
  if (current.failed)
373
542
  throw new Error("豆包媒体生成失败");
374
543
  if (current.pending || !current.artifacts.length || (parsed.expectVideo && !current.artifacts.some(item => item.kind === "video")))
375
544
  continue;
376
545
  artifacts = [...artifacts, ...current.artifacts];
377
546
  }
378
- const unique = [...new Map(artifacts.map(item => [`${item.kind}:${item.vid || item.uri || item.url}`, item])).values()];
547
+ const unique = [...new Map(artifacts.map(item => [`${item.kind}:${item.path || item.vid || item.uri || item.url}`, item])).values()];
379
548
  if (unique.length > attachments_1.MAX_ATTACHMENTS)
380
549
  throw new Error("豆包单条消息最多返回 8 个附件");
381
- const resolved = unique.some(item => !item.url) ? await this.evaluate(buildResolveArtifactsInjection(unique), true) : unique;
550
+ const resolved = unique.some(item => !item.url && !item.path) ? await this.evaluate(buildResolveArtifactsInjection(unique), true) : unique;
382
551
  const output = [];
383
552
  let total = 0;
384
553
  for (const item of resolved) {
@@ -399,7 +568,7 @@ class DoubaoCdpClient {
399
568
  if (signal?.aborted && parsed.pending !== true && !parsed.expectVideo)
400
569
  throw new Error("豆包请求已取消,已确认本轮页面请求结束,未返回正文或附件");
401
570
  }
402
- throw new Error("等待豆包回复超时");
571
+ throw new agent_1.AgentOperationError("DOUBAO_REPLY_TIMEOUT", "等待豆包回复", new Error("等待豆包回复超时,尚未确认原任务结束;保留原会话,未重复提交"));
403
572
  }
404
573
  async uploadAttachments(attachments, signal) {
405
574
  const token = (0, node_crypto_1.randomUUID)();
@@ -457,13 +626,16 @@ class DoubaoCdpClient {
457
626
  throw new Error(`豆包页面 JS 异常: ${JSON.stringify(response.result.exceptionDetails).slice(0, 300)}`); return response.result?.result?.value; }
458
627
  }
459
628
  exports.DoubaoCdpClient = DoubaoCdpClient;
629
+ function isRecoverableCdpError(error) {
630
+ return /豆包 CDP (?:已断开|尚未连接|WebSocket 连接(?:失败|超时))|CDP Runtime.evaluate 超时|ECONN(?:RESET|REFUSED)|fetch failed|TimeoutError/i.test((0, agent_1.errorDetails)(error));
631
+ }
460
632
  function buildResolveArtifactsInjection(artifacts) {
461
633
  return `(async () => {
462
634
  const controller = window.__mab_doubao_controller;
463
635
  const requestUrl = window.__mab_doubao_request_url;
464
636
  const result = [];
465
637
  for (const item of ${JSON.stringify(artifacts)}) {
466
- if (item.url) { result.push(item); continue; }
638
+ if (item.url || item.path) { result.push(item); continue; }
467
639
  const path = item.kind === 'video' ? '/samantha/video/get_play_info' : '/alice/message/get_file_url';
468
640
  const url = new URL(requestUrl);
469
641
  if (url.origin !== 'https://www.doubao.com') throw new Error('豆包产物接口地址无效');
@@ -640,17 +812,8 @@ exports.doubaoHistoryReadPageScript = String.raw `async (conversationId, cursor,
640
812
  return { error: '豆包会话历史读取失败,请检查桌面端历史接口是否可用' };
641
813
  }
642
814
  }`;
643
- function buildInjection(body) {
644
- const url = "https://www.doubao.com/chat/completion?aid=582478&channel=win&client_platform=pc_client&device_platform=web&doubao_device_platform=desktop&language=zh&pkg_type=release_version&real_aid=582478&region=CN&runtime=web&samantha_web=1&sys_region=CN&tz_name=Asia%2FShanghai&use-olympus-account=1&version_code=20800&web_platform=desktop";
645
- return String.raw `(() => {
646
- window.__mab_doubao_result = null;
647
- window.__mab_doubao_controller?.abort();
648
- const controller = new AbortController();
649
- window.__mab_doubao_controller = controller;
650
- const merge = ${mergeDoubaoReply.toString()};
651
- (async () => {
652
- try {
653
- const requestUrl = new URL(${JSON.stringify(url)});
815
+ exports.doubaoRequestUrlScript = String.raw `(() => {
816
+ const requestUrl = new URL("https://www.doubao.com/chat/completion?aid=582478&channel=win&client_platform=pc_client&device_platform=web&doubao_device_platform=desktop&language=zh&pkg_type=release_version&real_aid=582478&region=CN&runtime=web&samantha_web=1&sys_region=CN&tz_name=Asia%2FShanghai&use-olympus-account=1&version_code=20800&web_platform=desktop");
654
817
  const resources = [];
655
818
  for (const entry of performance.getEntriesByType('resource')) { try { const u = new URL(entry.name); if (u.origin === requestUrl.origin) resources.push(u); } catch {} }
656
819
  resources.reverse();
@@ -665,6 +828,20 @@ function buildInjection(body) {
665
828
  const chromium = userAgent.match(/Chrome\/([\d.]+)/i)?.[1];
666
829
  if (version) { requestUrl.searchParams.set('pc_version', version); requestUrl.searchParams.set('doubao_pc_version', version); }
667
830
  if (chromium) requestUrl.searchParams.set('chromium_version', chromium);
831
+ return requestUrl;
832
+ })`;
833
+ function buildInjection(body) {
834
+ return String.raw `(() => {
835
+ window.__mab_doubao_result = null;
836
+ window.__mab_doubao_controller?.abort();
837
+ const controller = new AbortController();
838
+ window.__mab_doubao_controller = controller;
839
+ window.__mab_doubao_request_id = ${JSON.stringify(JSON.parse(body).option.unique_key)};
840
+ const merge = ${mergeDoubaoReply.toString()};
841
+ (async () => {
842
+ let acknowledged = false, progressed = false, errorCode = '', retryable = false;
843
+ try {
844
+ const requestUrl = (${exports.doubaoRequestUrlScript})();
668
845
  window.__mab_doubao_request_url = requestUrl.href;
669
846
  const response = await fetch(requestUrl.href, { method: 'POST', headers: { 'content-type': 'application/json', ...(JSON.parse(${JSON.stringify(body)}).client_meta.conversation_id ? {} : { 'Agw-Js-Conv': 'str, str' }) }, credentials: 'include', body: ${JSON.stringify(body)}, signal: controller.signal });
670
847
  if (!response.ok) throw new Error('HTTP ' + response.status);
@@ -675,6 +852,7 @@ function buildInjection(body) {
675
852
  if (!value || typeof value !== 'object') return;
676
853
  if (value.ext?.has_video_gen === '1' || value.ext?.has_video_gen === 1) expectVideo = true;
677
854
  if ([2074, 10020].includes(Number(value.block_type))) {
855
+ progressed = true;
678
856
  const key = value.block_id || value.id || value.block_type + ':' + JSON.stringify(value.content || value.content_obj);
679
857
  const previous = blocks.get(key) || {};
680
858
  blocks.set(key, { ...previous, ...value, ...(value.content ? { content: { ...previous.content, ...value.content } } : {}) });
@@ -684,7 +862,15 @@ function buildInjection(body) {
684
862
  const consume = (block) => {
685
863
  const event = (block.match(/^event:\s*(.+)$/m) || [])[1] || '', data = (block.match(/^data:\s*(.+)$/m) || [])[1] || '';
686
864
  if (event) events.push(event);
687
- if (event === 'STREAM_ERROR') throw new Error((${doubaoStreamErrorMessage.toString()})(data));
865
+ if (event === 'SSE_ACK') acknowledged = true;
866
+ if (['STREAM_CHUNK', 'CHUNK_DELTA', 'STREAM_MSG_NOTIFY', 'FULL_MSG_NOTIFY', 'SSE_REPLY_END'].includes(event)) progressed = true;
867
+ if (event === 'STREAM_ERROR') {
868
+ let parsed; try { parsed = JSON.parse(data); } catch { parsed = {}; }
869
+ errorCode = String(parsed.error_code ?? parsed.code ?? parsed.error?.error_code ?? parsed.error?.code ?? '');
870
+ const reason = (${doubaoStreamErrorMessage.toString()})(data);
871
+ retryable = errorCode === '718013001' && !reason.includes('风控');
872
+ throw new Error(reason);
873
+ }
688
874
  if (!data) return;
689
875
  let json; try { json = JSON.parse(data); } catch { return; }
690
876
  if (event === 'SSE_ACK' && !conversationId && typeof json.ack_client_meta?.conversation_id === 'string' && /^\d+$/.test(json.ack_client_meta.conversation_id)) conversationId = json.ack_client_meta.conversation_id;
@@ -697,17 +883,17 @@ function buildInjection(body) {
697
883
  }
698
884
  const meta = json.meta || json.message;
699
885
  if (meta?.conversation_id === conversationId && meta.user_type === 2 && meta.message_id) messageIds.add(String(meta.message_id));
700
- if (event === 'STREAM_CHUNK') for (const patch of (json.patch_op || [])) reply = merge(reply, patch.patch_value?.tts_content || '');
701
- if (event === 'CHUNK_DELTA' && typeof json.text === 'string') reply = merge(reply, json.text);
702
- if (event === 'STREAM_MSG_NOTIFY' || event === 'FULL_MSG_NOTIFY') for (const text of (${exports.responseTextScript})(json)) reply = merge(reply, text);
703
- if (event === 'SSE_REPLY_END') reply = merge(reply, json.msg_finish_attr?.brief || json.data?.msg_finish_attr?.brief || '');
886
+ if (event === 'STREAM_CHUNK') for (const patch of (json.patch_op || [])) { const text = patch.patch_value?.tts_content || ''; if (text) progressed = true; reply = merge(reply, text); }
887
+ if (event === 'CHUNK_DELTA' && typeof json.text === 'string') { if (json.text) progressed = true; reply = merge(reply, json.text); }
888
+ if (event === 'STREAM_MSG_NOTIFY' || event === 'FULL_MSG_NOTIFY') for (const text of (${exports.responseTextScript})(json)) { if (text) progressed = true; reply = merge(reply, text); }
889
+ if (event === 'SSE_REPLY_END') { const text = json.msg_finish_attr?.brief || json.data?.msg_finish_attr?.brief || ''; if (text) progressed = true; reply = merge(reply, text); }
704
890
  };
705
891
  for (;;) { const item = await reader.read(); if (item.done) break; buffer += decoder.decode(item.value, { stream: true }).replace(/\r\n/g, '\n'); let index; while ((index = buffer.indexOf('\n\n')) >= 0) { consume(buffer.slice(0, index)); buffer = buffer.slice(index + 2); } }
706
892
  if (buffer.trim()) consume(buffer);
707
893
  const media = (${exports.responseArtifactsScript})([...blocks.values()]);
708
894
  const ok = Boolean(reply.trim() || media.artifacts.length || media.pending || expectVideo);
709
- if (window.__mab_doubao_controller === controller) window.__mab_doubao_result = JSON.stringify({ ok, reply, conversationId, ...media, expectVideo, questionIds: [...questionIds], messageIds: [...messageIds], reason: ok ? '' : 'SSE 未提取到文本或附件; events=' + events.slice(0, 20).join(',') });
710
- } catch (error) { if (window.__mab_doubao_controller === controller) window.__mab_doubao_result = JSON.stringify({ ok: false, reason: String(error) }); }
895
+ if (window.__mab_doubao_controller === controller) window.__mab_doubao_result = JSON.stringify({ ok, reply, conversationId, ...media, expectVideo, acknowledged, progressed, errorCode, questionIds: [...questionIds], messageIds: [...messageIds], reason: ok ? '' : 'SSE 未提取到文本或附件; events=' + events.slice(0, 20).join(',') });
896
+ } catch (error) { if (window.__mab_doubao_controller === controller) window.__mab_doubao_result = JSON.stringify({ ok: false, failed: true, acknowledged, progressed, errorCode, retryable, reason: String(error) }); }
711
897
  })(); return 'started';
712
898
  })()`;
713
899
  }