openmausbot 0.1.91 → 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 (150) hide show
  1. package/dist/assets/DesktopViewer-Do6lBa4C.js +5 -0
  2. package/dist/assets/{abnfDiagram-O67JEVCF-yP-w8pRq.js → abnfDiagram-O67JEVCF-Cd3X7Hxc.js} +1 -1
  3. package/dist/assets/{arc-DsivfZEh.js → arc-DIYc7Ymx.js} +1 -1
  4. package/dist/assets/{architectureDiagram-NJMV4G6O-CILw7lzT.js → architectureDiagram-NJMV4G6O-DBmRGVyP.js} +1 -1
  5. package/dist/assets/{blockDiagram-BEXU5L5S-DEQUV3HF.js → blockDiagram-BEXU5L5S-CPFePSEE.js} +1 -1
  6. package/dist/assets/{c4Diagram-YGBWAQC7-lSXCZoKA.js → c4Diagram-YGBWAQC7-DD_DO1qy.js} +1 -1
  7. package/dist/assets/channel-Dtk5Mtic.js +1 -0
  8. package/dist/assets/{chunk-2Q5K7J3B-QCD5bgWP.js → chunk-2Q5K7J3B-BNEeNSjX.js} +1 -1
  9. package/dist/assets/{chunk-3FUC2YCW-C5-iZJuL.js → chunk-3FUC2YCW-BbinLaqH.js} +2 -2
  10. package/dist/assets/{chunk-5VM5RSS4-cxsawMpY.js → chunk-5VM5RSS4-BBgYrgDR.js} +1 -1
  11. package/dist/assets/{chunk-GTNCS2PH-DV8tV8H7.js → chunk-GTNCS2PH-ezXj-kA-.js} +1 -1
  12. package/dist/assets/{chunk-GWA4HPMP-CJFZyfbW.js → chunk-GWA4HPMP-9FxZ3diV.js} +1 -1
  13. package/dist/assets/{chunk-JWPE2WC7-BigbMXco.js → chunk-JWPE2WC7-CO-40JpI.js} +1 -1
  14. package/dist/assets/{chunk-NETBCI7D-oVxTfpDy.js → chunk-NETBCI7D-BEhjslYE.js} +1 -1
  15. package/dist/assets/{chunk-WEXAMYUT-CH4BiroW.js → chunk-WEXAMYUT-CNeq4n7_.js} +1 -1
  16. package/dist/assets/{chunk-XXDRQBXY-KEDa6gax.js → chunk-XXDRQBXY-OsCY7KSi.js} +1 -1
  17. package/dist/assets/{classDiagram-v2-NBCMYWYE-BiPjfqVv.js → classDiagram-v2-NBCMYWYE-CloRUEtk.js} +1 -1
  18. package/dist/assets/{cose-bilkent-JH36ORCC-Caxew3OY.js → cose-bilkent-JH36ORCC-D1BGZhp4.js} +1 -1
  19. package/dist/assets/{cynefin-OW5HDTMX-B_Pui369.js → cynefin-OW5HDTMX-CUf4T2CN.js} +1 -1
  20. package/dist/assets/{cynefinDiagram-VND7K2PF-X64ydIhP.js → cynefinDiagram-VND7K2PF-DPEZrFGA.js} +1 -1
  21. package/dist/assets/{dagre-6A5THRUB-TqYCI1E1.js → dagre-6A5THRUB-bDe6GIv5.js} +1 -1
  22. package/dist/assets/{diagram-22UHCM2B-TeOt3rAQ.js → diagram-22UHCM2B-CdcUQz55.js} +1 -1
  23. package/dist/assets/{diagram-3UASUU5V-D70BLhDA.js → diagram-3UASUU5V-BZpqGg7c.js} +1 -1
  24. package/dist/assets/{diagram-ATOU4E4O-BbQDLwUC.js → diagram-ATOU4E4O-DvGM6ugp.js} +1 -1
  25. package/dist/assets/{diagram-CDSNMT55-ChNJZ1u-.js → diagram-CDSNMT55-DSTmMm0s.js} +1 -1
  26. package/dist/assets/{diagram-MLGK6HIB-DMBl2APa.js → diagram-MLGK6HIB-15cucnMn.js} +1 -1
  27. package/dist/assets/{diagram-MPIPVDR6-CEqDchOp.js → diagram-MPIPVDR6-tMbuaE6n.js} +1 -1
  28. package/dist/assets/{ebnfDiagram-ZINNZB2B-BVDKRp_-.js → ebnfDiagram-ZINNZB2B-BYxYzjHI.js} +1 -1
  29. package/dist/assets/{elk-276RUBZZ-CRkofO9B.js → elk-276RUBZZ-v6bj852W.js} +1 -1
  30. package/dist/assets/{erDiagram-OPXOYQCR-DruOMh5e.js → erDiagram-OPXOYQCR-5lBBlnd2.js} +1 -1
  31. package/dist/assets/{flowDiagram-KWPJA3E3-8GMQeB7t.js → flowDiagram-KWPJA3E3-CZyl1zIj.js} +1 -1
  32. package/dist/assets/{ganttDiagram-FUAMR5RP-ORE9jcsM.js → ganttDiagram-FUAMR5RP-BiAI-ltZ.js} +1 -1
  33. package/dist/assets/{gitGraphDiagram-X574FWY7-BmsedsWh.js → gitGraphDiagram-X574FWY7-BAZISkj3.js} +1 -1
  34. package/dist/assets/index-Cdmjrl28.css +1 -0
  35. package/dist/assets/{index-DKNEM8Q6.js → index-DGBaEXrM.js} +1 -1
  36. package/dist/assets/index-DR7YPMkW.js +597 -0
  37. package/dist/assets/{infoDiagram-VRGFBTTK-E_p7AooJ.js → infoDiagram-VRGFBTTK-B6ooOy-Z.js} +1 -1
  38. package/dist/assets/{ishikawaDiagram-OU5B5YK6-Y-B2mKda.js → ishikawaDiagram-OU5B5YK6-BZ9ksi-B.js} +1 -1
  39. package/dist/assets/{journeyDiagram-ZHPQQLJL-eA8RELcK.js → journeyDiagram-ZHPQQLJL-DWqlzIEe.js} +1 -1
  40. package/dist/assets/{kanban-definition-PNTS6WVX-DqIL2e8e.js → kanban-definition-PNTS6WVX-JX6piDwL.js} +1 -1
  41. package/dist/assets/{linear-yC_29R6e.js → linear-CmOWzX0P.js} +1 -1
  42. package/dist/assets/{mermaid.core-VhEMkSND.js → mermaid.core-CF1zwoQ-.js} +5 -5
  43. package/dist/assets/{mindmap-definition-NLK3R4M7-oJ7kyQZy.js → mindmap-definition-NLK3R4M7-CGh2K4fW.js} +1 -1
  44. package/dist/assets/{pegDiagram-GJSIUBJH-BmfYcY0O.js → pegDiagram-GJSIUBJH-CDwYN0s7.js} +1 -1
  45. package/dist/assets/{pieDiagram-5QR66LMP-DKvgP7w9.js → pieDiagram-5QR66LMP-1rfjJocB.js} +1 -1
  46. package/dist/assets/{quadrantDiagram-O4NWA36T-BEKL3W0L.js → quadrantDiagram-O4NWA36T-tKPvPiok.js} +1 -1
  47. package/dist/assets/{railroadDiagram-XR7U4H2S-BEsiqR5s.js → railroadDiagram-XR7U4H2S-BHLm5vXR.js} +1 -1
  48. package/dist/assets/{requirementDiagram-PLB6GJNP-DWBcWvAP.js → requirementDiagram-PLB6GJNP-CB45STp7.js} +1 -1
  49. package/dist/assets/{sankeyDiagram-IPEJSGJF-rpZ2_O4s.js → sankeyDiagram-IPEJSGJF-55QA_huC.js} +1 -1
  50. package/dist/assets/{sequenceDiagram-PO4LG4MO-B99HQdJR.js → sequenceDiagram-PO4LG4MO-BckFmQHn.js} +1 -1
  51. package/dist/assets/{sizeCapture-INFHLROL-BjN0qT5T.js → sizeCapture-INFHLROL-BFCOJF11.js} +1 -1
  52. package/dist/assets/{stateDiagram-v2-GCMORJYK-BbsVgGsC.js → stateDiagram-v2-GCMORJYK-CZvmiVVN.js} +1 -1
  53. package/dist/assets/{swimlanes-2SLR337P-DG3gKkeR.js → swimlanes-2SLR337P-BpDUCEKy.js} +1 -1
  54. package/dist/assets/swimlanesDiagram-TC7HE7FX-M0GYW9Y3.js +8 -0
  55. package/dist/assets/{timeline-definition-EJHVYXUP-BTHOdtF_.js → timeline-definition-EJHVYXUP-BpxHq6IK.js} +1 -1
  56. package/dist/assets/{usecaseDiagram-POWQR4AR-acLSLFwc.js → usecaseDiagram-POWQR4AR-Cj66vm02.js} +1 -1
  57. package/dist/assets/{vennDiagram-UO4OBE2U-C5JzNX9G.js → vennDiagram-UO4OBE2U-C9nRkWE_.js} +1 -1
  58. package/dist/assets/{wardleyDiagram-VNRHLVJA-DL1-PrR_.js → wardleyDiagram-VNRHLVJA-D-me_nWj.js} +1 -1
  59. package/dist/assets/{xychartDiagram-PMCCYNJV-Bvr1477p.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 +112 -14
  63. package/dist-server/container-mcp.js +195 -15
  64. package/dist-server/drivers/agents-proxy.js +5 -4
  65. package/dist-server/enterprise/server/index.js +5 -1
  66. package/dist-server/index.js +9300 -4155
  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 +3910 -1182
  71. package/dist-server/pair-cli.js +3910 -1182
  72. package/dist-server/server/boat.js +2 -2
  73. package/dist-server/server/browser-engine.js +10 -1
  74. package/dist-server/server/browser-live.js +46 -2
  75. package/dist-server/server/browser-runtime.js +74 -8
  76. package/dist-server/server/browser-tool-shape.js +6 -4
  77. package/dist-server/server/chief-of-staff.js +1 -1
  78. package/dist-server/server/cloud-home-start.js +118 -26
  79. package/dist-server/server/cloud-home.js +39 -3
  80. package/dist-server/server/cloud-lending.js +74 -10
  81. package/dist-server/server/cloud-owner.js +196 -0
  82. package/dist-server/server/cloud-secrets-boot.js +6 -0
  83. package/dist-server/server/cloud-secrets.js +65 -0
  84. package/dist-server/server/config.js +174 -14
  85. package/dist-server/server/container-computer.js +3 -2
  86. package/dist-server/server/desktop-viewer-targets.js +26 -0
  87. package/dist-server/server/drivers/acp/core.js +167 -21
  88. package/dist-server/server/drivers/acp/opencode-go.js +614 -85
  89. package/dist-server/server/drivers/agents-catalog.js +6 -6
  90. package/dist-server/server/drivers/antigravity-acp.js +69 -11
  91. package/dist-server/server/drivers/antigravity-temp.js +360 -0
  92. package/dist-server/server/drivers/antigravity.js +21 -0
  93. package/dist-server/server/drivers/chatgpt-plan-auth.js +481 -0
  94. package/dist-server/server/drivers/claude.js +312 -34
  95. package/dist-server/server/drivers/codex.js +228 -18
  96. package/dist-server/server/drivers/grok.js +2 -2
  97. package/dist-server/server/drivers/mistral.js +4 -3
  98. package/dist-server/server/drivers/openai-chat-protocol.js +4 -3
  99. package/dist-server/server/drivers/openai-chat.js +41 -3
  100. package/dist-server/server/drivers/openai-compat.js +73 -23
  101. package/dist-server/server/engine-install.js +46 -12
  102. package/dist-server/server/env-path.js +15 -3
  103. package/dist-server/server/harness/registry.js +2 -2
  104. package/dist-server/server/index.js +1338 -184
  105. package/dist-server/server/lending-memory.js +479 -0
  106. package/dist-server/server/local-vm-idle.js +24 -6
  107. package/dist-server/server/managed-desktop.js +3 -0
  108. package/dist-server/server/mcp-bridge.js +5 -3
  109. package/dist-server/server/mcp-http.js +6 -3
  110. package/dist-server/server/mcp-oauth-discovery.js +133 -0
  111. package/dist-server/server/mcp-oauth-store.js +70 -0
  112. package/dist-server/server/mcp-oauth.js +550 -0
  113. package/dist-server/server/mcp-probe.js +14 -1
  114. package/dist-server/server/mcp-registry.js +43 -0
  115. package/dist-server/server/memory-upkeep.js +94 -84
  116. package/dist-server/server/model-catalog/catalog.js +374 -0
  117. package/dist-server/server/model-catalog/presets.js +176 -0
  118. package/dist-server/server/model-catalog/trim.js +219 -0
  119. package/dist-server/server/provider-key-check.js +4 -2
  120. package/dist-server/server/recent-work.js +1 -1
  121. package/dist-server/server/request-auth.js +23 -0
  122. package/dist-server/server/routes/antigravity-leftovers.js +45 -0
  123. package/dist-server/server/routes/bot-memory.js +31 -6
  124. package/dist-server/server/routes/desktop-viewer.js +194 -0
  125. package/dist-server/server/routine-requests.js +1 -1
  126. package/dist-server/server/routines.js +1 -1
  127. package/dist-server/server/sessions.js +56 -9
  128. package/dist-server/server/skills.js +25 -10
  129. package/dist-server/server/store.js +62 -122
  130. package/dist-server/server/thread-events.js +2 -0
  131. package/dist-server/server/thread-starters.js +33 -0
  132. package/dist-server/server/thread-title.js +46 -0
  133. package/dist-server/server/tts/fish.js +4 -5
  134. package/dist-server/server/tts/index.js +5 -3
  135. package/dist-server/server/vps-computer.js +79 -12
  136. package/dist-server/server/webhooks.js +18 -1
  137. package/dist-server/server/workspace-backup-policy.js +3 -0
  138. package/dist-server/server/workspace-backup.js +3 -0
  139. package/dist-server/server/workspace.js +65 -15
  140. package/dist-server/shared/workspace-backup-client.js +1 -0
  141. package/dist-server/src/lib/citations.js +177 -0
  142. package/dist-server/src/lib/composer-attachments.js +7 -1
  143. package/dist-server/vps-container-mcp.js +196 -15
  144. package/dist-server/workspace-backup.worker.js +73 -9
  145. package/enterprise/server/index.js +5 -1
  146. package/package.json +1 -1
  147. package/dist/assets/channel-DrfGMu7X.js +0 -1
  148. package/dist/assets/index-BlnCEcU6.js +0 -588
  149. package/dist/assets/index-DVxjOvID.css +0 -1
  150. package/dist/assets/swimlanesDiagram-TC7HE7FX-BCZhxl7e.js +0 -8
@@ -15,6 +15,27 @@ const DEFAULT_MODELS = {
15
15
  { id: "llama-3.3-70b-versatile", label: "Llama 3.3 70B (Groq)", custom: true },
16
16
  ],
17
17
  };
