openmausbot 0.1.75 → 0.1.77

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 (66) hide show
  1. package/dist/assets/{index-BTlyRwLl.js → index-DeK-z9ld.js} +1 -1
  2. package/dist/assets/index-XdHJ-9CF.css +1 -0
  3. package/dist/assets/index-lhqE1umn.js +305 -0
  4. package/dist/index.html +2 -2
  5. package/dist-server/companion/src/routes.js +6 -2
  6. package/dist-server/container-mcp.js +5 -1
  7. package/dist-server/drivers/agents-proxy.js +184 -11
  8. package/dist-server/enterprise/server/index.js +387 -29
  9. package/dist-server/index.js +3786 -1135
  10. package/dist-server/mcp-gate.js +28 -18
  11. package/dist-server/openmausbot.js +927 -642
  12. package/dist-server/pair-cli.js +927 -642
  13. package/dist-server/server/agent-tool-policy.js +2 -0
  14. package/dist-server/server/atomic.js +37 -1
  15. package/dist-server/server/bot-overview.js +5 -1
  16. package/dist-server/server/browser-live.js +3 -0
  17. package/dist-server/server/chief-of-staff.js +17 -12
  18. package/dist-server/server/config.js +16 -0
  19. package/dist-server/server/delegations.js +177 -63
  20. package/dist-server/server/drivers/acp/core.js +3 -1
  21. package/dist-server/server/drivers/acp/hermes.js +21 -3
  22. package/dist-server/server/drivers/agents-proxy.js +169 -10
  23. package/dist-server/server/drivers/boxagent.js +1 -1
  24. package/dist-server/server/drivers/claude.js +34 -16
  25. package/dist-server/server/drivers/codex.js +3 -1
  26. package/dist-server/server/drivers/openai-chat.js +90 -67
  27. package/dist-server/server/drivers/openai-compat.js +9 -1
  28. package/dist-server/server/drivers/pi.js +22 -3
  29. package/dist-server/server/enterprise.js +38 -0
  30. package/dist-server/server/env-path.js +18 -17
  31. package/dist-server/server/environment.js +3 -0
  32. package/dist-server/server/fleet-agent.js +57 -13
  33. package/dist-server/server/fleet-cli.js +248 -31
  34. package/dist-server/server/fleet.js +88 -14
  35. package/dist-server/server/hosted-contract.js +16 -0
  36. package/dist-server/server/index.js +1777 -406
  37. package/dist-server/server/message-db.js +21 -0
  38. package/dist-server/server/message-file.js +4 -0
  39. package/dist-server/server/peer-roster.js +48 -9
  40. package/dist-server/server/procs.js +19 -3
  41. package/dist-server/server/provider-key-check.js +18 -1
  42. package/dist-server/server/request-auth.js +14 -5
  43. package/dist-server/server/room-handoffs.js +237 -0
  44. package/dist-server/server/section-context.js +64 -14
  45. package/dist-server/server/sessions.js +115 -8
  46. package/dist-server/server/shared-computer-control.js +24 -0
  47. package/dist-server/server/shared-computers.js +131 -0
  48. package/dist-server/server/steer-queue.js +2 -0
  49. package/dist-server/server/store.js +267 -15
  50. package/dist-server/server/surface.js +16 -5
  51. package/dist-server/server/team-backup.js +17 -1
  52. package/dist-server/server/team-computers.js +108 -0
  53. package/dist-server/server/team-setup-requests.js +252 -0
  54. package/dist-server/server/tool-summary.js +53 -1
  55. package/dist-server/server/turn-dispatch-guard.js +31 -3
  56. package/dist-server/server/workspace-backup.js +1 -0
  57. package/dist-server/shared/approval-mode.js +11 -1
  58. package/dist-server/shared/team-backup.js +1 -0
  59. package/dist-server/shared/team-computer.js +1 -0
  60. package/dist-server/shared/team-setup.js +1 -0
  61. package/dist-server/vps-container-mcp.js +5 -1
  62. package/enterprise/README.md +21 -3
  63. package/enterprise/server/index.js +387 -29
  64. package/package.json +1 -1
  65. package/dist/assets/index-CeMbvsgx.js +0 -305
  66. package/dist/assets/index-DH7Zax6L.css +0 -1
