openmausbot 0.1.67 → 0.1.69

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 (41) hide show
  1. package/dist/assets/{index-BwpNqgCm.js → index-BmHupgVJ.js} +1 -1
  2. package/dist/assets/index-COp5lMnv.js +279 -0
  3. package/dist/assets/index-Jk7TIeyW.css +1 -0
  4. package/dist/index.html +2 -2
  5. package/dist-server/container-mcp.js +40 -2
  6. package/dist-server/drivers/agents-proxy.js +112 -7
  7. package/dist-server/enterprise/server/index.js +14587 -0
  8. package/dist-server/index.js +1732 -681
  9. package/dist-server/openmausbot.js +939 -381
  10. package/dist-server/pair-cli.js +939 -381
  11. package/dist-server/server/avatar-image.js +86 -34
  12. package/dist-server/server/bot-package.js +60 -14
  13. package/dist-server/server/brand.js +7 -0
  14. package/dist-server/server/cli.js +23 -1
  15. package/dist-server/server/config.js +30 -3
  16. package/dist-server/server/delegations.js +56 -11
  17. package/dist-server/server/drivers/acp/qwen.js +142 -5
  18. package/dist-server/server/drivers/agents-proxy.js +135 -8
  19. package/dist-server/server/drivers/codex-device-auth.js +102 -3
  20. package/dist-server/server/drivers/codex-identity.js +115 -0
  21. package/dist-server/server/drivers/codex.js +6 -0
  22. package/dist-server/server/harness/registry.js +2 -0
  23. package/dist-server/server/index.js +220 -65
  24. package/dist-server/server/package-export.js +7 -0
  25. package/dist-server/server/provider-auth-sessions.js +24 -0
  26. package/dist-server/server/routine-requests.js +270 -25
  27. package/dist-server/server/routines.js +215 -21
  28. package/dist-server/server/service-cli.js +63 -0
  29. package/dist-server/server/service-unit.js +129 -0
  30. package/dist-server/server/steer-queue.js +30 -1
  31. package/dist-server/server/store.js +30 -4
  32. package/dist-server/server/system-prompt.js +1 -0
  33. package/dist-server/server/turn-dispatch-guard.js +6 -0
  34. package/dist-server/shared/image-generation.js +28 -0
  35. package/dist-server/vps-container-mcp.js +40 -2
  36. package/enterprise/LICENSE +32 -0
  37. package/enterprise/README.md +89 -0
  38. package/enterprise/server/index.js +14587 -0
  39. package/package.json +2 -1
  40. package/dist/assets/index-A3CBTxwl.css +0 -1
  41. package/dist/assets/index-CDuyoVPJ.js +0 -278
@@ -2298,7 +2298,7 @@ function displayText(value, multiline = false) {
2298
2298
  }
