openmausbot 0.1.95 → 0.1.97

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 (169) hide show
  1. package/dist/assets/{DesktopViewer-BwWpc4RK.js → DesktopViewer-CPidMCtt.js} +1 -1
  2. package/dist/assets/{abnfDiagram-O67JEVCF-jsQkIqTn.js → abnfDiagram-O67JEVCF-k1UCNXb7.js} +1 -1
  3. package/dist/assets/{arc-7kei4Hsu.js → arc-BUWGa9UU.js} +1 -1
  4. package/dist/assets/{architectureDiagram-NJMV4G6O-CRWDFDMq.js → architectureDiagram-NJMV4G6O-DiPor3UT.js} +1 -1
  5. package/dist/assets/{blockDiagram-BEXU5L5S-CTKW_IKs.js → blockDiagram-BEXU5L5S-Ag9nLW4H.js} +1 -1
  6. package/dist/assets/{c4Diagram-YGBWAQC7-BBTW7-6M.js → c4Diagram-YGBWAQC7-Bh0sjgUa.js} +1 -1
  7. package/dist/assets/channel-DR8eD6QW.js +1 -0
  8. package/dist/assets/{chunk-2Q5K7J3B-M_Ox-MkD.js → chunk-2Q5K7J3B-BFBcIIqu.js} +1 -1
  9. package/dist/assets/{chunk-3FUC2YCW-D2YiN-Ud.js → chunk-3FUC2YCW-Dvfpex-f.js} +2 -2
  10. package/dist/assets/{chunk-5VM5RSS4-BmVYJ0f8.js → chunk-5VM5RSS4-CMjP2YZW.js} +1 -1
  11. package/dist/assets/{chunk-GTNCS2PH-BtiLVVnZ.js → chunk-GTNCS2PH-BryoeTc7.js} +1 -1
  12. package/dist/assets/{chunk-GWA4HPMP-DFYSb6J6.js → chunk-GWA4HPMP-BhfvcchO.js} +1 -1
  13. package/dist/assets/{chunk-JWPE2WC7-mknKBzgV.js → chunk-JWPE2WC7-bcCVp8rp.js} +1 -1
  14. package/dist/assets/{chunk-NETBCI7D-DgSYGiOu.js → chunk-NETBCI7D-yKIjIlkQ.js} +1 -1
  15. package/dist/assets/{chunk-WEXAMYUT-C7ZvuguH.js → chunk-WEXAMYUT-D3Zqbmup.js} +1 -1
  16. package/dist/assets/{chunk-XXDRQBXY-CPqxINpA.js → chunk-XXDRQBXY-CjVoIMG6.js} +1 -1
  17. package/dist/assets/{classDiagram-v2-NBCMYWYE-Cd6xfJaC.js → classDiagram-v2-NBCMYWYE-mUCWdVyH.js} +1 -1
  18. package/dist/assets/{cose-bilkent-JH36ORCC-BSXY5qRh.js → cose-bilkent-JH36ORCC-BBmR1bbX.js} +1 -1
  19. package/dist/assets/{cynefin-OW5HDTMX-D_lhEMj1.js → cynefin-OW5HDTMX-BpRrxoO4.js} +1 -1
  20. package/dist/assets/{cynefinDiagram-VND7K2PF-Da9FrFwk.js → cynefinDiagram-VND7K2PF-D3VBWCPK.js} +1 -1
  21. package/dist/assets/{dagre-6A5THRUB-T77jMOXe.js → dagre-6A5THRUB-DxEIACOO.js} +1 -1
  22. package/dist/assets/{diagram-22UHCM2B-Dkj96X5E.js → diagram-22UHCM2B-BANgZEOS.js} +1 -1
  23. package/dist/assets/{diagram-3UASUU5V-CMi9zlD_.js → diagram-3UASUU5V-Cnq7nJir.js} +1 -1
  24. package/dist/assets/{diagram-ATOU4E4O-CXK6YUfS.js → diagram-ATOU4E4O-4TBDIWnz.js} +1 -1
  25. package/dist/assets/{diagram-CDSNMT55-BE7B1u9Q.js → diagram-CDSNMT55-CD_B39D4.js} +1 -1
  26. package/dist/assets/{diagram-MLGK6HIB-CCMQphKr.js → diagram-MLGK6HIB-CzvnFhEC.js} +1 -1
  27. package/dist/assets/{diagram-MPIPVDR6-iHJ_-tGH.js → diagram-MPIPVDR6-C4w2SNml.js} +1 -1
  28. package/dist/assets/{ebnfDiagram-ZINNZB2B-DONsCWQ7.js → ebnfDiagram-ZINNZB2B-gkUEBm2s.js} +1 -1
  29. package/dist/assets/{elk-276RUBZZ-CU_WOaXk.js → elk-276RUBZZ-B_56X6mw.js} +1 -1
  30. package/dist/assets/{erDiagram-OPXOYQCR-BeYwM04G.js → erDiagram-OPXOYQCR-C_oByoDS.js} +1 -1
  31. package/dist/assets/{flowDiagram-KWPJA3E3-Bdw9DqEl.js → flowDiagram-KWPJA3E3-CR9xXJkC.js} +1 -1
  32. package/dist/assets/{ganttDiagram-FUAMR5RP-C9iwq3qm.js → ganttDiagram-FUAMR5RP-gtYBTu0B.js} +1 -1
  33. package/dist/assets/{gitGraphDiagram-X574FWY7-Dx-VWzUg.js → gitGraphDiagram-X574FWY7-VJ6-Tp_p.js} +1 -1
  34. package/dist/assets/{index-KygHRE3E.js → index-Cp4S4NOL.js} +1 -1
  35. package/dist/assets/index-CpaIXRLp.js +614 -0
  36. package/dist/assets/index-Z81SMsgR.css +1 -0
  37. package/dist/assets/{infoDiagram-VRGFBTTK-BG7uaEd4.js → infoDiagram-VRGFBTTK-2Lu9te4D.js} +1 -1
  38. package/dist/assets/{ishikawaDiagram-OU5B5YK6-D72WAXnM.js → ishikawaDiagram-OU5B5YK6-DR49XY-e.js} +1 -1
  39. package/dist/assets/{journeyDiagram-ZHPQQLJL-DKH1gjYT.js → journeyDiagram-ZHPQQLJL-ZoR0M305.js} +1 -1
  40. package/dist/assets/{kanban-definition-PNTS6WVX-3upm9JUT.js → kanban-definition-PNTS6WVX-DLKHxrO-.js} +1 -1
  41. package/dist/assets/{linear-CcR4pc0-.js → linear-BASsfWly.js} +1 -1
  42. package/dist/assets/{mermaid.core-C6yU5FSS.js → mermaid.core-BVidiPkk.js} +5 -5
  43. package/dist/assets/{mindmap-definition-NLK3R4M7-DOI2xEuv.js → mindmap-definition-NLK3R4M7-g_Qx0U5C.js} +1 -1
  44. package/dist/assets/{pegDiagram-GJSIUBJH-BVWJSgLd.js → pegDiagram-GJSIUBJH-DI9Cjs8w.js} +1 -1
  45. package/dist/assets/{pieDiagram-5QR66LMP-jtbYezhq.js → pieDiagram-5QR66LMP-sRI0Uhr5.js} +1 -1
  46. package/dist/assets/{quadrantDiagram-O4NWA36T-g0u8mtPX.js → quadrantDiagram-O4NWA36T-CamKTby3.js} +1 -1
  47. package/dist/assets/{railroadDiagram-XR7U4H2S-k8689Wny.js → railroadDiagram-XR7U4H2S-DOLlkbf3.js} +1 -1
  48. package/dist/assets/{requirementDiagram-PLB6GJNP-DsJ22PDt.js → requirementDiagram-PLB6GJNP-DrRQ5r97.js} +1 -1
  49. package/dist/assets/{sankeyDiagram-IPEJSGJF-CneWBRgG.js → sankeyDiagram-IPEJSGJF-DGS_lXcX.js} +1 -1
  50. package/dist/assets/{sequenceDiagram-PO4LG4MO-DWsFCWap.js → sequenceDiagram-PO4LG4MO-CkjWqwtf.js} +1 -1
  51. package/dist/assets/{sizeCapture-INFHLROL-Bvf0YE3S.js → sizeCapture-INFHLROL-CepRgful.js} +1 -1
  52. package/dist/assets/{stateDiagram-v2-GCMORJYK-Bxb3Iei8.js → stateDiagram-v2-GCMORJYK-hykr-9RS.js} +1 -1
  53. package/dist/assets/{swimlanes-2SLR337P-DLkk7W4A.js → swimlanes-2SLR337P-CKOHEZbI.js} +1 -1
  54. package/dist/assets/swimlanesDiagram-TC7HE7FX-D-8vqZ4-.js +8 -0
  55. package/dist/assets/{timeline-definition-EJHVYXUP-KR9bIHYC.js → timeline-definition-EJHVYXUP-CB8pJ6AC.js} +1 -1
  56. package/dist/assets/{usecaseDiagram-POWQR4AR-DfsOrj8t.js → usecaseDiagram-POWQR4AR-DrclJHdl.js} +1 -1
  57. package/dist/assets/{vennDiagram-UO4OBE2U-DRSTzG3h.js → vennDiagram-UO4OBE2U-zS23qjv8.js} +1 -1
  58. package/dist/assets/{wardleyDiagram-VNRHLVJA-DtEdhPgT.js → wardleyDiagram-VNRHLVJA-52jgeKxz.js} +1 -1
  59. package/dist/assets/{xychartDiagram-PMCCYNJV-B-XhTxjG.js → xychartDiagram-PMCCYNJV-DSdl9rQ8.js} +1 -1
  60. package/dist/index.html +2 -2
  61. package/dist-server/cloud-home-start.js +26 -13
  62. package/dist-server/companion/src/listener.js +39 -18
  63. package/dist-server/container-mcp.js +14 -27
  64. package/dist-server/drivers/agents-proxy.js +108 -49
  65. package/dist-server/harness-mcp-proxy.js +1 -2
  66. package/dist-server/index.js +4664 -4644
  67. package/dist-server/openmausbot.js +1425 -1437
  68. package/dist-server/pair-cli.js +1425 -1437
  69. package/dist-server/permission-proxy.js +0 -4
  70. package/dist-server/server/admin-activity.js +1 -4
  71. package/dist-server/server/auto-approve.js +20 -5
  72. package/dist-server/server/auto-vm-claims.js +13 -1
  73. package/dist-server/server/automatic-recovery.js +1 -2
  74. package/dist-server/server/boat.js +71 -87
  75. package/dist-server/server/bot-overview.js +1 -1
  76. package/dist-server/server/browser-engine.js +38 -1
  77. package/dist-server/server/browser-live.js +112 -25
  78. package/dist-server/server/browser-runtime.js +31 -1
  79. package/dist-server/server/checkpoints.js +340 -167
  80. package/dist-server/server/chief-of-staff.js +7 -6
  81. package/dist-server/server/cli-setup.js +3 -3
  82. package/dist-server/server/cli.js +33 -26
  83. package/dist-server/server/cloud-computer-tools.js +11 -4
  84. package/dist-server/server/cloud-home-start.js +13 -16
  85. package/dist-server/server/cloud-home.js +3 -13
  86. package/dist-server/server/cloud-move-http.js +44 -30
  87. package/dist-server/server/cloud-move.js +20 -18
  88. package/dist-server/server/cloud-owner.js +1 -1
  89. package/dist-server/server/composio.js +10 -3
  90. package/dist-server/server/computer-engine-removal.js +70 -0
  91. package/dist-server/server/computer-selection.js +18 -0
  92. package/dist-server/server/config.js +99 -75
  93. package/dist-server/server/connector-scope-hint.js +83 -0
  94. package/dist-server/server/container-computer.js +16 -5
  95. package/dist-server/server/contracts.js +0 -9
  96. package/dist-server/server/decider/jev.js +1 -4
  97. package/dist-server/server/decider/log.js +1 -3
  98. package/dist-server/server/decision-log.js +1 -4
  99. package/dist-server/server/default-model-selection.js +20 -14
  100. package/dist-server/server/delegations.js +14 -7
  101. package/dist-server/server/delta-context.js +6 -1
  102. package/dist-server/server/drivers/acp/core.js +5 -4
  103. package/dist-server/server/drivers/acp/opencode-go.js +19 -23
  104. package/dist-server/server/drivers/agents-call.js +47 -64
  105. package/dist-server/server/drivers/agents-catalog.js +69 -3
  106. package/dist-server/server/drivers/agents-proxy.js +2 -1
  107. package/dist-server/server/drivers/builtIn.js +0 -2
  108. package/dist-server/server/drivers/claude-login-auth.js +2 -11
  109. package/dist-server/server/drivers/claude.js +49 -30
  110. package/dist-server/server/drivers/codex-device-auth.js +3 -1
  111. package/dist-server/server/drivers/codex.js +7 -6
  112. package/dist-server/server/drivers/grok.js +1 -1
  113. package/dist-server/server/drivers/openai-chat-protocol.js +4 -2
  114. package/dist-server/server/drivers/openai-chat.js +113 -11
  115. package/dist-server/server/drivers/pi.js +184 -49
  116. package/dist-server/server/drivers/prompt-split.js +4 -4
  117. package/dist-server/server/harness/bus.js +49 -0
  118. package/dist-server/server/harness-mcp-proxy.js +1 -1
  119. package/dist-server/server/index.js +942 -980
  120. package/dist-server/server/live-call.js +11 -6
  121. package/dist-server/server/managed-policy.js +1 -1
  122. package/dist-server/server/mcp-registry.js +35 -0
  123. package/dist-server/server/message-file.js +4 -4
  124. package/dist-server/server/model-catalog/presets.js +8 -0
  125. package/dist-server/server/peer-delivery.js +4 -19
  126. package/dist-server/server/peer-roster.js +40 -6
  127. package/dist-server/server/profile-versions.js +0 -31
  128. package/dist-server/server/provider-key-check.js +2 -1
  129. package/dist-server/server/restart.js +40 -0
  130. package/dist-server/server/room-handoffs.js +12 -0
  131. package/dist-server/server/room-turn-end.js +15 -0
  132. package/dist-server/server/routes/bot-memory.js +0 -24
  133. package/dist-server/server/routes/live.js +3 -3
  134. package/dist-server/server/routine-requests.js +2 -5
  135. package/dist-server/server/routines.js +6 -5
  136. package/dist-server/server/screen-frame-gate.js +1 -3
  137. package/dist-server/server/server-launcher.js +53 -0
  138. package/dist-server/server/store.js +126 -2
  139. package/dist-server/server/surface.js +40 -64
  140. package/dist-server/server/system-prompt.js +23 -13
  141. package/dist-server/server/team-manifest.js +1 -6
  142. package/dist-server/server/tunnel.js +4 -1
  143. package/dist-server/server/turn-continuation.js +162 -0
  144. package/dist-server/server/turn-images.js +1 -21
  145. package/dist-server/server/turn-resources.js +19 -150
  146. package/dist-server/server/usage-ledger.js +3 -1
  147. package/dist-server/server/workspace.js +3 -4
  148. package/dist-server/server-launcher.js +72 -0
  149. package/dist-server/shared/ask-question.js +1 -71
  150. package/dist-server/shared/cloud-computer.js +10 -0
  151. package/dist-server/shared/credential-request.js +1 -1
  152. package/dist-server/shared/failed-turn.js +22 -0
  153. package/dist-server/shared/live-approval.js +2 -2
  154. package/dist-server/shared/package-format.js +3 -3
  155. package/dist-server/shared/pairing-link.js +144 -0
  156. package/dist-server/shared/place-view.js +409 -0
  157. package/dist-server/shared/tool-scope-support.js +1 -2
  158. package/dist-server/vps-container-mcp.js +14 -27
  159. package/dist-server/workspace-backup.worker.js +14 -27
  160. package/package.json +1 -1
  161. package/dist/assets/channel-DS2NbT5x.js +0 -1
  162. package/dist/assets/index-NF7XCHAy.css +0 -1
  163. package/dist/assets/index-X_5ydXIE.js +0 -610
  164. package/dist/assets/swimlanesDiagram-TC7HE7FX-C6bZtBXk.js +0 -8
  165. package/dist-server/server/claim-idle.js +0 -33
  166. package/dist-server/server/cloud-overflow.js +0 -167
  167. package/dist-server/server/drivers/boatagent.js +0 -443
  168. package/dist-server/server/tightening-requests.js +0 -435
  169. package/dist-server/shared/tightening-request.js +0 -87
