openmausbot 0.1.75 → 0.1.76

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 (39) hide show
  1. package/dist/assets/index-BtT047dk.css +1 -0
  2. package/dist/assets/{index-BTlyRwLl.js → index-C6blGSq5.js} +1 -1
  3. package/dist/assets/index-fKXFpB_J.js +305 -0
  4. package/dist/index.html +2 -2
  5. package/dist-server/companion/src/routes.js +6 -2
  6. package/dist-server/drivers/agents-proxy.js +133 -11
  7. package/dist-server/index.js +2602 -869
  8. package/dist-server/mcp-gate.js +28 -18
  9. package/dist-server/openmausbot.js +202 -120
  10. package/dist-server/pair-cli.js +202 -120
  11. package/dist-server/server/agent-tool-policy.js +1 -0
  12. package/dist-server/server/atomic.js +37 -1
  13. package/dist-server/server/bot-overview.js +5 -1
  14. package/dist-server/server/chief-of-staff.js +17 -12
  15. package/dist-server/server/delegations.js +177 -63
  16. package/dist-server/server/drivers/acp/hermes.js +21 -3
  17. package/dist-server/server/drivers/agents-proxy.js +113 -10
  18. package/dist-server/server/drivers/claude.js +31 -14
  19. package/dist-server/server/drivers/openai-chat.js +90 -67
  20. package/dist-server/server/drivers/openai-compat.js +9 -1
  21. package/dist-server/server/drivers/pi.js +18 -3
  22. package/dist-server/server/env-path.js +18 -17
  23. package/dist-server/server/index.js +1112 -312
  24. package/dist-server/server/message-db.js +21 -0
  25. package/dist-server/server/peer-roster.js +48 -9
  26. package/dist-server/server/procs.js +19 -3
  27. package/dist-server/server/room-handoffs.js +237 -0
  28. package/dist-server/server/section-context.js +64 -14
  29. package/dist-server/server/store.js +247 -8
  30. package/dist-server/server/surface.js +16 -5
  31. package/dist-server/server/team-backup.js +17 -1
  32. package/dist-server/server/team-setup-requests.js +252 -0
  33. package/dist-server/server/turn-dispatch-guard.js +31 -3
  34. package/dist-server/shared/approval-mode.js +7 -0
  35. package/dist-server/shared/team-backup.js +1 -0
  36. package/dist-server/shared/team-setup.js +1 -0
  37. package/package.json +1 -1
  38. package/dist/assets/index-CeMbvsgx.js +0 -305
  39. 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
 
@@ -18609,28 +18627,29 @@ function isFile(p) {
18609
18627
  return false;
18610
18628
  }
18611
18629
  }
