bazilion 0.2.1 → 0.4.0

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.
package/dist/worker.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // ../daemon/src/runtime/worker/entry.ts
4
- import { randomUUID as randomUUID5 } from "crypto";
4
+ import { randomUUID as randomUUID6 } from "crypto";
5
5
  import { join as join14 } from "path";
6
6
 
7
7
  // ../daemon/src/core/agent/delete.ts
@@ -418,6 +418,80 @@ var SERVICES = [
418
418
  placeholder: "https://searxng.example.com"
419
419
  }
420
420
  ]
421
+ },
422
+ {
423
+ id: "browser",
424
+ displayName: "Browser Automation",
425
+ category: "service",
426
+ group: "Browser",
427
+ hint: "Playwright-driven browser tools (navigate, snapshot, click, screenshot). Run `pnpm exec playwright install chromium` once.",
428
+ fields: [
429
+ {
430
+ envVar: "BROWSER_ENABLED",
431
+ kind: "config",
432
+ label: "Enable browser tools",
433
+ placeholder: "true",
434
+ description: "Expose the browser_* tools to agents (true/false). Default true."
435
+ },
436
+ {
437
+ envVar: "BROWSER_HEADLESS",
438
+ kind: "config",
439
+ label: "Headless",
440
+ placeholder: "true",
441
+ description: "Run Chromium headless (true/false). Default true."
442
+ },
443
+ {
444
+ envVar: "BROWSER_ALLOW_PRIVATE_NETWORK",
445
+ kind: "config",
446
+ label: "Allow private network",
447
+ placeholder: "false",
448
+ description: "Permit the browser to reach loopback/private IPs (SSRF guard off). Default false \u2014 only enable for local dev."
449
+ },
450
+ {
451
+ envVar: "BROWSER_IDLE_MS",
452
+ kind: "config",
453
+ label: "Idle timeout (ms)",
454
+ placeholder: "900000",
455
+ description: "Close an idle browser session after this many ms. Default 900000 (15 min)."
456
+ },
457
+ {
458
+ envVar: "BROWSER_MAX_SESSIONS",
459
+ kind: "config",
460
+ label: "Max concurrent sessions",
461
+ placeholder: "4",
462
+ description: "Cap on simultaneously-open browser sessions (LRU-evicted). Default 4."
463
+ }
464
+ ]
465
+ },
466
+ // --- External integrations (chat bridges, etc) ---
467
+ // Each integration has its own dedicated /config/integrations/* page with
468
+ // workflow-specific UI (preflight health, setup wizard, …). The fields
469
+ // here exist so the keys are allowlisted in the config/secrets stores and
470
+ // surfaced through the generic `PUT /api/config/fields/:envVar` endpoint.
471
+ // Daemon-managed internal state keys (watermarks, derived topic ids) live
472
+ // in repos/config.ts:INTERNAL_CONFIG_KEYS instead, since the user never
473
+ // edits them.
474
+ {
475
+ id: "telegram",
476
+ displayName: "Telegram",
477
+ category: "integration",
478
+ hint: "Forum-supergroup bot for talking to your agents from a phone",
479
+ fields: [
480
+ {
481
+ envVar: "TELEGRAM_BOT_TOKEN",
482
+ kind: "secret",
483
+ label: "Bot token",
484
+ placeholder: "1234567890:ABC...",
485
+ description: "Get one from @BotFather \u2192 /newbot. Disable Privacy Mode in Bot Settings."
486
+ },
487
+ {
488
+ envVar: "TELEGRAM_CHAT_ID",
489
+ kind: "config",
490
+ label: "Supergroup chat ID",
491
+ placeholder: "-1001234567890",
492
+ description: "Numeric id of the forum-enabled supergroup the bot is admin in."
493
+ }
494
+ ]
421
495
  }
422
496
  ];