@@ -1,8 +0,0 @@
1
- import{c as r,s as e}from"./flowDiagram-KWPJA3E3-Bdw9DqEl.js";import{_ as a}from"./mermaid.core-C6yU5FSS.js";import"./chunk-5VM5RSS4-BmVYJ0f8.js";import"./chunk-XXDRQBXY-CPqxINpA.js";import"./chunk-WEXAMYUT-C7ZvuguH.js";import"./chunk-GWA4HPMP-DFYSb6J6.js";import"./channel-DS2NbT5x.js";import"./index-X_5ydXIE.js";var o=a(t=>`${e(t)}
2
- .swimlane.cluster:not([data-color-id]) rect {
3
- stroke: ${t.clusterBorder} !important;
4
- }
5
- [data-look="neo"].cluster rect {
6
- filter: none;
7
- }
8
- `,"getStyles"),s=o,u=r({styles:s});export{u as diagram};
@@ -1,33 +0,0 @@
1
- /** Quiet-window policy for releasing a held seat while its turn lives.
2
- *
3
- * The policy is deliberately synchronous and clock-injected: like the
4
- * claim map it serves, expiry is evaluated when a claim is touched
5
- * (claim, blocker, owns) rather than by a timer, so the server's single
6
- * thread decides every transition and tests pin time exactly. Slice
7
- * #1655 reuses this window math for its own idle release; keep this
8
- * module free of server imports. */
9
- /** Default quiet window: no screen activity for this long releases the seat. */
10
- export const COMPUTER_CLAIM_QUIET_MS = 90_000;
11
- /** Default reclaim window: the previous holder re-claims directly for this long. */
12
- export const COMPUTER_CLAIM_RECLAIM_MS = 10 * 60_000;
13
- export class IdleReleasePolicy {
14
- quietMs;
15
- reclaimMs;
16
- constructor(quietMs = COMPUTER_CLAIM_QUIET_MS, reclaimMs = COMPUTER_CLAIM_RECLAIM_MS) {
17
- if (!Number.isFinite(quietMs) || quietMs <= 0)
18
- throw new Error("Idle release quiet window must be positive");
19
- if (!Number.isFinite(reclaimMs) || reclaimMs <= 0)
20
- throw new Error("Idle release reclaim window must be positive");
21
- this.quietMs = quietMs;
22
- this.reclaimMs = reclaimMs;
23
- }
24
- /** True once the seat has been quiet for the full window. Screen-poller
25
- * frames never refresh the activity clock, so they cannot keep a seat. */
26
- quietElapsed(lastActivityAt, now) {
27
- return now - lastActivityAt >= this.quietMs;
28
- }
29
- /** True while a released seat's previous holder still re-claims directly. */
30
- reclaimLive(releasedAt, now) {
31
- return now < releasedAt + this.reclaimMs;
32
- }
33
- }
@@ -1,167 +0,0 @@
1
- /** Consent, cost, and idle-stop policy for overflowing a local computer
2
- * wait onto a per-second-billed cloud seat (#1655).
3
- *
4
- * Like server/claim-idle.ts this module is deliberately synchronous and
5
- * clock-injected: the server decides every transition on its single
6
- * thread and tests pin time exactly. Nothing here starts or stops a
7
- * machine — the wiring does, and only when the decision says to. Keep
8
- * this module free of server imports. */
9
- import { computerWaitDuration } from "./computer-wait.js";
10
- /** Default idle stop: a cloud seat no real screen work has touched for
11
- * this long is stopped (archived), because billing runs per second while
12
- * it runs. "A few idle minutes" per the issue. */
13
- export const CLOUD_SEAT_IDLE_STOP_MS = 5 * 60_000;
14
- /** Retry cadence for an idle stop that failed (#1655): the first retry
15
- * after one minute, doubling per failure up to ten. A failed stop keeps
16
- * its lease — the seat still bills — so the sweep always retries instead
17
- * of abandoning a running machine. */
18
- export const CLOUD_SEAT_SLEEP_RETRY_MS = 60_000;
19
- export const CLOUD_SEAT_SLEEP_RETRY_MAX_MS = 10 * 60_000;
20
- export function cloudOverflowAction(situation) {
21
- if (!situation.featureEnabled || !situation.cloudConfigured || situation.perSecondCostUsd === null)
22
- return { kind: "off" };
23
- if (situation.started)
24
- return { kind: "none" };
25
- if (situation.consented)
26
- return { kind: "start" };
27
- if (situation.offered)
28
- return { kind: "none" };
29
- return { kind: "offer" };
30
- }
31
- /** Per-conversation consent plus the offer bookkeeping that keeps the
32
- * card to one per conversation until it is revoked. A thread the operator
33
- * allowlisted in config carries standing consent and never needs the
34
- * card. */
35
- export class CloudOverflowConsent {
36
- grants = new Map();
37
- offers = new Map();
38
- revocations = new Set();
39
- /** Consent for this conversation. With `perSecondCostUsd`, a card
40
- * consent counts only at the rate its card showed — an allowlisted
41
- * thread carries the operator's standing consent, and the operator is
42
- * also the one who sets the rate. */
43
- consented(threadId, allowlistedThreads = new Set(), perSecondCostUsd) {
44
- if (this.revocations.has(threadId))
45
- return false;
46
- if (allowlistedThreads.has(threadId))
47
- return true;
48
- const grant = this.grants.get(threadId);
49
- if (!grant)
50
- return false;
51
- return perSecondCostUsd === undefined || grant.perSecondCostUsd === perSecondCostUsd;
52
- }
53
- /** `perSecondCostUsd` is the rate the answered card showed. */
54
- grant(threadId, perSecondCostUsd, now = Date.now()) {
55
- this.revocations.delete(threadId);
56
- this.grants.set(threadId, { at: now, perSecondCostUsd });
57
- }
58
- /** Revoking also clears the offered mark, so a later wait in the same
59
- * conversation may ask again instead of meeting silence. */
60
- revoke(threadId) {
61
- const had = this.grants.delete(threadId);
62
- this.revocations.add(threadId);
63
- this.offers.delete(threadId);
64
- return had;
65
- }
66
- /** A card is outstanding for this conversation — at the given rate
67
- * when one is supplied, so a config change re-offers with the new
68
- * price instead of meeting silence at the old one. */
69
- offered(threadId, perSecondCostUsd) {
70
- const offer = this.offers.get(threadId);
71
- if (!offer)
72
- return false;
73
- return perSecondCostUsd === undefined || offer.perSecondCostUsd === perSecondCostUsd;
74
- }
75
- /** The rate the outstanding card showed, or null when no card is out. */
76
- offeredRate(threadId) {
77
- return this.offers.get(threadId)?.perSecondCostUsd ?? null;
78
- }
79
- markOffered(threadId, perSecondCostUsd, now = Date.now()) {
80
- this.offers.set(threadId, { at: now, perSecondCostUsd });
81
- }
82
- }
83
- /** One running cloud seat started by overflow. `touch` is called only
84
- * for real computer tool completions on that seat — screen-poller frames
85
- * never reach it, exactly like #1653's activity clock, so preview
86
- * traffic cannot keep a paid machine awake. */
87
- export class CloudSeatLease {
88
- botId;
89
- threadId;
90
- /** The claim generation that started the seat: a stop releases the Box
91
- * claim for exactly this owner, never a newer turn's. */
92
- generation;
93
- startedAt;
94
- idleStopMs;
95
- lastActivityAt;
96
- sleepRetryNotBefore = 0;
97
- sleepRetryMs = CLOUD_SEAT_SLEEP_RETRY_MS;
98
- constructor(init) {
99
- const idleStopMs = init.idleStopMs ?? CLOUD_SEAT_IDLE_STOP_MS;
100
- if (!Number.isFinite(idleStopMs) || idleStopMs <= 0)
101
- throw new Error("Cloud seat idle stop window must be positive");
102
- this.botId = init.botId;
103
- this.threadId = init.threadId;
104
- this.generation = init.generation;
105
- this.startedAt = init.now ?? Date.now();
106
- this.lastActivityAt = this.startedAt;
107
- this.idleStopMs = idleStopMs;
108
- }
109
- touch(now = Date.now()) {
110
- this.lastActivityAt = now;
111
- // Real work earns a fresh stop attempt: the backoff only paces
112
- // retries against an idle seat that keeps failing to sleep.
113
- this.sleepRetryNotBefore = 0;
114
- this.sleepRetryMs = CLOUD_SEAT_SLEEP_RETRY_MS;
115
- }
116
- /** An idle stop may run now — true until a failure defers it. */
117
- sleepDue(now = Date.now()) {
118
- return now >= this.sleepRetryNotBefore;
119
- }
120
- /** The idle stop failed: keep the lease and back off, bounded, so the
121
- * sweep retries gently instead of hammering a sick Box — and never
122
- * abandons a seat that still bills. */
123
- deferSleep(now = Date.now()) {
124
- this.sleepRetryNotBefore = now + this.sleepRetryMs;
125
- this.sleepRetryMs = Math.min(this.sleepRetryMs * 2, CLOUD_SEAT_SLEEP_RETRY_MAX_MS);
126
- }
127
- idleFor(now = Date.now()) {
128
- return Math.max(0, now - this.lastActivityAt);
129
- }
130
- idleElapsed(now = Date.now()) {
131
- return now - this.lastActivityAt >= this.idleStopMs;
132
- }
133
- }
134
- /** Dollars per second at rate-card precision: enough places for small
135
- * per-second rates to stay visible, trailing zeros trimmed but never
136
- * past two decimals. */
137
- export function formatPerSecondUsd(perSecondCostUsd) {
138
- let text = perSecondCostUsd.toFixed(6).replace(/0+$/, "");
139
- if (text.endsWith("."))
140
- text += "00";
141
- else {
142
- const decimals = text.length - text.indexOf(".") - 1;
143
- if (decimals < 2)
144
- text += "0".repeat(2 - decimals);
145
- }
146
- return "$" + text;
147
- }
148
- /** The consent card (#1655): the per-second cost sits beside the local
149
- * wait picture before any choice is made, and nothing starts without an
150
- * explicit consent the person still has to give. */
151
- export function cloudOverflowOfferText(opts) {
152
- const idleStopMs = opts.idleStopMs ?? CLOUD_SEAT_IDLE_STOP_MS;
153
- const estimate = opts.waitEstimateMs === undefined ? "" : ` Recent local waits here have taken about ${computerWaitDuration(opts.waitEstimateMs)}.`;
154
- return `This computer is busy. Its work can overflow to a cloud computer for ${formatPerSecondUsd(opts.perSecondCostUsd)} per second.${estimate} Nothing starts without your consent — allow cloud overflow for this conversation to start it; the cloud computer stops after ${computerWaitDuration(idleStopMs)} idle.`;
155
- }
156
- export function cloudSeatStartedText(opts) {
157
- const idleStopMs = opts.idleStopMs ?? CLOUD_SEAT_IDLE_STOP_MS;
158
- return `Cloud computer started at ${formatPerSecondUsd(opts.perSecondCostUsd)} per second. It stops automatically after ${computerWaitDuration(idleStopMs)} idle, and the local wait continues until this computer is free.`;
159
- }
160
- export function cloudSeatStoppedText(idleMs) {
161
- return `Cloud computer stopped after ${computerWaitDuration(idleMs)} idle — billing pauses while it sleeps.`;
162
- }
163
- export function cloudOverflowConsentText(allowed, idleStopMs = CLOUD_SEAT_IDLE_STOP_MS) {
164
- return allowed
165
- ? `Cloud overflow allowed for this conversation. A waiting turn may start the cloud computer; it stops after ${computerWaitDuration(idleStopMs)} idle.`
166
- : "Cloud overflow consent revoked. Waits stay local, and no cloud computer starts without a new consent.";
167
- }
@@ -1,443 +0,0 @@
1
- import { newEventId, newId } from "../contracts.js";
2
- import { boatCredential, boatProviderApi } from "../included-services.js";
3
- import { appendNative } from "./native.js";
4
- import { assertToolScopeSupported } from "../../shared/tool-scope-support.js";
5
- import { OMB_ASK_TOOL, answerWithoutPreamble, askQuestionSummary, capAnswerEcho, ombAskProtocolPrompt, parseOmbAskQuestions, questionChoices, stripOmbAskBlock, } from "../../shared/ask-question.js";
6
- // "boxAgent" is Boat's historical driver kind (catalog and wire); keep it.
7
- const DRIVER_KIND = "boxAgent";
8
- const MODELS = {
9
- default: "claude-fable-5",
10
- options: [
11
- { id: "claude-fable-5", label: "Claude Fable 5 · on the box" },
12
- { id: "sonnet", label: "Claude Sonnet · on the box" },
13
- { id: "gpt-5.4", label: "GPT-5.4 (Codex) · on the box" },
14
- ],
15
- };
16
- /** Any fence whose info string names the ask protocol, even when its body
17
- * does not parse: the marker for "the model tried to ask and failed". */
18
- const ASK_FENCE_ANY = /(^|\n)[ \t]{0,3}(`{3,}|~{3,})[ \t]*omb-ask\b/;
19
- /** The boat runs every harness boat.dev ships (claude-code, codex, pi, opencode,
20
- * prime-agent, kimi). Which one a model id belongs to comes from the public
21
- * catalog, `GET /api/provider-models` at the API root: an object keyed by
22
- * harness, each with its `models`. A bot that arrives here from another engine
23
- * carries that engine's model id, so this is what lets it keep its model. The
24
- * root is the API in use (Boat, or Cloud Pro's relay), so it is cached per root. */
25
- let catalog = null;
26
- let catalogRoot = "";
27
- async function loadCatalog(api) {
28
- const root = api.replace(/\/api\/box\/v1\/?$/, "");
29
- if (catalog && catalogRoot === root)
30
- return;
31
- catalogRoot = root;
32
- catalog = await fetch(`${root}/api/provider-models`, { signal: AbortSignal.timeout(15_000) })
33
- .then((res) => (res.ok ? res.json() : null))
34
- .catch(() => null);
35
- }
36
- const providerFor = (model) => {
37
- const slash = model.indexOf("/");
38
- if (slash > 0 && catalog?.[model.slice(0, slash)])
39
- return { provider: model.slice(0, slash), model: model.slice(slash + 1) };
40
- for (const [provider, harness] of Object.entries(catalog ?? {})) {
41
- if (harness.models?.some((m) => m?.id === model))
42
- return { provider, model };
43
- }
44
- return { provider: model.startsWith("gpt") ? "codex" : "claude-code", model };
45
- };
46
- /** Boat answers a prompt it cannot run with 409 provider_not_configured: the
47
- * Boat has no AI sign-in for the harness the model needs. Say that, and the
48
- * one thing the person can do about it here. */
49
- export const PROVIDER_NOT_CONFIGURED_MESSAGE = "The Computer engine has no AI sign-in on its cloud computer. Choose another engine in this bot's settings.";
50
- /** An OMB Cloud's included Boat account has no agent sign-ins and never
51
- * will: the operator's account must not run (or pay for) customers' models. */
52
- export const COMPUTER_ENGINE_CLOUD_UNAVAILABLE = "The Computer engine isn't available on OpenMaus Cloud — choose another engine.";
53
- /** The words of a Boat error envelope ({code, message, error:{code,
54
- * message}}), never a bare code when Boat sent a sentence. */
55
- export function boatErrorMessage(body, status) {
56
- const code = body?.code ?? body?.error?.code;
57
- if (code === "provider_not_configured")
58
- return PROVIDER_NOT_CONFIGURED_MESSAGE;
59
- const message = body?.message ?? body?.error?.message ?? (typeof body?.error === "string" ? body.error : undefined) ?? code;
60
- return typeof message === "string" && message.trim() ? message : `box HTTP ${status}`;
61
- }
62
- function decodeConfig(raw) {
63
- const o = (raw ?? {});
64
- return {
65
- pollMs: typeof o.pollMs === "number" ? o.pollMs : 2500,
66
- ...(typeof o.askTimeoutMs === "number" ? { askTimeoutMs: o.askTimeoutMs } : {}),
67
- };
68
- }
69
- export const BoatAgentDriver = {
70
- driverKind: DRIVER_KIND,
71
- metadata: { displayName: "Computer", supportsMultipleInstances: false },
72
- models: MODELS,
73
- decodeConfig,
74
- defaultConfig: () => decodeConfig({}),
75
- async create(input) {
76
- const { instanceId, config } = input;
77
- // The person's own token (instanceConfigs injects it), else Cloud Pro's
78
- // included one, with the one base URL each may be sent to. Resolved per
79
- // request, like every other Boat call.
80
- const ownToken = input.environment.BOX_TOKEN ?? process.env.BOX_TOKEN;
81
- const account = () => boatCredential(ownToken);
82
- const listeners = new Set();
83
- const active = new Map();
84
- /** One open ask per thread: the turn-held transport's pending card. The
85
- * thread is busy for exactly as long as the ask is open, so a second
86
- * block can never race the first. */
87
- const heldAsks = new Map();
88
- /** Threads whose last run ended with an unparseable omb-ask fence: the
89
- * next prompt carries the correction so the ask is never lost silently. */
90
- const malformedAsks = new Set();
91
- const emit = (event) => {
92
- for (const l of Array.from(listeners))
93
- l(event);
94
- };
95
- const base = (threadId, turnId) => ({
96
- eventId: newEventId(),
97
- provider: DRIVER_KIND,
98
- threadId,
99
- turnId,
100
- createdAt: new Date().toISOString(),
101
- });
102
- const api = async (path, opts = {}) => {
103
- const credential = account();
104
- const res = await fetch(`${credential?.api ?? boatProviderApi()}${path}`, {
105
- ...opts,
106
- headers: { authorization: `Bearer ${credential?.token ?? ""}`, "content-type": "application/json", ...opts.headers },
107
- signal: opts.signal ?? AbortSignal.timeout(30_000),
108
- });
109
- const body = await res.json().catch(() => null);
110
- if (!res.ok || body?.ok === false)
111
- throw new Error(boatErrorMessage(body, res.status));
112
- return body;
113
- };
114
- const sendTurn = async (turn) => {
115
- turn = { ...turn, toolScope: assertToolScopeSupported(DRIVER_KIND, turn.toolScope) };
116
- const { threadId } = turn;
117
- const computer = turn.integrations?.computer;
118
- const boxId = computer && (!computer.kind || computer.kind === "box") ? computer.boxId : undefined;
119
- if (!account())
120
- throw new Error('box not configured — add {"box":{"token":"…"}} to ~/.openmausbot/config.json');
121
- if (account()?.included)
122
- throw new Error(COMPUTER_ENGINE_CLOUD_UNAVAILABLE);
123
- if (!boxId) {
124
- throw new Error("this bot has no computer yet — open the Computer panel and provision one");
125
- }
126
- if (active.has(threadId))
127
- throw new Error("a turn is already running on this thread");
128
- const turnId = newId();
129
- const model = turn.model || MODELS.default;
130
- const correction = malformedAsks.delete(threadId)
131
- ? "Your previous omb-ask block was malformed or empty, so the person never saw it. Ask again with a valid fenced omb-ask JSON block, or ask in plain words."
132
- : "";
133
- const prompt = [
134
- turn.system,
135
- "You are working on the assigned cloud computer — use its desktop, Chrome, and shell within the access described above.",
136
- ombAskProtocolPrompt(),
137
- ...(correction ? [correction] : []),
138
- "",
139
- turn.text,
140
- ]
141
- .filter((s) => s !== undefined)
142
- .join("\n");
143
- const postPrompt = async (promptText) => {
144
- await loadCatalog(account()?.api ?? boatProviderApi());
145
- const started = await api(`/boxes/${boxId}/prompt`, {
146
- method: "POST",
147
- body: JSON.stringify({ ...providerFor(model), prompt: promptText }),
148
- });
149
- appendNative(threadId, { dir: "out", source: "box.prompt", msg: { model, prompt: promptText, response: started } });
150
- // real shape (2026-08): {type:"prompt.queued", promptId, promptRun:{id,…},
151
- // id:<boat id>} — never fall back to the bare id, it's the boat's
152
- return started?.promptRun?.id ?? started?.prompt?.id ?? started?.promptId ?? null;
153
- };
154
- const promptId = await postPrompt(prompt);
155
- let cancelled = false;
156
- active.set(threadId, {
157
- turnId,
158
- boxId,
159
- cancel: () => {
160
- cancelled = true;
161
- heldAsks.get(threadId)?.settle(null, "system");
162
- void api(`/boxes/${boxId}/interrupt`, { method: "POST" }).catch(() => { });
163
- },
164
- });
165
- emit({ ...base(threadId, turnId), type: "turn.started" });
166
- emit({ ...base(threadId, turnId), type: "session.started", sessionId: promptId, model });
167
- // poll events + run status until the prompt settles — and, when the
168
- // run ends on an omb-ask block, until the person answers it: the OMB
169
- // turn is the unit the whole server already understands (busy thread,
170
- // waiting-on-you, routines waiting), so it stays open across the ask
171
- // and the continuation prompt continues it rather than starting a
172
- // driver-initiated turn nobody accounts for.
173
- (async () => {
174
- // Event ids seen this TURN. The events stream is the whole
175
- // conversation, so a continuation run must not re-ingest history;
176
- // `taskId` normally filters it, but ids survive even shape drift.
177
- const seen = new Set();
178
- let lastText = "";
179
- let pendingText = "";
180
- /** Why the boat could not answer (login expired, model refused, …). */
181
- let problem = null;
182
- /** Emit unflushed deltas as assistant_text and reset pendingText.
183
- * The omb-ask block is protocol, not prose: it streamed raw (the
184
- * card is its readable form), and the settled message shows the
185
- * words around it, never the JSON. */
186
- const flushAssistantText = () => {
187
- const text = stripOmbAskBlock(pendingText);
188
- pendingText = "";
189
- if (!text.trim())
190
- return;
191
- emit({ ...base(threadId, turnId), type: "item.completed", itemType: "assistant_text", text });
192
- };
193
- /** Stream a full-text snapshot as a delta and accumulate it for flush. */
194
- const ingest = (text) => {
195
- const delta = text.startsWith(lastText) ? text.slice(lastText.length) : text;
196
- lastText = text;
197
- if (!delta)
198
- return;
199
- pendingText += delta;
200
- emit({ ...base(threadId, turnId), type: "content.delta", streamKind: "assistant_text", delta });
201
- };
202
- /** The run has settled: flush its prose, then hold the OMB turn open
203
- * on an omb-ask block (if any) until the person answers or the ask
204
- * times out. An answer chains the continuation run under this same
205
- * turn; a deny or timeout ends it. */
206
- const finishRun = async (ok, stopReason) => {
207
- flushAssistantText();
208
- // An interrupt that lands between ask text streaming and settle
209
- // finds no heldAsks entry to settle; without this check the run
210
- // would register a fresh ask for a dead turn and hold stop pending
211
- // for the ask timeout.
212
- if (cancelled) {
213
- return { ok: false, stopReason: "interrupted" };
214
- }
215
- // A remotely interrupted or failed run must stay stopped, even
216
- // if its partial output already contains a complete question.
217
- if (!ok)
218
- return { ok, stopReason };
219
- const questions = parseOmbAskQuestions(lastText);
220
- if (!questions) {
221
- // A fence that did not parse is a question the person never saw.
222
- // Say so on the next prompt instead of losing it silently. (An
223
- // over-cap block does not land here: the parser caps it at six
224
- // questions and still shows the card.)
225
- if (ASK_FENCE_ANY.test(lastText))
226
- malformedAsks.add(threadId);
227
- return { ok: ok && !cancelled, stopReason: cancelled ? "interrupted" : stopReason };
228
- }
229
- const requestId = newId();
230
- let settleHeld;
231
- const held = new Promise((resolve) => {
232
- const timer = setTimeout(() => settleHeld(null, "timeout"), config.askTimeoutMs ?? 15 * 60_000);
233
- timer.unref?.();
234
- settleHeld = (reply, source) => {
235
- heldAsks.delete(threadId);
236
- clearTimeout(timer);
237
- resolve([reply, source]);
238
- };
239
- });
240
- // register before emitting — an answer can race the emit
241
- heldAsks.set(threadId, { requestId, settle: settleHeld });
242
- const choices = questionChoices(questions);
243
- emit({
244
- ...base(threadId, turnId),
245
- requestId,
246
- type: "request.opened",
247
- requestType: "question",
248
- tool: OMB_ASK_TOOL,
249
- summary: askQuestionSummary(questions),
250
- questions,
251
- ...(choices?.length ? { choices } : {}),
252
- origin: "output",
253
- });
254
- const [reply, source] = await held;
255
- emit({ ...base(threadId, turnId), requestId, type: "request.resolved", behavior: reply !== null ? "answer" : "deny", source });
256
- if (reply === null || cancelled) {
257
- return { ok: ok && !cancelled, stopReason: cancelled ? "interrupted" : stopReason };
258
- }
259
- // The turn is still open, so this prompt continues it: same turnId,
260
- // same accounting, and the answer reaches the boat the way the ask
261
- // contract promised — Q:/A: blocks, capped for echo.
262
- const continuation = [
263
- capAnswerEcho(answerWithoutPreamble(reply)),
264
- "",
265
- "The person answered the omb-ask questions above (Q:/A:). Continue the task with their answers; end with another omb-ask block only if you truly need more.",
266
- ].join("\n");
267
- const nextPromptId = await postPrompt(continuation);
268
- // Stop can land while the continuation POST is in flight: the
269
- // interrupt inside cancel() then hits a boat with no active run,
270
- // and the continuation would start after it. Interrupt the run
271
- // that just started before ending the turn.
272
- if (cancelled) {
273
- void api(`/boxes/${boxId}/interrupt`, { method: "POST" }).catch(() => { });
274
- return { ok: false, stopReason: "interrupted" };
275
- }
276
- return await settleRun(nextPromptId);
277
- };
278
- /** Poll one boat run to its settle. */
279
- const settleRun = async (runPromptId) => {
280
- const startedAt = Date.now(); // one 30-min ceiling per boat run, ask chains included
281
- lastText = "";
282
- pendingText = "";
283
- problem = null;
284
- for (;;) {
285
- if (cancelled)
286
- break;
287
- await new Promise((r) => setTimeout(r, config.pollMs));
288
- const events = await api(`/boxes/${boxId}/events`).catch(() => null);
289
- const list = events?.events ?? events?.items ?? [];
290
- for (const ev of list) {
291
- // The stream is the whole conversation from its start; `taskId`
292
- // names the prompt run an event belongs to. Earlier turns are
293
- // history, not this answer.
294
- if (runPromptId && ev.taskId && String(ev.taskId) !== runPromptId)
295
- continue;
296
- const id = String(ev.id ?? ev.eventId ?? JSON.stringify(ev).slice(0, 120));
297
- if (seen.has(id))
298
- continue;
299
- seen.add(id);
300
- appendNative(threadId, { dir: "in", source: "box.events", msg: ev });
301
- const kind = String(ev.type ?? ev.kind ?? "");
302
- // "response" events carry the agent's text at data.content —
303
- // the FULL text so far, not a chunk. Clients accumulate
304
- // deltas, so forward only the growth; a drifted (non-prefix)
305
- // event re-sends whole and the settled message replaces the
306
- // stream anyway.
307
- const text = ev.text ?? ev.message ?? ev.data?.text ?? ev.data?.content ?? null;
308
- if (/assistant|message|output|response/i.test(kind) && typeof text === "string" && text.trim()) {
309
- ingest(text);
310
- }
311
- else if (/usage_limit|error|fail/i.test(kind)) {
312
- const why = ev.data?.summary ?? ev.data?.message ?? ev.data?.error ?? ev.message;
313
- if (typeof why === "string" && why.trim())
314
- problem = why.trim();
315
- }
316
- else if (/tool|command|exec|browse/i.test(kind)) {
317
- flushAssistantText();
318
- emit({
319
- ...base(threadId, turnId),
320
- type: "item.started",
321
- itemType: "tool",
322
- itemId: id,
323
- title: String(ev.title ?? ev.command ?? kind).slice(0, 80),
324
- });
325
- }
326
- // shape-drift backstop: without a promptId the status poll
327
- // below can never see a terminal state, so settle off the
328
- // events themselves instead of hanging to the 30-min ceiling
329
- if (!runPromptId && /complete|finish|done|success|fail|error/i.test(kind)) {
330
- const failed = /fail|error/i.test(kind);
331
- return await finishRun(!failed, failed ? kind : null);
332
- }
333
- }
334
- if (runPromptId) {
335
- const status = await api(`/boxes/${boxId}/prompts/${runPromptId}`).catch(() => null);
336
- appendNative(threadId, { dir: "in", source: "box.prompt.status", msg: status });
337
- // real shape (2026-08): {promptRun:{status:"finished",…}} —
338
- // flat fallbacks kept for drift
339
- const run = status?.promptRun ?? status?.prompt ?? status ?? {};
340
- const state = String(run?.status ?? "");
341
- if (/completed|succeeded|done|finished/i.test(state)) {
342
- const result = run?.result ?? run?.output ?? lastText;
343
- if (typeof result === "string" && result.trim() && result !== lastText) {
344
- ingest(result);
345
- }
346
- if (!pendingText.trim() && !lastText.trim()) {
347
- // a run that ends with nothing said and a recorded problem
348
- // (login expired, …) is a failure the person must see
349
- if (problem)
350
- throw new Error(problem);
351
- pendingText = "(finished)";
352
- }
353
- return await finishRun(true, null);
354
- }
355
- if (/failed|error|cancelled|interrupted/i.test(state)) {
356
- const runError = [run?.error, run?.failureReason, run?.message].find((v) => typeof v === "string" && v.trim());
357
- if (problem || runError || /failed|error/i.test(state))
358
- throw new Error(problem ?? runError ?? `the box run ${state}`);
359
- return await finishRun(false, state);
360
- }
361
- }
362
- if (Date.now() - startedAt > 30 * 60_000) {
363
- throw new Error("box run exceeded 30 minutes — interrupted");
364
- }
365
- }
366
- // cancelled
367
- return await finishRun(false, "interrupted");
368
- };
369
- try {
370
- const outcome = await settleRun(promptId);
371
- active.delete(threadId);
372
- emit({ ...base(threadId, turnId), type: "turn.completed", ok: outcome.ok, stopReason: outcome.stopReason, cost: null });
373
- }
374
- catch (e) {
375
- flushAssistantText();
376
- heldAsks.delete(threadId);
377
- active.delete(threadId);
378
- emit({ ...base(threadId, turnId), type: "runtime.error", message: e.message });
379
- emit({ ...base(threadId, turnId), type: "turn.completed", ok: false, stopReason: "error", cost: null });
380
- }
381
- })();
382
- return { turnId };
383
- };
384
- const snapshot = async () => {
385
- if (!account()) {
386
- return { state: "unavailable", reason: 'no Boat token — add {"box":{"token":"…"}} to ~/.openmausbot/config.json' };
387
- }
388
- if (account()?.included)
389
- return { state: "unavailable", reason: COMPUTER_ENGINE_CLOUD_UNAVAILABLE };
390
- try {
391
- await api("/me");
392
- return { state: "available", authenticated: true, version: null };
393
- }
394
- catch (e) {
395
- return { state: "unavailable", reason: `box API unreachable: ${e.message}` };
396
- }
397
- };
398
- return {
399
- instanceId,
400
- driverKind: DRIVER_KIND,
401
- displayName: input.displayName,
402
- enabled: input.enabled,
403
- models: MODELS,
404
- snapshot,
405
- adapter: {
406
- provider: DRIVER_KIND,
407
- capabilities: { sessionModelSwitch: "in-session", remoteAgent: true },
408
- sendTurn,
409
- interruptTurn: async (threadId) => active.get(threadId)?.cancel(),
410
- respondToRequest: async (threadId, requestId, decision) => {
411
- const held = heldAsks.get(threadId);
412
- if (!held || held.requestId !== requestId)
413
- return "unavailable"; // settled, timed out, or restarted away
414
- const reply = decision.message?.trim();
415
- if (decision.behavior === "answer" && reply) {
416
- held.settle(decision.message, "user");
417
- return "answered";
418
- }
419
- if (decision.behavior === "deny") {
420
- held.settle(null, "user");
421
- return "rejected";
422
- }
423
- // "allow" is not an answer to a question, and blank text is not either
424
- return "unavailable";
425
- },
426
- hasSession: (threadId) => active.has(threadId),
427
- stopAll: async () => {
428
- for (const { cancel } of active.values())
429
- cancel();
430
- },
431
- onEvent: (listener) => {
432
- listeners.add(listener);
433
- return () => listeners.delete(listener);
434
- },
435
- },
436
- dispose: async () => {
437
- for (const { cancel } of active.values())
438
- cancel();
439
- listeners.clear();
440
- },
441
- };
442
- },
443
- };