openmausbot 0.1.88 → 0.1.90

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 (147) hide show
  1. package/README.md +1 -1
  2. package/dist/assets/{abnfDiagram-O67JEVCF-qd9VD3GV.js → abnfDiagram-O67JEVCF-DUyXd0kL.js} +1 -1
  3. package/dist/assets/{arc-3RwkUjRe.js → arc-q73qM-At.js} +1 -1
  4. package/dist/assets/{architectureDiagram-NJMV4G6O-CFKSJS7a.js → architectureDiagram-NJMV4G6O-D33O3Wur.js} +1 -1
  5. package/dist/assets/{blockDiagram-BEXU5L5S-CG1rEfWD.js → blockDiagram-BEXU5L5S-DLsJeoXC.js} +1 -1
  6. package/dist/assets/{c4Diagram-YGBWAQC7-CBW9N664.js → c4Diagram-YGBWAQC7-DnaljZnu.js} +1 -1
  7. package/dist/assets/channel-BCuyHjFV.js +1 -0
  8. package/dist/assets/{chunk-2Q5K7J3B-2mRfTksB.js → chunk-2Q5K7J3B-Da0h2i79.js} +1 -1
  9. package/dist/assets/{chunk-3FUC2YCW-Cv1dpErj.js → chunk-3FUC2YCW-CfzHi54f.js} +2 -2
  10. package/dist/assets/{chunk-5VM5RSS4-B7IACJp-.js → chunk-5VM5RSS4-LjE4d9Jf.js} +1 -1
  11. package/dist/assets/{chunk-GTNCS2PH-An0hZtYa.js → chunk-GTNCS2PH-DY-UGmvc.js} +1 -1
  12. package/dist/assets/{chunk-GWA4HPMP-BcJB2nEm.js → chunk-GWA4HPMP-BqeGhyhP.js} +1 -1
  13. package/dist/assets/{chunk-JWPE2WC7-CV_W7TN7.js → chunk-JWPE2WC7-BYa9EBAg.js} +1 -1
  14. package/dist/assets/{chunk-NETBCI7D-Bcs_kwnA.js → chunk-NETBCI7D-vTrDtWyb.js} +1 -1
  15. package/dist/assets/{chunk-WEXAMYUT-lrx_DxFP.js → chunk-WEXAMYUT-ic5tXadM.js} +1 -1
  16. package/dist/assets/{chunk-XXDRQBXY-DabHlLGM.js → chunk-XXDRQBXY-DvRCY-c6.js} +1 -1
  17. package/dist/assets/{classDiagram-v2-NBCMYWYE-BnHj6P7d.js → classDiagram-v2-NBCMYWYE-DVbmgz_L.js} +1 -1
  18. package/dist/assets/{cose-bilkent-JH36ORCC-CfLp6UDi.js → cose-bilkent-JH36ORCC-vdUs2n57.js} +1 -1
  19. package/dist/assets/{cynefin-OW5HDTMX-Dm4dbVA1.js → cynefin-OW5HDTMX-BvLGlV3U.js} +1 -1
  20. package/dist/assets/{cynefinDiagram-VND7K2PF-B9mH-PID.js → cynefinDiagram-VND7K2PF-C4GcJQDt.js} +1 -1
  21. package/dist/assets/{dagre-6A5THRUB-DDc_NVQJ.js → dagre-6A5THRUB-BMSaHpt_.js} +1 -1
  22. package/dist/assets/{diagram-22UHCM2B-lwr8Ip8-.js → diagram-22UHCM2B-D5b9UBhC.js} +1 -1
  23. package/dist/assets/{diagram-3UASUU5V-BLLe6dmI.js → diagram-3UASUU5V-CIKLlB7Y.js} +1 -1
  24. package/dist/assets/{diagram-ATOU4E4O-6eUIq4lz.js → diagram-ATOU4E4O-BzXb-7k7.js} +1 -1
  25. package/dist/assets/{diagram-CDSNMT55-O-pRN7VQ.js → diagram-CDSNMT55-HsTvJ7uT.js} +1 -1
  26. package/dist/assets/{diagram-MLGK6HIB-BietvGAI.js → diagram-MLGK6HIB-Db9ySNbQ.js} +1 -1
  27. package/dist/assets/{diagram-MPIPVDR6-Bd2Cl62X.js → diagram-MPIPVDR6-D6BKbpqY.js} +1 -1
  28. package/dist/assets/{ebnfDiagram-ZINNZB2B-B6iDy69W.js → ebnfDiagram-ZINNZB2B-Bq067L0t.js} +1 -1
  29. package/dist/assets/{elk-276RUBZZ-DQVHfKe0.js → elk-276RUBZZ-DhaD7fNJ.js} +1 -1
  30. package/dist/assets/{erDiagram-OPXOYQCR-BWdVp2tQ.js → erDiagram-OPXOYQCR-fwIhfz36.js} +1 -1
  31. package/dist/assets/{flowDiagram-KWPJA3E3-WLE6rKB4.js → flowDiagram-KWPJA3E3-DygIyu0V.js} +1 -1
  32. package/dist/assets/{ganttDiagram-FUAMR5RP-BbjS-4gn.js → ganttDiagram-FUAMR5RP-DPYfSO96.js} +1 -1
  33. package/dist/assets/{gitGraphDiagram-X574FWY7-Dpy2QSGq.js → gitGraphDiagram-X574FWY7-DhkHeCle.js} +1 -1
  34. package/dist/assets/index-CRvo-DS2.css +1 -0
  35. package/dist/assets/index-CWYGlA63.js +588 -0
  36. package/dist/assets/{index-CIaHn5NS.js → index-G1PHKGyh.js} +1 -1
  37. package/dist/assets/{infoDiagram-VRGFBTTK-D_jKWlKe.js → infoDiagram-VRGFBTTK-DstBLxME.js} +1 -1
  38. package/dist/assets/{ishikawaDiagram-OU5B5YK6-DLJohSuK.js → ishikawaDiagram-OU5B5YK6-CrIsGgch.js} +1 -1
  39. package/dist/assets/{journeyDiagram-ZHPQQLJL-DPOFj0qN.js → journeyDiagram-ZHPQQLJL-CYypGDbB.js} +1 -1
  40. package/dist/assets/{kanban-definition-PNTS6WVX-BI7v-xpK.js → kanban-definition-PNTS6WVX-DGjPt8E_.js} +1 -1
  41. package/dist/assets/{linear-C2pWPSxO.js → linear-Dwc3Hrru.js} +1 -1
  42. package/dist/assets/{mermaid.core-CR-j5WQU.js → mermaid.core-Dc4OePBL.js} +5 -5
  43. package/dist/assets/{mindmap-definition-NLK3R4M7-lz9GvXRh.js → mindmap-definition-NLK3R4M7-DV-vepj-.js} +1 -1
  44. package/dist/assets/{pegDiagram-GJSIUBJH-Dl4_ia61.js → pegDiagram-GJSIUBJH-lx5Kj-n5.js} +1 -1
  45. package/dist/assets/{pieDiagram-5QR66LMP-DWuT3n88.js → pieDiagram-5QR66LMP-Bq7Vmm15.js} +1 -1
  46. package/dist/assets/{quadrantDiagram-O4NWA36T-DXWe9Jx7.js → quadrantDiagram-O4NWA36T-DeqeqOBj.js} +1 -1
  47. package/dist/assets/{railroadDiagram-XR7U4H2S-COY1IBVY.js → railroadDiagram-XR7U4H2S-CXwRQzSC.js} +1 -1
  48. package/dist/assets/{requirementDiagram-PLB6GJNP-DnyVuhsQ.js → requirementDiagram-PLB6GJNP-8tAeoB88.js} +1 -1
  49. package/dist/assets/{sankeyDiagram-IPEJSGJF-BSc4NdGK.js → sankeyDiagram-IPEJSGJF-BG5zjGWU.js} +1 -1
  50. package/dist/assets/{sequenceDiagram-PO4LG4MO-kqEU2EGq.js → sequenceDiagram-PO4LG4MO-DKF3iLcw.js} +1 -1
  51. package/dist/assets/{sizeCapture-INFHLROL-BvB4bkWD.js → sizeCapture-INFHLROL-B-y1qUgT.js} +1 -1
  52. package/dist/assets/{stateDiagram-v2-GCMORJYK-BIrqNv7y.js → stateDiagram-v2-GCMORJYK-MkzPgxAc.js} +1 -1
  53. package/dist/assets/{swimlanes-2SLR337P-CFnalQUE.js → swimlanes-2SLR337P-DnbaOEwH.js} +1 -1
  54. package/dist/assets/swimlanesDiagram-TC7HE7FX-BZQ07DGE.js +8 -0
  55. package/dist/assets/{timeline-definition-EJHVYXUP-CsSWJnSQ.js → timeline-definition-EJHVYXUP-Bb4xG_Bt.js} +1 -1
  56. package/dist/assets/{usecaseDiagram-POWQR4AR-DkAmJ1EE.js → usecaseDiagram-POWQR4AR-JgkR9pFf.js} +1 -1
  57. package/dist/assets/{vennDiagram-UO4OBE2U-C3vd9drL.js → vennDiagram-UO4OBE2U-CIPIrAYe.js} +1 -1
  58. package/dist/assets/{wardleyDiagram-VNRHLVJA-j26CM-pi.js → wardleyDiagram-VNRHLVJA-BjCLuUvJ.js} +1 -1
  59. package/dist/assets/{xychartDiagram-PMCCYNJV-BYu1Q_at.js → xychartDiagram-PMCCYNJV-DKgiaYxT.js} +1 -1
  60. package/dist/index.html +2 -2
  61. package/dist-server/cloud-home-start.js +14795 -0
  62. package/dist-server/container-mcp.js +60 -4
  63. package/dist-server/drivers/agents-proxy.js +20 -10
  64. package/dist-server/index.js +48924 -46153
  65. package/dist-server/openmausbot.js +544 -253
  66. package/dist-server/pair-cli.js +544 -253
  67. package/dist-server/permission-proxy.js +1 -1
  68. package/dist-server/server/admission.js +46 -0
  69. package/dist-server/server/automatic-recovery.js +15 -0
  70. package/dist-server/server/{box-create-idempotency.js → boat-create-idempotency.js} +32 -31
  71. package/dist-server/server/{box-delete-journal.js → boat-delete-journal.js} +26 -25
  72. package/dist-server/server/{box.js → boat.js} +330 -328
  73. package/dist-server/server/bot-profile.js +7 -1
  74. package/dist-server/server/channel-queue.js +58 -18
  75. package/dist-server/server/checkpoints.js +98 -52
  76. package/dist-server/server/claim-idle.js +33 -0
  77. package/dist-server/server/cli-api-setup.js +0 -1
  78. package/dist-server/server/cloud-backend.js +5 -5
  79. package/dist-server/server/cloud-home-start.js +103 -0
  80. package/dist-server/server/cloud-home.js +218 -0
  81. package/dist-server/server/cloud-overflow.js +167 -0
  82. package/dist-server/server/composio.js +5 -0
  83. package/dist-server/server/computer-observation.js +2 -2
  84. package/dist-server/server/config.js +124 -6
  85. package/dist-server/server/container-computer.js +23 -0
  86. package/dist-server/server/contracts.js +10 -0
  87. package/dist-server/server/control-client.js +2 -2
  88. package/dist-server/server/delegations.js +11 -3
  89. package/dist-server/server/drivers/acp/core.js +129 -21
  90. package/dist-server/server/drivers/acp/qwen.js +3 -0
  91. package/dist-server/server/drivers/agents-call.js +5 -1
  92. package/dist-server/server/drivers/agents-catalog.js +9 -3
  93. package/dist-server/server/drivers/{boxagent.js → boatagent.js} +14 -13
  94. package/dist-server/server/drivers/builtIn.js +2 -2
  95. package/dist-server/server/drivers/{chat-box-tools.js → chat-boat-tools.js} +3 -3
  96. package/dist-server/server/drivers/chat-mcp-tools.js +2 -2
  97. package/dist-server/server/drivers/claude.js +177 -13
  98. package/dist-server/server/drivers/native.js +1 -1
  99. package/dist-server/server/drivers/openai-chat.js +19 -4
  100. package/dist-server/server/drivers/pi.js +3 -3
  101. package/dist-server/server/harness-capabilities.js +18 -3
  102. package/dist-server/server/hosted-models.js +6 -1
  103. package/dist-server/server/index.js +1163 -530
  104. package/dist-server/server/local-vm-inventory.js +11 -1
  105. package/dist-server/server/local-vm-seat-pool.js +141 -0
  106. package/dist-server/server/managed-policy.js +4 -1
  107. package/dist-server/server/memory-capture.js +194 -0
  108. package/dist-server/server/memory-entries.js +76 -0
  109. package/dist-server/server/memory-organize.js +102 -0
  110. package/dist-server/server/memory-tidy.js +144 -0
  111. package/dist-server/server/memory-topics.js +169 -0
  112. package/dist-server/server/memory-upkeep.js +452 -0
  113. package/dist-server/server/message-db.js +49 -5
  114. package/dist-server/server/permission-proxy.js +3 -3
  115. package/dist-server/server/profile-learned.js +98 -0
  116. package/dist-server/server/profile-versions.js +45 -11
  117. package/dist-server/server/recall.js +172 -0
  118. package/dist-server/server/redact.js +1 -1
  119. package/dist-server/server/room-cwd.js +1 -1
  120. package/dist-server/server/room-handoffs.js +61 -6
  121. package/dist-server/server/routes/bot-memory.js +215 -0
  122. package/dist-server/server/routine-requests.js +27 -16
  123. package/dist-server/server/routines.js +10 -6
  124. package/dist-server/server/steer-queue.js +41 -10
  125. package/dist-server/server/store.js +25 -1
  126. package/dist-server/server/surface.js +1 -1
  127. package/dist-server/server/system-prompt.js +16 -5
  128. package/dist-server/server/team-computers.js +2 -2
  129. package/dist-server/server/tightening-requests.js +34 -4
  130. package/dist-server/server/tool-summary.js +2 -2
  131. package/dist-server/server/tts/elevenlabs.js +1 -1
  132. package/dist-server/server/turn-resources.js +87 -11
  133. package/dist-server/server/vps-computer.js +1 -1
  134. package/dist-server/server/webhooks.js +21 -3
  135. package/dist-server/server/workspace.js +57 -22
  136. package/dist-server/shared/ask-question.js +7 -5
  137. package/dist-server/shared/bot-avatar.js +19 -0
  138. package/dist-server/shared/credential-request.js +3 -3
  139. package/dist-server/shared/tool-surface.js +1 -1
  140. package/dist-server/src/lib/composer-attachments.js +26 -0
  141. package/dist-server/vps-container-mcp.js +60 -4
  142. package/dist-server/workspace-backup.worker.js +60 -4
  143. package/package.json +1 -1
  144. package/dist/assets/channel-CnRnO3tF.js +0 -1
  145. package/dist/assets/index-BIMdIbaw.css +0 -1
  146. package/dist/assets/index-CHnODCr2.js +0 -583
  147. package/dist/assets/swimlanesDiagram-TC7HE7FX-C4a8qNGj.js +0 -8
