openmausbot 0.1.94 → 0.1.95

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 (145) hide show
  1. package/dist/assets/{DesktopViewer-bxM7NV3N.js → DesktopViewer-BwWpc4RK.js} +1 -1
  2. package/dist/assets/{abnfDiagram-O67JEVCF-BhMdJhRJ.js → abnfDiagram-O67JEVCF-jsQkIqTn.js} +1 -1
  3. package/dist/assets/{arc-B5itKDcx.js → arc-7kei4Hsu.js} +1 -1
  4. package/dist/assets/{architectureDiagram-NJMV4G6O-C5d7UY6U.js → architectureDiagram-NJMV4G6O-CRWDFDMq.js} +1 -1
  5. package/dist/assets/{blockDiagram-BEXU5L5S-BrZIhMmu.js → blockDiagram-BEXU5L5S-CTKW_IKs.js} +1 -1
  6. package/dist/assets/{c4Diagram-YGBWAQC7-7I0ArfbD.js → c4Diagram-YGBWAQC7-BBTW7-6M.js} +1 -1
  7. package/dist/assets/channel-DS2NbT5x.js +1 -0
  8. package/dist/assets/{chunk-2Q5K7J3B-BnEsyN36.js → chunk-2Q5K7J3B-M_Ox-MkD.js} +1 -1
  9. package/dist/assets/{chunk-3FUC2YCW-BXATQ52s.js → chunk-3FUC2YCW-D2YiN-Ud.js} +2 -2
  10. package/dist/assets/{chunk-5VM5RSS4-B0BpTkEx.js → chunk-5VM5RSS4-BmVYJ0f8.js} +1 -1
  11. package/dist/assets/{chunk-GTNCS2PH-BtLTz6FR.js → chunk-GTNCS2PH-BtiLVVnZ.js} +1 -1
  12. package/dist/assets/{chunk-GWA4HPMP-D6gcZOYt.js → chunk-GWA4HPMP-DFYSb6J6.js} +1 -1
  13. package/dist/assets/{chunk-JWPE2WC7-DCLrosvf.js → chunk-JWPE2WC7-mknKBzgV.js} +1 -1
  14. package/dist/assets/{chunk-NETBCI7D-B9JsknV9.js → chunk-NETBCI7D-DgSYGiOu.js} +1 -1
  15. package/dist/assets/{chunk-WEXAMYUT-CsoTxw7z.js → chunk-WEXAMYUT-C7ZvuguH.js} +1 -1
  16. package/dist/assets/{chunk-XXDRQBXY-0F_frB0c.js → chunk-XXDRQBXY-CPqxINpA.js} +1 -1
  17. package/dist/assets/{classDiagram-v2-NBCMYWYE-DkrpnflX.js → classDiagram-v2-NBCMYWYE-Cd6xfJaC.js} +1 -1
  18. package/dist/assets/{cose-bilkent-JH36ORCC-BNDwYBt9.js → cose-bilkent-JH36ORCC-BSXY5qRh.js} +1 -1
  19. package/dist/assets/{cynefin-OW5HDTMX-JUkNr1Mk.js → cynefin-OW5HDTMX-D_lhEMj1.js} +1 -1
  20. package/dist/assets/{cynefinDiagram-VND7K2PF-DKeh9N4M.js → cynefinDiagram-VND7K2PF-Da9FrFwk.js} +1 -1
  21. package/dist/assets/{dagre-6A5THRUB-ve-6T_wX.js → dagre-6A5THRUB-T77jMOXe.js} +1 -1
  22. package/dist/assets/{diagram-22UHCM2B-BuJ2d8rT.js → diagram-22UHCM2B-Dkj96X5E.js} +1 -1
  23. package/dist/assets/{diagram-3UASUU5V-BgOs_Ni-.js → diagram-3UASUU5V-CMi9zlD_.js} +1 -1
  24. package/dist/assets/{diagram-ATOU4E4O-DMK6VmcP.js → diagram-ATOU4E4O-CXK6YUfS.js} +1 -1
  25. package/dist/assets/{diagram-CDSNMT55-DksASlcY.js → diagram-CDSNMT55-BE7B1u9Q.js} +1 -1
  26. package/dist/assets/{diagram-MLGK6HIB-Bgn8I5k6.js → diagram-MLGK6HIB-CCMQphKr.js} +1 -1
  27. package/dist/assets/{diagram-MPIPVDR6-zK0jqO8N.js → diagram-MPIPVDR6-iHJ_-tGH.js} +1 -1
  28. package/dist/assets/{ebnfDiagram-ZINNZB2B-vKrbzTwr.js → ebnfDiagram-ZINNZB2B-DONsCWQ7.js} +1 -1
  29. package/dist/assets/{elk-276RUBZZ-Bzv2mPNf.js → elk-276RUBZZ-CU_WOaXk.js} +1 -1
  30. package/dist/assets/{erDiagram-OPXOYQCR-CvOyYuxu.js → erDiagram-OPXOYQCR-BeYwM04G.js} +1 -1
  31. package/dist/assets/{flowDiagram-KWPJA3E3-CtbnDbc0.js → flowDiagram-KWPJA3E3-Bdw9DqEl.js} +1 -1
  32. package/dist/assets/{ganttDiagram-FUAMR5RP-c4dMRYyG.js → ganttDiagram-FUAMR5RP-C9iwq3qm.js} +1 -1
  33. package/dist/assets/{gitGraphDiagram-X574FWY7-B34uaQho.js → gitGraphDiagram-X574FWY7-Dx-VWzUg.js} +1 -1
  34. package/dist/assets/{index-CPiqc_DN.js → index-KygHRE3E.js} +1 -1
  35. package/dist/assets/index-NF7XCHAy.css +1 -0
  36. package/dist/assets/index-X_5ydXIE.js +610 -0
  37. package/dist/assets/{infoDiagram-VRGFBTTK-o64ma4CI.js → infoDiagram-VRGFBTTK-BG7uaEd4.js} +1 -1
  38. package/dist/assets/{ishikawaDiagram-OU5B5YK6-DV4C9BAm.js → ishikawaDiagram-OU5B5YK6-D72WAXnM.js} +1 -1
  39. package/dist/assets/{journeyDiagram-ZHPQQLJL-Bf0AHk1H.js → journeyDiagram-ZHPQQLJL-DKH1gjYT.js} +1 -1
  40. package/dist/assets/{kanban-definition-PNTS6WVX-B1UMSKmE.js → kanban-definition-PNTS6WVX-3upm9JUT.js} +1 -1
  41. package/dist/assets/{linear-BpKhuyLm.js → linear-CcR4pc0-.js} +1 -1
  42. package/dist/assets/{mermaid.core-B_BKNCw5.js → mermaid.core-C6yU5FSS.js} +5 -5
  43. package/dist/assets/{mindmap-definition-NLK3R4M7-DeARruT1.js → mindmap-definition-NLK3R4M7-DOI2xEuv.js} +1 -1
  44. package/dist/assets/{pegDiagram-GJSIUBJH-BOQEk_1D.js → pegDiagram-GJSIUBJH-BVWJSgLd.js} +1 -1
  45. package/dist/assets/{pieDiagram-5QR66LMP-EIfoiTZo.js → pieDiagram-5QR66LMP-jtbYezhq.js} +1 -1
  46. package/dist/assets/{quadrantDiagram-O4NWA36T-GgZwT-fi.js → quadrantDiagram-O4NWA36T-g0u8mtPX.js} +1 -1
  47. package/dist/assets/{railroadDiagram-XR7U4H2S-D9rDQomj.js → railroadDiagram-XR7U4H2S-k8689Wny.js} +1 -1
  48. package/dist/assets/{requirementDiagram-PLB6GJNP-DAh1MIeo.js → requirementDiagram-PLB6GJNP-DsJ22PDt.js} +1 -1
  49. package/dist/assets/{sankeyDiagram-IPEJSGJF-CPr6Uz64.js → sankeyDiagram-IPEJSGJF-CneWBRgG.js} +1 -1
  50. package/dist/assets/{sequenceDiagram-PO4LG4MO-BU6VIdcm.js → sequenceDiagram-PO4LG4MO-DWsFCWap.js} +1 -1
  51. package/dist/assets/{sizeCapture-INFHLROL-qF2HO41r.js → sizeCapture-INFHLROL-Bvf0YE3S.js} +1 -1
  52. package/dist/assets/{stateDiagram-v2-GCMORJYK-DlJ8G4MD.js → stateDiagram-v2-GCMORJYK-Bxb3Iei8.js} +1 -1
  53. package/dist/assets/{swimlanes-2SLR337P-DzYE8I54.js → swimlanes-2SLR337P-DLkk7W4A.js} +1 -1
  54. package/dist/assets/swimlanesDiagram-TC7HE7FX-C6bZtBXk.js +8 -0
  55. package/dist/assets/{timeline-definition-EJHVYXUP-B7wzEQ0A.js → timeline-definition-EJHVYXUP-KR9bIHYC.js} +1 -1
  56. package/dist/assets/{usecaseDiagram-POWQR4AR-BvGgrxIP.js → usecaseDiagram-POWQR4AR-DfsOrj8t.js} +1 -1
  57. package/dist/assets/{vennDiagram-UO4OBE2U-CEECdgQg.js → vennDiagram-UO4OBE2U-DRSTzG3h.js} +1 -1
  58. package/dist/assets/{wardleyDiagram-VNRHLVJA-XQHA0P_P.js → wardleyDiagram-VNRHLVJA-DtEdhPgT.js} +1 -1
  59. package/dist/assets/{xychartDiagram-PMCCYNJV-B5mLcZI2.js → xychartDiagram-PMCCYNJV-B-XhTxjG.js} +1 -1
  60. package/dist/index.html +2 -2
  61. package/dist-server/cloud-home-start.js +2 -1
  62. package/dist-server/companion/src/routes.js +39 -0
  63. package/dist-server/container-mcp.js +2 -2
  64. package/dist-server/drivers/agents-proxy.js +58 -38
  65. package/dist-server/enterprise/server/index.js +2 -1
  66. package/dist-server/{browser-proxy.js → harness-mcp-proxy.js} +35 -19
  67. package/dist-server/index.js +3159 -2313
  68. package/dist-server/local-computer.js +2 -1
  69. package/dist-server/openmausbot.js +314 -1336
  70. package/dist-server/pair-cli.js +314 -1336
  71. package/dist-server/proxy-paths.js +2 -1
  72. package/dist-server/server/account-signin.js +0 -2
  73. package/dist-server/server/activity.js +201 -0
  74. package/dist-server/server/auto-approve.js +6 -0
  75. package/dist-server/server/automatic-recovery.js +1 -1
  76. package/dist-server/server/boat-create-idempotency.js +0 -7
  77. package/dist-server/server/boat-delete-journal.js +0 -4
  78. package/dist-server/server/bot-package.js +1 -19
  79. package/dist-server/server/browser-runtime.js +1 -2
  80. package/dist-server/server/chief-of-staff.js +2 -3
  81. package/dist-server/server/cloud-backend.js +0 -4
  82. package/dist-server/server/cloud-computer-tools.js +110 -0
  83. package/dist-server/server/cloud-home.js +0 -1
  84. package/dist-server/server/cloud-lending.js +0 -3
  85. package/dist-server/server/composio.js +31 -2
  86. package/dist-server/server/computer-control.js +0 -15
  87. package/dist-server/server/computer-observation.js +0 -74
  88. package/dist-server/server/config.js +29 -1
  89. package/dist-server/server/container-computer.js +7 -19
  90. package/dist-server/server/contracts.js +9 -0
  91. package/dist-server/server/control-client.js +4 -10
  92. package/dist-server/server/drivers/acp/core.js +7 -5
  93. package/dist-server/server/drivers/agents-call.js +42 -29
  94. package/dist-server/server/drivers/agents-catalog.js +24 -11
  95. package/dist-server/server/drivers/agents-proxy.js +8 -30
  96. package/dist-server/server/drivers/antigravity.js +1 -4
  97. package/dist-server/server/drivers/boatagent.js +23 -4
  98. package/dist-server/server/drivers/cerebras.js +3 -3
  99. package/dist-server/server/drivers/chat-mcp-tools.js +4 -7
  100. package/dist-server/server/drivers/claude.js +16 -14
  101. package/dist-server/server/drivers/openai-chat.js +3 -6
  102. package/dist-server/server/drivers/retry.js +8 -4
  103. package/dist-server/server/engine-install.js +1 -5
  104. package/dist-server/server/harness/registry.js +0 -1
  105. package/dist-server/server/{browser-proxy.js → harness-mcp-proxy.js} +40 -20
  106. package/dist-server/server/index.js +789 -256
  107. package/dist-server/server/lending-memory.js +12 -8
  108. package/dist-server/server/memory-entries.js +38 -8
  109. package/dist-server/server/memory-organize.js +1 -8
  110. package/dist-server/server/memory-topics.js +4 -4
  111. package/dist-server/server/memory-upkeep.js +13 -15
  112. package/dist-server/server/message-file.js +5 -9
  113. package/dist-server/server/outbound-counts.js +79 -0
  114. package/dist-server/server/outbound-requests.js +60 -0
  115. package/dist-server/server/package-import.js +1 -1
  116. package/dist-server/server/peer-delivery.js +1 -0
  117. package/dist-server/server/proxy-paths.js +2 -1
  118. package/dist-server/server/room-handoffs.js +5 -13
  119. package/dist-server/server/routine-requests.js +1 -1
  120. package/dist-server/server/routines.js +5 -1
  121. package/dist-server/server/setup-mode.js +0 -2
  122. package/dist-server/server/skill-learn.js +0 -1
  123. package/dist-server/server/skill-library.js +0 -15
  124. package/dist-server/server/store.js +9 -1
  125. package/dist-server/server/surface.js +65 -20
  126. package/dist-server/server/system-prompt.js +4 -4
  127. package/dist-server/server/team-memory.js +209 -0
  128. package/dist-server/server/thread-events.js +2 -2
  129. package/dist-server/server/vps-computer.js +4 -14
  130. package/dist-server/server/workspace.js +85 -47
  131. package/dist-server/shared/activity.js +3 -0
  132. package/dist-server/shared/bot-avatar.js +8 -0
  133. package/dist-server/shared/connector-scopes.js +78 -0
  134. package/dist-server/shared/outbound.js +128 -0
  135. package/dist-server/shared/tool-surface.js +3 -2
  136. package/dist-server/vps-container-mcp.js +2 -2
  137. package/enterprise/server/index.js +2 -1
  138. package/package.json +1 -1
  139. package/dist/assets/channel-CdJX6i8k.js +0 -1
  140. package/dist/assets/index-CPYrGsv1.js +0 -610
  141. package/dist/assets/index-CzT42GR8.css +0 -1
  142. package/dist/assets/swimlanesDiagram-TC7HE7FX-D-U4GtLQ.js +0 -8
  143. package/dist-server/server/drivers/chat-boat-tools.js +0 -90
  144. package/dist-server/server/drivers/grokagent.js +0 -4
  145. package/dist-server/server/openmaus-status-capsule.js +0 -329
