openmausbot 0.1.90 → 0.1.92

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 (177) hide show
  1. package/dist/assets/DesktopViewer-Do6lBa4C.js +5 -0
  2. package/dist/assets/{abnfDiagram-O67JEVCF-DUyXd0kL.js → abnfDiagram-O67JEVCF-Cd3X7Hxc.js} +1 -1
  3. package/dist/assets/{arc-q73qM-At.js → arc-DIYc7Ymx.js} +1 -1
  4. package/dist/assets/{architectureDiagram-NJMV4G6O-D33O3Wur.js → architectureDiagram-NJMV4G6O-DBmRGVyP.js} +1 -1
  5. package/dist/assets/{blockDiagram-BEXU5L5S-DLsJeoXC.js → blockDiagram-BEXU5L5S-CPFePSEE.js} +1 -1
  6. package/dist/assets/{c4Diagram-YGBWAQC7-DnaljZnu.js → c4Diagram-YGBWAQC7-DD_DO1qy.js} +1 -1
  7. package/dist/assets/channel-Dtk5Mtic.js +1 -0
  8. package/dist/assets/{chunk-2Q5K7J3B-Da0h2i79.js → chunk-2Q5K7J3B-BNEeNSjX.js} +1 -1
  9. package/dist/assets/{chunk-3FUC2YCW-CfzHi54f.js → chunk-3FUC2YCW-BbinLaqH.js} +2 -2
  10. package/dist/assets/{chunk-5VM5RSS4-LjE4d9Jf.js → chunk-5VM5RSS4-BBgYrgDR.js} +1 -1
  11. package/dist/assets/{chunk-GTNCS2PH-DY-UGmvc.js → chunk-GTNCS2PH-ezXj-kA-.js} +1 -1
  12. package/dist/assets/{chunk-GWA4HPMP-BqeGhyhP.js → chunk-GWA4HPMP-9FxZ3diV.js} +1 -1
  13. package/dist/assets/{chunk-JWPE2WC7-BYa9EBAg.js → chunk-JWPE2WC7-CO-40JpI.js} +1 -1
  14. package/dist/assets/{chunk-NETBCI7D-vTrDtWyb.js → chunk-NETBCI7D-BEhjslYE.js} +1 -1
  15. package/dist/assets/{chunk-WEXAMYUT-ic5tXadM.js → chunk-WEXAMYUT-CNeq4n7_.js} +1 -1
  16. package/dist/assets/{chunk-XXDRQBXY-DvRCY-c6.js → chunk-XXDRQBXY-OsCY7KSi.js} +1 -1
  17. package/dist/assets/{classDiagram-v2-NBCMYWYE-DVbmgz_L.js → classDiagram-v2-NBCMYWYE-CloRUEtk.js} +1 -1
  18. package/dist/assets/{cose-bilkent-JH36ORCC-vdUs2n57.js → cose-bilkent-JH36ORCC-D1BGZhp4.js} +1 -1
  19. package/dist/assets/{cynefin-OW5HDTMX-BvLGlV3U.js → cynefin-OW5HDTMX-CUf4T2CN.js} +1 -1
  20. package/dist/assets/{cynefinDiagram-VND7K2PF-C4GcJQDt.js → cynefinDiagram-VND7K2PF-DPEZrFGA.js} +1 -1
  21. package/dist/assets/{dagre-6A5THRUB-BMSaHpt_.js → dagre-6A5THRUB-bDe6GIv5.js} +1 -1
  22. package/dist/assets/{diagram-22UHCM2B-D5b9UBhC.js → diagram-22UHCM2B-CdcUQz55.js} +1 -1
  23. package/dist/assets/{diagram-3UASUU5V-CIKLlB7Y.js → diagram-3UASUU5V-BZpqGg7c.js} +1 -1
  24. package/dist/assets/{diagram-ATOU4E4O-BzXb-7k7.js → diagram-ATOU4E4O-DvGM6ugp.js} +1 -1
  25. package/dist/assets/{diagram-CDSNMT55-HsTvJ7uT.js → diagram-CDSNMT55-DSTmMm0s.js} +1 -1
  26. package/dist/assets/{diagram-MLGK6HIB-Db9ySNbQ.js → diagram-MLGK6HIB-15cucnMn.js} +1 -1
  27. package/dist/assets/{diagram-MPIPVDR6-D6BKbpqY.js → diagram-MPIPVDR6-tMbuaE6n.js} +1 -1
  28. package/dist/assets/{ebnfDiagram-ZINNZB2B-Bq067L0t.js → ebnfDiagram-ZINNZB2B-BYxYzjHI.js} +1 -1
  29. package/dist/assets/{elk-276RUBZZ-DhaD7fNJ.js → elk-276RUBZZ-v6bj852W.js} +1 -1
  30. package/dist/assets/{erDiagram-OPXOYQCR-fwIhfz36.js → erDiagram-OPXOYQCR-5lBBlnd2.js} +1 -1
  31. package/dist/assets/{flowDiagram-KWPJA3E3-DygIyu0V.js → flowDiagram-KWPJA3E3-CZyl1zIj.js} +1 -1
  32. package/dist/assets/{ganttDiagram-FUAMR5RP-DPYfSO96.js → ganttDiagram-FUAMR5RP-BiAI-ltZ.js} +1 -1
  33. package/dist/assets/{gitGraphDiagram-X574FWY7-DhkHeCle.js → gitGraphDiagram-X574FWY7-BAZISkj3.js} +1 -1
  34. package/dist/assets/index-Cdmjrl28.css +1 -0
  35. package/dist/assets/{index-G1PHKGyh.js → index-DGBaEXrM.js} +1 -1
  36. package/dist/assets/index-DR7YPMkW.js +597 -0
  37. package/dist/assets/{infoDiagram-VRGFBTTK-DstBLxME.js → infoDiagram-VRGFBTTK-B6ooOy-Z.js} +1 -1
  38. package/dist/assets/{ishikawaDiagram-OU5B5YK6-CrIsGgch.js → ishikawaDiagram-OU5B5YK6-BZ9ksi-B.js} +1 -1
  39. package/dist/assets/{journeyDiagram-ZHPQQLJL-CYypGDbB.js → journeyDiagram-ZHPQQLJL-DWqlzIEe.js} +1 -1
  40. package/dist/assets/{kanban-definition-PNTS6WVX-DGjPt8E_.js → kanban-definition-PNTS6WVX-JX6piDwL.js} +1 -1
  41. package/dist/assets/{linear-Dwc3Hrru.js → linear-CmOWzX0P.js} +1 -1
  42. package/dist/assets/{mermaid.core-Dc4OePBL.js → mermaid.core-CF1zwoQ-.js} +5 -5
  43. package/dist/assets/{mindmap-definition-NLK3R4M7-DV-vepj-.js → mindmap-definition-NLK3R4M7-CGh2K4fW.js} +1 -1
  44. package/dist/assets/{pegDiagram-GJSIUBJH-lx5Kj-n5.js → pegDiagram-GJSIUBJH-CDwYN0s7.js} +1 -1
  45. package/dist/assets/{pieDiagram-5QR66LMP-Bq7Vmm15.js → pieDiagram-5QR66LMP-1rfjJocB.js} +1 -1
  46. package/dist/assets/{quadrantDiagram-O4NWA36T-DeqeqOBj.js → quadrantDiagram-O4NWA36T-tKPvPiok.js} +1 -1
  47. package/dist/assets/{railroadDiagram-XR7U4H2S-CXwRQzSC.js → railroadDiagram-XR7U4H2S-BHLm5vXR.js} +1 -1
  48. package/dist/assets/{requirementDiagram-PLB6GJNP-8tAeoB88.js → requirementDiagram-PLB6GJNP-CB45STp7.js} +1 -1
  49. package/dist/assets/{sankeyDiagram-IPEJSGJF-BG5zjGWU.js → sankeyDiagram-IPEJSGJF-55QA_huC.js} +1 -1
  50. package/dist/assets/{sequenceDiagram-PO4LG4MO-DKF3iLcw.js → sequenceDiagram-PO4LG4MO-BckFmQHn.js} +1 -1
  51. package/dist/assets/{sizeCapture-INFHLROL-B-y1qUgT.js → sizeCapture-INFHLROL-BFCOJF11.js} +1 -1
  52. package/dist/assets/{stateDiagram-v2-GCMORJYK-MkzPgxAc.js → stateDiagram-v2-GCMORJYK-CZvmiVVN.js} +1 -1
  53. package/dist/assets/{swimlanes-2SLR337P-DnbaOEwH.js → swimlanes-2SLR337P-BpDUCEKy.js} +1 -1
  54. package/dist/assets/swimlanesDiagram-TC7HE7FX-M0GYW9Y3.js +8 -0
  55. package/dist/assets/{timeline-definition-EJHVYXUP-Bb4xG_Bt.js → timeline-definition-EJHVYXUP-BpxHq6IK.js} +1 -1
  56. package/dist/assets/{usecaseDiagram-POWQR4AR-JgkR9pFf.js → usecaseDiagram-POWQR4AR-Cj66vm02.js} +1 -1
  57. package/dist/assets/{vennDiagram-UO4OBE2U-CIPIrAYe.js → vennDiagram-UO4OBE2U-C9nRkWE_.js} +1 -1
  58. package/dist/assets/{wardleyDiagram-VNRHLVJA-BjCLuUvJ.js → wardleyDiagram-VNRHLVJA-D-me_nWj.js} +1 -1
  59. package/dist/assets/{xychartDiagram-PMCCYNJV-DKgiaYxT.js → xychartDiagram-PMCCYNJV-Dc_fL4mk.js} +1 -1
  60. package/dist/index.html +2 -2
  61. package/dist/novnc-NOTICE.txt +118 -0
  62. package/dist-server/cloud-home-start.js +128 -15
  63. package/dist-server/container-mcp.js +277 -45
  64. package/dist-server/drivers/agents-proxy.js +16 -4
  65. package/dist-server/enterprise/server/index.js +5 -1
  66. package/dist-server/index.js +46703 -39955
  67. package/dist-server/local-computer-proxy.js +128 -7
  68. package/dist-server/mcp-gate.js +11 -5
  69. package/dist-server/model-catalog/models-dev.snapshot.json +7299 -0
  70. package/dist-server/openmausbot.js +4099 -1217
  71. package/dist-server/pair-cli.js +4099 -1217
  72. package/dist-server/server/boat.js +67 -22
  73. package/dist-server/server/bot-overview.js +7 -4
  74. package/dist-server/server/browser-engine.js +10 -1
  75. package/dist-server/server/browser-live.js +46 -2
  76. package/dist-server/server/browser-runtime.js +74 -8
  77. package/dist-server/server/browser-tool-shape.js +6 -4
  78. package/dist-server/server/chief-of-staff.js +1 -1
  79. package/dist-server/server/cloud-home-start.js +136 -26
  80. package/dist-server/server/cloud-home.js +64 -5
  81. package/dist-server/server/cloud-lending.js +167 -0
  82. package/dist-server/server/cloud-move-http.js +268 -0
  83. package/dist-server/server/cloud-move.js +418 -0
  84. package/dist-server/server/cloud-owner.js +196 -0
  85. package/dist-server/server/cloud-secrets-boot.js +6 -0
  86. package/dist-server/server/cloud-secrets.js +65 -0
  87. package/dist-server/server/config.js +215 -17
  88. package/dist-server/server/container-computer.js +3 -2
  89. package/dist-server/server/decider/index.js +238 -0
  90. package/dist-server/server/decider/jev.js +183 -0
  91. package/dist-server/server/decider/log.js +62 -0
  92. package/dist-server/server/decider/relay.js +52 -0
  93. package/dist-server/server/decider/room-routing.js +88 -0
  94. package/dist-server/server/decider/types.js +5 -0
  95. package/dist-server/server/desktop-viewer-targets.js +26 -0
  96. package/dist-server/server/drivers/acp/core.js +167 -21
  97. package/dist-server/server/drivers/acp/opencode-go.js +614 -85
  98. package/dist-server/server/drivers/agents-catalog.js +22 -6
  99. package/dist-server/server/drivers/antigravity-acp.js +69 -11
  100. package/dist-server/server/drivers/antigravity-temp.js +360 -0
  101. package/dist-server/server/drivers/antigravity.js +21 -0
  102. package/dist-server/server/drivers/boatagent.js +20 -12
  103. package/dist-server/server/drivers/chatgpt-plan-auth.js +481 -0
  104. package/dist-server/server/drivers/claude.js +313 -34
  105. package/dist-server/server/drivers/codex.js +228 -18
  106. package/dist-server/server/drivers/grok.js +2 -2
  107. package/dist-server/server/drivers/mistral.js +4 -3
  108. package/dist-server/server/drivers/openai-chat-protocol.js +4 -3
  109. package/dist-server/server/drivers/openai-chat.js +41 -3
  110. package/dist-server/server/drivers/openai-compat.js +73 -23
  111. package/dist-server/server/engine-install.js +46 -12
  112. package/dist-server/server/env-path.js +15 -3
  113. package/dist-server/server/group-goal-run.js +6 -4
  114. package/dist-server/server/harness/registry.js +2 -2
  115. package/dist-server/server/harness-capabilities.js +2 -0
  116. package/dist-server/server/included-services.js +70 -0
  117. package/dist-server/server/index.js +1753 -263
  118. package/dist-server/server/lending-memory.js +479 -0
  119. package/dist-server/server/local-vm-idle.js +24 -6
  120. package/dist-server/server/managed-desktop.js +3 -0
  121. package/dist-server/server/mcp-bridge.js +5 -3
  122. package/dist-server/server/mcp-http.js +6 -3
  123. package/dist-server/server/mcp-oauth-discovery.js +133 -0
  124. package/dist-server/server/mcp-oauth-store.js +70 -0
  125. package/dist-server/server/mcp-oauth.js +550 -0
  126. package/dist-server/server/mcp-probe.js +14 -1
  127. package/dist-server/server/mcp-registry.js +43 -0
  128. package/dist-server/server/memory-upkeep.js +94 -84
  129. package/dist-server/server/model-catalog/catalog.js +374 -0
  130. package/dist-server/server/model-catalog/presets.js +176 -0
  131. package/dist-server/server/model-catalog/trim.js +219 -0
  132. package/dist-server/server/model-context-window.js +4 -4
  133. package/dist-server/server/package-export.js +17 -10
  134. package/dist-server/server/package-import.js +6 -3
  135. package/dist-server/server/package-parts.js +3 -1
  136. package/dist-server/server/provider-key-check.js +4 -2
  137. package/dist-server/server/recent-work.js +1 -1
  138. package/dist-server/server/request-auth.js +25 -0
  139. package/dist-server/server/routes/antigravity-leftovers.js +45 -0
  140. package/dist-server/server/routes/bot-memory.js +31 -6
  141. package/dist-server/server/routes/decider.js +19 -0
  142. package/dist-server/server/routes/desktop-viewer.js +194 -0
  143. package/dist-server/server/routine-requests.js +1 -1
  144. package/dist-server/server/routines.js +1 -1
  145. package/dist-server/server/sessions.js +56 -9
  146. package/dist-server/server/shared-computers.js +116 -30
  147. package/dist-server/server/skills.js +25 -10
  148. package/dist-server/server/store.js +79 -124
  149. package/dist-server/server/surface.js +13 -0
  150. package/dist-server/server/system-prompt.js +11 -0
  151. package/dist-server/server/team-backup.js +6 -2
  152. package/dist-server/server/team-manifest.js +11 -3
  153. package/dist-server/server/thread-events.js +2 -0
  154. package/dist-server/server/thread-starters.js +33 -0
  155. package/dist-server/server/thread-title.js +46 -0
  156. package/dist-server/server/tts/elevenlabs.js +11 -6
  157. package/dist-server/server/tts/fish.js +4 -5
  158. package/dist-server/server/tts/index.js +20 -11
  159. package/dist-server/server/vps-computer.js +79 -12
  160. package/dist-server/server/webhooks.js +18 -1
  161. package/dist-server/server/workspace-backup-policy.js +25 -3
  162. package/dist-server/server/workspace-backup.js +28 -8
  163. package/dist-server/server/workspace.js +65 -15
  164. package/dist-server/shared/cloud-home.js +4 -0
  165. package/dist-server/shared/package-format.js +30 -4
  166. package/dist-server/shared/team-backup.js +20 -3
  167. package/dist-server/shared/workspace-backup-client.js +1 -0
  168. package/dist-server/src/lib/citations.js +177 -0
  169. package/dist-server/src/lib/composer-attachments.js +7 -1
  170. package/dist-server/vps-container-mcp.js +278 -45
  171. package/dist-server/workspace-backup.worker.js +184 -38
  172. package/enterprise/server/index.js +5 -1
  173. package/package.json +1 -1
  174. package/dist/assets/channel-BCuyHjFV.js +0 -1
  175. package/dist/assets/index-CRvo-DS2.css +0 -1
  176. package/dist/assets/index-CWYGlA63.js +0 -588
  177. package/dist/assets/swimlanesDiagram-TC7HE7FX-BZQ07DGE.js +0 -8
