agent-bridge-doubao 1.0.5 → 1.0.7

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.
@@ -3,6 +3,9 @@
3
3
  "properties": {
4
4
  "aid": { "type": "string", "description": "复用的 EvolCore 已有 AID" },
5
5
  "cdpPort": { "type": "integer", "minimum": 1, "description": "豆包桌面端 CDP 调试端口,默认 9227" },
6
+ "executablePath": { "type": "string", "description": "Doubao.exe 路径,省略时自动查找" },
7
+ "autoStart": { "type": "boolean", "default": true, "description": "CDP 未连接时自动启动豆包" },
8
+ "startupTimeoutMs": { "type": "integer", "minimum": 1000, "default": 30000 },
6
9
  "conversationId": { "type": "string", "description": "豆包会话 ID" },
7
10
  "botId": { "type": "string", "description": "豆包 bot ID" },
8
11
  "timeoutMs": { "type": "integer", "minimum": 1000, "description": "等待回复超时时间(毫秒)" },
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.findWindowsExecutable = findWindowsExecutable;
4
+ exports.findExecutableInTree = findExecutableInTree;
5
+ exports.launchAndWait = launchAndWait;
6
+ exports.waitFor = waitFor;
7
+ const promises_1 = require("node:fs/promises");
8
+ const node_fs_1 = require("node:fs");
9
+ const node_path_1 = require("node:path");
10
+ /** 在 Windows 常见安装目录中查找桌面程序,避免依赖单一盘符。 */
11
+ async function findWindowsExecutable(fileName, configured) {
12
+ if (configured?.trim())
13
+ return (0, node_fs_1.existsSync)((0, node_path_1.resolve)(configured)) ? (0, node_path_1.resolve)(configured) : undefined;
14
+ if (process.platform !== "win32")
15
+ return undefined;
16
+ const pattern = /doubao|豆包|qwen|qwwork|千问/i;
17
+ const roots = [...new Set([
18
+ process.env.LOCALAPPDATA && (0, node_path_1.join)(process.env.LOCALAPPDATA, "Programs"),
19
+ process.env.ProgramFiles,
20
+ process.env["ProgramFiles(x86)"],
21
+ ...Array.from({ length: 24 }, (_, index) => `${String.fromCharCode(67 + index)}:\\`),
22
+ ].filter((value) => typeof value === "string" && (0, node_fs_1.existsSync)(value)))];
23
+ for (const root of roots) {
24
+ const found = await findExecutableInTree(root, fileName, pattern, 6);
25
+ if (found)
26
+ return found;
27
+ }
28
+ return undefined;
29
+ }
30
+ async function findExecutableInTree(directory, fileName, pattern, depth) {
31
+ return search(directory, fileName, pattern, depth, pattern.test(directory));
32
+ }
33
+ async function search(directory, fileName, pattern, depth, insideProduct) {
34
+ try {
35
+ const entries = await (0, promises_1.readdir)(directory, { withFileTypes: true });
36
+ for (const entry of entries)
37
+ if (entry.isFile() && insideProduct && entry.name.toLowerCase() === fileName.toLowerCase())
38
+ return (0, node_path_1.join)(directory, entry.name);
39
+ if (depth <= 0)
40
+ return undefined;
41
+ for (const entry of entries)
42
+ if (entry.isDirectory() && !entry.name.startsWith(".") && (insideProduct || pattern.test(entry.name) || /^(?:Program Files(?: \(x86\))?|Programs)$/i.test(entry.name))) {
43
+ const found = await search((0, node_path_1.join)(directory, entry.name), fileName, pattern, depth - 1, insideProduct || pattern.test(entry.name));
44
+ if (found)
45
+ return found;
46
+ }
47
+ }
48
+ catch { /* 无权限或目录在扫描期间消失时继续查找 */ }
49
+ return undefined;
50
+ }
51
+ async function launchAndWait(executable, args, connect, timeoutMs, launch = (file, argv) => (0, node_child_process_1.spawn)(file, argv, { detached: true, stdio: "ignore", windowsHide: false })) {
52
+ const child = launch(executable, args);
53
+ child.unref();
54
+ return waitFor(connect, timeoutMs, 250);
55
+ }
56
+ async function waitFor(action, timeoutMs, intervalMs = 500) {
57
+ const deadline = Date.now() + timeoutMs;
58
+ let last;
59
+ while (Date.now() < deadline) {
60
+ try {
61
+ return await action();
62
+ }
63
+ catch (error) {
64
+ last = error;
65
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
66
+ }
67
+ }
68
+ throw last instanceof Error ? last : new Error("桌面程序启动超时");
69
+ }
70
+ const node_child_process_1 = require("node:child_process");
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DoubaoCdpClient = void 0;
4
4
  exports.requestBody = requestBody;
5
+ exports.responseText = responseText;
5
6
  const node_crypto_1 = require("node:crypto");
6
7
  const defaults = { port: Number(process.env.DOUBAO_CDP_PORT ?? "9227"), conversationId: process.env.DOUBAO_CONVERSATION_ID ?? "759450172544514", botId: process.env.DOUBAO_BOT_ID ?? "7338286299411103781", timeoutMs: Number(process.env.DOUBAO_TIMEOUT_MS ?? "180000") };
7
8
  function sleep(ms, signal) {
@@ -14,6 +15,28 @@ function requestBody(text, conversationId, botId, pluginList = []) {
14
15
  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: [] };
15
16
  return JSON.stringify({ client_meta: { 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: [{ local_message_id: (0, node_crypto_1.randomUUID)(), content_block: [block], message_status: 0 }], 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, is_regen: false, is_replace: false, is_from_click_option: false, is_from_click_softlink: false, disable_sse_cache: false, scene_type: 0, unique_key: (0, node_crypto_1.randomUUID)(), start_seq: 0, need_create_conversation: false, regen_query_id: [], edit_query_id: [], message_from: 0, 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: 5 }, aggregate_params: { mention_skill_list: "[]", mention_plugin_list: JSON.stringify(pluginList), mention_ext: "[{}]", conversation_mode: "", mode_id: "1", model_item_key: "0", agent_mode: "2", reasoning_effort: "5", provider_id: "" } }, user_context: [], ext: { agent_mode: "2", use_deep_think: "0", general_task_param: JSON.stringify(general), collection_id: "", is_finish: "1", commerce_credit_config_enable: "0" } });
16
17
  }
