openmausbot 0.1.79 → 0.1.81

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 (60) hide show
  1. package/README.md +6 -5
  2. package/dist/assets/index-CKysBq-V.css +1 -0
  3. package/dist/assets/{index-C2Je5jdF.js → index-oLMljotU.js} +1 -1
  4. package/dist/assets/index-peLFiSH8.js +307 -0
  5. package/dist/index.html +2 -2
  6. package/dist-server/container-mcp.js +41 -10
  7. package/dist-server/drivers/agents-proxy.js +71 -17
  8. package/dist-server/index.js +4104 -1505
  9. package/dist-server/openmausbot.js +554 -163
  10. package/dist-server/pair-cli.js +554 -163
  11. package/dist-server/server/box-delete-journal.js +471 -0
  12. package/dist-server/server/box.js +345 -75
  13. package/dist-server/server/cli-phone-setup.js +2 -2
  14. package/dist-server/server/cli.js +84 -12
  15. package/dist-server/server/config.js +48 -18
  16. package/dist-server/server/container-computer.js +7 -0
  17. package/dist-server/server/contracts.js +5 -0
  18. package/dist-server/server/default-model-selection.js +1 -0
  19. package/dist-server/server/drivers/acp/core.js +112 -18
  20. package/dist-server/server/drivers/acp/opencode-go.js +6 -0
  21. package/dist-server/server/drivers/agents-proxy.js +76 -13
  22. package/dist-server/server/drivers/chat-mcp-tools.js +6 -1
  23. package/dist-server/server/drivers/claude.js +87 -63
  24. package/dist-server/server/drivers/codex.js +248 -51
  25. package/dist-server/server/drivers/native.js +6 -1
  26. package/dist-server/server/harness/bus.js +4 -0
  27. package/dist-server/server/harness/registry.js +3 -2
  28. package/dist-server/server/index.js +1007 -222
  29. package/dist-server/server/local-vm-lease.js +4 -0
  30. package/dist-server/server/managed-desktop.js +205 -0
  31. package/dist-server/server/mcp-http.js +337 -0
  32. package/dist-server/server/mcp-probe.js +67 -18
  33. package/dist-server/server/mcp-registry.js +153 -58
  34. package/dist-server/server/member-turn.js +13 -0
  35. package/dist-server/server/message-db.js +74 -8
  36. package/dist-server/server/model-context-window.js +20 -0
  37. package/dist-server/server/recall-disclosure.js +4 -0
  38. package/dist-server/server/recent-work.js +142 -0
  39. package/dist-server/server/redact.js +1 -0
  40. package/dist-server/server/request-auth.js +3 -1
  41. package/dist-server/server/room-handoffs.js +16 -6
  42. package/dist-server/server/routines.js +17 -0
  43. package/dist-server/server/screen-frame-gate.js +4 -94
  44. package/dist-server/server/sessions.js +22 -3
  45. package/dist-server/server/store.js +29 -2
  46. package/dist-server/server/surface.js +41 -19
  47. package/dist-server/server/team-setup-requests.js +1 -1
  48. package/dist-server/server/thread-log-rotation.js +104 -0
  49. package/dist-server/server/tts/fish.js +155 -0
  50. package/dist-server/server/tts/index.js +25 -3
  51. package/dist-server/server/vps-computer.js +26 -4
  52. package/dist-server/server/vps-ssh.js +88 -0
  53. package/dist-server/server/workspace-backup.js +11 -0
  54. package/dist-server/server/workspace.js +2 -1
  55. package/dist-server/shared/credential-request.js +10 -0
  56. package/dist-server/shared/tool-surface.js +62 -0
  57. package/dist-server/vps-container-mcp.js +41 -10
  58. package/package.json +1 -1
  59. package/dist/assets/index-D_TFZLlo.js +0 -305
  60. package/dist/assets/index-Pbb6Ao0s.css +0 -1
@@ -17569,6 +17569,9 @@ var init_image_generation = __esm({
17569
17569
  });
17570
17570
 
17571
17571
  // server/contracts.ts
17572
+ function isModelVariant(value) {
17573
+ return typeof value === "string" && value.length > 0 && value.length <= 256 && value.trim() === value && !new RegExp("\\p{Cc}", "u").test(value);
17574
+ }
17572
17575
  var EFFORT_LEVELS, eventCounter, newEventId, newId;