@@ -0,0 +1,65 @@
1
+ // The OMB Cloud home's secrets, and how the server gets them
2
+ // (docs/cloud-pro.md). This module imports nothing but node:fs, so the
3
+ // server can read them before anything else it loads can start a process
4
+ // (cloud-secrets-boot.ts, the server's first import).
5
+ import { closeSync, readFileSync } from "node:fs";
6
+ /** The Cloud home's secrets: the signing secret and the included services'
7
+ * relay tokens (server/included-services.ts). The Admin sets them as Fly app
8
+ * secrets, which reach the machine as the launcher's environment; the
9
+ * launcher never puts them in a child's environment, because
10
+ * /proc/<pid>/environ keeps a process's starting environment for anything
11
+ * running as the same user to read. It hands them to the server over an
12
+ * inherited pipe instead (CLOUD_SECRETS_FD_ENV names its descriptor). */
13
+ export const CLOUD_HOME_SECRET_KEYS = ["OMB_CLOUD_BOOTSTRAP_SECRET", "OMB_CLOUD_BOAT_TOKEN", "OMB_CLOUD_VOICE_TOKEN", "OMB_CLOUD_DECIDER_TOKEN"];
14
+ export const CLOUD_SECRETS_FD_ENV = "OMB_CLOUD_SECRETS_FD";
15
+ /** The secrets present in an environment (the launcher's). */
16
+ export function cloudHomeSecrets(env) {
17
+ return Object.fromEntries(CLOUD_HOME_SECRET_KEYS.flatMap((key) => env[key] ? [[key, env[key]]] : []));
18
+ }
19
+ /** An environment with no Cloud home secret in it. */
20
+ export function withoutCloudSecrets(env) {
21
+ const kept = { ...env };
22
+ for (const key of CLOUD_HOME_SECRET_KEYS)
23
+ delete kept[key];
24
+ return kept;
25
+ }
26
+ function invalid(why) {
27
+ throw new Error(`Cloud home configuration is invalid: ${why}.`);
28
+ }
29
+ /** At server startup: the secrets the launcher handed over its pipe (read
30
+ * to the end and closed, so no child inherits it), or none when the server
31
+ * was started another way and reads its environment as before. Only the
32
+ * known secret names are taken. */
33
+ export function takeCloudSecrets(env = process.env) {
34
+ const named = env[CLOUD_SECRETS_FD_ENV];
35
+ if (named === undefined)
36
+ return {};
37
+ delete env[CLOUD_SECRETS_FD_ENV];
38
+ const fd = Number(named);
39
+ if (!Number.isInteger(fd) || fd < 3 || fd > 1024)
40
+ invalid(`${CLOUD_SECRETS_FD_ENV} must name an inherited descriptor`);
41
+ let raw;
42
+ try {
43
+ raw = readFileSync(fd, "utf8");
44
+ }
45
+ catch {
46
+ invalid("the Cloud home's secrets could not be read from the launcher");
47
+ }
48
+ finally {
49
+ try {
50
+ closeSync(fd);
51
+ }
52
+ catch { /* already closed */ }
53
+ }
54
+ let parsed;
55
+ try {
56
+ parsed = JSON.parse(raw);
57
+ }
58
+ catch {
59
+ parsed = null;
60
+ }
61
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
62
+ invalid("the Cloud home's secrets from the launcher are not readable");
63
+ const record = parsed;
64
+ return Object.fromEntries(CLOUD_HOME_SECRET_KEYS.flatMap((key) => typeof record[key] === "string" && record[key] ? [[key, record[key]]] : []));
65
+ }
@@ -14,10 +14,14 @@ import { PROVIDER_ICON_PRESETS, providerIconError } from "../shared/provider-ico
14
14
  import { isRemoteMcpServer, parseStoredMcpServer } from "./mcp-registry.js";