18
+ function responseText(event, value) {
19
+ if (event !== "STREAM_MSG_NOTIFY" && event !== "FULL_MSG_NOTIFY")
20
+ return [];
21
+ const texts = [];
22
+ const visit = (item) => {
23
+ if (Array.isArray(item)) {
24
+ for (const child of item)
25
+ visit(child);
26
+ return;
27
+ }
28
+ if (!item || typeof item !== "object")
29
+ return;
30
+ const record = item;
31
+ const block = record.text_block;
32
+ if (block && typeof block === "object" && typeof block.text === "string")
33
+ texts.push(block.text);
34
+ for (const child of Object.values(record))
35
+ visit(child);
36
+ };
37
+ visit(value);
38
+ return texts;
39
+ }
17
40
  class DoubaoCdpClient {
18
41
  socket;
19
42
  sequence = 0;
@@ -84,7 +107,7 @@ function buildInjection(body) {
84
107
  if (!response.ok) throw new Error('HTTP ' + response.status);
85
108
  const reader = response.body?.getReader(); if (!reader) throw new Error('响应无 body');
86
109
  const decoder = new TextDecoder(); let buffer = '', reply = '', events = [];
87
- const consume = (block) => { const event = (block.match(/^event:\s*(.+)$/m) || [])[1] || ''; const data = (block.match(/^data:\s*(.+)$/m) || [])[1] || ''; if (event) events.push(event); if (event === 'STREAM_ERROR') throw new Error('STREAM_ERROR ' + data.slice(0, 500)); if (!data) return; try { const json = JSON.parse(data); if (event === 'STREAM_CHUNK') for (const patch of (json.patch_op || [])) reply = merge(reply, patch.patch_value?.tts_content || ''); if (event === 'CHUNK_DELTA' && typeof json.text === 'string') reply = merge(reply, json.text); if (event === 'STREAM_MSG_NOTIFY') for (const item of (json.content?.content_block || json.data?.content?.content_block || [])) reply = merge(reply, item.content?.text_block?.text || ''); if (event === 'SSE_REPLY_END') reply = merge(reply, json.msg_finish_attr?.brief || json.data?.msg_finish_attr?.brief || ''); } catch {} };
110
+ const consume = (block) => { const event = (block.match(/^event:\s*(.+)$/m) || [])[1] || ''; const data = (block.match(/^data:\s*(.+)$/m) || [])[1] || ''; if (event) events.push(event); if (event === 'STREAM_ERROR') throw new Error('STREAM_ERROR ' + data.slice(0, 500)); if (!data) return; try { const json = JSON.parse(data); if (event === 'STREAM_CHUNK') for (const patch of (json.patch_op || [])) reply = merge(reply, patch.patch_value?.tts_content || ''); if (event === 'CHUNK_DELTA' && typeof json.text === 'string') reply = merge(reply, json.text); for (const text of ${responseText.toString()}(event, json)) reply = merge(reply, text); if (event === 'SSE_REPLY_END') reply = merge(reply, json.msg_finish_attr?.brief || json.data?.msg_finish_attr?.brief || ''); } catch {} };
88
111
  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); } }