@@ -1113,6 +1113,21 @@ var require_main = __commonJS({
1113
1113
  // server/atomic.ts
1114
1114
  import { randomUUID as randomUUID3 } from "node:crypto";
1115
1115
  import { closeSync, fsyncSync, openSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
1116
+ function sleepSync(ms) {
1117
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
1118
+ }
1119
+ function renameWithRetry(tmp, path3, rename2 = renameSync) {
1120
+ for (let attempt = 0; ; attempt += 1) {
1121
+ try {
1122
+ rename2(tmp, path3);
1123
+ return;
1124
+ } catch (e) {
1125
+ const code = e.code;
1126
+ if (!code || !RETRYABLE_RENAME_CODES.has(code) || attempt >= RENAME_RETRY_DELAYS_MS.length) throw e;
1127
+ sleepSync(RENAME_RETRY_DELAYS_MS[attempt]);
1128
+ }
1129
+ }
1130
+ }
1116
1131
  function writeFileAtomic(path3, data, options = {}) {
1117
1132
  const tmp = `${path3}.${process.pid}.${randomUUID3()}.tmp`;
1118
1133
  let fd = null;
@@ -1122,7 +1137,7 @@ function writeFileAtomic(path3, data, options = {}) {
1122
1137
  fsyncSync(fd);
1123
1138
  closeSync(fd);
1124
1139
  fd = null;
1125
- renameSync(tmp, path3);
1140
+ renameWithRetry(tmp, path3);
1126
1141
  } catch (e) {
1127
1142
  if (fd !== null) {
1128
1143
  try {
@@ -1137,9 +1152,12 @@ function writeFileAtomic(path3, data, options = {}) {
1137
1152
  throw e;
1138
1153
  }
1139
1154
  }
1155
+ var RENAME_RETRY_DELAYS_MS, RETRYABLE_RENAME_CODES;
1140
1156
  var init_atomic = __esm({
1141
1157
  "server/atomic.ts"() {
1142
1158
  "use strict";
1159
+ RENAME_RETRY_DELAYS_MS = [5, 10, 20, 40, 80];
1160
+ RETRYABLE_RENAME_CODES = /* @__PURE__ */ new Set(["EPERM", "EACCES", "EBUSY"]);
1143
1161
  }
1144
1162
  });
1145
1163
 
@@ -17601,9 +17619,9 @@ var init_schema = __esm({
17601
17619
  });
17602
17620
 
17603
17621
  // server/config.ts
17604
- import { readFileSync as readFileSync4, mkdirSync as mkdirSync6, existsSync as existsSync5, renameSync as renameSync3 } from "node:fs";
17622
+ import { readFileSync as readFileSync3, mkdirSync as mkdirSync6, existsSync as existsSync5, renameSync as renameSync3 } from "node:fs";
17605
17623
  import { homedir as homedir4 } from "node:os";
17606
- import { join as join7 } from "node:path";
17624
+ import { join as join6 } from "node:path";
17607
17625
  function isValidSshAlias(value) {
17608
17626
  return typeof value === "string" && SSH_ALIAS.test(value);
17609
17627
  }
@@ -17736,7 +17754,7 @@ function parseStoredConfig(value) {
17736
17754
  function loadConfig() {
17737
17755
  let cfg = {};
17738
17756
  try {
17739
- cfg = parseStoredConfig(parseJson(readFileSync4(join7(DATA_DIR, "config.json"), "utf8")));
17757
+ cfg = parseStoredConfig(parseJson(readFileSync3(join6(DATA_DIR, "config.json"), "utf8")));
17740
17758
  } catch {
17741
17759
  }
17742
17760
  cfg.xai = { ...cfg.xai };
@@ -17772,10 +17790,10 @@ function stripWorkspaceCredentialEnv(env) {
17772
17790
  for (const key of WORKSPACE_CREDENTIAL_ENV) delete env[key];
17773
17791
  }
17774
17792
  function saveConfig(patch, options = {}) {
17775
- const p = join7(DATA_DIR, "config.json");
17793
+ const p = join6(DATA_DIR, "config.json");
17776
17794
  let disk = {};
17777
17795
  try {
17778
- const parsed = jsonObjectSchema.safeParse(parseJson(readFileSync4(p, "utf8")));
17796
+ const parsed = jsonObjectSchema.safeParse(parseJson(readFileSync3(p, "utf8")));
17779
17797
  if (parsed.success) disk = parsed.data;
17780
17798
  } catch {
17781
17799
  }
@@ -17977,7 +17995,11 @@ var init_config = __esm({
17977
17995
  showToolCalls: external_exports.boolean().optional(),
17978
17996
  /** Experimental built-in browser. Off until explicitly enabled; each bot
17979
17997
  * also has its own switch. */
17980
- browser: external_exports.boolean().optional()
17998
+ browser: external_exports.boolean().optional(),
17999
+ /** Opt-in computer sharing (a desktop lending folders, a terminal or
18000
+ * computer control to a workspace). Off until explicitly enabled; there is
18001
+ * no Settings toggle — see sharedComputersEnabled. */
18002
+ sharedComputers: external_exports.boolean().optional()
17981
18003
  });
17982
18004
  onboardingConfigSchema = external_exports.object({
17983
18005
  /** ISO timestamp of finishing (or skipping to the end of) the welcome flow. */
@@ -18094,10 +18116,10 @@ var init_config = __esm({
18094
18116
  });
18095
18117
  appConfigPatchSchema = appConfigSchema.omit({ instances: true, mcpServers: true, cliStartup: true, customDomain: true });
18096
18118
  jsonObjectSchema = external_exports.record(external_exports.string(), external_exports.json());
18097
- DATA_DIR = process.env.OMB_DATA_DIR ?? join7(homedir4(), ".openmausbot");
18098
- LEGACY_DATA_DIR = join7(homedir4(), ".opengrokbot");
18099
- EVENTS_DIR = join7(DATA_DIR, "events");
18100
- NATIVE_DIR = join7(DATA_DIR, "native");
18119
+ DATA_DIR = process.env.OMB_DATA_DIR ?? join6(homedir4(), ".openmausbot");
18120
+ LEGACY_DATA_DIR = join6(homedir4(), ".opengrokbot");
18121
+ EVENTS_DIR = join6(DATA_DIR, "events");
18122
+ NATIVE_DIR = join6(DATA_DIR, "native");
18101
18123
  WORKSPACE_CREDENTIAL_ENV = [
18102
18124
  "XAI_API_KEY",
18103
18125
  "OMB_ANTHROPIC_API_KEY",
@@ -18135,11 +18157,11 @@ var init_config = __esm({
18135
18157
  });
18136
18158
 
18137
18159
  // electron/data-dir-lease.mjs
18138
- import { execFileSync } from "node:child_process";
18160
+ import { execFileSync as execFileSync2 } from "node:child_process";
18139
18161
  import { createHash as createHash2, randomUUID as randomUUID5 } from "node:crypto";
18140
- import { existsSync as existsSync6, linkSync, mkdirSync as mkdirSync7, readFileSync as readFileSync5, renameSync as renameSync4, unlinkSync as unlinkSync2, writeFileSync as writeFileSync5 } from "node:fs";
18162
+ import { existsSync as existsSync6, linkSync, mkdirSync as mkdirSync7, readFileSync as readFileSync4, renameSync as renameSync4, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "node:fs";
18141
18163
  import { hostname as hostname4, uptime } from "node:os";
18142
- import { join as join8 } from "node:path";
18164
+ import { join as join7 } from "node:path";
18143
18165
  function leaseError(message2, cause) {
18144
18166
  return Object.assign(new DataDirLeaseError(message2), { cause });
18145
18167
  }
@@ -18149,10 +18171,10 @@ function isPid(value) {
18149
18171
  function readBootSession() {
18150
18172
  try {
18151
18173
  if (process.platform === "linux") {
18152
- return normalizeBootId(readFileSync5("/proc/sys/kernel/random/boot_id", "utf8"));
18174
+ return normalizeBootId(readFileSync4("/proc/sys/kernel/random/boot_id", "utf8"));
18153
18175
  }
18154
18176
  if (process.platform === "darwin") {
18155
- return normalizeBootId(execFileSync("/usr/sbin/sysctl", ["-n", "kern.bootsessionuuid"], {
18177
+ return normalizeBootId(execFileSync2("/usr/sbin/sysctl", ["-n", "kern.bootsessionuuid"], {
18156
18178
  encoding: "utf8",
18157
18179
  timeout: 5e3,
18158
18180
  stdio: ["ignore", "pipe", "ignore"]
@@ -18196,7 +18218,7 @@ function isReaperOwner(value, targetToken) {
18196
18218
  function parseRecord(path3, invalidMessage, validate) {
18197
18219
  let raw;
18198
18220
  try {
18199
- raw = readFileSync5(path3, "utf8");
18221
+ raw = readFileSync4(path3, "utf8");
18200
18222
  } catch (error51) {
18201
18223
  if (error51?.code === "ENOENT") return null;
18202
18224
  throw leaseError("OpenMausBot cannot read the data-directory lease; refusing to start to protect its state.", error51);
@@ -18244,7 +18266,7 @@ function unlinkExact(path3, message2) {
18244
18266
  function publishRecord(path3, record3, prepareMessage, acquireMessage) {
18245
18267
  const candidatePath = `${path3}.candidate-${record3.pid}-${record3.token}`;
18246
18268
  try {
18247
- writeFileSync5(candidatePath, `${JSON.stringify(record3)}
18269
+ writeFileSync4(candidatePath, `${JSON.stringify(record3)}
18248
18270
  `, { flag: "wx", mode: 384, flush: true });
18249
18271
  } catch (error51) {
18250
18272
  throw leaseError(prepareMessage, error51);
@@ -18331,10 +18353,10 @@ function prepareDataDir(dataDir, legacyDataDir) {
18331
18353
  } catch (error51) {
18332
18354
  throw leaseError("OpenMausBot cannot create its data directory.", error51);
18333
18355
  }
18334
- return join8(dataDir, LEASE_NAME);
18356
+ return join7(dataDir, LEASE_NAME);
18335
18357
  }
18336
18358
  function assertNoLiveDelegatedChild(dataDir) {
18337
- const childLeasePath = join8(dataDir, DELEGATED_CHILD_DIR, LEASE_NAME);
18359
+ const childLeasePath = join7(dataDir, DELEGATED_CHILD_DIR, LEASE_NAME);
18338
18360
  const child = readOwner(childLeasePath);
18339
18361
  if (!child) return;
18340
18362
  if (child.host !== hostname4()) {
@@ -18365,7 +18387,7 @@ function acquireDataDirLeaseInternal(dataDir, options = {}) {
18365
18387
  };
18366
18388
  const candidatePath = `${leasePath}.candidate-${owner.pid}-${owner.token}`;
18367
18389
  try {
18368
- writeFileSync5(candidatePath, `${JSON.stringify(owner)}
18390
+ writeFileSync4(candidatePath, `${JSON.stringify(owner)}
18369
18391
  `, { flag: "wx", mode: 384, flush: true });
18370
18392
  } catch (error51) {
18371
18393
  throw leaseError("OpenMausBot cannot prepare its data-directory lease.", error51);
@@ -18457,13 +18479,13 @@ var init_data_dir_lease2 = __esm({
18457
18479
 
18458
18480
  // server/env-path.ts
18459
18481
  import { execFile as execFile2 } from "node:child_process";
18460
- import { closeSync as closeSync2, existsSync as existsSync7, openSync as openSync2, readFileSync as readFileSync6, readSync, statSync as statSync2, readdirSync } from "node:fs";
18482
+ import { closeSync as closeSync3, existsSync as existsSync7, openSync as openSync3, readFileSync as readFileSync5, readSync, statSync as statSync2, readdirSync } from "node:fs";
18461
18483
  import { homedir as homedir5 } from "node:os";
18462
- import { basename as basename2, delimiter as delimiter3, dirname as dirname4, extname, join as join9 } from "node:path";
18484
+ import { basename as basename2, delimiter as delimiter3, dirname as dirname4, extname, join as join8 } from "node:path";
18463
18485
  function nvmBinDirs() {
18464
18486
  try {
18465
- const base2 = join9(homedir5(), ".nvm", "versions", "node");
18466
- return readdirSync(base2).filter((v) => v.startsWith("v")).sort((a2, b) => b.localeCompare(a2, void 0, { numeric: true })).map((v) => join9(base2, v, "bin"));
18487
+ const base2 = join8(homedir5(), ".nvm", "versions", "node");
18488
+ return readdirSync(base2).filter((v) => v.startsWith("v")).sort((a2, b) => b.localeCompare(a2, void 0, { numeric: true })).map((v) => join8(base2, v, "bin"));
18467
18489
  } catch {
18468
18490
  return [];
18469
18491
  }
@@ -18471,49 +18493,49 @@ function nvmBinDirs() {
18471
18493
  function knownDirs() {
18472
18494
  const home = homedir5();
18473
18495
  return [
18474
- join9(home, ".local", "bin"),
18496
+ join8(home, ".local", "bin"),
18475
18497
  // claude installer default
18476
- join9(home, ".npm-global", "bin"),
18498
+ join8(home, ".npm-global", "bin"),
18477
18499
  // npm prefix ~/.npm-global (claude, opencode)
18478
- join9(home, ".kimi-code", "bin"),
18500
+ join8(home, ".kimi-code", "bin"),
18479
18501
  // kimi-code installer
18480
- join9(home, ".grok", "bin"),
18502
+ join8(home, ".grok", "bin"),
18481
18503
  // x.ai installer
18482
- join9(home, ".opencode", "bin"),
18504
+ join8(home, ".opencode", "bin"),
18483
18505
  // opencode installer
18484
- join9(home, ".claude", "local"),
18506
+ join8(home, ".claude", "local"),
18485
18507
  // claude "local install"
18486
18508
  "/opt/homebrew/bin",
18487
18509
  // brew, Apple silicon
18488
18510
  "/usr/local/bin",
18489
18511
  // brew Intel / classic installs
18490
- join9(home, ".volta", "bin"),
18491
- join9(home, ".bun", "bin"),
18492
- join9(home, ".asdf", "shims"),
18493
- join9(home, ".deno", "bin"),
18494
- join9(home, "bin"),
18512
+ join8(home, ".volta", "bin"),
18513
+ join8(home, ".bun", "bin"),
18514
+ join8(home, ".asdf", "shims"),
18515
+ join8(home, ".deno", "bin"),
18516
+ join8(home, "bin"),
18495
18517
  ...nvmBinDirs()
18496
18518
  ];
18497
18519
  }
18498
18520
  function windowsKnownDirs() {
18499
18521
  const home = homedir5();
18500
- const appData = process.env.APPDATA ?? join9(home, "AppData", "Roaming");
18501
- const localAppData = process.env.LOCALAPPDATA ?? join9(home, "AppData", "Local");
18522
+ const appData = process.env.APPDATA ?? join8(home, "AppData", "Roaming");
18523
+ const localAppData = process.env.LOCALAPPDATA ?? join8(home, "AppData", "Local");
18502
18524
  return [
18503
- join9(appData, "npm"),
18525
+ join8(appData, "npm"),
18504
18526
  // npm -g shims: claude, codex
18505
- join9(home, ".grok", "bin"),
18527
+ join8(home, ".grok", "bin"),
18506
18528
  // x.ai installer
18507
- join9(localAppData, "agy", "bin"),
18529
+ join8(localAppData, "agy", "bin"),
18508
18530
  // Antigravity installer
18509
- join9(home, ".local", "bin"),
18531
+ join8(home, ".local", "bin"),
18510
18532
  // claude native installer
18511
- join9(home, ".claude", "local"),
18512
- join9(home, "bin"),
18533
+ join8(home, ".claude", "local"),
18534
+ join8(home, "bin"),
18513
18535
  // Factory droid installer (%USERPROFILE%\bin)
18514
- join9(home, ".bun", "bin"),
18515
- join9(home, ".deno", "bin"),
18516
- join9(home, "go", "bin")
18536
+ join8(home, ".bun", "bin"),
18537
+ join8(home, ".deno", "bin"),
18538
+ join8(home, "go", "bin")
18517
18539
  ];
18518
18540
  }
18519
18541
  function registerPathDir(dir) {
@@ -18573,7 +18595,7 @@ function findCliCandidates(name) {
18573
18595
  for (const dir of augmentedPath().split(delimiter3)) {
18574
18596
  if (!dir) continue;
18575
18597
  for (const ext of exts) {
18576
- const p = join9(dir, name + ext);
18598
+ const p = join8(dir, name + ext);
18577
18599
  if (existsSync7(p)) {
18578
18600
  out.push(p);
18579
18601
  break;
@@ -18609,49 +18631,50 @@ function isFile(p) {
18609
18631
  return false;
18610
18632
  }
18611
18633
  }
18612
- function whichWin(cli) {
18613
- const exts = (process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean);
18634
+ function whichWin(cli, env) {
18635
+ const exts = ((env ?? process.env).PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean);
18614
18636
  const probe = (base2) => {
18615
18637
  const order = extname(base2) ? [base2, ...exts.map((e) => base2 + e)] : [...exts.map((e) => base2 + e), base2];
18616
18638
  return order.find(isFile) ?? null;
18617
18639
  };
18618
18640
  if (/[\\/]/.test(cli) || /^[a-zA-Z]:/.test(cli)) return probe(cli);
18619
- for (const dir of augmentedPath().split(delimiter3)) {
18641
+ const path3 = env ? env.PATH ?? env.Path ?? "" : augmentedPath();
18642
+ for (const dir of path3.split(delimiter3)) {
18620
18643
  if (!dir) continue;
18621
- const hit = probe(join9(dir, cli));
18644
+ const hit = probe(join8(dir, cli));
18622
18645
  if (hit) return hit;
18623
18646
  }
18624
18647
  return null;
18625
18648
  }
18626
- function nodeExe(near) {
18627
- const local = join9(near, "node.exe");
18649
+ function nodeExe(near, env) {
18650
+ const local = join8(near, "node.exe");
18628
18651
  if (isFile(local)) return local;
18629
- const onPath2 = whichWin("node.exe");
18652
+ const onPath2 = whichWin("node.exe", env);
18630
18653
  if (onPath2 && extname(onPath2).toLowerCase() === ".exe") return onPath2;
18631
18654
  return process.versions.electron ? null : process.execPath;
18632
18655
  }
18633
- function parseCmdShim(shim) {
18656
+ function parseCmdShim(shim, env) {
18634
18657
  let text4;
18635
18658
  try {
18636
- text4 = readFileSync6(shim, "utf8");
18659
+ text4 = readFileSync5(shim, "utf8");
18637
18660
  } catch {
18638
18661
  return null;
18639
18662
  }
18640
18663
  const dir = dirname4(shim);
18641
- const targets = [...text4.matchAll(/"%~?dp0%?\\?([^"]+)"/g)].map((m) => join9(dir, m[1])).filter((p) => isFile(p) && basename2(p).toLowerCase() !== "node.exe");
18664
+ const targets = [...text4.matchAll(/"%~?dp0%?\\?([^"]+)"/g)].map((m) => join8(dir, m[1])).filter((p) => isFile(p) && basename2(p).toLowerCase() !== "node.exe");
18642
18665
  const script = targets.find((p) => /\.[cm]?js$/i.test(p));
18643
18666
  if (script) {
18644
- const node = nodeExe(dir);
18667
+ const node = nodeExe(dir, env);
18645
18668
  if (node) return { command: node, args: [script] };
18646
18669
  }
18647
18670
  const exe = targets.find((p) => extname(p).toLowerCase() === ".exe");
18648
18671
  return exe ? { command: exe, args: [] } : null;
18649
18672
  }
18650
- function parseNodeShebang(file2) {
18673
+ function parseNodeShebang(file2, env) {
18651
18674
  let head = "";
18652
18675
  let fd = null;
18653
18676
  try {
18654
- fd = openSync2(file2, "r");
18677
+ fd = openSync3(file2, "r");
18655
18678
  const buf = Buffer.alloc(128);
18656
18679
  const n3 = readSync(fd, buf, 0, buf.length, 0);
18657
18680
  head = buf.subarray(0, n3).toString("utf8").split("\n", 1)[0];
@@ -18660,38 +18683,38 @@ function parseNodeShebang(file2) {
18660
18683
  } finally {
18661
18684
  if (fd !== null) {
18662
18685
  try {
18663
- closeSync2(fd);
18686
+ closeSync3(fd);
18664
18687
  } catch {
18665
18688
  }
18666
18689
  }
18667
18690
  }
18668
18691
  if (!/^#!.*\bnode(\.exe)?\b/.test(head)) return null;
18669
- const node = nodeExe(dirname4(file2));
18692
+ const node = nodeExe(dirname4(file2), env);
18670
18693
  return node ? { command: node, args: [file2] } : null;
18671
18694
  }
18672
- function resolveWord(cli, args) {
18695
+ function resolveWord(cli, args, env) {
18673
18696
  if (process.platform !== "win32") return { command: cli, args };
18674
- const file2 = whichWin(cli);
18697
+ const file2 = whichWin(cli, env);
18675
18698
  if (!file2) return { command: cli, args };
18676
18699
  const ext = extname(file2).toLowerCase();
18677
18700
  if (ext === ".cmd" || ext === ".bat") {
18678
- const direct = parseCmdShim(file2);
18701
+ const direct = parseCmdShim(file2, env);
18679
18702
  return direct ? { command: direct.command, args: [...direct.args, ...args] } : { command: file2, args };
18680
18703
  }
18681
18704
  if (ext === ".exe" || ext === ".com") return { command: file2, args };
18682
- const viaNode = parseNodeShebang(file2);
18705
+ const viaNode = parseNodeShebang(file2, env);
18683
18706
  return viaNode ? { command: viaNode.command, args: [...viaNode.args, ...args] } : { command: file2, args };
18684
18707
  }
18685
- function resolveCliSpawn(cli, args) {
18708
+ function resolveCliSpawn(cli, args, env) {
18686
18709
  const trimmed = cli.trim();
18687
- if (trimmed.includes(" ") && existsSync7(trimmed)) return resolveWord(trimmed, args);
18710
+ if (trimmed.includes(" ") && existsSync7(trimmed)) return resolveWord(trimmed, args, env);
18688
18711
  const split = splitCliString(cli);
18689
18712
  if (split.length > 1 || split[0] !== trimmed) {
18690
18713
  const [head, ...fixed] = split;
18691
18714
  if (!head) return { command: cli, args };
18692
- return resolveWord(head, [...fixed, ...args]);
18715
+ return resolveWord(head, [...fixed, ...args], env);
18693
18716
  }
18694
- return resolveWord(cli, args);
18717
+ return resolveWord(cli, args, env);
18695
18718
  }
18696
18719
  var cached2, probed, loginShellPath, registeredDirs;
18697
18720
  var init_env_path = __esm({
@@ -18709,9 +18732,9 @@ import {
18709
18732
  spawn as spawn5,
18710
18733
  execFile as execFile3
18711
18734
  } from "node:child_process";
18712
- import { join as join10 } from "node:path";
18713
- function resolveCli(cli, args = []) {
18714
- return resolveCliSpawn(cli, args);
18735
+ import { join as join9 } from "node:path";
18736
+ function resolveCli(cli, args = [], env) {
18737
+ return resolveCliSpawn(cli, args, env);
18715
18738
  }
18716
18739
  function estimatedWindowsCommandLineChars(resolved) {
18717
18740
  return [resolved.command, ...resolved.args].reduce(
@@ -18825,9 +18848,13 @@ async function stopCliTree(child, pid, timeoutMs) {
18825
18848
  if (exited()) return true;
18826
18849
  return new Promise((resolve13) => {
18827
18850
  let timer;
18851
+ let finished = false;
18828
18852
  const done = (stopped) => {
18853
+ if (finished) return;
18854
+ finished = true;
18829
18855
  clearTimeout(timer);
18830
18856
  child.off("close", closed);
18857
+ child.off("exit", closed);
18831
18858
  resolve13(stopped);
18832
18859
  };
18833
18860
  const closed = () => done(true);
@@ -18835,7 +18862,12 @@ async function stopCliTree(child, pid, timeoutMs) {
18835
18862
  timer = setTimeout(() => done(false), timeoutMs);
18836
18863
  timer.unref?.();
18837
18864
  execFile3("taskkill", ["/PID", String(pid), "/T", "/F"], { windowsHide: true }, (err) => {
18838
- if (!err) return;
18865
+ if (finished) return;
18866
+ if (!err) {
18867
+ if (exited()) done(true);
18868
+ else child.once("exit", closed);
18869
+ return;
18870
+ }
18839
18871
  try {
18840
18872
  child.kill();
18841
18873
  } catch {
@@ -18844,7 +18876,7 @@ async function stopCliTree(child, pid, timeoutMs) {
18844
18876
  });
18845
18877
  }
18846
18878
  function brokerSocketPath(dataDir, tag) {
18847
- return process.platform === "win32" ? `\\\\.\\pipe\\openmausbot-perm-${process.pid}-${tag}` : join10(dataDir, `perm-${tag}.sock`);
18879
+ return process.platform === "win32" ? `\\\\.\\pipe\\openmausbot-perm-${process.pid}-${tag}` : join9(dataDir, `perm-${tag}.sock`);
18848
18880
  }
18849
18881
  var WINDOWS_SAFE_COMMAND_LINE_CHARS, cliGroups, stopping;
18850
18882
  var init_procs = __esm({
@@ -18859,13 +18891,13 @@ var init_procs = __esm({
18859
18891
 
18860
18892
  // server/engine-install.ts
18861
18893
  import { existsSync as existsSync8, mkdirSync as mkdirSync8 } from "node:fs";
18862
- import { join as join11 } from "node:path";
18894
+ import { join as join10 } from "node:path";
18863
18895
  import { stripVTControlCharacters as stripVTControlCharacters3 } from "node:util";
18864
18896
  function enginesPrefix(baseDir = DATA_DIR) {
18865
- return join11(baseDir, "tools", "npm");
18897
+ return join10(baseDir, "tools", "npm");
18866
18898
  }
18867
18899
  function enginesBinDir(baseDir = DATA_DIR, platform = process.platform) {
18868
- return platform === "win32" ? enginesPrefix(baseDir) : join11(enginesPrefix(baseDir), "bin");
18900
+ return platform === "win32" ? enginesPrefix(baseDir) : join10(enginesPrefix(baseDir), "bin");
18869
18901
  }
18870
18902
  function npmPackageOf(install) {
18871
18903
  const line = install?.command?.linux ?? install?.command?.darwin ?? install?.command?.win32;
@@ -18915,9 +18947,9 @@ async function installOnce(pkg, options) {
18915
18947
  }
18916
18948
  }
18917
18949
  function tail(output) {
18918
- const clean3 = stripVTControlCharacters3(output).trim();
18919
- if (!clean3) return "";
18920
- const lines = clean3.split(/\r?\n/).filter((line) => line.trim()).slice(-6);
18950
+ const clean = stripVTControlCharacters3(output).trim();
18951
+ if (!clean) return "";
18952
+ const lines = clean.split(/\r?\n/).filter((line) => line.trim()).slice(-6);
18921
18953
  return `
18922
18954
  ${lines.join("\n").slice(-600)}`;
18923
18955
  }
@@ -19211,9 +19243,9 @@ var init_registry = __esm({
19211
19243
  });
19212
19244
 
19213
19245
  // server/drivers/local-inject.ts
19214
- import { readFileSync as readFileSync7 } from "node:fs";
19246
+ import { readFileSync as readFileSync6 } from "node:fs";
19215
19247
  import { homedir as homedir6 } from "node:os";
19216
- import { join as join12 } from "node:path";
19248
+ import { join as join11 } from "node:path";
19217
19249
  function contextWindowsFromPs(extra) {
19218
19250
  const out = /* @__PURE__ */ new Map();
19219
19251
  const rec = extra && typeof extra === "object" ? extra : null;
@@ -19298,7 +19330,7 @@ function firstUnslothToken(row) {
19298
19330
  function readUnslothKey(env) {
19299
19331
  const home = env.HOME || env.USERPROFILE || homedir6();
19300
19332
  try {
19301
- const raw = JSON.parse(readFileSync7(join12(home, ".unsloth", "studio", "auth", "agent_api_key.json"), "utf8"));
19333
+ const raw = JSON.parse(readFileSync6(join11(home, ".unsloth", "studio", "auth", "agent_api_key.json"), "utf8"));
19302
19334
  if (raw.servers && typeof raw.servers === "object") {
19303
19335
  const servers = raw.servers;
19304
19336
  for (const url2 of ["http://127.0.0.1:8888", "http://localhost:8888"]) {
@@ -19348,7 +19380,7 @@ async function timedJson(url2, env, host, fetchImpl) {
19348
19380
  function loadedIdsFromPayloads(_host, catalog, extra) {
19349
19381
  const loaded = /* @__PURE__ */ new Set();
19350
19382
  const catalogIds = new Set(idsFromModelsPayload(catalog));
19351
- const add2 = (id) => {
19383
+ const add = (id) => {
19352
19384
  const base2 = id.split(":")[0];
19353
19385
  if (catalogIds.size && !catalogIds.has(id) && !catalogIds.has(base2)) return;
19354
19386
  if (!MODEL_ID.test(id)) return;
@@ -19361,10 +19393,10 @@ function loadedIdsFromPayloads(_host, catalog, extra) {
19361
19393
  const hasLoadedFlags = running2.some(
19362
19394
  (row) => row && typeof row === "object" && ("loaded" in row || "state" in row)
19363
19395
  );
19364
- if (!hasLoadedFlags && typeof rec.default_model === "string") add2(rec.default_model);
19396
+ if (!hasLoadedFlags && typeof rec.default_model === "string") add(rec.default_model);
19365
19397
  for (const row of running2) {
19366
19398
  if (typeof row === "string") {
19367
- if (!hasLoadedFlags) add2(row);
19399
+ if (!hasLoadedFlags) add(row);
19368
19400
  continue;
19369
19401
  }
19370
19402
  if (!row || typeof row !== "object") continue;
@@ -19374,20 +19406,20 @@ function loadedIdsFromPayloads(_host, catalog, extra) {
19374
19406
  const state = typeof item.state === "string" ? item.state.toLowerCase() : "";
19375
19407
  if (item.loaded === false || state === "not-loaded" || state === "unloaded") continue;
19376
19408
  if (item.loaded === true || state === "loaded" || state === "idle" || !hasLoadedFlags) {
19377
- add2(id);
19409
+ add(id);
19378
19410
  }
19379
19411
  }
19380
19412
  }
19381
19413
  if (!loaded.size && catalog && typeof catalog === "object") {
19382
19414
  const rec = catalog;
19383
- if (typeof rec.default_model === "string") add2(rec.default_model);
19415
+ if (typeof rec.default_model === "string") add(rec.default_model);
19384
19416
  const records = Array.isArray(rec.data) ? rec.data : [];
19385
19417
  for (const row of records) {
19386
19418
  if (!row || typeof row !== "object") continue;
19387
19419
  const item = row;
19388
19420
  if (typeof item.id !== "string") continue;
19389
19421
  const state = typeof item.state === "string" ? item.state.toLowerCase() : "";
19390
- if (item.loaded === true || state === "loaded") add2(item.id);
19422
+ if (item.loaded === true || state === "loaded") add(item.id);
19391
19423
  }
19392
19424
  }
19393
19425
  return loaded;
@@ -19500,11 +19532,11 @@ var init_local_inject = __esm({
19500
19532
 
19501
19533
  // server/proxy-paths.ts
19502
19534
  import { existsSync as existsSync9 } from "node:fs";
19503
- import { dirname as dirname5, join as join13 } from "node:path";
19535
+ import { dirname as dirname5, join as join12 } from "node:path";
19504
19536
  import { fileURLToPath as fileURLToPath2 } from "node:url";
19505
19537
  function resolveProxy(relative2) {
19506
- const source = join13(SERVER_ROOT, `${relative2}.ts`);
19507
- return existsSync9(source) ? source : join13(SERVER_ROOT, `${relative2}.js`);
19538
+ const source = join12(SERVER_ROOT, `${relative2}.ts`);
19539
+ return existsSync9(source) ? source : join12(SERVER_ROOT, `${relative2}.js`);
19508
19540
  }
19509
19541
  var SERVER_ROOT, SPAWNED_PROXIES;
19510
19542
  var init_proxy_paths = __esm({
@@ -19533,7 +19565,7 @@ var init_proxy_paths = __esm({
19533
19565
 
19534
19566
  // server/container-computer.ts
19535
19567
  import { execFile as execFile4 } from "node:child_process";
19536
- import { join as join14, resolve as resolve3 } from "node:path";
19568
+ import { join as join13, resolve as resolve3 } from "node:path";
19537
19569
  import { promisify } from "node:util";
19538
19570
  function computerProxyEnv(computer) {
19539
19571
  return {
@@ -19557,7 +19589,7 @@ var init_container_computer = __esm({
19557
19589
  IMAGE_REPOSITORY = "localhost/openmausbot/cua-local-vm";
19558
19590
  IMAGE_LAYER_VERSION = "5";
19559
19591
  IMAGE = `${IMAGE_REPOSITORY}:driver-${CUA_DRIVER_VERSION}-v${IMAGE_LAYER_VERSION}`;
19560
- VM_WORKSPACE_DIR = join14(DATA_DIR, "vm-home");
19592
+ VM_WORKSPACE_DIR = join13(DATA_DIR, "vm-home");
19561
19593
  MEMORY_BYTES = 4 * 1024 * 1024 * 1024;
19562
19594
  SHM_BYTES = 512 * 1024 * 1024;
19563
19595
  containerMcpPath = SPAWNED_PROXIES.containerMcp;
@@ -19661,11 +19693,11 @@ var init_redact = __esm({
19661
19693
 
19662
19694
  // server/drivers/native.ts
19663
19695
  import { appendFileSync as appendFileSync3 } from "node:fs";
19664
- import { join as join15 } from "node:path";
19696
+ import { join as join14 } from "node:path";
19665
19697
  function appendNative(threadId, entry) {
19666
19698
  try {
19667
19699
  appendFileSync3(
19668
- join15(NATIVE_DIR, `${threadId}.ndjson`),
19700
+ join14(NATIVE_DIR, `${threadId}.ndjson`),
19669
19701
  JSON.stringify({ at: (/* @__PURE__ */ new Date()).toISOString(), ...entry, msg: redactSecrets(entry.msg) }) + "\n",
19670
19702
  { mode: 384 }
19671
19703
  );
@@ -19681,6 +19713,41 @@ var init_native = __esm({
19681
19713
  });
19682
19714
 
19683
19715
  // server/tool-summary.ts
19716
+ function toolDetailPreview(value) {
19717
+ if (value === void 0) return void 0;
19718
+ let budget = 200;
19719
+ let textBudget = 256e3;
19720
+ const bounded = (item, depth = 0) => {
19721
+ if (--budget < 0 || depth > 6) return "[additional data omitted]";
19722
+ if (typeof item === "string") {
19723
+ if (item.length > textBudget) return "[large content omitted]";
19724
+ if (/^data:[^,\s]+;base64,/i.test(item)) return "[binary content omitted]";
19725
+ if (/^\s*[[{]/.test(item)) {
19726
+ try {
19727
+ return bounded(JSON.parse(item), depth + 1);
19728
+ } catch {
19729
+ }
19730
+ }
19731
+ textBudget -= item.length;
19732
+ return item;
19733
+ }
19734
+ if (typeof item === "bigint") return String(item);
19735
+ if (item === null || typeof item !== "object") return item;
19736
+ if (Array.isArray(item)) return item.slice(0, 40).map((child) => bounded(child, depth + 1)).concat(item.length > 40 ? ["[additional items omitted]"] : []);
19737
+ const entries = Object.entries(item);
19738
+ const result = /* @__PURE__ */ Object.create(null);
19739
+ for (const [key, child] of entries.slice(0, 40)) {
19740
+ result[key] = /token|secret|password|passwd|cookie|authorization|api.?key|(^|[_.-])keys?$/i.test(key) ? "[redacted]" : /^(data|base64|blob)$/i.test(key) && typeof child === "string" ? "[binary content omitted]" : bounded(child, depth + 1);
19741
+ }
19742
+ if (entries.length > 40) result["\u2026"] = "[additional fields omitted]";
19743
+ return result;
19744
+ };
19745
+ const safe = redactSecrets(bounded(value));
19746
+ const text4 = typeof safe === "string" ? safe : JSON.stringify(safe, null, 2);
19747
+ if (!text4?.trim() || text4 === "{}" || text4 === "[]") return void 0;
19748
+ return text4.length > 6e3 ? `${text4.slice(0, 6e3)}
19749
+ [\u2026 preview shortened]` : text4;
19750
+ }
19684
19751
  function fieldsOf(input) {
19685
19752
  if (input === null || typeof input !== "object" || Array.isArray(input)) return void 0;
19686
19753
  return input;
@@ -19810,12 +19877,12 @@ function createAcpDriver(support11) {
19810
19877
  support11.transformEnv?.(env, activeConfig, instanceId);
19811
19878
  return env;
19812
19879
  };
19813
- let models = support11.models;
19880
+ let models2 = support11.models;
19814
19881
  const refreshModels = async () => {
19815
19882
  if (!support11.resolveModels) return;
19816
19883
  try {
19817
19884
  const resolved = await support11.resolveModels(childEnv(), config2, instanceId);
19818
- if (resolved.options.length) models = resolved;
19885
+ if (resolved.options.length) models2 = resolved;
19819
19886
  } catch {
19820
19887
  }
19821
19888
  };
@@ -20186,7 +20253,8 @@ function createAcpDriver(support11) {
20186
20253
  itemType: "tool",
20187
20254
  itemId: u4.toolCallId,
20188
20255
  title: String(u4.rawInput?.command ?? u4.title ?? "tool").slice(0, 80),
20189
- summary: commandSummary(u4.rawInput)
20256
+ summary: commandSummary(u4.rawInput),
20257
+ input: toolDetailPreview(u4.rawInput)
20190
20258
  });
20191
20259
  break;
20192
20260
  }
@@ -20197,7 +20265,8 @@ function createAcpDriver(support11) {
20197
20265
  type: "item.completed",
20198
20266
  itemType: "tool",
20199
20267
  itemId: u4.toolCallId,
20200
- ok: u4.status !== "failed"
20268
+ ok: u4.status !== "failed",
20269
+ output: toolDetailPreview(u4.rawOutput ?? u4.content)
20201
20270
  });
20202
20271
  }
20203
20272
  break;
@@ -20438,7 +20507,7 @@ ${turn.text}` : turn.text;
20438
20507
  displayName: input.displayName,
20439
20508
  enabled: input.enabled,
20440
20509
  get models() {
20441
- return models;
20510
+ return models2;
20442
20511
  },
20443
20512
  refreshModels: support11.resolveModels ? refreshModels : void 0,
20444
20513
  snapshot,
@@ -20515,7 +20584,7 @@ import { createHash as createHash3, randomUUID as randomUUID6 } from "node:crypt
20515
20584
  import { mkdir as mkdir2, mkdtemp, readFile as readFile2, rm, stat as stat2 } from "node:fs/promises";
20516
20585
  import { request as httpRequest } from "node:http";
20517
20586
  import { tmpdir as tmpdir2 } from "node:os";
20518
- import { delimiter as delimiter4, join as join16, resolve as resolve5 } from "node:path";
20587
+ import { delimiter as delimiter4, join as join15, resolve as resolve5 } from "node:path";
20519
20588
  function quoteBrowserArgument(value) {
20520
20589
  return `'${value.replaceAll("'", `'"'"'`)}'`;
20521
20590
  }
@@ -20550,11 +20619,11 @@ function authorizationUrlFromLine(line) {
20550
20619
  }
20551
20620
  }
20552
20621
  function antigravityProfileDirectory(instanceId, baseDir = DATA_DIR) {
20553
- return join16(baseDir, "providers", "antigravity", createHash3("sha256").update(instanceId).digest("hex"));
20622
+ return join15(baseDir, "providers", "antigravity", createHash3("sha256").update(instanceId).digest("hex"));
20554
20623
  }
20555
20624
  async function prepareAntigravityProfile(input) {
20556
20625
  const directory = resolve5(input.profileDirectory ?? antigravityProfileDirectory(input.instanceId, input.baseDir));
20557
- const acpDirectory = join16(directory, "antigravity-acp");
20626
+ const acpDirectory = join15(directory, "antigravity-acp");
20558
20627
  await mkdir2(acpDirectory, { recursive: true, mode: 448 });
20559
20628
  if (process.platform !== "win32") {
20560
20629
  const { chmod: chmod2 } = await import("node:fs/promises");
@@ -20562,7 +20631,7 @@ async function prepareAntigravityProfile(input) {
20562
20631
  await chmod2(acpDirectory, 448);
20563
20632
  }
20564
20633
  writeFileAtomic(
20565
- join16(acpDirectory, "settings.json"),
20634
+ join15(acpDirectory, "settings.json"),
20566
20635
  `${JSON.stringify({ auth: { type: "oauth-personal" } })}
20567
20636
  `,
20568
20637
  { mode: 384 }
@@ -20585,7 +20654,7 @@ async function prepareAntigravityProfile(input) {
20585
20654
  PYTHONUNBUFFERED: "1",
20586
20655
  ELECTRON_RUN_AS_NODE: "1"
20587
20656
  });
20588
- return { directory, tokenPath: join16(acpDirectory, "acp_token.json"), environment };
20657
+ return { directory, tokenPath: join15(acpDirectory, "acp_token.json"), environment };
20589
20658
  }
20590
20659
  async function antigravityProfileAuthenticated(profile) {
20591
20660
  try {
@@ -20714,7 +20783,7 @@ function isValidAntigravityInitializeResult(initialized, expectedVersion) {
20714
20783
  return true;
20715
20784
  }
20716
20785
  async function validateAntigravityRuntime(runtime, expectedVersion) {
20717
- const profileDirectory = await mkdtemp(join16(tmpdir2(), "openmaus-antigravity-verify-"));
20786
+ const profileDirectory = await mkdtemp(join15(tmpdir2(), "openmaus-antigravity-verify-"));
20718
20787
  let client;
20719
20788
  let failed2 = false;
20720
20789
  let failure;
@@ -21104,7 +21173,7 @@ import {
21104
21173
  writeFile as writeFile2
21105
21174
  } from "node:fs/promises";
21106
21175
  import { constants } from "node:fs";
21107
- import { delimiter as delimiter5, dirname as dirname7, isAbsolute as isAbsolute2, join as join17, resolve as resolve6 } from "node:path";
21176
+ import { delimiter as delimiter5, dirname as dirname7, isAbsolute as isAbsolute2, join as join16, resolve as resolve6 } from "node:path";
21108
21177
  import { Transform } from "node:stream";
21109
21178
  import { pipeline } from "node:stream/promises";
21110
21179
  import { createInflateRaw } from "node:zlib";
@@ -21125,7 +21194,7 @@ async function retryOnWindowsFileLock(step, options = {}) {
21125
21194
  }
21126
21195
  }
21127
21196
  function runtimeRoot(baseDir = DATA_DIR) {
21128
- return join17(baseDir, "tools", "antigravity-acp", `${process.platform}-${process.arch}`);
21197
+ return join16(baseDir, "tools", "antigravity-acp", `${process.platform}-${process.arch}`);
21129
21198
  }
21130
21199
  function executableNames(platform = process.platform) {
21131
21200
  return platform === "win32" ? { executable: "agy_acp_server.exe", harness: "localharness_external.exe" } : { executable: "agy_acp_server.par", harness: "localharness_external" };
@@ -21142,10 +21211,10 @@ async function executableFile(path3, bytes) {
21142
21211
  }
21143
21212
  async function completedRelease(directory, asset) {
21144
21213
  try {
21145
- const record3 = JSON.parse(await readFile3(join17(directory, INSTALL_RECORD), "utf8"));
21214
+ const record3 = JSON.parse(await readFile3(join16(directory, INSTALL_RECORD), "utf8"));
21146
21215
  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;
21147
- const executablePath = join17(directory, asset.executable.name);
21148
- const harnessPath = join17(directory, asset.harness.name);
21216
+ const executablePath = join16(directory, asset.executable.name);
21217
+ const harnessPath = join16(directory, asset.harness.name);
21149
21218
  if (!await executableFile(executablePath, asset.executable.bytes)) return null;
21150
21219
  if (!await executableFile(harnessPath, asset.harness.bytes)) return null;
21151
21220
  return { executablePath, harnessPath, source: "managed", version: asset.version };
@@ -21161,7 +21230,7 @@ function pathCandidates(binary2, env) {
21161
21230
  async function externalRuntime(candidate, source) {
21162
21231
  const names = executableNames();
21163
21232
  const executablePath = resolve6(candidate);
21164
- const harnessPath = join17(dirname7(executablePath), names.harness);
21233
+ const harnessPath = join16(dirname7(executablePath), names.harness);
21165
21234
  if (!await executableFile(executablePath) || !await executableFile(harnessPath)) return null;
21166
21235
  return { executablePath, harnessPath, source, version: null };
21167
21236
  }
@@ -21181,7 +21250,7 @@ async function resolveAntigravityRuntime(binaryPath, env = process.env, baseDir
21181
21250
  }
21182
21251
  const asset = resolveAntigravityReleaseAsset();
21183
21252
  if (asset) {
21184
- const managed = await completedRelease(join17(runtimeRoot(baseDir), "versions", asset.sha256), asset);
21253
+ const managed = await completedRelease(join16(runtimeRoot(baseDir), "versions", asset.sha256), asset);
21185
21254
  if (managed) return managed;
21186
21255
  }
21187
21256
  const names = executableNames();
@@ -21303,7 +21372,7 @@ async function extractPinnedArchive(archivePath, outputDirectory, asset) {
21303
21372
  start: dataOffset,
21304
21373
  end: dataOffset + entry.compressedBytes - 1
21305
21374
  });
21306
- const destination = createWriteStream(join17(outputDirectory, entry.name), { flags: "wx", mode: 448 });
21375
+ const destination = createWriteStream(join16(outputDirectory, entry.name), { flags: "wx", mode: 448 });
21307
21376
  if (entry.compression === 8) await pipeline(source, createInflateRaw(), counter, destination);
21308
21377
  else await pipeline(source, counter, destination);
21309
21378
  if (bytes !== expectedBytes) throw new Error("An extracted file was truncated.");
@@ -21314,8 +21383,8 @@ async function installOnce2(options) {
21314
21383
  const asset = options.asset === void 0 ? resolveAntigravityReleaseAsset() : options.asset;
21315
21384
  if (!asset) throw new Error(`Google does not publish an Antigravity runtime for ${process.platform}-${process.arch}.`);
21316
21385
  const root = runtimeRoot(options.baseDir);
21317
- const versions = join17(root, "versions");
21318
- const destination = join17(versions, asset.sha256);
21386
+ const versions = join16(root, "versions");
21387
+ const destination = join16(versions, asset.sha256);
21319
21388
  await mkdir3(versions, { recursive: true, mode: 448 });
21320
21389
  const existing = await completedRelease(destination, asset);
21321
21390
  if (existing) {
@@ -21331,11 +21400,11 @@ async function installOnce2(options) {
21331
21400
  } catch (error51) {
21332
21401
  if (error51 instanceof Error && error51.message.includes("Antigravity needs")) throw error51;
21333
21402
  }
21334
- const staging = await mkdtemp2(join17(versions, `${STAGING_PREFIX}${randomUUID7()}-`));
21403
+ const staging = await mkdtemp2(join16(versions, `${STAGING_PREFIX}${randomUUID7()}-`));
21335
21404
  let failed2 = false;
21336
21405
  try {
21337
- const archivePath = join17(staging, "download.zip");
21338
- const runtimeDirectory = join17(staging, "runtime");
21406
+ const archivePath = join16(staging, "download.zip");
21407
+ const runtimeDirectory = join16(staging, "runtime");
21339
21408
  await mkdir3(runtimeDirectory, { mode: 448 });
21340
21409
  const controller = new AbortController();
21341
21410
  const timeout = setTimeout(() => controller.abort(), DOWNLOAD_TIMEOUT_MS2);
@@ -21381,12 +21450,12 @@ async function installOnce2(options) {
21381
21450
  }
21382
21451
  await extractPinnedArchive(archivePath, runtimeDirectory, asset);
21383
21452
  if (process.platform !== "win32") {
21384
- await chmod(join17(runtimeDirectory, asset.executable.name), 493);
21385
- await chmod(join17(runtimeDirectory, asset.harness.name), 493);
21453
+ await chmod(join16(runtimeDirectory, asset.executable.name), 493);
21454
+ await chmod(join16(runtimeDirectory, asset.harness.name), 493);
21386
21455
  }
21387
21456
  const candidate = {
21388
- executablePath: join17(runtimeDirectory, asset.executable.name),
21389
- harnessPath: join17(runtimeDirectory, asset.harness.name),
21457
+ executablePath: join16(runtimeDirectory, asset.executable.name),
21458
+ harnessPath: join16(runtimeDirectory, asset.harness.name),
21390
21459
  source: "managed",
21391
21460
  version: asset.version
21392
21461
  };
@@ -21397,7 +21466,7 @@ async function installOnce2(options) {
21397
21466
  executable: asset.executable,
21398
21467
  harness: asset.harness
21399
21468
  };
21400
- await writeFile2(join17(runtimeDirectory, INSTALL_RECORD), `${JSON.stringify(record3)}
21469
+ await writeFile2(join16(runtimeDirectory, INSTALL_RECORD), `${JSON.stringify(record3)}
21401
21470
  `, { flag: "wx", mode: 384 });
21402
21471
  try {
21403
21472
  await retryOnWindowsFileLock(() => rename(runtimeDirectory, destination));
@@ -21678,7 +21747,7 @@ var init_boxagent = __esm({
21678
21747
  const model = turn.model || MODELS.default;
21679
21748
  const prompt = [
21680
21749
  turn.system,
21681
- "You are working on your own cloud computer \u2014 its desktop, Chrome, and shell are yours.",
21750
+ "You are working on the assigned cloud computer \u2014 use its desktop, Chrome, and shell within the access described above.",
21682
21751
  "",
21683
21752
  turn.text
21684
21753
  ].filter((s) => s !== void 0).join("\n");
@@ -21859,14 +21928,14 @@ var init_resume_recovery = __esm({
21859
21928
  import { randomUUID as randomUUID8 } from "node:crypto";
21860
21929
  import { realpathSync } from "node:fs";
21861
21930
  import { homedir as homedir8 } from "node:os";
21862
- import { basename as basename4, dirname as dirname8, isAbsolute as isAbsolute3, join as join18, resolve as resolve7 } from "node:path";
21931
+ import { basename as basename4, dirname as dirname8, isAbsolute as isAbsolute3, join as join17, resolve as resolve7 } from "node:path";
21863
21932
  import { stripVTControlCharacters as stripVTControlCharacters4 } from "node:util";
21864
21933
  function canonicalPath(path3) {
21865
21934
  try {
21866
21935
  return realpathSync(path3);
21867
21936
  } catch {
21868
21937
  const parent = dirname8(path3);
21869
- return parent === path3 ? path3 : join18(canonicalPath(parent), basename4(path3));
21938
+ return parent === path3 ? path3 : join17(canonicalPath(parent), basename4(path3));
21870
21939
  }
21871
21940
  }
21872
21941
  function claudeSignInLink(value) {
@@ -21923,7 +21992,7 @@ var init_claude_login_auth = __esm({
21923
21992
  if (!isAbsolute3(home) || env.CLAUDE_CONFIG_DIR && !isAbsolute3(env.CLAUDE_CONFIG_DIR)) {
21924
21993
  throw new Error("Use an absolute HOME and CLAUDE_CONFIG_DIR path for this server's Claude provider before signing in.");
21925
21994
  }
21926
- const homeKey = canonicalPath(resolve7(env.CLAUDE_CONFIG_DIR || join18(home, ".claude")));
21995
+ const homeKey = canonicalPath(resolve7(env.CLAUDE_CONFIG_DIR || join17(home, ".claude")));
21927
21996
  if (authenticatingHomes.has(homeKey)) {
21928
21997
  throw new Error("A Claude sign-in is already running for this server account. Finish or cancel it before starting another.");
21929
21998
  }
@@ -22019,7 +22088,7 @@ var init_claude_login_auth = __esm({
22019
22088
  if (!isAbsolute3(home) || env.CLAUDE_CONFIG_DIR && !isAbsolute3(env.CLAUDE_CONFIG_DIR)) {
22020
22089
  throw new Error("Use an absolute HOME and CLAUDE_CONFIG_DIR path for this server's Claude provider before signing out.");
22021
22090
  }
22022
- const homeKey = canonicalPath(resolve7(env.CLAUDE_CONFIG_DIR || join18(home, ".claude")));
22091
+ const homeKey = canonicalPath(resolve7(env.CLAUDE_CONFIG_DIR || join17(home, ".claude")));
22023
22092
  if (authenticatingHomes.has(homeKey)) {
22024
22093
  throw new Error("A Claude sign-in is running for this server account. Finish or cancel it before signing out.");
22025
22094
  }
@@ -22235,7 +22304,7 @@ var init_mcp_trim = __esm({
22235
22304
  });
22236
22305
 
22237
22306
  // server/mcp-gate-config.ts
22238
- import { join as join19 } from "node:path";
22307
+ import { join as join18 } from "node:path";
22239
22308
  function resultBudget(env = process.env) {
22240
22309
  const raw = env.OMB_MCP_RESULT_BUDGET;
22241
22310
  if (raw === void 0 || raw === "") return DEFAULT_RESULT_BUDGET;
@@ -22243,7 +22312,7 @@ function resultBudget(env = process.env) {
22243
22312
  return Number.isFinite(parsed) && parsed >= 0 ? Math.floor(parsed) : DEFAULT_RESULT_BUDGET;
22244
22313
  }
22245
22314
  function spillDir(threadId) {
22246
- return join19(DATA_DIR, "tool-results", threadId.replace(/[^A-Za-z0-9_-]/g, "-").slice(0, 80) || "thread");
22315
+ return join18(DATA_DIR, "tool-results", threadId.replace(/[^A-Za-z0-9_-]/g, "-").slice(0, 80) || "thread");
22247
22316
  }
22248
22317
  function gateServer(input) {
22249
22318
  const { name, server, budget } = input;
@@ -22445,10 +22514,10 @@ var init_ask_question = __esm({
22445
22514
 
22446
22515
  // server/drivers/claude.ts
22447
22516
  import { createHash as createHash5, randomBytes as randomBytes2 } from "node:crypto";
22448
- import { chmodSync as chmodSync4, mkdtempSync as mkdtempSync2, readFileSync as readFileSync8, rmSync as rmSync4, unlinkSync as unlinkSync3, writeFileSync as writeFileSync6 } from "node:fs";
22517
+ import { chmodSync as chmodSync4, mkdtempSync as mkdtempSync2, readFileSync as readFileSync7, rmSync as rmSync4, unlinkSync as unlinkSync3, writeFileSync as writeFileSync5 } from "node:fs";
22449
22518
  import { createServer as createNetServer } from "node:net";
22450
22519
  import { homedir as homedir9, tmpdir as tmpdir3 } from "node:os";
22451
- import { join as join20, dirname as dirname9, isAbsolute as isAbsolute4, normalize } from "node:path";
22520
+ import { join as join19, dirname as dirname9, isAbsolute as isAbsolute4, normalize } from "node:path";
22452
22521
  function claudeAuthStatus(cli, env, run3 = execCli) {
22453
22522
  return new Promise((resolve13) => {
22454
22523
  run3(cli, ["auth", "status", "--json"], { timeout: 8e3, maxBuffer: 65536, env }, (_error, stdout2) => {
@@ -22476,8 +22545,8 @@ function claudeAuthStatus(cli, env, run3 = execCli) {
22476
22545
  }
22477
22546
  function resolveClaudeConfigDir(configDir, env = process.env) {
22478
22547
  const home = env.HOME || env.USERPROFILE || homedir9();
22479
- const configured = configDir?.trim() || env.CLAUDE_CONFIG_DIR?.trim() || join20(home, ".claude");
22480
- const expanded = configured === "~" ? home : configured.startsWith("~/") ? join20(home, configured.slice(2)) : configured;
22548
+ const configured = configDir?.trim() || env.CLAUDE_CONFIG_DIR?.trim() || join19(home, ".claude");
22549
+ const expanded = configured === "~" ? home : configured.startsWith("~/") ? join19(home, configured.slice(2)) : configured;
22481
22550
  if (!isAbsolute4(expanded) || /[\p{Cc}\p{Cf}]/u.test(expanded)) {
22482
22551
  throw new Error("claude: configDir must be an absolute path or start with ~/");
22483
22552
  }
@@ -22512,7 +22581,7 @@ function inheritsUserConfig(env) {
22512
22581
  function readClaudeAuthSettings(env, instanceEnvironment = {}) {
22513
22582
  if (CLAUDE_ACCOUNT_ENV_KEYS.some((key) => instanceEnvironment[key])) return {};
22514
22583
  try {
22515
- const settings2 = JSON.parse(readFileSync8(join20(resolveClaudeConfigDir(void 0, env), "settings.json"), "utf8"));
22584
+ const settings2 = JSON.parse(readFileSync7(join19(resolveClaudeConfigDir(void 0, env), "settings.json"), "utf8"));
22516
22585
  const authEnv = {};
22517
22586
  for (const key of CLAUDE_ACCOUNT_ENV_KEYS) {
22518
22587
  if (!key.endsWith("_FILE_DESCRIPTOR") && typeof settings2?.env?.[key] === "string") {
@@ -22530,7 +22599,7 @@ function readClaudeAuthSettings(env, instanceEnvironment = {}) {
22530
22599
  function projectMcpServers(cwd) {
22531
22600
  let parsed;
22532
22601
  try {
22533
- parsed = JSON.parse(readFileSync8(join20(cwd, ".mcp.json"), "utf8"));
22602
+ parsed = JSON.parse(readFileSync7(join19(cwd, ".mcp.json"), "utf8"));
22534
22603
  } catch {
22535
22604
  return {};
22536
22605
  }
@@ -22598,7 +22667,7 @@ function extrasFromUnknown(value) {
22598
22667
  function readClaudeModelCatalog(env = process.env) {
22599
22668
  let settings2 = {};
22600
22669
  try {
22601
- settings2 = JSON.parse(readFileSync8(join20(resolveClaudeConfigDir(void 0, env), "settings.json"), "utf8"));
22670
+ settings2 = JSON.parse(readFileSync7(join19(resolveClaudeConfigDir(void 0, env), "settings.json"), "utf8"));
22602
22671
  } catch {
22603
22672
  return STATIC_CLAUDE_MODELS;
22604
22673
  }
@@ -22639,16 +22708,17 @@ function askSummary(ask) {
22639
22708
  if (questions) return askQuestionSummary(questions).slice(0, 300);
22640
22709
  return askInputSummary(ask.input) ?? ask.tool ?? "tool";
22641
22710
  }
22642
- function permissionSocketPath(threadId) {
22711
+ function permissionSocketPath(threadId, botId) {
22712
+ const key = botId ? `${botId}\0${threadId}` : threadId;
22643
22713
  const prefix = threadId.replace(/[^\w-]/g, "").slice(0, 4);
22644
- const digest2 = createHash5("sha256").update(threadId).digest("hex").slice(0, 4);
22714
+ const digest2 = createHash5("sha256").update(key).digest("hex").slice(0, 4);
22645
22715
  return brokerSocketPath(DATA_DIR, `${prefix}${digest2}`);
22646
22716
  }
22647
- function brokerSocketCandidates(threadId) {
22648
- const base2 = permissionSocketPath(threadId);
22717
+ function brokerSocketCandidates(threadId, botId) {
22718
+ const base2 = permissionSocketPath(threadId, botId);
22649
22719
  if (process.platform !== "win32") {
22650
- const scope = createHash5("sha256").update(`${DATA_DIR}\0${process.pid}\0${threadId}`).digest("hex").slice(0, 16);
22651
- return [base2, join20(tmpdir3(), `omb-perm-${scope}.sock`)];
22720
+ const scope = createHash5("sha256").update(`${DATA_DIR}\0${process.pid}\0${botId ?? ""}\0${threadId}`).digest("hex").slice(0, 16);
22721
+ return [base2, join19(tmpdir3(), `omb-perm-${scope}.sock`)];
22652
22722
  }
22653
22723
  return [
22654
22724
  base2,
@@ -22866,7 +22936,7 @@ function claudeUserMessage(text4, images) {
22866
22936
  source: {
22867
22937
  type: "base64",
22868
22938
  media_type: image.mime,
22869
- data: readFileSync8(image.path).toString("base64")
22939
+ data: readFileSync7(image.path).toString("base64")
22870
22940
  }
22871
22941
  }));
22872
22942
  if (text4) content.push({ type: "text", text: text4 });
@@ -22970,11 +23040,11 @@ var init_claude = __esm({
22970
23040
  const { instanceId, config: config2 } = input;
22971
23041
  const environment = (model) => claudeEnvironment(model, { ...process.env, ...input.environment }, config2.configDir, input.environment);
22972
23042
  const catalogEnv = environment();
22973
- let models = STATIC_CLAUDE_MODELS;
23043
+ let models2 = STATIC_CLAUDE_MODELS;
22974
23044
  const refreshModels = async () => {
22975
23045
  try {
22976
23046
  const resolved = await mergeLocalInject(readClaudeModelCatalog(catalogEnv), catalogEnv);
22977
- if (resolved.options.length) models = resolved;
23047
+ if (resolved.options.length) models2 = resolved;
22978
23048
  } catch {
22979
23049
  }
22980
23050
  };
@@ -23045,8 +23115,8 @@ var init_claude = __esm({
23045
23115
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
23046
23116
  });
23047
23117
  const retryState = /* @__PURE__ */ new Map();
23048
- const sendTurn = async (turn) => {
23049
- const { threadId } = turn;
23118
+ const sendTurn = async (turn, logicalTurnId) => {
23119
+ const { threadId, botId } = turn;
23050
23120
  if (active.has(threadId)) throw new Error("a turn is already running on this thread");
23051
23121
  const permissionMode = turn.approvalMode === void 0 ? config2.permissionMode : turn.approvalMode === "full" ? "bypassPermissions" : turn.approvalMode === "auto" ? "auto" : turn.approvalMode === "edits" ? "acceptEdits" : "default";
23052
23122
  const controlsHost = turn.integrations?.localComputer?.scope === "local-computer";
@@ -23054,7 +23124,7 @@ var init_claude = __esm({
23054
23124
  throw new Error("local computer control requires the interactive approval broker");
23055
23125
  }
23056
23126
  const promptMsg = claudeUserMessage(turn.text, turn.images);
23057
- const turnId = newId();
23127
+ const turnId = logicalTurnId ?? newId();
23058
23128
  const retryAbort = new AbortController();
23059
23129
  const retry = retryState.get(threadId) ?? { attempt: 0, cancelled: false };
23060
23130
  retry.cancelled = false;
@@ -23159,7 +23229,7 @@ var init_claude = __esm({
23159
23229
  if (gated) mcpServers[name] = gated;
23160
23230
  }
23161
23231
  let broker;
23162
- const socketPath = permissionSocketPath(threadId);
23232
+ const socketPath = permissionSocketPath(threadId, botId);
23163
23233
  if (permissionMode !== "bypassPermissions") {
23164
23234
  args.push("--permission-prompt-tool", "mcp__ogb__approve");
23165
23235
  }
@@ -23167,13 +23237,13 @@ var init_claude = __esm({
23167
23237
  allowed.push("mcp__ogb");
23168
23238
  let mcpConfigPath = null;
23169
23239
  if (Object.keys(mcpServers).length) {
23170
- mcpConfigPath = join20(mkdtempSync2(join20(tmpdir3(), "omb-mcp-")), "mcp.json");
23240
+ mcpConfigPath = join19(mkdtempSync2(join19(tmpdir3(), "omb-mcp-")), "mcp.json");
23171
23241
  args.push("--mcp-config", mcpConfigPath);
23172
23242
  args.push("--allowedTools", allowed.join(","));
23173
23243
  }
23174
23244
  const env = environment(turnModel);
23175
23245
  const authSettings = isolated && !injected.injected ? readClaudeAuthSettings(env, input.environment) : {};
23176
- const authSettingsPath = mcpConfigPath && Object.keys(authSettings).length ? join20(dirname9(mcpConfigPath), "auth-settings.json") : null;
23246
+ const authSettingsPath = mcpConfigPath && Object.keys(authSettings).length ? join19(dirname9(mcpConfigPath), "auth-settings.json") : null;
23177
23247
  if (authSettingsPath) args.push("--settings", authSettingsPath);
23178
23248
  env.CLAUDE_CODE_DISABLE_BACKGROUND_TASKS = "1";
23179
23249
  const cwd = turn.cwd ?? homedir9();
@@ -23198,9 +23268,11 @@ var init_claude = __esm({
23198
23268
  const live = sessions.get(threadId);
23199
23269
  if (live && !live.turn && !live.closing && live.child.exitCode === null && live.argsKey === argsKey && (!sessionId || sessionId === live.sessionId)) {
23200
23270
  if (live.idleTimer) clearTimeout(live.idleTimer);
23201
- live.turn = { turnId, settled: false, sawStreamDelta: false };
23271
+ live.turn = { turnId, input: turn, retryAbort, settled: false, sawStreamDelta: false };
23202
23272
  active.set(threadId, { stop: () => {
23203
23273
  closeSession(threadId, "interrupted");
23274
+ retry.cancelled = true;
23275
+ retryAbort.abort();
23204
23276
  stopSession(live);
23205
23277
  }, turnId, broker: live.broker });
23206
23278
  emit({ ...base2(threadId, turnId), type: "turn.started" });
@@ -23248,14 +23320,14 @@ var init_claude = __esm({
23248
23320
  };
23249
23321
  try {
23250
23322
  if (turn.system) {
23251
- systemPromptPath = join20(mkdtempSync2(join20(tmpdir3(), "omb-system-")), "prompt.txt");
23252
- writeFileSync6(systemPromptPath, turn.system, { mode: 384 });
23323
+ systemPromptPath = join19(mkdtempSync2(join19(tmpdir3(), "omb-system-")), "prompt.txt");
23324
+ writeFileSync5(systemPromptPath, turn.system, { mode: 384 });
23253
23325
  args.push("--append-system-prompt-file", systemPromptPath);
23254
23326
  }
23255
23327
  if (socketPath) {
23256
23328
  const askTools = /* @__PURE__ */ new Map();
23257
23329
  broker = await createPermissionBroker({
23258
- socketPaths: brokerSocketCandidates(threadId),
23330
+ socketPaths: brokerSocketCandidates(threadId, botId),
23259
23331
  isActive: () => Boolean(sessions.get(threadId)?.turn),
23260
23332
  onAsk: (ask) => {
23261
23333
  const eventTurnId = sessions.get(threadId)?.turn?.turnId ?? turnId;
@@ -23299,10 +23371,10 @@ var init_claude = __esm({
23299
23371
  }
23300
23372
  }
23301
23373
  if (mcpConfigPath) {
23302
- writeFileSync6(mcpConfigPath, JSON.stringify({ mcpServers }), { mode: 384 });
23374
+ writeFileSync5(mcpConfigPath, JSON.stringify({ mcpServers }), { mode: 384 });
23303
23375
  }
23304
23376
  if (authSettingsPath) {
23305
- writeFileSync6(authSettingsPath, JSON.stringify(authSettings), { mode: 384 });
23377
+ writeFileSync5(authSettingsPath, JSON.stringify(authSettings), { mode: 384 });
23306
23378
  }
23307
23379
  if (sessionId) args.push("--resume", sessionId);
23308
23380
  else args.push("--session-id", newSessionId);
@@ -23331,7 +23403,7 @@ var init_claude = __esm({
23331
23403
  sessionId: sessionId ?? newSessionId,
23332
23404
  sawInit: false,
23333
23405
  nativePermissionMode: null,
23334
- turn: { turnId, settled: false, sawStreamDelta: false },
23406
+ turn: { turnId, input: turn, retryAbort, settled: false, sawStreamDelta: false },
23335
23407
  idleTimer: null,
23336
23408
  closing: false,
23337
23409
  stderr: ""
@@ -23415,7 +23487,8 @@ var init_claude = __esm({
23415
23487
  itemType: "tool",
23416
23488
  itemId: b.id,
23417
23489
  title: b.name,
23418
- summary: commandSummary(b.input)
23490
+ summary: commandSummary(b.input),
23491
+ input: toolDetailPreview(b.input)
23419
23492
  });
23420
23493
  }
23421
23494
  }
@@ -23433,7 +23506,7 @@ var init_claude = __esm({
23433
23506
  case "user":
23434
23507
  for (const b of Array.isArray(o.message?.content) ? o.message.content : []) {
23435
23508
  if (b.type === "tool_result") {
23436
- emit({ ...base2(threadId, currentTurnId()), type: "item.completed", itemType: "tool", itemId: b.tool_use_id, ok: !b.is_error });
23509
+ emit({ ...base2(threadId, currentTurnId()), type: "item.completed", itemType: "tool", itemId: b.tool_use_id, ok: !b.is_error, output: toolDetailPreview(b.content) });
23437
23510
  }
23438
23511
  }
23439
23512
  break;
@@ -23483,9 +23556,11 @@ var init_claude = __esm({
23483
23556
  if (closeFinalized) return;
23484
23557
  closeFinalized = true;
23485
23558
  if (session.turn && !session.turn.settled) {
23559
+ const { turnId: turnId2, input: turn2, retryAbort: retryAbort2 } = session.turn;
23560
+ const retry2 = retryState.get(threadId) ?? { attempt: 0, cancelled: false };
23486
23561
  const message2 = `claude exited ${code} before result${session.stderr ? `: ${session.stderr.trim().slice(-300)}` : ""}`;
23487
23562
  const verdict = classifyError({ exitCode: code, stderr: message2 });
23488
- if (!retry.cancelled && code !== 0 && verdict.transient && !session.turn.sawStreamDelta && retry.attempt < RETRY_MAX_ATTEMPTS - 1) {
23563
+ if (!retry2.cancelled && code !== 0 && verdict.transient && !session.turn.sawStreamDelta && retry2.attempt < RETRY_MAX_ATTEMPTS - 1) {
23489
23564
  const failedBroker = session.broker;
23490
23565
  session.broker = void 0;
23491
23566
  failedBroker?.pause();
@@ -23503,23 +23578,23 @@ var init_claude = __esm({
23503
23578
  }
23504
23579
  sessions.delete(threadId);
23505
23580
  session.turn = null;
23506
- retry.attempt++;
23507
- const delayMs = computeBackoff(retry.attempt - 1);
23581
+ retry2.attempt++;
23582
+ const delayMs = computeBackoff(retry2.attempt - 1);
23508
23583
  emit({
23509
- ...base2(threadId, turnId),
23584
+ ...base2(threadId, turnId2),
23510
23585
  type: "turn.retrying",
23511
- attempt: retry.attempt,
23586
+ attempt: retry2.attempt,
23512
23587
  delayMs,
23513
23588
  reason: verdict.reason
23514
23589
  });
23515
23590
  void (async () => {
23516
- const wait2 = interruptibleDelay(delayMs * retryScale, retryAbort.signal);
23591
+ const wait2 = interruptibleDelay(delayMs * retryScale, retryAbort2.signal);
23517
23592
  await wait2.promise;
23518
- if (retry.cancelled) {
23593
+ if (retry2.cancelled) {
23519
23594
  active.delete(threadId);
23520
23595
  retryState.delete(threadId);
23521
23596
  emit({
23522
- ...base2(threadId, turnId),
23597
+ ...base2(threadId, turnId2),
23523
23598
  type: "turn.completed",
23524
23599
  ok: false,
23525
23600
  stopReason: "interrupted",
@@ -23530,16 +23605,16 @@ var init_claude = __esm({
23530
23605
  active.delete(threadId);
23531
23606
  try {
23532
23607
  const cursor3 = session.sessionId ?? sessionId ?? void 0;
23533
- await sendTurn({ ...turn, resumeCursor: cursor3 });
23608
+ await sendTurn({ ...turn2, resumeCursor: cursor3 }, turnId2);
23534
23609
  } catch (e) {
23535
23610
  retryState.delete(threadId);
23536
23611
  emit({
23537
- ...base2(threadId, turnId),
23612
+ ...base2(threadId, turnId2),
23538
23613
  type: "runtime.error",
23539
23614
  message: e instanceof Error ? e.message : String(e)
23540
23615
  });
23541
23616
  emit({
23542
- ...base2(threadId, turnId),
23617
+ ...base2(threadId, turnId2),
23543
23618
  type: "turn.completed",
23544
23619
  ok: false,
23545
23620
  stopReason: "exit_before_result",
@@ -23555,10 +23630,10 @@ var init_claude = __esm({
23555
23630
  promptSubmitted: session.sawInit,
23556
23631
  producedOutput: session.turn.sawStreamDelta
23557
23632
  });
23558
- if (mayReplay(resumeFailure) && !retry.cancelled) {
23633
+ if (mayReplay(resumeFailure) && !retry2.cancelled) {
23559
23634
  const recovery = recoveryPromptFor({
23560
- recoveryText: turn.recoveryText,
23561
- currentText: turn.text,
23635
+ recoveryText: turn2.recoveryText,
23636
+ currentText: turn2.text,
23562
23637
  failure: resumeFailure
23563
23638
  });
23564
23639
  session.broker?.close();
@@ -23578,23 +23653,23 @@ var init_claude = __esm({
23578
23653
  session.turn = null;
23579
23654
  active.delete(threadId);
23580
23655
  emit({
23581
- ...base2(threadId, turnId),
23656
+ ...base2(threadId, turnId2),
23582
23657
  type: "turn.retrying",
23583
- attempt: retry.attempt + 1,
23658
+ attempt: retry2.attempt + 1,
23584
23659
  delayMs: 0,
23585
23660
  reason: "resume_rejected"
23586
23661
  });
23587
23662
  void (async () => {
23588
23663
  try {
23589
- await sendTurn({ ...turn, resumeCursor: void 0, recoveryText: void 0, text: recovery.text });
23664
+ await sendTurn({ ...turn2, resumeCursor: void 0, recoveryText: void 0, text: recovery.text }, turnId2);
23590
23665
  } catch (e) {
23591
23666
  retryState.delete(threadId);
23592
23667
  emit({
23593
- ...base2(threadId, turnId),
23668
+ ...base2(threadId, turnId2),
23594
23669
  type: "runtime.error",
23595
23670
  message: e instanceof Error ? e.message : String(e)
23596
23671
  });
23597
- emit({ ...base2(threadId, turnId), type: "turn.completed", ok: false, stopReason: "exit_before_result", cost: null });
23672
+ emit({ ...base2(threadId, turnId2), type: "turn.completed", ok: false, stopReason: "exit_before_result", cost: null });
23598
23673
  }
23599
23674
  })();
23600
23675
  return;
@@ -23718,7 +23793,7 @@ var init_claude = __esm({
23718
23793
  displayName: input.displayName,
23719
23794
  enabled: input.enabled,
23720
23795
  get models() {
23721
- return models;
23796
+ return models2;
23722
23797
  },
23723
23798
  refreshModels,
23724
23799
  snapshot,
@@ -23783,9 +23858,9 @@ var init_claude = __esm({
23783
23858
  });
23784
23859
 
23785
23860
  // server/drivers/codex-catalog.ts
23786
- import { readdirSync as readdirSync2, readFileSync as readFileSync9 } from "node:fs";
23861
+ import { readdirSync as readdirSync2, readFileSync as readFileSync8 } from "node:fs";
23787
23862
  import { homedir as homedir10 } from "node:os";
23788
- import { basename as basename5, join as join21 } from "node:path";
23863
+ import { basename as basename5, join as join20 } from "node:path";
23789
23864
  function encodeCodexSelection(provider, model) {
23790
23865
  return `${provider}${SEP}${model}`;
23791
23866
  }
@@ -23807,7 +23882,7 @@ function readCodexAppServerModelCatalog(cli, env, timeoutMs = 8e3) {
23807
23882
  let settled = false;
23808
23883
  let buffer = "";
23809
23884
  let nextId = 1;
23810
- const models = [];
23885
+ const models2 = [];
23811
23886
  const cursors = /* @__PURE__ */ new Set();
23812
23887
  const pending = /* @__PURE__ */ new Map();
23813
23888
  const finish = (catalog) => {
@@ -23865,7 +23940,7 @@ function readCodexAppServerModelCatalog(cli, env, timeoutMs = 8e3) {
23865
23940
  continue;
23866
23941
  }
23867
23942
  const page = message2.result;
23868
- if (Array.isArray(page?.data)) models.push(...page.data);
23943
+ if (Array.isArray(page?.data)) models2.push(...page.data);
23869
23944
  const cursor3 = typeof page?.nextCursor === "string" && page.nextCursor ? page.nextCursor : null;
23870
23945
  if (cursor3 && !cursors.has(cursor3)) {
23871
23946
  cursors.add(cursor3);
@@ -23875,7 +23950,7 @@ function readCodexAppServerModelCatalog(cli, env, timeoutMs = 8e3) {
23875
23950
  const options = [];
23876
23951
  const seen = /* @__PURE__ */ new Set();
23877
23952
  let defaultModel = null;
23878
- for (const row of models) {
23953
+ for (const row of models2) {
23879
23954
  if (row.hidden === true || typeof row.id !== "string" || !MODEL_ID2.test(row.id) || seen.has(row.id)) continue;
23880
23955
  seen.add(row.id);
23881
23956
  options.push({
@@ -23901,7 +23976,7 @@ function readCodexAppServerModelCatalog(cli, env, timeoutMs = 8e3) {
23901
23976
  }
23902
23977
  function codexHome(env) {
23903
23978
  if (env.CODEX_HOME) return env.CODEX_HOME;
23904
- return join21(env.HOME || env.USERPROFILE || homedir10(), ".codex");
23979
+ return join20(env.HOME || env.USERPROFILE || homedir10(), ".codex");
23905
23980
  }
23906
23981
  function unquote(raw) {
23907
23982
  let value = raw.trim();
@@ -23964,7 +24039,7 @@ function parseCodexToml(text4) {
23964
24039
  }
23965
24040
  function readText(path3) {
23966
24041
  try {
23967
- return readFileSync9(path3, "utf8");
24042
+ return readFileSync8(path3, "utf8");
23968
24043
  } catch {
23969
24044
  return null;
23970
24045
  }
@@ -23987,11 +24062,11 @@ function niceLabel(model, provider, known, named) {
23987
24062
  }
23988
24063
  function collectCatalogNames(home) {
23989
24064
  const named = /* @__PURE__ */ new Map();
23990
- for (const file2 of listDir(join21(home, "model-catalogs"))) {
24065
+ for (const file2 of listDir(join20(home, "model-catalogs"))) {
23991
24066
  if (!file2.endsWith(".json")) continue;
23992
24067
  const provider = basename5(file2, ".json").replace(/-models$/, "");
23993
24068
  if (!PROVIDER_ID.test(provider)) continue;
23994
- const raw = readText(join21(home, "model-catalogs", file2));
24069
+ const raw = readText(join20(home, "model-catalogs", file2));
23995
24070
  if (!raw) continue;
23996
24071
  let parsed;
23997
24072
  try {
@@ -24043,7 +24118,7 @@ async function probeProviderModels(provider, env, fetchImpl) {
24043
24118
  async function readCodexModelCatalog(env = process.env, fetchImpl = fetch, cli) {
24044
24119
  const official = (cli ? await readCodexAppServerModelCatalog(cli, env) : null) ?? STATIC_CODEX_MODELS;
24045
24120
  const home = codexHome(env);
24046
- const mainText = readText(join21(home, "config.toml"));
24121
+ const mainText = readText(join20(home, "config.toml"));
24047
24122
  if (!mainText) return mergeLocalInject(official, env, fetchImpl);
24048
24123
  const main2 = parseCodexToml(mainText);
24049
24124
  const known = new Map(main2.providers.map((provider) => [provider.id, provider]));
@@ -24058,7 +24133,7 @@ async function readCodexModelCatalog(env = process.env, fetchImpl = fetch, cli)
24058
24133
  remember(main2.modelProvider, main2.model);
24059
24134
  for (const file2 of listDir(home)) {
24060
24135
  if (!file2.endsWith(".config.toml")) continue;
24061
- const profile = parseCodexToml(readText(join21(home, file2)) ?? "");
24136
+ const profile = parseCodexToml(readText(join20(home, file2)) ?? "");
24062
24137
  for (const provider of profile.providers) {
24063
24138
  if (!known.has(provider.id)) known.set(provider.id, provider);
24064
24139
  }
@@ -24124,8 +24199,8 @@ var init_codex_catalog = __esm({
24124
24199
 
24125
24200
  // server/drivers/codex-instructions.ts
24126
24201
  import { createHash as createHash6 } from "node:crypto";
24127
- import { mkdirSync as mkdirSync9, readFileSync as readFileSync10 } from "node:fs";
24128
- import { join as join22 } from "node:path";
24202
+ import { mkdirSync as mkdirSync9, readFileSync as readFileSync9 } from "node:fs";
24203
+ import { join as join21 } from "node:path";
24129
24204
  function codexDeveloperInstructions(config2, botInstructions) {
24130
24205
  if (!config2 || typeof config2 !== "object" || Array.isArray(config2)) {
24131
24206
  throw new Error("Codex returned no effective configuration; cannot safely update bot instructions.");
@@ -24139,12 +24214,12 @@ function codexDeveloperInstructions(config2, botInstructions) {
24139
24214
  ${configured}` : botInstructions;
24140
24215
  }
24141
24216
  async function syncCodexInstructions(key, nativeThreadId, instructions, resumed, request) {
24142
- const directory = join22(DATA_DIR, "codex-instructions");
24143
- const path3 = join22(directory, `${digest(JSON.stringify([key, nativeThreadId]))}.sha256`);
24217
+ const directory = join21(DATA_DIR, "codex-instructions");
24218
+ const path3 = join21(directory, `${digest(JSON.stringify([key, nativeThreadId]))}.sha256`);
24144
24219
  const fingerprint = digest(instructions);
24145
24220
  let previous;
24146
24221
  try {
24147
- previous = readFileSync10(path3, "utf8");
24222
+ previous = readFileSync9(path3, "utf8");
24148
24223
  } catch (error51) {
24149
24224
  if (error51.code !== "ENOENT") throw error51;
24150
24225
  }
@@ -24187,19 +24262,19 @@ var init_codex_instructions = __esm({
24187
24262
  import { randomUUID as randomUUID9 } from "node:crypto";
24188
24263
  import { realpathSync as realpathSync2 } from "node:fs";
24189
24264
  import { homedir as homedir11 } from "node:os";
24190
- import { basename as basename6, dirname as dirname10, isAbsolute as isAbsolute5, join as join23, resolve as resolve8 } from "node:path";
24265
+ import { basename as basename6, dirname as dirname10, isAbsolute as isAbsolute5, join as join22, resolve as resolve8 } from "node:path";
24191
24266
  import { stripVTControlCharacters as stripVTControlCharacters5 } from "node:util";
24192
24267
  function canonicalPath2(path3) {
24193
24268
  try {
24194
24269
  return realpathSync2(path3);
24195
24270
  } catch {
24196
24271
  const parent = dirname10(path3);
24197
- return parent === path3 ? path3 : join23(canonicalPath2(parent), basename6(path3));
24272
+ return parent === path3 ? path3 : join22(canonicalPath2(parent), basename6(path3));
24198
24273
  }
24199
24274
  }
24200
24275
  function codexDevicePrompt(output) {
24201
- const clean3 = stripVTControlCharacters5(output);
24202
- const lines = clean3.split(/\r?\n/).slice(0, -1).map((line) => line.trim());
24276
+ const clean = stripVTControlCharacters5(output);
24277
+ const lines = clean.split(/\r?\n/).slice(0, -1).map((line) => line.trim());
24203
24278
  if (!lines.some((line) => /^https:\/\/auth\.openai\.com\/codex\/device\/?$/.test(line))) return null;
24204
24279
  const userCode = lines.find((line) => /^[A-Z0-9]{4,8}-[A-Z0-9]{4,8}$/.test(line));
24205
24280
  return userCode ? { authorizationUrl: DEVICE_URL, userCode } : null;
@@ -24245,7 +24320,7 @@ var init_codex_device_auth = __esm({
24245
24320
  if (!isAbsolute5(home) || env.CODEX_HOME && !isAbsolute5(env.CODEX_HOME)) {
24246
24321
  throw new Error("Use an absolute HOME and CODEX_HOME path for this server's Codex provider before signing in.");
24247
24322
  }
24248
- const homeKey = canonicalPath2(resolve8(env.CODEX_HOME || join23(home, ".codex")));
24323
+ const homeKey = canonicalPath2(resolve8(env.CODEX_HOME || join22(home, ".codex")));
24249
24324
  if (authenticatingHomes2.has(homeKey)) {
24250
24325
  throw new Error("A ChatGPT sign-in is already running for this server account. Finish or cancel it before starting another.");
24251
24326
  }
@@ -24324,7 +24399,7 @@ var init_codex_device_auth = __esm({
24324
24399
  if (!isAbsolute5(home) || env.CODEX_HOME && !isAbsolute5(env.CODEX_HOME)) {
24325
24400
  throw new Error("Use an absolute HOME and CODEX_HOME path for this server's Codex provider before signing out.");
24326
24401
  }
24327
- const homeKey = canonicalPath2(resolve8(env.CODEX_HOME || join23(home, ".codex")));
24402
+ const homeKey = canonicalPath2(resolve8(env.CODEX_HOME || join22(home, ".codex")));
24328
24403
  if (authenticatingHomes2.has(homeKey)) {
24329
24404
  throw new Error("A ChatGPT sign-in is running for this server account. Finish or cancel it before signing out.");
24330
24405
  }
@@ -24483,11 +24558,11 @@ var init_codex_device_auth = __esm({
24483
24558
 
24484
24559
  // server/drivers/codex-identity.ts
24485
24560
  import { homedir as homedir12 } from "node:os";
24486
- import { isAbsolute as isAbsolute6, join as join24, resolve as resolve9 } from "node:path";
24561
+ import { isAbsolute as isAbsolute6, join as join23, resolve as resolve9 } from "node:path";
24487
24562
  function codexHome2(env) {
24488
24563
  if (env.CODEX_HOME) return isAbsolute6(env.CODEX_HOME) ? resolve9(env.CODEX_HOME) : null;
24489
24564
  const home = env.HOME || env.USERPROFILE || homedir12();
24490
- return isAbsolute6(home) ? join24(home, ".codex") : null;
24565
+ return isAbsolute6(home) ? join23(home, ".codex") : null;
24491
24566
  }
24492
24567
  async function codexAccountEmail(cli, env, timeoutMs = 3e3) {
24493
24568
  const cwd = env.HOME || env.USERPROFILE || homedir12();
@@ -24617,8 +24692,8 @@ function codexUpdateCommand(cli, platform = process.platform) {
24617
24692
  const command = (tokens.length > 0 ? tokens : [trimmed]).map(quote).join(" ");
24618
24693
  return platform === "win32" ? `& ${command} update` : `${command} update`;
24619
24694
  }
24620
- function codexAstraUpdate(version2, models, cli) {
24621
- if (models.options.some((model) => model.id === ASTRA_MODEL_ID) || !codexPredatesAstra(version2)) {
24695
+ function codexAstraUpdate(version2, models2, cli) {
24696
+ if (models2.options.some((model) => model.id === ASTRA_MODEL_ID) || !codexPredatesAstra(version2)) {
24622
24697
  return void 0;
24623
24698
  }
24624
24699
  return {
@@ -24912,11 +24987,11 @@ var init_codex = __esm({
24912
24987
  return env;
24913
24988
  };
24914
24989
  const catalogEnv = childEnv();
24915
- let models = STATIC_CODEX_MODELS;
24990
+ let models2 = STATIC_CODEX_MODELS;
24916
24991
  const refreshModels = async () => {
24917
24992
  try {
24918
24993
  const resolved = await readCodexModelCatalog(catalogEnv, fetch, config2.cli);
24919
- if (resolved.options.length) models = resolved;
24994
+ if (resolved.options.length) models2 = resolved;
24920
24995
  } catch {
24921
24996
  }
24922
24997
  };
@@ -25211,7 +25286,8 @@ var init_codex = __esm({
25211
25286
  itemType: "tool",
25212
25287
  itemId: item.id,
25213
25288
  title,
25214
- summary: item.type === "commandExecution" ? commandSummary({ command: item.command }) : void 0
25289
+ summary: item.type === "commandExecution" ? commandSummary({ command: item.command }) : void 0,
25290
+ input: toolDetailPreview(item.type === "commandExecution" ? { command: item.command, cwd: item.cwd } : item.type === "mcpToolCall" ? item.arguments : item.type === "fileChange" ? item.changes : item.query)
25215
25291
  });
25216
25292
  }
25217
25293
  break;
@@ -25244,7 +25320,8 @@ var init_codex = __esm({
25244
25320
  type: "item.completed",
25245
25321
  itemType: "tool",
25246
25322
  itemId: item.id,
25247
- ok: item.status !== "failed" && item.status !== "declined"
25323
+ ok: item.status !== "failed" && item.status !== "declined",
25324
+ output: toolDetailPreview(item.type === "commandExecution" ? { output: item.aggregatedOutput, exitCode: item.exitCode } : item.type === "mcpToolCall" ? item.error ?? item.result : item.type === "fileChange" ? item.changes : item.action)
25248
25325
  });
25249
25326
  } else if (item.type === "reasoning") {
25250
25327
  emit({ ...base2(threadId, turnId), type: "item.updated", itemType: "reasoning", tokens: null });
@@ -25535,7 +25612,7 @@ ${stderr ?? ""}`))
25535
25612
  version: version2,
25536
25613
  authenticated,
25537
25614
  ...email3 ? { account: { email: email3 } } : {},
25538
- update: codexAstraUpdate(version2, models, config2.cli),
25615
+ update: codexAstraUpdate(version2, models2, config2.cli),
25539
25616
  billing: "subscription"
25540
25617
  };
25541
25618
  };
@@ -25545,7 +25622,7 @@ ${stderr ?? ""}`))
25545
25622
  displayName: input.displayName,
25546
25623
  enabled: input.enabled,
25547
25624
  get models() {
25548
- return models;
25625
+ return models2;
25549
25626
  },
25550
25627
  refreshModels,
25551
25628
  startAuthentication: () => authentication.start(),
@@ -25615,72 +25692,89 @@ function createOpenAIChatRuntime(options) {
25615
25692
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
25616
25693
  });
25617
25694
  const complete = async (messages, model, stream, signal, onDelta) => {
25618
- const timeout = AbortSignal.timeout(options.timeoutMs);
25619
- const response = await fetch(`${options.apiUrl}/chat/completions`, {
25620
- method: "POST",
25621
- headers: { authorization: `Bearer ${options.apiKey}`, "content-type": "application/json" },
25622
- body: JSON.stringify(options.requestBody(model, messages, stream)),
25623
- signal: signal ? AbortSignal.any([signal, timeout]) : timeout
25624
- });
25625
- if (!response.ok) {
25626
- const body = await response.text().catch(() => "");
25627
- throw new Error(`${options.httpErrorLabel} HTTP ${response.status}${body ? `: ${body.slice(0, 200)}` : ""}`);
25628
- }
25629
- if (!stream) {
25630
- const json2 = await response.json();
25631
- const message2 = json2.choices?.[0]?.message;
25632
- return {
25633
- text: typeof message2?.content === "string" ? message2.content : "",
25634
- reasoning: options.reasoning && typeof message2?.reasoning_content === "string" ? message2.reasoning_content : "",
25635
- usage: usageFrom(json2.usage)
25636
- };
25637
- }
25638
- if (!response.body) {
25639
- throw new Error(options.noBodyError ?? `${options.httpErrorLabel} returned no response body`);
25640
- }
25641
- let text4 = "";
25642
- let reasoning = "";
25643
- let usage = null;
25644
- const reader = response.body.getReader();
25645
- const decoder = new TextDecoder();
25646
- let buffer = "";
25695
+ const timeoutController = new AbortController();
25696
+ let idleTimer = null;
25697
+ const resetIdleTimer = () => {
25698
+ if (idleTimer) clearTimeout(idleTimer);
25699
+ idleTimer = setTimeout(() => {
25700
+ timeoutController.abort(new DOMException("Streaming idle timeout elapsed", "AbortError"));
25701
+ }, options.timeoutMs);
25702
+ };
25703
+ resetIdleTimer();
25647
25704
  try {
25648
- readLoop: for (; ; ) {
25649
- const { done, value } = await reader.read();
25650
- if (done) break;
25651
- buffer += decoder.decode(value, { stream: true });
25652
- let newline;
25653
- while ((newline = buffer.indexOf("\n")) !== -1) {
25654
- const line = buffer.slice(0, newline).trim();
25655
- buffer = buffer.slice(newline + 1);
25656
- if (!line.startsWith("data:")) continue;
25657
- const data = line.slice(5).trim();
25658
- if (data === "[DONE]") break readLoop;
25659
- let chunk;
25660
- try {
25661
- chunk = JSON.parse(data);
25662
- } catch {
25663
- continue;
25664
- }
25665
- const delta = chunk.choices?.[0]?.delta;
25666
- const reasoningDelta = options.reasoning && typeof delta?.reasoning_content === "string" ? delta.reasoning_content : "";
25667
- const contentDelta = typeof delta?.content === "string" ? delta.content : "";
25668
- if (reasoningDelta) {
25669
- reasoning += reasoningDelta;
25670
- onDelta?.(reasoningDelta, "reasoning_text");
25671
- }
25672
- if (contentDelta) {
25673
- text4 += contentDelta;
25674
- onDelta?.(contentDelta, "assistant_text");
25705
+ const activeSignal = signal ? AbortSignal.any([signal, timeoutController.signal]) : timeoutController.signal;
25706
+ const response = await fetch(`${options.apiUrl}/chat/completions`, {
25707
+ method: "POST",
25708
+ headers: { authorization: `Bearer ${options.apiKey}`, "content-type": "application/json" },
25709
+ body: JSON.stringify(options.requestBody(model, messages, stream)),
25710
+ signal: activeSignal
25711
+ });
25712
+ if (!response.ok) {
25713
+ const body = await response.text().catch(() => "");
25714
+ throw new Error(`${options.httpErrorLabel} HTTP ${response.status}${body ? `: ${body.slice(0, 200)}` : ""}`);
25715
+ }
25716
+ if (!stream) {
25717
+ const json2 = await response.json();
25718
+ const message2 = json2.choices?.[0]?.message;
25719
+ return {
25720
+ text: typeof message2?.content === "string" ? message2.content : "",
25721
+ reasoning: options.reasoning && typeof message2?.reasoning_content === "string" ? message2.reasoning_content : "",
25722
+ usage: usageFrom(json2.usage)
25723
+ };
25724
+ }
25725
+ if (!response.body) {
25726
+ throw new Error(options.noBodyError ?? `${options.httpErrorLabel} returned no response body`);
25727
+ }
25728
+ let text4 = "";
25729
+ let reasoning = "";
25730
+ let usage = null;
25731
+ const reader = response.body.getReader();
25732
+ const decoder = new TextDecoder();
25733
+ let buffer = "";
25734
+ try {
25735
+ readLoop: for (; ; ) {
25736
+ const { done, value } = await reader.read();
25737
+ if (done) {
25738
+ if (buffer.trim() === "data: [DONE]") break;
25739
+ throw new DOMException("Stream ended before completion", "AbortError");
25740
+ }
25741
+ resetIdleTimer();
25742
+ buffer += decoder.decode(value, { stream: true });
25743
+ let newline;
25744
+ while ((newline = buffer.indexOf("\n")) !== -1) {
25745
+ const line = buffer.slice(0, newline).trim();
25746
+ buffer = buffer.slice(newline + 1);
25747
+ if (!line.startsWith("data:")) continue;
25748
+ const data = line.slice(5).trim();
25749
+ if (data === "[DONE]") break readLoop;
25750
+ let chunk;
25751
+ try {
25752
+ chunk = JSON.parse(data);
25753
+ } catch {
25754
+ continue;
25755
+ }
25756
+ const delta = chunk.choices?.[0]?.delta;
25757
+ const reasoningDelta = options.reasoning && typeof delta?.reasoning_content === "string" ? delta.reasoning_content : "";
25758
+ const contentDelta = typeof delta?.content === "string" ? delta.content : "";
25759
+ if (reasoningDelta) {
25760
+ reasoning += reasoningDelta;
25761
+ onDelta?.(reasoningDelta, "reasoning_text");
25762
+ }
25763
+ if (contentDelta) {
25764
+ text4 += contentDelta;
25765
+ onDelta?.(contentDelta, "assistant_text");
25766
+ }
25767
+ if (chunk.usage) usage = usageFrom(chunk.usage);
25675
25768
  }
25676
- if (chunk.usage) usage = usageFrom(chunk.usage);
25677
25769
  }
25770
+ } finally {
25771
+ await reader.cancel().catch(() => {
25772
+ });
25678
25773
  }
25774
+ return { text: text4, reasoning, usage };
25679
25775
  } finally {
25680
- await reader.cancel().catch(() => {
25681
- });
25776
+ if (idleTimer) clearTimeout(idleTimer);
25682
25777
  }
25683
- return { text: text4, reasoning, usage };
25684
25778
  };
25685
25779
  const messagesFor = (turn) => [
25686
25780
  ...turn.system ? [{ role: "system", content: turn.system }] : [],
@@ -25875,12 +25969,12 @@ var init_grok = __esm({
25875
25969
  });
25876
25970
 
25877
25971
  // server/drivers/acp/grok.ts
25878
- import { existsSync as existsSync11, readFileSync as readFileSync11, writeFileSync as writeFileSync7 } from "node:fs";
25972
+ import { existsSync as existsSync11, readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "node:fs";
25879
25973
  import { homedir as homedir14 } from "node:os";
25880
- import { join as join25 } from "node:path";
25974
+ import { join as join24 } from "node:path";
25881
25975
  function grokHome(env) {
25882
25976
  if (env.GROK_HOME) return env.GROK_HOME;
25883
- return join25(env.HOME || env.USERPROFILE || homedir14(), ".grok");
25977
+ return join24(env.HOME || env.USERPROFILE || homedir14(), ".grok");
25884
25978
  }
25885
25979
  function unquote2(raw) {
25886
25980
  const value = raw.trim();
@@ -25890,10 +25984,10 @@ function unquote2(raw) {
25890
25984
  return value;
25891
25985
  }
25892
25986
  function readGrokModelCatalog(env = process.env) {
25893
- const path3 = join25(grokHome(env), "config.toml");
25987
+ const path3 = join24(grokHome(env), "config.toml");
25894
25988
  let text4 = "";
25895
25989
  try {
25896
- text4 = readFileSync11(path3, "utf8");
25990
+ text4 = readFileSync10(path3, "utf8");
25897
25991
  } catch {
25898
25992
  return STATIC_GROK_MODELS;
25899
25993
  }
@@ -25963,10 +26057,10 @@ function ensureGrokInjectSlug(modelId, env = process.env) {
25963
26057
  if (!inject) return modelId;
25964
26058
  const host = localHost(inject.host);
25965
26059
  if (!host) return modelId;
25966
- const path3 = join25(grokHome(env), "config.toml");
26060
+ const path3 = join24(grokHome(env), "config.toml");
25967
26061
  let text4 = "";
25968
26062
  try {
25969
- text4 = readFileSync11(path3, "utf8");
26063
+ text4 = readFileSync10(path3, "utf8");
25970
26064
  } catch {
25971
26065
  text4 = "";
25972
26066
  }
@@ -26017,7 +26111,7 @@ function ensureGrokInjectSlug(modelId, env = process.env) {
26017
26111
  const next = text4 && !text4.endsWith("\n") ? `${text4}
26018
26112
 
26019
26113
  ${block2}` : `${text4}${text4 ? "\n" : ""}${block2}`;
26020
- writeFileSync7(path3, next);
26114
+ writeFileSync6(path3, next);
26021
26115
  return slug;
26022
26116
  }
26023
26117
  function grokAcceptsUnadvertisedImages(init) {
@@ -26109,7 +26203,7 @@ var init_grok2 = __esm({
26109
26203
  // an unauthenticated CLI is a user action, not something to paper over.
26110
26204
  pickAuthMethod: (methods) => methods.some((m) => m.id === "cached_token") ? "cached_token" : null,
26111
26205
  authFailure: "fail",
26112
- isAuthenticated: () => existsSync11(join25(homedir14(), ".grok", "auth.json")),
26206
+ isAuthenticated: () => existsSync11(join24(homedir14(), ".grok", "auth.json")),
26113
26207
  // `--append-system-prompt`/`--rules` are accepted by the CLI but do NOT
26114
26208
  // reach the agent-stdio system prompt (verified against 1.0.0), so the
26115
26209
  // persona is prepended codex-style.
@@ -26122,12 +26216,12 @@ ${turn.text}` : turn.text
26122
26216
  });
26123
26217
 
26124
26218
  // server/drivers/acp/gemini.ts
26125
- import { existsSync as existsSync12, readFileSync as readFileSync12 } from "node:fs";
26219
+ import { existsSync as existsSync12, readFileSync as readFileSync11 } from "node:fs";
26126
26220
  import { homedir as homedir15 } from "node:os";
26127
- import { join as join26 } from "node:path";
26221
+ import { join as join25 } from "node:path";
26128
26222
  function liveOauthCredential() {
26129
26223
  try {
26130
- const creds = JSON.parse(readFileSync12(join26(homedir15(), ".gemini", "oauth_creds.json"), "utf8"));
26224
+ const creds = JSON.parse(readFileSync11(join25(homedir15(), ".gemini", "oauth_creds.json"), "utf8"));
26131
26225
  if (typeof creds.access_token !== "string" || creds.access_token.trim().length === 0) return false;
26132
26226
  const hasRefresh = typeof creds.refresh_token === "string" && creds.refresh_token.trim().length > 0;
26133
26227
  if (creds.expiry_date !== void 0) {
@@ -26140,7 +26234,7 @@ function liveOauthCredential() {
26140
26234
  }
26141
26235
  }
26142
26236
  function geminiIsAuthenticated(env) {
26143
- return nonBlank(env.GEMINI_API_KEY) || nonBlank(env.GOOGLE_API_KEY) || existsSync12(join26(homedir15(), ".gemini", "oauth_creds.json")) && liveOauthCredential();
26237
+ return nonBlank(env.GEMINI_API_KEY) || nonBlank(env.GOOGLE_API_KEY) || existsSync12(join25(homedir15(), ".gemini", "oauth_creds.json")) && liveOauthCredential();
26144
26238
  }
26145
26239
  var AUTH_PREFERENCE, nonBlank, support3, GeminiAgentDriver;
26146
26240
  var init_gemini = __esm({
@@ -26182,17 +26276,17 @@ ${turn.text}` : turn.text
26182
26276
  });
26183
26277
 
26184
26278
  // server/drivers/acp/kimi.ts
26185
- import { existsSync as existsSync13, mkdirSync as mkdirSync10, readFileSync as readFileSync13, writeFileSync as writeFileSync8 } from "node:fs";
26279
+ import { existsSync as existsSync13, mkdirSync as mkdirSync10, readFileSync as readFileSync12, writeFileSync as writeFileSync7 } from "node:fs";
26186
26280
  import { homedir as homedir16 } from "node:os";
26187
- import { join as join27 } from "node:path";
26281
+ import { join as join26 } from "node:path";
26188
26282
  function isLocalInjectAlias(slug) {
26189
26283
  return LOCAL_PROVIDER_PREFIXES.some((prefix) => slug.startsWith(prefix));
26190
26284
  }
26191
26285
  function kimiDataRoot(env) {
26192
- return env.KIMI_CODE_HOME || join27(env.HOME || env.USERPROFILE || homedir16(), ".kimi-code");
26286
+ return env.KIMI_CODE_HOME || join26(env.HOME || env.USERPROFILE || homedir16(), ".kimi-code");
26193
26287
  }
26194
26288
  function credentialsPath(env) {
26195
- return join27(kimiDataRoot(env), "credentials", "kimi-code.json");
26289
+ return join26(kimiDataRoot(env), "credentials", "kimi-code.json");
26196
26290
  }
26197
26291
  function quoteToml2(value) {
26198
26292
  return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
@@ -26480,10 +26574,10 @@ function ensureKimiInjectAlias(modelId, env = process.env) {
26480
26574
  const alias = `${inject.host}/${inject.model.replace(/\//g, "-")}`;
26481
26575
  const dataRoot = kimiDataRoot(env);
26482
26576
  mkdirSync10(dataRoot, { recursive: true });
26483
- const path3 = join27(dataRoot, "config.toml");
26577
+ const path3 = join26(dataRoot, "config.toml");
26484
26578
  let text4 = "";
26485
26579
  try {
26486
- text4 = readFileSync13(path3, "utf8");
26580
+ text4 = readFileSync12(path3, "utf8");
26487
26581
  } catch {
26488
26582
  text4 = "";
26489
26583
  }
@@ -26521,9 +26615,9 @@ function ensureKimiInjectAlias(modelId, env = process.env) {
26521
26615
 
26522
26616
  ` : text4 ? `${text4}
26523
26617
  ` : "";
26524
- writeFileSync8(path3, `${prefix}${blocks.join("\n")}`);
26618
+ writeFileSync7(path3, `${prefix}${blocks.join("\n")}`);
26525
26619
  } else if (text4 !== original) {
26526
- writeFileSync8(path3, text4);
26620
+ writeFileSync7(path3, text4);
26527
26621
  }
26528
26622
  return alias;
26529
26623
  }
@@ -26545,7 +26639,7 @@ function readKimiModelCatalog(env) {
26545
26639
  const dataRoot = kimiDataRoot(env);
26546
26640
  let text4 = "";
26547
26641
  try {
26548
- text4 = readFileSync13(join27(dataRoot, "config.toml"), "utf8");
26642
+ text4 = readFileSync12(join26(dataRoot, "config.toml"), "utf8");
26549
26643
  } catch {
26550
26644
  return STATIC_KIMI_MODELS;
26551
26645
  }
@@ -26664,12 +26758,12 @@ ${turn.text}` : turn.text
26664
26758
  });
26665
26759
 
26666
26760
  // server/drivers/acp/droid.ts
26667
- import { existsSync as existsSync14, mkdirSync as mkdirSync11, readFileSync as readFileSync14, writeFileSync as writeFileSync9 } from "node:fs";
26761
+ import { existsSync as existsSync14, mkdirSync as mkdirSync11, readFileSync as readFileSync13, writeFileSync as writeFileSync8 } from "node:fs";
26668
26762
  import { homedir as homedir17 } from "node:os";
26669
- import { join as join28 } from "node:path";
26763
+ import { join as join27 } from "node:path";
26670
26764
  function authFilePaths(env) {
26671
26765
  const home = env.FACTORY_HOME_OVERRIDE || env.HOME || homedir17();
26672
- return AUTH_FILES.map((name) => join28(home, ".factory", name));
26766
+ return AUTH_FILES.map((name) => join27(home, ".factory", name));
26673
26767
  }
26674
26768
  function droidInjectId(host, model) {
26675
26769
  const safe = `${host}-${model}`.replace(/[^a-zA-Z0-9._+-]+/g, "-").replace(/-+/g, "-");
@@ -26684,12 +26778,12 @@ function ensureDroidInjectModel(modelId, env = process.env) {
26684
26778
  const host = localHost(inject.host);
26685
26779
  if (!host) return modelId;
26686
26780
  const id = droidInjectId(inject.host, inject.model);
26687
- const dir = join28(factoryHome(env), ".factory");
26781
+ const dir = join27(factoryHome(env), ".factory");
26688
26782
  mkdirSync11(dir, { recursive: true });
26689
- const path3 = join28(dir, "settings.json");
26783
+ const path3 = join27(dir, "settings.json");
26690
26784
  let settings2 = {};
26691
26785
  try {
26692
- settings2 = JSON.parse(readFileSync14(path3, "utf8"));
26786
+ settings2 = JSON.parse(readFileSync13(path3, "utf8"));
26693
26787
  } catch (error51) {
26694
26788
  if (existsSync14(path3)) throw error51;
26695
26789
  }
@@ -26701,7 +26795,7 @@ function ensureDroidInjectModel(modelId, env = process.env) {
26701
26795
  if (!match.id) {
26702
26796
  match.id = id;
26703
26797
  settings2.customModels = custom2;
26704
- writeFileSync9(path3, `${JSON.stringify(settings2, null, 2)}
26798
+ writeFileSync8(path3, `${JSON.stringify(settings2, null, 2)}
26705
26799
  `);
26706
26800
  }
26707
26801
  return match.id;
@@ -26715,7 +26809,7 @@ function ensureDroidInjectModel(modelId, env = process.env) {
26715
26809
  provider: "generic-chat-completion-api"
26716
26810
  });
26717
26811
  settings2.customModels = custom2;
26718
- writeFileSync9(path3, `${JSON.stringify(settings2, null, 2)}
26812
+ writeFileSync8(path3, `${JSON.stringify(settings2, null, 2)}
26719
26813
  `);
26720
26814
  return id;
26721
26815
  }
@@ -26726,7 +26820,7 @@ function applyDroidLocalAuthEnv(env, modelId) {
26726
26820
  env.FACTORY_API_KEY = "openmausbot-local";
26727
26821
  }
26728
26822
  function readSettings(env) {
26729
- return JSON.parse(readFileSync14(join28(factoryHome(env), ".factory", "settings.json"), "utf8"));
26823
+ return JSON.parse(readFileSync13(join27(factoryHome(env), ".factory", "settings.json"), "utf8"));
26730
26824
  }
26731
26825
  async function resolveModels(env) {
26732
26826
  let catalog = MODELS3;
@@ -27116,9 +27210,9 @@ ${turn.text}` : turn.text
27116
27210
  });
27117
27211
 
27118
27212
  // server/drivers/acp/opencode-go.ts
27119
- import { existsSync as existsSync15, mkdirSync as mkdirSync12, readFileSync as readFileSync15, writeFileSync as writeFileSync10 } from "node:fs";
27213
+ import { existsSync as existsSync15, mkdirSync as mkdirSync12, readFileSync as readFileSync14, writeFileSync as writeFileSync9 } from "node:fs";
27120
27214
  import { homedir as homedir18 } from "node:os";
27121
- import { join as join29 } from "node:path";
27215
+ import { join as join28 } from "node:path";
27122
27216
  function labelForModel(id) {
27123
27217
  return id.split(/[-_.]+/g).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
27124
27218
  }
@@ -27219,7 +27313,7 @@ async function discoverOpenCodeModels(environment, cli = "opencode") {
27219
27313
  }
27220
27314
  function opencodeConfigDir(env) {
27221
27315
  const home = env.HOME || env.USERPROFILE || homedir18();
27222
- return join29(env.XDG_CONFIG_HOME || join29(home, ".config"), "opencode");
27316
+ return join28(env.XDG_CONFIG_HOME || join28(home, ".config"), "opencode");
27223
27317
  }
27224
27318
  function ensureOpenCodeInjectModel(modelId, env = process.env) {
27225
27319
  const inject = decodeInjectId(modelId);
@@ -27229,11 +27323,11 @@ function ensureOpenCodeInjectModel(modelId, env = process.env) {
27229
27323
  const native = `${inject.host}/${inject.model}`;
27230
27324
  const dir = opencodeConfigDir(env);
27231
27325
  mkdirSync12(dir, { recursive: true });
27232
- const path3 = join29(dir, "opencode.json");
27326
+ const path3 = join28(dir, "opencode.json");
27233
27327
  let config2 = { $schema: "https://opencode.ai/config.json" };
27234
27328
  if (existsSync15(path3)) {
27235
27329
  try {
27236
- config2 = JSON.parse(readFileSync15(path3, "utf8"));
27330
+ config2 = JSON.parse(readFileSync14(path3, "utf8"));
27237
27331
  } catch {
27238
27332
  }
27239
27333
  }
@@ -27248,29 +27342,29 @@ function ensureOpenCodeInjectModel(modelId, env = process.env) {
27248
27342
  const options = existing.options && typeof existing.options === "object" && !Array.isArray(existing.options) ? { ...existing.options } : {};
27249
27343
  options.baseURL = host.baseUrl;
27250
27344
  if (!options.apiKey) options.apiKey = hostApiKey(host, env);
27251
- const models = existing.models && typeof existing.models === "object" && !Array.isArray(existing.models) ? { ...existing.models } : {};
27252
- if (!models[inject.model]) {
27253
- models[inject.model] = { name: `${inject.model} (${host.label})` };
27345
+ const models2 = existing.models && typeof existing.models === "object" && !Array.isArray(existing.models) ? { ...existing.models } : {};
27346
+ if (!models2[inject.model]) {
27347
+ models2[inject.model] = { name: `${inject.model} (${host.label})` };
27254
27348
  }
27255
27349
  providers[inject.host] = {
27256
27350
  ...existing,
27257
27351
  npm: existing.npm || "@ai-sdk/openai-compatible",
27258
27352
  name: existing.name || host.label,
27259
27353
  options,
27260
- models
27354
+ models: models2
27261
27355
  };
27262
27356
  config2.provider = providers;
27263
- writeFileSync10(path3, `${JSON.stringify(config2, null, 2)}
27357
+ writeFileSync9(path3, `${JSON.stringify(config2, null, 2)}
27264
27358
  `);
27265
27359
  return native;
27266
27360
  }
27267
27361
  function storedAuthPaths(env) {
27268
27362
  const home = env.HOME || env.USERPROFILE || homedir18();
27269
27363
  const roots = [
27270
- env.XDG_DATA_HOME || join29(home, ".local", "share"),
27271
- process.platform === "darwin" ? join29(home, "Library", "Application Support") : process.platform === "win32" ? env.LOCALAPPDATA || join29(home, "AppData", "Local") : ""
27364
+ env.XDG_DATA_HOME || join28(home, ".local", "share"),
27365
+ process.platform === "darwin" ? join28(home, "Library", "Application Support") : process.platform === "win32" ? env.LOCALAPPDATA || join28(home, "AppData", "Local") : ""
27272
27366
  ].filter(Boolean);
27273
- return [...new Set(roots)].map((root) => join29(root, "opencode", "auth.json"));
27367
+ return [...new Set(roots)].map((root) => join28(root, "opencode", "auth.json"));
27274
27368
  }
27275
27369
  function usableAuthEntry(parsed) {
27276
27370
  return Object.values(parsed).some((auth) => {
@@ -27284,7 +27378,7 @@ function hasStoredOpenCodeAuth(env) {
27284
27378
  if (env.OPENCODE_AUTH_CONTENT) candidates.push(env.OPENCODE_AUTH_CONTENT);
27285
27379
  for (const path3 of storedAuthPaths(env)) {
27286
27380
  try {
27287
- candidates.push(readFileSync15(path3, "utf8"));
27381
+ candidates.push(readFileSync14(path3, "utf8"));
27288
27382
  } catch {
27289
27383
  }
27290
27384
  }
@@ -27400,13 +27494,13 @@ ${turn.text}` : turn.text
27400
27494
  });
27401
27495
 
27402
27496
  // server/drivers/acp/qwen.ts
27403
- import { chmodSync as chmodSync5, existsSync as existsSync16, mkdirSync as mkdirSync13, readFileSync as readFileSync16, writeFileSync as writeFileSync11 } from "node:fs";
27497
+ import { chmodSync as chmodSync5, existsSync as existsSync16, mkdirSync as mkdirSync13, readFileSync as readFileSync15, writeFileSync as writeFileSync10 } from "node:fs";
27404
27498
  import { createHash as createHash7 } from "node:crypto";
27405
27499
  import { homedir as homedir19 } from "node:os";
27406
- import { join as join30 } from "node:path";
27500
+ import { join as join29 } from "node:path";
27407
27501
  function qwenHome(env) {
27408
27502
  const home = process.platform === "win32" ? env.USERPROFILE || env.HOME || homedir19() : env.HOME || env.USERPROFILE || homedir19();
27409
- return join30(home, ".qwen");
27503
+ return join29(home, ".qwen");
27410
27504
  }
27411
27505
  function isRecord2(value) {
27412
27506
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
@@ -27418,7 +27512,7 @@ function qwenModelLabel(id, name) {
27418
27512
  function readQwenRoutes(env) {
27419
27513
  let settings2;
27420
27514
  try {
27421
- settings2 = JSON.parse(readFileSync16(join30(qwenHome(env), "settings.json"), "utf8"));
27515
+ settings2 = JSON.parse(readFileSync15(join29(qwenHome(env), "settings.json"), "utf8"));
27422
27516
  } catch {
27423
27517
  return [];
27424
27518
  }
@@ -27502,11 +27596,11 @@ function ensureQwenInjectModel(modelId, env = process.env) {
27502
27596
  if (!host) return modelId;
27503
27597
  const dir = qwenHome(env);
27504
27598
  mkdirSync13(dir, { recursive: true });
27505
- const path3 = join30(dir, "settings.json");
27599
+ const path3 = join29(dir, "settings.json");
27506
27600
  let settings2 = {};
27507
27601
  if (existsSync16(path3)) {
27508
27602
  try {
27509
- settings2 = JSON.parse(readFileSync16(path3, "utf8"));
27603
+ settings2 = JSON.parse(readFileSync15(path3, "utf8"));
27510
27604
  } catch {
27511
27605
  }
27512
27606
  }
@@ -27530,7 +27624,7 @@ function ensureQwenInjectModel(modelId, env = process.env) {
27530
27624
  providers.openai = openai;
27531
27625
  settings2.modelProviders = providers;
27532
27626
  }
27533
- writeFileSync11(path3, `${JSON.stringify(settings2, null, 2)}
27627
+ writeFileSync10(path3, `${JSON.stringify(settings2, null, 2)}
27534
27628
  `, { mode: 384 });
27535
27629
  try {
27536
27630
  chmodSync5(path3, 384);
@@ -29594,7 +29688,7 @@ var init_YAMLMap = __esm({
29594
29688
  static from(schema4, obj, ctx) {
29595
29689
  const { keepUndefined, replacer } = ctx;
29596
29690
  const map3 = new this(schema4);
29597
- const add2 = (key, value) => {
29691
+ const add = (key, value) => {
29598
29692
  if (typeof replacer === "function")
29599
29693
  value = replacer.call(obj, key, value);
29600
29694
  else if (Array.isArray(replacer) && !replacer.includes(key))
@@ -29604,10 +29698,10 @@ var init_YAMLMap = __esm({
29604
29698
  };
29605
29699
  if (obj instanceof Map) {
29606
29700
  for (const [key, value] of obj)
29607
- add2(key, value);
29701
+ add(key, value);
29608
29702
  } else if (obj && typeof obj === "object") {
29609
29703
  for (const key of Object.keys(obj))
29610
- add2(key, obj[key]);
29704
+ add(key, obj[key]);
29611
29705
  }
29612
29706
  if (typeof schema4.sortMapEntries === "function") {
29613
29707
  map3.items.sort(schema4.sortMapEntries);
@@ -34501,9 +34595,9 @@ var init_browser = __esm({
34501
34595
 
34502
34596
  // server/drivers/acp/hermes.ts
34503
34597
  import { spawn as spawn6 } from "node:child_process";
34504
- import { mkdirSync as mkdirSync14, readFileSync as readFileSync17, writeFileSync as writeFileSync12 } from "node:fs";
34598
+ import { mkdirSync as mkdirSync14, readFileSync as readFileSync16, writeFileSync as writeFileSync11 } from "node:fs";
34505
34599
  import { homedir as homedir20 } from "node:os";
34506
- import { join as join31 } from "node:path";
34600
+ import { join as join30 } from "node:path";
34507
34601
  function bindHermesScreenshotCompat(env, modelId) {
34508
34602
  delete env[HERMES_OPENMAUS_SCREENSHOT_COMPAT];
34509
34603
  delete env[HERMES_OPENMAUS_SCREENSHOT_COMPAT_MODEL];
@@ -34513,7 +34607,7 @@ function bindHermesScreenshotCompat(env, modelId) {
34513
34607
  env[HERMES_OPENMAUS_SCREENSHOT_COMPAT_MODEL] = inject.model;
34514
34608
  }
34515
34609
  function hermesHome(env) {
34516
- return env.HERMES_HOME || join31(env.HOME || env.USERPROFILE || homedir20(), ".hermes");
34610
+ return env.HERMES_HOME || join30(env.HOME || env.USERPROFILE || homedir20(), ".hermes");
34517
34611
  }
34518
34612
  function quoteYaml(value) {
34519
34613
  if (/^[\w./:+-]+$/.test(value)) return value;
@@ -34555,15 +34649,15 @@ function ensureHermesInjectProvider(modelId, env = process.env) {
34555
34649
  if (!host) return modelId;
34556
34650
  const dir = hermesHome(env);
34557
34651
  mkdirSync14(dir, { recursive: true });
34558
- const path3 = join31(dir, "config.yaml");
34652
+ const path3 = join30(dir, "config.yaml");
34559
34653
  let text4 = "";
34560
34654
  try {
34561
- text4 = readFileSync17(path3, "utf8");
34655
+ text4 = readFileSync16(path3, "utf8");
34562
34656
  } catch {
34563
34657
  text4 = "";
34564
34658
  }
34565
34659
  const next = upsertHermesProvider(text4, inject.host, host.baseUrl, hostApiKey(host, env));
34566
- if (next !== text4) writeFileSync12(path3, next);
34660
+ if (next !== text4) writeFileSync11(path3, next);
34567
34661
  return hermesAcpModelId(modelId) ?? modelId;
34568
34662
  }
34569
34663
  function hermesAcpModelId(modelId) {
@@ -34626,13 +34720,13 @@ function hermesConfiguredModel(env = process.env) {
34626
34720
  const dir = hermesHome(env);
34627
34721
  let secrets = "";
34628
34722
  try {
34629
- secrets = readFileSync17(join31(dir, ".env"), "utf8");
34723
+ secrets = readFileSync16(join30(dir, ".env"), "utf8");
34630
34724
  } catch {
34631
34725
  }
34632
34726
  const hasHostedProviderKey = HERMES_HOSTED_PROVIDER_KEYS.some((name) => nonEmptyDotenvValue(secrets, name));
34633
34727
  let configuredDefault = null;
34634
34728
  try {
34635
- configuredDefault = hermesConfigDefault(readFileSync17(join31(dir, "config.yaml"), "utf8"));
34729
+ configuredDefault = hermesConfigDefault(readFileSync16(join30(dir, "config.yaml"), "utf8"));
34636
34730
  } catch {
34637
34731
  }
34638
34732
  const configuredProvider = configuredDefault?.provider.toLowerCase() ?? "";
@@ -34647,11 +34741,18 @@ function hermesConfiguredModel(env = process.env) {
34647
34741
  custom: true
34648
34742
  };
34649
34743
  }
34744
+ function hermesAcpModelsTimeoutMs(env) {
34745
+ const raw = env[HERMES_ACP_MODELS_TIMEOUT_ENV];
34746
+ if (raw === void 0) return HERMES_ACP_MODELS_DEFAULT_TIMEOUT_MS;
34747
+ const parsed = Number(raw);
34748
+ return Number.isInteger(parsed) && parsed > 0 && parsed <= 2147483647 ? parsed : HERMES_ACP_MODELS_DEFAULT_TIMEOUT_MS;
34749
+ }
34650
34750
  async function fetchHermesAcpModels(cli, env) {
34651
34751
  return await new Promise((resolve13) => {
34652
34752
  let child;
34653
34753
  try {
34654
- child = spawn6(cli, ["acp"], { stdio: ["pipe", "pipe", "ignore"], env });
34754
+ const resolved = resolveCli(cli, ["acp"], env);
34755
+ child = spawn6(resolved.command, resolved.args, { stdio: ["pipe", "pipe", "ignore"], env, windowsHide: true });
34655
34756
  } catch {
34656
34757
  return resolve13([]);
34657
34758
  }
@@ -34676,7 +34777,7 @@ async function fetchHermesAcpModels(cli, env) {
34676
34777
  }
34677
34778
  resolve13(out);
34678
34779
  };
34679
- timer = setTimeout(() => done([]), 5e3);
34780
+ timer = setTimeout(() => done([]), hermesAcpModelsTimeoutMs(env));
34680
34781
  child.once("error", () => done([]));
34681
34782
  child.once("close", () => {
34682
34783
  if (hardKillTimer) clearTimeout(hardKillTimer);
@@ -34756,11 +34857,12 @@ async function applySetting2(request, method, params, what) {
34756
34857
  throw new Error(`Hermes rejected ${what} via ${method}: ${e.message}`);
34757
34858
  }
34758
34859
  }
34759
- var EMPTY2, HERMES_OPENMAUS_SCREENSHOT_COMPAT, HERMES_OPENMAUS_SCREENSHOT_COMPAT_MODEL, HERMES_CONFIG_MODEL_ID, HERMES_HOSTED_PROVIDER_KEYS, HERMES_LOCAL_CONFIG_PROVIDERS, support10, HermesAgentDriver;
34860
+ var EMPTY2, HERMES_OPENMAUS_SCREENSHOT_COMPAT, HERMES_OPENMAUS_SCREENSHOT_COMPAT_MODEL, HERMES_CONFIG_MODEL_ID, HERMES_HOSTED_PROVIDER_KEYS, HERMES_LOCAL_CONFIG_PROVIDERS, HERMES_ACP_MODELS_TIMEOUT_ENV, HERMES_ACP_MODELS_DEFAULT_TIMEOUT_MS, support10, HermesAgentDriver;
34760
34861
  var init_hermes = __esm({
34761
34862
  "server/drivers/acp/hermes.ts"() {
34762
34863
  "use strict";
34763
34864
  init_browser();
34865
+ init_procs();
34764
34866
  init_local_inject();
34765
34867
  init_core3();
34766
34868
  EMPTY2 = { default: "", options: [] };
@@ -34774,6 +34876,8 @@ var init_hermes = __esm({
34774
34876
  "Z_AI_API_KEY"
34775
34877
  ];
34776
34878
  HERMES_LOCAL_CONFIG_PROVIDERS = /* @__PURE__ */ new Set(["custom", "lmstudio", "ollama", "vllm", "llamacpp"]);
34879
+ HERMES_ACP_MODELS_TIMEOUT_ENV = "HERMES_ACP_MODELS_TIMEOUT_MS";
34880
+ HERMES_ACP_MODELS_DEFAULT_TIMEOUT_MS = 15e3;
34777
34881
  support10 = {
34778
34882
  driverKind: "hermesAgent",
34779
34883
  displayName: "Hermes",
@@ -34846,12 +34950,19 @@ function decodeConfig5(raw) {
34846
34950
  provider: typeof config2.provider === "string" ? config2.provider || void 0 : process.env.OPENAI_COMPAT_PROVIDER || void 0
34847
34951
  };
34848
34952
  }
34849
- var DRIVER_KIND5, DEFAULT_MODELS, OpenAICompatDriver;
34953
+ var DRIVER_KIND5, DEFAULT_IDLE_TIMEOUT_MS, idleTimeoutMs, DEFAULT_MODELS, OpenAICompatDriver;
34850
34954
  var init_openai_compat = __esm({
34851
34955
  "server/drivers/openai-compat.ts"() {
34852
34956
  "use strict";
34853
34957
  init_openai_chat();
34854
34958
  DRIVER_KIND5 = "openai-compat";
34959
+ DEFAULT_IDLE_TIMEOUT_MS = 18e4;
34960
+ idleTimeoutMs = () => {
34961
+ const raw = process.env.OPENMAUS_OPENAI_COMPAT_IDLE_TIMEOUT_MS;
34962
+ if (!raw) return DEFAULT_IDLE_TIMEOUT_MS;
34963
+ const value = Number(raw);
34964
+ return Number.isSafeInteger(value) && value >= 1e3 && value <= 2147483647 ? value : DEFAULT_IDLE_TIMEOUT_MS;
34965
+ };
34855
34966
  DEFAULT_MODELS = {
34856
34967
  default: "meta-llama/llama-3.3-70b-instruct",
34857
34968
  options: [
@@ -34932,7 +35043,7 @@ var init_openai_compat = __esm({
34932
35043
  httpErrorLabel: "upstream",
34933
35044
  missingKeyError: `no API key \u2014 set ${config2.apiKeyEnv} or add it to the instance config`,
34934
35045
  unavailableReason: `no API key \u2014 set ${config2.apiKeyEnv} or add it to the instance config`,
34935
- timeoutMs: 12e4,
35046
+ timeoutMs: idleTimeoutMs(),
34936
35047
  reasoning: true,
34937
35048
  billing: "metered",
34938
35049
  includeUsageInCompleted: true,
@@ -34952,13 +35063,13 @@ var init_openai_compat = __esm({
34952
35063
  });
34953
35064
 
34954
35065
  // server/drivers/pi.ts
34955
- import { chmodSync as chmodSync6, existsSync as existsSync17, mkdirSync as mkdirSync15, mkdtempSync as mkdtempSync3, readFileSync as readFileSync18, rmSync as rmSync5, writeFileSync as writeFileSync13 } from "node:fs";
35066
+ import { chmodSync as chmodSync6, existsSync as existsSync17, mkdirSync as mkdirSync15, mkdtempSync as mkdtempSync3, readFileSync as readFileSync17, rmSync as rmSync5, writeFileSync as writeFileSync12 } from "node:fs";
34956
35067
  import { homedir as homedir21, tmpdir as tmpdir4 } from "node:os";
34957
- import { join as join32 } from "node:path";
35068
+ import { join as join31 } from "node:path";
34958
35069
  function readPiPromptImages(turn) {
34959
35070
  return (turn.images ?? []).map((image) => ({
34960
35071
  type: "image",
34961
- data: readFileSync18(image.path).toString("base64"),
35072
+ data: readFileSync17(image.path).toString("base64"),
34962
35073
  mimeType: image.mime
34963
35074
  }));
34964
35075
  }
@@ -35061,7 +35172,7 @@ async function applyPiLocalCatalog(catalog, env = process.env, fetchImpl = fetch
35061
35172
  return preferPiInjectRows(await mergeLocalInject(catalog, env, fetchImpl));
35062
35173
  }
35063
35174
  function piAgentDir(env) {
35064
- return join32(env.HOME || env.USERPROFILE || homedir21(), ".pi", "agent");
35175
+ return join31(env.HOME || env.USERPROFILE || homedir21(), ".pi", "agent");
35065
35176
  }
35066
35177
  function ensurePiInjectModel(modelId, env = process.env) {
35067
35178
  const split = splitPiModel(modelId);
@@ -35072,11 +35183,11 @@ function ensurePiInjectModel(modelId, env = process.env) {
35072
35183
  if (!host) return split;
35073
35184
  const dir = piAgentDir(env);
35074
35185
  mkdirSync15(dir, { recursive: true });
35075
- const path3 = join32(dir, "models.json");
35186
+ const path3 = join31(dir, "models.json");
35076
35187
  let root = { providers: {} };
35077
35188
  if (existsSync17(path3)) {
35078
35189
  try {
35079
- const parsed = JSON.parse(readFileSync18(path3, "utf8"));
35190
+ const parsed = JSON.parse(readFileSync17(path3, "utf8"));
35080
35191
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
35081
35192
  root = parsed;
35082
35193
  } else {
@@ -35101,11 +35212,11 @@ function ensurePiInjectModel(modelId, env = process.env) {
35101
35212
  if (!existing.compat) {
35102
35213
  existing.compat = { supportsDeveloperRole: false, supportsReasoningEffort: true };
35103
35214
  }
35104
- const models = Array.isArray(existing.models) ? existing.models.filter(
35215
+ const models2 = Array.isArray(existing.models) ? existing.models.filter(
35105
35216
  (row) => Boolean(row) && typeof row === "object" && !Array.isArray(row)
35106
35217
  ) : [];
35107
- if (!models.some((row) => row.id === inject.model)) {
35108
- models.push({
35218
+ if (!models2.some((row) => row.id === inject.model)) {
35219
+ models2.push({
35109
35220
  id: inject.model,
35110
35221
  name: inject.model,
35111
35222
  reasoning: true,
@@ -35114,10 +35225,10 @@ function ensurePiInjectModel(modelId, env = process.env) {
35114
35225
  maxTokens: 16384
35115
35226
  });
35116
35227
  }
35117
- existing.models = models;
35228
+ existing.models = models2;
35118
35229
  providers[inject.host] = existing;
35119
35230
  root.providers = providers;
35120
- writeFileSync13(path3, `${JSON.stringify(root, null, 2)}
35231
+ writeFileSync12(path3, `${JSON.stringify(root, null, 2)}
35121
35232
  `, { mode: 384 });
35122
35233
  try {
35123
35234
  chmodSync6(path3, 384);
@@ -35128,7 +35239,7 @@ function ensurePiInjectModel(modelId, env = process.env) {
35128
35239
  function readPiDefaultModel(env) {
35129
35240
  const home = env.HOME || env.USERPROFILE || homedir21();
35130
35241
  try {
35131
- const s = JSON.parse(readFileSync18(`${home}/.pi/agent/settings.json`, "utf8"));
35242
+ const s = JSON.parse(readFileSync17(`${home}/.pi/agent/settings.json`, "utf8"));
35132
35243
  return s.defaultProvider && s.defaultModel ? `${s.defaultProvider}/${s.defaultModel}` : "";
35133
35244
  } catch {
35134
35245
  return "";
@@ -35176,6 +35287,16 @@ async function fetchPiModels(cli, env) {
35176
35287
  }
35177
35288
  });
35178
35289
  }
35290
+ async function updatePiModelCatalog(cli, env) {
35291
+ return new Promise((resolve13) => {
35292
+ execCli(
35293
+ cli,
35294
+ PI_MODEL_UPDATE_ARGS,
35295
+ { env, timeout: 6e4, maxBuffer: 1024 * 1024 },
35296
+ (error51) => resolve13(!error51)
35297
+ );
35298
+ });
35299
+ }
35179
35300
  function decodeConfig6(raw) {
35180
35301
  if (raw === null || raw === void 0) return { cli: "pi", fullAuto: false };
35181
35302
  if (typeof raw !== "object") throw new Error("pi config must be an object");
@@ -35193,7 +35314,7 @@ function piEnvironment(source) {
35193
35314
  for (const key of PROVIDER_CREDENTIAL_ENV) delete env[key];
35194
35315
  return env;
35195
35316
  }
35196
- var DRIVER_KIND6, PI_ARGS, NODE_ENV_FLAG2, EMPTY3, PiDriver;
35317
+ var DRIVER_KIND6, PI_ARGS, PI_MODEL_UPDATE_ARGS, NODE_ENV_FLAG2, EMPTY3, PiDriver;
35197
35318
  var init_pi = __esm({
35198
35319
  "server/drivers/pi.ts"() {
35199
35320
  "use strict";
@@ -35202,11 +35323,13 @@ var init_pi = __esm({
35202
35323
  init_env_path();
35203
35324
  init_procs();
35204
35325
  init_proxy_paths();
35326
+ init_tool_summary();
35205
35327
  init_contracts();
35206
35328
  init_local_inject();
35207
35329
  init_native();
35208
35330
  DRIVER_KIND6 = "piAgent";
35209
35331
  PI_ARGS = ["--mode", "rpc", "--no-session"];
35332
+ PI_MODEL_UPDATE_ARGS = ["update", "--models", "--no-approve"];
35210
35333
  NODE_ENV_FLAG2 = { ELECTRON_RUN_AS_NODE: "1" };
35211
35334
  EMPTY3 = { default: "", options: [] };
35212
35335
  PiDriver = {
@@ -35227,9 +35350,9 @@ var init_pi = __esm({
35227
35350
  async create(input) {
35228
35351
  const { instanceId, config: config2 } = input;
35229
35352
  const catalogEnv = piEnvironment({ ...process.env, ...input.environment });
35230
- let models = EMPTY3;
35231
- const refreshModels = async () => {
35232
- let base3 = models;
35353
+ let models2 = EMPTY3;
35354
+ const readModels = async () => {
35355
+ let base3 = models2;
35233
35356
  try {
35234
35357
  const resolved = await fetchPiModels(config2.cli, catalogEnv);
35235
35358
  if (resolved.options.length) base3 = resolved;
@@ -35237,12 +35360,16 @@ var init_pi = __esm({
35237
35360
  }
35238
35361
  try {
35239
35362
  const next = await applyPiLocalCatalog(base3, catalogEnv);
35240
- if (next.options.length) models = next;
35363
+ if (next.options.length) models2 = next;
35241
35364
  } catch {
35242
- if (base3.options.length) models = base3;
35365
+ if (base3.options.length) models2 = base3;
35243
35366
  }
35244
35367
  };
35245
- await refreshModels();
35368
+ const refreshModels = async () => {
35369
+ await updatePiModelCatalog(config2.cli, catalogEnv);
35370
+ await readModels();
35371
+ };
35372
+ await readModels();
35246
35373
  const listeners = /* @__PURE__ */ new Set();
35247
35374
  const active = /* @__PURE__ */ new Map();
35248
35375
  const emit = (event) => {
@@ -35274,9 +35401,9 @@ var init_pi = __esm({
35274
35401
  const mcpServers = buildMcpServers(turn);
35275
35402
  let mcpTempDir = null;
35276
35403
  if (mcpServers) {
35277
- mcpTempDir = mkdtempSync3(join32(tmpdir4(), "omb-pi-mcp-"));
35404
+ mcpTempDir = mkdtempSync3(join31(tmpdir4(), "omb-pi-mcp-"));
35278
35405
  try {
35279
- writeFileSync13(join32(mcpTempDir, "mcp.json"), JSON.stringify({ mcpServers }), { mode: 384 });
35406
+ writeFileSync12(join31(mcpTempDir, "mcp.json"), JSON.stringify({ mcpServers }), { mode: 384 });
35280
35407
  } catch (err) {
35281
35408
  try {
35282
35409
  rmSync5(mcpTempDir, { recursive: true, force: true });
@@ -35294,7 +35421,7 @@ var init_pi = __esm({
35294
35421
  env: piEnvironment({
35295
35422
  ...process.env,
35296
35423
  ...input.environment,
35297
- ...mcpServers && mcpTempDir ? { OMB_MCP_CONFIG: join32(mcpTempDir, "mcp.json") } : {}
35424
+ ...mcpServers && mcpTempDir ? { OMB_MCP_CONFIG: join31(mcpTempDir, "mcp.json") } : {}
35298
35425
  })
35299
35426
  });
35300
35427
  } catch (err) {
@@ -35406,7 +35533,9 @@ var init_pi = __esm({
35406
35533
  type: "item.started",
35407
35534
  itemType: "tool",
35408
35535
  itemId: evt.toolCallId,
35409
- title: String(evt.toolName ?? "tool").slice(0, 80)
35536
+ title: String(evt.toolName ?? "tool").slice(0, 80),
35537
+ summary: commandSummary(evt.args),
35538
+ input: toolDetailPreview(evt.args)
35410
35539
  });
35411
35540
  return;
35412
35541
  }
@@ -35416,7 +35545,8 @@ var init_pi = __esm({
35416
35545
  type: "item.completed",
35417
35546
  itemType: "tool",
35418
35547
  itemId: evt.toolCallId,
35419
- ok: !evt.isError
35548
+ ok: !evt.isError,
35549
+ output: toolDetailPreview(evt.result)
35420
35550
  });
35421
35551
  return;
35422
35552
  }
@@ -35565,7 +35695,7 @@ ${turn.text}` : turn.text;
35565
35695
  displayName: input.displayName,
35566
35696
  enabled: input.enabled,
35567
35697
  get models() {
35568
- return models;
35698
+ return models2;
35569
35699
  },
35570
35700
  refreshModels,
35571
35701
  snapshot,
@@ -35631,16 +35761,16 @@ ${turn.text}` : turn.text;
35631
35761
  });
35632
35762
 
35633
35763
  // server/drivers/minimax.ts
35634
- import { readFileSync as readFileSync19 } from "node:fs";
35764
+ import { readFileSync as readFileSync18 } from "node:fs";
35635
35765
  import { homedir as homedir22 } from "node:os";
35636
- import { join as join33 } from "node:path";
35766
+ import { join as join32 } from "node:path";
35637
35767
  function normalizedApiUrl(value) {
35638
35768
  const root = value.trim().replace(/\/+$/, "");
35639
35769
  return root.endsWith("/v1") ? root : `${root}/v1`;
35640
35770
  }
35641
35771
  function loadLocalMiniMaxConfig(home = homedir22()) {
35642
35772
  try {
35643
- const raw = localConfigSchema.parse(JSON.parse(readFileSync19(join33(home, ".mmx", "config.json"), "utf8")));
35773
+ const raw = localConfigSchema.parse(JSON.parse(readFileSync18(join32(home, ".mmx", "config.json"), "utf8")));
35644
35774
  const configuredUrl = raw.base_url?.trim() ? raw.base_url : raw.region === "cn" ? CN_URL : DEFAULT_URL2;
35645
35775
  return {
35646
35776
  apiKey: raw.api_key?.trim() ?? "",
@@ -35703,13 +35833,13 @@ var init_minimax = __esm({
35703
35833
  const local = loadLocalMiniMaxConfig();
35704
35834
  const apiKey = input.environment[API_KEY_ENV]?.trim() || process.env[API_KEY_ENV]?.trim() || local.apiKey;
35705
35835
  const apiUrl = input.config.url === DEFAULT_URL2 && local.url !== DEFAULT_URL2 ? local.url : input.config.url;
35706
- const models = local.defaultModel && MODELS4.options.some((model) => model.id === local.defaultModel) ? { ...MODELS4, default: local.defaultModel } : MODELS4;
35836
+ const models2 = local.defaultModel && MODELS4.options.some((model) => model.id === local.defaultModel) ? { ...MODELS4, default: local.defaultModel } : MODELS4;
35707
35837
  return createOpenAIChatRuntime({
35708
35838
  input,
35709
35839
  driverKind: DRIVER_KIND7,
35710
35840
  apiKey,
35711
35841
  apiUrl,
35712
- models: () => models,
35842
+ models: () => models2,
35713
35843
  requestBody: (model, messages, stream) => ({
35714
35844
  model,
35715
35845
  messages,
@@ -35854,16 +35984,16 @@ async function setupJson(url2, key, body) {
35854
35984
  }
35855
35985
  async function fetchSetupModels(url2, key) {
35856
35986
  const json2 = record2(await setupJson(url2, key));
35857
- const models = /* @__PURE__ */ new Map();
35987
+ const models2 = /* @__PURE__ */ new Map();
35858
35988
  for (const entry of Array.isArray(json2?.data) ? json2.data : []) {
35859
35989
  const item = record2(entry);
35860
35990
  const id = typeof item?.id === "string" ? item.id.trim() : "";
35861
- if (!id || /[\u0000-\u001f\u007f]/u.test(id) || models.has(id)) continue;
35991
+ if (!id || /[\u0000-\u001f\u007f]/u.test(id) || models2.has(id)) continue;
35862
35992
  const name = typeof item?.name === "string" ? item.name.replace(/[\u0000-\u001f\u007f]/gu, "").trim() : "";
35863
- models.set(id, { id, label: name || id });
35993
+ models2.set(id, { id, label: name || id });
35864
35994
  }
35865
- if (!models.size) throw new SetupApiError("The API returned no usable models. Check the API URL and model access, or enter a model ID from your provider manually.");
35866
- return [...models.values()];
35995
+ if (!models2.size) throw new SetupApiError("The API returned no usable models. Check the API URL and model access, or enter a model ID from your provider manually.");
35996
+ return [...models2.values()];
35867
35997
  }
35868
35998
  async function verifySetupCompletion(url2, key, model, provider) {
35869
35999
  const id = model.trim();
@@ -35907,8 +36037,8 @@ __export(cli_setup_exports, {
35907
36037
  saveCliStartup: () => saveCliStartup
35908
36038
  });
35909
36039
  import { spawn as spawn7 } from "node:child_process";
35910
- import { readFileSync as readFileSync20 } from "node:fs";
35911
- import { join as join34, resolve as resolve10 } from "node:path";
36040
+ import { readFileSync as readFileSync19 } from "node:fs";
36041
+ import { join as join33, resolve as resolve10 } from "node:path";
35912
36042
  import { randomUUID as randomUUID10 } from "node:crypto";
35913
36043
  async function inspect(id, entry) {
35914
36044
  const registry2 = new ProviderRegistry(BUILT_IN_DRIVERS);
@@ -35945,7 +36075,7 @@ function assertDataDir(dataDir) {
35945
36075
  }
35946
36076
  function checkStoredConfig(dataDir) {
35947
36077
  try {
35948
- parseStoredConfig(JSON.parse(readFileSync20(join34(dataDir, "config.json"), "utf8")));
36078
+ parseStoredConfig(JSON.parse(readFileSync19(join33(dataDir, "config.json"), "utf8")));
35949
36079
  } catch (error51) {
35950
36080
  if (error51.code === "ENOENT") return;
35951
36081
  throw new Error("Existing config.json could not be read or validated. Setup has not changed it; repair it before continuing.");
@@ -35977,14 +36107,14 @@ function saveCliStartup(dataDir, settings2) {
35977
36107
  function rawObject(value) {
35978
36108
  return value && typeof value === "object" && !Array.isArray(value) ? value : {};
35979
36109
  }
35980
- async function chooseModel(io, models) {
35981
- if (!models.options.length) throw new Error("This connection returned no models. Check the provider and try again.");
36110
+ async function chooseModel(io, models2) {
36111
+ if (!models2.options.length) throw new Error("This connection returned no models. Check the provider and try again.");
35982
36112
  let query = "";
35983
36113
  for (; ; ) {
35984
- const matches = models.options.filter((m) => `${m.label} ${m.id}`.toLowerCase().includes(query.toLowerCase()));
35985
- const preferred = matches.find((m) => m.id === models.default);
36114
+ const matches = models2.options.filter((m) => `${m.label} ${m.id}`.toLowerCase().includes(query.toLowerCase()));
36115
+ const preferred = matches.find((m) => m.id === models2.default);
35986
36116
  const visible = (preferred ? [preferred, ...matches.filter((m) => m !== preferred)] : matches).slice(0, 20);
35987
- const defaultIndex = visible.findIndex((m) => m.id === models.default);
36117
+ const defaultIndex = visible.findIndex((m) => m.id === models2.default);
35988
36118
  const selected = await io.choose("Choose your model", [
35989
36119
  ...visible.map((m) => m.label.trim() || m.id),
35990
36120
  "Search models\u2026"
@@ -36058,11 +36188,11 @@ async function runSetup(options, io = defaultSetupIo(), deps = dependencies) {
36058
36188
  id = prior?.[0] ?? native.id;
36059
36189
  if (!prior && runtime[id] && runtime[id].driver !== native.driver) id = `${native.id}-${randomUUID10().slice(0, 8)}`;
36060
36190
  entry = { ...cfg.instances?.[id] ?? { driver: native.driver }, enabled: true };
36061
- const models = await connectNative(native, id, entry, io, deps);
36191
+ const models2 = await connectNative(native, id, entry, io, deps);
36062
36192
  const saved = cfg.defaultModelSelection;
36063
36193
  model = await chooseModel(io, {
36064
- ...models,
36065
- default: saved?.instanceId === id ? saved.model : models.default
36194
+ ...models2,
36195
+ default: saved?.instanceId === id ? saved.model : models2.default
36066
36196
  });
36067
36197
  } else {
36068
36198
  io.log("Connect an API key");
@@ -36092,21 +36222,21 @@ async function runSetup(options, io = defaultSetupIo(), deps = dependencies) {
36092
36222
  }
36093
36223
  if (!key) throw new Error("An API key is required. Nothing was saved.");
36094
36224
  io.log("Checking the model catalog\u2026");
36095
- let models;
36225
+ let models2;
36096
36226
  try {
36097
- models = await deps.models(url2, key);
36227
+ models2 = await deps.models(url2, key);
36098
36228
  } catch (error51) {
36099
36229
  if (error51 instanceof SetupCancelled) throw error51;
36100
36230
  io.log(error51 instanceof Error ? error51.message : "Could not load this model catalog.");
36101
36231
  if (!await io.confirm("Enter an exact chat model ID and check it directly instead?", false)) throw error51;
36102
36232
  const manual = (await io.ask("Chat model ID: ")).trim();
36103
36233
  if (!manual) throw new Error("A model ID is required. Nothing was saved.");
36104
- models = [{ id: manual, label: manual }];
36234
+ models2 = [{ id: manual, label: manual }];
36105
36235
  }
36106
36236
  const previousModel = prior ? rawObject(prior[1].config).model : void 0;
36107
36237
  const preferredModel = cfg.defaultModelSelection?.instanceId === prior?.[0] ? cfg.defaultModelSelection?.model : previousModel;
36108
36238
  io.log("Choose a chat model; image, audio and embedding-only models cannot reply here.");
36109
- model = await chooseModel(io, { default: typeof preferredModel === "string" ? preferredModel : "", options: models });
36239
+ model = await chooseModel(io, { default: typeof preferredModel === "string" ? preferredModel : "", options: models2 });
36110
36240
  if (!await io.confirm("Send one short test message? Your API provider may charge for this request.", true)) throw new SetupCancelled();
36111
36241
  if (routingProvider) await deps.verify(url2, key, model, routingProvider);
36112
36242
  else await deps.verify(url2, key, model);
@@ -36223,7 +36353,7 @@ var init_browser_engine_release = __esm({
36223
36353
  });
36224
36354
 
36225
36355
  // server/browser-bundle-release.ts
36226
- import { join as join35 } from "node:path";
36356
+ import { join as join34 } from "node:path";
36227
36357
  function browserBundleSpec(target) {
36228
36358
  if (!Object.hasOwn(CHROME_ASSETS, target)) throw new Error(`Unsupported desktop browser target: ${target}`);
36229
36359
  const pinned = CHROME_ASSETS[target];
@@ -36259,10 +36389,10 @@ function browserBundlePaths(bundleDirectory, target) {
36259
36389
  const spec = browserBundleSpec(target);
36260
36390
  return {
36261
36391
  directory: bundleDirectory,
36262
- manifest: join35(bundleDirectory, "manifest.json"),
36263
- engine: join35(bundleDirectory, spec.engine.executable),
36264
- chrome: join35(bundleDirectory, spec.chrome.executable),
36265
- licenses: join35(bundleDirectory, "licenses")
36392
+ manifest: join34(bundleDirectory, "manifest.json"),
36393
+ engine: join34(bundleDirectory, spec.engine.executable),
36394
+ chrome: join34(bundleDirectory, spec.chrome.executable),
36395
+ licenses: join34(bundleDirectory, "licenses")
36266
36396
  };
36267
36397
  }
36268
36398
  var CHROME_VERSION, CHROME_ASSETS;
@@ -36302,7 +36432,7 @@ var init_bot_profile = __esm({
36302
36432
  });
36303
36433
 
36304
36434
  // server/bot-folder.ts
36305
- import { join as join36 } from "node:path";
36435
+ import { join as join35 } from "node:path";
36306
36436
  var BOTS_DIR;
36307
36437
  var init_bot_folder = __esm({
36308
36438
  "server/bot-folder.ts"() {
@@ -36310,7 +36440,7 @@ var init_bot_folder = __esm({
36310
36440
  init_atomic();
36311
36441
  init_config();
36312
36442
  init_bot_profile();
36313
- BOTS_DIR = join36(DATA_DIR, "bots");
36443
+ BOTS_DIR = join35(DATA_DIR, "bots");
36314
36444
  }
36315
36445
  });
36316
36446
 
@@ -36347,9 +36477,9 @@ __export(browser_engine_exports, {
36347
36477
  });
36348
36478
  import { spawn as spawn8 } from "node:child_process";
36349
36479
  import { createHash as createHash8, randomBytes as randomBytes3, randomUUID as randomUUID11 } from "node:crypto";
36350
- import { chmodSync as chmodSync7, constants as constants2, copyFileSync, existsSync as existsSync18, mkdirSync as mkdirSync16, readFileSync as readFileSync21, readdirSync as readdirSync3, renameSync as renameSync5, rmSync as rmSync6, statSync as statSync3, unlinkSync as unlinkSync4, writeFileSync as writeFileSync14 } from "node:fs";
36480
+ import { chmodSync as chmodSync7, constants as constants2, copyFileSync, existsSync as existsSync18, mkdirSync as mkdirSync16, readFileSync as readFileSync20, readdirSync as readdirSync3, renameSync as renameSync5, rmSync as rmSync6, statSync as statSync3, unlinkSync as unlinkSync4, writeFileSync as writeFileSync13 } from "node:fs";
36351
36481
  import { homedir as homedir23, tmpdir as tmpdir5 } from "node:os";
36352
- import { delimiter as delimiter6, dirname as dirname11, join as join37, resolve as resolve11 } from "node:path";
36482
+ import { delimiter as delimiter6, dirname as dirname11, join as join36, resolve as resolve11 } from "node:path";
36353
36483
  function browserRestoreKey(session) {
36354
36484
  if (!/^[A-Za-z0-9_-]{1,96}$/.test(session)) throw new Error("Invalid browser session.");
36355
36485
  return `omb-${createHash8("sha256").update(session).digest("hex")}`;
@@ -36358,20 +36488,20 @@ function browserSessionsDirectory(env) {
36358
36488
  const namespace = env.AGENT_BROWSER_NAMESPACE;
36359
36489
  if (namespace && !/^[a-z0-9]+(?:[-_][a-z0-9]+)*$/.test(namespace)) throw new Error("Invalid browser namespace.");
36360
36490
  const home = (process.platform === "win32" ? env.USERPROFILE : env.HOME) || homedir23();
36361
- const stateRoot = namespace ? join37(home, ".agent-browser", "namespaces", namespace, "state") : join37(home, ".agent-browser");
36362
- return join37(stateRoot, "sessions");
36491
+ const stateRoot = namespace ? join36(home, ".agent-browser", "namespaces", namespace, "state") : join36(home, ".agent-browser");
36492
+ return join36(stateRoot, "sessions");
36363
36493
  }
36364
36494
  function managedBrowserConfigPath(env) {
36365
- return join37(browserSessionsDirectory(env), "..", "omb-managed-config.json");
36495
+ return join36(browserSessionsDirectory(env), "..", "omb-managed-config.json");
36366
36496
  }
36367
36497
  function ensureManagedBrowserConfig(env) {
36368
36498
  const path3 = managedBrowserConfigPath(env);
36369
36499
  mkdirSync16(dirname11(path3), { recursive: true, mode: 448 });
36370
36500
  try {
36371
- writeFileSync14(path3, "{}\n", { flag: "wx", mode: 384 });
36501
+ writeFileSync13(path3, "{}\n", { flag: "wx", mode: 384 });
36372
36502
  } catch (error51) {
36373
36503
  if (error51.code !== "EEXIST") throw error51;
36374
- if (readFileSync21(path3, "utf8").trim() !== "{}") throw new Error("The managed browser configuration was changed. Restore it to an empty JSON object before connecting.");
36504
+ if (readFileSync20(path3, "utf8").trim() !== "{}") throw new Error("The managed browser configuration was changed. Restore it to an empty JSON object before connecting.");
36375
36505
  }
36376
36506
  return path3;
36377
36507
  }
@@ -36449,11 +36579,11 @@ async function prepareBrowserSessionState(binaryPath, session, options = {}) {
36449
36579
  ensureManagedBrowserConfig(env);
36450
36580
  if (options.persistent === false) return;
36451
36581
  const directory = browserSessionsDirectory(env);
36452
- const key = join37(directory, session);
36582
+ const key = join36(directory, session);
36453
36583
  let pending = preparedBrowserSessions.get(key);
36454
36584
  if (!pending) {
36455
36585
  pending = (async () => {
36456
- const targetBase = join37(directory, `${restoreKey}-${session}`);
36586
+ const targetBase = join36(directory, `${restoreKey}-${session}`);
36457
36587
  const hasTarget = () => existsSync18(`${targetBase}.json.enc`) || existsSync18(`${targetBase}.json`);
36458
36588
  if (hasTarget()) return;
36459
36589
  check2();
@@ -36463,7 +36593,7 @@ async function prepareBrowserSessionState(binaryPath, session, options = {}) {
36463
36593
  check2();
36464
36594
  if (hasTarget()) return;
36465
36595
  const candidates = [".json.enc", ".json"].flatMap((suffix) => {
36466
- const path3 = join37(directory, `${session}-${session}${suffix}`);
36596
+ const path3 = join36(directory, `${session}-${session}${suffix}`);
36467
36597
  try {
36468
36598
  const stat4 = statSync3(path3);
36469
36599
  if (!stat4.isFile()) throw new Error("Saved browser state is not a file.");
@@ -36498,7 +36628,7 @@ function isMusl(platform = process.platform, exists = existsSync18) {
36498
36628
  }
36499
36629
  function pinnedBinaryPath(dataDir = DATA_DIR, platform = process.platform, arch = process.arch) {
36500
36630
  const version2 = agentBrowserReleaseVersion(resolveAgentBrowserReleaseAsset(platform, arch));
36501
- return join37(dataDir, ENGINE_DIR, version2, executableName(platform));
36631
+ return join36(dataDir, ENGINE_DIR, version2, executableName(platform));
36502
36632
  }
36503
36633
  function onPath(env, platform, exists) {
36504
36634
  const pathValue = platform === "win32" ? Object.entries(env).findLast(([key]) => key.toUpperCase() === "PATH")?.[1] : env.PATH;
@@ -36514,7 +36644,7 @@ function packagedBrowser(options) {
36514
36644
  const resources = (options.env ?? process.env).OMB_RESOURCES_PATH;
36515
36645
  if (!resources) return null;
36516
36646
  try {
36517
- return browserBundlePaths(join37(resolve11(resources), "browser-engine"), `${options.platform ?? process.platform}-${options.arch ?? process.arch}`);
36647
+ return browserBundlePaths(join36(resolve11(resources), "browser-engine"), `${options.platform ?? process.platform}-${options.arch ?? process.arch}`);
36518
36648
  } catch {
36519
36649
  return null;
36520
36650
  }
@@ -36539,7 +36669,7 @@ async function installAgentBrowserBinary(options = {}) {
36539
36669
  const asset = options.asset ?? resolveAgentBrowserReleaseAsset(platform, options.arch ?? process.arch, options.musl ?? isMusl(platform));
36540
36670
  if (!asset) throw new Error(`agent-browser publishes no build for ${platform}-${options.arch ?? process.arch}.`);
36541
36671
  const destination = pinnedBinaryPath(options.dataDir, platform, options.arch);
36542
- const directory = join37(destination, "..");
36672
+ const directory = join36(destination, "..");
36543
36673
  mkdirSync16(directory, { recursive: true, mode: 448 });
36544
36674
  const url2 = agentBrowserReleaseUrl(asset);
36545
36675
  options.log?.(`downloading agent-browser ${agentBrowserReleaseVersion(asset)} (${Math.round(asset.bytes / 1024 / 1024)} MB, digest pinned)`);
@@ -36558,7 +36688,7 @@ async function installAgentBrowserBinary(options = {}) {
36558
36688
  const digest2 = createHash8("sha256").update(body).digest("hex");
36559
36689
  if (digest2 !== asset.sha256) throw new Error("the agent-browser download failed its SHA-256 check; nothing was installed");
36560
36690
  const staging = `${destination}.${randomBytes3(6).toString("hex")}.part`;
36561
- writeFileSync14(staging, body, { mode: 493 });
36691
+ writeFileSync13(staging, body, { mode: 493 });
36562
36692
  if (platform !== "win32") chmodSync7(staging, 493);
36563
36693
  renameSync5(staging, destination);
36564
36694
  return destination;
@@ -36591,9 +36721,9 @@ function ensureChrome(binaryPath, options = {}) {
36591
36721
  });
36592
36722
  }
36593
36723
  function browserEngineEncryptionKey(dataDir = DATA_DIR) {
36594
- const file2 = join37(dataDir, KEY_FILE);
36724
+ const file2 = join36(dataDir, KEY_FILE);
36595
36725
  try {
36596
- const existing = readFileSync21(file2, "utf8").trim();
36726
+ const existing = readFileSync20(file2, "utf8").trim();
36597
36727
  if (/^[0-9a-f]{64}$/u.test(existing)) return existing;
36598
36728
  } catch {
36599
36729
  }
@@ -36651,7 +36781,7 @@ function agentBrowserIntegration(input) {
36651
36781
  return { command: input.binaryPath, args: ["mcp", "--tools", "core", "--no-webmcp"], env };
36652
36782
  }
36653
36783
  function agentBrowserFrame(input) {
36654
- const file2 = join37(tmpdir5(), `openmausbot-browser-${randomUUID11()}.png`);
36784
+ const file2 = join36(tmpdir5(), `openmausbot-browser-${randomUUID11()}.png`);
36655
36785
  return new Promise((settle, fail) => {
36656
36786
  const child = spawn8(input.binaryPath, ["screenshot", file2], {
36657
36787
  env: browserRuntimeEnv(input.env),
@@ -36673,7 +36803,7 @@ function agentBrowserFrame(input) {
36673
36803
  fail(error51);
36674
36804
  return;
36675
36805
  }
36676
- settle({ png: readFileSync21(file2).toString("base64"), format: "png" });
36806
+ settle({ png: readFileSync20(file2).toString("base64"), format: "png" });
36677
36807
  } catch {
36678
36808
  fail(new Error("the browser reported a picture it did not write"));
36679
36809
  } finally {
@@ -36721,8 +36851,8 @@ async function clearBrowserSessionState(binaryPath, session, options = {}) {
36721
36851
  }
36722
36852
  };
36723
36853
  try {
36724
- for (const base2 of bases) for (const suffix of [".json", ".json.enc", ".json.previous", ".json.enc.previous"]) remove(join37(directory, base2 + suffix));
36725
- const pendingDirectory = join37(directory, ".tmp");
36854
+ for (const base2 of bases) for (const suffix of [".json", ".json.enc", ".json.previous", ".json.enc.previous"]) remove(join36(directory, base2 + suffix));
36855
+ const pendingDirectory = join36(directory, ".tmp");
36726
36856
  let pendingFiles = [];
36727
36857
  try {
36728
36858
  pendingFiles = readdirSync3(pendingDirectory);
@@ -36730,8 +36860,8 @@ async function clearBrowserSessionState(binaryPath, session, options = {}) {
36730
36860
  if (error51.code !== "ENOENT") throw error51;
36731
36861
  }
36732
36862
  const candidates = bases.map((base2) => new RegExp(`^${base2}-candidate-[0-9]+\\.json(?:\\.enc)?$`));
36733
- for (const name of pendingFiles) if (candidates.some((candidate) => candidate.test(name))) remove(join37(pendingDirectory, name));
36734
- preparedBrowserSessions.delete(join37(directory, session));
36863
+ for (const name of pendingFiles) if (candidates.some((candidate) => candidate.test(name))) remove(join36(pendingDirectory, name));
36864
+ preparedBrowserSessions.delete(join36(directory, session));
36735
36865
  return true;
36736
36866
  } catch {
36737
36867
  return false;
@@ -36759,9 +36889,9 @@ var init_browser_engine = __esm({
36759
36889
  // server/cli.ts
36760
36890
  var import_qrcode_terminal = __toESM(require_main(), 1);
36761
36891
  import { spawn as spawn9 } from "node:child_process";
36762
- import { closeSync as closeSync3, existsSync as existsSync19, mkdirSync as mkdirSync17, openSync as openSync3, readFileSync as readFileSync22 } from "node:fs";
36892
+ import { closeSync as closeSync4, existsSync as existsSync19, mkdirSync as mkdirSync17, openSync as openSync4, readFileSync as readFileSync21 } from "node:fs";
36763
36893
  import { homedir as homedir24 } from "node:os";
36764
- import { dirname as dirname12, join as join38, resolve as resolve12 } from "node:path";
36894
+ import { dirname as dirname12, join as join37, resolve as resolve12 } from "node:path";
36765
36895
  import { createInterface as createInterface2 } from "node:readline/promises";
36766
36896
  import { fileURLToPath as fileURLToPath3 } from "node:url";
36767
36897
 
@@ -38533,8 +38663,9 @@ function runServiceCommand(input, io) {
38533
38663
  }
38534
38664
 
38535
38665
  // server/fleet-cli.ts
38536
- import { spawn as spawn3 } from "node:child_process";
38537
- import { appendFileSync, existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "node:fs";
38666
+ init_atomic();
38667
+ import { execFileSync, spawn as spawn3 } from "node:child_process";
38668
+ import { appendFileSync, closeSync as closeSync2, existsSync as existsSync2, fsyncSync as fsyncSync2, lstatSync, mkdirSync as mkdirSync3, openSync as openSync2, readFileSync, rmSync as rmSync2 } from "node:fs";
38538
38669
  import { posix as posix3 } from "node:path";
38539
38670
 
38540
38671
  // server/fleet.ts
@@ -38626,8 +38757,9 @@ function templateUnit(spec) {
38626
38757
  "# Written by `openmausbot fleet init`. One unit for every workspace: %i is the slug.",
38627
38758
  "[Unit]",
38628
38759
  "Description=OpenMausBot workspace %i",
38629
- "After=network-online.target",
38760
+ "After=network-online.target openmausbot-fence.service",
38630
38761
  "Wants=network-online.target",
38762
+ "Requires=openmausbot-fence.service",
38631
38763
  "",
38632
38764
  "[Service]",
38633
38765
  "Type=simple",
@@ -38659,7 +38791,6 @@ function fenceUnit(layout = fleetLayout()) {
38659
38791
  "# Written by `openmausbot fleet init`: keeps each workspace's loopback ports to its own user.",
38660
38792
  "[Unit]",
38661
38793
  "Description=OpenMausBot per-workspace loopback fence",
38662
- "Before=openmausbot@.service",
38663
38794
  "",
38664
38795
  "[Service]",
38665
38796
  "Type=oneshot",
@@ -38674,14 +38805,16 @@ function fenceUnit(layout = fleetLayout()) {
38674
38805
  function fenceRules(workspaces) {
38675
38806
  const lines = ["#!/usr/sbin/nft -f", "# Written by `openmausbot fleet`; regenerated on every create, suspend, resume and delete.", "add table inet openmausbot", "flush table inet openmausbot", "table inet openmausbot {", " chain output {", " type filter hook output priority 0; policy accept;"];
38676
38807
  for (const workspace of [...workspaces].sort((a2, b) => a2.slug.localeCompare(b.slug))) {
38808
+ if (workspace.status !== "running" && workspace.status !== "suspended" && workspace.status !== "retained" && !workspace.accountCreated) continue;
38677
38809
  lines.push(` oif lo tcp dport { ${workspace.port}, ${workspace.webhookPort} } meta skuid != { ${fleetUser(workspace.slug)}, caddy, root } reject`);
38678
38810
  }
38679
38811
  lines.push(" }", "}", "");
38680
38812
  return lines.join("\n");
38681
38813
  }
38682
38814
  function caddySite(workspace) {
38683
- if (workspace.status === "suspended") {
38684
- return [`${workspace.host} {`, ' respond "This workspace is suspended." 503', "}", ""].join("\n");
38815
+ if (workspace.status !== "running") {
38816
+ const message2 = workspace.status === "suspended" ? "This workspace is suspended." : "This workspace is not available.";
38817
+ return [`${workspace.host} {`, ` respond "${message2}" 503`, "}", ""].join("\n");
38685
38818
  }
38686
38819
  return [
38687
38820
  `${workspace.host} {`,
@@ -38709,11 +38842,23 @@ function instanceEnv(input) {
38709
38842
  `OMB_PUBLIC_URL=https://${input.workspace.host}`
38710
38843
  ];
38711
38844
  if (input.licenseKey) lines.push(`OMB_LICENSE_KEY=${input.licenseKey}`);
38845
+ if (input.portalUrl) lines.push(`OMB_ADMIN_URL=${input.portalUrl}`, `OMB_ADMIN_WORKSPACE=${input.workspace.slug}`);
38712
38846
  return lines.join("\n") + "\n";
38713
38847
  }
38848
+ var MANAGED_OPENROUTER = "omb-managed-openrouter";
38849
+ function openRouterModelIds(value) {
38850
+ if (!Array.isArray(value) || value.length > 100 || value.some((id) => typeof id !== "string" || !/^[a-zA-Z0-9][a-zA-Z0-9._:/+-]{0,159}$/.test(id))) {
38851
+ throw new Error("OpenRouter models must be an array of at most 100 valid model IDs");
38852
+ }
38853
+ return [...new Set(value)];
38854
+ }
38855
+ function managedOpenRouterModels(models2) {
38856
+ return Object.fromEntries(openRouterModelIds(models2).map((id) => [id, { name: id }]));
38857
+ }
38714
38858
  function initialConfig(seed) {
38715
38859
  const config2 = { signIn: { admins: seed.admins, members: seed.members } };
38716
- if (seed.anthropicKey) config2.anthropic = { key: seed.anthropicKey };
38860
+ if (seed.anthropicKey) config2.anthropic = { key: seed.anthropicKey, ...seed.anthropicUrl ? { url: seed.anthropicUrl } : {} };
38861
+ if (seed.openrouterDefault && seed.openrouterModels?.length) config2.defaultModelSelection = { instanceId: "opencodeGo", model: `${MANAGED_OPENROUTER}/${seed.openrouterModels[0]}` };
38717
38862
  if (seed.monthlyCapUsd !== void 0) config2.budgets = { monthlyUsd: seed.monthlyCapUsd };
38718
38863
  return `${JSON.stringify(config2, null, 2)}
38719
38864
  `;
@@ -38757,6 +38902,18 @@ function initPlan(input) {
38757
38902
  }
38758
38903
  function createPlan(input) {
38759
38904
  assertSlug(input.slug);
38905
+ if (input.seed.portalUrl) {
38906
+ const portal = new URL(input.seed.portalUrl);
38907
+ if (portal.protocol !== "https:" || portal.origin !== input.seed.portalUrl) throw new Error("portalUrl must be an HTTPS origin without a path or credentials");
38908
+ if (portal.hostname === `${input.slug}.${input.registry.domain}`) throw new Error("this workspace name is reserved for the admin portal");
38909
+ if (input.seed.anthropicUrl && input.seed.anthropicUrl !== `${portal.origin}/api/gateway/${input.slug}/anthropic`) throw new Error("managed provider URL must belong to this workspace's portal gateway");
38910
+ } else if (input.seed.anthropicUrl) throw new Error("a managed provider URL requires portalUrl");
38911
+ if (input.seed.openrouterKey !== void 0 || input.seed.openrouterUrl !== void 0 || input.seed.openrouterModels !== void 0 || input.seed.openrouterDefault) {
38912
+ if (!input.seed.portalUrl || input.seed.openrouterUrl !== `${input.seed.portalUrl}/api/gateway/${input.slug}/openrouter/v1`) throw new Error("managed OpenRouter URL must belong to this workspace's portal gateway");
38913
+ if (!input.seed.openrouterKey || !/^[!-~]+$/.test(input.seed.openrouterKey)) throw new Error("managed OpenRouter requires a non-empty workspace credential without whitespace");
38914
+ const models2 = openRouterModelIds(input.seed.openrouterModels ?? []);
38915
+ if (input.seed.openrouterDefault && !models2.length) throw new Error("a default OpenRouter model requires a model assignment");
38916
+ }
38760
38917
  if (input.registry.workspaces[input.slug]) throw new Error(`workspace "${input.slug}" already exists`);
38761
38918
  if (!input.seed.admins.length) throw new Error("a workspace needs at least one admin email (--admin)");
38762
38919
  for (const entry of [...input.seed.admins, ...input.seed.members]) assertEmailOrDomain(entry);
@@ -38772,15 +38929,36 @@ function createPlan(input) {
38772
38929
  createdAt: (input.now ?? /* @__PURE__ */ new Date()).toISOString()
38773
38930
  };
38774
38931
  const registry2 = { ...input.registry, nextPort: next, workspaces: { ...input.registry.workspaces, [workspace.slug]: workspace } };
38932
+ const reserved = (accountCreated) => ({
38933
+ ...registry2,
38934
+ workspaces: { ...registry2.workspaces, [workspace.slug]: { ...workspace, status: "provisioning", accountCreated } }
38935
+ });
38775
38936
  const user = fleetUser(workspace.slug);
38776
38937
  const home = workspaceHome(layout, workspace.slug);
38777
38938
  const dataDir = workspaceDataDir(layout, workspace.slug);
38778
38939
  const steps = [
38940
+ // Reserve the identity and ports before any external resource is created.
38941
+ // A crash leaves an inspectable reservation, never a silently reusable home.
38942
+ { kind: "write", path: layout.registryFile, content: `${JSON.stringify(reserved(false), null, 2)}
38943
+ `, mode: 384 },
38779
38944
  { kind: "run", argv: ["useradd", "--system", "--create-home", "--home-dir", home, "--shell", "/usr/sbin/nologin", "--user-group", user], why: `the workspace's own account` },
38945
+ { kind: "write", path: layout.registryFile, content: `${JSON.stringify(reserved(true), null, 2)}
38946
+ `, mode: 384 },
38780
38947
  { kind: "mkdir", path: dataDir, mode: 448, owner: user },
38781
38948
  { kind: "write", path: join3(dataDir, "config.json"), content: initialConfig(input.seed), mode: 384, owner: user },
38949
+ ...input.seed.openrouterKey ? [
38950
+ { kind: "mkdir", path: join3(home, ".config"), mode: 448, owner: user },
38951
+ { kind: "mkdir", path: join3(home, ".config", "opencode"), mode: 448, owner: user },
38952
+ { kind: "write", path: join3(home, ".config", "opencode", "opencode.json"), mode: 384, owner: user, content: `${JSON.stringify({ provider: { [MANAGED_OPENROUTER]: {
38953
+ npm: "@ai-sdk/openai-compatible",
38954
+ name: "Managed OpenRouter",
38955
+ options: { baseURL: input.seed.openrouterUrl, apiKey: input.seed.openrouterKey },
38956
+ models: managedOpenRouterModels(input.seed.openrouterModels ?? [])
38957
+ } } }, null, 2)}
38958
+ ` }
38959
+ ] : [],
38782
38960
  ...input.seed.brandJson ? [{ kind: "write", path: join3(dataDir, "brand.json"), content: input.seed.brandJson, mode: 384, owner: user }] : [],
38783
- { kind: "write", path: join3(layout.instancesDir, `${workspace.slug}.env`), content: instanceEnv({ workspace, dataDir, licenseKey: input.licenseKey }), mode: 384 },
38961
+ { kind: "write", path: join3(layout.instancesDir, `${workspace.slug}.env`), content: instanceEnv({ workspace, dataDir, licenseKey: input.licenseKey, portalUrl: input.seed.portalUrl }), mode: 384 },
38784
38962
  ...input.memoryMax ? [
38785
38963
  { kind: "mkdir", path: join3(layout.unitFile.replace(/openmausbot@\.service$/, ""), `openmausbot@${workspace.slug}.service.d`), mode: 493 },
38786
38964
  { kind: "write", path: join3(layout.unitFile.replace(/openmausbot@\.service$/, ""), `openmausbot@${workspace.slug}.service.d`, "limits.conf"), content: `[Service]
@@ -38800,10 +38978,14 @@ MemoryMax=${input.memoryMax}
38800
38978
  ];
38801
38979
  return { steps, registry: registry2, workspace };
38802
38980
  }
38981
+ function assertManagedWorkspace(workspace) {
38982
+ if (workspace.status !== "running" && workspace.status !== "suspended") throw new Error(`workspace "${workspace.slug}" is ${workspace.status} and requires operator recovery`);
38983
+ }
38803
38984
  function withStatus(registry2, slug, status) {
38804
38985
  assertSlug(slug);
38805
38986
  const current = registry2.workspaces[slug];
38806
38987
  if (!current) throw new Error(`no workspace "${slug}"`);
38988
+ assertManagedWorkspace(current);
38807
38989
  const workspace = { ...current, status };
38808
38990
  return { registry: { ...registry2, workspaces: { ...registry2.workspaces, [slug]: workspace } }, workspace };
38809
38991
  }
@@ -38841,8 +39023,9 @@ function deletePlan(input) {
38841
39023
  const layout = input.layout ?? fleetLayout();
38842
39024
  const workspace = input.registry.workspaces[input.slug];
38843
39025
  if (!workspace) throw new Error(`no workspace "${input.slug}"`);
39026
+ assertManagedWorkspace(workspace);
38844
39027
  const { [input.slug]: _gone, ...rest } = input.registry.workspaces;
38845
- const registry2 = { ...input.registry, workspaces: rest };
39028
+ const registry2 = { ...input.registry, workspaces: input.keepData ? { ...rest, [input.slug]: { ...workspace, status: "retained" } } : rest };
38846
39029
  const unitsDir = layout.unitFile.replace(/openmausbot@\.service$/, "");
38847
39030
  return {
38848
39031
  registry: registry2,
@@ -38853,8 +39036,10 @@ function deletePlan(input) {
38853
39036
  { kind: "remove", path: join3(layout.instancesDir, `${workspace.slug}.env`) },
38854
39037
  { kind: "remove", path: join3(unitsDir, `openmausbot@${workspace.slug}.service.d`, "limits.conf") },
38855
39038
  { kind: "write", path: layout.fenceFile, content: fenceRules(Object.values(registry2.workspaces)), mode: 384 },
38856
- { kind: "run", argv: ["nft", "-f", layout.fenceFile], why: "drop its fence rule" },
38857
- { kind: "run", argv: input.keepData ? ["userdel", fleetUser(workspace.slug)] : ["userdel", "--remove", fleetUser(workspace.slug)], why: input.keepData ? "remove the account, keep its home and data" : "remove the account and everything it owned" },
39039
+ { kind: "run", argv: ["nft", "-f", layout.fenceFile], why: input.keepData ? "keep the retained account's fence" : "drop its fence rule" },
39040
+ // Retain the nologin account too: recycling its UID would give a new
39041
+ // workspace ownership of the preserved home, even under a different slug.
39042
+ ...input.keepData ? [{ kind: "note", text: "keeping the disabled workspace's account, home and name reserved; operator recovery is required before reuse" }] : [{ kind: "run", argv: ["userdel", "--remove", fleetUser(workspace.slug)], why: "remove the account and everything it owned" }],
38858
39043
  { kind: "write", path: layout.registryFile, content: `${JSON.stringify(registry2, null, 2)}
38859
39044
  `, mode: 384 }
38860
39045
  ]
@@ -38881,10 +39066,12 @@ function applySignIn(configText, action, email3, chatOnly) {
38881
39066
  const members = without(list(current.members));
38882
39067
  if (action === "remove") {
38883
39068
  if (admins.length + members.length === list(current.admins).length + list(current.members).length) throw new Error(`${entry} is not on the list`);
39069
+ if (!admins.length) throw new Error("a workspace needs at least one admin; add another admin first");
38884
39070
  return { config: `${JSON.stringify({ ...raw, signIn: { admins, members } }, null, 2)}
38885
- `, summary: `${entry} can no longer sign in; existing sessions stay until they expire or are revoked` };
39071
+ `, summary: `${entry} was removed from the allow-list; existing email sessions are rechecked against the remaining entries` };
38886
39072
  }
38887
39073
  const next = chatOnly ? { admins, members: [...members, entry] } : { admins: [...admins, entry], members };
39074
+ if (!next.admins.length) throw new Error("a workspace needs at least one admin; add another admin first");
38888
39075
  return { config: `${JSON.stringify({ ...raw, signIn: next }, null, 2)}
38889
39076
  `, summary: `${entry} can sign in with an emailed code (${chatOnly ? "chat and approvals" : "full access"})` };
38890
39077
  }
@@ -38896,11 +39083,14 @@ function describeSteps(steps) {
38896
39083
  for (const step of steps) {
38897
39084
  switch (step.kind) {
38898
39085
  case "mkdir":
38899
- lines.push(`install -d -m ${step.mode.toString(8)}${step.owner ? ` -o ${step.owner} -g ${step.owner}` : ""} ${shellQuote(step.path)}`);
39086
+ lines.push(step.owner ? `# Create ${shellQuote(step.path)} mode ${step.mode.toString(8)} as ${step.owner} (use fleet --yes; never root chown on tenant paths)` : `install -d -m ${step.mode.toString(8)} ${shellQuote(step.path)}`);
38900
39087
  break;
38901
39088
  case "write":
39089
+ if (step.owner) {
39090
+ lines.push(`# Write ${shellQuote(step.path)} mode ${step.mode.toString(8)} as ${step.owner} (use fleet --yes; tenant content omitted)`);
39091
+ break;
39092
+ }
38902
39093
  lines.push(`cat > ${shellQuote(step.path)} <<'OMB_EOF'`, step.content.replace(/\n$/, ""), "OMB_EOF", `chmod ${step.mode.toString(8)} ${shellQuote(step.path)}`);
38903
- if (step.owner) lines.push(`chown ${step.owner}:${step.owner} ${shellQuote(step.path)}`);
38904
39094
  break;
38905
39095
  case "append-once":
38906
39096
  lines.push(`grep -qxF ${shellQuote(step.line)} ${shellQuote(step.path)} || printf '\\n%s\\n' ${shellQuote(step.line)} >> ${shellQuote(step.path)}`);
@@ -38923,6 +39113,113 @@ function describeSteps(steps) {
38923
39113
  }
38924
39114
 
38925
39115
  // server/fleet-cli.ts
39116
+ var TENANT_IO = String.raw`
39117
+ const fs = require("node:fs");
39118
+ const path = require("node:path");
39119
+ const { randomUUID } = require("node:crypto");
39120
+ const request = JSON.parse(fs.readFileSync(0, "utf8"));
39121
+ const { uid, gid, home, file, action, content, mode } = request;
39122
+ if (process.getuid() === 0) {
39123
+ process.setgroups([]);
39124
+ process.setgid(gid);
39125
+ process.setuid(uid);
39126
+ }
39127
+ if (process.getuid() !== uid || process.getgid() !== gid || uid === 0 || gid === 0) throw Error("could not drop fleet file privileges");
39128
+ const limit = 4 * 1024 * 1024;
39129
+ const check = (stat, directory) => {
39130
+ if (stat.uid !== uid || (directory ? !stat.isDirectory() : !stat.isFile() || stat.nlink !== 1)) throw Error("unsafe workspace file or directory");
39131
+ };
39132
+ const totals = { turns: 0, costUsd: null, billableUsd: null };
39133
+ let cursor = home;
39134
+ try {
39135
+ check(fs.lstatSync(cursor), true);
39136
+ for (const part of path.relative(home, path.dirname(file)).split(path.sep).filter(Boolean)) {
39137
+ cursor = path.join(cursor, part);
39138
+ check(fs.lstatSync(cursor), true);
39139
+ }
39140
+ } catch (error) {
39141
+ if (action !== "usage" || error.code !== "ENOENT") throw error;
39142
+ process.stdout.write(JSON.stringify(totals));
39143
+ process.exit(0);
39144
+ }
39145
+ let result = null;
39146
+ if (action === "mkdir") {
39147
+ try { fs.mkdirSync(file, { mode }); } catch (error) { if (error.code !== "EEXIST") throw error; }
39148
+ check(fs.lstatSync(file), true);
39149
+ } else if (action === "read" || action === "usage") {
39150
+ let fd;
39151
+ try { fd = fs.openSync(file, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK); }
39152
+ catch (error) { if (error.code !== "ENOENT") throw error; }
39153
+ if (fd !== undefined) {
39154
+ try {
39155
+ const stat = fs.fstatSync(fd);
39156
+ check(stat, false);
39157
+ if (stat.size > limit) throw Error("workspace file exceeds 4 MiB");
39158
+ const data = Buffer.alloc(limit + 1);
39159
+ let size = 0;
39160
+ while (size < data.length) { const read = fs.readSync(fd, data, size, data.length - size, null); if (!read) break; size += read; }
39161
+ if (size > limit) throw Error("workspace file exceeds 4 MiB");
39162
+ result = data.subarray(0, size).toString("utf8");
39163
+ } finally { fs.closeSync(fd); }
39164
+ }
39165
+ if (action === "usage") {
39166
+ const to = Date.parse(content);
39167
+ const date = new Date(to);
39168
+ const from = Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), 1);
39169
+ // Scan one bounded month's text without retaining rows or allocating groups.
39170
+ const text = result || "";
39171
+ for (let offset = 0; offset < text.length;) {
39172
+ const end = text.indexOf("\n", offset);
39173
+ const line = text.slice(offset, end < 0 ? text.length : end);
39174
+ offset = end < 0 ? text.length : end + 1;
39175
+ let row;
39176
+ try { row = JSON.parse(line); } catch { continue; }
39177
+ if (!row || typeof row !== "object" || typeof row.at !== "string" || typeof row.botId !== "string" || typeof row.model !== "string" || typeof row.input !== "number" || typeof row.output !== "number" || !row.trigger || typeof row.trigger !== "object" || typeof row.trigger.kind !== "string") continue;
39178
+ const at = Date.parse(row.at);
39179
+ if (!(at >= from && at <= to)) continue;
39180
+ totals.turns++;
39181
+ if (typeof row.costUsd === "number" && Number.isFinite(row.costUsd) && row.costUsd >= 0) {
39182
+ const sum = (totals.costUsd || 0) + row.costUsd;
39183
+ if (!Number.isFinite(sum)) throw Error("workspace usage total exceeds numeric range");
39184
+ totals.costUsd = sum;
39185
+ }
39186
+ }
39187
+ result = totals;
39188
+ }
39189
+ } else if (action === "write") {
39190
+ if (Buffer.byteLength(content) > limit) throw Error("workspace file exceeds 4 MiB");
39191
+ try { check(fs.lstatSync(file), false); } catch (error) { if (error.code !== "ENOENT") throw error; }
39192
+ const temp = file + ".fleet-" + randomUUID();
39193
+ let fd;
39194
+ try {
39195
+ fd = fs.openSync(temp, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_NOFOLLOW, mode);
39196
+ fs.writeFileSync(fd, content);
39197
+ fs.fsyncSync(fd);
39198
+ fs.closeSync(fd); fd = undefined;
39199
+ fs.renameSync(temp, file);
39200
+ } finally {
39201
+ if (fd !== undefined) fs.closeSync(fd);
39202
+ try { fs.unlinkSync(temp); } catch (error) { if (error.code !== "ENOENT") throw error; }
39203
+ }
39204
+ } else throw Error("unknown fleet file operation");
39205
+ process.stdout.write(JSON.stringify(result));
39206
+ `;
39207
+ function tenantIo(owner, file2, action, content, mode) {
39208
+ assertUnixUser(owner);
39209
+ const options = { encoding: "utf8", timeout: 5e3, killSignal: "SIGKILL", cwd: "/", env: { PATH: "/usr/bin:/bin" }, stdio: ["pipe", "pipe", "pipe"] };
39210
+ const account = execFileSync("/usr/bin/getent", ["passwd", owner], { ...options, maxBuffer: 8192 }).trim().split(":");
39211
+ const uid = Number(account[2]);
39212
+ const gid = Number(account[3]);
39213
+ const home = account[5];
39214
+ if (account.length !== 7 || account[0] !== owner || !Number.isSafeInteger(uid) || uid <= 0 || !Number.isSafeInteger(gid) || gid <= 0 || !home?.startsWith("/") || posix3.normalize(home) !== home || !file2.startsWith(`${home}/`) || posix3.normalize(file2) !== file2) {
39215
+ throw new Error(`unsafe filesystem identity or path for ${owner}`);
39216
+ }
39217
+ try {
39218
+ return JSON.parse(execFileSync(process.execPath, ["--eval", TENANT_IO], { ...options, maxBuffer: action === "usage" ? 8192 : 32 * 1024 * 1024, input: JSON.stringify({ uid, gid, home, file: file2, action, content, mode }) }));
39219
+ } catch {
39220
+ throw new Error(`could not ${action} workspace file as ${owner}: check ownership, links, file size and permissions`);
39221
+ }
39222
+ }
38926
39223
  function defaultFleetDeps() {
38927
39224
  return {
38928
39225
  isRoot: () => typeof process.getuid === "function" && process.getuid() === 0,
@@ -38942,9 +39239,37 @@ function defaultFleetDeps() {
38942
39239
  resolve13({ code: null, output: error51 instanceof Error ? error51.message : String(error51) });
38943
39240
  }
38944
39241
  }),
38945
- readText: (path3) => existsSync2(path3) ? readFileSync(path3, "utf8") : null,
38946
- writeText: (path3, content, mode) => writeFileSync4(path3, content, { mode }),
38947
- mkdir: (path3, mode) => mkdirSync3(path3, { recursive: true, mode }),
39242
+ pathExists: (path3) => {
39243
+ try {
39244
+ lstatSync(path3);
39245
+ return true;
39246
+ } catch (error51) {
39247
+ if (error51.code === "ENOENT") return false;
39248
+ throw error51;
39249
+ }
39250
+ },
39251
+ readText: (path3, owner) => owner ? tenantIo(owner, path3, "read") : existsSync2(path3) ? readFileSync(path3, "utf8") : null,
39252
+ usage: (dataDir, owner, now) => {
39253
+ const at = now.toISOString();
39254
+ return tenantIo(owner, posix3.join(dataDir, "usage", `${at.slice(0, 7)}.jsonl`), "usage", at);
39255
+ },
39256
+ writeText: (path3, content, mode, owner) => {
39257
+ if (owner) {
39258
+ tenantIo(owner, path3, "write", content, mode);
39259
+ return;
39260
+ }
39261
+ writeFileAtomic(path3, content, { mode });
39262
+ const directory = openSync2(posix3.dirname(path3), "r");
39263
+ try {
39264
+ fsyncSync2(directory);
39265
+ } finally {
39266
+ closeSync2(directory);
39267
+ }
39268
+ },
39269
+ mkdir: (path3, mode, owner) => {
39270
+ if (owner) tenantIo(owner, path3, "mkdir", void 0, mode);
39271
+ else mkdirSync3(path3, { recursive: true, mode });
39272
+ },
38948
39273
  appendOnce: (path3, line) => {
38949
39274
  const current = existsSync2(path3) ? readFileSync(path3, "utf8") : "";
38950
39275
  if (!current.split("\n").some((existing) => existing.trim() === line)) appendFileSync(path3, `${current.endsWith("\n") || !current ? "" : "\n"}
@@ -38971,18 +39296,10 @@ async function executePlan(steps, deps, io) {
38971
39296
  for (const step of steps) {
38972
39297
  switch (step.kind) {
38973
39298
  case "mkdir":
38974
- deps.mkdir(step.path, step.mode);
38975
- if (step.owner) {
38976
- const chown = await deps.run(["chown", `${step.owner}:${step.owner}`, step.path]);
38977
- if (chown.code !== 0) return failed(io, `chown ${step.path}`, chown.output);
38978
- }
39299
+ deps.mkdir(step.path, step.mode, step.owner);
38979
39300
  break;
38980
39301
  case "write":
38981
- deps.writeText(step.path, step.content, step.mode);
38982
- if (step.owner) {
38983
- const chown = await deps.run(["chown", `${step.owner}:${step.owner}`, step.path]);
38984
- if (chown.code !== 0) return failed(io, `chown ${step.path}`, chown.output);
38985
- }
39302
+ deps.writeText(step.path, step.content, step.mode, step.owner);
38986
39303
  break;
38987
39304
  case "append-once":
38988
39305
  deps.appendOnce(step.path, step.line);
@@ -39025,19 +39342,28 @@ function planFleetAction(input, deps) {
39025
39342
  if (warning) throw new Error(warning);
39026
39343
  const existing = deps.readText(layout.registryFile);
39027
39344
  if (existing !== null && !input.yes) throw new Error(`${layout.registryFile} exists; pass --yes to rewrite the template unit and fence (workspaces are kept)`);
39028
- const plan = initPlan({ domain: input.domain, node: input.node, script: input.script, operator: input.operator, layout });
39029
- if (existing) {
39030
- const kept = parseRegistry(existing);
39031
- plan.registry.workspaces = kept.workspaces;
39032
- plan.registry.nextPort = kept.nextPort;
39033
- return plan.steps.map((step) => step.kind === "write" && step.path === layout.registryFile ? { ...step, content: `${JSON.stringify(plan.registry, null, 2)}
39034
- ` } : step);
39345
+ const kept = existing !== null ? parseRegistry(existing) : void 0;
39346
+ const plan = initPlan({ domain: input.domain, node: input.node, script: input.script, operator: input.operator ?? kept?.operator, layout });
39347
+ if (kept) {
39348
+ const registry2 = { ...kept, ...plan.registry, workspaces: kept.workspaces, nextPort: kept.nextPort };
39349
+ return plan.steps.flatMap((step) => {
39350
+ if (step.kind === "write" && step.path === layout.registryFile) return [{ ...step, content: `${JSON.stringify(registry2, null, 2)}
39351
+ ` }];
39352
+ if (step.kind === "write" && step.path === layout.fenceFile) return [{ ...step, content: fenceRules(Object.values(registry2.workspaces)) }];
39353
+ if (step.kind === "run" && step.argv.join(" ") === "systemctl enable --now openmausbot-fence.service") return [step, { kind: "run", argv: ["nft", "-f", layout.fenceFile], why: "reapply the preserved workspace fence" }];
39354
+ return [step];
39355
+ });
39035
39356
  }
39036
39357
  return plan.steps;
39037
39358
  }
39038
39359
  case "create": {
39039
39360
  if (!input.slug) throw new Error("fleet create needs a workspace name");
39361
+ assertSlug(input.slug);
39040
39362
  const registry2 = loadRegistry(layout, deps);
39363
+ if (registry2.workspaces[input.slug]) throw new Error(`workspace "${input.slug}" already exists`);
39364
+ for (const path3 of [workspaceHome(layout, input.slug), posix3.join(layout.instancesDir, `${input.slug}.env`), posix3.join(layout.caddyDir, `${input.slug}.caddy`), posix3.join(posix3.dirname(layout.unitFile), `openmausbot@${input.slug}.service.d`)]) {
39365
+ if (deps.pathExists(path3)) throw new Error(`workspace path already exists: ${path3}; recover or remove it explicitly before provisioning a fresh workspace`);
39366
+ }
39041
39367
  const brandJson = input.brandJson ?? (input.brandFile ? deps.readText(input.brandFile) : void 0);
39042
39368
  if (input.brandFile && brandJson === null) throw new Error(`${input.brandFile} not found`);
39043
39369
  if (brandJson) JSON.parse(brandJson);
@@ -39046,19 +39372,60 @@ function planFleetAction(input, deps) {
39046
39372
  return createPlan({
39047
39373
  registry: registry2,
39048
39374
  slug: input.slug,
39049
- seed: { admins: input.admins, members: input.members, ...anthropicKey ? { anthropicKey } : {}, ...input.cap !== void 0 ? { monthlyCapUsd: input.cap } : {}, ...brandJson ? { brandJson } : {} },
39375
+ seed: { admins: input.admins, members: input.members, ...anthropicKey ? { anthropicKey } : {}, ...input.anthropicUrl ? { anthropicUrl: input.anthropicUrl } : {}, ...input.portalUrl ? { portalUrl: input.portalUrl } : {}, ...input.cap !== void 0 ? { monthlyCapUsd: input.cap } : {}, ...brandJson ? { brandJson } : {}, openrouterKey: input.openrouterKey, openrouterUrl: input.openrouterUrl, openrouterModels: input.openrouterModels, openrouterDefault: input.openrouterDefault },
39050
39376
  ...input.licenseKey ? { licenseKey: input.licenseKey } : {},
39051
39377
  ...input.memory ? { memoryMax: input.memory } : {},
39052
39378
  layout
39053
39379
  }).steps;
39054
39380
  }
39381
+ case "providers": {
39382
+ if (!input.slug) throw new Error("fleet providers needs a workspace name");
39383
+ assertSlug(input.slug);
39384
+ const workspace = loadRegistry(layout, deps).workspaces[input.slug];
39385
+ if (!workspace) throw new Error(`no workspace "${input.slug}"`);
39386
+ assertManagedWorkspace(workspace);
39387
+ const models2 = managedOpenRouterModels(input.openrouterModels);
39388
+ const env = deps.readText(posix3.join(layout.instancesDir, `${input.slug}.env`)) ?? "";
39389
+ const portal = /^OMB_ADMIN_URL=(.+)$/m.exec(env)?.[1];
39390
+ if (!portal || !env.split("\n").includes(`OMB_ADMIN_WORKSPACE=${input.slug}`)) throw new Error("workspace has no trusted portal configuration; operator recovery is required");
39391
+ try {
39392
+ const url2 = new URL(portal);
39393
+ if (url2.protocol !== "https:" || url2.origin !== portal) throw new Error();
39394
+ } catch {
39395
+ throw new Error("workspace portal configuration is invalid; operator recovery is required");
39396
+ }
39397
+ const file2 = posix3.join(workspaceHome(layout, input.slug), ".config", "opencode", "opencode.json");
39398
+ const raw = deps.readText(file2, fleetUser(input.slug));
39399
+ const object2 = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
39400
+ let config2;
39401
+ try {
39402
+ const parsed = JSON.parse(raw ?? "null");
39403
+ if (!object2(parsed)) throw new Error();
39404
+ config2 = parsed;
39405
+ } catch {
39406
+ throw new Error("managed OpenRouter configuration is missing or invalid; operator recovery is required");
39407
+ }
39408
+ const providers = config2.provider;
39409
+ const managed = object2(providers) ? providers[MANAGED_OPENROUTER] : void 0;
39410
+ const options = object2(managed) ? managed.options : void 0;
39411
+ if (!object2(managed) || managed.npm !== "@ai-sdk/openai-compatible" || !object2(options) || options.baseURL !== `${portal}/api/gateway/${input.slug}/openrouter/v1` || typeof options.apiKey !== "string" || !/^[!-~]+$/.test(options.apiKey)) {
39412
+ throw new Error("managed OpenRouter configuration does not match the workspace portal; operator recovery is required");
39413
+ }
39414
+ managed.models = models2;
39415
+ return [
39416
+ { kind: "write", path: file2, content: `${JSON.stringify(config2, null, 2)}
39417
+ `, mode: 384, owner: fleetUser(input.slug) },
39418
+ { kind: "note", text: "managed OpenRouter models updated; refresh the workspace model picker to load its catalog" }
39419
+ ];
39420
+ }
39055
39421
  case "users": {
39056
39422
  if (!input.slug || !input.userAction || !input.email) throw new Error("fleet users needs: NAME add|remove EMAIL [--chat-only]");
39057
39423
  assertSlug(input.slug);
39058
39424
  const registry2 = loadRegistry(layout, deps);
39059
39425
  if (!registry2.workspaces[input.slug]) throw new Error(`no workspace "${input.slug}"`);
39426
+ assertManagedWorkspace(registry2.workspaces[input.slug]);
39060
39427
  const file2 = posix3.join(workspaceDataDir(layout, input.slug), "config.json");
39061
- const next = applySignIn(deps.readText(file2) ?? "{}", input.userAction, input.email, Boolean(input.chatOnly));
39428
+ const next = applySignIn(deps.readText(file2, fleetUser(input.slug)) ?? "{}", input.userAction, input.email, Boolean(input.chatOnly));
39062
39429
  return [
39063
39430
  { kind: "write", path: file2, content: next.config, mode: 384, owner: fleetUser(input.slug) },
39064
39431
  { kind: "note", text: next.summary }
@@ -39072,7 +39439,7 @@ function planFleetAction(input, deps) {
39072
39439
  }
39073
39440
  case "delete": {
39074
39441
  if (!input.slug) throw new Error("fleet delete needs a workspace name");
39075
- if (!input.yes) throw new Error(`fleet delete removes the workspace's account${input.keepData ? "" : " and all of its data"}; pass --yes to confirm${input.keepData ? "" : ", or --keep-data to keep the home folder"}`);
39442
+ if (!input.yes) throw new Error(`fleet delete ${input.keepData ? "retires the workspace, keeping its data and reserved Unix account" : "removes the workspace's account and all of its data"}; pass --yes to confirm${input.keepData ? "" : ", or --keep-data to keep the home folder and account reserved"}`);
39076
39443
  const registry2 = loadRegistry(layout, deps);
39077
39444
  return deletePlan({ registry: registry2, slug: input.slug, keepData: input.keepData, layout }).steps;
39078
39445
  }
@@ -39082,8 +39449,18 @@ function planFleetAction(input, deps) {
39082
39449
  return [];
39083
39450
  }
39084
39451
  }
39452
+ function markProvisioningFailed(input, deps) {
39453
+ if (input.action !== "create" || !input.slug) return;
39454
+ const layout = fleetLayout(input.root ?? "/");
39455
+ const registry2 = loadRegistry(layout, deps);
39456
+ const workspace = registry2.workspaces[input.slug];
39457
+ if (workspace?.status !== "provisioning") return;
39458
+ registry2.workspaces[input.slug] = { ...workspace, status: "error" };
39459
+ deps.writeText(layout.registryFile, `${JSON.stringify(registry2, null, 2)}
39460
+ `, 384);
39461
+ }
39085
39462
  function printPlan(steps, io, reason) {
39086
- io.log(`${reason}; run these as root:`);
39463
+ io.log(`${reason}; inspect this plan, then rerun the fleet command as root without --dry-run (with --yes where required):`);
39087
39464
  io.log("");
39088
39465
  for (const line of describeSteps(steps)) io.log(line);
39089
39466
  return 0;
@@ -39100,13 +39477,21 @@ async function runFleetCommand(input, io, deps = defaultFleetDeps()) {
39100
39477
  return 0;
39101
39478
  }
39102
39479
  for (const workspace of rows) {
39103
- const live = asRoot ? (await deps.run(["systemctl", "is-active", `openmausbot@${workspace.slug}.service`])).output.trim() : workspace.status;
39480
+ const live = asRoot && (workspace.status === "running" || workspace.status === "suspended") ? (await deps.run(["systemctl", "is-active", `openmausbot@${workspace.slug}.service`])).output.trim() : workspace.status;
39104
39481
  io.log(`${workspace.slug.padEnd(24)} https://${workspace.host.padEnd(40)} :${String(workspace.port).padEnd(6)} ${live}`);
39105
39482
  }
39106
39483
  return 0;
39107
39484
  }
39108
39485
  const steps = planFleetAction(input, deps);
39109
- return asRoot ? await executePlan(steps, deps, io) : printPlan(steps, io, input.dryRun ? "dry run" : "not running as root");
39486
+ if (!asRoot) return printPlan(steps, io, input.dryRun ? "dry run" : "not running as root");
39487
+ try {
39488
+ const code = await executePlan(steps, deps, io);
39489
+ if (code !== 0) markProvisioningFailed(input, deps);
39490
+ return code;
39491
+ } catch (error51) {
39492
+ markProvisioningFailed(input, deps);
39493
+ throw error51;
39494
+ }
39110
39495
  } catch (error51) {
39111
39496
  io.error(`fleet: ${error51 instanceof Error ? error51.message : String(error51)}`);
39112
39497
  return 2;
@@ -39117,147 +39502,6 @@ async function runFleetCommand(input, io, deps = defaultFleetDeps()) {
39117
39502
  import { appendFileSync as appendFileSync2, chmodSync as chmodSync2, existsSync as existsSync3, mkdirSync as mkdirSync4, rmSync as rmSync3 } from "node:fs";
39118
39503
  import { createServer } from "node:http";
39119
39504
  import { dirname as dirname2 } from "node:path";
39120
-
39121
- // server/usage-ledger.ts
39122
- import { readFileSync as readFileSync2 } from "node:fs";
39123
- import { join as join4 } from "node:path";
39124
-
39125
- // server/prices.ts
39126
- function priceFor(turn, prices) {
39127
- return prices[`${turn.driverKind}/${turn.model}`] ?? prices[turn.model] ?? prices.default ?? null;
39128
- }
39129
- var clean = (value) => typeof value === "number" && Number.isFinite(value) ? Math.max(0, value) : 0;
39130
- function billableFor(turn, prices) {
39131
- const price = priceFor(turn, prices);
39132
- if (!price) return null;
39133
- const input = clean(turn.input);
39134
- const cached3 = Math.min(clean(turn.cachedInput), input);
39135
- const fresh = input - cached3;
39136
- const cachedRate = price.cachedInputPerMillion ?? price.inputPerMillion;
39137
- return (fresh * price.inputPerMillion + cached3 * cachedRate + clean(turn.output) * price.outputPerMillion) / 1e6;
39138
- }
39139
-
39140
- // server/usage-ledger.ts
39141
- var DIR = "usage";
39142
- var DAY_MS = 24 * 60 * 6e4;
39143
- var clean2 = (value) => typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.trunc(value)) : 0;
39144
- var finiteOrNull = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : null;
39145
- function monthKey(at) {
39146
- return `${at.getUTCFullYear()}-${String(at.getUTCMonth() + 1).padStart(2, "0")}`;
39147
- }
39148
- var isTrigger = (value) => typeof value === "object" && value !== null && typeof value.kind === "string";
39149
- var isUsageRow = (value) => typeof value === "object" && value !== null && typeof value.at === "string" && typeof value.botId === "string" && typeof value.model === "string" && typeof value.input === "number" && typeof value.output === "number" && isTrigger(value.trigger);
39150
- function monthsBetween(from, to) {
39151
- const keys = [];
39152
- const cursor3 = new Date(Date.UTC(from.getUTCFullYear(), from.getUTCMonth(), 1));
39153
- while (cursor3.getTime() <= to.getTime()) {
39154
- keys.push(monthKey(cursor3));
39155
- cursor3.setUTCMonth(cursor3.getUTCMonth() + 1);
39156
- }
39157
- return keys;
39158
- }
39159
- function readUsage(dataDir, range) {
39160
- const rows = [];
39161
- const from = range.from.getTime();
39162
- const to = range.to.getTime();
39163
- for (const key of monthsBetween(range.from, range.to)) {
39164
- let text4;
39165
- try {
39166
- text4 = readFileSync2(join4(dataDir, DIR, `${key}.jsonl`), "utf8");
39167
- } catch {
39168
- continue;
39169
- }
39170
- for (const line of text4.split("\n")) {
39171
- if (!line) continue;
39172
- try {
39173
- const value = JSON.parse(line);
39174
- if (!isUsageRow(value)) continue;
39175
- const at = Date.parse(value.at);
39176
- if (at >= from && at <= to) rows.push(value);
39177
- } catch {
39178
- }
39179
- }
39180
- }
39181
- return rows;
39182
- }
39183
- function triggerKey(trigger) {
39184
- switch (trigger.kind) {
39185
- case "user":
39186
- return `user:${(trigger.email ?? trigger.label ?? "").toLowerCase() || "unknown"}`;
39187
- case "owner":
39188
- return "owner";
39189
- case "routine":
39190
- return `routine:${trigger.routineId ?? trigger.label ?? "unknown"}`;
39191
- default:
39192
- return "bot";
39193
- }
39194
- }
39195
- function triggerLabel(trigger) {
39196
- switch (trigger.kind) {
39197
- case "user":
39198
- return trigger.email ?? trigger.label ?? "Signed-in user";
39199
- case "owner":
39200
- return "This computer";
39201
- case "routine":
39202
- return `Routine: ${trigger.label ?? trigger.routineId ?? "unknown"}`;
39203
- default:
39204
- return "Bot to bot";
39205
- }
39206
- }
39207
- function groupOf(row, groupBy) {
39208
- switch (groupBy) {
39209
- case "bot":
39210
- return { key: `bot:${row.botId}`, label: row.botName || row.botId };
39211
- case "model":
39212
- return { key: `model:${row.driverKind}/${row.model}`, label: row.model };
39213
- case "user":
39214
- return { key: triggerKey(row.trigger), label: triggerLabel(row.trigger) };
39215
- case "day":
39216
- return { key: `day:${row.at.slice(0, 10)}`, label: row.at.slice(0, 10) };
39217
- default:
39218
- return { key: `engine:${row.driverKind}`, label: row.driverKind };
39219
- }
39220
- }
39221
- function emptyGroup(key, label) {
39222
- return { key, label, turns: 0, input: 0, output: 0, cachedInput: 0, costUsd: null, unpriced: 0, billableUsd: null };
39223
- }
39224
- function add(group, row, prices) {
39225
- group.turns += 1;
39226
- group.input += clean2(row.input);
39227
- group.output += clean2(row.output);
39228
- group.cachedInput += clean2(row.cachedInput);
39229
- const cost = finiteOrNull(row.costUsd);
39230
- if (cost === null) group.unpriced += 1;
39231
- else group.costUsd = (group.costUsd ?? 0) + cost;
39232
- const billable = prices ? billableFor(row, prices) : null;
39233
- if (billable !== null) group.billableUsd = (group.billableUsd ?? 0) + billable;
39234
- }
39235
- function summarizeUsage(rows, groupBy, prices = null) {
39236
- const groups = /* @__PURE__ */ new Map();
39237
- const total = emptyGroup("total", "total");
39238
- for (const row of rows) {
39239
- const { key, label } = groupOf(row, groupBy);
39240
- let group = groups.get(key);
39241
- if (!group) {
39242
- group = emptyGroup(key, label);
39243
- groups.set(key, group);
39244
- }
39245
- add(group, row, prices);
39246
- add(total, row, prices);
39247
- }
39248
- const ordered = [...groups.values()];
39249
- if (groupBy === "day") ordered.sort((a2, b) => a2.key.localeCompare(b.key));
39250
- else {
39251
- ordered.sort((a2, b) => {
39252
- const costA = a2.costUsd ?? Number.NEGATIVE_INFINITY;
39253
- const costB = b.costUsd ?? Number.NEGATIVE_INFINITY;
39254
- return costB - costA || b.input + b.output - (a2.input + a2.output);
39255
- });
39256
- }
39257
- return { groups: ordered, total };
39258
- }
39259
-
39260
- // server/fleet-agent.ts
39261
39505
  var MAX_BODY = 256 * 1024;
39262
39506
  function readBody(req) {
39263
39507
  return new Promise((resolve13, reject) => {
@@ -39281,10 +39525,20 @@ function readBody(req) {
39281
39525
  var strings = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string").map((item) => item.trim()).filter(Boolean) : [];
39282
39526
  var text = (value) => typeof value === "string" && value.trim() ? value.trim() : void 0;
39283
39527
  var number = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
39284
- function workspaceUsage(layout, slug, now) {
39285
- const from = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1));
39286
- const total = summarizeUsage(readUsage(workspaceDataDir(layout, slug), { from, to: now }), "bot").total;
39287
- return { month: now.toISOString().slice(0, 7), turns: total.turns, costUsd: total.costUsd, billableUsd: total.billableUsd };
39528
+ var models = (value) => {
39529
+ try {
39530
+ return openRouterModelIds(value);
39531
+ } catch (error51) {
39532
+ throw Object.assign(error51, { status: 400 });
39533
+ }
39534
+ };
39535
+ function workspaceUsage(layout, slug, now, deps) {
39536
+ const month = now.toISOString().slice(0, 7);
39537
+ try {
39538
+ return { month, ...deps.usage(workspaceDataDir(layout, slug), fleetUser(slug), now) };
39539
+ } catch {
39540
+ return { month, turns: null, costUsd: null, billableUsd: null, unavailable: true };
39541
+ }
39288
39542
  }
39289
39543
  function createFleetAgent(options) {
39290
39544
  const deps = options.deps ?? defaultFleetDeps();
@@ -39311,7 +39565,7 @@ function createFleetAgent(options) {
39311
39565
  } catch {
39312
39566
  }
39313
39567
  };
39314
- const act = async (input) => {
39568
+ const runAction = async (input) => {
39315
39569
  const log = [];
39316
39570
  const errors = [];
39317
39571
  const io = { log: (line) => log.push(line), error: (line) => errors.push(line) };
@@ -39322,10 +39576,29 @@ function createFleetAgent(options) {
39322
39576
  audit({ action: input.action, slug: input.slug, ok: false, error: error51 instanceof Error ? error51.message : String(error51) });
39323
39577
  return { status: 400, body: { error: error51 instanceof Error ? error51.message : String(error51) } };
39324
39578
  }
39325
- const code = await executePlan(steps, deps, io);
39579
+ let code = 1;
39580
+ try {
39581
+ code = await executePlan(steps, deps, io);
39582
+ } catch (error51) {
39583
+ errors.push(error51 instanceof Error ? error51.message : String(error51));
39584
+ }
39585
+ if (code !== 0) {
39586
+ try {
39587
+ markProvisioningFailed(input, deps);
39588
+ } catch {
39589
+ errors.push("could not record the failed provisioning; inspect the reserved workspace before recovery");
39590
+ }
39591
+ }
39326
39592
  audit({ action: input.action, slug: input.slug, ok: code === 0, ...input.email ? { email: input.email } : {} });
39327
39593
  return code === 0 ? { status: 200, body: { ok: true, log } } : { status: 500, body: { error: errors[0] ?? "the step failed", log: [...log, ...errors] } };
39328
39594
  };
39595
+ let mutationTail = Promise.resolve();
39596
+ const act = (input) => {
39597
+ const result = mutationTail.then(() => runAction(input));
39598
+ mutationTail = result.catch(() => {
39599
+ });
39600
+ return result;
39601
+ };
39329
39602
  const handle = async (req, res) => {
39330
39603
  const url2 = new URL(req.url ?? "/", "http://fleet");
39331
39604
  const method = req.method ?? "GET";
@@ -39337,10 +39610,11 @@ function createFleetAgent(options) {
39337
39610
  if (method === "GET" && url2.pathname === "/health") return send(200, { ok: true });
39338
39611
  if (method === "GET" && url2.pathname === "/workspaces") {
39339
39612
  const registry2 = loadRegistry(layout, deps);
39613
+ const statusOnly = url2.searchParams.get("statusOnly") === "true";
39340
39614
  const workspaces = [];
39341
39615
  for (const workspace of Object.values(registry2.workspaces).sort((a2, b) => a2.slug.localeCompare(b.slug))) {
39342
- const live = (await deps.run(["systemctl", "is-active", `openmausbot@${workspace.slug}.service`])).output.trim() || "unknown";
39343
- workspaces.push({ ...workspace, live, usage: workspaceUsage(layout, workspace.slug, now()) });
39616
+ const live = workspace.status === "running" || workspace.status === "suspended" ? (await deps.run(["systemctl", "is-active", `openmausbot@${workspace.slug}.service`])).output.trim() || "unknown" : workspace.status;
39617
+ workspaces.push(statusOnly ? { slug: workspace.slug, live } : { ...workspace, live, usage: workspaceUsage(layout, workspace.slug, now(), deps) });
39344
39618
  }
39345
39619
  return send(200, { domain: registry2.domain, operator: registry2.operator ?? null, workspaces });
39346
39620
  }
@@ -39355,7 +39629,13 @@ function createFleetAgent(options) {
39355
39629
  cap: number(body.cap),
39356
39630
  memory: text(body.memory),
39357
39631
  brandJson: text(body.brandJson),
39358
- anthropicKey: text(body.anthropicKey)
39632
+ anthropicKey: text(body.anthropicKey),
39633
+ portalUrl: text(body.portalUrl),
39634
+ anthropicUrl: text(body.anthropicUrl),
39635
+ openrouterKey: text(body.openrouterKey),
39636
+ openrouterUrl: text(body.openrouterUrl),
39637
+ openrouterModels: body.openrouterModels === void 0 ? void 0 : models(body.openrouterModels),
39638
+ openrouterDefault: body.openrouterDefault === true
39359
39639
  });
39360
39640
  return send(result.status, result.body);
39361
39641
  }
@@ -39363,11 +39643,16 @@ function createFleetAgent(options) {
39363
39643
  const result = await act({ ...base2(), action: "upgrade" });
39364
39644
  return send(result.status, result.body);
39365
39645
  }
39366
- const match = /^\/workspaces\/([a-z0-9-]+)(?:\/(users|suspend|resume))?$/.exec(url2.pathname);
39646
+ const match = /^\/workspaces\/([a-z0-9-]+)(?:\/(users|providers|suspend|resume))?$/.exec(url2.pathname);
39367
39647
  if (match) {
39368
39648
  const slug = match[1];
39369
39649
  assertSlug(slug);
39370
39650
  const sub = match[2];
39651
+ if (method === "POST" && sub === "providers") {
39652
+ const body = await readBody(req);
39653
+ const result = await act({ ...base2(), action: "providers", slug, openrouterModels: models(body.models) });
39654
+ return send(result.status, result.body);
39655
+ }
39371
39656
  if (method === "POST" && sub === "users") {
39372
39657
  const body = await readBody(req);
39373
39658
  const action = body.action === "remove" ? "remove" : body.action === "add" ? "add" : void 0;
@@ -39418,11 +39703,11 @@ import { execFile } from "node:child_process";
39418
39703
 
39419
39704
  // companion/src/listener.ts
39420
39705
  import { homedir as homedir3, networkInterfaces } from "node:os";
39421
- import { delimiter as delimiter2, join as join5 } from "node:path";
39706
+ import { delimiter as delimiter2, join as join4 } from "node:path";
39422
39707
  function tailscaleCandidates(home = homedir3()) {
39423
39708
  return [
39424
39709
  "/Applications/Tailscale.app/Contents/MacOS/Tailscale",
39425
- join5(home, "Applications", "Tailscale.app", "Contents", "MacOS", "Tailscale"),
39710
+ join4(home, "Applications", "Tailscale.app", "Contents", "MacOS", "Tailscale"),
39426
39711
  "/opt/homebrew/bin/tailscale",
39427
39712
  "/usr/local/bin/tailscale",
39428
39713
  "/usr/bin/tailscale",
@@ -39640,9 +39925,9 @@ function phonePairingInstructions(phone, input) {
39640
39925
  // server/tunnel.ts
39641
39926
  import { spawn as spawn4 } from "node:child_process";
39642
39927
  import { randomUUID as randomUUID4 } from "node:crypto";
39643
- import { chmodSync as chmodSync3, existsSync as existsSync4, mkdirSync as mkdirSync5, readFileSync as readFileSync3, statSync } from "node:fs";
39928
+ import { chmodSync as chmodSync3, existsSync as existsSync4, mkdirSync as mkdirSync5, readFileSync as readFileSync2, statSync } from "node:fs";
39644
39929
  import { hostname } from "node:os";
39645
- import { dirname as dirname3, join as join6, resolve as resolve2 } from "node:path";
39930
+ import { dirname as dirname3, join as join5, resolve as resolve2 } from "node:path";
39646
39931
  import { fileURLToPath } from "node:url";
39647
39932
 
39648
39933
  // electron/secure-credential-state.mjs
@@ -39725,13 +40010,13 @@ function plainDocument(value) {
39725
40010
  return document;
39726
40011
  }
39727
40012
  function openTunnelCredentials(dataDir) {
39728
- const file2 = join6(dataDir, TUNNEL_CREDENTIALS_FILE);
40013
+ const file2 = join5(dataDir, TUNNEL_CREDENTIALS_FILE);
39729
40014
  let initial = {};
39730
40015
  let status = "empty";
39731
40016
  if (existsSync4(file2)) {
39732
40017
  try {
39733
40018
  if (process.platform !== "win32" && (statSync(file2).mode & 63) !== 0) chmodSync3(file2, 384);
39734
- const parsed = plainDocument(JSON.parse(readFileSync3(file2, "utf8")));
40019
+ const parsed = plainDocument(JSON.parse(readFileSync2(file2, "utf8")));
39735
40020
  if (!parsed) throw new Error("not a JSON object");
39736
40021
  initial = parsed;
39737
40022
  status = "ok";
@@ -39807,11 +40092,11 @@ function cloudflaredPath(dataDir, env = process.env) {
39807
40092
  return resolveCloudflaredBinary({ isPackaged: false, appPath: dataDir, environment: env });
39808
40093
  }
39809
40094
  function prepareEntry(here = HERE) {
39810
- const bundled = join6(here, "prepare-cloudflared.js");
40095
+ const bundled = join5(here, "prepare-cloudflared.js");
39811
40096
  return existsSync4(bundled) ? bundled : resolve2(here, "..", "scripts", "prepare-cloudflared.mjs");
39812
40097
  }
39813
40098
  function guardianEntry(here = HERE) {
39814
- const bundled = join6(here, "tunnel-guardian.js");
40099
+ const bundled = join5(here, "tunnel-guardian.js");
39815
40100
  if (existsSync4(bundled)) return bundled;
39816
40101
  const source = resolve2(here, "..", "electron", "managed-companion-guardian-main.mjs");
39817
40102
  return existsSync4(source) ? source : null;
@@ -39846,7 +40131,7 @@ function startTunnel(options) {
39846
40131
  const tunnel = createManagedCompanionTunnel({
39847
40132
  binaryPath: options.binaryPath,
39848
40133
  guardianEntry: options.guardian,
39849
- runtimeRoot: join6(options.dataDir, TUNNEL_RUNTIME_DIR),
40134
+ runtimeRoot: join5(options.dataDir, TUNNEL_RUNTIME_DIR),
39850
40135
  originPort,
39851
40136
  environment: env,
39852
40137
  fetchImpl: options.fetchImpl,
@@ -39892,7 +40177,7 @@ function parseArgs(argv, env = process.env) {
39892
40177
  const options = {
39893
40178
  command: command === "--help" || command === "-h" ? "help" : command,
39894
40179
  port: Number(env.OMB_PORT || 8799),
39895
- dataDir: env.OMB_DATA_DIR || join38(homedir24(), ".openmausbot"),
40180
+ dataDir: env.OMB_DATA_DIR || join37(homedir24(), ".openmausbot"),
39896
40181
  tailscale: false,
39897
40182
  tunnel: false,
39898
40183
  client: false,
@@ -40059,7 +40344,7 @@ function defaultIo() {
40059
40344
  }
40060
40345
  function serverVersion(here = HERE2) {
40061
40346
  try {
40062
- const parsed = JSON.parse(readFileSync22(resolve12(here, "..", "package.json"), "utf8"));
40347
+ const parsed = JSON.parse(readFileSync21(resolve12(here, "..", "package.json"), "utf8"));
40063
40348
  const version2 = typeof parsed === "object" && parsed !== null ? Reflect.get(parsed, "version") : void 0;
40064
40349
  return typeof version2 === "string" && version2 ? version2 : "0.0.0";
40065
40350
  } catch {
@@ -40084,7 +40369,7 @@ async function isWorkspaceRunning(options) {
40084
40369
  try {
40085
40370
  const { status, body } = await api(options.port, "/api/health");
40086
40371
  if (status !== 200 || body?.app !== "openmausbot") return false;
40087
- const expected = readFileSync22(join38(options.dataDir, "environment-id"), "utf8").trim();
40372
+ const expected = readFileSync21(join37(options.dataDir, "environment-id"), "utf8").trim();
40088
40373
  const descriptor = await api(options.port, "/.well-known/openmausbot/environment");
40089
40374
  return /^[0-9a-f-]{36}$/i.test(expected) && descriptor.status === 200 && descriptor.body?.environmentId === expected;
40090
40375
  } catch {
@@ -40284,11 +40569,11 @@ async function runStatus(options, io = defaultIo()) {
40284
40569
  return code;
40285
40570
  }
40286
40571
  async function runAccess(options, io = defaultIo()) {
40287
- const file2 = join38(options.dataDir, "config.json");
40572
+ const file2 = join37(options.dataDir, "config.json");
40288
40573
  let raw = {};
40289
40574
  if (existsSync19(file2)) {
40290
40575
  try {
40291
- const parsed = JSON.parse(readFileSync22(file2, "utf8"));
40576
+ const parsed = JSON.parse(readFileSync21(file2, "utf8"));
40292
40577
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error("not an object");
40293
40578
  raw = Object.fromEntries(Object.entries(parsed));
40294
40579
  } catch (error51) {
@@ -40436,16 +40721,16 @@ async function runBrowser(options, io = defaultIo()) {
40436
40721
  return 0;
40437
40722
  }
40438
40723
  function serverEntry(here = HERE2) {
40439
- const bundled = join38(here, "index.js");
40724
+ const bundled = join37(here, "index.js");
40440
40725
  const root = resolve12(here, "..");
40441
40726
  if (existsSync19(bundled)) {
40442
- const staticDir2 = [join38(root, "dist"), join38(here, "..", "ui")].find((d) => existsSync19(join38(d, "index.html"))) ?? null;
40443
- const skillsDir = existsSync19(join38(root, "skills")) ? join38(root, "skills") : null;
40727
+ const staticDir2 = [join37(root, "dist"), join37(here, "..", "ui")].find((d) => existsSync19(join37(d, "index.html"))) ?? null;
40728
+ const skillsDir = existsSync19(join37(root, "skills")) ? join37(root, "skills") : null;
40444
40729
  return { command: process.execPath, args: [bundled], staticDir: staticDir2, skillsDir };
40445
40730
  }
40446
- const source = join38(here, "index.ts");
40447
- const staticDir = existsSync19(join38(root, "dist", "index.html")) ? join38(root, "dist") : null;
40448
- return { command: process.execPath, args: ["--experimental-strip-types", source], staticDir, skillsDir: existsSync19(join38(root, "skills")) ? join38(root, "skills") : null };
40731
+ const source = join37(here, "index.ts");
40732
+ const staticDir = existsSync19(join37(root, "dist", "index.html")) ? join37(root, "dist") : null;
40733
+ return { command: process.execPath, args: ["--experimental-strip-types", source], staticDir, skillsDir: existsSync19(join37(root, "skills")) ? join37(root, "skills") : null };
40449
40734
  }
40450
40735
  async function planTunnel(options, log) {
40451
40736
  let access2 = null;
@@ -40545,10 +40830,10 @@ async function runServe(options, log = console.log) {
40545
40830
  let child;
40546
40831
  try {
40547
40832
  if (options.guided) {
40548
- const logsDir = join38(options.dataDir, "logs");
40833
+ const logsDir = join37(options.dataDir, "logs");
40549
40834
  mkdirSync17(logsDir, { recursive: true, mode: 448 });
40550
- logPath = join38(logsDir, `server-${Date.now()}-${process.pid}.log`);
40551
- logFd = openSync3(logPath, "wx", 384);
40835
+ logPath = join37(logsDir, `server-${Date.now()}-${process.pid}.log`);
40836
+ logFd = openSync4(logPath, "wx", 384);
40552
40837
  log("\nStarting your workspace\u2026");
40553
40838
  }
40554
40839
  if (tailscale) {
@@ -40572,7 +40857,7 @@ async function runServe(options, log = console.log) {
40572
40857
  }
40573
40858
  throw error51;
40574
40859
  } finally {
40575
- if (logFd !== void 0) closeSync3(logFd);
40860
+ if (logFd !== void 0) closeSync4(logFd);
40576
40861
  process.removeListener("SIGINT", cancelStartup);
40577
40862
  process.removeListener("SIGTERM", cancelStartup);
40578
40863
  }
@@ -40739,7 +41024,7 @@ async function runOnboardingCommand(options, io = defaultIo(), startServer = run
40739
41024
  }
40740
41025
  if (options.command === "setup") {
40741
41026
  io.log("\nAll set. Start with: openmausbot (or npx openmausbot without a global install).");
40742
- if (options.dataDir !== join38(homedir24(), ".openmausbot") || options.port !== 8799) {
41027
+ if (options.dataDir !== join37(homedir24(), ".openmausbot") || options.port !== 8799) {
40743
41028
  io.log(`Use the same --data-dir (${options.dataDir}) and --port (${options.port}) options when starting.`);
40744
41029
  }
40745
41030
  return 0;