17573
17576
  var init_contracts = __esm({
17574
17577
  "server/contracts.ts"() {
@@ -17584,21 +17587,31 @@ var init_contracts = __esm({
17584
17587
  function isHarnessOwnedMcpEnvName(name) {
17585
17588
  return name === "ELECTRON_RUN_AS_NODE" || name.startsWith("OMB_") || name.startsWith("OGB_");
17586
17589
  }
17587
- var MAX_ARGS, storedEntrySchema, mutationEntrySchema;
17590
+ var MAX_ARGS, stdioEntrySchema, stdioMutationSchema, remoteEntrySchema, remoteMutationSchema;
17588
17591
  var init_mcp_registry = __esm({
17589
17592
  "server/mcp-registry.ts"() {
17590
17593
  "use strict";
17591
17594
  init_zod();
17592
17595
  MAX_ARGS = 64;
17593
- storedEntrySchema = external_exports.object({
17596
+ stdioEntrySchema = external_exports.object({
17594
17597
  command: external_exports.string().trim().min(1).max(1024),
17595
17598
  args: external_exports.array(external_exports.string().max(4096)).max(MAX_ARGS).optional(),
17596
17599
  env: external_exports.record(external_exports.string(), external_exports.string().max(16384)).optional(),
17597
17600
  enabled: external_exports.boolean().optional()
17598
17601
  }).strict();
17599
- mutationEntrySchema = storedEntrySchema.extend({
17602
+ stdioMutationSchema = stdioEntrySchema.extend({
17600
17603
  env: external_exports.record(external_exports.string(), external_exports.union([external_exports.string().max(16384), external_exports.literal(true)])).optional()
17601
17604
  });
17605
+ remoteEntrySchema = external_exports.object({
17606
+ /** Streamable HTTP unless the entry says the older SSE transport. */
17607
+ type: external_exports.enum(["http", "sse"]).optional(),
17608
+ url: external_exports.string().trim().min(1).max(2048),
17609
+ headers: external_exports.record(external_exports.string(), external_exports.string().max(16384)).optional(),
17610
+ enabled: external_exports.boolean().optional()
17611
+ }).strict();
17612
+ remoteMutationSchema = remoteEntrySchema.extend({
17613
+ headers: external_exports.record(external_exports.string(), external_exports.union([external_exports.string().max(16384), external_exports.literal(true)])).optional()
17614
+ });
17602
17615
  }
17603
17616
  });
17604
17617
 
@@ -17775,6 +17788,7 @@ function loadConfig() {
17775
17788
  if (process.env.OPENCODE_API_KEY !== void 0) cfg.opencodeGo.apiKey = process.env.OPENCODE_API_KEY;
17776
17789
  cfg.tts = { ...cfg.tts };
17777
17790
  if (process.env.OMB_TTS_KEY !== void 0) cfg.tts.key = process.env.OMB_TTS_KEY;
17791
+ if (process.env.OMB_FISH_AUDIO_API_KEY !== void 0) cfg.tts.fishKey = process.env.OMB_FISH_AUDIO_API_KEY;
17778
17792
  cfg.imageGen = { ...cfg.imageGen };
17779
17793
  if (process.env.OMB_OPENAI_IMAGE_KEY !== void 0) cfg.imageGen.key = process.env.OMB_OPENAI_IMAGE_KEY;
17780
17794
  if (process.env.OMB_CUSTOM_IMAGE_KEY !== void 0) cfg.imageGen.customApiKey = process.env.OMB_CUSTOM_IMAGE_KEY;
@@ -17923,7 +17937,7 @@ function instanceConfigs(cfg) {
17923
17937
  }
17924
17938
  return map3;
17925
17939
  }
17926
- var optionalText, SSH_ALIAS, LEGACY_BROWSER_PROFILE_ID, BROWSER_PROFILE_ID, MIN_ROOM_TURN_TIMEOUT_MINUTES, MAX_ROOM_TURN_TIMEOUT_MINUTES, MAX_CONCURRENT_BOT_THREADS, MIN_LOCAL_VM_MAX_INSTANCES, MAX_LOCAL_VM_MAX_INSTANCES, vpsConfigSchema, roomConfigSchema, localVmConfigSchema, browserProfileSchema, legacyBrowserProfileSchema, legacyBrowserProfilesSchema, storedBrowserProfilesSchema, browserProfilesSchema, featureConfigSchema, onboardingConfigSchema, instanceConfigSchema, instanceConfigMapSchema, defaultModelSelectionSchema, appConfigSchema, storedAppConfigSchema, appConfigPatchSchema, jsonObjectSchema, DATA_DIR, LEGACY_DATA_DIR, EVENTS_DIR, NATIVE_DIR, WORKSPACE_CREDENTIAL_ENV, PROVIDER_CREDENTIAL_ENV;
17940
+ var optionalText, SSH_ALIAS, LEGACY_BROWSER_PROFILE_ID, BROWSER_PROFILE_ID, MIN_ROOM_TURN_TIMEOUT_MINUTES, MAX_ROOM_TURN_TIMEOUT_MINUTES, MAX_CONCURRENT_BOT_THREADS, MIN_THREAD_EVENT_LOG_BYTES, MAX_THREAD_EVENT_LOG_BYTES, MIN_LOCAL_VM_MAX_INSTANCES, MAX_LOCAL_VM_MAX_INSTANCES, vpsConfigSchema, roomConfigSchema, localVmConfigSchema, browserProfileSchema, legacyBrowserProfileSchema, legacyBrowserProfilesSchema, storedBrowserProfilesSchema, browserProfilesSchema, featureConfigSchema, onboardingConfigSchema, instanceConfigSchema, instanceConfigMapSchema, defaultModelSelectionSchema, appConfigSchema, storedAppConfigSchema, appConfigPatchSchema, jsonObjectSchema, DATA_DIR, LEGACY_DATA_DIR, EVENTS_DIR, NATIVE_DIR, WORKSPACE_CREDENTIAL_ENV, PROVIDER_CREDENTIAL_ENV;
17927
17941
  var init_config = __esm({
17928
17942
  "server/config.ts"() {
17929
17943
  "use strict";
@@ -17940,6 +17954,8 @@ var init_config = __esm({
17940
17954
  MIN_ROOM_TURN_TIMEOUT_MINUTES = 1;
17941
17955
  MAX_ROOM_TURN_TIMEOUT_MINUTES = 1440;
17942
17956
  MAX_CONCURRENT_BOT_THREADS = 10;
17957
+ MIN_THREAD_EVENT_LOG_BYTES = 256 * 1024;
17958
+ MAX_THREAD_EVENT_LOG_BYTES = 4 * 1024 * 1024 * 1024;
17943
17959
  MIN_LOCAL_VM_MAX_INSTANCES = 1;
17944
17960
  MAX_LOCAL_VM_MAX_INSTANCES = 4;
17945
17961
  vpsConfigSchema = external_exports.object({
@@ -17999,7 +18015,11 @@ var init_config = __esm({
17999
18015
  /** Opt-in computer sharing (a desktop lending folders, a terminal or
18000
18016
  * computer control to a workspace). Off until explicitly enabled; there is
18001
18017
  * no Settings toggle — see sharedComputersEnabled. */
18002
- sharedComputers: external_exports.boolean().optional()
18018
+ sharedComputers: external_exports.boolean().optional(),
18019
+ /** Claude bots also load the MCP servers and connectors from this
18020
+ * machine's own Claude Code setup (Plugins → MCP servers switch). Off by
18021
+ * default: each extra tool costs tokens on every model call. */
18022
+ claudeUserMcp: external_exports.boolean().optional()
18003
18023
  });
18004
18024
  onboardingConfigSchema = external_exports.object({
18005
18025
  /** ISO timestamp of finishing (or skipping to the end of) the welcome flow. */
@@ -18021,8 +18041,12 @@ var init_config = __esm({
18021
18041
  defaultModelSelectionSchema = external_exports.object({
18022
18042
  instanceId: external_exports.string().trim().min(1),
18023
18043
  model: external_exports.string().trim().min(1),
18024
- effort: external_exports.enum(EFFORT_LEVELS).optional()
18025
- });
18044
+ effort: external_exports.enum(EFFORT_LEVELS).optional(),
18045
+ variant: external_exports.string().refine(isModelVariant, "invalid model variant").optional()
18046
+ }).refine(
18047
+ (selection) => selection.variant === void 0 || selection.effort === void 0,
18048
+ "choose either a model variant or an effort level"
18049
+ );
18026
18050
  appConfigSchema = external_exports.object({
18027
18051
  /** Verified by the dedicated domain endpoint, never a generic config patch. */
18028
18052
  customDomain: external_exports.string().optional(),
@@ -18071,13 +18095,16 @@ var init_config = __esm({
18071
18095
  /** Optional OpenCode key; persisted write-only and passed only to its child. */
18072
18096
  opencodeGo: external_exports.object({ apiKey: optionalText }).optional(),
18073
18097
  /** Voice settings and the selected voice id. `provider` picks the
18074
- * engine: "elevenlabs" (default; needs a key), "system" (the Mac's
18075
- * built-in voices, no key), or "chatterbox" (a local OpenAI-compatible
18076
- * Chatterbox server; `baseUrl` and `model` are settings, not secrets). */
18098
+ * engine: "elevenlabs" (default; needs `key`), "fish" (needs its own
18099
+ * `fishKey`), "system" (the Mac's built-in voices, no key), or
18100
+ * "chatterbox" (a local OpenAI-compatible Chatterbox server; `baseUrl`
18101
+ * and `model` are settings, not secrets). Cloud keys stay separate so
18102
+ * switching providers never overwrites or misuses the other key. */
18077
18103
  tts: external_exports.object({
18078
18104
  key: optionalText,
18105
+ fishKey: optionalText,
18079
18106
  voice: optionalText,
18080
- provider: external_exports.enum(["elevenlabs", "system", "chatterbox"]).optional(),
18107
+ provider: external_exports.enum(["elevenlabs", "fish", "system", "chatterbox"]).optional(),
18081
18108
  baseUrl: external_exports.string().trim().max(2048).refine((value) => !value || /^https?:\/\//i.test(value), "the Chatterbox server address must start with http:// or https://").optional(),
18082
18109
  model: optionalText
18083
18110
  }).optional(),
@@ -18106,7 +18133,12 @@ var init_config = __esm({
18106
18133
  * system language. Unknown tags degrade to English in the renderer. */
18107
18134
  language: optionalText,
18108
18135
  rooms: roomConfigSchema.optional(),
18109
- threads: external_exports.object({ maxConcurrentPerBot: external_exports.number().int().min(1).max(MAX_CONCURRENT_BOT_THREADS) }).strict().optional(),
18136
+ threads: external_exports.object({
18137
+ maxConcurrentPerBot: external_exports.number().int().min(1).max(MAX_CONCURRENT_BOT_THREADS),
18138
+ /** Cap each per-thread events/ and native/ NDJSON log at this many
18139
+ * bytes; absent (the default) keeps today's unbounded growth (#1280). */
18140
+ eventLogMaxBytes: external_exports.number().int().min(MIN_THREAD_EVENT_LOG_BYTES).max(MAX_THREAD_EVENT_LOG_BYTES).optional()
18141
+ }).strict().optional(),
18110
18142
  localVm: localVmConfigSchema.optional(),
18111
18143
  features: featureConfigSchema.optional(),
18112
18144
  onboarding: onboardingConfigSchema.optional(),
@@ -18136,6 +18168,7 @@ var init_config = __esm({
18136
18168
  "BOX_TOKEN",
18137
18169
  "OPENCODE_API_KEY",
18138
18170
  "OMB_TTS_KEY",
18171
+ "OMB_FISH_AUDIO_API_KEY",
18139
18172
  "OMB_OPENAI_IMAGE_KEY",
18140
18173
  "OMB_CUSTOM_IMAGE_KEY",
18141
18174
  "COMPOSIO_API_KEY",
@@ -19049,7 +19082,7 @@ var init_registry = __esm({
19049
19082
  this.npmPresent = options.npmAvailable ?? npmAvailable;
19050
19083
  this.driversByKind = new Map(drivers.map((d) => [d.driverKind, d]));
19051
19084
  }
19052
- async load(configs) {
19085
+ async load(configs, decorate) {
19053
19086
  for (const [instanceId, entry] of Object.entries(configs)) {
19054
19087
  await this.dispose(instanceId);
19055
19088
  const driver = this.driversByKind.get(entry.driver);
@@ -19078,7 +19111,7 @@ var init_registry = __esm({
19078
19111
  enabled: entry.enabled ?? true,
19079
19112
  config: config2
19080
19113
  });
19081
- this.byId.set(instanceId, { instanceId, live });
19114
+ this.byId.set(instanceId, { instanceId, live: decorate ? decorate(live) : live });
19082
19115
  } catch (e) {
19083
19116
  this.byId.set(instanceId, {
19084
19117
  instanceId,
@@ -19212,6 +19245,7 @@ var init_registry = __esm({
19212
19245
  browserMcp: inst.adapter.capabilities.browserMcp === true,
19213
19246
  images: inst.adapter.capabilities.images === true,
19214
19247
  effortLevels: inst.adapter.capabilities.effortLevels,
19248
+ modelVariants: inst.adapter.capabilities.modelVariants === true,
19215
19249
  queueing: inst.adapter.capabilities.queueing === true,
19216
19250
  localComputerMcp: inst.adapter.capabilities.localComputerMcp === true,
19217
19251
  approvalReview: inst.reviewPermission !== void 0
@@ -19600,6 +19634,8 @@ var init_redact = __esm({
19600
19634
  REDACTION_MARKER = /^«redacted \d+ chars»$/;
19601
19635
  mask = (value) => REDACTION_MARKER.test(value) ? value : `\xABredacted ${value.length} chars\xBB`;
19602
19636
  KEY_PREFIXES = [
19637
+ /\bom[dg]_[A-Za-z0-9_-]{43}(?![A-Za-z0-9_-])/g,
19638
+ // desktop device and model-only credentials
19603
19639
  /\bsk-(?:ant-|proj-|live-|test-)?[A-Za-z0-9_-]{16,}/g,
19604
19640
  // anthropic / openai / stripe
19605
19641
  /\bxai-[A-Za-z0-9_-]{20,}/g,
@@ -19632,16 +19668,84 @@ var init_redact = __esm({
19632
19668
  }
19633
19669
  });
19634
19670
 
19671
+ // server/thread-log-rotation.ts
19672
+ import { closeSync as closeSync4, fstatSync, openSync as openSync4, readSync as readSync2, renameSync as renameSync5, rmSync as rmSync4, statSync as statSync3, writeSync } from "node:fs";
19673
+ function currentThreadLogCap() {
19674
+ try {
19675
+ return capProvider();
19676
+ } catch {
19677
+ return null;
19678
+ }
19679
+ }
19680
+ function capThreadLog(file2, maxBytes) {
19681
+ if (typeof maxBytes !== "number" || !Number.isFinite(maxBytes) || maxBytes <= 0) return;
19682
+ let size;
19683
+ try {
19684
+ size = statSync3(file2).size;
19685
+ } catch {
19686
+ return;
19687
+ }
19688
+ if (size <= maxBytes) return;
19689
+ trimToTail(file2, Math.max(1, Math.floor(maxBytes / 2)));
19690
+ }
19691
+ function trimToTail(file2, keepBytes) {
19692
+ const staging = `${file2}.trim`;
19693
+ try {
19694
+ const source = openSync4(file2, "r");
19695
+ try {
19696
+ const size = fstatSync(source).size;
19697
+ const start = Math.max(0, size - keepBytes);
19698
+ const window = Buffer.allocUnsafe(size - start);
19699
+ let filled = 0;
19700
+ while (filled < window.length) {
19701
+ const count = readSync2(source, window, filled, window.length - filled, start + filled);
19702
+ if (count === 0) return;
19703
+ filled += count;
19704
+ }
19705
+ const firstNewline = window.indexOf(10);
19706
+ if (firstNewline === -1 || firstNewline + 1 >= window.length) return;
19707
+ const target = openSync4(staging, "w", 384);
19708
+ try {
19709
+ let written = firstNewline + 1;
19710
+ while (written < window.length) {
19711
+ const count = writeSync(target, window, written, window.length - written);
19712
+ if (count === 0) throw new Error(`no progress writing ${staging}`);
19713
+ written += count;
19714
+ }
19715
+ } finally {
19716
+ closeSync4(target);
19717
+ }
19718
+ } finally {
19719
+ closeSync4(source);
19720
+ }
19721
+ renameSync5(staging, file2);
19722
+ } catch {
19723
+ try {
19724
+ rmSync4(staging, { force: true });
19725
+ } catch {
19726
+ }
19727
+ }
19728
+ }
19729
+ var capProvider;
19730
+ var init_thread_log_rotation = __esm({
19731
+ "server/thread-log-rotation.ts"() {
19732
+ "use strict";
19733
+ capProvider = () => null;
19734
+ }
19735
+ });
19736
+
19635
19737
  // server/drivers/native.ts
19636
19738
  import { appendFileSync as appendFileSync3 } from "node:fs";
19637
19739
  import { join as join12 } from "node:path";
19638
19740
  function appendNative(threadId, entry) {
19741
+ const file2 = join12(NATIVE_DIR, `${threadId}.ndjson`);
19639
19742
  try {
19640
19743
  appendFileSync3(
19641
- join12(NATIVE_DIR, `${threadId}.ndjson`),
19744
+ file2,
19642
19745
  JSON.stringify({ at: (/* @__PURE__ */ new Date()).toISOString(), ...entry, msg: redactSecrets(entry.msg) }) + "\n",
19643
19746
  { mode: 384 }
19644
19747
  );
19748
+ capThreadLog(file2, currentThreadLogCap());
19645
19749
  } catch {
19646
19750
  }
19647
19751
  }
@@ -19650,6 +19754,7 @@ var init_native = __esm({
19650
19754
  "use strict";
19651
19755
  init_config();
19652
19756
  init_redact();
19757
+ init_thread_log_rotation();
19653
19758
  }
19654
19759
  });
19655
19760
 
@@ -19725,6 +19830,29 @@ import { basename as basename3, dirname as dirname5, isAbsolute, relative, resol
19725
19830
  function skipSubscriptionAuthForLocalInject(model) {
19726
19831
  return Boolean(decodeInjectId(model));
19727
19832
  }
19833
+ function acpVariantOption(result) {
19834
+ const option = (Array.isArray(result?.configOptions) ? result.configOptions : []).find(
19835
+ (entry) => entry?.type === "select" && typeof entry.id === "string" && (entry.id === "effort" || entry.category === "thought_level")
19836
+ );
19837
+ if (!option) return;
19838
+ const options = [];
19839
+ const seen = /* @__PURE__ */ new Set();
19840
+ const collect = (entries) => {
19841
+ if (!Array.isArray(entries)) return;
19842
+ for (const entry of entries) {
19843
+ if (typeof entry?.value === "string" && !seen.has(entry.value)) {
19844
+ seen.add(entry.value);
19845
+ options.push({ id: entry.value, label: typeof entry.name === "string" ? entry.name : entry.value });
19846
+ } else if (Array.isArray(entry?.options)) collect(entry.options);
19847
+ }
19848
+ };
19849
+ collect(option.options);
19850
+ return {
19851
+ configId: option.id,
19852
+ options,
19853
+ ...typeof option.currentValue === "string" ? { currentValue: option.currentValue } : {}
19854
+ };
19855
+ }
19728
19856
  async function readAcpImageBlocks(images) {
19729
19857
  return Promise.all(images.map(async (image) => ({
19730
19858
  type: "image",
@@ -19900,6 +20028,10 @@ function createAcpDriver(support11) {
19900
20028
  }
19901
20029
  for (const [name, server] of Object.entries(turn.integrations?.custom ?? {})) {
19902
20030
  if (servers.some((existing) => existing.name === name)) continue;
20031
+ if ("url" in server) {
20032
+ servers.push({ type: server.type, name, url: server.url, headers: acpEnv(server.headers) });
20033
+ continue;
20034
+ }
19903
20035
  servers.push({ name, command: server.command, args: server.args, env: acpEnv(server.env) });
19904
20036
  }
19905
20037
  return servers;
@@ -19948,6 +20080,37 @@ function createAcpDriver(support11) {
19948
20080
  const asks = /* @__PURE__ */ new Map();
19949
20081
  let nextId = 1;
19950
20082
  let sessionId = null;
20083
+ let sessionConfigResult = null;
20084
+ const modelOf = (result) => {
20085
+ const option = (Array.isArray(result?.configOptions) ? result.configOptions : []).find(
20086
+ (entry) => entry?.id === (support11.selectModel?.configId ?? "model")
20087
+ );
20088
+ return typeof option?.currentValue === "string" ? option.currentValue : null;
20089
+ };
20090
+ const receiveModelVariants = (result) => {
20091
+ sessionConfigResult = result;
20092
+ if (!support11.modelVariants) return;
20093
+ const nativeModel = modelOf(result) ?? cliTurn.model;
20094
+ if (!nativeModel) return;
20095
+ const option = acpVariantOption(result);
20096
+ emit({
20097
+ ...base2(threadId, turnId),
20098
+ type: "session.model-variants",
20099
+ model: nativeModel === cliTurn.model ? turn.model ?? nativeModel : nativeModel,
20100
+ variants: {
20101
+ options: option?.options ?? [],
20102
+ ...option?.currentValue !== void 0 ? { currentValue: option.currentValue } : {}
20103
+ }
20104
+ });
20105
+ };
20106
+ const requestedVariantOption = () => {
20107
+ if (!support11.modelVariants) throw new Error(`${support11.displayName} does not support model variants`);
20108
+ const option = acpVariantOption(sessionConfigResult);
20109
+ if (!option || !option.options.some((entry) => entry.id === turn.variant)) {
20110
+ throw new Error(`${support11.displayName} does not advertise variant ${turn.variant} for this model`);
20111
+ }
20112
+ return option;
20113
+ };
19951
20114
  let interruptTimer = null;
19952
20115
  const rpcPending = /* @__PURE__ */ new Map();
19953
20116
  const send = (obj) => {
@@ -19957,7 +20120,7 @@ function createAcpDriver(support11) {
19957
20120
  }
19958
20121
  appendNative(threadId, { dir: "out", source: SOURCE, msg: nativeLogMessage(obj) });
19959
20122
  };
19960
- const request = (method, params, timeoutMs) => new Promise((resolve12, reject) => {
20123
+ const request = (method, params, timeoutMs, receive) => new Promise((resolve12, reject) => {
19961
20124
  const id = nextId++;
19962
20125
  let timer = null;
19963
20126
  if (timeoutMs) {
@@ -19967,7 +20130,16 @@ function createAcpDriver(support11) {
19967
20130
  }, timeoutMs);
19968
20131
  timer.unref?.();
19969
20132
  }
19970
- rpcPending.set(id, { resolve: resolve12, reject, timer });
20133
+ rpcPending.set(id, {
20134
+ // Consume configuration in wire order: an update following this
20135
+ // response may arrive before the awaiting continuation resumes.
20136
+ resolve: (result) => {
20137
+ receive?.(result);
20138
+ resolve12(result);
20139
+ },
20140
+ reject,
20141
+ timer
20142
+ });
19971
20143
  send({ jsonrpc: "2.0", id, method, params });
19972
20144
  });
19973
20145
  const stop = () => killCliTree(child);
@@ -20150,7 +20322,12 @@ function createAcpDriver(support11) {
20150
20322
  const handleNotification = (msg) => {
20151
20323
  if (msg.method !== "session/update") return;
20152
20324
  const p = msg.params ?? {};
20153
- if (!state.promptSent || p._meta?.isReplay === true) return;
20325
+ if (p._meta?.isReplay === true) return;
20326
+ if (support11.modelVariants && p.update?.sessionUpdate === "config_option_update") {
20327
+ if (!state.settled && sessionId && p.sessionId === sessionId) receiveModelVariants(p.update);
20328
+ return;
20329
+ }
20330
+ if (!state.promptSent) return;
20154
20331
  const u4 = p.update ?? {};
20155
20332
  switch (u4.sessionUpdate) {
20156
20333
  case "agent_message_chunk": {
@@ -20307,6 +20484,7 @@ function createAcpDriver(support11) {
20307
20484
  `${support11.displayName} is configured for image attachments, but this installed runtime does not advertise ACP image input. Update the ${support11.displayName} CLI or send the message without an image.`
20308
20485
  );
20309
20486
  }
20487
+ const sessionServers = mcpServers.filter((server) => !("type" in server) || init?.agentCapabilities?.mcpCapabilities?.[server.type] === true);
20310
20488
  const cursor3 = typeof turn.resumeCursor === "string" ? turn.resumeCursor : null;
20311
20489
  if (!cursor3) sessionAllows.delete(threadId);
20312
20490
  let sessionResult = null;
@@ -20314,16 +20492,23 @@ function createAcpDriver(support11) {
20314
20492
  try {
20315
20493
  sessionResult = await request(
20316
20494
  support11.resumeMethod === "resume" ? "session/resume" : "session/load",
20317
- { sessionId: cursor3, cwd, mcpServers },
20318
- LOAD_SESSION_TIMEOUT
20495
+ { sessionId: cursor3, cwd, mcpServers: sessionServers },
20496
+ LOAD_SESSION_TIMEOUT,
20497
+ (result2) => {
20498
+ if (result2) {
20499
+ sessionId = cursor3;
20500
+ receiveModelVariants(result2);
20501
+ }
20502
+ }
20319
20503
  );
20320
- if (sessionResult) sessionId = cursor3;
20321
20504
  } catch {
20322
20505
  }
20323
20506
  }
20324
20507
  if (!sessionId) {
20325
- sessionResult = await request("session/new", { cwd, mcpServers }, NEW_SESSION_TIMEOUT);
20326
- sessionId = typeof sessionResult?.sessionId === "string" ? sessionResult.sessionId : null;
20508
+ sessionResult = await request("session/new", { cwd, mcpServers: sessionServers }, NEW_SESSION_TIMEOUT, (result2) => {
20509
+ sessionId = typeof result2?.sessionId === "string" ? result2.sessionId : null;
20510
+ receiveModelVariants(result2);
20511
+ });
20327
20512
  if (!sessionId) throw new Error("session/new returned no sessionId");
20328
20513
  }
20329
20514
  let selectedModel = null;
@@ -20341,16 +20526,15 @@ function createAcpDriver(support11) {
20341
20526
  try {
20342
20527
  if (support11.selectModel) {
20343
20528
  const { configId } = support11.selectModel;
20344
- const currentOf = (r2) => (Array.isArray(r2?.configOptions) ? r2.configOptions : []).find((o) => o?.id === configId)?.currentValue ?? null;
20345
- selectedModel = currentOf(sessionResult);
20529
+ selectedModel = modelOf(sessionConfigResult);
20346
20530
  if (cliTurn.model && cliTurn.model !== selectedModel) {
20347
- selectedModel = currentOf(
20348
- await request(
20349
- "session/set_config_option",
20350
- { sessionId, configId, value: cliTurn.model },
20351
- INIT_TIMEOUT
20352
- )
20531
+ sessionResult = await request(
20532
+ "session/set_config_option",
20533
+ { sessionId, configId, value: cliTurn.model },
20534
+ INIT_TIMEOUT,
20535
+ receiveModelVariants
20353
20536
  );
20537
+ selectedModel = modelOf(sessionConfigResult);
20354
20538
  if (selectedModel !== cliTurn.model) {
20355
20539
  throw new Error(
20356
20540
  `${DRIVER_KIND8} did not switch to ${cliTurn.model} (still ${selectedModel ?? "unknown"})`
@@ -20368,16 +20552,34 @@ function createAcpDriver(support11) {
20368
20552
  });
20369
20553
  if (!selectedModel && cliTurn.model) selectedModel = cliTurn.model;
20370
20554
  }
20555
+ if (turn.variant !== void 0) {
20556
+ const option = requestedVariantOption();
20557
+ await request(
20558
+ "session/set_config_option",
20559
+ { sessionId, configId: option.configId, value: turn.variant },
20560
+ SESSION_CONFIG_TIMEOUT,
20561
+ receiveModelVariants
20562
+ );
20563
+ if (requestedVariantOption().currentValue !== turn.variant) {
20564
+ throw new Error(`${support11.displayName} did not apply variant ${turn.variant}`);
20565
+ }
20566
+ }
20371
20567
  } catch (error51) {
20372
20568
  emitSessionStarted();
20373
20569
  throw error51;
20374
20570
  }
20375
20571
  emitSessionStarted();
20376
- state.promptSent = true;
20377
20572
  const text4 = support11.buildPromptText ? support11.buildPromptText(turn) : turn.system ? `${turn.system}
20378
20573
 
20379
20574
  ${turn.text}` : turn.text;
20380
20575
  const imageBlocks = support11.images === true && runtimeAcceptsImages ? await readAcpImageBlocks(images) : [];
20576
+ if (support11.modelVariants && cliTurn.model && modelOf(sessionConfigResult) !== cliTurn.model) {
20577
+ throw new Error(`${support11.displayName} changed model before the prompt`);
20578
+ }
20579
+ if (turn.variant !== void 0 && requestedVariantOption().currentValue !== turn.variant) {
20580
+ throw new Error(`${support11.displayName} changed variant before the prompt`);
20581
+ }
20582
+ state.promptSent = true;
20381
20583
  const result = await request("session/prompt", {
20382
20584
  sessionId,
20383
20585
  prompt: [{ type: "text", text: text4 }, ...imageBlocks]
@@ -20456,6 +20658,7 @@ ${turn.text}` : turn.text;
20456
20658
  images: support11.images !== false,
20457
20659
  nativeImageInput: support11.images === true,
20458
20660
  effortLevels: support11.effortLevels,
20661
+ modelVariants: support11.modelVariants === true,
20459
20662
  // OpenMausBot supplies a per-bot approvalMode on every harness
20460
20663
  // turn, which safely overrides a legacy instance fullAuto value.
20461
20664
  // Direct adapter calls that omit it still fail closed in sendTurn.
@@ -22513,7 +22716,7 @@ var init_ask_question = __esm({
22513
22716
 
22514
22717
  // server/drivers/claude.ts
22515
22718
  import { createHash as createHash5, randomBytes as randomBytes2 } from "node:crypto";
22516
- import { chmodSync as chmodSync4, mkdtempSync as mkdtempSync2, readFileSync as readFileSync7, rmSync as rmSync4, unlinkSync as unlinkSync3, writeFileSync as writeFileSync5 } from "node:fs";
22719
+ import { chmodSync as chmodSync4, mkdtempSync as mkdtempSync2, readFileSync as readFileSync7, rmSync as rmSync5, unlinkSync as unlinkSync3, writeFileSync as writeFileSync5 } from "node:fs";
22517
22720
  import { createServer as createNetServer } from "node:net";
22518
22721
  import { homedir as homedir9, tmpdir as tmpdir3 } from "node:os";
22519
22722
  import { join as join18, dirname as dirname9, isAbsolute as isAbsolute4, normalize } from "node:path";
@@ -22577,6 +22780,13 @@ function claudeEnvironment(model, source = process.env, configDir, instanceEnvir
22577
22780
  function inheritsUserConfig(env) {
22578
22781
  return env.OMB_CLAUDE_INHERIT_USER_CONFIG === "1";
22579
22782
  }
22783
+ function claudeInheritWarning(env) {
22784
+ if (!inheritsUserConfig(env)) return void 0;
22785
+ return {
22786
+ title: "Bots inherit this machine's Claude Code setup",
22787
+ message: "OMB_CLAUDE_INHERIT_USER_CONFIG=1 is set on the OpenMausBot process, so every Claude bot also loads this computer's own MCP servers, connectors, skills, hooks and personal CLAUDE.md on every turn \u2014 often thousands of extra tokens per model call, and tools nobody gave the bot. Unless a bot genuinely needs a server from your user-scope Claude config, remove the variable and restart; add the server under Settings \u2192 MCP servers or the bot project's .mcp.json instead."
22788
+ };
22789
+ }
22580
22790
  function readClaudeAuthSettings(env, instanceEnvironment = {}) {
22581
22791
  if (CLAUDE_ACCOUNT_ENV_KEYS.some((key) => instanceEnvironment[key])) return {};
22582
22792
  try {
@@ -22637,21 +22847,17 @@ function claudeCliUpdate(version2, cli) {
22637
22847
  if (!parsed || versionAtLeast(parsed, CLAUDE_CONTEXT_CONTROL_MIN_VERSION)) return void 0;
22638
22848
  const floor = CLAUDE_CONTEXT_CONTROL_MIN_VERSION.join(".");
22639
22849
  const missing = Object.keys(CLAUDE_FLAG_FLOORS).filter((flag) => !claudeCliSupports(parsed, flag));
22850
+ const effects = [
22851
+ ...missing.includes("--autocompact") ? ["no compaction window picked by OpenMausBot"] : [],
22852
+ ...missing.includes("--setting-sources") ? ["bots still see this machine's own Claude Code setup"] : [],
22853
+ ...missing.includes("--system-prompt-snapshot") ? ["coordinated resumed turns cannot refresh stale system prompts"] : []
22854
+ ];
22640
22855
  return {
22641
22856
  title: "Update Claude Code for context controls",
22642
- message: `Claude Code ${parsed.join(".")} predates ${floor}, so bots run without ${missing.join(", ")}: no compaction window picked by OpenMausBot` + (missing.includes("--setting-sources") ? ", and bots still see this machine's own Claude Code setup" : "") + ". Update it, then refresh Engines.",
22857
+ message: `Claude Code ${parsed.join(".")} predates ${floor}, so bots run without ${missing.join(", ")}: ${effects.join("; ")}. Update it, then refresh Engines.`,
22643
22858
  command: cli === "claude" ? "claude update" : `${cli} update`
22644
22859
  };
22645
22860
  }
22646
- function claudeCliHelpSupportsFlag(help, flag) {
22647
- if (!help) return false;
22648
- const escaped = flag.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
22649
- return new RegExp(`^\\s*${escaped}\\b`, "m").test(help);
22650
- }
22651
- function claudeAutoCompactSupported(version2, help) {
22652
- if (help !== null) return claudeCliHelpSupportsFlag(help, "--autocompact");
22653
- return claudeCliSupports(version2, "--autocompact");
22654
- }
22655
22861
  async function resolveClaudeTurnModel(model, env) {
22656
22862
  if (!model || decodeInjectId(model) || STATIC_CLAUDE_MODELS.options.some((option) => option.id === model)) {
22657
22863
  return model;
@@ -22699,7 +22905,7 @@ function readClaudeModelCatalog(env = process.env) {
22699
22905
  function removePrivateTempDir(filePath) {
22700
22906
  if (!filePath) return true;
22701
22907
  try {
22702
- rmSync4(dirname9(filePath), { recursive: true, force: true, maxRetries: 3, retryDelay: 50 });
22908
+ rmSync5(dirname9(filePath), { recursive: true, force: true, maxRetries: 3, retryDelay: 50 });
22703
22909
  return true;
22704
22910
  } catch {
22705
22911
  return false;
@@ -22914,6 +23120,7 @@ function decodeConfig2(raw) {
22914
23120
  return {
22915
23121
  cli: typeof o.cli === "string" ? o.cli : "claude",
22916
23122
  ...configDir ? { configDir } : {},
23123
+ ...o.managed === true ? { managed: true } : {},
22917
23124
  permissionMode: mode ?? "acceptEdits",
22918
23125
  ...tools !== void 0 ? { tools } : {},
22919
23126
  ...disallowedTools !== void 0 ? { disallowedTools } : {}
@@ -23003,9 +23210,12 @@ var init_claude = __esm({
23003
23210
  CLAUDE_FLAG_FLOORS = {
23004
23211
  "--strict-mcp-config": [1, 0, 60],
23005
23212
  "--setting-sources": [1, 0, 122],
23006
- "--autocompact": [2, 1, 122]
23213
+ "--autocompact": [2, 1, 122],
23214
+ // 2.1.267 is the first CLI that accepts it; below that the recorded prompt
23215
+ // simply is not refreshed, which is the pre-existing behaviour.
23216
+ "--system-prompt-snapshot": [2, 1, 267]
23007
23217
  };
23008
- CLAUDE_CONTEXT_CONTROL_MIN_VERSION = CLAUDE_FLAG_FLOORS["--autocompact"];
23218
+ CLAUDE_CONTEXT_CONTROL_MIN_VERSION = CLAUDE_FLAG_FLOORS["--system-prompt-snapshot"];
23009
23219
  DRIVER_KIND2 = "claudeAgent";
23010
23220
  STATIC_CLAUDE_MODELS = {
23011
23221
  default: "claude-sonnet-5",
@@ -23044,10 +23254,14 @@ var init_claude = __esm({
23044
23254
  defaultConfig: () => decodeConfig2({}),
23045
23255
  async create(input) {
23046
23256
  const { instanceId, config: config2 } = input;
23047
- const environment = (model) => claudeEnvironment(model, { ...process.env, ...input.environment }, config2.configDir, input.environment);
23257
+ const environment = (model) => claudeEnvironment(config2.managed ? void 0 : model, { ...process.env, ...input.environment }, config2.configDir, input.environment);
23048
23258
  const catalogEnv = environment();
23259
+ if (inheritsUserConfig(catalogEnv)) {
23260
+ console.error(`claude (${instanceId}): OMB_CLAUDE_INHERIT_USER_CONFIG=1 \u2014 bots inherit this machine's Claude Code MCP servers, skills, hooks and CLAUDE.md on every turn; remove it unless a bot needs a user-scope server`);
23261
+ }
23049
23262
  let models2 = STATIC_CLAUDE_MODELS;
23050
23263
  const refreshModels = async () => {
23264
+ if (config2.managed) return;
23051
23265
  try {
23052
23266
  const resolved = await mergeLocalInject(readClaudeModelCatalog(catalogEnv), catalogEnv);
23053
23267
  if (resolved.options.length) models2 = resolved;
@@ -23056,8 +23270,15 @@ var init_claude = __esm({
23056
23270
  };
23057
23271
  await refreshModels();
23058
23272
  let cliVersion = null;
23059
- let cliHelp = null;
23060
- let cliHelpVersion = null;
23273
+ let cliVersionChecked = false;
23274
+ const readCliVersion = (env) => new Promise((resolve12) => {
23275
+ execCli(
23276
+ config2.cli,
23277
+ ["--version"],
23278
+ { timeout: 8e3, env },
23279
+ (err, stdout2) => resolve12(err ? null : stdout2.trim() || null)
23280
+ );
23281
+ });
23061
23282
  const listeners = /* @__PURE__ */ new Set();
23062
23283
  const active = /* @__PURE__ */ new Map();
23063
23284
  const sessions = /* @__PURE__ */ new Map();
@@ -23124,6 +23345,9 @@ var init_claude = __esm({
23124
23345
  });
23125
23346
  const retryState = /* @__PURE__ */ new Map();
23126
23347
  const sendTurn = async (turn, logicalTurnId) => {
23348
+ if (config2.managed && (!turn.model || turn.model.includes("::") || !config2.configDir || !input.environment.ANTHROPIC_API_KEY || !input.environment.ANTHROPIC_BASE_URL)) {
23349
+ throw new Error("Company model access is unavailable. Reconnect your organization; personal billing will not be used.");
23350
+ }
23127
23351
  const { threadId, botId } = turn;
23128
23352
  const relaunch = logicalTurnId !== void 0;
23129
23353
  if (active.has(threadId) && !relaunch) throw new Error("a turn is already running on this thread");
@@ -23160,17 +23384,27 @@ var init_claude = __esm({
23160
23384
  args.push("--disallowedTools", config2.disallowedTools.join(","));
23161
23385
  }
23162
23386
  const turnEnvironment = environment();
23387
+ if (turn.refreshSystemPrompt && !cliVersionChecked) {
23388
+ const version2 = await readCliVersion(turnEnvironment);
23389
+ if (version2) {
23390
+ cliVersion = parseClaudeCliVersion(version2);
23391
+ cliVersionChecked = true;
23392
+ }
23393
+ }
23163
23394
  const isolated = !inheritsUserConfig(turnEnvironment);
23164
23395
  if (isolated) {
23165
- if (claudeCliSupports(cliVersion, "--strict-mcp-config")) args.push("--strict-mcp-config");
23396
+ if (!turn.mcpFromUserConfig && claudeCliSupports(cliVersion, "--strict-mcp-config")) args.push("--strict-mcp-config");
23166
23397
  if (claudeCliSupports(cliVersion, "--setting-sources")) args.push("--setting-sources", "project");
23167
23398
  }
23168
23399
  const compactWindow = autoCompactWindow(turnEnvironment);
23169
- if (compactWindow && claudeAutoCompactSupported(cliVersion, cliHelp)) {
23400
+ if (compactWindow && claudeCliSupports(cliVersion, "--autocompact")) {
23170
23401
  args.push("--autocompact", compactWindow);
23171
23402
  }
23172
- const turnModel = await resolveClaudeTurnModel(turn.model, turnEnvironment);
23173
- const injected = applyClaudeInject({ ...turnEnvironment }, turnModel);
23403
+ if (turn.refreshSystemPrompt && cliVersionChecked && claudeCliSupports(cliVersion, "--system-prompt-snapshot")) {
23404
+ args.push("--system-prompt-snapshot", "off");
23405
+ }
23406
+ const turnModel = config2.managed ? turn.model : await resolveClaudeTurnModel(turn.model, turnEnvironment);
23407
+ const injected = config2.managed ? { model: turnModel ?? null, injected: false } : applyClaudeInject({ ...turnEnvironment }, turnModel);
23174
23408
  if (injected.model) args.push("--model", injected.model);
23175
23409
  if (turn.effort) args.push("--effort", turn.effort);
23176
23410
  let systemPromptPath = null;
@@ -23289,7 +23523,7 @@ var init_claude = __esm({
23289
23523
  retryState.delete(threadId);
23290
23524
  if (mcpConfigPath) {
23291
23525
  try {
23292
- rmSync4(dirname9(mcpConfigPath), { recursive: true, force: true });
23526
+ rmSync5(dirname9(mcpConfigPath), { recursive: true, force: true });
23293
23527
  } catch {
23294
23528
  }
23295
23529
  }
@@ -23297,7 +23531,7 @@ var init_claude = __esm({
23297
23531
  }
23298
23532
  if (mcpConfigPath) {
23299
23533
  try {
23300
- rmSync4(dirname9(mcpConfigPath), { recursive: true, force: true });
23534
+ rmSync5(dirname9(mcpConfigPath), { recursive: true, force: true });
23301
23535
  } catch {
23302
23536
  }
23303
23537
  }
@@ -23309,7 +23543,7 @@ var init_claude = __esm({
23309
23543
  broker = void 0;
23310
23544
  if (mcpConfigPath) {
23311
23545
  try {
23312
- rmSync4(dirname9(mcpConfigPath), { recursive: true, force: true });
23546
+ rmSync5(dirname9(mcpConfigPath), { recursive: true, force: true });
23313
23547
  } catch {
23314
23548
  }
23315
23549
  mcpConfigPath = null;
@@ -23424,7 +23658,7 @@ var init_claude = __esm({
23424
23658
  session.broker?.pause();
23425
23659
  if (session.mcpConfigPath) {
23426
23660
  try {
23427
- rmSync4(dirname9(session.mcpConfigPath), { recursive: true, force: true });
23661
+ rmSync5(dirname9(session.mcpConfigPath), { recursive: true, force: true });
23428
23662
  } catch {
23429
23663
  }
23430
23664
  session.mcpConfigPath = null;
@@ -23506,7 +23740,10 @@ var init_claude = __esm({
23506
23740
  type: "thread.token-usage.updated",
23507
23741
  input: (msg.usage.input_tokens || 0) + (msg.usage.cache_read_input_tokens || 0),
23508
23742
  output: msg.usage.output_tokens || 0,
23509
- ...typeof msg.usage.cache_read_input_tokens === "number" ? { cachedInput: msg.usage.cache_read_input_tokens } : {}
23743
+ ...typeof msg.usage.cache_read_input_tokens === "number" ? { cachedInput: msg.usage.cache_read_input_tokens } : {},
23744
+ // one assistant message = one model call, and its prompt is
23745
+ // everything in the window: fresh text, cache reads and writes
23746
+ contextTokens: (msg.usage.input_tokens || 0) + (msg.usage.cache_read_input_tokens || 0) + (msg.usage.cache_creation_input_tokens || 0)
23510
23747
  });
23511
23748
  }
23512
23749
  break;
@@ -23575,7 +23812,7 @@ var init_claude = __esm({
23575
23812
  failedBroker?.close();
23576
23813
  if (session.mcpConfigPath) {
23577
23814
  try {
23578
- rmSync4(dirname9(session.mcpConfigPath), { recursive: true, force: true });
23815
+ rmSync5(dirname9(session.mcpConfigPath), { recursive: true, force: true });
23579
23816
  } catch {
23580
23817
  }
23581
23818
  session.mcpConfigPath = null;
@@ -23653,7 +23890,7 @@ var init_claude = __esm({
23653
23890
  session.broker = void 0;
23654
23891
  if (session.mcpConfigPath) {
23655
23892
  try {
23656
- rmSync4(dirname9(session.mcpConfigPath), { recursive: true, force: true });
23893
+ rmSync5(dirname9(session.mcpConfigPath), { recursive: true, force: true });
23657
23894
  } catch {
23658
23895
  }
23659
23896
  session.mcpConfigPath = null;
@@ -23704,7 +23941,7 @@ var init_claude = __esm({
23704
23941
  session.broker?.close();
23705
23942
  if (session.mcpConfigPath) {
23706
23943
  try {
23707
- rmSync4(dirname9(session.mcpConfigPath), { recursive: true, force: true });
23944
+ rmSync5(dirname9(session.mcpConfigPath), { recursive: true, force: true });
23708
23945
  } catch {
23709
23946
  }
23710
23947
  }
@@ -23739,38 +23976,14 @@ var init_claude = __esm({
23739
23976
  } });
23740
23977
  const snapshot = async () => {
23741
23978
  const env = environment();
23742
- const version2 = await new Promise((resolve12) => {
23743
- execCli(
23744
- config2.cli,
23745
- ["--version"],
23746
- { timeout: 8e3, env },
23747
- (err, stdout2) => resolve12(err ? null : stdout2.trim())
23748
- );
23749
- });
23979
+ const version2 = await readCliVersion(env);
23750
23980
  if (!version2) return { state: "unavailable", reason: `\`${config2.cli}\` CLI not found` };
23751
23981
  cliVersion = parseClaudeCliVersion(version2);
23752
- if (version2 !== cliHelpVersion) {
23753
- cliHelp = await new Promise((resolve12) => {
23754
- execCli(
23755
- config2.cli,
23756
- ["--help"],
23757
- { timeout: 8e3, env },
23758
- (err, stdout2) => resolve12(err ? null : stdout2)
23759
- );
23760
- });
23761
- cliHelpVersion = version2;
23762
- }
23763
- const features = cliHelp !== null ? { autocompact: claudeCliHelpSupportsFlag(cliHelp, "--autocompact") } : void 0;
23982
+ cliVersionChecked = true;
23764
23983
  const auth = await claudeAuthStatus(config2.cli, env);
23765
23984
  const update = claudeCliUpdate(version2, config2.cli);
23766
- return {
23767
- state: "available",
23768
- version: version2,
23769
- ...auth,
23770
- ...update ? { update } : {},
23771
- ...features ? { features } : {},
23772
- billing: "subscription"
23773
- };
23985
+ const warning = claudeInheritWarning(env);
23986
+ return { state: "available", version: version2, ...auth, ...update ? { update } : {}, ...warning ? { warning } : {}, billing: "subscription" };
23774
23987
  };
23775
23988
  const generateReview = (prompt, signal) => new Promise((resolve12, reject) => {
23776
23989
  const child = spawnCli(
@@ -24709,6 +24922,9 @@ var init_codex_identity = __esm({
24709
24922
  // server/drivers/codex.ts
24710
24923
  import { existsSync as existsSync10 } from "node:fs";
24711
24924
  import { homedir as homedir13 } from "node:os";
24925
+ function missingNativeCodexThread(error51, cursor3) {
24926
+ return error51 instanceof CodexRpcError && error51.code === -32600 && error51.message === `no rollout found for thread id ${cursor3}`;
24927
+ }
24712
24928
  function codexPredatesAstra(version2) {
24713
24929
  const value = version2.trim();
24714
24930
  const match = /\bcodex-cli\s+v?(\d+)\.(\d+)\.(\d+)(?:[-+][0-9a-z.-]+)?(?![\d.])\b/i.exec(value) ?? /^v?(\d+)\.(\d+)\.(\d+)(?:[-+][0-9a-z.-]+)?$/i.exec(value);
@@ -24743,9 +24959,48 @@ function decodeConfig3(raw) {
24743
24959
  const o = raw ?? {};
24744
24960
  return {
24745
24961
  cli: typeof o.cli === "string" ? o.cli : "codex",
24746
- fullAuto: o.fullAuto === true
24962
+ fullAuto: o.fullAuto === true,
24963
+ ...o.managed && typeof o.managed === "object" ? { managed: decodeManagedCodex(o.managed) } : {}
24747
24964
  };
24748
24965
  }
24966
+ function decodeManagedCodex(raw) {
24967
+ const value = raw;
24968
+ if (typeof value.url !== "string" || !Array.isArray(value.models) || !value.models.length || value.models.some((model) => typeof model !== "string" || !/^[\w][\w./+-]*$/.test(model))) {
24969
+ throw new Error("Invalid Company Codex configuration.");
24970
+ }
24971
+ const url2 = new URL(value.url);
24972
+ const loopback = url2.hostname === "localhost" || url2.hostname === "[::1]" || /^127(?:\.\d{1,3}){3}$/.test(url2.hostname);
24973
+ if (url2.username || url2.password || url2.search || url2.hash || url2.protocol !== "https:" && !(url2.protocol === "http:" && loopback)) throw new Error("Invalid Company Codex endpoint.");
24974
+ return { url: url2.href.replace(/\/$/, ""), models: value.models };
24975
+ }
24976
+ function managedCodexArgs(config2) {
24977
+ return [
24978
+ "-c",
24979
+ 'model_provider="openmaus_company"',
24980
+ "-c",
24981
+ 'model_providers.openmaus_company.name="Company"',
24982
+ "-c",
24983
+ `model_providers.openmaus_company.base_url=${JSON.stringify(config2.url)}`,
24984
+ "-c",
24985
+ 'model_providers.openmaus_company.env_key="OPENMAUSBOT_COMPANY_API_KEY"',
24986
+ "-c",
24987
+ 'model_providers.openmaus_company.wire_api="responses"',
24988
+ "-c",
24989
+ "model_providers.openmaus_company.requires_openai_auth=false",
24990
+ "-c",
24991
+ 'cli_auth_credentials_store="ephemeral"',
24992
+ "-c",
24993
+ "shell_environment_policy.ignore_default_excludes=false"
24994
+ ];
24995
+ }
24996
+ function noteSkippedSseServer(name) {
24997
+ if (skippedSseServers.has(name)) return;
24998
+ skippedSseServers.add(name);
24999
+ console.error(`codex: MCP server ${JSON.stringify(name)} uses the SSE transport, which codex does not speak \u2014 it is available to Claude bots only`);
25000
+ }
25001
+ function tomlInlineTable(entries) {
25002
+ return `{ ${Object.entries(entries).map(([key, value]) => `${JSON.stringify(key)} = ${JSON.stringify(value)}`).join(", ")} }`;
25003
+ }
24749
25004
  function grantedPermissions(raw) {
24750
25005
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) return {};
24751
25006
  return Object.fromEntries(
@@ -24945,23 +25200,43 @@ function codexNativeIncomingLogMessage(message2, sensitiveResponseIds) {
24945
25200
  return message2;
24946
25201
  }
24947
25202
  function mountMcpServer(appServerArgs, env, name, server, preApproved = true) {
24948
- Object.assign(env, server.env);
24949
25203
  const prefix = `mcp_servers.${name}`;
24950
- appServerArgs.push(
24951
- "-c",
24952
- `${prefix}.command=${JSON.stringify(server.command)}`,
24953
- "-c",
24954
- `${prefix}.args=${JSON.stringify(server.args)}`,
24955
- // Values stay in the child environment; argv contains names only so
24956
- // credentials never appear in process listings or diagnostics.
24957
- "-c",
24958
- `${prefix}.env_vars=${JSON.stringify(Object.keys(server.env))}`
24959
- );
25204
+ if ("url" in server) {
25205
+ appServerArgs.push("-c", `${prefix}.url=${JSON.stringify(server.url)}`);
25206
+ const stem = `OMB_MCP_HEADER_${name.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`;
25207
+ const variables = {};
25208
+ Object.entries(server.headers).forEach(([header, value], index) => {
25209
+ const bearer = header.toLowerCase() === "authorization" ? /^Bearer\s+(\S+)$/i.exec(value) : null;
25210
+ if (bearer) {
25211
+ env[`${stem}_BEARER`] = bearer[1];
25212
+ appServerArgs.push("-c", `${prefix}.bearer_token_env_var=${JSON.stringify(`${stem}_BEARER`)}`);
25213
+ return;
25214
+ }
25215
+ const variable = `${stem}_${index}`;
25216
+ env[variable] = value;
25217
+ variables[header] = variable;
25218
+ });
25219
+ if (Object.keys(variables).length) {
25220
+ appServerArgs.push("-c", `${prefix}.env_http_headers=${tomlInlineTable(variables)}`);
25221
+ }
25222
+ } else {
25223
+ Object.assign(env, server.env);
25224
+ appServerArgs.push(
25225
+ "-c",
25226
+ `${prefix}.command=${JSON.stringify(server.command)}`,
25227
+ "-c",
25228
+ `${prefix}.args=${JSON.stringify(server.args)}`,
25229
+ // Values stay in the child environment; argv contains names only so
25230
+ // credentials never appear in process listings or diagnostics.
25231
+ "-c",
25232
+ `${prefix}.env_vars=${JSON.stringify(Object.keys(server.env))}`
25233
+ );
25234
+ }
24960
25235
  if (preApproved) {
24961
25236
  appServerArgs.push("-c", `${prefix}.default_tools_approval_mode="auto"`);
24962
25237
  }
24963
25238
  }
24964
- var DRIVER_KIND3, ASTRA_MODEL_ID, ASTRA_MIN_CODEX_VERSION, QUESTION_TIMEOUT_NOTE2, DENY_TIMEOUT_NOTE2, plainRecord, containsControlCharacter, CodexDriver;
25239
+ var DRIVER_KIND3, ASTRA_MODEL_ID, ASTRA_MIN_CODEX_VERSION, CodexRpcError, QUESTION_TIMEOUT_NOTE2, DENY_TIMEOUT_NOTE2, skippedSseServers, plainRecord, containsControlCharacter, CodexDriver;
24965
25240
  var init_codex = __esm({
24966
25241
  "server/drivers/codex.ts"() {
24967
25242
  "use strict";
@@ -24978,12 +25253,21 @@ var init_codex = __esm({
24978
25253
  init_codex_instructions();
24979
25254
  init_codex_device_auth();
24980
25255
  init_codex_identity();
25256
+ init_resume_recovery();
24981
25257
  init_codex_catalog();
24982
25258
  DRIVER_KIND3 = "codex";
24983
25259
  ASTRA_MODEL_ID = "gpt-6-astra";
24984
25260
  ASTRA_MIN_CODEX_VERSION = [0, 153, 1];
25261
+ CodexRpcError = class extends Error {
25262
+ code;
25263
+ constructor(error51) {
25264
+ super(error51.message ?? JSON.stringify(error51));
25265
+ this.code = error51.code;
25266
+ }
25267
+ };
24985
25268
  QUESTION_TIMEOUT_NOTE2 = "No answer was given \u2014 use your best judgment.";
24986
25269
  DENY_TIMEOUT_NOTE2 = "OpenMausBot: nobody answered this permission request in time. Skip this action and finish what you can without it.";
25270
+ skippedSseServers = /* @__PURE__ */ new Set();
24987
25271
  plainRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
24988
25272
  containsControlCharacter = (value) => {
24989
25273
  for (const character of value) {
@@ -25022,8 +25306,9 @@ var init_codex = __esm({
25022
25306
  return env;
25023
25307
  };
25024
25308
  const catalogEnv = childEnv();
25025
- let models2 = STATIC_CODEX_MODELS;
25309
+ let models2 = config2.managed ? { default: config2.managed.models[0], options: config2.managed.models.map((id) => ({ id, label: id })) } : STATIC_CODEX_MODELS;
25026
25310
  const refreshModels = async () => {
25311
+ if (config2.managed) return;
25027
25312
  try {
25028
25313
  const resolved = await readCodexModelCatalog(catalogEnv, fetch, config2.cli);
25029
25314
  if (resolved.options.length) models2 = resolved;
@@ -25049,11 +25334,28 @@ var init_codex = __esm({
25049
25334
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
25050
25335
  });
25051
25336
  const sendTurn = async (turn) => {
25337
+ if (config2.managed) {
25338
+ if (!turn.model) {
25339
+ throw new Error("Company model access is unavailable: no model is selected. Reconnect your organization; personal billing will not be used.");
25340
+ }
25341
+ if (!config2.managed.models.includes(turn.model)) {
25342
+ throw new Error("Company model access is unavailable: " + turn.model + " is not approved for your organization. Reconnect your organization; personal billing will not be used.");
25343
+ }
25344
+ if (!input.environment.OPENMAUSBOT_COMPANY_API_KEY) {
25345
+ throw new Error("Company model access is unavailable: OPENMAUSBOT_COMPANY_API_KEY is missing. Reconnect your organization; personal billing will not be used.");
25346
+ }
25347
+ if (!input.environment.CODEX_HOME) {
25348
+ throw new Error("Company model access is unavailable: CODEX_HOME is missing. Reconnect your organization; personal billing will not be used.");
25349
+ }
25350
+ }
25052
25351
  let stopRequested = false;
25352
+ let promptSubmitted = false;
25353
+ let recoveredMissingSession = false;
25053
25354
  const stopSignal = new AbortController();
25054
25355
  const { threadId } = turn;
25055
25356
  const approvalMode = turn.approvalMode ?? (config2.fullAuto ? "full" : "ask");
25056
25357
  for (const [name, server] of Object.entries(turn.integrations?.custom ?? {})) {
25358
+ if ("url" in server) continue;
25057
25359
  const reserved = Object.keys(server.env).find(isHarnessOwnedMcpEnvName);
25058
25360
  if (reserved) {
25059
25361
  throw new Error(`Custom MCP server \u201C${name}\u201D cannot set reserved environment variable \u201C${reserved}\u201D`);
@@ -25065,7 +25367,7 @@ var init_codex = __esm({
25065
25367
  const retryScale = Number(process.env.FAKE_CODEX_RETRY_SCALE ?? "1");
25066
25368
  const launchAttempt = async (attempt) => {
25067
25369
  const env = childEnv();
25068
- const appServerArgs = ["app-server", ...codexLocalProviderArgs(env, turn.model)];
25370
+ const appServerArgs = ["app-server", ...config2.managed ? managedCodexArgs(config2.managed) : codexLocalProviderArgs(env, turn.model)];
25069
25371
  if (turn.integrations?.composio) {
25070
25372
  mountMcpServer(appServerArgs, env, "openmausbot_connectors", turn.integrations.composio);
25071
25373
  }
@@ -25079,6 +25381,10 @@ var init_codex = __esm({
25079
25381
  mountMcpServer(appServerArgs, env, "browser", turn.integrations.browser);
25080
25382
  }
25081
25383
  for (const [name, server] of Object.entries(turn.integrations?.custom ?? {})) {
25384
+ if ("url" in server && server.type === "sse") {
25385
+ noteSkippedSseServer(name);
25386
+ continue;
25387
+ }
25082
25388
  mountMcpServer(appServerArgs, env, name, server, false);
25083
25389
  }
25084
25390
  if (turn.integrations?.phone) {
@@ -25111,9 +25417,12 @@ var init_codex = __esm({
25111
25417
  lastError: "",
25112
25418
  lastText: "",
25113
25419
  sawStreamDelta: false,
25114
- // codex reports token usage as a running THREAD total; the harness
25115
- // wants this turn's figure, so the last report is banked on settle
25116
- usage: void 0
25420
+ // codex reports token usage as a running total for this app-server
25421
+ // process. The harness wants this turn's figure: the total minus
25422
+ // whatever the process already carried before turn/start (a resumed
25423
+ // thread may restore earlier usage), banked on settle.
25424
+ usage: void 0,
25425
+ usageBaseline: void 0
25117
25426
  };
25118
25427
  const asks = /* @__PURE__ */ new Map();
25119
25428
  let nextId = 1;
@@ -25140,6 +25449,12 @@ var init_codex = __esm({
25140
25449
  rpcPending.set(id, {
25141
25450
  resolve: (v) => {
25142
25451
  clearTimeout(timer);
25452
+ if (method === "thread/start" || method === "thread/resume") {
25453
+ const returnedId = v?.thread?.id;
25454
+ const requestedId = method === "thread/resume" && params && typeof params === "object" && "threadId" in params ? params.threadId : null;
25455
+ if (typeof returnedId === "string" && returnedId) codexThreadId = returnedId;
25456
+ else if (typeof requestedId === "string" && requestedId) codexThreadId = requestedId;
25457
+ }
25143
25458
  if (method === "turn/start") {
25144
25459
  if (typeof v?.turn?.id !== "string" || !v.turn.id) {
25145
25460
  reject(new Error("Codex did not return a native turn id"));
@@ -25214,6 +25529,18 @@ var init_codex = __esm({
25214
25529
  }
25215
25530
  return;
25216
25531
  }
25532
+ if (isQuestion && (!Array.isArray(params.questions) || params.questions.length !== 1)) {
25533
+ const bundled = Array.isArray(params.questions) && params.questions.length > 1;
25534
+ send({
25535
+ jsonrpc: "2.0",
25536
+ id: msg.id,
25537
+ error: {
25538
+ code: -32602,
25539
+ message: bundled ? `ask supports one question per call; this request bundled ${params.questions.length}. Split it into separate asks, one question each.` : Array.isArray(params.questions) ? "ask supports one question per call; this request sent none." : "ask supports one question per call; params.questions must be an array with exactly one question."
25540
+ }
25541
+ });
25542
+ return;
25543
+ }
25217
25544
  const mcpTool = isLegacyMcpPermission ? String(params.message ?? "").match(/tool "([^"]+)"/)?.[1] : void 0;
25218
25545
  const tool = mcpAppApproval ? mcpAppApproval.tool : isLegacyMcpPermission ? mcpTool ?? "mcp" : isAdditionalPermission ? "permissions" : method === "item/fileChange/requestApproval" || method === "applyPatchApproval" ? "edit" : isQuestion ? "ask_user" : "shell";
25219
25546
  const permissionResult = (allow) => isMcpPermission ? allow ? mcpAppApproval?.allowResult ?? { action: "accept", content: {} } : { action: "decline" } : isAdditionalPermission ? { permissions: allow ? grantedPermissions(params.permissions) : {}, scope: "turn" } : { decision: allow ? legacy ? "approved" : "accept" : legacy ? "denied" : "decline" };
@@ -25268,6 +25595,11 @@ var init_codex = __esm({
25268
25595
  const connectionError = msg.method === "error" && !("threadId" in p) && !("turnId" in p);
25269
25596
  if (!connectionError) {
25270
25597
  if (!codexThreadId || p.threadId !== codexThreadId) return;
25598
+ if (!codexTurnId && msg.method === "thread/tokenUsage/updated" && p.tokenUsage?.total) {
25599
+ const t2 = p.tokenUsage.total;
25600
+ state.usageBaseline = { input: t2.inputTokens ?? 0, output: t2.outputTokens ?? 0, cachedInput: t2.cachedInputTokens ?? 0 };
25601
+ return;
25602
+ }
25271
25603
  if (!codexTurnId) {
25272
25604
  if (startingNativeTurn) {
25273
25605
  if (earlyNotifications.length >= 1024) {
@@ -25351,22 +25683,35 @@ var init_codex = __esm({
25351
25683
  break;
25352
25684
  }
25353
25685
  case "thread/tokenUsage/updated": {
25354
- const turnUsage = p.tokenUsage?.last ?? p.tokenUsage?.total;
25355
- if (turnUsage) {
25686
+ const t2 = p.tokenUsage?.total;
25687
+ const last = p.tokenUsage?.last;
25688
+ const shape = (u4) => ({
25689
+ input: u4.inputTokens ?? 0,
25690
+ output: u4.outputTokens ?? 0,
25691
+ cachedInput: u4.cachedInputTokens ?? 0
25692
+ });
25693
+ if (t2) {
25694
+ const b = state.usageBaseline ?? { input: 0, output: 0, cachedInput: 0 };
25695
+ const now = shape(t2);
25356
25696
  state.usage = {
25357
- input: turnUsage.inputTokens ?? 0,
25358
- output: turnUsage.outputTokens ?? 0,
25359
- ...typeof turnUsage.cachedInputTokens === "number" ? { cachedInput: turnUsage.cachedInputTokens } : {}
25697
+ input: Math.max(0, now.input - b.input),
25698
+ output: Math.max(0, now.output - b.output),
25699
+ ...typeof t2.cachedInputTokens === "number" ? { cachedInput: Math.max(0, now.cachedInput - b.cachedInput) } : {}
25360
25700
  };
25701
+ } else if (last) {
25702
+ state.usage = { input: last.inputTokens ?? 0, output: last.outputTokens ?? 0, ...typeof last.cachedInputTokens === "number" ? { cachedInput: last.cachedInputTokens } : {} };
25361
25703
  }
25362
- const t2 = p.tokenUsage?.total;
25363
25704
  if (t2) {
25705
+ const window = p.tokenUsage?.modelContextWindow;
25364
25706
  emit({
25365
25707
  ...base2(threadId, turnId),
25366
25708
  type: "thread.token-usage.updated",
25367
25709
  input: t2.inputTokens ?? 0,
25368
25710
  output: t2.outputTokens ?? 0,
25369
- ...typeof t2.cachedInputTokens === "number" ? { cachedInput: t2.cachedInputTokens } : {}
25711
+ ...typeof t2.cachedInputTokens === "number" ? { cachedInput: t2.cachedInputTokens } : {},
25712
+ // the last call's prompt is what fills the window
25713
+ ...last && typeof last.inputTokens === "number" ? { contextTokens: last.inputTokens } : {},
25714
+ ...typeof window === "number" && window > 0 ? { contextWindow: window } : {}
25370
25715
  });
25371
25716
  }
25372
25717
  break;
@@ -25420,7 +25765,7 @@ var init_codex = __esm({
25420
25765
  const pend = rpcPending.get(msg.id);
25421
25766
  if (pend) {
25422
25767
  rpcPending.delete(msg.id);
25423
- if (msg.error) pend.reject(new Error(msg.error.message ?? JSON.stringify(msg.error)));
25768
+ if (msg.error) pend.reject(new CodexRpcError(msg.error));
25424
25769
  else pend.resolve(msg.result);
25425
25770
  }
25426
25771
  } else if (msg.id !== void 0 && msg.method) {
@@ -25451,7 +25796,7 @@ var init_codex = __esm({
25451
25796
  }
25452
25797
  const recentStderr = stderrSinceOutput.trim();
25453
25798
  const hadStreamedOutput = codexTurnId !== null || state.sawStreamDelta;
25454
- const verdict = classifyError({ exitCode: code, stderr: recentStderr || stderr });
25799
+ const verdict = signal !== null ? { transient: false, reason: "interrupted" } : classifyError({ exitCode: code, stderr: recentStderr });
25455
25800
  if (!stopRequested && codexTurnId === null && earlyNotifications.length === 0 && verdict.transient && attempt < RETRY_MAX_ATTEMPTS - 1) {
25456
25801
  const delayMs = computeBackoff(attempt);
25457
25802
  attempt++;
@@ -25518,30 +25863,39 @@ var init_codex = __esm({
25518
25863
  autoAcceptPermissions = approvalMode === "full";
25519
25864
  const cursor3 = typeof turn.resumeCursor === "string" ? turn.resumeCursor : null;
25520
25865
  let startedModel = null;
25866
+ let resumedNativeThread = false;
25867
+ let promptText = turn.text;
25521
25868
  if (cursor3) {
25869
+ const resumeThread = () => request("thread/resume", {
25870
+ threadId: cursor3,
25871
+ developerInstructions,
25872
+ ...approvalParams.thread
25873
+ });
25522
25874
  try {
25523
- const resumed = await request("thread/resume", {
25524
- threadId: cursor3,
25525
- developerInstructions,
25526
- ...approvalParams.thread
25527
- });
25528
- codexThreadId = resumed?.thread?.id ?? cursor3;
25529
- } catch (error51) {
25530
- if (approvalParams.fallback && permissionProfileUnsupported(error51)) {
25875
+ let resumed;
25876
+ try {
25877
+ resumed = await resumeThread();
25878
+ } catch (error51) {
25879
+ if (!approvalParams.fallback || !permissionProfileUnsupported(error51)) throw error51;
25531
25880
  approvalParams = approvalParams.fallback;
25532
- const resumed = await request("thread/resume", {
25533
- threadId: cursor3,
25534
- developerInstructions,
25535
- ...approvalParams.thread
25536
- });
25537
- codexThreadId = resumed?.thread?.id ?? cursor3;
25538
- } else {
25539
- throw error51;
25881
+ resumed = await resumeThread();
25540
25882
  }
25883
+ codexThreadId = resumed?.thread?.id ?? cursor3;
25884
+ resumedNativeThread = true;
25885
+ } catch (error51) {
25886
+ const failure = classifyResumeFailure({
25887
+ attempted: true,
25888
+ rejected: error51 instanceof CodexRpcError,
25889
+ promptSubmitted,
25890
+ producedOutput: state.sawStreamDelta
25891
+ });
25892
+ if (!config2.managed || recoveredMissingSession || stopRequested || state.settled || !turn.recoveryText?.trim() || !missingNativeCodexThread(error51, cursor3) || !mayReplay(failure)) throw error51;
25893
+ recoveredMissingSession = true;
25894
+ promptText = recoveryPromptFor({ recoveryText: turn.recoveryText, currentText: turn.text, failure }).text;
25541
25895
  }
25542
25896
  }
25543
25897
  if (!codexThreadId) {
25544
- const selection = decodeCodexSelection(turn.model);
25898
+ const selection = config2.managed ? { model: turn.model, modelProvider: "openmaus_company" } : decodeCodexSelection(turn.model);
25545
25899
  const startThread = () => request("thread/start", {
25546
25900
  developerInstructions,
25547
25901
  cwd: turn.cwd ?? homedir13(),
@@ -25562,14 +25916,14 @@ var init_codex = __esm({
25562
25916
  startedModel = started?.model ?? null;
25563
25917
  }
25564
25918
  if (!codexThreadId) throw new Error("Codex did not return a native thread id");
25565
- await syncCodexInstructions(threadId, codexThreadId, developerInstructions, Boolean(cursor3), request);
25919
+ await syncCodexInstructions(threadId, codexThreadId, developerInstructions, resumedNativeThread, request);
25566
25920
  emit({ ...base2(threadId, turnId), type: "session.started", sessionId: codexThreadId, model: startedModel ?? turn.model ?? null });
25567
- const promptText = turn.text;
25568
25921
  const turnInput = [
25569
25922
  ...promptText ? [{ type: "text", text: promptText }] : [],
25570
25923
  ...(turn.images ?? []).map((image) => ({ type: "localImage", path: image.path }))
25571
25924
  ];
25572
25925
  const startTurn = () => {
25926
+ promptSubmitted = true;
25573
25927
  startingNativeTurn = true;
25574
25928
  return request("turn/start", {
25575
25929
  threadId: codexThreadId,
@@ -25599,7 +25953,7 @@ var init_codex = __esm({
25599
25953
  const message2 = e instanceof Error ? e.message : String(e);
25600
25954
  const needsAuth = /(?:\b401\b|unauthorized|missing bearer|authentication required)/i.test(message2);
25601
25955
  const verdict = classifyError(failure);
25602
- if (!state.settled && !abandoned && !needsAuth && verdict.transient && attempt < RETRY_MAX_ATTEMPTS - 1 && state.sawStreamDelta === false) {
25956
+ if (!state.settled && !abandoned && !recoveredMissingSession && !stopRequested && !needsAuth && verdict.transient && attempt < RETRY_MAX_ATTEMPTS - 1 && state.sawStreamDelta === false) {
25603
25957
  const delayMs = computeBackoff(attempt);
25604
25958
  attempt++;
25605
25959
  emit({
@@ -25649,6 +26003,7 @@ var init_codex = __esm({
25649
26003
  );
25650
26004
  });
25651
26005
  if (!version2) return { state: "unavailable", reason: `\`${config2.cli}\` CLI not found` };
26006
+ if (config2.managed) return { state: "available", version: version2, authenticated: Boolean(input.environment.OPENMAUSBOT_COMPANY_API_KEY && input.environment.CODEX_HOME), billing: "metered" };
25652
26007
  const authenticated = await new Promise((resolve12) => {
25653
26008
  execCli(
25654
26009
  config2.cli,
@@ -33457,7 +33812,9 @@ async function mountChatTools(integrations, signal) {
33457
33812
  const servers = [];
33458
33813
  if (integrations?.agents) servers.push(["agents", integrations.agents]);
33459
33814
  if (integrations?.composio) servers.push(["composio", integrations.composio]);
33460
- servers.push(...Object.entries(integrations?.custom ?? {}));
33815
+ for (const [name, server] of Object.entries(integrations?.custom ?? {})) {
33816
+ if ("command" in server) servers.push([name, server]);
33817
+ }
33461
33818
  if (servers.length > 32) throw new Error("MCP server count exceeds the 32-server limit");
33462
33819
  const clients = [];
33463
33820
  let closed = false;
@@ -35682,12 +36039,14 @@ function parseOpenCodeModelsOutput(stdout2) {
35682
36039
  const name = typeof record3.name === "string" && record3.name.trim() ? record3.name.trim() : labelForModel(model);
35683
36040
  const limit = record3.limit && typeof record3.limit === "object" && !Array.isArray(record3.limit) ? record3.limit : {};
35684
36041
  const contextWindow = typeof limit.context === "number" && Number.isFinite(limit.context) && limit.context > 0 ? Math.floor(limit.context) : void 0;
36042
+ const variants = record3.variants && typeof record3.variants === "object" && !Array.isArray(record3.variants) ? Object.entries(record3.variants).filter(([, settings2]) => settings2 && typeof settings2 === "object" && !Array.isArray(settings2) && settings2.disabled !== true).map(([id]) => ({ id, label: labelForModel(id) })) : void 0;
35685
36043
  seen.add(slug);
35686
36044
  options.push({
35687
36045
  id: slug,
35688
36046
  label: `${providerLabel(provider)} \xB7 ${name}`,
35689
36047
  ...localModelRecord(record3) ? { custom: true, loaded: true } : {},
35690
- ...contextWindow ? { contextWindow } : {}
36048
+ ...contextWindow ? { contextWindow } : {},
36049
+ ...variants ? { variants } : {}
35691
36050
  });
35692
36051
  };
35693
36052
  for (const line of stdout2.split(/\r?\n/u)) {
@@ -35894,6 +36253,7 @@ var init_opencode_go = __esm({
35894
36253
  spawnArgs: () => ["acp"],
35895
36254
  credentialEnv: ["OPENCODE_API_KEY"],
35896
36255
  selectModel: { configId: "model" },
36256
+ modelVariants: true,
35897
36257
  resolveTurnModel: (model, env) => model ? ensureOpenCodeInjectModel(normalizeLegacyOpenCodeModel(model, env), env) : model,
35898
36258
  transformEnv: stripForeignProviderKeys,
35899
36259
  pickAuthMethod: () => null,
@@ -43486,7 +43846,7 @@ var init_openai_compat = __esm({
43486
43846
  });
43487
43847
 
43488
43848
  // server/drivers/pi.ts
43489
- import { chmodSync as chmodSync6, existsSync as existsSync17, mkdirSync as mkdirSync15, mkdtempSync as mkdtempSync3, readFileSync as readFileSync17, rmSync as rmSync5, writeFileSync as writeFileSync12 } from "node:fs";
43849
+ import { chmodSync as chmodSync6, existsSync as existsSync17, mkdirSync as mkdirSync15, mkdtempSync as mkdtempSync3, readFileSync as readFileSync17, rmSync as rmSync6, writeFileSync as writeFileSync12 } from "node:fs";
43490
43850
  import { homedir as homedir21, tmpdir as tmpdir4 } from "node:os";
43491
43851
  import { join as join30 } from "node:path";
43492
43852
  function readPiPromptImages(turn) {
@@ -43822,7 +44182,7 @@ var init_pi = __esm({
43822
44182
  writeFileSync12(join30(mcpTempDir, "mcp.json"), JSON.stringify({ mcpServers }), { mode: 384 });
43823
44183
  } catch (err) {
43824
44184
  try {
43825
- rmSync5(mcpTempDir, { recursive: true, force: true });
44185
+ rmSync6(mcpTempDir, { recursive: true, force: true });
43826
44186
  } catch {
43827
44187
  }
43828
44188
  throw err;
@@ -43843,7 +44203,7 @@ var init_pi = __esm({
43843
44203
  } catch (err) {
43844
44204
  if (mcpTempDir) {
43845
44205
  try {
43846
- rmSync5(mcpTempDir, { recursive: true, force: true });
44206
+ rmSync6(mcpTempDir, { recursive: true, force: true });
43847
44207
  } catch {
43848
44208
  }
43849
44209
  }
@@ -43900,7 +44260,7 @@ var init_pi = __esm({
43900
44260
  }
43901
44261
  if (mcpTempDir) {
43902
44262
  try {
43903
- rmSync5(mcpTempDir, { recursive: true, force: true });
44263
+ rmSync6(mcpTempDir, { recursive: true, force: true });
43904
44264
  } catch {
43905
44265
  }
43906
44266
  }
@@ -44913,7 +45273,7 @@ __export(browser_engine_exports, {
44913
45273
  });
44914
45274
  import { spawn as spawn8 } from "node:child_process";
44915
45275
  import { createHash as createHash8, randomBytes as randomBytes3, randomUUID as randomUUID11 } from "node:crypto";
44916
- import { chmodSync as chmodSync7, constants as constants2, copyFileSync, existsSync as existsSync18, mkdirSync as mkdirSync16, readFileSync as readFileSync20, readdirSync as readdirSync3, renameSync as renameSync5, rmSync as rmSync6, statSync as statSync3, unlinkSync as unlinkSync4, writeFileSync as writeFileSync13 } from "node:fs";
45276
+ import { chmodSync as chmodSync7, constants as constants2, copyFileSync, existsSync as existsSync18, mkdirSync as mkdirSync16, readFileSync as readFileSync20, readdirSync as readdirSync3, renameSync as renameSync6, rmSync as rmSync7, statSync as statSync4, unlinkSync as unlinkSync4, writeFileSync as writeFileSync13 } from "node:fs";
44917
45277
  import { homedir as homedir23, tmpdir as tmpdir5 } from "node:os";
44918
45278
  import { delimiter as delimiter6, dirname as dirname11, join as join35, resolve as resolve10 } from "node:path";
44919
45279
  function browserRestoreKey(session) {
@@ -45031,7 +45391,7 @@ async function prepareBrowserSessionState(binaryPath, session, options = {}) {
45031
45391
  const candidates = [".json.enc", ".json"].flatMap((suffix) => {
45032
45392
  const path3 = join35(directory, `${session}-${session}${suffix}`);
45033
45393
  try {
45034
- const stat4 = statSync3(path3);
45394
+ const stat4 = statSync4(path3);
45035
45395
  if (!stat4.isFile()) throw new Error("Saved browser state is not a file.");
45036
45396
  return [{ path: path3, suffix, modified: stat4.mtimeMs }];
45037
45397
  } catch (error51) {
@@ -45126,7 +45486,7 @@ async function installAgentBrowserBinary(options = {}) {
45126
45486
  const staging = `${destination}.${randomBytes3(6).toString("hex")}.part`;
45127
45487
  writeFileSync13(staging, body, { mode: 493 });
45128
45488
  if (platform !== "win32") chmodSync7(staging, 493);
45129
- renameSync5(staging, destination);
45489
+ renameSync6(staging, destination);
45130
45490
  return destination;
45131
45491
  }
45132
45492
  function ensureChrome(binaryPath, options = {}) {
@@ -45243,7 +45603,7 @@ function agentBrowserFrame(input) {
45243
45603
  } catch {
45244
45604
  fail(new Error("the browser reported a picture it did not write"));
45245
45605
  } finally {
45246
- rmSync6(file2, { force: true });
45606
+ rmSync7(file2, { force: true });
45247
45607
  }
45248
45608
  };
45249
45609
  child.on("error", (error51) => done(error51 instanceof Error ? error51 : new Error(String(error51))));
@@ -45262,7 +45622,7 @@ function describeBrowserEngine(status) {
45262
45622
  }
45263
45623
  function agentBrowserBinaryExists(dataDir = DATA_DIR) {
45264
45624
  try {
45265
- return statSync3(pinnedBinaryPath(dataDir)).isFile();
45625
+ return statSync4(pinnedBinaryPath(dataDir)).isFile();
45266
45626
  } catch {
45267
45627
  return false;
45268
45628
  }
@@ -45325,7 +45685,7 @@ var init_browser_engine = __esm({
45325
45685
  // server/cli.ts
45326
45686
  var import_qrcode_terminal = __toESM(require_main(), 1);
45327
45687
  import { spawn as spawn9 } from "node:child_process";
45328
- import { closeSync as closeSync4, existsSync as existsSync19, mkdirSync as mkdirSync17, openSync as openSync4, readFileSync as readFileSync21 } from "node:fs";
45688
+ import { closeSync as closeSync5, existsSync as existsSync19, mkdirSync as mkdirSync17, openSync as openSync5, readFileSync as readFileSync21 } from "node:fs";
45329
45689
  import { homedir as homedir24 } from "node:os";
45330
45690
  import { dirname as dirname12, join as join36, resolve as resolve11 } from "node:path";
45331
45691
  import { createInterface as createInterface2 } from "node:readline/promises";
@@ -48288,7 +48648,7 @@ async function runPhoneSetup(options, io, deps) {
48288
48648
  const device = await io.choose("Use OpenMausBot on your phone?", [
48289
48649
  "Skip for now",
48290
48650
  "iPhone / iPad \u2014 native app or Safari",
48291
- "Android \u2014 web browser"
48651
+ "Android \u2014 app or browser"
48292
48652
  ], 0);
48293
48653
  if (device === 0) return skip();
48294
48654
  const phone = device === 1 ? "ios" : "android";
@@ -48351,7 +48711,7 @@ function phonePairingInstructions(phone, input) {
48351
48711
  ];
48352
48712
  }
48353
48713
  return [
48354
- phone === "ios" ? "On iPhone or iPad, scan the QR with Camera to open Safari. If you already have the OpenMausBot iOS app, use its pairing scanner or paste the full pairing link there." : "On Android, scan the QR with Camera and open it in your web browser. This link is for the web app, not the Android native pairing scanner.",
48714
+ phone === "ios" ? "On iPhone or iPad, scan the QR with Camera to open Safari. If you already have the OpenMausBot iOS app, use its pairing scanner or paste the full pairing link there." : "On Android, open the OpenMausBot app and scan the QR with its pairing scanner. The QR is an app link, so Camera will not open it in a browser.",
48355
48715
  `Or open ${origin}/pair on your phone and enter the code.`,
48356
48716
  "Choose Connect on the phone. Scanning a QR does not mean the phone is paired.",
48357
48717
  "The code works once and expires after five minutes. This phone receives client access: chat and approvals, not settings or pairing administration."
@@ -48642,7 +49002,11 @@ function parseArgs(argv, env = process.env) {
48642
49002
  options.domain = domain2;
48643
49003
  } else if (arg === "--tunnel") options.tunnel = true;
48644
49004
  else if (arg === "--client") options.client = true;
48645
- else if (arg === "--no-pair") options.pair = false;
49005
+ else if (arg === "--phone") {
49006
+ const kind = value().toLowerCase();
49007
+ if (kind !== "ios" && kind !== "android") return { error: "--phone takes ios or android" };
49008
+ options.phone = kind;
49009
+ } else if (arg === "--no-pair") options.pair = false;
48646
49010
  else if (arg === "--no-open") options.open = false;
48647
49011
  else if (arg === "--local") options.local = true;
48648
49012
  else if (arg === "--json") options.json = true;
@@ -48702,7 +49066,8 @@ var USAGE = `openmausbot \u2014 your team of AI bots, ready in a few steps
48702
49066
  openmausbot start [the same options as serve]
48703
49067
  openmausbot serve [--port 8799] [--data-dir DIR] [--label NAME]
48704
49068
  [--public-url https://host] [--tailscale | --tunnel | --domain HOST] [--no-pair]
48705
- openmausbot pair [--label NAME] [--client] [--public-url https://host]
49069
+ openmausbot pair [--label NAME] [--client] [--phone ios|android]
49070
+ [--public-url https://host]
48706
49071
  openmausbot sessions [revoke ID]
48707
49072
  openmausbot status
48708
49073
  openmausbot login [--email you@example.com]
@@ -48877,22 +49242,46 @@ async function showPhonePairing(options, origin, log) {
48877
49242
  return false;
48878
49243
  }
48879
49244
  for (const line of phonePairingInstructions(options.phone ?? "ios", { origin, ready })) log(line);
48880
- log(await mintPairing(options.port, { client: true, label: options.label ?? (options.phone === "android" ? "Android" : "iPhone / iPad"), publicUrl: origin }));
49245
+ log(await mintPairing(options.port, { client: true, label: options.label ?? (options.phone === "android" ? "Android" : "iPhone / iPad"), publicUrl: origin, phone: options.phone }));
48881
49246
  log("Waiting for you to connect on the phone. Keep this terminal and the code private.");
48882
49247
  return true;
48883
49248
  }
48884
49249
  function pairingBlock(input) {
48885
49250
  const lines = [`pairing code: ${input.code}`, `expires: ${new Date(input.expiresAt).toLocaleTimeString()} (single use)`];
48886
- if (input.url) {
48887
- lines.push(`open or scan: ${input.url}`);
48888
- lines.push("");
48889
- lines.push(qrToString(input.url));
48890
- } else {
49251
+ if (!input.url && !input.inviteUrl) {
48891
49252
  lines.push(`open: /pair on the address you use for this server, and type the code`);
48892
49253
  if (input.hint) lines.push(` (${input.hint})`);
49254
+ return lines.join("\n");
49255
+ }
49256
+ const scanInvite = input.phone === "android" && !!input.inviteUrl;
49257
+ if (input.url) lines.push(scanInvite ? `web browser: ${input.url}` : `open or scan: ${input.url}`);
49258
+ if (input.inviteUrl) lines.push(`phone app: ${input.inviteUrl}`);
49259
+ const target = scanInvite ? input.inviteUrl : input.url;
49260
+ if (target) {
49261
+ lines.push("");
49262
+ lines.push(qrToString(target));
49263
+ lines.push("");
49264
+ if (scanInvite) {
49265
+ lines.push(`Scan that in the OpenMausBot app. For a browser instead, open the web`);
49266
+ lines.push(`address above and type the code.`);
49267
+ } else if (input.phone === "android") {
49268
+ lines.push(`That QR opens the web app. The Android app needs the phone-app link,`);
49269
+ lines.push(`which this server cannot build without a public address: set`);
49270
+ lines.push(`OMB_PUBLIC_URL, or open the web address above and type the code.`);
49271
+ } else if (input.inviteUrl) {
49272
+ lines.push(`Scan that with Camera for the browser, or paste the phone-app link`);
49273
+ lines.push(`above into the OpenMausBot app.`);
49274
+ }
48893
49275
  }
48894
49276
  return lines.join("\n");
48895
49277
  }
49278
+ function originOf(link) {
49279
+ try {
49280
+ return new URL(link).origin;
49281
+ } catch {
49282
+ return null;
49283
+ }
49284
+ }
48896
49285
  function qrToString(text4) {
48897
49286
  let out = "";
48898
49287
  import_qrcode_terminal.default.generate(text4, { small: true }, (rendered) => {
@@ -48907,7 +49296,9 @@ async function mintPairing(port, options) {
48907
49296
  const { status, body } = await api(port, "/api/auth/pairing", { method: "POST", body: JSON.stringify(request) });
48908
49297
  if (status !== 200) throw new Error(`server refused to mint a pairing code: ${typeof body?.error === "string" ? body.error : status}`);
48909
49298
  const url2 = options.publicUrl ? `${options.publicUrl}/pair#code=${body.code}` : typeof body.url === "string" ? body.url : null;
48910
- return pairingBlock({ code: body.code, url: url2, expiresAt: body.expiresAt, hint: typeof body.hint === "string" ? body.hint : null });
49299
+ const address = options.publicUrl ?? (typeof body.url === "string" ? originOf(body.url) : null);
49300
+ const invite = typeof body.credential === "string" && address ? `openmausbot://pair?address=${encodeURIComponent(address)}&token=${encodeURIComponent(body.credential)}${typeof body.serverName === "string" ? `&name=${encodeURIComponent(body.serverName)}` : ""}` : typeof body.inviteUrl === "string" ? body.inviteUrl : null;
49301
+ return pairingBlock({ code: body.code, url: url2, inviteUrl: invite, expiresAt: body.expiresAt, hint: typeof body.hint === "string" ? body.hint : null, phone: options.phone });
48911
49302
  }
48912
49303
  async function runPair(options) {
48913
49304
  if (!await serverUp(options.port)) {
@@ -48935,7 +49326,7 @@ async function runPair(options) {
48935
49326
  }
48936
49327
  const ui = defaultSetupIo();
48937
49328
  try {
48938
- const selected = await ui.choose("Which phone are you connecting?", ["iPhone / iPad \u2014 app or Safari", "Android \u2014 web browser", "Cancel"], 0);
49329
+ const selected = await ui.choose("Which phone are you connecting?", ["iPhone / iPad \u2014 app or Safari", "Android \u2014 app or browser", "Cancel"], 0);
48939
49330
  if (selected === 2) return 0;
48940
49331
  launch = { ...launch, phone: selected === 0 ? "ios" : "android" };
48941
49332
  return await showPhonePairing(launch, origin, ui.log) ? 0 : 1;
@@ -48945,7 +49336,7 @@ async function runPair(options) {
48945
49336
  return 130;
48946
49337
  }
48947
49338
  }
48948
- console.log(await mintPairing(options.port, { label: options.label, client: options.client, publicUrl: options.publicUrl }));
49339
+ console.log(await mintPairing(options.port, { label: options.label, client: options.client, publicUrl: options.publicUrl, phone: options.phone }));
48949
49340
  if (options.client) console.log("(client scope: chat and approvals only; cannot change settings or pair others)");
48950
49341
  return 0;
48951
49342
  }
@@ -49269,7 +49660,7 @@ async function runServe(options, log = console.log) {
49269
49660
  const logsDir = join36(options.dataDir, "logs");
49270
49661
  mkdirSync17(logsDir, { recursive: true, mode: 448 });
49271
49662
  logPath = join36(logsDir, `server-${Date.now()}-${process.pid}.log`);
49272
- logFd = openSync4(logPath, "wx", 384);
49663
+ logFd = openSync5(logPath, "wx", 384);
49273
49664
  log("\nStarting your workspace\u2026");
49274
49665
  }
49275
49666
  if (tailscale) {
@@ -49293,7 +49684,7 @@ async function runServe(options, log = console.log) {
49293
49684
  }
49294
49685
  throw error51;
49295
49686
  } finally {
49296
- if (logFd !== void 0) closeSync4(logFd);
49687
+ if (logFd !== void 0) closeSync5(logFd);
49297
49688
  process.removeListener("SIGINT", cancelStartup);
49298
49689
  process.removeListener("SIGTERM", cancelStartup);
49299
49690
  }