423
497
  var FIELD_INDEX = (() => {
@@ -431,22 +505,35 @@ var FIELD_INDEX = (() => {
431
505
  })();
432
506
 
433
507
  // ../daemon/src/core/repos/config.ts
434
- var CONFIG_KEYS = SERVICES.flatMap(
435
- (s) => s.fields.filter((f) => f.kind === "config").map((f) => f.envVar)
436
- );
508
+ var INTERNAL_CONFIG_KEYS = [
509
+ // Telegram (step 2+): polling watermark + derived topic/message ids.
510
+ "TELEGRAM_LAST_UPDATE_ID",
511
+ "TELEGRAM_SERVICE_TOPIC_ID",
512
+ "TELEGRAM_DIRECTORY_MESSAGE_ID",
513
+ // Phase 5: proposed new chat id after a migrate_to_chat_id event, pending
514
+ // operator confirmation via POST /api/config/telegram/reconnect.
515
+ "TELEGRAM_MIGRATED_CHAT_ID"
516
+ ];
517
+ var CONFIG_KEYS = [
518
+ ...SERVICES.flatMap((s) => s.fields.filter((f) => f.kind === "config").map((f) => f.envVar)),
519
+ ...INTERNAL_CONFIG_KEYS
520
+ ];
437
521
  var CONFIG_KEY_SET = new Set(CONFIG_KEYS);
438
522
 
439
- // ../daemon/src/core/repos/messages.ts
523
+ // ../daemon/src/core/repos/mcpServers.ts
440
524
  import { randomUUID as randomUUID2 } from "crypto";
441
525
 
526
+ // ../daemon/src/core/repos/messages.ts
527
+ import { randomUUID as randomUUID3 } from "crypto";
528
+
442
529
  // ../daemon/src/core/repos/secrets.ts
443
530
  import { createCipheriv, createDecipheriv, pbkdf2Sync, randomBytes } from "crypto";
444
531
 
445
532
  // ../daemon/src/core/repos/triggers.ts
446
- import { randomUUID as randomUUID3 } from "crypto";
533
+ import { randomUUID as randomUUID4 } from "crypto";
447
534
 
448
535
  // ../daemon/src/core/repos/webTokens.ts
449
- import { createHash, randomBytes as randomBytes2, randomUUID as randomUUID4 } from "crypto";
536
+ import { createHash, randomBytes as randomBytes2, randomUUID as randomUUID5 } from "crypto";
450
537
 
451
538
  // ../daemon/src/core/secrets.ts
452
539
  import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
@@ -626,7 +713,16 @@ function translatePiEvent(e) {
626
713
  if (e.isError) {
627
714
  return [{ type: "tool_error", id: e.toolCallId, name: e.toolName, error: text }];
628
715
  }
629
- return [{ type: "tool_result", id: e.toolCallId, name: e.toolName, result: text }];
716
+ const images = extractToolResultImages(e.result);
717
+ return [
718
+ {
719
+ type: "tool_result",
720
+ id: e.toolCallId,
721
+ name: e.toolName,
722
+ result: text,
723
+ ...images.length > 0 ? { images } : {}
724
+ }
725
+ ];
630
726
  }
631
727
  default:
632
728
  return [];
@@ -657,6 +753,20 @@ function extractToolResultText(result) {
657
753
  }
658
754
  return out;
659
755
  }
756
+ function extractToolResultImages(result) {
757
+ const r = result;
758
+ if (!r?.content) return [];
759
+ const out = [];
760
+ for (const block of r.content) {
761
+ if (block.type === "image") {
762
+ const b = block;
763
+ if (typeof b.data === "string" && typeof b.mimeType === "string") {
764
+ out.push({ data: b.data, mimeType: b.mimeType });
765
+ }
766
+ }
767
+ }
768
+ return out;
769
+ }
660
770
  function stringifyContent(content) {
661
771
  if (typeof content === "string") return content;
662
772
  if (!Array.isArray(content)) return "";
@@ -671,7 +781,13 @@ function piMessagesToProviderView(messages) {
671
781
  for (const m of messages) {
672
782
  switch (m.role) {
673
783
  case "user": {
674
- out.push({ role: "user", content: stringifyContent(m.content) });
784
+ const content = m.content;
785
+ const images = extractToolResultImages({ content });
786
+ out.push({
787
+ role: "user",
788
+ content: stringifyContent(content),
789
+ ...images.length > 0 ? { images } : {}
790
+ });
675
791
  break;
676
792
  }
677
793
  case "assistant": {
@@ -685,11 +801,13 @@ function piMessagesToProviderView(messages) {
685
801
  }
686
802
  case "toolResult": {
687
803
  const tr = m;
804
+ const images = extractToolResultImages({ content: tr.content });
688
805
  out.push({
689
806
  role: "tool",
690
807
  content: stringifyContent(tr.content),
691
808
  toolCallId: tr.toolCallId,
692
- toolName: tr.toolName
809
+ toolName: tr.toolName,
810
+ ...images.length > 0 ? { images } : {}
693
811
  });
694
812
  break;
695
813
  }
@@ -701,8 +819,8 @@ function piMessagesToProviderView(messages) {
701
819
  }
702
820
 
703
821
  // ../daemon/src/runtime/pi/session.ts
704
- import { existsSync as existsSync11, mkdirSync as mkdirSync5, readdirSync as readdirSync7, statSync as statSync5 } from "fs";
705
- import { basename as basename2, join as join13 } from "path";
822
+ import { existsSync as existsSync11, mkdirSync as mkdirSync5, readdirSync as readdirSync7, statSync as statSync6 } from "fs";
823
+ import { basename as basename3, join as join13 } from "path";
706
824
  import {
707
825
  AuthStorage,
708
826
  createAgentSession,
@@ -882,8 +1000,195 @@ function bootstrapTool(agentDir) {
882
1000
  };
883
1001
  }
884
1002
 
1003
+ // ../daemon/src/runtime/tools/browser.ts
1004
+ var REF_DESC = 'Element ref from the latest browser_snapshot, e.g. "e5".';
1005
+ function browserTools(host, agentId) {
1006
+ const proxy = (name, action, description, properties, required = []) => ({
1007
+ def: {
1008
+ name,
1009
+ description,
1010
+ parameters: { type: "object", properties, required, additionalProperties: false }
1011
+ },
1012
+ invoke: (args) => host.invoke(agentId, action, args)
1013
+ });
1014
+ return [
1015
+ proxy(
1016
+ "browser_navigate",
1017
+ "navigate",
1018
+ "Open a URL in the browser and return an accessibility snapshot of the loaded page. Starts a persistent browser session that survives across turns (cookies/logins carry over).",
1019
+ { url: { type: "string", description: "Absolute URL to navigate to." } },
1020
+ ["url"]
1021
+ ),
1022
+ proxy(
1023
+ "browser_snapshot",
1024
+ "snapshot",
1025
+ 'Capture the current page as an accessibility tree (YAML) with [ref=eN] element references. This is the primary way to "see" the page \u2014 prefer it over screenshots. Use the refs with browser_click/type/etc.',
1026
+ {}
1027
+ ),
1028
+ proxy(
1029
+ "browser_click",
1030
+ "click",
1031
+ "Click an element identified by its snapshot ref. Returns a fresh snapshot.",
1032
+ { ref: { type: "string", description: REF_DESC } },
1033
+ ["ref"]
1034
+ ),
1035
+ proxy(
1036
+ "browser_type",
1037
+ "type",
1038
+ "Type text into an input/textarea identified by its ref. Set submit=true to press Enter afterwards. Returns a fresh snapshot.",
1039
+ {
1040
+ ref: { type: "string", description: REF_DESC },
1041
+ text: { type: "string", description: "Text to type into the field." },
1042
+ submit: { type: "boolean", description: "Press Enter after typing." }
1043
+ },
1044
+ ["ref", "text"]
1045
+ ),
1046
+ proxy(
1047
+ "browser_hover",
1048
+ "hover",
1049
+ "Hover the pointer over an element identified by its ref. Returns a fresh snapshot.",
1050
+ { ref: { type: "string", description: REF_DESC } },
1051
+ ["ref"]
1052
+ ),
1053
+ proxy(
1054
+ "browser_select",
1055
+ "select",
1056
+ "Select option(s) in a <select> element identified by its ref.",
1057
+ {
1058
+ ref: { type: "string", description: REF_DESC },
1059
+ values: {
1060
+ type: "array",
1061
+ items: { type: "string" },
1062
+ description: "Option values to select."
1063
+ }
1064
+ },
1065
+ ["ref", "values"]
1066
+ ),
1067
+ proxy(
1068
+ "browser_fill_form",
1069
+ "fill_form",
1070
+ "Fill multiple form fields in one call. Each field is { ref, value }.",
1071
+ {
1072
+ fields: {
1073
+ type: "array",
1074
+ description: "Fields to fill.",
1075
+ items: {
1076
+ type: "object",
1077
+ properties: {
1078
+ ref: { type: "string", description: REF_DESC },
1079
+ value: { type: "string" }
1080
+ },
1081
+ required: ["ref", "value"]
1082
+ }
1083
+ }
1084
+ },
1085
+ ["fields"]
1086
+ ),
1087
+ proxy(
1088
+ "browser_press_key",
1089
+ "press_key",
1090
+ 'Press a keyboard key (e.g. "Enter", "Escape", "ArrowDown", "Control+A").',
1091
+ { key: { type: "string", description: "Key or chord to press." } },
1092
+ ["key"]
1093
+ ),
1094
+ proxy(
1095
+ "browser_go_back",
1096
+ "go_back",
1097
+ "Navigate back to the previous page in history. Returns a fresh snapshot.",
1098
+ {}
1099
+ ),
1100
+ proxy(
1101
+ "browser_tabs",
1102
+ "tabs",
1103
+ 'Manage tabs. op="list" lists open tabs; "new" opens a tab (optional url); "select" focuses tab at index; "close" closes tab at index (defaults to active).',
1104
+ {
1105
+ op: { type: "string", enum: ["list", "new", "select", "close"] },
1106
+ index: { type: "number", description: "Tab index for select/close." },
1107
+ url: { type: "string", description: "URL to open for op=new." }
1108
+ },
1109
+ ["op"]
1110
+ ),
1111
+ proxy(
1112
+ "browser_take_screenshot",
1113
+ "take_screenshot",
1114
+ "Take a PNG screenshot of the current page (returned as an image). Use only for visual verification or canvas/pixel content the accessibility snapshot cannot represent \u2014 prefer browser_snapshot for interaction.",
1115
+ { full_page: { type: "boolean", description: "Capture the full scrollable page." } }
1116
+ ),
1117
+ proxy(
1118
+ "browser_console",
1119
+ "console",
1120
+ "Return recent browser console messages (logs, warnings, errors).",
1121
+ {}
1122
+ ),
1123
+ proxy(
1124
+ "browser_network",
1125
+ "network",
1126
+ "Return recent network requests issued by the page (method, status, URL).",
1127
+ {}
1128
+ )
1129
+ ];
1130
+ }
1131
+
1132
+ // ../daemon/src/runtime/tools/deliver-file.ts
1133
+ import { readFileSync as readFileSync8, statSync as statSync4 } from "fs";
1134
+ import { basename as basename2, extname, isAbsolute, resolve as resolve3 } from "path";
1135
+ var MAX_DELIVER_BYTES = 25 * 1024 * 1024;
1136
+ var MIME = {
1137
+ ".pdf": "application/pdf",
1138
+ ".txt": "text/plain",
1139
+ ".md": "text/markdown",
1140
+ ".csv": "text/csv",
1141
+ ".json": "application/json",
1142
+ ".html": "text/html",
1143
+ ".zip": "application/zip",
1144
+ ".png": "image/png",
1145
+ ".jpg": "image/jpeg",
1146
+ ".jpeg": "image/jpeg",
1147
+ ".gif": "image/gif",
1148
+ ".webp": "image/webp"
1149
+ };
1150
+ function deliverFileTool(cwd, sink) {
1151
+ return {
1152
+ def: {
1153
+ name: "deliver_file",
1154
+ description: "Send a file from your workspace to the user so they can download it (web), receive it as a document (Telegram), or save it (CLI). Use this to deliver reports, exports, or any artifact you produced. Max 25 MB.",
1155
+ parameters: {
1156
+ type: "object",
1157
+ properties: {
1158
+ path: {
1159
+ type: "string",
1160
+ description: "Path to the file, relative to your workspace or absolute."
1161
+ }
1162
+ },
1163
+ required: ["path"],
1164
+ additionalProperties: false
1165
+ }
1166
+ },
1167
+ async invoke(args) {
1168
+ const p = String(args.path ?? "");
1169
+ if (!p) throw new Error("deliver_file: path is required");
1170
+ const abs = isAbsolute(p) ? p : resolve3(cwd, p);
1171
+ let size;
1172
+ try {
1173
+ size = statSync4(abs).size;
1174
+ } catch {
1175
+ throw new Error(`deliver_file: no such file: ${p}`);
1176
+ }
1177
+ if (size > MAX_DELIVER_BYTES) {
1178
+ throw new Error(
1179
+ `deliver_file: "${basename2(abs)}" is too large (${(size / 1024 / 1024).toFixed(1)} MB > 25 MB)`
1180
+ );
1181
+ }
1182
+ const name = basename2(abs);
1183
+ const mimeType = MIME[extname(abs).toLowerCase()] ?? "application/octet-stream";
1184
+ sink({ name, mimeType, data: readFileSync8(abs).toString("base64") });
1185
+ return `Delivered "${name}" (${mimeType}) to the user.`;
1186
+ }
1187
+ };
1188
+ }
1189
+
885
1190
  // ../daemon/src/runtime/tools/home.ts
886
- import { readdirSync as readdirSync6, readFileSync as readFileSync8, statSync as statSync4, writeFileSync as writeFileSync5 } from "fs";
1191
+ import { readdirSync as readdirSync6, readFileSync as readFileSync9, statSync as statSync5, writeFileSync as writeFileSync5 } from "fs";
887
1192
  import { join as join12 } from "path";
888
1193
  var HOME_FILES_READABLE = [
889
1194
  "IDENTITY.md",
@@ -923,7 +1228,7 @@ function homeTools(agentDir) {
923
1228
  }
924
1229
  const path = join12(agentDir, file);
925
1230
  try {
926
- return readFileSync8(path, "utf8");
1231
+ return readFileSync9(path, "utf8");
927
1232
  } catch (err) {
928
1233
  const msg = err instanceof Error ? err.message : String(err);
929
1234
  throw new Error(`home_read: could not read ${file}: ${msg}`);
@@ -967,7 +1272,7 @@ function homeTools(agentDir) {
967
1272
  for (const file of HOME_FILES_READABLE) {
968
1273
  const path = join12(agentDir, file);
969
1274
  try {
970
- const s = statSync4(path);
1275
+ const s = statSync5(path);
971
1276
  entries.push(`${file} (${s.size}b)`);
972
1277
  } catch {
973
1278
  }
@@ -988,6 +1293,18 @@ function homeTools(agentDir) {
988
1293
  ];
989
1294
  }
990
1295
 
1296
+ // ../daemon/src/runtime/tools/mcp.ts
1297
+ function mcpProxyTools(host, tools) {
1298
+ return tools.map((t) => ({
1299
+ def: {
1300
+ name: t.toolName,
1301
+ description: t.description,
1302
+ parameters: t.inputSchema
1303
+ },
1304
+ invoke: (args) => host.invoke(t.serverId, t.rawName, args)
1305
+ }));
1306
+ }
1307
+
991
1308
  // ../daemon/src/runtime/tools/memory.ts
992
1309
  function memoryTools(memory) {
993
1310
  return [
@@ -1813,14 +2130,20 @@ function ourToolToPiTool(h) {
1813
2130
  description: h.def.description,
1814
2131
  parameters: Type2.Unsafe(h.def.parameters),
1815
2132
  async execute(_toolCallId, params) {
1816
- const text = await h.invoke(params);
2133
+ const out = await h.invoke(params);
1817
2134
  return {
1818
- content: [{ type: "text", text }],
2135
+ content: toPiContent(out),
1819
2136
  details: {}
1820
2137
  };
1821
2138
  }
1822
2139
  };
1823
2140
  }
2141
+ function toPiContent(out) {
2142
+ if (typeof out === "string") return [{ type: "text", text: out }];
2143
+ return out.map(
2144
+ (p) => p.type === "text" ? { type: "text", text: p.text } : { type: "image", data: p.data, mimeType: p.mimeType }
2145
+ );
2146
+ }
1824
2147
  function createBazilionCustomTools(opts) {
1825
2148
  const handlers = [
1826
2149
  ...memoryTools(opts.memory),
@@ -1834,13 +2157,35 @@ function createBazilionCustomTools(opts) {
1834
2157
  if (opts.userMdHost) {
1835
2158
  handlers.push(...userMdTools(opts.userMdHost, opts.agent.group.id));
1836
2159
  }
2160
+ if (opts.browserHost) {
2161
+ handlers.push(...browserTools(opts.browserHost, opts.agent.agent.id));
2162
+ }
2163
+ if (opts.mcpHost && opts.mcpTools && opts.mcpTools.length > 0) {
2164
+ handlers.push(...mcpProxyTools(opts.mcpHost, opts.mcpTools));
2165
+ }
2166
+ if (opts.fileSink) {
2167
+ handlers.push(deliverFileTool(opts.agent.group.path, opts.fileSink));
2168
+ }
1837
2169
  return handlers.map(ourToolToPiTool);
1838
2170
  }
1839
2171
 
1840
2172
  // ../daemon/src/runtime/pi/session.ts
1841
2173
  var BUILTIN_TOOL_NAMES = ["read", "bash", "edit", "write", "grep", "find", "ls"];
1842
2174
  async function createBazilionSession(opts) {
1843
- const { agent, paths, env, memory, enabledProviders, messagingHost, userMdHost, refreshApiKey } = opts;
2175
+ const {
2176
+ agent,
2177
+ paths,
2178
+ env,
2179
+ memory,
2180
+ enabledProviders,
2181
+ messagingHost,
2182
+ userMdHost,
2183
+ browserHost,
2184
+ mcpHost,
2185
+ mcpTools,
2186
+ fileSink,
2187
+ refreshApiKey
2188
+ } = opts;
1844
2189
  const { providerName, modelId } = splitModelString(agent.model);
1845
2190
  if (enabledProviders.size > 0 && !enabledProviders.has(providerName)) {
1846
2191
  throw new Error(`${providerName} provider is disabled \u2014 enable it on the /config page`);
@@ -1887,7 +2232,17 @@ async function createBazilionSession(opts) {
1887
2232
  const bazilionPrompt = buildSystemPrompt(agent);
1888
2233
  const resourceLoader = createBazilionResourceLoader(bazilionPrompt);
1889
2234
  await resourceLoader.reload();
1890
- const customTools = createBazilionCustomTools({ agent, memory, messagingHost, userMdHost, env });
2235
+ const customTools = createBazilionCustomTools({
2236
+ agent,
2237
+ memory,
2238
+ messagingHost,
2239
+ userMdHost,
2240
+ browserHost,
2241
+ mcpHost,
2242
+ mcpTools,
2243
+ fileSink,
2244
+ env
2245
+ });
1891
2246
  const allowedTools = [...BUILTIN_TOOL_NAMES, ...customTools.map((t) => t.name)];
1892
2247
  const { session } = await createAgentSession({
1893
2248
  cwd,
@@ -2023,7 +2378,7 @@ function findMostRecent(sessionDir) {
2023
2378
  if (!entry.endsWith(".jsonl")) continue;
2024
2379
  const path = join13(sessionDir, entry);
2025
2380
  try {
2026
- const s = statSync5(path);
2381
+ const s = statSync6(path);
2027
2382
  if (!newest || s.mtimeMs > newest.mtimeMs) newest = { path, mtimeMs: s.mtimeMs };
2028
2383
  } catch {
2029
2384
  }
@@ -2065,10 +2420,10 @@ function createIpcClient() {
2065
2420
  if (!process.send) {
2066
2421
  return Promise.reject(new Error("worker: no IPC channel \u2014 daemon must spawn with stdio:ipc"));
2067
2422
  }
2068
- const id = randomUUID5();
2423
+ const id = randomUUID6();
2069
2424
  const message = { type: "rpc", id, method, args };
2070
- return new Promise((resolve3, reject) => {
2071
- pending.set(id, { resolve: (v) => resolve3(v), reject });
2425
+ return new Promise((resolve4, reject) => {
2426
+ pending.set(id, { resolve: (v) => resolve4(v), reject });
2072
2427
  process.send?.(message, void 0, void 0, (err) => {
2073
2428
  if (err) {
2074
2429
  pending.delete(id);
@@ -2095,6 +2450,16 @@ function createIpcUserMdHost(call) {
2095
2450
  write: (groupId, content, ifMatch) => call("userMdWrite", { groupId, content, ifMatch })
2096
2451
  };
2097
2452
  }
2453
+ function createIpcBrowserHost(call) {
2454
+ return {
2455
+ invoke: (agentId, action, args) => call("browserInvoke", { agentId, action, args })
2456
+ };
2457
+ }
2458
+ function createIpcMcpHost(call) {
2459
+ return {
2460
+ invoke: (serverId, toolName, args) => call("mcpInvoke", { serverId, toolName, args })
2461
+ };
2462
+ }
2098
2463
  function isIpcReply(msg) {
2099
2464
  if (!msg || typeof msg !== "object") return false;
2100
2465
  const m = msg;
@@ -2117,13 +2482,15 @@ async function main() {
2117
2482
  };
2118
2483
  process.on("SIGTERM", onSignal);
2119
2484
  process.on("SIGINT", onSignal);
2120
- const { agent, message, enabledProviders, apiKey } = await readInput();
2485
+ const { agent, message, enabledProviders, apiKey, browserEnabled, mcpTools, images } = await readInput();
2121
2486
  const paths = resolvePaths();
2122
2487
  const memory = qmdBackend(join14(agent.group.path, "memory"));
2123
2488
  await memory.init();
2124
2489
  const ipcCall = createIpcClient();
2125
2490
  const messagingHost = createIpcMessagingHost(ipcCall);
2126
2491
  const userMdHost = createIpcUserMdHost(ipcCall);
2492
+ const browserHost = browserEnabled ? createIpcBrowserHost(ipcCall) : void 0;
2493
+ const mcpHost = mcpTools && mcpTools.length > 0 ? createIpcMcpHost(ipcCall) : void 0;
2127
2494
  const { session, dispose } = await createBazilionSession({
2128
2495
  agent,
2129
2496
  paths,
@@ -2132,7 +2499,12 @@ async function main() {
2132
2499
  enabledProviders: new Set(enabledProviders),
2133
2500
  messagingHost,
2134
2501
  userMdHost,
2135
- apiKey
2502
+ apiKey,
2503
+ browserHost,
2504
+ mcpHost,
2505
+ mcpTools,
2506
+ // deliver_file emits a `file` event straight onto our stdout frame stream.
2507
+ fileSink: (f) => emit({ kind: "event", event: { type: "file", ...f } })
2136
2508
  });
2137
2509
  abortSession = () => {
2138
2510
  void session.abort();
@@ -2143,7 +2515,12 @@ async function main() {
2143
2515
  }
2144
2516
  });
2145
2517
  try {
2146
- await session.prompt(message);
2518
+ const promptImages = (images ?? []).map((img) => ({
2519
+ type: "image",
2520
+ data: img.data,
2521
+ mimeType: img.mimeType
2522
+ }));
2523
+ await session.prompt(message, promptImages.length > 0 ? { images: promptImages } : void 0);
2147
2524
  await session.agent.waitForIdle();
2148
2525
  emit({
2149
2526
  kind: "done",