@@ -7,7 +7,6 @@ import { stripControlPlaneEnv } from "../config.js";
7
7
  import { augmentedPath } from "../env-path.js";
8
8
  import { killCliTree, spawnCli } from "../procs.js";
9
9
  import { chatImage } from "./chat-images.js";
10
- import { ChatBoatClient } from "./chat-boat-tools.js";
11
10
  import { mcpStdioServer } from "../mcp-gate-config.js";
12
11
  import { allowsTool, canUseMcpServer, parseToolScope } from "../../shared/tool-scope.js";
13
12
  /** The transport cannot safely continue this turn. A dispatched operation may
@@ -238,7 +237,7 @@ const validatorOptions = {
238
237
  // or describe an open tuple. Ajv still enforces every constraint.
239
238
  strictRequired: false, strictTypes: false, strictTuples: false,
240
239
  };
241
- function compileSchema(schema) {
240
+ export function compileToolSchema(schema) {
242
241
  const dialect = schema.$schema;
243
242
  if (dialect !== undefined && dialect !== "http://json-schema.org/draft-07/schema#" && dialect !== "https://json-schema.org/draft/2020-12/schema") {
244
243
  throw new Error("MCP tool schema uses an unsupported dialect; use JSON Schema draft-07 or 2020-12");
@@ -291,8 +290,6 @@ export async function mountChatTools(integrations, signal, computerUse = false,
291
290
  const scope = parsed.scope;
292
291
  const servers = [];
293
292
  const eligible = (server) => scope === undefined || canUseMcpServer(scope, server);
294
- if (computerUse && integrations?.computer && eligible("computer"))
295
- servers.push(["computer", integrations.computer]);
296
293
  if (computerUse && integrations?.localComputer && eligible("computer"))
297
294
  servers.push(["computer", integrations.localComputer]);
298
295
  if (computerUse && integrations?.browser && eligible("browser"))
@@ -339,10 +336,10 @@ export async function mountChatTools(integrations, signal, computerUse = false,
339
336
  throw aborted();
340
337
  // Every mounted MCP server can return images when the caller enables
341
338
  // image delivery, including custom servers. Text stays bounded below.
342
- const client = "boxId" in descriptor ? new ChatBoatClient(descriptor) : new ChatMcpClient(descriptor, computerUse);
339
+ const client = new ChatMcpClient(descriptor, computerUse);
343
340
  clients.push(client);
344
341
  const include = (tool) => scope === undefined || (object(tool) && typeof tool.name === "string" && allowsTool(scope, { kind: "mcp", server: name, name: tool.name }));
345
- const tools = client instanceof ChatMcpClient ? await client.tools(signal, include) : (await client.tools()).filter(include);
342
+ const tools = await client.tools(signal, include);
346
343
  return { name, client, builtInBrowser: descriptor === integrations?.browser, tools };
347
344
  }));
348
345
  for (const mount of mounts) {
@@ -360,7 +357,7 @@ export async function mountChatTools(integrations, signal, computerUse = false,
360
357
  throw new Error("MCP tools require an object input schema");
361
358
  if (Buffer.byteLength(JSON.stringify(tool.inputSchema)) > SCHEMA_BYTES)
362
359
  throw new Error("MCP tool schema exceeds the 64KB limit");
363
- const schema = compileSchema(tool.inputSchema);
360
+ const schema = compileToolSchema(tool.inputSchema);
364
361
  const parameters = { ...tool.inputSchema };
365
362
  const constraints = {};
366
363
  if (computerUse) {
@@ -6,8 +6,9 @@
6
6
  //
7
7
  // Integrations become MCP servers on the CLI:
8
8
  // - Composio Sessions (connected apps → tools) over streamable HTTP
9
- // - the bot's cloud computer (boat.dev) via server/computer-proxy.ts
10
- // — screenshot/exec/open_url, the CUA-on-the-boat bridge
9
+ // - every computer (this Mac, a Local VM, a VPS, or a Boat cloud
10
+ // computer through server/harness-mcp-proxy.ts computer) as the one
11
+ // stdio `computer` server in turn.integrations.localComputer
11
12
  import { createHash, randomBytes, randomUUID } from "node:crypto";
12
13
  import { chmodSync, mkdirSync, mkdtempSync, readFileSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
13
14
  import { createServer as createNetServer } from "node:net";
@@ -339,7 +340,7 @@ export function claudeCliUpdate(version, cli) {
339
340
  const effects = [
340
341
  ...(missing.includes("--autocompact") ? ["no compaction window picked by OpenMausBot"] : []),
341
342
  ...(missing.includes("--setting-sources") ? ["bots still see this machine's own Claude Code setup"] : []),
342
- ...(missing.includes("--system-prompt-snapshot") ? ["coordinated resumed turns cannot refresh stale system prompts"] : []),
343
+ ...(missing.includes("--system-prompt-snapshot") ? ["resumed turns cannot refresh stale system prompts"] : []),
343
344
  ];
344
345
  return {
345
346
  title: "Update Claude Code for context controls",
@@ -1034,10 +1035,11 @@ export const ClaudeDriver = {
1034
1035
  // harness snapshots every instance whenever it describes them — app
1035
1036
  // load, the Engines page, and right after `claude update`, which is
1036
1037
  // exactly when the answer changes — so a turn normally finds it filled.
1037
- // Most flags before any snapshot assume a current CLI; autocompact
1038
- // requires confirmed help support. A coordinated turn checks first
1039
- // because the snapshot-refresh flag is newer than the
1040
- // other context controls and an unknown flag would reject that request.
1038
+ // A turn that finds it empty reads the version itself first: the
1039
+ // snapshot-refresh flag every turn passes is newer than the other context
1040
+ // controls, and an unknown flag would reject the turn. If that read
1041
+ // fails, most flags assume a current CLI; the snapshot flag needs a
1042
+ // confirmed version and autocompact a confirmed help listing.
1041
1043
  let cliVersion = null;
1042
1044
  let cliVersionChecked = false;
1043
1045
  // Whether `claude --help` lists --autocompact, read once per CLI version
@@ -1203,7 +1205,7 @@ export const ClaudeDriver = {
1203
1205
  args.push("--disallowedTools", config.disallowedTools.join(","));
1204
1206
  }
1205
1207
  const turnEnvironment = environment();
1206
- if ((turn.refreshSystemPrompt || turn.guestConfined || turn.toolScope !== undefined) && !cliVersionChecked) {
1208
+ if (!cliVersionChecked) {
1207
1209
  const version = await readCliVersion(turnEnvironment);
1208
1210
  if (version) {
1209
1211
  cliVersion = parseClaudeCliVersion(version);
@@ -1246,12 +1248,12 @@ export const ClaudeDriver = {
1246
1248
  if (compactWindow && cliHasAutocompact === true) {
1247
1249
  args.push("--autocompact", compactWindow);
1248
1250
  }
1249
- // An old pair conversation can still carry its first assignment in
1250
- // Claude's recorded system prompt. The current brief rides in the user
1251
- // turn, so refresh the recorded prompt on --resume too. Gated by the
1252
- // version floor like every other flag the CLI may predate: an unknown
1253
- // flag is a hard argument error, not a graceful degrade.
1254
- if (turn.refreshSystemPrompt && cliVersionChecked && claudeCliSupports(cliVersion, "--system-prompt-snapshot")) {
1251
+ // A resumed conversation can still carry an earlier assignment, place
1252
+ // or teammate list in Claude's recorded system prompt, so every turn
1253
+ // refreshes the recorded prompt, on --resume too. Gated by the version
1254
+ // floor like every other flag the CLI may predate: an unknown flag is a
1255
+ // hard argument error, not a graceful degrade.
1256
+ if (cliVersionChecked && claudeCliSupports(cliVersion, "--system-prompt-snapshot")) {
1255
1257
  args.push("--system-prompt-snapshot", "off");
1256
1258
  }
1257
1259
  const turnModel = config.managed ? turn.model : await resolveClaudeTurnModel(turn.model, turnEnvironment);
@@ -325,7 +325,7 @@ export function createOpenAIChatRuntime(options) {
325
325
  throw new Error(options.missingKeyError);
326
326
  if (active.has(turn.threadId))
327
327
  throw new Error("a turn is already running on this thread");
328
- if (options.computerUse && (turn.images?.length || turn.integrations?.computer || turn.integrations?.localComputer || turn.integrations?.browser))
328
+ if (options.computerUse && (turn.images?.length || turn.integrations?.localComputer || turn.integrations?.browser))
329
329
  assertImageTransport(options.apiUrl);
330
330
  const turnId = newId();
331
331
  const abort = new AbortController();
@@ -334,7 +334,7 @@ export function createOpenAIChatRuntime(options) {
334
334
  for (const message of messages)
335
335
  retainImages(message.content);
336
336
  const model = turn.model || options.models().default;
337
- const secrets = [options.apiKey, turn.integrations?.computer?.token, turn.integrations?.computer?.control?.token].filter((value) => Boolean(value));
337
+ const secrets = [options.apiKey].filter((value) => Boolean(value));
338
338
  for (const integration of Object.values(turn.integrations ?? {})) {
339
339
  const entries = object(integration);
340
340
  const specs = entries && "command" in entries ? [entries] : Object.values(entries ?? {}).map(object);
@@ -695,10 +695,7 @@ export function createOpenAIChatRuntime(options) {
695
695
  adapter: {
696
696
  provider: options.driverKind,
697
697
  capabilities: { ...(options.computerUse ? { computerMcp: options.tools !== false,
698
- // Same gate as cloudComputerMcp: with tools off the runtime cannot
699
- // mount the leased Boat descriptor either. The fleet invariant test
700
- // pins usesCloudComputer === (remoteAgent || cloudComputerMcp).
701
- usesCloudComputer: options.tools !== false, cloudComputerMcp: options.tools !== false, localComputerMcp: options.tools !== false,
698
+ localComputerMcp: options.tools !== false,
702
699
  browserMcp: options.tools !== false, nativeImageInput: true, images: true } : {}),
703
700
  sessionModelSwitch: "in-session", customMcp: options.tools !== false, agentsMcp: options.tools !== false, composioMcp: options.tools !== false,
704
701
  // The runtime owns the whole tool loop, so it can always take a
@@ -17,6 +17,10 @@ const TERMINAL_PATTERNS = [
17
17
  pattern: /\b(?:40[13]|unauthorized|forbidden|invalid api key|missing bearer|authentication required|not logged in|logged out)\b/i,
18
18
  reason: "auth",
19
19
  },
20
+ // A subscription's usage window is hours away, so its limit is terminal
21
+ // for this engine even when the provider phrases it as a rate limit;
22
+ // checked before the transient 429 pattern for that reason.
23
+ { pattern: /\busage limit\b|\bhit your (?:usage )?limit\b|\b(?:weekly|daily|monthly|subscription|usage) limit reached\b|\bout of credits\b/i, reason: "quota" },
20
24
  { pattern: /\b402\b|\bquota\b|\bbilling\b|\bsubscription\b/i, reason: "quota" },
21
25
  { pattern: /\bmodel not found\b|\bunknown model\b|\bdoes not exist for model\b|\bunsupported model\b/i, reason: "unknown_model" },
22
26
  { pattern: /\b400\b|\b422\b|\binvalid request\b|\bmalformed\b|\bunexpected status\b/i, reason: "invalid_request" },
@@ -50,14 +54,14 @@ export function classifyError(err) {
50
54
  const { exitCode: code } = err;
51
55
  if (code !== null && code < 0)
52
56
  return { transient: false, reason: "interrupted" };
53
- for (const { pattern, reason } of TRANSIENT_PATTERNS) {
54
- if (pattern.test(text))
55
- return { transient: true, reason };
56
- }
57
57
  for (const { pattern, reason } of TERMINAL_PATTERNS) {
58
58
  if (pattern.test(text))
59
59
  return { transient: false, reason };
60
60
  }
61
+ for (const { pattern, reason } of TRANSIENT_PATTERNS) {
62
+ if (pattern.test(text))
63
+ return { transient: true, reason };
64
+ }
61
65
  return { transient: false, reason: "terminal_exit" };
62
66
  }
63
67
  for (const { pattern, reason } of TERMINAL_PATTERNS) {
@@ -4,7 +4,7 @@
4
4
  // driver's own install descriptor and never from a request, npm runs with
5
5
  // a fixed argument list, and the binary is found on the engines' PATH
6
6
  // afterwards because that directory is registered ahead of everything else.
7
- import { existsSync, mkdirSync } from "node:fs";
7
+ import { mkdirSync } from "node:fs";
8
8
  import { join } from "node:path";
9
9
  import { stripVTControlCharacters } from "node:util";
10
10
  import { DATA_DIR, stripWorkspaceCredentialEnv } from "./config.js";
@@ -169,7 +169,3 @@ function runNpm(args, env, cwd, timeoutMs) {
169
169
  });
170
170
  });
171
171
  }
172
- /** True once something has been installed here, for status pages. */
173
- export function enginesInstalled(baseDir = DATA_DIR) {
174
- return existsSync(enginesBinDir(baseDir));
175
- }
@@ -228,7 +228,6 @@ export class ProviderRegistry {
228
228
  modelVariants: inst.adapter.capabilities.modelVariants === true,
229
229
  queueing: inst.adapter.capabilities.queueing === true,
230
230
  localComputerMcp: inst.adapter.capabilities.localComputerMcp === true,
231
- cloudComputerMcp: inst.adapter.capabilities.cloudComputerMcp === true,
232
231
  approvalReview: inst.reviewPermission !== undefined,
233
232
  },