18
+ const DEFAULT_KEY_ENV = "OPENAI_COMPAT_API_KEY";
19
+ /** A provider's own key (the built-in OpenAI and OpenRouter instances). An
20
+ * instance reading one of these never falls back to the workspace
21
+ * OpenAI-compatible key, URL, model or routing. */
22
+ const OWN_KEY_ENVS = new Set(["OMB_OPENAI_API_KEY", "OMB_OPENROUTER_API_KEY"]);
23
+ /** OpenAI's own API, seeded until its live catalog loads. */
24
+ const OPENAI_MODELS = {
25
+ default: "gpt-5",
26
+ options: [
27
+ { id: "gpt-5", label: "gpt-5" },
28
+ { id: "gpt-5-mini", label: "gpt-5-mini" },
29
+ ],
30
+ };
31
+ /** OpenAI's /models lists every model the key can reach: embeddings, speech,
32
+ * images, moderation and Responses-only models too. Only chat models belong
33
+ * in a chat picker. */
34
+ export function isOpenAIChatModel(id) {
35
+ if (!/^(gpt-|chatgpt-|o\d)/i.test(id))
36
+ return false;
37
+ return !/(audio|realtime|tts|transcribe|search|image|embedding|moderation|instruct|codex|computer-use|deep-research|-pro\b)/i.test(id);
38
+ }
18
39
  function isOpenRouterUrl(url) {
19
40
  try {
20
41
  const host = new URL(url).hostname.toLowerCase();
@@ -30,7 +51,11 @@ function decodeConfig(raw) {
30
51
  throw new Error("tools must be a boolean");
31
52
  if (config.managedModels !== undefined && (!Array.isArray(config.managedModels) || !config.managedModels.length || config.managedModels.some(model => typeof model !== "string" || !model.trim())))
32
53
  throw new Error("Invalid managed models.");
33
- const envUrl = process.env.OPENAI_COMPAT_URL;
54
+ const ownKey = typeof config.apiKeyEnv === "string" && OWN_KEY_ENVS.has(config.apiKeyEnv);
55
+ if (config.catalog !== undefined && config.catalog !== "openai")
56
+ throw new Error("catalog must be \"openai\"");
57
+ // Workspace defaults belong to the shared OpenAI-compatible connection only.
58
+ const envUrl = ownKey ? undefined : process.env.OPENAI_COMPAT_URL;
34
59
  return {
35
60
  ...(config.tools !== undefined ? { tools: config.tools } : {}),
36
61
  ...(config.managedModels ? { managedModels: config.managedModels } : {}),
@@ -38,55 +63,65 @@ function decodeConfig(raw) {
38
63
  .replace(/\/+$/, ""),
39
64
  apiKeyEnv: typeof config.apiKeyEnv === "string" && config.apiKeyEnv
40
65
  ? config.apiKeyEnv
41
- : "OPENAI_COMPAT_API_KEY",
66
+ : DEFAULT_KEY_ENV,
67
+ ...(config.catalog === "openai" ? { catalog: "openai" } : {}),
42
68
  key: typeof config.key === "string" && config.key ? config.key : undefined,
43
69
  model: typeof config.model === "string" && config.model
44
70
  ? config.model
45
- : process.env.OPENAI_COMPAT_MODEL || undefined,
71
+ : ownKey ? undefined : process.env.OPENAI_COMPAT_MODEL || undefined,
46
72
  // An explicit empty override disables inherited routing for an isolated
47
73
  // connection (CLI setup uses this). Absent still inherits the global pin.
48
74
  provider: typeof config.provider === "string"
49
75
  ? config.provider || undefined
50
- : process.env.OPENAI_COMPAT_PROVIDER || undefined,
76
+ : ownKey ? undefined : process.env.OPENAI_COMPAT_PROVIDER || undefined,
51
77
  };
52
78
  }
53
79
  export const OpenAICompatDriver = {
54
80
  driverKind: DRIVER_KIND,
55
81
  metadata: {
56
- displayName: "OpenAI-compatible (OpenRouter / Groq)",
82
+ displayName: "Other (OpenAI-compatible)",
57
83
  supportsMultipleInstances: true,
58
- access: "custom",
84
+ access: "api",
59
85
  },
60
86
  models: DEFAULT_MODELS,
87
+ // Nothing to install: the key and base URL are saved in the app. The old
88
+ // descriptor offered a config.json sentence as an "Open install in
89
+ // Terminal" command.
61
90
  install: {
62
91
  docsUrl: "https://openrouter.ai/keys",
63
- signInCommand: "add {\"openaiCompat\":{\"key\":\"sk-or-v1-…\"}} to ~/.openmausbot/config.json (or set OPENAI_COMPAT_API_KEY)",
64
- command: {
65
- darwin: "Get a free key at https://openrouter.ai/keys (or https://console.groq.com) then add it to ~/.openmausbot/config.json under openaiCompat.key",
66
- linux: "Get a free key at https://openrouter.ai/keys (or https://console.groq.com) then add it to ~/.openmausbot/config.json under openaiCompat.key",
67
- win32: "Get a free key at https://openrouter.ai/keys (or https://console.groq.com) then add it to %USERPROFILE%\\.openmausbot\\config.json under openaiCompat.key",
68
- },
92
+ settings: "connections",
93
+ signInCommand: "Save an OpenAI-compatible API key in Settings → API keys, or set OPENAI_COMPAT_API_KEY on the server.",
69
94
  },
70
95
  decodeConfig,
71
96
  defaultConfig: () => decodeConfig({}),
72
97
  async create(input) {
73
98
  const { config } = input;
99
+ // An instance that names its own key variable reads only that one: the
100
+ // workspace key (OPENAI_COMPAT_API_KEY) belongs to the workspace's
101
+ // endpoint and must not reach this instance's host.
102
+ const ownKeyVariable = config.apiKeyEnv !== DEFAULT_KEY_ENV;
74
103
  const apiKey = config.key ??
75
104
  input.environment[config.apiKeyEnv] ??
76
- input.environment.OPENAI_COMPAT_API_KEY ??
105
+ (ownKeyVariable ? undefined : input.environment[DEFAULT_KEY_ENV]) ??
77
106
  process.env[config.apiKeyEnv] ??
78
- process.env.OPENAI_COMPAT_API_KEY ??
107
+ (ownKeyVariable ? undefined : process.env[DEFAULT_KEY_ENV]) ??
79
108
  "";
109
+ // The default key and the built-in providers' keys are saved in
110
+ // Settings → API keys; any other variable is configured where it was written.
111
+ const missingKey = !ownKeyVariable || OWN_KEY_ENVS.has(config.apiKeyEnv)
112
+ ? "No API key — open Settings → API keys."
113
+ : `no API key — set ${config.apiKeyEnv} or add it to the instance config`;
114
+ const seeded = config.catalog === "openai" ? OPENAI_MODELS : DEFAULT_MODELS;
80
115
  let catalog = config.managedModels
81
116
  ? { default: config.managedModels[0], options: config.managedModels.map(id => ({ id, label: id })) }
82
117
  : config.model
83
118
  ? {
84
119
  default: config.model,
85
- options: DEFAULT_MODELS.options.some((model) => model.id === config.model)
86
- ? DEFAULT_MODELS.options
87
- : [{ id: config.model, label: config.model, custom: true }, ...DEFAULT_MODELS.options],
120
+ options: seeded.options.some((model) => model.id === config.model)
121
+ ? seeded.options
122
+ : [{ id: config.model, label: config.model, custom: true }, ...seeded.options],
88
123
  }
89
- : DEFAULT_MODELS;
124
+ : seeded;
90
125
  const fetchModels = async () => {
91
126
  if (config.managedModels)
92
127
  return;
@@ -100,7 +135,14 @@ export const OpenAICompatDriver = {
100
135
  if (!response.ok)
101
136
  return;
102
137
  const json = await response.json();
103
- const rows = Array.isArray(json) ? json : Array.isArray(json.data) ? json.data : [];
138
+ let rows = Array.isArray(json) ? json : Array.isArray(json.data) ? json.data : [];
139
+ // OpenAI lists its catalog unordered and mixed with non-chat models:
140
+ // keep chat models, newest first.
141
+ if (config.catalog === "openai") {
142
+ rows = rows
143
+ .filter((row) => typeof row.id === "string" && isOpenAIChatModel(row.id))
144
+ .sort((a, b) => (typeof b.created === "number" ? b.created : 0) - (typeof a.created === "number" ? a.created : 0));
145
+ }
104
146
  const seen = new Set();
105
147
  const options = [];
106
148
  for (const row of rows) {
@@ -111,7 +153,8 @@ export const OpenAICompatDriver = {
111
153
  options.push({
112
154
  id,
113
155
  label: typeof row.name === "string" && row.name.trim() ? row.name : id,
114
- custom: true,
156
+ // A provider's own catalog is its official list, not a custom model.
157
+ ...(config.catalog === "openai" ? {} : { custom: true }),
115
158
  });
116
159
  }
117
160
  if (!options.length)
@@ -146,15 +189,22 @@ export const OpenAICompatDriver = {
146
189
  : {}),
147
190
  }),
148
191
  httpErrorLabel: "upstream",
149
- missingKeyError: `no API key — set ${config.apiKeyEnv} or add it to the instance config`,
150
- unavailableReason: `no API key — set ${config.apiKeyEnv} or add it to the instance config`,
192
+ missingKeyError: missingKey,
193
+ unavailableReason: missingKey,
151
194
  timeoutMs: idleTimeoutMs(),
152
195
  reasoning: true,
153
196
  billing: "metered",
154
197
  includeUsageInCompleted: true,
155
198
  nativeLog: {
156
199
  source: "openai-compat.chat.completions",
157
- outgoing: (_turn, messages, model) => ({ model, messageCount: messages.length }),
200
+ // Tool names are the answer to "did the harness send them?" — the
201
+ // LiteLLM/proxy hop after this point is what drops tools silently,
202
+ // and until now the tee held no record either side could compare.
203
+ outgoing: (_turn, messages, model, tools) => ({
204
+ model,
205
+ messageCount: messages.length,
206
+ tools: tools.map(tool => tool.function.name),
207
+ }),
158
208
  incoming: ({ text, reasoning, usage }) => ({
159
209
  textLength: text.length,
160
210
  reasoningLength: reasoning.length,
@@ -9,9 +9,10 @@ import { join } from "node:path";
9
9
  import { stripVTControlCharacters } from "node:util";
10
10
  import { DATA_DIR, stripWorkspaceCredentialEnv } from "./config.js";
11
11
  import { augmentedPath, findCliCandidates, registerPathDir, resetPathCache } from "./env-path.js";
12
- import { killCliTree, spawnCli } from "./procs.js";
12
+ import { execCli, killCliTree, spawnCli } from "./procs.js";
13
13
  const MAX_OUTPUT = 64 * 1024;
14
14
  const DEFAULT_TIMEOUT_MS = 10 * 60_000;
15
+ const PROBE_TIMEOUT_MS = 30_000;
15
16
  /** npm's global prefix for engines the app installs itself. */
16
17
  export function enginesPrefix(baseDir = DATA_DIR) {
17
18
  return join(baseDir, "tools", "npm");
@@ -68,21 +69,54 @@ async function installOnce(pkg, options) {
68
69
  stripWorkspaceCredentialEnv(env);
69
70
  // npm 11 skips a dependency's install script unless the package is named
70
71
  // here; the engines that need one (Claude Code) are exactly these.
71
- const args = ["install", "-g", "--prefix", prefix, "--loglevel=error", `--allow-scripts=${pkg}`, `${pkg}@latest`];
72
- const result = await runNpm(args, env, prefix, options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
73
- if (result.code !== 0) {
74
- throw new Error(`npm could not install ${pkg} on this server.${tail(result.output)}`);
75
- }
72
+ // Engines like Codex ship their native binary as a platform optional
73
+ // dependency, so a user-level `omit=optional` must not leave it out.
74
+ const args = ["install", "-g", "--prefix", prefix, "--loglevel=error", "--include=optional", `--allow-scripts=${pkg}`, `${pkg}@latest`];
75
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
76
+ await runInstall(pkg, args, env, prefix, timeoutMs);
76
77
  // Whatever the boot registration did, the directory we just filled must
77
78
  // be on the engines' PATH from here on.
78
- registerPathDir(enginesBinDir(options.baseDir));
79
+ const binDir = enginesBinDir(options.baseDir);
80
+ registerPathDir(binDir);
79
81
  resetPathCache();
80
- if (options.cli) {
81
- const binDir = enginesBinDir(options.baseDir);
82
- if (!findCliCandidates(options.cli).some((path) => path.startsWith(binDir))) {
83
- throw new Error(`${pkg} installed, but it did not provide a \`${options.cli}\` command. Check the package name in this engine's install descriptor.`);
84
- }
82
+ if (!options.cli)
83
+ return;
84
+ const installed = findCliCandidates(options.cli).find((path) => path.startsWith(binDir));
85
+ if (!installed) {
86
+ throw new Error(`${pkg} installed, but it did not provide a \`${options.cli}\` command. Check the package name in this engine's install descriptor.`);
85
87
  }
88
+ // npm exits 0 even when it drops an optional dependency it failed to
89
+ // download, which leaves a launcher without its platform binary (#2064).
90
+ // A copy here shadows every other one on PATH, so prove it starts; a
91
+ // second run fetches what the first one dropped.
92
+ let failure = await probeCli(installed, env);
93
+ if (failure === null)
94
+ return;
95
+ await runInstall(pkg, args, env, prefix, timeoutMs);
96
+ failure = await probeCli(installed, env);
97
+ if (failure === null)
98
+ return;
99
+ throw new Error(`npm installed ${pkg}, but \`${options.cli}\` does not start: ${failure}\nnpm can leave out a platform package when its download fails, even after a retry. Check this server's network connection, then install again from Settings.`);
100
+ }
101
+ async function runInstall(pkg, args, env, prefix, timeoutMs) {
102
+ const result = await runNpm(args, env, prefix, timeoutMs);
103
+ if (result.code !== 0) {
104
+ throw new Error(`npm could not install ${pkg} on this server.${tail(result.output)}`);
105
+ }
106
+ }
107
+ /** Null when `<cli> --version` succeeds, else why it did not. A Node
108
+ * launcher that throws prints its source line first, so its `Error:` line
109
+ * is the one worth showing. */
110
+ function probeCli(cli, env) {
111
+ return new Promise((resolveProbe) => {
112
+ execCli(cli, ["--version"], { env: { ...env, PATH: augmentedPath() }, timeout: PROBE_TIMEOUT_MS, killSignal: "SIGKILL", maxBuffer: MAX_OUTPUT }, (error, stdout, stderr) => {
113
+ if (!error)
114
+ return resolveProbe(null);
115
+ const lines = stripVTControlCharacters(`${stderr ?? ""}\n${stdout}`).split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
116
+ const said = lines.find((line) => /^\w*Error: /.test(line)) ?? lines[0] ?? error.message;
117
+ resolveProbe(said.slice(0, 400));
118
+ });
119
+ });
86
120
  }
87
121
  function tail(output) {
88
122
  const clean = stripVTControlCharacters(output).trim();
@@ -27,6 +27,15 @@ function nvmBinDirs() {
27
27
  return [];
28
28
  }
29
29
  }
30
+ /** A sealed test fixture finds CLIs only where it was granted them: its own
31
+ * PATH, OMB_EXTRA_PATH, app-managed dirs, and install dirs under its own
32
+ * temporary home. The machine-wide install dirs and the login shell's PATH
33
+ * would hand it this computer's real CLIs — a Homebrew `codex` made the
34
+ * fixture's ChatGPT plan engine "available" on a developer Mac (#2035).
35
+ * Only the verification launcher (scripts/control-omb.ts) sets it. */
36
+ function sealedFixture() {
37
+ return process.env.OMB_TEST_SEALED_PATH === "1";
38
+ }
30
39
  function knownDirs() {
31
40
  const home = homedir();
32
41
  return [
@@ -36,8 +45,11 @@ function knownDirs() {
36
45
  join(home, ".grok", "bin"), // x.ai installer
37
46
  join(home, ".opencode", "bin"), // opencode installer
38
47
  join(home, ".claude", "local"), // claude "local install"
39
- "/opt/homebrew/bin", // brew, Apple silicon
40
- "/usr/local/bin", // brew Intel / classic installs
48
+ // Machine-wide rather than under the (possibly temporary) home.
49
+ ...(sealedFixture() ? [] : [
50
+ "/opt/homebrew/bin", // brew, Apple silicon
51
+ "/usr/local/bin", // brew Intel / classic installs
52
+ ]),
41
53
  join(home, ".volta", "bin"),
42
54
  join(home, ".bun", "bin"),
43
55
  join(home, ".asdf", "shims"),
@@ -115,7 +127,7 @@ export function augmentedPath() {
115
127
  // belt-and-braces: fold in the login shell's PATH once, in the
116
128
  // background — catches anything the known-dirs list doesn't (custom
117
129
  // rc exports). Never blocks a spawn; the next one benefits.
118
- if (!probed && !process.env.VITEST && process.platform !== "win32") {
130
+ if (!probed && !process.env.VITEST && !sealedFixture() && process.platform !== "win32") {
119
131
  probed = true;
120
132
  probeLoginShellPath();
121
133
  }
@@ -235,11 +235,11 @@ export class ProviderRegistry {
235
235
  install: withServerInstall(driver?.install, npmPresent),
236
236
  authentication: inst.startAuthentication
237
237
  ? {
238
- method: inst.getAuthentication && inst.completeAuthentication
238
+ method: inst.authenticationMethod ?? (inst.getAuthentication && inst.completeAuthentication
239
239
  ? "paste-code" // a link to open, then a code pasted back (Claude)
240
240
  : inst.getAuthentication
241
241
  ? "device-code" // a code to enter at the provider's page (Codex)
242
- : "browser", // a link and a callback URL (managed engines)
242
+ : "browser"), // a link and a callback URL (managed engines)
243
243
  // the browser may remove the stored sign-in to switch accounts
244
244
  signOut: inst.signOut !== undefined,
245
245
  }