2299
2299
  function plainText(question, hidden, context) {
2300
2300
  const { input, output, signal } = context;
2301
- return new Promise((resolve12, reject) => {
2301
+ return new Promise((resolve13, reject) => {
2302
2302
  input.setRawMode?.(hidden);
2303
2303
  const rl = createInterface({ input, terminal: hidden });
2304
2304
  let finished = false;
@@ -2309,7 +2309,7 @@ function plainText(question, hidden, context) {
2309
2309
  rl.close();
2310
2310
  output.write("\n");
2311
2311
  if (error51) reject(error51);
2312
- else resolve12(answer ?? "");
2312
+ else resolve13(answer ?? "");
2313
2313
  };
2314
2314
  const cancel = () => finish(void 0, signal.reason ?? new SetupCancelled());
2315
2315
  signal.addEventListener("abort", cancel, { once: true });
@@ -17526,6 +17526,30 @@ var init_zod = __esm({
17526
17526
  }
17527
17527
  });
17528
17528
 
17529
+ // shared/image-generation.ts
17530
+ function normalizeImageGenerationUrl(value) {
17531
+ const message2 = "Use an HTTPS API base URL, or HTTP for a local router, without passwords, query parameters or fragments";
17532
+ let url2;
17533
+ try {
17534
+ url2 = new URL(value.trim());
17535
+ } catch {
17536
+ throw new Error(message2);
17537
+ }
17538
+ if (value.length > 2048 || url2.username || url2.password || url2.search || url2.hash) throw new Error(message2);
17539
+ const host = url2.hostname.toLowerCase();
17540
+ const ipv43 = /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/.exec(host);
17541
+ const local = host === "localhost" || host.endsWith(".localhost") || host.endsWith(".local") || host === "host.docker.internal" || host === "host.containers.internal" || host === "[::1]" || /^\[f[cd][0-9a-f]{2}:/.test(host) || ipv43 && (Number(ipv43[1]) === 127 || Number(ipv43[1]) === 10 || Number(ipv43[1]) === 192 && Number(ipv43[2]) === 168 || Number(ipv43[1]) === 172 && Number(ipv43[2]) >= 16 && Number(ipv43[2]) <= 31);
17542
+ if (url2.protocol !== "https:" && !(url2.protocol === "http:" && local)) throw new Error(message2);
17543
+ if (ipv43 && (Number(ipv43[1]) === 0 || Number(ipv43[1]) === 169 && Number(ipv43[2]) === 254)) throw new Error(message2);
17544
+ url2.pathname = url2.pathname.replace(/\/+$/, "").replace(/\/images\/generations$/, "");
17545
+ return url2.toString().replace(/\/+$/, "");
17546
+ }
17547
+ var init_image_generation = __esm({
17548
+ "shared/image-generation.ts"() {
17549
+ "use strict";
17550
+ }
17551
+ });
17552
+
17529
17553
  // server/contracts.ts
17530
17554
  var EFFORT_LEVELS, eventCounter, newEventId, newId;
17531
17555
  var init_contracts = __esm({
@@ -17577,9 +17601,9 @@ var init_schema = __esm({
17577
17601
  });
17578
17602
 
17579
17603
  // server/config.ts
17580
- import { readFileSync as readFileSync2, mkdirSync as mkdirSync3, existsSync as existsSync3, renameSync as renameSync3 } from "node:fs";
17581
- import { homedir as homedir2 } from "node:os";
17582
- import { join as join4 } from "node:path";
17604
+ import { readFileSync as readFileSync2, mkdirSync as mkdirSync4, existsSync as existsSync3, renameSync as renameSync3 } from "node:fs";
17605
+ import { homedir as homedir4 } from "node:os";
17606
+ import { join as join5 } from "node:path";
17583
17607
  function isValidSshAlias(value) {
17584
17608
  return typeof value === "string" && SSH_ALIAS.test(value);
17585
17609
  }
@@ -17712,7 +17736,7 @@ function parseStoredConfig(value) {
17712
17736
  function loadConfig() {
17713
17737
  let cfg = {};
17714
17738
  try {
17715
- cfg = parseStoredConfig(parseJson(readFileSync2(join4(DATA_DIR, "config.json"), "utf8")));
17739
+ cfg = parseStoredConfig(parseJson(readFileSync2(join5(DATA_DIR, "config.json"), "utf8")));
17716
17740
  } catch {
17717
17741
  }
17718
17742
  cfg.xai = { ...cfg.xai };
@@ -17732,6 +17756,7 @@ function loadConfig() {
17732
17756
  if (process.env.OMB_TTS_KEY !== void 0) cfg.tts.key = process.env.OMB_TTS_KEY;
17733
17757
  cfg.imageGen = { ...cfg.imageGen };
17734
17758
  if (process.env.OMB_OPENAI_IMAGE_KEY !== void 0) cfg.imageGen.key = process.env.OMB_OPENAI_IMAGE_KEY;
17759
+ if (process.env.OMB_CUSTOM_IMAGE_KEY !== void 0) cfg.imageGen.customApiKey = process.env.OMB_CUSTOM_IMAGE_KEY;
17735
17760
  const splitEmails = (value) => value.split(/[,\s]+/).map((entry) => entry.trim().toLowerCase()).filter(Boolean);
17736
17761
  if (process.env.OMB_SIGNIN_EMAILS !== void 0 || process.env.OMB_SIGNIN_MEMBER_EMAILS !== void 0) {
17737
17762
  cfg.signIn = { ...cfg.signIn };
@@ -17744,7 +17769,7 @@ function stripWorkspaceCredentialEnv(env) {
17744
17769
  for (const key of WORKSPACE_CREDENTIAL_ENV) delete env[key];
17745
17770
  }
17746
17771
  function saveConfig(patch, options = {}) {
17747
- const p = join4(DATA_DIR, "config.json");
17772
+ const p = join5(DATA_DIR, "config.json");
17748
17773
  let disk = {};
17749
17774
  try {
17750
17775
  const parsed = jsonObjectSchema.safeParse(parseJson(readFileSync2(p, "utf8")));
@@ -17754,7 +17779,7 @@ function saveConfig(patch, options = {}) {
17754
17779
  const checkedPatch = appConfigSchema.partial().parse(patch);
17755
17780
  const storedProfiles = storedBrowserProfilesSchema.safeParse(disk.browserProfiles);
17756
17781
  if (storedProfiles.success) disk.browserProfiles = storedProfiles.data;
17757
- for (const key of ["xai", "openaiCompat", "composio", "box", "opencodeGo", "tts", "imageGen", "profile", "rooms", "localVm", "features"]) {
17782
+ for (const key of ["xai", "openaiCompat", "composio", "box", "opencodeGo", "tts", "imageGen", "profile", "rooms", "threads", "localVm", "features"]) {
17758
17783
  const section = checkedPatch[key];
17759
17784
  if (!section) continue;
17760
17785
  const current = jsonObjectSchema.safeParse(disk[key]);
@@ -17803,7 +17828,7 @@ function saveConfig(patch, options = {}) {
17803
17828
  }
17804
17829
  disk.instances = diskInstances;
17805
17830
  }
17806
- mkdirSync3(DATA_DIR, { recursive: true });
17831
+ mkdirSync4(DATA_DIR, { recursive: true });
17807
17832
  writeFileAtomic(p, JSON.stringify(disk, null, 2), { mode: 384 });
17808
17833
  }
17809
17834
  function injectedEnvironment(cfg, driver) {
@@ -17875,11 +17900,12 @@ function instanceConfigs(cfg) {
17875
17900
  }
17876
17901
  return map3;
17877
17902
  }
17878
- var optionalText, SSH_ALIAS, LEGACY_BROWSER_PROFILE_ID, BROWSER_PROFILE_ID, MIN_ROOM_TURN_TIMEOUT_MINUTES, MAX_ROOM_TURN_TIMEOUT_MINUTES, MIN_LOCAL_VM_MAX_INSTANCES, MAX_LOCAL_VM_MAX_INSTANCES, vpsConfigSchema, roomConfigSchema, localVmConfigSchema, browserProfileSchema, legacyBrowserProfileSchema, legacyBrowserProfilesSchema, storedBrowserProfilesSchema, browserProfilesSchema, featureConfigSchema, instanceConfigSchema, instanceConfigMapSchema, defaultModelSelectionSchema, appConfigSchema, storedAppConfigSchema, appConfigPatchSchema, jsonObjectSchema, DATA_DIR, LEGACY_DATA_DIR, EVENTS_DIR, NATIVE_DIR, WORKSPACE_CREDENTIAL_ENV, PROVIDER_CREDENTIAL_ENV;
17903
+ 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, instanceConfigSchema, instanceConfigMapSchema, defaultModelSelectionSchema, appConfigSchema, storedAppConfigSchema, appConfigPatchSchema, jsonObjectSchema, DATA_DIR, LEGACY_DATA_DIR, EVENTS_DIR, NATIVE_DIR, WORKSPACE_CREDENTIAL_ENV, PROVIDER_CREDENTIAL_ENV;
17879
17904
  var init_config = __esm({
17880
17905
  "server/config.ts"() {
17881
17906
  "use strict";
17882
17907
  init_zod();
17908
+ init_image_generation();
17883
17909
  init_atomic();
17884
17910
  init_contracts();
17885
17911
  init_mcp_registry();
@@ -17890,6 +17916,7 @@ var init_config = __esm({
17890
17916
  BROWSER_PROFILE_ID = /^[a-z0-9_-]{1,40}$/;
17891
17917
  MIN_ROOM_TURN_TIMEOUT_MINUTES = 1;
17892
17918
  MAX_ROOM_TURN_TIMEOUT_MINUTES = 1440;
17919
+ MAX_CONCURRENT_BOT_THREADS = 10;
17893
17920
  MIN_LOCAL_VM_MAX_INSTANCES = 1;
17894
17921
  MAX_LOCAL_VM_MAX_INSTANCES = 4;
17895
17922
  vpsConfigSchema = external_exports.object({
@@ -17987,14 +18014,32 @@ var init_config = __esm({
17987
18014
  * engine: "elevenlabs" (default; needs a key) or "system" (the Mac's
17988
18015
  * built-in voices, no key). */
17989
18016
  tts: external_exports.object({ key: optionalText, voice: optionalText, provider: external_exports.enum(["elevenlabs", "system"]).optional() }).optional(),
17990
- /** OpenAI key used only by the in-process avatar image generator. */
17991
- imageGen: external_exports.object({ key: optionalText }).optional(),
18017
+ /** Avatar provider credentials stay separate; choosing a router never reuses a cloud key. */
18018
+ imageGen: external_exports.object({
18019
+ provider: external_exports.enum(["openai", "xai", "custom"]).optional(),
18020
+ key: optionalText,
18021
+ customApiKey: optionalText,
18022
+ customUrl: external_exports.string().trim().max(2048).transform((value, ctx) => {
18023
+ if (!value) return "";
18024
+ try {
18025
+ return normalizeImageGenerationUrl(value);
18026
+ } catch (error51) {
18027
+ ctx.addIssue({ code: "custom", message: error51.message });
18028
+ return external_exports.NEVER;
18029
+ }
18030
+ }).optional(),
18031
+ customModel: external_exports.string().trim().max(200).refine(
18032
+ (value) => !["\r", "\n", "\0"].some((character) => value.includes(character)),
18033
+ "Use a model ID without control characters"
18034
+ ).optional()
18035
+ }).optional(),
17992
18036
  /** Non-secret profile details shown in the sidebar. */
17993
18037
  profile: external_exports.object({ name: optionalText, email: optionalText }).optional(),
17994
18038
  /** UI language override (BCP-47, lowercase). Empty/absent = follow the
17995
18039
  * system language. Unknown tags degrade to English in the renderer. */
17996
18040
  language: optionalText,
17997
18041
  rooms: roomConfigSchema.optional(),
18042
+ threads: external_exports.object({ maxConcurrentPerBot: external_exports.number().int().min(1).max(MAX_CONCURRENT_BOT_THREADS) }).strict().optional(),
17998
18043
  localVm: localVmConfigSchema.optional(),
17999
18044
  features: featureConfigSchema.optional(),
18000
18045
  browserProfiles: browserProfilesSchema.optional(),
@@ -18010,10 +18055,10 @@ var init_config = __esm({
18010
18055
  });
18011
18056
  appConfigPatchSchema = appConfigSchema.omit({ instances: true, mcpServers: true, cliStartup: true, customDomain: true });
18012
18057
  jsonObjectSchema = external_exports.record(external_exports.string(), external_exports.json());
18013
- DATA_DIR = process.env.OMB_DATA_DIR ?? join4(homedir2(), ".openmausbot");
18014
- LEGACY_DATA_DIR = join4(homedir2(), ".opengrokbot");
18015
- EVENTS_DIR = join4(DATA_DIR, "events");
18016
- NATIVE_DIR = join4(DATA_DIR, "native");
18058
+ DATA_DIR = process.env.OMB_DATA_DIR ?? join5(homedir4(), ".openmausbot");
18059
+ LEGACY_DATA_DIR = join5(homedir4(), ".opengrokbot");
18060
+ EVENTS_DIR = join5(DATA_DIR, "events");
18061
+ NATIVE_DIR = join5(DATA_DIR, "native");
18017
18062
  WORKSPACE_CREDENTIAL_ENV = [
18018
18063
  "XAI_API_KEY",
18019
18064
  "OPENAI_COMPAT_API_KEY",
@@ -18022,6 +18067,7 @@ var init_config = __esm({
18022
18067
  "OPENCODE_API_KEY",
18023
18068
  "OMB_TTS_KEY",
18024
18069
  "OMB_OPENAI_IMAGE_KEY",
18070
+ "OMB_CUSTOM_IMAGE_KEY",
18025
18071
  "COMPOSIO_API_KEY",
18026
18072
  "OMB_COMPOSIO_BROKER_TOKEN",
18027
18073
  // Harness-private filesystem hints are not credentials themselves, but
@@ -18050,9 +18096,9 @@ var init_config = __esm({
18050
18096
  // electron/data-dir-lease.mjs
18051
18097
  import { execFileSync } from "node:child_process";
18052
18098
  import { createHash as createHash2, randomUUID as randomUUID5 } from "node:crypto";
18053
- import { existsSync as existsSync4, linkSync, mkdirSync as mkdirSync4, readFileSync as readFileSync3, renameSync as renameSync4, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "node:fs";
18099
+ import { existsSync as existsSync4, linkSync, mkdirSync as mkdirSync5, readFileSync as readFileSync3, renameSync as renameSync4, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "node:fs";
18054
18100
  import { hostname as hostname4, uptime } from "node:os";
18055
- import { join as join5 } from "node:path";
18101
+ import { join as join6 } from "node:path";
18056
18102
  function leaseError(message2, cause) {
18057
18103
  return Object.assign(new DataDirLeaseError(message2), { cause });
18058
18104
  }
@@ -18157,7 +18203,7 @@ function unlinkExact(path3, message2) {
18157
18203
  function publishRecord(path3, record3, prepareMessage, acquireMessage) {
18158
18204
  const candidatePath = `${path3}.candidate-${record3.pid}-${record3.token}`;
18159
18205
  try {
18160
- writeFileSync3(candidatePath, `${JSON.stringify(record3)}
18206
+ writeFileSync4(candidatePath, `${JSON.stringify(record3)}
18161
18207
  `, { flag: "wx", mode: 384, flush: true });
18162
18208
  } catch (error51) {
18163
18209
  throw leaseError(prepareMessage, error51);
@@ -18240,14 +18286,14 @@ function prepareDataDir(dataDir, legacyDataDir) {
18240
18286
  }
18241
18287
  }
18242
18288
  try {
18243
- mkdirSync4(dataDir, { recursive: true, mode: 448 });
18289
+ mkdirSync5(dataDir, { recursive: true, mode: 448 });
18244
18290
  } catch (error51) {
18245
18291
  throw leaseError("OpenMausBot cannot create its data directory.", error51);
18246
18292
  }
18247
- return join5(dataDir, LEASE_NAME);
18293
+ return join6(dataDir, LEASE_NAME);
18248
18294
  }
18249
18295
  function assertNoLiveDelegatedChild(dataDir) {
18250
- const childLeasePath = join5(dataDir, DELEGATED_CHILD_DIR, LEASE_NAME);
18296
+ const childLeasePath = join6(dataDir, DELEGATED_CHILD_DIR, LEASE_NAME);
18251
18297
  const child = readOwner(childLeasePath);
18252
18298
  if (!child) return;
18253
18299
  if (child.host !== hostname4()) {
@@ -18278,7 +18324,7 @@ function acquireDataDirLeaseInternal(dataDir, options = {}) {
18278
18324
  };
18279
18325
  const candidatePath = `${leasePath}.candidate-${owner.pid}-${owner.token}`;
18280
18326
  try {
18281
- writeFileSync3(candidatePath, `${JSON.stringify(owner)}
18327
+ writeFileSync4(candidatePath, `${JSON.stringify(owner)}
18282
18328
  `, { flag: "wx", mode: 384, flush: true });
18283
18329
  } catch (error51) {
18284
18330
  throw leaseError("OpenMausBot cannot prepare its data-directory lease.", error51);
@@ -18371,62 +18417,62 @@ var init_data_dir_lease2 = __esm({
18371
18417
  // server/env-path.ts
18372
18418
  import { execFile as execFile2 } from "node:child_process";
18373
18419
  import { closeSync as closeSync2, existsSync as existsSync5, openSync as openSync2, readFileSync as readFileSync4, readSync, statSync as statSync2, readdirSync } from "node:fs";
18374
- import { homedir as homedir3 } from "node:os";
18375
- import { basename, delimiter as delimiter3, dirname as dirname2, extname, join as join6 } from "node:path";
18420
+ import { homedir as homedir5 } from "node:os";
18421
+ import { basename as basename2, delimiter as delimiter3, dirname as dirname3, extname, join as join7 } from "node:path";
18376
18422
  function nvmBinDirs() {
18377
18423
  try {
18378
- const base2 = join6(homedir3(), ".nvm", "versions", "node");
18379
- return readdirSync(base2).filter((v) => v.startsWith("v")).sort((a2, b) => b.localeCompare(a2, void 0, { numeric: true })).map((v) => join6(base2, v, "bin"));
18424
+ const base2 = join7(homedir5(), ".nvm", "versions", "node");
18425
+ return readdirSync(base2).filter((v) => v.startsWith("v")).sort((a2, b) => b.localeCompare(a2, void 0, { numeric: true })).map((v) => join7(base2, v, "bin"));
18380
18426
  } catch {
18381
18427
  return [];
18382
18428
  }
18383
18429
  }
18384
18430
  function knownDirs() {
18385
- const home = homedir3();
18431
+ const home = homedir5();
18386
18432
  return [
18387
- join6(home, ".local", "bin"),
18433
+ join7(home, ".local", "bin"),
18388
18434
  // claude installer default
18389
- join6(home, ".npm-global", "bin"),
18435
+ join7(home, ".npm-global", "bin"),
18390
18436
  // npm prefix ~/.npm-global (claude, opencode)
18391
- join6(home, ".kimi-code", "bin"),
18437
+ join7(home, ".kimi-code", "bin"),
18392
18438
  // kimi-code installer
18393
- join6(home, ".grok", "bin"),
18439
+ join7(home, ".grok", "bin"),
18394
18440
  // x.ai installer
18395
- join6(home, ".opencode", "bin"),
18441
+ join7(home, ".opencode", "bin"),
18396
18442
  // opencode installer
18397
- join6(home, ".claude", "local"),
18443
+ join7(home, ".claude", "local"),
18398
18444
  // claude "local install"
18399
18445
  "/opt/homebrew/bin",
18400
18446
  // brew, Apple silicon
18401
18447
  "/usr/local/bin",
18402
18448
  // brew Intel / classic installs
18403
- join6(home, ".volta", "bin"),
18404
- join6(home, ".bun", "bin"),
18405
- join6(home, ".asdf", "shims"),
18406
- join6(home, ".deno", "bin"),
18407
- join6(home, "bin"),
18449
+ join7(home, ".volta", "bin"),
18450
+ join7(home, ".bun", "bin"),
18451
+ join7(home, ".asdf", "shims"),
18452
+ join7(home, ".deno", "bin"),
18453
+ join7(home, "bin"),
18408
18454
  ...nvmBinDirs()
18409
18455
  ];
18410
18456
  }
18411
18457
  function windowsKnownDirs() {
18412
- const home = homedir3();
18413
- const appData = process.env.APPDATA ?? join6(home, "AppData", "Roaming");
18414
- const localAppData = process.env.LOCALAPPDATA ?? join6(home, "AppData", "Local");
18458
+ const home = homedir5();
18459
+ const appData = process.env.APPDATA ?? join7(home, "AppData", "Roaming");
18460
+ const localAppData = process.env.LOCALAPPDATA ?? join7(home, "AppData", "Local");
18415
18461
  return [
18416
- join6(appData, "npm"),
18462
+ join7(appData, "npm"),
18417
18463
  // npm -g shims: claude, codex
18418
- join6(home, ".grok", "bin"),
18464
+ join7(home, ".grok", "bin"),
18419
18465
  // x.ai installer
18420
- join6(localAppData, "agy", "bin"),
18466
+ join7(localAppData, "agy", "bin"),
18421
18467
  // Antigravity installer
18422
- join6(home, ".local", "bin"),
18468
+ join7(home, ".local", "bin"),
18423
18469
  // claude native installer
18424
- join6(home, ".claude", "local"),
18425
- join6(home, "bin"),
18470
+ join7(home, ".claude", "local"),
18471
+ join7(home, "bin"),
18426
18472
  // Factory droid installer (%USERPROFILE%\bin)
18427
- join6(home, ".bun", "bin"),
18428
- join6(home, ".deno", "bin"),
18429
- join6(home, "go", "bin")
18473
+ join7(home, ".bun", "bin"),
18474
+ join7(home, ".deno", "bin"),
18475
+ join7(home, "go", "bin")
18430
18476
  ];
18431
18477
  }
18432
18478
  function resetPathCache() {
@@ -18480,7 +18526,7 @@ function findCliCandidates(name) {
18480
18526
  for (const dir of augmentedPath().split(delimiter3)) {
18481
18527
  if (!dir) continue;
18482
18528
  for (const ext of exts) {
18483
- const p = join6(dir, name + ext);
18529
+ const p = join7(dir, name + ext);
18484
18530
  if (existsSync5(p)) {
18485
18531
  out.push(p);
18486
18532
  break;
@@ -18525,13 +18571,13 @@ function whichWin(cli) {
18525
18571
  if (/[\\/]/.test(cli) || /^[a-zA-Z]:/.test(cli)) return probe(cli);
18526
18572
  for (const dir of augmentedPath().split(delimiter3)) {
18527
18573
  if (!dir) continue;
18528
- const hit = probe(join6(dir, cli));
18574
+ const hit = probe(join7(dir, cli));
18529
18575
  if (hit) return hit;
18530
18576
  }
18531
18577
  return null;
18532
18578
  }
18533
18579
  function nodeExe(near) {
18534
- const local = join6(near, "node.exe");
18580
+ const local = join7(near, "node.exe");
18535
18581
  if (isFile(local)) return local;
18536
18582
  const onPath2 = whichWin("node.exe");
18537
18583
  if (onPath2 && extname(onPath2).toLowerCase() === ".exe") return onPath2;
@@ -18544,8 +18590,8 @@ function parseCmdShim(shim) {
18544
18590
  } catch {
18545
18591
  return null;
18546
18592
  }
18547
- const dir = dirname2(shim);
18548
- const targets = [...text2.matchAll(/"%~?dp0%?\\?([^"]+)"/g)].map((m) => join6(dir, m[1])).filter((p) => isFile(p) && basename(p).toLowerCase() !== "node.exe");
18593
+ const dir = dirname3(shim);
18594
+ const targets = [...text2.matchAll(/"%~?dp0%?\\?([^"]+)"/g)].map((m) => join7(dir, m[1])).filter((p) => isFile(p) && basename2(p).toLowerCase() !== "node.exe");
18549
18595
  const script = targets.find((p) => /\.[cm]?js$/i.test(p));
18550
18596
  if (script) {
18551
18597
  const node = nodeExe(dir);
@@ -18573,7 +18619,7 @@ function parseNodeShebang(file2) {
18573
18619
  }
18574
18620
  }
18575
18621
  if (!/^#!.*\bnode(\.exe)?\b/.test(head)) return null;
18576
- const node = nodeExe(dirname2(file2));
18622
+ const node = nodeExe(dirname3(file2));
18577
18623
  return node ? { command: node, args: [file2] } : null;
18578
18624
  }
18579
18625
  function resolveWord(cli, args) {
@@ -18615,7 +18661,7 @@ import {
18615
18661
  spawn as spawn4,
18616
18662
  execFile as execFile3
18617
18663
  } from "node:child_process";
18618
- import { join as join7 } from "node:path";
18664
+ import { join as join8 } from "node:path";
18619
18665
  function resolveCli(cli, args = []) {
18620
18666
  return resolveCliSpawn(cli, args);
18621
18667
  }
@@ -18677,12 +18723,12 @@ function describeSpawnFailure(err, cli) {
18677
18723
  function killCliTree(child, timeoutMs = 5e3) {
18678
18724
  const pid = child.pid;
18679
18725
  if (!pid || child.exitCode !== null || child.signalCode !== null) return Promise.resolve(true);
18680
- return new Promise((resolve12) => {
18726
+ return new Promise((resolve13) => {
18681
18727
  let timer;
18682
18728
  const done = (stopped) => {
18683
18729
  clearTimeout(timer);
18684
18730
  child.off("close", closed);
18685
- resolve12(stopped);
18731
+ resolve13(stopped);
18686
18732
  };
18687
18733
  const closed = () => done(true);
18688
18734
  child.once("close", closed);
@@ -18709,7 +18755,7 @@ function killCliTree(child, timeoutMs = 5e3) {
18709
18755
  });
18710
18756
  }
18711
18757
  function brokerSocketPath(dataDir, tag) {
18712
- return process.platform === "win32" ? `\\\\.\\pipe\\openmausbot-perm-${process.pid}-${tag}` : join7(dataDir, `perm-${tag}.sock`);
18758
+ return process.platform === "win32" ? `\\\\.\\pipe\\openmausbot-perm-${process.pid}-${tag}` : join8(dataDir, `perm-${tag}.sock`);
18713
18759
  }
18714
18760
  var WINDOWS_SAFE_COMMAND_LINE_CHARS;
18715
18761
  var init_procs = __esm({
@@ -18907,8 +18953,10 @@ var init_registry = __esm({
18907
18953
  access: driver?.metadata.access ?? "subscription",
18908
18954
  install: driver?.install,
18909
18955
  authentication: inst.startAuthentication ? {
18910
- method: inst.getAuthentication && inst.completeAuthentication ? "paste-code" : inst.getAuthentication ? "device-code" : "browser"
18956
+ method: inst.getAuthentication && inst.completeAuthentication ? "paste-code" : inst.getAuthentication ? "device-code" : "browser",
18911
18957
  // a link and a callback URL (managed engines)
18958
+ // the browser may remove the stored sign-in to switch accounts
18959
+ signOut: inst.signOut !== void 0
18912
18960
  } : void 0,
18913
18961
  cli: this.cliByInstance.get(inst.instanceId),
18914
18962
  cliDefault: cliDefaultOf(driver),
@@ -18937,8 +18985,8 @@ var init_registry = __esm({
18937
18985
 
18938
18986
  // server/drivers/local-inject.ts
18939
18987
  import { readFileSync as readFileSync5 } from "node:fs";
18940
- import { homedir as homedir4 } from "node:os";
18941
- import { join as join8 } from "node:path";
18988
+ import { homedir as homedir6 } from "node:os";
18989
+ import { join as join9 } from "node:path";
18942
18990
  function contextWindowsFromPs(extra) {
18943
18991
  const out = /* @__PURE__ */ new Map();
18944
18992
  const rec = extra && typeof extra === "object" ? extra : null;
@@ -19021,9 +19069,9 @@ function firstUnslothToken(row) {
19021
19069
  return null;
19022
19070
  }
19023
19071
  function readUnslothKey(env) {
19024
- const home = env.HOME || env.USERPROFILE || homedir4();
19072
+ const home = env.HOME || env.USERPROFILE || homedir6();
19025
19073
  try {
19026
- const raw = JSON.parse(readFileSync5(join8(home, ".unsloth", "studio", "auth", "agent_api_key.json"), "utf8"));
19074
+ const raw = JSON.parse(readFileSync5(join9(home, ".unsloth", "studio", "auth", "agent_api_key.json"), "utf8"));
19027
19075
  if (raw.servers && typeof raw.servers === "object") {
19028
19076
  const servers = raw.servers;
19029
19077
  for (const url2 of ["http://127.0.0.1:8888", "http://localhost:8888"]) {
@@ -19225,17 +19273,17 @@ var init_local_inject = __esm({
19225
19273
 
19226
19274
  // server/proxy-paths.ts
19227
19275
  import { existsSync as existsSync6 } from "node:fs";
19228
- import { dirname as dirname3, join as join9 } from "node:path";
19276
+ import { dirname as dirname4, join as join10 } from "node:path";
19229
19277
  import { fileURLToPath as fileURLToPath2 } from "node:url";
19230
19278
  function resolveProxy(relative2) {
19231
- const source = join9(SERVER_ROOT, `${relative2}.ts`);
19232
- return existsSync6(source) ? source : join9(SERVER_ROOT, `${relative2}.js`);
19279
+ const source = join10(SERVER_ROOT, `${relative2}.ts`);
19280
+ return existsSync6(source) ? source : join10(SERVER_ROOT, `${relative2}.js`);
19233
19281
  }
19234
19282
  var SERVER_ROOT, SPAWNED_PROXIES;
19235
19283
  var init_proxy_paths = __esm({
19236
19284
  "server/proxy-paths.ts"() {
19237
19285
  "use strict";
19238
- SERVER_ROOT = dirname3(fileURLToPath2(import.meta.url));
19286
+ SERVER_ROOT = dirname4(fileURLToPath2(import.meta.url));
19239
19287
  SPAWNED_PROXIES = {
19240
19288
  browser: resolveProxy("browser-proxy"),
19241
19289
  computer: resolveProxy("computer-proxy"),
@@ -19257,7 +19305,7 @@ var init_proxy_paths = __esm({
19257
19305
 
19258
19306
  // server/container-computer.ts
19259
19307
  import { execFile as execFile4 } from "node:child_process";
19260
- import { join as join10, resolve as resolve3 } from "node:path";
19308
+ import { join as join11, resolve as resolve3 } from "node:path";
19261
19309
  import { promisify } from "node:util";
19262
19310
  function computerProxyEnv(computer) {
19263
19311
  return {
@@ -19281,7 +19329,7 @@ var init_container_computer = __esm({
19281
19329
  IMAGE_REPOSITORY = "localhost/openmausbot/cua-local-vm";
19282
19330
  IMAGE_LAYER_VERSION = "5";
19283
19331
  IMAGE = `${IMAGE_REPOSITORY}:driver-${CUA_DRIVER_VERSION}-v${IMAGE_LAYER_VERSION}`;
19284
- VM_WORKSPACE_DIR = join10(DATA_DIR, "vm-home");
19332
+ VM_WORKSPACE_DIR = join11(DATA_DIR, "vm-home");
19285
19333
  MEMORY_BYTES = 4 * 1024 * 1024 * 1024;
19286
19334
  SHM_BYTES = 512 * 1024 * 1024;
19287
19335
  containerMcpPath = SPAWNED_PROXIES.containerMcp;
@@ -19372,11 +19420,11 @@ var init_redact = __esm({
19372
19420
 
19373
19421
  // server/drivers/native.ts
19374
19422
  import { appendFileSync } from "node:fs";
19375
- import { join as join11 } from "node:path";
19423
+ import { join as join12 } from "node:path";
19376
19424
  function appendNative(threadId, entry) {
19377
19425
  try {
19378
19426
  appendFileSync(
19379
- join11(NATIVE_DIR, `${threadId}.ndjson`),
19427
+ join12(NATIVE_DIR, `${threadId}.ndjson`),
19380
19428
  JSON.stringify({ at: (/* @__PURE__ */ new Date()).toISOString(), ...entry, msg: redactSecrets(entry.msg) }) + "\n",
19381
19429
  { mode: 384 }
19382
19430
  );
@@ -19392,9 +19440,9 @@ var init_native = __esm({
19392
19440
  });
19393
19441
 
19394
19442
  // server/drivers/acp/core.ts
19395
- import { homedir as homedir5 } from "node:os";
19443
+ import { homedir as homedir7 } from "node:os";
19396
19444
  import { lstat, mkdir, readFile, realpath, stat, writeFile } from "node:fs/promises";
19397
- import { basename as basename2, dirname as dirname4, isAbsolute, relative, resolve as resolve4 } from "node:path";
19445
+ import { basename as basename3, dirname as dirname5, isAbsolute, relative, resolve as resolve4 } from "node:path";
19398
19446
  function skipSubscriptionAuthForLocalInject(model) {
19399
19447
  return Boolean(decodeInjectId(model));
19400
19448
  }
@@ -19579,7 +19627,7 @@ function createAcpDriver(support11) {
19579
19627
  throw new Error("local computer control requires interactive provider approvals");
19580
19628
  }
19581
19629
  const turnId = newId();
19582
- const cwd = turn.cwd ?? turnConfig.workspace ?? homedir5();
19630
+ const cwd = turn.cwd ?? turnConfig.workspace ?? homedir7();
19583
19631
  const env = childEnv(turnConfig);
19584
19632
  if (support11.requireAuthenticationBeforeSpawn && !skipSubscriptionAuthForLocalInject(turn.model) && !await support11.isAuthenticated(env, turnConfig, instanceId)) {
19585
19633
  emit({ ...base2(threadId, turnId), type: "turn.started" });
@@ -19623,7 +19671,7 @@ function createAcpDriver(support11) {
19623
19671
  }
19624
19672
  appendNative(threadId, { dir: "out", source: SOURCE, msg: nativeLogMessage(obj) });
19625
19673
  };
19626
- const request = (method, params, timeoutMs) => new Promise((resolve12, reject) => {
19674
+ const request = (method, params, timeoutMs) => new Promise((resolve13, reject) => {
19627
19675
  const id = nextId++;
19628
19676
  let timer = null;
19629
19677
  if (timeoutMs) {
@@ -19633,7 +19681,7 @@ function createAcpDriver(support11) {
19633
19681
  }, timeoutMs);
19634
19682
  timer.unref?.();
19635
19683
  }
19636
- rpcPending.set(id, { resolve: resolve12, reject, timer });
19684
+ rpcPending.set(id, { resolve: resolve13, reject, timer });
19637
19685
  send({ jsonrpc: "2.0", id, method, params });
19638
19686
  });
19639
19687
  const stop = () => killCliTree(child);
@@ -19647,12 +19695,12 @@ function createAcpDriver(support11) {
19647
19695
  if (lexical === ".." || lexical.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) || isAbsolute(lexical)) {
19648
19696
  throw new Error("ACP file path is outside the session workspace.");
19649
19697
  }
19650
- const suffix = [basename2(requested)];
19651
- let ancestor = dirname4(requested);
19698
+ const suffix = [basename3(requested)];
19699
+ let ancestor = dirname5(requested);
19652
19700
  while (!await lstat(ancestor).catch(() => null)) {
19653
- const parent = dirname4(ancestor);
19701
+ const parent = dirname5(ancestor);
19654
19702
  if (parent === ancestor) throw new Error("ACP file path has no accessible parent.");
19655
- suffix.unshift(basename2(ancestor));
19703
+ suffix.unshift(basename3(ancestor));
19656
19704
  ancestor = parent;
19657
19705
  }
19658
19706
  const candidate = resolve4(await realpath(ancestor), ...suffix);
@@ -19698,7 +19746,7 @@ function createAcpDriver(support11) {
19698
19746
  if (typeof params.content !== "string" || Buffer.byteLength(params.content) > CLIENT_FILE_MAX_BYTES) {
19699
19747
  throw new Error(`ACP can only write text files under ${CLIENT_FILE_MAX_BYTES} bytes.`);
19700
19748
  }
19701
- await mkdir(dirname4(path3), { recursive: true });
19749
+ await mkdir(dirname5(path3), { recursive: true });
19702
19750
  await writeFile(path3, params.content, "utf8");
19703
19751
  send({ jsonrpc: "2.0", id: msg.id, result: {} });
19704
19752
  } catch (error51) {
@@ -20069,12 +20117,12 @@ ${turn.text}` : turn.text;
20069
20117
  const snapshot = async () => {
20070
20118
  const env = childEnv();
20071
20119
  if (support11.snapshot) return support11.snapshot(env, config2, instanceId);
20072
- const version2 = await new Promise((resolve12) => {
20120
+ const version2 = await new Promise((resolve13) => {
20073
20121
  execCli(
20074
20122
  config2.cli,
20075
20123
  ["--version"],
20076
20124
  { timeout: 8e3, env },
20077
- (err, stdout2) => resolve12(err ? null : stdout2.trim())
20125
+ (err, stdout2) => resolve13(err ? null : stdout2.trim())
20078
20126
  );
20079
20127
  });
20080
20128
  if (!version2) return { state: "unavailable", reason: `\`${config2.cli}\` CLI not found` };
@@ -20162,7 +20210,7 @@ import { createHash as createHash3, randomUUID as randomUUID6 } from "node:crypt
20162
20210
  import { mkdir as mkdir2, mkdtemp, readFile as readFile2, rm, stat as stat2 } from "node:fs/promises";
20163
20211
  import { request as httpRequest } from "node:http";
20164
20212
  import { tmpdir as tmpdir2 } from "node:os";
20165
- import { delimiter as delimiter4, join as join12, resolve as resolve5 } from "node:path";
20213
+ import { delimiter as delimiter4, join as join13, resolve as resolve5 } from "node:path";
20166
20214
  function quoteBrowserArgument(value) {
20167
20215
  return `'${value.replaceAll("'", `'"'"'`)}'`;
20168
20216
  }
@@ -20197,11 +20245,11 @@ function authorizationUrlFromLine(line) {
20197
20245
  }
20198
20246
  }
20199
20247
  function antigravityProfileDirectory(instanceId, baseDir = DATA_DIR) {
20200
- return join12(baseDir, "providers", "antigravity", createHash3("sha256").update(instanceId).digest("hex"));
20248
+ return join13(baseDir, "providers", "antigravity", createHash3("sha256").update(instanceId).digest("hex"));
20201
20249
  }
20202
20250
  async function prepareAntigravityProfile(input) {
20203
20251
  const directory = resolve5(input.profileDirectory ?? antigravityProfileDirectory(input.instanceId, input.baseDir));
20204
- const acpDirectory = join12(directory, "antigravity-acp");
20252
+ const acpDirectory = join13(directory, "antigravity-acp");
20205
20253
  await mkdir2(acpDirectory, { recursive: true, mode: 448 });
20206
20254
  if (process.platform !== "win32") {
20207
20255
  const { chmod: chmod2 } = await import("node:fs/promises");
@@ -20209,7 +20257,7 @@ async function prepareAntigravityProfile(input) {
20209
20257
  await chmod2(acpDirectory, 448);
20210
20258
  }
20211
20259
  writeFileAtomic(
20212
- join12(acpDirectory, "settings.json"),
20260
+ join13(acpDirectory, "settings.json"),
20213
20261
  `${JSON.stringify({ auth: { type: "oauth-personal" } })}
20214
20262
  `,
20215
20263
  { mode: 384 }
@@ -20232,7 +20280,7 @@ async function prepareAntigravityProfile(input) {
20232
20280
  PYTHONUNBUFFERED: "1",
20233
20281
  ELECTRON_RUN_AS_NODE: "1"
20234
20282
  });
20235
- return { directory, tokenPath: join12(acpDirectory, "acp_token.json"), environment };
20283
+ return { directory, tokenPath: join13(acpDirectory, "acp_token.json"), environment };
20236
20284
  }
20237
20285
  async function antigravityProfileAuthenticated(profile) {
20238
20286
  try {
@@ -20361,7 +20409,7 @@ function isValidAntigravityInitializeResult(initialized, expectedVersion) {
20361
20409
  return true;
20362
20410
  }
20363
20411
  async function validateAntigravityRuntime(runtime, expectedVersion) {
20364
- const profileDirectory = await mkdtemp(join12(tmpdir2(), "openmaus-antigravity-verify-"));
20412
+ const profileDirectory = await mkdtemp(join13(tmpdir2(), "openmaus-antigravity-verify-"));
20365
20413
  let client;
20366
20414
  let failed = false;
20367
20415
  let failure;
@@ -20464,14 +20512,14 @@ var init_antigravity_acp = __esm({
20464
20512
  this.child.stderr.setEncoding("utf8");
20465
20513
  this.child.stderr.on("data", (chunk) => this.consumeDiagnostics(chunk));
20466
20514
  this.child.once("error", (error51) => this.failAll(error51));
20467
- this.exited = new Promise((resolve12) => {
20515
+ this.exited = new Promise((resolve13) => {
20468
20516
  this.child.once("close", (code, signal) => {
20469
20517
  this.noteStartupDiagnostic(this.diagnosticBuffer);
20470
20518
  this.diagnosticBuffer = "";
20471
20519
  if (!this.closed) this.failAll(new Error(
20472
20520
  `Antigravity ACP exited ${code ?? signal ?? "unexpectedly"}.${this.nativeStartupHint ? ` ${this.nativeStartupHint}` : ""}`
20473
20521
  ));
20474
- resolve12();
20522
+ resolve13();
20475
20523
  });
20476
20524
  });
20477
20525
  }
@@ -20577,8 +20625,8 @@ var init_antigravity_acp = __esm({
20577
20625
  async closeAndWait(timeoutMs = 5e3) {
20578
20626
  this.close();
20579
20627
  let timer;
20580
- const timedOut = new Promise((resolve12) => {
20581
- timer = setTimeout(() => resolve12(false), timeoutMs);
20628
+ const timedOut = new Promise((resolve13) => {
20629
+ timer = setTimeout(() => resolve13(false), timeoutMs);
20582
20630
  timer.unref?.();
20583
20631
  });
20584
20632
  try {
@@ -20750,7 +20798,7 @@ import {
20750
20798
  writeFile as writeFile2
20751
20799
  } from "node:fs/promises";
20752
20800
  import { constants } from "node:fs";
20753
- import { delimiter as delimiter5, dirname as dirname5, isAbsolute as isAbsolute2, join as join13, resolve as resolve6 } from "node:path";
20801
+ import { delimiter as delimiter5, dirname as dirname6, isAbsolute as isAbsolute2, join as join14, resolve as resolve6 } from "node:path";
20754
20802
  import { Transform } from "node:stream";
20755
20803
  import { pipeline } from "node:stream/promises";
20756
20804
  import { createInflateRaw } from "node:zlib";
@@ -20771,7 +20819,7 @@ async function retryOnWindowsFileLock(step, options = {}) {
20771
20819
  }
20772
20820
  }
20773
20821
  function runtimeRoot(baseDir = DATA_DIR) {
20774
- return join13(baseDir, "tools", "antigravity-acp", `${process.platform}-${process.arch}`);
20822
+ return join14(baseDir, "tools", "antigravity-acp", `${process.platform}-${process.arch}`);
20775
20823
  }
20776
20824
  function executableNames(platform = process.platform) {
20777
20825
  return platform === "win32" ? { executable: "agy_acp_server.exe", harness: "localharness_external.exe" } : { executable: "agy_acp_server.par", harness: "localharness_external" };
@@ -20788,10 +20836,10 @@ async function executableFile(path3, bytes) {
20788
20836
  }
20789
20837
  async function completedRelease(directory, asset) {
20790
20838
  try {
20791
- const record3 = JSON.parse(await readFile3(join13(directory, INSTALL_RECORD), "utf8"));
20839
+ const record3 = JSON.parse(await readFile3(join14(directory, INSTALL_RECORD), "utf8"));
20792
20840
  if (record3.sha256 !== asset.sha256 || record3.version !== asset.version || record3.executable.name !== asset.executable.name || record3.executable.bytes !== asset.executable.bytes || record3.harness.name !== asset.harness.name || record3.harness.bytes !== asset.harness.bytes) return null;
20793
- const executablePath = join13(directory, asset.executable.name);
20794
- const harnessPath = join13(directory, asset.harness.name);
20841
+ const executablePath = join14(directory, asset.executable.name);
20842
+ const harnessPath = join14(directory, asset.harness.name);
20795
20843
  if (!await executableFile(executablePath, asset.executable.bytes)) return null;
20796
20844
  if (!await executableFile(harnessPath, asset.harness.bytes)) return null;
20797
20845
  return { executablePath, harnessPath, source: "managed", version: asset.version };
@@ -20807,7 +20855,7 @@ function pathCandidates(binary2, env) {
20807
20855
  async function externalRuntime(candidate, source) {
20808
20856
  const names = executableNames();
20809
20857
  const executablePath = resolve6(candidate);
20810
- const harnessPath = join13(dirname5(executablePath), names.harness);
20858
+ const harnessPath = join14(dirname6(executablePath), names.harness);
20811
20859
  if (!await executableFile(executablePath) || !await executableFile(harnessPath)) return null;
20812
20860
  return { executablePath, harnessPath, source, version: null };
20813
20861
  }
@@ -20827,7 +20875,7 @@ async function resolveAntigravityRuntime(binaryPath, env = process.env, baseDir
20827
20875
  }
20828
20876
  const asset = resolveAntigravityReleaseAsset();
20829
20877
  if (asset) {
20830
- const managed = await completedRelease(join13(runtimeRoot(baseDir), "versions", asset.sha256), asset);
20878
+ const managed = await completedRelease(join14(runtimeRoot(baseDir), "versions", asset.sha256), asset);
20831
20879
  if (managed) return managed;
20832
20880
  }
20833
20881
  const names = executableNames();
@@ -20949,7 +20997,7 @@ async function extractPinnedArchive(archivePath, outputDirectory, asset) {
20949
20997
  start: dataOffset,
20950
20998
  end: dataOffset + entry.compressedBytes - 1
20951
20999
  });
20952
- const destination = createWriteStream(join13(outputDirectory, entry.name), { flags: "wx", mode: 448 });
21000
+ const destination = createWriteStream(join14(outputDirectory, entry.name), { flags: "wx", mode: 448 });
20953
21001
  if (entry.compression === 8) await pipeline(source, createInflateRaw(), counter, destination);
20954
21002
  else await pipeline(source, counter, destination);
20955
21003
  if (bytes !== expectedBytes) throw new Error("An extracted file was truncated.");
@@ -20960,8 +21008,8 @@ async function installOnce(options) {
20960
21008
  const asset = options.asset === void 0 ? resolveAntigravityReleaseAsset() : options.asset;
20961
21009
  if (!asset) throw new Error(`Google does not publish an Antigravity runtime for ${process.platform}-${process.arch}.`);
20962
21010
  const root = runtimeRoot(options.baseDir);
20963
- const versions = join13(root, "versions");
20964
- const destination = join13(versions, asset.sha256);
21011
+ const versions = join14(root, "versions");
21012
+ const destination = join14(versions, asset.sha256);
20965
21013
  await mkdir3(versions, { recursive: true, mode: 448 });
20966
21014
  const existing = await completedRelease(destination, asset);
20967
21015
  if (existing) {
@@ -20977,11 +21025,11 @@ async function installOnce(options) {
20977
21025
  } catch (error51) {
20978
21026
  if (error51 instanceof Error && error51.message.includes("Antigravity needs")) throw error51;
20979
21027
  }
20980
- const staging = await mkdtemp2(join13(versions, `${STAGING_PREFIX}${randomUUID7()}-`));
21028
+ const staging = await mkdtemp2(join14(versions, `${STAGING_PREFIX}${randomUUID7()}-`));
20981
21029
  let failed = false;
20982
21030
  try {
20983
- const archivePath = join13(staging, "download.zip");
20984
- const runtimeDirectory = join13(staging, "runtime");
21031
+ const archivePath = join14(staging, "download.zip");
21032
+ const runtimeDirectory = join14(staging, "runtime");
20985
21033
  await mkdir3(runtimeDirectory, { mode: 448 });
20986
21034
  const controller = new AbortController();
20987
21035
  const timeout = setTimeout(() => controller.abort(), DOWNLOAD_TIMEOUT_MS2);
@@ -21027,12 +21075,12 @@ async function installOnce(options) {
21027
21075
  }
21028
21076
  await extractPinnedArchive(archivePath, runtimeDirectory, asset);
21029
21077
  if (process.platform !== "win32") {
21030
- await chmod(join13(runtimeDirectory, asset.executable.name), 493);
21031
- await chmod(join13(runtimeDirectory, asset.harness.name), 493);
21078
+ await chmod(join14(runtimeDirectory, asset.executable.name), 493);
21079
+ await chmod(join14(runtimeDirectory, asset.harness.name), 493);
21032
21080
  }
21033
21081
  const candidate = {
21034
- executablePath: join13(runtimeDirectory, asset.executable.name),
21035
- harnessPath: join13(runtimeDirectory, asset.harness.name),
21082
+ executablePath: join14(runtimeDirectory, asset.executable.name),
21083
+ harnessPath: join14(runtimeDirectory, asset.harness.name),
21036
21084
  source: "managed",
21037
21085
  version: asset.version
21038
21086
  };
@@ -21043,7 +21091,7 @@ async function installOnce(options) {
21043
21091
  executable: asset.executable,
21044
21092
  harness: asset.harness
21045
21093
  };
21046
- await writeFile2(join13(runtimeDirectory, INSTALL_RECORD), `${JSON.stringify(record3)}
21094
+ await writeFile2(join14(runtimeDirectory, INSTALL_RECORD), `${JSON.stringify(record3)}
21047
21095
  `, { flag: "wx", mode: 384 });
21048
21096
  try {
21049
21097
  await retryOnWindowsFileLock(() => rename(runtimeDirectory, destination));
@@ -21087,7 +21135,7 @@ var init_antigravity_runtime = __esm({
21087
21135
  FREE_SPACE_MARGIN = 256 * 1024 * 1024;
21088
21136
  DOWNLOAD_TIMEOUT_MS2 = 45 * 6e4;
21089
21137
  STAGING_PREFIX = ".install-";
21090
- wait = (ms) => new Promise((resolve12) => setTimeout(resolve12, ms));
21138
+ wait = (ms) => new Promise((resolve13) => setTimeout(resolve13, ms));
21091
21139
  INSTALL_RECORD = ".install-complete.json";
21092
21140
  AntigravitySetupError = class extends Error {
21093
21141
  status = 409;
@@ -21482,15 +21530,15 @@ var init_boxagent = __esm({
21482
21530
  // server/drivers/claude-login-auth.ts
21483
21531
  import { randomUUID as randomUUID8 } from "node:crypto";
21484
21532
  import { realpathSync } from "node:fs";
21485
- import { homedir as homedir6 } from "node:os";
21486
- import { basename as basename3, dirname as dirname6, isAbsolute as isAbsolute3, join as join14, resolve as resolve7 } from "node:path";
21533
+ import { homedir as homedir8 } from "node:os";
21534
+ import { basename as basename4, dirname as dirname7, isAbsolute as isAbsolute3, join as join15, resolve as resolve7 } from "node:path";
21487
21535
  import { stripVTControlCharacters as stripVTControlCharacters3 } from "node:util";
21488
21536
  function canonicalPath(path3) {
21489
21537
  try {
21490
21538
  return realpathSync(path3);
21491
21539
  } catch {
21492
- const parent = dirname6(path3);
21493
- return parent === path3 ? path3 : join14(canonicalPath(parent), basename3(path3));
21540
+ const parent = dirname7(path3);
21541
+ return parent === path3 ? path3 : join15(canonicalPath(parent), basename4(path3));
21494
21542
  }
21495
21543
  }
21496
21544
  function claudeSignInLink(value) {
@@ -21542,11 +21590,11 @@ var init_claude_login_auth = __esm({
21542
21590
  await this.flow?.stopping;
21543
21591
  if (this.disposed) throw new Error("This provider was removed. Refresh Settings before signing in.");
21544
21592
  const env = this.options.environment();
21545
- const home = env.HOME || env.USERPROFILE || homedir6();
21593
+ const home = env.HOME || env.USERPROFILE || homedir8();
21546
21594
  if (!isAbsolute3(home) || env.CLAUDE_CONFIG_DIR && !isAbsolute3(env.CLAUDE_CONFIG_DIR)) {
21547
21595
  throw new Error("Use an absolute HOME and CLAUDE_CONFIG_DIR path for this server's Claude provider before signing in.");
21548
21596
  }
21549
- const homeKey = canonicalPath(resolve7(env.CLAUDE_CONFIG_DIR || join14(home, ".claude")));
21597
+ const homeKey = canonicalPath(resolve7(env.CLAUDE_CONFIG_DIR || join15(home, ".claude")));
21550
21598
  if (authenticatingHomes.has(homeKey)) {
21551
21599
  throw new Error("A Claude sign-in is already running for this server account. Finish or cancel it before starting another.");
21552
21600
  }
@@ -21801,13 +21849,13 @@ function computeBackoff(attempt, random = Math.random) {
21801
21849
  function interruptibleDelay(ms, signal) {
21802
21850
  let timer = null;
21803
21851
  let onCancel = null;
21804
- const promise2 = new Promise((resolve12) => {
21805
- timer = setTimeout(() => resolve12("elapsed"), Math.max(1, ms));
21852
+ const promise2 = new Promise((resolve13) => {
21853
+ timer = setTimeout(() => resolve13("elapsed"), Math.max(1, ms));
21806
21854
  timer.unref?.();
21807
- if (signal?.aborted) return resolve12("cancelled");
21855
+ if (signal?.aborted) return resolve13("cancelled");
21808
21856
  onCancel = () => {
21809
21857
  clearTimeout(timer);
21810
- resolve12("cancelled");
21858
+ resolve13("cancelled");
21811
21859
  };
21812
21860
  signal?.addEventListener("abort", onCancel, { once: true });
21813
21861
  });
@@ -21856,37 +21904,37 @@ var init_retry = __esm({
21856
21904
 
21857
21905
  // server/drivers/claude.ts
21858
21906
  import { createHash as createHash5, randomBytes as randomBytes2 } from "node:crypto";
21859
- import { chmodSync as chmodSync3, mkdtempSync as mkdtempSync2, readFileSync as readFileSync6, rmSync as rmSync2, unlinkSync as unlinkSync3, writeFileSync as writeFileSync4 } from "node:fs";
21907
+ import { chmodSync as chmodSync3, mkdtempSync as mkdtempSync2, readFileSync as readFileSync6, rmSync as rmSync2, unlinkSync as unlinkSync3, writeFileSync as writeFileSync5 } from "node:fs";
21860
21908
  import { createServer as createNetServer } from "node:net";
21861
- import { homedir as homedir7, tmpdir as tmpdir3 } from "node:os";
21862
- import { join as join15, dirname as dirname7, isAbsolute as isAbsolute4, normalize } from "node:path";
21909
+ import { homedir as homedir9, tmpdir as tmpdir3 } from "node:os";
21910
+ import { join as join16, dirname as dirname8, isAbsolute as isAbsolute4, normalize } from "node:path";
21863
21911
  function claudeAuthStatus(cli, env, run3 = execCli) {
21864
- return new Promise((resolve12) => {
21912
+ return new Promise((resolve13) => {
21865
21913
  run3(cli, ["auth", "status", "--json"], { timeout: 8e3, maxBuffer: 65536, env }, (_error, stdout2) => {
21866
21914
  try {
21867
21915
  const status = JSON.parse(stdout2);
21868
21916
  if (!status || typeof status !== "object" || !("loggedIn" in status) || status.loggedIn !== true) {
21869
- return resolve12({ authenticated: false });
21917
+ return resolve13({ authenticated: false });
21870
21918
  }
21871
21919
  const identity = status;
21872
21920
  const boundedText = (value, max) => typeof value === "string" && value.trim().length > 0 && value.length <= max && !/[\p{Cc}\p{Cf}]/u.test(value) ? value.trim() : void 0;
21873
21921
  const candidateEmail = boundedText(identity.email, 254);
21874
21922
  const email3 = candidateEmail && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(candidateEmail) ? candidateEmail : void 0;
21875
21923
  const organization = boundedText(identity.orgName, 160);
21876
- resolve12({
21924
+ resolve13({
21877
21925
  authenticated: true,
21878
21926
  ...email3 || organization ? { account: { ...email3 ? { email: email3 } : {}, ...organization ? { organization } : {} } } : {}
21879
21927
  });
21880
21928
  } catch {
21881
- resolve12({ authenticated: false });
21929
+ resolve13({ authenticated: false });
21882
21930
  }
21883
21931
  });
21884
21932
  });
21885
21933
  }
21886
21934
  function resolveClaudeConfigDir(configDir, env = process.env) {
21887
- const home = env.HOME || env.USERPROFILE || homedir7();
21888
- const configured = configDir?.trim() || env.CLAUDE_CONFIG_DIR?.trim() || join15(home, ".claude");
21889
- const expanded = configured === "~" ? home : configured.startsWith("~/") ? join15(home, configured.slice(2)) : configured;
21935
+ const home = env.HOME || env.USERPROFILE || homedir9();
21936
+ const configured = configDir?.trim() || env.CLAUDE_CONFIG_DIR?.trim() || join16(home, ".claude");
21937
+ const expanded = configured === "~" ? home : configured.startsWith("~/") ? join16(home, configured.slice(2)) : configured;
21890
21938
  if (!isAbsolute4(expanded) || /[\p{Cc}\p{Cf}]/u.test(expanded)) {
21891
21939
  throw new Error("claude: configDir must be an absolute path or start with ~/");
21892
21940
  }
@@ -21938,7 +21986,7 @@ function extrasFromUnknown(value) {
21938
21986
  function readClaudeModelCatalog(env = process.env) {
21939
21987
  let settings2 = {};
21940
21988
  try {
21941
- settings2 = JSON.parse(readFileSync6(join15(resolveClaudeConfigDir(void 0, env), "settings.json"), "utf8"));
21989
+ settings2 = JSON.parse(readFileSync6(join16(resolveClaudeConfigDir(void 0, env), "settings.json"), "utf8"));
21942
21990
  } catch {
21943
21991
  return STATIC_CLAUDE_MODELS;
21944
21992
  }
@@ -21962,7 +22010,7 @@ function readClaudeModelCatalog(env = process.env) {
21962
22010
  function removePrivateTempDir(filePath) {
21963
22011
  if (!filePath) return true;
21964
22012
  try {
21965
- rmSync2(dirname7(filePath), { recursive: true, force: true, maxRetries: 3, retryDelay: 50 });
22013
+ rmSync2(dirname8(filePath), { recursive: true, force: true, maxRetries: 3, retryDelay: 50 });
21966
22014
  return true;
21967
22015
  } catch {
21968
22016
  return false;
@@ -21988,7 +22036,7 @@ function brokerSocketCandidates(threadId) {
21988
22036
  const base2 = permissionSocketPath(threadId);
21989
22037
  if (process.platform !== "win32") {
21990
22038
  const scope = createHash5("sha256").update(`${DATA_DIR}\0${process.pid}\0${threadId}`).digest("hex").slice(0, 16);
21991
- return [base2, join15(tmpdir3(), `omb-perm-${scope}.sock`)];
22039
+ return [base2, join16(tmpdir3(), `omb-perm-${scope}.sock`)];
21992
22040
  }
21993
22041
  return [
21994
22042
  base2,
@@ -22069,9 +22117,9 @@ async function createPermissionBroker(opts) {
22069
22117
  unlinkSync3(candidate);
22070
22118
  } catch {
22071
22119
  }
22072
- let outcome = await new Promise((resolve12) => {
22073
- attempt.once("listening", () => resolve12("listening"));
22074
- attempt.once("error", (error51) => resolve12(error51));
22120
+ let outcome = await new Promise((resolve13) => {
22121
+ attempt.once("listening", () => resolve13("listening"));
22122
+ attempt.once("error", (error51) => resolve13(error51));
22075
22123
  attempt.listen(candidate);
22076
22124
  });
22077
22125
  if (outcome === "listening" && process.platform !== "win32") {
@@ -22328,19 +22376,19 @@ var init_claude = __esm({
22328
22376
  };
22329
22377
  const writeUser = (s, threadId, promptMsg) => {
22330
22378
  if (!s.child.stdin.writable || s.child.stdin.destroyed) return Promise.resolve(false);
22331
- return new Promise((resolve12) => {
22379
+ return new Promise((resolve13) => {
22332
22380
  try {
22333
22381
  s.child.stdin.write(JSON.stringify(promptMsg) + "\n", (error51) => {
22334
- if (error51) return resolve12(false);
22382
+ if (error51) return resolve13(false);
22335
22383
  appendNative(threadId, {
22336
22384
  dir: "out",
22337
22385
  source: "claude.sdk.message",
22338
22386
  msg: diagnosticClaudeUserMessage(promptMsg)
22339
22387
  });
22340
- resolve12(true);
22388
+ resolve13(true);
22341
22389
  });
22342
22390
  } catch {
22343
- resolve12(false);
22391
+ resolve13(false);
22344
22392
  }
22345
22393
  });
22346
22394
  };
@@ -22454,13 +22502,13 @@ var init_claude = __esm({
22454
22502
  allowed.push("mcp__ogb");
22455
22503
  let mcpConfigPath = null;
22456
22504
  if (Object.keys(mcpServers).length) {
22457
- mcpConfigPath = join15(mkdtempSync2(join15(tmpdir3(), "omb-mcp-")), "mcp.json");
22505
+ mcpConfigPath = join16(mkdtempSync2(join16(tmpdir3(), "omb-mcp-")), "mcp.json");
22458
22506
  args.push("--mcp-config", mcpConfigPath);
22459
22507
  args.push("--allowedTools", allowed.join(","));
22460
22508
  }
22461
22509
  const env = environment(turnModel);
22462
22510
  env.CLAUDE_CODE_DISABLE_BACKGROUND_TASKS = "1";
22463
- const cwd = turn.cwd ?? homedir7();
22511
+ const cwd = turn.cwd ?? homedir9();
22464
22512
  const privateFileFlags = /* @__PURE__ */ new Set(["--mcp-config"]);
22465
22513
  const keyArgs = args.filter((a2, i2) => !privateFileFlags.has(a2) && !privateFileFlags.has(args[i2 - 1] ?? ""));
22466
22514
  const argsKey = JSON.stringify({
@@ -22489,7 +22537,7 @@ var init_claude = __esm({
22489
22537
  retryState.delete(threadId);
22490
22538
  if (mcpConfigPath) {
22491
22539
  try {
22492
- rmSync2(dirname7(mcpConfigPath), { recursive: true, force: true });
22540
+ rmSync2(dirname8(mcpConfigPath), { recursive: true, force: true });
22493
22541
  } catch {
22494
22542
  }
22495
22543
  }
@@ -22497,7 +22545,7 @@ var init_claude = __esm({
22497
22545
  }
22498
22546
  if (mcpConfigPath) {
22499
22547
  try {
22500
- rmSync2(dirname7(mcpConfigPath), { recursive: true, force: true });
22548
+ rmSync2(dirname8(mcpConfigPath), { recursive: true, force: true });
22501
22549
  } catch {
22502
22550
  }
22503
22551
  }
@@ -22509,7 +22557,7 @@ var init_claude = __esm({
22509
22557
  broker = void 0;
22510
22558
  if (mcpConfigPath) {
22511
22559
  try {
22512
- rmSync2(dirname7(mcpConfigPath), { recursive: true, force: true });
22560
+ rmSync2(dirname8(mcpConfigPath), { recursive: true, force: true });
22513
22561
  } catch {
22514
22562
  }
22515
22563
  mcpConfigPath = null;
@@ -22522,8 +22570,8 @@ var init_claude = __esm({
22522
22570
  };
22523
22571
  try {
22524
22572
  if (turn.system) {
22525
- systemPromptPath = join15(mkdtempSync2(join15(tmpdir3(), "omb-system-")), "prompt.txt");
22526
- writeFileSync4(systemPromptPath, turn.system, { mode: 384 });
22573
+ systemPromptPath = join16(mkdtempSync2(join16(tmpdir3(), "omb-system-")), "prompt.txt");
22574
+ writeFileSync5(systemPromptPath, turn.system, { mode: 384 });
22527
22575
  args.push("--append-system-prompt-file", systemPromptPath);
22528
22576
  }
22529
22577
  if (socketPath) {
@@ -22563,7 +22611,7 @@ var init_claude = __esm({
22563
22611
  }
22564
22612
  }
22565
22613
  if (mcpConfigPath) {
22566
- writeFileSync4(mcpConfigPath, JSON.stringify({ mcpServers }), { mode: 384 });
22614
+ writeFileSync5(mcpConfigPath, JSON.stringify({ mcpServers }), { mode: 384 });
22567
22615
  }
22568
22616
  if (sessionId) args.push("--resume", sessionId);
22569
22617
  else args.push("--session-id", newSessionId);
@@ -22602,7 +22650,7 @@ var init_claude = __esm({
22602
22650
  session.broker?.pause();
22603
22651
  if (session.mcpConfigPath) {
22604
22652
  try {
22605
- rmSync2(dirname7(session.mcpConfigPath), { recursive: true, force: true });
22653
+ rmSync2(dirname8(session.mcpConfigPath), { recursive: true, force: true });
22606
22654
  } catch {
22607
22655
  }
22608
22656
  session.mcpConfigPath = null;
@@ -22730,7 +22778,7 @@ var init_claude = __esm({
22730
22778
  failedBroker?.close();
22731
22779
  if (session.mcpConfigPath) {
22732
22780
  try {
22733
- rmSync2(dirname7(session.mcpConfigPath), { recursive: true, force: true });
22781
+ rmSync2(dirname8(session.mcpConfigPath), { recursive: true, force: true });
22734
22782
  } catch {
22735
22783
  }
22736
22784
  session.mcpConfigPath = null;
@@ -22799,7 +22847,7 @@ var init_claude = __esm({
22799
22847
  session.broker?.close();
22800
22848
  if (session.mcpConfigPath) {
22801
22849
  try {
22802
- rmSync2(dirname7(session.mcpConfigPath), { recursive: true, force: true });
22850
+ rmSync2(dirname8(session.mcpConfigPath), { recursive: true, force: true });
22803
22851
  } catch {
22804
22852
  }
22805
22853
  }
@@ -22830,19 +22878,19 @@ var init_claude = __esm({
22830
22878
  } });
22831
22879
  const snapshot = async () => {
22832
22880
  const env = environment();
22833
- const version2 = await new Promise((resolve12) => {
22881
+ const version2 = await new Promise((resolve13) => {
22834
22882
  execCli(
22835
22883
  config2.cli,
22836
22884
  ["--version"],
22837
22885
  { timeout: 8e3, env },
22838
- (err, stdout2) => resolve12(err ? null : stdout2.trim())
22886
+ (err, stdout2) => resolve13(err ? null : stdout2.trim())
22839
22887
  );
22840
22888
  });
22841
22889
  if (!version2) return { state: "unavailable", reason: `\`${config2.cli}\` CLI not found` };
22842
22890
  const auth = await claudeAuthStatus(config2.cli, env);
22843
22891
  return { state: "available", version: version2, ...auth, billing: "subscription" };
22844
22892
  };
22845
- const generateReview = (prompt, signal) => new Promise((resolve12, reject) => {
22893
+ const generateReview = (prompt, signal) => new Promise((resolve13, reject) => {
22846
22894
  const child = spawnCli(
22847
22895
  config2.cli,
22848
22896
  ["-p", "--model", "claude-haiku-4-5", "--output-format", "text"],
@@ -22860,7 +22908,7 @@ var init_claude = __esm({
22860
22908
  clearTimeout(timer);
22861
22909
  signal?.removeEventListener("abort", onAbort);
22862
22910
  if (error51) reject(error51);
22863
- else resolve12(stdout2.trim());
22911
+ else resolve13(stdout2.trim());
22864
22912
  };
22865
22913
  const onAbort = () => {
22866
22914
  killCliTree(child);
@@ -22961,8 +23009,8 @@ var init_claude = __esm({
22961
23009
 
22962
23010
  // server/drivers/codex-catalog.ts
22963
23011
  import { readdirSync as readdirSync2, readFileSync as readFileSync7 } from "node:fs";
22964
- import { homedir as homedir8 } from "node:os";
22965
- import { basename as basename4, join as join16 } from "node:path";
23012
+ import { homedir as homedir10 } from "node:os";
23013
+ import { basename as basename5, join as join17 } from "node:path";
22966
23014
  function encodeCodexSelection(provider, model) {
22967
23015
  return `${provider}${SEP}${model}`;
22968
23016
  }
@@ -22975,9 +23023,9 @@ function decodeCodexSelection(id) {
22975
23023
  return { model: id, modelProvider: MODEL_ID2.test(id) ? OFFICIAL_CODEX_PROVIDER : null };
22976
23024
  }
22977
23025
  function readCodexAppServerModelCatalog(cli, env, timeoutMs = 8e3) {
22978
- return new Promise((resolve12) => {
23026
+ return new Promise((resolve13) => {
22979
23027
  const child = spawnCli(cli, ["app-server"], {
22980
- cwd: homedir8(),
23028
+ cwd: homedir10(),
22981
23029
  env,
22982
23030
  stdio: ["pipe", "pipe", "pipe"]
22983
23031
  });
@@ -22992,7 +23040,7 @@ function readCodexAppServerModelCatalog(cli, env, timeoutMs = 8e3) {
22992
23040
  settled = true;
22993
23041
  clearTimeout(timer);
22994
23042
  killCliTree(child);
22995
- resolve12(catalog);
23043
+ resolve13(catalog);
22996
23044
  };
22997
23045
  const request = (method, params, kind) => {
22998
23046
  const id = nextId++;
@@ -23078,7 +23126,7 @@ function readCodexAppServerModelCatalog(cli, env, timeoutMs = 8e3) {
23078
23126
  }
23079
23127
  function codexHome(env) {
23080
23128
  if (env.CODEX_HOME) return env.CODEX_HOME;
23081
- return join16(env.HOME || env.USERPROFILE || homedir8(), ".codex");
23129
+ return join17(env.HOME || env.USERPROFILE || homedir10(), ".codex");
23082
23130
  }
23083
23131
  function unquote(raw) {
23084
23132
  let value = raw.trim();
@@ -23164,11 +23212,11 @@ function niceLabel(model, provider, known, named) {
23164
23212
  }
23165
23213
  function collectCatalogNames(home) {
23166
23214
  const named = /* @__PURE__ */ new Map();
23167
- for (const file2 of listDir(join16(home, "model-catalogs"))) {
23215
+ for (const file2 of listDir(join17(home, "model-catalogs"))) {
23168
23216
  if (!file2.endsWith(".json")) continue;
23169
- const provider = basename4(file2, ".json").replace(/-models$/, "");
23217
+ const provider = basename5(file2, ".json").replace(/-models$/, "");
23170
23218
  if (!PROVIDER_ID.test(provider)) continue;
23171
- const raw = readText(join16(home, "model-catalogs", file2));
23219
+ const raw = readText(join17(home, "model-catalogs", file2));
23172
23220
  if (!raw) continue;
23173
23221
  let parsed;
23174
23222
  try {
@@ -23220,7 +23268,7 @@ async function probeProviderModels(provider, env, fetchImpl) {
23220
23268
  async function readCodexModelCatalog(env = process.env, fetchImpl = fetch, cli) {
23221
23269
  const official = (cli ? await readCodexAppServerModelCatalog(cli, env) : null) ?? STATIC_CODEX_MODELS;
23222
23270
  const home = codexHome(env);
23223
- const mainText = readText(join16(home, "config.toml"));
23271
+ const mainText = readText(join17(home, "config.toml"));
23224
23272
  if (!mainText) return mergeLocalInject(official, env, fetchImpl);
23225
23273
  const main2 = parseCodexToml(mainText);
23226
23274
  const known = new Map(main2.providers.map((provider) => [provider.id, provider]));
@@ -23235,7 +23283,7 @@ async function readCodexModelCatalog(env = process.env, fetchImpl = fetch, cli)
23235
23283
  remember(main2.modelProvider, main2.model);
23236
23284
  for (const file2 of listDir(home)) {
23237
23285
  if (!file2.endsWith(".config.toml")) continue;
23238
- const profile = parseCodexToml(readText(join16(home, file2)) ?? "");
23286
+ const profile = parseCodexToml(readText(join17(home, file2)) ?? "");
23239
23287
  for (const provider of profile.providers) {
23240
23288
  if (!known.has(provider.id)) known.set(provider.id, provider);
23241
23289
  }
@@ -23301,8 +23349,8 @@ var init_codex_catalog = __esm({
23301
23349
 
23302
23350
  // server/drivers/codex-instructions.ts
23303
23351
  import { createHash as createHash6 } from "node:crypto";
23304
- import { mkdirSync as mkdirSync5, readFileSync as readFileSync8 } from "node:fs";
23305
- import { join as join17 } from "node:path";
23352
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync8 } from "node:fs";
23353
+ import { join as join18 } from "node:path";
23306
23354
  function codexDeveloperInstructions(config2, botInstructions) {
23307
23355
  if (!config2 || typeof config2 !== "object" || Array.isArray(config2)) {
23308
23356
  throw new Error("Codex returned no effective configuration; cannot safely update bot instructions.");
@@ -23316,8 +23364,8 @@ function codexDeveloperInstructions(config2, botInstructions) {
23316
23364
  ${configured}` : botInstructions;
23317
23365
  }
23318
23366
  async function syncCodexInstructions(key, nativeThreadId, instructions, resumed, request) {
23319
- const directory = join17(DATA_DIR, "codex-instructions");
23320
- const path3 = join17(directory, `${digest(JSON.stringify([key, nativeThreadId]))}.sha256`);
23367
+ const directory = join18(DATA_DIR, "codex-instructions");
23368
+ const path3 = join18(directory, `${digest(JSON.stringify([key, nativeThreadId]))}.sha256`);
23321
23369
  const fingerprint = digest(instructions);
23322
23370
  let previous;
23323
23371
  try {
@@ -23346,7 +23394,7 @@ async function syncCodexInstructions(key, nativeThreadId, instructions, resumed,
23346
23394
  }
23347
23395
  }
23348
23396
  if (!resumed || previous !== fingerprint) {
23349
- mkdirSync5(directory, { recursive: true });
23397
+ mkdirSync6(directory, { recursive: true });
23350
23398
  writeFileAtomic(path3, fingerprint, { mode: 384 });
23351
23399
  }
23352
23400
  }
@@ -23363,15 +23411,15 @@ var init_codex_instructions = __esm({
23363
23411
  // server/drivers/codex-device-auth.ts
23364
23412
  import { randomUUID as randomUUID9 } from "node:crypto";
23365
23413
  import { realpathSync as realpathSync2 } from "node:fs";
23366
- import { homedir as homedir9 } from "node:os";
23367
- import { basename as basename5, dirname as dirname8, isAbsolute as isAbsolute5, join as join18, resolve as resolve8 } from "node:path";
23414
+ import { homedir as homedir11 } from "node:os";
23415
+ import { basename as basename6, dirname as dirname9, isAbsolute as isAbsolute5, join as join19, resolve as resolve8 } from "node:path";
23368
23416
  import { stripVTControlCharacters as stripVTControlCharacters4 } from "node:util";
23369
23417
  function canonicalPath2(path3) {
23370
23418
  try {
23371
23419
  return realpathSync2(path3);
23372
23420
  } catch {
23373
- const parent = dirname8(path3);
23374
- return parent === path3 ? path3 : join18(canonicalPath2(parent), basename5(path3));
23421
+ const parent = dirname9(path3);
23422
+ return parent === path3 ? path3 : join19(canonicalPath2(parent), basename6(path3));
23375
23423
  }
23376
23424
  }
23377
23425
  function codexDevicePrompt(output) {
@@ -23403,6 +23451,7 @@ var init_codex_device_auth = __esm({
23403
23451
  authenticatingHomes2 = /* @__PURE__ */ new Set();
23404
23452
  CodexDeviceAuthController = class {
23405
23453
  flow = null;
23454
+ command = null;
23406
23455
  disposed = false;
23407
23456
  options;
23408
23457
  constructor(options) {
@@ -23417,11 +23466,11 @@ var init_codex_device_auth = __esm({
23417
23466
  await this.flow?.stopping;
23418
23467
  if (this.disposed) throw new Error("This provider was removed. Refresh Settings before signing in.");
23419
23468
  const env = this.options.environment();
23420
- const home = env.HOME || env.USERPROFILE || homedir9();
23469
+ const home = env.HOME || env.USERPROFILE || homedir11();
23421
23470
  if (!isAbsolute5(home) || env.CODEX_HOME && !isAbsolute5(env.CODEX_HOME)) {
23422
23471
  throw new Error("Use an absolute HOME and CODEX_HOME path for this server's Codex provider before signing in.");
23423
23472
  }
23424
- const homeKey = canonicalPath2(resolve8(env.CODEX_HOME || join18(home, ".codex")));
23473
+ const homeKey = canonicalPath2(resolve8(env.CODEX_HOME || join19(home, ".codex")));
23425
23474
  if (authenticatingHomes2.has(homeKey)) {
23426
23475
  throw new Error("A ChatGPT sign-in is already running for this server account. Finish or cancel it before starting another.");
23427
23476
  }
@@ -23478,12 +23527,92 @@ var init_codex_device_auth = __esm({
23478
23527
  if (this.flow?.status.phase === "waiting") this.finish(this.flow, "cancelled", "ChatGPT sign-in cancelled.");
23479
23528
  await this.flow?.stopping;
23480
23529
  if (this.flow?.terminationFailed) throw new Error(this.flow.status.message);
23530
+ if (this.command && !await this.stopChild(this.command)) {
23531
+ throw new Error("Codex could not be stopped on this server. Ask the server administrator to stop the account command before trying again.");
23532
+ }
23481
23533
  }
23482
23534
  async dispose() {
23483
23535
  this.disposed = true;
23484
23536
  await this.cancel();
23485
23537
  this.flow = null;
23486
23538
  }
23539
+ /** Remove the ChatGPT sign-in Codex stores for this server account so a
23540
+ * different account can connect. A sign-in in progress is never pulled
23541
+ * away underneath the browser completing it. */
23542
+ async signOut() {
23543
+ if (this.disposed) throw new Error("This provider was removed. Refresh Settings before signing out.");
23544
+ if (this.flow?.status.phase === "waiting") throw new Error("Finish or cancel the ChatGPT sign-in in progress before signing out.");
23545
+ await this.flow?.stopping;
23546
+ if (this.disposed) throw new Error("This provider was removed. Refresh Settings before signing out.");
23547
+ const env = this.options.environment();
23548
+ const home = env.HOME || env.USERPROFILE || homedir11();
23549
+ if (!isAbsolute5(home) || env.CODEX_HOME && !isAbsolute5(env.CODEX_HOME)) {
23550
+ throw new Error("Use an absolute HOME and CODEX_HOME path for this server's Codex provider before signing out.");
23551
+ }
23552
+ const homeKey = canonicalPath2(resolve8(env.CODEX_HOME || join19(home, ".codex")));
23553
+ if (authenticatingHomes2.has(homeKey)) {
23554
+ throw new Error("A ChatGPT sign-in is running for this server account. Finish or cancel it before signing out.");
23555
+ }
23556
+ authenticatingHomes2.add(homeKey);
23557
+ try {
23558
+ const before = await this.exec(["login", "status"], env, home);
23559
+ if (before.code === 1 && /^not logged in\b/im.test(before.output)) return;
23560
+ if (before.code !== 0 || !/^logged in.*chatgpt\b/im.test(before.output)) {
23561
+ throw new Error("Codex did not confirm a ChatGPT sign-in. Other authentication methods will not be removed; ask the server administrator to check the account.");
23562
+ }
23563
+ const logout = await this.exec(["logout"], env, home);
23564
+ if (logout.code !== 0) throw new Error("Codex could not remove the sign-in on this server. Check the server's Codex installation and try again.");
23565
+ const status = await this.exec(["login", "status"], env, home);
23566
+ if (status.code !== 1 || !/^not logged in\b/im.test(status.output)) {
23567
+ throw new Error("Codex still reports a sign-in on this server. Update Codex and check its login status before trying again.");
23568
+ }
23569
+ } finally {
23570
+ const child = this.command;
23571
+ if (child && child.exitCode === null && child.signalCode === null) {
23572
+ child.once("close", () => authenticatingHomes2.delete(homeKey));
23573
+ } else authenticatingHomes2.delete(homeKey);
23574
+ }
23575
+ }
23576
+ /** One bounded, non-interactive Codex command. Its output stays here;
23577
+ * callers see an exit code and a status-line match, never the text. */
23578
+ exec(args, env, cwd) {
23579
+ return new Promise((resolveExec, rejectExec) => {
23580
+ if (this.disposed) {
23581
+ rejectExec(new Error("This provider was removed. Refresh Settings before signing out."));
23582
+ return;
23583
+ }
23584
+ let child;
23585
+ try {
23586
+ child = spawnCli(this.options.cli, args, { env: { ...env, NO_COLOR: "1" }, cwd, stdio: ["pipe", "pipe", "pipe"] });
23587
+ } catch {
23588
+ rejectExec(new Error("Codex could not start on this server. Install or update the configured Codex CLI, then try again."));
23589
+ return;
23590
+ }
23591
+ this.command = child;
23592
+ child.stdin.end();
23593
+ let output = "";
23594
+ const receive = (chunk) => {
23595
+ if (output.length < MAX_OUTPUT2) output += chunk.toString("utf8").slice(0, MAX_OUTPUT2 - output.length);
23596
+ };
23597
+ child.stdout.on("data", receive);
23598
+ child.stderr.on("data", receive);
23599
+ const timer = setTimeout(() => {
23600
+ void this.stopChild(child).catch(() => false).then((stopped) => {
23601
+ if (!stopped) rejectExec(new Error("Codex could not be stopped on this server. Ask the server administrator to stop the account command before trying again."));
23602
+ });
23603
+ }, this.options.startupTimeoutMs ?? 3e4);
23604
+ timer.unref();
23605
+ child.once("error", (error51) => {
23606
+ clearTimeout(timer);
23607
+ rejectExec(new Error(error51.code === "ENOENT" ? "Codex is not installed on this server. Run npm install -g @openai/codex@latest on the server, then try again." : "Codex could not start on this server. Check the configured CLI path and its executable permissions."));
23608
+ });
23609
+ child.once("close", (code) => {
23610
+ clearTimeout(timer);
23611
+ if (this.command === child) this.command = null;
23612
+ resolveExec({ code, output: stripVTControlCharacters4(output) });
23613
+ });
23614
+ });
23615
+ }
23487
23616
  run(flow, args, env, cwd, done, parsePrompt = false) {
23488
23617
  if (flow.status.phase !== "waiting") return;
23489
23618
  let child;
@@ -23538,7 +23667,7 @@ var init_codex_device_auth = __esm({
23538
23667
  if (phase === "succeeded") flow.resolve({ ...flow.status, phase });
23539
23668
  else flow.reject(new Error(message2 ?? "ChatGPT sign-in did not finish."));
23540
23669
  }
23541
- flow.stopping = this.stopChild(flow).catch(() => false).then((stopped) => {
23670
+ flow.stopping = this.stopChild(flow.child).catch(() => false).then((stopped) => {
23542
23671
  const release = () => authenticatingHomes2.delete(flow.homeKey);
23543
23672
  if (stopped) {
23544
23673
  release();
@@ -23553,8 +23682,7 @@ var init_codex_device_auth = __esm({
23553
23682
  if (phase === "succeeded") void this.options.onAuthenticated?.().catch(() => {
23554
23683
  });
23555
23684
  }
23556
- async stopChild(flow) {
23557
- const child = flow.child;
23685
+ async stopChild(child) {
23558
23686
  if (!child || await killCliTree(child, this.options.terminateTimeoutMs ?? 1500)) return true;
23559
23687
  if (process.platform !== "win32" && child.pid) {
23560
23688
  try {
@@ -23578,9 +23706,122 @@ var init_codex_device_auth = __esm({
23578
23706
  }
23579
23707
  });
23580
23708
 
23709
+ // server/drivers/codex-identity.ts
23710
+ import { homedir as homedir12 } from "node:os";
23711
+ import { isAbsolute as isAbsolute6, join as join20, resolve as resolve9 } from "node:path";
23712
+ function codexHome2(env) {
23713
+ if (env.CODEX_HOME) return isAbsolute6(env.CODEX_HOME) ? resolve9(env.CODEX_HOME) : null;
23714
+ const home = env.HOME || env.USERPROFILE || homedir12();
23715
+ return isAbsolute6(home) ? join20(home, ".codex") : null;
23716
+ }
23717
+ async function codexAccountEmail(cli, env, timeoutMs = 3e3) {
23718
+ const cwd = env.HOME || env.USERPROFILE || homedir12();
23719
+ if (!isAbsolute6(cwd) || !codexHome2(env)) return null;
23720
+ return new Promise((resolveEmail) => {
23721
+ let child;
23722
+ try {
23723
+ child = spawnCli(cli, ["app-server"], { cwd, env, stdio: ["pipe", "pipe", "pipe"] });
23724
+ } catch {
23725
+ resolveEmail(null);
23726
+ return;
23727
+ }
23728
+ let finishing = false;
23729
+ let buffer = "";
23730
+ let outputBytes = 0;
23731
+ let initialized = false;
23732
+ const finish = async (email3) => {
23733
+ if (finishing) return;
23734
+ finishing = true;
23735
+ clearTimeout(timer);
23736
+ let stopped = await killCliTree(child, 1e3);
23737
+ if (!stopped && child.pid) {
23738
+ try {
23739
+ if (process.platform === "win32") child.kill("SIGKILL");
23740
+ else process.kill(-child.pid, "SIGKILL");
23741
+ } catch {
23742
+ try {
23743
+ child.kill("SIGKILL");
23744
+ } catch {
23745
+ }
23746
+ }
23747
+ stopped = await killCliTree(child, 1e3);
23748
+ }
23749
+ resolveEmail(stopped ? email3 : null);
23750
+ };
23751
+ const timer = setTimeout(() => {
23752
+ void finish(null);
23753
+ }, timeoutMs);
23754
+ timer.unref();
23755
+ const send = (message2) => {
23756
+ try {
23757
+ child.stdin.write(`${JSON.stringify(message2)}
23758
+ `);
23759
+ } catch {
23760
+ void finish(null);
23761
+ }
23762
+ };
23763
+ child.stdin.on("error", () => {
23764
+ void finish(null);
23765
+ });
23766
+ child.stderr.resume();
23767
+ child.on("error", () => {
23768
+ void finish(null);
23769
+ });
23770
+ child.on("close", () => {
23771
+ void finish(null);
23772
+ });
23773
+ child.stdout.setEncoding("utf8");
23774
+ child.stdout.on("data", (chunk) => {
23775
+ if (finishing) return;
23776
+ outputBytes += Buffer.byteLength(chunk);
23777
+ if (outputBytes > MAX_OUTPUT3) {
23778
+ void finish(null);
23779
+ return;
23780
+ }
23781
+ buffer += chunk;
23782
+ let newline;
23783
+ while (!finishing && (newline = buffer.indexOf("\n")) !== -1) {
23784
+ const line = buffer.slice(0, newline);
23785
+ buffer = buffer.slice(newline + 1);
23786
+ if (!line.trim()) continue;
23787
+ let message2;
23788
+ try {
23789
+ message2 = JSON.parse(line);
23790
+ } catch {
23791
+ void finish(null);
23792
+ return;
23793
+ }
23794
+ if (!initialized && message2?.id === 1) {
23795
+ if (message2.error || !message2.result) {
23796
+ void finish(null);
23797
+ return;
23798
+ }
23799
+ initialized = true;
23800
+ send({ method: "initialized", params: {} });
23801
+ send({ id: 2, method: "account/read", params: { refreshToken: false } });
23802
+ } else if (initialized && message2?.id === 2) {
23803
+ const account = message2.error ? null : message2.result?.account;
23804
+ const email3 = account?.type === "chatgpt" ? account.email : null;
23805
+ void finish(typeof email3 === "string" && email3.length <= 254 && EMAIL.test(email3) && !/[\p{Cc}\p{Cf}]/u.test(email3) ? email3 : null);
23806
+ }
23807
+ }
23808
+ });
23809
+ send({ id: 1, method: "initialize", params: { clientInfo: { name: "openmausbot", version: "1" } } });
23810
+ });
23811
+ }
23812
+ var MAX_OUTPUT3, EMAIL;
23813
+ var init_codex_identity = __esm({
23814
+ "server/drivers/codex-identity.ts"() {
23815
+ "use strict";
23816
+ init_procs();
23817
+ MAX_OUTPUT3 = 16384;
23818
+ EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
23819
+ }
23820
+ });
23821
+
23581
23822
  // server/drivers/codex.ts
23582
23823
  import { existsSync as existsSync7 } from "node:fs";
23583
- import { homedir as homedir10 } from "node:os";
23824
+ import { homedir as homedir13 } from "node:os";
23584
23825
  function codexPredatesAstra(version2) {
23585
23826
  const value = version2.trim();
23586
23827
  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);
@@ -23850,6 +24091,7 @@ var init_codex = __esm({
23850
24091
  init_native();
23851
24092
  init_codex_instructions();
23852
24093
  init_codex_device_auth();
24094
+ init_codex_identity();
23853
24095
  init_codex_catalog();
23854
24096
  DRIVER_KIND3 = "codex";
23855
24097
  ASTRA_MODEL_ID = "gpt-6-astra";
@@ -23983,7 +24225,7 @@ var init_codex = __esm({
23983
24225
  );
23984
24226
  }
23985
24227
  const child = spawnCli(config2.cli, appServerArgs, {
23986
- cwd: turn.cwd ?? homedir10(),
24228
+ cwd: turn.cwd ?? homedir13(),
23987
24229
  env,
23988
24230
  stdio: ["pipe", "pipe", "pipe"]
23989
24231
  });
@@ -24011,7 +24253,7 @@ var init_codex = __esm({
24011
24253
  msg: codexNativeLogMessage(obj)
24012
24254
  });
24013
24255
  };
24014
- const request = (method, params, timeoutMs = 6e4) => new Promise((resolve12, reject) => {
24256
+ const request = (method, params, timeoutMs = 6e4) => new Promise((resolve13, reject) => {
24015
24257
  const id = nextId++;
24016
24258
  if (method === "config/read") sensitiveResponseIds.add(id);
24017
24259
  const timer = setTimeout(() => {
@@ -24021,7 +24263,7 @@ var init_codex = __esm({
24021
24263
  rpcPending.set(id, {
24022
24264
  resolve: (v) => {
24023
24265
  clearTimeout(timer);
24024
- resolve12(v);
24266
+ resolve13(v);
24025
24267
  },
24026
24268
  reject: (e) => {
24027
24269
  clearTimeout(timer);
@@ -24289,7 +24531,7 @@ var init_codex = __esm({
24289
24531
  let effectiveConfig;
24290
24532
  try {
24291
24533
  const configured = await request("config/read", {
24292
- cwd: turn.cwd ?? homedir10(),
24534
+ cwd: turn.cwd ?? homedir13(),
24293
24535
  includeLayers: false
24294
24536
  });
24295
24537
  effectiveConfig = configured?.config;
@@ -24333,7 +24575,7 @@ var init_codex = __esm({
24333
24575
  const selection = decodeCodexSelection(turn.model);
24334
24576
  const startThread = () => request("thread/start", {
24335
24577
  developerInstructions,
24336
- cwd: turn.cwd ?? homedir10(),
24578
+ cwd: turn.cwd ?? homedir13(),
24337
24579
  model: selection.model,
24338
24580
  ...selection.modelProvider ? { modelProvider: selection.modelProvider } : {},
24339
24581
  ...approvalParams.thread,
@@ -24400,8 +24642,8 @@ var init_codex = __esm({
24400
24642
  void settle(false, "shutdown_timeout");
24401
24643
  return;
24402
24644
  }
24403
- await new Promise((resolve12) => {
24404
- const timer = setTimeout(resolve12, Math.max(1, Math.round(delayMs * retryScale)));
24645
+ await new Promise((resolve13) => {
24646
+ const timer = setTimeout(resolve13, Math.max(1, Math.round(delayMs * retryScale)));
24405
24647
  timer.unref?.();
24406
24648
  });
24407
24649
  if (!stopRequested) {
@@ -24429,28 +24671,30 @@ var init_codex = __esm({
24429
24671
  };
24430
24672
  const snapshot = async () => {
24431
24673
  const env = childEnv();
24432
- const version2 = await new Promise((resolve12) => {
24674
+ const version2 = await new Promise((resolve13) => {
24433
24675
  execCli(
24434
24676
  config2.cli,
24435
24677
  ["--version"],
24436
24678
  { timeout: 8e3, env },
24437
- (err, stdout2) => resolve12(err ? null : stdout2.trim())
24679
+ (err, stdout2) => resolve13(err ? null : stdout2.trim())
24438
24680
  );
24439
24681
  });
24440
24682
  if (!version2) return { state: "unavailable", reason: `\`${config2.cli}\` CLI not found` };
24441
- const authenticated = await new Promise((resolve12) => {
24683
+ const authenticated = await new Promise((resolve13) => {
24442
24684
  execCli(
24443
24685
  config2.cli,
24444
24686
  ["login", "status"],
24445
24687
  { timeout: 8e3, env },
24446
- (err, stdout2, stderr) => resolve12(!err && /^logged in\b/im.test(`${stdout2}
24688
+ (err, stdout2, stderr) => resolve13(!err && /^logged in\b/im.test(`${stdout2}
24447
24689
  ${stderr ?? ""}`))
24448
24690
  );
24449
24691
  });
24692
+ const email3 = authenticated ? await codexAccountEmail(config2.cli, env) : null;
24450
24693
  return {
24451
24694
  state: "available",
24452
24695
  version: version2,
24453
24696
  authenticated,
24697
+ ...email3 ? { account: { email: email3 } } : {},
24454
24698
  update: codexAstraUpdate(version2, models, config2.cli),
24455
24699
  billing: "subscription"
24456
24700
  };
@@ -24467,6 +24711,7 @@ ${stderr ?? ""}`))
24467
24711
  startAuthentication: () => authentication.start(),
24468
24712
  getAuthentication: (flowId) => authentication.get(flowId),
24469
24713
  cancelAuthentication: () => authentication.cancel(),
24714
+ signOut: () => authentication.signOut(),
24470
24715
  snapshot,
24471
24716
  adapter: {
24472
24717
  provider: DRIVER_KIND3,
@@ -24790,12 +25035,12 @@ var init_grok = __esm({
24790
25035
  });
24791
25036
 
24792
25037
  // server/drivers/acp/grok.ts
24793
- import { existsSync as existsSync8, readFileSync as readFileSync9, writeFileSync as writeFileSync5 } from "node:fs";
24794
- import { homedir as homedir11 } from "node:os";
24795
- import { join as join19 } from "node:path";
25038
+ import { existsSync as existsSync8, readFileSync as readFileSync9, writeFileSync as writeFileSync6 } from "node:fs";
25039
+ import { homedir as homedir14 } from "node:os";
25040
+ import { join as join21 } from "node:path";
24796
25041
  function grokHome(env) {
24797
25042
  if (env.GROK_HOME) return env.GROK_HOME;
24798
- return join19(env.HOME || env.USERPROFILE || homedir11(), ".grok");
25043
+ return join21(env.HOME || env.USERPROFILE || homedir14(), ".grok");
24799
25044
  }
24800
25045
  function unquote2(raw) {
24801
25046
  const value = raw.trim();
@@ -24805,7 +25050,7 @@ function unquote2(raw) {
24805
25050
  return value;
24806
25051
  }
24807
25052
  function readGrokModelCatalog(env = process.env) {
24808
- const path3 = join19(grokHome(env), "config.toml");
25053
+ const path3 = join21(grokHome(env), "config.toml");
24809
25054
  let text2 = "";
24810
25055
  try {
24811
25056
  text2 = readFileSync9(path3, "utf8");
@@ -24878,7 +25123,7 @@ function ensureGrokInjectSlug(modelId, env = process.env) {
24878
25123
  if (!inject) return modelId;
24879
25124
  const host = localHost(inject.host);
24880
25125
  if (!host) return modelId;
24881
- const path3 = join19(grokHome(env), "config.toml");
25126
+ const path3 = join21(grokHome(env), "config.toml");
24882
25127
  let text2 = "";
24883
25128
  try {
24884
25129
  text2 = readFileSync9(path3, "utf8");
@@ -24932,7 +25177,7 @@ function ensureGrokInjectSlug(modelId, env = process.env) {
24932
25177
  const next = text2 && !text2.endsWith("\n") ? `${text2}
24933
25178
 
24934
25179
  ${block2}` : `${text2}${text2 ? "\n" : ""}${block2}`;
24935
- writeFileSync5(path3, next);
25180
+ writeFileSync6(path3, next);
24936
25181
  return slug;
24937
25182
  }
24938
25183
  var STATIC_GROK_MODELS, SLUG, support2, GrokAgentDriver;
@@ -25013,7 +25258,7 @@ var init_grok2 = __esm({
25013
25258
  // an unauthenticated CLI is a user action, not something to paper over.
25014
25259
  pickAuthMethod: (methods) => methods.some((m) => m.id === "cached_token") ? "cached_token" : null,
25015
25260
  authFailure: "fail",
25016
- isAuthenticated: () => existsSync8(join19(homedir11(), ".grok", "auth.json")),
25261
+ isAuthenticated: () => existsSync8(join21(homedir14(), ".grok", "auth.json")),
25017
25262
  // `--append-system-prompt`/`--rules` are accepted by the CLI but do NOT
25018
25263
  // reach the agent-stdio system prompt (verified against 1.0.0), so the
25019
25264
  // persona is prepended codex-style.
@@ -25027,11 +25272,11 @@ ${turn.text}` : turn.text
25027
25272
 
25028
25273
  // server/drivers/acp/gemini.ts
25029
25274
  import { existsSync as existsSync9, readFileSync as readFileSync10 } from "node:fs";
25030
- import { homedir as homedir12 } from "node:os";
25031
- import { join as join20 } from "node:path";
25275
+ import { homedir as homedir15 } from "node:os";
25276
+ import { join as join22 } from "node:path";
25032
25277
  function liveOauthCredential() {
25033
25278
  try {
25034
- const creds = JSON.parse(readFileSync10(join20(homedir12(), ".gemini", "oauth_creds.json"), "utf8"));
25279
+ const creds = JSON.parse(readFileSync10(join22(homedir15(), ".gemini", "oauth_creds.json"), "utf8"));
25035
25280
  if (typeof creds.access_token !== "string" || creds.access_token.trim().length === 0) return false;
25036
25281
  const hasRefresh = typeof creds.refresh_token === "string" && creds.refresh_token.trim().length > 0;
25037
25282
  if (creds.expiry_date !== void 0) {
@@ -25044,7 +25289,7 @@ function liveOauthCredential() {
25044
25289
  }
25045
25290
  }
25046
25291
  function geminiIsAuthenticated(env) {
25047
- return nonBlank(env.GEMINI_API_KEY) || nonBlank(env.GOOGLE_API_KEY) || existsSync9(join20(homedir12(), ".gemini", "oauth_creds.json")) && liveOauthCredential();
25292
+ return nonBlank(env.GEMINI_API_KEY) || nonBlank(env.GOOGLE_API_KEY) || existsSync9(join22(homedir15(), ".gemini", "oauth_creds.json")) && liveOauthCredential();
25048
25293
  }
25049
25294
  var AUTH_PREFERENCE, nonBlank, support3, GeminiAgentDriver;
25050
25295
  var init_gemini = __esm({
@@ -25086,17 +25331,17 @@ ${turn.text}` : turn.text
25086
25331
  });
25087
25332
 
25088
25333
  // server/drivers/acp/kimi.ts
25089
- import { existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync11, writeFileSync as writeFileSync6 } from "node:fs";
25090
- import { homedir as homedir13 } from "node:os";
25091
- import { join as join21 } from "node:path";
25334
+ import { existsSync as existsSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync11, writeFileSync as writeFileSync7 } from "node:fs";
25335
+ import { homedir as homedir16 } from "node:os";
25336
+ import { join as join23 } from "node:path";
25092
25337
  function isLocalInjectAlias(slug) {
25093
25338
  return LOCAL_PROVIDER_PREFIXES.some((prefix) => slug.startsWith(prefix));
25094
25339
  }
25095
25340
  function kimiDataRoot(env) {
25096
- return env.KIMI_CODE_HOME || join21(env.HOME || env.USERPROFILE || homedir13(), ".kimi-code");
25341
+ return env.KIMI_CODE_HOME || join23(env.HOME || env.USERPROFILE || homedir16(), ".kimi-code");
25097
25342
  }
25098
25343
  function credentialsPath(env) {
25099
- return join21(kimiDataRoot(env), "credentials", "kimi-code.json");
25344
+ return join23(kimiDataRoot(env), "credentials", "kimi-code.json");
25100
25345
  }
25101
25346
  function quoteToml2(value) {
25102
25347
  return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
@@ -25383,8 +25628,8 @@ function ensureKimiInjectAlias(modelId, env = process.env) {
25383
25628
  if (!host) return modelId;
25384
25629
  const alias = `${inject.host}/${inject.model.replace(/\//g, "-")}`;
25385
25630
  const dataRoot = kimiDataRoot(env);
25386
- mkdirSync6(dataRoot, { recursive: true });
25387
- const path3 = join21(dataRoot, "config.toml");
25631
+ mkdirSync7(dataRoot, { recursive: true });
25632
+ const path3 = join23(dataRoot, "config.toml");
25388
25633
  let text2 = "";
25389
25634
  try {
25390
25635
  text2 = readFileSync11(path3, "utf8");
@@ -25425,9 +25670,9 @@ function ensureKimiInjectAlias(modelId, env = process.env) {
25425
25670
 
25426
25671
  ` : text2 ? `${text2}
25427
25672
  ` : "";
25428
- writeFileSync6(path3, `${prefix}${blocks.join("\n")}`);
25673
+ writeFileSync7(path3, `${prefix}${blocks.join("\n")}`);
25429
25674
  } else if (text2 !== original) {
25430
- writeFileSync6(path3, text2);
25675
+ writeFileSync7(path3, text2);
25431
25676
  }
25432
25677
  return alias;
25433
25678
  }
@@ -25449,7 +25694,7 @@ function readKimiModelCatalog(env) {
25449
25694
  const dataRoot = kimiDataRoot(env);
25450
25695
  let text2 = "";
25451
25696
  try {
25452
- text2 = readFileSync11(join21(dataRoot, "config.toml"), "utf8");
25697
+ text2 = readFileSync11(join23(dataRoot, "config.toml"), "utf8");
25453
25698
  } catch {
25454
25699
  return STATIC_KIMI_MODELS;
25455
25700
  }
@@ -25568,19 +25813,19 @@ ${turn.text}` : turn.text
25568
25813
  });
25569
25814
 
25570
25815
  // server/drivers/acp/droid.ts
25571
- import { existsSync as existsSync11, mkdirSync as mkdirSync7, readFileSync as readFileSync12, writeFileSync as writeFileSync7 } from "node:fs";
25572
- import { homedir as homedir14 } from "node:os";
25573
- import { join as join22 } from "node:path";
25816
+ import { existsSync as existsSync11, mkdirSync as mkdirSync8, readFileSync as readFileSync12, writeFileSync as writeFileSync8 } from "node:fs";
25817
+ import { homedir as homedir17 } from "node:os";
25818
+ import { join as join24 } from "node:path";
25574
25819
  function authFilePaths(env) {
25575
- const home = env.FACTORY_HOME_OVERRIDE || env.HOME || homedir14();
25576
- return AUTH_FILES.map((name) => join22(home, ".factory", name));
25820
+ const home = env.FACTORY_HOME_OVERRIDE || env.HOME || homedir17();
25821
+ return AUTH_FILES.map((name) => join24(home, ".factory", name));
25577
25822
  }
25578
25823
  function droidInjectId(host, model) {
25579
25824
  const safe = `${host}-${model}`.replace(/[^a-zA-Z0-9._+-]+/g, "-").replace(/-+/g, "-");
25580
25825
  return `${INJECT_ID_PREFIX}${safe}`;
25581
25826
  }
25582
25827
  function factoryHome(env) {
25583
- return env.FACTORY_HOME_OVERRIDE || env.HOME || env.USERPROFILE || homedir14();
25828
+ return env.FACTORY_HOME_OVERRIDE || env.HOME || env.USERPROFILE || homedir17();
25584
25829
  }
25585
25830
  function ensureDroidInjectModel(modelId, env = process.env) {
25586
25831
  const inject = decodeInjectId(modelId);
@@ -25588,9 +25833,9 @@ function ensureDroidInjectModel(modelId, env = process.env) {
25588
25833
  const host = localHost(inject.host);
25589
25834
  if (!host) return modelId;
25590
25835
  const id = droidInjectId(inject.host, inject.model);
25591
- const dir = join22(factoryHome(env), ".factory");
25592
- mkdirSync7(dir, { recursive: true });
25593
- const path3 = join22(dir, "settings.json");
25836
+ const dir = join24(factoryHome(env), ".factory");
25837
+ mkdirSync8(dir, { recursive: true });
25838
+ const path3 = join24(dir, "settings.json");
25594
25839
  let settings2 = {};
25595
25840
  try {
25596
25841
  settings2 = JSON.parse(readFileSync12(path3, "utf8"));
@@ -25605,7 +25850,7 @@ function ensureDroidInjectModel(modelId, env = process.env) {
25605
25850
  if (!match.id) {
25606
25851
  match.id = id;
25607
25852
  settings2.customModels = custom2;
25608
- writeFileSync7(path3, `${JSON.stringify(settings2, null, 2)}
25853
+ writeFileSync8(path3, `${JSON.stringify(settings2, null, 2)}
25609
25854
  `);
25610
25855
  }
25611
25856
  return match.id;
@@ -25619,7 +25864,7 @@ function ensureDroidInjectModel(modelId, env = process.env) {
25619
25864
  provider: "generic-chat-completion-api"
25620
25865
  });
25621
25866
  settings2.customModels = custom2;
25622
- writeFileSync7(path3, `${JSON.stringify(settings2, null, 2)}
25867
+ writeFileSync8(path3, `${JSON.stringify(settings2, null, 2)}
25623
25868
  `);
25624
25869
  return id;
25625
25870
  }
@@ -25630,7 +25875,7 @@ function applyDroidLocalAuthEnv(env, modelId) {
25630
25875
  env.FACTORY_API_KEY = "openmausbot-local";
25631
25876
  }
25632
25877
  function readSettings(env) {
25633
- return JSON.parse(readFileSync12(join22(factoryHome(env), ".factory", "settings.json"), "utf8"));
25878
+ return JSON.parse(readFileSync12(join24(factoryHome(env), ".factory", "settings.json"), "utf8"));
25634
25879
  }
25635
25880
  async function resolveModels(env) {
25636
25881
  let catalog = MODELS3;
@@ -25899,10 +26144,10 @@ function decodeCursorAuthText(text2) {
25899
26144
  return null;
25900
26145
  }
25901
26146
  function execText(run3, cli, args, env) {
25902
- return new Promise((resolve12) => {
26147
+ return new Promise((resolve13) => {
25903
26148
  run3(cli, args, { timeout: EXEC_TIMEOUT_MS, env }, (err, stdout2) => {
25904
- if (err) return resolve12(null);
25905
- resolve12(String(stdout2 ?? ""));
26149
+ if (err) return resolve13(null);
26150
+ resolve13(String(stdout2 ?? ""));
25906
26151
  });
25907
26152
  });
25908
26153
  }
@@ -26020,9 +26265,9 @@ ${turn.text}` : turn.text
26020
26265
  });
26021
26266
 
26022
26267
  // server/drivers/acp/opencode-go.ts
26023
- import { existsSync as existsSync12, mkdirSync as mkdirSync8, readFileSync as readFileSync13, writeFileSync as writeFileSync8 } from "node:fs";
26024
- import { homedir as homedir15 } from "node:os";
26025
- import { join as join23 } from "node:path";
26268
+ import { existsSync as existsSync12, mkdirSync as mkdirSync9, readFileSync as readFileSync13, writeFileSync as writeFileSync9 } from "node:fs";
26269
+ import { homedir as homedir18 } from "node:os";
26270
+ import { join as join25 } from "node:path";
26026
26271
  function labelForModel(id) {
26027
26272
  return id.split(/[-_.]+/g).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
26028
26273
  }
@@ -26096,7 +26341,7 @@ function parseOpenCodeModelsOutput(stdout2) {
26096
26341
  return { default: (preferred ?? options[0]).id, options };
26097
26342
  }
26098
26343
  function runOpenCodeModels(cli, environment, verbose) {
26099
- return new Promise((resolve12, reject) => {
26344
+ return new Promise((resolve13, reject) => {
26100
26345
  execCli(
26101
26346
  cli,
26102
26347
  ["models", ...verbose ? ["--verbose"] : []],
@@ -26106,7 +26351,7 @@ function runOpenCodeModels(cli, environment, verbose) {
26106
26351
  reject(new Error(stderr?.trim() || error51.message, { cause: error51 }));
26107
26352
  return;
26108
26353
  }
26109
- resolve12(stdout2);
26354
+ resolve13(stdout2);
26110
26355
  }
26111
26356
  );
26112
26357
  });
@@ -26122,8 +26367,8 @@ async function discoverOpenCodeModels(environment, cli = "opencode") {
26122
26367
  }
26123
26368
  }
26124
26369
  function opencodeConfigDir(env) {
26125
- const home = env.HOME || env.USERPROFILE || homedir15();
26126
- return join23(env.XDG_CONFIG_HOME || join23(home, ".config"), "opencode");
26370
+ const home = env.HOME || env.USERPROFILE || homedir18();
26371
+ return join25(env.XDG_CONFIG_HOME || join25(home, ".config"), "opencode");
26127
26372
  }
26128
26373
  function ensureOpenCodeInjectModel(modelId, env = process.env) {
26129
26374
  const inject = decodeInjectId(modelId);
@@ -26132,8 +26377,8 @@ function ensureOpenCodeInjectModel(modelId, env = process.env) {
26132
26377
  if (!host) return modelId;
26133
26378
  const native = `${inject.host}/${inject.model}`;
26134
26379
  const dir = opencodeConfigDir(env);
26135
- mkdirSync8(dir, { recursive: true });
26136
- const path3 = join23(dir, "opencode.json");
26380
+ mkdirSync9(dir, { recursive: true });
26381
+ const path3 = join25(dir, "opencode.json");
26137
26382
  let config2 = { $schema: "https://opencode.ai/config.json" };
26138
26383
  if (existsSync12(path3)) {
26139
26384
  try {
@@ -26164,17 +26409,17 @@ function ensureOpenCodeInjectModel(modelId, env = process.env) {
26164
26409
  models
26165
26410
  };
26166
26411
  config2.provider = providers;
26167
- writeFileSync8(path3, `${JSON.stringify(config2, null, 2)}
26412
+ writeFileSync9(path3, `${JSON.stringify(config2, null, 2)}
26168
26413
  `);
26169
26414
  return native;
26170
26415
  }
26171
26416
  function storedAuthPaths(env) {
26172
- const home = env.HOME || env.USERPROFILE || homedir15();
26417
+ const home = env.HOME || env.USERPROFILE || homedir18();
26173
26418
  const roots = [
26174
- env.XDG_DATA_HOME || join23(home, ".local", "share"),
26175
- process.platform === "darwin" ? join23(home, "Library", "Application Support") : process.platform === "win32" ? env.LOCALAPPDATA || join23(home, "AppData", "Local") : ""
26419
+ env.XDG_DATA_HOME || join25(home, ".local", "share"),
26420
+ process.platform === "darwin" ? join25(home, "Library", "Application Support") : process.platform === "win32" ? env.LOCALAPPDATA || join25(home, "AppData", "Local") : ""
26176
26421
  ].filter(Boolean);
26177
- return [...new Set(roots)].map((root) => join23(root, "opencode", "auth.json"));
26422
+ return [...new Set(roots)].map((root) => join25(root, "opencode", "auth.json"));
26178
26423
  }
26179
26424
  function usableAuthEntry(parsed) {
26180
26425
  return Object.values(parsed).some((auth) => {
@@ -26304,11 +26549,97 @@ ${turn.text}` : turn.text
26304
26549
  });
26305
26550
 
26306
26551
  // server/drivers/acp/qwen.ts
26307
- import { chmodSync as chmodSync4, existsSync as existsSync13, mkdirSync as mkdirSync9, readFileSync as readFileSync14, writeFileSync as writeFileSync9 } from "node:fs";
26308
- import { homedir as homedir16 } from "node:os";
26309
- import { join as join24 } from "node:path";
26552
+ import { chmodSync as chmodSync4, existsSync as existsSync13, mkdirSync as mkdirSync10, readFileSync as readFileSync14, writeFileSync as writeFileSync10 } from "node:fs";
26553
+ import { createHash as createHash7 } from "node:crypto";
26554
+ import { homedir as homedir19 } from "node:os";
26555
+ import { join as join26 } from "node:path";
26310
26556
  function qwenHome(env) {
26311
- return join24(env.HOME || env.USERPROFILE || homedir16(), ".qwen");
26557
+ const home = process.platform === "win32" ? env.USERPROFILE || env.HOME || homedir19() : env.HOME || env.USERPROFILE || homedir19();
26558
+ return join26(home, ".qwen");
26559
+ }
26560
+ function isRecord(value) {
26561
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
26562
+ }
26563
+ function qwenModelLabel(id, name) {
26564
+ if (!name || name.toLocaleLowerCase().endsWith(id.toLocaleLowerCase())) return id;
26565
+ return `${id} \u2014 ${name}`;
26566
+ }
26567
+ function readQwenRoutes(env) {
26568
+ let settings2;
26569
+ try {
26570
+ settings2 = JSON.parse(readFileSync14(join26(qwenHome(env), "settings.json"), "utf8"));
26571
+ } catch {
26572
+ return [];
26573
+ }
26574
+ if (!isRecord(settings2) || !isRecord(settings2.modelProviders)) return [];
26575
+ const routes = [];
26576
+ const seen = /* @__PURE__ */ new Set();
26577
+ const mappings = isRecord(settings2.providerProtocol) ? settings2.providerProtocol : {};
26578
+ for (const [provider, rows] of Object.entries(settings2.modelProviders)) {
26579
+ const protocol = Object.hasOwn(mappings, provider) ? mappings[provider] : provider;
26580
+ if (typeof protocol !== "string" || !PROTOCOLS.has(protocol) || !Array.isArray(rows)) continue;
26581
+ for (const row of rows) {
26582
+ if (!isRecord(row) || typeof row.id !== "string") continue;
26583
+ const model = row.id;
26584
+ if (!model.trim()) continue;
26585
+ if (row.baseUrl !== void 0 && typeof row.baseUrl !== "string") continue;
26586
+ const baseUrl = typeof row.baseUrl === "string" && row.baseUrl ? row.baseUrl : void 0;
26587
+ if (baseUrl) {
26588
+ try {
26589
+ new URL(baseUrl);
26590
+ } catch {
26591
+ throw new Error("A Qwen model has an invalid endpoint. Fix its baseUrl in Qwen settings and refresh models.");
26592
+ }
26593
+ }
26594
+ const identity = JSON.stringify([protocol, model, baseUrl ?? null]);
26595
+ if (seen.has(identity)) continue;
26596
+ seen.add(identity);
26597
+ if (row.imageOnly === true || row.voiceOnly === true || row.fastOnly === true) continue;
26598
+ routes.push({
26599
+ id: `${model}(${protocol})`,
26600
+ model,
26601
+ provider,
26602
+ protocol,
26603
+ baseUrl,
26604
+ label: typeof row.name === "string" && row.name ? row.name : model,
26605
+ envKey: typeof row.envKey === "string" ? row.envKey : void 0
26606
+ });
26607
+ }
26608
+ }
26609
+ const counts = /* @__PURE__ */ new Map();
26610
+ for (const route of routes) counts.set(route.id, (counts.get(route.id) ?? 0) + 1);
26611
+ for (const route of routes) {
26612
+ if (counts.get(route.id) === 1) continue;
26613
+ const endpoint = route.baseUrl ?? (route.protocol === "openai" ? "https://api.openai.com/v1" : "");
26614
+ let publicEndpoint = null;
26615
+ if (endpoint) {
26616
+ try {
26617
+ const url2 = new URL(endpoint);
26618
+ url2.username = "";
26619
+ url2.password = "";
26620
+ url2.search = "";
26621
+ url2.hash = "";
26622
+ publicEndpoint = url2.href;
26623
+ } catch {
26624
+ throw new Error("A Qwen model has an invalid endpoint. Fix its baseUrl in Qwen settings and refresh models.");
26625
+ }
26626
+ }
26627
+ const identity = [route.id, route.label, route.envKey ?? null, route.baseUrl === void 0, publicEndpoint];
26628
+ route.id = `qwen-route:v1:${createHash7("sha256").update(JSON.stringify(identity)).digest("base64url").slice(0, 16)}`;
26629
+ }
26630
+ if (new Set(routes.map((route) => route.id)).size !== routes.length) {
26631
+ throw new Error("Qwen has indistinguishable model routes. Give them distinct names, envKey values, or public endpoints in Qwen settings.");
26632
+ }
26633
+ return routes;
26634
+ }
26635
+ function readQwenModelCatalog(env = process.env) {
26636
+ const options = readQwenRoutes(env).map(({ id, model, label, provider }) => ({
26637
+ id,
26638
+ label: qwenModelLabel(model, label),
26639
+ custom: true,
26640
+ provider
26641
+ }));
26642
+ return { default: options[0]?.id ?? "", options };
26312
26643
  }
26313
26644
  function envKeyFor(hostId) {
26314
26645
  return `OPENMAUSBOT_QWEN_${hostId.toUpperCase().replace(/[^A-Z0-9]+/g, "_")}_API_KEY`;
@@ -26319,8 +26650,8 @@ function ensureQwenInjectModel(modelId, env = process.env) {
26319
26650
  const host = localHost(inject.host);
26320
26651
  if (!host) return modelId;
26321
26652
  const dir = qwenHome(env);
26322
- mkdirSync9(dir, { recursive: true });
26323
- const path3 = join24(dir, "settings.json");
26653
+ mkdirSync10(dir, { recursive: true });
26654
+ const path3 = join26(dir, "settings.json");
26324
26655
  let settings2 = {};
26325
26656
  if (existsSync13(path3)) {
26326
26657
  try {
@@ -26348,7 +26679,7 @@ function ensureQwenInjectModel(modelId, env = process.env) {
26348
26679
  providers.openai = openai;
26349
26680
  settings2.modelProviders = providers;
26350
26681
  }
26351
- writeFileSync9(path3, `${JSON.stringify(settings2, null, 2)}
26682
+ writeFileSync10(path3, `${JSON.stringify(settings2, null, 2)}
26352
26683
  `, { mode: 384 });
26353
26684
  try {
26354
26685
  chmodSync4(path3, 384);
@@ -26357,23 +26688,54 @@ function ensureQwenInjectModel(modelId, env = process.env) {
26357
26688
  return inject.model;
26358
26689
  }
26359
26690
  async function resolveModels2(env) {
26360
- const catalog = await mergeLocalInject(EMPTY, env);
26361
- return { default: catalog.options[0]?.id ?? "", options: catalog.options };
26362
- }
26363
- var EMPTY, support8, QwenAgentDriver;
26691
+ const catalog = await mergeLocalInject(readQwenModelCatalog(env), env);
26692
+ const routes = readQwenRoutes(env);
26693
+ const replacements = /* @__PURE__ */ new Map();
26694
+ for (const option of catalog.options) {
26695
+ const injected = decodeInjectId(option.id);
26696
+ if (!injected) continue;
26697
+ const endpoint = localHost(injected.host)?.baseUrl;
26698
+ for (const route of routes) {
26699
+ if (route.protocol === "openai" && route.model === injected.model && route.baseUrl === endpoint) {
26700
+ replacements.set(route.id, option.id);
26701
+ }
26702
+ }
26703
+ }
26704
+ const options = catalog.options.filter((option) => !replacements.has(option.id));
26705
+ const preferred = replacements.get(catalog.default) ?? catalog.default;
26706
+ return { default: options.some((option) => option.id === preferred) ? preferred : options[0]?.id ?? "", options };
26707
+ }
26708
+ function resolveQwenTurnModel(model, env) {
26709
+ if (!model) return model;
26710
+ const injected = decodeInjectId(model);
26711
+ if (injected) ensureQwenInjectModel(model, env);
26712
+ const routes = readQwenRoutes(env);
26713
+ if (injected) {
26714
+ const route = routes.find((row) => row.protocol === "openai" && row.model === injected.model && row.baseUrl === localHost(injected.host)?.baseUrl);
26715
+ if (!route) throw new Error("Qwen could not configure this local model. Refresh models and select it again.");
26716
+ return route.id;
26717
+ }
26718
+ if (routes.some((route) => route.id === model)) return model;
26719
+ const matches = routes.filter((route) => route.model === model);
26720
+ if (matches.length > 1) throw new Error("This Qwen model has multiple providers. Refresh models and select the intended provider again.");
26721
+ if (!matches.length) throw new Error("This Qwen model is no longer configured. Refresh models and select it again.");
26722
+ return matches[0].id;
26723
+ }
26724
+ var EMPTY, PROTOCOLS, support8, QwenAgentDriver;
26364
26725
  var init_qwen = __esm({
26365
26726
  "server/drivers/acp/qwen.ts"() {
26366
26727
  "use strict";
26367
26728
  init_local_inject();
26368
26729
  init_core3();
26369
26730
  EMPTY = { default: "", options: [] };
26731
+ PROTOCOLS = /* @__PURE__ */ new Set(["openai", "anthropic", "gemini", "vertex-ai"]);
26370
26732
  support8 = {
26371
26733
  driverKind: "qwenAgent",
26372
26734
  displayName: "Qwen",
26373
26735
  access: "custom",
26374
26736
  models: EMPTY,
26375
26737
  resolveModels: resolveModels2,
26376
- resolveTurnModel: (model, env) => model ? ensureQwenInjectModel(model, env) : model,
26738
+ resolveTurnModel: resolveQwenTurnModel,
26377
26739
  defaultCli: "qwen",
26378
26740
  nativeSource: "qwen.acp",
26379
26741
  loginNote: "Qwen Code CLI is not installed",
@@ -26385,7 +26747,10 @@ var init_qwen = __esm({
26385
26747
  },
26386
26748
  docsUrl: "https://qwenlm.github.io/qwen-code-docs/en/users/overview/"
26387
26749
  },
26388
- spawnArgs: (_config, turn) => ["--acp", ...turn.model ? ["-m", turn.model] : []],
26750
+ // A raw -m only changes the model within the saved provider. ACP switches
26751
+ // the complete route and confirms it before any prompt leaves OMB.
26752
+ spawnArgs: () => ["--acp"],
26753
+ selectModel: { configId: "model" },
26389
26754
  pickAuthMethod: () => null,
26390
26755
  authFailure: "continue",
26391
26756
  isAuthenticated: () => true,
@@ -33285,9 +33650,9 @@ var init_browser = __esm({
33285
33650
 
33286
33651
  // server/drivers/acp/hermes.ts
33287
33652
  import { spawn as spawn5 } from "node:child_process";
33288
- import { mkdirSync as mkdirSync10, readFileSync as readFileSync15, writeFileSync as writeFileSync10 } from "node:fs";
33289
- import { homedir as homedir17 } from "node:os";
33290
- import { join as join25 } from "node:path";
33653
+ import { mkdirSync as mkdirSync11, readFileSync as readFileSync15, writeFileSync as writeFileSync11 } from "node:fs";
33654
+ import { homedir as homedir20 } from "node:os";
33655
+ import { join as join27 } from "node:path";
33291
33656
  function bindHermesScreenshotCompat(env, modelId) {
33292
33657
  delete env[HERMES_OPENMAUS_SCREENSHOT_COMPAT];
33293
33658
  delete env[HERMES_OPENMAUS_SCREENSHOT_COMPAT_MODEL];
@@ -33297,7 +33662,7 @@ function bindHermesScreenshotCompat(env, modelId) {
33297
33662
  env[HERMES_OPENMAUS_SCREENSHOT_COMPAT_MODEL] = inject.model;
33298
33663
  }
33299
33664
  function hermesHome(env) {
33300
- return env.HERMES_HOME || join25(env.HOME || env.USERPROFILE || homedir17(), ".hermes");
33665
+ return env.HERMES_HOME || join27(env.HOME || env.USERPROFILE || homedir20(), ".hermes");
33301
33666
  }
33302
33667
  function quoteYaml(value) {
33303
33668
  if (/^[\w./:+-]+$/.test(value)) return value;
@@ -33338,8 +33703,8 @@ function ensureHermesInjectProvider(modelId, env = process.env) {
33338
33703
  const host = localHost(inject.host);
33339
33704
  if (!host) return modelId;
33340
33705
  const dir = hermesHome(env);
33341
- mkdirSync10(dir, { recursive: true });
33342
- const path3 = join25(dir, "config.yaml");
33706
+ mkdirSync11(dir, { recursive: true });
33707
+ const path3 = join27(dir, "config.yaml");
33343
33708
  let text2 = "";
33344
33709
  try {
33345
33710
  text2 = readFileSync15(path3, "utf8");
@@ -33347,7 +33712,7 @@ function ensureHermesInjectProvider(modelId, env = process.env) {
33347
33712
  text2 = "";
33348
33713
  }
33349
33714
  const next = upsertHermesProvider(text2, inject.host, host.baseUrl, hostApiKey(host, env));
33350
- if (next !== text2) writeFileSync10(path3, next);
33715
+ if (next !== text2) writeFileSync11(path3, next);
33351
33716
  return hermesAcpModelId(modelId) ?? modelId;
33352
33717
  }
33353
33718
  function hermesAcpModelId(modelId) {
@@ -33410,13 +33775,13 @@ function hermesConfiguredModel(env = process.env) {
33410
33775
  const dir = hermesHome(env);
33411
33776
  let secrets = "";
33412
33777
  try {
33413
- secrets = readFileSync15(join25(dir, ".env"), "utf8");
33778
+ secrets = readFileSync15(join27(dir, ".env"), "utf8");
33414
33779
  } catch {
33415
33780
  }
33416
33781
  const hasHostedProviderKey = HERMES_HOSTED_PROVIDER_KEYS.some((name) => nonEmptyDotenvValue(secrets, name));
33417
33782
  let configuredDefault = null;
33418
33783
  try {
33419
- configuredDefault = hermesConfigDefault(readFileSync15(join25(dir, "config.yaml"), "utf8"));
33784
+ configuredDefault = hermesConfigDefault(readFileSync15(join27(dir, "config.yaml"), "utf8"));
33420
33785
  } catch {
33421
33786
  }
33422
33787
  const configuredProvider = configuredDefault?.provider.toLowerCase() ?? "";
@@ -33432,12 +33797,12 @@ function hermesConfiguredModel(env = process.env) {
33432
33797
  };
33433
33798
  }
33434
33799
  async function fetchHermesAcpModels(cli, env) {
33435
- return await new Promise((resolve12) => {
33800
+ return await new Promise((resolve13) => {
33436
33801
  let child;
33437
33802
  try {
33438
33803
  child = spawn5(cli, ["acp"], { stdio: ["pipe", "pipe", "ignore"], env });
33439
33804
  } catch {
33440
- return resolve12([]);
33805
+ return resolve13([]);
33441
33806
  }
33442
33807
  let settled = false;
33443
33808
  let timer;
@@ -33458,7 +33823,7 @@ async function fetchHermesAcpModels(cli, env) {
33458
33823
  }
33459
33824
  } catch {
33460
33825
  }
33461
- resolve12(out);
33826
+ resolve13(out);
33462
33827
  };
33463
33828
  timer = setTimeout(() => done([]), 5e3);
33464
33829
  child.once("error", () => done([]));
@@ -33501,7 +33866,7 @@ async function fetchHermesAcpModels(cli, env) {
33501
33866
  }
33502
33867
  if (msg?.id === initId) {
33503
33868
  if (!msg.result) return done([]);
33504
- sessionId = send("session/new", { cwd: env.HOME || env.USERPROFILE || homedir17(), mcpServers: [] });
33869
+ sessionId = send("session/new", { cwd: env.HOME || env.USERPROFILE || homedir20(), mcpServers: [] });
33505
33870
  } else if (sessionId && msg?.id === sessionId) {
33506
33871
  const list = Array.isArray(msg.result?.models?.availableModels) ? msg.result.models.availableModels : [];
33507
33872
  done(
@@ -33736,9 +34101,9 @@ var init_openai_compat = __esm({
33736
34101
  });
33737
34102
 
33738
34103
  // server/drivers/pi.ts
33739
- import { chmodSync as chmodSync5, existsSync as existsSync14, mkdirSync as mkdirSync11, mkdtempSync as mkdtempSync3, readFileSync as readFileSync16, rmSync as rmSync3, writeFileSync as writeFileSync11 } from "node:fs";
33740
- import { homedir as homedir18, tmpdir as tmpdir4 } from "node:os";
33741
- import { join as join26 } from "node:path";
34104
+ import { chmodSync as chmodSync5, existsSync as existsSync14, mkdirSync as mkdirSync12, mkdtempSync as mkdtempSync3, readFileSync as readFileSync16, rmSync as rmSync3, writeFileSync as writeFileSync12 } from "node:fs";
34105
+ import { homedir as homedir21, tmpdir as tmpdir4 } from "node:os";
34106
+ import { join as join28 } from "node:path";
33742
34107
  function readPiPromptImages(turn) {
33743
34108
  return (turn.images ?? []).map((image) => ({
33744
34109
  type: "image",
@@ -33845,7 +34210,7 @@ async function applyPiLocalCatalog(catalog, env = process.env, fetchImpl = fetch
33845
34210
  return preferPiInjectRows(await mergeLocalInject(catalog, env, fetchImpl));
33846
34211
  }
33847
34212
  function piAgentDir(env) {
33848
- return join26(env.HOME || env.USERPROFILE || homedir18(), ".pi", "agent");
34213
+ return join28(env.HOME || env.USERPROFILE || homedir21(), ".pi", "agent");
33849
34214
  }
33850
34215
  function ensurePiInjectModel(modelId, env = process.env) {
33851
34216
  const split = splitPiModel(modelId);
@@ -33855,8 +34220,8 @@ function ensurePiInjectModel(modelId, env = process.env) {
33855
34220
  const host = localHost(inject.host);
33856
34221
  if (!host) return split;
33857
34222
  const dir = piAgentDir(env);
33858
- mkdirSync11(dir, { recursive: true });
33859
- const path3 = join26(dir, "models.json");
34223
+ mkdirSync12(dir, { recursive: true });
34224
+ const path3 = join28(dir, "models.json");
33860
34225
  let root = { providers: {} };
33861
34226
  if (existsSync14(path3)) {
33862
34227
  try {
@@ -33901,7 +34266,7 @@ function ensurePiInjectModel(modelId, env = process.env) {
33901
34266
  existing.models = models;
33902
34267
  providers[inject.host] = existing;
33903
34268
  root.providers = providers;
33904
- writeFileSync11(path3, `${JSON.stringify(root, null, 2)}
34269
+ writeFileSync12(path3, `${JSON.stringify(root, null, 2)}
33905
34270
  `, { mode: 384 });
33906
34271
  try {
33907
34272
  chmodSync5(path3, 384);
@@ -33910,7 +34275,7 @@ function ensurePiInjectModel(modelId, env = process.env) {
33910
34275
  return split;
33911
34276
  }
33912
34277
  function readPiDefaultModel(env) {
33913
- const home = env.HOME || env.USERPROFILE || homedir18();
34278
+ const home = env.HOME || env.USERPROFILE || homedir21();
33914
34279
  try {
33915
34280
  const s = JSON.parse(readFileSync16(`${home}/.pi/agent/settings.json`, "utf8"));
33916
34281
  return s.defaultProvider && s.defaultModel ? `${s.defaultProvider}/${s.defaultModel}` : "";
@@ -33920,7 +34285,7 @@ function readPiDefaultModel(env) {
33920
34285
  }
33921
34286
  async function fetchPiModels(cli, env) {
33922
34287
  const child = spawnCli(cli, PI_ARGS, { stdio: ["pipe", "pipe", "pipe"], env });
33923
- return new Promise((resolve12) => {
34288
+ return new Promise((resolve13) => {
33924
34289
  let buf = "";
33925
34290
  let done = false;
33926
34291
  const fallbackDefault = readPiDefaultModel(env);
@@ -33931,7 +34296,7 @@ async function fetchPiModels(cli, env) {
33931
34296
  killCliTree(child);
33932
34297
  } catch {
33933
34298
  }
33934
- resolve12(catalog);
34299
+ resolve13(catalog);
33935
34300
  };
33936
34301
  const timer = setTimeout(() => finish({ default: "", options: [] }), 15e3);
33937
34302
  timer.unref?.();
@@ -34058,9 +34423,9 @@ var init_pi = __esm({
34058
34423
  const mcpServers = buildMcpServers(turn);
34059
34424
  let mcpTempDir = null;
34060
34425
  if (mcpServers) {
34061
- mcpTempDir = mkdtempSync3(join26(tmpdir4(), "omb-pi-mcp-"));
34426
+ mcpTempDir = mkdtempSync3(join28(tmpdir4(), "omb-pi-mcp-"));
34062
34427
  try {
34063
- writeFileSync11(join26(mcpTempDir, "mcp.json"), JSON.stringify({ mcpServers }), { mode: 384 });
34428
+ writeFileSync12(join28(mcpTempDir, "mcp.json"), JSON.stringify({ mcpServers }), { mode: 384 });
34064
34429
  } catch (err) {
34065
34430
  try {
34066
34431
  rmSync3(mcpTempDir, { recursive: true, force: true });
@@ -34078,7 +34443,7 @@ var init_pi = __esm({
34078
34443
  env: piEnvironment({
34079
34444
  ...process.env,
34080
34445
  ...input.environment,
34081
- ...mcpServers && mcpTempDir ? { OMB_MCP_CONFIG: join26(mcpTempDir, "mcp.json") } : {}
34446
+ ...mcpServers && mcpTempDir ? { OMB_MCP_CONFIG: join28(mcpTempDir, "mcp.json") } : {}
34082
34447
  })
34083
34448
  });
34084
34449
  } catch (err) {
@@ -34101,13 +34466,13 @@ var init_pi = __esm({
34101
34466
  }
34102
34467
  responseWaiters.clear();
34103
34468
  };
34104
- const awaitResponse = (command, timeoutMs = 2e4) => new Promise((resolve12, reject) => {
34469
+ const awaitResponse = (command, timeoutMs = 2e4) => new Promise((resolve13, reject) => {
34105
34470
  const timer = setTimeout(() => {
34106
34471
  responseWaiters.delete(command);
34107
34472
  reject(new Error(`pi ${command} timed out`));
34108
34473
  }, timeoutMs);
34109
34474
  timer.unref?.();
34110
- responseWaiters.set(command, { resolve: resolve12, reject, timer });
34475
+ responseWaiters.set(command, { resolve: resolve13, reject, timer });
34111
34476
  });
34112
34477
  child.stdin.on("error", () => rejectWaiters(new Error("pi stdin closed")));
34113
34478
  const send = (obj) => {
@@ -34315,7 +34680,7 @@ ${turn.text}` : turn.text;
34315
34680
  return { turnId };
34316
34681
  };
34317
34682
  const snapshot = async () => {
34318
- const version2 = await new Promise((resolve12) => {
34683
+ const version2 = await new Promise((resolve13) => {
34319
34684
  const child = spawnCli(config2.cli, ["--version"], {
34320
34685
  stdio: ["ignore", "pipe", "pipe"],
34321
34686
  env: piEnvironment({ ...process.env, ...input.environment })
@@ -34328,16 +34693,16 @@ ${turn.text}` : turn.text;
34328
34693
  killCliTree(child);
34329
34694
  } catch {
34330
34695
  }
34331
- resolve12(null);
34696
+ resolve13(null);
34332
34697
  }, 8e3);
34333
34698
  timer.unref?.();
34334
34699
  child.on("error", () => {
34335
34700
  clearTimeout(timer);
34336
- resolve12(null);
34701
+ resolve13(null);
34337
34702
  });
34338
34703
  child.on("close", () => {
34339
34704
  clearTimeout(timer);
34340
- resolve12(out.trim() || null);
34705
+ resolve13(out.trim() || null);
34341
34706
  });
34342
34707
  });
34343
34708
  if (!version2) return { state: "unavailable", reason: `\`${config2.cli}\` CLI not found` };
@@ -34416,15 +34781,15 @@ ${turn.text}` : turn.text;
34416
34781
 
34417
34782
  // server/drivers/minimax.ts
34418
34783
  import { readFileSync as readFileSync17 } from "node:fs";
34419
- import { homedir as homedir19 } from "node:os";
34420
- import { join as join27 } from "node:path";
34784
+ import { homedir as homedir22 } from "node:os";
34785
+ import { join as join29 } from "node:path";
34421
34786
  function normalizedApiUrl(value) {
34422
34787
  const root = value.trim().replace(/\/+$/, "");
34423
34788
  return root.endsWith("/v1") ? root : `${root}/v1`;
34424
34789
  }
34425
- function loadLocalMiniMaxConfig(home = homedir19()) {
34790
+ function loadLocalMiniMaxConfig(home = homedir22()) {
34426
34791
  try {
34427
- const raw = localConfigSchema.parse(JSON.parse(readFileSync17(join27(home, ".mmx", "config.json"), "utf8")));
34792
+ const raw = localConfigSchema.parse(JSON.parse(readFileSync17(join29(home, ".mmx", "config.json"), "utf8")));
34428
34793
  const configuredUrl = raw.base_url?.trim() ? raw.base_url : raw.region === "cn" ? CN_URL : DEFAULT_URL2;
34429
34794
  return {
34430
34795
  apiKey: raw.api_key?.trim() ?? "",
@@ -34692,7 +35057,7 @@ __export(cli_setup_exports, {
34692
35057
  });
34693
35058
  import { spawn as spawn6 } from "node:child_process";
34694
35059
  import { readFileSync as readFileSync18 } from "node:fs";
34695
- import { join as join28, resolve as resolve9 } from "node:path";
35060
+ import { join as join30, resolve as resolve10 } from "node:path";
34696
35061
  import { randomUUID as randomUUID10 } from "node:crypto";
34697
35062
  async function inspect(id, entry) {
34698
35063
  const registry2 = new ProviderRegistry(BUILT_IN_DRIVERS);
@@ -34725,11 +35090,11 @@ async function runSetupCli(cli, args, environment = {}) {
34725
35090
  resetPathCache();
34726
35091
  }
34727
35092
  function assertDataDir(dataDir) {
34728
- if (resolve9(dataDir) !== resolve9(DATA_DIR)) throw new Error("Setup data directory mismatch. Restart the CLI with --data-dir.");
35093
+ if (resolve10(dataDir) !== resolve10(DATA_DIR)) throw new Error("Setup data directory mismatch. Restart the CLI with --data-dir.");
34729
35094
  }
34730
35095
  function checkStoredConfig(dataDir) {
34731
35096
  try {
34732
- parseStoredConfig(JSON.parse(readFileSync18(join28(dataDir, "config.json"), "utf8")));
35097
+ parseStoredConfig(JSON.parse(readFileSync18(join30(dataDir, "config.json"), "utf8")));
34733
35098
  } catch (error51) {
34734
35099
  if (error51.code === "ENOENT") return;
34735
35100
  throw new Error("Existing config.json could not be read or validated. Setup has not changed it; repair it before continuing.");
@@ -35007,7 +35372,7 @@ var init_browser_engine_release = __esm({
35007
35372
  });
35008
35373
 
35009
35374
  // server/browser-bundle-release.ts
35010
- import { join as join29 } from "node:path";
35375
+ import { join as join31 } from "node:path";
35011
35376
  function browserBundleSpec(target) {
35012
35377
  if (!Object.hasOwn(CHROME_ASSETS, target)) throw new Error(`Unsupported desktop browser target: ${target}`);
35013
35378
  const pinned = CHROME_ASSETS[target];
@@ -35043,10 +35408,10 @@ function browserBundlePaths(bundleDirectory, target) {
35043
35408
  const spec = browserBundleSpec(target);
35044
35409
  return {
35045
35410
  directory: bundleDirectory,
35046
- manifest: join29(bundleDirectory, "manifest.json"),
35047
- engine: join29(bundleDirectory, spec.engine.executable),
35048
- chrome: join29(bundleDirectory, spec.chrome.executable),
35049
- licenses: join29(bundleDirectory, "licenses")
35411
+ manifest: join31(bundleDirectory, "manifest.json"),
35412
+ engine: join31(bundleDirectory, spec.engine.executable),
35413
+ chrome: join31(bundleDirectory, spec.chrome.executable),
35414
+ licenses: join31(bundleDirectory, "licenses")
35050
35415
  };
35051
35416
  }
35052
35417
  var CHROME_VERSION, CHROME_ASSETS;
@@ -35100,29 +35465,29 @@ __export(browser_engine_exports, {
35100
35465
  resolveAgentBrowserBinary: () => resolveAgentBrowserBinary
35101
35466
  });
35102
35467
  import { spawn as spawn7 } from "node:child_process";
35103
- import { createHash as createHash7, randomBytes as randomBytes3, randomUUID as randomUUID11 } from "node:crypto";
35104
- import { chmodSync as chmodSync6, constants as constants2, copyFileSync, existsSync as existsSync15, mkdirSync as mkdirSync12, readFileSync as readFileSync19, readdirSync as readdirSync3, renameSync as renameSync5, rmSync as rmSync4, statSync as statSync3, unlinkSync as unlinkSync4, writeFileSync as writeFileSync12 } from "node:fs";
35105
- import { homedir as homedir20, tmpdir as tmpdir5 } from "node:os";
35106
- import { delimiter as delimiter6, dirname as dirname9, join as join30, resolve as resolve10 } from "node:path";
35468
+ import { createHash as createHash8, randomBytes as randomBytes3, randomUUID as randomUUID11 } from "node:crypto";
35469
+ import { chmodSync as chmodSync6, constants as constants2, copyFileSync, existsSync as existsSync15, mkdirSync as mkdirSync13, readFileSync as readFileSync19, readdirSync as readdirSync3, renameSync as renameSync5, rmSync as rmSync4, statSync as statSync3, unlinkSync as unlinkSync4, writeFileSync as writeFileSync13 } from "node:fs";
35470
+ import { homedir as homedir23, tmpdir as tmpdir5 } from "node:os";
35471
+ import { delimiter as delimiter6, dirname as dirname10, join as join32, resolve as resolve11 } from "node:path";
35107
35472
  function browserRestoreKey(session) {
35108
35473
  if (!/^[A-Za-z0-9_-]{1,96}$/.test(session)) throw new Error("Invalid browser session.");
35109
- return `omb-${createHash7("sha256").update(session).digest("hex")}`;
35474
+ return `omb-${createHash8("sha256").update(session).digest("hex")}`;
35110
35475
  }
35111
35476
  function browserSessionsDirectory(env) {
35112
35477
  const namespace = env.AGENT_BROWSER_NAMESPACE;
35113
35478
  if (namespace && !/^[a-z0-9]+(?:[-_][a-z0-9]+)*$/.test(namespace)) throw new Error("Invalid browser namespace.");
35114
- const home = (process.platform === "win32" ? env.USERPROFILE : env.HOME) || homedir20();
35115
- const stateRoot = namespace ? join30(home, ".agent-browser", "namespaces", namespace, "state") : join30(home, ".agent-browser");
35116
- return join30(stateRoot, "sessions");
35479
+ const home = (process.platform === "win32" ? env.USERPROFILE : env.HOME) || homedir23();
35480
+ const stateRoot = namespace ? join32(home, ".agent-browser", "namespaces", namespace, "state") : join32(home, ".agent-browser");
35481
+ return join32(stateRoot, "sessions");
35117
35482
  }
35118
35483
  function managedBrowserConfigPath(env) {
35119
- return join30(browserSessionsDirectory(env), "..", "omb-managed-config.json");
35484
+ return join32(browserSessionsDirectory(env), "..", "omb-managed-config.json");
35120
35485
  }
35121
35486
  function ensureManagedBrowserConfig(env) {
35122
35487
  const path3 = managedBrowserConfigPath(env);
35123
- mkdirSync12(dirname9(path3), { recursive: true, mode: 448 });
35488
+ mkdirSync13(dirname10(path3), { recursive: true, mode: 448 });
35124
35489
  try {
35125
- writeFileSync12(path3, "{}\n", { flag: "wx", mode: 384 });
35490
+ writeFileSync13(path3, "{}\n", { flag: "wx", mode: 384 });
35126
35491
  } catch (error51) {
35127
35492
  if (error51.code !== "EEXIST") throw error51;
35128
35493
  if (readFileSync19(path3, "utf8").trim() !== "{}") throw new Error("The managed browser configuration was changed. Restore it to an empty JSON object before connecting.");
@@ -35189,7 +35554,7 @@ async function closeBrowserSession(binaryPath, env, timeoutMs = 15e3) {
35189
35554
  } catch {
35190
35555
  return false;
35191
35556
  }
35192
- await new Promise((resolve12) => setTimeout(resolve12, Math.min(25, Math.max(1, deadline - Date.now()))));
35557
+ await new Promise((resolve13) => setTimeout(resolve13, Math.min(25, Math.max(1, deadline - Date.now()))));
35193
35558
  }
35194
35559
  return false;
35195
35560
  }
@@ -35203,11 +35568,11 @@ async function prepareBrowserSessionState(binaryPath, session, options = {}) {
35203
35568
  ensureManagedBrowserConfig(env);
35204
35569
  if (options.persistent === false) return;
35205
35570
  const directory = browserSessionsDirectory(env);
35206
- const key = join30(directory, session);
35571
+ const key = join32(directory, session);
35207
35572
  let pending = preparedBrowserSessions.get(key);
35208
35573
  if (!pending) {
35209
35574
  pending = (async () => {
35210
- const targetBase = join30(directory, `${restoreKey}-${session}`);
35575
+ const targetBase = join32(directory, `${restoreKey}-${session}`);
35211
35576
  const hasTarget = () => existsSync15(`${targetBase}.json.enc`) || existsSync15(`${targetBase}.json`);
35212
35577
  if (hasTarget()) return;
35213
35578
  check2();
@@ -35217,7 +35582,7 @@ async function prepareBrowserSessionState(binaryPath, session, options = {}) {
35217
35582
  check2();
35218
35583
  if (hasTarget()) return;
35219
35584
  const candidates = [".json.enc", ".json"].flatMap((suffix) => {
35220
- const path3 = join30(directory, `${session}-${session}${suffix}`);
35585
+ const path3 = join32(directory, `${session}-${session}${suffix}`);
35221
35586
  try {
35222
35587
  const stat4 = statSync3(path3);
35223
35588
  if (!stat4.isFile()) throw new Error("Saved browser state is not a file.");
@@ -35252,14 +35617,14 @@ function isMusl(platform = process.platform, exists = existsSync15) {
35252
35617
  }
35253
35618
  function pinnedBinaryPath(dataDir = DATA_DIR, platform = process.platform, arch = process.arch) {
35254
35619
  const version2 = agentBrowserReleaseVersion(resolveAgentBrowserReleaseAsset(platform, arch));
35255
- return join30(dataDir, ENGINE_DIR, version2, executableName(platform));
35620
+ return join32(dataDir, ENGINE_DIR, version2, executableName(platform));
35256
35621
  }
35257
35622
  function onPath(env, platform, exists) {
35258
35623
  const pathValue = platform === "win32" ? Object.entries(env).findLast(([key]) => key.toUpperCase() === "PATH")?.[1] : env.PATH;
35259
35624
  for (const part of (pathValue ?? "").split(delimiter6)) {
35260
35625
  const dir = part.trim().replace(/^"|"$/gu, "");
35261
35626
  if (!dir) continue;
35262
- const candidate = resolve10(dir, executableName(platform));
35627
+ const candidate = resolve11(dir, executableName(platform));
35263
35628
  if (exists(candidate)) return candidate;
35264
35629
  }
35265
35630
  return null;
@@ -35268,7 +35633,7 @@ function packagedBrowser(options) {
35268
35633
  const resources = (options.env ?? process.env).OMB_RESOURCES_PATH;
35269
35634
  if (!resources) return null;
35270
35635
  try {
35271
- return browserBundlePaths(join30(resolve10(resources), "browser-engine"), `${options.platform ?? process.platform}-${options.arch ?? process.arch}`);
35636
+ return browserBundlePaths(join32(resolve11(resources), "browser-engine"), `${options.platform ?? process.platform}-${options.arch ?? process.arch}`);
35272
35637
  } catch {
35273
35638
  return null;
35274
35639
  }
@@ -35281,7 +35646,7 @@ function resolveAgentBrowserBinary(options = {}) {
35281
35646
  const platform = options.platform ?? process.platform;
35282
35647
  const exists = options.exists ?? existsSync15;
35283
35648
  const override = env.OMB_AGENT_BROWSER_PATH?.trim();
35284
- if (override) return resolve10(override) && exists(resolve10(override)) ? resolve10(override) : null;
35649
+ if (override) return resolve11(override) && exists(resolve11(override)) ? resolve11(override) : null;
35285
35650
  const bundle = packagedBrowser(options);
35286
35651
  if (bundle && exists(bundle.directory)) return completePackage(bundle, exists) ? bundle.engine : null;
35287
35652
  const pinned = pinnedBinaryPath(options.dataDir, platform, options.arch);
@@ -35293,8 +35658,8 @@ async function installAgentBrowserBinary(options = {}) {
35293
35658
  const asset = options.asset ?? resolveAgentBrowserReleaseAsset(platform, options.arch ?? process.arch, options.musl ?? isMusl(platform));
35294
35659
  if (!asset) throw new Error(`agent-browser publishes no build for ${platform}-${options.arch ?? process.arch}.`);
35295
35660
  const destination = pinnedBinaryPath(options.dataDir, platform, options.arch);
35296
- const directory = join30(destination, "..");
35297
- mkdirSync12(directory, { recursive: true, mode: 448 });
35661
+ const directory = join32(destination, "..");
35662
+ mkdirSync13(directory, { recursive: true, mode: 448 });
35298
35663
  const url2 = agentBrowserReleaseUrl(asset);
35299
35664
  options.log?.(`downloading agent-browser ${agentBrowserReleaseVersion(asset)} (${Math.round(asset.bytes / 1024 / 1024)} MB, digest pinned)`);
35300
35665
  const controller = new AbortController();
@@ -35309,17 +35674,17 @@ async function installAgentBrowserBinary(options = {}) {
35309
35674
  clearTimeout(timer);
35310
35675
  }
35311
35676
  if (body.length !== asset.bytes) throw new Error("the agent-browser download did not match its pinned size; nothing was installed");
35312
- const digest2 = createHash7("sha256").update(body).digest("hex");
35677
+ const digest2 = createHash8("sha256").update(body).digest("hex");
35313
35678
  if (digest2 !== asset.sha256) throw new Error("the agent-browser download failed its SHA-256 check; nothing was installed");
35314
35679
  const staging = `${destination}.${randomBytes3(6).toString("hex")}.part`;
35315
- writeFileSync12(staging, body, { mode: 493 });
35680
+ writeFileSync13(staging, body, { mode: 493 });
35316
35681
  if (platform !== "win32") chmodSync6(staging, 493);
35317
35682
  renameSync5(staging, destination);
35318
35683
  return destination;
35319
35684
  }
35320
35685
  function ensureChrome(binaryPath, options = {}) {
35321
35686
  const bundle = packagedBrowser(options);
35322
- if (!options.withDeps && bundle && resolve10(binaryPath) === bundle.engine && completePackage(bundle, existsSync15)) {
35687
+ if (!options.withDeps && bundle && resolve11(binaryPath) === bundle.engine && completePackage(bundle, existsSync15)) {
35323
35688
  options.log?.("agent-browser: the bundled browser is ready; no download needed");
35324
35689
  return Promise.resolve();
35325
35690
  }
@@ -35345,14 +35710,14 @@ function ensureChrome(binaryPath, options = {}) {
35345
35710
  });
35346
35711
  }
35347
35712
  function browserEngineEncryptionKey(dataDir = DATA_DIR) {
35348
- const file2 = join30(dataDir, KEY_FILE);
35713
+ const file2 = join32(dataDir, KEY_FILE);
35349
35714
  try {
35350
35715
  const existing = readFileSync19(file2, "utf8").trim();
35351
35716
  if (/^[0-9a-f]{64}$/u.test(existing)) return existing;
35352
35717
  } catch {
35353
35718
  }
35354
35719
  const key = randomBytes3(32).toString("hex");
35355
- mkdirSync12(dataDir, { recursive: true });
35720
+ mkdirSync13(dataDir, { recursive: true });
35356
35721
  writeFileAtomic(file2, `${key}
35357
35722
  `, { mode: 384 });
35358
35723
  return key;
@@ -35399,13 +35764,13 @@ function agentBrowserIntegration(input) {
35399
35764
  if (sourceEnv[name]) env[name] = sourceEnv[name];
35400
35765
  }
35401
35766
  const bundle = packagedBrowser({ env: sourceEnv });
35402
- if (!env.AGENT_BROWSER_EXECUTABLE_PATH && bundle && resolve10(input.binaryPath) === bundle.engine && completePackage(bundle, existsSync15)) {
35767
+ if (!env.AGENT_BROWSER_EXECUTABLE_PATH && bundle && resolve11(input.binaryPath) === bundle.engine && completePackage(bundle, existsSync15)) {
35403
35768
  env.AGENT_BROWSER_EXECUTABLE_PATH = bundle.chrome;
35404
35769
  }
35405
35770
  return { command: input.binaryPath, args: ["mcp", "--tools", "core", "--no-webmcp"], env };
35406
35771
  }
35407
35772
  function agentBrowserFrame(input) {
35408
- const file2 = join30(tmpdir5(), `openmausbot-browser-${randomUUID11()}.png`);
35773
+ const file2 = join32(tmpdir5(), `openmausbot-browser-${randomUUID11()}.png`);
35409
35774
  return new Promise((settle, fail) => {
35410
35775
  const child = spawn7(input.binaryPath, ["screenshot", file2], {
35411
35776
  env: browserRuntimeEnv(input.env),
@@ -35475,8 +35840,8 @@ async function clearBrowserSessionState(binaryPath, session, options = {}) {
35475
35840
  }
35476
35841
  };
35477
35842
  try {
35478
- for (const base2 of bases) for (const suffix of [".json", ".json.enc", ".json.previous", ".json.enc.previous"]) remove(join30(directory, base2 + suffix));
35479
- const pendingDirectory = join30(directory, ".tmp");
35843
+ for (const base2 of bases) for (const suffix of [".json", ".json.enc", ".json.previous", ".json.enc.previous"]) remove(join32(directory, base2 + suffix));
35844
+ const pendingDirectory = join32(directory, ".tmp");
35480
35845
  let pendingFiles = [];
35481
35846
  try {
35482
35847
  pendingFiles = readdirSync3(pendingDirectory);
@@ -35484,8 +35849,8 @@ async function clearBrowserSessionState(binaryPath, session, options = {}) {
35484
35849
  if (error51.code !== "ENOENT") throw error51;
35485
35850
  }
35486
35851
  const candidates = bases.map((base2) => new RegExp(`^${base2}-candidate-[0-9]+\\.json(?:\\.enc)?$`));
35487
- for (const name of pendingFiles) if (candidates.some((candidate) => candidate.test(name))) remove(join30(pendingDirectory, name));
35488
- preparedBrowserSessions.delete(join30(directory, session));
35852
+ for (const name of pendingFiles) if (candidates.some((candidate) => candidate.test(name))) remove(join32(pendingDirectory, name));
35853
+ preparedBrowserSessions.delete(join32(directory, session));
35489
35854
  return true;
35490
35855
  } catch {
35491
35856
  return false;
@@ -35512,9 +35877,9 @@ var init_browser_engine = __esm({
35512
35877
  // server/cli.ts
35513
35878
  var import_qrcode_terminal = __toESM(require_main(), 1);
35514
35879
  import { spawn as spawn8 } from "node:child_process";
35515
- import { closeSync as closeSync3, existsSync as existsSync16, mkdirSync as mkdirSync13, openSync as openSync3, readFileSync as readFileSync20 } from "node:fs";
35516
- import { homedir as homedir21 } from "node:os";
35517
- import { dirname as dirname10, join as join31, resolve as resolve11 } from "node:path";
35880
+ import { closeSync as closeSync3, existsSync as existsSync16, mkdirSync as mkdirSync14, openSync as openSync3, readFileSync as readFileSync20 } from "node:fs";
35881
+ import { homedir as homedir24 } from "node:os";
35882
+ import { dirname as dirname11, join as join33, resolve as resolve12 } from "node:path";
35518
35883
  import { createInterface as createInterface2 } from "node:readline/promises";
35519
35884
  import { fileURLToPath as fileURLToPath3 } from "node:url";
35520
35885
 
@@ -36112,13 +36477,13 @@ function writePrivateToken(runtimeRoot2, token, { fileSystem, processId, identif
36112
36477
  }
36113
36478
  async function waitForExit(child, milliseconds) {
36114
36479
  if (child.exitCode !== null || child.signalCode !== null) return;
36115
- await new Promise((resolve12) => {
36480
+ await new Promise((resolve13) => {
36116
36481
  let settled = false;
36117
36482
  const finish = () => {
36118
36483
  if (settled) return;
36119
36484
  settled = true;
36120
36485
  clearTimeout(timer);
36121
- resolve12();
36486
+ resolve13();
36122
36487
  };
36123
36488
  child.once("exit", finish);
36124
36489
  const timer = setTimeout(finish, milliseconds);
@@ -36282,8 +36647,8 @@ function createManagedCompanionTunnel({
36282
36647
  child = spawned;
36283
36648
  const cancellationController = new AbortController();
36284
36649
  let resolveCancellation;
36285
- const cancellation = new Promise((resolve12) => {
36286
- resolveCancellation = resolve12;
36650
+ const cancellation = new Promise((resolve13) => {
36651
+ resolveCancellation = resolve13;
36287
36652
  });
36288
36653
  let cancelled = false;
36289
36654
  attemptHandle = {
@@ -36299,8 +36664,8 @@ function createManagedCompanionTunnel({
36299
36664
  let terminated = false;
36300
36665
  let expectedStop = false;
36301
36666
  let resolveExit;
36302
- const exit = new Promise((resolve12) => {
36303
- resolveExit = resolve12;
36667
+ const exit = new Promise((resolve13) => {
36668
+ resolveExit = resolve13;
36304
36669
  });
36305
36670
  const onTerminated = () => {
36306
36671
  if (terminated) return;
@@ -36334,8 +36699,8 @@ function createManagedCompanionTunnel({
36334
36699
  }
36335
36700
  if (Date.now() >= deadline) break;
36336
36701
  await Promise.race([
36337
- new Promise((resolve12) => {
36338
- const timer = setTimeout(resolve12, verifyIntervalMs);
36702
+ new Promise((resolve13) => {
36703
+ const timer = setTimeout(resolve13, verifyIntervalMs);
36339
36704
  timer.unref?.();
36340
36705
  }),
36341
36706
  exit,
@@ -37111,16 +37476,190 @@ function normalizeDomainOption(raw) {
37111
37476
  return value;
37112
37477
  }
37113
37478
 
37479
+ // server/service-cli.ts
37480
+ import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync3 } from "node:fs";
37481
+ import { homedir as homedir2 } from "node:os";
37482
+
37483
+ // server/service-unit.ts
37484
+ import { homedir, userInfo } from "node:os";
37485
+ import { posix } from "node:path";
37486
+ var { basename, dirname, join: join2 } = posix;
37487
+ var SYSTEMD_UNIT_NAME = "openmausbot.service";
37488
+ var LAUNCHD_LABEL = "com.openmausbot.serve";
37489
+ function quoteSystemd(value) {
37490
+ return /[\s"\\]/.test(value) ? `"${value.replace(/[\\"]/g, "\\$&")}"` : value;
37491
+ }
37492
+ function needsStripTypes(script) {
37493
+ return script.endsWith(".ts");
37494
+ }
37495
+ function serviceCommand(spec) {
37496
+ return [spec.node, ...needsStripTypes(spec.script) ? ["--experimental-strip-types"] : [], spec.script, "serve", ...spec.serveArgs];
37497
+ }
37498
+ function systemdUnit(spec) {
37499
+ const lines = [
37500
+ "# Written by `openmausbot service install`. Re-run it to change the options.",
37501
+ "[Unit]",
37502
+ `Description=OpenMausBot${spec.label ? ` (${spec.label})` : ""}`,
37503
+ "After=network-online.target",
37504
+ "Wants=network-online.target",
37505
+ "",
37506
+ "[Service]",
37507
+ "Type=simple",
37508
+ `User=${spec.user}`,
37509
+ `WorkingDirectory=${spec.home}`,
37510
+ `Environment=HOME=${spec.home}`,
37511
+ `Environment=OMB_DATA_DIR=${spec.dataDir}`,
37512
+ `ExecStart=${serviceCommand(spec).map(quoteSystemd).join(" ")}`,
37513
+ "Restart=always",
37514
+ "RestartSec=3",
37515
+ "KillMode=mixed",
37516
+ "TimeoutStopSec=30"
37517
+ ];
37518
+ if (spec.bindsLowPorts) {
37519
+ lines.push("# serve --domain: Caddy binds ports 80 and 443 without running as root.");
37520
+ lines.push("AmbientCapabilities=CAP_NET_BIND_SERVICE");
37521
+ lines.push("CapabilityBoundingSet=CAP_NET_BIND_SERVICE");
37522
+ }
37523
+ lines.push("", "[Install]", "WantedBy=multi-user.target", "");
37524
+ return lines.join("\n");
37525
+ }
37526
+ function xml(value) {
37527
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
37528
+ }
37529
+ function launchdPlist(spec) {
37530
+ const args = serviceCommand(spec).map((arg) => ` <string>${xml(arg)}</string>`).join("\n");
37531
+ return [
37532
+ '<?xml version="1.0" encoding="UTF-8"?>',
37533
+ '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
37534
+ '<plist version="1.0">',
37535
+ "<dict>",
37536
+ " <key>Label</key>",
37537
+ ` <string>${LAUNCHD_LABEL}</string>`,
37538
+ " <key>ProgramArguments</key>",
37539
+ " <array>",
37540
+ args,
37541
+ " </array>",
37542
+ " <key>EnvironmentVariables</key>",
37543
+ " <dict>",
37544
+ " <key>HOME</key>",
37545
+ ` <string>${xml(spec.home)}</string>`,
37546
+ " <key>OMB_DATA_DIR</key>",
37547
+ ` <string>${xml(spec.dataDir)}</string>`,
37548
+ " <key>PATH</key>",
37549
+ " <string>/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin</string>",
37550
+ " </dict>",
37551
+ " <key>WorkingDirectory</key>",
37552
+ ` <string>${xml(spec.home)}</string>`,
37553
+ " <key>RunAtLoad</key>",
37554
+ " <true/>",
37555
+ " <key>KeepAlive</key>",
37556
+ " <true/>",
37557
+ " <key>StandardOutPath</key>",
37558
+ ` <string>${xml(join2(spec.dataDir, "logs", "service.log"))}</string>`,
37559
+ " <key>StandardErrorPath</key>",
37560
+ ` <string>${xml(join2(spec.dataDir, "logs", "service.log"))}</string>`,
37561
+ "</dict>",
37562
+ "</plist>",
37563
+ ""
37564
+ ].join("\n");
37565
+ }
37566
+ function unstableInstallWarning(script) {
37567
+ const normalized = script.replace(/\\/g, "/");
37568
+ if (/\/_npx\//.test(normalized) || /\/\.npm\/_npx\//.test(normalized)) {
37569
+ return `this command runs from an npx cache (${dirname(script)}), which npm may delete at any time. Install it permanently first (npm install -g openmausbot) and run \`openmausbot service install\` from that install.`;
37570
+ }
37571
+ return null;
37572
+ }
37573
+ function servicePlan(platform, dataDir, home = homedir()) {
37574
+ if (platform === "linux") {
37575
+ const installed = `/etc/systemd/system/${SYSTEMD_UNIT_NAME}`;
37576
+ return {
37577
+ file: join2(dataDir, SYSTEMD_UNIT_NAME),
37578
+ installed,
37579
+ activate: [`sudo install -m 644 ${join2(dataDir, SYSTEMD_UNIT_NAME)} ${installed}`, "sudo systemctl daemon-reload", `sudo systemctl enable --now ${basename(SYSTEMD_UNIT_NAME, ".service")}`],
37580
+ deactivate: [`sudo systemctl disable --now ${basename(SYSTEMD_UNIT_NAME, ".service")}`, `sudo rm ${installed}`, "sudo systemctl daemon-reload"]
37581
+ };
37582
+ }
37583
+ if (platform === "darwin") {
37584
+ const installed = join2(home, "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
37585
+ return {
37586
+ file: join2(dataDir, `${LAUNCHD_LABEL}.plist`),
37587
+ installed,
37588
+ activate: [`mkdir -p ${dirname(installed)} && cp ${join2(dataDir, `${LAUNCHD_LABEL}.plist`)} ${installed}`, `launchctl bootstrap gui/$(id -u) ${installed}`],
37589
+ deactivate: [`launchctl bootout gui/$(id -u)/${LAUNCHD_LABEL}`, `rm ${installed}`]
37590
+ };
37591
+ }
37592
+ return null;
37593
+ }
37594
+ function currentUser() {
37595
+ try {
37596
+ return userInfo().username;
37597
+ } catch {
37598
+ return process.env.USER || process.env.USERNAME || "openmausbot";
37599
+ }
37600
+ }
37601
+
37602
+ // server/service-cli.ts
37603
+ function serviceServeArgs(input) {
37604
+ const args = ["--port", String(input.port), "--data-dir", input.dataDir, "--no-pair"];
37605
+ if (input.domain) args.push("--domain", input.domain);
37606
+ else if (input.tunnel) args.push("--tunnel");
37607
+ else if (input.tailscale) args.push("--tailscale");
37608
+ if (input.label) args.push("--label", input.label);
37609
+ return args;
37610
+ }
37611
+ function runServiceCommand(input, io) {
37612
+ const platform = input.platform ?? process.platform;
37613
+ const home = input.home ?? homedir2();
37614
+ const plan = servicePlan(platform, input.dataDir, home);
37615
+ if (!plan) {
37616
+ io.error("services are written for Linux (systemd) and macOS (launchd); on Windows, use Task Scheduler to run `openmausbot serve` at startup");
37617
+ return 1;
37618
+ }
37619
+ if (input.action === "uninstall") {
37620
+ io.log(`to stop and remove the service:`);
37621
+ for (const line of plan.deactivate) io.log(` ${line}`);
37622
+ return 0;
37623
+ }
37624
+ const warning = unstableInstallWarning(input.script);
37625
+ if (warning) {
37626
+ io.error(warning);
37627
+ return 1;
37628
+ }
37629
+ const spec = {
37630
+ node: input.node,
37631
+ script: input.script,
37632
+ serveArgs: serviceServeArgs(input),
37633
+ dataDir: input.dataDir,
37634
+ user: input.user ?? currentUser(),
37635
+ home,
37636
+ bindsLowPorts: Boolean(input.domain),
37637
+ ...input.label ? { label: input.label } : {}
37638
+ };
37639
+ const rendered = platform === "darwin" ? launchdPlist(spec) : systemdUnit(spec);
37640
+ mkdirSync2(input.dataDir, { recursive: true, mode: 448 });
37641
+ writeFileSync3(plan.file, rendered, { mode: 420 });
37642
+ io.log(`wrote ${plan.file}`);
37643
+ io.log("");
37644
+ io.log(platform === "darwin" ? "to install and start it (runs at login):" : "to install and start it (runs at boot, restarts if it stops):");
37645
+ for (const line of plan.activate) io.log(` ${line}`);
37646
+ io.log("");
37647
+ if (input.domain && platform === "linux") io.log("the unit grants Caddy the capability for ports 80 and 443, so no setcap is needed under the service");
37648
+ io.log(`logs: ${platform === "darwin" ? `${input.dataDir}/logs/service.log` : "journalctl -u openmausbot -f"}`);
37649
+ io.log(`change options later by running \`openmausbot service install\` again with the new ones, then: ${platform === "darwin" ? plan.activate[1] : "sudo systemctl daemon-reload && sudo systemctl restart openmausbot"}`);
37650
+ return 0;
37651
+ }
37652
+
37114
37653
  // server/tailscale.ts
37115
37654
  import { execFile } from "node:child_process";
37116
37655
 
37117
37656
  // companion/src/listener.ts
37118
- import { homedir, networkInterfaces } from "node:os";
37119
- import { delimiter as delimiter2, join as join2 } from "node:path";
37120
- function tailscaleCandidates(home = homedir()) {
37657
+ import { homedir as homedir3, networkInterfaces } from "node:os";
37658
+ import { delimiter as delimiter2, join as join3 } from "node:path";
37659
+ function tailscaleCandidates(home = homedir3()) {
37121
37660
  return [
37122
37661
  "/Applications/Tailscale.app/Contents/MacOS/Tailscale",
37123
- join2(home, "Applications", "Tailscale.app", "Contents", "MacOS", "Tailscale"),
37662
+ join3(home, "Applications", "Tailscale.app", "Contents", "MacOS", "Tailscale"),
37124
37663
  "/opt/homebrew/bin/tailscale",
37125
37664
  "/usr/local/bin/tailscale",
37126
37665
  "/usr/bin/tailscale",
@@ -37161,14 +37700,14 @@ function explainTailscaleFailure(reason) {
37161
37700
  }
37162
37701
  }
37163
37702
  function run(cli, args, timeoutMs) {
37164
- return new Promise((resolve12) => {
37703
+ return new Promise((resolve13) => {
37165
37704
  execFile(
37166
37705
  cli,
37167
37706
  args,
37168
37707
  { timeout: timeoutMs, killSignal: "SIGKILL", maxBuffer: 16 * 1024 * 1024, env: { ...process.env, PATH: searchPath() } },
37169
37708
  (error51, stdout2, stderr) => {
37170
37709
  const code = error51 && "code" in error51 && typeof error51.code === "number" ? error51.code : error51 ? null : 0;
37171
- resolve12({ ok: !error51, stdout: String(stdout2 ?? ""), stderr: String(stderr ?? ""), code });
37710
+ resolve13({ ok: !error51, stdout: String(stdout2 ?? ""), stderr: String(stderr ?? ""), code });
37172
37711
  }
37173
37712
  );
37174
37713
  });
@@ -37333,9 +37872,9 @@ function phonePairingInstructions(phone, input) {
37333
37872
  // server/tunnel.ts
37334
37873
  import { spawn as spawn3 } from "node:child_process";
37335
37874
  import { randomUUID as randomUUID4 } from "node:crypto";
37336
- import { chmodSync as chmodSync2, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, statSync } from "node:fs";
37875
+ import { chmodSync as chmodSync2, existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync, statSync } from "node:fs";
37337
37876
  import { hostname } from "node:os";
37338
- import { dirname, join as join3, resolve as resolve2 } from "node:path";
37877
+ import { dirname as dirname2, join as join4, resolve as resolve2 } from "node:path";
37339
37878
  import { fileURLToPath } from "node:url";
37340
37879
 
37341
37880
  // electron/secure-credential-state.mjs
@@ -37408,7 +37947,7 @@ function createSecureCredentialState(initialCredentials, persist, { writable = t
37408
37947
 
37409
37948
  // server/tunnel.ts
37410
37949
  init_atomic();
37411
- var HERE = dirname(fileURLToPath(import.meta.url));
37950
+ var HERE = dirname2(fileURLToPath(import.meta.url));
37412
37951
  var TUNNEL_CREDENTIALS_FILE = "tunnel-account.json";
37413
37952
  var TUNNEL_RUNTIME_DIR = "tunnel-runtime";
37414
37953
  function plainDocument(value) {
@@ -37418,7 +37957,7 @@ function plainDocument(value) {
37418
37957
  return document;
37419
37958
  }
37420
37959
  function openTunnelCredentials(dataDir) {
37421
- const file2 = join3(dataDir, TUNNEL_CREDENTIALS_FILE);
37960
+ const file2 = join4(dataDir, TUNNEL_CREDENTIALS_FILE);
37422
37961
  let initial = {};
37423
37962
  let status = "empty";
37424
37963
  if (existsSync2(file2)) {
@@ -37433,7 +37972,7 @@ function openTunnelCredentials(dataDir) {
37433
37972
  }
37434
37973
  }
37435
37974
  const persist = (next) => {
37436
- mkdirSync2(dataDir, { recursive: true, mode: 448 });
37975
+ mkdirSync3(dataDir, { recursive: true, mode: 448 });
37437
37976
  writeFileAtomic(file2, `${JSON.stringify(next, null, 2)}
37438
37977
  `, { mode: 384 });
37439
37978
  };
@@ -37500,11 +38039,11 @@ function cloudflaredPath(dataDir, env = process.env) {
37500
38039
  return resolveCloudflaredBinary({ isPackaged: false, appPath: dataDir, environment: env });
37501
38040
  }
37502
38041
  function prepareEntry(here = HERE) {
37503
- const bundled = join3(here, "prepare-cloudflared.js");
38042
+ const bundled = join4(here, "prepare-cloudflared.js");
37504
38043
  return existsSync2(bundled) ? bundled : resolve2(here, "..", "scripts", "prepare-cloudflared.mjs");
37505
38044
  }
37506
38045
  function guardianEntry(here = HERE) {
37507
- const bundled = join3(here, "tunnel-guardian.js");
38046
+ const bundled = join4(here, "tunnel-guardian.js");
37508
38047
  if (existsSync2(bundled)) return bundled;
37509
38048
  const source = resolve2(here, "..", "electron", "managed-companion-guardian-main.mjs");
37510
38049
  return existsSync2(source) ? source : null;
@@ -37539,7 +38078,7 @@ function startTunnel(options) {
37539
38078
  const tunnel = createManagedCompanionTunnel({
37540
38079
  binaryPath: options.binaryPath,
37541
38080
  guardianEntry: options.guardian,
37542
- runtimeRoot: join3(options.dataDir, TUNNEL_RUNTIME_DIR),
38081
+ runtimeRoot: join4(options.dataDir, TUNNEL_RUNTIME_DIR),
37543
38082
  originPort,
37544
38083
  environment: env,
37545
38084
  fetchImpl: options.fetchImpl,
@@ -37574,8 +38113,8 @@ function describeTunnelState(state, address) {
37574
38113
  }
37575
38114
 
37576
38115
  // server/cli.ts
37577
- var HERE2 = dirname10(fileURLToPath3(import.meta.url));
37578
- var COMMANDS = ["setup", "start", "serve", "pair", "sessions", "status", "login", "logout", "access", "browser", "help", "--help", "-h"];
38116
+ var HERE2 = dirname11(fileURLToPath3(import.meta.url));
38117
+ var COMMANDS = ["setup", "start", "serve", "pair", "sessions", "status", "login", "logout", "access", "service", "browser", "help", "--help", "-h"];
37579
38118
  function parseArgs(argv, env = process.env) {
37580
38119
  const implicitStart = !argv.length || argv[0].startsWith("--") && argv[0] !== "--help";
37581
38120
  const [command = "start", ...rest] = implicitStart ? ["start", ...argv] : argv;
@@ -37585,7 +38124,7 @@ function parseArgs(argv, env = process.env) {
37585
38124
  const options = {
37586
38125
  command: command === "--help" || command === "-h" ? "help" : command,
37587
38126
  port: Number(env.OMB_PORT || 8799),
37588
- dataDir: env.OMB_DATA_DIR || join31(homedir21(), ".openmausbot"),
38127
+ dataDir: env.OMB_DATA_DIR || join33(homedir24(), ".openmausbot"),
37589
38128
  tailscale: false,
37590
38129
  tunnel: false,
37591
38130
  client: false,
@@ -37604,7 +38143,7 @@ function parseArgs(argv, env = process.env) {
37604
38143
  };
37605
38144
  try {
37606
38145
  if (arg === "--port") options.port = Number(value());
37607
- else if (arg === "--data-dir") options.dataDir = resolve11(value());
38146
+ else if (arg === "--data-dir") options.dataDir = resolve12(value());
37608
38147
  else if (arg === "--label") options.label = value();
37609
38148
  else if (arg === "--public-url") options.publicUrl = value().replace(/\/+$/, "");
37610
38149
  else if (arg === "--tailscale") options.tailscale = true;
@@ -37624,6 +38163,7 @@ function parseArgs(argv, env = process.env) {
37624
38163
  options.accessAction = arg;
37625
38164
  if (arg !== "list") options.email = value();
37626
38165
  } else if (options.command === "access" && arg === "--chat-only") options.chatOnly = true;
38166
+ else if (options.command === "service" && !options.serviceAction && (arg === "install" || arg === "uninstall")) options.serviceAction = arg;
37627
38167
  else if (options.command === "browser" && (arg === "install" || arg === "status")) options.browserAction = arg;
37628
38168
  else if (options.command === "browser" && arg === "--with-deps") options.withDeps = true;
37629
38169
  else return { error: `unknown argument "${arg}"` };
@@ -37635,6 +38175,7 @@ function parseArgs(argv, env = process.env) {
37635
38175
  if (options.publicUrl && !/^https?:\/\//.test(options.publicUrl)) return { error: "--public-url must start with http:// or https://" };
37636
38176
  if (options.tailscale && options.tunnel) return { error: "choose one of --tailscale (your tailnet) and --tunnel (a public address)" };
37637
38177
  if (options.command === "access" && !options.accessAction) return { error: "access needs one of: list, add EMAIL [--chat-only], remove EMAIL" };
38178
+ if (options.command === "service" && !options.serviceAction) return { error: "service needs one of: install [the same options as serve], uninstall" };
37638
38179
  if (options.domain && (options.tailscale || options.tunnel || options.publicUrl)) return { error: "--domain already gives the server its address; drop --tailscale, --tunnel and --public-url" };
37639
38180
  if (options.local && (options.tailscale || options.tunnel || options.publicUrl)) return { error: "--local cannot be combined with a remote-access option" };
37640
38181
  if (options.command === "browser" && !options.browserAction) return { error: "browser needs an action: install or status" };
@@ -37653,6 +38194,7 @@ var USAGE = `openmausbot \u2014 your team of AI bots, ready in a few steps
37653
38194
  openmausbot login [--email you@example.com]
37654
38195
  openmausbot logout
37655
38196
  openmausbot access list | add EMAIL [--chat-only] | remove EMAIL
38197
+ openmausbot service install [--domain HOST | --tunnel | --tailscale] [--port N] [--data-dir DIR] | uninstall
37656
38198
  openmausbot browser install [--with-deps] | status
37657
38199
 
37658
38200
  setup choose AI access and optional phone access; keep existing bots and chats
@@ -37667,6 +38209,10 @@ logout releases that address and signs out
37667
38209
  access who may sign in with an emailed code at /pair: an address or
37668
38210
  @domain; --chat-only gives chat and approvals without settings.
37669
38211
  Takes effect at once, no restart.
38212
+ service keep the server running across reboots: writes a systemd unit
38213
+ (Linux) or a launchd agent (macOS) for the same serve options and
38214
+ prints the commands that install it. Install the package
38215
+ permanently first (npm install -g openmausbot).
37670
38216
  browser install: the bots' browser engine (agent-browser, pinned) into the
37671
38217
  data dir, and Chrome for Testing into the user's browser cache.
37672
38218
  --with-deps also installs
@@ -37708,7 +38254,7 @@ function defaultIo() {
37708
38254
  }
37709
38255
  function serverVersion(here = HERE2) {
37710
38256
  try {
37711
- const parsed = JSON.parse(readFileSync20(resolve11(here, "..", "package.json"), "utf8"));
38257
+ const parsed = JSON.parse(readFileSync20(resolve12(here, "..", "package.json"), "utf8"));
37712
38258
  const version2 = typeof parsed === "object" && parsed !== null ? Reflect.get(parsed, "version") : void 0;
37713
38259
  return typeof version2 === "string" && version2 ? version2 : "0.0.0";
37714
38260
  } catch {
@@ -37733,7 +38279,7 @@ async function isWorkspaceRunning(options) {
37733
38279
  try {
37734
38280
  const { status, body } = await api(options.port, "/api/health");
37735
38281
  if (status !== 200 || body?.app !== "openmausbot") return false;
37736
- const expected = readFileSync20(join31(options.dataDir, "environment-id"), "utf8").trim();
38282
+ const expected = readFileSync20(join33(options.dataDir, "environment-id"), "utf8").trim();
37737
38283
  const descriptor = await api(options.port, "/.well-known/openmausbot/environment");
37738
38284
  return /^[0-9a-f-]{36}$/i.test(expected) && descriptor.status === 200 && descriptor.body?.environmentId === expected;
37739
38285
  } catch {
@@ -37933,7 +38479,7 @@ async function runStatus(options, io = defaultIo()) {
37933
38479
  return code;
37934
38480
  }
37935
38481
  async function runAccess(options, io = defaultIo()) {
37936
- const file2 = join31(options.dataDir, "config.json");
38482
+ const file2 = join33(options.dataDir, "config.json");
37937
38483
  let raw = {};
37938
38484
  if (existsSync16(file2)) {
37939
38485
  try {
@@ -37951,7 +38497,7 @@ async function runAccess(options, io = defaultIo()) {
37951
38497
  const members = list(Reflect.get(current, "members"));
37952
38498
  const overridden = process.env.OMB_SIGNIN_EMAILS !== void 0 || process.env.OMB_SIGNIN_MEMBER_EMAILS !== void 0;
37953
38499
  const write = (next) => {
37954
- mkdirSync13(options.dataDir, { recursive: true, mode: 448 });
38500
+ mkdirSync14(options.dataDir, { recursive: true, mode: 448 });
37955
38501
  writeFileAtomic(file2, `${JSON.stringify({ ...raw, signIn: next }, null, 2)}
37956
38502
  `, { mode: 384 });
37957
38503
  };
@@ -38085,16 +38631,16 @@ async function runBrowser(options, io = defaultIo()) {
38085
38631
  return 0;
38086
38632
  }
38087
38633
  function serverEntry(here = HERE2) {
38088
- const bundled = join31(here, "index.js");
38089
- const root = resolve11(here, "..");
38634
+ const bundled = join33(here, "index.js");
38635
+ const root = resolve12(here, "..");
38090
38636
  if (existsSync16(bundled)) {
38091
- const staticDir2 = [join31(root, "dist"), join31(here, "..", "ui")].find((d) => existsSync16(join31(d, "index.html"))) ?? null;
38092
- const skillsDir = existsSync16(join31(root, "skills")) ? join31(root, "skills") : null;
38637
+ const staticDir2 = [join33(root, "dist"), join33(here, "..", "ui")].find((d) => existsSync16(join33(d, "index.html"))) ?? null;
38638
+ const skillsDir = existsSync16(join33(root, "skills")) ? join33(root, "skills") : null;
38093
38639
  return { command: process.execPath, args: [bundled], staticDir: staticDir2, skillsDir };
38094
38640
  }
38095
- const source = join31(here, "index.ts");
38096
- const staticDir = existsSync16(join31(root, "dist", "index.html")) ? join31(root, "dist") : null;
38097
- return { command: process.execPath, args: ["--experimental-strip-types", source], staticDir, skillsDir: existsSync16(join31(root, "skills")) ? join31(root, "skills") : null };
38641
+ const source = join33(here, "index.ts");
38642
+ const staticDir = existsSync16(join33(root, "dist", "index.html")) ? join33(root, "dist") : null;
38643
+ return { command: process.execPath, args: ["--experimental-strip-types", source], staticDir, skillsDir: existsSync16(join33(root, "skills")) ? join33(root, "skills") : null };
38098
38644
  }
38099
38645
  async function planTunnel(options, log) {
38100
38646
  let access2 = null;
@@ -38194,9 +38740,9 @@ async function runServe(options, log = console.log) {
38194
38740
  let child;
38195
38741
  try {
38196
38742
  if (options.guided) {
38197
- const logsDir = join31(options.dataDir, "logs");
38198
- mkdirSync13(logsDir, { recursive: true, mode: 448 });
38199
- logPath = join31(logsDir, `server-${Date.now()}-${process.pid}.log`);
38743
+ const logsDir = join33(options.dataDir, "logs");
38744
+ mkdirSync14(logsDir, { recursive: true, mode: 448 });
38745
+ logPath = join33(logsDir, `server-${Date.now()}-${process.pid}.log`);
38200
38746
  logFd = openSync3(logPath, "wx", 384);
38201
38747
  log("\nStarting your workspace\u2026");
38202
38748
  }
@@ -38388,7 +38934,7 @@ async function runOnboardingCommand(options, io = defaultIo(), startServer = run
38388
38934
  }
38389
38935
  if (options.command === "setup") {
38390
38936
  io.log("\nAll set. Start with: openmausbot (or npx openmausbot without a global install).");
38391
- if (options.dataDir !== join31(homedir21(), ".openmausbot") || options.port !== 8799) {
38937
+ if (options.dataDir !== join33(homedir24(), ".openmausbot") || options.port !== 8799) {
38392
38938
  io.log(`Use the same --data-dir (${options.dataDir}) and --port (${options.port}) options when starting.`);
38393
38939
  }
38394
38940
  return 0;
@@ -38426,6 +38972,18 @@ ${USAGE}`);
38426
38972
  return runLogin(options);
38427
38973
  case "access":
38428
38974
  return runAccess(options);
38975
+ case "service":
38976
+ return runServiceCommand({
38977
+ action: options.serviceAction ?? "install",
38978
+ dataDir: options.dataDir,
38979
+ port: options.port,
38980
+ domain: options.domain,
38981
+ tunnel: options.tunnel,
38982
+ tailscale: options.tailscale,
38983
+ label: options.label,
38984
+ script: process.argv[1] ?? "",
38985
+ node: process.execPath
38986
+ }, { log: (line) => console.log(line), error: (line) => console.error(line) });
38429
38987
  case "logout":
38430
38988
  return runLogout(options);
38431
38989
  case "browser":