18612
- function whichWin(cli) {
18613
- const exts = (process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean);
18630
+ function whichWin(cli, env) {
18631
+ const exts = ((env ?? process.env).PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean);
18614
18632
  const probe = (base2) => {
18615
18633
  const order = extname(base2) ? [base2, ...exts.map((e) => base2 + e)] : [...exts.map((e) => base2 + e), base2];
18616
18634
  return order.find(isFile) ?? null;
18617
18635
  };
18618
18636
  if (/[\\/]/.test(cli) || /^[a-zA-Z]:/.test(cli)) return probe(cli);
18619
- for (const dir of augmentedPath().split(delimiter3)) {
18637
+ const path3 = env ? env.PATH ?? env.Path ?? "" : augmentedPath();
18638
+ for (const dir of path3.split(delimiter3)) {
18620
18639
  if (!dir) continue;
18621
18640
  const hit = probe(join9(dir, cli));
18622
18641
  if (hit) return hit;
18623
18642
  }
18624
18643
  return null;
18625
18644
  }
18626
- function nodeExe(near) {
18645
+ function nodeExe(near, env) {
18627
18646
  const local = join9(near, "node.exe");
18628
18647
  if (isFile(local)) return local;
18629
- const onPath2 = whichWin("node.exe");
18648
+ const onPath2 = whichWin("node.exe", env);
18630
18649
  if (onPath2 && extname(onPath2).toLowerCase() === ".exe") return onPath2;
18631
18650
  return process.versions.electron ? null : process.execPath;
18632
18651
  }
18633
- function parseCmdShim(shim) {
18652
+ function parseCmdShim(shim, env) {
18634
18653
  let text4;
18635
18654
  try {
18636
18655
  text4 = readFileSync6(shim, "utf8");
@@ -18641,13 +18660,13 @@ function parseCmdShim(shim) {
18641
18660
  const targets = [...text4.matchAll(/"%~?dp0%?\\?([^"]+)"/g)].map((m) => join9(dir, m[1])).filter((p) => isFile(p) && basename2(p).toLowerCase() !== "node.exe");
18642
18661
  const script = targets.find((p) => /\.[cm]?js$/i.test(p));
18643
18662
  if (script) {
18644
- const node = nodeExe(dir);
18663
+ const node = nodeExe(dir, env);
18645
18664
  if (node) return { command: node, args: [script] };
18646
18665
  }
18647
18666
  const exe = targets.find((p) => extname(p).toLowerCase() === ".exe");
18648
18667
  return exe ? { command: exe, args: [] } : null;
18649
18668
  }
18650
- function parseNodeShebang(file2) {
18669
+ function parseNodeShebang(file2, env) {
18651
18670
  let head = "";
18652
18671
  let fd = null;
18653
18672
  try {
@@ -18666,32 +18685,32 @@ function parseNodeShebang(file2) {
18666
18685
  }
18667
18686
  }
18668
18687
  if (!/^#!.*\bnode(\.exe)?\b/.test(head)) return null;
18669
- const node = nodeExe(dirname4(file2));
18688
+ const node = nodeExe(dirname4(file2), env);
18670
18689
  return node ? { command: node, args: [file2] } : null;
18671
18690
  }
18672
- function resolveWord(cli, args) {
18691
+ function resolveWord(cli, args, env) {
18673
18692
  if (process.platform !== "win32") return { command: cli, args };
18674
- const file2 = whichWin(cli);
18693
+ const file2 = whichWin(cli, env);
18675
18694
  if (!file2) return { command: cli, args };
18676
18695
  const ext = extname(file2).toLowerCase();
18677
18696
  if (ext === ".cmd" || ext === ".bat") {
18678
- const direct = parseCmdShim(file2);
18697
+ const direct = parseCmdShim(file2, env);
18679
18698
  return direct ? { command: direct.command, args: [...direct.args, ...args] } : { command: file2, args };
18680
18699
  }
18681
18700
  if (ext === ".exe" || ext === ".com") return { command: file2, args };
18682
- const viaNode = parseNodeShebang(file2);
18701
+ const viaNode = parseNodeShebang(file2, env);
18683
18702
  return viaNode ? { command: viaNode.command, args: [...viaNode.args, ...args] } : { command: file2, args };
18684
18703
  }
18685
- function resolveCliSpawn(cli, args) {
18704
+ function resolveCliSpawn(cli, args, env) {
18686
18705
  const trimmed = cli.trim();
18687
- if (trimmed.includes(" ") && existsSync7(trimmed)) return resolveWord(trimmed, args);
18706
+ if (trimmed.includes(" ") && existsSync7(trimmed)) return resolveWord(trimmed, args, env);
18688
18707
  const split = splitCliString(cli);
18689
18708
  if (split.length > 1 || split[0] !== trimmed) {
18690
18709
  const [head, ...fixed] = split;
18691
18710
  if (!head) return { command: cli, args };
18692
- return resolveWord(head, [...fixed, ...args]);
18711
+ return resolveWord(head, [...fixed, ...args], env);
18693
18712
  }
18694
- return resolveWord(cli, args);
18713
+ return resolveWord(cli, args, env);
18695
18714
  }
18696
18715
  var cached2, probed, loginShellPath, registeredDirs;
18697
18716
  var init_env_path = __esm({
@@ -18710,8 +18729,8 @@ import {
18710
18729
  execFile as execFile3
18711
18730
  } from "node:child_process";
18712
18731
  import { join as join10 } from "node:path";
18713
- function resolveCli(cli, args = []) {
18714
- return resolveCliSpawn(cli, args);
18732
+ function resolveCli(cli, args = [], env) {
18733
+ return resolveCliSpawn(cli, args, env);
18715
18734
  }
18716
18735
  function estimatedWindowsCommandLineChars(resolved) {
18717
18736
  return [resolved.command, ...resolved.args].reduce(
@@ -18825,9 +18844,13 @@ async function stopCliTree(child, pid, timeoutMs) {
18825
18844
  if (exited()) return true;
18826
18845
  return new Promise((resolve13) => {
18827
18846
  let timer;
18847
+ let finished = false;
18828
18848
  const done = (stopped) => {
18849
+ if (finished) return;
18850
+ finished = true;
18829
18851
  clearTimeout(timer);
18830
18852
  child.off("close", closed);
18853
+ child.off("exit", closed);
18831
18854
  resolve13(stopped);
18832
18855
  };
18833
18856
  const closed = () => done(true);
@@ -18835,7 +18858,12 @@ async function stopCliTree(child, pid, timeoutMs) {
18835
18858
  timer = setTimeout(() => done(false), timeoutMs);
18836
18859
  timer.unref?.();
18837
18860
  execFile3("taskkill", ["/PID", String(pid), "/T", "/F"], { windowsHide: true }, (err) => {
18838
- if (!err) return;
18861
+ if (finished) return;
18862
+ if (!err) {
18863
+ if (exited()) done(true);
18864
+ else child.once("exit", closed);
18865
+ return;
18866
+ }
18839
18867
  try {
18840
18868
  child.kill();
18841
18869
  } catch {
@@ -22639,15 +22667,16 @@ function askSummary(ask) {
22639
22667
  if (questions) return askQuestionSummary(questions).slice(0, 300);
22640
22668
  return askInputSummary(ask.input) ?? ask.tool ?? "tool";
22641
22669
  }
22642
- function permissionSocketPath(threadId) {
22670
+ function permissionSocketPath(threadId, botId) {
22671
+ const key = botId ? `${botId}\0${threadId}` : threadId;
22643
22672
  const prefix = threadId.replace(/[^\w-]/g, "").slice(0, 4);
22644
- const digest2 = createHash5("sha256").update(threadId).digest("hex").slice(0, 4);
22673
+ const digest2 = createHash5("sha256").update(key).digest("hex").slice(0, 4);
22645
22674
  return brokerSocketPath(DATA_DIR, `${prefix}${digest2}`);
22646
22675
  }
22647
- function brokerSocketCandidates(threadId) {
22648
- const base2 = permissionSocketPath(threadId);
22676
+ function brokerSocketCandidates(threadId, botId) {
22677
+ const base2 = permissionSocketPath(threadId, botId);
22649
22678
  if (process.platform !== "win32") {
22650
- const scope = createHash5("sha256").update(`${DATA_DIR}\0${process.pid}\0${threadId}`).digest("hex").slice(0, 16);
22679
+ const scope = createHash5("sha256").update(`${DATA_DIR}\0${process.pid}\0${botId ?? ""}\0${threadId}`).digest("hex").slice(0, 16);
22651
22680
  return [base2, join20(tmpdir3(), `omb-perm-${scope}.sock`)];
22652
22681
  }
22653
22682
  return [
@@ -23045,8 +23074,8 @@ var init_claude = __esm({
23045
23074
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
23046
23075
  });
23047
23076
  const retryState = /* @__PURE__ */ new Map();
23048
- const sendTurn = async (turn) => {
23049
- const { threadId } = turn;
23077
+ const sendTurn = async (turn, logicalTurnId) => {
23078
+ const { threadId, botId } = turn;
23050
23079
  if (active.has(threadId)) throw new Error("a turn is already running on this thread");
23051
23080
  const permissionMode = turn.approvalMode === void 0 ? config2.permissionMode : turn.approvalMode === "full" ? "bypassPermissions" : turn.approvalMode === "auto" ? "auto" : turn.approvalMode === "edits" ? "acceptEdits" : "default";
23052
23081
  const controlsHost = turn.integrations?.localComputer?.scope === "local-computer";
@@ -23054,7 +23083,7 @@ var init_claude = __esm({
23054
23083
  throw new Error("local computer control requires the interactive approval broker");
23055
23084
  }
23056
23085
  const promptMsg = claudeUserMessage(turn.text, turn.images);
23057
- const turnId = newId();
23086
+ const turnId = logicalTurnId ?? newId();
23058
23087
  const retryAbort = new AbortController();
23059
23088
  const retry = retryState.get(threadId) ?? { attempt: 0, cancelled: false };
23060
23089
  retry.cancelled = false;
@@ -23159,7 +23188,7 @@ var init_claude = __esm({
23159
23188
  if (gated) mcpServers[name] = gated;
23160
23189
  }
23161
23190
  let broker;
23162
- const socketPath = permissionSocketPath(threadId);
23191
+ const socketPath = permissionSocketPath(threadId, botId);
23163
23192
  if (permissionMode !== "bypassPermissions") {
23164
23193
  args.push("--permission-prompt-tool", "mcp__ogb__approve");
23165
23194
  }
@@ -23198,9 +23227,11 @@ var init_claude = __esm({
23198
23227
  const live = sessions.get(threadId);
23199
23228
  if (live && !live.turn && !live.closing && live.child.exitCode === null && live.argsKey === argsKey && (!sessionId || sessionId === live.sessionId)) {
23200
23229
  if (live.idleTimer) clearTimeout(live.idleTimer);
23201
- live.turn = { turnId, settled: false, sawStreamDelta: false };
23230
+ live.turn = { turnId, input: turn, retryAbort, settled: false, sawStreamDelta: false };
23202
23231
  active.set(threadId, { stop: () => {
23203
23232
  closeSession(threadId, "interrupted");
23233
+ retry.cancelled = true;
23234
+ retryAbort.abort();
23204
23235
  stopSession(live);
23205
23236
  }, turnId, broker: live.broker });
23206
23237
  emit({ ...base2(threadId, turnId), type: "turn.started" });
@@ -23255,7 +23286,7 @@ var init_claude = __esm({
23255
23286
  if (socketPath) {
23256
23287
  const askTools = /* @__PURE__ */ new Map();
23257
23288
  broker = await createPermissionBroker({
23258
- socketPaths: brokerSocketCandidates(threadId),
23289
+ socketPaths: brokerSocketCandidates(threadId, botId),
23259
23290
  isActive: () => Boolean(sessions.get(threadId)?.turn),
23260
23291
  onAsk: (ask) => {
23261
23292
  const eventTurnId = sessions.get(threadId)?.turn?.turnId ?? turnId;
@@ -23331,7 +23362,7 @@ var init_claude = __esm({
23331
23362
  sessionId: sessionId ?? newSessionId,
23332
23363
  sawInit: false,
23333
23364
  nativePermissionMode: null,
23334
- turn: { turnId, settled: false, sawStreamDelta: false },
23365
+ turn: { turnId, input: turn, retryAbort, settled: false, sawStreamDelta: false },
23335
23366
  idleTimer: null,
23336
23367
  closing: false,
23337
23368
  stderr: ""
@@ -23483,9 +23514,11 @@ var init_claude = __esm({
23483
23514
  if (closeFinalized) return;
23484
23515
  closeFinalized = true;
23485
23516
  if (session.turn && !session.turn.settled) {
23517
+ const { turnId: turnId2, input: turn2, retryAbort: retryAbort2 } = session.turn;
23518
+ const retry2 = retryState.get(threadId) ?? { attempt: 0, cancelled: false };
23486
23519
  const message2 = `claude exited ${code} before result${session.stderr ? `: ${session.stderr.trim().slice(-300)}` : ""}`;
23487
23520
  const verdict = classifyError({ exitCode: code, stderr: message2 });
23488
- if (!retry.cancelled && code !== 0 && verdict.transient && !session.turn.sawStreamDelta && retry.attempt < RETRY_MAX_ATTEMPTS - 1) {
23521
+ if (!retry2.cancelled && code !== 0 && verdict.transient && !session.turn.sawStreamDelta && retry2.attempt < RETRY_MAX_ATTEMPTS - 1) {
23489
23522
  const failedBroker = session.broker;
23490
23523
  session.broker = void 0;
23491
23524
  failedBroker?.pause();
@@ -23503,23 +23536,23 @@ var init_claude = __esm({
23503
23536
  }
23504
23537
  sessions.delete(threadId);
23505
23538
  session.turn = null;
23506
- retry.attempt++;
23507
- const delayMs = computeBackoff(retry.attempt - 1);
23539
+ retry2.attempt++;
23540
+ const delayMs = computeBackoff(retry2.attempt - 1);
23508
23541
  emit({
23509
- ...base2(threadId, turnId),
23542
+ ...base2(threadId, turnId2),
23510
23543
  type: "turn.retrying",
23511
- attempt: retry.attempt,
23544
+ attempt: retry2.attempt,
23512
23545
  delayMs,
23513
23546
  reason: verdict.reason
23514
23547
  });
23515
23548
  void (async () => {
23516
- const wait2 = interruptibleDelay(delayMs * retryScale, retryAbort.signal);
23549
+ const wait2 = interruptibleDelay(delayMs * retryScale, retryAbort2.signal);
23517
23550
  await wait2.promise;
23518
- if (retry.cancelled) {
23551
+ if (retry2.cancelled) {
23519
23552
  active.delete(threadId);
23520
23553
  retryState.delete(threadId);
23521
23554
  emit({
23522
- ...base2(threadId, turnId),
23555
+ ...base2(threadId, turnId2),
23523
23556
  type: "turn.completed",
23524
23557
  ok: false,
23525
23558
  stopReason: "interrupted",
@@ -23530,16 +23563,16 @@ var init_claude = __esm({
23530
23563
  active.delete(threadId);
23531
23564
  try {
23532
23565
  const cursor3 = session.sessionId ?? sessionId ?? void 0;
23533
- await sendTurn({ ...turn, resumeCursor: cursor3 });
23566
+ await sendTurn({ ...turn2, resumeCursor: cursor3 }, turnId2);
23534
23567
  } catch (e) {
23535
23568
  retryState.delete(threadId);
23536
23569
  emit({
23537
- ...base2(threadId, turnId),
23570
+ ...base2(threadId, turnId2),
23538
23571
  type: "runtime.error",
23539
23572
  message: e instanceof Error ? e.message : String(e)
23540
23573
  });
23541
23574
  emit({
23542
- ...base2(threadId, turnId),
23575
+ ...base2(threadId, turnId2),
23543
23576
  type: "turn.completed",
23544
23577
  ok: false,
23545
23578
  stopReason: "exit_before_result",
@@ -23555,10 +23588,10 @@ var init_claude = __esm({
23555
23588
  promptSubmitted: session.sawInit,
23556
23589
  producedOutput: session.turn.sawStreamDelta
23557
23590
  });
23558
- if (mayReplay(resumeFailure) && !retry.cancelled) {
23591
+ if (mayReplay(resumeFailure) && !retry2.cancelled) {
23559
23592
  const recovery = recoveryPromptFor({
23560
- recoveryText: turn.recoveryText,
23561
- currentText: turn.text,
23593
+ recoveryText: turn2.recoveryText,
23594
+ currentText: turn2.text,
23562
23595
  failure: resumeFailure
23563
23596
  });
23564
23597
  session.broker?.close();
@@ -23578,23 +23611,23 @@ var init_claude = __esm({
23578
23611
  session.turn = null;
23579
23612
  active.delete(threadId);
23580
23613
  emit({
23581
- ...base2(threadId, turnId),
23614
+ ...base2(threadId, turnId2),
23582
23615
  type: "turn.retrying",
23583
- attempt: retry.attempt + 1,
23616
+ attempt: retry2.attempt + 1,
23584
23617
  delayMs: 0,
23585
23618
  reason: "resume_rejected"
23586
23619
  });
23587
23620
  void (async () => {
23588
23621
  try {
23589
- await sendTurn({ ...turn, resumeCursor: void 0, recoveryText: void 0, text: recovery.text });
23622
+ await sendTurn({ ...turn2, resumeCursor: void 0, recoveryText: void 0, text: recovery.text }, turnId2);
23590
23623
  } catch (e) {
23591
23624
  retryState.delete(threadId);
23592
23625
  emit({
23593
- ...base2(threadId, turnId),
23626
+ ...base2(threadId, turnId2),
23594
23627
  type: "runtime.error",
23595
23628
  message: e instanceof Error ? e.message : String(e)
23596
23629
  });
23597
- emit({ ...base2(threadId, turnId), type: "turn.completed", ok: false, stopReason: "exit_before_result", cost: null });
23630
+ emit({ ...base2(threadId, turnId2), type: "turn.completed", ok: false, stopReason: "exit_before_result", cost: null });
23598
23631
  }
23599
23632
  })();
23600
23633
  return;
@@ -25615,72 +25648,89 @@ function createOpenAIChatRuntime(options) {
25615
25648
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
25616
25649
  });
25617
25650
  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 = "";
25651
+ const timeoutController = new AbortController();
25652
+ let idleTimer = null;
25653
+ const resetIdleTimer = () => {
25654
+ if (idleTimer) clearTimeout(idleTimer);
25655
+ idleTimer = setTimeout(() => {
25656
+ timeoutController.abort(new DOMException("Streaming idle timeout elapsed", "AbortError"));
25657
+ }, options.timeoutMs);
25658
+ };
25659
+ resetIdleTimer();
25647
25660
  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");
25661
+ const activeSignal = signal ? AbortSignal.any([signal, timeoutController.signal]) : timeoutController.signal;
25662
+ const response = await fetch(`${options.apiUrl}/chat/completions`, {
25663
+ method: "POST",
25664
+ headers: { authorization: `Bearer ${options.apiKey}`, "content-type": "application/json" },
25665
+ body: JSON.stringify(options.requestBody(model, messages, stream)),
25666
+ signal: activeSignal
25667
+ });
25668
+ if (!response.ok) {
25669
+ const body = await response.text().catch(() => "");
25670
+ throw new Error(`${options.httpErrorLabel} HTTP ${response.status}${body ? `: ${body.slice(0, 200)}` : ""}`);
25671
+ }
25672
+ if (!stream) {
25673
+ const json2 = await response.json();
25674
+ const message2 = json2.choices?.[0]?.message;
25675
+ return {
25676
+ text: typeof message2?.content === "string" ? message2.content : "",
25677
+ reasoning: options.reasoning && typeof message2?.reasoning_content === "string" ? message2.reasoning_content : "",
25678
+ usage: usageFrom(json2.usage)
25679
+ };
25680
+ }
25681
+ if (!response.body) {
25682
+ throw new Error(options.noBodyError ?? `${options.httpErrorLabel} returned no response body`);
25683
+ }
25684
+ let text4 = "";
25685
+ let reasoning = "";
25686
+ let usage = null;
25687
+ const reader = response.body.getReader();
25688
+ const decoder = new TextDecoder();
25689
+ let buffer = "";
25690
+ try {
25691
+ readLoop: for (; ; ) {
25692
+ const { done, value } = await reader.read();
25693
+ if (done) {
25694
+ if (buffer.trim() === "data: [DONE]") break;
25695
+ throw new DOMException("Stream ended before completion", "AbortError");
25696
+ }
25697
+ resetIdleTimer();
25698
+ buffer += decoder.decode(value, { stream: true });
25699
+ let newline;
25700
+ while ((newline = buffer.indexOf("\n")) !== -1) {
25701
+ const line = buffer.slice(0, newline).trim();
25702
+ buffer = buffer.slice(newline + 1);
25703
+ if (!line.startsWith("data:")) continue;
25704
+ const data = line.slice(5).trim();
25705
+ if (data === "[DONE]") break readLoop;
25706
+ let chunk;
25707
+ try {
25708
+ chunk = JSON.parse(data);
25709
+ } catch {
25710
+ continue;
25711
+ }
25712
+ const delta = chunk.choices?.[0]?.delta;
25713
+ const reasoningDelta = options.reasoning && typeof delta?.reasoning_content === "string" ? delta.reasoning_content : "";
25714
+ const contentDelta = typeof delta?.content === "string" ? delta.content : "";
25715
+ if (reasoningDelta) {
25716
+ reasoning += reasoningDelta;
25717
+ onDelta?.(reasoningDelta, "reasoning_text");
25718
+ }
25719
+ if (contentDelta) {
25720
+ text4 += contentDelta;
25721
+ onDelta?.(contentDelta, "assistant_text");
25722
+ }
25723
+ if (chunk.usage) usage = usageFrom(chunk.usage);
25675
25724
  }
25676
- if (chunk.usage) usage = usageFrom(chunk.usage);
25677
25725
  }
25726
+ } finally {
25727
+ await reader.cancel().catch(() => {
25728
+ });
25678
25729
  }
25730
+ return { text: text4, reasoning, usage };
25679
25731
  } finally {
25680
- await reader.cancel().catch(() => {
25681
- });
25732
+ if (idleTimer) clearTimeout(idleTimer);
25682
25733
  }
25683
- return { text: text4, reasoning, usage };
25684
25734
  };
25685
25735
  const messagesFor = (turn) => [
25686
25736
  ...turn.system ? [{ role: "system", content: turn.system }] : [],
@@ -34647,11 +34697,18 @@ function hermesConfiguredModel(env = process.env) {
34647
34697
  custom: true
34648
34698
  };
34649
34699
  }
34700
+ function hermesAcpModelsTimeoutMs(env) {
34701
+ const raw = env[HERMES_ACP_MODELS_TIMEOUT_ENV];
34702
+ if (raw === void 0) return HERMES_ACP_MODELS_DEFAULT_TIMEOUT_MS;
34703
+ const parsed = Number(raw);
34704
+ return Number.isInteger(parsed) && parsed > 0 && parsed <= 2147483647 ? parsed : HERMES_ACP_MODELS_DEFAULT_TIMEOUT_MS;
34705
+ }
34650
34706
  async function fetchHermesAcpModels(cli, env) {
34651
34707
  return await new Promise((resolve13) => {
34652
34708
  let child;
34653
34709
  try {
34654
- child = spawn6(cli, ["acp"], { stdio: ["pipe", "pipe", "ignore"], env });
34710
+ const resolved = resolveCli(cli, ["acp"], env);
34711
+ child = spawn6(resolved.command, resolved.args, { stdio: ["pipe", "pipe", "ignore"], env, windowsHide: true });
34655
34712
  } catch {
34656
34713
  return resolve13([]);
34657
34714
  }
@@ -34676,7 +34733,7 @@ async function fetchHermesAcpModels(cli, env) {
34676
34733
  }
34677
34734
  resolve13(out);
34678
34735
  };
34679
- timer = setTimeout(() => done([]), 5e3);
34736
+ timer = setTimeout(() => done([]), hermesAcpModelsTimeoutMs(env));
34680
34737
  child.once("error", () => done([]));
34681
34738
  child.once("close", () => {
34682
34739
  if (hardKillTimer) clearTimeout(hardKillTimer);
@@ -34756,11 +34813,12 @@ async function applySetting2(request, method, params, what) {
34756
34813
  throw new Error(`Hermes rejected ${what} via ${method}: ${e.message}`);
34757
34814
  }
34758
34815
  }
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;
34816
+ 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
34817
  var init_hermes = __esm({
34761
34818
  "server/drivers/acp/hermes.ts"() {
34762
34819
  "use strict";
34763
34820
  init_browser();
34821
+ init_procs();
34764
34822
  init_local_inject();
34765
34823
  init_core3();
34766
34824
  EMPTY2 = { default: "", options: [] };
@@ -34774,6 +34832,8 @@ var init_hermes = __esm({
34774
34832
  "Z_AI_API_KEY"
34775
34833
  ];
34776
34834
  HERMES_LOCAL_CONFIG_PROVIDERS = /* @__PURE__ */ new Set(["custom", "lmstudio", "ollama", "vllm", "llamacpp"]);
34835
+ HERMES_ACP_MODELS_TIMEOUT_ENV = "HERMES_ACP_MODELS_TIMEOUT_MS";
34836
+ HERMES_ACP_MODELS_DEFAULT_TIMEOUT_MS = 15e3;
34777
34837
  support10 = {
34778
34838
  driverKind: "hermesAgent",
34779
34839
  displayName: "Hermes",
@@ -34846,12 +34906,19 @@ function decodeConfig5(raw) {
34846
34906
  provider: typeof config2.provider === "string" ? config2.provider || void 0 : process.env.OPENAI_COMPAT_PROVIDER || void 0
34847
34907
  };
34848
34908
  }
34849
- var DRIVER_KIND5, DEFAULT_MODELS, OpenAICompatDriver;
34909
+ var DRIVER_KIND5, DEFAULT_IDLE_TIMEOUT_MS, idleTimeoutMs, DEFAULT_MODELS, OpenAICompatDriver;
34850
34910
  var init_openai_compat = __esm({
34851
34911
  "server/drivers/openai-compat.ts"() {
34852
34912
  "use strict";
34853
34913
  init_openai_chat();
34854
34914
  DRIVER_KIND5 = "openai-compat";
34915
+ DEFAULT_IDLE_TIMEOUT_MS = 18e4;
34916
+ idleTimeoutMs = () => {
34917
+ const raw = process.env.OPENMAUS_OPENAI_COMPAT_IDLE_TIMEOUT_MS;
34918
+ if (!raw) return DEFAULT_IDLE_TIMEOUT_MS;
34919
+ const value = Number(raw);
34920
+ return Number.isSafeInteger(value) && value >= 1e3 && value <= 2147483647 ? value : DEFAULT_IDLE_TIMEOUT_MS;
34921
+ };
34855
34922
  DEFAULT_MODELS = {
34856
34923
  default: "meta-llama/llama-3.3-70b-instruct",
34857
34924
  options: [
@@ -34932,7 +34999,7 @@ var init_openai_compat = __esm({
34932
34999
  httpErrorLabel: "upstream",
34933
35000
  missingKeyError: `no API key \u2014 set ${config2.apiKeyEnv} or add it to the instance config`,
34934
35001
  unavailableReason: `no API key \u2014 set ${config2.apiKeyEnv} or add it to the instance config`,
34935
- timeoutMs: 12e4,
35002
+ timeoutMs: idleTimeoutMs(),
34936
35003
  reasoning: true,
34937
35004
  billing: "metered",
34938
35005
  includeUsageInCompleted: true,
@@ -35176,6 +35243,16 @@ async function fetchPiModels(cli, env) {
35176
35243
  }
35177
35244
  });
35178
35245
  }
35246
+ async function updatePiModelCatalog(cli, env) {
35247
+ return new Promise((resolve13) => {
35248
+ execCli(
35249
+ cli,
35250
+ PI_MODEL_UPDATE_ARGS,
35251
+ { env, timeout: 6e4, maxBuffer: 1024 * 1024 },
35252
+ (error51) => resolve13(!error51)
35253
+ );
35254
+ });
35255
+ }
35179
35256
  function decodeConfig6(raw) {
35180
35257
  if (raw === null || raw === void 0) return { cli: "pi", fullAuto: false };
35181
35258
  if (typeof raw !== "object") throw new Error("pi config must be an object");
@@ -35193,7 +35270,7 @@ function piEnvironment(source) {
35193
35270
  for (const key of PROVIDER_CREDENTIAL_ENV) delete env[key];
35194
35271
  return env;
35195
35272
  }
35196
- var DRIVER_KIND6, PI_ARGS, NODE_ENV_FLAG2, EMPTY3, PiDriver;
35273
+ var DRIVER_KIND6, PI_ARGS, PI_MODEL_UPDATE_ARGS, NODE_ENV_FLAG2, EMPTY3, PiDriver;
35197
35274
  var init_pi = __esm({
35198
35275
  "server/drivers/pi.ts"() {
35199
35276
  "use strict";
@@ -35207,6 +35284,7 @@ var init_pi = __esm({
35207
35284
  init_native();
35208
35285
  DRIVER_KIND6 = "piAgent";
35209
35286
  PI_ARGS = ["--mode", "rpc", "--no-session"];
35287
+ PI_MODEL_UPDATE_ARGS = ["update", "--models", "--no-approve"];
35210
35288
  NODE_ENV_FLAG2 = { ELECTRON_RUN_AS_NODE: "1" };
35211
35289
  EMPTY3 = { default: "", options: [] };
35212
35290
  PiDriver = {
@@ -35228,7 +35306,7 @@ var init_pi = __esm({
35228
35306
  const { instanceId, config: config2 } = input;
35229
35307
  const catalogEnv = piEnvironment({ ...process.env, ...input.environment });
35230
35308
  let models = EMPTY3;
35231
- const refreshModels = async () => {
35309
+ const readModels = async () => {
35232
35310
  let base3 = models;
35233
35311
  try {
35234
35312
  const resolved = await fetchPiModels(config2.cli, catalogEnv);
@@ -35242,7 +35320,11 @@ var init_pi = __esm({
35242
35320
  if (base3.options.length) models = base3;
35243
35321
  }
35244
35322
  };
35245
- await refreshModels();
35323
+ const refreshModels = async () => {
35324
+ await updatePiModelCatalog(config2.cli, catalogEnv);
35325
+ await readModels();
35326
+ };
35327
+ await readModels();
35246
35328
  const listeners = /* @__PURE__ */ new Set();
35247
35329
  const active = /* @__PURE__ */ new Map();
35248
35330
  const emit = (event) => {
@@ -6,6 +6,7 @@
6
6
  */
7
7
  export const READ_ONLY_AGENT_TOOL_NAMES = new Set([
8
8
  "list_bots",
9
+ "list_team_setup",
9
10
  "list_rooms",
10
11
  "list_threads",
11
12
  "check_delegation",