234
233
  access: driver?.metadata.access ?? "subscription",
@@ -1,17 +1,29 @@
1
- // Per-turn MCP entry point. Only a scoped browser capability crosses into the
2
- // agent process; engine commands, session names, and credentials stay in OMB.
1
+ // Per-turn MCP entry point for the tools the harness serves itself: the
2
+ // built-in browser and the cloud computer. argv[2] names which one. Only a
3
+ // turn-scoped capability crosses into the agent process; engine commands,
4
+ // session names, and provider credentials (the Boat token) stay in OMB.
3
5
  import { pathToFileURL } from "node:url";
4
6
  const MAX_INPUT_BYTES = 1_048_576;
5
7
  const MAX_OUTPUT_BYTES = 16_777_216;
6
8
  const MAX_PENDING = 16;
9
+ const NAMES = { browser: "Browser", computer: "Cloud computer" };
10
+ /** What a model needs after a failed call, per tool family: the failure is
11
+ * about this one connection, and an uncertain action must be checked first. */
12
+ const RECOVERY = {
13
+ browser: " This failure does not establish that all browsers are unavailable. Inspect available connections with select_computer if that tool is present. Keep the requested computer and account; do not bypass a permission refusal or human takeover, and do not repeat an uncertain action without checking its result.",
14
+ computer: " Keep working on the assigned cloud computer only; do not bypass a person's control of it, and take a fresh screenshot before repeating an action whose result is uncertain.",
15
+ };
16
+ export function parseHarnessMcpKind(value) {
17
+ return value === "browser" || value === "computer" ? value : null;
18
+ }
7
19
  function failure(id, method, message, code = -32603) {
8
20
  return method === "tools/call"
9
21
  ? { jsonrpc: "2.0", id, result: { content: [{ type: "text", text: message }], isError: true } }
10
22
  : { jsonrpc: "2.0", id, error: { code, message } };
11
23
  }