15
15
  import { parseJson, schemaIssue } from "./schema.js";
16
16
  import { CLOUD_SEAT_IDLE_STOP_MS } from "./cloud-overflow.js";
17
+ import { cloudHomeConfigured } from "./cloud-home.js";
17
18
  const optionalText = z.string().optional();
18
19
  const SSH_ALIAS = /^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/;
19
20
  const LEGACY_BROWSER_PROFILE_ID = /^[A-Za-z0-9_-]{1,40}$/;
20
21
  const BROWSER_PROFILE_ID = /^[a-z0-9_-]{1,40}$/;
22
+ /** Fish Audio speech models (`tts.fishModel`). `s2.1-pro-free` is Fish's
23
+ * free developer tier; an unset model means `s2.1-pro`. */
24
+ export const FISH_TTS_MODELS = ["s2.1-pro", "s2.1-pro-free"];
21
25
  export const DEFAULT_ROOM_TURN_TIMEOUT_MINUTES = 5;
22
26
  export const MIN_ROOM_TURN_TIMEOUT_MINUTES = 1;
23
27
  export const MAX_ROOM_TURN_TIMEOUT_MINUTES = 1_440;
@@ -35,6 +39,11 @@ export const DEFAULT_LOCAL_VM_MODE = "shared";
35
39
  export const DEFAULT_LOCAL_VM_MAX_INSTANCES = 2;
