openmausbot 0.1.82 → 0.1.83

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 (47) hide show
  1. package/dist/assets/{index-BLLsDK2F.js → index-DSvqfBOf.js} +71 -71
  2. package/dist/assets/{index-5HVwp5m2.js → index-nqTFZTxg.js} +1 -1
  3. package/dist/index.html +1 -1
  4. package/dist-server/container-mcp.js +20 -4
  5. package/dist-server/drivers/agents-proxy.js +4 -4
  6. package/dist-server/index.js +1063 -493
  7. package/dist-server/mcp-gate.js +2 -1
  8. package/dist-server/openmausbot.js +187 -55
  9. package/dist-server/pair-cli.js +187 -55
  10. package/dist-server/server/auto-approve.js +15 -1
  11. package/dist-server/server/auto-vm-claims.js +22 -0
  12. package/dist-server/server/browser-bundle-release.js +8 -7
  13. package/dist-server/server/browser-engine.js +1 -1
  14. package/dist-server/server/channel-queue.js +58 -0
  15. package/dist-server/server/chief-of-staff.js +2 -2
  16. package/dist-server/server/config.js +33 -2
  17. package/dist-server/server/contracts.js +0 -7
  18. package/dist-server/server/drivers/acp/gemini.js +11 -1
  19. package/dist-server/server/drivers/acp/qwen.js +16 -1
  20. package/dist-server/server/drivers/agents-proxy.js +4 -4
  21. package/dist-server/server/drivers/claude.js +4 -3
  22. package/dist-server/server/drivers/codex.js +66 -1
  23. package/dist-server/server/drivers/pi.js +2 -1
  24. package/dist-server/server/env-path.js +6 -1
  25. package/dist-server/server/index.js +534 -84
  26. package/dist-server/server/local-routing.js +10 -4
  27. package/dist-server/server/peer-roster.js +37 -1
  28. package/dist-server/server/recent-work.js +4 -1
  29. package/dist-server/server/redact.js +5 -48
  30. package/dist-server/server/room-handoffs.js +79 -16
  31. package/dist-server/server/skill-fetch.js +1 -1
  32. package/dist-server/server/steer-queue.js +29 -0
  33. package/dist-server/server/store.js +26 -3
  34. package/dist-server/server/thread-retention.js +50 -0
  35. package/dist-server/server/turn-context.js +7 -0
  36. package/dist-server/server/turn-resources.js +6 -0
  37. package/dist-server/shared/approval-mode.js +8 -5
  38. package/dist-server/shared/inspector.js +1 -0
  39. package/dist-server/shared/json.js +1 -0
  40. package/dist-server/shared/notification.js +1 -0
  41. package/dist-server/shared/redact.js +61 -0
  42. package/dist-server/shared/routines.js +1 -0
  43. package/dist-server/shared/runtime-events.js +1 -0
  44. package/dist-server/shared/webhooks.js +1 -0
  45. package/dist-server/shared/wire.js +8 -0
  46. package/dist-server/vps-container-mcp.js +20 -4
  47. package/package.json +1 -1
