openmausbot 0.1.73 → 0.1.74

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 (33) hide show
  1. package/dist/assets/index-BRLHsQ5g.js +298 -0
  2. package/dist/assets/index-ByLQ7a3F.css +1 -0
  3. package/dist/assets/{index-BYbK7ber.js → index-CwoQdM0z.js} +1 -1
  4. package/dist/index.html +2 -2
  5. package/dist-server/container-mcp.js +9 -0
  6. package/dist-server/index.js +592 -453
  7. package/dist-server/mcp-gate.js +58 -16
  8. package/dist-server/openmausbot.js +186 -64
  9. package/dist-server/pair-cli.js +186 -64
  10. package/dist-server/server/auto-approve.js +30 -206
  11. package/dist-server/server/channel-queue.js +20 -2
  12. package/dist-server/server/config.js +36 -1
  13. package/dist-server/server/drivers/acp/core.js +56 -3
  14. package/dist-server/server/drivers/acp/grok.js +3 -1
  15. package/dist-server/server/drivers/antigravity-acp.js +5 -4
  16. package/dist-server/server/drivers/antigravity.js +9 -4
  17. package/dist-server/server/drivers/claude.js +54 -11
  18. package/dist-server/server/drivers/codex.js +22 -11
  19. package/dist-server/server/engine-install.js +9 -5
  20. package/dist-server/server/index.js +185 -223
  21. package/dist-server/server/message-db.js +70 -3
  22. package/dist-server/server/procs.js +82 -23
  23. package/dist-server/server/routines.js +67 -8
  24. package/dist-server/server/steer-queue.js +27 -7
  25. package/dist-server/server/team-manifest.js +1 -1
  26. package/dist-server/server/webhooks.js +4 -1
  27. package/dist-server/server/workspace-backup.js +24 -9
  28. package/dist-server/shared/approval-mode.js +11 -10
  29. package/dist-server/vps-container-mcp.js +9 -0
  30. package/package.json +1 -1
  31. package/dist/assets/index-BLJ7byG3.js +0 -296
  32. package/dist/assets/index-De2TRLxu.css +0 -1
  33. package/dist-server/server/auto-review.js +0 -79