36
40
  export const MIN_LOCAL_VM_MAX_INSTANCES = 1;
37
41
  export const MAX_LOCAL_VM_MAX_INSTANCES = 8;
42
+ /** Idle window before a Local VM's disposable container is recycled. The
43
+ * default keeps the historical 8-hour window for existing configs. */
44
+ export const DEFAULT_LOCAL_VM_IDLE_TIMEOUT_MINUTES = 480;
45
+ export const MIN_LOCAL_VM_IDLE_TIMEOUT_MINUTES = 5;
46
+ export const MAX_LOCAL_VM_IDLE_TIMEOUT_MINUTES = 1_440;
38
47
  export function isValidSshAlias(value) {
39
48
  return typeof value === "string" && SSH_ALIAS.test(value);
40
49
  }
@@ -111,6 +120,12 @@ const localVmConfigSchema = z.object({
111
120
  .min(MIN_LOCAL_VM_MAX_INSTANCES)
112
121
  .max(MAX_LOCAL_VM_MAX_INSTANCES)
113
122
  .optional(),
123
+ idleTimeoutMinutes: z
124
+ .number()
125
+ .int()
126
+ .min(MIN_LOCAL_VM_IDLE_TIMEOUT_MINUTES)
127
+ .max(MAX_LOCAL_VM_IDLE_TIMEOUT_MINUTES)
128
+ .optional(),
114
129
  });
115
130
  /** A named, shareable browser session ("Work", "Client A"). The id names a
116
131
  * durable Electron partition; user-controlled characters never reach it. */
@@ -301,6 +316,10 @@ const onboardingConfigSchema = z.object({
301
316
  version: z.number().int().min(0).max(1000).optional(),
302
317
  reelSeen: z.boolean().optional(),
303
318
  hintsSeen: z.array(z.string().trim().min(1).max(60)).max(100).optional(),
319
+ /** OMB Cloud home only: when a bot's turn first finished on this machine
320
+ * (cloud-home.ts firstCloudTurnPatch). Written by the server, read by the
321
+ * Cloud's setup checklist. */
322
+ firstTurnAt: z.string().trim().max(40).optional(),
304
323
  }).strict();
305
324
  const instanceConfigSchema = z.object({
306
325
  driver: z.string().min(1),
@@ -313,6 +332,7 @@ const instanceConfigSchema = z.object({
313
332
  ]).optional(),
314
333
  environment: z.record(z.string(), z.string()).optional(),
315
334
  enabled: z.boolean().optional(),
335
+ access: z.enum(["subscription", "custom", "api"]).optional(),
316
336
  config: z.json().optional(),
317
337
  });
318
338
  const instanceConfigMapSchema = z.record(z.string(), instanceConfigSchema);