@@ -40,10 +40,10 @@ import { describeSpawnFailure, execCli, killCliTree, spawnCli } from "../../proc
40
40
  export function skipSubscriptionAuthForLocalInject(model) {
41
41
  return Boolean(decodeInjectId(model));
42
42
  }
43
- import { newEventId, newId } from "../../contracts.js";
43
+ import { newEventId, newId, TurnNotStartedError } from "../../contracts.js";
44
44
  import { augmentedPath } from "../../env-path.js";
45
45
  import { supportsApprovalMode } from "../../../shared/approval-mode.js";
46
- import { parseAskQuestions, parseChoices, questionAnswersByQuestion } from "../../../shared/ask-question.js";
46
+ import { MAX_QUESTION_TEXT, parseAskQuestions, parseChoices, questionAnswersByQuestion } from "../../../shared/ask-question.js";
47
47
  import { appendNative } from "../native.js";
48
48
  import { acpPermissionCommand, permissionLaunchCwd } from "../permission-command.js";
49
49
  import { commandSummary, toolDetailPreview } from "../../tool-summary.js";
@@ -51,6 +51,7 @@ import { extractMcpImages } from "../../mcp-tool-images.js";
51
51
  import { redactSecretsInText } from "../../redact.js";
52
52
  import { recoveryPromptFor } from "../../resume-recovery.js";