12
- async function boundedJson(response) {
24
+ async function boundedJson(response, name) {
13
25
  if (!response.body)
14
- throw new Error("Browser server returned an empty response.");
26
+ throw new Error(`${name} server returned an empty response.`);
15
27
  const reader = response.body.getReader();
16
28
  const chunks = [];
17
29
  let size = 0;
@@ -22,7 +34,7 @@ async function boundedJson(response) {
22
34
  break;
23
35
  size += value.byteLength;
24
36
  if (size > MAX_OUTPUT_BYTES)
25
- throw new Error("Browser response exceeded the size limit.");
37
+ throw new Error(`${name} response exceeded the size limit.`);
26
38
  chunks.push(Buffer.from(value));
27
39
  }
28
40
  return JSON.parse(Buffer.concat(chunks).toString("utf8"));
@@ -31,7 +43,7 @@ async function boundedJson(response) {
31
43
  await reader.cancel().catch(() => { });
32
44
  }
33
45
  }
34
- export async function browserProxyRequest(frame, connection) {
46
+ export async function harnessMcpRequest(frame, connection) {
35
47
  if (!frame || typeof frame !== "object" || Array.isArray(frame))
36
48
  return failure(null, null, "Invalid request.", -32600);
37
49
  const message = frame;
@@ -40,10 +52,11 @@ export async function browserProxyRequest(frame, connection) {
40
52
  return failure(null, null, "Invalid request.", -32600);
41
53
  if (!Object.hasOwn(message, "id"))
42
54
  return undefined;
55
+ const name = NAMES[connection.kind];
43
56
  if (message.method === "initialize")
44
57
  return {
45
58
  jsonrpc: "2.0", id,
46
- result: { protocolVersion: "2024-11-05", capabilities: { tools: {} }, serverInfo: { name: "openmausbot-browser", version: "1" } },
59
+ result: { protocolVersion: "2024-11-05", capabilities: { tools: {} }, serverInfo: { name: `openmausbot-${connection.kind}`, version: "1" } },
47
60
  };
48
61
  if (message.method === "ping")
49
62
  return { jsonrpc: "2.0", id, result: {} };
@@ -52,30 +65,30 @@ export async function browserProxyRequest(frame, connection) {
52
65
  try {
53
66
  const url = new URL(connection.url);
54
67
  if (!connection.token || url.protocol !== "http:" || !["127.0.0.1", "localhost", "[::1]"].includes(url.hostname) || url.username || url.password || url.pathname !== "/" || url.search || url.hash) {
55
- throw new Error("Browser connection is not configured. Start a new bot turn from OpenMausBot.");
68
+ throw new Error(`${name} connection is not configured. Start a new bot turn from OpenMausBot.`);
56
69
  }
57
70
  const body = JSON.stringify({ method: message.method, params: message.params ?? {} });
58
71
  if (Buffer.byteLength(body) > MAX_INPUT_BYTES)
59
- throw new Error("Browser request exceeded the size limit.");
60
- const response = await fetch(new URL("/api/internal/browser/mcp", url), {
72
+ throw new Error(`${name} request exceeded the size limit.`);
73
+ const response = await fetch(new URL(`/api/internal/${connection.kind}/mcp`, url), {
61
74
  method: "POST", redirect: "error",
62
75
  headers: { "content-type": "application/json", authorization: `Bearer ${connection.token}` },
63
76
  body, signal: AbortSignal.timeout(130_000),
64
77
  });
65
- const payload = await boundedJson(response);
78
+ const payload = await boundedJson(response, name);
66
79
  if (!response.ok || !payload || typeof payload !== "object" || !Object.hasOwn(payload, "result")) {
67
- throw new Error(typeof payload?.error === "string" ? payload.error.slice(0, 2_000) : "Browser server is unavailable. Start a new bot turn or try again.");
80
+ throw new Error(typeof payload?.error === "string" ? payload.error.slice(0, 2_000) : `${name} server is unavailable. Start a new bot turn or try again.`);
68
81
  }
69
82
  return { jsonrpc: "2.0", id, result: payload.result };
70
83
  }
71
84
  catch (error) {
72
85
  const detail = error instanceof Error && !/fetch failed|abort|timeout/i.test(error.message)
73
- ? error.message : "Browser connection was interrupted. Do not repeat the action until its state is checked.";
74
- return failure(id, message.method, detail + " This failure does not establish that all browsers are unavailable. Inspect available connections with select_computer if that tool is present. Keep the requested computer and account; do not bypass a permission refusal or human takeover, and do not repeat an uncertain action without checking its result.");
86
+ ? error.message : `${name} connection was interrupted. Do not repeat the action until its state is checked.`;
87
+ return failure(id, message.method, detail + RECOVERY[connection.kind]);
75
88
  }
76
89
  }
77
- function run() {
78
- const connection = { url: process.env.OMB_HARNESS_URL ?? "", token: process.env.OMB_BROWSER_TOKEN ?? "" };
90
+ function run(kind) {
91
+ const connection = { url: process.env.OMB_HARNESS_URL ?? "", token: process.env.OMB_MCP_TOKEN ?? "", kind };
79
92
  let input = Buffer.alloc(0);
80
93
  let pending = 0;
81
94
  const output = (message) => {
@@ -114,11 +127,11 @@ function run() {
114
127
  if (pending >= MAX_PENDING) {
115
128
  const request = frame;
116
129
  if (request && Object.hasOwn(request, "id"))
117
- output(failure(request.id ?? null, request.method, "Too many pending browser requests."));
130
+ output(failure(request.id ?? null, request.method, `Too many pending ${kind} requests.`));
118
131
  continue;
119
132
  }
120
133
  pending++;
121
- void browserProxyRequest(frame, connection).then(output).finally(() => { pending--; });
134
+ void harnessMcpRequest(frame, connection).then(output).finally(() => { pending--; });
122
135
  }
123
136
  if (input.length > MAX_INPUT_BYTES) {
124
137
  process.stdin.destroy();
@@ -126,5 +139,12 @@ function run() {
126
139
  }
127
140
  });
128
141
  }
129
- if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href)
130
- run();
142
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
143
+ const kind = parseHarnessMcpKind(process.argv[2]);
144
+ if (!kind) {
145
+ process.stderr.write("usage: harness-mcp-proxy browser|computer\n");
146
+ process.exitCode = 2;
147
+ }
148
+ else
149
+ run(kind);
150
+ }