@@ -372,7 +392,12 @@ const appConfigSchema = z.object({
372
392
  /** Anthropic API key for Claude Code billed per token, handed only to
373
393
  * Claude instances; `url` only for a proxy or a test double. Never a
374
394
  * personal-login OAuth token. */
375
- anthropic: z.object({ key: optionalText, url: optionalText }).optional(),
395
+ anthropic: z.object({ key: optionalText, url: optionalText, everyClaudeBot: z.boolean().optional() }).optional(),
396
+ /** OpenAI's own API key, for the `openai` instance only. Codex keeps its
397
+ * own ChatGPT or API login and never reads this. */
398
+ openai: z.object({ key: optionalText }).optional(),
399
+ /** OpenRouter key, for the `openrouter` instance only. */
400
+ openrouter: z.object({ key: optionalText }).optional(),
376
401
  /** Monthly spend limit for the whole workspace, against the cost engines
377
402
  * report to the usage ledger. Enforced only with the `budgets` entitlement. */
378
403
  budgets: z
@@ -411,8 +436,8 @@ const appConfigSchema = z.object({
411
436
  opencodeGo: z.object({ apiKey: optionalText }).optional(),
412
437
  /** Voice settings and the selected voice id. `provider` picks the
413
438
  * engine: "elevenlabs" (default; needs `key`), "fish" (needs its own
414
- * `fishKey`), "system" (the Mac's built-in voices, no key), or
415
- * "xai" (Grok TTS, reusing `xai.key`), or
439
+ * `fishKey`; `fishModel` picks its speech model), "system" (the Mac's
440
+ * built-in voices, no key), "xai" (Grok TTS, reusing `xai.key`), or
416
441
  * "chatterbox" (a local OpenAI-compatible Chatterbox server; `baseUrl`
417
442
  * and `model` are settings, not secrets). Cloud keys stay separate so
418
443
  * switching providers never overwrites or misuses the other key. */
@@ -428,6 +453,25 @@ const appConfigSchema = z.object({
428
453
  .refine((value) => !value || /^https?:\/\//i.test(value), "the Chatterbox server address must start with http:// or https://")
429
454
  .optional(),
430
455
  model: optionalText,
456
+ fishModel: z.enum(FISH_TTS_MODELS).optional(),
457
+ }).optional(),
458
+ /** The decision model (server/decider): a fast classifier that picks
459
+ * things for bots, starting with who answers a room message. `key` is
460
+ * write-only like every credential; `baseUrl` points at a Jev-compatible
461
+ * server instead of TypeSafe's (an operator setting with no UI); `jobs`
462
+ * switches each decision on or off. Not `decisions`: that section is the
463
+ * authorization log's retention. */
464
+ decider: z.object({
465
+ enabled: z.boolean().optional(),
466
+ provider: z.enum(["jev", "off"]).optional(),
467
+ key: optionalText,
468
+ baseUrl: z
469
+ .string()
470
+ .trim()
471
+ .max(2048)
472
+ .refine((value) => !value || /^https?:\/\//i.test(value), "the decision model address must start with http:// or https://")
473
+ .optional(),
474
+ jobs: z.object({ roomRouting: z.boolean().optional() }).optional(),
431
475
  }).optional(),
432
476
  /** Avatar provider credentials stay separate; choosing a router never reuses a cloud key. */
433
477
  imageGen: z.object({
@@ -619,6 +663,9 @@ export function localVmMode(cfg) {
619
663
  export function localVmMaxInstances(cfg) {
620
664
  return cfg.localVm?.maxInstances ?? DEFAULT_LOCAL_VM_MAX_INSTANCES;
621
665
  }
666
+ export function localVmIdleTimeoutMinutes(cfg) {
667
+ return cfg.localVm?.idleTimeoutMinutes ?? DEFAULT_LOCAL_VM_IDLE_TIMEOUT_MINUTES;
668
+ }
622
669
  /** On by default; only an explicit `false` (the Settings toggle, or a legacy
623
670
  * `skillRecorder: false` carried over at startup) switches it off. */
624
671
  export function skillAuthoringEnabled(cfg) {
@@ -645,9 +692,13 @@ export function routinesInConversationEnabled(cfg) {
645
692
  return cfg.features?.routinesInConversation === true;
646
693
  }
647
694
  /** Workspace-level gate for the experimental built-in browser. A bot's own
648
- * switch sits under it, so either can withhold the browser. */
649
- export function builtInBrowserEnabled(cfg) {
650
- return cfg.features?.browser === true;
695
+ * switch sits under it, so either can withhold the browser. Off until the
696
+ * desktop's first-run welcome turns it on. A Cloud home (cloud-home.ts)
697
+ * skips that welcome, ships the browser in its image and has no other screen
698
+ * of its own, so there it is on unless the person switched it off. */
699
+ export function builtInBrowserEnabled(cfg, env = process.env) {
700
+ const chosen = cfg.features?.browser;
701
+ return chosen === undefined ? cloudHomeConfigured(env) : chosen === true;
651
702
  }
652
703
  /** Opt-in computer sharing: the routes, the agent tools, the advertised
653
704
  * capability and the desktop connector. Off unless an explicit `true` turns
@@ -723,6 +774,8 @@ export const FLEET_NEUTRAL_KEYS = new Set([
723
774
  "profile",
724
775
  "language",
725
776
  "tts",
777
+ // no engine reads it: the harness asks it before a turn starts
778
+ "decider",
726
779
  "imageGen",
727
780
  "vps",
728
781
  "rooms",
@@ -842,6 +895,12 @@ export function loadConfig() {
842
895
  cfg.anthropic.key = process.env.OMB_ANTHROPIC_API_KEY;
843
896
  if (process.env.OMB_ANTHROPIC_API_URL !== undefined)
844
897
  cfg.anthropic.url = process.env.OMB_ANTHROPIC_API_URL;
898
+ cfg.openai = { ...cfg.openai };
899
+ if (process.env.OMB_OPENAI_API_KEY !== undefined)
900
+ cfg.openai.key = process.env.OMB_OPENAI_API_KEY;
901
+ cfg.openrouter = { ...cfg.openrouter };
902
+ if (process.env.OMB_OPENROUTER_API_KEY !== undefined)
903
+ cfg.openrouter.key = process.env.OMB_OPENROUTER_API_KEY;
845
904
  cfg.openaiCompat = { ...cfg.openaiCompat };
846
905
  if (process.env.OPENAI_COMPAT_API_KEY !== undefined)
847
906
  cfg.openaiCompat.key = process.env.OPENAI_COMPAT_API_KEY;
@@ -871,6 +930,9 @@ export function loadConfig() {
871
930
  cfg.tts.voice = presetVoice;
872
931
  if (process.env.OMB_FISH_AUDIO_API_KEY !== undefined)
873
932
  cfg.tts.fishKey = process.env.OMB_FISH_AUDIO_API_KEY;
933
+ cfg.decider = { ...cfg.decider };
934
+ if (process.env.OMB_JEV_API_KEY !== undefined)
935
+ cfg.decider.key = process.env.OMB_JEV_API_KEY;
874
936
  cfg.imageGen = { ...cfg.imageGen };
875
937
  if (process.env.OMB_OPENAI_IMAGE_KEY !== undefined)
876
938
  cfg.imageGen.key = process.env.OMB_OPENAI_IMAGE_KEY;
@@ -901,11 +963,14 @@ export function syncCredentialEnv(patch) {
901
963
  [patch.mistral?.key, "MISTRAL_API_KEY"],
902
964
  [patch.anthropic?.key, "OMB_ANTHROPIC_API_KEY"],
903
965
  [patch.openaiCompat?.key, "OPENAI_COMPAT_API_KEY"],
966
+ [patch.openai?.key, "OMB_OPENAI_API_KEY"],
967
+ [patch.openrouter?.key, "OMB_OPENROUTER_API_KEY"],
904
968
  [patch.composio?.apiKey, "COMPOSIO_API_KEY"],
905
969
  [patch.box?.token, "BOX_TOKEN"],
906
970
  [patch.opencodeGo?.apiKey, "OPENCODE_API_KEY"],
907
971
  [patch.tts?.key, "OMB_TTS_KEY"],
908
972
  [patch.tts?.fishKey, "OMB_FISH_AUDIO_API_KEY"],
973
+ [patch.decider?.key, "OMB_JEV_API_KEY"],
909
974
  [patch.imageGen?.key, "OMB_OPENAI_IMAGE_KEY"],
910
975
  [patch.imageGen?.customApiKey, "OMB_CUSTOM_IMAGE_KEY"],
911
976
  ];
@@ -948,14 +1013,23 @@ export const WORKSPACE_CREDENTIAL_ENV = [
948
1013
  "OMB_HOSTED_MODELS",
949
1014
  "OPENAI_COMPAT_API_KEY",
950
1015
  "OPENAI_COMPAT_URL",
1016
+ "OMB_OPENAI_API_KEY",
1017
+ "OMB_OPENROUTER_API_KEY",
951
1018
  "BOX_TOKEN",
952
1019
  "OPENCODE_API_KEY",
953
1020
  "OMB_TTS_KEY",
954
1021
  "OMB_FISH_AUDIO_API_KEY",
1022
+ "OMB_JEV_API_KEY",
955
1023
  "OMB_OPENAI_IMAGE_KEY",
956
1024
  "OMB_CUSTOM_IMAGE_KEY",
957
1025
  "COMPOSIO_API_KEY",
958
1026
  "OMB_COMPOSIO_BROKER_TOKEN",
1027
+ // Cloud Pro's included Boat, voice and decision relay tokens
1028
+ // (included-services.ts), used only in-process by the Boat, voice and
1029
+ // decider modules.
1030
+ "OMB_CLOUD_BOAT_TOKEN",
1031
+ "OMB_CLOUD_VOICE_TOKEN",
1032
+ "OMB_CLOUD_DECIDER_TOKEN",
959
1033
  // Harness-private filesystem hints are not credentials themselves, but
960
1034
  // exposing them to a shell-capable agent points straight at app-owned
961
1035
  // state. The built-in browser master is delivered privately in memory.
@@ -1037,7 +1111,7 @@ export function saveConfig(patch, options = {}) {
1037
1111
  const storedProfiles = storedBrowserProfilesSchema.safeParse(disk.browserProfiles);
1038
1112
  if (storedProfiles.success)
1039
1113
  disk.browserProfiles = storedProfiles.data;
1040
- for (const key of ["xai", "anthropic", "mistral", "openaiCompat", "composio", "box", "opencodeGo", "tts", "imageGen", "profile", "rooms", "threads", "context", "memory", "localVm", "features", "cloudOverflow", "budgets", "billing", "decisions", "onboarding", "browserEngine", "newBots"]) {
1114
+ for (const key of ["xai", "anthropic", "mistral", "openai", "openrouter", "openaiCompat", "composio", "box", "opencodeGo", "tts", "decider", "imageGen", "profile", "rooms", "threads", "context", "memory", "localVm", "features", "cloudOverflow", "budgets", "billing", "decisions", "onboarding", "browserEngine", "newBots"]) {
1041
1115
  const section = checkedPatch[key];
1042
1116
  if (!section)
1043
1117
  continue;
@@ -1223,13 +1297,63 @@ export function persistableInstanceConfigs(cfg) {
1223
1297
  }
1224
1298
  return map;
1225
1299
  }
1300
+ /** Claude Code variables that choose where a turn goes and how it signs in.
1301
+ * An instance that sets any of them (a router, a gateway, a cloud platform)
1302
+ * owns that routing and credential pair outright. */
1303
+ const CLAUDE_ROUTING_ENV = [
1304
+ "ANTHROPIC_BASE_URL",
1305
+ "ANTHROPIC_AUTH_TOKEN",
1306
+ "ANTHROPIC_API_KEY",
1307
+ "ANTHROPIC_CUSTOM_HEADERS",
1308
+ "CLAUDE_CODE_USE_BEDROCK",
1309
+ "CLAUDE_CODE_USE_VERTEX",
1310
+ "CLAUDE_CODE_USE_FOUNDRY",
1311
+ ];
1312
+ /** Whether a Claude instance brings its own endpoint or credential. The
1313
+ * workspace Anthropic key must never reach it: the key would be sent as
1314
+ * x-api-key to that instance's host (a third-party router), or would
1315
+ * silently re-route its turns to the workspace URL. */
1316
+ export function claudeInstanceOwnsRouting(environment) {
1317
+ return CLAUDE_ROUTING_ENV.some((key) => typeof environment?.[key] === "string" && environment[key] !== "");
1318
+ }
1319
+ const sameUrl = (a, b) => a.trim().replace(/\/+$/u, "") === b.trim().replace(/\/+$/u, "");
1320
+ /** Whether an instance brings its own endpoint or credential, so the
1321
+ * workspace key for its driver must never reach it: that key would be sent
1322
+ * to the instance's own host (a third-party router or proxy). The same rule
1323
+ * as a Claude router instance, for the API-key engines. An instance on the
1324
+ * workspace's own endpoint with nothing of its own still gets the key. */
1325
+ export function instanceOwnsRouting(cfg, entry, routingDefaults) {
1326
+ const config = typeof entry.config === "object" && entry.config !== null && !Array.isArray(entry.config)
1327
+ ? entry.config
1328
+ : {};
1329
+ const own = (value) => typeof value === "string" && value.trim() !== "";
1330
+ const ownUrl = (...workspace) => own(config.url)
1331
+ && !workspace.some((url) => url !== undefined && sameUrl(config.url, url));
1332
+ const compatKeyEnv = routingDefaults?.apiKeyEnv ?? "OPENAI_COMPAT_API_KEY";
1333
+ switch (entry.driver) {
1334
+ case "claudeAgent":
1335
+ return claudeInstanceOwnsRouting(entry.environment);
1336
+ case "openai-compat":
1337
+ return own(config.key) || own(entry.environment?.OPENAI_COMPAT_API_KEY)
1338
+ || own(entry.environment?.[compatKeyEnv])
1339
+ || (own(config.apiKeyEnv) && config.apiKeyEnv !== compatKeyEnv)
1340
+ || ownUrl(routingDefaults?.url || cfg.openaiCompat?.url || process.env.OPENAI_COMPAT_URL || "https://openrouter.ai/api/v1");
1341
+ case "mistral":
1342
+ return own(entry.environment?.MISTRAL_API_KEY) || ownUrl("https://api.mistral.ai/v1");
1343
+ case "grok":
1344
+ return own(entry.environment?.XAI_API_KEY) || (own(config.apiKeyEnv) && config.apiKeyEnv !== "XAI_API_KEY")
1345
+ || ownUrl("https://api.x.ai/v1", cfg.xai?.url);
1346
+ default:
1347
+ return false;
1348
+ }
1349
+ }
1226
1350
  /** The credential env instanceConfigs() injects for one driver at runtime.
1227
1351
  * Each secret goes only to the driver that actually reads it: the API-key
1228
1352
  * Grok driver reads XAI_API_KEY, the Computer driver reads BOX_TOKEN, and
1229
1353
  * OpenCode reads OPENCODE_API_KEY. Every other engine brings its own
1230
1354
  * login, so handing it a key it never uses would only put that key in the
1231
1355
  * environment of an unrelated child process. */
1232
- function injectedEnvironment(cfg, driver) {
1356
+ function injectedEnvironment(cfg, instanceId, driver) {
1233
1357
  const environment = new Map();
1234
1358
  if (driver === "mistral" && cfg.mistral?.key)
1235
1359
  environment.set("MISTRAL_API_KEY", cfg.mistral.key);
@@ -1238,21 +1362,64 @@ function injectedEnvironment(cfg, driver) {
1238
1362
  // The workspace Anthropic key reaches Claude Code as the variable it
1239
1363
  // reads, carried in the instance environment so the driver can tell a
1240
1364
  // deliberate workspace key from one riding along in the parent's env.
1241
- if (driver === "claudeAgent" && cfg.anthropic?.key)
1365
+ // By default it runs only the `claudeApi` instance, so signed-in Claude
1366
+ // bots stay on their plan; `everyClaudeBot` (the pre-split behaviour, and
1367
+ // what a hosted workspace seeded with only a key relies on) runs every
1368
+ // other Claude instance on it too.
1369
+ const anthropicHere = driver === "claudeAgent" && Boolean(cfg.anthropic?.key)
1370
+ && (cfg.anthropic?.everyClaudeBot === false ? instanceId === CLAUDE_API_INSTANCE : instanceId !== CLAUDE_API_INSTANCE);
1371
+ if (anthropicHere)
1242
1372
  environment.set("ANTHROPIC_API_KEY", cfg.anthropic.key);
1243
- if (driver === "claudeAgent" && cfg.anthropic?.key && cfg.anthropic.url)
1373
+ if (anthropicHere && cfg.anthropic.url)
1244
1374
  environment.set("ANTHROPIC_BASE_URL", cfg.anthropic.url);
1245
- if (driver === "openai-compat" && cfg.openaiCompat?.key)
1246
- environment.set("OPENAI_COMPAT_API_KEY", cfg.openaiCompat.key);
1247
- if (driver === "openai-compat" && cfg.openaiCompat?.url)
1248
- environment.set("OPENAI_COMPAT_URL", cfg.openaiCompat.url);
1375
+ // OpenAI and OpenRouter have their own instances and keys; the workspace
1376
+ // OpenAI-compatible key and URL belong to every other openai-compat
1377
+ // instance ("Other" in Settings).
1378
+ if (driver === "openai-compat" && instanceId === OPENAI_API_INSTANCE) {
1379
+ if (cfg.openai?.key)
1380
+ environment.set("OMB_OPENAI_API_KEY", cfg.openai.key);
1381
+ }
1382
+ else if (driver === "openai-compat" && instanceId === OPENROUTER_API_INSTANCE) {
1383
+ if (cfg.openrouter?.key)
1384
+ environment.set("OMB_OPENROUTER_API_KEY", cfg.openrouter.key);
1385
+ }
1386
+ else {
1387
+ if (driver === "openai-compat" && cfg.openaiCompat?.key)
1388
+ environment.set("OPENAI_COMPAT_API_KEY", cfg.openaiCompat.key);
1389
+ if (driver === "openai-compat" && cfg.openaiCompat?.url)
1390
+ environment.set("OPENAI_COMPAT_URL", cfg.openaiCompat.url);
1391
+ }
1249
1392
  // driverKind "boxAgent" and env BOX_TOKEN keep their historical names.
1393
+ // Only the person's own token: without one the driver itself falls back
1394
+ // to Cloud Pro's included token, which never enters an environment map.
1250
1395
  if (driver === "boxAgent" && cfg.box?.token)
1251
1396
  environment.set("BOX_TOKEN", cfg.box.token);
1252
1397
  if (driver === "opencodeGo" && cfg.opencodeGo?.apiKey)
1253
1398
  environment.set("OPENCODE_API_KEY", cfg.opencodeGo.apiKey);
1254
1399
  return environment;
1255
1400
  }
1401
+ /** One instance per pasted provider key, each running only on its own key.
1402
+ * They sit in the picker's "API keys" group and stay hidden until their key
1403
+ * is saved (unavailable without one). */
1404
+ export const OPENAI_API_INSTANCE = "openai";
1405
+ export const OPENROUTER_API_INSTANCE = "openrouter";
1406
+ export const XAI_API_INSTANCE = "xaiApi";
1407
+ export const CLAUDE_API_INSTANCE = "claudeApi";
1408
+ const API_KEY_FLEET = {
1409
+ [OPENAI_API_INSTANCE]: {
1410
+ driver: "openai-compat", displayName: "OpenAI", access: "api", icon: { kind: "preset", preset: "openai" },
1411
+ config: { url: "https://api.openai.com/v1", apiKeyEnv: "OMB_OPENAI_API_KEY", catalog: "openai" },
1412
+ },
1413
+ [CLAUDE_API_INSTANCE]: {
1414
+ driver: "claudeAgent", displayName: "Claude (API key)", access: "api", icon: { kind: "preset", preset: "anthropic" },
1415
+ config: { requireApiKey: true },
1416
+ },
1417
+ [XAI_API_INSTANCE]: { driver: "grok", displayName: "xAI", access: "api", icon: { kind: "preset", preset: "xai" } },
1418
+ [OPENROUTER_API_INSTANCE]: {
1419
+ driver: "openai-compat", displayName: "OpenRouter", access: "api", icon: { kind: "preset", preset: "openrouter" },
1420
+ config: { url: "https://openrouter.ai/api/v1", apiKeyEnv: "OMB_OPENROUTER_API_KEY" },
1421
+ },
1422
+ };
1256
1423
  // Default fleet: one instance per built-in driver (upstream
1257
1424
  // defaultInstanceIdForDriver — instanceId defaults to the driver kind).
1258
1425
  // Config-file keys are injected as per-instance environment so drivers
@@ -1280,11 +1447,13 @@ export function instanceConfigs(cfg) {
1280
1447
  cursor: { driver: "cursorAgent" },
1281
1448
  claude: { driver: "claudeAgent" },
1282
1449
  codex: { driver: "codex" },
1450
+ chatgpt: { driver: "codex", displayName: "ChatGPT plan", config: { authMode: "chatgpt-plan" } },
1283
1451
  antigravity: { driver: "antigravityAgent" },
1284
1452
  opencodeGo: { driver: "opencodeGo" },
1285
1453
  computer: { driver: "boxAgent" },
1286
1454
  openaiCompat: { driver: "openai-compat" },
1287
1455
  mistral: { driver: "mistral" },
1456
+ ...API_KEY_FLEET,
1288
1457
  qwen: { driver: "qwenAgent" },
1289
1458
  hermes: { driver: "hermesAgent" },
1290
1459
  pi: { driver: "piAgent" },
@@ -1298,9 +1467,11 @@ export function instanceConfigs(cfg) {
1298
1467
  // never see. Custom-only engines stay in CUSTOM_ONLY so a one-off test map
1299
1468
  // is not expanded, matching the claude/grok/codex product-fleet probe.
1300
1469
  const PRODUCT_FLEET_ADDITIONS = {
1470
+ chatgpt: { driver: "codex", displayName: "ChatGPT plan", config: { authMode: "chatgpt-plan" } },
1301
1471
  cursor: { driver: "cursorAgent" },
1302
1472
  openaiCompat: { driver: "openai-compat" },
1303
1473
  mistral: { driver: "mistral" },
1474
+ ...API_KEY_FLEET,
1304
1475
  ...CUSTOM_ONLY,
1305
1476
  };
1306
1477
  const configured = cfg.instances && Object.keys(cfg.instances).length ? cfg.instances : null;
@@ -1314,6 +1485,22 @@ export function instanceConfigs(cfg) {
1314
1485
  map[id] = { ...entry };
1315
1486
  }
1316
1487
  }
1488
+ // A saved `instances` map (any engine edit persists the whole fleet) keeps
1489
+ // these entries without their built-in routing. Re-apply it on every load
1490
+ // so the OpenAI and OpenRouter instances can never fall back to the shared
1491
+ // OpenAI-compatible key and URL; saved fields still win.
1492
+ for (const [id, builtIn] of Object.entries(API_KEY_FLEET)) {
1493
+ const saved = map[id];
1494
+ if (!saved || saved.driver !== builtIn.driver)
1495
+ continue;
1496
+ const savedConfig = typeof saved.config === "object" && saved.config !== null && !Array.isArray(saved.config) ? saved.config : {};
1497
+ map[id] = { ...builtIn, ...saved, config: { ...builtIn.config, ...savedConfig } };
1498
+ }
1499
+ if (map.chatgpt?.driver === "codex" && !map.chatgpt.config?.cli) {
1500
+ const cli = map.codex?.config?.cli;
1501
+ if (typeof cli === "string" && cli)
1502
+ map.chatgpt = { ...map.chatgpt, config: { ...map.chatgpt.config, cli } };
1503
+ }
1317
1504
  for (const [id, sourceEntry] of Object.entries(map)) {
1318
1505
  // instanceConfigs() builds a transient runtime map. Never mutate the
1319
1506
  // caller's persisted entries while injecting workspace defaults: doing so
@@ -1321,14 +1508,25 @@ export function instanceConfigs(cfg) {
1321
1508
  const entry = { ...sourceEntry };
1322
1509
  map[id] = entry;
1323
1510
  const environment = { ...entry.environment };
1324
- for (const [key, value] of injectedEnvironment(cfg, entry.driver))
1325
- environment[key] = value;
1511
+ // Main's rule: an instance that brought its own key or host gets no
1512
+ // workspace credential. Built-in per-provider routing is a default,
1513
+ // not an override, but saved custom routing on those IDs still owns its
1514
+ // credential rather than receiving the workspace's provider key.
1515
+ const builtIn = API_KEY_FLEET[id];
1516
+ const routingDefaults = builtIn?.driver === entry.driver
1517
+ ? builtIn.config
1518
+ : undefined;
1519
+ const ownsRouting = instanceOwnsRouting(cfg, entry, routingDefaults);
1520
+ if (!ownsRouting) {
1521
+ for (const [key, value] of injectedEnvironment(cfg, id, entry.driver))
1522
+ environment[key] = value;
1523
+ }
1326
1524
  entry.environment = environment;
1327
1525
  // The driver URL is configuration, not a credential. Environment is
1328
1526
  // intentionally not consulted by ProviderRegistry when it decodes a
1329
1527
  // driver's config, so carry the workspace default into the transient
1330
1528
  // instance map while preserving a per-instance override.
1331
- if (entry.driver === "openai-compat" && cfg.openaiCompat) {
1529
+ if (entry.driver === "openai-compat" && cfg.openaiCompat && id !== OPENAI_API_INSTANCE && id !== OPENROUTER_API_INSTANCE) {
1332
1530
  const defaults = {};
1333
1531
  if (cfg.openaiCompat.url)
1334
1532
  defaults.url = cfg.openaiCompat.url;
@@ -408,7 +408,7 @@ export function cuaExecArgs(args, options = {}) {
408
408
  ...args,
409
409
  ];
410
410
  }
411
- export async function containerComputerStatus(runner = sh, platform = process.platform, target = SHARED_LOCAL_VM_TARGET) {
411
+ export async function containerComputerStatus(runner = sh, platform = process.platform, target = SHARED_LOCAL_VM_TARGET, options = {}) {
412
412
  const status = emptyStatus(platform, target);
413
413
  const runtimeStatus = await containerRuntimeStatus(runner, platform);
414
414
  status.available = runtimeStatus.available;
@@ -475,7 +475,8 @@ export async function containerComputerStatus(runner = sh, platform = process.pl
475
475
  catch {
476
476
  // No container with this name.
477
477
  }
478
- const canProbe = status.container === "running" &&
478
+ const canProbe = options.probeDesktop !== false &&
479
+ status.container === "running" &&
479
480
  status.imageMatches &&
480
481
  status.managed &&
481
482
  status.network === "loopback" &&