@@ -17782,7 +17782,7 @@ function saveConfig(patch, options = {}) {
17782
17782
  const checkedPatch = appConfigSchema.partial().parse(patch);
17783
17783
  const storedProfiles = storedBrowserProfilesSchema.safeParse(disk.browserProfiles);
17784
17784
  if (storedProfiles.success) disk.browserProfiles = storedProfiles.data;
17785
- for (const key of ["xai", "anthropic", "openaiCompat", "composio", "box", "opencodeGo", "tts", "imageGen", "profile", "rooms", "threads", "localVm", "features", "budgets", "billing"]) {
17785
+ for (const key of ["xai", "anthropic", "openaiCompat", "composio", "box", "opencodeGo", "tts", "imageGen", "profile", "rooms", "threads", "localVm", "features", "budgets", "billing", "onboarding"]) {
17786
17786
  const section = checkedPatch[key];
17787
17787
  if (!section) continue;
17788
17788
  const current = jsonObjectSchema.safeParse(disk[key]);
@@ -17905,7 +17905,7 @@ function instanceConfigs(cfg) {
17905
17905
  }
17906
17906
  return map3;
17907
17907
  }
17908
- var optionalText, SSH_ALIAS, LEGACY_BROWSER_PROFILE_ID, BROWSER_PROFILE_ID, MIN_ROOM_TURN_TIMEOUT_MINUTES, MAX_ROOM_TURN_TIMEOUT_MINUTES, MAX_CONCURRENT_BOT_THREADS, MIN_LOCAL_VM_MAX_INSTANCES, MAX_LOCAL_VM_MAX_INSTANCES, vpsConfigSchema, roomConfigSchema, localVmConfigSchema, browserProfileSchema, legacyBrowserProfileSchema, legacyBrowserProfilesSchema, storedBrowserProfilesSchema, browserProfilesSchema, featureConfigSchema, instanceConfigSchema, instanceConfigMapSchema, defaultModelSelectionSchema, appConfigSchema, storedAppConfigSchema, appConfigPatchSchema, jsonObjectSchema, DATA_DIR, LEGACY_DATA_DIR, EVENTS_DIR, NATIVE_DIR, WORKSPACE_CREDENTIAL_ENV, PROVIDER_CREDENTIAL_ENV;
17908
+ var optionalText, SSH_ALIAS, LEGACY_BROWSER_PROFILE_ID, BROWSER_PROFILE_ID, MIN_ROOM_TURN_TIMEOUT_MINUTES, MAX_ROOM_TURN_TIMEOUT_MINUTES, MAX_CONCURRENT_BOT_THREADS, MIN_LOCAL_VM_MAX_INSTANCES, MAX_LOCAL_VM_MAX_INSTANCES, vpsConfigSchema, roomConfigSchema, localVmConfigSchema, browserProfileSchema, legacyBrowserProfileSchema, legacyBrowserProfilesSchema, storedBrowserProfilesSchema, browserProfilesSchema, featureConfigSchema, onboardingConfigSchema, instanceConfigSchema, instanceConfigMapSchema, defaultModelSelectionSchema, appConfigSchema, storedAppConfigSchema, appConfigPatchSchema, jsonObjectSchema, DATA_DIR, LEGACY_DATA_DIR, EVENTS_DIR, NATIVE_DIR, WORKSPACE_CREDENTIAL_ENV, PROVIDER_CREDENTIAL_ENV;
17909
17909
  var init_config = __esm({
17910
17910
  "server/config.ts"() {
17911
17911
  "use strict";
@@ -17979,6 +17979,14 @@ var init_config = __esm({
17979
17979
  * also has its own switch. */
17980
17980
  browser: external_exports.boolean().optional()
17981
17981
  });
17982
+ onboardingConfigSchema = external_exports.object({
17983
+ /** ISO timestamp of finishing (or skipping to the end of) the welcome flow. */
17984
+ completedAt: external_exports.string().trim().max(40).optional(),
17985
+ /** Which welcome flow was completed; a newer flow may re-show itself. */
17986
+ version: external_exports.number().int().min(0).max(1e3).optional(),
17987
+ reelSeen: external_exports.boolean().optional(),
17988
+ hintsSeen: external_exports.array(external_exports.string().trim().min(1).max(60)).max(100).optional()
17989
+ }).strict();
17982
17990
  instanceConfigSchema = external_exports.object({
17983
17991
  driver: external_exports.string().min(1),
17984
17992
  displayName: optionalText,
@@ -18072,6 +18080,7 @@ var init_config = __esm({
18072
18080
  threads: external_exports.object({ maxConcurrentPerBot: external_exports.number().int().min(1).max(MAX_CONCURRENT_BOT_THREADS) }).strict().optional(),
18073
18081
  localVm: localVmConfigSchema.optional(),
18074
18082
  features: featureConfigSchema.optional(),
18083
+ onboarding: onboardingConfigSchema.optional(),
18075
18084
  browserProfiles: browserProfilesSchema.optional(),
18076
18085
  instances: instanceConfigMapSchema.optional(),
18077
18086
  /** User-configured MCP servers, mounted into every capable engine. Kept
@@ -18728,6 +18737,7 @@ function spawnCli(cli, args, opts) {
18728
18737
  // win32: taskkill /T does the reaping instead (see killCliTree)
18729
18738
  ...process.platform === "win32" ? { windowsHide: true } : { detached: true }
18730
18739
  });
18740
+ if (process.platform !== "win32") cliGroups.add(child);
18731
18741
  child.stdin?.on("error", () => {
18732
18742
  });
18733
18743
  return child;
@@ -18760,8 +18770,59 @@ function describeSpawnFailure(err, cli) {
18760
18770
  return { message: `spawn failed: ${err.message}`, setup: false };
18761
18771
  }
18762
18772
  function killCliTree(child, timeoutMs = 5e3) {
18773
+ const existing = stopping.get(child);
18774
+ if (existing) return existing;
18763
18775
  const pid = child.pid;
18764
- if (!pid || child.exitCode !== null || child.signalCode !== null) return Promise.resolve(true);
18776
+ if (!pid) return Promise.resolve(true);
18777
+ if (!Number.isInteger(pid) || pid <= 1 || pid === process.pid) return Promise.resolve(false);
18778
+ const run3 = stopCliTree(child, pid, Number.isFinite(timeoutMs) ? Math.max(0, timeoutMs) : 5e3);
18779
+ stopping.set(child, run3);
18780
+ void run3.then((stopped) => {
18781
+ if (!stopped) stopping.delete(child);
18782
+ });
18783
+ return run3;
18784
+ }
18785
+ async function stopCliTree(child, pid, timeoutMs) {
18786
+ const exited = () => child.exitCode !== null || child.signalCode !== null;
18787
+ if (process.platform !== "win32") {
18788
+ const group = cliGroups.has(child);
18789
+ const settled = () => {
18790
+ if (!exited()) return false;
18791
+ if (!group) return true;
18792
+ try {
18793
+ process.kill(-pid, 0);
18794
+ return false;
18795
+ } catch (error51) {
18796
+ return error51.code === "ESRCH";
18797
+ }
18798
+ };
18799
+ const signal = (value) => {
18800
+ try {
18801
+ if (group) process.kill(-pid, value);
18802
+ else child.kill(value);
18803
+ } catch {
18804
+ try {
18805
+ child.kill(value);
18806
+ } catch {
18807
+ }
18808
+ }
18809
+ };
18810
+ const wait2 = async (ms) => {
18811
+ const deadline = Date.now() + ms;
18812
+ while (!settled()) {
18813
+ const remaining = deadline - Date.now();
18814
+ if (remaining <= 0) return false;
18815
+ await new Promise((resolve13) => setTimeout(resolve13, Math.min(25, remaining)));
18816
+ }
18817
+ return true;
18818
+ };
18819
+ if (settled()) return true;
18820
+ signal("SIGTERM");
18821
+ if (await wait2(timeoutMs)) return true;
18822
+ signal("SIGKILL");
18823
+ return wait2(1e3);
18824
+ }
18825
+ if (exited()) return true;
18765
18826
  return new Promise((resolve13) => {
18766
18827
  let timer;
18767
18828
  const done = (stopped) => {
@@ -18773,35 +18834,26 @@ function killCliTree(child, timeoutMs = 5e3) {
18773
18834
  child.once("close", closed);
18774
18835
  timer = setTimeout(() => done(false), timeoutMs);
18775
18836
  timer.unref?.();
18776
- if (process.platform === "win32") {
18777
- execFile3("taskkill", ["/PID", String(pid), "/T", "/F"], { windowsHide: true }, (err) => {
18778
- if (!err) return;
18779
- try {
18780
- child.kill();
18781
- } catch {
18782
- }
18783
- });
18784
- return;
18785
- }
18786
- try {
18787
- process.kill(-pid, "SIGTERM");
18788
- } catch {
18837
+ execFile3("taskkill", ["/PID", String(pid), "/T", "/F"], { windowsHide: true }, (err) => {
18838
+ if (!err) return;
18789
18839
  try {
18790
- child.kill("SIGTERM");
18840
+ child.kill();
18791
18841
  } catch {
18792
18842
  }
18793
- }
18843
+ });
18794
18844
  });
18795
18845
  }
18796
18846
  function brokerSocketPath(dataDir, tag) {
18797
18847
  return process.platform === "win32" ? `\\\\.\\pipe\\openmausbot-perm-${process.pid}-${tag}` : join10(dataDir, `perm-${tag}.sock`);
18798
18848
  }
18799
- var WINDOWS_SAFE_COMMAND_LINE_CHARS;
18849
+ var WINDOWS_SAFE_COMMAND_LINE_CHARS, cliGroups, stopping;
18800
18850
  var init_procs = __esm({
18801
18851
  "server/procs.ts"() {
18802
18852
  "use strict";
18803
18853
  init_env_path();
18804
18854
  WINDOWS_SAFE_COMMAND_LINE_CHARS = 3e4;
18855
+ cliGroups = /* @__PURE__ */ new WeakSet();
18856
+ stopping = /* @__PURE__ */ new WeakMap();
18805
18857
  }
18806
18858
  });
18807
18859
 
@@ -18888,19 +18940,19 @@ function runNpm(args, env, cwd, timeoutMs) {
18888
18940
  let timedOut = false;
18889
18941
  const timer = setTimeout(() => {
18890
18942
  timedOut = true;
18891
- void killCliTree(child);
18943
+ void killCliTree(child).then((stopped) => {
18944
+ rejectRun(new Error(stopped ? "The install took too long and was stopped. Check the server's network connection and try again." : "The install took too long, but npm could not be confirmed stopped. Ask the server administrator to stop the install process before trying again."));
18945
+ });
18892
18946
  }, timeoutMs);
18893
18947
  timer.unref();
18894
18948
  child.once("error", (error51) => {
18949
+ if (timedOut) return;
18895
18950
  clearTimeout(timer);
18896
18951
  rejectRun(new Error(error51.code === "ENOENT" ? "npm is not installed on this server. Install Node.js with npm for the user running OpenMausBot, then try again." : "npm could not start on this server. Check that Node.js is installed for the user running OpenMausBot."));
18897
18952
  });
18898
18953
  child.once("close", (code) => {
18899
18954
  clearTimeout(timer);
18900
- if (timedOut) {
18901
- rejectRun(new Error("The install took too long and was stopped. Check the server's network connection and try again."));
18902
- return;
18903
- }
18955
+ if (timedOut) return;
18904
18956
  resolveRun({ code, output });
18905
18957
  });
18906
18958
  });
@@ -19515,6 +19567,7 @@ var init_container_computer = __esm({
19515
19567
  // shared/approval-mode.ts
19516
19568
  function supportsApprovalMode(driverKind, mode) {
19517
19569
  if (mode === "custom") return driverKind === "codex";
19570
+ if (mode === "edits") return ["claudeAgent", "grokAgent", "antigravityAgent"].includes(driverKind ?? "");
19518
19571
  if (mode !== "full") return true;
19519
19572
  return ["codex", "claudeAgent", "antigravityAgent", "cursorAgent", "grokAgent", "opencodeGo"].includes(driverKind ?? "");
19520
19573
  }
@@ -19712,6 +19765,20 @@ function decodeAcpConfig(defaultCli) {
19712
19765
  };
19713
19766
  };
19714
19767
  }
19768
+ function sessionOperationKey(toolCall) {
19769
+ const rawInput = toolCall?.rawInput;
19770
+ const command = typeof rawInput?.command === "string" ? rawInput.command : void 0;
19771
+ const hasInput = rawInput && typeof rawInput === "object" && Object.keys(rawInput).length > 0;
19772
+ if (!command && !hasInput) return null;
19773
+ const stable = (value) => Array.isArray(value) ? value.map(stable) : value && typeof value === "object" ? Object.fromEntries(Object.keys(value).sort().map((key) => [key, stable(value[key])])) : value;
19774
+ return JSON.stringify(stable({
19775
+ kind: toolCall?.kind,
19776
+ title: toolCall?.title,
19777
+ command,
19778
+ input: rawInput,
19779
+ locations: toolCall?.locations
19780
+ }));
19781
+ }
19715
19782
  function createAcpDriver(support11) {
19716
19783
  const DRIVER_KIND8 = support11.driverKind;
19717
19784
  const SOURCE = support11.nativeSource;
@@ -19755,6 +19822,7 @@ function createAcpDriver(support11) {
19755
19822
  if (support11.resolveModelsOnCreate !== false) await refreshModels();
19756
19823
  const listeners = /* @__PURE__ */ new Set();
19757
19824
  const active = /* @__PURE__ */ new Map();
19825
+ const sessionAllows = /* @__PURE__ */ new Map();
19758
19826
  const emit = (event) => {
19759
19827
  for (const listener of listeners) listener(event);
19760
19828
  };
@@ -20004,6 +20072,7 @@ function createAcpDriver(support11) {
20004
20072
  flushAssistantText();
20005
20073
  const options = Array.isArray(params.options) ? params.options : [];
20006
20074
  const optionFor = (want) => options.find((o) => o.kind === `${want}_once` && typeof o.optionId === "string")?.optionId ?? options.find((o) => String(o.kind ?? "").startsWith(want) && typeof o.optionId === "string")?.optionId ?? null;
20075
+ const optionAlways = options.find((o) => o.kind === "allow_always" && typeof o.optionId === "string")?.optionId ?? null;
20007
20076
  const cancelled = { outcome: { outcome: "cancelled" } };
20008
20077
  const missing = (want) => emit({
20009
20078
  ...base2(threadId, turnId),
@@ -20022,15 +20091,28 @@ function createAcpDriver(support11) {
20022
20091
  });
20023
20092
  }
20024
20093
  const kind = String(toolCall.kind ?? "");
20094
+ const operationKey = isQuestion || controlsHost ? null : sessionOperationKey(toolCall);
20095
+ if (operationKey && sessionAllows.get(threadId)?.has(operationKey)) {
20096
+ const allow = optionFor("allow");
20097
+ if (allow) {
20098
+ return send({ jsonrpc: "2.0", id: msg.id, result: { outcome: { outcome: "selected", optionId: allow } } });
20099
+ }
20100
+ }
20025
20101
  const tool = kind === "execute" ? "shell" : kind === "edit" ? "edit" : kind || "tool";
20026
20102
  const summary = String(toolCall.rawInput?.command ?? toolCall.title ?? tool).slice(0, 200);
20027
20103
  const requestId = newId();
20028
- const finish = (behavior, source = "user", message2) => {
20104
+ const finish = (behavior, source = "user", message2, always) => {
20029
20105
  if (!asks.delete(requestId)) return;
20030
20106
  clearTimeout(timer);
20031
20107
  const want = behavior === "allow" ? "allow" : "reject";
20108
+ const forSession = want === "allow" && always === true && !isQuestion && !controlsHost;
20032
20109
  const named = isQuestion && behavior === "answer" ? options.filter((option) => option.optionId === message2 || option.name?.trim() === message2) : [];
20033
- const optionId = behavior === "cancel" ? null : isQuestion ? named.length === 1 && typeof named[0].optionId === "string" ? named[0].optionId : null : optionFor(want);
20110
+ const optionId = behavior === "cancel" ? null : isQuestion ? named.length === 1 && typeof named[0].optionId === "string" ? named[0].optionId : null : forSession ? optionAlways ?? optionFor("allow") : optionFor(want);
20111
+ if (forSession && !optionAlways && optionId && operationKey) {
20112
+ const remembered = sessionAllows.get(threadId) ?? /* @__PURE__ */ new Set();
20113
+ remembered.add(operationKey);
20114
+ sessionAllows.set(threadId, remembered);
20115
+ }
20034
20116
  if (behavior !== "cancel" && !optionId) missing(isQuestion ? "matching answer" : want);
20035
20117
  send({
20036
20118
  jsonrpc: "2.0",
@@ -20060,7 +20142,9 @@ function createAcpDriver(support11) {
20060
20142
  tool,
20061
20143
  summary,
20062
20144
  choices: isQuestion ? options.flatMap((option) => typeof option.name === "string" && option.name.trim() ? [option.name.trim()] : []) : void 0,
20063
- approvalScope: controlsHost ? "local-computer" : void 0
20145
+ approvalScope: controlsHost ? "local-computer" : void 0,
20146
+ // the driver can honor a session-wide allow either way
20147
+ allowSession: !isQuestion && !controlsHost ? true : void 0
20064
20148
  });
20065
20149
  };
20066
20150
  const handleNotification = (msg) => {
@@ -20222,6 +20306,7 @@ function createAcpDriver(support11) {
20222
20306
  );
20223
20307
  }
20224
20308
  const cursor3 = typeof turn.resumeCursor === "string" ? turn.resumeCursor : null;
20309
+ if (!cursor3) sessionAllows.delete(threadId);
20225
20310
  let sessionResult = null;
20226
20311
  if (cursor3) {
20227
20312
  try {
@@ -20380,7 +20465,7 @@ ${turn.text}` : turn.text;
20380
20465
  const turn = active.get(threadId);
20381
20466
  const finish = turn?.asks.get(requestId);
20382
20467
  if (!finish) return "unavailable";
20383
- finish(decision.behavior, "user", decision.message);
20468
+ finish(decision.behavior, "user", decision.message, decision.always === true);
20384
20469
  return decision.behavior === "allow" ? "allowed-once" : decision.behavior === "answer" ? "answered" : "rejected";
20385
20470
  },
20386
20471
  hasSession: (threadId) => active.has(threadId),
@@ -20715,6 +20800,7 @@ var init_antigravity_acp = __esm({
20715
20800
  startupDiagnosticBytes = 0;
20716
20801
  nativeStartupHint;
20717
20802
  closed = false;
20803
+ stopping;
20718
20804
  onAuthorizationUrl;
20719
20805
  /** Settles once the runtime process is gone. On Windows a running
20720
20806
  * executable pins its file and its directory, so an installer must not
@@ -20839,10 +20925,10 @@ var init_antigravity_acp = __esm({
20839
20925
  if (this.closed) return;
20840
20926
  this.closed = true;
20841
20927
  this.failAll(new Error("Antigravity ACP was closed."));
20842
- killCliTree(this.child);
20928
+ this.stopping = killCliTree(this.child);
20843
20929
  }
20844
- /** Close, then wait (bounded) for the process to be gone. */
20845
- async closeAndWait(timeoutMs = 5e3) {
20930
+ /** Allow the shared 5s TERM grace and 1s force-stop verification to finish. */
20931
+ async closeAndWait(timeoutMs = 7e3) {
20846
20932
  this.close();
20847
20933
  let timer;
20848
20934
  const timedOut = new Promise((resolve13) => {
@@ -20850,7 +20936,7 @@ var init_antigravity_acp = __esm({
20850
20936
  timer.unref?.();
20851
20937
  });
20852
20938
  try {
20853
- return await Promise.race([this.exited.then(() => true), timedOut]);
20939
+ return await Promise.race([this.stopping, timedOut]);
20854
20940
  } finally {
20855
20941
  clearTimeout(timer);
20856
20942
  }
@@ -21373,8 +21459,9 @@ var init_antigravity_runtime = __esm({
21373
21459
  });
21374
21460
 
21375
21461
  // server/drivers/antigravity.ts
21376
- function antigravityPermissionMode(fullAuto) {
21377
- return fullAuto ? "yolo" : "default";
21462
+ function antigravityPermissionMode(fullAuto, approvalMode) {
21463
+ if (fullAuto) return "yolo";
21464
+ return approvalMode === "edits" ? "auto_edit" : "default";
21378
21465
  }
21379
21466
  var STATIC_ANTIGRAVITY_MODELS, managedAsset, support, AcpAntigravityDriver, AntigravityDriver;
21380
21467
  var init_antigravity = __esm({
@@ -21458,8 +21545,8 @@ var init_antigravity = __esm({
21458
21545
  return { state: "unavailable", reason: error51 instanceof Error ? error51.message : String(error51) };
21459
21546
  }
21460
21547
  },
21461
- configureSession: async ({ request, sessionId, config: config2 }) => {
21462
- const wanted = antigravityPermissionMode(config2.fullAuto);
21548
+ configureSession: async ({ request, sessionId, config: config2, turn }) => {
21549
+ const wanted = antigravityPermissionMode(config2.fullAuto, turn.approvalMode);
21463
21550
  const result = await request("session/set_config_option", {
21464
21551
  sessionId,
21465
21552
  configId: "mode",
@@ -22505,11 +22592,11 @@ async function createPermissionBroker(opts) {
22505
22592
  continue;
22506
22593
  }
22507
22594
  const ask = { id: askId, kind, tool: msg.tool ?? "tool", input: msg.input ?? {}, at: Date.now() };
22508
- const finish = (behavior, message2, source) => {
22595
+ const finish = (behavior, message2, source, always) => {
22509
22596
  if (!pending.delete(askId)) return;
22510
22597
  clearTimeout(timer);
22511
22598
  try {
22512
- conn.write(JSON.stringify({ t: "answer", id: askId, behavior, message: message2 }) + "\n");
22599
+ conn.write(JSON.stringify({ t: "answer", id: askId, behavior, message: message2, ...always ? { always: true } : {} }) + "\n");
22513
22600
  } catch {
22514
22601
  }
22515
22602
  opts.onResolve({ ...ask, behavior, source });
@@ -22579,11 +22666,11 @@ async function createPermissionBroker(opts) {
22579
22666
  }
22580
22667
  };
22581
22668
  return {
22582
- answer(askId, behavior, message2) {
22669
+ answer(askId, behavior, message2, always) {
22583
22670
  const p = pending.get(askId);
22584
22671
  if (!p) return false;
22585
22672
  if (p.ask.kind === "question" ? behavior !== "answer" : behavior === "answer") return false;
22586
- p.finish(behavior, message2, "user");
22673
+ p.finish(behavior, message2, "user", always && behavior === "allow");
22587
22674
  return true;
22588
22675
  },
22589
22676
  pause() {
@@ -22785,6 +22872,11 @@ var init_claude = __esm({
22785
22872
  const configuredIdleMinimum = Number(process.env.OMB_CLAUDE_SESSION_IDLE_MIN_MS);
22786
22873
  const sessionIdleMinimum = Number.isFinite(configuredIdleMinimum) && configuredIdleMinimum > 0 ? configuredIdleMinimum : 1e4;
22787
22874
  const SESSION_IDLE_MS = Math.max(sessionIdleMinimum, Number(process.env.OMB_CLAUDE_SESSION_IDLE_MS) || 10 * 6e4);
22875
+ const stopSession = (session) => {
22876
+ void killCliTree(session.child).then((stopped) => {
22877
+ if (stopped) void session.finishClose?.();
22878
+ });
22879
+ };
22788
22880
  const closeSession = (threadId, why) => {
22789
22881
  const s = sessions.get(threadId);
22790
22882
  if (!s || s.closing) return;
@@ -22799,7 +22891,7 @@ var init_claude = __esm({
22799
22891
  } catch {
22800
22892
  }
22801
22893
  const kill = setTimeout(() => {
22802
- if (s.child.exitCode === null) killCliTree(s.child);
22894
+ stopSession(s);
22803
22895
  }, 5e3);
22804
22896
  kill.unref?.();
22805
22897
  };
@@ -22842,7 +22934,7 @@ var init_claude = __esm({
22842
22934
  const sendTurn = async (turn) => {
22843
22935
  const { threadId } = turn;
22844
22936
  if (active.has(threadId)) throw new Error("a turn is already running on this thread");
22845
- const permissionMode = turn.approvalMode === void 0 ? config2.permissionMode : turn.approvalMode === "full" ? "bypassPermissions" : turn.approvalMode === "auto" ? "auto" : "default";
22937
+ const permissionMode = turn.approvalMode === void 0 ? config2.permissionMode : turn.approvalMode === "full" ? "bypassPermissions" : turn.approvalMode === "auto" ? "auto" : turn.approvalMode === "edits" ? "acceptEdits" : "default";
22846
22938
  const controlsHost = turn.integrations?.localComputer?.scope === "local-computer";
22847
22939
  if (controlsHost && permissionMode === "bypassPermissions" && turn.approvalMode !== "full") {
22848
22940
  throw new Error("local computer control requires the interactive approval broker");
@@ -22987,7 +23079,7 @@ var init_claude = __esm({
22987
23079
  live.turn = { turnId, settled: false, sawStreamDelta: false };
22988
23080
  active.set(threadId, { stop: () => {
22989
23081
  closeSession(threadId, "interrupted");
22990
- killCliTree(live.child);
23082
+ stopSession(live);
22991
23083
  }, turnId, broker: live.broker });
22992
23084
  emit({ ...base2(threadId, turnId), type: "turn.started" });
22993
23085
  const volatile = turn.systemVolatile ?? "";
@@ -23046,6 +23138,8 @@ var init_claude = __esm({
23046
23138
  onAsk: (ask) => {
23047
23139
  const eventTurnId = sessions.get(threadId)?.turn?.turnId ?? turnId;
23048
23140
  askTools.set(ask.id, typeof ask.tool === "string" ? ask.tool : void 0);
23141
+ const nativeMode = sessions.get(threadId)?.nativePermissionMode ?? null;
23142
+ const nativeReview = permissionMode === "auto" && nativeMode !== null ? nativeMode === "auto" ? "active" : "inactive" : void 0;
23049
23143
  emit({
23050
23144
  ...base2(threadId, eventTurnId),
23051
23145
  type: "request.opened",
@@ -23053,6 +23147,10 @@ var init_claude = __esm({
23053
23147
  requestType: ask.kind,
23054
23148
  tool: ask.tool,
23055
23149
  summary: askSummary(ask),
23150
+ nativeReview,
23151
+ // the proxy hands Claude its own suggested rules on `always`;
23152
+ // host control stays one action at a time
23153
+ allowSession: ask.kind === "permission" && !(controlsHost && typeof ask.tool === "string" && ask.tool.startsWith("mcp__computer")) ? true : void 0,
23056
23154
  approvalScope: typeof ask.tool === "string" && controlsHost && ask.tool.startsWith("mcp__computer") ? "local-computer" : void 0,
23057
23155
  choices: Array.isArray(ask.input?.choices) ? ask.input.choices.slice(0, 5) : void 0
23058
23156
  });
@@ -23103,6 +23201,7 @@ var init_claude = __esm({
23103
23201
  volatile: turn.systemVolatile ?? "",
23104
23202
  sessionId: sessionId ?? newSessionId,
23105
23203
  sawInit: false,
23204
+ nativePermissionMode: null,
23106
23205
  turn: { turnId, settled: false, sawStreamDelta: false },
23107
23206
  idleTimer: null,
23108
23207
  closing: false,
@@ -23132,6 +23231,7 @@ var init_claude = __esm({
23132
23231
  };
23133
23232
  const currentTurnId = () => session.turn?.turnId ?? turnId;
23134
23233
  const handleLine = (line) => {
23234
+ if (session.closing) return;
23135
23235
  let o;
23136
23236
  try {
23137
23237
  o = JSON.parse(line);
@@ -23143,6 +23243,7 @@ var init_claude = __esm({
23143
23243
  case "system":
23144
23244
  if (o.subtype === "init") {
23145
23245
  session.sawInit = true;
23246
+ session.nativePermissionMode = typeof o.permissionMode === "string" ? o.permissionMode : null;
23146
23247
  if (typeof o.session_id === "string") session.sessionId = o.session_id;
23147
23248
  emit({ ...base2(threadId, currentTurnId()), type: "session.started", sessionId: o.session_id, model: o.model });
23148
23249
  } else if (o.subtype === "thinking_tokens") {
@@ -23241,7 +23342,17 @@ var init_claude = __esm({
23241
23342
  emit({ ...base2(threadId, currentTurnId()), type: "runtime.error", ...describeSpawnFailure(e, config2.cli) });
23242
23343
  settle(false, "spawn_error");
23243
23344
  });
23244
- child.on("close", (code) => {
23345
+ let closeFinalized = false;
23346
+ const finalizeClose = async (code) => {
23347
+ if (closeFinalized) return;
23348
+ if (!await killCliTree(child, 0)) {
23349
+ session.broker?.close();
23350
+ session.broker = void 0;
23351
+ emit({ ...base2(threadId, currentTurnId()), type: "runtime.error", message: "Claude could not be confirmed stopped; its helper processes may still be running." });
23352
+ return;
23353
+ }
23354
+ if (closeFinalized) return;
23355
+ closeFinalized = true;
23245
23356
  if (session.turn && !session.turn.settled) {
23246
23357
  const message2 = `claude exited ${code} before result${session.stderr ? `: ${session.stderr.trim().slice(-300)}` : ""}`;
23247
23358
  const verdict = classifyError({ exitCode: code, stderr: message2 });
@@ -23377,12 +23488,16 @@ var init_claude = __esm({
23377
23488
  }
23378
23489
  removePrivateTempDir(session.systemPromptPath);
23379
23490
  if (sessions.get(threadId) === session) sessions.delete(threadId);
23491
+ };
23492
+ child.on("close", (code) => {
23493
+ session.finishClose = () => finalizeClose(code);
23494
+ void session.finishClose();
23380
23495
  });
23381
23496
  const stop = () => {
23382
23497
  closeSession(threadId, "interrupted");
23383
23498
  retry.cancelled = true;
23384
23499
  retryAbort.abort();
23385
- killCliTree(child);
23500
+ stopSession(session);
23386
23501
  };
23387
23502
  active.set(threadId, { stop, turnId, broker });
23388
23503
  emit({ ...base2(threadId, turnId), type: "turn.started" });
@@ -23508,7 +23623,7 @@ var init_claude = __esm({
23508
23623
  const broker = sessions.get(threadId)?.broker ?? active.get(threadId)?.broker;
23509
23624
  if (!broker) return "unavailable";
23510
23625
  const behavior = decision.behavior === "answer" ? "answer" : decision.behavior;
23511
- if (!broker.answer(requestId, behavior, decision.message)) return "unavailable";
23626
+ if (!broker.answer(requestId, behavior, decision.message, decision.always)) return "unavailable";
23512
23627
  return behavior === "allow" ? "allowed-once" : behavior === "answer" ? "answered" : "rejected";
23513
23628
  },
23514
23629
  hasSession: (threadId) => active.has(threadId),
@@ -24820,11 +24935,17 @@ var init_codex = __esm({
24820
24935
  });
24821
24936
  send({ jsonrpc: "2.0", id, method, params });
24822
24937
  });
24823
- let stopping;
24824
- const terminate = () => stopping ??= killCliTree(child);
24825
- const stop = () => {
24938
+ let stopping2;
24939
+ const terminate = () => stopping2 ??= killCliTree(child).then((stopped) => {
24940
+ if (!stopped) stopping2 = void 0;
24941
+ return stopped;
24942
+ });
24943
+ let completeStoppedTurn;
24944
+ const stop = async () => {
24826
24945
  stopRequested = true;
24827
- return terminate();
24946
+ const stopped = await terminate();
24947
+ if (stopped) completeStoppedTurn?.();
24948
+ return stopped;
24828
24949
  };
24829
24950
  const settle = async (ok, stopReason) => {
24830
24951
  if (state.settled) return;
@@ -24837,12 +24958,9 @@ var init_codex = __esm({
24837
24958
  active.delete(threadId);
24838
24959
  emit({ ...base2(threadId, turnId), type: "turn.completed", ok, stopReason, cost: null, ...state.usage ? { usage: state.usage } : {} });
24839
24960
  };
24840
- if (await stop()) {
24841
- complete();
24842
- } else {
24961
+ completeStoppedTurn = complete;
24962
+ if (!await stop()) {
24843
24963
  emit({ ...base2(threadId, turnId), type: "runtime.error", message: "codex did not shut down after termination was requested" });
24844
- if (child.exitCode !== null || child.signalCode !== null) complete();
24845
- else child.once("close", complete);
24846
24964
  }
24847
24965
  };
24848
24966
  const controlsHost = turn.integrations?.localComputer?.scope === "local-computer";
@@ -25081,6 +25199,10 @@ var init_codex = __esm({
25081
25199
  });
25082
25200
  child.on("close", (code) => {
25083
25201
  if (abandoned) return;
25202
+ if (state.settled) {
25203
+ void stop();
25204
+ return;
25205
+ }
25084
25206
  if (!state.settled) {
25085
25207
  emit({
25086
25208
  ...base2(threadId, turnId),
@@ -25808,7 +25930,7 @@ var init_grok2 = __esm({
25808
25930
  // crates/codegen/xai-grok-pager-bin/src/main.rs:1259-1273.
25809
25931
  spawnArgs: (config2, turn) => [
25810
25932
  "--permission-mode",
25811
- config2.fullAuto ? "bypassPermissions" : turn.approvalMode === "auto" ? "auto" : "default",
25933
+ config2.fullAuto ? "bypassPermissions" : turn.approvalMode === "auto" ? "auto" : turn.approvalMode === "edits" ? "acceptEdits" : "default",
25812
25934
  "agent",
25813
25935
  ...turn.model ? ["-m", turn.model] : [],
25814
25936
  // long form on purpose: `--effort` is documented as an alias, and an
@@ -40317,9 +40439,9 @@ async function runServe(options, log = console.log) {
40317
40439
  });
40318
40440
  let tunnel = null;
40319
40441
  let caddy = null;
40320
- let stopping = null;
40442
+ let stopping2 = null;
40321
40443
  const stop = () => {
40322
- stopping ??= (async () => {
40444
+ stopping2 ??= (async () => {
40323
40445
  if (tunnel) await tunnel.stop().catch(() => void 0);
40324
40446
  if (caddy) await caddy.stop().catch(() => void 0);
40325
40447
  if (tailscaleServing && tailscale) await tailscaleServeOff(tailscale).catch(() => void 0);
@@ -40332,7 +40454,7 @@ async function runServe(options, log = console.log) {
40332
40454
  }
40333
40455
  if (plan) cleanupTunnelOrigin(plan.origin);
40334
40456
  })();
40335
- return stopping;
40457
+ return stopping2;
40336
40458
  };
40337
40459
  const onSignal = () => {
40338
40460
  void stop();
@@ -40341,7 +40463,7 @@ async function runServe(options, log = console.log) {
40341
40463
  process.on("SIGTERM", onSignal);
40342
40464
  try {
40343
40465
  const deadline = Date.now() + 6e4;
40344
- while (Date.now() < deadline && exited === null && !stopping) {
40466
+ while (Date.now() < deadline && exited === null && !stopping2) {
40345
40467
  if (await serverUp(options.port, child.pid)) break;
40346
40468
  await new Promise((r2) => setTimeout(r2, 250));
40347
40469
  }
@@ -40349,19 +40471,19 @@ async function runServe(options, log = console.log) {
40349
40471
  if (exited !== 0) log(`OpenMausBot could not start.${logPath ? ` Details: ${logPath}` : " See the output above."}`);
40350
40472
  return exited;
40351
40473
  }
40352
- if (stopping) return await childExit;
40474
+ if (stopping2) return await childExit;
40353
40475
  if (!await serverUp(options.port, child.pid)) {
40354
40476
  console.error(`OpenMausBot did not become ready within a minute.${logPath ? ` Details: ${logPath}` : " See its output above."}`);
40355
40477
  await stop();
40356
40478
  return 1;
40357
40479
  }
40358
- if (stopping || exited !== null) return await childExit;
40480
+ if (stopping2 || exited !== null) return await childExit;
40359
40481
  if (options.domain && caddyBinary) {
40360
40482
  try {
40361
40483
  caddy = await startCaddy({ binary: caddyBinary, dataDir: options.dataDir, domain: options.domain, appPort: options.port, webhookPort: Number(env.OMB_WEBHOOK_PORT), log });
40362
40484
  log(`https: Caddy serves ${publicUrl} \u2192 http://127.0.0.1:${options.port}; it gets the certificate from Let's Encrypt once DNS for ${options.domain} points at this machine`);
40363
40485
  void caddy.exited.then((code) => {
40364
- if (!stopping) log(`caddy: stopped (exit ${code ?? "signal"}); ${publicUrl} is no longer served. Stop and start the server again.`);
40486
+ if (!stopping2) log(`caddy: stopped (exit ${code ?? "signal"}); ${publicUrl} is no longer served. Stop and start the server again.`);
40365
40487
  });
40366
40488
  } catch (error51) {
40367
40489
  console.error(`--domain: ${message(error51)}`);
@@ -40404,7 +40526,7 @@ async function runServe(options, log = console.log) {
40404
40526
  ]);
40405
40527
  if (timer) clearTimeout(timer);
40406
40528
  }
40407
- if (!stopping && exited === null) await showPhonePairing(options, publicUrl, log);
40529
+ if (!stopping2 && exited === null) await showPhonePairing(options, publicUrl, log);
40408
40530
  } else if (options.pair && !options.guided) {
40409
40531
  log("");
40410
40532
  log(await mintPairing(options.port, { label: options.label ? `${options.label} owner` : void 0, client: options.client, publicUrl: publicUrl ?? void 0 }));