openmausbot 0.1.96 → 0.1.98

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 (185) hide show
  1. package/dist/assets/ActivityPanel-H6D3SD39.js +1 -0
  2. package/dist/assets/BotSettingsDialog-Dqz_AiBZ.js +7 -0
  3. package/dist/assets/CloudBackendPicker-iezpJFpF.js +1 -0
  4. package/dist/assets/ComputerPanel-DZPj-IWu.js +1 -0
  5. package/dist/assets/DesktopViewer-utONms1p.js +5 -0
  6. package/dist/assets/InspectorPanel-OVaBVrzC.js +5 -0
  7. package/dist/assets/KeyboardShortcutsModal-gk9mvHj_.js +1 -0
  8. package/dist/assets/LocalVmWorkspace-DLIvd2kC.js +1 -0
  9. package/dist/assets/MacLocalControl-bJv8yiPB.js +1 -0
  10. package/dist/assets/NewBotDialog-1qGOAdUM.js +2 -0
  11. package/dist/assets/RemoteAgentSettingsPanel-DqKr0HkO.js +1 -0
  12. package/dist/assets/RoutinesPage-BM2D4qGi.js +1 -0
  13. package/dist/assets/RoutinesSection-Dny9ra-c.js +1 -0
  14. package/dist/assets/SettingsModal-gC6XJJmt.js +11 -0
  15. package/dist/assets/TeamMapPage-B6fuu4r_.js +8 -0
  16. package/dist/assets/TriggersPanel-j0qTP2CO.js +1 -0
  17. package/dist/assets/VisibilitySection-DT0FqPvu.js +12 -0
  18. package/dist/assets/WebhooksPanel--YQyCjKO.js +3 -0
  19. package/dist/assets/{abnfDiagram-O67JEVCF-Dkbd2hcj.js → abnfDiagram-O67JEVCF-BsjcFX0-.js} +1 -1
  20. package/dist/assets/activity-BxOkayvD.js +1 -0
  21. package/dist/assets/{arc-Duj5Coy2.js → arc-CJNA94c6.js} +1 -1
  22. package/dist/assets/architectureDiagram-NJMV4G6O-dz2ud7lP.js +36 -0
  23. package/dist/assets/{blockDiagram-BEXU5L5S-CWDlTpXe.js → blockDiagram-BEXU5L5S-ChBEALoR.js} +1 -1
  24. package/dist/assets/brain-CFZ7PEXc.js +1 -0
  25. package/dist/assets/{c4Diagram-YGBWAQC7-B0t15PDc.js → c4Diagram-YGBWAQC7-KZNRKDQe.js} +1 -1
  26. package/dist/assets/channel-WhFyujhZ.js +1 -0
  27. package/dist/assets/{chunk-2Q5K7J3B-BJSCL-HV.js → chunk-2Q5K7J3B-BLLwmCET.js} +1 -1
  28. package/dist/assets/{chunk-3FUC2YCW-D3_ILccd.js → chunk-3FUC2YCW-CQxyreBZ.js} +2 -2
  29. package/dist/assets/{chunk-5VM5RSS4-D3dmwT_S.js → chunk-5VM5RSS4-D6sFMW0u.js} +1 -1
  30. package/dist/assets/{chunk-GTNCS2PH-Br73HFPD.js → chunk-GTNCS2PH-BaZ1YgLF.js} +1 -1
  31. package/dist/assets/{chunk-GWA4HPMP-uSsjxubC.js → chunk-GWA4HPMP-CcalphmC.js} +1 -1
  32. package/dist/assets/{chunk-JWPE2WC7-C40VWFyr.js → chunk-JWPE2WC7-CSk8R3ve.js} +1 -1
  33. package/dist/assets/{chunk-NETBCI7D-D6WNNYwU.js → chunk-NETBCI7D-CTmLjA57.js} +1 -1
  34. package/dist/assets/{chunk-WEXAMYUT-DOEhq64P.js → chunk-WEXAMYUT-CM9Z77Ne.js} +1 -1
  35. package/dist/assets/{chunk-XXDRQBXY-BqJEi6Pi.js → chunk-XXDRQBXY-B1fxpMS2.js} +1 -1
  36. package/dist/assets/{classDiagram-v2-NBCMYWYE-ByU_GnxT.js → classDiagram-v2-NBCMYWYE-DCCyoLpj.js} +1 -1
  37. package/dist/assets/computer-control-NLov352p.js +1 -0
  38. package/dist/assets/cose-bilkent-JH36ORCC-KyImBKvE.js +1 -0
  39. package/dist/assets/{cynefin-OW5HDTMX-CZWO7aYj.js → cynefin-OW5HDTMX-fsZc6MGa.js} +1 -1
  40. package/dist/assets/{cynefinDiagram-VND7K2PF-C70-w0la.js → cynefinDiagram-VND7K2PF-CGmHsShB.js} +1 -1
  41. package/dist/assets/{dagre-6A5THRUB-CnhjhExB.js → dagre-6A5THRUB-Dah1h-CH.js} +1 -1
  42. package/dist/assets/{diagram-22UHCM2B-bnjWCNb4.js → diagram-22UHCM2B-BIu71OyV.js} +1 -1
  43. package/dist/assets/{diagram-3UASUU5V-A4uTWoFV.js → diagram-3UASUU5V-gxhUB_Mu.js} +1 -1
  44. package/dist/assets/{diagram-ATOU4E4O-Zi6m3NUl.js → diagram-ATOU4E4O-CBhusUBn.js} +1 -1
  45. package/dist/assets/{diagram-CDSNMT55-DtCcmd5k.js → diagram-CDSNMT55-BdPrc86x.js} +1 -1
  46. package/dist/assets/{diagram-MLGK6HIB-DofuETS3.js → diagram-MLGK6HIB-Clp110R_.js} +1 -1
  47. package/dist/assets/{diagram-MPIPVDR6-Dw2T9PuU.js → diagram-MPIPVDR6-DJKfbugw.js} +1 -1
  48. package/dist/assets/{ebnfDiagram-ZINNZB2B-DbrwPiem.js → ebnfDiagram-ZINNZB2B-DnsltoBu.js} +1 -1
  49. package/dist/assets/{elk-276RUBZZ-BvCt1dCb.js → elk-276RUBZZ-jWdq6gqH.js} +1 -1
  50. package/dist/assets/{erDiagram-OPXOYQCR-Y-QKqPnU.js → erDiagram-OPXOYQCR-DArPVf8u.js} +1 -1
  51. package/dist/assets/{flowDiagram-KWPJA3E3-CDkL1B5w.js → flowDiagram-KWPJA3E3-D2HZ-2f7.js} +1 -1
  52. package/dist/assets/ganttDiagram-FUAMR5RP--o0KIxEo.js +292 -0
  53. package/dist/assets/{gitGraphDiagram-X574FWY7-nL9iFTCv.js → gitGraphDiagram-X574FWY7-CsoXr2D7.js} +1 -1
  54. package/dist/assets/image-plus-BbVjGHin.js +1 -0
  55. package/dist/assets/index-BrPW5HDO.css +1 -0
  56. package/dist/assets/index-C3NqfdVU.js +585 -0
  57. package/dist/assets/{index-DreDnjOu.js → index-CJTp9JEp.js} +1 -1
  58. package/dist/assets/{infoDiagram-VRGFBTTK-C7aHjzsf.js → infoDiagram-VRGFBTTK-g-6lNp7r.js} +1 -1
  59. package/dist/assets/{ishikawaDiagram-OU5B5YK6-KUZsNBTh.js → ishikawaDiagram-OU5B5YK6-B1d6qVTV.js} +1 -1
  60. package/dist/assets/{journeyDiagram-ZHPQQLJL-CGUuodFl.js → journeyDiagram-ZHPQQLJL-BTPuiU6T.js} +1 -1
  61. package/dist/assets/{kanban-definition-PNTS6WVX-Cy6r868Z.js → kanban-definition-PNTS6WVX-bDDuRJvP.js} +1 -1
  62. package/dist/assets/{linear-Bl669148.js → linear-BVVKbEMy.js} +1 -1
  63. package/dist/assets/{mermaid.core-86Xqz5Mr.js → mermaid.core-JJdoKyKB.js} +5 -5
  64. package/dist/assets/{mindmap-definition-NLK3R4M7-G4DzHLrd.js → mindmap-definition-NLK3R4M7-B4aE3T0q.js} +1 -1
  65. package/dist/assets/module-DCUsy2BQ.js +3 -0
  66. package/dist/assets/page-visible-BBo_Zm8g.js +1 -0
  67. package/dist/assets/{pegDiagram-GJSIUBJH-K4eruzY0.js → pegDiagram-GJSIUBJH-DFKQWpR7.js} +1 -1
  68. package/dist/assets/{pieDiagram-5QR66LMP-DYdR9PPq.js → pieDiagram-5QR66LMP-Iv7OKVJi.js} +1 -1
  69. package/dist/assets/{quadrantDiagram-O4NWA36T-BCAh0nEy.js → quadrantDiagram-O4NWA36T-U2YnfwBk.js} +1 -1
  70. package/dist/assets/{railroadDiagram-XR7U4H2S-p0dGRc4g.js → railroadDiagram-XR7U4H2S-CuPy6RqM.js} +1 -1
  71. package/dist/assets/remote-desktop-panel-BNEj7tUx.js +1 -0
  72. package/dist/assets/{requirementDiagram-PLB6GJNP-tLeBG0aL.js → requirementDiagram-PLB6GJNP-bMI5AK7T.js} +1 -1
  73. package/dist/assets/{sankeyDiagram-IPEJSGJF-QRuN16rV.js → sankeyDiagram-IPEJSGJF-BKcFE2WQ.js} +1 -1
  74. package/dist/assets/{sequenceDiagram-PO4LG4MO-ueT1nVoa.js → sequenceDiagram-PO4LG4MO-Bn9q88kl.js} +1 -1
  75. package/dist/assets/{sizeCapture-INFHLROL-wRS2zWg2.js → sizeCapture-INFHLROL-Cvmdq9HD.js} +1 -1
  76. package/dist/assets/{stateDiagram-v2-GCMORJYK-CYzA_hE1.js → stateDiagram-v2-GCMORJYK-JJQYw3eQ.js} +1 -1
  77. package/dist/assets/{swimlanes-2SLR337P-kDnv6LMc.js → swimlanes-2SLR337P-BkSCBEwp.js} +1 -1
  78. package/dist/assets/swimlanesDiagram-TC7HE7FX-CmTirbVT.js +8 -0
  79. package/dist/assets/table-file.worker-KkMx-SNx.js +10 -0
  80. package/dist/assets/{timeline-definition-EJHVYXUP-D1y2UbbH.js → timeline-definition-EJHVYXUP-DsaM4GHr.js} +1 -1
  81. package/dist/assets/{usecaseDiagram-POWQR4AR-IqB4v9HX.js → usecaseDiagram-POWQR4AR-3FOwLq7B.js} +1 -1
  82. package/dist/assets/user-DcZBnAuv.js +1 -0
  83. package/dist/assets/{vennDiagram-UO4OBE2U-BEi7KYVF.js → vennDiagram-UO4OBE2U-DyNjJsKt.js} +1 -1
  84. package/dist/assets/{wardleyDiagram-VNRHLVJA-Dkybo-Yf.js → wardleyDiagram-VNRHLVJA-Y4ZMcnBC.js} +1 -1
  85. package/dist/assets/{xychartDiagram-PMCCYNJV-BAPFJLpg.js → xychartDiagram-PMCCYNJV-BXZpg955.js} +1 -1
  86. package/dist/index.html +2 -2
  87. package/dist-server/cloud-home-start.js +20 -9
  88. package/dist-server/container-mcp.js +8 -0
  89. package/dist-server/desktop-entry.mjs +54 -0
  90. package/dist-server/drivers/agents-proxy.js +26 -9
  91. package/dist-server/index.js +3734 -2360
  92. package/dist-server/openmausbot.js +1664 -1553
  93. package/dist-server/pair-cli.js +1664 -1553
  94. package/dist-server/permission-proxy.js +2 -4
  95. package/dist-server/server/atomic.js +70 -2
  96. package/dist-server/server/auto-approve.js +1 -1
  97. package/dist-server/server/auto-vm-claims.js +13 -1
  98. package/dist-server/server/automatic-recovery.js +1 -2
  99. package/dist-server/server/boat.js +68 -84
  100. package/dist-server/server/bot-overview.js +1 -1
  101. package/dist-server/server/browser-engine.js +38 -1
  102. package/dist-server/server/browser-live.js +112 -25
  103. package/dist-server/server/browser-runtime.js +31 -1
  104. package/dist-server/server/checkpoints.js +275 -44
  105. package/dist-server/server/cli-setup.js +3 -3
  106. package/dist-server/server/cloud-computer-tools.js +11 -4
  107. package/dist-server/server/cloud-home-start.js +4 -4
  108. package/dist-server/server/cloud-home.js +3 -9
  109. package/dist-server/server/cloud-owner.js +1 -1
  110. package/dist-server/server/computer-engine-removal.js +70 -0
  111. package/dist-server/server/computer-selection.js +18 -0
  112. package/dist-server/server/config.js +29 -12
  113. package/dist-server/server/connector-card-text.js +15 -0
  114. package/dist-server/server/connector-scope-hint.js +83 -0
  115. package/dist-server/server/default-model-selection.js +23 -14
  116. package/dist-server/server/delegations.js +11 -5
  117. package/dist-server/server/delta-context.js +6 -1
  118. package/dist-server/server/drivers/acp/core.js +11 -1
  119. package/dist-server/server/drivers/acp/grok.js +47 -5
  120. package/dist-server/server/drivers/acp/opencode-go.js +16 -21
  121. package/dist-server/server/drivers/agents-call.js +1 -1
  122. package/dist-server/server/drivers/agents-catalog.js +27 -7
  123. package/dist-server/server/drivers/agents-proxy.js +1 -0
  124. package/dist-server/server/drivers/builtIn.js +0 -2
  125. package/dist-server/server/drivers/chat-mcp-tools.js +5 -3
  126. package/dist-server/server/drivers/claude-login-auth.js +1 -1
  127. package/dist-server/server/drivers/claude.js +84 -41
  128. package/dist-server/server/drivers/codex-device-auth.js +27 -319
  129. package/dist-server/server/drivers/codex.js +2 -2
  130. package/dist-server/server/drivers/device-auth.js +342 -0
  131. package/dist-server/server/drivers/openai-chat.js +110 -10
  132. package/dist-server/server/drivers/pi.js +184 -49
  133. package/dist-server/server/fleet-agent.js +18 -5
  134. package/dist-server/server/harness/http.js +5 -2
  135. package/dist-server/server/harness/registry.js +19 -0
  136. package/dist-server/server/harness-capabilities.js +7 -4
  137. package/dist-server/server/hosted-models.js +3 -1
  138. package/dist-server/server/index.js +823 -333
  139. package/dist-server/server/key-rejections.js +57 -0
  140. package/dist-server/server/live-call.js +11 -6
  141. package/dist-server/server/managed-policy.js +1 -1
  142. package/dist-server/server/message-file.js +7 -4
  143. package/dist-server/server/model-catalog/presets.js +8 -0
  144. package/dist-server/server/peer-roster.js +10 -2
  145. package/dist-server/server/permission-proxy.js +4 -0
  146. package/dist-server/server/provider-key-check.js +5 -1
  147. package/dist-server/server/request-auth.js +1 -1
  148. package/dist-server/server/room-turn-end.js +15 -0
  149. package/dist-server/server/room-turn-timeout.js +28 -0
  150. package/dist-server/server/routes/live.js +3 -3
  151. package/dist-server/server/routes/thread-models.js +36 -0
  152. package/dist-server/server/routine-requests.js +1 -1
  153. package/dist-server/server/routines.js +3 -3
  154. package/dist-server/server/screen-frame-gate.js +1 -3
  155. package/dist-server/server/skills.js +3 -2
  156. package/dist-server/server/store.js +273 -61
  157. package/dist-server/server/surface.js +29 -49
  158. package/dist-server/server/system-prompt.js +10 -10
  159. package/dist-server/server/team-backup.js +19 -4
  160. package/dist-server/server/thread-model.js +25 -0
  161. package/dist-server/server/turn-continuation.js +168 -0
  162. package/dist-server/server/webhook-ingress.js +8 -3
  163. package/dist-server/server/webhooks.js +40 -5
  164. package/dist-server/server/workspace.js +1 -1
  165. package/dist-server/shared/ask-question.js +0 -70
  166. package/dist-server/shared/cloud-computer.js +4 -6
  167. package/dist-server/shared/credential-request.js +1 -1
  168. package/dist-server/shared/device-sign-in.js +57 -0
  169. package/dist-server/shared/failed-turn.js +1 -4
  170. package/dist-server/shared/place-view.js +409 -0
  171. package/dist-server/shared/team-backup.js +2 -0
  172. package/dist-server/shared/thread-model.js +13 -0
  173. package/dist-server/shared/tool-scope-support.js +1 -2
  174. package/dist-server/vps-container-mcp.js +8 -0
  175. package/dist-server/workspace-backup.worker.js +41 -22
  176. package/package.json +1 -1
  177. package/dist/assets/DesktopViewer-DsnA23KM.js +0 -5
  178. package/dist/assets/architectureDiagram-NJMV4G6O-uYUsDawP.js +0 -36
  179. package/dist/assets/channel-BZxwC-Gw.js +0 -1
  180. package/dist/assets/cose-bilkent-JH36ORCC-D-Uglxqq.js +0 -1
  181. package/dist/assets/ganttDiagram-FUAMR5RP-CKV5D2F6.js +0 -292
  182. package/dist/assets/index-Du7o6Dsf.js +0 -610
  183. package/dist/assets/index-KxG4bWSr.css +0 -1
  184. package/dist/assets/swimlanesDiagram-TC7HE7FX-C7voMDle.js +0 -8
  185. package/dist-server/server/drivers/boatagent.js +0 -443