53
53
  import { sessionIdlePolicy } from "../session-idle.js";
54
+ import { classifyError } from "../retry.js";
54
55
  /** ACP vendors put the actionable cause in error.data while keeping the
55
56
  * JSON-RPC message generic. Only surface known text fields, never a response
56
57
  * body/config dump, and redact before bounding the displayed diagnostic. */
@@ -98,6 +99,20 @@ const promptIdleTimeoutMs = () => {
98
99
  const ms = Number(raw);
99
100
  return Number.isFinite(ms) && ms > 0 ? ms : 0;
100
101
  };
102
+ /** Keep a turn's set of running tool calls in step with the agent's
103
+ * `tool_call` / `tool_call_update` notifications: a call is running from
104
+ * the first update that is not terminal until one that is. A `tool_call`
105
+ * without a status is pending (ACP's default); a `tool_call_update` without
106
+ * one leaves the call as it was. */
107
+ function trackRunningTool(current, update, defaultStatus) {
108
+ if (typeof update.toolCallId !== "string" || !update.toolCallId)
109
+ return;
110
+ const status = update.status ?? defaultStatus;
111
+ if (status === "completed" || status === "failed")
112
+ current.runningTools.delete(update.toolCallId);
113
+ else if (status === "pending" || status === "in_progress")
114
+ current.runningTools.add(update.toolCallId);
115
+ }
101
116
  const CLIENT_FILE_MAX_BYTES = 8 * 1024 * 1024;