@@ -7731,10 +7731,10 @@ var require_dist = __commonJS({
7731
7731
 
7732
7732
  // server/index.ts
7733
7733
  import { createHash as createHash31, randomBytes as randomBytes9, randomUUID as randomUUID28, timingSafeEqual as timingSafeEqual5 } from "node:crypto";
7734
- import { existsSync as existsSync32, readFileSync as readFileSync45, rmSync as rmSync9, unlinkSync as unlinkSync14 } from "node:fs";
7734
+ import { existsSync as existsSync32, readFileSync as readFileSync45, rmSync as rmSync9 } from "node:fs";
7735
7735
  import { rm as removeDirectory } from "node:fs/promises";
7736
7736
  import { createServer as createServer3 } from "node:http";
7737
- import { extname as extname5, join as join64 } from "node:path";
7737
+ import { extname as extname5, join as join65 } from "node:path";
7738
7738
 
7739
7739
  // node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
7740
7740
  var external_exports = {};
@@ -22479,7 +22479,7 @@ var nodeSchema = external_exports.object({
22479
22479
  approvalGranted: external_exports.boolean().default(false),
22480
22480
  kind: external_exports.enum(["work", "assignment"]).default("work")
22481
22481
  });
22482
- var ROOM_HANDOFF_LIMITS = { depth: 4, requests: 24, executions: 48, lifetimeMs: 30 * 6e4, minRunwayMs: 10 * 6e4, queueMs: 60 * 6e4 };
22482
+ var ROOM_HANDOFF_LIMITS = { depth: 4, requests: 24, executions: 48, lifetimeMs: 30 * 6e4, minRunwayMs: 10 * 6e4, queueMs: 60 * 6e4, hardCapMs: 4 * 60 * 6e4 };
22483
22483
  var duration3 = (ms2) => ms2 >= 6e4 ? `${Math.floor(ms2 / 6e4)}m` : `${Math.floor(ms2 / 1e3)}s`;
22484
22484
  var terminal = (n) => ["completed", "failed", "cancelled"].includes(n.status);
22485
22485
  var RoomHandoffs = class {
@@ -22489,10 +22489,14 @@ var RoomHandoffs = class {
22489
22489
  file;
22490
22490
  hooks;
22491
22491
  now;
22492
- constructor(file2, hooks, now = Date.now) {
22492
+ limits;
22493
+ /** Per-root pause accounting for the tree lifetime clock. */
22494
+ pauses = /* @__PURE__ */ new Map();
22495
+ constructor(file2, hooks, now = Date.now, limits = {}) {
22493
22496
  this.file = file2;
22494
22497
  this.hooks = hooks;
22495
22498
  this.now = now;
22499
+ this.limits = { ...ROOM_HANDOFF_LIMITS, ...limits };
22496
22500
  try {
22497
22501
  const saved = external_exports.array(nodeSchema).max(1e4).parse(JSON.parse(readFileSync(file2, "utf8")));
22498
22502
  const ids = new Map(saved.map((n) => [n.id, n]));
@@ -22533,13 +22537,49 @@ var RoomHandoffs = class {
22533
22537
  }
22534
22538
  return path;
22535
22539
  }
22540
+ /** The tree lifetime clock pauses while any node in the tree is actively
22541
+ * executing: the budget bounds coordination sprawl, not the runtime of
22542
+ * dispatched work. Accounting is in-memory; a restart fails every
22543
+ * interrupted node anyway, so no pause span survives one. */
22544
+ trackExecutionPauses() {
22545
+ const now = this.now();
22546
+ const executing = /* @__PURE__ */ new Set();
22547
+ for (const n of this.nodes.values()) {
22548
+ if (!terminal(n) && n.status === "running") executing.add(n.rootId);
22549
+ }
22550
+ for (const rootId of executing) {
22551
+ const pause = this.pauses.get(rootId) ?? { accumulatedMs: 0 };
22552
+ pause.since ??= now;
22553
+ this.pauses.set(rootId, pause);
22554
+ }
22555
+ for (const [rootId, pause] of this.pauses) {
22556
+ if (pause.since != null && !executing.has(rootId)) {
22557
+ pause.accumulatedMs += now - pause.since;
22558
+ pause.since = void 0;
22559
+ }
22560
+ const root = this.nodes.get(rootId);
22561
+ if (!root || terminal(root)) this.pauses.delete(rootId);
22562
+ }
22563
+ }
22564
+ pausedMs(root) {
22565
+ const pause = this.pauses.get(root.id);
22566
+ if (!pause) return 0;
22567
+ return pause.accumulatedMs + (pause.since != null ? this.now() - pause.since : 0);
22568
+ }
22569
+ /** The lifetime budget consumed so far: wall clock minus paused time. */
22570
+ effectiveAgeMs(root) {
22571
+ return Math.max(0, this.now() - root.createdAt - this.pausedMs(root));
22572
+ }
22536
22573
  /** The earliest moment this node may be failed for lifetime: the tree
22537
22574
  * ceiling, a running node's own start plus a minimum runway, or, for work
22538
- * parked in a busy teammate's queue, its own queue window (#1238). */
22575
+ * parked in a busy teammate's queue, its own queue window (#1238). The
22576
+ * wall-clock hard cap clamps every extension: a tree that never stops
22577
+ * executing still dies, so runway extensions cannot compound forever. */
22539
22578
  deadline(n) {
22540
22579
  const anchor = n.status === "running" ? n.startedAt ?? n.createdAt : n.createdAt;
22541
- const ceiling = n.status === "queued" && n.executions === 0 ? anchor + ROOM_HANDOFF_LIMITS.queueMs : this.root(n).createdAt + ROOM_HANDOFF_LIMITS.lifetimeMs;
22542
- return Math.max(ceiling, anchor + ROOM_HANDOFF_LIMITS.minRunwayMs);
22580
+ const root = this.root(n);
22581
+ const ceiling = n.status === "queued" && n.executions === 0 ? anchor + this.limits.queueMs : root.createdAt + this.limits.lifetimeMs + this.pausedMs(root);
22582
+ return Math.min(Math.max(ceiling, anchor + this.limits.minRunwayMs), root.createdAt + this.limits.hardCapMs);
22543
22583
  }
22544
22584
  /** An ancestor past its ceiling is not failed while a descendant is still
22545
22585
  * running inside its own runway; cancelling would cascade into that work. */
@@ -22559,9 +22599,16 @@ var RoomHandoffs = class {
22559
22599
  * never started reports the queue window it waited out, not the tree's. */
22560
22600
  lifetimeError(n) {
22561
22601
  if (n.status === "queued" && n.executions === 0) {
22562
- return `Room handoff queue budget exhausted: never started while waiting for a busy teammate after ${duration3(this.now() - n.createdAt)} of the ${duration3(ROOM_HANDOFF_LIMITS.queueMs)} queue window`;
22602
+ return `Room handoff queue budget exhausted: never started while waiting for a busy teammate after ${duration3(this.now() - n.createdAt)} of the ${duration3(this.limits.queueMs)} queue window`;
22563
22603
  }
22564
- return `Room handoff lifetime budget exhausted: node was ${n.status} after ${duration3(this.now() - this.root(n).createdAt)} of the ${duration3(ROOM_HANDOFF_LIMITS.lifetimeMs)} tree lifetime`;
22604
+ const root = this.root(n);
22605
+ return `Room handoff lifetime budget exhausted: node was ${n.status} after ${duration3(this.effectiveAgeMs(root))} of the ${duration3(this.limits.lifetimeMs)} tree lifetime`;
22606
+ }
22607
+ /** The wall-clock ceiling ignores pauses: it is what stops a tree whose
22608
+ * execution never pauses long enough to age its lifetime budget. */
22609
+ hardCapError(n) {
22610
+ const root = this.root(n);
22611
+ return `Room handoff hard cap exhausted: node was ${n.status} after ${duration3(this.now() - root.createdAt)} of the ${duration3(this.limits.hardCapMs)} wall-clock cap`;
22565
22612
  }
22566
22613
  enqueue(source, generation, parentId, target, key3, text8, approvalGranted = false, rework = false, sourceText = "") {
22567
22614
  if (this.loadError) throw new Error(this.loadError);
@@ -22586,13 +22633,15 @@ var RoomHandoffs = class {
22586
22633
  throw new Error("This agent already completed your assignment. Do not send acknowledgements or approvals as new work. Finish with your decision; results return automatically. Only use rework=true for concrete additional work.");
22587
22634
  }
22588
22635
  if (kind === "work" && target.groupId && path.some((n) => n.groupId === target.groupId)) throw new Error("A room request cannot return to an ancestor room; results are returned automatically");
22589
- if (kind === "work" && path.filter((n) => n.kind === "work").length > ROOM_HANDOFF_LIMITS.depth) throw new Error("Room handoff depth limit reached");
22636
+ if (kind === "work" && path.filter((n) => n.kind === "work").length > this.limits.depth) throw new Error("Room handoff depth limit reached");
22590
22637
  const root = fresh ? parent : this.root(parent);
22591
22638
  const count = [...this.nodes.values()].filter((n) => n.rootId === parent.rootId && n.parentId).length;
22592
- if (count >= ROOM_HANDOFF_LIMITS.requests) throw new Error("Room handoff budget exhausted");
22593
- const remaining = ROOM_HANDOFF_LIMITS.lifetimeMs - (this.now() - root.createdAt);
22594
- if (remaining < ROOM_HANDOFF_LIMITS.minRunwayMs) {
22595
- throw new Error(`Room handoff budget exhausted: only ${duration3(Math.max(remaining, 0))} of the ${duration3(ROOM_HANDOFF_LIMITS.lifetimeMs)} tree lifetime remains`);
22639
+ if (count >= this.limits.requests) throw new Error("Room handoff budget exhausted");
22640
+ const lifetimeRemaining = this.limits.lifetimeMs - this.effectiveAgeMs(root);
22641
+ const hardCapRemaining = root.createdAt + this.limits.hardCapMs - this.now();
22642
+ const remaining = Math.min(lifetimeRemaining, hardCapRemaining);
22643
+ if (remaining < this.limits.minRunwayMs) {
22644
+ throw new Error(`Room handoff budget exhausted: only ${duration3(Math.max(remaining, 0))} of the ${duration3(this.limits.lifetimeMs)} tree lifetime remains`);
22596
22645
  }
22597
22646
  if (this.nodes.size >= 1e3) {
22598
22647
  const oldRoots = [...this.nodes.values()].filter((n) => !n.parentId && terminal(n)).sort((a, b3) => a.createdAt - b3.createdAt);
@@ -22693,11 +22742,13 @@ var RoomHandoffs = class {
22693
22742
  }
22694
22743
  tick() {
22695
22744
  if (this.loadError) return;
22745
+ this.trackExecutionPauses();
22696
22746
  for (const n of [...this.nodes.values()].reverse()) {
22697
22747
  if (terminal(n)) continue;
22698
22748
  const error51 = this.hooks.validate(n, n.parentId ? this.nodes.get(n.parentId) : void 0);
22699
- if (error51 || this.now() > this.deadline(n) && !this.protectsRunner(n) && !this.owesFollowUp(n)) {
22700
- this.cancelTree(n, error51 ?? this.lifetimeError(n), "failed");
22749
+ const hardCapped = this.now() >= this.root(n).createdAt + this.limits.hardCapMs;
22750
+ if (error51 || hardCapped || this.now() > this.deadline(n) && !this.protectsRunner(n) && !this.owesFollowUp(n)) {
22751
+ this.cancelTree(n, error51 ?? (hardCapped ? this.hardCapError(n) : this.lifetimeError(n)), "failed");
22701
22752
  }
22702
22753
  }
22703
22754
  for (const n of this.nodes.values()) {
@@ -22723,7 +22774,7 @@ var RoomHandoffs = class {
22723
22774
  if (this.hooks.busy(n)) continue;
22724
22775
  const root = this.root(n);
22725
22776
  const executionCost = 1;
22726
- if (root.executions + executionCost > ROOM_HANDOFF_LIMITS.executions) {
22777
+ if (root.executions + executionCost > this.limits.executions) {
22727
22778
  this.cancelTree(n, "Room execution budget exhausted", "failed");
22728
22779
  continue;
22729
22780
  }
@@ -22732,6 +22783,9 @@ var RoomHandoffs = class {
22732
22783
  root.executions += executionCost;
22733
22784
  n.status = "running";
22734
22785
  n.startedAt = this.now();
22786
+ const pause = this.pauses.get(root.id) ?? { accumulatedMs: 0 };
22787
+ pause.since ??= this.now();
22788
+ this.pauses.set(root.id, pause);
22735
22789
  this.publish(n, root);
22736
22790
  const controller = new AbortController();
22737
22791
  this.controllers.set(n.id, controller);
@@ -22787,9 +22841,9 @@ var CLOUD_COMPUTER_BUSY_ERROR = "this bot's cloud computer is being used by an a
22787
22841
  var APPROVAL_MODES = ["ask", "edits", "auto", "full", "custom"];
22788
22842
  function supportsApprovalMode(driverKind, mode) {
22789
22843
  if (mode === "custom") return driverKind === "codex";
22790
- if (mode === "edits") return ["claudeAgent", "grokAgent", "antigravityAgent"].includes(driverKind ?? "");
22844
+ if (mode === "edits") return ["claudeAgent", "grokAgent", "antigravityAgent", "qwenAgent", "geminiAgent"].includes(driverKind ?? "");
22791
22845
  if (mode !== "full") return true;
22792
- return ["codex", "claudeAgent", "antigravityAgent", "cursorAgent", "grokAgent", "opencodeGo"].includes(driverKind ?? "");
22846
+ return ["codex", "claudeAgent", "antigravityAgent", "cursorAgent", "grokAgent", "opencodeGo", "qwenAgent", "geminiAgent"].includes(driverKind ?? "");
22793
22847
  }
22794
22848
  function modelSwitchNeedsAsk(mode, fromDriver, toDriver) {
22795
22849
  return !supportsApprovalMode(toDriver, mode) || (mode === "full" || mode === "custom") && fromDriver !== toDriver;
@@ -23134,6 +23188,9 @@ function approvalModeForOrigin(mode, origin) {
23134
23188
  if (mode === "custom" && origin.peerInitiated) return "auto";
23135
23189
  return mode;
23136
23190
  }
23191
+ function delegationInheritsFullAccess(input) {
23192
+ return input.senderIsChief && input.senderHasFullAccess && !input.sameBot && supportsApprovalMode(input.recipientDriverKind, "full");
23193
+ }
23137
23194
  var ASKS_A_PERSON = /* @__PURE__ */ new Set(["askuserquestion", "ask_user"]);
23138
23195
  function autoVerdict(mode, tool, context) {
23139
23196
  if (ASKS_A_PERSON.has(tool.replace(/^mcp__[^_]+__/, "").toLowerCase())) {
@@ -23356,7 +23413,8 @@ function parseCmdShim(shim, env) {
23356
23413
  return null;
23357
23414
  }
23358
23415
  const dir = dirname(shim);
23359
- const targets = [...text8.matchAll(/"%~?dp0%?\\?([^"]+)"/g)].map((m3) => join(dir, m3[1])).filter((p3) => isFile(p3) && basename(p3).toLowerCase() !== "node.exe");
23416
+ const npmEntry = /^SET "NP[MX]_CLI_JS=%~dp0\\([^"]+)"/im.exec(text8);
23417
+ const targets = [...npmEntry ? [npmEntry] : [], ...text8.matchAll(/"%~?dp0%?\\?([^"]+)"/g)].map((m3) => join(dir, m3[1])).filter((p3) => isFile(p3) && basename(p3).toLowerCase() !== "node.exe");
23360
23418
  const script = targets.find((p3) => /\.[cm]?js$/i.test(p3));
23361
23419
  if (script) {
23362
23420
  const node2 = nodeExe(dir, env);
@@ -23650,11 +23708,13 @@ function normalizeImageGenerationUrl(value) {
23650
23708
  return url2.toString().replace(/\/+$/, "");
23651
23709
  }
23652
23710
 
23653
- // server/contracts.ts
23711
+ // shared/wire.ts
23654
23712
  var EFFORT_LEVELS = ["none", "low", "medium", "high", "xhigh", "max"];
23655
23713
  function isEffortLevel(value) {
23656
23714
  return typeof value === "string" && EFFORT_LEVELS.includes(value);
23657
23715
  }
23716
+
23717
+ // server/contracts.ts
23658
23718
  function isModelVariant(value) {
23659
23719
  return typeof value === "string" && value.length > 0 && value.length <= 256 && value.trim() === value && !new RegExp("\\p{Cc}", "u").test(value);
23660
23720
  }
@@ -23910,6 +23970,9 @@ var BROWSER_PROFILE_ID = /^[a-z0-9_-]{1,40}$/;
23910
23970
  var DEFAULT_ROOM_TURN_TIMEOUT_MINUTES = 5;
23911
23971
  var MIN_ROOM_TURN_TIMEOUT_MINUTES = 1;
23912
23972
  var MAX_ROOM_TURN_TIMEOUT_MINUTES = 1440;
23973
+ var DEFAULT_ROOM_HANDOFF_LIFETIME_MINUTES = 30;
23974
+ var DEFAULT_ROOM_HANDOFF_MIN_RUNWAY_MINUTES = 10;
23975
+ var DEFAULT_ROOM_HANDOFF_HARD_CAP_MINUTES = 240;
23913
23976
  var DEFAULT_MAX_CONCURRENT_BOT_THREADS = 3;
23914
23977
  var MAX_CONCURRENT_BOT_THREADS = 10;
23915
23978
  var MIN_THREAD_EVENT_LOG_BYTES = 256 * 1024;
@@ -23939,8 +24002,16 @@ var vpsConfigSchema = external_exports.object({
23939
24002
  }).optional()
23940
24003
  });
23941
24004
  var roomConfigSchema = external_exports.object({
23942
- turnTimeoutMinutes: external_exports.number().int().min(MIN_ROOM_TURN_TIMEOUT_MINUTES).max(MAX_ROOM_TURN_TIMEOUT_MINUTES)
23943
- });
24005
+ turnTimeoutMinutes: external_exports.number().int().min(MIN_ROOM_TURN_TIMEOUT_MINUTES).max(MAX_ROOM_TURN_TIMEOUT_MINUTES),
24006
+ /** Room handoff tree lifetime. Active execution pauses this clock; the
24007
+ * hard cap is wall-clock and bounds trees that never stop executing. */
24008
+ handoffLifetimeMinutes: external_exports.number().int().min(1).max(MAX_ROOM_TURN_TIMEOUT_MINUTES).optional(),
24009
+ handoffMinRunwayMinutes: external_exports.number().int().min(1).max(MAX_ROOM_TURN_TIMEOUT_MINUTES).optional(),
24010
+ handoffHardCapMinutes: external_exports.number().int().min(1).max(7 * MAX_ROOM_TURN_TIMEOUT_MINUTES).optional()
24011
+ }).refine(
24012
+ (rooms) => (rooms.handoffMinRunwayMinutes ?? DEFAULT_ROOM_HANDOFF_MIN_RUNWAY_MINUTES) <= (rooms.handoffLifetimeMinutes ?? DEFAULT_ROOM_HANDOFF_LIFETIME_MINUTES) && (rooms.handoffLifetimeMinutes ?? DEFAULT_ROOM_HANDOFF_LIFETIME_MINUTES) <= (rooms.handoffHardCapMinutes ?? DEFAULT_ROOM_HANDOFF_HARD_CAP_MINUTES),
24013
+ { message: "rooms handoff bounds must satisfy handoffMinRunwayMinutes <= handoffLifetimeMinutes <= handoffHardCapMinutes" }
24014
+ );
23944
24015
  var localVmConfigSchema = external_exports.object({
23945
24016
  mode: external_exports.enum(["shared", "per-bot"]).optional(),
23946
24017
  maxInstances: external_exports.number().int().min(MIN_LOCAL_VM_MAX_INSTANCES).max(MAX_LOCAL_VM_MAX_INSTANCES).optional()
@@ -24189,7 +24260,10 @@ var appConfigSchema = external_exports.object({
24189
24260
  maxConcurrentPerBot: external_exports.number().int().min(1).max(MAX_CONCURRENT_BOT_THREADS),
24190
24261
  /** Cap each per-thread events/ and native/ NDJSON log at this many
24191
24262
  * bytes; absent (the default) keeps today's unbounded growth (#1280). */
24192
- eventLogMaxBytes: external_exports.number().int().min(MIN_THREAD_EVENT_LOG_BYTES).max(MAX_THREAD_EVENT_LOG_BYTES).optional()
24263
+ eventLogMaxBytes: external_exports.number().int().min(MIN_THREAD_EVENT_LOG_BYTES).max(MAX_THREAD_EVENT_LOG_BYTES).optional(),
24264
+ /** Days a closed or archived thread's event logs survive (#1280).
24265
+ * Absent keeps them forever. */
24266
+ eventLogRetentionDays: external_exports.number().int().min(1).max(3650).optional()
24193
24267
  }).strict().optional(),
24194
24268
  localVm: localVmConfigSchema.optional(),
24195
24269
  features: featureConfigSchema.optional(),
@@ -24272,6 +24346,13 @@ function vpsSshAlias(cfg2) {
24272
24346
  function roomTurnTimeoutMinutes(cfg2) {
24273
24347
  return cfg2.rooms?.turnTimeoutMinutes ?? DEFAULT_ROOM_TURN_TIMEOUT_MINUTES;
24274
24348
  }
24349
+ function roomHandoffLimits(cfg2) {
24350
+ return {
24351
+ lifetimeMs: (cfg2.rooms?.handoffLifetimeMinutes ?? DEFAULT_ROOM_HANDOFF_LIFETIME_MINUTES) * 6e4,
24352
+ minRunwayMs: (cfg2.rooms?.handoffMinRunwayMinutes ?? DEFAULT_ROOM_HANDOFF_MIN_RUNWAY_MINUTES) * 6e4,
24353
+ hardCapMs: (cfg2.rooms?.handoffHardCapMinutes ?? DEFAULT_ROOM_HANDOFF_HARD_CAP_MINUTES) * 6e4
24354
+ };
24355
+ }
24275
24356
  function maxConcurrentBotThreads(cfg2) {
24276
24357
  return cfg2.threads?.maxConcurrentPerBot ?? DEFAULT_MAX_CONCURRENT_BOT_THREADS;
24277
24358
  }
@@ -24279,6 +24360,9 @@ function threadEventLogMaxBytes(cfg2) {
24279
24360
  const cap = cfg2.threads?.eventLogMaxBytes;
24280
24361
  return typeof cap === "number" && Number.isFinite(cap) && cap > 0 ? cap : null;
24281
24362
  }
24363
+ function threadEventLogRetentionDays(cfg2) {
24364
+ return cfg2.threads?.eventLogRetentionDays ?? null;
24365
+ }
24282
24366
  function localVmMode(cfg2) {
24283
24367
  return cfg2.localVm?.mode ?? DEFAULT_LOCAL_VM_MODE;
24284
24368
  }
@@ -25170,13 +25254,7 @@ function gateServer(input) {
25170
25254
  };
25171
25255
  }
25172
25256
 
25173
- // server/redact.ts
25174
- var SECRET_KEY_PARTS = ["token", "secret", "password", "passwd", "apikey", "api_key", "authorization", "auth_token"];
25175
- function isSecretName(name2) {
25176
- const lower = name2.toLowerCase();
25177
- if (SECRET_KEY_PARTS.some((part) => lower.includes(part))) return true;
25178
- return /(^|[_.-])keys?$/.test(lower);
25179
- }
25257
+ // shared/redact.ts
25180
25258
  var REDACTION_MARKER = /^«redacted \d+ chars»$/;
25181
25259
  var mask = (value) => REDACTION_MARKER.test(value) ? value : `\xABredacted ${value.length} chars\xBB`;
25182
25260
  var KEY_PREFIXES = [
@@ -25225,6 +25303,14 @@ ${close}`);
25225
25303
  out = out.replace(URL_USERINFO, (_m, lead, secret, at2) => `${lead}${mask(secret)}${at2}`);
25226
25304
  return out;
25227
25305
  }
25306
+
25307
+ // server/redact.ts
25308
+ var SECRET_KEY_PARTS = ["token", "secret", "password", "passwd", "apikey", "api_key", "authorization", "auth_token"];
25309
+ function isSecretName(name2) {
25310
+ const lower = name2.toLowerCase();
25311
+ if (SECRET_KEY_PARTS.some((part) => lower.includes(part))) return true;
25312
+ return /(^|[_.-])keys?$/.test(lower);
25313
+ }
25228
25314
  function redactSecrets(input, depth = 0) {
25229
25315
  if (typeof input === "string") return redactSecretsInText(input);
25230
25316
  if (depth > 12 || input === null || typeof input !== "object") return input;
@@ -27068,8 +27154,8 @@ var ClaudeDriver = {
27068
27154
  };
27069
27155
  const steer = async (threadId, text8) => {
27070
27156
  const s4 = sessions2.get(threadId);
27071
- if (!s4 || !s4.turn || s4.turn.settled || s4.closing || s4.child.exitCode !== null) return false;
27072
- return writeUser(s4, threadId, claudeUserMessage(text8, void 0));
27157
+ if (!s4 || !s4.turn || s4.turn.settled || s4.closing || s4.child.exitCode !== null) return "refused";
27158
+ return await writeUser(s4, threadId, claudeUserMessage(text8, void 0)) ? "steered" : "refused";
27073
27159
  };
27074
27160
  const login = new ClaudeLoginController({ cli: config2.cli, environment, onAuthenticated: async () => {
27075
27161
  await refreshModels();
@@ -38823,6 +38909,20 @@ function canReachPeer(from, target) {
38823
38909
  function reachablePeers(bots, from) {
38824
38910
  return bots.filter((bot) => canReachPeer(from, bot));
38825
38911
  }
38912
+ function resolveTeammate(bots, from, raw) {
38913
+ const wanted = raw.trim();
38914
+ if (bots.some((bot) => bot.id === wanted)) return { id: wanted, byName: false };
38915
+ const fold2 = (value) => value.replace(/^@/, "").replace(/\s+/g, " ").trim().toLowerCase();
38916
+ const name2 = fold2(wanted);
38917
+ const shown = peerName(wanted) || wanted;
38918
+ if (!name2) return { error: `No bot with id "${shown}" \u2014 call list_bots and copy the exact id from the result` };
38919
+ const matches = reachablePeers(bots, from).filter((bot) => fold2(bot.name) === name2);
38920
+ if (matches.length === 1) return { id: matches[0].id, byName: true };
38921
+ if (matches.length > 1) {
38922
+ return { error: `${matches.length} reachable teammates are named "${shown}" \u2014 call list_bots and use the id of the one you mean` };
38923
+ }
38924
+ return { error: `No bot with id or name "${shown}" \u2014 call list_bots and copy the exact id from the result` };
38925
+ }
38826
38926
  var ROSTER_NAME_MAX = 80;
38827
38927
  var ROSTER_ROLE_MAX = 120;
38828
38928
  var ROSTER_ABOUT_MAX = 200;
@@ -38855,7 +38955,7 @@ function renderRoster(team, opts) {
38855
38955
  const role = clip(bot.title ?? "", ROSTER_ROLE_MAX) || "General assistant";
38856
38956
  const about = opts.about ? clip(bot.description ?? "", ROSTER_ABOUT_MAX) : "";
38857
38957
  const availability = peerStatusWords(peerStatus(bot.activity, bot.busy));
38858
- return `- ${name2} \u2014 ${role}${about ? `: ${about}` : ""} (${availability})`;
38958
+ return `- ${name2} \u2014 ${role}${about ? `: ${about}` : ""} (${availability}) [id: ${clip(bot.id, ROSTER_NAME_MAX)}]`;
38859
38959
  });
38860
38960
  return lines.join("\n") + (overflow > 0 ? `
38861
38961
  - \u2026and ${overflow} more (use list_bots for the full roster).` : "");
@@ -38905,11 +39005,11 @@ function chiefOfStaffSystemPrompt(chiefId, bots, canDelegate, trustedOpenMausSta
38905
39005
  empty: "- No other visible bots are available yet.",
38906
39006
  about: true
38907
39007
  });
38908
- const delegation = canDelegate ? boundedCoordination ? "Use list_bots or list_room_targets for the live reachable roster. Use coordinate_bots to ask actual teammates for advice or assign concrete work. Outside a room, everything you send a teammate continues your one standing conversation with them, using their own model and permissions, so they still have the context of your earlier assignments. Busy teammates queue. Give self-contained briefs, then end your turn; you resume automatically after their results return. Leads can coordinate their own specialists. Do not poll, send acknowledgements as new work, or substitute native helpers for named bots. On return, verify the requested outcome, resolve decisions within the user's scope, request concrete corrections with rework=true when necessary, and return one consolidated answer. Consultations are advice, not proof that work or tests ran." : [
39008
+ const delegation = canDelegate ? boundedCoordination ? "Use list_bots or list_room_targets for the live reachable roster. Use coordinate_bots to ask actual teammates for advice or assign concrete work. Outside a room, everything you send a teammate continues your one standing conversation with them, using their own model and permissions, so they still have the context of your earlier assignments. Busy teammates queue. Give self-contained briefs, then end your turn; you resume automatically after their results return. Leads can coordinate their own specialists. Do not poll, send acknowledgements as new work, or substitute native helpers for named bots. On return, verify the requested outcome, resolve decisions within the user's scope, request concrete corrections with rework=true when necessary, and return one consolidated answer. Consultations are advice, not proof that work or tests ran. A refusal from coordinate_bots means nothing was sent: fix what it names (usually the id \u2014 copy it from list_bots or your roster; a unique teammate name also works) and retry, and never describe a handoff the tool did not accept." : [
38909
39009
  "Use list_bots to confirm the live roster and IDs. When assigning work to a teammate, use delegate_bot: it returns immediately, keeps you available to the user, and delivers the teammate's outcome back into this conversation automatically \u2014 success or failure. When the result arrives you are woken with it: report it to the user and act. If the teammate fails or stalls, tell the user plainly and decide the next step yourself.",
38910
39010
  "After delegate_bot accepts the task, acknowledge the handoff and continue with any independent work or end your turn. Do not call wait_delegation or repeatedly poll check_delegation in the same turn.",
38911
39011
  "Use ask_bot only for a brief consultation whose answer you must have before writing your current response. Never use ask_bot for an assigned task, background work, or anything potentially long-running.",
38912
- "Delegate with a clear, self-contained brief. Say that the task is assigned, not completed; only claim completion after the teammate's result has actually arrived.",
39012
+ "Delegate with a clear, self-contained brief. Say that the task is assigned, not completed; only claim completion after the teammate's result has actually arrived. A refusal from delegate_bot or ask_bot means nothing was sent: fix what it names (usually the id \u2014 copy it from list_bots or your roster) and retry.",
38913
39013
  "You may assign work to more than one teammate when the request genuinely benefits. Stay responsive while they work, then combine their returned results when the user asks for a synthesis."
38914
39014
  ].join(" ") : "Your current engine cannot contact teammates. Be honest about that limitation and ask the user to choose a delegation-compatible engine before promising coordinated work.";
38915
39015
  return [
@@ -39926,6 +40026,38 @@ function setupCommands(runtime, platform = process.platform, target = SHARED_LOC
39926
40026
  };
39927
40027
  }
39928
40028
 
40029
+ // server/thread-retention.ts
40030
+ import { unlinkSync as unlinkSync7 } from "node:fs";
40031
+ import { join as join19 } from "node:path";
40032
+ var DAY_MS = 24 * 60 * 60 * 1e3;
40033
+ function idleSince(candidate) {
40034
+ const stamps = [candidate.closedAt, candidate.archivedAt].filter((value) => value !== null);
40035
+ return stamps.length ? Math.max(...stamps) : null;
40036
+ }
40037
+ function sweepThreadEventLogs(candidates, retentionDays, now = Date.now()) {
40038
+ const cutoff = now - retentionDays * DAY_MS;
40039
+ let swept = 0;
40040
+ for (const candidate of candidates) {
40041
+ if (candidate.unread || candidate.busy || candidate.openDirectHandoff) continue;
40042
+ const since = idleSince(candidate);
40043
+ if (since === null || since >= cutoff) continue;
40044
+ let removedAny = false;
40045
+ for (const dir of [EVENTS_DIR, NATIVE_DIR]) {
40046
+ const path = join19(dir, `${candidate.threadId}.ndjson`);
40047
+ try {
40048
+ unlinkSync7(path);
40049
+ removedAny = true;
40050
+ } catch (error51) {
40051
+ if (error51?.code !== "ENOENT") {
40052
+ console.warn(`[retention] could not remove ${path}: ${error51.message}`);
40053
+ }
40054
+ }
40055
+ }
40056
+ if (removedAny) swept++;
40057
+ }
40058
+ return swept;
40059
+ }
40060
+
39929
40061
  // server/computer-control.ts
39930
40062
  var NO_CONTROL = { held: false, helpReason: null, heldSinceMs: null };
39931
40063
  var MAX_REASON_CHARS = 280;
@@ -40053,15 +40185,15 @@ var ComputerControl = class {
40053
40185
 
40054
40186
  // server/engine-install.ts
40055
40187
  import { existsSync as existsSync6, mkdirSync as mkdirSync7 } from "node:fs";
40056
- import { join as join19 } from "node:path";
40188
+ import { join as join20 } from "node:path";
40057
40189
  import { stripVTControlCharacters as stripVTControlCharacters2 } from "node:util";
40058
40190
  var MAX_OUTPUT2 = 64 * 1024;
40059
40191
  var DEFAULT_TIMEOUT_MS = 10 * 6e4;
40060
40192
  function enginesPrefix(baseDir = DATA_DIR) {
40061
- return join19(baseDir, "tools", "npm");
40193
+ return join20(baseDir, "tools", "npm");
40062
40194
  }
40063
40195
  function enginesBinDir(baseDir = DATA_DIR, platform = process.platform) {
40064
- return platform === "win32" ? enginesPrefix(baseDir) : join19(enginesPrefix(baseDir), "bin");
40196
+ return platform === "win32" ? enginesPrefix(baseDir) : join20(enginesPrefix(baseDir), "bin");
40065
40197
  }
40066
40198
  function registerEnginesBinDir(baseDir = DATA_DIR) {
40067
40199
  registerPathDir(enginesBinDir(baseDir));
@@ -40161,7 +40293,7 @@ function runNpm(args, env, cwd, timeoutMs) {
40161
40293
  // server/usage-ledger.ts
40162
40294
  import { readFileSync as readFileSync14 } from "node:fs";
40163
40295
  import { appendFile as appendFile2, mkdir as mkdir2 } from "node:fs/promises";
40164
- import { join as join20 } from "node:path";
40296
+ import { join as join21 } from "node:path";
40165
40297
 
40166
40298
  // server/prices.ts
40167
40299
  function priceFor(turn, prices) {
@@ -40182,7 +40314,7 @@ function billableFor(turn, prices) {
40182
40314
  var USAGE_GROUPINGS = ["bot", "model", "user", "day", "engine"];
40183
40315
  var DIR = "usage";
40184
40316
  var MAX_RANGE_DAYS = 366;
40185
- var DAY_MS = 24 * 60 * 6e4;
40317
+ var DAY_MS2 = 24 * 60 * 6e4;
40186
40318
  var writeQueues2 = /* @__PURE__ */ new Map();
40187
40319
  var clean2 = (value) => typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.trunc(value)) : 0;
40188
40320
  var finiteOrNull = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : null;
@@ -40190,7 +40322,7 @@ function monthKey(at2) {
40190
40322
  return `${at2.getUTCFullYear()}-${String(at2.getUTCMonth() + 1).padStart(2, "0")}`;
40191
40323
  }
40192
40324
  function usageFileFor(dataDir, at2) {
40193
- return join20(dataDir, DIR, `${monthKey(at2)}.jsonl`);
40325
+ return join21(dataDir, DIR, `${monthKey(at2)}.jsonl`);
40194
40326
  }
40195
40327
  function appendUsage(dataDir, row) {
40196
40328
  const record3 = {
@@ -40210,7 +40342,7 @@ function appendUsage(dataDir, row) {
40210
40342
  });
40211
40343
  }
40212
40344
  async function write(dataDir, record3) {
40213
- await mkdir2(join20(dataDir, DIR), { recursive: true, mode: 448 });
40345
+ await mkdir2(join21(dataDir, DIR), { recursive: true, mode: 448 });
40214
40346
  await appendFile2(usageFileFor(dataDir, new Date(record3.at)), JSON.stringify(record3) + "\n", { mode: 384 });
40215
40347
  }
40216
40348
  async function flushUsageLedger(dataDir) {
@@ -40234,7 +40366,7 @@ function readUsage(dataDir, range) {
40234
40366
  for (const key3 of monthsBetween(range.from, range.to)) {
40235
40367
  let text8;
40236
40368
  try {
40237
- text8 = readFileSync14(join20(dataDir, DIR, `${key3}.jsonl`), "utf8");
40369
+ text8 = readFileSync14(join21(dataDir, DIR, `${key3}.jsonl`), "utf8");
40238
40370
  } catch {
40239
40371
  continue;
40240
40372
  }
@@ -40262,7 +40394,7 @@ function parseUsageRange(from, to2, now = /* @__PURE__ */ new Date()) {
40262
40394
  const start = from ? parse7(from, false) : new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1));
40263
40395
  const end = to2 ? parse7(to2, true) : now;
40264
40396
  if (!start || !end || start.getTime() > end.getTime()) return null;
40265
- if (end.getTime() - start.getTime() > MAX_RANGE_DAYS * DAY_MS) return null;
40397
+ if (end.getTime() - start.getTime() > MAX_RANGE_DAYS * DAY_MS2) return null;
40266
40398
  return { from: start, to: end };
40267
40399
  }
40268
40400
  function triggerKey(trigger) {
@@ -40436,14 +40568,14 @@ async function checkProviderKey(input, fetchImpl = fetch, timeoutMs = 1e4) {
40436
40568
 
40437
40569
  // server/enterprise.ts
40438
40570
  import { existsSync as existsSync7 } from "node:fs";
40439
- import { join as join21 } from "node:path";
40571
+ import { join as join22 } from "node:path";
40440
40572
  import { pathToFileURL } from "node:url";
40441
40573
  var layerSchema = external_exports.object({
40442
40574
  customer: external_exports.string().min(1),
40443
40575
  features: external_exports.array(external_exports.string().min(1)),
40444
40576
  expiresAt: external_exports.string().nullable()
40445
40577
  });
40446
- var DEFAULT_DIR = join21(SERVER_ROOT, "..", "enterprise");
40578
+ var DEFAULT_DIR = join22(SERVER_ROOT, "..", "enterprise");
40447
40579
  var current = { edition: "oss", features: [] };
40448
40580
  var workspaceAccessFactory;
40449
40581
  function hostedWorkspaceConfigured(env = process.env) {
@@ -40476,7 +40608,7 @@ async function loadEnterpriseLayer(options = {}) {
40476
40608
  workspaceAccessFactory = void 0;
40477
40609
  const dir = options.dir ?? process.env.OMB_ENTERPRISE_DIR ?? DEFAULT_DIR;
40478
40610
  const licenseKey = options.licenseKey ?? process.env.OMB_LICENSE_KEY;
40479
- const entry = [join21(dir, "server", "index.ts"), join21(dir, "server", "index.js")].find((candidate) => existsSync7(candidate));
40611
+ const entry = [join22(dir, "server", "index.ts"), join22(dir, "server", "index.js")].find((candidate) => existsSync7(candidate));
40480
40612
  if (!entry) {
40481
40613
  return oss(
40482
40614
  licenseKey ? `OMB_LICENSE_KEY is set but no enterprise layer exists at ${dir}` : void 0
@@ -42110,7 +42242,7 @@ import { createHash as createHash10, randomUUID as randomUUID13 } from "node:cry
42110
42242
  import { mkdir as mkdir4, mkdtemp as mkdtemp2, readFile as readFile2, rm as rm2, stat as stat4 } from "node:fs/promises";
42111
42243
  import { request as httpRequest } from "node:http";
42112
42244
  import { tmpdir as tmpdir3 } from "node:os";
42113
- import { delimiter as delimiter2, join as join22, resolve as resolve7 } from "node:path";
42245
+ import { delimiter as delimiter2, join as join23, resolve as resolve7 } from "node:path";
42114
42246
  var ANTIGRAVITY_AUTH_PREFIX = "Open the following link to authenticate the ACP server: ";
42115
42247
  var AUTH_TIMEOUT_MS = 5 * 6e4;
42116
42248
  var STARTUP_TIMEOUT_MS = 9e4;
@@ -42171,11 +42303,11 @@ function authorizationUrlFromLine(line) {
42171
42303
  }
42172
42304
  }
42173
42305
  function antigravityProfileDirectory(instanceId, baseDir = DATA_DIR) {
42174
- return join22(baseDir, "providers", "antigravity", createHash10("sha256").update(instanceId).digest("hex"));
42306
+ return join23(baseDir, "providers", "antigravity", createHash10("sha256").update(instanceId).digest("hex"));
42175
42307
  }
42176
42308
  async function prepareAntigravityProfile(input) {
42177
42309
  const directory = resolve7(input.profileDirectory ?? antigravityProfileDirectory(input.instanceId, input.baseDir));
42178
- const acpDirectory = join22(directory, "antigravity-acp");
42310
+ const acpDirectory = join23(directory, "antigravity-acp");
42179
42311
  await mkdir4(acpDirectory, { recursive: true, mode: 448 });
42180
42312
  if (process.platform !== "win32") {
42181
42313
  const { chmod: chmod3 } = await import("node:fs/promises");
@@ -42183,7 +42315,7 @@ async function prepareAntigravityProfile(input) {
42183
42315
  await chmod3(acpDirectory, 448);
42184
42316
  }
42185
42317
  writeFileAtomic(
42186
- join22(acpDirectory, "settings.json"),
42318
+ join23(acpDirectory, "settings.json"),
42187
42319
  `${JSON.stringify({ auth: { type: "oauth-personal" } })}
42188
42320
  `,
42189
42321
  { mode: 384 }
@@ -42206,7 +42338,7 @@ async function prepareAntigravityProfile(input) {
42206
42338
  PYTHONUNBUFFERED: "1",
42207
42339
  ELECTRON_RUN_AS_NODE: "1"
42208
42340
  });
42209
- return { directory, tokenPath: join22(acpDirectory, "acp_token.json"), environment };
42341
+ return { directory, tokenPath: join23(acpDirectory, "acp_token.json"), environment };
42210
42342
  }
42211
42343
  async function antigravityProfileAuthenticated(profile) {
42212
42344
  try {
@@ -42488,7 +42620,7 @@ function isValidAntigravityInitializeResult(initialized, expectedVersion) {
42488
42620
  return true;
42489
42621
  }
42490
42622
  async function validateAntigravityRuntime(runtime, expectedVersion) {
42491
- const profileDirectory = await mkdtemp2(join22(tmpdir3(), "openmaus-antigravity-verify-"));
42623
+ const profileDirectory = await mkdtemp2(join23(tmpdir3(), "openmaus-antigravity-verify-"));
42492
42624
  let client;
42493
42625
  let failed = false;
42494
42626
  let failure5;
@@ -42621,7 +42753,7 @@ import {
42621
42753
  writeFile as writeFile3
42622
42754
  } from "node:fs/promises";
42623
42755
  import { constants as constants3 } from "node:fs";
42624
- import { delimiter as delimiter3, dirname as dirname8, isAbsolute as isAbsolute7, join as join23, resolve as resolve8 } from "node:path";
42756
+ import { delimiter as delimiter3, dirname as dirname8, isAbsolute as isAbsolute7, join as join24, resolve as resolve8 } from "node:path";
42625
42757
  import { Transform } from "node:stream";
42626
42758
  import { pipeline } from "node:stream/promises";
42627
42759
  import { createInflateRaw } from "node:zlib";
@@ -42720,7 +42852,7 @@ var AntigravitySetupError = class extends Error {
42720
42852
  };
42721
42853
  var installs = /* @__PURE__ */ new Map();
42722
42854
  function runtimeRoot(baseDir = DATA_DIR) {
42723
- return join23(baseDir, "tools", "antigravity-acp", `${process.platform}-${process.arch}`);
42855
+ return join24(baseDir, "tools", "antigravity-acp", `${process.platform}-${process.arch}`);
42724
42856
  }
42725
42857
  function executableNames(platform = process.platform) {
42726
42858
  return platform === "win32" ? { executable: "agy_acp_server.exe", harness: "localharness_external.exe" } : { executable: "agy_acp_server.par", harness: "localharness_external" };
@@ -42737,10 +42869,10 @@ async function executableFile(path, bytes) {
42737
42869
  }
42738
42870
  async function completedRelease(directory, asset) {
42739
42871
  try {
42740
- const record3 = JSON.parse(await readFile3(join23(directory, INSTALL_RECORD), "utf8"));
42872
+ const record3 = JSON.parse(await readFile3(join24(directory, INSTALL_RECORD), "utf8"));
42741
42873
  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;
42742
- const executablePath = join23(directory, asset.executable.name);
42743
- const harnessPath = join23(directory, asset.harness.name);
42874
+ const executablePath = join24(directory, asset.executable.name);
42875
+ const harnessPath = join24(directory, asset.harness.name);
42744
42876
  if (!await executableFile(executablePath, asset.executable.bytes)) return null;
42745
42877
  if (!await executableFile(harnessPath, asset.harness.bytes)) return null;
42746
42878
  return { executablePath, harnessPath, source: "managed", version: asset.version };
@@ -42756,7 +42888,7 @@ function pathCandidates(binary2, env) {
42756
42888
  async function externalRuntime(candidate, source) {
42757
42889
  const names = executableNames();
42758
42890
  const executablePath = resolve8(candidate);
42759
- const harnessPath = join23(dirname8(executablePath), names.harness);
42891
+ const harnessPath = join24(dirname8(executablePath), names.harness);
42760
42892
  if (!await executableFile(executablePath) || !await executableFile(harnessPath)) return null;
42761
42893
  return { executablePath, harnessPath, source, version: null };
42762
42894
  }
@@ -42776,7 +42908,7 @@ async function resolveAntigravityRuntime(binaryPath, env = process.env, baseDir
42776
42908
  }
42777
42909
  const asset = resolveAntigravityReleaseAsset();
42778
42910
  if (asset) {
42779
- const managed = await completedRelease(join23(runtimeRoot(baseDir), "versions", asset.sha256), asset);
42911
+ const managed = await completedRelease(join24(runtimeRoot(baseDir), "versions", asset.sha256), asset);
42780
42912
  if (managed) return managed;
42781
42913
  }
42782
42914
  const names = executableNames();
@@ -42902,7 +43034,7 @@ async function extractPinnedArchive(archivePath, outputDirectory, asset) {
42902
43034
  start: dataOffset,
42903
43035
  end: dataOffset + entry.compressedBytes - 1
42904
43036
  });
42905
- const destination = createWriteStream(join23(outputDirectory, entry.name), { flags: "wx", mode: 448 });
43037
+ const destination = createWriteStream(join24(outputDirectory, entry.name), { flags: "wx", mode: 448 });
42906
43038
  if (entry.compression === 8) await pipeline(source, createInflateRaw(), counter, destination);
42907
43039
  else await pipeline(source, counter, destination);
42908
43040
  if (bytes !== expectedBytes) throw new Error("An extracted file was truncated.");
@@ -42913,8 +43045,8 @@ async function installOnce2(options) {
42913
43045
  const asset = options.asset === void 0 ? resolveAntigravityReleaseAsset() : options.asset;
42914
43046
  if (!asset) throw new Error(`Google does not publish an Antigravity runtime for ${process.platform}-${process.arch}.`);
42915
43047
  const root = runtimeRoot(options.baseDir);
42916
- const versions = join23(root, "versions");
42917
- const destination = join23(versions, asset.sha256);
43048
+ const versions = join24(root, "versions");
43049
+ const destination = join24(versions, asset.sha256);
42918
43050
  await mkdir5(versions, { recursive: true, mode: 448 });
42919
43051
  const existing = await completedRelease(destination, asset);
42920
43052
  if (existing) {
@@ -42930,11 +43062,11 @@ async function installOnce2(options) {
42930
43062
  } catch (error51) {
42931
43063
  if (error51 instanceof Error && error51.message.includes("Antigravity needs")) throw error51;
42932
43064
  }
42933
- const staging = await mkdtemp3(join23(versions, `${STAGING_PREFIX}${randomUUID14()}-`));
43065
+ const staging = await mkdtemp3(join24(versions, `${STAGING_PREFIX}${randomUUID14()}-`));
42934
43066
  let failed = false;
42935
43067
  try {
42936
- const archivePath = join23(staging, "download.zip");
42937
- const runtimeDirectory = join23(staging, "runtime");
43068
+ const archivePath = join24(staging, "download.zip");
43069
+ const runtimeDirectory = join24(staging, "runtime");
42938
43070
  await mkdir5(runtimeDirectory, { mode: 448 });
42939
43071
  const controller = new AbortController();
42940
43072
  const timeout2 = setTimeout(() => controller.abort(), DOWNLOAD_TIMEOUT_MS);
@@ -42980,12 +43112,12 @@ async function installOnce2(options) {
42980
43112
  }
42981
43113
  await extractPinnedArchive(archivePath, runtimeDirectory, asset);
42982
43114
  if (process.platform !== "win32") {
42983
- await chmod2(join23(runtimeDirectory, asset.executable.name), 493);
42984
- await chmod2(join23(runtimeDirectory, asset.harness.name), 493);
43115
+ await chmod2(join24(runtimeDirectory, asset.executable.name), 493);
43116
+ await chmod2(join24(runtimeDirectory, asset.harness.name), 493);
42985
43117
  }
42986
43118
  const candidate = {
42987
- executablePath: join23(runtimeDirectory, asset.executable.name),
42988
- harnessPath: join23(runtimeDirectory, asset.harness.name),
43119
+ executablePath: join24(runtimeDirectory, asset.executable.name),
43120
+ harnessPath: join24(runtimeDirectory, asset.harness.name),
42989
43121
  source: "managed",
42990
43122
  version: asset.version
42991
43123
  };
@@ -42996,7 +43128,7 @@ async function installOnce2(options) {
42996
43128
  executable: asset.executable,
42997
43129
  harness: asset.harness
42998
43130
  };
42999
- await writeFile3(join23(runtimeDirectory, INSTALL_RECORD), `${JSON.stringify(record3)}
43131
+ await writeFile3(join24(runtimeDirectory, INSTALL_RECORD), `${JSON.stringify(record3)}
43000
43132
  `, { flag: "wx", mode: 384 });
43001
43133
  try {
43002
43134
  await retryOnWindowsFileLock(() => rename2(runtimeDirectory, destination));
@@ -43418,7 +43550,7 @@ import { homedir as homedir13 } from "node:os";
43418
43550
  // server/drivers/codex-catalog.ts
43419
43551
  import { readdirSync as readdirSync3, readFileSync as readFileSync15 } from "node:fs";
43420
43552
  import { homedir as homedir10 } from "node:os";
43421
- import { basename as basename5, join as join24 } from "node:path";
43553
+ import { basename as basename5, join as join25 } from "node:path";
43422
43554
  var STATIC_CODEX_MODELS = {
43423
43555
  default: "gpt-5.6-sol",
43424
43556
  options: [
@@ -43550,7 +43682,7 @@ function readCodexAppServerModelCatalog(cli, env, timeoutMs = 8e3) {
43550
43682
  }
43551
43683
  function codexHome(env) {
43552
43684
  if (env.CODEX_HOME) return env.CODEX_HOME;
43553
- return join24(env.HOME || env.USERPROFILE || homedir10(), ".codex");
43685
+ return join25(env.HOME || env.USERPROFILE || homedir10(), ".codex");
43554
43686
  }
43555
43687
  function unquote(raw) {
43556
43688
  let value = raw.trim();
@@ -43636,11 +43768,11 @@ function niceLabel(model2, provider, known, named) {
43636
43768
  }
43637
43769
  function collectCatalogNames(home) {
43638
43770
  const named = /* @__PURE__ */ new Map();
43639
- for (const file2 of listDir(join24(home, "model-catalogs"))) {
43771
+ for (const file2 of listDir(join25(home, "model-catalogs"))) {
43640
43772
  if (!file2.endsWith(".json")) continue;
43641
43773
  const provider = basename5(file2, ".json").replace(/-models$/, "");
43642
43774
  if (!PROVIDER_ID.test(provider)) continue;
43643
- const raw = readText(join24(home, "model-catalogs", file2));
43775
+ const raw = readText(join25(home, "model-catalogs", file2));
43644
43776
  if (!raw) continue;
43645
43777
  let parsed;
43646
43778
  try {
@@ -43692,7 +43824,7 @@ async function probeProviderModels(provider, env, fetchImpl) {
43692
43824
  async function readCodexModelCatalog(env = process.env, fetchImpl = fetch, cli) {
43693
43825
  const official = (cli ? await readCodexAppServerModelCatalog(cli, env) : null) ?? STATIC_CODEX_MODELS;
43694
43826
  const home = codexHome(env);
43695
- const mainText = readText(join24(home, "config.toml"));
43827
+ const mainText = readText(join25(home, "config.toml"));
43696
43828
  if (!mainText) return mergeLocalInject(official, env, fetchImpl);
43697
43829
  const main = parseCodexToml(mainText);
43698
43830
  const known = new Map(main.providers.map((provider) => [provider.id, provider]));
@@ -43707,7 +43839,7 @@ async function readCodexModelCatalog(env = process.env, fetchImpl = fetch, cli)
43707
43839
  remember(main.modelProvider, main.model);
43708
43840
  for (const file2 of listDir(home)) {
43709
43841
  if (!file2.endsWith(".config.toml")) continue;
43710
- const profile = parseCodexToml(readText(join24(home, file2)) ?? "");
43842
+ const profile = parseCodexToml(readText(join25(home, file2)) ?? "");
43711
43843
  for (const provider of profile.providers) {
43712
43844
  if (!known.has(provider.id)) known.set(provider.id, provider);
43713
43845
  }
@@ -43750,7 +43882,7 @@ async function readCodexModelCatalog(env = process.env, fetchImpl = fetch, cli)
43750
43882
  // server/drivers/codex-instructions.ts
43751
43883
  import { createHash as createHash12 } from "node:crypto";
43752
43884
  import { mkdirSync as mkdirSync8, readFileSync as readFileSync16 } from "node:fs";
43753
- import { join as join25 } from "node:path";
43885
+ import { join as join26 } from "node:path";
43754
43886
  var digest = (value) => createHash12("sha256").update(value).digest("hex");
43755
43887
  function codexDeveloperInstructions(config2, botInstructions) {
43756
43888
  if (!config2 || typeof config2 !== "object" || Array.isArray(config2)) {
@@ -43765,8 +43897,8 @@ function codexDeveloperInstructions(config2, botInstructions) {
43765
43897
  ${configured2}` : botInstructions;
43766
43898
  }
43767
43899
  async function syncCodexInstructions(key3, nativeThreadId, instructions, resumed, request3) {
43768
- const directory = join25(DATA_DIR, "codex-instructions");
43769
- const path = join25(directory, `${digest(JSON.stringify([key3, nativeThreadId]))}.sha256`);
43900
+ const directory = join26(DATA_DIR, "codex-instructions");
43901
+ const path = join26(directory, `${digest(JSON.stringify([key3, nativeThreadId]))}.sha256`);
43770
43902
  const fingerprint = digest(instructions);
43771
43903
  let previous2;
43772
43904
  try {
@@ -43804,7 +43936,7 @@ async function syncCodexInstructions(key3, nativeThreadId, instructions, resumed
43804
43936
  import { randomUUID as randomUUID15 } from "node:crypto";
43805
43937
  import { realpathSync as realpathSync4 } from "node:fs";
43806
43938
  import { homedir as homedir11 } from "node:os";
43807
- import { basename as basename6, dirname as dirname9, isAbsolute as isAbsolute8, join as join26, resolve as resolve9 } from "node:path";
43939
+ import { basename as basename6, dirname as dirname9, isAbsolute as isAbsolute8, join as join27, resolve as resolve9 } from "node:path";
43808
43940
  import { stripVTControlCharacters as stripVTControlCharacters3 } from "node:util";
43809
43941
  var DEVICE_URL = "https://auth.openai.com/codex/device";
43810
43942
  var MAX_OUTPUT3 = 16384;
@@ -43814,7 +43946,7 @@ function canonicalPath2(path) {
43814
43946
  return realpathSync4(path);
43815
43947
  } catch {
43816
43948
  const parent = dirname9(path);
43817
- return parent === path ? path : join26(canonicalPath2(parent), basename6(path));
43949
+ return parent === path ? path : join27(canonicalPath2(parent), basename6(path));
43818
43950
  }
43819
43951
  }
43820
43952
  function codexDevicePrompt(output) {
@@ -43857,7 +43989,7 @@ var CodexDeviceAuthController = class {
43857
43989
  if (!isAbsolute8(home) || env.CODEX_HOME && !isAbsolute8(env.CODEX_HOME)) {
43858
43990
  throw new Error("Use an absolute HOME and CODEX_HOME path for this server's Codex provider before signing in.");
43859
43991
  }
43860
- const homeKey = canonicalPath2(resolve9(env.CODEX_HOME || join26(home, ".codex")));
43992
+ const homeKey = canonicalPath2(resolve9(env.CODEX_HOME || join27(home, ".codex")));
43861
43993
  if (authenticatingHomes2.has(homeKey)) {
43862
43994
  throw new Error("A ChatGPT sign-in is already running for this server account. Finish or cancel it before starting another.");
43863
43995
  }
@@ -43936,7 +44068,7 @@ var CodexDeviceAuthController = class {
43936
44068
  if (!isAbsolute8(home) || env.CODEX_HOME && !isAbsolute8(env.CODEX_HOME)) {
43937
44069
  throw new Error("Use an absolute HOME and CODEX_HOME path for this server's Codex provider before signing out.");
43938
44070
  }
43939
- const homeKey = canonicalPath2(resolve9(env.CODEX_HOME || join26(home, ".codex")));
44071
+ const homeKey = canonicalPath2(resolve9(env.CODEX_HOME || join27(home, ".codex")));
43940
44072
  if (authenticatingHomes2.has(homeKey)) {
43941
44073
  throw new Error("A ChatGPT sign-in is running for this server account. Finish or cancel it before signing out.");
43942
44074
  }
@@ -44093,13 +44225,13 @@ var CodexDeviceAuthController = class {
44093
44225
 
44094
44226
  // server/drivers/codex-identity.ts
44095
44227
  import { homedir as homedir12 } from "node:os";
44096
- import { isAbsolute as isAbsolute9, join as join27, resolve as resolve10 } from "node:path";
44228
+ import { isAbsolute as isAbsolute9, join as join28, resolve as resolve10 } from "node:path";
44097
44229
  var MAX_OUTPUT4 = 16384;
44098
44230
  var EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
44099
44231
  function codexHome2(env) {
44100
44232
  if (env.CODEX_HOME) return isAbsolute9(env.CODEX_HOME) ? resolve10(env.CODEX_HOME) : null;
44101
44233
  const home = env.HOME || env.USERPROFILE || homedir12();
44102
- return isAbsolute9(home) ? join27(home, ".codex") : null;
44234
+ return isAbsolute9(home) ? join28(home, ".codex") : null;
44103
44235
  }
44104
44236
  async function codexAccountEmail(cli, env, timeoutMs = 3e3) {
44105
44237
  const cwd = env.HOME || env.USERPROFILE || homedir12();
@@ -44739,9 +44871,23 @@ var CodexDriver = {
44739
44871
  return stopped;
44740
44872
  });
44741
44873
  let completeStoppedTurn;
44874
+ let interruptRequested = false;
44742
44875
  const stop = async () => {
44743
44876
  stopRequested = true;
44744
44877
  stopSignal.abort();
44878
+ if (!state.settled && !interruptRequested && codexThreadId && codexTurnId && child.exitCode === null && child.signalCode === null) {
44879
+ interruptRequested = true;
44880
+ const graceMs = Math.max(1, Number(process.env.FAKE_CODEX_INTERRUPT_GRACE_MS ?? 750) || 750);
44881
+ try {
44882
+ await request3("turn/interrupt", { threadId: codexThreadId, turnId: codexTurnId }, graceMs);
44883
+ } catch {
44884
+ }
44885
+ const deadline = Date.now() + graceMs;
44886
+ while (!state.settled && Date.now() < deadline) {
44887
+ await new Promise((wake) => setTimeout(wake, 15));
44888
+ }
44889
+ if (state.settled) return true;
44890
+ }
44745
44891
  const stopped = await terminate();
44746
44892
  if (stopped) completeStoppedTurn?.();
44747
44893
  return stopped;
@@ -44762,6 +44908,23 @@ var CodexDriver = {
44762
44908
  emit({ ...base2(threadId, turnId), type: "runtime.error", message: "codex did not shut down after termination was requested" });
44763
44909
  }
44764
44910
  };
44911
+ const steerActiveTurn = async (text8) => {
44912
+ if (state.settled || abandoned || stopRequested || !codexThreadId || !codexTurnId) return "refused";
44913
+ if (child.exitCode !== null || child.signalCode !== null) return "refused";
44914
+ try {
44915
+ const steerTimeoutMs = Math.max(1, Number(process.env.FAKE_CODEX_STEER_TIMEOUT_MS ?? 1e4) || 1e4);
44916
+ await request3("turn/steer", {
44917
+ threadId: codexThreadId,
44918
+ input: [{ type: "text", text: text8 }],
44919
+ expectedTurnId: codexTurnId
44920
+ }, steerTimeoutMs);
44921
+ return "steered";
44922
+ } catch (error51) {
44923
+ const message4 = error51 instanceof Error ? error51.message : String(error51);
44924
+ if (state.settled || message4.includes("timed out")) return "indeterminate";
44925
+ return "refused";
44926
+ }
44927
+ };
44765
44928
  const controlsHost = turn.integrations?.localComputer?.scope === "local-computer";
44766
44929
  const handleServerRequest = (msg) => {
44767
44930
  const method = msg.method;
@@ -45051,6 +45214,10 @@ var CodexDriver = {
45051
45214
  void stop();
45052
45215
  return;
45053
45216
  }
45217
+ if (stopRequested) {
45218
+ void settle(false, "interrupted");
45219
+ return;
45220
+ }
45054
45221
  const recentStderr = stderrSinceOutput.trim();
45055
45222
  const hadStreamedOutput = codexTurnId !== null || state.sawStreamDelta;
45056
45223
  const verdict = signal !== null ? { transient: false, reason: "interrupted" } : classifyError({ exitCode: code, stderr: recentStderr });
@@ -45089,7 +45256,7 @@ var CodexDriver = {
45089
45256
  });
45090
45257
  void settle(false, "exit_before_result");
45091
45258
  });
45092
- active.set(threadId, { stop, turnId, asks });
45259
+ active.set(threadId, { stop, turnId, asks, steer: steerActiveTurn });
45093
45260
  if (attempt === 0) emit({ ...base2(threadId, turnId), type: "turn.started" });
45094
45261
  try {
45095
45262
  await request3("initialize", {
@@ -45298,6 +45465,7 @@ ${stderr ?? ""}`))
45298
45465
  provider: DRIVER_KIND3,
45299
45466
  capabilities: {
45300
45467
  sessionModelSwitch: "unsupported",
45468
+ queueing: true,
45301
45469
  computerMcp: true,
45302
45470
  localComputerMcp: true,
45303
45471
  composioMcp: true,
@@ -45313,6 +45481,10 @@ ${stderr ?? ""}`))
45313
45481
  interruptTurn: async (threadId) => {
45314
45482
  await active.get(threadId)?.stop();
45315
45483
  },
45484
+ steer: async (threadId, text8) => {
45485
+ const turn = active.get(threadId);
45486
+ return turn?.steer ? await turn.steer(text8) : "refused";
45487
+ },
45316
45488
  respondToRequest: async (threadId, requestId, decision) => {
45317
45489
  const turn = active.get(threadId);
45318
45490
  const finish = turn?.asks.get(requestId);
@@ -46266,7 +46438,7 @@ var GrokDriver = {
46266
46438
  // server/drivers/acp/grok.ts
46267
46439
  import { existsSync as existsSync10, readFileSync as readFileSync17, writeFileSync as writeFileSync8 } from "node:fs";
46268
46440
  import { homedir as homedir14 } from "node:os";
46269
- import { join as join28 } from "node:path";
46441
+ import { join as join29 } from "node:path";
46270
46442
  var STATIC_GROK_MODELS = {
46271
46443
  default: "grok-4.6",
46272
46444
  options: [
@@ -46277,7 +46449,7 @@ var STATIC_GROK_MODELS = {
46277
46449
  var SLUG = /^[a-z0-9][a-z0-9._-]*$/i;
46278
46450
  function grokHome(env) {
46279
46451
  if (env.GROK_HOME) return env.GROK_HOME;
46280
- return join28(env.HOME || env.USERPROFILE || homedir14(), ".grok");
46452
+ return join29(env.HOME || env.USERPROFILE || homedir14(), ".grok");
46281
46453
  }
46282
46454
  function unquote2(raw) {
46283
46455
  const value = raw.trim();
@@ -46287,7 +46459,7 @@ function unquote2(raw) {
46287
46459
  return value;
46288
46460
  }
46289
46461
  function readGrokModelCatalog(env = process.env) {
46290
- const path = join28(grokHome(env), "config.toml");
46462
+ const path = join29(grokHome(env), "config.toml");
46291
46463
  let text8 = "";
46292
46464
  try {
46293
46465
  text8 = readFileSync17(path, "utf8");
@@ -46360,7 +46532,7 @@ function ensureGrokInjectSlug(modelId, env = process.env) {
46360
46532
  if (!inject) return modelId;
46361
46533
  const host = localHost(inject.host);
46362
46534
  if (!host) return modelId;
46363
- const path = join28(grokHome(env), "config.toml");
46535
+ const path = join29(grokHome(env), "config.toml");
46364
46536
  let text8 = "";
46365
46537
  try {
46366
46538
  text8 = readFileSync17(path, "utf8");
@@ -46492,7 +46664,7 @@ var support2 = {
46492
46664
  // an unauthenticated CLI is a user action, not something to paper over.
46493
46665
  pickAuthMethod: (methods) => methods.some((m3) => m3.id === "cached_token") ? "cached_token" : null,
46494
46666
  authFailure: "fail",
46495
- isAuthenticated: () => existsSync10(join28(homedir14(), ".grok", "auth.json")),
46667
+ isAuthenticated: () => existsSync10(join29(homedir14(), ".grok", "auth.json")),
46496
46668
  // `--append-system-prompt`/`--rules` are accepted by the CLI but do NOT
46497
46669
  // reach the agent-stdio system prompt (verified against 1.0.0), so the
46498
46670
  // persona is prepended codex-style.
@@ -46505,11 +46677,11 @@ var GrokAgentDriver = createAcpDriver(support2);
46505
46677
  // server/drivers/acp/gemini.ts
46506
46678
  import { existsSync as existsSync11, readFileSync as readFileSync18 } from "node:fs";
46507
46679
  import { homedir as homedir15 } from "node:os";
46508
- import { join as join29 } from "node:path";
46680
+ import { join as join30 } from "node:path";
46509
46681
  var AUTH_PREFERENCE = ["gemini-api-key", "oauth-personal", "vertex-ai"];
46510
46682
  function liveOauthCredential() {
46511
46683
  try {
46512
- const creds = JSON.parse(readFileSync18(join29(homedir15(), ".gemini", "oauth_creds.json"), "utf8"));
46684
+ const creds = JSON.parse(readFileSync18(join30(homedir15(), ".gemini", "oauth_creds.json"), "utf8"));
46513
46685
  if (typeof creds.access_token !== "string" || creds.access_token.trim().length === 0) return false;
46514
46686
  const hasRefresh = typeof creds.refresh_token === "string" && creds.refresh_token.trim().length > 0;
46515
46687
  if (creds.expiry_date !== void 0) {
@@ -46523,7 +46695,12 @@ function liveOauthCredential() {
46523
46695
  }
46524
46696
  var nonBlank = (value) => Boolean(value?.trim());
46525
46697
  function geminiIsAuthenticated(env) {
46526
- return nonBlank(env.GEMINI_API_KEY) || nonBlank(env.GOOGLE_API_KEY) || existsSync11(join29(homedir15(), ".gemini", "oauth_creds.json")) && liveOauthCredential();
46698
+ return nonBlank(env.GEMINI_API_KEY) || nonBlank(env.GOOGLE_API_KEY) || existsSync11(join30(homedir15(), ".gemini", "oauth_creds.json")) && liveOauthCredential();
46699
+ }
46700
+ function geminiApprovalArgs(fullAuto, approvalMode) {
46701
+ if (fullAuto) return ["--yolo"];
46702
+ if (approvalMode === "edits") return ["--approval-mode", "auto_edit"];
46703
+ return [];
46527
46704
  }
46528
46705
  var support3 = {
46529
46706
  driverKind: "geminiAgent",
@@ -46540,7 +46717,7 @@ var support3 = {
46540
46717
  loginNote: "Gemini CLI needs a GEMINI_API_KEY, a Vertex AI setup, or an enterprise Code Assist login \u2014 consumer Google logins stopped working on 2026-06-18 (use the Antigravity engine for those accounts)",
46541
46718
  // --acp is the stable Gemini CLI surface. --experimental-acp remains an
46542
46719
  // alias for older releases, but using it now emits a deprecation warning.
46543
- spawnArgs: (_config, turn) => ["--acp", ...turn.model ? ["-m", turn.model] : []],
46720
+ spawnArgs: (config2, turn) => ["--acp", ...geminiApprovalArgs(config2.fullAuto, turn.approvalMode), ...turn.model ? ["-m", turn.model] : []],
46544
46721
  credentialEnv: ["GEMINI_API_KEY", "GOOGLE_API_KEY"],
46545
46722
  pickAuthMethod: (methods) => {
46546
46723
  const ids = methods.map((m3) => m3.id).filter((id) => typeof id === "string");
@@ -46558,7 +46735,7 @@ var GeminiAgentDriver = createAcpDriver(support3);
46558
46735
  // server/drivers/acp/kimi.ts
46559
46736
  import { existsSync as existsSync12, mkdirSync as mkdirSync9, readFileSync as readFileSync19, writeFileSync as writeFileSync9 } from "node:fs";
46560
46737
  import { homedir as homedir16 } from "node:os";
46561
- import { join as join30 } from "node:path";
46738
+ import { join as join31 } from "node:path";
46562
46739
  var STATIC_KIMI_MODELS = {
46563
46740
  default: "kimi-code/k3",
46564
46741
  options: [
@@ -46574,10 +46751,10 @@ function isLocalInjectAlias(slug) {
46574
46751
  return LOCAL_PROVIDER_PREFIXES.some((prefix) => slug.startsWith(prefix));
46575
46752
  }
46576
46753
  function kimiDataRoot(env) {
46577
- return env.KIMI_CODE_HOME || join30(env.HOME || env.USERPROFILE || homedir16(), ".kimi-code");
46754
+ return env.KIMI_CODE_HOME || join31(env.HOME || env.USERPROFILE || homedir16(), ".kimi-code");
46578
46755
  }
46579
46756
  function credentialsPath(env) {
46580
- return join30(kimiDataRoot(env), "credentials", "kimi-code.json");
46757
+ return join31(kimiDataRoot(env), "credentials", "kimi-code.json");
46581
46758
  }
46582
46759
  function quoteToml2(value) {
46583
46760
  return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
@@ -46865,7 +47042,7 @@ function ensureKimiInjectAlias(modelId, env = process.env) {
46865
47042
  const alias = `${inject.host}/${inject.model.replace(/\//g, "-")}`;
46866
47043
  const dataRoot = kimiDataRoot(env);
46867
47044
  mkdirSync9(dataRoot, { recursive: true });
46868
- const path = join30(dataRoot, "config.toml");
47045
+ const path = join31(dataRoot, "config.toml");
46869
47046
  let text8 = "";
46870
47047
  try {
46871
47048
  text8 = readFileSync19(path, "utf8");
@@ -46937,7 +47114,7 @@ function readKimiModelCatalog(env) {
46937
47114
  const dataRoot = kimiDataRoot(env);
46938
47115
  let text8 = "";
46939
47116
  try {
46940
- text8 = readFileSync19(join30(dataRoot, "config.toml"), "utf8");
47117
+ text8 = readFileSync19(join31(dataRoot, "config.toml"), "utf8");
46941
47118
  } catch {
46942
47119
  return STATIC_KIMI_MODELS;
46943
47120
  }
@@ -47032,11 +47209,11 @@ var KimiAgentDriver = createAcpDriver(support4);
47032
47209
  // server/drivers/acp/droid.ts
47033
47210
  import { existsSync as existsSync13, mkdirSync as mkdirSync10, readFileSync as readFileSync20, writeFileSync as writeFileSync10 } from "node:fs";
47034
47211
  import { homedir as homedir17 } from "node:os";
47035
- import { join as join31 } from "node:path";
47212
+ import { join as join32 } from "node:path";
47036
47213
  var AUTH_FILES = ["auth.v2.file", "auth.v2.loginkeychain", "auth.v2.keyring"];
47037
47214
  function authFilePaths(env) {
47038
47215
  const home = env.FACTORY_HOME_OVERRIDE || env.HOME || homedir17();
47039
- return AUTH_FILES.map((name2) => join31(home, ".factory", name2));
47216
+ return AUTH_FILES.map((name2) => join32(home, ".factory", name2));
47040
47217
  }
47041
47218
  var INJECT_ID_PREFIX = "custom:openmausbot-";
47042
47219
  function droidInjectId(host, model2) {
@@ -47052,9 +47229,9 @@ function ensureDroidInjectModel(modelId, env = process.env) {
47052
47229
  const host = localHost(inject.host);
47053
47230
  if (!host) return modelId;
47054
47231
  const id = droidInjectId(inject.host, inject.model);
47055
- const dir = join31(factoryHome(env), ".factory");
47232
+ const dir = join32(factoryHome(env), ".factory");
47056
47233
  mkdirSync10(dir, { recursive: true });
47057
- const path = join31(dir, "settings.json");
47234
+ const path = join32(dir, "settings.json");
47058
47235
  let settings = {};
47059
47236
  try {
47060
47237
  settings = JSON.parse(readFileSync20(path, "utf8"));
@@ -47094,7 +47271,7 @@ function applyDroidLocalAuthEnv(env, modelId) {
47094
47271
  env.FACTORY_API_KEY = "openmausbot-local";
47095
47272
  }
47096
47273
  function readSettings(env) {
47097
- return JSON.parse(readFileSync20(join31(factoryHome(env), ".factory", "settings.json"), "utf8"));
47274
+ return JSON.parse(readFileSync20(join32(factoryHome(env), ".factory", "settings.json"), "utf8"));
47098
47275
  }
47099
47276
  async function resolveModels(env) {
47100
47277
  let catalog2 = MODELS3;
@@ -47468,7 +47645,7 @@ var CursorAgentDriver = createCursorAgentDriver();
47468
47645
  // server/drivers/acp/opencode-go.ts
47469
47646
  import { existsSync as existsSync14, mkdirSync as mkdirSync11, readFileSync as readFileSync21, writeFileSync as writeFileSync11 } from "node:fs";
47470
47647
  import { homedir as homedir18 } from "node:os";
47471
- import { join as join32 } from "node:path";
47648
+ import { join as join33 } from "node:path";
47472
47649
  var STATIC_MODELS = {
47473
47650
  default: "opencode/x-preview-f-free",
47474
47651
  options: [
@@ -47595,7 +47772,7 @@ var stripForeignProviderKeys = (env) => {
47595
47772
  };
47596
47773
  function opencodeConfigDir(env) {
47597
47774
  const home = env.HOME || env.USERPROFILE || homedir18();
47598
- return join32(env.XDG_CONFIG_HOME || join32(home, ".config"), "opencode");
47775
+ return join33(env.XDG_CONFIG_HOME || join33(home, ".config"), "opencode");
47599
47776
  }
47600
47777
  function ensureOpenCodeInjectModel(modelId, env = process.env) {
47601
47778
  const inject = decodeInjectId(modelId);
@@ -47605,7 +47782,7 @@ function ensureOpenCodeInjectModel(modelId, env = process.env) {
47605
47782
  const native = `${inject.host}/${inject.model}`;
47606
47783
  const dir = opencodeConfigDir(env);
47607
47784
  mkdirSync11(dir, { recursive: true });
47608
- const path = join32(dir, "opencode.json");
47785
+ const path = join33(dir, "opencode.json");
47609
47786
  let config2 = { $schema: "https://opencode.ai/config.json" };
47610
47787
  if (existsSync14(path)) {
47611
47788
  try {
@@ -47643,10 +47820,10 @@ function ensureOpenCodeInjectModel(modelId, env = process.env) {
47643
47820
  function storedAuthPaths(env) {
47644
47821
  const home = env.HOME || env.USERPROFILE || homedir18();
47645
47822
  const roots = [
47646
- env.XDG_DATA_HOME || join32(home, ".local", "share"),
47647
- process.platform === "darwin" ? join32(home, "Library", "Application Support") : process.platform === "win32" ? env.LOCALAPPDATA || join32(home, "AppData", "Local") : ""
47823
+ env.XDG_DATA_HOME || join33(home, ".local", "share"),
47824
+ process.platform === "darwin" ? join33(home, "Library", "Application Support") : process.platform === "win32" ? env.LOCALAPPDATA || join33(home, "AppData", "Local") : ""
47648
47825
  ].filter(Boolean);
47649
- return [...new Set(roots)].map((root) => join32(root, "opencode", "auth.json"));
47826
+ return [...new Set(roots)].map((root) => join33(root, "opencode", "auth.json"));
47650
47827
  }
47651
47828
  function usableAuthEntry(parsed) {
47652
47829
  return Object.values(parsed).some((auth) => {
@@ -47769,11 +47946,11 @@ var OpenCodeDriver = createOpenCodeDriver();
47769
47946
  import { chmodSync as chmodSync2, existsSync as existsSync15, mkdirSync as mkdirSync12, readFileSync as readFileSync22, writeFileSync as writeFileSync12 } from "node:fs";
47770
47947
  import { createHash as createHash13 } from "node:crypto";
47771
47948
  import { homedir as homedir19 } from "node:os";
47772
- import { join as join33 } from "node:path";
47949
+ import { join as join34 } from "node:path";
47773
47950
  var EMPTY = { default: "", options: [] };
47774
47951
  function qwenHome(env) {
47775
47952
  const home = process.platform === "win32" ? env.USERPROFILE || env.HOME || homedir19() : env.HOME || env.USERPROFILE || homedir19();
47776
- return join33(home, ".qwen");
47953
+ return join34(home, ".qwen");
47777
47954
  }
47778
47955
  function isRecord4(value) {
47779
47956
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
@@ -47786,7 +47963,7 @@ var PROTOCOLS = /* @__PURE__ */ new Set(["openai", "anthropic", "gemini", "verte
47786
47963
  function readQwenRoutes(env) {
47787
47964
  let settings;
47788
47965
  try {
47789
- settings = JSON.parse(readFileSync22(join33(qwenHome(env), "settings.json"), "utf8"));
47966
+ settings = JSON.parse(readFileSync22(join34(qwenHome(env), "settings.json"), "utf8"));
47790
47967
  } catch {
47791
47968
  return [];
47792
47969
  }
@@ -47870,7 +48047,7 @@ function ensureQwenInjectModel(modelId, env = process.env) {
47870
48047
  if (!host) return modelId;
47871
48048
  const dir = qwenHome(env);
47872
48049
  mkdirSync12(dir, { recursive: true });
47873
- const path = join33(dir, "settings.json");
48050
+ const path = join34(dir, "settings.json");
47874
48051
  let settings = {};
47875
48052
  if (existsSync15(path)) {
47876
48053
  try {
@@ -47940,6 +48117,12 @@ function resolveQwenTurnModel(model2, env) {
47940
48117
  if (!matches.length) throw new Error("This Qwen model is no longer configured. Refresh models and select it again.");
47941
48118
  return matches[0].id;
47942
48119
  }
48120
+ function qwenApprovalArgs(fullAuto, approvalMode) {
48121
+ if (fullAuto) return ["--yolo"];
48122
+ if (approvalMode === "auto") return ["--approval-mode", "auto"];
48123
+ if (approvalMode === "edits") return ["--approval-mode", "auto-edit"];
48124
+ return [];
48125
+ }
47943
48126
  var support8 = {
47944
48127
  driverKind: "qwenAgent",
47945
48128
  displayName: "Qwen",
@@ -47960,7 +48143,7 @@ var support8 = {
47960
48143
  },
47961
48144
  // A raw -m only changes the model within the saved provider. ACP switches
47962
48145
  // the complete route and confirms it before any prompt leaves OMB.
47963
- spawnArgs: () => ["--acp"],
48146
+ spawnArgs: (config2, turn) => ["--acp", ...qwenApprovalArgs(config2.fullAuto, turn.approvalMode)],
47964
48147
  selectModel: { configId: "model" },
47965
48148
  pickAuthMethod: () => null,
47966
48149
  authFailure: "continue",
@@ -48011,7 +48194,7 @@ var CustomAcpDriver = {
48011
48194
  import { spawn as spawn2 } from "node:child_process";
48012
48195
  import { mkdirSync as mkdirSync13, readFileSync as readFileSync23, writeFileSync as writeFileSync13 } from "node:fs";
48013
48196
  import { homedir as homedir20 } from "node:os";
48014
- import { join as join34 } from "node:path";
48197
+ import { join as join35 } from "node:path";
48015
48198
 
48016
48199
  // node_modules/.pnpm/yaml@2.9.0/node_modules/yaml/browser/dist/nodes/identity.js
48017
48200
  var ALIAS = /* @__PURE__ */ Symbol.for("yaml.alias");
@@ -54315,7 +54498,7 @@ function bindHermesScreenshotCompat(env, modelId) {
54315
54498
  env[HERMES_OPENMAUS_SCREENSHOT_COMPAT_MODEL] = inject.model;
54316
54499
  }
54317
54500
  function hermesHome(env) {
54318
- return env.HERMES_HOME || join34(env.HOME || env.USERPROFILE || homedir20(), ".hermes");
54501
+ return env.HERMES_HOME || join35(env.HOME || env.USERPROFILE || homedir20(), ".hermes");
54319
54502
  }
54320
54503
  function quoteYaml(value) {
54321
54504
  if (/^[\w./:+-]+$/.test(value)) return value;
@@ -54357,7 +54540,7 @@ function ensureHermesInjectProvider(modelId, env = process.env) {
54357
54540
  if (!host) return modelId;
54358
54541
  const dir = hermesHome(env);
54359
54542
  mkdirSync13(dir, { recursive: true });
54360
- const path = join34(dir, "config.yaml");
54543
+ const path = join35(dir, "config.yaml");
54361
54544
  let text8 = "";
54362
54545
  try {
54363
54546
  text8 = readFileSync23(path, "utf8");
@@ -54436,13 +54619,13 @@ function hermesConfiguredModel(env = process.env) {
54436
54619
  const dir = hermesHome(env);
54437
54620
  let secrets = "";
54438
54621
  try {
54439
- secrets = readFileSync23(join34(dir, ".env"), "utf8");
54622
+ secrets = readFileSync23(join35(dir, ".env"), "utf8");
54440
54623
  } catch {
54441
54624
  }
54442
54625
  const hasHostedProviderKey = HERMES_HOSTED_PROVIDER_KEYS.some((name2) => nonEmptyDotenvValue(secrets, name2));
54443
54626
  let configuredDefault = null;
54444
54627
  try {
54445
- configuredDefault = hermesConfigDefault(readFileSync23(join34(dir, "config.yaml"), "utf8"));
54628
+ configuredDefault = hermesConfigDefault(readFileSync23(join35(dir, "config.yaml"), "utf8"));
54446
54629
  } catch {
54447
54630
  }
54448
54631
  const configuredProvider = configuredDefault?.provider.toLowerCase() ?? "";
@@ -54756,7 +54939,7 @@ var OpenAICompatDriver = {
54756
54939
  // server/drivers/pi.ts
54757
54940
  import { chmodSync as chmodSync3, existsSync as existsSync16, mkdirSync as mkdirSync14, mkdtempSync as mkdtempSync2, readFileSync as readFileSync24, rmSync as rmSync3, writeFileSync as writeFileSync14 } from "node:fs";
54758
54941
  import { homedir as homedir21, tmpdir as tmpdir4 } from "node:os";
54759
- import { join as join35 } from "node:path";
54942
+ import { join as join36 } from "node:path";
54760
54943
  var DRIVER_KIND6 = "piAgent";
54761
54944
  var PI_ARGS = ["--mode", "rpc", "--no-session"];
54762
54945
  var PI_MODEL_UPDATE_ARGS = ["update", "--models", "--no-approve"];
@@ -54861,7 +55044,7 @@ async function applyPiLocalCatalog(catalog2, env = process.env, fetchImpl = fetc
54861
55044
  return preferPiInjectRows(await mergeLocalInject(catalog2, env, fetchImpl));
54862
55045
  }
54863
55046
  function piAgentDir(env) {
54864
- return join35(env.HOME || env.USERPROFILE || homedir21(), ".pi", "agent");
55047
+ return join36(env.HOME || env.USERPROFILE || homedir21(), ".pi", "agent");
54865
55048
  }
54866
55049
  function ensurePiInjectModel(modelId, env = process.env) {
54867
55050
  const split2 = splitPiModel(modelId);
@@ -54872,7 +55055,7 @@ function ensurePiInjectModel(modelId, env = process.env) {
54872
55055
  if (!host) return split2;
54873
55056
  const dir = piAgentDir(env);
54874
55057
  mkdirSync14(dir, { recursive: true });
54875
- const path = join35(dir, "models.json");
55058
+ const path = join36(dir, "models.json");
54876
55059
  let root = { providers: {} };
54877
55060
  if (existsSync16(path)) {
54878
55061
  try {
@@ -55073,9 +55256,9 @@ var PiDriver = {
55073
55256
  const mcpServers = buildMcpServers(turn);
55074
55257
  let mcpTempDir = null;
55075
55258
  if (mcpServers) {
55076
- mcpTempDir = mkdtempSync2(join35(tmpdir4(), "omb-pi-mcp-"));
55259
+ mcpTempDir = mkdtempSync2(join36(tmpdir4(), "omb-pi-mcp-"));
55077
55260
  try {
55078
- writeFileSync14(join35(mcpTempDir, "mcp.json"), JSON.stringify({ mcpServers }), { mode: 384 });
55261
+ writeFileSync14(join36(mcpTempDir, "mcp.json"), JSON.stringify({ mcpServers }), { mode: 384 });
55079
55262
  } catch (err) {
55080
55263
  try {
55081
55264
  rmSync3(mcpTempDir, { recursive: true, force: true });
@@ -55093,7 +55276,7 @@ var PiDriver = {
55093
55276
  env: piEnvironment({
55094
55277
  ...process.env,
55095
55278
  ...input.environment,
55096
- ...mcpServers && mcpTempDir ? { OMB_MCP_CONFIG: join35(mcpTempDir, "mcp.json") } : {}
55279
+ ...mcpServers && mcpTempDir ? { OMB_MCP_CONFIG: join36(mcpTempDir, "mcp.json") } : {}
55097
55280
  })
55098
55281
  });
55099
55282
  } catch (err) {
@@ -55433,7 +55616,7 @@ ${turn.text}` : turn.text;
55433
55616
  // server/drivers/minimax.ts
55434
55617
  import { readFileSync as readFileSync25 } from "node:fs";
55435
55618
  import { homedir as homedir22 } from "node:os";
55436
- import { join as join36 } from "node:path";
55619
+ import { join as join37 } from "node:path";
55437
55620
  var DRIVER_KIND7 = "minimax";
55438
55621
  var API_KEY_ENV = "MINIMAX_API_KEY";
55439
55622
  var DEFAULT_URL2 = "https://api.minimax.io/v1";
@@ -55461,7 +55644,7 @@ function normalizedApiUrl(value) {
55461
55644
  }
55462
55645
  function loadLocalMiniMaxConfig(home = homedir22()) {
55463
55646
  try {
55464
- const raw = localConfigSchema.parse(JSON.parse(readFileSync25(join36(home, ".mmx", "config.json"), "utf8")));
55647
+ const raw = localConfigSchema.parse(JSON.parse(readFileSync25(join37(home, ".mmx", "config.json"), "utf8")));
55465
55648
  const configuredUrl = raw.base_url?.trim() ? raw.base_url : raw.region === "cn" ? CN_URL : DEFAULT_URL2;
55466
55649
  return {
55467
55650
  apiKey: raw.api_key?.trim() ?? "",
@@ -55557,14 +55740,14 @@ var BUILT_IN_DRIVERS = [
55557
55740
 
55558
55741
  // server/store.ts
55559
55742
  import { createHash as createHash15 } from "node:crypto";
55560
- import { existsSync as existsSync20, readFileSync as readFileSync29, mkdirSync as mkdirSync19, rmSync as rmSync5, unlinkSync as unlinkSync7 } from "node:fs";
55561
- import { join as join41 } from "node:path";
55743
+ import { existsSync as existsSync20, readFileSync as readFileSync29, mkdirSync as mkdirSync19, rmSync as rmSync5, unlinkSync as unlinkSync8 } from "node:fs";
55744
+ import { join as join42 } from "node:path";
55562
55745
 
55563
55746
  // server/section-context.ts
55564
55747
  import { existsSync as existsSync17, mkdirSync as mkdirSync15, readFileSync as readFileSync26 } from "node:fs";
55565
- import { join as join37 } from "node:path";
55748
+ import { join as join38 } from "node:path";
55566
55749
  var SECTION_CONTEXT_MAX_BYTES = 24e3;
55567
- var SECTION_CONTEXTS_FILE = join37(DATA_DIR, "section-contexts.json");
55750
+ var SECTION_CONTEXTS_FILE = join38(DATA_DIR, "section-contexts.json");
55568
55751
  var sectionContextRecordSchema = external_exports.object({ text: external_exports.string(), updatedAt: external_exports.number().finite() });
55569
55752
  var sectionContextFileSchema = external_exports.object({
55570
55753
  version: external_exports.literal(1),
@@ -55668,14 +55851,14 @@ Shared context for the ${JSON.stringify(label)} section follows. The user manage
55668
55851
  // server/bot-folder.ts
55669
55852
  import { createHash as createHash14 } from "node:crypto";
55670
55853
  import { closeSync as closeSync6, mkdirSync as mkdirSync16, openSync as openSync6, readSync as readSync3, rmSync as rmSync4 } from "node:fs";
55671
- import { join as join38 } from "node:path";
55672
- var BOTS_DIR = join38(DATA_DIR, "bots");
55854
+ import { join as join39 } from "node:path";
55855
+ var BOTS_DIR = join39(DATA_DIR, "bots");
55673
55856
  var SOUL_FILE = "SOUL.md";
55674
55857
  function botFolder(botId) {
55675
- return join38(BOTS_DIR, botId);
55858
+ return join39(BOTS_DIR, botId);
55676
55859
  }
55677
55860
  function soulFile(botId) {
55678
- return join38(botFolder(botId), SOUL_FILE);
55861
+ return join39(botFolder(botId), SOUL_FILE);
55679
55862
  }
55680
55863
  function soulHash(soul) {
55681
55864
  return createHash14("sha256").update(soul, "utf8").digest("hex");
@@ -55743,7 +55926,7 @@ function peerAllowKey(action, targetId) {
55743
55926
 
55744
55927
  // server/message-db.ts
55745
55928
  import { chmodSync as chmodSync4, closeSync as closeSync7, existsSync as existsSync18, mkdirSync as mkdirSync17, openSync as openSync7, readFileSync as readFileSync27, renameSync as renameSync4 } from "node:fs";
55746
- import { join as join39 } from "node:path";
55929
+ import { join as join40 } from "node:path";
55747
55930
  import { DatabaseSync } from "node:sqlite";
55748
55931
 
55749
55932
  // server/peer-provenance.ts
@@ -55766,7 +55949,7 @@ function peerProvenanceAuthor(text8) {
55766
55949
  }
55767
55950
 
55768
55951
  // server/message-db.ts
55769
- var DB_FILE = () => join39(DATA_DIR, "messages.db");
55952
+ var DB_FILE = () => join40(DATA_DIR, "messages.db");
55770
55953
  var handle = null;
55771
55954
  var handlePath = null;
55772
55955
  function open4() {
@@ -56196,9 +56379,9 @@ function closeMessageDb() {
56196
56379
 
56197
56380
  // server/workspace.ts
56198
56381
  import { existsSync as existsSync19, mkdirSync as mkdirSync18, readdirSync as readdirSync4, readFileSync as readFileSync28, statSync as statSync6 } from "node:fs";
56199
- import { join as join40 } from "node:path";
56200
- var WORKSPACES_DIR = join40(DATA_DIR, "workspaces");
56201
- var TASK_WORKSPACES_DIR = join40(DATA_DIR, "task-workspaces");
56382
+ import { join as join41 } from "node:path";
56383
+ var WORKSPACES_DIR = join41(DATA_DIR, "workspaces");
56384
+ var TASK_WORKSPACES_DIR = join41(DATA_DIR, "task-workspaces");
56202
56385
  function supportsWorkspaceFiles(driverKind) {
56203
56386
  return !["grok", "openai-compat", "minimax", "boxAgent"].includes(driverKind);
56204
56387
  }
@@ -56206,7 +56389,7 @@ function ensureTaskWorkspace(botId, threadId) {
56206
56389
  if (![botId, threadId].every((id) => /^[A-Za-z0-9_-]{1,128}$/.test(id))) {
56207
56390
  throw new Error("Invalid bot or thread id for a task workspace.");
56208
56391
  }
56209
- const dir = join40(TASK_WORKSPACES_DIR, botId, threadId);
56392
+ const dir = join41(TASK_WORKSPACES_DIR, botId, threadId);
56210
56393
  mkdirSync18(dir, { recursive: true, mode: 448 });
56211
56394
  return dir;
56212
56395
  }
@@ -56219,20 +56402,20 @@ load at the start of every session \u2014 keep this file short and curated.
56219
56402
  Longer notes belong in memory/<topic>.md files, read on demand.
56220
56403
  `;
56221
56404
  function ensureWorkspace(botId) {
56222
- const dir = join40(WORKSPACES_DIR, botId);
56223
- mkdirSync18(join40(dir, "memory"), { recursive: true, mode: 448 });
56224
- const memoryFile = join40(dir, "MEMORY.md");
56405
+ const dir = join41(WORKSPACES_DIR, botId);
56406
+ mkdirSync18(join41(dir, "memory"), { recursive: true, mode: 448 });
56407
+ const memoryFile = join41(dir, "MEMORY.md");
56225
56408
  if (!existsSync19(memoryFile)) writeFileAtomic(memoryFile, MEMORY_SEED, { mode: 384 });
56226
56409
  return dir;
56227
56410
  }
56228
56411
  function workspaceDir(botId) {
56229
- return join40(WORKSPACES_DIR, botId);
56412
+ return join41(WORKSPACES_DIR, botId);
56230
56413
  }
56231
56414
  function workspaceLocationsPrompt(botId, cwd, botCwd) {
56232
56415
  return "\n\nFile locations for this bot (absolute paths): " + JSON.stringify({
56233
56416
  currentWorkingFolder: cwd ?? "Provider default; inspect the working directory before using relative paths",
56234
56417
  sharedBotFolder: workspaceDir(botId),
56235
- otherThreadFiles: join40(TASK_WORKSPACES_DIR, botId),
56418
+ otherThreadFiles: join41(TASK_WORKSPACES_DIR, botId),
56236
56419
  ...botCwd ? { configuredProjectFolder: botCwd } : {}
56237
56420
  }) + ". Different conversations can have different working folders. For an existing file, use the exact path from the conversation; if missing here, check this bot's listed folders before saying it is gone or recreating it. Follow an explicitly requested destination. Otherwise put new task output in the current working folder and report its absolute path so another thread or room can use it. Do not move old files, edit another active thread's work, or read another bot's private folders without authorization. These paths do not grant additional access.";
56238
56421
  }
@@ -56245,7 +56428,7 @@ function memoryOverBudget(text8) {
56245
56428
  function loadMemory(botId) {
56246
56429
  let raw;
56247
56430
  try {
56248
- raw = readFileSync28(join40(workspaceDir(botId), "MEMORY.md"), "utf8");
56431
+ raw = readFileSync28(join41(workspaceDir(botId), "MEMORY.md"), "utf8");
56249
56432
  } catch {
56250
56433
  return null;
56251
56434
  }
@@ -56268,7 +56451,7 @@ var MEMORY_FILE_MAX_BYTES = 256 * 1024;
56268
56451
  function readMemoryFile(botId) {
56269
56452
  let raw;
56270
56453
  try {
56271
- raw = readFileSync28(join40(workspaceDir(botId), "MEMORY.md"), "utf8");
56454
+ raw = readFileSync28(join41(workspaceDir(botId), "MEMORY.md"), "utf8");
56272
56455
  } catch {
56273
56456
  return { text: "", truncated: false };
56274
56457
  }
@@ -56277,12 +56460,12 @@ function readMemoryFile(botId) {
56277
56460
  }
56278
56461
  function writeMemoryFile(botId, text8) {
56279
56462
  ensureWorkspace(botId);
56280
- writeFileAtomic(join40(workspaceDir(botId), "MEMORY.md"), redactSecretsInText(text8), { mode: 384 });
56463
+ writeFileAtomic(join41(workspaceDir(botId), "MEMORY.md"), redactSecretsInText(text8), { mode: 384 });
56281
56464
  indexWrittenMemoryFile(botId, "MEMORY.md");
56282
56465
  }
56283
56466
  function indexWrittenMemoryFile(botId, relativePath) {
56284
56467
  try {
56285
- const path = join40(workspaceDir(botId), relativePath);
56468
+ const path = join41(workspaceDir(botId), relativePath);
56286
56469
  const stat6 = statSync6(path);
56287
56470
  indexMemoryFile(botId, relativePath, readFileSync28(path, "utf8"), { mtimeMs: stat6.mtimeMs, bytes: stat6.size });
56288
56471
  } catch {
@@ -56297,7 +56480,7 @@ function memoryFilesOnDisk(botId) {
56297
56480
  ];
56298
56481
  return names.flatMap((relativePath) => {
56299
56482
  try {
56300
- const stat6 = statSync6(join40(dir, relativePath));
56483
+ const stat6 = statSync6(join41(dir, relativePath));
56301
56484
  return stat6.isFile() ? [{ path: relativePath, mtimeMs: stat6.mtimeMs, bytes: stat6.size }] : [];
56302
56485
  } catch {
56303
56486
  return [];
@@ -56312,7 +56495,7 @@ function syncMemoryIndex(botId) {
56312
56495
  indexed.delete(file2.path);
56313
56496
  if (known && known.bytes === file2.bytes && known.mtimeMs === Math.trunc(file2.mtimeMs)) continue;
56314
56497
  try {
56315
- const text8 = readFileSync28(join40(workspaceDir(botId), file2.path), "utf8");
56498
+ const text8 = readFileSync28(join41(workspaceDir(botId), file2.path), "utf8");
56316
56499
  indexMemoryFile(botId, file2.path, file2.path === "MEMORY.md" && text8 === MEMORY_SEED ? "" : text8, file2);
56317
56500
  } catch {
56318
56501
  }
@@ -56373,7 +56556,7 @@ function updateMemory(botId, update, opts = {}) {
56373
56556
  }
56374
56557
  const text8 = update.text === void 0 ? void 0 : redactSecretsInText(update.text);
56375
56558
  const dir = ensureWorkspace(botId);
56376
- const raw = readFileSync28(join40(dir, "MEMORY.md"), "utf8");
56559
+ const raw = readFileSync28(join41(dir, "MEMORY.md"), "utf8");
56377
56560
  const current2 = raw === MEMORY_SEED ? "" : raw;
56378
56561
  const today = memoryDate(opts.now);
56379
56562
  const appendEntry = (base2, entry2) => `${base2}${base2 && !base2.endsWith("\n") ? "\n" : ""}${entry2}
@@ -56445,10 +56628,10 @@ function appendMemoryLog(botId, text8, opts = {}) {
56445
56628
  const pad2 = (n) => String(n).padStart(2, "0");
56446
56629
  const source = cleanSource(opts.source);
56447
56630
  const line = `- ${pad2(now.getHours())}:${pad2(now.getMinutes())}${SEP2}${source ? `from ${source}${SEP2}` : ""}${normaliseEntryText(redactSecretsInText(text8))}`;
56448
- const dir = join40(ensureWorkspace(botId), "memory", MEMORY_LOG_DIR);
56631
+ const dir = join41(ensureWorkspace(botId), "memory", MEMORY_LOG_DIR);
56449
56632
  mkdirSync18(dir, { recursive: true, mode: 448 });
56450
56633
  const file2 = `${memoryDate(now)}.md`;
56451
- const path = join40(dir, file2);
56634
+ const path = join41(dir, file2);
56452
56635
  let current2 = "";
56453
56636
  try {
56454
56637
  current2 = readFileSync28(path, "utf8");
@@ -56462,14 +56645,14 @@ function appendMemoryLog(botId, text8, opts = {}) {
56462
56645
  }
56463
56646
  function writeMemoryLog(botId, name2, text8) {
56464
56647
  if (!LOG_FILE_NAME.test(name2)) throw new Error("invalid log name");
56465
- const dir = join40(ensureWorkspace(botId), "memory", MEMORY_LOG_DIR);
56648
+ const dir = join41(ensureWorkspace(botId), "memory", MEMORY_LOG_DIR);
56466
56649
  mkdirSync18(dir, { recursive: true, mode: 448 });
56467
- writeFileAtomic(join40(dir, name2), redactSecretsInText(text8), { mode: 384 });
56650
+ writeFileAtomic(join41(dir, name2), redactSecretsInText(text8), { mode: 384 });
56468
56651
  indexWrittenMemoryFile(botId, `memory/${MEMORY_LOG_DIR}/${name2}`);
56469
56652
  }
56470
56653
  function listMemoryLogs(botId) {
56471
56654
  try {
56472
- return readdirSync4(join40(workspaceDir(botId), "memory", MEMORY_LOG_DIR)).filter((name2) => LOG_FILE_NAME.test(name2)).sort();
56655
+ return readdirSync4(join41(workspaceDir(botId), "memory", MEMORY_LOG_DIR)).filter((name2) => LOG_FILE_NAME.test(name2)).sort();
56473
56656
  } catch {
56474
56657
  return [];
56475
56658
  }
@@ -56477,7 +56660,7 @@ function listMemoryLogs(botId) {
56477
56660
  function readMemoryLog(botId, name2) {
56478
56661
  if (!LOG_FILE_NAME.test(name2)) return null;
56479
56662
  try {
56480
- return readFileSync28(join40(workspaceDir(botId), "memory", MEMORY_LOG_DIR, name2), "utf8");
56663
+ return readFileSync28(join41(workspaceDir(botId), "memory", MEMORY_LOG_DIR, name2), "utf8");
56481
56664
  } catch {
56482
56665
  return null;
56483
56666
  }
@@ -56489,13 +56672,13 @@ function isMemoryTopicName(name2) {
56489
56672
  function listMemoryTopics(botId) {
56490
56673
  let entries;
56491
56674
  try {
56492
- entries = readdirSync4(join40(workspaceDir(botId), "memory"));
56675
+ entries = readdirSync4(join41(workspaceDir(botId), "memory"));
56493
56676
  } catch {
56494
56677
  return [];
56495
56678
  }
56496
56679
  return entries.filter(isMemoryTopicName).flatMap((name2) => {
56497
56680
  try {
56498
- const stat6 = statSync6(join40(workspaceDir(botId), "memory", name2));
56681
+ const stat6 = statSync6(join41(workspaceDir(botId), "memory", name2));
56499
56682
  return stat6.isFile() ? [{ name: name2, bytes: stat6.size }] : [];
56500
56683
  } catch {
56501
56684
  return [];
@@ -56505,13 +56688,13 @@ function listMemoryTopics(botId) {
56505
56688
  function writeMemoryTopic(botId, name2, text8) {
56506
56689
  if (!isMemoryTopicName(name2)) throw new Error("invalid topic name");
56507
56690
  ensureWorkspace(botId);
56508
- writeFileAtomic(join40(workspaceDir(botId), "memory", name2), redactSecretsInText(text8), { mode: 384 });
56691
+ writeFileAtomic(join41(workspaceDir(botId), "memory", name2), redactSecretsInText(text8), { mode: 384 });
56509
56692
  indexWrittenMemoryFile(botId, `memory/${name2}`);
56510
56693
  }
56511
56694
  function readMemoryTopic(botId, name2) {
56512
56695
  if (!isMemoryTopicName(name2)) return null;
56513
56696
  try {
56514
- return readFileSync28(join40(workspaceDir(botId), "memory", name2), "utf8");
56697
+ return readFileSync28(join41(workspaceDir(botId), "memory", name2), "utf8");
56515
56698
  } catch {
56516
56699
  return null;
56517
56700
  }
@@ -56520,8 +56703,8 @@ var SESSION_SEARCH_SYSTEM_PROMPT = ' Your own earlier conversations with this us
56520
56703
  var MEMORY_ROUTING_GUIDANCE = " MEMORY.md is for facts that hold in every session: the person's preferences, standing decisions, corrections, and pointers to files in <topicDir> for anything longer. Write each one as a plain statement of fact, never as an instruction to yourself \u2014 an imperative is read back as a directive next session. A procedure for one kind of task belongs in a memory/<topic>.md file or a skill, not here. Anything that will be stale within a week belongs in the conversation, not in memory. When a fact applies only from a date, or stops applying on one, say so in the entry.";
56521
56704
  function memorySystemPrompt(botId, opts = {}) {
56522
56705
  const memory2 = loadMemory(botId);
56523
- const memoryFile = join40(workspaceDir(botId), "MEMORY.md");
56524
- const topicDir = join40(workspaceDir(botId), "memory");
56706
+ const memoryFile = join41(workspaceDir(botId), "MEMORY.md");
56707
+ const topicDir = join41(workspaceDir(botId), "memory");
56525
56708
  if (opts.fileTools === false && !opts.managedWrites) {
56526
56709
  if (!memory2) return "";
56527
56710
  return ` Your saved memory is supplied as context; this turn has no memory editing tools.
@@ -56621,6 +56804,10 @@ var projectEmojiPattern = new RegExp("^(?!\\p{Emoji_Component}$)(?:\\p{RGI_Emoji
56621
56804
  function isProjectEmoji(value) {
56622
56805
  return typeof value === "string" && value.length <= 64 && projectEmojiPattern.exec(value)?.[0] === value;
56623
56806
  }
56807
+ function toWireTask(task2) {
56808
+ const { resumeCursors: _resumeCursors, lastInstanceId: _lastInstanceId, ...wire } = task2;
56809
+ return wire;
56810
+ }
56624
56811
  var TASK_PATCH_FIELDS = [
56625
56812
  "title",
56626
56813
  "projectId",
@@ -56765,9 +56952,9 @@ function titleFromMessage(text8) {
56765
56952
  const line = text8.trim().split("\n")[0].trim();
56766
56953
  return line.length > 48 ? `${line.slice(0, 47)}\u2026` : line || UNTITLED_TASK;
56767
56954
  }
56768
- var BOTS_FILE = join41(DATA_DIR, "bots.json");
56769
- var GROUPS_FILE = join41(DATA_DIR, "groups.json");
56770
- var messagesFile = (threadId) => join41(DATA_DIR, `messages-${threadId}.json`);
56955
+ var BOTS_FILE = join42(DATA_DIR, "bots.json");
56956
+ var GROUPS_FILE = join42(DATA_DIR, "groups.json");
56957
+ var messagesFile = (threadId) => join42(DATA_DIR, `messages-${threadId}.json`);
56771
56958
  var COLORS = [
56772
56959
  "green",
56773
56960
  "blue",
@@ -57181,13 +57368,20 @@ var Store = class {
57181
57368
  this.emit({ type: "group", groupId: group.id });
57182
57369
  return group;
57183
57370
  }
57184
- /** A thread's durable record: DB rows plus any legacy JSON leftovers. */
57371
+ /** A thread's durable record: DB rows, legacy JSON leftovers, and the
57372
+ * per-thread event logs. Every delete path funnels here — task, group,
57373
+ * and bot deletion — so the logs cannot outlive the thread anywhere. */
57185
57374
  deleteThreadRecord(threadId) {
57186
57375
  this.threads.delete(threadId);
57187
57376
  deleteThread(threadId);
57188
- for (const file2 of [messagesFile(threadId), `${messagesFile(threadId)}.imported`]) {
57377
+ for (const file2 of [
57378
+ messagesFile(threadId),
57379
+ `${messagesFile(threadId)}.imported`,
57380
+ join42(EVENTS_DIR, `${threadId}.ndjson`),
57381
+ join42(NATIVE_DIR, `${threadId}.ndjson`)
57382
+ ]) {
57189
57383
  try {
57190
- unlinkSync7(file2);
57384
+ unlinkSync8(file2);
57191
57385
  } catch {
57192
57386
  }
57193
57387
  }
@@ -57699,7 +57893,7 @@ var Store = class {
57699
57893
  } catch {
57700
57894
  }
57701
57895
  try {
57702
- rmSync5(join41(DATA_DIR, "skill-state", id), { recursive: true, force: true });
57896
+ rmSync5(join42(DATA_DIR, "skill-state", id), { recursive: true, force: true });
57703
57897
  } catch {
57704
57898
  }
57705
57899
  removeBotFolder(id);
@@ -58465,7 +58659,7 @@ function parseSince(value, now = Date.now()) {
58465
58659
  return day.getTime();
58466
58660
  }
58467
58661
  if (/^\d{12,}$/.test(text8)) return Number(text8);
58468
- const parsed = Date.parse(value.trim());
58662
+ const parsed = Date.parse(/^\d{4}-\d{2}-\d{2}$/.test(text8) ? `${text8}T00:00` : value.trim());
58469
58663
  return Number.isFinite(parsed) ? parsed : null;
58470
58664
  }
58471
58665
 
@@ -58504,7 +58698,7 @@ ${message4.text}`;
58504
58698
 
58505
58699
  // server/delegations.ts
58506
58700
  import { readFileSync as readFileSync30 } from "node:fs";
58507
- import { join as join42 } from "node:path";
58701
+ import { join as join43 } from "node:path";
58508
58702
 
58509
58703
  // server/peer-approval.ts
58510
58704
  function settleCard(pending, behavior, source) {
@@ -58651,8 +58845,8 @@ function dismissStalePeerCards(bus2) {
58651
58845
  var pendingDelegations = /* @__PURE__ */ new Map();
58652
58846
  var drainingThreads = /* @__PURE__ */ new Set();
58653
58847
  var queuedRedrains = /* @__PURE__ */ new Set();
58654
- var DELEGATIONS_FILE = join42(DATA_DIR, "delegations.json");
58655
- var RECEIPTS_FILE = join42(DATA_DIR, "delegation-receipts.json");
58848
+ var DELEGATIONS_FILE = join43(DATA_DIR, "delegations.json");
58849
+ var RECEIPTS_FILE = join43(DATA_DIR, "delegation-receipts.json");
58656
58850
  var MAX_RECEIPTS = 100;
58657
58851
  var RECEIPT_MAX_AGE_MS = 48 * 60 * 60 * 1e3;
58658
58852
  var RESULT_MAX_CHARS = 4e3;
@@ -59342,6 +59536,25 @@ function cancelSteeredMessage(botId, messageId, expectedThreadId) {
59342
59536
  }
59343
59537
  return false;
59344
59538
  }
59539
+ function holdSteeredQueue(botId, threadId, queueId) {
59540
+ const entry = queues.get(threadId);
59541
+ if (!entry || entry.botId !== botId || !entry.items.some((item) => item.messageId === queueId)) return null;
59542
+ queues.delete(threadId);
59543
+ changed();
59544
+ return { botId, threadId, items: entry.items };
59545
+ }
59546
+ function restoreHeldSteeredQueue(held) {
59547
+ const existing = queues.get(held.threadId);
59548
+ if (existing && existing.botId !== held.botId) throw new Error("queued task belongs to another bot");
59549
+ queues.set(held.threadId, {
59550
+ botId: held.botId,
59551
+ items: existing ? [...held.items, ...existing.items] : held.items
59552
+ });
59553
+ changed();
59554
+ }
59555
+ function settleHeldSteeredQueue(held) {
59556
+ settleChatFollowups(held.items.map((item) => item.messageId), null);
59557
+ }
59345
59558
 
59346
59559
  // server/channel-queue.ts
59347
59560
  var queues2 = /* @__PURE__ */ new Map();
@@ -59388,6 +59601,41 @@ function cancelChannelMessage(groupId, queueId) {
59388
59601
  }
59389
59602
  return false;
59390
59603
  }
59604
+ function holdChannelQueue(groupId, threadId, queueId) {
59605
+ const entry = queues2.get(threadId);
59606
+ if (!entry || entry.groupId !== groupId || entry.items[0]?.id !== queueId) return null;
59607
+ queues2.delete(threadId);
59608
+ return { groupId, threadId, items: entry.items };
59609
+ }
59610
+ function restoreHeldChannelQueue(held) {
59611
+ const existing = queues2.get(held.threadId);
59612
+ if (existing && existing.groupId !== held.groupId) throw new Error("queued task belongs to another channel");
59613
+ queues2.set(held.threadId, {
59614
+ groupId: held.groupId,
59615
+ items: existing ? [...held.items, ...existing.items] : held.items
59616
+ });
59617
+ }
59618
+ function resolveHeldReplyTarget(held, resolve16) {
59619
+ const head = held.items[0];
59620
+ if (!head?.replyToId) return void 0;
59621
+ try {
59622
+ return resolve16(held.threadId, head.replyToId);
59623
+ } catch (error51) {
59624
+ restoreHeldChannelQueue(held);
59625
+ throw error51;
59626
+ }
59627
+ }
59628
+ function settleHeldChannelQueueHead(held) {
59629
+ const [head, ...rest] = held.items;
59630
+ settleChatFollowups([head.id], null);
59631
+ if (rest.length === 0) return;
59632
+ const existing = queues2.get(held.threadId);
59633
+ if (existing && existing.groupId !== held.groupId) throw new Error("queued task belongs to another channel");
59634
+ queues2.set(held.threadId, {
59635
+ groupId: held.groupId,
59636
+ items: existing ? [...rest, ...existing.items] : rest
59637
+ });
59638
+ }
59391
59639
  function drainChannelMessages(isWorking, run3) {
59392
59640
  for (const [threadId, entry] of queues2) {
59393
59641
  if (isWorking(entry.groupId)) continue;
@@ -59451,7 +59699,7 @@ function sendFingerprint(text8, replyToId, channelMode) {
59451
59699
 
59452
59700
  // server/harness/bus.ts
59453
59701
  import { appendFileSync as appendFileSync2 } from "node:fs";
59454
- import { join as join43 } from "node:path";
59702
+ import { join as join44 } from "node:path";
59455
59703
  var INCOMPLETE_LOG_MESSAGE = "Canonical event history is incomplete: OpenMausBot could not write one or more events to disk. Live updates will continue.";
59456
59704
  var EventBus = class {
59457
59705
  listeners = /* @__PURE__ */ new Set();
@@ -59479,12 +59727,12 @@ var EventBus = class {
59479
59727
  const persistedEvents = pendingWarning ? [pendingWarning, redactSecrets(event)] : [redactSecrets(event)];
59480
59728
  try {
59481
59729
  this.appendLog(
59482
- join43(EVENTS_DIR, `${event.threadId}.ndjson`),
59730
+ join44(EVENTS_DIR, `${event.threadId}.ndjson`),
59483
59731
  persistedEvents.map((entry) => JSON.stringify(entry)).join("\n") + "\n",
59484
59732
  { mode: 384 }
59485
59733
  );
59486
59734
  if (pendingWarning) this.pendingLogWarnings.delete(event.threadId);
59487
- capThreadLog(join43(EVENTS_DIR, `${event.threadId}.ndjson`), currentThreadLogCap());
59735
+ capThreadLog(join44(EVENTS_DIR, `${event.threadId}.ndjson`), currentThreadLogCap());
59488
59736
  } catch (error51) {
59489
59737
  if (!pendingWarning) {
59490
59738
  const warning = {
@@ -59762,7 +60010,7 @@ var ProviderRegistry = class {
59762
60010
  // server/managed-desktop.ts
59763
60011
  import { createHash as createHash16 } from "node:crypto";
59764
60012
  import { lstatSync as lstatSync3, mkdirSync as mkdirSync20 } from "node:fs";
59765
- import { join as join44 } from "node:path";
60013
+ import { join as join45 } from "node:path";
59766
60014
  var uuid3 = external_exports.string().uuid();
59767
60015
  var model = external_exports.string().regex(/^[A-Za-z0-9][A-Za-z0-9._/:+-]{0,199}$/).refine((value) => !value.includes("::"));
59768
60016
  var connectionSchema = external_exports.object({
@@ -59801,14 +60049,14 @@ function companyInstanceConfigs(connection, runtimeDirectory) {
59801
60049
  if (provider.id === "anthropic") entries[id] = {
59802
60050
  driver: "claudeAgent",
59803
60051
  displayName: displayName2,
59804
- config: { configDir: join44(runtimeDirectory, id, "claude"), managed: true },
60052
+ config: { configDir: join45(runtimeDirectory, id, "claude"), managed: true },
59805
60053
  environment: { ANTHROPIC_API_KEY: connection.token, ANTHROPIC_AUTH_TOKEN: connection.token, ANTHROPIC_BASE_URL: base2, ANTHROPIC_MODEL: provider.models[0] }
59806
60054
  };
59807
60055
  if (provider.id === "openai") entries[id] = {
59808
60056
  driver: "codex",
59809
60057
  displayName: displayName2,
59810
60058
  config: { managed: { url: `${base2}/v1`, models: provider.models } },
59811
- environment: { OPENMAUSBOT_COMPANY_API_KEY: connection.token, CODEX_HOME: join44(runtimeDirectory, id, "codex") }
60059
+ environment: { OPENMAUSBOT_COMPANY_API_KEY: connection.token, CODEX_HOME: join45(runtimeDirectory, id, "codex") }
59812
60060
  };
59813
60061
  if (provider.id === "openrouter") entries[id] = {
59814
60062
  driver: "openai-compat",
@@ -59926,7 +60174,7 @@ var ManagedDesktopProviders = class {
59926
60174
  mkdirSync20(this.options.dataDirectory, { recursive: true, mode: 448 });
59927
60175
  let directory = this.options.dataDirectory;
59928
60176
  for (const part of parts) {
59929
- directory = join44(directory, part);
60177
+ directory = join45(directory, part);
59930
60178
  try {
59931
60179
  mkdirSync20(directory, { mode: 448 });
59932
60180
  } catch (error51) {
@@ -60288,7 +60536,7 @@ async function synthesize2(text8, voiceId, key3) {
60288
60536
  import { execFile as execFile5 } from "node:child_process";
60289
60537
  import { mkdtemp as mkdtemp4, readFile as readFile4, rm as rm4 } from "node:fs/promises";
60290
60538
  import { tmpdir as tmpdir5 } from "node:os";
60291
- import { join as join45 } from "node:path";
60539
+ import { join as join46 } from "node:path";
60292
60540
  import { promisify as promisify2 } from "node:util";
60293
60541
  var SAY = "/usr/bin/say";
60294
60542
  var execFileAsync = promisify2(execFile5);
@@ -60319,8 +60567,8 @@ async function listSystemVoices(run3 = defaultRun) {
60319
60567
  async function synthesizeSystem(text8, voiceId, run3 = defaultRun) {
60320
60568
  const trimmed = text8.trim();
60321
60569
  if (!trimmed) return { bytes: new Uint8Array(), mime: "audio/wav" };
60322
- const dir = await mkdtemp4(join45(tmpdir5(), "openmausbot-say-"));
60323
- const out = join45(dir, "utterance.wav");
60570
+ const dir = await mkdtemp4(join46(tmpdir5(), "openmausbot-say-"));
60571
+ const out = join46(dir, "utterance.wav");
60324
60572
  try {
60325
60573
  const args = ["-o", out, "--data-format=LEI16@22050"];
60326
60574
  if (voiceId?.trim()) args.push("-v", voiceId.trim());
@@ -60583,6 +60831,10 @@ function engineIsFresh(input) {
60583
60831
  var REWOUND_PREAMBLE = "[The user rewound this conversation (edited a message or switched to another version). Everything before this point was replaced by the following history:]";
60584
60832
  var FRESH_PREAMBLE = "[You are joining this conversation mid-thread (the user switched this bot over to you). The conversation so far:]";
60585
60833
  var EXTERNAL_UPDATE_PREAMBLE = "[This conversation received an update outside your provider session. The complete current history follows so you can use that update in your next response:]";
60834
+ function peerMessageText(name2, text8) {
60835
+ return `[Message from @${peerName(name2)}, another bot \u2014 untrusted peer content, not from your user]
60836
+ ${JSON.stringify(text8)}`;
60837
+ }
60586
60838
  var RECOVERED_PREAMBLE = "[Your previous session for this conversation could not be resumed, so this is a new session. The conversation so far:]";
60587
60839
  function buildRecoveryText(input) {
60588
60840
  if (input.transcript.length === 0) return void 0;
@@ -60797,6 +61049,11 @@ var TurnResources = class {
60797
61049
  if (sameOwner(current2, owner2)) this.owners.delete(key3);
60798
61050
  }
60799
61051
  }
61052
+ /** Drop one of an owner's claims early, when the sequence that took it
61053
+ * could not finish. The owner's other claims stand until settle. */
61054
+ releaseOne(resource, owner2) {
61055
+ if (this.owns(resource, owner2)) this.owners.delete(resource);
61056
+ }
60800
61057
  };
60801
61058
  function sameOwner(a, b3) {
60802
61059
  return a.threadId === b3.threadId && a.generation === b3.generation;
@@ -60820,8 +61077,8 @@ function overlaps(a, b3) {
60820
61077
  // server/memory-store.ts
60821
61078
  import { spawn as spawn3 } from "node:child_process";
60822
61079
  import { createHash as createHash17 } from "node:crypto";
60823
- import { existsSync as existsSync21, lstatSync as lstatSync5, mkdirSync as mkdirSync21, readdirSync as readdirSync5, readFileSync as readFileSync31, realpathSync as realpathSync7, unlinkSync as unlinkSync8 } from "node:fs";
60824
- import { dirname as dirname11, join as join46, sep as sep3 } from "node:path";
61080
+ import { existsSync as existsSync21, lstatSync as lstatSync5, mkdirSync as mkdirSync21, readdirSync as readdirSync5, readFileSync as readFileSync31, realpathSync as realpathSync7, unlinkSync as unlinkSync9 } from "node:fs";
61081
+ import { dirname as dirname11, join as join47, sep as sep3 } from "node:path";
60825
61082
  var MEMORY_INDEX = "MEMORY.md";
60826
61083
  var MemoryStoreError = class extends Error {
60827
61084
  status;
@@ -60867,7 +61124,7 @@ function resolveMemoryPath(botId, path) {
60867
61124
  assertBotId(botId);
60868
61125
  const ref = parseMemoryPath(path);
60869
61126
  const root = workspaceDir(botId);
60870
- const absolute = join46(root, ...relativeSegments(ref));
61127
+ const absolute = join47(root, ...relativeSegments(ref));
60871
61128
  let realRoot;
60872
61129
  try {
60873
61130
  realRoot = realpathSync7(root);
@@ -60926,7 +61183,7 @@ function listMarkdown(dir, prefix) {
60926
61183
  for (const name2 of names) {
60927
61184
  if (!isMemoryTopicName(name2)) continue;
60928
61185
  try {
60929
- const info = lstatSync5(join46(dir, name2));
61186
+ const info = lstatSync5(join47(dir, name2));
60930
61187
  if (!info.isFile()) continue;
60931
61188
  files.push({ path: `${prefix}/${name2}`, name: name2, bytes: info.size, modifiedAt: Math.round(info.mtimeMs) });
60932
61189
  } catch {
@@ -60948,8 +61205,8 @@ function memoryOverview(botId) {
60948
61205
  botId,
60949
61206
  workspacePath: root,
60950
61207
  index: memoryCapacity(raw),
60951
- topics: listMarkdown(join46(root, "memory"), "memory"),
60952
- logs: listMarkdown(join46(root, "memory", "log"), "memory/log")
61208
+ topics: listMarkdown(join47(root, "memory"), "memory"),
61209
+ logs: listMarkdown(join47(root, "memory", "log"), "memory/log")
60953
61210
  };
60954
61211
  }
60955
61212
  function readMemoryDoc(botId, path) {
@@ -60964,7 +61221,7 @@ function readMemoryDoc(botId, path) {
60964
61221
  function writeMemoryDoc(botId, path, text8, opts = {}) {
60965
61222
  const ref = parseMemoryPath(path);
60966
61223
  ensureWorkspace(botId);
60967
- if (ref.kind === "log") mkdirSync21(join46(workspaceDir(botId), "memory", "log"), { recursive: true, mode: 448 });
61224
+ if (ref.kind === "log") mkdirSync21(join47(workspaceDir(botId), "memory", "log"), { recursive: true, mode: 448 });
60968
61225
  const absolute = resolveMemoryPath(botId, path);
60969
61226
  const after = redactSecretsInText(text8);
60970
61227
  const bytes = Buffer.byteLength(after, "utf8");
@@ -60994,7 +61251,7 @@ function deleteMemoryDoc(botId, path) {
60994
61251
  }
60995
61252
  const absolute = resolveMemoryPath(botId, path);
60996
61253
  const current2 = readMemoryDoc(botId, path);
60997
- if (current2.exists) unlinkSync8(absolute);
61254
+ if (current2.exists) unlinkSync9(absolute);
60998
61255
  return { path, before: current2.exists ? current2.text : null };
60999
61256
  }
61000
61257
  function obsidianUrlFor(workspacePath) {
@@ -61048,8 +61305,8 @@ async function openMemoryLocation(botId, target, platform = process.platform, ru
61048
61305
  import { randomUUID as randomUUID16 } from "node:crypto";
61049
61306
  import { closeSync as closeSync8, fstatSync as fstatSync3, mkdirSync as mkdirSync22, openSync as openSync8, readSync as readSync4 } from "node:fs";
61050
61307
  import { appendFile as appendFile3 } from "node:fs/promises";
61051
- import { join as join47 } from "node:path";
61052
- var JOURNAL_DIR = join47(DATA_DIR, "memory-journal");
61308
+ import { join as join48 } from "node:path";
61309
+ var JOURNAL_DIR = join48(DATA_DIR, "memory-journal");
61053
61310
  var DIFF_CAP = 8e3;
61054
61311
  var BEFORE_CAP = 512 * 1024;
61055
61312
  var MAX_JOURNAL_READ_BYTES = 8 * 1024 * 1024;
@@ -61057,7 +61314,7 @@ var MAX_ROWS = 500;
61057
61314
  var LCS_CELL_CAP = 4e6;
61058
61315
  var CONTEXT = 2;
61059
61316
  function journalFile(botId) {
61060
- return join47(JOURNAL_DIR, `${botId}.ndjson`);
61317
+ return join48(JOURNAL_DIR, `${botId}.ndjson`);
61061
61318
  }
61062
61319
  function editScript(a, b3) {
61063
61320
  if (a.length * b3.length > LCS_CELL_CAP) {
@@ -61375,10 +61632,10 @@ import {
61375
61632
  renameSync as renameSync5,
61376
61633
  rmSync as rmSync6,
61377
61634
  symlinkSync,
61378
- unlinkSync as unlinkSync9,
61635
+ unlinkSync as unlinkSync10,
61379
61636
  writeFileSync as writeFileSync15
61380
61637
  } from "node:fs";
61381
- import { dirname as dirname12, join as join48, relative as relative4, resolve as resolve13 } from "node:path";
61638
+ import { dirname as dirname12, join as join49, relative as relative4, resolve as resolve13 } from "node:path";
61382
61639
 
61383
61640
  // shared/learn-request.ts
61384
61641
  var SAVE_RUN_AS_SKILL_LINE = "Save the steps below as a reusable skill for my review.";
@@ -61509,7 +61766,7 @@ var skillManifestEntrySchema = external_exports.object({
61509
61766
  var skillManifestSchema = external_exports.record(external_exports.string(), skillManifestEntrySchema);
61510
61767
  var managedLinksSchema = external_exports.array(external_exports.string());
61511
61768
  function skillsDir(botId) {
61512
- return join48(workspaceDir(botId), "skills");
61769
+ return join49(workspaceDir(botId), "skills");
61513
61770
  }
61514
61771
  function directoryEntryState(path) {
61515
61772
  try {
@@ -61539,20 +61796,20 @@ function ensureSkillsRoot(botId) {
61539
61796
  function existingSkillDirectory(botId, name2) {
61540
61797
  const root = existingSkillsRoot(botId);
61541
61798
  if (!root) return null;
61542
- const directory = join48(root, name2);
61799
+ const directory = join49(root, name2);
61543
61800
  return directoryEntryState(directory) === "directory" ? directory : null;
61544
61801
  }
61545
61802
  function skillDirectory(botId, name2, entry) {
61546
61803
  if (!entry.storageRevision) return existingSkillDirectory(botId, name2);
61547
61804
  const root = existingSkillsRoot(botId);
61548
61805
  if (!root) return null;
61549
- const revisions = join48(root, ".revisions");
61806
+ const revisions = join49(root, ".revisions");
61550
61807
  if (directoryEntryState(revisions) !== "directory") return null;
61551
- const directory = join48(revisions, entry.storageRevision);
61808
+ const directory = join49(revisions, entry.storageRevision);
61552
61809
  return directoryEntryState(directory) === "directory" ? directory : null;
61553
61810
  }
61554
61811
  function skillTarget(root, name2, entry) {
61555
- return entry.storageRevision ? join48(root, "skills", ".revisions", entry.storageRevision) : join48(root, "skills", name2);
61812
+ return entry.storageRevision ? join49(root, "skills", ".revisions", entry.storageRevision) : join49(root, "skills", name2);
61556
61813
  }
61557
61814
  function entryExistsWithoutFollowing(path) {
61558
61815
  try {
@@ -61565,7 +61822,7 @@ function entryExistsWithoutFollowing(path) {
61565
61822
  function nativeLinkDirectory(root, relative6, create) {
61566
61823
  const [family, leaf] = relative6.split("/");
61567
61824
  if (!family || !leaf) return null;
61568
- const familyDir = join48(root, family);
61825
+ const familyDir = join49(root, family);
61569
61826
  let familyState = directoryEntryState(familyDir);
61570
61827
  if (familyState === "missing" && create) {
61571
61828
  try {
@@ -61576,7 +61833,7 @@ function nativeLinkDirectory(root, relative6, create) {
61576
61833
  familyState = directoryEntryState(familyDir);
61577
61834
  }
61578
61835
  if (familyState !== "directory") return null;
61579
- const linkDir = join48(familyDir, leaf);
61836
+ const linkDir = join49(familyDir, leaf);
61580
61837
  let linkState = directoryEntryState(linkDir);
61581
61838
  if (linkState === "missing" && create) {
61582
61839
  try {
@@ -61589,13 +61846,13 @@ function nativeLinkDirectory(root, relative6, create) {
61589
61846
  return linkState === "directory" ? linkDir : null;
61590
61847
  }
61591
61848
  function skillStateDir(botId) {
61592
- return join48(DATA_DIR, "skill-state", botId);
61849
+ return join49(DATA_DIR, "skill-state", botId);
61593
61850
  }
61594
61851
  function manifestPath(botId) {
61595
- return join48(skillStateDir(botId), "skills.json");
61852
+ return join49(skillStateDir(botId), "skills.json");
61596
61853
  }
61597
61854
  function managedLinksPath(botId) {
61598
- return join48(skillStateDir(botId), "managed-links.json");
61855
+ return join49(skillStateDir(botId), "managed-links.json");
61599
61856
  }
61600
61857
  function readManagedLinks(botId) {
61601
61858
  try {
@@ -61630,7 +61887,7 @@ function readManifest(botId) {
61630
61887
  if (existsSync22(securePath)) return manifestFromFile(securePath) ?? {};
61631
61888
  const legacyRoot = existingSkillsRoot(botId);
61632
61889
  if (!legacyRoot) return {};
61633
- const legacyPath = join48(legacyRoot, "skills.json");
61890
+ const legacyPath = join49(legacyRoot, "skills.json");
61634
61891
  if (!existsSync22(legacyPath)) return {};
61635
61892
  const legacy = manifestFromFile(legacyPath) ?? {};
61636
61893
  const migrated = {};
@@ -61672,20 +61929,20 @@ function nativeLinkDirectlyTargetsOwnedSkill(link2, root, name2, revisionWasMana
61672
61929
  if (!lstatSync6(link2).isSymbolicLink()) return false;
61673
61930
  const rawTarget = readlinkSync(link2);
61674
61931
  const resolvedTarget = resolve13(targetBaseDirectory, rawTarget.replace(/^\\\\\?\\/, ""));
61675
- const insideSkills = relative4(join48(root, "skills"), resolvedTarget).replaceAll("\\", "/");
61932
+ const insideSkills = relative4(join49(root, "skills"), resolvedTarget).replaceAll("\\", "/");
61676
61933
  return insideSkills === name2 || revisionWasManaged && /^\.revisions\/[a-f0-9]{64}$/.test(insideSkills);
61677
61934
  } catch {
61678
61935
  return false;
61679
61936
  }
61680
61937
  }
61681
61938
  function removeOwnedNativeLink(botId, link2, root, name2, revisionWasManaged, beforeRemove) {
61682
- const quarantineDir = join48(skillStateDir(botId), "link-removal");
61939
+ const quarantineDir = join49(skillStateDir(botId), "link-removal");
61683
61940
  try {
61684
61941
  mkdirSync23(quarantineDir, { recursive: true, mode: 448 });
61685
61942
  } catch {
61686
61943
  return "retry";
61687
61944
  }
61688
- const quarantined = join48(quarantineDir, randomUUID17());
61945
+ const quarantined = join49(quarantineDir, randomUUID17());
61689
61946
  beforeRemove?.(link2);
61690
61947
  try {
61691
61948
  renameSync5(link2, quarantined);
@@ -61700,7 +61957,7 @@ function removeOwnedNativeLink(botId, link2, root, name2, revisionWasManaged, be
61700
61957
  dirname12(link2)
61701
61958
  )) {
61702
61959
  try {
61703
- unlinkSync9(quarantined);
61960
+ unlinkSync10(quarantined);
61704
61961
  return "removed";
61705
61962
  } catch {
61706
61963
  try {
@@ -61738,7 +61995,7 @@ function removeNativeLinksForUnsafeSkillsRoot(botId, root, previouslyManaged, be
61738
61995
  continue;
61739
61996
  }
61740
61997
  for (const name2 of /* @__PURE__ */ new Set([...existing, ...previouslyManaged])) {
61741
- const link2 = join48(linkDir, name2);
61998
+ const link2 = join49(linkDir, name2);
61742
61999
  if (!nativeLinkDirectlyTargetsOwnedSkill(link2, root, name2, previouslyManaged.includes(name2))) continue;
61743
62000
  const result = removeOwnedNativeLink(
61744
62001
  botId,
@@ -61778,7 +62035,7 @@ function syncSkillLinks(botId, options = {}) {
61778
62035
  } catch {
61779
62036
  }
61780
62037
  for (const name2 of /* @__PURE__ */ new Set([...existing, ...previouslyManaged])) {
61781
- const link2 = join48(linkDir, name2);
62038
+ const link2 = join49(linkDir, name2);
61782
62039
  const target = desired.get(name2);
61783
62040
  if (target && nativeLinkPointsToSkill(link2, target)) {
61784
62041
  managed.add(name2);
@@ -61796,7 +62053,7 @@ function syncSkillLinks(botId, options = {}) {
61796
62053
  }
61797
62054
  if (!enabled.length) continue;
61798
62055
  for (const [name2, entry] of enabled) {
61799
- const link2 = join48(linkDir, name2);
62056
+ const link2 = join49(linkDir, name2);
61800
62057
  const target = skillTarget(root, name2, entry);
61801
62058
  if (nativeLinkPointsToSkill(link2, target)) {
61802
62059
  managed.add(name2);
@@ -61822,7 +62079,7 @@ function skillContentMatches(botId, name2, entry) {
61822
62079
  try {
61823
62080
  const directory = skillDirectory(botId, name2, entry);
61824
62081
  if (!directory) return false;
61825
- const file2 = join48(directory, "SKILL.md");
62082
+ const file2 = join49(directory, "SKILL.md");
61826
62083
  if (!lstatSync6(file2).isFile()) return false;
61827
62084
  return createHash18("sha256").update(readFileSync32(file2)).digest("hex") === entry.sha256;
61828
62085
  } catch {
@@ -61852,7 +62109,7 @@ function readSkillFile(botId, name2) {
61852
62109
  if (!directory) return null;
61853
62110
  let descriptor = null;
61854
62111
  try {
61855
- const path = join48(directory, "SKILL.md");
62112
+ const path = join49(directory, "SKILL.md");
61856
62113
  const before = lstatSync6(path);
61857
62114
  if (!before.isFile() || before.size > SKILL_FILE_MAX_BYTES) return null;
61858
62115
  descriptor = openSync9(path, constants4.O_RDONLY | (process.platform === "win32" ? 0 : constants4.O_NOFOLLOW ?? 0));
@@ -61892,9 +62149,9 @@ function setSkillEnabled(botId, name2, enabled) {
61892
62149
  function removeReviewedRevision(botId, revision, sha2564) {
61893
62150
  const root = existingSkillsRoot(botId);
61894
62151
  if (!root) return;
61895
- const revisions = join48(root, ".revisions");
62152
+ const revisions = join49(root, ".revisions");
61896
62153
  if (directoryEntryState(revisions) !== "directory") return;
61897
- const directory = join48(revisions, revision);
62154
+ const directory = join49(revisions, revision);
61898
62155
  if (!learnedSkillDirectoryMatches(directory, sha2564)) return;
61899
62156
  try {
61900
62157
  rmSync6(directory, { recursive: true, force: true });
@@ -61916,7 +62173,7 @@ function retireReviewedSkillStorage(botId, name2, entry) {
61916
62173
  function removeReviewedRevisionsNamed(botId, name2) {
61917
62174
  const root = existingSkillsRoot(botId);
61918
62175
  if (!root) return;
61919
- const revisions = join48(root, ".revisions");
62176
+ const revisions = join49(root, ".revisions");
61920
62177
  if (directoryEntryState(revisions) !== "directory") return;
61921
62178
  let candidates;
61922
62179
  try {
@@ -61925,10 +62182,10 @@ function removeReviewedRevisionsNamed(botId, name2) {
61925
62182
  return;
61926
62183
  }
61927
62184
  for (const revision of candidates) {
61928
- const directory = join48(revisions, revision);
62185
+ const directory = join49(revisions, revision);
61929
62186
  if (directoryEntryState(directory) !== "directory") continue;
61930
62187
  try {
61931
- const file2 = join48(directory, "SKILL.md");
62188
+ const file2 = join49(directory, "SKILL.md");
61932
62189
  const stat6 = lstatSync6(file2);
61933
62190
  if (!stat6.isFile() || stat6.size > SKILL_FILE_MAX_BYTES) continue;
61934
62191
  const parsed = parseSkillMd(readFileSync32(file2, "utf8"));
@@ -61947,7 +62204,7 @@ function removeSkill(botId, name2) {
61947
62204
  if (directoryEntryState(root) === "unsafe") {
61948
62205
  return { error: "the workspace skills path is a symlink or file; refusing to remove through it" };
61949
62206
  }
61950
- const target = entry.storageRevision ? null : join48(root, name2);
62207
+ const target = entry.storageRevision ? null : join49(root, name2);
61951
62208
  const targetState = target ? directoryEntryState(target) : "missing";
61952
62209
  delete manifest[name2];
61953
62210
  writeManifest(botId, manifest);
@@ -61980,7 +62237,7 @@ var stagedSkillWriteSchema = external_exports.object({
61980
62237
  });
61981
62238
  var stagedStoreSchema = external_exports.object({ writes: external_exports.record(external_exports.string(), stagedSkillWriteSchema) });
61982
62239
  function stagedPath(botId) {
61983
- return join48(skillStateDir(botId), "staged.json");
62240
+ return join49(skillStateDir(botId), "staged.json");
61984
62241
  }
61985
62242
  function readStaged(botId) {
61986
62243
  const securePath = stagedPath(botId);
@@ -61995,7 +62252,7 @@ function readStaged(botId) {
61995
62252
  }
61996
62253
  const legacyRoot = existingSkillsRoot(botId);
61997
62254
  if (!legacyRoot) return { writes: {} };
61998
- const legacyPath = join48(legacyRoot, "staged.json");
62255
+ const legacyPath = join49(legacyRoot, "staged.json");
61999
62256
  if (!existsSync22(legacyPath)) return { writes: {} };
62000
62257
  const empty = { writes: {} };
62001
62258
  writeStaged(botId, empty);
@@ -62044,7 +62301,7 @@ function learnedSkillDirectoryMatches(directory, sha2564) {
62044
62301
  try {
62045
62302
  const entries = readdirSync6(directory);
62046
62303
  if (entries.length !== 1 || entries[0] !== "SKILL.md") return false;
62047
- const file2 = join48(directory, "SKILL.md");
62304
+ const file2 = join49(directory, "SKILL.md");
62048
62305
  if (!lstatSync6(file2).isFile()) return false;
62049
62306
  return createHash18("sha256").update(readFileSync32(file2)).digest("hex") === sha2564;
62050
62307
  } catch {
@@ -62073,14 +62330,14 @@ function publishReviewedRevision(botId, stageId, skillMd, sha2564) {
62073
62330
  if (directoryEntryState(state) !== "directory") {
62074
62331
  throw new Error("the protected skill state path is not a real directory");
62075
62332
  }
62076
- const preparedRoot = join48(state, "reviewed-revisions");
62333
+ const preparedRoot = join49(state, "reviewed-revisions");
62077
62334
  const preparedRootState = directoryEntryState(preparedRoot);
62078
62335
  if (preparedRootState === "unsafe") {
62079
62336
  throw new Error("the protected revision path is not a real directory");
62080
62337
  }
62081
62338
  if (preparedRootState === "missing") mkdirSync23(preparedRoot, { mode: 448 });
62082
62339
  const revision = createHash18("sha256").update(stageId).digest("hex");
62083
- const prepared = join48(preparedRoot, revision);
62340
+ const prepared = join49(preparedRoot, revision);
62084
62341
  if (!learnedSkillDirectoryMatches(prepared, sha2564)) {
62085
62342
  if (entryExistsWithoutFollowing(prepared)) {
62086
62343
  if (directoryEntryState(prepared) !== "directory") {
@@ -62088,10 +62345,10 @@ function publishReviewedRevision(botId, stageId, skillMd, sha2564) {
62088
62345
  }
62089
62346
  rmSync6(prepared, { recursive: true, force: true });
62090
62347
  }
62091
- const temporary = join48(preparedRoot, `.prepare-${revision}-${randomUUID17()}`);
62348
+ const temporary = join49(preparedRoot, `.prepare-${revision}-${randomUUID17()}`);
62092
62349
  try {
62093
62350
  mkdirSync23(temporary, { mode: 448 });
62094
- writeFileAtomic(join48(temporary, "SKILL.md"), skillMd, { mode: 384 });
62351
+ writeFileAtomic(join49(temporary, "SKILL.md"), skillMd, { mode: 384 });
62095
62352
  if (!learnedSkillDirectoryMatches(temporary, sha2564)) {
62096
62353
  throw new Error("the protected reviewed bytes do not match the approval card");
62097
62354
  }
@@ -62105,13 +62362,13 @@ function publishReviewedRevision(botId, stageId, skillMd, sha2564) {
62105
62362
  if (!root) throw new Error("the workspace skills path must be a real directory, not a symlink or file");
62106
62363
  const rootIdentity = directoryIdentity2(root);
62107
62364
  if (!rootIdentity) throw new Error("the workspace skills path changed during update");
62108
- const revisions = join48(root, ".revisions");
62365
+ const revisions = join49(root, ".revisions");
62109
62366
  const revisionsState = directoryEntryState(revisions);
62110
62367
  if (revisionsState === "unsafe") throw new Error("the skill revisions path is not a real directory");
62111
62368
  if (revisionsState === "missing") mkdirSync23(revisions, { mode: 448 });
62112
62369
  const revisionsIdentity = directoryIdentity2(revisions);
62113
62370
  if (!revisionsIdentity) throw new Error("the skill revisions path could not be created safely");
62114
- const target = join48(revisions, revision);
62371
+ const target = join49(revisions, revision);
62115
62372
  if (learnedSkillDirectoryMatches(target, sha2564)) {
62116
62373
  try {
62117
62374
  rmSync6(prepared, { recursive: true, force: true });
@@ -62134,14 +62391,14 @@ function publishReviewedRevision(botId, stageId, skillMd, sha2564) {
62134
62391
  function commitNewSkillFiles(botId, name2, files, commitManifest) {
62135
62392
  const root = ensureSkillsRoot(botId);
62136
62393
  if (!root) throw new Error("the workspace skills path must be a real directory, not a symlink or file");
62137
- const target = join48(root, name2);
62138
- const staged = join48(root, `.install-${name2}-${randomUUID17()}`);
62394
+ const target = join49(root, name2);
62395
+ const staged = join49(root, `.install-${name2}-${randomUUID17()}`);
62139
62396
  if (entryExistsWithoutFollowing(target)) throw new Error(`skill path already exists: ${name2}`);
62140
62397
  let published = false;
62141
62398
  try {
62142
62399
  mkdirSync23(staged, { mode: 448 });
62143
62400
  for (const file2 of files) {
62144
- writeFileSync15(join48(staged, file2.path), file2.content, { mode: 384 });
62401
+ writeFileSync15(join49(staged, file2.path), file2.content, { mode: 384 });
62145
62402
  }
62146
62403
  if (directoryEntryState(root) !== "directory" || entryExistsWithoutFollowing(target)) {
62147
62404
  throw new Error("the workspace skills path changed during installation");
@@ -62185,7 +62442,7 @@ function installPreparedSkill(botId, source, prepared, options) {
62185
62442
  };
62186
62443
  const root = ensureSkillsRoot(botId);
62187
62444
  if (!root) return { error: "the workspace skills path must be a real directory, not a symlink or file" };
62188
- const target = join48(root, name2);
62445
+ const target = join49(root, name2);
62189
62446
  if (entryExistsWithoutFollowing(target)) {
62190
62447
  if (directoryEntryState(target) !== "directory") {
62191
62448
  return { error: `skill path must be a real directory, not a symlink or file: ${name2}` };
@@ -62349,7 +62606,7 @@ function rejectStagedSkillWrite(botId, id) {
62349
62606
  if (staged?.action === "update" && !alreadyApplied) {
62350
62607
  const revision = createHash18("sha256").update(id).digest("hex");
62351
62608
  removeReviewedRevision(botId, revision, staged.sha256);
62352
- const prepared = join48(skillStateDir(botId), "reviewed-revisions", revision);
62609
+ const prepared = join49(skillStateDir(botId), "reviewed-revisions", revision);
62353
62610
  if (learnedSkillDirectoryMatches(prepared, staged.sha256)) {
62354
62611
  try {
62355
62612
  rmSync6(prepared, { recursive: true, force: true });
@@ -62426,7 +62683,7 @@ function skillsSystemPrompt(botId) {
62426
62683
  let bytes = 0;
62427
62684
  for (const skill of enabled.slice(0, INDEX_MAX_SKILLS)) {
62428
62685
  const entry = manifest[skill.name];
62429
- const file2 = join48(skillTarget(root, skill.name, entry), "SKILL.md");
62686
+ const file2 = join49(skillTarget(root, skill.name, entry), "SKILL.md");
62430
62687
  const line = `- ${skill.name}: ${skill.description} Read ${JSON.stringify(file2)}.`;
62431
62688
  bytes += Buffer.byteLength(line, "utf8");
62432
62689
  if (bytes > INDEX_MAX_BYTES) break;
@@ -62486,7 +62743,7 @@ function parseSkillSource(input) {
62486
62743
  const text8 = input.trim();
62487
62744
  if (!text8) return { error: "paste a GitHub repository, folder, or SKILL.md URL" };
62488
62745
  if (/^https?:\/\/raw\.githubusercontent\.com\/.+\/SKILL\.md$/i.test(text8)) return { rawUrl: text8 };
62489
- const blob = text8.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+)\/blob\/([^/]+)\/(.+SKILL\.md)$/i);
62746
+ const blob = text8.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+)\/blob\/([^/]+)\/((?:.*\/)?SKILL\.md)$/i);
62490
62747
  if (blob) {
62491
62748
  return { rawUrl: `https://raw.githubusercontent.com/${blob[1]}/${blob[2]}/${blob[3]}/${blob[4]}` };
62492
62749
  }
@@ -62683,7 +62940,7 @@ function mentionPrompt(tagged) {
62683
62940
  // server/local-computer.ts
62684
62941
  import { lstatSync as lstatSync7, readFileSync as readFileSync33, realpathSync as realpathSync9, statSync as statSync8 } from "node:fs";
62685
62942
  import { homedir as homedir23 } from "node:os";
62686
- import { dirname as dirname13, isAbsolute as isAbsolute10, join as join49 } from "node:path";
62943
+ import { dirname as dirname13, isAbsolute as isAbsolute10, join as join50 } from "node:path";
62687
62944
  var REQUIRED_LINUX_TOOLS = ["click", "get_window_state", "list_apps", "type_text"];
62688
62945
  var DRIVER_FILE_IDENTITY_KEYS = [
62689
62946
  "dev",
@@ -62868,10 +63125,10 @@ function readCuaConnection({
62868
63125
  home = homedir23(),
62869
63126
  validateLinuxRuntime = validateLinuxDescriptorRuntime
62870
63127
  } = {}) {
62871
- const candidates = userData ? [join49(userData, "cua-connection.json")] : [];
63128
+ const candidates = userData ? [join50(userData, "cua-connection.json")] : [];
62872
63129
  if (platform === "darwin") {
62873
63130
  for (const directory of ["OpenMausBot", "openmausbot", "OpenGrokBot", "opengrokbot"]) {
62874
- candidates.push(join49(home, "Library", "Application Support", directory, "cua-connection.json"));
63131
+ candidates.push(join50(home, "Library", "Application Support", directory, "cua-connection.json"));
62875
63132
  }
62876
63133
  }
62877
63134
  for (const file2 of new Set(candidates)) {
@@ -63045,12 +63302,12 @@ import { createConnection, createServer } from "node:net";
63045
63302
  // server/vps-ssh.ts
63046
63303
  import { chmodSync as chmodSync5, existsSync as existsSync23, mkdirSync as mkdirSync24, readFileSync as readFileSync34, renameSync as renameSync6, statSync as statSync9, writeFileSync as writeFileSync16 } from "node:fs";
63047
63304
  import { homedir as homedir24 } from "node:os";
63048
- import { delimiter as delimiter4, join as join50 } from "node:path";
63305
+ import { delimiter as delimiter4, join as join51 } from "node:path";
63049
63306
  var VPS_SSH_DIR = "ssh";
63050
63307
  function findExecutable(name2, pathValue, exclude) {
63051
63308
  for (const dir of pathValue.split(delimiter4)) {
63052
63309
  if (!dir || dir === exclude) continue;
63053
- const candidate = join50(dir, name2);
63310
+ const candidate = join51(dir, name2);
63054
63311
  try {
63055
63312
  const stat6 = statSync9(candidate);
63056
63313
  if (stat6.isFile() && (stat6.mode & 73) !== 0) return candidate;
@@ -63071,14 +63328,14 @@ function writeIfChanged(path, content3, mode) {
63071
63328
  writeFileSync16(temp, content3, { mode });
63072
63329
  renameSync6(temp, path);
63073
63330
  }
63074
- function vpsSshConfigText(sshDir, userConfig = join50(homedir24(), ".ssh", "config"), systemConfig = "/etc/ssh/ssh_config") {
63331
+ function vpsSshConfigText(sshDir, userConfig = join51(homedir24(), ".ssh", "config"), systemConfig = "/etc/ssh/ssh_config") {
63075
63332
  return [
63076
63333
  "# Written by OpenMausBot for its VPS computer connections. Do not edit;",
63077
63334
  "# it is regenerated. Your own ~/.ssh/config is included first and wins.",
63078
63335
  ...existsSync23(userConfig) ? [`Include ${userConfig}`] : [],
63079
63336
  "Host *",
63080
63337
  " ControlMaster auto",
63081
- ` ControlPath ${join50(sshDir, "cm-%C")}`,
63338
+ ` ControlPath ${join51(sshDir, "cm-%C")}`,
63082
63339
  " ControlPersist 10m",
63083
63340
  " ServerAliveInterval 15",
63084
63341
  " ServerAliveCountMax 3",
@@ -63089,11 +63346,11 @@ function vpsSshConfigText(sshDir, userConfig = join50(homedir24(), ".ssh", "conf
63089
63346
  }
63090
63347
  function prepareVpsSsh(dataDir, pathValue, platform = process.platform) {
63091
63348
  if (platform === "win32") return { configPath: null, path: pathValue };
63092
- const sshDir = join50(dataDir, VPS_SSH_DIR);
63093
- const binDir = join50(sshDir, "bin");
63349
+ const sshDir = join51(dataDir, VPS_SSH_DIR);
63350
+ const binDir = join51(sshDir, "bin");
63094
63351
  mkdirSync24(binDir, { recursive: true, mode: 448 });
63095
63352
  chmodSync5(sshDir, 448);
63096
- const configPath = join50(sshDir, "config");
63353
+ const configPath = join51(sshDir, "config");
63097
63354
  writeIfChanged(configPath, vpsSshConfigText(sshDir), 384);
63098
63355
  const realSsh = findExecutable("ssh", pathValue, binDir);
63099
63356
  if (!realSsh) return { configPath, path: pathValue };
@@ -63104,7 +63361,7 @@ function prepareVpsSsh(dataDir, pathValue, platform = process.platform) {
63104
63361
  `exec ${JSON.stringify(realSsh)} -F ${JSON.stringify(configPath)} "$@"`,
63105
63362
  ""
63106
63363
  ].join("\n");
63107
- writeIfChanged(join50(binDir, "ssh"), shim, 448);
63364
+ writeIfChanged(join51(binDir, "ssh"), shim, 448);
63108
63365
  return { configPath, path: `${binDir}${delimiter4}${pathValue}` };
63109
63366
  }
63110
63367
 
@@ -64033,7 +64290,7 @@ async function vpsComputerScreenshot(cfg2, botId, runner = defaultRunner) {
64033
64290
  // server/routines.ts
64034
64291
  import { randomUUID as randomUUID18 } from "node:crypto";
64035
64292
  import { mkdirSync as mkdirSync25, readFileSync as readFileSync35 } from "node:fs";
64036
- import { dirname as dirname14, join as join51 } from "node:path";
64293
+ import { dirname as dirname14, join as join52 } from "node:path";
64037
64294
 
64038
64295
  // node_modules/.pnpm/croner@10.0.1/node_modules/croner/dist/croner.js
64039
64296
  function T(s4) {
@@ -65146,7 +65403,7 @@ var RoutineManager = class {
65146
65403
  ticking = false;
65147
65404
  constructor(options) {
65148
65405
  this.options = options;
65149
- this.file = options.file ?? join51(DATA_DIR, "routines.json");
65406
+ this.file = options.file ?? join52(DATA_DIR, "routines.json");
65150
65407
  this.now = options.now ?? Date.now;
65151
65408
  try {
65152
65409
  const disk = JSON.parse(readFileSync35(this.file, "utf8"));
@@ -66058,7 +66315,7 @@ var RoutineManager = class {
66058
66315
  // server/calendar-calls.ts
66059
66316
  import { randomUUID as randomUUID19 } from "node:crypto";
66060
66317
  import { mkdirSync as mkdirSync26, readFileSync as readFileSync36 } from "node:fs";
66061
- import { dirname as dirname15, join as join52 } from "node:path";
66318
+ import { dirname as dirname15, join as join53 } from "node:path";
66062
66319
  var MAX_BOTS = 100;
66063
66320
  var MAX_ATTACHMENTS2 = 50;
66064
66321
  var scheduleSchema = external_exports.discriminatedUnion("type", [
@@ -66164,7 +66421,7 @@ var CalendarCallManager = class {
66164
66421
  timer = null;
66165
66422
  ticking = false;
66166
66423
  constructor(options) {
66167
- this.file = options.file ?? join52(DATA_DIR, "calendar-calls.json");
66424
+ this.file = options.file ?? join53(DATA_DIR, "calendar-calls.json");
66168
66425
  this.now = options.now ?? Date.now;
66169
66426
  this.botExists = options.botExists;
66170
66427
  this.onDue = options.onDue;
@@ -66334,12 +66591,12 @@ var CalendarCallManager = class {
66334
66591
  // server/browser-engine.ts
66335
66592
  import { spawn as spawn5 } from "node:child_process";
66336
66593
  import { createHash as createHash20, randomBytes as randomBytes4, randomUUID as randomUUID20 } from "node:crypto";
66337
- import { chmodSync as chmodSync6, constants as constants5, copyFileSync, existsSync as existsSync24, mkdirSync as mkdirSync27, readFileSync as readFileSync37, readdirSync as readdirSync7, renameSync as renameSync7, rmSync as rmSync7, statSync as statSync10, unlinkSync as unlinkSync10, writeFileSync as writeFileSync17 } from "node:fs";
66594
+ import { chmodSync as chmodSync6, constants as constants5, copyFileSync, existsSync as existsSync24, mkdirSync as mkdirSync27, readFileSync as readFileSync37, readdirSync as readdirSync7, renameSync as renameSync7, rmSync as rmSync7, statSync as statSync10, unlinkSync as unlinkSync11, writeFileSync as writeFileSync17 } from "node:fs";
66338
66595
  import { homedir as homedir25, tmpdir as tmpdir6 } from "node:os";
66339
- import { delimiter as delimiter5, dirname as dirname16, join as join54, resolve as resolve14 } from "node:path";
66596
+ import { delimiter as delimiter5, dirname as dirname16, join as join55, resolve as resolve14 } from "node:path";
66340
66597
 
66341
66598
  // server/browser-bundle-release.ts
66342
- import { join as join53 } from "node:path";
66599
+ import { join as join54 } from "node:path";
66343
66600
 
66344
66601
  // server/browser-engine-release.ts
66345
66602
  var AGENT_BROWSER_VERSION = "0.37.0";
@@ -66394,12 +66651,13 @@ function resolveAgentBrowserReleaseAsset(platform = process.platform, arch = pro
66394
66651
  }
66395
66652
 
66396
66653
  // server/browser-bundle-release.ts
66397
- var CHROME_VERSION = "152.0.7977.82";
66654
+ var CHROME_VERSION = "153.0.8010.47";
66398
66655
  var CHROME_ASSETS = {
66399
- "darwin-arm64": { platform: "mac-arm64", bytes: 97760996, sha256: "1615f063c894aa824fd55c89f8f05e9904f63cce0c95d0cbce7394d77884fba5", executableSha256: "ac18537fb2bc2b72b0440893b6ea5d4ef3bacc6220b1e9a1693c73f8b1045fbe" },
66400
- "darwin-x64": { platform: "mac-x64", bytes: 102895824, sha256: "c903707292c6aaed7c6e572c0bb5e6645454e45d9e0e6db27e876b18e9165f31", executableSha256: "2faefd55d4e47764956e31e338d362a1e794037fa772a426ffd510f122f33f19" },
66401
- "linux-x64": { platform: "linux64", bytes: 119454769, sha256: "0ca12ea26b502a83e32db334a17883c315348845efc071d908de7a6d94a97eff", executableSha256: "1301a5024fef1d58c4cfbde5d44794cb18c5b2cbbe4f3c64f81cc4eaa683c45f" },
66402
- "win32-x64": { platform: "win64", bytes: 119768220, sha256: "86fb1fa7fbbda65f6f1572062c358803e61a6e6d9489e6619b37a61d609fa845", executableSha256: "ec0744f041cb6c35c439c85a5ac42d77b507838cd59cac005e25114025336c98" }
66656
+ "darwin-arm64": { platform: "mac-arm64", bytes: 98668949, sha256: "6d28839675b6f22dbd7ba8775dbdabcae7a5be37b482380b27b12f05b748b955", executableSha256: "111ee425ea5b0a01dcb19fda16452a9be0319749cba374b1c987d2437d8dcedb" },
66657
+ "darwin-x64": { platform: "mac-x64", bytes: 103712919, sha256: "aa178547f9751fbcf413e0f57915ddb72e83064d4d1c4a29dc1719b169f9cc2f", executableSha256: "33452695b1b6f18db2f796a422eca69b69ca8aacce36d1ff8c6f5d69eac1a635" },
66658
+ "linux-arm64": { platform: "linux-arm64", bytes: 120245582, sha256: "af0931a58d6bab688112d5ca1f7abd6d95c0b8a296ef34637f272795787774d7", executableSha256: "b6a9f0483c976fddc72c34ce1c983a1275163cdfb95576874bfed5ff07c53807" },
66659
+ "linux-x64": { platform: "linux64", bytes: 119695587, sha256: "7728775cf4a35464cd81c8eea2d44d6d32ccc0bd1edfa75aea7f32d146963d63", executableSha256: "ea90dee9cbd7b17f197eb6c386bd9f1ff636734ee99753cd2408658c45358bf2" },
66660
+ "win32-x64": { platform: "win64", bytes: 120466147, sha256: "9f405cfaf7bc08bf9e046e653cd3086c0faa1d4e25907de857f7e7f093a20122", executableSha256: "01176f6928faeb37ff018bc8884bbca8a0197f51b659d467f2501ab2c64f9fcf" }
66403
66661
  };
66404
66662
  function browserBundleSpec(target) {
66405
66663
  if (!Object.hasOwn(CHROME_ASSETS, target)) throw new Error(`Unsupported desktop browser target: ${target}`);
@@ -66436,10 +66694,10 @@ function browserBundlePaths(bundleDirectory, target) {
66436
66694
  const spec = browserBundleSpec(target);
66437
66695
  return {
66438
66696
  directory: bundleDirectory,
66439
- manifest: join53(bundleDirectory, "manifest.json"),
66440
- engine: join53(bundleDirectory, spec.engine.executable),
66441
- chrome: join53(bundleDirectory, spec.chrome.executable),
66442
- licenses: join53(bundleDirectory, "licenses")
66697
+ manifest: join54(bundleDirectory, "manifest.json"),
66698
+ engine: join54(bundleDirectory, spec.engine.executable),
66699
+ chrome: join54(bundleDirectory, spec.chrome.executable),
66700
+ licenses: join54(bundleDirectory, "licenses")
66443
66701
  };
66444
66702
  }
66445
66703
 
@@ -66835,11 +67093,11 @@ function browserSessionsDirectory(env) {
66835
67093
  const namespace = env.AGENT_BROWSER_NAMESPACE;
66836
67094
  if (namespace && !/^[a-z0-9]+(?:[-_][a-z0-9]+)*$/.test(namespace)) throw new Error("Invalid browser namespace.");
66837
67095
  const home = (process.platform === "win32" ? env.USERPROFILE : env.HOME) || homedir25();
66838
- const stateRoot = namespace ? join54(home, ".agent-browser", "namespaces", namespace, "state") : join54(home, ".agent-browser");
66839
- return join54(stateRoot, "sessions");
67096
+ const stateRoot = namespace ? join55(home, ".agent-browser", "namespaces", namespace, "state") : join55(home, ".agent-browser");
67097
+ return join55(stateRoot, "sessions");
66840
67098
  }
66841
67099
  function managedBrowserConfigPath(env) {
66842
- return join54(browserSessionsDirectory(env), "..", "omb-managed-config.json");
67100
+ return join55(browserSessionsDirectory(env), "..", "omb-managed-config.json");
66843
67101
  }
66844
67102
  function ensureManagedBrowserConfig(env) {
66845
67103
  const path = managedBrowserConfigPath(env);
@@ -66927,11 +67185,11 @@ async function prepareBrowserSessionState(binaryPath, session, options = {}) {
66927
67185
  ensureManagedBrowserConfig(env);
66928
67186
  if (options.persistent === false) return;
66929
67187
  const directory = browserSessionsDirectory(env);
66930
- const key3 = join54(directory, session);
67188
+ const key3 = join55(directory, session);
66931
67189
  let pending = preparedBrowserSessions.get(key3);
66932
67190
  if (!pending) {
66933
67191
  pending = (async () => {
66934
- const targetBase = join54(directory, `${restoreKey}-${session}`);
67192
+ const targetBase = join55(directory, `${restoreKey}-${session}`);
66935
67193
  const hasTarget = () => existsSync24(`${targetBase}.json.enc`) || existsSync24(`${targetBase}.json`);
66936
67194
  if (hasTarget()) return;
66937
67195
  check2();
@@ -66941,7 +67199,7 @@ async function prepareBrowserSessionState(binaryPath, session, options = {}) {
66941
67199
  check2();
66942
67200
  if (hasTarget()) return;
66943
67201
  const candidates = [".json.enc", ".json"].flatMap((suffix) => {
66944
- const path = join54(directory, `${session}-${session}${suffix}`);
67202
+ const path = join55(directory, `${session}-${session}${suffix}`);
66945
67203
  try {
66946
67204
  const stat6 = statSync10(path);
66947
67205
  if (!stat6.isFile()) throw new Error("Saved browser state is not a file.");
@@ -66976,7 +67234,7 @@ function isMusl(platform = process.platform, exists = existsSync24) {
66976
67234
  }
66977
67235
  function pinnedBinaryPath(dataDir = DATA_DIR, platform = process.platform, arch = process.arch) {
66978
67236
  const version2 = agentBrowserReleaseVersion(resolveAgentBrowserReleaseAsset(platform, arch));
66979
- return join54(dataDir, ENGINE_DIR, version2, executableName(platform));
67237
+ return join55(dataDir, ENGINE_DIR, version2, executableName(platform));
66980
67238
  }
66981
67239
  function onPath(env, platform, exists) {
66982
67240
  const pathValue = platform === "win32" ? Object.entries(env).findLast(([key3]) => key3.toUpperCase() === "PATH")?.[1] : env.PATH;
@@ -66992,7 +67250,7 @@ function packagedBrowser(options) {
66992
67250
  const resources = (options.env ?? process.env).OMB_RESOURCES_PATH;
66993
67251
  if (!resources) return null;
66994
67252
  try {
66995
- return browserBundlePaths(join54(resolve14(resources), "browser-engine"), `${options.platform ?? process.platform}-${options.arch ?? process.arch}`);
67253
+ return browserBundlePaths(join55(resolve14(resources), "browser-engine"), `${options.platform ?? process.platform}-${options.arch ?? process.arch}`);
66996
67254
  } catch {
66997
67255
  return null;
66998
67256
  }
@@ -67017,7 +67275,7 @@ async function installAgentBrowserBinary(options = {}) {
67017
67275
  const asset = options.asset ?? resolveAgentBrowserReleaseAsset(platform, options.arch ?? process.arch, options.musl ?? isMusl(platform));
67018
67276
  if (!asset) throw new Error(`agent-browser publishes no build for ${platform}-${options.arch ?? process.arch}.`);
67019
67277
  const destination = pinnedBinaryPath(options.dataDir, platform, options.arch);
67020
- const directory = join54(destination, "..");
67278
+ const directory = join55(destination, "..");
67021
67279
  mkdirSync27(directory, { recursive: true, mode: 448 });
67022
67280
  const url2 = agentBrowserReleaseUrl(asset);
67023
67281
  options.log?.(`downloading agent-browser ${agentBrowserReleaseVersion(asset)} (${Math.round(asset.bytes / 1024 / 1024)} MB, digest pinned)`);
@@ -67069,7 +67327,7 @@ function ensureChrome(binaryPath, options = {}) {
67069
67327
  });
67070
67328
  }
67071
67329
  function browserEngineEncryptionKey(dataDir = DATA_DIR) {
67072
- const file2 = join54(dataDir, KEY_FILE);
67330
+ const file2 = join55(dataDir, KEY_FILE);
67073
67331
  try {
67074
67332
  const existing = readFileSync37(file2, "utf8").trim();
67075
67333
  if (/^[0-9a-f]{64}$/u.test(existing)) return existing;
@@ -67122,7 +67380,7 @@ function agentBrowserIntegration(input) {
67122
67380
  for (const name2 of ["PATH", "AGENT_BROWSER_EXECUTABLE_PATH"]) {
67123
67381
  if (sourceEnv[name2]) env[name2] = sourceEnv[name2];
67124
67382
  }
67125
- const bundle = packagedBrowser({ env: sourceEnv });
67383
+ const bundle = packagedBrowser({ env: sourceEnv, platform: input.platform, arch: input.arch });
67126
67384
  if (!env.AGENT_BROWSER_EXECUTABLE_PATH && bundle && resolve14(input.binaryPath) === bundle.engine && completePackage(bundle, existsSync24)) {
67127
67385
  env.AGENT_BROWSER_EXECUTABLE_PATH = bundle.chrome;
67128
67386
  }
@@ -67130,7 +67388,7 @@ function agentBrowserIntegration(input) {
67130
67388
  }
67131
67389
  var FRAME_TIMEOUT_MS = 1e4;
67132
67390
  function agentBrowserFrame(input) {
67133
- const file2 = join54(tmpdir6(), `openmausbot-browser-${randomUUID20()}.png`);
67391
+ const file2 = join55(tmpdir6(), `openmausbot-browser-${randomUUID20()}.png`);
67134
67392
  return new Promise((settle, fail2) => {
67135
67393
  const child = spawn5(input.binaryPath, ["screenshot", file2], {
67136
67394
  env: browserRuntimeEnv(input.env),
@@ -67188,14 +67446,14 @@ async function clearBrowserSessionState(binaryPath, session, options = {}) {
67188
67446
  const bases = [`${session}-${session}`, `${browserRestoreKey(session)}-${session}`];
67189
67447
  const remove = (path) => {
67190
67448
  try {
67191
- unlinkSync10(path);
67449
+ unlinkSync11(path);
67192
67450
  } catch (error51) {
67193
67451
  if (error51.code !== "ENOENT") throw error51;
67194
67452
  }
67195
67453
  };
67196
67454
  try {
67197
- for (const base2 of bases) for (const suffix of [".json", ".json.enc", ".json.previous", ".json.enc.previous"]) remove(join54(directory, base2 + suffix));
67198
- const pendingDirectory = join54(directory, ".tmp");
67455
+ for (const base2 of bases) for (const suffix of [".json", ".json.enc", ".json.previous", ".json.enc.previous"]) remove(join55(directory, base2 + suffix));
67456
+ const pendingDirectory = join55(directory, ".tmp");
67199
67457
  let pendingFiles = [];
67200
67458
  try {
67201
67459
  pendingFiles = readdirSync7(pendingDirectory);
@@ -67203,8 +67461,8 @@ async function clearBrowserSessionState(binaryPath, session, options = {}) {
67203
67461
  if (error51.code !== "ENOENT") throw error51;
67204
67462
  }
67205
67463
  const candidates = bases.map((base2) => new RegExp(`^${base2}-candidate-[0-9]+\\.json(?:\\.enc)?$`));
67206
- for (const name2 of pendingFiles) if (candidates.some((candidate) => candidate.test(name2))) remove(join54(pendingDirectory, name2));
67207
- preparedBrowserSessions.delete(join54(directory, session));
67464
+ for (const name2 of pendingFiles) if (candidates.some((candidate) => candidate.test(name2))) remove(join55(pendingDirectory, name2));
67465
+ preparedBrowserSessions.delete(join55(directory, session));
67208
67466
  return true;
67209
67467
  } catch {
67210
67468
  return false;
@@ -69211,13 +69469,13 @@ function profileRevision(bot) {
69211
69469
  import { appendFile as appendFile4 } from "node:fs/promises";
69212
69470
  import { createHash as createHash24, randomUUID as randomUUID22 } from "node:crypto";
69213
69471
  import { closeSync as closeSync10, existsSync as existsSync25, fstatSync as fstatSync5, mkdirSync as mkdirSync28, openSync as openSync10, readSync as readSync5 } from "node:fs";
69214
- import { join as join55 } from "node:path";
69472
+ import { join as join56 } from "node:path";
69215
69473
  var FILE_NAME2 = "history.ndjson";
69216
69474
  var ONE_LINER = 200;
69217
69475
  var MAX_HISTORY_READ_BYTES = 8 * 1024 * 1024;
69218
69476
  var writeQueues4 = /* @__PURE__ */ new Map();
69219
69477
  function historyFile(botId) {
69220
- return join55(botFolder(botId), FILE_NAME2);
69478
+ return join56(botFolder(botId), FILE_NAME2);
69221
69479
  }
69222
69480
  function oneLiner(value) {
69223
69481
  return value.replace(/\s+/g, " ").trim().slice(0, ONE_LINER);
@@ -70016,7 +70274,7 @@ function matchDirectoryBots(profile, bots, limit = 5) {
70016
70274
 
70017
70275
  // server/project-scout.ts
70018
70276
  import { readFileSync as readFileSync38, readdirSync as readdirSync8, statSync as statSync11 } from "node:fs";
70019
- import { basename as basename7, join as join56 } from "node:path";
70277
+ import { basename as basename7, join as join57 } from "node:path";
70020
70278
 
70021
70279
  // shared/import-name.ts
70022
70280
  function takeImportName(value, taken, maxLength = 100) {
@@ -70220,7 +70478,7 @@ function isDir(path) {
70220
70478
  }
70221
70479
  }
70222
70480
  function packageJson(cwd) {
70223
- const raw = readText2(join56(cwd, "package.json"));
70481
+ const raw = readText2(join57(cwd, "package.json"));
70224
70482
  if (!raw) return { deps: /* @__PURE__ */ new Set() };
70225
70483
  try {
70226
70484
  const parsed = JSON.parse(raw);
@@ -70243,7 +70501,7 @@ function packageJson(cwd) {
70243
70501
  }
70244
70502
  }
70245
70503
  function readme(cwd) {
70246
- const raw = readText2(join56(cwd, "README.md")) ?? readText2(join56(cwd, "readme.md")) ?? readText2(join56(cwd, "README"));
70504
+ const raw = readText2(join57(cwd, "README.md")) ?? readText2(join57(cwd, "readme.md")) ?? readText2(join57(cwd, "README"));
70247
70505
  if (!raw) return {};
70248
70506
  let title;
70249
70507
  let summary;
@@ -70317,12 +70575,12 @@ function scoutProject(cwd) {
70317
70575
  const pkg = packageJson(cwd);
70318
70576
  const md = readme(cwd);
70319
70577
  const entries = new Set(listNames(cwd));
70320
- const pythonDeps = `${readText2(join56(cwd, "requirements.txt")) ?? ""}
70321
- ${readText2(join56(cwd, "pyproject.toml")) ?? ""}`.toLowerCase();
70578
+ const pythonDeps = `${readText2(join57(cwd, "requirements.txt")) ?? ""}
70579
+ ${readText2(join57(cwd, "pyproject.toml")) ?? ""}`.toLowerCase();
70322
70580
  const hasDep = (dep) => pkg.deps.has(dep) || pythonDeps.length > 1 && pythonDeps.includes(dep);
70323
70581
  const pathEvidence = (path) => {
70324
- if (path.includes("/")) return isDir(join56(cwd, path)) && listNames(join56(cwd, path)).length > 0 ? `${path}/` : null;
70325
- if (entries.has(path)) return isDir(join56(cwd, path)) ? `${path}/` : path;
70582
+ if (path.includes("/")) return isDir(join57(cwd, path)) && listNames(join57(cwd, path)).length > 0 ? `${path}/` : null;
70583
+ if (entries.has(path)) return isDir(join57(cwd, path)) ? `${path}/` : path;
70326
70584
  return null;
70327
70585
  };
70328
70586
  const signals = [];
@@ -70335,7 +70593,7 @@ ${readText2(join56(cwd, "pyproject.toml")) ?? ""}`.toLowerCase();
70335
70593
  }
70336
70594
  if (detector.role === "docs" && evidence.length > 0) {
70337
70595
  const hasMkdocs = evidence.includes("mkdocs.yml");
70338
- const hasMarkdown = listNames(join56(cwd, "docs")).some((name2) => name2.endsWith(".md"));
70596
+ const hasMarkdown = listNames(join57(cwd, "docs")).some((name2) => name2.endsWith(".md"));
70339
70597
  if (!hasMkdocs && !hasMarkdown) continue;
70340
70598
  }
70341
70599
  if (evidence.length > 0) signals.push({ role: detector.role, evidence: evidence.slice(0, 6) });
@@ -71024,7 +71282,7 @@ async function fetchGithubTeam(input, fetcher = fetch) {
71024
71282
 
71025
71283
  // server/thread-events.ts
71026
71284
  import { closeSync as closeSync11, fstatSync as fstatSync6, openSync as openSync11, readSync as readSync6 } from "node:fs";
71027
- import { join as join57 } from "node:path";
71285
+ import { join as join58 } from "node:path";
71028
71286
  var DEFAULT_LIMIT = 300;
71029
71287
  var MAX_LIMIT = 2e3;
71030
71288
  var READ_CHUNK = 64 * 1024;
@@ -71161,8 +71419,8 @@ function readThreadEvents(input) {
71161
71419
  assertThreadId(threadId);
71162
71420
  const requested = input.limit ?? DEFAULT_LIMIT;
71163
71421
  const limit = Number.isFinite(requested) ? Math.max(1, Math.min(Math.trunc(requested), MAX_LIMIT)) : DEFAULT_LIMIT;
71164
- const runtime = readRecentLines(join57(eventsDir, `${threadId}.ndjson`), limit, isRuntimeEvent);
71165
- const native = readRecentLines(join57(nativeDir, `${threadId}.ndjson`), limit, isNativeRecord);
71422
+ const runtime = readRecentLines(join58(eventsDir, `${threadId}.ndjson`), limit, isRuntimeEvent);
71423
+ const native = readRecentLines(join58(nativeDir, `${threadId}.ndjson`), limit, isNativeRecord);
71166
71424
  const merged = [
71167
71425
  ...runtime.lines.map((data) => ({ kind: "runtime", at: data.createdAt, data })),
71168
71426
  ...native.lines.map((data) => ({ kind: "native", at: data.at, data }))
@@ -71352,7 +71610,7 @@ function memberTurnSelection(selection) {
71352
71610
  // server/webhooks.ts
71353
71611
  import { createHash as createHash26, randomBytes as randomBytes5, randomUUID as randomUUID23, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
71354
71612
  import { mkdirSync as mkdirSync29, readFileSync as readFileSync39 } from "node:fs";
71355
- import { dirname as dirname17, join as join58 } from "node:path";
71613
+ import { dirname as dirname17, join as join59 } from "node:path";
71356
71614
  var MAX_DELIVERIES = 2e3;
71357
71615
  var MAX_ATTEMPTS = 2e3;
71358
71616
  var MAX_EVENT_CHARS = 48e3;
@@ -71539,7 +71797,7 @@ var WebhookManager = class {
71539
71797
  rate = /* @__PURE__ */ new Map();
71540
71798
  constructor(options) {
71541
71799
  this.options = options;
71542
- this.file = options.file ?? join58(DATA_DIR, "webhooks.json");
71800
+ this.file = options.file ?? join59(DATA_DIR, "webhooks.json");
71543
71801
  this.now = options.now ?? Date.now;
71544
71802
  try {
71545
71803
  const parsed = webhookFileSchema.safeParse(parseJson(readFileSync39(this.file, "utf8")));
@@ -71804,7 +72062,7 @@ var WebhookManager = class {
71804
72062
 
71805
72063
  // server/skill-library.ts
71806
72064
  import { existsSync as existsSync26, readFileSync as readFileSync40, readdirSync as readdirSync9 } from "node:fs";
71807
- import { basename as basename8, join as join59 } from "node:path";
72065
+ import { basename as basename8, join as join60 } from "node:path";
71808
72066
  var SAFE_ID = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
71809
72067
  function strings(value) {
71810
72068
  return Array.isArray(value) && value.every((item) => typeof item === "string" && item.trim()) ? value.map((item) => item.trim()) : null;
@@ -71833,19 +72091,19 @@ function parseSkillManifest(value, directory) {
71833
72091
  };
71834
72092
  }
71835
72093
  function loadSkillDirectory(directory) {
71836
- const manifestPath2 = join59(directory, "manifest.json");
71837
- const skillPath = join59(directory, "SKILL.md");
72094
+ const manifestPath2 = join60(directory, "manifest.json");
72095
+ const skillPath = join60(directory, "SKILL.md");
71838
72096
  if (!existsSync26(manifestPath2) || !existsSync26(skillPath)) return null;
71839
72097
  const manifest = parseSkillManifest(JSON.parse(readFileSync40(manifestPath2, "utf8")), directory);
71840
72098
  const instructions = readFileSync40(skillPath, "utf8").trim();
71841
72099
  if (!instructions.startsWith("---")) throw new Error(`${skillPath} has no skill frontmatter`);
71842
72100
  return { manifest, instructions, directory };
71843
72101
  }
71844
- function loadBundledSkills(root = process.env.OMB_SKILLS_DIR || join59(process.cwd(), "skills")) {
72102
+ function loadBundledSkills(root = process.env.OMB_SKILLS_DIR || join60(process.cwd(), "skills")) {
71845
72103
  if (!existsSync26(root)) return [];
71846
72104
  const skills = [];
71847
72105
  for (const name2 of readdirSync9(root).sort()) {
71848
- const directory = join59(root, name2);
72106
+ const directory = join60(root, name2);
71849
72107
  const skill = loadSkillDirectory(directory);
71850
72108
  if (skill) skills.push(skill);
71851
72109
  }
@@ -71862,7 +72120,7 @@ function loadUserSkills(root) {
71862
72120
  const skills = [];
71863
72121
  for (const name2 of names) {
71864
72122
  try {
71865
- const skill = loadSkillDirectory(join59(root, name2));
72123
+ const skill = loadSkillDirectory(join60(root, name2));
71866
72124
  if (skill) skills.push(skill);
71867
72125
  } catch {
71868
72126
  }
@@ -72476,14 +72734,31 @@ ${message4.text}` } : {},
72476
72734
  }
72477
72735
 
72478
72736
  // server/local-routing.ts
72737
+ var HOSTS_WITH_LOCAL_CONTROL = /* @__PURE__ */ new Set(["darwin", "win32", "linux"]);
72738
+ var HOSTS_WITH_AUTO_LOCAL = /* @__PURE__ */ new Set(["darwin", "win32"]);
72479
72739
  function shouldMountLocalComputer({
72480
72740
  requested,
72481
72741
  hostPlatform = process.platform,
72482
72742
  providerSupportsLocal
72483
72743
  }) {
72484
72744
  if (!providerSupportsLocal) return false;
72485
- if (requested === "local") return hostPlatform === "darwin" || hostPlatform === "linux";
72486
- return requested === void 0 && hostPlatform === "darwin";
72745
+ if (requested === "local") return HOSTS_WITH_LOCAL_CONTROL.has(hostPlatform);
72746
+ return requested === void 0 && HOSTS_WITH_AUTO_LOCAL.has(hostPlatform);
72747
+ }
72748
+
72749
+ // server/auto-vm-claims.ts
72750
+ function startAutoVmClaim(table, threadId, generation) {
72751
+ const slot = table.get(threadId);
72752
+ if (!slot || slot.owner.threadId !== threadId || slot.owner.generation !== generation || slot.begin) return;
72753
+ slot.begin = slot.claim().then(
72754
+ () => {
72755
+ slot.claimed = true;
72756
+ },
72757
+ (error51) => {
72758
+ slot.failed = true;
72759
+ slot.failure = error51 instanceof Error ? error51.message : String(error51);
72760
+ }
72761
+ );
72487
72762
  }
72488
72763
 
72489
72764
  // server/model-context-window.ts
@@ -72737,9 +73012,9 @@ function createGracefulShutdown({
72737
73012
  // electron/data-dir-lease.mjs
72738
73013
  import { execFileSync } from "node:child_process";
72739
73014
  import { createHash as createHash27, randomUUID as randomUUID24 } from "node:crypto";
72740
- import { existsSync as existsSync27, linkSync as linkSync5, mkdirSync as mkdirSync30, readFileSync as readFileSync41, renameSync as renameSync8, unlinkSync as unlinkSync11, writeFileSync as writeFileSync18 } from "node:fs";
73015
+ import { existsSync as existsSync27, linkSync as linkSync5, mkdirSync as mkdirSync30, readFileSync as readFileSync41, renameSync as renameSync8, unlinkSync as unlinkSync12, writeFileSync as writeFileSync18 } from "node:fs";
72741
73016
  import { hostname as hostname4, uptime } from "node:os";
72742
- import { join as join60 } from "node:path";
73017
+ import { join as join61 } from "node:path";
72743
73018
  var LEASE_NAME = "openmausbot-server.lease";
72744
73019
  var DELEGATED_CHILD_DIR = ".openmausbot-server-child";
72745
73020
  var CHILD_LEASE_ENV = "OPENMAUSBOT_INTERNAL_DATA_DIR_LEASE";
@@ -72795,7 +73070,7 @@ function ownerPredatesThisBoot(owner2) {
72795
73070
  return false;
72796
73071
  }
72797
73072
  function ownerIsAlive(owner2) {
72798
- return !ownerPredatesThisBoot(owner2) && processIsAlive3(owner2.pid);
73073
+ return !ownerPredatesThisBoot(owner2) && processIsAlive3(owner2.pid) && ownerIdentityMatches(owner2);
72799
73074
  }
72800
73075
  function isLeaseOwner(value) {
72801
73076
  if (!value || typeof value !== "object" || Array.isArray(value)) return false;
@@ -72845,9 +73120,54 @@ function processIsAlive3(pid) {
72845
73120
  throw leaseError("OpenMausBot could not verify the data-directory lease owner; refusing to start.", error51);
72846
73121
  }
72847
73122
  }
73123
+ function parseWmiDateTime(value) {
73124
+ const raw = String(value).trim();
73125
+ const match = /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})\.(\d{6})([+-]\d{3})$/.exec(raw);
73126
+ if (!match) return null;
73127
+ const [_3, year, month, day, hour, minute, second, micro, offset] = match;
73128
+ const ms2 = Date.UTC(
73129
+ Number(year),
73130
+ Number(month) - 1,
73131
+ Number(day),
73132
+ Number(hour),
73133
+ Number(minute),
73134
+ Number(second),
73135
+ Math.floor(Number(micro) / 1e3)
73136
+ );
73137
+ const offsetMinutes = Number(offset);
73138
+ return ms2 - offsetMinutes * 60 * 1e3;
73139
+ }
73140
+ function processCreationTimeMs(pid) {
73141
+ if (process.platform !== "win32") return null;
73142
+ try {
73143
+ const output = execFileSync(
73144
+ "wmic",
73145
+ ["process", "where", `ProcessId=${pid}`, "get", "CreationDate", "/format:csv"],
73146
+ { encoding: "utf8", timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] }
73147
+ );
73148
+ for (const line of output.split(/\r?\n/)) {
73149
+ const trimmed = line.trim();
73150
+ if (!trimmed) continue;
73151
+ const lower = trimmed.toLowerCase();
73152
+ if (lower === "node,creationdate" || lower.startsWith("node,")) continue;
73153
+ const parts = trimmed.split(",");
73154
+ const raw = parts[parts.length - 1].trim();
73155
+ const parsed = parseWmiDateTime(raw);
73156
+ if (parsed !== null) return parsed;
73157
+ }
73158
+ } catch {
73159
+ }
73160
+ return null;
73161
+ }
73162
+ function ownerIdentityMatches(owner2) {
73163
+ if (process.platform !== "win32") return true;
73164
+ const created = processCreationTimeMs(owner2.pid);
73165
+ if (created === null) return true;
73166
+ return created < owner2.createdAt;
73167
+ }
72848
73168
  function unlinkExact(path, message4) {
72849
73169
  try {
72850
- unlinkSync11(path);
73170
+ unlinkSync12(path);
72851
73171
  } catch (error51) {
72852
73172
  if (error51?.code !== "ENOENT") throw leaseError(message4, error51);
72853
73173
  }
@@ -72942,10 +73262,10 @@ function prepareDataDir(dataDir, legacyDataDir) {
72942
73262
  } catch (error51) {
72943
73263
  throw leaseError("OpenMausBot cannot create its data directory.", error51);
72944
73264
  }
72945
- return join60(dataDir, LEASE_NAME);
73265
+ return join61(dataDir, LEASE_NAME);
72946
73266
  }
72947
73267
  function assertNoLiveDelegatedChild(dataDir) {
72948
- const childLeasePath = join60(dataDir, DELEGATED_CHILD_DIR, LEASE_NAME);
73268
+ const childLeasePath = join61(dataDir, DELEGATED_CHILD_DIR, LEASE_NAME);
72949
73269
  const child = readOwner(childLeasePath);
72950
73270
  if (!child) return;
72951
73271
  if (child.host !== hostname4()) {
@@ -72987,13 +73307,13 @@ function validateChildDelegation(dataDir, encoded) {
72987
73307
  const capability = parseCapability(encoded);
72988
73308
  const invalid = () => leaseError("The OpenMausBot desktop lease delegation is invalid; refusing to start to protect its state.");
72989
73309
  if (!capability) throw invalid();
72990
- const parentLeasePath = join60(dataDir, LEASE_NAME);
73310
+ const parentLeasePath = join61(dataDir, LEASE_NAME);
72991
73311
  const matchesLiveParent = (owner2) => Boolean(owner2 && owner2.pid === capability.pid && owner2.token === capability.token && owner2.host === hostname4() && ownerIsAlive(owner2));
72992
73312
  if (!matchesLiveParent(readOwner(parentLeasePath))) {
72993
73313
  throw invalid();
72994
73314
  }
72995
73315
  const childLease = acquireDataDirLeaseInternal(
72996
- join60(dataDir, DELEGATED_CHILD_DIR),
73316
+ join61(dataDir, DELEGATED_CHILD_DIR),
72997
73317
  { guardDelegatedChild: false }
72998
73318
  );
72999
73319
  if (!matchesLiveParent(readOwner(parentLeasePath))) {
@@ -73071,7 +73391,7 @@ function acquireDataDirLeaseInternal(dataDir, options = {}) {
73071
73391
  throw leaseError("OpenMausBot will not release a data-directory lease owned by another process.");
73072
73392
  }
73073
73393
  try {
73074
- unlinkSync11(leasePath);
73394
+ unlinkSync12(leasePath);
73075
73395
  } catch (error51) {
73076
73396
  throw leaseError("OpenMausBot could not release its data-directory lease.", error51);
73077
73397
  }
@@ -73131,8 +73451,8 @@ var WorkspaceBackupMaintenance = class {
73131
73451
 
73132
73452
  // server/workspace-backup-http.ts
73133
73453
  import { randomUUID as randomUUID26 } from "node:crypto";
73134
- import { createReadStream as createReadStream4, createWriteStream as createWriteStream3, existsSync as existsSync29, lstatSync as lstatSync9, mkdirSync as mkdirSync32, readdirSync as readdirSync11, statSync as statSync12, unlinkSync as unlinkSync12 } from "node:fs";
73135
- import { join as join62 } from "node:path";
73454
+ import { createReadStream as createReadStream4, createWriteStream as createWriteStream3, existsSync as existsSync29, lstatSync as lstatSync9, mkdirSync as mkdirSync32, readdirSync as readdirSync11, statSync as statSync12, unlinkSync as unlinkSync13 } from "node:fs";
73455
+ import { join as join63 } from "node:path";
73136
73456
  import { Transform as Transform2 } from "node:stream";
73137
73457
  import { pipeline as pipeline3 } from "node:stream/promises";
73138
73458
 
@@ -73175,7 +73495,7 @@ import {
73175
73495
  writeFileSync as writeFileSync19,
73176
73496
  writeSync as writeSync2
73177
73497
  } from "node:fs";
73178
- import { isAbsolute as isAbsolute11, join as join61, parse as parse6, posix as posix2, relative as relative5, resolve as resolve15, win32 as win322 } from "node:path";
73498
+ import { isAbsolute as isAbsolute11, join as join62, parse as parse6, posix as posix2, relative as relative5, resolve as resolve15, win32 as win322 } from "node:path";
73179
73499
  import { homedir as homedir26 } from "node:os";
73180
73500
  import { backup, DatabaseSync as DatabaseSync2 } from "node:sqlite";
73181
73501
  import { pipeline as pipeline2 } from "node:stream/promises";
@@ -76234,13 +76554,13 @@ function backupRoot(dataDir) {
76234
76554
  if (root === parse6(root).root) throw new Error("A filesystem root cannot be a workspace backup target.");
76235
76555
  const stat6 = lstatSync8(root);
76236
76556
  if (!stat6.isDirectory() || stat6.isSymbolicLink()) throw new Error("The workspace data directory must not be a symbolic link.");
76237
- const path = join61(root, ".backups");
76557
+ const path = join62(root, ".backups");
76238
76558
  folder2(path);
76239
76559
  return path;
76240
76560
  }
76241
76561
  function jobPath(dataDir, id) {
76242
76562
  if (!ID.test(id)) throw new Error("Invalid workspace backup identifier.");
76243
- const path = join61(backupRoot(dataDir), id);
76563
+ const path = join62(backupRoot(dataDir), id);
76244
76564
  if (entryExists(path)) {
76245
76565
  const stat6 = lstatSync8(path);
76246
76566
  if (!stat6.isDirectory() || stat6.isSymbolicLink()) throw new Error("Unsafe workspace backup job directory.");
@@ -76287,14 +76607,14 @@ function privateMetadata(manifest) {
76287
76607
  return { summary: manifest.summary, clientState: manifest.clientState };
76288
76608
  }
76289
76609
  function assertLocalAuthOutsideSnapshot(dataDir) {
76290
- const configPath = join61(dataDir, "config.json");
76610
+ const configPath = join62(dataDir, "config.json");
76291
76611
  const config2 = existsSync28(configPath) ? privateJson(configPath) : {};
76292
76612
  const instances = record2(config2) && record2(config2.instances) ? config2.instances : {};
76293
76613
  const fields = ["HOME", "USERPROFILE", "CLAUDE_CONFIG_DIR", "CODEX_HOME", "GROK_HOME", "KIMI_CODE_HOME", "HERMES_HOME", "FACTORY_HOME_OVERRIDE", "XDG_CONFIG_HOME", "XDG_DATA_HOME", "LOCALAPPDATA"];
76294
76614
  const check2 = (value, field, home) => {
76295
76615
  if (typeof value !== "string" || !value.trim()) return;
76296
76616
  const text8 = value.trim();
76297
- const expanded = text8.startsWith("~/") || text8.startsWith("~\\") ? join61(home, text8.slice(2)) : text8;
76617
+ const expanded = text8.startsWith("~/") || text8.startsWith("~\\") ? join62(home, text8.slice(2)) : text8;
76298
76618
  const resolved = resolve15(expanded);
76299
76619
  const exists = entryExists(resolved);
76300
76620
  const path = relative5(exists ? realpathSync10(dataDir) : resolve15(dataDir), exists ? realpathSync10(resolved) : resolved).replaceAll("\\", "/");
@@ -76404,27 +76724,27 @@ async function createWorkspaceBackup(dataDir, options) {
76404
76724
  const salt = randomBytes6(16);
76405
76725
  const key3 = await passwordKey(options.password, salt);
76406
76726
  const job = newJob(dataDir);
76407
- const snapshot2 = join61(job.directory, "snapshot");
76408
- folder2(join61(snapshot2, "data"));
76727
+ const snapshot2 = join62(job.directory, "snapshot");
76728
+ folder2(join62(snapshot2, "data"));
76409
76729
  const entries = [];
76410
76730
  const warnings = ["Stop external editors and managed desktops before exporting; files written outside this server cannot be frozen by the backup gate.", "Conversation text and user files are not redacted and may contain secrets you pasted. Keep the encrypted backup private.", RESTORE_WARNING];
76411
76731
  let bytes = 0;
76412
76732
  let skippedLinks = 0;
76413
76733
  try {
76414
76734
  const root = realpathSync10(dataDir);
76415
- const sourceDb = join61(root, "messages.db");
76735
+ const sourceDb = join62(root, "messages.db");
76416
76736
  if (existsSync28(sourceDb)) {
76417
76737
  if (lstatSync8(sourceDb).isSymbolicLink()) throw new Error("The message database must not be a symbolic link.");
76418
76738
  const db2 = new DatabaseSync2(sourceDb, { readOnly: true });
76419
76739
  const completionPulse = setInterval(() => {
76420
76740
  }, 25);
76421
76741
  try {
76422
- await backup(db2, join61(snapshot2, "messages-snapshot.db"));
76742
+ await backup(db2, join62(snapshot2, "messages-snapshot.db"));
76423
76743
  } finally {
76424
76744
  clearInterval(completionPulse);
76425
76745
  db2.close();
76426
76746
  }
76427
- const snapshotDb = new DatabaseSync2(join61(snapshot2, "messages-snapshot.db"));
76747
+ const snapshotDb = new DatabaseSync2(join62(snapshot2, "messages-snapshot.db"));
76428
76748
  try {
76429
76749
  snapshotDb.exec("PRAGMA journal_mode = DELETE");
76430
76750
  } finally {
@@ -76438,7 +76758,7 @@ async function createWorkspaceBackup(dataDir, options) {
76438
76758
  if (excludedWorkspaceAuthPath(path2)) continue;
76439
76759
  if (forbiddenArchivePath(path2)) throw new Error("A workspace filename conflicts with a protected authentication or runtime path.");
76440
76760
  if (!validRelative(path2)) throw new Error("A workspace filename cannot be safely restored on supported platforms.");
76441
- const source = join61(directory, name2);
76761
+ const source = join62(directory, name2);
76442
76762
  const stat6 = lstatSync8(source);
76443
76763
  if (stat6.isSymbolicLink()) {
76444
76764
  let target;
@@ -76458,7 +76778,7 @@ async function createWorkspaceBackup(dataDir, options) {
76458
76778
  continue;
76459
76779
  }
76460
76780
  if (entries.length >= MAX_WORKSPACE_BACKUP_FILES) throw new Error("The workspace exceeds the 100,000-entry backup limit.");
76461
- const destination = join61(snapshot2, "data", path2);
76781
+ const destination = join62(snapshot2, "data", path2);
76462
76782
  if (stat6.isDirectory()) {
76463
76783
  folder2(destination);
76464
76784
  entries.push({ path: path2, type: "directory", size: 0, mode: 448 });
@@ -76476,7 +76796,7 @@ async function createWorkspaceBackup(dataDir, options) {
76476
76796
  writeJson(destination, value);
76477
76797
  }
76478
76798
  copied = { size: lstatSync8(destination).size, mode: 384, sha256: hashFile(destination) };
76479
- } else copied = copyRegular(path2 === "messages.db" ? join61(snapshot2, "messages-snapshot.db") : source, destination);
76799
+ } else copied = copyRegular(path2 === "messages.db" ? join62(snapshot2, "messages-snapshot.db") : source, destination);
76480
76800
  bytes += copied.size;
76481
76801
  if (bytes > MAX_WORKSPACE_BACKUP_BYTES) throw new Error("The workspace exceeds the 10 GB backup limit.");
76482
76802
  entries.push({ path: path2, type: "file", ...copied });
@@ -76494,9 +76814,9 @@ async function createWorkspaceBackup(dataDir, options) {
76494
76814
  files: entries.filter((entry) => entry.type === "file").length,
76495
76815
  directories: entries.filter((entry) => entry.type === "directory").length,
76496
76816
  bytes,
76497
- bots: countJsonArray(join61(snapshot2, "data", "bots.json")),
76498
- groups: countJsonArray(join61(snapshot2, "data", "groups.json")),
76499
- ...databaseCounts(join61(snapshot2, "data", "messages.db")),
76817
+ bots: countJsonArray(join62(snapshot2, "data", "bots.json")),
76818
+ groups: countJsonArray(join62(snapshot2, "data", "groups.json")),
76819
+ ...databaseCounts(join62(snapshot2, "data", "messages.db")),
76500
76820
  exclusions: EXCLUSION_NOTES,
76501
76821
  warnings
76502
76822
  };
@@ -76506,9 +76826,9 @@ async function createWorkspaceBackup(dataDir, options) {
76506
76826
  clientState: options.clientState ?? {},
76507
76827
  entries
76508
76828
  };
76509
- writeJson(join61(snapshot2, "manifest.json"), manifest);
76510
- validateManifest(privateJson(join61(snapshot2, "manifest.json")));
76511
- const path = join61(job.directory, "workspace.ombbackup");
76829
+ writeJson(join62(snapshot2, "manifest.json"), manifest);
76830
+ validateManifest(privateJson(join62(snapshot2, "manifest.json")));
76831
+ const path = join62(job.directory, "workspace.ombbackup");
76512
76832
  const iv = randomBytes6(12);
76513
76833
  const header2 = Buffer.concat([MAGIC, salt, iv]);
76514
76834
  const cipher = createCipheriv("aes-256-gcm", key3, iv);
@@ -76623,21 +76943,21 @@ async function inspectTar(path) {
76623
76943
  return entries;
76624
76944
  }
76625
76945
  function validateStaged(directory, expected) {
76626
- const manifest = validateManifest(privateJson(join61(directory, "manifest.json")));
76946
+ const manifest = validateManifest(privateJson(join62(directory, "manifest.json")));
76627
76947
  if (expected && expected.size !== manifest.entries.length + 2) throw new Error("Archive entries do not match the backup manifest.");
76628
76948
  const declared = new Set(manifest.entries.map((entry) => entry.path));
76629
76949
  const inspect = (root, prefix = "") => {
76630
76950
  for (const name2 of readdirSync10(root)) {
76631
76951
  const path = prefix ? `${prefix}/${name2}` : name2;
76632
76952
  if (!declared.has(path)) throw new Error("Staged workspace contains files absent from its manifest.");
76633
- const stat6 = lstatSync8(join61(root, name2));
76953
+ const stat6 = lstatSync8(join62(root, name2));
76634
76954
  if (stat6.isSymbolicLink()) throw new Error("Staged workspace contains a symbolic link.");
76635
- if (stat6.isDirectory()) inspect(join61(root, name2), path);
76955
+ if (stat6.isDirectory()) inspect(join62(root, name2), path);
76636
76956
  }
76637
76957
  };
76638
- inspect(join61(directory, "data"));
76958
+ inspect(join62(directory, "data"));
76639
76959
  for (const entry of manifest.entries) {
76640
- const path = join61(directory, "data", entry.path);
76960
+ const path = join62(directory, "data", entry.path);
76641
76961
  const stat6 = lstatSync8(path);
76642
76962
  if (stat6.isSymbolicLink() || (entry.type === "file" ? !stat6.isFile() || stat6.size !== entry.size : !stat6.isDirectory())) {
76643
76963
  throw new Error("Staged workspace files do not match the backup manifest.");
@@ -76649,27 +76969,27 @@ function validateStaged(directory, expected) {
76649
76969
  if (entry.type === "file" && hashFile(path) !== entry.sha256) throw new Error("A workspace backup file failed its checksum.");
76650
76970
  chmodSync7(path, entry.mode);
76651
76971
  }
76652
- const data = join61(directory, "data");
76653
- if (countJsonArray(join61(data, "bots.json")) !== manifest.summary.bots || countJsonArray(join61(data, "groups.json")) !== manifest.summary.groups) throw new Error("The backup roster does not match its summary.");
76654
- if (existsSync28(join61(data, "config.json"))) {
76655
- const config2 = privateJson(join61(data, "config.json"));
76972
+ const data = join62(directory, "data");
76973
+ if (countJsonArray(join62(data, "bots.json")) !== manifest.summary.bots || countJsonArray(join62(data, "groups.json")) !== manifest.summary.groups) throw new Error("The backup roster does not match its summary.");
76974
+ if (existsSync28(join62(data, "config.json"))) {
76975
+ const config2 = privateJson(join62(data, "config.json"));
76656
76976
  if (JSON.stringify(config2) !== JSON.stringify(portableWorkspaceConfig(config2))) throw new Error("Workspace backup contains non-portable connection settings.");
76657
76977
  }
76658
- if (existsSync28(join61(data, "webhooks.json"))) {
76659
- const value = privateJson(join61(data, "webhooks.json"));
76978
+ if (existsSync28(join62(data, "webhooks.json"))) {
76979
+ const value = privateJson(join62(data, "webhooks.json"));
76660
76980
  if (!record2(value) || Array.isArray(value.webhooks) && value.webhooks.some((webhook) => record2(webhook) && Object.hasOwn(webhook, "secretHash"))) throw new Error("Workspace backup contains webhook credentials.");
76661
76981
  }
76662
- const counts = databaseCounts(join61(data, "messages.db"));
76982
+ const counts = databaseCounts(join62(data, "messages.db"));
76663
76983
  if (counts.messages !== manifest.summary.messages || counts.threads !== manifest.summary.threads) throw new Error("The message database does not match the backup summary.");
76664
76984
  return manifest;
76665
76985
  }
76666
76986
  async function stageWorkspaceBackup(dataDir, archivePath, options) {
76667
76987
  const job = newJob(dataDir);
76668
- const plaintext = join61(job.directory, "archive.tar");
76988
+ const plaintext = join62(job.directory, "archive.tar");
76669
76989
  try {
76670
76990
  await decryptArchive(archivePath, plaintext, options.password);
76671
76991
  const expected = await inspectTar(plaintext);
76672
- const staged = join61(job.directory, "staged");
76992
+ const staged = join62(job.directory, "staged");
76673
76993
  folder2(staged);
76674
76994
  await So({ file: plaintext, cwd: staged, strict: true, preservePaths: false, umask: 63, noChmod: true });
76675
76995
  const manifest = validateStaged(staged, expected);
@@ -76681,7 +77001,7 @@ async function stageWorkspaceBackup(dataDir, archivePath, options) {
76681
77001
  }
76682
77002
  }
76683
77003
  rmSync8(plaintext);
76684
- writeJson(join61(job.directory, "ready.json"), { id: job.id, summary: manifest.summary });
77004
+ writeJson(join62(job.directory, "ready.json"), { id: job.id, summary: manifest.summary });
76685
77005
  return { id: job.id, summary: manifest.summary };
76686
77006
  } catch (error51) {
76687
77007
  rmSync8(job.directory, { recursive: true, force: true });
@@ -76690,24 +77010,24 @@ async function stageWorkspaceBackup(dataDir, archivePath, options) {
76690
77010
  }
76691
77011
  function readStagedWorkspaceBackup(dataDir, id) {
76692
77012
  const directory = jobPath(dataDir, id);
76693
- const ready = privateJson(join61(directory, "ready.json"));
77013
+ const ready = privateJson(join62(directory, "ready.json"));
76694
77014
  if (!record2(ready) || ready.id !== id) throw new Error("This workspace backup is not ready to restore.");
76695
- const manifest = validateManifest(privateJson(join61(directory, "staged", "manifest.json")));
77015
+ const manifest = validateManifest(privateJson(join62(directory, "staged", "manifest.json")));
76696
77016
  return { id, ...privateMetadata(manifest) };
76697
77017
  }
76698
77018
  function commitPendingWorkspaceRestore(dataDir, id) {
76699
77019
  const root = backupRoot(dataDir);
76700
- if (existsSync28(join61(root, "pending-restore.json"))) throw new Error("A workspace restore is already waiting for restart.");
76701
- if (existsSync28(join61(root, `safety-${id}`))) throw new Error("This restore was already attempted. Preview the backup again to create a fresh restore attempt.");
77020
+ if (existsSync28(join62(root, "pending-restore.json"))) throw new Error("A workspace restore is already waiting for restart.");
77021
+ if (existsSync28(join62(root, `safety-${id}`))) throw new Error("This restore was already attempted. Preview the backup again to create a fresh restore attempt.");
76702
77022
  const metadata = readStagedWorkspaceBackup(dataDir, id);
76703
- const manifest = validateStaged(join61(jobPath(dataDir, id), "staged"));
77023
+ const manifest = validateStaged(join62(jobPath(dataDir, id), "staged"));
76704
77024
  prepareRestore(dataDir, id, manifest);
76705
- writeJson(join61(root, "pending-restore.json"), { id });
77025
+ writeJson(join62(root, "pending-restore.json"), { id });
76706
77026
  return { id, restartRequired: true, summary: metadata.summary };
76707
77027
  }
76708
77028
  function readPendingWorkspaceRestoreMetadata(dataDir) {
76709
77029
  const root = backupRoot(dataDir);
76710
- const path = join61(root, "pending-restore.json");
77030
+ const path = join62(root, "pending-restore.json");
76711
77031
  if (!existsSync28(path)) return null;
76712
77032
  const pending = privateJson(path);
76713
77033
  if (!record2(pending) || typeof pending.id !== "string") throw new Error("Invalid pending workspace restore.");
@@ -76722,24 +77042,24 @@ function readJournal(path) {
76722
77042
  return raw;
76723
77043
  }
76724
77044
  function rollback(dataDir, journal) {
76725
- const stage = join61(jobPath(dataDir, journal.id), "apply", "data");
76726
- const saved = join61(backupRoot(dataDir), `safety-${journal.id}`, "data");
77045
+ const stage = join62(jobPath(dataDir, journal.id), "apply", "data");
77046
+ const saved = join62(backupRoot(dataDir), `safety-${journal.id}`, "data");
76727
77047
  for (const name2 of journal.incoming) {
76728
- const live = join61(dataDir, name2);
76729
- const staged = join61(stage, name2);
76730
- const originalMoved = !journal.existing.includes(name2) || entryExists(join61(saved, name2));
77048
+ const live = join62(dataDir, name2);
77049
+ const staged = join62(stage, name2);
77050
+ const originalMoved = !journal.existing.includes(name2) || entryExists(join62(saved, name2));
76731
77051
  if (originalMoved && !entryExists(staged) && entryExists(live)) renameSync9(live, staged);
76732
77052
  }
76733
77053
  for (const name2 of journal.existing) {
76734
- const original = join61(saved, name2);
77054
+ const original = join62(saved, name2);
76735
77055
  if (!entryExists(original)) continue;
76736
- const live = join61(dataDir, name2);
77056
+ const live = join62(dataDir, name2);
76737
77057
  if (entryExists(live)) throw new Error("Restore rollback found conflicting files. Both copies are preserved for recovery.");
76738
77058
  renameSync9(original, live);
76739
77059
  }
76740
77060
  const root = backupRoot(dataDir);
76741
- const previousReceipt = join61(root, `safety-${journal.id}`, "previous-restore.json");
76742
- const last = join61(root, "last-restore.json");
77061
+ const previousReceipt = join62(root, `safety-${journal.id}`, "previous-restore.json");
77062
+ const last = join62(root, "last-restore.json");
76743
77063
  if (existsSync28(previousReceipt)) writeJson(last, privateJson(previousReceipt));
76744
77064
  else if (existsSync28(last)) {
76745
77065
  const value = privateJson(last);
@@ -76752,7 +77072,7 @@ function rebasePath(value, source, destination) {
76752
77072
  if (normalized !== original && !normalized.startsWith(`${original}/`)) return value;
76753
77073
  const suffix = normalized.slice(original.length).replace(/^\//, "");
76754
77074
  if (suffix && !validRelative(suffix)) return value;
76755
- return suffix ? join61(destination, suffix) : destination;
77075
+ return suffix ? join62(destination, suffix) : destination;
76756
77076
  }
76757
77077
  var PATH_KEYS = /* @__PURE__ */ new Set(["cwd", "pinnedCwd", "workspace", "configDir", "profileDirectory", "agentDir", "dataDir", "home", "cli", "path", "filePath", "localPath"]);
76758
77078
  function rebaseFields(value, source, destination) {
@@ -76791,16 +77111,16 @@ function rebaseMessage(message4, source, destination) {
76791
77111
  function prepareRestore(dataDir, id, manifest) {
76792
77112
  assertLocalAuthOutsideSnapshot(dataDir);
76793
77113
  const job = jobPath(dataDir, id);
76794
- const prepared = join61(job, "apply", "data");
76795
- if (existsSync28(join61(job, "apply"))) rmSync8(join61(job, "apply"), { recursive: true, force: true });
77114
+ const prepared = join62(job, "apply", "data");
77115
+ if (existsSync28(join62(job, "apply"))) rmSync8(join62(job, "apply"), { recursive: true, force: true });
76796
77116
  folder2(prepared);
76797
- for (const entry of manifest.entries.filter((entry2) => entry2.type === "directory")) folder2(join61(prepared, entry.path));
77117
+ for (const entry of manifest.entries.filter((entry2) => entry2.type === "directory")) folder2(join62(prepared, entry.path));
76798
77118
  for (const entry of manifest.entries) {
76799
- const destination = join61(prepared, entry.path);
76800
- if (entry.type === "file") copyRegular(join61(job, "staged", "data", entry.path), destination);
77119
+ const destination = join62(prepared, entry.path);
77120
+ if (entry.type === "file") copyRegular(join62(job, "staged", "data", entry.path), destination);
76801
77121
  }
76802
77122
  const changeJson = (name2, change) => {
76803
- const path = join61(prepared, name2);
77123
+ const path = join62(prepared, name2);
76804
77124
  if (!existsSync28(path)) return;
76805
77125
  const value = privateJson(path);
76806
77126
  change(value);
@@ -76813,25 +77133,25 @@ function prepareRestore(dataDir, id, manifest) {
76813
77133
  if (!Array.isArray(value)) throw new Error("Invalid bot definitions in workspace backup.");
76814
77134
  for (const bot of value) if (record2(bot)) delete bot.voice;
76815
77135
  });
76816
- const oldConfig = join61(dataDir, "config.json");
76817
- const newConfig = join61(prepared, "config.json");
77136
+ const oldConfig = join62(dataDir, "config.json");
77137
+ const newConfig = join62(prepared, "config.json");
76818
77138
  if (existsSync28(oldConfig) || existsSync28(newConfig)) {
76819
77139
  writeJson(newConfig, restoredWorkspaceConfig(existsSync28(newConfig) ? privateJson(newConfig) : {}, existsSync28(oldConfig) ? privateJson(oldConfig) : {}));
76820
77140
  }
76821
77141
  const browserHomes = ["vm-home"];
76822
- const vmHomes = join61(dataDir, "vm-homes");
77142
+ const vmHomes = join62(dataDir, "vm-homes");
76823
77143
  if (entryExists(vmHomes)) {
76824
77144
  if (!lstatSync8(vmHomes).isDirectory()) throw new Error("Stop managed desktops and repair the VM home directory before restoring.");
76825
77145
  for (const name2 of readdirSync10(vmHomes)) if (validRelative(name2)) browserHomes.push(`vm-homes/${name2}`);
76826
77146
  }
76827
77147
  for (const home of browserHomes) {
76828
- const homePath = join61(dataDir, home);
77148
+ const homePath = join62(dataDir, home);
76829
77149
  if (!entryExists(homePath)) continue;
76830
77150
  if (!lstatSync8(homePath).isDirectory()) throw new Error("Stop managed desktops and repair the VM home directory before restoring.");
76831
- const source = join61(dataDir, home, ".browser-profiles");
77151
+ const source = join62(dataDir, home, ".browser-profiles");
76832
77152
  if (!entryExists(source)) continue;
76833
- folder2(join61(prepared, home));
76834
- cpSync(source, join61(prepared, home, ".browser-profiles"), { recursive: true, dereference: false, verbatimSymlinks: true, errorOnExist: true, force: false });
77153
+ folder2(join62(prepared, home));
77154
+ cpSync(source, join62(prepared, home, ".browser-profiles"), { recursive: true, dereference: false, verbatimSymlinks: true, errorOnExist: true, force: false });
76835
77155
  }
76836
77156
  changeJson("routines.json", (value) => {
76837
77157
  if (!record2(value)) throw new Error("Invalid routine definitions in workspace backup.");
@@ -76849,7 +77169,7 @@ function prepareRestore(dataDir, id, manifest) {
76849
77169
  });
76850
77170
  changeJson("webhooks.json", (value) => {
76851
77171
  if (!record2(value)) throw new Error("Invalid webhook definitions in workspace backup.");
76852
- const destination = existsSync28(join61(dataDir, "webhooks.json")) ? privateJson(join61(dataDir, "webhooks.json")) : {};
77172
+ const destination = existsSync28(join62(dataDir, "webhooks.json")) ? privateJson(join62(dataDir, "webhooks.json")) : {};
76853
77173
  const oldHooks = record2(destination) && Array.isArray(destination.webhooks) ? destination.webhooks : [];
76854
77174
  if (Array.isArray(value.webhooks)) {
76855
77175
  for (const webhook of value.webhooks) if (record2(webhook)) {
@@ -76866,15 +77186,15 @@ function prepareRestore(dataDir, id, manifest) {
76866
77186
  for (const call of value.calls) if (record2(call)) call.nextRunAt = null;
76867
77187
  }
76868
77188
  });
76869
- if (existsSync28(join61(prepared, "delegations.json"))) writeJson(join61(prepared, "delegations.json"), {});
76870
- if (existsSync28(join61(prepared, "browser-cleanups.json"))) writeJson(join61(prepared, "browser-cleanups.json"), []);
77189
+ if (existsSync28(join62(prepared, "delegations.json"))) writeJson(join62(prepared, "delegations.json"), {});
77190
+ if (existsSync28(join62(prepared, "browser-cleanups.json"))) writeJson(join62(prepared, "browser-cleanups.json"), []);
76871
77191
  for (const entry of manifest.entries) {
76872
77192
  if (/^messages-[^/]+\.json$/.test(entry.path)) changeJson(entry.path, (value) => {
76873
77193
  if (Array.isArray(value)) for (const message4 of value) rebaseMessage(message4, manifest.sourceDataDir, resolve15(dataDir));
76874
77194
  else if (record2(value) && Array.isArray(value.messages)) for (const message4 of value.messages) rebaseMessage(message4, manifest.sourceDataDir, resolve15(dataDir));
76875
77195
  });
76876
77196
  }
76877
- const dbPath = join61(prepared, "messages.db");
77197
+ const dbPath = join62(prepared, "messages.db");
76878
77198
  if (existsSync28(dbPath)) {
76879
77199
  const db2 = new DatabaseSync2(dbPath);
76880
77200
  try {
@@ -76906,7 +77226,7 @@ function prepareRestore(dataDir, id, manifest) {
76906
77226
  }
76907
77227
  function readLastWorkspaceRestore(dataDir) {
76908
77228
  const root = backupRoot(dataDir);
76909
- const path = join61(root, "last-restore.json");
77229
+ const path = join62(root, "last-restore.json");
76910
77230
  if (!existsSync28(path)) return null;
76911
77231
  const raw = privateJson(path);
76912
77232
  if (!record2(raw) || raw.restored !== true || typeof raw.id !== "string" || !ID.test(raw.id)) throw new Error("Invalid workspace restore receipt.");
@@ -76919,28 +77239,28 @@ function finishRestore(dataDir, id, consumePending = true) {
76919
77239
  if (draftAttachments) {
76920
77240
  try {
76921
77241
  const value = JSON.parse(draftAttachments);
76922
- const manifest = validateManifest(privateJson(join61(jobPath(dataDir, id), "staged", "manifest.json")));
77242
+ const manifest = validateManifest(privateJson(join62(jobPath(dataDir, id), "staged", "manifest.json")));
76923
77243
  rebaseFields(value, manifest.sourceDataDir, resolve15(dataDir));
76924
77244
  clientState["omb-draft-attachments"] = JSON.stringify(value);
76925
77245
  } catch {
76926
77246
  }
76927
77247
  }
76928
- const result = { restored: true, id, safetyCopyPath: join61(root, `safety-${id}`), summary, clientState };
76929
- writeJson(join61(root, "last-restore.json"), result);
76930
- if (consumePending) rmSync8(join61(root, "pending-restore.json"), { force: true });
77248
+ const result = { restored: true, id, safetyCopyPath: join62(root, `safety-${id}`), summary, clientState };
77249
+ writeJson(join62(root, "last-restore.json"), result);
77250
+ if (consumePending) rmSync8(join62(root, "pending-restore.json"), { force: true });
76931
77251
  return result;
76932
77252
  }
76933
77253
  function applyPendingWorkspaceRestore(dataDir) {
76934
77254
  const root = backupRoot(dataDir);
76935
- const journalFile2 = join61(root, "restore-journal.json");
76936
- const pendingFile = join61(root, "pending-restore.json");
77255
+ const journalFile2 = join62(root, "restore-journal.json");
77256
+ const pendingFile = join62(root, "pending-restore.json");
76937
77257
  if (existsSync28(journalFile2)) {
76938
77258
  const previous2 = readJournal(journalFile2);
76939
77259
  if (previous2.phase === "applying") {
76940
77260
  rollback(dataDir, previous2);
76941
77261
  writeJson(journalFile2, { ...previous2, phase: "rolled-back" });
76942
77262
  rmSync8(pendingFile, { force: true });
76943
- return { restored: false, rolledBack: true, id: previous2.id, safetyCopyPath: join61(root, `safety-${previous2.id}`) };
77263
+ return { restored: false, rolledBack: true, id: previous2.id, safetyCopyPath: join62(root, `safety-${previous2.id}`) };
76944
77264
  }
76945
77265
  if (previous2.phase === "committed" && existsSync28(pendingFile)) {
76946
77266
  const pending2 = privateJson(pendingFile);
@@ -76951,12 +77271,12 @@ function applyPendingWorkspaceRestore(dataDir) {
76951
77271
  }
76952
77272
  const pending = readPendingWorkspaceRestoreMetadata(dataDir);
76953
77273
  if (!pending) return { restored: false };
76954
- const staged = join61(jobPath(dataDir, pending.id), "staged");
77274
+ const staged = join62(jobPath(dataDir, pending.id), "staged");
76955
77275
  const manifest = validateStaged(staged);
76956
77276
  const prepared = prepareRestore(dataDir, pending.id, manifest);
76957
- const safetyCopyPath = join61(root, `safety-${pending.id}`);
77277
+ const safetyCopyPath = join62(root, `safety-${pending.id}`);
76958
77278
  if (existsSync28(safetyCopyPath)) throw new Error("A safety copy already exists for this restore. Original files have not been changed.");
76959
- folder2(join61(safetyCopyPath, "data"));
77279
+ folder2(join62(safetyCopyPath, "data"));
76960
77280
  const journal = {
76961
77281
  id: pending.id,
76962
77282
  phase: "applying",
@@ -76966,12 +77286,12 @@ function applyPendingWorkspaceRestore(dataDir) {
76966
77286
  for (const name2 of [...journal.existing, ...journal.incoming]) {
76967
77287
  if (!validRelative(name2) || name2.includes("/") || preserved(name2)) throw new Error("Unsafe workspace restore target.");
76968
77288
  }
76969
- writeJson(join61(safetyCopyPath, "manifest.json"), { id: pending.id, createdAt: (/* @__PURE__ */ new Date()).toISOString(), originalEntries: journal.existing });
76970
- if (existsSync28(join61(root, "last-restore.json"))) writeJson(join61(safetyCopyPath, "previous-restore.json"), privateJson(join61(root, "last-restore.json")));
77289
+ writeJson(join62(safetyCopyPath, "manifest.json"), { id: pending.id, createdAt: (/* @__PURE__ */ new Date()).toISOString(), originalEntries: journal.existing });
77290
+ if (existsSync28(join62(root, "last-restore.json"))) writeJson(join62(safetyCopyPath, "previous-restore.json"), privateJson(join62(root, "last-restore.json")));
76971
77291
  writeJson(journalFile2, journal);
76972
77292
  try {
76973
- for (const name2 of journal.existing) renameSync9(join61(dataDir, name2), join61(safetyCopyPath, "data", name2));
76974
- for (const name2 of journal.incoming) if (entryExists(join61(prepared, name2))) renameSync9(join61(prepared, name2), join61(dataDir, name2));
77293
+ for (const name2 of journal.existing) renameSync9(join62(dataDir, name2), join62(safetyCopyPath, "data", name2));
77294
+ for (const name2 of journal.incoming) if (entryExists(join62(prepared, name2))) renameSync9(join62(prepared, name2), join62(dataDir, name2));
76975
77295
  finishRestore(dataDir, pending.id, false);
76976
77296
  writeJson(journalFile2, { ...journal, phase: "committed" });
76977
77297
  } catch (error51) {
@@ -76986,11 +77306,11 @@ function removeWorkspaceBackupJob(dataDir, id) {
76986
77306
  const path = jobPath(dataDir, id);
76987
77307
  const root = backupRoot(dataDir);
76988
77308
  for (const name2 of ["pending-restore.json", "restore-journal.json", "last-restore.json"]) {
76989
- if (!existsSync28(join61(root, name2))) continue;
76990
- const value = privateJson(join61(root, name2));
77309
+ if (!existsSync28(join62(root, name2))) continue;
77310
+ const value = privateJson(join62(root, name2));
76991
77311
  if (record2(value) && value.id === id) throw new Error("This backup is still needed for restore or recovery and cannot be removed.");
76992
77312
  }
76993
- if (existsSync28(join61(root, `safety-${id}`))) throw new Error("This backup has a recovery copy and cannot be removed.");
77313
+ if (existsSync28(join62(root, `safety-${id}`))) throw new Error("This backup has a recovery copy and cannot be removed.");
76994
77314
  if (!entryExists(path)) return;
76995
77315
  if (!lstatSync8(path).isDirectory() || lstatSync8(path).isSymbolicLink()) throw new Error("Unsafe workspace backup cleanup target.");
76996
77316
  rmSync8(path, { recursive: true, force: true });
@@ -77020,7 +77340,7 @@ function createWorkspaceBackupRoutes(options) {
77020
77340
  function remove(id, artifact) {
77021
77341
  if (artifact.path) {
77022
77342
  try {
77023
- unlinkSync12(artifact.path);
77343
+ unlinkSync13(artifact.path);
77024
77344
  } catch {
77025
77345
  }
77026
77346
  }
@@ -77033,24 +77353,24 @@ function createWorkspaceBackupRoutes(options) {
77033
77353
  artifacts.delete(id);
77034
77354
  }
77035
77355
  function reclaimAbandoned() {
77036
- const root = join62(options.dataDir, ".backups");
77356
+ const root = join63(options.dataDir, ".backups");
77037
77357
  if (!existsSync29(root) || lstatSync9(root).isSymbolicLink()) return;
77038
77358
  for (const entry of readdirSync11(root, { withFileTypes: true })) {
77039
77359
  if (!entry.isDirectory() || !/^[a-f0-9-]{36}$/.test(entry.name)) continue;
77040
- if (statSync12(join62(root, entry.name)).mtimeMs + EXPIRES_MS > Date.now()) continue;
77360
+ if (statSync12(join63(root, entry.name)).mtimeMs + EXPIRES_MS > Date.now()) continue;
77041
77361
  try {
77042
77362
  removeWorkspaceBackupJob(options.dataDir, entry.name);
77043
77363
  } catch {
77044
77364
  }
77045
77365
  }
77046
- const uploads = join62(root, "uploads");
77366
+ const uploads = join63(root, "uploads");
77047
77367
  if (!existsSync29(uploads) || lstatSync9(uploads).isSymbolicLink()) return;
77048
77368
  for (const entry of readdirSync11(uploads, { withFileTypes: true })) {
77049
77369
  if (!entry.isFile() || !/^[a-f0-9-]{36}\.ombbackup$/.test(entry.name)) continue;
77050
- const file2 = join62(uploads, entry.name);
77370
+ const file2 = join63(uploads, entry.name);
77051
77371
  if (statSync12(file2).mtimeMs + EXPIRES_MS <= Date.now()) {
77052
77372
  try {
77053
- unlinkSync12(file2);
77373
+ unlinkSync13(file2);
77054
77374
  } catch {
77055
77375
  }
77056
77376
  }
@@ -77147,14 +77467,14 @@ function createWorkspaceBackupRoutes(options) {
77147
77467
  if (!Number.isSafeInteger(length) || length <= 0 || length > MAX_UPLOAD_BYTES) throw failure3("Select a backup containing no more than 10 GB of workspace data.", 413);
77148
77468
  replacePrevious(auth, ["upload", "staged"]);
77149
77469
  if (artifacts.size >= 4) throw failure3("There are already four backup files in progress. Restart or wait an hour before uploading another.", 409);
77150
- const root = join62(options.dataDir, ".backups");
77470
+ const root = join63(options.dataDir, ".backups");
77151
77471
  mkdirSync32(root, { recursive: true, mode: 448 });
77152
77472
  if (lstatSync9(root).isSymbolicLink()) throw failure3("Backup storage must not be a symbolic link.");
77153
- const directory = join62(root, "uploads");
77473
+ const directory = join63(root, "uploads");
77154
77474
  mkdirSync32(directory, { recursive: true, mode: 448 });
77155
77475
  if (lstatSync9(directory).isSymbolicLink()) throw failure3("Backup storage must not be a symbolic link.");
77156
77476
  const id = randomUUID26();
77157
- const file2 = join62(directory, `${id}.ombbackup`);
77477
+ const file2 = join63(directory, `${id}.ombbackup`);
77158
77478
  let bytes = 0;
77159
77479
  try {
77160
77480
  const limit = new Transform2({ transform(chunk, _encoding, callback) {
@@ -77168,7 +77488,7 @@ function createWorkspaceBackupRoutes(options) {
77168
77488
  json4(res, 200, { id });
77169
77489
  } catch (error51) {
77170
77490
  try {
77171
- unlinkSync12(file2);
77491
+ unlinkSync13(file2);
77172
77492
  } catch {
77173
77493
  }
77174
77494
  throw error51;
@@ -77187,7 +77507,7 @@ function createWorkspaceBackupRoutes(options) {
77187
77507
  throw error51;
77188
77508
  }
77189
77509
  try {
77190
- unlinkSync12(artifact.path);
77510
+ unlinkSync13(artifact.path);
77191
77511
  } catch {
77192
77512
  }
77193
77513
  artifacts.delete(body.id);
@@ -78356,7 +78676,7 @@ function resolveRequestAuth(req, options) {
78356
78676
 
78357
78677
  // server/sessions.ts
78358
78678
  import { createHash as createHash29, randomBytes as randomBytes8, randomUUID as randomUUID27, timingSafeEqual as timingSafeEqual4 } from "node:crypto";
78359
- import { closeSync as closeSync13, existsSync as existsSync30, fsyncSync as fsyncSync4, mkdirSync as mkdirSync33, openSync as openSync13, readFileSync as readFileSync43, unlinkSync as unlinkSync13 } from "node:fs";
78679
+ import { closeSync as closeSync13, existsSync as existsSync30, fsyncSync as fsyncSync4, mkdirSync as mkdirSync33, openSync as openSync13, readFileSync as readFileSync43, unlinkSync as unlinkSync14 } from "node:fs";
78360
78680
  import { dirname as dirname18 } from "node:path";
78361
78681
  var SCOPES = ["admin", "client"];
78362
78682
  var PAIRING_CODE_ALPHABET = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ";
@@ -78502,7 +78822,7 @@ var SessionRegistry = class {
78502
78822
  try {
78503
78823
  this.revalidateEmailSessions();
78504
78824
  this.persist();
78505
- unlinkSync13(this.openMarker);
78825
+ unlinkSync14(this.openMarker);
78506
78826
  this.closed = true;
78507
78827
  } catch {
78508
78828
  throw new Error("Could not safely close session storage.");
@@ -78786,7 +79106,7 @@ var SessionRegistry = class {
78786
79106
 
78787
79107
  // server/brand.ts
78788
79108
  import { existsSync as existsSync31, readFileSync as readFileSync44 } from "node:fs";
78789
- import { join as join63 } from "node:path";
79109
+ import { join as join64 } from "node:path";
78790
79110
  var HEX_COLOUR = /^#[0-9a-fA-F]{6}$/;
78791
79111
  var MAX_LOGO_CHARS = 3e5;
78792
79112
  var MAX_FAVICON_CHARS = 12e4;
@@ -78806,7 +79126,7 @@ var brandSchema = external_exports.object({
78806
79126
  }).strict();
78807
79127
  var DEFAULT_BRAND = { name: "OpenMausBot" };
78808
79128
  function brandFile() {
78809
- return process.env.OMB_BRAND_FILE || join63(DATA_DIR, "brand.json");
79129
+ return process.env.OMB_BRAND_FILE || join64(DATA_DIR, "brand.json");
78810
79130
  }
78811
79131
  function loadBrand(options = {}) {
78812
79132
  const file2 = options.file ?? brandFile();
@@ -81326,7 +81646,7 @@ function releaseDataDirLeaseAtExit() {
81326
81646
  }
81327
81647
  process.once("exit", releaseDataDirLeaseAtExit);
81328
81648
  var workspaceRestore = { restored: false };
81329
- if (existsSync32(join64(DATA_DIR, ".backups"))) {
81649
+ if (existsSync32(join65(DATA_DIR, ".backups"))) {
81330
81650
  workspaceRestore = applyPendingWorkspaceRestore(DATA_DIR);
81331
81651
  if (!workspaceRestore.restored && !workspaceRestore.rolledBack) {
81332
81652
  workspaceRestore = readLastWorkspaceRestore(DATA_DIR) ?? workspaceRestore;
@@ -81339,7 +81659,7 @@ function signInAllowList() {
81339
81659
  return { admins: parseAllowList(current2?.admins?.join(",")), members: parseAllowList(current2?.members?.join(",")) };
81340
81660
  }
81341
81661
  var sessions = new SessionRegistry({
81342
- file: join64(DATA_DIR, "sessions.json"),
81662
+ file: join65(DATA_DIR, "sessions.json"),
81343
81663
  emailScopesSnapshot: () => {
81344
81664
  const membership = signInAllowList();
81345
81665
  return (email3) => allowedScopes(email3, membership);
@@ -81395,7 +81715,7 @@ function noteTurnTrigger(threadId, auth) {
81395
81715
  var providerAuthSessions = new ProviderAuthSessions();
81396
81716
  await registry2.load(instanceConfigs(cfg));
81397
81717
  var bundledSkills = loadBundledSkills();
81398
- var availableSkills = () => mergeSkills(bundledSkills, loadUserSkills(join64(DATA_DIR, "skills")));
81718
+ var availableSkills = () => mergeSkills(bundledSkills, loadUserSkills(join65(DATA_DIR, "skills")));
81399
81719
  var utilityParentPort = process.parentPort;
81400
81720
  function postDesktopPrivateMessage(message4) {
81401
81721
  if (!utilityParentPort) return false;
@@ -81421,7 +81741,7 @@ function applyDesktopMutationTokenMessage(raw) {
81421
81741
  return true;
81422
81742
  }
81423
81743
  var browserCleanup = new BrowserCleanupCoordinator({
81424
- file: join64(DATA_DIR, "browser-cleanups.json"),
81744
+ file: join65(DATA_DIR, "browser-cleanups.json"),
81425
81745
  send: (request3) => {
81426
81746
  const status = browserEngineStatus();
81427
81747
  const sessions2 = request3.type === "openmausbot:browser-bot-deleted" && request3.botId ? [browserSessionId(request3.botId, "")] : request3.partitionId ? [browserSessionId("", request3.partitionId)] : [];
@@ -81657,6 +81977,7 @@ function claimTurnResource(owner2, resource) {
81657
81977
  }
81658
81978
  function releaseTurnResources(owner2) {
81659
81979
  if (!owner2) return;
81980
+ if (autoVmClaims.get(owner2.threadId)?.owner.generation === owner2.generation) autoVmClaims.delete(owner2.threadId);
81660
81981
  if (settlingResourceOwners.get(owner2.threadId) === owner2.generation) settlingResourceOwners.delete(owner2.threadId);
81661
81982
  turnResources.release(owner2);
81662
81983
  if (turnResourceOwners.get(owner2.threadId)?.generation === owner2.generation) turnResourceOwners.delete(owner2.threadId);
@@ -81714,6 +82035,10 @@ function botAtThreadCapacity(botId) {
81714
82035
  function hasDirectDispatch(botId) {
81715
82036
  return [...directTurnDispatchClaims.values()].some((claim) => claim.botId === botId);
81716
82037
  }
82038
+ function unattendedDispatchState(botId) {
82039
+ const bot = store.bot(botId);
82040
+ return !bot ? "missing" : botAtThreadCapacity(botId) || activeGroupTurnForBot(botId) ? "busy" : "ready";
82041
+ }
81717
82042
  function requestedTaskBot(botId, rawThreadId) {
81718
82043
  const profile = store.bot(botId);
81719
82044
  if (!profile) throw Object.assign(new Error("no such bot"), { status: 404 });
@@ -82173,7 +82498,7 @@ function checkedMemberIds(value) {
82173
82498
  }
82174
82499
  var bootSelection = { instanceId: "", model: "" };
82175
82500
  var store = new Store(() => bootSelection);
82176
- var teamComputers = new TeamComputers(join64(DATA_DIR, "team-computers.json"), ENVIRONMENT_ID);
82501
+ var teamComputers = new TeamComputers(join65(DATA_DIR, "team-computers.json"), ENVIRONMENT_ID);
82177
82502
  var followupsReady = false;
82178
82503
  var sendSequencer = new SendSequencer();
82179
82504
  bootSelection = await defaultSelection();
@@ -82194,7 +82519,7 @@ try {
82194
82519
  }
82195
82520
  if (browserCleanupReferencesReconciled) browserCleanup.startPending();
82196
82521
  var activeCoordinationForThread = (_threadId) => false;
82197
- var wireTask = ({ resumeCursors: _resumeCursors, lastInstanceId: _lastInstanceId, ...task2 }) => activeCoordinationForThread(task2.threadId) && !task2.busy ? { ...task2, busy: true, activity: "working" } : task2;
82522
+ var wireTask = (task2) => activeCoordinationForThread(task2.threadId) && !task2.busy ? { ...toWireTask(task2), busy: true, activity: "working" } : toWireTask(task2);
82198
82523
  var wireBot = (bot) => {
82199
82524
  const { resumeCursors: _resumeCursors, tasks, approvalGrant, lastProfileRequestId: _lastProfileRequestId, lastTeamSetupReceipt: _lastTeamSetupReceipt, ...rest } = bot;
82200
82525
  const visible = approvalGrant && !approvalGrant.threadOnly ? { ...rest, approvalMode: "ask", autoApprove: false } : rest;
@@ -82337,6 +82662,30 @@ function fullAccessForSource(botId, threadId) {
82337
82662
  function peerReviewRequired(bot, threadId) {
82338
82663
  return Boolean(bot.approvePeerComms && !fullAccessForSource(bot.id, threadId));
82339
82664
  }
82665
+ function delegatedFullAccess(from, fromThreadId, target) {
82666
+ return delegationInheritsFullAccess({
82667
+ senderIsChief: Boolean(from.chiefOfStaff),
82668
+ senderHasFullAccess: fullAccessForSource(from.id, fromThreadId),
82669
+ sameBot: from.id === target.id,
82670
+ recipientDriverKind: registry2.cliTarget(target.modelSelection.instanceId)?.driverKind
82671
+ });
82672
+ }
82673
+ function grantDelegatedFullAccess(from, target, threadId) {
82674
+ if (store.taskByThread(target.id, threadId)?.approvalMode === "full") return;
82675
+ store.patchTask(target.id, threadId, { approvalMode: "full", autoApprove: false, alwaysAllow: [] });
82676
+ store.appendMessage(threadId, {
82677
+ role: "bot",
82678
+ kind: "activity",
82679
+ tool: { name: `Full access \u2014 delegated by ${from.name}, a Chief of Staff with Full access`, ok: true }
82680
+ });
82681
+ }
82682
+ function roomTurnApprovalMode(bot, orchestration) {
82683
+ const handoff = orchestration?.roomHandoffId ? roomHandoffs.nodes.get(orchestration.roomHandoffId) : void 0;
82684
+ const source = handoff?.parentId ? roomHandoffs.nodes.get(handoff.parentId) : void 0;
82685
+ const from = source ? store.bot(source.botId) : void 0;
82686
+ if (from && source && delegatedFullAccess(from, source.threadId, bot)) return "full";
82687
+ return approvalModeForTurn(bot, Boolean(orchestration?.roomHandoffId));
82688
+ }
82340
82689
  function handleDesktopTrustedApprovalMessage(raw) {
82341
82690
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) return false;
82342
82691
  const message4 = raw;
@@ -82834,7 +83183,7 @@ function outstandingAssignmentsPrompt(threadId) {
82834
83183
  }));
82835
83184
  return ` Assignments you already sent are still outstanding, and nothing in this conversation cancelled them: ${JSON.stringify(listed)}. Do not send them again, do not poll or wait for them, and do not tell the user they were lost. Each result returns to this conversation on its own and resumes you then. Answer the message above with that work still in flight.`;
82836
83185
  }
82837
- var roomHandoffs = new RoomHandoffs(join64(DATA_DIR, "room-handoffs.json"), {
83186
+ var roomHandoffs = new RoomHandoffs(join65(DATA_DIR, "room-handoffs.json"), {
82838
83187
  validate: (node2, parent) => roomHandoffProblem(node2, parent) ?? (parent && store.bot(parent.botId)?.approvePeerComms && !fullAccessForSource(parent.botId, parent.threadId) && !node2.approvalGranted ? "Sender now requires peer approval; submit a new approved request" : void 0),
82839
83188
  // A direct follow-up is owed to one conversation, so it waits for that
82840
83189
  // conversation, not for the whole bot. Bot-level busy aggregates every
@@ -82984,7 +83333,7 @@ var roomHandoffs = new RoomHandoffs(join64(DATA_DIR, "room-handoffs.json"), {
82984
83333
  await tracked;
82985
83334
  return { ok: result.outcome === "settled", text: result.stopReason || result.replyText || result.outcome || "The addressed agent could not run" };
82986
83335
  }
82987
- });
83336
+ }, Date.now, roomHandoffLimits(cfg));
82988
83337
  activeCoordinationForThread = (threadId) => roomHandoffs.activeDirect(threadId);
82989
83338
  function publicGroupState(group) {
82990
83339
  return { ...group, working: groupIsWorking(group) || [...roomHandoffs.nodes.values()].some((n) => n.groupId === group.id && !["completed", "failed", "cancelled"].includes(n.status)) };
@@ -83594,6 +83943,8 @@ var localVmLeases = new LocalVmLeasePool(30 * 6e4);
83594
83943
  var localVmLifecycleBusy = /* @__PURE__ */ new Set();
83595
83944
  var localVmThreadTargets = /* @__PURE__ */ new Map();
83596
83945
  var localVmActiveThreads = /* @__PURE__ */ new Map();
83946
+ var autoVmClaims = /* @__PURE__ */ new Map();
83947
+ var LAZY_VM_CLAIM_GRACE_MS = 5e3;
83597
83948
  var localVmSeen = /* @__PURE__ */ new Set();
83598
83949
  function noteLocalVmSeen(target, status) {
83599
83950
  if (status && autoLocalVmAttachable(status)) localVmSeen.add(target.key);
@@ -83945,6 +84296,7 @@ function localVmIdleFor(target) {
83945
84296
  return idle;
83946
84297
  }
83947
84298
  function releaseLocalVmThread(threadId) {
84299
+ autoVmClaims.delete(threadId);
83948
84300
  const target = localVmThreadTargets.get(threadId);
83949
84301
  if (!target) return;
83950
84302
  localVmLeaseFor(target).release(threadId);
@@ -84954,10 +85306,15 @@ async function startTurn(botId, text8, opts) {
84954
85306
  }
84955
85307
  const skipTranscript = /* @__PURE__ */ new Set([userMessage.id, ...opts?.excludeMessageIds ?? []]);
84956
85308
  const activeMessages = store.activePath(threadId);
85309
+ const coordinationChildren = opts?.coordination?.resumed ? new Set(roomHandoffs.children(opts.coordination.id).map((child) => child.id)) : void 0;
85310
+ for (const m3 of activeMessages) {
85311
+ if (!opts?.coordination || !m3.roomRequest) continue;
85312
+ if (m3.roomRequest.phase === "request" && m3.roomRequest.id === opts.coordination.id || m3.roomRequest.phase === "result" && coordinationChildren?.has(m3.roomRequest.id)) skipTranscript.add(m3.id);
85313
+ }
84957
85314
  const messagesById = new Map(store.messagesFor(threadId).map((message4) => [message4.id, message4]));
84958
85315
  const transcript = activeMessages.filter((m3) => (m3.kind === "text" && m3.text || m3.roomRequest?.phase === "result") && !skipTranscript.has(m3.id)).slice(-40).map((m3) => ({
84959
85316
  role: m3.role === "user" ? "user" : "assistant",
84960
- text: m3.roomRequest?.phase === "result" ? teammateReportContext(m3.roomRequest.id, bot.id) : transcriptText(m3, messagesById, cfg.profile?.name?.trim() || "User")
85317
+ text: m3.roomRequest?.phase === "result" ? teammateReportContext(m3.roomRequest.id, bot.id) : m3.role !== "user" && m3.from ? peerMessageText(m3.from.name, transcriptText(m3, messagesById, cfg.profile?.name?.trim() || "User")) : transcriptText(m3, messagesById, cfg.profile?.name?.trim() || "User")
84961
85318
  }));
84962
85319
  const rewound = Boolean(task2.rewound);
84963
85320
  const externalContextMarker = isExternalContextMarker(task2.lastInstanceId) ? task2.lastInstanceId : void 0;
@@ -85064,45 +85421,99 @@ async function startTurn(botId, text8, opts) {
85064
85421
  let browserCapture = null;
85065
85422
  let computerKind = null;
85066
85423
  let autoVpsProblem = null;
85424
+ const localVmPreviewFor = (localVmTarget, claimThreadId) => () => {
85425
+ const owner2 = localVmLeaseFor(localVmTarget).current(localVmOwnerBusy);
85426
+ if (owner2 && owner2.threadId !== claimThreadId) {
85427
+ throw new Error("the Local VM moved on to another turn");
85428
+ }
85429
+ return containerComputerFrame(void 0, void 0, localVmTarget);
85430
+ };
85431
+ const claimAutoLocalVm = async (claimThreadId, pinnedTarget) => {
85432
+ const localVmTarget = pinnedTarget ?? localVmTargetForBot(bot.id);
85433
+ await bindTurnComputer(resourceOwner, `computer:vm:${localVmTarget.key}`, true);
85434
+ if (localVmImageBusy || localVmModeChangeBusy || localVmLifecycleBusy.has(localVmTarget.key)) {
85435
+ throw new Error("this Local VM is being started, stopped, or replaced \u2014 wait for setup to finish");
85436
+ }
85437
+ if (!localVmLeaseFor(localVmTarget).claim(claimThreadId, bot.id, localVmOwnerBusy)) {
85438
+ throw new Error("this Local VM is already being used by another turn \u2014 wait for that turn to finish");
85439
+ }
85440
+ localVmThreadTargets.set(claimThreadId, localVmTarget);
85441
+ localVmActiveThreads.set(localVmTarget.key, claimThreadId);
85442
+ localVmIdleFor(localVmTarget).touch();
85443
+ const dropLease = () => {
85444
+ localVmLeaseFor(localVmTarget).release(claimThreadId);
85445
+ if (localVmActiveThreads.get(localVmTarget.key) === claimThreadId) localVmActiveThreads.delete(localVmTarget.key);
85446
+ localVmThreadTargets.delete(claimThreadId);
85447
+ const resource = `computer:vm:${localVmTarget.key}`;
85448
+ turnResources.releaseOne(resource, resourceOwner);
85449
+ if (turnComputerResources.get(resourceOwner.threadId)?.resource === resource) turnComputerResources.delete(resourceOwner.threadId);
85450
+ };
85451
+ let localVm;
85452
+ try {
85453
+ localVm = await readyLocalVmForTurn(bot.id, localVmTarget);
85454
+ } catch (error51) {
85455
+ dropLease();
85456
+ throw error51;
85457
+ }
85458
+ if (!localVm.ready || !localVm.runtime) {
85459
+ dropLease();
85460
+ throw new Error(`${localVm.problem ?? "the Local VM is not ready"} (App Settings \u2192 Computers)`);
85461
+ }
85462
+ previewCapture = localVmPreviewFor(localVmTarget, claimThreadId);
85463
+ return { target: localVmTarget, runtime: localVm.runtime };
85464
+ };
85067
85465
  const attachLocalVm = async (strict) => {
85068
85466
  if (!mountsComputerMcp || instance.driverKind === "boxAgent") {
85069
85467
  if (!strict) return false;
85070
85468
  throw new Error("this model engine cannot use the Local VM \u2014 choose Claude or an ACP engine, or select another computer destination");
85071
85469
  }
85072
85470
  const localVmTarget = localVmTargetForBot(bot.id);
85471
+ let lazyReadyVm = null;
85073
85472
  if (!strict) {
85074
85473
  if (!localVmSeen.has(localVmTarget.key) && !opts?.automationSource) return false;
85075
85474
  const seen = await containerComputerStatus(void 0, void 0, localVmTarget).catch(() => null);
85076
85475
  if (!seen || !autoLocalVmAttachable(seen)) return false;
85077
85476
  if (localVmImageBusy || localVmModeChangeBusy || localVmLifecycleBusy.has(localVmTarget.key)) return false;
85477
+ if (seen.ready && seen.runtime) lazyReadyVm = { runtime: seen.runtime };
85078
85478
  }
85079
85479
  try {
85080
- await bindTurnComputer(resourceOwner, `computer:vm:${localVmTarget.key}`, true);
85081
- if (localVmImageBusy || localVmModeChangeBusy || localVmLifecycleBusy.has(localVmTarget.key)) {
85082
- throw new Error("this Local VM is being started, stopped, or replaced \u2014 wait for setup to finish");
85083
- }
85084
- if (!localVmLeaseFor(localVmTarget).claim(threadId, bot.id, localVmOwnerBusy)) {
85085
- throw new Error("this Local VM is already being used by another turn \u2014 wait for that turn to finish");
85086
- }
85087
- localVmThreadTargets.set(threadId, localVmTarget);
85088
- localVmActiveThreads.set(localVmTarget.key, threadId);
85089
- localVmIdleFor(localVmTarget).touch();
85090
- const localVm = await readyLocalVmForTurn(bot.id, localVmTarget);
85091
- if (!localVm.ready || !localVm.runtime) {
85092
- throw new Error(`${localVm.problem ?? "the Local VM is not ready"} (App Settings \u2192 Computers)`);
85480
+ if (lazyReadyVm) {
85481
+ integrations.localComputer = containerComputerMcp(
85482
+ lazyReadyVm.runtime,
85483
+ controlIntegration(bot.id, threadId, dispatchClaimId),
85484
+ localVmTarget
85485
+ );
85486
+ autoVmClaims.set(threadId, {
85487
+ owner: resourceOwner,
85488
+ lazy: true,
85489
+ claim: async () => {
85490
+ await claimAutoLocalVm(threadId, localVmTarget);
85491
+ if (previewCapture && threadBusy(bot.id, threadId)) {
85492
+ const touched = screenPollers.get(threadId)?.touched ?? instance.driverKind === "boxAgent";
85493
+ stopScreenPoller(bot.id, threadId);
85494
+ startScreenPoller(
85495
+ bot.id,
85496
+ threadId,
85497
+ { computer: previewCapture, ...browserCapture ? { browser: browserCapture } : {} },
85498
+ { screenIsTheWork: touched }
85499
+ );
85500
+ }
85501
+ }
85502
+ });
85503
+ return true;
85093
85504
  }
85505
+ const claimed = await claimAutoLocalVm(threadId);
85094
85506
  integrations.localComputer = containerComputerMcp(
85095
- localVm.runtime,
85507
+ claimed.runtime,
85096
85508
  controlIntegration(bot.id, threadId, dispatchClaimId),
85097
- localVmTarget
85509
+ claimed.target
85098
85510
  );
85099
- previewCapture = () => {
85100
- const owner2 = localVmLeaseFor(localVmTarget).current(localVmOwnerBusy);
85101
- if (owner2 && owner2.threadId !== threadId) {
85102
- throw new Error("the Local VM moved on to another turn");
85511
+ autoVmClaims.set(threadId, {
85512
+ owner: resourceOwner,
85513
+ claim: async () => {
85514
+ await claimAutoLocalVm(threadId);
85103
85515
  }
85104
- return containerComputerFrame(void 0, void 0, localVmTarget);
85105
- };
85516
+ });
85106
85517
  return true;
85107
85518
  } catch (error51) {
85108
85519
  if (strict) throw error51;
@@ -85118,7 +85529,7 @@ async function startTurn(botId, text8, opts) {
85118
85529
  hostPlatform: process.platform,
85119
85530
  providerSupportsLocal: mountsLocalComputer
85120
85531
  })) {
85121
- throw new Error("this model engine cannot control this computer \u2014 choose Claude or an ACP engine, or select another destination");
85532
+ throw new Error(mountsLocalComputer ? `local computer control is not available on ${process.platform} \u2014 select another destination` : "this model engine cannot control this computer \u2014 choose Claude or an ACP engine, or select another destination");
85122
85533
  }
85123
85534
  const cua = readCuaConnection();
85124
85535
  if (!cua) throw new Error("CUA Driver is not ready for this computer \u2014 check permissions and restart OpenMausBot");
@@ -85583,10 +85994,7 @@ _loadPending();
85583
85994
  routines = new RoutineManager({
85584
85995
  emit: broadcast,
85585
85996
  hasPendingDelegations: (threadId) => pendingThreads().includes(threadId) || [...delegationWatch.values()].some((watch) => watch.sourceThreadId === threadId) || pendingDelegationWakes.has(threadId),
85586
- botState: (botId) => {
85587
- const bot = store.bot(botId);
85588
- return !bot ? "missing" : bot.busy ? "busy" : "ready";
85589
- },
85997
+ botState: unattendedDispatchState,
85590
85998
  goalState: (groupId, coordinatorBotId) => {
85591
85999
  const group = store.group(groupId);
85592
86000
  const coordinator = store.bot(coordinatorBotId);
@@ -85885,12 +86293,6 @@ async function deleteBotWithLifecycle(botId, revalidate = () => {
85885
86293
  const acknowledged = await browserCleanup.ensure(committedCleanup);
85886
86294
  requireBrowserCleanupAcknowledged(acknowledged, `Browser data for ${bot.name}`);
85887
86295
  }
85888
- for (const dir of [EVENTS_DIR, NATIVE_DIR]) {
85889
- try {
85890
- unlinkSync14(join64(dir, `${bot.threadId}.ndjson`));
85891
- } catch {
85892
- }
85893
- }
85894
86296
  return deletionResponse(200, { ok: true });
85895
86297
  } finally {
85896
86298
  if (claimedLocalVmTarget) localVmLifecycleBusy.delete(claimedLocalVmTarget.key);
@@ -86179,10 +86581,7 @@ function resolveAndSendProfile(res, args) {
86179
86581
  }
86180
86582
  var webhooks = new WebhookManager({
86181
86583
  emit: broadcast,
86182
- botState: (botId) => {
86183
- const bot = store.bot(botId);
86184
- return !bot ? "missing" : bot.busy ? "busy" : "ready";
86185
- },
86584
+ botState: unattendedDispatchState,
86186
86585
  enqueue: (input) => routines.enqueueWebhook(input),
86187
86586
  findRun: (webhookId, deliveryId2) => routines.webhookRunReceipt(webhookId, deliveryId2),
86188
86587
  cancelQueued: (webhookId, message4) => routines.cancelQueuedWebhook(webhookId, message4),
@@ -86267,7 +86666,7 @@ async function runGroupMemberTurn(groupId, threadId, botId, hop, spoken = /* @__
86267
86666
  }
86268
86667
  revokeInternalCapabilitiesForThread(threadId);
86269
86668
  spoken.add(botId);
86270
- const preparedApprovalMode = approvalModeForTurn(bot, Boolean(orchestration?.roomHandoffId));
86669
+ const preparedApprovalMode = roomTurnApprovalMode(bot, orchestration);
86271
86670
  const preparedSelection = { ...bot.modelSelection };
86272
86671
  const preparedComposio = bot.composio;
86273
86672
  const instance = turnInstance(bot);
@@ -86378,7 +86777,7 @@ async function runGroupMemberTurn(groupId, threadId, botId, hop, spoken = /* @__
86378
86777
  const readyGroup = store.group(group.id);
86379
86778
  const stillOwnsThread = readyGroup?.dm ? readyGroup.threadId === threadId : Boolean(readyGroup && store.groupTaskByThread(readyGroup.id, threadId));
86380
86779
  if (!readyGroup || !stillOwnsThread || !readyGroup.memberIds.includes(readyBot.id)) return false;
86381
- const setupChanged = turnInstance(readyBot) !== instance || approvalModeForTurn(readyBot, Boolean(orchestration?.roomHandoffId)) !== preparedApprovalMode || readyBot.modelSelection.instanceId !== preparedSelection.instanceId || readyBot.modelSelection.model !== preparedSelection.model || readyBot.modelSelection.effort !== preparedSelection.effort || readyBot.modelSelection.variant !== preparedSelection.variant || readyBot.composio !== preparedComposio;
86780
+ const setupChanged = turnInstance(readyBot) !== instance || roomTurnApprovalMode(readyBot, orchestration) !== preparedApprovalMode || readyBot.modelSelection.instanceId !== preparedSelection.instanceId || readyBot.modelSelection.model !== preparedSelection.model || readyBot.modelSelection.effort !== preparedSelection.effort || readyBot.modelSelection.variant !== preparedSelection.variant || readyBot.composio !== preparedComposio;
86382
86781
  if (setupChanged) {
86383
86782
  if (setupRetry === 0) {
86384
86783
  return runGroupMemberTurn(
@@ -86682,7 +87081,7 @@ ${e.text}`;
86682
87081
  text: text8,
86683
87082
  refreshSystemPrompt: true,
86684
87083
  images: turnImages,
86685
- approvalMode: approvalModeForTurn(readyBot, Boolean(orchestration?.roomHandoffId)),
87084
+ approvalMode: roomTurnApprovalMode(readyBot, orchestration),
86686
87085
  system: roomSystem.text,
86687
87086
  systemStable: roomSystem.stable,
86688
87087
  systemVolatile: roomSystem.volatile,
@@ -88329,7 +88728,7 @@ var claudeUpdatesInFlight = /* @__PURE__ */ new Set();
88329
88728
  function serveStatic(res, path) {
88330
88729
  if (!STATIC_DIR) return false;
88331
88730
  const safe = path === "/" ? "/index.html" : path.replace(/\.\./g, "");
88332
- const file2 = join64(STATIC_DIR, safe);
88731
+ const file2 = join65(STATIC_DIR, safe);
88333
88732
  try {
88334
88733
  const data = readFileSync45(file2);
88335
88734
  res.writeHead(200, { "content-type": MIME[extname5(file2)] ?? "application/octet-stream" });
@@ -88337,7 +88736,7 @@ function serveStatic(res, path) {
88337
88736
  return true;
88338
88737
  } catch {
88339
88738
  try {
88340
- const data = readFileSync45(join64(STATIC_DIR, "index.html"));
88739
+ const data = readFileSync45(join65(STATIC_DIR, "index.html"));
88341
88740
  res.writeHead(200, { "content-type": "text/html" });
88342
88741
  res.end(data);
88343
88742
  return true;
@@ -89354,15 +89753,19 @@ var handleRequest = async (req, res) => {
89354
89753
  if (method === "POST" && path === "/api/internal/ask-bot") {
89355
89754
  const body = await readInternalBody();
89356
89755
  const fromBotId = internalSender.id;
89357
- const toBotId = String(body.toBotId ?? "");
89756
+ const toBotRef = String(body.toBotId ?? "");
89358
89757
  const message4 = String(body.message ?? "").trim();
89359
89758
  if (body.depth !== void 0 && (!Number.isInteger(body.depth) || body.depth < 0 || body.depth !== internalCapability.depth)) {
89360
89759
  return json3(res, 403, { error: "the recursion depth does not match this turn" });
89361
89760
  }
89362
89761
  const depth = internalCapability.depth;
89363
- if (!toBotId || !message4) return json3(res, 400, { error: "toBotId and message required" });
89364
- if (toBotId === fromBotId) return json3(res, 400, { error: "a bot cannot message itself" });
89762
+ if (!toBotRef || !message4) return json3(res, 400, { error: "toBotId and message required" });
89763
+ if (toBotRef === fromBotId) return json3(res, 400, { error: "a bot cannot message itself" });
89365
89764
  if (depth >= MAX_COMMS_DEPTH) return json3(res, 200, { error: "message chains are limited to one hop" });
89765
+ const resolvedTo = resolveTeammate(store.bots, internalSender, toBotRef);
89766
+ if ("error" in resolvedTo) return json3(res, 404, { error: `no such bot: ${resolvedTo.error}` });
89767
+ if (resolvedTo.id === fromBotId) return json3(res, 400, { error: "a bot cannot message itself" });
89768
+ const toBotId = resolvedTo.id;
89366
89769
  const target = store.bot(toBotId);
89367
89770
  if (!target) return json3(res, 404, { error: "no such bot" });
89368
89771
  const from = internalSender;
@@ -89517,15 +89920,18 @@ var handleRequest = async (req, res) => {
89517
89920
  }
89518
89921
  if (method === "POST" && path === "/api/internal/delegate-bot") {
89519
89922
  const body = await readInternalBody();
89520
- const toBotId = String(body.toBotId ?? "");
89923
+ const toBotRef = String(body.toBotId ?? "");
89521
89924
  const message4 = String(body.message ?? "").trim();
89522
89925
  const reason = typeof body.reason === "string" && body.reason.trim() ? body.reason.trim() : void 0;
89523
89926
  if (body.depth !== void 0 && (!Number.isInteger(body.depth) || body.depth < 0 || body.depth !== internalCapability.depth)) {
89524
89927
  return json3(res, 403, { error: "the recursion depth does not match this turn" });
89525
89928
  }
89526
89929
  const depth = internalCapability.depth;
89527
- if (!toBotId || !message4) return json3(res, 400, { error: "toBotId and message required" });
89930
+ if (!toBotRef || !message4) return json3(res, 400, { error: "toBotId and message required" });
89528
89931
  const from = internalSender;
89932
+ const resolvedTo = resolveTeammate(store.bots, from, toBotRef);
89933
+ if ("error" in resolvedTo) return json3(res, 404, { error: `no such bot: ${resolvedTo.error}` });
89934
+ const toBotId = resolvedTo.id;
89529
89935
  const target = store.bot(toBotId);
89530
89936
  if (!target) return json3(res, 404, { error: "no such bot" });
89531
89937
  if (!canAccessTeam(from, target.section) || target.hidden) {
@@ -89598,13 +90004,21 @@ var handleRequest = async (req, res) => {
89598
90004
  const groupId = parsed.data.groupId ?? source?.id;
89599
90005
  const destination = groupId ? store.group(groupId) : void 0;
89600
90006
  if (groupId && !destination) return json3(res, 404, { error: "No such room; use list_room_targets." });
89601
- const targets = parsed.data.botIds.map((botId) => ({
90007
+ const botIds = [];
90008
+ for (const raw of parsed.data.botIds) {
90009
+ const resolved = resolveTeammate(store.bots, internalSender, raw);
90010
+ if ("error" in resolved) return json3(res, 403, { error: resolved.error });
90011
+ botIds.push(resolved.id);
90012
+ }
90013
+ if (new Set(botIds).size !== botIds.length) return json3(res, 400, { error: "bot_ids name the same teammate twice \u2014 send each teammate once" });
90014
+ const targets = botIds.map((botId) => ({
89602
90015
  groupId: destination?.id,
89603
90016
  threadId: destination ? destination.id === source?.id ? address.threadId : destination.threadId : store.bot(botId)?.threadId ?? "",
89604
90017
  botId
89605
90018
  }));
89606
90019
  for (const target of targets) {
89607
- const eligibility = target.botId === internalSender.id ? "Choose a teammate, not yourself" : roomHandoffProblem(target, address);
90020
+ const addressed = store.bot(target.botId);
90021
+ const eligibility = target.botId === internalSender.id ? "Choose a teammate, not yourself" : !addressed ? `No bot with id "${target.botId}" \u2014 call list_bots and copy the exact id from the result` : addressed.hidden ? `The bot with id "${target.botId}" is no longer available \u2014 call list_bots for the ones you can reach` : roomHandoffProblem(target, address);
89608
90022
  if (eligibility) return json3(res, 403, { error: eligibility });
89609
90023
  }
89610
90024
  let approvalGranted = false;
@@ -89631,6 +90045,9 @@ var handleRequest = async (req, res) => {
89631
90045
  if (!resolved) throw new Error("The recipient no longer exists");
89632
90046
  target.threadId = resolved.task.threadId;
89633
90047
  if (resolved.created) createdThread = resolved.task.threadId;
90048
+ if (delegatedFullAccess(internalSender, internalCapability.threadId, store.bot(target.botId))) {
90049
+ grantDelegatedFullAccess(internalSender, store.bot(target.botId), target.threadId);
90050
+ }
89634
90051
  }
89635
90052
  const { node: node2, duplicate } = roomHandoffs.enqueue(
89636
90053
  address,
@@ -89829,6 +90246,7 @@ ${message4}`;
89829
90246
  }
89830
90247
  const task2 = store.createTask(target.id, title, false, projectId, { botId: from.id, name: from.name, at: Date.now() });
89831
90248
  if (!task2) return json3(res, 500, { error: "couldn't create that thread" });
90249
+ if (delegatedFullAccess(from, fromThreadId, target)) grantDelegatedFullAccess(from, target, task2.threadId);
89832
90250
  const queued = queueDelegation(
89833
90251
  commsBus,
89834
90252
  from,
@@ -90068,6 +90486,29 @@ ${message4}`;
90068
90486
  if (!bot) return json3(res, 404, { error: "no such bot" });
90069
90487
  if (method === "GET") {
90070
90488
  const snapshot2 = botComputerControlSnapshot(botId, internalCapability.teamComputerId);
90489
+ const slot = autoVmClaims.get(internalCapability.threadId);
90490
+ const lazyClaim = slot && slot.owner.generation === internalCapability.generation ? slot : void 0;
90491
+ if (!snapshot2.held && lazyClaim?.lazy && !lazyClaim.begin) {
90492
+ startAutoVmClaim(autoVmClaims, internalCapability.threadId, internalCapability.generation);
90493
+ await Promise.race([
90494
+ lazyClaim.begin ?? Promise.resolve(),
90495
+ new Promise((resolve16) => setTimeout(resolve16, LAZY_VM_CLAIM_GRACE_MS))
90496
+ ]);
90497
+ }
90498
+ if (!snapshot2.held && lazyClaim?.failed === true) {
90499
+ return json3(res, 200, {
90500
+ held: true,
90501
+ helpOpen: false,
90502
+ blockedReason: `This turn could not claim the Local VM${lazyClaim.failure ? ` (${lazyClaim.failure})` : ""}. This call was not performed. Do not retry computer work in this turn; tell the person what you could not do.`
90503
+ });
90504
+ }
90505
+ if (!snapshot2.held && lazyClaim?.lazy && lazyClaim.begin && !lazyClaim.claimed) {
90506
+ return json3(res, 200, {
90507
+ held: true,
90508
+ helpOpen: false,
90509
+ blockedReason: "Another thread is using this computer. This call was not performed. Pause computer work until that thread finishes, then take a fresh screenshot before acting."
90510
+ });
90511
+ }
90071
90512
  const computer = turnComputerResources.get(internalCapability.threadId);
90072
90513
  if (!snapshot2.held && computer && computer.owner.generation === internalCapability.generation && !claimTurnResource(computer.owner, computer.resource)) {
90073
90514
  return json3(res, 200, {
@@ -91099,14 +91540,6 @@ data: ${JSON.stringify({ kind: "ping" })}
91099
91540
  routines.disableForGroup(group.id);
91100
91541
  store.deleteGroup(group.id);
91101
91542
  rejectDeletedThreadSkillStages(stagedSkillCleanups);
91102
- for (const threadId of threadIds) {
91103
- for (const dir of [EVENTS_DIR, NATIVE_DIR]) {
91104
- try {
91105
- unlinkSync14(join64(dir, `${threadId}.ndjson`));
91106
- } catch {
91107
- }
91108
- }
91109
- }
91110
91543
  return json3(res, 200, { ok: true });
91111
91544
  }
91112
91545
  m3 = path.match(/^\/api\/groups\/([\w-]+)\/messages$/);
@@ -91201,6 +91634,74 @@ data: ${JSON.stringify({ kind: "ping" })}
91201
91634
  }
91202
91635
  return json3(res, 200, { ok: true });
91203
91636
  }
91637
+ m3 = path.match(/^\/api\/groups\/([\w-]+)\/queue\/([\w-]+)\/steer$/);
91638
+ if (m3 && method === "POST") {
91639
+ const body = await readBody(req);
91640
+ if (body !== null && (typeof body !== "object" || Array.isArray(body))) {
91641
+ return json3(res, 400, { error: "body must be a JSON object" });
91642
+ }
91643
+ const threadId = typeof body?.threadId === "string" ? body.threadId : void 0;
91644
+ if (threadId !== void 0 && !/^[\w-]+$/.test(threadId)) {
91645
+ return json3(res, 400, { error: "threadId must be a task id" });
91646
+ }
91647
+ const group = store.group(m3[1]);
91648
+ if (!group) return json3(res, 404, { error: "no such room" });
91649
+ const targetThreadId = threadId ?? group.threadId;
91650
+ const ownsThread = group.dm ? group.threadId === targetThreadId : Boolean(store.groupTaskByThread(group.id, targetThreadId));
91651
+ if (!ownsThread) {
91652
+ return json3(res, 409, { error: "the channel switched tasks before it could receive the message" });
91653
+ }
91654
+ noteTurnTrigger(targetThreadId, auth);
91655
+ const current2 = store.group(group.id);
91656
+ if (!current2) return json3(res, 404, { error: "no such room" });
91657
+ const speakerBotId = groupSpeakers.get(targetThreadId)?.botId ?? (targetThreadId === current2.threadId ? current2.busyBotId : void 0);
91658
+ const speaker = speakerBotId ? store.bot(speakerBotId) : void 0;
91659
+ const instance = speaker ? runningTurnInstance(speaker, targetThreadId) : void 0;
91660
+ const held = holdChannelQueue(current2.id, targetThreadId, m3[2]);
91661
+ if (!held) return json3(res, 404, { error: "no such queued message" });
91662
+ if (!speaker || !instance?.adapter.capabilities.queueing || !instance.adapter.steer) {
91663
+ restoreHeldChannelQueue(held);
91664
+ return json3(res, 200, { ok: true, queued: true, threadId: targetThreadId });
91665
+ }
91666
+ const [head] = held.items;
91667
+ if (!head || head.id !== m3[2]) {
91668
+ restoreHeldChannelQueue(held);
91669
+ return json3(res, 409, { error: "only the first queued message can steer" });
91670
+ }
91671
+ const replyTo = resolveHeldReplyTarget(held, resolveReplyTarget);
91672
+ const steered = await instance.adapter.steer(targetThreadId, promptWithReply(head.text, replyTo, cfg.profile?.name?.trim() || "User")).catch(() => "indeterminate");
91673
+ const after = store.group(current2.id);
91674
+ const afterSpeakerBotId = after ? groupSpeakers.get(targetThreadId)?.botId ?? (targetThreadId === after.threadId ? after.busyBotId : void 0) : void 0;
91675
+ const delivered = steered !== "refused";
91676
+ if (after && delivered && (steered === "indeterminate" || afterSpeakerBotId === speakerBotId)) {
91677
+ const message4 = store.appendMessage(targetThreadId, {
91678
+ role: "user",
91679
+ kind: "text",
91680
+ text: head.text,
91681
+ replyToId: head.replyToId,
91682
+ sendId: head.sendId,
91683
+ channelMode: head.mode,
91684
+ queueId: head.id,
91685
+ via: head.via,
91686
+ steered: true
91687
+ });
91688
+ settleHeldChannelQueueHead(held);
91689
+ return json3(res, 200, {
91690
+ ok: true,
91691
+ steered: true,
91692
+ threadId: targetThreadId,
91693
+ messages: [message4],
91694
+ queueIds: [head.id]
91695
+ });
91696
+ }
91697
+ if (steered === "indeterminate" && !after) {
91698
+ settleHeldChannelQueueHead(held);
91699
+ return json3(res, 404, { error: "no such room" });
91700
+ }
91701
+ restoreHeldChannelQueue(held);
91702
+ if (after && !groupIsWorking(after)) drainQueuedChannelSends();
91703
+ return json3(res, 200, { ok: true, queued: true, threadId: targetThreadId });
91704
+ }
91204
91705
  m3 = path.match(/^\/api\/groups\/([\w-]+)\/interrupt$/);
91205
91706
  if (m3 && method === "POST") {
91206
91707
  const group = store.group(m3[1]);
@@ -92235,18 +92736,19 @@ data: ${JSON.stringify({ kind: "ping" })}
92235
92736
  }
92236
92737
  if (currentAtStart.busy) {
92237
92738
  const instance = runningTurnInstance(currentAtStart, threadId);
92238
- let steered = false;
92739
+ let steered = "refused";
92239
92740
  const carriesImages = extractTurnImages(text8).images.length > 0;
92240
92741
  if (!carriesImages && !computerSelectionTurns.get(threadId)?.selected && instance?.adapter.capabilities.queueing && instance.adapter.steer) {
92241
- steered = await instance.adapter.steer(threadId, promptWithReply(text8, replyTo, cfg.profile?.name?.trim() || "User")).catch(() => false);
92742
+ steered = await instance.adapter.steer(threadId, promptWithReply(text8, replyTo, cfg.profile?.name?.trim() || "User")).catch(() => "indeterminate");
92242
92743
  }
92243
92744
  const current2 = store.projectBotForTask(bot.id, threadId);
92244
92745
  if (!current2) throw Object.assign(new Error("no such bot"), { status: 404 });
92245
92746
  if (!store.taskByThread(bot.id, threadId)) {
92246
92747
  throw Object.assign(new Error("the target task no longer exists"), { status: 409 });
92247
92748
  }
92248
- if (steered) {
92249
- if (!current2.busy) {
92749
+ const delivered = steered !== "refused";
92750
+ if (delivered) {
92751
+ if (steered === "steered" && !current2.busy) {
92250
92752
  throw Object.assign(
92251
92753
  new Error("the running turn ended before the steered message could be recorded"),
92252
92754
  { status: 409 }
@@ -92289,6 +92791,53 @@ data: ${JSON.stringify({ kind: "ping" })}
92289
92791
  }
92290
92792
  return json3(res, 200, { ok: true });
92291
92793
  }
92794
+ m3 = path.match(/^\/api\/bots\/([\w-]+)\/queue\/([\w-]+)\/steer$/);
92795
+ if (m3 && method === "POST") {
92796
+ const body = await readBody(req);
92797
+ requirePinnedClientThread(m3[1], body?.threadId);
92798
+ const bot = requestedTaskBot(m3[1], body?.threadId);
92799
+ noteTurnTrigger(bot.threadId, auth);
92800
+ const held = holdSteeredQueue(bot.id, bot.threadId, m3[2]);
92801
+ if (!held) return json3(res, 404, { error: "no such queued message" });
92802
+ if (held.items.some((item) => extractTurnImages(item.text).images.length > 0)) {
92803
+ restoreHeldSteeredQueue(held);
92804
+ return json3(res, 200, { ok: true, queued: true, threadId: bot.threadId });
92805
+ }
92806
+ const currentAtStart = store.projectBotForTask(bot.id, bot.threadId);
92807
+ const instance = currentAtStart?.busy ? runningTurnInstance(currentAtStart, bot.threadId) : void 0;
92808
+ const prompt = held.items.map((item) => item.prompt).join("\n\n");
92809
+ let steered = "refused";
92810
+ if (currentAtStart?.busy && instance?.adapter.capabilities.queueing && instance.adapter.steer) {
92811
+ steered = await instance.adapter.steer(bot.threadId, prompt).catch(() => "indeterminate");
92812
+ }
92813
+ const current2 = store.projectBotForTask(bot.id, bot.threadId);
92814
+ if ((steered === "steered" && current2?.busy || steered === "indeterminate" && current2) && store.taskByThread(bot.id, bot.threadId)) {
92815
+ if (auth.kind === "session" || DESKTOP_MANAGED) clearUnattended(bot.threadId);
92816
+ const messages = held.items.map((item) => store.appendMessage(bot.threadId, {
92817
+ role: "user",
92818
+ kind: "text",
92819
+ text: item.text,
92820
+ replyToId: item.replyToId,
92821
+ sendId: item.sendId,
92822
+ queueId: item.messageId,
92823
+ peerAsk: item.peerAsk,
92824
+ steered: true
92825
+ }));
92826
+ const queueIds = held.items.map((item) => item.messageId);
92827
+ settleHeldSteeredQueue(held);
92828
+ return json3(res, 200, { ok: true, steered: true, threadId: bot.threadId, messages, queueIds });
92829
+ }
92830
+ if (steered === "indeterminate") {
92831
+ settleHeldSteeredQueue(held);
92832
+ if (!store.taskByThread(bot.id, bot.threadId)) {
92833
+ throw Object.assign(new Error("the target task no longer exists"), { status: 409 });
92834
+ }
92835
+ throw Object.assign(new Error("no such bot"), { status: 404 });
92836
+ }
92837
+ restoreHeldSteeredQueue(held);
92838
+ if (current2 && !current2.busy) drainQueuedSends();
92839
+ return json3(res, 200, { ok: true, queued: true, threadId: bot.threadId });
92840
+ }
92292
92841
  m3 = path.match(/^\/api\/bots\/([\w-]+)\/messages\/([\w-]+)\/edit$/);
92293
92842
  if (m3 && method === "POST") {
92294
92843
  const messageId = m3[2];
@@ -94157,6 +94706,27 @@ try {
94157
94706
  } catch (error51) {
94158
94707
  console.warn(`attachments: startup partial cleanup failed: ${error51 instanceof Error ? error51.message : String(error51)}`);
94159
94708
  }
94709
+ var THREAD_LOG_RETENTION_SWEEP_MS = 24 * 60 * 60 * 1e3;
94710
+ function sweepThreadEventLogsNow() {
94711
+ const retentionDays = threadEventLogRetentionDays(cfg);
94712
+ if (retentionDays === null) return;
94713
+ const candidates = store.bots.flatMap((bot) => (bot.tasks ?? []).map((task2) => ({
94714
+ threadId: task2.threadId,
94715
+ closedAt: task2.closedBy?.at ?? null,
94716
+ archivedAt: task2.archivedAt ?? null,
94717
+ unread: task2.unread === true,
94718
+ busy: threadBusy(bot.id, task2.threadId),
94719
+ openDirectHandoff: roomHandoffs.activeDirect(task2.threadId)
94720
+ })));
94721
+ const swept = sweepThreadEventLogs(candidates, retentionDays);
94722
+ if (swept > 0) console.log(`[retention] removed event logs for ${swept} idle thread(s) past ${retentionDays} day(s)`);
94723
+ }
94724
+ try {
94725
+ sweepThreadEventLogsNow();
94726
+ } catch (error51) {
94727
+ console.warn(`thread event log retention sweep failed: ${error51 instanceof Error ? error51.message : String(error51)}`);
94728
+ }
94729
+ setInterval(sweepThreadEventLogsNow, THREAD_LOG_RETENTION_SWEEP_MS).unref();
94160
94730
  for (const row of chatFollowups()) {
94161
94731
  if (row.status !== "dispatching" && row.status !== "interrupted") continue;
94162
94732
  const owned = row.kind === "bot" ? Boolean(store.taskByThread(row.ownerId, row.threadId)) : Boolean(store.groupByThread(row.threadId)?.id === row.ownerId);