89
112
  if (buffer.trim()) consume(buffer);
90
113
  window.__mab_doubao_result = JSON.stringify({ ok: Boolean(reply.trim()), reply, reason: reply.trim() ? '' : 'SSE 未提取到文本; events=' + events.slice(0, 20).join(',') });
@@ -3,9 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DoubaoPlugin = exports.doubaoManifest = void 0;
4
4
  const cdp_client_1 = require("./cdp-client");
5
5
  const plugin_1 = require("../../plugin");
6
+ const desktop_launch_1 = require("../desktop-launch");
6
7
  exports.doubaoManifest = {
7
8
  id: "doubao",
8
- version: "1.0.5",
9
+ version: "1.0.7",
9
10
  displayName: "豆包",
10
11
  entry: "dist/index.js",
11
12
  apiVersion: "1",
@@ -38,11 +39,29 @@ class DoubaoPlugin {
38
39
  this.state = "ready";
39
40
  this.message = "";
40
41
  }
41
- catch (error) {
42
+ catch (first) {
43
+ if (context.config.autoStart !== false && !this.injectedClient && process.platform === "win32") {
44
+ const port = (0, plugin_1.resolveRuntimePort)(context.config, context.runtime, "doubao", "DOUBAO_CDP_PORT", 9227);
45
+ const executable = await (0, desktop_launch_1.findWindowsExecutable)("Doubao.exe", typeof context.config.executablePath === "string" ? context.config.executablePath : undefined);
46
+ if (executable) {
47
+ context.logger?.warn?.(`豆包已启动,等待 CDP ${port}`);
48
+ try {
49
+ await (0, desktop_launch_1.launchAndWait)(executable, [`--remote-debugging-port=${port}`], () => this.client.connect(), typeof context.config.startupTimeoutMs === "number" ? context.config.startupTimeoutMs : 30_000);
50
+ this.state = "ready";
51
+ this.message = "";
52
+ return;
53
+ }
54
+ catch (error) {
55
+ first = new Error(`已启动豆包,但 CDP ${port} 未就绪:${error instanceof Error ? error.message : String(error)}`);
56
+ }
57
+ }
58
+ else
59
+ first = new Error("未找到 Doubao.exe,请配置 executablePath");
60
+ }
42
61
  this.state = "failed";
43
- this.message = error instanceof Error ? error.message : String(error);
62
+ this.message = first instanceof Error ? first.message : String(first);
44
63
  context.logger?.warn?.(this.message);
45
- throw error;
64
+ throw first;
46
65
  }
47
66
  }
48
67
  async deactivate() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-bridge-doubao",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "description": "agent-bridge 的豆包插件。",
5
5
  "keywords": [
6
6
  "mu-agent-bridge-plugin",
package/plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "doubao",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "type": "agent",
5
5
  "displayName": "豆包",
6
6
  "entry": "dist/src-ts/plugins/doubao/index.js",