102
117
  function acpVariantOption(result) {
103
118
  const option = (Array.isArray(result?.configOptions) ? result.configOptions : []).find((entry) => entry?.type === "select" && typeof entry.id === "string"
@@ -260,7 +275,7 @@ export function createAcpDriver(support) {
260
275
  };
261
276
  const allowedCredentials = new Set(support.credentialEnv ?? []);
262
277
  // two lists, one rule: foreign PROVIDER keys must not flip a CLI's
263
- // billing off its own login, and WORKSPACE credentials (box token,
278
+ // billing off its own login, and WORKSPACE credentials (boat token,
264
279
  // voice key, …) are the harness's secrets — riding along in
265
280
  // `...process.env` is not a grant. A driver keeps only what its
266
281
  // credentialEnv allowlist names.
@@ -296,6 +311,23 @@ export function createAcpDriver(support) {
296
311
  // contract prompts the live session instead of paying the handshake
297
312
  // again. An idle session closes after SESSION_IDLE_MS of quiet.
298
313
  const sessions = new Map();
314
+ // Closing removes a session from the pool, not its native writer lease.
315
+ // Retain every closing Qwen child until its process tree is gone, even
316
+ // when Stop or a failed turn lets another turn start during cleanup.
317
+ const retiring = new Map();
318
+ const retireChild = async (threadId, child) => {
319
+ let children = retiring.get(threadId);
320
+ if (!children)
321
+ retiring.set(threadId, children = new Set());
322
+ children.add(child);
323
+ const stopped = await killCliTree(child);
324
+ if (stopped) {
325
+ children.delete(child);
326
+ if (!children.size && retiring.get(threadId) === children)
327
+ retiring.delete(threadId);
328
+ }
329
+ return stopped;
330
+ };
299
331
  const { idleMs: SESSION_IDLE_MS } = sessionIdlePolicy("ACP");
300
332
  const closeSession = (threadId, why) => {
301
333
  const session = sessions.get(threadId);
@@ -308,16 +340,20 @@ export function createAcpDriver(support) {
308
340
  // a new turn must never adopt a closing session
309
341
  sessions.delete(threadId);
310
342
  session.acp.close();
311
- // stdin EOF asks the agent to exit; EOF is not a guaranteed exit
312
- // signal for ACP agents, so insist after a grace period
343
+ // EOF is not a guaranteed exit signal. Qwen must be tracked before
344
+ // another turn can resume its history; other agents keep their grace.
313
345
  try {
314
346
  session.child.stdin.end();
315
347
  }
316
348
  catch { }
317
- const kill = setTimeout(() => {
318
- void killCliTree(session.child);
319
- }, 5_000);
320
- kill.unref?.();
349
+ if (support.restartOnMcpChange)
350
+ void retireChild(threadId, session.child);
351
+ else {
352
+ const kill = setTimeout(() => {
353
+ void killCliTree(session.child);
354
+ }, 5_000);
355
+ kill.unref?.();
356
+ }
321
357
  };
322
358
  const armIdle = (threadId) => {
323
359
  const session = sessions.get(threadId);
@@ -403,7 +439,7 @@ export function createAcpDriver(support) {
403
439
  }
404
440
  // The bot's computer, mounted exactly like the Claude driver does:
405
441
  // host and sandbox Cua connections expose Cua Driver's own MCP server.
406
- // (A cloud box is not mounted here at all: a cloud turn runs ON the box.)
442
+ // (A cloud boat is not mounted here at all: a cloud turn runs ON the boat.)
407
443
  if (turn.integrations?.localComputer) {
408
444
  const local = turn.integrations.localComputer;
409
445
  servers.push({
@@ -436,6 +472,7 @@ export function createAcpDriver(support) {
436
472
  if (!current || current.state.settled)
437
473
  return;
438
474
  current.state.settled = true;
475
+ current.acknowledge();
439
476
  if (current.interruptTimer)
440
477
  clearTimeout(current.interruptTimer);
441
478
  for (const finish of current.asks.values())
@@ -521,8 +558,9 @@ export function createAcpDriver(support) {
521
558
  if (idleTimer)
522
559
  clearTimeout(idleTimer);
523
560
  idleTimer = setTimeout(() => {
524
- // Waiting for a person is not an unresponsive agent.
525
- if (session.current?.asks.size) {
561
+ // Waiting for a person, or for a tool the agent is running,
562
+ // is not an unresponsive agent.
563
+ if (session.current?.asks.size || session.current?.runningTools.size) {
526
564
  armIdle();
527
565
  return;
528
566
  }
@@ -639,6 +677,7 @@ export function createAcpDriver(support) {
639
677
  // server→client permission request → canonical request.opened,
640
678
  // answered fail-closed for the running turn
641
679
  const handleServerRequest = (msg, current) => {
680
+ current.state.startupActivity = true;
642
681
  if (msg.method === "fs/read_text_file" || msg.method === "fs/write_text_file") {
643
682
  void handleClientFileRequest(msg);
644
683
  return;
@@ -683,7 +722,8 @@ export function createAcpDriver(support) {
683
722
  }
684
723
  const tool = kind === "execute" ? "shell" : kind === "edit" ? "edit" : kind || "tool";
685
724
  const isShellCommand = !isQuestion && kind === "execute" && !/^mcp(?:__|[.:])/i.test(String(toolCall.title ?? ""));
686
- const summary = String(toolCall.rawInput?.command ?? toolCall.title ?? tool).slice(0, 200);
725
+ const rawSummary = String(toolCall.rawInput?.command ?? toolCall.title ?? tool);
726
+ const summary = rawSummary.slice(0, isQuestion ? MAX_QUESTION_TEXT : 200);
687
727
  // One structured question beside the flat choices: the richer card
688
728
  // renders from it while older clients keep answering through
689
729
  // `choices`. Built once here so the emit and the answer path can
@@ -778,6 +818,9 @@ export function createAcpDriver(support) {
778
818
  current.receiveModelVariants(p.update);
779
819
  return;
780
820
  }
821
+ if (current && ["agent_message_chunk", "agent_thought_chunk", "tool_call", "tool_call_update"].includes(p.update?.sessionUpdate)) {
822
+ current.state.startupActivity = true;
823
+ }
781
824
  if (!current || !current.state.promptSent)
782
825
  return;
783
826
  const u = p.update ?? {};
@@ -811,6 +854,7 @@ export function createAcpDriver(support) {
811
854
  }
812
855
  case "tool_call": {
813
856
  current.flushAssistantText();
857
+ trackRunningTool(current, u, "pending");
814
858
  emit({
815
859
  ...base(threadId, current.turnId),
816
860
  type: "item.started",
@@ -823,6 +867,7 @@ export function createAcpDriver(support) {
823
867
  break;
824
868
  }
825
869
  case "tool_call_update": {
870
+ trackRunningTool(current, u);
826
871
  if (u.status === "completed" || u.status === "failed") {
827
872
  current.state.producedItem = true;
828
873
  emit({
@@ -1010,8 +1055,8 @@ export function createAcpDriver(support) {
1010
1055
  // transformEnv-policy supports (opencode). mcpServers are session
1011
1056
  // establishment inputs — they ride session/new and session/load over
1012
1057
  // the wire — and the harness mints fresh integration bearer tokens
1013
- // every turn, so they must not respawn the process; a change instead
1014
- // re-establishes the session below (see sessionKey).
1058
+ // every turn. Most agents apply changes on live load; those that cache
1059
+ // the old MCP clients must resume on a fresh process (see sessionKey).
1015
1060
  // The env the spawned child actually receives is part of the
1016
1061
  // contract too, and arrives hashed as envFingerprint for the same
1017
1062
  // reason.
@@ -1030,7 +1075,8 @@ export function createAcpDriver(support) {
1030
1075
  }
1031
1076
  const pooled = sessions.get(threadId);
1032
1077
  let session;
1033
- if (pooled && !pooled.dead && !pooled.closing && pooled.contractKey === contractKey) {
1078
+ if (pooled && !pooled.dead && !pooled.closing && pooled.contractKey === contractKey
1079
+ && (!support.restartOnMcpChange || pooled.sessionKey === sessionKey)) {
1034
1080
  // adoption cancels the idle countdown — a running turn is not quiet
1035
1081
  if (pooled.idleTimer)
1036
1082
  clearTimeout(pooled.idleTimer);
@@ -1053,7 +1099,13 @@ export function createAcpDriver(support) {
1053
1099
  // respawns the child, every wire call must reach the live record, so
1054
1100
  // nothing captures the connection off it.
1055
1101
  const request = (method, params, timeoutMs, receive, idleMs, idleMessage) => session.acp.request(method, params, timeoutMs, receive, idleMs, idleMessage);
1056
- const state = { settled: false, promptSent: false, text: "", producedItem: false };
1102
+ const state = { settled: false, promptSent: false, text: "", producedItem: false, startupActivity: false, stopped: false };
1103
+ let acknowledge = () => { };
1104
+ let rejectStartup = (_error) => { };
1105
+ const startupAck = turn.startupRecovery ? new Promise((resolve, reject) => {
1106
+ acknowledge = () => resolve({ turnId });
1107
+ rejectStartup = reject;
1108
+ }) : null;
1057
1109
  const asks = new Map();
1058
1110
  const modelOf = (result) => {
1059
1111
  const option = (Array.isArray(result?.configOptions) ? result.configOptions : []).find((entry) => entry?.id === (support.selectModel?.configId ?? "model"));
@@ -1101,12 +1153,15 @@ export function createAcpDriver(support) {
1101
1153
  turnConfig,
1102
1154
  controlsHost,
1103
1155
  state,
1156
+ acknowledge,
1104
1157
  asks,
1158
+ runningTools: new Set(),
1105
1159
  interruptTimer: null,
1106
1160
  flushAssistantText,
1107
1161
  receiveModelVariants,
1108
1162
  };
1109
1163
  const interrupt = () => {
1164
+ state.stopped = true;
1110
1165
  if (session.sessionId) {
1111
1166
  session.acp.send({ jsonrpc: "2.0", method: "session/cancel", params: { sessionId: session.sessionId } });
1112
1167
  if (current.interruptTimer)
@@ -1123,7 +1178,7 @@ export function createAcpDriver(support) {
1123
1178
  closeSession(threadId, "stop");
1124
1179
  }
1125
1180
  };
1126
- active.set(threadId, { stop: () => closeSession(threadId, "stop"), interrupt, turnId, asks });
1181
+ active.set(threadId, { stop: () => { state.stopped = true; closeSession(threadId, "stop"); }, interrupt, turnId, asks });
1127
1182
  emit({ ...base(threadId, turnId), type: "turn.started" });
1128
1183
  session.current = current;
1129
1184
  (async () => {
@@ -1134,6 +1189,19 @@ export function createAcpDriver(support) {
1134
1189
  // Returns whether this runtime accepts image prompts, for the
1135
1190
  // prompt phase below.
1136
1191
  const handshake = async () => {
1192
+ // Also covers timeout/reset/re-establishment, not just rotated
1193
+ // MCP credentials. Stop remains wired while cleanup is pending.
1194
+ for (const child of retiring.get(threadId) ?? []) {
1195
+ const stopped = await retireChild(threadId, child);
1196
+ if (state.settled || session.closing)
1197
+ throw new Error("session closed");
1198
+ if (!stopped) {
1199
+ closeSession(threadId, "replace-failed");
1200
+ throw new Error(`${support.displayName} could not close its previous tool session. Try again.`);
1201
+ }
1202
+ }
1203
+ if (state.settled || session.closing)
1204
+ throw new Error("session closed");
1137
1205
  if (!session.initResult) {
1138
1206
  session.initResult = await request("initialize", {
1139
1207
  protocolVersion: 1,
@@ -1356,10 +1424,13 @@ export function createAcpDriver(support) {
1356
1424
  if (turn.variant !== undefined && requestedVariantOption().currentValue !== turn.variant) {
1357
1425
  throw new Error(`${support.displayName} changed variant before the prompt`);
1358
1426
  }
1427
+ if (state.settled || state.stopped || session.closing)
1428
+ throw new Error("turn stopped");
1359
1429
  state.promptSent = true;
1430
+ acknowledge();
1360
1431
  const promptIdleMs = promptIdleTimeoutMs();
1361
- const result = await request("session/prompt", { sessionId, prompt: [{ type: "text", text }, ...imageBlocks] }, undefined, undefined, promptIdleMs, `${DRIVER_KIND} went fully silent ${Math.round(promptIdleMs / 1000)} s after the message and the turn was stopped. ` +
1362
- "Raise OPENMAUS_ACP_PROMPT_IDLE_TIMEOUT_MS if this model legitimately takes longer to answer.");
1432
+ const result = await request("session/prompt", { sessionId, prompt: [{ type: "text", text }, ...imageBlocks] }, undefined, undefined, promptIdleMs, `${DRIVER_KIND} sent nothing for ${Math.round(promptIdleMs / 1000)} s with no tool running, so the turn was stopped as stuck. ` +
1433
+ "Send the message again to retry. On a self-hosted server, OPENMAUS_ACP_PROMPT_IDLE_TIMEOUT_MS sets this limit (0 turns it off).");
1363
1434
  if (pendingSplitReceipt) {
1364
1435
  // session/prompt resolving is the acceptance boundary: a
1365
1436
  // rejected prompt leaves the receipt unwritten, so the next
@@ -1405,6 +1476,35 @@ export function createAcpDriver(support) {
1405
1476
  // fallback for existing ACP supports.
1406
1477
  const needsAuth = code === "invalid_credentials" || code === "inactive_subscription"
1407
1478
  || message === support.loginNote;
1479
+ const failure = classifyError({ text: message });
1480
+ const transientStartup = failure.transient || (failure.reason === "unknown" && code === "upstream_outage");
1481
+ const denied = /\b(?:(?:permission|access) denied|(?:approval|permission) (?:required|denied|rejected)|requires? (?:approval|permission)|policy (?:restriction|violation)|(?:blocked|denied|restricted) by (?:the )?policy)\b/i.test(message);
1482
+ if (turn.startupRecovery && transientStartup && !needsAuth && !denied && (!code || code === "upstream_outage")
1483
+ && ![-32700, -32600, -32601, -32602].includes(e?.code)
1484
+ && !state.promptSent && !state.startupActivity && !state.producedItem && !state.text
1485
+ && !state.stopped && !session.closing && !asks.size && !current.runningTools.size) {
1486
+ // Quiesce before killing: the child's close event must not
1487
+ // publish a terminal completion before the harness can recover.
1488
+ state.settled = true;
1489
+ session.current = null;
1490
+ if (current.interruptTimer)
1491
+ clearTimeout(current.interruptTimer);
1492
+ closeSession(threadId, "startup-recovery");
1493
+ const stopped = await killCliTree(session.child).catch(() => false);
1494
+ if (current.interruptTimer)
1495
+ clearTimeout(current.interruptTimer);
1496
+ active.delete(threadId);
1497
+ if (stopped && !state.stopped) {
1498
+ rejectStartup(new TurnNotStartedError(turnId, message));
1499
+ return;
1500
+ }
1501
+ acknowledge();
1502
+ if (!state.stopped)
1503
+ emit({ ...base(threadId, turnId), type: "runtime.error", message });
1504
+ emit({ ...base(threadId, turnId), type: "turn.completed", ok: state.stopped,
1505
+ stopReason: state.stopped ? "cancelled" : "rpc_error", cost: null });
1506
+ return;
1507
+ }
1408
1508
  emit({
1409
1509
  ...base(threadId, turnId),
1410
1510
  type: "runtime.error",
@@ -1424,7 +1524,7 @@ export function createAcpDriver(support) {
1424
1524
  }
1425
1525
  }
1426
1526
  })();
1427
- return { turnId };
1527
+ return startupAck ?? { turnId };
1428
1528
  };
1429
1529
  const snapshot = async () => {
1430
1530
  const env = childEnv();
@@ -1481,6 +1581,10 @@ export function createAcpDriver(support) {
1481
1581
  // idle pooled sessions have no running turn — close them too
1482
1582
  for (const threadId of Array.from(sessions.keys()))
1483
1583
  closeSession(threadId, "stopAll");
1584
+ for (const [threadId, children] of retiring) {
1585
+ for (const child of children)
1586
+ void retireChild(threadId, child);
1587
+ }
1484
1588
  },
1485
1589
  onEvent: (listener) => {
1486
1590
  listeners.add(listener);
@@ -1492,6 +1596,10 @@ export function createAcpDriver(support) {
1492
1596
  stop();
1493
1597
  for (const threadId of Array.from(sessions.keys()))
1494
1598
  closeSession(threadId, "dispose");
1599
+ for (const [threadId, children] of retiring) {
1600
+ for (const child of children)
1601
+ void retireChild(threadId, child);
1602
+ }
1495
1603
  listeners.clear();
1496
1604
  },
1497
1605
  };
@@ -235,6 +235,9 @@ const support = {
235
235
  // A raw -m only changes the model within the saved provider. ACP switches
236
236
  // the complete route and confirms it before any prompt leaves OMB.
237
237
  spawnArgs: (config, turn) => ["--acp", ...qwenApprovalArgs(config.fullAuto, turn.approvalMode)],
238
+ // Qwen's live session/load returns its cached MCP clients, ignoring fresh
239
+ // credentials. Cold-load the same conversation when those inputs change.
240
+ restartOnMcpChange: true,
238
241
  selectModel: { configId: "model" },
239
242
  pickAuthMethod: () => null,
240
243
  authFailure: "continue",
@@ -278,7 +278,7 @@ function routineFields(args) {
278
278
  const runOn = destination(args.run_on ?? args.runOn);
279
279
  const timeoutMinutes = args.timeout_minutes ?? args.timeoutMinutes;
280
280
  if (runOn != null && runOn !== "maus" && runOn !== "cloud") {
281
- return { fields, error: 'Use run_on="maus" for the bot’s current model and configured computer (including VPS), or run_on="box" only for the Box-hosted agent. Legacy "cloud" also means Box.' };
281
+ return { fields, error: 'Use run_on="maus" for the bot’s current model and configured computer (including VPS), or run_on="box" only for the Boat-hosted agent. Legacy "cloud" also means Boat.' };
282
282
  }
283
283
  if (timeoutMinutes != null && (typeof timeoutMinutes !== "number" || !Number.isInteger(timeoutMinutes) || timeoutMinutes < 5 || timeoutMinutes > 240)) {
284
284
  return { fields, error: "timeout_minutes must be a whole number from 5 to 240. Use clear_timeout to remove a limit." };
@@ -982,11 +982,14 @@ export async function callTool(name, args, context) {
982
982
  if (!routineId || !action) {
983
983
  return { text: "propose_routine_action needs a routine_id and supported action.", isError: true };
984
984
  }
985
+ const forBotId = String(args.for_bot_id ?? "").trim();
985
986
  const body = {
986
987
  fromBotId: BOT_ID,
987
988
  fromThreadId: THREAD_ID,
988
989
  action,
989
990
  routineId,
991
+ // JSON.stringify drops the key entirely when no target was named
992
+ ...(forBotId ? { forBotId } : {}),
990
993
  };
991
994
  if (action === "update") {
992
995
  if (!jsonRecord(args.changes)) {
@@ -1098,6 +1101,7 @@ export async function callTool(name, args, context) {
1098
1101
  action: args.action,
1099
1102
  text: args.text,
1100
1103
  oldText: args.old_text,
1104
+ ...(typeof args.until === "string" && args.until.trim() ? { until: args.until.trim() } : {}),
1101
1105
  }),
1102
1106
  });
1103
1107
  if (r.error || r.ok !== true) {
@@ -128,8 +128,9 @@ const ROUTINE_FIELDS_SCHEMA = {
128
128
  schedule: ROUTINE_SCHEDULE_SCHEMA,
129
129
  run_on: {
130
130
  type: "string",
131
+ // "box" is Boat's historical run_on destination id (agents wire contract).
131
132
  enum: ["maus", "box"],
132
- description: "Default maus keeps the bot's selected model and configured computer, INCLUDING a self-hosted VPS. Omit this field for normal schedules. box explicitly switches the agent to the Box-hosted runner; it requires Box setup and is not the generic cloud/VPS option. Legacy cloud values from list_routines mean box, not VPS.",
133
+ description: "Default maus keeps the bot's selected model and configured computer, INCLUDING a self-hosted VPS. Omit this field for normal schedules. box explicitly switches the agent to the Boat-hosted runner; it requires Boat setup and is not the generic cloud/VPS option. Legacy cloud values from list_routines mean box, not VPS.",
133
134
  },
134
135
  timeout_minutes: {
135
136
  type: "integer",
@@ -293,7 +294,7 @@ const toolDefinitions = (externalRuntime) => [
293
294
  description: "Choose where this conversation does computer work. Call with no arguments to inspect actual available choices and the current place. For a task needing computer interaction, select the requested place, or auto to choose a suitable configured computer without asking the user to use menus. OpenMausBot reuses an existing computer first; with a configured provider it can start or provision one when needed. Do not provision for ordinary chat or just to inspect availability. A pending result means end this turn immediately: OpenMausBot updates the conversation selector and resumes the original request with that computer's real tools. Do not use the old tools after requesting a switch, repeat the task, or claim the action is done. This cannot change permissions, override Off, or switch a teammate/routine/channel.",
294
295
  inputSchema: { type: "object", additionalProperties: false, properties: {
295
296
  surface: { type: "string", enum: ["auto", "cloud", "vm", "local", "browser"],
296
- description: "auto = suitable configured computer, cloud = remote Box/VPS, vm = isolated Local VM, local = user's own desktop, browser = built-in browser. Omit to list." },
297
+ description: "auto = suitable configured computer, cloud = remote Boat/VPS, vm = isolated Local VM, local = user's own desktop, browser = built-in browser. Omit to list." },
297
298
  } },
298
299
  },
299
300
  {
@@ -523,6 +524,7 @@ const toolDefinitions = (externalRuntime) => [
523
524
  action: { type: "string", enum: ["append", "replace", "remove", "supersede"] },
524
525
  text: { type: "string", minLength: 1, description: "Non-blank new text for append, replace, or supersede: the fact itself, without a date or bullet. Omit for remove; use remove to delete a passage." },
525
526
  old_text: { type: "string", minLength: 1, description: "Exact unique existing passage for replace, supersede, or remove. Omit for append." },
527
+ until: { type: "string", pattern: "^\\d{4}-\\d{2}-\\d{2}$", description: "Optional, for append or supersede: YYYY-MM-DD, the last day a temporary fact holds (an exam this weekend, a trip next week). After that day the entry is hidden from your memory." },
526
528
  },
527
529
  required: ["action"],
528
530
  },
@@ -614,12 +616,16 @@ const toolDefinitions = (externalRuntime) => [
614
616
  },
615
617
  {
616
618
  name: "propose_routine_action",
617
- description: "Prepare a user-requested change to one of this bot's existing routines. Use list_routines first to get the routine id." + PROPOSAL_OUTCOME,
619
+ description: "Prepare a user-requested change to one of this bot's existing routines. Use list_routines first to get the routine id. If the user asks to change ANOTHER bot's routine and that bot is in your section, call list_bots and pass that bot's id as for_bot_id; the routine keeps its owner and every run keeps that bot's engine and permissions." + PROPOSAL_OUTCOME,
618
620
  inputSchema: {
619
621
  type: "object",
620
622
  additionalProperties: false,
621
623
  properties: {
622
624
  routine_id: { type: "string", minLength: 1, description: "Routine id from list_routines." },
625
+ for_bot_id: {
626
+ type: "string",
627
+ description: "Only when the requested change targets ANOTHER bot's routine and that bot is in your section: that bot's id from list_bots. Learn the routine id from that bot's own routines (it can run list_routines). Omit to change one of your own routines.",
628
+ },
623
629
  action: {
624
630
  type: "string",
625
631
  enum: ["update", "pause", "resume", "run_now", "delete"],
@@ -1,9 +1,10 @@
1
1
  import { newEventId, newId } from "../contracts.js";
2
2
  import { appendNative } from "./native.js";
3
3
  import { OMB_ASK_TOOL, answerWithoutPreamble, askQuestionSummary, capAnswerEcho, ombAskProtocolPrompt, parseOmbAskQuestions, questionChoices, stripOmbAskBlock, } from "../../shared/ask-question.js";
4
+ // "boxAgent" is Boat's historical driver kind (catalog and wire); keep it.
4
5
  const DRIVER_KIND = "boxAgent";
5
6
  // overridable so tests and a dev backend can be pointed at instead of the live provider
6
- const BOX_API = process.env.OMB_BOX_API || "https://ascii.dev/api/box/v1";
7
+ const BOAT_API = process.env.OMB_BOX_API || "https://ascii.dev/api/box/v1";
7
8
  const MODELS = {
8
9
  default: "claude-fable-5",
9
10
  options: [
@@ -15,14 +16,14 @@ const MODELS = {
15
16
  /** Any fence whose info string names the ask protocol, even when its body
16
17
  * does not parse: the marker for "the model tried to ask and failed". */
17
18
  const ASK_FENCE_ANY = /(^|\n)[ \t]{0,3}(`{3,}|~{3,})[ \t]*omb-ask\b/;
18
- /** The box runs every harness boat.dev ships (claude-code, codex, pi, opencode,
19
+ /** The boat runs every harness boat.dev ships (claude-code, codex, pi, opencode,
19
20
  * prime-agent, kimi). Which one a model id belongs to comes from the public
20
21
  * catalog, `GET /api/provider-models` at the API root: an object keyed by
21
22
  * harness, each with its `models`. A bot that arrives here from another engine
22
23
  * carries that engine's model id, so this is what lets it keep its model. */
23
24
  let catalog = null;
24
25
  async function loadCatalog() {
25
- const root = BOX_API.replace(/\/api\/box\/v1\/?$/, "");
26
+ const root = BOAT_API.replace(/\/api\/box\/v1\/?$/, "");
26
27
  catalog = await fetch(`${root}/api/provider-models`, { signal: AbortSignal.timeout(15_000) })
27
28
  .then((res) => (res.ok ? res.json() : null))
28
29
  .catch(() => null);
@@ -44,7 +45,7 @@ function decodeConfig(raw) {
44
45
  ...(typeof o.askTimeoutMs === "number" ? { askTimeoutMs: o.askTimeoutMs } : {}),
45
46
  };
46
47
  }
47
- export const BoxAgentDriver = {
48
+ export const BoatAgentDriver = {
48
49
  driverKind: DRIVER_KIND,
49
50
  metadata: { displayName: "Computer", supportsMultipleInstances: false },
50
51
  models: MODELS,
@@ -74,7 +75,7 @@ export const BoxAgentDriver = {
74
75
  createdAt: new Date().toISOString(),
75
76
  });
76
77
  const api = async (path, opts = {}) => {
77
- const res = await fetch(`${BOX_API}${path}`, {
78
+ const res = await fetch(`${BOAT_API}${path}`, {
78
79
  ...opts,
79
80
  headers: { authorization: `Bearer ${token}`, "content-type": "application/json", ...opts.headers },
80
81
  signal: opts.signal ?? AbortSignal.timeout(30_000),
@@ -120,7 +121,7 @@ export const BoxAgentDriver = {
120
121
  });
121
122
  appendNative(threadId, { dir: "out", source: "box.prompt", msg: { model, prompt: promptText, response: started } });
122
123
  // real shape (2026-08): {type:"prompt.queued", promptId, promptRun:{id,…},
123
- // id:<box id>} — never fall back to the bare id, it's the box's
124
+ // id:<boat id>} — never fall back to the bare id, it's the boat's
124
125
  return started?.promptRun?.id ?? started?.prompt?.id ?? started?.promptId ?? null;
125
126
  };
126
127
  const promptId = await postPrompt(prompt);
@@ -149,7 +150,7 @@ export const BoxAgentDriver = {
149
150
  const seen = new Set();
150
151
  let lastText = "";
151
152
  let pendingText = "";
152
- /** Why the box could not answer (login expired, model refused, …). */
153
+ /** Why the boat could not answer (login expired, model refused, …). */
153
154
  let problem = null;
154
155
  /** Emit unflushed deltas as assistant_text and reset pendingText.
155
156
  * The omb-ask block is protocol, not prose: it streamed raw (the
@@ -229,7 +230,7 @@ export const BoxAgentDriver = {
229
230
  return { ok: ok && !cancelled, stopReason: cancelled ? "interrupted" : stopReason };
230
231
  }
231
232
  // The turn is still open, so this prompt continues it: same turnId,
232
- // same accounting, and the answer reaches the box the way the ask
233
+ // same accounting, and the answer reaches the boat the way the ask
233
234
  // contract promised — Q:/A: blocks, capped for echo.
234
235
  const continuation = [
235
236
  capAnswerEcho(answerWithoutPreamble(reply)),
@@ -238,7 +239,7 @@ export const BoxAgentDriver = {
238
239
  ].join("\n");
239
240
  const nextPromptId = await postPrompt(continuation);
240
241
  // Stop can land while the continuation POST is in flight: the
241
- // interrupt inside cancel() then hits a box with no active run,
242
+ // interrupt inside cancel() then hits a boat with no active run,
242
243
  // and the continuation would start after it. Interrupt the run
243
244
  // that just started before ending the turn.
244
245
  if (cancelled) {
@@ -247,9 +248,9 @@ export const BoxAgentDriver = {
247
248
  }
248
249
  return await settleRun(nextPromptId);
249
250
  };
250
- /** Poll one box run to its settle. */
251
+ /** Poll one boat run to its settle. */
251
252
  const settleRun = async (runPromptId) => {
252
- const startedAt = Date.now(); // one 30-min ceiling per box run, ask chains included
253
+ const startedAt = Date.now(); // one 30-min ceiling per boat run, ask chains included
253
254
  lastText = "";
254
255
  pendingText = "";
255
256
  problem = null;
@@ -355,7 +356,7 @@ export const BoxAgentDriver = {
355
356
  };
356
357
  const snapshot = async () => {
357
358
  if (!token) {
358
- return { state: "unavailable", reason: 'no Box token — add {"box":{"token":"…"}} to ~/.openmausbot/config.json' };
359
+ return { state: "unavailable", reason: 'no Boat token — add {"box":{"token":"…"}} to ~/.openmausbot/config.json' };
359
360
  }
360
361
  try {
361
362
  await api("/me");
@@ -374,7 +375,7 @@ export const BoxAgentDriver = {
374
375
  snapshot,
375
376
  adapter: {
376
377
  provider: DRIVER_KIND,
377
- capabilities: { sessionModelSwitch: "in-session" },
378
+ capabilities: { sessionModelSwitch: "in-session", remoteAgent: true, usesCloudComputer: true },
378
379
  sendTurn,
379
380
  interruptTurn: async (threadId) => active.get(threadId)?.cancel(),
380
381
  respondToRequest: async (threadId, requestId, decision) => {
@@ -1,5 +1,5 @@
1
1
  import { AntigravityDriver } from "./antigravity.js";
2
- import { BoxAgentDriver } from "./boxagent.js";
2
+ import { BoatAgentDriver } from "./boatagent.js";
3
3
  import { ClaudeDriver } from "./claude.js";
4
4
  import { CodexDriver } from "./codex.js";
5
5
  import { GrokDriver } from "./grok.js";
@@ -32,7 +32,7 @@ export const BUILT_IN_DRIVERS = [
32
32
  ClaudeDriver,
33
33
  CodexDriver,
34
34
  AntigravityDriver,
35
- BoxAgentDriver,
35
+ BoatAgentDriver,
36
36
  MinimaxDriver,
37
37
  MistralDriver,
38
38
  ];
@@ -1,4 +1,4 @@
1
- import { isolatedRemoteCommand, runCommand, screenshotBox } from "../box.js";
1
+ import { isolatedRemoteCommand, runCommand, screenshotBoat } from "../boat.js";
2
2
  import { createControlClient, CONTROL_REFUSAL_PLAIN } from "../control-client.js";
3
3
  const coordinate = { type: "integer", minimum: 0, maximum: 32767 };
4
4
  const tool = (name, description, properties = {}, required = []) => ({ name, description, inputSchema: { type: "object", properties, required, additionalProperties: false } });
@@ -19,7 +19,7 @@ const buttons = { left: 1, middle: 2, right: 3 };
19
19
  const scrollButtons = { up: 4, down: 5, left: 6, right: 7 };
20
20
  /** Uses only the leased descriptor supplied by the harness. Schema validation
21
21
  * and user approval happen in the shared chat tool executor before this call. */
22
- export class ChatBoxClient {
22
+ export class ChatBoatClient {
23
23
  closed = false;
24
24
  control;
25
25
  descriptor;
@@ -45,7 +45,7 @@ export class ChatBoxClient {
45
45
  const config = { box: { token: this.descriptor.token } };
46
46
  const id = this.descriptor.boxId;
47
47
  if (name === "screenshot") {
48
- const shot = await screenshotBox(config, "", id, { signal, nativeSize: true });
48
+ const shot = await screenshotBoat(config, "", id, { signal, nativeSize: true });
49
49
  signal.throwIfAborted();
50
50
  return { content: [{ type: "image", mimeType: "image/jpeg", data: shot.png }] };
51
51
  }
@@ -7,7 +7,7 @@ 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 { ChatBoxClient } from "./chat-box-tools.js";
10
+ import { ChatBoatClient } from "./chat-boat-tools.js";
11
11
  /** The transport cannot safely continue this turn. A dispatched operation may
12
12
  * already have taken effect, so callers must not retry it through a new round. */
13
13
  export class ChatToolSessionError extends Error {
@@ -314,7 +314,7 @@ export async function mountChatTools(integrations, signal, computerUse = false)
314
314
  throw aborted();
315
315
  // Every mounted MCP server can return images when the caller enables
316
316
  // image delivery, including custom servers. Text stays bounded below.
317
- const client = "boxId" in descriptor ? new ChatBoxClient(descriptor) : new ChatMcpClient(descriptor, computerUse);
317
+ const client = "boxId" in descriptor ? new ChatBoatClient(descriptor) : new ChatMcpClient(descriptor, computerUse);
318
318
  clients.push(client);
319
319
  return { name, client, tools: await client.tools(signal) };
320
320
  }));