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
@@ -0,0 +1,144 @@
1
+ // The one `openmausbot://pair` link builder. The desktop's phone setup, the
2
+ // headless server's pairing endpoint and `openmausbot pair` all print this
3
+ // link; the Android and iOS scanners read it (android/core Connection.kt
4
+ // `PairingInvite.parse`, ios CompanionCore Client.swift `PairingInvite.parse`).
5
+ //
6
+ // Every value is written with encodeURIComponent, so a space is `%20`. The
7
+ // desktop used URLSearchParams until Oct 2026, which writes a space as `+`;
8
+ // neither phone read that back as a space, and "Miguel's computer" showed up
9
+ // as "Miguel's+computer". The phones now also read `+` as a space, for links
10
+ // from desktops still running the old builder.
11
+ /** How many fallback routes a link will carry. The list is tiny in practice;
12
+ * the cap only keeps a pathological interface table from bloating the QR. */
13
+ export const MAX_PAIRING_HOSTS = 8;
14
+ const ENDPOINT_KINDS = new Set(["hosted", "tailnet", "lan", "bonjour"]);
15
+ /** Keep the QR contract strict even though its input came from our own
16
+ * sidecar. A public URL with credentials or a path is not a companion base
17
+ * URL, and filtering it is safer than teaching the phone to reinterpret it.
18
+ * Sorted by priority, deduplicated by origin, capped. */
19
+ export function qrEndpoints(endpoints) {
20
+ const seen = new Set();
21
+ const valid = [];
22
+ for (const endpoint of endpoints ?? []) {
23
+ if (!endpoint ||
24
+ !ENDPOINT_KINDS.has(endpoint.kind) ||
25
+ !Number.isInteger(endpoint.priority) ||
26
+ endpoint.priority < 0 ||
27
+ endpoint.priority > 1_000_000) {
28
+ continue;
29
+ }
30
+ try {
31
+ const parsed = new URL(endpoint.url);
32
+ const expectedProtocol = endpoint.kind === "hosted" ? "https:" : "http:";
33
+ const explicitPort = parsed.port ? Number(parsed.port) : null;
34
+ const hostname = parsed.hostname.toLowerCase().replace(/\.$/, "");
35
+ if (parsed.protocol !== expectedProtocol ||
36
+ // No empty DNS label (".ts.net", "mac..local"): the phones refuse
37
+ // one, and one endpoint they refuse makes them refuse the whole QR.
38
+ hostname.split(".").includes("") ||
39
+ (endpoint.kind === "tailnet" && !hostname.endsWith(".ts.net")) ||
40
+ (explicitPort !== null && (!Number.isInteger(explicitPort) || explicitPort < 1 || explicitPort > 65_535)) ||
41
+ parsed.username ||
42
+ parsed.password ||
43
+ parsed.pathname !== "/" ||
44
+ parsed.search ||
45
+ parsed.hash ||
46
+ seen.has(parsed.origin)) {
47
+ continue;
48
+ }
49
+ seen.add(parsed.origin);
50
+ valid.push({ url: parsed.origin, kind: endpoint.kind, priority: endpoint.priority });
51
+ }
52
+ catch {
53
+ // One malformed advisory route must not invalidate an otherwise usable
54
+ // pairing QR. It is simply omitted from the route walk.
55
+ }
56
+ }
57
+ return valid.sort((left, right) => left.priority - right.priority).slice(0, MAX_PAIRING_HOSTS);
58
+ }
59
+ /** URL-safe, unpadded base64 keeps the structured JSON smaller than query
60
+ * escaping every quote and slash while remaining straightforward to decode
61
+ * with Foundation on iOS. */
62
+ function encodeEndpoints(endpoints) {
63
+ const bytes = new TextEncoder().encode(JSON.stringify(endpoints));
64
+ let binary = "";
65
+ for (const byte of bytes)
66
+ binary += String.fromCharCode(byte);
67
+ return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
68
+ }
69
+ function validSecretPublicKey(value) {
70
+ if (!value || !/^[A-Za-z0-9_-]{87}$/.test(value))
71
+ return null;
72
+ try {
73
+ const base64 = value.replaceAll("-", "+").replaceAll("_", "/") + "=";
74
+ const decoded = atob(base64);
75
+ if (decoded.length !== 65 || decoded.charCodeAt(0) !== 4)
76
+ return null;
77
+ let binary = "";
78
+ for (let index = 0; index < decoded.length; index += 1)
79
+ binary += decoded[index];
80
+ return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "") === value
81
+ ? value
82
+ : null;
83
+ }
84
+ catch {
85
+ return null;
86
+ }
87
+ }
88
+ /** The `address` value: `host:port` (IPv6 bracketed) or a bare http(s)
89
+ * origin, or null when it is neither. Both phones refuse an address with
90
+ * credentials, a path, a query or a fragment, as `qrEndpoints` does. */
91
+ function linkAddress(address, port) {
92
+ const value = address.trim();
93
+ if (!value)
94
+ return null;
95
+ if (/^https?:\/\//i.test(value)) {
96
+ try {
97
+ const parsed = new URL(value);
98
+ if (parsed.username || parsed.password || parsed.pathname !== "/" || parsed.search || parsed.hash)
99
+ return null;
100
+ return parsed.origin;
101
+ }
102
+ catch {
103
+ return null;
104
+ }
105
+ }
106
+ if (port === undefined || !Number.isInteger(port) || port < 1 || port > 65_535)
107
+ return null;
108
+ const authority = value.includes(":") && !value.startsWith("[") ? `[${value}]` : value;
109
+ return `${authority}:${port}`;
110
+ }
111
+ /**
112
+ * A short-lived handoff from a trusted pairing screen to the mobile app. The
113
+ * credential still has to be redeemed; putting it in the link does not
114
+ * create or expose the long-lived device token. Null when any required part
115
+ * is malformed, so a caller never prints a link a phone would refuse.
116
+ */
117
+ export function phonePairingLink({ address, port, token, code, name, hosts, endpoints, secretPublicKey, }) {
118
+ const dial = linkAddress(address, port);
119
+ if (!dial || !/^omb_pair_[A-Za-z0-9_-]{43}$/.test(token))
120
+ return null;
121
+ if (code !== undefined && !/^\d{6}$/.test(code))
122
+ return null;
123
+ const fields = [["address", dial], ["token", token]];
124
+ if (code !== undefined)
125
+ fields.push(["code", code]);
126
+ if (name?.trim())
127
+ fields.push(["name", name.trim()]);
128
+ // Comma-joined, which no hostname or IP literal can contain. Filtered
129
+ // rather than refused: a bad candidate costs the phone one failed dial,
130
+ // and dropping the whole link over it would break pairing entirely.
131
+ const candidates = (hosts ?? [])
132
+ .map((candidate) => candidate.trim())
133
+ .filter((candidate) => candidate && !/[\s/?#,[\]]/.test(candidate))
134
+ .slice(0, MAX_PAIRING_HOSTS);
135
+ if (candidates.length)
136
+ fields.push(["hosts", candidates.join(",")]);
137
+ const routes = qrEndpoints(endpoints);
138
+ if (routes.length)
139
+ fields.push(["endpoints", encodeEndpoints(routes)]);
140
+ const secretKey = validSecretPublicKey(secretPublicKey);
141
+ if (secretKey)
142
+ fields.push(["secretKey", secretKey]);
143
+ return `openmausbot://pair?${fields.map(([key, value]) => `${key}=${encodeURIComponent(value)}`).join("&")}`;
144
+ }
@@ -0,0 +1,409 @@
1
+ // Where a bot works, said once: one plain line and at most one next action
2
+ // for every state a place can be in, from the facts the server and the app
3
+ // already hold. The server words a failed turn's row from it (English, the
4
+ // language phones read), and every control that shows or picks a place
5
+ // (the Computer panel's cards and Simple grid, the composer chip, the failed
6
+ // turn row, the bot's Access settings) renders the same state from it in the
7
+ // person's language. Nothing else decides what a place is called or what to
8
+ // do when it can't be used.
9
+ //
10
+ // The English below mirrors src/locales/en.json `place.view.*`,
11
+ // `place.action.*` and `place.askAdmin`/`place.backOnAuto` exactly (a test
12
+ // holds them together), the way shared/live-approval.ts mirrors its keys:
13
+ // the server has no i18n.
14
+ /** Every state a place can be in. cc-* is the cloud computer. */
15
+ export const PLACE_STATES = [
16
+ "auto", "auto-team",
17
+ "cc-cannot", "cc-tools-off", "cc-sign-in", "cc-new", "cc-starting", "cc-waking", "cc-on", "cc-asleep",
18
+ "cc-no-hours", "cc-at-once", "cc-unavailable", "cc-ended", "cc-no-start", "cc-clearing", "cc-provider",
19
+ "cc-on-my-cloud", "cc-needs-key", "vps",
20
+ "browser", "browser-off", "browser-cannot",
21
+ "local", "local-unavailable", "vm", "vm-cannot", "place-failed",
22
+ "off",
23
+ ];
24
+ /** The next actions a place offers. Each is one button in the app and, on a
25
+ * stored row, one sentence a phone can read. */
26
+ export const PLACE_ACTIONS = [
27
+ "choose-model", "open-tools", "sign-in", "start", "wake", "watch", "see-plan", "manage-computers",
28
+ "try-again", "open-my-cloud", "add-boat-key", "turn-on-browser", "open-team-map", "open-vm-settings",
29
+ "clear-pin", "change-routine", "open-computer-panel",
30
+ ];
31
+ /** The English catalog, mirrored by en.json. */
32
+ export const PLACE_EN = {
33
+ "place.view.auto.short": "Chooses for you",
34
+ "place.view.auto.lineMyCloud": "Uses the built-in browser. In a chat, it starts its cloud computer by itself when a task needs desktop apps.",
35
+ "place.view.auto.lineDesktop": "Uses the built-in browser, a private desktop on this computer, or this computer's screen, whichever the task needs.",
36
+ "place.view.auto.lineLinux": "Uses the built-in browser, or a running Local VM when a task needs a desktop.",
37
+ "place.view.autoTeam.short": "Shared",
38
+ "place.view.autoTeam.line": "Uses {computer}, the cloud computer this team shares.",
39
+ "place.view.ccCannot.short": "Not with this model",
40
+ "place.view.ccCannot.line": "{model} can't use a computer. Choose a model that can, such as Claude or ChatGPT.",
41
+ "place.view.ccToolsOff.short": "Not allowed",
42
+ "place.view.ccToolsOff.line": "What {bot} can use doesn't include a computer.",
43
+ "place.view.ccSignIn.short": "Sign in first",
44
+ "place.view.ccSignIn.line": "Sign in to {engine} first. {bot} uses it on the cloud computer too.",
45
+ "place.view.ccSignIn.lineMyCloud": "Sign in to {engine} on My Cloud first. {bot} uses it on the cloud computer too.",
46
+ "place.view.ccNew.short": "Ready",
47
+ "place.view.ccNew.line": "{bot} gets its own cloud computer the first time a task needs one. The first start takes about a minute.",
48
+ "place.view.ccStarting.short": "Starting…",
49
+ "place.view.ccStarting.line": "Starting {bot}'s cloud computer. The first start takes about a minute.",
50
+ "place.view.ccWaking.short": "Waking…",
51
+ "place.view.ccWaking.line": "Waking {bot}'s cloud computer. This takes a few seconds.",
52
+ "place.view.ccOn.short": "On",
53
+ "place.view.ccOn.line": "{bot}'s cloud computer is on.",
54
+ "place.view.ccAsleep.short": "Asleep",
55
+ "place.view.ccAsleep.line": "{bot}'s cloud computer is asleep. It wakes in a few seconds when {bot} needs it.",
56
+ "place.view.ccNoHours.short": "No hours left",
57
+ "place.view.ccNoHours.line": "This month's {hours} cloud computer hours are used up. They come back on 1 {month}.",
58
+ "place.view.ccNoHours.lineUnknown": "This month's cloud computer hours are used up.",
59
+ "place.view.ccAtOnce.short": "In use",
60
+ "place.view.ccAtOnce.lineOne": "Your {plan} plan includes 1 cloud computer, and {holders} has it.",
61
+ "place.view.ccAtOnce.lineMany": "Your {plan} plan includes {max} cloud computers, and {holders} have them.",
62
+ "place.view.ccAtOnce.lineUnknown": "All the cloud computers your plan includes are in use.",
63
+ "place.view.ccUnavailable.short": "Not available now",
64
+ "place.view.ccUnavailable.line": "Cloud computers can't start right now. It isn't anything you did.",
65
+ "place.view.ccEnded.short": "Plan ended",
66
+ "place.view.ccEnded.line": "Your OpenMausBot Cloud plan has ended, so cloud computers are off.",
67
+ "place.view.ccNoStart.short": "Didn't start",
68
+ "place.view.ccNoStart.line": "{bot}'s cloud computer didn't start.",
69
+ "place.view.ccClearing.short": "Clearing up",
70
+ "place.view.ccClearing.line": "{bot}'s previous cloud computer is still being removed.",
71
+ "place.view.ccProvider.short": "Couldn't start",
72
+ "place.view.ccProvider.line": "Your Boat account couldn't start {bot}'s cloud computer: {words}",
73
+ "place.view.ccOnMyCloud.short": "On My Cloud",
74
+ "place.view.ccOnMyCloud.line": "Cloud computers from your plan work for bots on My Cloud for now.",
75
+ "place.view.ccNeedsKey.short": "Needs a Boat key",
76
+ "place.view.ccNeedsKey.line": "A cloud computer here needs your own Boat key, a paid service.",
77
+ "place.view.vps.short": "Your server",
78
+ "place.view.vps.line": "Uses your own server, connected over SSH.",
79
+ "place.view.browser.short": "Web pages only",
80
+ "place.view.browser.line": "Works in the built-in browser only: web pages, no desktop apps.",
81
+ "place.view.browserOff.short": "Browser is off",
82
+ "place.view.browserOff.line": "The built-in browser is switched off.",
83
+ "place.view.browserCannot.short": "Not with this model",
84
+ "place.view.browserCannot.line": "{model} can't use the built-in browser.",
85
+ "place.view.local.short": "Your screen",
86
+ "place.view.local.line": "Uses your screen, mouse, and keyboard.",
87
+ "place.view.localUnavailable.short": "Not ready",
88
+ "place.view.localUnavailable.line": "{reason}",
89
+ "place.view.vm.short": "Private desktop",
90
+ "place.view.vm.line": "A private Linux desktop on this device, separate from your own screen.",
91
+ "place.view.vmCannot.short": "Not with this model",
92
+ "place.view.vmCannot.line": "{model} can't use a Local VM. Choose a model that can, such as Claude or ChatGPT.",
93
+ "place.view.placeFailed.short": "Didn't start",
94
+ "place.view.placeFailed.line": "{cause}",
95
+ "place.view.off.short": "No screen",
96
+ "place.view.off.line": "{bot} has no screen. It can still chat and do anything that doesn't need one.",
97
+ "place.action.chooseModel": "Choose a model",
98
+ "place.action.openTools": "Change what {bot} can use",
99
+ "place.action.signIn": "Sign in",
100
+ "place.action.start": "Start it now",
101
+ "place.action.wake": "Wake it now",
102
+ "place.action.watch": "Watch",
103
+ "place.action.seePlan": "See your plan",
104
+ "place.action.manageComputers": "Manage cloud computers",
105
+ "place.action.tryAgain": "Try again",
106
+ "place.action.openMyCloud": "Open My Cloud",
107
+ "place.action.addBoatKey": "Add Boat key",
108
+ "place.action.turnOnBrowser": "Turn on the browser",
109
+ "place.action.openTeamMap": "Open Team map",
110
+ "place.action.openVmSettings": "Local VM settings",
111
+ "place.action.clearPin": "Clear this conversation's place",
112
+ "place.action.usePlace": "Use {place}",
113
+ "place.action.changeRoutine": "Change where it runs",
114
+ "place.action.openComputerPanel": "Open Computer panel",
115
+ "place.askAdmin": "Ask an Admin to change it.",
116
+ "place.backOnAuto": "This conversation is back on Auto.",
117
+ };
118
+ /** A stored row's next action as one English sentence: what a phone, which
119
+ * shows only the row's words, reads instead of a button. English only, like
120
+ * the row it ends. */
121
+ export const PLACE_WORDS = {
122
+ "choose-model": "Choose another model in {bot}'s settings.",
123
+ "open-tools": "Change what {bot} can use in its settings.",
124
+ "sign-in": "Sign in to {engine} in Settings.",
125
+ start: "Start it from the Computer panel.",
126
+ wake: "Wake it from the Computer panel.",
127
+ watch: "Watch it from the Computer panel.",
128
+ "see-plan": "See your plan on the Plan page.",
129
+ "manage-computers": "Manage your cloud computers in Settings → Local VM.",
130
+ "try-again": "Try again.",
131
+ "open-my-cloud": "Open My Cloud from the menu at the top of the sidebar.",
132
+ "add-boat-key": "Add a Boat key in Settings → API keys.",
133
+ "turn-on-browser": "Turn on the built-in browser in the Computer panel.",
134
+ "open-team-map": "Open Team map to manage it.",
135
+ "open-vm-settings": "Check it in Settings → Local VM.",
136
+ "clear-pin": "Clear this conversation's place in the composer to continue.",
137
+ "change-routine": "Change where this routine runs.",
138
+ "open-computer-panel": "Check it in the Computer panel.",
139
+ };
140
+ function fill(template, params = {}) {
141
+ return template.replace(/\{(\w+)\}/g, (whole, name) => (Object.hasOwn(params, name) ? String(params[name]) : whole));
142
+ }
143
+ export const english = (key, params) => fill(PLACE_EN[key], params);
144
+ /** Each state's key segment, short and line keys, and own action. */
145
+ const STATE = {
146
+ auto: { key: "auto", action: null },
147
+ "auto-team": { key: "autoTeam", action: "open-team-map" },
148
+ "cc-cannot": { key: "ccCannot", action: "choose-model" },
149
+ "cc-tools-off": { key: "ccToolsOff", action: "open-tools" },
150
+ "cc-sign-in": { key: "ccSignIn", action: "sign-in" },
151
+ "cc-new": { key: "ccNew", action: "start" },
152
+ "cc-starting": { key: "ccStarting", action: "watch" },
153
+ "cc-waking": { key: "ccWaking", action: "watch" },
154
+ "cc-on": { key: "ccOn", action: "watch" },
155
+ "cc-asleep": { key: "ccAsleep", action: "wake" },
156
+ "cc-no-hours": { key: "ccNoHours", action: "see-plan" },
157
+ "cc-at-once": { key: "ccAtOnce", action: "manage-computers" },
158
+ "cc-unavailable": { key: "ccUnavailable", action: "try-again" },
159
+ "cc-ended": { key: "ccEnded", action: "see-plan" },
160
+ "cc-no-start": { key: "ccNoStart", action: "try-again" },
161
+ "cc-clearing": { key: "ccClearing", action: "try-again" },
162
+ "cc-provider": { key: "ccProvider", action: "try-again" },
163
+ "cc-on-my-cloud": { key: "ccOnMyCloud", action: "open-my-cloud" },
164
+ "cc-needs-key": { key: "ccNeedsKey", action: "add-boat-key" },
165
+ vps: { key: "vps", action: null },
166
+ browser: { key: "browser", action: null },
167
+ "browser-off": { key: "browserOff", action: "turn-on-browser" },
168
+ "browser-cannot": { key: "browserCannot", action: "choose-model" },
169
+ local: { key: "local", action: null },
170
+ "local-unavailable": { key: "localUnavailable", action: null },
171
+ vm: { key: "vm", action: "open-vm-settings" },
172
+ "vm-cannot": { key: "vmCannot", action: "choose-model" },
173
+ "place-failed": { key: "placeFailed", action: "open-computer-panel" },
174
+ off: { key: "off", action: null },
175
+ };
176
+ /** A passing cause: nothing to fix, so where the place came from decides the
177
+ * way on (sourceAction). Every other state's action removes its cause. */
178
+ const PASSING = new Set(["cc-unavailable", "cc-no-start", "cc-clearing", "cc-provider", "place-failed"]);
179
+ /** Actions that change a setting, a sign-in or the plan: a User can't take
180
+ * them, so the line ends "Ask an Admin to change it." instead. */
181
+ const ADMIN_ONLY = new Set([
182
+ "choose-model", "open-tools", "sign-in", "see-plan", "manage-computers", "open-my-cloud",
183
+ "add-boat-key", "turn-on-browser", "open-team-map", "open-vm-settings", "change-routine",
184
+ ]);
185
+ /** Starting or waking a computer early: a User's own message does it, so
186
+ * there is nothing to ask anyone for. */
187
+ const ADMIN_SHORTCUT = new Set(["start", "wake"]);
188
+ const ACTION_KEY = {
189
+ "choose-model": "place.action.chooseModel",
190
+ "open-tools": "place.action.openTools",
191
+ "sign-in": "place.action.signIn",
192
+ start: "place.action.start",
193
+ wake: "place.action.wake",
194
+ watch: "place.action.watch",
195
+ "see-plan": "place.action.seePlan",
196
+ "manage-computers": "place.action.manageComputers",
197
+ "try-again": "place.action.tryAgain",
198
+ "open-my-cloud": "place.action.openMyCloud",
199
+ "add-boat-key": "place.action.addBoatKey",
200
+ "turn-on-browser": "place.action.turnOnBrowser",
201
+ "open-team-map": "place.action.openTeamMap",
202
+ "open-vm-settings": "place.action.openVmSettings",
203
+ "clear-pin": "place.action.clearPin",
204
+ "change-routine": "place.action.changeRoutine",
205
+ "open-computer-panel": "place.action.openComputerPanel",
206
+ };
207
+ /** The state and its params, from the facts. Pure: the panel, the chip, the
208
+ * Access line and the server all ask this. */
209
+ export function placeState(facts) {
210
+ const bot = facts.bot;
211
+ const model = facts.engine.model || facts.engine.name;
212
+ if (facts.place === "off")
213
+ return { state: "off", params: { bot } };
214
+ if (facts.place === "auto") {
215
+ return facts.teamComputer
216
+ ? { state: "auto-team", params: { bot, computer: facts.teamComputer } }
217
+ : { state: "auto", params: { bot } };
218
+ }
219
+ if (facts.place === "browser") {
220
+ if (!facts.engine.browser)
221
+ return { state: "browser-cannot", params: { bot, model } };
222
+ return facts.browserOn ? { state: "browser", params: { bot } } : { state: "browser-off", params: { bot } };
223
+ }
224
+ if (facts.place === "local") {
225
+ return facts.local?.ready === false
226
+ ? { state: "local-unavailable", params: { bot, reason: facts.local.reason ?? "" } }
227
+ : { state: "local", params: { bot } };
228
+ }
229
+ if (facts.place === "vm") {
230
+ return facts.engine.computer ? { state: "vm", params: { bot } } : { state: "vm-cannot", params: { bot, model } };
231
+ }
232
+ // The cloud computer: on the person's own server (VPS) or on Boat, one
233
+ // rule for both (shared/cloud-computer.ts).
234
+ const vps = facts.backend === "vps";
235
+ if (!facts.engine.computer)
236
+ return { state: "cc-cannot", params: { bot, model } };
237
+ if (!facts.toolsAllowComputer)
238
+ return { state: "cc-tools-off", params: { bot } };
239
+ if (!facts.engine.signedIn)
240
+ return { state: "cc-sign-in", params: { bot, engine: facts.engine.name } };
241
+ if (vps)
242
+ return { state: "vps", params: { bot } };
243
+ if (facts.refusal)
244
+ return { state: facts.refusal.state, params: { bot, ...facts.refusal.params } };
245
+ if (facts.boat === "none") {
246
+ // My Cloud always has the plan's computers while the plan is active.
247
+ if (facts.server === "my-cloud")
248
+ return { state: "cc-unavailable", params: { bot } };
249
+ return { state: facts.plan ? "cc-on-my-cloud" : "cc-needs-key", params: { bot } };
250
+ }
251
+ switch (facts.computer) {
252
+ case "starting": return { state: "cc-starting", params: { bot } };
253
+ case "waking": return { state: "cc-waking", params: { bot } };
254
+ case "on": return { state: "cc-on", params: { bot } };
255
+ case "asleep": return { state: "cc-asleep", params: { bot } };
256
+ default: return { state: "cc-new", params: { bot } };
257
+ }
258
+ }
259
+ /** Where the person goes when a passing cause stops a place: try again in
260
+ * place, clear the pin that holds this conversation, change where the
261
+ * routine runs. A room member's turn has no retry of its own. */
262
+ function sourceAction(source, state) {
263
+ switch (source) {
264
+ case "works-on": return state === "place-failed" ? "open-computer-panel" : "try-again";
265
+ case "auto-pin": return "try-again";
266
+ case "pin": return "clear-pin";
267
+ case "routine": return "change-routine";
268
+ case "room": return null;
269
+ }
270
+ }
271
+ function lineKey(state, params, server) {
272
+ const segment = STATE[state].key;
273
+ if (state === "auto") {
274
+ return server === "my-cloud" ? "place.view.auto.lineMyCloud" : server === "linux" ? "place.view.auto.lineLinux" : "place.view.auto.lineDesktop";
275
+ }
276
+ if (state === "cc-sign-in" && server === "my-cloud")
277
+ return "place.view.ccSignIn.lineMyCloud";
278
+ // A refusal read from a code alone has no numbers or names to give.
279
+ if (state === "cc-no-hours" && (params.hours === undefined || !params.month))
280
+ return "place.view.ccNoHours.lineUnknown";
281
+ if (state === "cc-at-once") {
282
+ if (!params.holders?.length || !params.plan || params.max === undefined)
283
+ return "place.view.ccAtOnce.lineUnknown";
284
+ return params.max === 1 ? "place.view.ccAtOnce.lineOne" : "place.view.ccAtOnce.lineMany";
285
+ }
286
+ return `place.view.${segment}.line`;
287
+ }
288
+ /** Join names the way a sentence lists them, in the reader's language. */
289
+ export function listNames(names, locale = "en") {
290
+ try {
291
+ return new Intl.ListFormat(locale, { style: "long", type: "conjunction" }).format(names);
292
+ }
293
+ catch {
294
+ return names.join(", ");
295
+ }
296
+ }
297
+ function templateParams(params, locale) {
298
+ const out = {};
299
+ for (const [name, value] of Object.entries(params)) {
300
+ if (value === undefined)
301
+ continue;
302
+ out[name] = Array.isArray(value) ? listNames(value, locale) : value;
303
+ }
304
+ return out;
305
+ }
306
+ /** One state, worded: its short label, its one line and its one action. */
307
+ export function placeViewOf(state, params, context) {
308
+ const translate = context.translate ?? english;
309
+ const values = templateParams(params, context.locale ?? "en");
310
+ const segment = STATE[state].key;
311
+ let line = translate(lineKey(state, params, context.server), values);
312
+ let action = context.source && PASSING.has(state) ? sourceAction(context.source, state) : STATE[state].action;
313
+ // A failed row never offers what only the panel can do on the spot.
314
+ if (context.source && (action === "start" || action === "wake" || action === "watch"))
315
+ action = null;
316
+ if (context.source === "auto-pin")
317
+ line = `${line} ${translate("place.backOnAuto")}`;
318
+ // Simple mode leaves Local VM setup to Advanced.
319
+ if (action === "open-vm-settings" && context.mode === "simple")
320
+ action = null;
321
+ if (action && context.role === "user" && ADMIN_SHORTCUT.has(action))
322
+ action = null;
323
+ if (action && context.role === "user" && ADMIN_ONLY.has(action)) {
324
+ action = null;
325
+ line = `${line} ${translate("place.askAdmin")}`;
326
+ }
327
+ // Simple mode has no composer chip, so a pin's way back is the grid's
328
+ // choice, by the grid's own name for it.
329
+ const label = !action ? null
330
+ : action === "clear-pin" && context.mode === "simple" && context.worksOnLabel
331
+ ? translate("place.action.usePlace", { place: context.worksOnLabel })
332
+ : translate(ACTION_KEY[action], values);
333
+ return {
334
+ state,
335
+ params,
336
+ short: translate(`place.view.${segment}.short`, values),
337
+ line,
338
+ action: action && label ? { id: action, label } : null,
339
+ };
340
+ }
341
+ /** The view of a place from its facts. */
342
+ export function placeView(facts, options = {}) {
343
+ const { state, params } = placeState(facts);
344
+ return placeViewOf(state, params, { server: facts.server, role: facts.role, mode: facts.mode, ...options });
345
+ }
346
+ /** A stored failed-turn row, worded for this reader. */
347
+ export function placeRowView(row, context) {
348
+ return placeViewOf(row.state, row.params, { ...context, source: row.source });
349
+ }
350
+ /** What the server stores as the row's words: the line and its action as a
351
+ * sentence, in English, so a phone that reads only the words knows both. */
352
+ export function placeRowText(row, server) {
353
+ const view = placeViewOf(row.state, row.params, { server, role: "admin", source: row.source });
354
+ if (!view.action)
355
+ return view.line;
356
+ const words = row.source === "auto-pin" && view.action.id === "try-again"
357
+ ? "Send your message again."
358
+ : fill(PLACE_WORDS[view.action.id], templateParams(row.params, "en"));
359
+ return `${view.line} ${words}`;
360
+ }
361
+ // ── Reading a failed start ──────────────────────────────────────────────
362
+ const MONTH = "(January|February|March|April|May|June|July|August|September|October|November|December)";
363
+ const HOURS_USED = new RegExp(`Your (.+?) plan's (\\d+) cloud computer hours for \\w+ are used up\\. They reset on 1 ${MONTH}`);
364
+ const AT_ONCE = /Your (.+?) plan includes (\d+) cloud computers? at once/;
365
+ const PASSING_WORDS = [
366
+ /cloud computers are busy right now/i, /temporarily unavailable/i, /can't be checked right now/i, /could not be reached/i,
367
+ /returned an unexpected answer/i, /could not complete that request/i, /started too often/i, /woken too often/i,
368
+ /included with your Cloud plan aren't available/i, /are not available right now/i, /fetch failed/i, /ECONNREFUSED/,
369
+ /rate-limiting/i, /\b50[234]\b/,
370
+ ];
371
+ const NO_START_WORDS = [
372
+ /did not become ready/i, /desktop link could not be created/i, /could not be created or reached/i, /did not wake/i,
373
+ /no computer yet/i, /didn't start in time/i,
374
+ ];
375
+ const CLEARING_WORDS = [/is being deleted/i, /previous cloud computer deletion/i, /deletion was not confirmed/i, /deletion is still being reconciled/i];
376
+ /** Read a failed cloud computer start as a state. The Admin's own codes come
377
+ * first; until every refusal has one, its English is read (the words below
378
+ * are exactly the Admin's, server/cloud-services.ts). `included`: the plan's
379
+ * computers, not the person's own Boat key, whose provider's words are shown
380
+ * as they are. */
381
+ export function cloudRefusal(failure, included) {
382
+ const message = failure.message.trim();
383
+ const code = failure.code;
384
+ const hours = HOURS_USED.exec(message);
385
+ if (code === "hours_used" || hours) {
386
+ return { state: "cc-no-hours", params: hours ? { plan: hours[1], hours: Number(hours[2]), month: hours[3] } : {} };
387
+ }
388
+ const atOnce = AT_ONCE.exec(message);
389
+ if (code === "at_once" || atOnce) {
390
+ return { state: "cc-at-once", params: atOnce ? { plan: atOnce[1], max: Number(atOnce[2]) } : {} };
391
+ }
392
+ if (code === "subscription_inactive")
393
+ return { state: "cc-ended", params: {} };
394
+ if (code === "too_many_starts" || code === "rate_limited" || code === "usage_unavailable" || code === "service_unavailable"
395
+ || code === "upstream_unavailable" || code === "invalid_response" || code === "upstream_error") {
396
+ return { state: "cc-unavailable", params: {} };
397
+ }
398
+ // A start that failed and left its new computer still being removed
399
+ // didn't start; the next attempt meets the removal.
400
+ if (NO_START_WORDS.some((words) => words.test(message)))
401
+ return { state: "cc-no-start", params: {} };
402
+ if (CLEARING_WORDS.some((words) => words.test(message)))
403
+ return { state: "cc-clearing", params: {} };
404
+ if ((failure.status ?? 0) >= 500 || PASSING_WORDS.some((words) => words.test(message)))
405
+ return { state: "cc-unavailable", params: {} };
406
+ if (!included && message)
407
+ return { state: "cc-provider", params: { words: message.replace(/[\s.]+$/, "") + "." } };
408
+ return { state: "cc-no-start", params: {} };
409
+ }
@@ -5,7 +5,6 @@ export const TOOL_SCOPE_SUPPORT = {
5
5
  "openai-compat": "native-and-mcp", mistral: "native-and-mcp", minimax: "native-and-mcp", cerebras: "native-and-mcp",
6
6
  claudeAgent: "mcp", codex: "mcp", geminiAgent: "mcp", kimiAgent: "mcp", droidAgent: "mcp",
7
7
  cursorAgent: "mcp", opencodeGo: "mcp", qwenAgent: "mcp", hermesAgent: "mcp", customAcp: "mcp", antigravityAgent: "mcp",
8
- boxAgent: "none",
9
8
  };
10
9
  export function assertToolScopeSupported(engine, value) {
11
10
  const parsed = parseToolScope(value);
@@ -13,7 +12,7 @@ export function assertToolScopeSupported(engine, value) {
13
12
  throw new Error(parsed.error);
14
13
  const support = TOOL_SCOPE_SUPPORT[engine];
15
14
  if (parsed.scope !== undefined && support !== "native-and-mcp") {
16
- if (support === "none" || support === undefined) {
15
+ if (support === undefined) {
17
16
  if (parsed.scope.allow !== undefined || parsed.scope.deny?.length)
18
17
  throw new Error(`${engine}: tool selection is not supported by this engine. Choose a supported engine.`);
19
18
  }
@@ -16202,10 +16202,6 @@ var remoteMutationSchema = remoteEntrySchema.extend({
16202
16202
  }).optional()
16203
16203
  });
16204
16204
 
16205
- // server/cloud-overflow.ts
16206
- var CLOUD_SEAT_IDLE_STOP_MS = 5 * 6e4;
16207
- var CLOUD_SEAT_SLEEP_RETRY_MAX_MS = 10 * 6e4;
16208
-
16209
16205
  // server/config.ts
16210
16206
  var optionalText = external_exports.string().optional();
16211
16207
  var SSH_ALIAS = /^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/;
@@ -16403,17 +16399,6 @@ var featureConfigSchema = external_exports.object({
16403
16399
  * conversations that share words with the message ride into the turn.
16404
16400
  * Read-only; on unless explicitly switched off — see autoRecallEnabled. */
16405
16401
  autoRecall: external_exports.boolean().optional(),
16406
- /** Idle release for computer claims (#1653): a desktop seat that stays
16407
- * screen-quiet for 90 seconds is released to waiting turns while its
16408
- * holder's turn still lives; the previous holder re-claims directly
16409
- * for 10 minutes and yields to an occupied seat. Off until baked; see
16410
- * computerClaimIdleReleaseEnabled for how to enable it by hand. */
16411
- computerClaimIdleRelease: external_exports.boolean().optional(),
16412
- /** Consented cloud overflow for local computer waits (#1655): a wait
16413
- * may offer a per-second-billed cloud seat, but only through an explicit
16414
- * consent card or a configured allowlist — never a silent Auto default.
16415
- * Off until baked; see cloudOverflowEnabled for how to enable it by hand. */
16416
- cloudOverflow: external_exports.boolean().optional(),
16417
16402
  /** Opt-in shared skills library: one store at the data dir that bots
16418
16403
  * read by assignment instead of per-workspace copies. Off until
16419
16404
  * explicitly enabled — see skillsLibraryEnabled. */
@@ -16539,8 +16524,12 @@ var appConfigSchema = external_exports.object({
16539
16524
  * provider is Boat now and the key is kept for compatibility. */
16540
16525
  box: external_exports.object({ token: optionalText }).optional(),
16541
16526
  vps: vpsConfigSchema.optional(),
16542
- /** Optional OpenCode key; persisted write-only and passed only to its child. */
16543
- opencodeGo: external_exports.object({ apiKey: optionalText }).optional(),
16527
+ /** Optional OpenCode key; persisted write-only and passed only to its child.
16528
+ * `providerKeys`: keys for OpenCode's other providers (Venice, Groq…) by the
16529
+ * environment name OpenCode reads, also write-only and only for its child.
16530
+ * Settings changes go through mergeOpenCodeProviderKeys; the stored copy
16531
+ * is read loosely (storedAppConfigSchema). */
16532
+ opencodeGo: external_exports.object({ apiKey: optionalText, providerKeys: external_exports.record(external_exports.string(), external_exports.string()).optional() }).optional(),
16544
16533
  /** Voice settings and the selected voice id. `provider` picks the
16545
16534
  * engine: "elevenlabs" (default; needs `key`), "fish" (needs its own
16546
16535
  * `fishKey`; `fishModel` picks its speech model), "system" (the Mac's
@@ -16620,15 +16609,6 @@ var appConfigSchema = external_exports.object({
16620
16609
  /** The authorization decision log (server/decision-log.ts): days of month
16621
16610
  * files kept, at least; OMB_DECISION_RETENTION_DAYS wins when set. */
16622
16611
  decisions: external_exports.object({ retentionDays: external_exports.number().int().min(1).max(3650).optional() }).strict().optional(),
16623
- /** #1655 cloud-overflow settings. perSecondCostUsd is the operator's own
16624
- * verified rate: with no price configured the feature stays inert rather
16625
- * than show an invented one. allowlistedThreads carries standing consent
16626
- * for exact thread ids. */
16627
- cloudOverflow: external_exports.object({
16628
- perSecondCostUsd: external_exports.number().positive().max(10).optional(),
16629
- idleStopMs: external_exports.number().int().positive().max(24 * 60 * 6e4).optional(),
16630
- allowlistedThreads: external_exports.array(external_exports.string().min(1)).max(1e3).optional()
16631
- }).strict().optional(),
16632
16612
  localVm: localVmConfigSchema.optional(),
16633
16613
  features: featureConfigSchema.optional(),
16634
16614
  onboarding: onboardingConfigSchema.optional(),
@@ -16643,7 +16623,14 @@ var appConfigSchema = external_exports.object({
16643
16623
  mcpServers: external_exports.record(external_exports.string(), external_exports.unknown()).optional()
16644
16624
  });
16645
16625
  var storedAppConfigSchema = appConfigSchema.extend({
16646
- browserProfiles: storedBrowserProfilesSchema.optional()
16626
+ browserProfiles: storedBrowserProfilesSchema.optional(),
16627
+ /** Read loosely, like mcpServers: a hand-edited provider key that isn't
16628
+ * text is dropped here, and a bad name or key later
16629
+ * (openCodeProviderKeys), never the whole file. */
16630
+ opencodeGo: external_exports.object({
16631
+ apiKey: optionalText,
16632
+ providerKeys: external_exports.record(external_exports.string(), external_exports.unknown()).catch({}).transform((keys) => Object.fromEntries(Object.entries(keys).filter((entry) => typeof entry[1] === "string"))).optional()
16633
+ }).optional()
16647
16634
  });
16648
16635
  var appConfigPatchSchema = appConfigSchema.omit({ instances: true, mcpServers: true, cliStartup: true, customDomain: true }).extend({ threads: threadsPatchSchema.optional(), newBots: newBotsPatchSchema.optional() });
16649
16636
  var jsonObjectSchema = external_exports.record(external_exports.string(), external_exports.json());