@@ -432,8 +432,6 @@ export function resetOpenCodeModelCache() {
432
432
  /** Compatibility export for older tests/imports while the product migrates
433
433
  * from the Go-only name. */
434
434
  export const resetOpenCodeGoModelCache = resetOpenCodeModelCache;
435
- /** The names live in config.ts, beside the keys the owner saves for OpenCode. */
436
- export { OPENCODE_PROVIDER_ENV };
437
435
  /** Whether OpenCode may read provider keys from the server's own
438
436
  * environment. Only when that environment is the person's own shell: not on a
439
437
  * Cloud home, a hosted team workspace or an organisation-managed desktop, and
@@ -453,23 +451,16 @@ let providerKeysAllowed = () => openCodeProviderKeysAllowed({
453
451
  export function setOpenCodeProviderKeyPolicy(allowed) {
454
452
  providerKeysAllowed = allowed;
455
453
  }
456
- let ownProviderKeys = () => ({});
457
- /** The keys the owner saved for OpenCode in Settings (config.ts
458
- * openCodeProviderKeys). The server sets this beside the policy above. */
459
- export function setOpenCodeOwnProviderKeys(keys) {
460
- ownProviderKeys = keys;
461
- }
462
- function withholdProviderKeysWhenManaged(env) {
454
+ /** Where OpenCode may not read the server's own environment, a provider key
455
+ * reaches it only through its instance environment: a key the owner saved
456
+ * in Settings (config.ts injectedEnvironment), which is theirs on any
457
+ * server. The same name riding along in the server's env stays out. */
458
+ function withholdProviderKeysWhenManaged(env, _config, _instanceId, instanceEnvironment) {
463
459
  if (providerKeysAllowed())
464
460
  return;
465
- const own = ownProviderKeys();
466
461
  for (const key of OPENCODE_PROVIDER_ENV) {
467
- // A key the owner saved for OpenCode in Settings is theirs to use on any
468
- // server. Only that exact value: the server's own key under the same
469
- // name still stays out.
470
- if (own[key] !== undefined && env[key] === own[key])
471
- continue;
472
- delete env[key];
462
+ if (!Object.hasOwn(instanceEnvironment, key))
463
+ delete env[key];
473
464
  }
474
465
  }
475
466
  function opencodeConfigDir(env) {
@@ -766,8 +757,10 @@ function allowOwnedDirectories(env, botId, cwd) {
766
757
  /** Account failures in plain words, with the fix, naming the provider that
767
758
  * refused: with provider keys and `opencode auth login` a model may be
768
759
  * OpenRouter's or Anthropic's, not Zen's. Each stays under the 160
769
- * characters a chat error row shows. */
770
- export function describeOpenCodeAccountError(code, model) {
760
+ * characters a chat error row shows. Zen and Go take the key saved in
761
+ * Settings. On a Cloud the owner has no terminal for `opencode auth login`:
762
+ * another provider's key is saved under Keys for other OpenCode providers. */
763
+ export function describeOpenCodeAccountError(code, model, where = { cloudHome: cloudHomeConfigured() }) {
771
764
  const provider = model && model.includes("/") ? model.slice(0, model.indexOf("/")) : "";
772
765
  const zen = provider === "opencode";
773
766
  const go = provider === "opencode-go";
@@ -776,8 +769,10 @@ export function describeOpenCodeAccountError(code, model) {
776
769
  switch (code) {
777
770
  case "invalid_credentials":
778
771
  return zen || go || !provider
779
- ? "OpenCode rejected its key, or has none for this model. Fix it in Settings → API keys or with `opencode auth login`."
780
- : `OpenCode's ${name} key for this model is missing or was rejected. Fix it with \`opencode auth login\`, or choose another model.`;
772
+ ? "OpenCode rejected its key, or has none for this model. Add or replace the OpenCode key in Settings → API keys."
773
+ : where.cloudHome
774
+ ? `OpenCode's ${name} key for this model is missing or was rejected. Save it under Keys for other OpenCode providers in Settings → API keys.`
775
+ : `OpenCode's ${name} key for this model is missing or was rejected. Fix it with \`opencode auth login\`, or choose another model.`;
781
776
  case "insufficient_funds":
782
777
  return zen || !provider
783
778
  ? "Your OpenCode Zen balance has run out. Add credit at opencode.ai, or choose one of Zen's free models for this bot."
@@ -812,7 +807,7 @@ const support = (loadCatalog) => ({
812
807
  models: NO_MODELS,
813
808
  defaultCli: "opencode",
814
809
  nativeSource: "opencode.acp",
815
- loginNote: "OpenCode has no usable models — run `opencode auth login` or connect a provider in the OpenCode app",
810
+ loginNote: "OpenCode has no usable models. Add an OpenCode key in Settings → API keys.",
816
811
  install: {
817
812
  command: {
818
813
  darwin: "npm install -g opencode-ai",
@@ -259,7 +259,7 @@ function routineFields(args) {
259
259
  const runOn = destination(args.run_on ?? args.runOn);
260
260
  const timeoutMinutes = args.timeout_minutes ?? args.timeoutMinutes;
261
261
  if (runOn != null && runOn !== "maus" && runOn !== "cloud") {
262
- 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 bot’s Boat cloud computer. Legacy "cloud" also means Boat.' };
262
+ 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 bot’s cloud computer. Legacy "cloud" also means box.' };
263
263
  }
264
264
  if (timeoutMinutes != null && (typeof timeoutMinutes !== "number" || !Number.isInteger(timeoutMinutes) || timeoutMinutes < 5 || timeoutMinutes > 240)) {
265
265
  return { fields, error: "timeout_minutes must be a whole number from 5 to 240. Use clear_timeout to remove a limit." };
@@ -26,6 +26,7 @@ export function catalogProfileFromEnv(env) {
26
26
  voiceNotes: env.OMB_VOICE_NOTES === "1",
27
27
  cloudHome: env.OMB_CLOUD_HOME === "1",
28
28
  memoryEnabled: env.OMB_MEMORY_ENABLED !== "0",
29
+ chief: env.OMB_CHIEF_OF_STAFF === "1",
29
30
  botId: env.OMB_BOT_ID ?? "",
30
31
  };
31
32
  }
@@ -132,7 +133,7 @@ const ROUTINE_FIELDS_SCHEMA = {
132
133
  type: "string",
133
134
  // "box" is Boat's historical run_on destination id (agents wire contract).
134
135
  enum: ["maus", "box"],
135
- description: "Default maus keeps the bot's selected model and configured computer, INCLUDING a self-hosted VPS. Omit this field for normal schedules. box runs on the bot's Boat cloud computer, same model; it requires Boat setup and is not the generic cloud/VPS option. Legacy cloud values from list_routines mean box, not VPS.",
136
+ description: "Default maus keeps the bot's selected model and configured computer, INCLUDING a self-hosted VPS. Omit this field for normal schedules. box runs on the bot's cloud computer, same model; it needs cloud computers set up and is not the generic cloud/VPS option. Legacy cloud values from list_routines mean box, not VPS.",
136
137
  },
137
138
  timeout_minutes: {
138
139
  type: "integer",
@@ -146,7 +147,7 @@ const ROUTINE_FIELDS_SCHEMA = {
146
147
  },
147
148
  continuity: {
148
149
  type: "boolean",
149
- description: "Opt in to using the latest completed run's bounded report as historical context. Defaults to false; set false in an update to start fresh again. Included in the applied result or pending confirmation.",
150
+ description: "Opt in to using the latest completed run's bounded report as historical context. Use it for recurring work that builds on last time, such as QA passes, monitoring or follow-ups. Defaults to false; set false in an update to start fresh again. Included in the applied result or pending confirmation.",
150
151
  },
151
152
  overlap: {
152
153
  type: "string",
@@ -154,6 +155,9 @@ const ROUTINE_FIELDS_SCHEMA = {
154
155
  description: "While this routine is still working, skip scheduled occurrences (default) or queue at most one run. Queue skips further occurrences until the pending run starts; it never builds an unlimited backlog. Manual and webhook requests are separate.",
155
156
  },
156
157
  };
158
+ // propose_profile's one sentence about for_bot_id, which a bot that is not a
159
+ // Chief is not shown (catalogTools), along with the parameter itself.
160
+ const CHIEF_PROFILE_TARGET = " A Chief of Staff may pass for_bot_id (from list_bots) for a requested change to another bot in its section.";
157
161
  const PROPOSAL_OUTCOME = " Read the result: granted Full Access may apply the change immediately. If applied, continue the requested work without another confirmation. Only a pending result requires ending the turn and waiting for the in-app decision. Never claim success from the permission mode alone; report failed or cancelled results honestly. This does not elevate another bot's execution permissions.";
158
162
  /** Every tool, in the order it is listed. Four peer tools are worded
159
163
  * differently for an external runtime, which may poll inside one process. */
@@ -303,7 +307,7 @@ const toolDefinitions = (externalRuntime) => [
303
307
  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.",
304
308
  inputSchema: { type: "object", additionalProperties: false, properties: {
305
309
  surface: { type: "string", enum: ["auto", "cloud", "vm", "local", "browser"],
306
- 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." },
310
+ description: "auto = suitable configured computer, cloud = the bot's cloud computer or self-hosted VPS, vm = isolated Local VM, local = user's own desktop, browser = built-in browser. Omit to list." },
307
311
  } },
308
312
  },
309
313
  {
@@ -652,7 +656,7 @@ const toolDefinitions = (externalRuntime) => [
652
656
  },
653
657
  {
654
658
  name: "propose_profile",
655
- description: "Submit user-requested changes to your own name, title, description, standing instructions (SOUL.md), working folder (cwd), or your alert and voice toggles (notifications, speakReplies). Keep SOUL.md short — who you are and the rules you never break; put step-by-step procedure into a skill instead. A Chief of Staff may pass for_bot_id (from list_bots) for a requested change to another bot in its section." + PROPOSAL_OUTCOME,
659
+ description: "Submit user-requested changes to your own name, title, description, standing instructions (SOUL.md), working folder (cwd), or your alert and voice toggles (notifications, speakReplies). Keep SOUL.md short — who you are and the rules you never break; put step-by-step procedure into a skill instead." + CHIEF_PROFILE_TARGET + PROPOSAL_OUTCOME,
656
660
  inputSchema: {
657
661
  type: "object",
658
662
  additionalProperties: false,
@@ -842,6 +846,14 @@ export const SHARED_COMPUTER_TOOL_NAMES = new Set(["list_shared_computers", "sha
842
846
  // tool whose every call would end in a setup error. The route behind it
843
847
  // refuses regardless; this keeps the catalog honest about what can work.
844
848
  const VOICE_TOOL_NAMES = new Set(["send_voice_note"]);
849
+ // And for a role: every route behind these refuses a bot that is not its
850
+ // section's Chief of Staff, as it does a for_bot_id naming another bot on
851
+ // propose_profile or propose_model. (A routine's for_bot_id is open to any
852
+ // bot that can reach that peer, so it stays.)
853
+ const CHIEF_ONLY_TOOL_NAMES = new Set([
854
+ "create_bot", "list_team_setup", "propose_team_setup", "propose_bot_deletion", "create_room", "manage_room", "retry_thread",
855
+ ]);
856
+ const CHIEF_TARGET_TOOL_NAMES = new Set(["propose_profile", "propose_model"]);
845
857
  // One teamwork path in room turns; keep all unrelated integrations available.
846
858
  // Ordinary direct chats use this same bounded coordinator. Goal-owned turns
847
859
  // retain their independent loop and cannot start a second coordinator.
@@ -854,7 +866,7 @@ const WATCHER_TOOL_NAMES = new Set(["create_options_card"]);
854
866
  const LOCAL_VM_TOOL_NAMES = new Set(["vm_exec"]);
855
867
  const CLOUD_HOME_SURFACE = {
856
868
  type: "string", enum: ["auto", "cloud", "browser"],
857
- description: "auto = suitable configured computer, cloud = remote Boat/VPS, browser = built-in browser. Omit to list. This server runs in the cloud: the user's own computer and a Local VM are not places here.",
869
+ description: "auto = suitable configured computer, cloud = the bot's cloud computer, browser = built-in browser. Omit to list. This server runs in the cloud: the user's own computer and a Local VM are not places here.",
858
870
  };
859
871
  /** The tools one turn is shown, exactly as tools/list serializes them. */
860
872
  export function availableTools(profile) {
@@ -878,10 +890,18 @@ function catalogTools(profile) {
878
890
  const VOICE_READY_TOOLS = profile.voiceNotes
879
891
  ? SHAREABLE_TOOLS
880
892
  : SHAREABLE_TOOLS.filter((tool) => !VOICE_TOOL_NAMES.has(tool.name));
893
+ const ROLE_TOOLS = profile.chief
894
+ ? VOICE_READY_TOOLS
895
+ : VOICE_READY_TOOLS.filter((tool) => !CHIEF_ONLY_TOOL_NAMES.has(tool.name)).map((tool) => {
896
+ if (!CHIEF_TARGET_TOOL_NAMES.has(tool.name))
897
+ return tool;
898
+ const properties = Object.fromEntries(Object.entries(tool.inputSchema.properties).filter(([key]) => key !== "for_bot_id"));
899
+ return { ...tool, description: tool.description.replace(CHIEF_PROFILE_TARGET, ""), inputSchema: { ...tool.inputSchema, properties } };
900
+ });
881
901
  return profile.externalRuntime
882
902
  ? BOT_SCOPED_TOOLS.filter(tool => EXTERNAL_TOOL_NAMES.has(tool.name))
883
903
  : profile.coordinating
884
- ? VOICE_READY_TOOLS.filter(tool => !ROOM_REPLACED_TOOLS.has(tool.name) || (tool.name === "start_thread" && profile.ownThreadCreation))
904
+ ? ROLE_TOOLS.filter(tool => !ROOM_REPLACED_TOOLS.has(tool.name) || (tool.name === "start_thread" && profile.ownThreadCreation))
885
905
  .map(tool => tool.name === "start_thread" ? {
886
906
  ...tool,
887
907
  description: "Open a separate job on yourself with its own history and run, without switching the person's selected conversation. Use only when the user requests independent jobs (for example one review per pull request). Give a short specific title and complete instructions; you can open at most five per turn. This is not a teammate handoff: use coordinate_bots for teammates and their automatic replies. Self-opened jobs cannot recursively open more jobs. If refused, do not retry; explain what remains.",
@@ -889,5 +909,5 @@ function catalogTools(profile) {
889
909
  bot_id: { type: "string", enum: [profile.botId], description: "Leave out, or use your own bot ID. For teammates use coordinate_bots." },
890
910
  } },
891
911
  } : tool)
892
- : VOICE_READY_TOOLS.filter(tool => !ROOM_ONLY_TOOLS.has(tool.name));
912
+ : ROLE_TOOLS.filter(tool => !ROOM_ONLY_TOOLS.has(tool.name));
893
913
  }
@@ -12,6 +12,7 @@
12
12
  // OMB_COMMS_TOKEN shared secret for the localhost-only internal endpoints
13
13
  // OMB_TURN_DEPTH this turn's comms depth (the harness refuses recursion)
14
14
  // OMB_EXTERNAL_RUNTIME "1" for a standing process: peer tools and polling only
15
+ // OMB_CHIEF_OF_STAFF "1" for a Chief of Staff, the only bot shown the Chief-only tools
15
16
  //
16
17
  // This file is the stdio front end only. What the tools are and which a turn
17
18
  // sees: agents-catalog.ts. What a call does: agents-call.ts. How the harness
@@ -1,5 +1,4 @@
1
1
  import { AntigravityDriver } from "./antigravity.js";
2
- import { BoatAgentDriver } from "./boatagent.js";
3
2
  import { ClaudeDriver } from "./claude.js";
4
3
  import { CodexDriver } from "./codex.js";
5
4
  import { GrokDriver } from "./grok.js";
@@ -33,7 +32,6 @@ export const BUILT_IN_DRIVERS = [
33
32
  ClaudeDriver,
34
33
  CodexDriver,
35
34
  AntigravityDriver,
36
- BoatAgentDriver,
37
35
  MinimaxDriver,
38
36
  MistralDriver,
39
37
  CerebrasDriver,
@@ -14,7 +14,9 @@ import { allowsTool, canUseMcpServer, parseToolScope } from "../../shared/tool-s
14
14
  export class ChatToolSessionError extends Error {
15
15
  }
16
16
  const STARTUP_MS = 8_000;
17
- const CALL_MS = 10 * 60_000;
17
+ /** Historic per-call ceiling for a bot's MCP tools; a turn can override it
18
+ * with SendTurnInput.mcpCallTimeoutMs (server config `mcp.callTimeoutMinutes`). */
19
+ const DEFAULT_CALL_MS = 10 * 60_000;
18
20
  const FRAME_BYTES = 2 * 1024 * 1024;
19
21
  const OUTPUT_BYTES = 50 * 1024;
20
22
  const TOOL_COUNT = 128;
@@ -283,7 +285,7 @@ function omitBlankDefaults(builtInBrowser, tool, args) {
283
285
  delete args[field];
284
286
  }
285
287
  }
286
- export async function mountChatTools(integrations, signal, computerUse = false, toolScope) {
288
+ export async function mountChatTools(integrations, signal, computerUse = false, toolScope, callTimeoutMs = DEFAULT_CALL_MS) {
287
289
  const parsed = parseToolScope(toolScope);
288
290
  if (!parsed.ok)
289
291
  throw new Error(parsed.error);
@@ -411,7 +413,7 @@ export async function mountChatTools(integrations, signal, computerUse = false,
411
413
  }
412
414
  const tool = registered.get(name);
413
415
  try {
414
- const result = await tool.client.call("tools/call", { name: tool.name, arguments: args }, AbortSignal.any([signal, callSignal]), CALL_MS);
416
+ const result = await tool.client.call("tools/call", { name: tool.name, arguments: args }, AbortSignal.any([signal, callSignal]), callTimeoutMs);
415
417
  if (signal.aborted || callSignal.aborted)
416
418
  throw aborted();
417
419
  if (!object(result) || !Array.isArray(result.content) || (result.isError !== undefined && typeof result.isError !== "boolean"))
@@ -3,7 +3,7 @@ import { homedir } from "node:os";
3
3
  import { isAbsolute, join, resolve } from "node:path";
4
4
  import { stripVTControlCharacters } from "node:util";
5
5
  import { killCliTree, spawnCli } from "../procs.js";
6
- import { canonicalPath } from "./codex-device-auth.js";
6
+ import { canonicalPath } from "./device-auth.js";
7
7
  const MAX_OUTPUT = 32_768;
8
8
  /** Where Anthropic's sign-in lives. Anything else in the CLI's output is not a link we show. */
9
9
  const SIGN_IN_HOSTS = ["claude.com", "claude.ai", "console.anthropic.com", "platform.claude.com"];
@@ -15,7 +15,7 @@ import { createServer as createNetServer } from "node:net";
15
15
  import { homedir, tmpdir } from "node:os";
16
16
  import { join, dirname, isAbsolute, normalize } from "node:path";
17
17
  import { DATA_DIR, stripWorkspaceCredentialEnv } from "../config.js";
18
- import { writeFileAtomic } from "../atomic.js";
18
+ import { writeFileAtomic, writeFileAtomicIfChanged } from "../atomic.js";
19
19
  import { augmentedPath } from "../env-path.js";
20
20
  import { brokerSocketPath, describeSpawnFailure, execCli, killCliTree, spawnCli } from "../procs.js";
21
21
  import { classifyResumeFailure, mayReplay, recoveryPromptFor } from "../resume-recovery.js";
@@ -599,6 +599,9 @@ export async function createPermissionBroker(opts) {
599
599
  let boundPath = opts.socketPaths[0] ?? "";
600
600
  const connectionHandler = (conn) => {
601
601
  conn.on("error", () => { });
602
+ // An ask carries the whole tool input (a Write's file content), so one
603
+ // line spans many reads. Decode as a stream so no character is split.
604
+ conn.setEncoding("utf8");
602
605
  let buf = "";
603
606
  conn.on("data", (chunk) => {
604
607
  buf += chunk;
@@ -932,8 +935,10 @@ function recordCostState(sessionId, state) {
932
935
  const ids = Object.keys(history);
933
936
  for (const id of ids.slice(0, Math.max(0, ids.length - COST_HISTORY_SESSIONS)))
934
937
  delete history[id];
938
+ // Written after every turn, so not fsynced: what a power cut could lose is
939
+ // the same thing a failed write already gives up (below).
935
940
  try {
936
- writeFileAtomic(COST_HISTORY_FILE, JSON.stringify(history), { mode: 0o600 });
941
+ writeFileAtomic(COST_HISTORY_FILE, JSON.stringify(history), { mode: 0o600, durable: false });
937
942
  }
938
943
  catch {
939
944
  // a lost state only means a later resume keeps its whole figure
@@ -1051,6 +1056,26 @@ export const ClaudeDriver = {
1051
1056
  const readCliVersion = (env) => new Promise((resolve) => {
1052
1057
  execCli(config.cli, ["--version"], { timeout: 8000, env }, (err, stdout) => resolve(err ? null : stdout.trim() || null));
1053
1058
  });
1059
+ // The --help read while it runs: snapshots that overlap (the server's
1060
+ // read at start and the app's first one) share it.
1061
+ let helpRead = null;
1062
+ const readCliHelp = (version, env) => {
1063
+ if (helpRead?.version === version)
1064
+ return helpRead.done;
1065
+ const read = {
1066
+ version,
1067
+ done: new Promise((resolve) => {
1068
+ execCli(config.cli, ["--help"], { timeout: 8000, env }, (err, stdout) => resolve(err ? null : stdout));
1069
+ }).then((help) => {
1070
+ cliHasAutocompact = help === null ? null : /^\s*--autocompact\b/m.test(help);
1071
+ cliHelpVersion = version;
1072
+ if (helpRead === read)
1073
+ helpRead = null;
1074
+ }),
1075
+ };
1076
+ helpRead = read;
1077
+ return read.done;
1078
+ };
1054
1079
  const listeners = new Set();
1055
1080
  // one active turn per thread; a second send while busy is a caller bug
1056
1081
  const active = new Map();
@@ -1145,8 +1170,20 @@ export const ClaudeDriver = {
1145
1170
  // logical turn's stop handle in `active` while it sets up, so Stop is
1146
1171
  // never a silent no-op between two CLI processes of the same turn.
1147
1172
  const relaunch = logicalTurnId !== undefined;
1148
- if (active.has(threadId) && !relaunch)
1149
- throw new Error("a turn is already running on this thread");
1173
+ if (!relaunch) {
1174
+ // Stop is acknowledged at once and the process tree reaped after it
1175
+ // (taskkill is asynchronous on Windows); the stopped turn leaves
1176
+ // `active` only when its close is handled. A Stop that lands while
1177
+ // a turn is still starting lets the harness send the next message
1178
+ // before then. That turn waits for the stopped process to be gone,
1179
+ // never overlapping its helpers, instead of being refused.
1180
+ const stopped = sessions.get(threadId);
1181
+ if (stopped?.turn?.stopRequested && active.get(threadId)?.turnId === stopped.turn.turnId &&
1182
+ await killCliTree(stopped.child))
1183
+ await stopped.closed;
1184
+ if (active.has(threadId))
1185
+ throw new Error("a turn is already running on this thread");
1186
+ }
1150
1187
  // A bot-level mode is authoritative for this turn. In particular, an
1151
1188
  // old provider instance may still be configured with
1152
1189
  // `bypassPermissions`; Ask/Auto must restore Claude's interactive
@@ -1399,14 +1436,18 @@ export const ClaudeDriver = {
1399
1436
  ? readClaudeAuthSettings(env, input.environment) : {};
1400
1437
  // Harness hooks (item 0.2): one helper command for the events the
1401
1438
  // harness observes. The helper reads its bearer from a per-thread file
1402
- // the harness rewrites every turn, so a long-lived CLI process never
1439
+ // the harness refreshes every turn, so a long-lived CLI process never
1403
1440
  // presents a stale token. Registered through the same private
1404
1441
  // --settings file as the auth override; both are 0600 and per launch.
1405
1442
  const hooks = turn.integrations?.hooks;
1406
1443
  const hookTokenPath = hooks ? hookTokenFile(threadId, botId) : null;
1407
1444
  if (hooks && hookTokenPath) {
1408
1445
  mkdirSync(dirname(hookTokenPath), { recursive: true, mode: 0o700 });
1409
- writeFileAtomic(hookTokenPath, hooks.token, { mode: 0o600 });
1446
+ // The bearer is usually the same as last turn, and it only lives in
1447
+ // this process's memory, so a restart invalidates the file anyway:
1448
+ // skip identical bytes and the fsync. A rotated bearer differs from
1449
+ // what is on disk, so it is always written before the CLI launches.
1450
+ writeFileAtomicIfChanged(hookTokenPath, hooks.token, { mode: 0o600, durable: false });
1410
1451
  env.OMB_HOOK_URL = hooks.url;
1411
1452
  env.OMB_HOOK_TOKEN_FILE = hookTokenPath;
1412
1453
  env.OMB_HOOK_NODE = process.execPath;
@@ -1459,7 +1500,7 @@ export const ClaudeDriver = {
1459
1500
  if (!turn.sessionReset && live && !live.turn && !live.closing && live.child.exitCode === null && live.argsKey === argsKey && (!sessionId || sessionId === live.sessionId)) {
1460
1501
  if (live.idleTimer)
1461
1502
  clearTimeout(live.idleTimer);
1462
- live.turn = { turnId, input: turn, retryAbort, settled: false, sawStreamDelta: false, pendingSteers: new Set(), deferred: null, continuationGrace: null, continuationSilence: null };
1503
+ live.turn = { turnId, input: turn, retryAbort, settled: false, sawStreamDelta: false, awaitingInit: true, pendingSteers: new Set(), deferred: null, continuationGrace: null, continuationSilence: null };
1463
1504
  active.set(threadId, { stop: () => {
1464
1505
  if (live.turn)
1465
1506
  live.turn.stopRequested = true;
@@ -1475,20 +1516,13 @@ export const ClaudeDriver = {
1475
1516
  : claudeUserMessage(withVolatileNote(turn.text, volatile), turn.images);
1476
1517
  live.volatile = volatile;
1477
1518
  const running = live.turn;
1478
- const written = await writeUser(live, threadId, message);
1479
- if (!written && !running?.stopRequested) {
1480
- active.delete(threadId);
1481
- live.turn = null;
1519
+ // A warm process can end between turns just as the next one is
1520
+ // written: this driver learns of an exit only when the event loop
1521
+ // gets to it (later still on Windows), so the check above can find it
1522
+ // live and the write then fail. Nothing was submitted; its close
1523
+ // resumes this same turn on a fresh process (see `awaitingInit`).
1524
+ if (!(await writeUser(live, threadId, message)) && !running?.stopRequested)
1482
1525
  closeSession(threadId, "stdin write failed");
1483
- retryState.delete(threadId);
1484
- if (mcpConfigPath) {
1485
- try {
1486
- rmSync(dirname(mcpConfigPath), { recursive: true, force: true });
1487
- }
1488
- catch { }
1489
- }
1490
- throw new Error("claude session stdin is not writable");
1491
- }
1492
1526
  // the MCP config was for the first spawn; nothing to clean here
1493
1527
  if (mcpConfigPath) {
1494
1528
  try {
@@ -1630,7 +1664,9 @@ export const ClaudeDriver = {
1630
1664
  cleanupUnownedLaunch();
1631
1665
  throw error;
1632
1666
  }
1667
+ let markClosed;
1633
1668
  const session = {
1669
+ closed: new Promise((resolve) => { markClosed = resolve; }),
1634
1670
  child,
1635
1671
  broker,
1636
1672
  mcpConfigPath,
@@ -1749,6 +1785,8 @@ export const ClaudeDriver = {
1749
1785
  break;
1750
1786
  }
1751
1787
  session.sawInit = true;
1788
+ if (session.turn)
1789
+ session.turn.awaitingInit = false;
1752
1790
  session.nativePermissionMode = typeof o.permissionMode === "string" ? o.permissionMode : null;
1753
1791
  if (typeof o.session_id === "string")
1754
1792
  session.sessionId = o.session_id;
@@ -2051,11 +2089,18 @@ export const ClaudeDriver = {
2051
2089
  const retry = retryState.get(threadId) ?? { attempt: 0, cancelled: false };
2052
2090
  const message = `claude exited ${code} before result${session.stderr ? `: ${session.stderr.trim().slice(-300)}` : ""}`;
2053
2091
  const verdict = classifyError({ exitCode: code, stderr: message });
2092
+ // A process kept warm from an earlier turn ended before it
2093
+ // announced this one: it never took the prompt. That is a session
2094
+ // ending between turns, not a failed turn, so the same turn
2095
+ // resumes the session on a fresh process at once — no retry row,
2096
+ // no retry budget spent. (A fresh process is never retained, so
2097
+ // this happens at most once per turn.)
2098
+ const endedBeforeTurn = session.turn.awaitingInit === true;
2054
2099
  if (!retry.cancelled &&
2055
- code !== 0 &&
2056
- verdict.transient &&
2057
- !session.turn.sawStreamDelta &&
2058
- retry.attempt < RETRY_MAX_ATTEMPTS - 1) {
2100
+ (endedBeforeTurn || (code !== 0 &&
2101
+ verdict.transient &&
2102
+ !session.turn.sawStreamDelta &&
2103
+ retry.attempt < RETRY_MAX_ATTEMPTS - 1))) {
2059
2104
  // the CLI is gone but the TURN continues: keep the thread busy,
2060
2105
  // emit no terminal event, and relaunch after the backoff. The
2061
2106
  // `active` entry STAYS — it is what makes an interrupt during
@@ -2077,15 +2122,18 @@ export const ClaudeDriver = {
2077
2122
  }
2078
2123
  sessions.delete(threadId);
2079
2124
  session.turn = null;
2080
- retry.attempt++;
2081
- const delayMs = computeBackoff(retry.attempt - 1);
2082
- emit({
2083
- ...base(threadId, turnId),
2084
- type: "turn.retrying",
2085
- attempt: retry.attempt,
2086
- delayMs,
2087
- reason: verdict.reason,
2088
- });
2125
+ let delayMs = 0;
2126
+ if (!endedBeforeTurn) {
2127
+ retry.attempt++;
2128
+ delayMs = computeBackoff(retry.attempt - 1);
2129
+ emit({
2130
+ ...base(threadId, turnId),
2131
+ type: "turn.retrying",
2132
+ attempt: retry.attempt,
2133
+ delayMs,
2134
+ reason: verdict.reason,
2135
+ });
2136
+ }
2089
2137
  void (async () => {
2090
2138
  const wait = interruptibleDelay(delayMs * retryScale, retryAbort.signal);
2091
2139
  await wait.promise;
@@ -2225,7 +2273,7 @@ export const ClaudeDriver = {
2225
2273
  };
2226
2274
  child.on("close", (code) => {
2227
2275
  session.finishClose = () => finalizeClose(code);
2228
- void session.finishClose();
2276
+ void session.finishClose().finally(markClosed);
2229
2277
  });
2230
2278
  const stop = () => {
2231
2279
  if (session.turn)
@@ -2284,13 +2332,8 @@ export const ClaudeDriver = {
2284
2332
  return { state: "unavailable", reason: `\`${config.cli}\` CLI not found` };
2285
2333
  cliVersion = parseClaudeCliVersion(version);
2286
2334
  cliVersionChecked = true;
2287
- if (version !== cliHelpVersion) {
2288
- const help = await new Promise((resolve) => {
2289
- execCli(config.cli, ["--help"], { timeout: 8000, env }, (err, stdout) => resolve(err ? null : stdout));
2290
- });
2291
- cliHasAutocompact = help === null ? null : /^\s*--autocompact\b/m.test(help);
2292
- cliHelpVersion = version;
2293
- }
2335
+ if (version !== cliHelpVersion)
2336
+ await readCliHelp(version, env);
2294
2337
  const update = claudeCliUpdate(version, config.cli);
2295
2338
  const warning = claudeInheritWarning(env);
2296
2339
  if (config.requireApiKey) {