openmausbot 0.1.81 → 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 (51) hide show
  1. package/dist/assets/{index-peLFiSH8.js → index-DSvqfBOf.js} +151 -149
  2. package/dist/assets/{index-oLMljotU.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 +1155 -511
  7. package/dist-server/mcp-gate.js +2 -1
  8. package/dist-server/openmausbot.js +210 -56
  9. package/dist-server/pair-cli.js +210 -56
  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/core.js +1 -1
  19. package/dist-server/server/drivers/acp/gemini.js +11 -1
  20. package/dist-server/server/drivers/acp/opencode-go.js +13 -0
  21. package/dist-server/server/drivers/acp/qwen.js +16 -1
  22. package/dist-server/server/drivers/agents-proxy.js +4 -4
  23. package/dist-server/server/drivers/claude.js +4 -3
  24. package/dist-server/server/drivers/codex.js +66 -1
  25. package/dist-server/server/drivers/pi.js +2 -1
  26. package/dist-server/server/env-path.js +6 -1
  27. package/dist-server/server/index.js +596 -97
  28. package/dist-server/server/local-routing.js +10 -4
  29. package/dist-server/server/notify.js +8 -3
  30. package/dist-server/server/peer-roster.js +37 -1
  31. package/dist-server/server/recent-work.js +4 -1
  32. package/dist-server/server/redact.js +5 -48
  33. package/dist-server/server/room-handoffs.js +79 -16
  34. package/dist-server/server/routines.js +19 -1
  35. package/dist-server/server/skill-fetch.js +1 -1
  36. package/dist-server/server/steer-queue.js +29 -0
  37. package/dist-server/server/store.js +26 -3
  38. package/dist-server/server/thread-retention.js +50 -0
  39. package/dist-server/server/turn-context.js +7 -0
  40. package/dist-server/server/turn-resources.js +13 -2
  41. package/dist-server/shared/approval-mode.js +8 -5
  42. package/dist-server/shared/inspector.js +1 -0
  43. package/dist-server/shared/json.js +1 -0
  44. package/dist-server/shared/notification.js +1 -0
  45. package/dist-server/shared/redact.js +61 -0
  46. package/dist-server/shared/routines.js +1 -0
  47. package/dist-server/shared/runtime-events.js +1 -0
  48. package/dist-server/shared/webhooks.js +1 -0
  49. package/dist-server/shared/wire.js +8 -0
  50. package/dist-server/vps-container-mcp.js +20 -4
  51. 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
@@ -40642,7 +40774,7 @@ function buildNotification(kind, bot, threadId, detail, extra) {
40642
40774
  if (bot.notifications === false) return null;
40643
40775
  const body = summarize(detail);
40644
40776
  const who = extra?.group ? `${bot.name} in ${extra.group.name}` : bot.name;
40645
- const title = kind === "approval" ? `${who} needs approval` : kind === "question" ? `${who} has a question` : kind === "takeover" ? `${who} needs your hands` : kind === "routine-failed" ? `${who}'s routine failed` : kind === "turn-failed" ? `${who} couldn't start` : `${who} finished`;
40777
+ const title = kind === "approval" ? `${who} needs approval` : kind === "question" ? `${who} has a question` : kind === "takeover" ? `${who} needs your hands` : kind === "routine-failed" ? `${who}'s routine failed` : kind === "routine-deferred" ? `${who}'s routine is waiting` : kind === "turn-failed" ? `${who} couldn't start` : `${who} finished`;
40646
40778
  if (kind === "done" && !body) return null;
40647
40779
  const notification = { kind, botId: bot.id, botName: bot.name, threadId, title, body };
40648
40780
  if (extra?.avatarUrl) notification.avatarUrl = extra.avatarUrl;
@@ -41468,7 +41600,7 @@ function createAcpDriver(support11) {
41468
41600
  return { turnId };
41469
41601
  }
41470
41602
  const resolvedModel = support11.resolveTurnModel?.(turn.model, env);
41471
- support11.applyTurnEnv?.(env, { model: resolvedModel, requestedModel: turn.model });
41603
+ support11.applyTurnEnv?.(env, { model: resolvedModel, requestedModel: turn.model, fullAuto: turnConfig.fullAuto === true });
41472
41604
  const cliTurn = resolvedModel !== void 0 && resolvedModel !== turn.model ? { ...turn, model: resolvedModel } : turn;
41473
41605
  const mcpServers = acpMcpServers(turn);
41474
41606
  let launch;
@@ -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) => {
@@ -47714,6 +47891,28 @@ var support7 = (loadCatalog2) => ({
47714
47891
  modelVariants: true,
47715
47892
  resolveTurnModel: (model2, env) => model2 ? ensureOpenCodeInjectModel(normalizeLegacyOpenCodeModel(model2, env), env) : model2,
47716
47893
  transformEnv: stripForeignProviderKeys,
47894
+ applyTurnEnv: (env, { fullAuto }) => {
47895
+ if (!fullAuto) return;
47896
+ env.OPENCODE_PERMISSION = JSON.stringify(Object.fromEntries([
47897
+ "*",
47898
+ "external_directory",
47899
+ "read",
47900
+ "edit",
47901
+ "bash",
47902
+ "glob",
47903
+ "grep",
47904
+ "list",
47905
+ "task",
47906
+ "lsp",
47907
+ "skill",
47908
+ "webfetch",
47909
+ "websearch",
47910
+ "codesearch",
47911
+ "todoread",
47912
+ "todowrite",
47913
+ "doom_loop"
47914
+ ].map((permission) => [permission, "allow"])));
47915
+ },
47717
47916
  pickAuthMethod: () => null,
47718
47917
  authFailure: "continue",
47719
47918
  isAuthenticated: async (env, config2) => Boolean(env.OPENCODE_API_KEY) || hasStoredOpenCodeAuth(env) || await canListOpenCodeModels(env, config2.cli),
@@ -47747,11 +47946,11 @@ var OpenCodeDriver = createOpenCodeDriver();
47747
47946
  import { chmodSync as chmodSync2, existsSync as existsSync15, mkdirSync as mkdirSync12, readFileSync as readFileSync22, writeFileSync as writeFileSync12 } from "node:fs";
47748
47947
  import { createHash as createHash13 } from "node:crypto";
47749
47948
  import { homedir as homedir19 } from "node:os";
47750
- import { join as join33 } from "node:path";
47949
+ import { join as join34 } from "node:path";
47751
47950
  var EMPTY = { default: "", options: [] };
47752
47951
  function qwenHome(env) {
47753
47952
  const home = process.platform === "win32" ? env.USERPROFILE || env.HOME || homedir19() : env.HOME || env.USERPROFILE || homedir19();
47754
- return join33(home, ".qwen");
47953
+ return join34(home, ".qwen");
47755
47954
  }
47756
47955
  function isRecord4(value) {
47757
47956
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
@@ -47764,7 +47963,7 @@ var PROTOCOLS = /* @__PURE__ */ new Set(["openai", "anthropic", "gemini", "verte
47764
47963
  function readQwenRoutes(env) {
47765
47964
  let settings;
47766
47965
  try {
47767
- settings = JSON.parse(readFileSync22(join33(qwenHome(env), "settings.json"), "utf8"));
47966
+ settings = JSON.parse(readFileSync22(join34(qwenHome(env), "settings.json"), "utf8"));
47768
47967
  } catch {
47769
47968
  return [];
47770
47969
  }
@@ -47848,7 +48047,7 @@ function ensureQwenInjectModel(modelId, env = process.env) {
47848
48047
  if (!host) return modelId;
47849
48048
  const dir = qwenHome(env);
47850
48049
  mkdirSync12(dir, { recursive: true });
47851
- const path = join33(dir, "settings.json");
48050
+ const path = join34(dir, "settings.json");
47852
48051
  let settings = {};
47853
48052
  if (existsSync15(path)) {
47854
48053
  try {
@@ -47918,6 +48117,12 @@ function resolveQwenTurnModel(model2, env) {
47918
48117
  if (!matches.length) throw new Error("This Qwen model is no longer configured. Refresh models and select it again.");
47919
48118
  return matches[0].id;
47920
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
+ }
47921
48126
  var support8 = {
47922
48127
  driverKind: "qwenAgent",
47923
48128
  displayName: "Qwen",
@@ -47938,7 +48143,7 @@ var support8 = {
47938
48143
  },
47939
48144
  // A raw -m only changes the model within the saved provider. ACP switches
47940
48145
  // the complete route and confirms it before any prompt leaves OMB.
47941
- spawnArgs: () => ["--acp"],
48146
+ spawnArgs: (config2, turn) => ["--acp", ...qwenApprovalArgs(config2.fullAuto, turn.approvalMode)],
47942
48147
  selectModel: { configId: "model" },
47943
48148
  pickAuthMethod: () => null,
47944
48149
  authFailure: "continue",
@@ -47989,7 +48194,7 @@ var CustomAcpDriver = {
47989
48194
  import { spawn as spawn2 } from "node:child_process";
47990
48195
  import { mkdirSync as mkdirSync13, readFileSync as readFileSync23, writeFileSync as writeFileSync13 } from "node:fs";
47991
48196
  import { homedir as homedir20 } from "node:os";
47992
- import { join as join34 } from "node:path";
48197
+ import { join as join35 } from "node:path";
47993
48198
 
47994
48199
  // node_modules/.pnpm/yaml@2.9.0/node_modules/yaml/browser/dist/nodes/identity.js
47995
48200
  var ALIAS = /* @__PURE__ */ Symbol.for("yaml.alias");
@@ -54293,7 +54498,7 @@ function bindHermesScreenshotCompat(env, modelId) {
54293
54498
  env[HERMES_OPENMAUS_SCREENSHOT_COMPAT_MODEL] = inject.model;
54294
54499
  }
54295
54500
  function hermesHome(env) {
54296
- return env.HERMES_HOME || join34(env.HOME || env.USERPROFILE || homedir20(), ".hermes");
54501
+ return env.HERMES_HOME || join35(env.HOME || env.USERPROFILE || homedir20(), ".hermes");
54297
54502
  }
54298
54503
  function quoteYaml(value) {
54299
54504
  if (/^[\w./:+-]+$/.test(value)) return value;
@@ -54335,7 +54540,7 @@ function ensureHermesInjectProvider(modelId, env = process.env) {
54335
54540
  if (!host) return modelId;
54336
54541
  const dir = hermesHome(env);
54337
54542
  mkdirSync13(dir, { recursive: true });
54338
- const path = join34(dir, "config.yaml");
54543
+ const path = join35(dir, "config.yaml");
54339
54544
  let text8 = "";
54340
54545
  try {
54341
54546
  text8 = readFileSync23(path, "utf8");
@@ -54414,13 +54619,13 @@ function hermesConfiguredModel(env = process.env) {
54414
54619
  const dir = hermesHome(env);
54415
54620
  let secrets = "";
54416
54621
  try {
54417
- secrets = readFileSync23(join34(dir, ".env"), "utf8");
54622
+ secrets = readFileSync23(join35(dir, ".env"), "utf8");
54418
54623
  } catch {
54419
54624
  }
54420
54625
  const hasHostedProviderKey = HERMES_HOSTED_PROVIDER_KEYS.some((name2) => nonEmptyDotenvValue(secrets, name2));
54421
54626
  let configuredDefault = null;
54422
54627
  try {
54423
- configuredDefault = hermesConfigDefault(readFileSync23(join34(dir, "config.yaml"), "utf8"));
54628
+ configuredDefault = hermesConfigDefault(readFileSync23(join35(dir, "config.yaml"), "utf8"));
54424
54629
  } catch {
54425
54630
  }
54426
54631
  const configuredProvider = configuredDefault?.provider.toLowerCase() ?? "";
@@ -54734,7 +54939,7 @@ var OpenAICompatDriver = {
54734
54939
  // server/drivers/pi.ts
54735
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";
54736
54941
  import { homedir as homedir21, tmpdir as tmpdir4 } from "node:os";
54737
- import { join as join35 } from "node:path";
54942
+ import { join as join36 } from "node:path";
54738
54943
  var DRIVER_KIND6 = "piAgent";
54739
54944
  var PI_ARGS = ["--mode", "rpc", "--no-session"];
54740
54945
  var PI_MODEL_UPDATE_ARGS = ["update", "--models", "--no-approve"];
@@ -54839,7 +55044,7 @@ async function applyPiLocalCatalog(catalog2, env = process.env, fetchImpl = fetc
54839
55044
  return preferPiInjectRows(await mergeLocalInject(catalog2, env, fetchImpl));
54840
55045
  }
54841
55046
  function piAgentDir(env) {
54842
- return join35(env.HOME || env.USERPROFILE || homedir21(), ".pi", "agent");
55047
+ return join36(env.HOME || env.USERPROFILE || homedir21(), ".pi", "agent");
54843
55048
  }
54844
55049
  function ensurePiInjectModel(modelId, env = process.env) {
54845
55050
  const split2 = splitPiModel(modelId);
@@ -54850,7 +55055,7 @@ function ensurePiInjectModel(modelId, env = process.env) {
54850
55055
  if (!host) return split2;
54851
55056
  const dir = piAgentDir(env);
54852
55057
  mkdirSync14(dir, { recursive: true });
54853
- const path = join35(dir, "models.json");
55058
+ const path = join36(dir, "models.json");
54854
55059
  let root = { providers: {} };
54855
55060
  if (existsSync16(path)) {
54856
55061
  try {
@@ -55051,9 +55256,9 @@ var PiDriver = {
55051
55256
  const mcpServers = buildMcpServers(turn);
55052
55257
  let mcpTempDir = null;
55053
55258
  if (mcpServers) {
55054
- mcpTempDir = mkdtempSync2(join35(tmpdir4(), "omb-pi-mcp-"));
55259
+ mcpTempDir = mkdtempSync2(join36(tmpdir4(), "omb-pi-mcp-"));
55055
55260
  try {
55056
- writeFileSync14(join35(mcpTempDir, "mcp.json"), JSON.stringify({ mcpServers }), { mode: 384 });
55261
+ writeFileSync14(join36(mcpTempDir, "mcp.json"), JSON.stringify({ mcpServers }), { mode: 384 });
55057
55262
  } catch (err) {
55058
55263
  try {
55059
55264
  rmSync3(mcpTempDir, { recursive: true, force: true });
@@ -55071,7 +55276,7 @@ var PiDriver = {
55071
55276
  env: piEnvironment({
55072
55277
  ...process.env,
55073
55278
  ...input.environment,
55074
- ...mcpServers && mcpTempDir ? { OMB_MCP_CONFIG: join35(mcpTempDir, "mcp.json") } : {}
55279
+ ...mcpServers && mcpTempDir ? { OMB_MCP_CONFIG: join36(mcpTempDir, "mcp.json") } : {}
55075
55280
  })
55076
55281
  });
55077
55282
  } catch (err) {
@@ -55411,7 +55616,7 @@ ${turn.text}` : turn.text;
55411
55616
  // server/drivers/minimax.ts
55412
55617
  import { readFileSync as readFileSync25 } from "node:fs";
55413
55618
  import { homedir as homedir22 } from "node:os";
55414
- import { join as join36 } from "node:path";
55619
+ import { join as join37 } from "node:path";
55415
55620
  var DRIVER_KIND7 = "minimax";
55416
55621
  var API_KEY_ENV = "MINIMAX_API_KEY";
55417
55622
  var DEFAULT_URL2 = "https://api.minimax.io/v1";
@@ -55439,7 +55644,7 @@ function normalizedApiUrl(value) {
55439
55644
  }
55440
55645
  function loadLocalMiniMaxConfig(home = homedir22()) {
55441
55646
  try {
55442
- 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")));
55443
55648
  const configuredUrl = raw.base_url?.trim() ? raw.base_url : raw.region === "cn" ? CN_URL : DEFAULT_URL2;
55444
55649
  return {
55445
55650
  apiKey: raw.api_key?.trim() ?? "",
@@ -55535,14 +55740,14 @@ var BUILT_IN_DRIVERS = [
55535
55740
 
55536
55741
  // server/store.ts
55537
55742
  import { createHash as createHash15 } from "node:crypto";
55538
- import { existsSync as existsSync20, readFileSync as readFileSync29, mkdirSync as mkdirSync19, rmSync as rmSync5, unlinkSync as unlinkSync7 } from "node:fs";
55539
- 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";
55540
55745
 
55541
55746
  // server/section-context.ts
55542
55747
  import { existsSync as existsSync17, mkdirSync as mkdirSync15, readFileSync as readFileSync26 } from "node:fs";
55543
- import { join as join37 } from "node:path";
55748
+ import { join as join38 } from "node:path";
55544
55749
  var SECTION_CONTEXT_MAX_BYTES = 24e3;
55545
- var SECTION_CONTEXTS_FILE = join37(DATA_DIR, "section-contexts.json");
55750
+ var SECTION_CONTEXTS_FILE = join38(DATA_DIR, "section-contexts.json");
55546
55751
  var sectionContextRecordSchema = external_exports.object({ text: external_exports.string(), updatedAt: external_exports.number().finite() });
55547
55752
  var sectionContextFileSchema = external_exports.object({
55548
55753
  version: external_exports.literal(1),
@@ -55646,14 +55851,14 @@ Shared context for the ${JSON.stringify(label)} section follows. The user manage
55646
55851
  // server/bot-folder.ts
55647
55852
  import { createHash as createHash14 } from "node:crypto";
55648
55853
  import { closeSync as closeSync6, mkdirSync as mkdirSync16, openSync as openSync6, readSync as readSync3, rmSync as rmSync4 } from "node:fs";
55649
- import { join as join38 } from "node:path";
55650
- var BOTS_DIR = join38(DATA_DIR, "bots");
55854
+ import { join as join39 } from "node:path";
55855
+ var BOTS_DIR = join39(DATA_DIR, "bots");
55651
55856
  var SOUL_FILE = "SOUL.md";
55652
55857
  function botFolder(botId) {
55653
- return join38(BOTS_DIR, botId);
55858
+ return join39(BOTS_DIR, botId);
55654
55859
  }
55655
55860
  function soulFile(botId) {
55656
- return join38(botFolder(botId), SOUL_FILE);
55861
+ return join39(botFolder(botId), SOUL_FILE);
55657
55862
  }
55658
55863
  function soulHash(soul) {
55659
55864
  return createHash14("sha256").update(soul, "utf8").digest("hex");
@@ -55721,7 +55926,7 @@ function peerAllowKey(action, targetId) {
55721
55926
 
55722
55927
  // server/message-db.ts
55723
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";
55724
- import { join as join39 } from "node:path";
55929
+ import { join as join40 } from "node:path";
55725
55930
  import { DatabaseSync } from "node:sqlite";
55726
55931
 
55727
55932
  // server/peer-provenance.ts
@@ -55744,7 +55949,7 @@ function peerProvenanceAuthor(text8) {
55744
55949
  }
55745
55950
 
55746
55951
  // server/message-db.ts
55747
- var DB_FILE = () => join39(DATA_DIR, "messages.db");
55952
+ var DB_FILE = () => join40(DATA_DIR, "messages.db");
55748
55953
  var handle = null;
55749
55954
  var handlePath = null;
55750
55955
  function open4() {
@@ -56174,9 +56379,9 @@ function closeMessageDb() {
56174
56379
 
56175
56380
  // server/workspace.ts
56176
56381
  import { existsSync as existsSync19, mkdirSync as mkdirSync18, readdirSync as readdirSync4, readFileSync as readFileSync28, statSync as statSync6 } from "node:fs";
56177
- import { join as join40 } from "node:path";
56178
- var WORKSPACES_DIR = join40(DATA_DIR, "workspaces");
56179
- 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");
56180
56385
  function supportsWorkspaceFiles(driverKind) {
56181
56386
  return !["grok", "openai-compat", "minimax", "boxAgent"].includes(driverKind);
56182
56387
  }
@@ -56184,7 +56389,7 @@ function ensureTaskWorkspace(botId, threadId) {
56184
56389
  if (![botId, threadId].every((id) => /^[A-Za-z0-9_-]{1,128}$/.test(id))) {
56185
56390
  throw new Error("Invalid bot or thread id for a task workspace.");
56186
56391
  }
56187
- const dir = join40(TASK_WORKSPACES_DIR, botId, threadId);
56392
+ const dir = join41(TASK_WORKSPACES_DIR, botId, threadId);
56188
56393
  mkdirSync18(dir, { recursive: true, mode: 448 });
56189
56394
  return dir;
56190
56395
  }
@@ -56197,20 +56402,20 @@ load at the start of every session \u2014 keep this file short and curated.
56197
56402
  Longer notes belong in memory/<topic>.md files, read on demand.
56198
56403
  `;
56199
56404
  function ensureWorkspace(botId) {
56200
- const dir = join40(WORKSPACES_DIR, botId);
56201
- mkdirSync18(join40(dir, "memory"), { recursive: true, mode: 448 });
56202
- 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");
56203
56408
  if (!existsSync19(memoryFile)) writeFileAtomic(memoryFile, MEMORY_SEED, { mode: 384 });
56204
56409
  return dir;
56205
56410
  }
56206
56411
  function workspaceDir(botId) {
56207
- return join40(WORKSPACES_DIR, botId);
56412
+ return join41(WORKSPACES_DIR, botId);
56208
56413
  }
56209
56414
  function workspaceLocationsPrompt(botId, cwd, botCwd) {
56210
56415
  return "\n\nFile locations for this bot (absolute paths): " + JSON.stringify({
56211
56416
  currentWorkingFolder: cwd ?? "Provider default; inspect the working directory before using relative paths",
56212
56417
  sharedBotFolder: workspaceDir(botId),
56213
- otherThreadFiles: join40(TASK_WORKSPACES_DIR, botId),
56418
+ otherThreadFiles: join41(TASK_WORKSPACES_DIR, botId),
56214
56419
  ...botCwd ? { configuredProjectFolder: botCwd } : {}
56215
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.";
56216
56421
  }
@@ -56223,7 +56428,7 @@ function memoryOverBudget(text8) {
56223
56428
  function loadMemory(botId) {
56224
56429
  let raw;
56225
56430
  try {
56226
- raw = readFileSync28(join40(workspaceDir(botId), "MEMORY.md"), "utf8");
56431
+ raw = readFileSync28(join41(workspaceDir(botId), "MEMORY.md"), "utf8");
56227
56432
  } catch {
56228
56433
  return null;
56229
56434
  }
@@ -56246,7 +56451,7 @@ var MEMORY_FILE_MAX_BYTES = 256 * 1024;
56246
56451
  function readMemoryFile(botId) {
56247
56452
  let raw;
56248
56453
  try {
56249
- raw = readFileSync28(join40(workspaceDir(botId), "MEMORY.md"), "utf8");
56454
+ raw = readFileSync28(join41(workspaceDir(botId), "MEMORY.md"), "utf8");
56250
56455
  } catch {
56251
56456
  return { text: "", truncated: false };
56252
56457
  }
@@ -56255,12 +56460,12 @@ function readMemoryFile(botId) {
56255
56460
  }
56256
56461
  function writeMemoryFile(botId, text8) {
56257
56462
  ensureWorkspace(botId);
56258
- writeFileAtomic(join40(workspaceDir(botId), "MEMORY.md"), redactSecretsInText(text8), { mode: 384 });
56463
+ writeFileAtomic(join41(workspaceDir(botId), "MEMORY.md"), redactSecretsInText(text8), { mode: 384 });
56259
56464
  indexWrittenMemoryFile(botId, "MEMORY.md");
56260
56465
  }
56261
56466
  function indexWrittenMemoryFile(botId, relativePath) {
56262
56467
  try {
56263
- const path = join40(workspaceDir(botId), relativePath);
56468
+ const path = join41(workspaceDir(botId), relativePath);
56264
56469
  const stat6 = statSync6(path);
56265
56470
  indexMemoryFile(botId, relativePath, readFileSync28(path, "utf8"), { mtimeMs: stat6.mtimeMs, bytes: stat6.size });
56266
56471
  } catch {
@@ -56275,7 +56480,7 @@ function memoryFilesOnDisk(botId) {
56275
56480
  ];
56276
56481
  return names.flatMap((relativePath) => {
56277
56482
  try {
56278
- const stat6 = statSync6(join40(dir, relativePath));
56483
+ const stat6 = statSync6(join41(dir, relativePath));
56279
56484
  return stat6.isFile() ? [{ path: relativePath, mtimeMs: stat6.mtimeMs, bytes: stat6.size }] : [];
56280
56485
  } catch {
56281
56486
  return [];
@@ -56290,7 +56495,7 @@ function syncMemoryIndex(botId) {
56290
56495
  indexed.delete(file2.path);
56291
56496
  if (known && known.bytes === file2.bytes && known.mtimeMs === Math.trunc(file2.mtimeMs)) continue;
56292
56497
  try {
56293
- const text8 = readFileSync28(join40(workspaceDir(botId), file2.path), "utf8");
56498
+ const text8 = readFileSync28(join41(workspaceDir(botId), file2.path), "utf8");
56294
56499
  indexMemoryFile(botId, file2.path, file2.path === "MEMORY.md" && text8 === MEMORY_SEED ? "" : text8, file2);
56295
56500
  } catch {
56296
56501
  }
@@ -56351,7 +56556,7 @@ function updateMemory(botId, update, opts = {}) {
56351
56556
  }
56352
56557
  const text8 = update.text === void 0 ? void 0 : redactSecretsInText(update.text);
56353
56558
  const dir = ensureWorkspace(botId);
56354
- const raw = readFileSync28(join40(dir, "MEMORY.md"), "utf8");
56559
+ const raw = readFileSync28(join41(dir, "MEMORY.md"), "utf8");
56355
56560
  const current2 = raw === MEMORY_SEED ? "" : raw;
56356
56561
  const today = memoryDate(opts.now);
56357
56562
  const appendEntry = (base2, entry2) => `${base2}${base2 && !base2.endsWith("\n") ? "\n" : ""}${entry2}
@@ -56423,10 +56628,10 @@ function appendMemoryLog(botId, text8, opts = {}) {
56423
56628
  const pad2 = (n) => String(n).padStart(2, "0");
56424
56629
  const source = cleanSource(opts.source);
56425
56630
  const line = `- ${pad2(now.getHours())}:${pad2(now.getMinutes())}${SEP2}${source ? `from ${source}${SEP2}` : ""}${normaliseEntryText(redactSecretsInText(text8))}`;
56426
- const dir = join40(ensureWorkspace(botId), "memory", MEMORY_LOG_DIR);
56631
+ const dir = join41(ensureWorkspace(botId), "memory", MEMORY_LOG_DIR);
56427
56632
  mkdirSync18(dir, { recursive: true, mode: 448 });
56428
56633
  const file2 = `${memoryDate(now)}.md`;
56429
- const path = join40(dir, file2);
56634
+ const path = join41(dir, file2);
56430
56635
  let current2 = "";
56431
56636
  try {
56432
56637
  current2 = readFileSync28(path, "utf8");
@@ -56440,14 +56645,14 @@ function appendMemoryLog(botId, text8, opts = {}) {
56440
56645
  }
56441
56646
  function writeMemoryLog(botId, name2, text8) {
56442
56647
  if (!LOG_FILE_NAME.test(name2)) throw new Error("invalid log name");
56443
- const dir = join40(ensureWorkspace(botId), "memory", MEMORY_LOG_DIR);
56648
+ const dir = join41(ensureWorkspace(botId), "memory", MEMORY_LOG_DIR);
56444
56649
  mkdirSync18(dir, { recursive: true, mode: 448 });
56445
- writeFileAtomic(join40(dir, name2), redactSecretsInText(text8), { mode: 384 });
56650
+ writeFileAtomic(join41(dir, name2), redactSecretsInText(text8), { mode: 384 });
56446
56651
  indexWrittenMemoryFile(botId, `memory/${MEMORY_LOG_DIR}/${name2}`);
56447
56652
  }
56448
56653
  function listMemoryLogs(botId) {
56449
56654
  try {
56450
- 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();
56451
56656
  } catch {
56452
56657
  return [];
56453
56658
  }
@@ -56455,7 +56660,7 @@ function listMemoryLogs(botId) {
56455
56660
  function readMemoryLog(botId, name2) {
56456
56661
  if (!LOG_FILE_NAME.test(name2)) return null;
56457
56662
  try {
56458
- return readFileSync28(join40(workspaceDir(botId), "memory", MEMORY_LOG_DIR, name2), "utf8");
56663
+ return readFileSync28(join41(workspaceDir(botId), "memory", MEMORY_LOG_DIR, name2), "utf8");
56459
56664
  } catch {
56460
56665
  return null;
56461
56666
  }
@@ -56467,13 +56672,13 @@ function isMemoryTopicName(name2) {
56467
56672
  function listMemoryTopics(botId) {
56468
56673
  let entries;
56469
56674
  try {
56470
- entries = readdirSync4(join40(workspaceDir(botId), "memory"));
56675
+ entries = readdirSync4(join41(workspaceDir(botId), "memory"));
56471
56676
  } catch {
56472
56677
  return [];
56473
56678
  }
56474
56679
  return entries.filter(isMemoryTopicName).flatMap((name2) => {
56475
56680
  try {
56476
- const stat6 = statSync6(join40(workspaceDir(botId), "memory", name2));
56681
+ const stat6 = statSync6(join41(workspaceDir(botId), "memory", name2));
56477
56682
  return stat6.isFile() ? [{ name: name2, bytes: stat6.size }] : [];
56478
56683
  } catch {
56479
56684
  return [];
@@ -56483,13 +56688,13 @@ function listMemoryTopics(botId) {
56483
56688
  function writeMemoryTopic(botId, name2, text8) {
56484
56689
  if (!isMemoryTopicName(name2)) throw new Error("invalid topic name");
56485
56690
  ensureWorkspace(botId);
56486
- writeFileAtomic(join40(workspaceDir(botId), "memory", name2), redactSecretsInText(text8), { mode: 384 });
56691
+ writeFileAtomic(join41(workspaceDir(botId), "memory", name2), redactSecretsInText(text8), { mode: 384 });
56487
56692
  indexWrittenMemoryFile(botId, `memory/${name2}`);
56488
56693
  }
56489
56694
  function readMemoryTopic(botId, name2) {
56490
56695
  if (!isMemoryTopicName(name2)) return null;
56491
56696
  try {
56492
- return readFileSync28(join40(workspaceDir(botId), "memory", name2), "utf8");
56697
+ return readFileSync28(join41(workspaceDir(botId), "memory", name2), "utf8");
56493
56698
  } catch {
56494
56699
  return null;
56495
56700
  }
@@ -56498,8 +56703,8 @@ var SESSION_SEARCH_SYSTEM_PROMPT = ' Your own earlier conversations with this us
56498
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.";
56499
56704
  function memorySystemPrompt(botId, opts = {}) {
56500
56705
  const memory2 = loadMemory(botId);
56501
- const memoryFile = join40(workspaceDir(botId), "MEMORY.md");
56502
- const topicDir = join40(workspaceDir(botId), "memory");
56706
+ const memoryFile = join41(workspaceDir(botId), "MEMORY.md");
56707
+ const topicDir = join41(workspaceDir(botId), "memory");
56503
56708
  if (opts.fileTools === false && !opts.managedWrites) {
56504
56709
  if (!memory2) return "";
56505
56710
  return ` Your saved memory is supplied as context; this turn has no memory editing tools.
@@ -56599,6 +56804,10 @@ var projectEmojiPattern = new RegExp("^(?!\\p{Emoji_Component}$)(?:\\p{RGI_Emoji
56599
56804
  function isProjectEmoji(value) {
56600
56805
  return typeof value === "string" && value.length <= 64 && projectEmojiPattern.exec(value)?.[0] === value;
56601
56806
  }
56807
+ function toWireTask(task2) {
56808
+ const { resumeCursors: _resumeCursors, lastInstanceId: _lastInstanceId, ...wire } = task2;
56809
+ return wire;
56810
+ }
56602
56811
  var TASK_PATCH_FIELDS = [
56603
56812
  "title",
56604
56813
  "projectId",
@@ -56743,9 +56952,9 @@ function titleFromMessage(text8) {
56743
56952
  const line = text8.trim().split("\n")[0].trim();
56744
56953
  return line.length > 48 ? `${line.slice(0, 47)}\u2026` : line || UNTITLED_TASK;
56745
56954
  }
56746
- var BOTS_FILE = join41(DATA_DIR, "bots.json");
56747
- var GROUPS_FILE = join41(DATA_DIR, "groups.json");
56748
- 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`);
56749
56958
  var COLORS = [
56750
56959
  "green",
56751
56960
  "blue",
@@ -57159,13 +57368,20 @@ var Store = class {
57159
57368
  this.emit({ type: "group", groupId: group.id });
57160
57369
  return group;
57161
57370
  }
57162
- /** 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. */
57163
57374
  deleteThreadRecord(threadId) {
57164
57375
  this.threads.delete(threadId);
57165
57376
  deleteThread(threadId);
57166
- 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
+ ]) {
57167
57383
  try {
57168
- unlinkSync7(file2);
57384
+ unlinkSync8(file2);
57169
57385
  } catch {
57170
57386
  }
57171
57387
  }
@@ -57677,7 +57893,7 @@ var Store = class {
57677
57893
  } catch {
57678
57894
  }
57679
57895
  try {
57680
- rmSync5(join41(DATA_DIR, "skill-state", id), { recursive: true, force: true });
57896
+ rmSync5(join42(DATA_DIR, "skill-state", id), { recursive: true, force: true });
57681
57897
  } catch {
57682
57898
  }
57683
57899
  removeBotFolder(id);
@@ -58443,7 +58659,7 @@ function parseSince(value, now = Date.now()) {
58443
58659
  return day.getTime();
58444
58660
  }
58445
58661
  if (/^\d{12,}$/.test(text8)) return Number(text8);
58446
- const parsed = Date.parse(value.trim());
58662
+ const parsed = Date.parse(/^\d{4}-\d{2}-\d{2}$/.test(text8) ? `${text8}T00:00` : value.trim());
58447
58663
  return Number.isFinite(parsed) ? parsed : null;
58448
58664
  }
58449
58665
 
@@ -58482,7 +58698,7 @@ ${message4.text}`;
58482
58698
 
58483
58699
  // server/delegations.ts
58484
58700
  import { readFileSync as readFileSync30 } from "node:fs";
58485
- import { join as join42 } from "node:path";
58701
+ import { join as join43 } from "node:path";
58486
58702
 
58487
58703
  // server/peer-approval.ts
58488
58704
  function settleCard(pending, behavior, source) {
@@ -58629,8 +58845,8 @@ function dismissStalePeerCards(bus2) {
58629
58845
  var pendingDelegations = /* @__PURE__ */ new Map();
58630
58846
  var drainingThreads = /* @__PURE__ */ new Set();
58631
58847
  var queuedRedrains = /* @__PURE__ */ new Set();
58632
- var DELEGATIONS_FILE = join42(DATA_DIR, "delegations.json");
58633
- 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");
58634
58850
  var MAX_RECEIPTS = 100;
58635
58851
  var RECEIPT_MAX_AGE_MS = 48 * 60 * 60 * 1e3;
58636
58852
  var RESULT_MAX_CHARS = 4e3;
@@ -59320,6 +59536,25 @@ function cancelSteeredMessage(botId, messageId, expectedThreadId) {
59320
59536
  }
59321
59537
  return false;
59322
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
+ }
59323
59558
 
59324
59559
  // server/channel-queue.ts
59325
59560
  var queues2 = /* @__PURE__ */ new Map();
@@ -59366,6 +59601,41 @@ function cancelChannelMessage(groupId, queueId) {
59366
59601
  }
59367
59602
  return false;
59368
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
+ }
59369
59639
  function drainChannelMessages(isWorking, run3) {
59370
59640
  for (const [threadId, entry] of queues2) {
59371
59641
  if (isWorking(entry.groupId)) continue;
@@ -59429,7 +59699,7 @@ function sendFingerprint(text8, replyToId, channelMode) {
59429
59699
 
59430
59700
  // server/harness/bus.ts
59431
59701
  import { appendFileSync as appendFileSync2 } from "node:fs";
59432
- import { join as join43 } from "node:path";
59702
+ import { join as join44 } from "node:path";
59433
59703
  var INCOMPLETE_LOG_MESSAGE = "Canonical event history is incomplete: OpenMausBot could not write one or more events to disk. Live updates will continue.";
59434
59704
  var EventBus = class {
59435
59705
  listeners = /* @__PURE__ */ new Set();
@@ -59457,12 +59727,12 @@ var EventBus = class {
59457
59727
  const persistedEvents = pendingWarning ? [pendingWarning, redactSecrets(event)] : [redactSecrets(event)];
59458
59728
  try {
59459
59729
  this.appendLog(
59460
- join43(EVENTS_DIR, `${event.threadId}.ndjson`),
59730
+ join44(EVENTS_DIR, `${event.threadId}.ndjson`),
59461
59731
  persistedEvents.map((entry) => JSON.stringify(entry)).join("\n") + "\n",
59462
59732
  { mode: 384 }
59463
59733
  );
59464
59734
  if (pendingWarning) this.pendingLogWarnings.delete(event.threadId);
59465
- capThreadLog(join43(EVENTS_DIR, `${event.threadId}.ndjson`), currentThreadLogCap());
59735
+ capThreadLog(join44(EVENTS_DIR, `${event.threadId}.ndjson`), currentThreadLogCap());
59466
59736
  } catch (error51) {
59467
59737
  if (!pendingWarning) {
59468
59738
  const warning = {
@@ -59740,7 +60010,7 @@ var ProviderRegistry = class {
59740
60010
  // server/managed-desktop.ts
59741
60011
  import { createHash as createHash16 } from "node:crypto";
59742
60012
  import { lstatSync as lstatSync3, mkdirSync as mkdirSync20 } from "node:fs";
59743
- import { join as join44 } from "node:path";
60013
+ import { join as join45 } from "node:path";
59744
60014
  var uuid3 = external_exports.string().uuid();
59745
60015
  var model = external_exports.string().regex(/^[A-Za-z0-9][A-Za-z0-9._/:+-]{0,199}$/).refine((value) => !value.includes("::"));
59746
60016
  var connectionSchema = external_exports.object({
@@ -59779,14 +60049,14 @@ function companyInstanceConfigs(connection, runtimeDirectory) {
59779
60049
  if (provider.id === "anthropic") entries[id] = {
59780
60050
  driver: "claudeAgent",
59781
60051
  displayName: displayName2,
59782
- config: { configDir: join44(runtimeDirectory, id, "claude"), managed: true },
60052
+ config: { configDir: join45(runtimeDirectory, id, "claude"), managed: true },
59783
60053
  environment: { ANTHROPIC_API_KEY: connection.token, ANTHROPIC_AUTH_TOKEN: connection.token, ANTHROPIC_BASE_URL: base2, ANTHROPIC_MODEL: provider.models[0] }
59784
60054
  };
59785
60055
  if (provider.id === "openai") entries[id] = {
59786
60056
  driver: "codex",
59787
60057
  displayName: displayName2,
59788
60058
  config: { managed: { url: `${base2}/v1`, models: provider.models } },
59789
- 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") }
59790
60060
  };
59791
60061
  if (provider.id === "openrouter") entries[id] = {
59792
60062
  driver: "openai-compat",
@@ -59904,7 +60174,7 @@ var ManagedDesktopProviders = class {
59904
60174
  mkdirSync20(this.options.dataDirectory, { recursive: true, mode: 448 });
59905
60175
  let directory = this.options.dataDirectory;
59906
60176
  for (const part of parts) {
59907
- directory = join44(directory, part);
60177
+ directory = join45(directory, part);
59908
60178
  try {
59909
60179
  mkdirSync20(directory, { mode: 448 });
59910
60180
  } catch (error51) {
@@ -60266,7 +60536,7 @@ async function synthesize2(text8, voiceId, key3) {
60266
60536
  import { execFile as execFile5 } from "node:child_process";
60267
60537
  import { mkdtemp as mkdtemp4, readFile as readFile4, rm as rm4 } from "node:fs/promises";
60268
60538
  import { tmpdir as tmpdir5 } from "node:os";
60269
- import { join as join45 } from "node:path";
60539
+ import { join as join46 } from "node:path";
60270
60540
  import { promisify as promisify2 } from "node:util";
60271
60541
  var SAY = "/usr/bin/say";
60272
60542
  var execFileAsync = promisify2(execFile5);
@@ -60297,8 +60567,8 @@ async function listSystemVoices(run3 = defaultRun) {
60297
60567
  async function synthesizeSystem(text8, voiceId, run3 = defaultRun) {
60298
60568
  const trimmed = text8.trim();
60299
60569
  if (!trimmed) return { bytes: new Uint8Array(), mime: "audio/wav" };
60300
- const dir = await mkdtemp4(join45(tmpdir5(), "openmausbot-say-"));
60301
- const out = join45(dir, "utterance.wav");
60570
+ const dir = await mkdtemp4(join46(tmpdir5(), "openmausbot-say-"));
60571
+ const out = join46(dir, "utterance.wav");
60302
60572
  try {
60303
60573
  const args = ["-o", out, "--data-format=LEI16@22050"];
60304
60574
  if (voiceId?.trim()) args.push("-v", voiceId.trim());
@@ -60561,6 +60831,10 @@ function engineIsFresh(input) {
60561
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:]";
60562
60832
  var FRESH_PREAMBLE = "[You are joining this conversation mid-thread (the user switched this bot over to you). The conversation so far:]";
60563
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
+ }
60564
60838
  var RECOVERED_PREAMBLE = "[Your previous session for this conversation could not be resumed, so this is a new session. The conversation so far:]";
60565
60839
  function buildRecoveryText(input) {
60566
60840
  if (input.transcript.length === 0) return void 0;
@@ -60755,10 +61029,14 @@ import { realpathSync as realpathSync6 } from "node:fs";
60755
61029
  import { relative as relative3, resolve as resolve12, sep as sep2 } from "node:path";
60756
61030
  var TurnResources = class {
60757
61031
  owners = /* @__PURE__ */ new Map();
60758
- claim(resource, owner2) {
61032
+ blocker(resource, owner2) {
60759
61033
  for (const [key3, current2] of this.owners) {
60760
- if (overlaps(key3, resource) && !sameOwner(current2, owner2)) return false;
61034
+ if (overlaps(key3, resource) && !sameOwner(current2, owner2)) return current2;
60761
61035
  }
61036
+ return void 0;
61037
+ }
61038
+ claim(resource, owner2) {
61039
+ if (this.blocker(resource, owner2)) return false;
60762
61040
  this.owners.set(resource, owner2);
60763
61041
  return true;
60764
61042
  }
@@ -60771,6 +61049,11 @@ var TurnResources = class {
60771
61049
  if (sameOwner(current2, owner2)) this.owners.delete(key3);
60772
61050
  }
60773
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
+ }
60774
61057
  };
60775
61058
  function sameOwner(a, b3) {
60776
61059
  return a.threadId === b3.threadId && a.generation === b3.generation;
@@ -60794,8 +61077,8 @@ function overlaps(a, b3) {
60794
61077
  // server/memory-store.ts
60795
61078
  import { spawn as spawn3 } from "node:child_process";
60796
61079
  import { createHash as createHash17 } from "node:crypto";
60797
- 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";
60798
- 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";
60799
61082
  var MEMORY_INDEX = "MEMORY.md";
60800
61083
  var MemoryStoreError = class extends Error {
60801
61084
  status;
@@ -60841,7 +61124,7 @@ function resolveMemoryPath(botId, path) {
60841
61124
  assertBotId(botId);
60842
61125
  const ref = parseMemoryPath(path);
60843
61126
  const root = workspaceDir(botId);
60844
- const absolute = join46(root, ...relativeSegments(ref));
61127
+ const absolute = join47(root, ...relativeSegments(ref));
60845
61128
  let realRoot;
60846
61129
  try {
60847
61130
  realRoot = realpathSync7(root);
@@ -60900,7 +61183,7 @@ function listMarkdown(dir, prefix) {
60900
61183
  for (const name2 of names) {
60901
61184
  if (!isMemoryTopicName(name2)) continue;
60902
61185
  try {
60903
- const info = lstatSync5(join46(dir, name2));
61186
+ const info = lstatSync5(join47(dir, name2));
60904
61187
  if (!info.isFile()) continue;
60905
61188
  files.push({ path: `${prefix}/${name2}`, name: name2, bytes: info.size, modifiedAt: Math.round(info.mtimeMs) });
60906
61189
  } catch {
@@ -60922,8 +61205,8 @@ function memoryOverview(botId) {
60922
61205
  botId,
60923
61206
  workspacePath: root,
60924
61207
  index: memoryCapacity(raw),
60925
- topics: listMarkdown(join46(root, "memory"), "memory"),
60926
- logs: listMarkdown(join46(root, "memory", "log"), "memory/log")
61208
+ topics: listMarkdown(join47(root, "memory"), "memory"),
61209
+ logs: listMarkdown(join47(root, "memory", "log"), "memory/log")
60927
61210
  };
60928
61211
  }
60929
61212
  function readMemoryDoc(botId, path) {
@@ -60938,7 +61221,7 @@ function readMemoryDoc(botId, path) {
60938
61221
  function writeMemoryDoc(botId, path, text8, opts = {}) {
60939
61222
  const ref = parseMemoryPath(path);
60940
61223
  ensureWorkspace(botId);
60941
- 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 });
60942
61225
  const absolute = resolveMemoryPath(botId, path);
60943
61226
  const after = redactSecretsInText(text8);
60944
61227
  const bytes = Buffer.byteLength(after, "utf8");
@@ -60968,7 +61251,7 @@ function deleteMemoryDoc(botId, path) {
60968
61251
  }
60969
61252
  const absolute = resolveMemoryPath(botId, path);
60970
61253
  const current2 = readMemoryDoc(botId, path);
60971
- if (current2.exists) unlinkSync8(absolute);
61254
+ if (current2.exists) unlinkSync9(absolute);
60972
61255
  return { path, before: current2.exists ? current2.text : null };
60973
61256
  }
60974
61257
  function obsidianUrlFor(workspacePath) {
@@ -61022,8 +61305,8 @@ async function openMemoryLocation(botId, target, platform = process.platform, ru
61022
61305
  import { randomUUID as randomUUID16 } from "node:crypto";
61023
61306
  import { closeSync as closeSync8, fstatSync as fstatSync3, mkdirSync as mkdirSync22, openSync as openSync8, readSync as readSync4 } from "node:fs";
61024
61307
  import { appendFile as appendFile3 } from "node:fs/promises";
61025
- import { join as join47 } from "node:path";
61026
- 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");
61027
61310
  var DIFF_CAP = 8e3;
61028
61311
  var BEFORE_CAP = 512 * 1024;
61029
61312
  var MAX_JOURNAL_READ_BYTES = 8 * 1024 * 1024;
@@ -61031,7 +61314,7 @@ var MAX_ROWS = 500;
61031
61314
  var LCS_CELL_CAP = 4e6;
61032
61315
  var CONTEXT = 2;
61033
61316
  function journalFile(botId) {
61034
- return join47(JOURNAL_DIR, `${botId}.ndjson`);
61317
+ return join48(JOURNAL_DIR, `${botId}.ndjson`);
61035
61318
  }
61036
61319
  function editScript(a, b3) {
61037
61320
  if (a.length * b3.length > LCS_CELL_CAP) {
@@ -61349,10 +61632,10 @@ import {
61349
61632
  renameSync as renameSync5,
61350
61633
  rmSync as rmSync6,
61351
61634
  symlinkSync,
61352
- unlinkSync as unlinkSync9,
61635
+ unlinkSync as unlinkSync10,
61353
61636
  writeFileSync as writeFileSync15
61354
61637
  } from "node:fs";
61355
- 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";
61356
61639
 
61357
61640
  // shared/learn-request.ts
61358
61641
  var SAVE_RUN_AS_SKILL_LINE = "Save the steps below as a reusable skill for my review.";
@@ -61483,7 +61766,7 @@ var skillManifestEntrySchema = external_exports.object({
61483
61766
  var skillManifestSchema = external_exports.record(external_exports.string(), skillManifestEntrySchema);
61484
61767
  var managedLinksSchema = external_exports.array(external_exports.string());
61485
61768
  function skillsDir(botId) {
61486
- return join48(workspaceDir(botId), "skills");
61769
+ return join49(workspaceDir(botId), "skills");
61487
61770
  }
61488
61771
  function directoryEntryState(path) {
61489
61772
  try {
@@ -61513,20 +61796,20 @@ function ensureSkillsRoot(botId) {
61513
61796
  function existingSkillDirectory(botId, name2) {
61514
61797
  const root = existingSkillsRoot(botId);
61515
61798
  if (!root) return null;
61516
- const directory = join48(root, name2);
61799
+ const directory = join49(root, name2);
61517
61800
  return directoryEntryState(directory) === "directory" ? directory : null;
61518
61801
  }
61519
61802
  function skillDirectory(botId, name2, entry) {
61520
61803
  if (!entry.storageRevision) return existingSkillDirectory(botId, name2);
61521
61804
  const root = existingSkillsRoot(botId);
61522
61805
  if (!root) return null;
61523
- const revisions = join48(root, ".revisions");
61806
+ const revisions = join49(root, ".revisions");
61524
61807
  if (directoryEntryState(revisions) !== "directory") return null;
61525
- const directory = join48(revisions, entry.storageRevision);
61808
+ const directory = join49(revisions, entry.storageRevision);
61526
61809
  return directoryEntryState(directory) === "directory" ? directory : null;
61527
61810
  }
61528
61811
  function skillTarget(root, name2, entry) {
61529
- 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);
61530
61813
  }
61531
61814
  function entryExistsWithoutFollowing(path) {
61532
61815
  try {
@@ -61539,7 +61822,7 @@ function entryExistsWithoutFollowing(path) {
61539
61822
  function nativeLinkDirectory(root, relative6, create) {
61540
61823
  const [family, leaf] = relative6.split("/");
61541
61824
  if (!family || !leaf) return null;
61542
- const familyDir = join48(root, family);
61825
+ const familyDir = join49(root, family);
61543
61826
  let familyState = directoryEntryState(familyDir);
61544
61827
  if (familyState === "missing" && create) {
61545
61828
  try {
@@ -61550,7 +61833,7 @@ function nativeLinkDirectory(root, relative6, create) {
61550
61833
  familyState = directoryEntryState(familyDir);
61551
61834
  }
61552
61835
  if (familyState !== "directory") return null;
61553
- const linkDir = join48(familyDir, leaf);
61836
+ const linkDir = join49(familyDir, leaf);
61554
61837
  let linkState = directoryEntryState(linkDir);
61555
61838
  if (linkState === "missing" && create) {
61556
61839
  try {
@@ -61563,13 +61846,13 @@ function nativeLinkDirectory(root, relative6, create) {
61563
61846
  return linkState === "directory" ? linkDir : null;
61564
61847
  }
61565
61848
  function skillStateDir(botId) {
61566
- return join48(DATA_DIR, "skill-state", botId);
61849
+ return join49(DATA_DIR, "skill-state", botId);
61567
61850
  }
61568
61851
  function manifestPath(botId) {
61569
- return join48(skillStateDir(botId), "skills.json");
61852
+ return join49(skillStateDir(botId), "skills.json");
61570
61853
  }
61571
61854
  function managedLinksPath(botId) {
61572
- return join48(skillStateDir(botId), "managed-links.json");
61855
+ return join49(skillStateDir(botId), "managed-links.json");
61573
61856
  }
61574
61857
  function readManagedLinks(botId) {
61575
61858
  try {
@@ -61604,7 +61887,7 @@ function readManifest(botId) {
61604
61887
  if (existsSync22(securePath)) return manifestFromFile(securePath) ?? {};
61605
61888
  const legacyRoot = existingSkillsRoot(botId);
61606
61889
  if (!legacyRoot) return {};
61607
- const legacyPath = join48(legacyRoot, "skills.json");
61890
+ const legacyPath = join49(legacyRoot, "skills.json");
61608
61891
  if (!existsSync22(legacyPath)) return {};
61609
61892
  const legacy = manifestFromFile(legacyPath) ?? {};
61610
61893
  const migrated = {};
@@ -61646,20 +61929,20 @@ function nativeLinkDirectlyTargetsOwnedSkill(link2, root, name2, revisionWasMana
61646
61929
  if (!lstatSync6(link2).isSymbolicLink()) return false;
61647
61930
  const rawTarget = readlinkSync(link2);
61648
61931
  const resolvedTarget = resolve13(targetBaseDirectory, rawTarget.replace(/^\\\\\?\\/, ""));
61649
- const insideSkills = relative4(join48(root, "skills"), resolvedTarget).replaceAll("\\", "/");
61932
+ const insideSkills = relative4(join49(root, "skills"), resolvedTarget).replaceAll("\\", "/");
61650
61933
  return insideSkills === name2 || revisionWasManaged && /^\.revisions\/[a-f0-9]{64}$/.test(insideSkills);
61651
61934
  } catch {
61652
61935
  return false;
61653
61936
  }
61654
61937
  }
61655
61938
  function removeOwnedNativeLink(botId, link2, root, name2, revisionWasManaged, beforeRemove) {
61656
- const quarantineDir = join48(skillStateDir(botId), "link-removal");
61939
+ const quarantineDir = join49(skillStateDir(botId), "link-removal");
61657
61940
  try {
61658
61941
  mkdirSync23(quarantineDir, { recursive: true, mode: 448 });
61659
61942
  } catch {
61660
61943
  return "retry";
61661
61944
  }
61662
- const quarantined = join48(quarantineDir, randomUUID17());
61945
+ const quarantined = join49(quarantineDir, randomUUID17());
61663
61946
  beforeRemove?.(link2);
61664
61947
  try {
61665
61948
  renameSync5(link2, quarantined);
@@ -61674,7 +61957,7 @@ function removeOwnedNativeLink(botId, link2, root, name2, revisionWasManaged, be
61674
61957
  dirname12(link2)
61675
61958
  )) {
61676
61959
  try {
61677
- unlinkSync9(quarantined);
61960
+ unlinkSync10(quarantined);
61678
61961
  return "removed";
61679
61962
  } catch {
61680
61963
  try {
@@ -61712,7 +61995,7 @@ function removeNativeLinksForUnsafeSkillsRoot(botId, root, previouslyManaged, be
61712
61995
  continue;
61713
61996
  }
61714
61997
  for (const name2 of /* @__PURE__ */ new Set([...existing, ...previouslyManaged])) {
61715
- const link2 = join48(linkDir, name2);
61998
+ const link2 = join49(linkDir, name2);
61716
61999
  if (!nativeLinkDirectlyTargetsOwnedSkill(link2, root, name2, previouslyManaged.includes(name2))) continue;
61717
62000
  const result = removeOwnedNativeLink(
61718
62001
  botId,
@@ -61752,7 +62035,7 @@ function syncSkillLinks(botId, options = {}) {
61752
62035
  } catch {
61753
62036
  }
61754
62037
  for (const name2 of /* @__PURE__ */ new Set([...existing, ...previouslyManaged])) {
61755
- const link2 = join48(linkDir, name2);
62038
+ const link2 = join49(linkDir, name2);
61756
62039
  const target = desired.get(name2);
61757
62040
  if (target && nativeLinkPointsToSkill(link2, target)) {
61758
62041
  managed.add(name2);
@@ -61770,7 +62053,7 @@ function syncSkillLinks(botId, options = {}) {
61770
62053
  }
61771
62054
  if (!enabled.length) continue;
61772
62055
  for (const [name2, entry] of enabled) {
61773
- const link2 = join48(linkDir, name2);
62056
+ const link2 = join49(linkDir, name2);
61774
62057
  const target = skillTarget(root, name2, entry);
61775
62058
  if (nativeLinkPointsToSkill(link2, target)) {
61776
62059
  managed.add(name2);
@@ -61796,7 +62079,7 @@ function skillContentMatches(botId, name2, entry) {
61796
62079
  try {
61797
62080
  const directory = skillDirectory(botId, name2, entry);
61798
62081
  if (!directory) return false;
61799
- const file2 = join48(directory, "SKILL.md");
62082
+ const file2 = join49(directory, "SKILL.md");
61800
62083
  if (!lstatSync6(file2).isFile()) return false;
61801
62084
  return createHash18("sha256").update(readFileSync32(file2)).digest("hex") === entry.sha256;
61802
62085
  } catch {
@@ -61826,7 +62109,7 @@ function readSkillFile(botId, name2) {
61826
62109
  if (!directory) return null;
61827
62110
  let descriptor = null;
61828
62111
  try {
61829
- const path = join48(directory, "SKILL.md");
62112
+ const path = join49(directory, "SKILL.md");
61830
62113
  const before = lstatSync6(path);
61831
62114
  if (!before.isFile() || before.size > SKILL_FILE_MAX_BYTES) return null;
61832
62115
  descriptor = openSync9(path, constants4.O_RDONLY | (process.platform === "win32" ? 0 : constants4.O_NOFOLLOW ?? 0));
@@ -61866,9 +62149,9 @@ function setSkillEnabled(botId, name2, enabled) {
61866
62149
  function removeReviewedRevision(botId, revision, sha2564) {
61867
62150
  const root = existingSkillsRoot(botId);
61868
62151
  if (!root) return;
61869
- const revisions = join48(root, ".revisions");
62152
+ const revisions = join49(root, ".revisions");
61870
62153
  if (directoryEntryState(revisions) !== "directory") return;
61871
- const directory = join48(revisions, revision);
62154
+ const directory = join49(revisions, revision);
61872
62155
  if (!learnedSkillDirectoryMatches(directory, sha2564)) return;
61873
62156
  try {
61874
62157
  rmSync6(directory, { recursive: true, force: true });
@@ -61890,7 +62173,7 @@ function retireReviewedSkillStorage(botId, name2, entry) {
61890
62173
  function removeReviewedRevisionsNamed(botId, name2) {
61891
62174
  const root = existingSkillsRoot(botId);
61892
62175
  if (!root) return;
61893
- const revisions = join48(root, ".revisions");
62176
+ const revisions = join49(root, ".revisions");
61894
62177
  if (directoryEntryState(revisions) !== "directory") return;
61895
62178
  let candidates;
61896
62179
  try {
@@ -61899,10 +62182,10 @@ function removeReviewedRevisionsNamed(botId, name2) {
61899
62182
  return;
61900
62183
  }
61901
62184
  for (const revision of candidates) {
61902
- const directory = join48(revisions, revision);
62185
+ const directory = join49(revisions, revision);
61903
62186
  if (directoryEntryState(directory) !== "directory") continue;
61904
62187
  try {
61905
- const file2 = join48(directory, "SKILL.md");
62188
+ const file2 = join49(directory, "SKILL.md");
61906
62189
  const stat6 = lstatSync6(file2);
61907
62190
  if (!stat6.isFile() || stat6.size > SKILL_FILE_MAX_BYTES) continue;
61908
62191
  const parsed = parseSkillMd(readFileSync32(file2, "utf8"));
@@ -61921,7 +62204,7 @@ function removeSkill(botId, name2) {
61921
62204
  if (directoryEntryState(root) === "unsafe") {
61922
62205
  return { error: "the workspace skills path is a symlink or file; refusing to remove through it" };
61923
62206
  }
61924
- const target = entry.storageRevision ? null : join48(root, name2);
62207
+ const target = entry.storageRevision ? null : join49(root, name2);
61925
62208
  const targetState = target ? directoryEntryState(target) : "missing";
61926
62209
  delete manifest[name2];
61927
62210
  writeManifest(botId, manifest);
@@ -61954,7 +62237,7 @@ var stagedSkillWriteSchema = external_exports.object({
61954
62237
  });
61955
62238
  var stagedStoreSchema = external_exports.object({ writes: external_exports.record(external_exports.string(), stagedSkillWriteSchema) });
61956
62239
  function stagedPath(botId) {
61957
- return join48(skillStateDir(botId), "staged.json");
62240
+ return join49(skillStateDir(botId), "staged.json");
61958
62241
  }
61959
62242
  function readStaged(botId) {
61960
62243
  const securePath = stagedPath(botId);
@@ -61969,7 +62252,7 @@ function readStaged(botId) {
61969
62252
  }
61970
62253
  const legacyRoot = existingSkillsRoot(botId);
61971
62254
  if (!legacyRoot) return { writes: {} };
61972
- const legacyPath = join48(legacyRoot, "staged.json");
62255
+ const legacyPath = join49(legacyRoot, "staged.json");
61973
62256
  if (!existsSync22(legacyPath)) return { writes: {} };
61974
62257
  const empty = { writes: {} };
61975
62258
  writeStaged(botId, empty);
@@ -62018,7 +62301,7 @@ function learnedSkillDirectoryMatches(directory, sha2564) {
62018
62301
  try {
62019
62302
  const entries = readdirSync6(directory);
62020
62303
  if (entries.length !== 1 || entries[0] !== "SKILL.md") return false;
62021
- const file2 = join48(directory, "SKILL.md");
62304
+ const file2 = join49(directory, "SKILL.md");
62022
62305
  if (!lstatSync6(file2).isFile()) return false;
62023
62306
  return createHash18("sha256").update(readFileSync32(file2)).digest("hex") === sha2564;
62024
62307
  } catch {
@@ -62047,14 +62330,14 @@ function publishReviewedRevision(botId, stageId, skillMd, sha2564) {
62047
62330
  if (directoryEntryState(state) !== "directory") {
62048
62331
  throw new Error("the protected skill state path is not a real directory");
62049
62332
  }
62050
- const preparedRoot = join48(state, "reviewed-revisions");
62333
+ const preparedRoot = join49(state, "reviewed-revisions");
62051
62334
  const preparedRootState = directoryEntryState(preparedRoot);
62052
62335
  if (preparedRootState === "unsafe") {
62053
62336
  throw new Error("the protected revision path is not a real directory");
62054
62337
  }
62055
62338
  if (preparedRootState === "missing") mkdirSync23(preparedRoot, { mode: 448 });
62056
62339
  const revision = createHash18("sha256").update(stageId).digest("hex");
62057
- const prepared = join48(preparedRoot, revision);
62340
+ const prepared = join49(preparedRoot, revision);
62058
62341
  if (!learnedSkillDirectoryMatches(prepared, sha2564)) {
62059
62342
  if (entryExistsWithoutFollowing(prepared)) {
62060
62343
  if (directoryEntryState(prepared) !== "directory") {
@@ -62062,10 +62345,10 @@ function publishReviewedRevision(botId, stageId, skillMd, sha2564) {
62062
62345
  }
62063
62346
  rmSync6(prepared, { recursive: true, force: true });
62064
62347
  }
62065
- const temporary = join48(preparedRoot, `.prepare-${revision}-${randomUUID17()}`);
62348
+ const temporary = join49(preparedRoot, `.prepare-${revision}-${randomUUID17()}`);
62066
62349
  try {
62067
62350
  mkdirSync23(temporary, { mode: 448 });
62068
- writeFileAtomic(join48(temporary, "SKILL.md"), skillMd, { mode: 384 });
62351
+ writeFileAtomic(join49(temporary, "SKILL.md"), skillMd, { mode: 384 });
62069
62352
  if (!learnedSkillDirectoryMatches(temporary, sha2564)) {
62070
62353
  throw new Error("the protected reviewed bytes do not match the approval card");
62071
62354
  }
@@ -62079,13 +62362,13 @@ function publishReviewedRevision(botId, stageId, skillMd, sha2564) {
62079
62362
  if (!root) throw new Error("the workspace skills path must be a real directory, not a symlink or file");
62080
62363
  const rootIdentity = directoryIdentity2(root);
62081
62364
  if (!rootIdentity) throw new Error("the workspace skills path changed during update");
62082
- const revisions = join48(root, ".revisions");
62365
+ const revisions = join49(root, ".revisions");
62083
62366
  const revisionsState = directoryEntryState(revisions);
62084
62367
  if (revisionsState === "unsafe") throw new Error("the skill revisions path is not a real directory");
62085
62368
  if (revisionsState === "missing") mkdirSync23(revisions, { mode: 448 });
62086
62369
  const revisionsIdentity = directoryIdentity2(revisions);
62087
62370
  if (!revisionsIdentity) throw new Error("the skill revisions path could not be created safely");
62088
- const target = join48(revisions, revision);
62371
+ const target = join49(revisions, revision);
62089
62372
  if (learnedSkillDirectoryMatches(target, sha2564)) {
62090
62373
  try {
62091
62374
  rmSync6(prepared, { recursive: true, force: true });
@@ -62108,14 +62391,14 @@ function publishReviewedRevision(botId, stageId, skillMd, sha2564) {
62108
62391
  function commitNewSkillFiles(botId, name2, files, commitManifest) {
62109
62392
  const root = ensureSkillsRoot(botId);
62110
62393
  if (!root) throw new Error("the workspace skills path must be a real directory, not a symlink or file");
62111
- const target = join48(root, name2);
62112
- const staged = join48(root, `.install-${name2}-${randomUUID17()}`);
62394
+ const target = join49(root, name2);
62395
+ const staged = join49(root, `.install-${name2}-${randomUUID17()}`);
62113
62396
  if (entryExistsWithoutFollowing(target)) throw new Error(`skill path already exists: ${name2}`);
62114
62397
  let published = false;
62115
62398
  try {
62116
62399
  mkdirSync23(staged, { mode: 448 });
62117
62400
  for (const file2 of files) {
62118
- writeFileSync15(join48(staged, file2.path), file2.content, { mode: 384 });
62401
+ writeFileSync15(join49(staged, file2.path), file2.content, { mode: 384 });
62119
62402
  }
62120
62403
  if (directoryEntryState(root) !== "directory" || entryExistsWithoutFollowing(target)) {
62121
62404
  throw new Error("the workspace skills path changed during installation");
@@ -62159,7 +62442,7 @@ function installPreparedSkill(botId, source, prepared, options) {
62159
62442
  };
62160
62443
  const root = ensureSkillsRoot(botId);
62161
62444
  if (!root) return { error: "the workspace skills path must be a real directory, not a symlink or file" };
62162
- const target = join48(root, name2);
62445
+ const target = join49(root, name2);
62163
62446
  if (entryExistsWithoutFollowing(target)) {
62164
62447
  if (directoryEntryState(target) !== "directory") {
62165
62448
  return { error: `skill path must be a real directory, not a symlink or file: ${name2}` };
@@ -62323,7 +62606,7 @@ function rejectStagedSkillWrite(botId, id) {
62323
62606
  if (staged?.action === "update" && !alreadyApplied) {
62324
62607
  const revision = createHash18("sha256").update(id).digest("hex");
62325
62608
  removeReviewedRevision(botId, revision, staged.sha256);
62326
- const prepared = join48(skillStateDir(botId), "reviewed-revisions", revision);
62609
+ const prepared = join49(skillStateDir(botId), "reviewed-revisions", revision);
62327
62610
  if (learnedSkillDirectoryMatches(prepared, staged.sha256)) {
62328
62611
  try {
62329
62612
  rmSync6(prepared, { recursive: true, force: true });
@@ -62400,7 +62683,7 @@ function skillsSystemPrompt(botId) {
62400
62683
  let bytes = 0;
62401
62684
  for (const skill of enabled.slice(0, INDEX_MAX_SKILLS)) {
62402
62685
  const entry = manifest[skill.name];
62403
- const file2 = join48(skillTarget(root, skill.name, entry), "SKILL.md");
62686
+ const file2 = join49(skillTarget(root, skill.name, entry), "SKILL.md");
62404
62687
  const line = `- ${skill.name}: ${skill.description} Read ${JSON.stringify(file2)}.`;
62405
62688
  bytes += Buffer.byteLength(line, "utf8");
62406
62689
  if (bytes > INDEX_MAX_BYTES) break;
@@ -62460,7 +62743,7 @@ function parseSkillSource(input) {
62460
62743
  const text8 = input.trim();
62461
62744
  if (!text8) return { error: "paste a GitHub repository, folder, or SKILL.md URL" };
62462
62745
  if (/^https?:\/\/raw\.githubusercontent\.com\/.+\/SKILL\.md$/i.test(text8)) return { rawUrl: text8 };
62463
- const blob = text8.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+)\/blob\/([^/]+)\/(.+SKILL\.md)$/i);
62746
+ const blob = text8.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+)\/blob\/([^/]+)\/((?:.*\/)?SKILL\.md)$/i);
62464
62747
  if (blob) {
62465
62748
  return { rawUrl: `https://raw.githubusercontent.com/${blob[1]}/${blob[2]}/${blob[3]}/${blob[4]}` };
62466
62749
  }
@@ -62657,7 +62940,7 @@ function mentionPrompt(tagged) {
62657
62940
  // server/local-computer.ts
62658
62941
  import { lstatSync as lstatSync7, readFileSync as readFileSync33, realpathSync as realpathSync9, statSync as statSync8 } from "node:fs";
62659
62942
  import { homedir as homedir23 } from "node:os";
62660
- 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";
62661
62944
  var REQUIRED_LINUX_TOOLS = ["click", "get_window_state", "list_apps", "type_text"];
62662
62945
  var DRIVER_FILE_IDENTITY_KEYS = [
62663
62946
  "dev",
@@ -62842,10 +63125,10 @@ function readCuaConnection({
62842
63125
  home = homedir23(),
62843
63126
  validateLinuxRuntime = validateLinuxDescriptorRuntime
62844
63127
  } = {}) {
62845
- const candidates = userData ? [join49(userData, "cua-connection.json")] : [];
63128
+ const candidates = userData ? [join50(userData, "cua-connection.json")] : [];
62846
63129
  if (platform === "darwin") {
62847
63130
  for (const directory of ["OpenMausBot", "openmausbot", "OpenGrokBot", "opengrokbot"]) {
62848
- candidates.push(join49(home, "Library", "Application Support", directory, "cua-connection.json"));
63131
+ candidates.push(join50(home, "Library", "Application Support", directory, "cua-connection.json"));
62849
63132
  }
62850
63133
  }
62851
63134
  for (const file2 of new Set(candidates)) {
@@ -63019,12 +63302,12 @@ import { createConnection, createServer } from "node:net";
63019
63302
  // server/vps-ssh.ts
63020
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";
63021
63304
  import { homedir as homedir24 } from "node:os";
63022
- import { delimiter as delimiter4, join as join50 } from "node:path";
63305
+ import { delimiter as delimiter4, join as join51 } from "node:path";
63023
63306
  var VPS_SSH_DIR = "ssh";
63024
63307
  function findExecutable(name2, pathValue, exclude) {
63025
63308
  for (const dir of pathValue.split(delimiter4)) {
63026
63309
  if (!dir || dir === exclude) continue;
63027
- const candidate = join50(dir, name2);
63310
+ const candidate = join51(dir, name2);
63028
63311
  try {
63029
63312
  const stat6 = statSync9(candidate);
63030
63313
  if (stat6.isFile() && (stat6.mode & 73) !== 0) return candidate;
@@ -63045,14 +63328,14 @@ function writeIfChanged(path, content3, mode) {
63045
63328
  writeFileSync16(temp, content3, { mode });
63046
63329
  renameSync6(temp, path);
63047
63330
  }
63048
- 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") {
63049
63332
  return [
63050
63333
  "# Written by OpenMausBot for its VPS computer connections. Do not edit;",
63051
63334
  "# it is regenerated. Your own ~/.ssh/config is included first and wins.",
63052
63335
  ...existsSync23(userConfig) ? [`Include ${userConfig}`] : [],
63053
63336
  "Host *",
63054
63337
  " ControlMaster auto",
63055
- ` ControlPath ${join50(sshDir, "cm-%C")}`,
63338
+ ` ControlPath ${join51(sshDir, "cm-%C")}`,
63056
63339
  " ControlPersist 10m",
63057
63340
  " ServerAliveInterval 15",
63058
63341
  " ServerAliveCountMax 3",
@@ -63063,11 +63346,11 @@ function vpsSshConfigText(sshDir, userConfig = join50(homedir24(), ".ssh", "conf
63063
63346
  }
63064
63347
  function prepareVpsSsh(dataDir, pathValue, platform = process.platform) {
63065
63348
  if (platform === "win32") return { configPath: null, path: pathValue };
63066
- const sshDir = join50(dataDir, VPS_SSH_DIR);
63067
- const binDir = join50(sshDir, "bin");
63349
+ const sshDir = join51(dataDir, VPS_SSH_DIR);
63350
+ const binDir = join51(sshDir, "bin");
63068
63351
  mkdirSync24(binDir, { recursive: true, mode: 448 });
63069
63352
  chmodSync5(sshDir, 448);
63070
- const configPath = join50(sshDir, "config");
63353
+ const configPath = join51(sshDir, "config");
63071
63354
  writeIfChanged(configPath, vpsSshConfigText(sshDir), 384);
63072
63355
  const realSsh = findExecutable("ssh", pathValue, binDir);
63073
63356
  if (!realSsh) return { configPath, path: pathValue };
@@ -63078,7 +63361,7 @@ function prepareVpsSsh(dataDir, pathValue, platform = process.platform) {
63078
63361
  `exec ${JSON.stringify(realSsh)} -F ${JSON.stringify(configPath)} "$@"`,
63079
63362
  ""
63080
63363
  ].join("\n");
63081
- writeIfChanged(join50(binDir, "ssh"), shim, 448);
63364
+ writeIfChanged(join51(binDir, "ssh"), shim, 448);
63082
63365
  return { configPath, path: `${binDir}${delimiter4}${pathValue}` };
63083
63366
  }
63084
63367
 
@@ -64007,7 +64290,7 @@ async function vpsComputerScreenshot(cfg2, botId, runner = defaultRunner) {
64007
64290
  // server/routines.ts
64008
64291
  import { randomUUID as randomUUID18 } from "node:crypto";
64009
64292
  import { mkdirSync as mkdirSync25, readFileSync as readFileSync35 } from "node:fs";
64010
- import { dirname as dirname14, join as join51 } from "node:path";
64293
+ import { dirname as dirname14, join as join52 } from "node:path";
64011
64294
 
64012
64295
  // node_modules/.pnpm/croner@10.0.1/node_modules/croner/dist/croner.js
64013
64296
  function T(s4) {
@@ -64739,6 +65022,7 @@ function routineRequestOwnerKey(owner2) {
64739
65022
  var ALL_DAYS = [0, 1, 2, 3, 4, 5, 6];
64740
65023
  var CATCH_UP_MS = 12 * 60 * 6e4;
64741
65024
  var WAKE_HORIZON_MS = 60 * 6e4;
65025
+ var ROUTINE_DEFERRAL_NOTICE_MS = 30 * 6e4;
64742
65026
  var MAX_DATE_MS = 864e13;
64743
65027
  var LOCAL_DAY_MS = 24 * 60 * 6e4;
64744
65028
  var INTERVAL_RESTRICTION_SEARCH_MS = 9 * LOCAL_DAY_MS;
@@ -65119,7 +65403,7 @@ var RoutineManager = class {
65119
65403
  ticking = false;
65120
65404
  constructor(options) {
65121
65405
  this.options = options;
65122
- this.file = options.file ?? join51(DATA_DIR, "routines.json");
65406
+ this.file = options.file ?? join52(DATA_DIR, "routines.json");
65123
65407
  this.now = options.now ?? Date.now;
65124
65408
  try {
65125
65409
  const disk = JSON.parse(readFileSync35(this.file, "utf8"));
@@ -65713,7 +65997,20 @@ var RoutineManager = class {
65713
65997
  }
65714
65998
  }
65715
65999
  const state = this.targetState(run3);
65716
- if (state === "busy") continue;
66000
+ if (state === "busy") {
66001
+ if (run3.deferredAt == null) {
66002
+ run3.deferredAt = now;
66003
+ this.save();
66004
+ this.emitRun(run3);
66005
+ }
66006
+ if (run3.deferredNoticeAt == null && now - run3.deferredAt >= ROUTINE_DEFERRAL_NOTICE_MS) {
66007
+ run3.deferredNoticeAt = now;
66008
+ this.save();
66009
+ this.emitRun(run3);
66010
+ this.options.onRunDeferred?.(cloneRun(run3));
66011
+ }
66012
+ continue;
66013
+ }
65717
66014
  if (state === "missing") {
65718
66015
  this.failRun(run3, this.missingTargetMessage(run3.target));
65719
66016
  continue;
@@ -66018,7 +66315,7 @@ var RoutineManager = class {
66018
66315
  // server/calendar-calls.ts
66019
66316
  import { randomUUID as randomUUID19 } from "node:crypto";
66020
66317
  import { mkdirSync as mkdirSync26, readFileSync as readFileSync36 } from "node:fs";
66021
- import { dirname as dirname15, join as join52 } from "node:path";
66318
+ import { dirname as dirname15, join as join53 } from "node:path";
66022
66319
  var MAX_BOTS = 100;
66023
66320
  var MAX_ATTACHMENTS2 = 50;
66024
66321
  var scheduleSchema = external_exports.discriminatedUnion("type", [
@@ -66124,7 +66421,7 @@ var CalendarCallManager = class {
66124
66421
  timer = null;
66125
66422
  ticking = false;
66126
66423
  constructor(options) {
66127
- this.file = options.file ?? join52(DATA_DIR, "calendar-calls.json");
66424
+ this.file = options.file ?? join53(DATA_DIR, "calendar-calls.json");
66128
66425
  this.now = options.now ?? Date.now;
66129
66426
  this.botExists = options.botExists;
66130
66427
  this.onDue = options.onDue;
@@ -66294,12 +66591,12 @@ var CalendarCallManager = class {
66294
66591
  // server/browser-engine.ts
66295
66592
  import { spawn as spawn5 } from "node:child_process";
66296
66593
  import { createHash as createHash20, randomBytes as randomBytes4, randomUUID as randomUUID20 } from "node:crypto";
66297
- 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";
66298
66595
  import { homedir as homedir25, tmpdir as tmpdir6 } from "node:os";
66299
- 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";
66300
66597
 
66301
66598
  // server/browser-bundle-release.ts
66302
- import { join as join53 } from "node:path";
66599
+ import { join as join54 } from "node:path";
66303
66600
 
66304
66601
  // server/browser-engine-release.ts
66305
66602
  var AGENT_BROWSER_VERSION = "0.37.0";
@@ -66354,12 +66651,13 @@ function resolveAgentBrowserReleaseAsset(platform = process.platform, arch = pro
66354
66651
  }
66355
66652
 
66356
66653
  // server/browser-bundle-release.ts
66357
- var CHROME_VERSION = "152.0.7977.82";
66654
+ var CHROME_VERSION = "153.0.8010.47";
66358
66655
  var CHROME_ASSETS = {
66359
- "darwin-arm64": { platform: "mac-arm64", bytes: 97760996, sha256: "1615f063c894aa824fd55c89f8f05e9904f63cce0c95d0cbce7394d77884fba5", executableSha256: "ac18537fb2bc2b72b0440893b6ea5d4ef3bacc6220b1e9a1693c73f8b1045fbe" },
66360
- "darwin-x64": { platform: "mac-x64", bytes: 102895824, sha256: "c903707292c6aaed7c6e572c0bb5e6645454e45d9e0e6db27e876b18e9165f31", executableSha256: "2faefd55d4e47764956e31e338d362a1e794037fa772a426ffd510f122f33f19" },
66361
- "linux-x64": { platform: "linux64", bytes: 119454769, sha256: "0ca12ea26b502a83e32db334a17883c315348845efc071d908de7a6d94a97eff", executableSha256: "1301a5024fef1d58c4cfbde5d44794cb18c5b2cbbe4f3c64f81cc4eaa683c45f" },
66362
- "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" }
66363
66661
  };
66364
66662
  function browserBundleSpec(target) {
66365
66663
  if (!Object.hasOwn(CHROME_ASSETS, target)) throw new Error(`Unsupported desktop browser target: ${target}`);
@@ -66396,10 +66694,10 @@ function browserBundlePaths(bundleDirectory, target) {
66396
66694
  const spec = browserBundleSpec(target);
66397
66695
  return {
66398
66696
  directory: bundleDirectory,
66399
- manifest: join53(bundleDirectory, "manifest.json"),
66400
- engine: join53(bundleDirectory, spec.engine.executable),
66401
- chrome: join53(bundleDirectory, spec.chrome.executable),
66402
- 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")
66403
66701
  };
66404
66702
  }
66405
66703
 
@@ -66795,11 +67093,11 @@ function browserSessionsDirectory(env) {
66795
67093
  const namespace = env.AGENT_BROWSER_NAMESPACE;
66796
67094
  if (namespace && !/^[a-z0-9]+(?:[-_][a-z0-9]+)*$/.test(namespace)) throw new Error("Invalid browser namespace.");
66797
67095
  const home = (process.platform === "win32" ? env.USERPROFILE : env.HOME) || homedir25();
66798
- const stateRoot = namespace ? join54(home, ".agent-browser", "namespaces", namespace, "state") : join54(home, ".agent-browser");
66799
- return join54(stateRoot, "sessions");
67096
+ const stateRoot = namespace ? join55(home, ".agent-browser", "namespaces", namespace, "state") : join55(home, ".agent-browser");
67097
+ return join55(stateRoot, "sessions");
66800
67098
  }
66801
67099
  function managedBrowserConfigPath(env) {
66802
- return join54(browserSessionsDirectory(env), "..", "omb-managed-config.json");
67100
+ return join55(browserSessionsDirectory(env), "..", "omb-managed-config.json");
66803
67101
  }
66804
67102
  function ensureManagedBrowserConfig(env) {
66805
67103
  const path = managedBrowserConfigPath(env);
@@ -66887,11 +67185,11 @@ async function prepareBrowserSessionState(binaryPath, session, options = {}) {
66887
67185
  ensureManagedBrowserConfig(env);
66888
67186
  if (options.persistent === false) return;
66889
67187
  const directory = browserSessionsDirectory(env);
66890
- const key3 = join54(directory, session);
67188
+ const key3 = join55(directory, session);
66891
67189
  let pending = preparedBrowserSessions.get(key3);
66892
67190
  if (!pending) {
66893
67191
  pending = (async () => {
66894
- const targetBase = join54(directory, `${restoreKey}-${session}`);
67192
+ const targetBase = join55(directory, `${restoreKey}-${session}`);
66895
67193
  const hasTarget = () => existsSync24(`${targetBase}.json.enc`) || existsSync24(`${targetBase}.json`);
66896
67194
  if (hasTarget()) return;
66897
67195
  check2();
@@ -66901,7 +67199,7 @@ async function prepareBrowserSessionState(binaryPath, session, options = {}) {
66901
67199
  check2();
66902
67200
  if (hasTarget()) return;
66903
67201
  const candidates = [".json.enc", ".json"].flatMap((suffix) => {
66904
- const path = join54(directory, `${session}-${session}${suffix}`);
67202
+ const path = join55(directory, `${session}-${session}${suffix}`);
66905
67203
  try {
66906
67204
  const stat6 = statSync10(path);
66907
67205
  if (!stat6.isFile()) throw new Error("Saved browser state is not a file.");
@@ -66936,7 +67234,7 @@ function isMusl(platform = process.platform, exists = existsSync24) {
66936
67234
  }
66937
67235
  function pinnedBinaryPath(dataDir = DATA_DIR, platform = process.platform, arch = process.arch) {
66938
67236
  const version2 = agentBrowserReleaseVersion(resolveAgentBrowserReleaseAsset(platform, arch));
66939
- return join54(dataDir, ENGINE_DIR, version2, executableName(platform));
67237
+ return join55(dataDir, ENGINE_DIR, version2, executableName(platform));
66940
67238
  }
66941
67239
  function onPath(env, platform, exists) {
66942
67240
  const pathValue = platform === "win32" ? Object.entries(env).findLast(([key3]) => key3.toUpperCase() === "PATH")?.[1] : env.PATH;
@@ -66952,7 +67250,7 @@ function packagedBrowser(options) {
66952
67250
  const resources = (options.env ?? process.env).OMB_RESOURCES_PATH;
66953
67251
  if (!resources) return null;
66954
67252
  try {
66955
- 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}`);
66956
67254
  } catch {
66957
67255
  return null;
66958
67256
  }
@@ -66977,7 +67275,7 @@ async function installAgentBrowserBinary(options = {}) {
66977
67275
  const asset = options.asset ?? resolveAgentBrowserReleaseAsset(platform, options.arch ?? process.arch, options.musl ?? isMusl(platform));
66978
67276
  if (!asset) throw new Error(`agent-browser publishes no build for ${platform}-${options.arch ?? process.arch}.`);
66979
67277
  const destination = pinnedBinaryPath(options.dataDir, platform, options.arch);
66980
- const directory = join54(destination, "..");
67278
+ const directory = join55(destination, "..");
66981
67279
  mkdirSync27(directory, { recursive: true, mode: 448 });
66982
67280
  const url2 = agentBrowserReleaseUrl(asset);
66983
67281
  options.log?.(`downloading agent-browser ${agentBrowserReleaseVersion(asset)} (${Math.round(asset.bytes / 1024 / 1024)} MB, digest pinned)`);
@@ -67029,7 +67327,7 @@ function ensureChrome(binaryPath, options = {}) {
67029
67327
  });
67030
67328
  }
67031
67329
  function browserEngineEncryptionKey(dataDir = DATA_DIR) {
67032
- const file2 = join54(dataDir, KEY_FILE);
67330
+ const file2 = join55(dataDir, KEY_FILE);
67033
67331
  try {
67034
67332
  const existing = readFileSync37(file2, "utf8").trim();
67035
67333
  if (/^[0-9a-f]{64}$/u.test(existing)) return existing;
@@ -67082,7 +67380,7 @@ function agentBrowserIntegration(input) {
67082
67380
  for (const name2 of ["PATH", "AGENT_BROWSER_EXECUTABLE_PATH"]) {
67083
67381
  if (sourceEnv[name2]) env[name2] = sourceEnv[name2];
67084
67382
  }
67085
- const bundle = packagedBrowser({ env: sourceEnv });
67383
+ const bundle = packagedBrowser({ env: sourceEnv, platform: input.platform, arch: input.arch });
67086
67384
  if (!env.AGENT_BROWSER_EXECUTABLE_PATH && bundle && resolve14(input.binaryPath) === bundle.engine && completePackage(bundle, existsSync24)) {
67087
67385
  env.AGENT_BROWSER_EXECUTABLE_PATH = bundle.chrome;
67088
67386
  }
@@ -67090,7 +67388,7 @@ function agentBrowserIntegration(input) {
67090
67388
  }
67091
67389
  var FRAME_TIMEOUT_MS = 1e4;
67092
67390
  function agentBrowserFrame(input) {
67093
- const file2 = join54(tmpdir6(), `openmausbot-browser-${randomUUID20()}.png`);
67391
+ const file2 = join55(tmpdir6(), `openmausbot-browser-${randomUUID20()}.png`);
67094
67392
  return new Promise((settle, fail2) => {
67095
67393
  const child = spawn5(input.binaryPath, ["screenshot", file2], {
67096
67394
  env: browserRuntimeEnv(input.env),
@@ -67148,14 +67446,14 @@ async function clearBrowserSessionState(binaryPath, session, options = {}) {
67148
67446
  const bases = [`${session}-${session}`, `${browserRestoreKey(session)}-${session}`];
67149
67447
  const remove = (path) => {
67150
67448
  try {
67151
- unlinkSync10(path);
67449
+ unlinkSync11(path);
67152
67450
  } catch (error51) {
67153
67451
  if (error51.code !== "ENOENT") throw error51;
67154
67452
  }
67155
67453
  };
67156
67454
  try {
67157
- for (const base2 of bases) for (const suffix of [".json", ".json.enc", ".json.previous", ".json.enc.previous"]) remove(join54(directory, base2 + suffix));
67158
- 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");
67159
67457
  let pendingFiles = [];
67160
67458
  try {
67161
67459
  pendingFiles = readdirSync7(pendingDirectory);
@@ -67163,8 +67461,8 @@ async function clearBrowserSessionState(binaryPath, session, options = {}) {
67163
67461
  if (error51.code !== "ENOENT") throw error51;
67164
67462
  }
67165
67463
  const candidates = bases.map((base2) => new RegExp(`^${base2}-candidate-[0-9]+\\.json(?:\\.enc)?$`));
67166
- for (const name2 of pendingFiles) if (candidates.some((candidate) => candidate.test(name2))) remove(join54(pendingDirectory, name2));
67167
- 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));
67168
67466
  return true;
67169
67467
  } catch {
67170
67468
  return false;
@@ -69171,13 +69469,13 @@ function profileRevision(bot) {
69171
69469
  import { appendFile as appendFile4 } from "node:fs/promises";
69172
69470
  import { createHash as createHash24, randomUUID as randomUUID22 } from "node:crypto";
69173
69471
  import { closeSync as closeSync10, existsSync as existsSync25, fstatSync as fstatSync5, mkdirSync as mkdirSync28, openSync as openSync10, readSync as readSync5 } from "node:fs";
69174
- import { join as join55 } from "node:path";
69472
+ import { join as join56 } from "node:path";
69175
69473
  var FILE_NAME2 = "history.ndjson";
69176
69474
  var ONE_LINER = 200;
69177
69475
  var MAX_HISTORY_READ_BYTES = 8 * 1024 * 1024;
69178
69476
  var writeQueues4 = /* @__PURE__ */ new Map();
69179
69477
  function historyFile(botId) {
69180
- return join55(botFolder(botId), FILE_NAME2);
69478
+ return join56(botFolder(botId), FILE_NAME2);
69181
69479
  }
69182
69480
  function oneLiner(value) {
69183
69481
  return value.replace(/\s+/g, " ").trim().slice(0, ONE_LINER);
@@ -69976,7 +70274,7 @@ function matchDirectoryBots(profile, bots, limit = 5) {
69976
70274
 
69977
70275
  // server/project-scout.ts
69978
70276
  import { readFileSync as readFileSync38, readdirSync as readdirSync8, statSync as statSync11 } from "node:fs";
69979
- import { basename as basename7, join as join56 } from "node:path";
70277
+ import { basename as basename7, join as join57 } from "node:path";
69980
70278
 
69981
70279
  // shared/import-name.ts
69982
70280
  function takeImportName(value, taken, maxLength = 100) {
@@ -70180,7 +70478,7 @@ function isDir(path) {
70180
70478
  }
70181
70479
  }
70182
70480
  function packageJson(cwd) {
70183
- const raw = readText2(join56(cwd, "package.json"));
70481
+ const raw = readText2(join57(cwd, "package.json"));
70184
70482
  if (!raw) return { deps: /* @__PURE__ */ new Set() };
70185
70483
  try {
70186
70484
  const parsed = JSON.parse(raw);
@@ -70203,7 +70501,7 @@ function packageJson(cwd) {
70203
70501
  }
70204
70502
  }
70205
70503
  function readme(cwd) {
70206
- 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"));
70207
70505
  if (!raw) return {};
70208
70506
  let title;
70209
70507
  let summary;
@@ -70277,12 +70575,12 @@ function scoutProject(cwd) {
70277
70575
  const pkg = packageJson(cwd);
70278
70576
  const md = readme(cwd);
70279
70577
  const entries = new Set(listNames(cwd));
70280
- const pythonDeps = `${readText2(join56(cwd, "requirements.txt")) ?? ""}
70281
- ${readText2(join56(cwd, "pyproject.toml")) ?? ""}`.toLowerCase();
70578
+ const pythonDeps = `${readText2(join57(cwd, "requirements.txt")) ?? ""}
70579
+ ${readText2(join57(cwd, "pyproject.toml")) ?? ""}`.toLowerCase();
70282
70580
  const hasDep = (dep) => pkg.deps.has(dep) || pythonDeps.length > 1 && pythonDeps.includes(dep);
70283
70581
  const pathEvidence = (path) => {
70284
- if (path.includes("/")) return isDir(join56(cwd, path)) && listNames(join56(cwd, path)).length > 0 ? `${path}/` : null;
70285
- 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;
70286
70584
  return null;
70287
70585
  };
70288
70586
  const signals = [];
@@ -70295,7 +70593,7 @@ ${readText2(join56(cwd, "pyproject.toml")) ?? ""}`.toLowerCase();
70295
70593
  }
70296
70594
  if (detector.role === "docs" && evidence.length > 0) {
70297
70595
  const hasMkdocs = evidence.includes("mkdocs.yml");
70298
- const hasMarkdown = listNames(join56(cwd, "docs")).some((name2) => name2.endsWith(".md"));
70596
+ const hasMarkdown = listNames(join57(cwd, "docs")).some((name2) => name2.endsWith(".md"));
70299
70597
  if (!hasMkdocs && !hasMarkdown) continue;
70300
70598
  }
70301
70599
  if (evidence.length > 0) signals.push({ role: detector.role, evidence: evidence.slice(0, 6) });
@@ -70984,7 +71282,7 @@ async function fetchGithubTeam(input, fetcher = fetch) {
70984
71282
 
70985
71283
  // server/thread-events.ts
70986
71284
  import { closeSync as closeSync11, fstatSync as fstatSync6, openSync as openSync11, readSync as readSync6 } from "node:fs";
70987
- import { join as join57 } from "node:path";
71285
+ import { join as join58 } from "node:path";
70988
71286
  var DEFAULT_LIMIT = 300;
70989
71287
  var MAX_LIMIT = 2e3;
70990
71288
  var READ_CHUNK = 64 * 1024;
@@ -71121,8 +71419,8 @@ function readThreadEvents(input) {
71121
71419
  assertThreadId(threadId);
71122
71420
  const requested = input.limit ?? DEFAULT_LIMIT;
71123
71421
  const limit = Number.isFinite(requested) ? Math.max(1, Math.min(Math.trunc(requested), MAX_LIMIT)) : DEFAULT_LIMIT;
71124
- const runtime = readRecentLines(join57(eventsDir, `${threadId}.ndjson`), limit, isRuntimeEvent);
71125
- 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);
71126
71424
  const merged = [
71127
71425
  ...runtime.lines.map((data) => ({ kind: "runtime", at: data.createdAt, data })),
71128
71426
  ...native.lines.map((data) => ({ kind: "native", at: data.at, data }))
@@ -71312,7 +71610,7 @@ function memberTurnSelection(selection) {
71312
71610
  // server/webhooks.ts
71313
71611
  import { createHash as createHash26, randomBytes as randomBytes5, randomUUID as randomUUID23, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
71314
71612
  import { mkdirSync as mkdirSync29, readFileSync as readFileSync39 } from "node:fs";
71315
- import { dirname as dirname17, join as join58 } from "node:path";
71613
+ import { dirname as dirname17, join as join59 } from "node:path";
71316
71614
  var MAX_DELIVERIES = 2e3;
71317
71615
  var MAX_ATTEMPTS = 2e3;
71318
71616
  var MAX_EVENT_CHARS = 48e3;
@@ -71499,7 +71797,7 @@ var WebhookManager = class {
71499
71797
  rate = /* @__PURE__ */ new Map();
71500
71798
  constructor(options) {
71501
71799
  this.options = options;
71502
- this.file = options.file ?? join58(DATA_DIR, "webhooks.json");
71800
+ this.file = options.file ?? join59(DATA_DIR, "webhooks.json");
71503
71801
  this.now = options.now ?? Date.now;
71504
71802
  try {
71505
71803
  const parsed = webhookFileSchema.safeParse(parseJson(readFileSync39(this.file, "utf8")));
@@ -71764,7 +72062,7 @@ var WebhookManager = class {
71764
72062
 
71765
72063
  // server/skill-library.ts
71766
72064
  import { existsSync as existsSync26, readFileSync as readFileSync40, readdirSync as readdirSync9 } from "node:fs";
71767
- import { basename as basename8, join as join59 } from "node:path";
72065
+ import { basename as basename8, join as join60 } from "node:path";
71768
72066
  var SAFE_ID = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
71769
72067
  function strings(value) {
71770
72068
  return Array.isArray(value) && value.every((item) => typeof item === "string" && item.trim()) ? value.map((item) => item.trim()) : null;
@@ -71793,19 +72091,19 @@ function parseSkillManifest(value, directory) {
71793
72091
  };
71794
72092
  }
71795
72093
  function loadSkillDirectory(directory) {
71796
- const manifestPath2 = join59(directory, "manifest.json");
71797
- const skillPath = join59(directory, "SKILL.md");
72094
+ const manifestPath2 = join60(directory, "manifest.json");
72095
+ const skillPath = join60(directory, "SKILL.md");
71798
72096
  if (!existsSync26(manifestPath2) || !existsSync26(skillPath)) return null;
71799
72097
  const manifest = parseSkillManifest(JSON.parse(readFileSync40(manifestPath2, "utf8")), directory);
71800
72098
  const instructions = readFileSync40(skillPath, "utf8").trim();
71801
72099
  if (!instructions.startsWith("---")) throw new Error(`${skillPath} has no skill frontmatter`);
71802
72100
  return { manifest, instructions, directory };
71803
72101
  }
71804
- 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")) {
71805
72103
  if (!existsSync26(root)) return [];
71806
72104
  const skills = [];
71807
72105
  for (const name2 of readdirSync9(root).sort()) {
71808
- const directory = join59(root, name2);
72106
+ const directory = join60(root, name2);
71809
72107
  const skill = loadSkillDirectory(directory);
71810
72108
  if (skill) skills.push(skill);
71811
72109
  }
@@ -71822,7 +72120,7 @@ function loadUserSkills(root) {
71822
72120
  const skills = [];
71823
72121
  for (const name2 of names) {
71824
72122
  try {
71825
- const skill = loadSkillDirectory(join59(root, name2));
72123
+ const skill = loadSkillDirectory(join60(root, name2));
71826
72124
  if (skill) skills.push(skill);
71827
72125
  } catch {
71828
72126
  }
@@ -72436,14 +72734,31 @@ ${message4.text}` } : {},
72436
72734
  }
72437
72735
 
72438
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"]);
72439
72739
  function shouldMountLocalComputer({
72440
72740
  requested,
72441
72741
  hostPlatform = process.platform,
72442
72742
  providerSupportsLocal
72443
72743
  }) {
72444
72744
  if (!providerSupportsLocal) return false;
72445
- if (requested === "local") return hostPlatform === "darwin" || hostPlatform === "linux";
72446
- 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
+ );
72447
72762
  }
72448
72763
 
72449
72764
  // server/model-context-window.ts
@@ -72697,9 +73012,9 @@ function createGracefulShutdown({
72697
73012
  // electron/data-dir-lease.mjs
72698
73013
  import { execFileSync } from "node:child_process";
72699
73014
  import { createHash as createHash27, randomUUID as randomUUID24 } from "node:crypto";
72700
- 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";
72701
73016
  import { hostname as hostname4, uptime } from "node:os";
72702
- import { join as join60 } from "node:path";
73017
+ import { join as join61 } from "node:path";
72703
73018
  var LEASE_NAME = "openmausbot-server.lease";
72704
73019
  var DELEGATED_CHILD_DIR = ".openmausbot-server-child";
72705
73020
  var CHILD_LEASE_ENV = "OPENMAUSBOT_INTERNAL_DATA_DIR_LEASE";
@@ -72755,7 +73070,7 @@ function ownerPredatesThisBoot(owner2) {
72755
73070
  return false;
72756
73071
  }
72757
73072
  function ownerIsAlive(owner2) {
72758
- return !ownerPredatesThisBoot(owner2) && processIsAlive3(owner2.pid);
73073
+ return !ownerPredatesThisBoot(owner2) && processIsAlive3(owner2.pid) && ownerIdentityMatches(owner2);
72759
73074
  }
72760
73075
  function isLeaseOwner(value) {
72761
73076
  if (!value || typeof value !== "object" || Array.isArray(value)) return false;
@@ -72805,9 +73120,54 @@ function processIsAlive3(pid) {
72805
73120
  throw leaseError("OpenMausBot could not verify the data-directory lease owner; refusing to start.", error51);
72806
73121
  }
72807
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
+ }
72808
73168
  function unlinkExact(path, message4) {
72809
73169
  try {
72810
- unlinkSync11(path);
73170
+ unlinkSync12(path);
72811
73171
  } catch (error51) {
72812
73172
  if (error51?.code !== "ENOENT") throw leaseError(message4, error51);
72813
73173
  }
@@ -72902,10 +73262,10 @@ function prepareDataDir(dataDir, legacyDataDir) {
72902
73262
  } catch (error51) {
72903
73263
  throw leaseError("OpenMausBot cannot create its data directory.", error51);
72904
73264
  }
72905
- return join60(dataDir, LEASE_NAME);
73265
+ return join61(dataDir, LEASE_NAME);
72906
73266
  }
72907
73267
  function assertNoLiveDelegatedChild(dataDir) {
72908
- const childLeasePath = join60(dataDir, DELEGATED_CHILD_DIR, LEASE_NAME);
73268
+ const childLeasePath = join61(dataDir, DELEGATED_CHILD_DIR, LEASE_NAME);
72909
73269
  const child = readOwner(childLeasePath);
72910
73270
  if (!child) return;
72911
73271
  if (child.host !== hostname4()) {
@@ -72947,13 +73307,13 @@ function validateChildDelegation(dataDir, encoded) {
72947
73307
  const capability = parseCapability(encoded);
72948
73308
  const invalid = () => leaseError("The OpenMausBot desktop lease delegation is invalid; refusing to start to protect its state.");
72949
73309
  if (!capability) throw invalid();
72950
- const parentLeasePath = join60(dataDir, LEASE_NAME);
73310
+ const parentLeasePath = join61(dataDir, LEASE_NAME);
72951
73311
  const matchesLiveParent = (owner2) => Boolean(owner2 && owner2.pid === capability.pid && owner2.token === capability.token && owner2.host === hostname4() && ownerIsAlive(owner2));
72952
73312
  if (!matchesLiveParent(readOwner(parentLeasePath))) {
72953
73313
  throw invalid();
72954
73314
  }
72955
73315
  const childLease = acquireDataDirLeaseInternal(
72956
- join60(dataDir, DELEGATED_CHILD_DIR),
73316
+ join61(dataDir, DELEGATED_CHILD_DIR),
72957
73317
  { guardDelegatedChild: false }
72958
73318
  );
72959
73319
  if (!matchesLiveParent(readOwner(parentLeasePath))) {
@@ -73031,7 +73391,7 @@ function acquireDataDirLeaseInternal(dataDir, options = {}) {
73031
73391
  throw leaseError("OpenMausBot will not release a data-directory lease owned by another process.");
73032
73392
  }
73033
73393
  try {
73034
- unlinkSync11(leasePath);
73394
+ unlinkSync12(leasePath);
73035
73395
  } catch (error51) {
73036
73396
  throw leaseError("OpenMausBot could not release its data-directory lease.", error51);
73037
73397
  }
@@ -73091,8 +73451,8 @@ var WorkspaceBackupMaintenance = class {
73091
73451
 
73092
73452
  // server/workspace-backup-http.ts
73093
73453
  import { randomUUID as randomUUID26 } from "node:crypto";
73094
- 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";
73095
- 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";
73096
73456
  import { Transform as Transform2 } from "node:stream";
73097
73457
  import { pipeline as pipeline3 } from "node:stream/promises";
73098
73458
 
@@ -73135,7 +73495,7 @@ import {
73135
73495
  writeFileSync as writeFileSync19,
73136
73496
  writeSync as writeSync2
73137
73497
  } from "node:fs";
73138
- 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";
73139
73499
  import { homedir as homedir26 } from "node:os";
73140
73500
  import { backup, DatabaseSync as DatabaseSync2 } from "node:sqlite";
73141
73501
  import { pipeline as pipeline2 } from "node:stream/promises";
@@ -76194,13 +76554,13 @@ function backupRoot(dataDir) {
76194
76554
  if (root === parse6(root).root) throw new Error("A filesystem root cannot be a workspace backup target.");
76195
76555
  const stat6 = lstatSync8(root);
76196
76556
  if (!stat6.isDirectory() || stat6.isSymbolicLink()) throw new Error("The workspace data directory must not be a symbolic link.");
76197
- const path = join61(root, ".backups");
76557
+ const path = join62(root, ".backups");
76198
76558
  folder2(path);
76199
76559
  return path;
76200
76560
  }
76201
76561
  function jobPath(dataDir, id) {
76202
76562
  if (!ID.test(id)) throw new Error("Invalid workspace backup identifier.");
76203
- const path = join61(backupRoot(dataDir), id);
76563
+ const path = join62(backupRoot(dataDir), id);
76204
76564
  if (entryExists(path)) {
76205
76565
  const stat6 = lstatSync8(path);
76206
76566
  if (!stat6.isDirectory() || stat6.isSymbolicLink()) throw new Error("Unsafe workspace backup job directory.");
@@ -76247,14 +76607,14 @@ function privateMetadata(manifest) {
76247
76607
  return { summary: manifest.summary, clientState: manifest.clientState };
76248
76608
  }
76249
76609
  function assertLocalAuthOutsideSnapshot(dataDir) {
76250
- const configPath = join61(dataDir, "config.json");
76610
+ const configPath = join62(dataDir, "config.json");
76251
76611
  const config2 = existsSync28(configPath) ? privateJson(configPath) : {};
76252
76612
  const instances = record2(config2) && record2(config2.instances) ? config2.instances : {};
76253
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"];
76254
76614
  const check2 = (value, field, home) => {
76255
76615
  if (typeof value !== "string" || !value.trim()) return;
76256
76616
  const text8 = value.trim();
76257
- 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;
76258
76618
  const resolved = resolve15(expanded);
76259
76619
  const exists = entryExists(resolved);
76260
76620
  const path = relative5(exists ? realpathSync10(dataDir) : resolve15(dataDir), exists ? realpathSync10(resolved) : resolved).replaceAll("\\", "/");
@@ -76364,27 +76724,27 @@ async function createWorkspaceBackup(dataDir, options) {
76364
76724
  const salt = randomBytes6(16);
76365
76725
  const key3 = await passwordKey(options.password, salt);
76366
76726
  const job = newJob(dataDir);
76367
- const snapshot2 = join61(job.directory, "snapshot");
76368
- folder2(join61(snapshot2, "data"));
76727
+ const snapshot2 = join62(job.directory, "snapshot");
76728
+ folder2(join62(snapshot2, "data"));
76369
76729
  const entries = [];
76370
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];
76371
76731
  let bytes = 0;
76372
76732
  let skippedLinks = 0;
76373
76733
  try {
76374
76734
  const root = realpathSync10(dataDir);
76375
- const sourceDb = join61(root, "messages.db");
76735
+ const sourceDb = join62(root, "messages.db");
76376
76736
  if (existsSync28(sourceDb)) {
76377
76737
  if (lstatSync8(sourceDb).isSymbolicLink()) throw new Error("The message database must not be a symbolic link.");
76378
76738
  const db2 = new DatabaseSync2(sourceDb, { readOnly: true });
76379
76739
  const completionPulse = setInterval(() => {
76380
76740
  }, 25);
76381
76741
  try {
76382
- await backup(db2, join61(snapshot2, "messages-snapshot.db"));
76742
+ await backup(db2, join62(snapshot2, "messages-snapshot.db"));
76383
76743
  } finally {
76384
76744
  clearInterval(completionPulse);
76385
76745
  db2.close();
76386
76746
  }
76387
- const snapshotDb = new DatabaseSync2(join61(snapshot2, "messages-snapshot.db"));
76747
+ const snapshotDb = new DatabaseSync2(join62(snapshot2, "messages-snapshot.db"));
76388
76748
  try {
76389
76749
  snapshotDb.exec("PRAGMA journal_mode = DELETE");
76390
76750
  } finally {
@@ -76398,7 +76758,7 @@ async function createWorkspaceBackup(dataDir, options) {
76398
76758
  if (excludedWorkspaceAuthPath(path2)) continue;
76399
76759
  if (forbiddenArchivePath(path2)) throw new Error("A workspace filename conflicts with a protected authentication or runtime path.");
76400
76760
  if (!validRelative(path2)) throw new Error("A workspace filename cannot be safely restored on supported platforms.");
76401
- const source = join61(directory, name2);
76761
+ const source = join62(directory, name2);
76402
76762
  const stat6 = lstatSync8(source);
76403
76763
  if (stat6.isSymbolicLink()) {
76404
76764
  let target;
@@ -76418,7 +76778,7 @@ async function createWorkspaceBackup(dataDir, options) {
76418
76778
  continue;
76419
76779
  }
76420
76780
  if (entries.length >= MAX_WORKSPACE_BACKUP_FILES) throw new Error("The workspace exceeds the 100,000-entry backup limit.");
76421
- const destination = join61(snapshot2, "data", path2);
76781
+ const destination = join62(snapshot2, "data", path2);
76422
76782
  if (stat6.isDirectory()) {
76423
76783
  folder2(destination);
76424
76784
  entries.push({ path: path2, type: "directory", size: 0, mode: 448 });
@@ -76436,7 +76796,7 @@ async function createWorkspaceBackup(dataDir, options) {
76436
76796
  writeJson(destination, value);
76437
76797
  }
76438
76798
  copied = { size: lstatSync8(destination).size, mode: 384, sha256: hashFile(destination) };
76439
- } 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);
76440
76800
  bytes += copied.size;
76441
76801
  if (bytes > MAX_WORKSPACE_BACKUP_BYTES) throw new Error("The workspace exceeds the 10 GB backup limit.");
76442
76802
  entries.push({ path: path2, type: "file", ...copied });
@@ -76454,9 +76814,9 @@ async function createWorkspaceBackup(dataDir, options) {
76454
76814
  files: entries.filter((entry) => entry.type === "file").length,
76455
76815
  directories: entries.filter((entry) => entry.type === "directory").length,
76456
76816
  bytes,
76457
- bots: countJsonArray(join61(snapshot2, "data", "bots.json")),
76458
- groups: countJsonArray(join61(snapshot2, "data", "groups.json")),
76459
- ...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")),
76460
76820
  exclusions: EXCLUSION_NOTES,
76461
76821
  warnings
76462
76822
  };
@@ -76466,9 +76826,9 @@ async function createWorkspaceBackup(dataDir, options) {
76466
76826
  clientState: options.clientState ?? {},
76467
76827
  entries
76468
76828
  };
76469
- writeJson(join61(snapshot2, "manifest.json"), manifest);
76470
- validateManifest(privateJson(join61(snapshot2, "manifest.json")));
76471
- 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");
76472
76832
  const iv = randomBytes6(12);
76473
76833
  const header2 = Buffer.concat([MAGIC, salt, iv]);
76474
76834
  const cipher = createCipheriv("aes-256-gcm", key3, iv);
@@ -76583,21 +76943,21 @@ async function inspectTar(path) {
76583
76943
  return entries;
76584
76944
  }
76585
76945
  function validateStaged(directory, expected) {
76586
- const manifest = validateManifest(privateJson(join61(directory, "manifest.json")));
76946
+ const manifest = validateManifest(privateJson(join62(directory, "manifest.json")));
76587
76947
  if (expected && expected.size !== manifest.entries.length + 2) throw new Error("Archive entries do not match the backup manifest.");
76588
76948
  const declared = new Set(manifest.entries.map((entry) => entry.path));
76589
76949
  const inspect = (root, prefix = "") => {
76590
76950
  for (const name2 of readdirSync10(root)) {
76591
76951
  const path = prefix ? `${prefix}/${name2}` : name2;
76592
76952
  if (!declared.has(path)) throw new Error("Staged workspace contains files absent from its manifest.");
76593
- const stat6 = lstatSync8(join61(root, name2));
76953
+ const stat6 = lstatSync8(join62(root, name2));
76594
76954
  if (stat6.isSymbolicLink()) throw new Error("Staged workspace contains a symbolic link.");
76595
- if (stat6.isDirectory()) inspect(join61(root, name2), path);
76955
+ if (stat6.isDirectory()) inspect(join62(root, name2), path);
76596
76956
  }
76597
76957
  };
76598
- inspect(join61(directory, "data"));
76958
+ inspect(join62(directory, "data"));
76599
76959
  for (const entry of manifest.entries) {
76600
- const path = join61(directory, "data", entry.path);
76960
+ const path = join62(directory, "data", entry.path);
76601
76961
  const stat6 = lstatSync8(path);
76602
76962
  if (stat6.isSymbolicLink() || (entry.type === "file" ? !stat6.isFile() || stat6.size !== entry.size : !stat6.isDirectory())) {
76603
76963
  throw new Error("Staged workspace files do not match the backup manifest.");
@@ -76609,27 +76969,27 @@ function validateStaged(directory, expected) {
76609
76969
  if (entry.type === "file" && hashFile(path) !== entry.sha256) throw new Error("A workspace backup file failed its checksum.");
76610
76970
  chmodSync7(path, entry.mode);
76611
76971
  }
76612
- const data = join61(directory, "data");
76613
- 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.");
76614
- if (existsSync28(join61(data, "config.json"))) {
76615
- 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"));
76616
76976
  if (JSON.stringify(config2) !== JSON.stringify(portableWorkspaceConfig(config2))) throw new Error("Workspace backup contains non-portable connection settings.");
76617
76977
  }
76618
- if (existsSync28(join61(data, "webhooks.json"))) {
76619
- const value = privateJson(join61(data, "webhooks.json"));
76978
+ if (existsSync28(join62(data, "webhooks.json"))) {
76979
+ const value = privateJson(join62(data, "webhooks.json"));
76620
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.");
76621
76981
  }
76622
- const counts = databaseCounts(join61(data, "messages.db"));
76982
+ const counts = databaseCounts(join62(data, "messages.db"));
76623
76983
  if (counts.messages !== manifest.summary.messages || counts.threads !== manifest.summary.threads) throw new Error("The message database does not match the backup summary.");
76624
76984
  return manifest;
76625
76985
  }
76626
76986
  async function stageWorkspaceBackup(dataDir, archivePath, options) {
76627
76987
  const job = newJob(dataDir);
76628
- const plaintext = join61(job.directory, "archive.tar");
76988
+ const plaintext = join62(job.directory, "archive.tar");
76629
76989
  try {
76630
76990
  await decryptArchive(archivePath, plaintext, options.password);
76631
76991
  const expected = await inspectTar(plaintext);
76632
- const staged = join61(job.directory, "staged");
76992
+ const staged = join62(job.directory, "staged");
76633
76993
  folder2(staged);
76634
76994
  await So({ file: plaintext, cwd: staged, strict: true, preservePaths: false, umask: 63, noChmod: true });
76635
76995
  const manifest = validateStaged(staged, expected);
@@ -76641,7 +77001,7 @@ async function stageWorkspaceBackup(dataDir, archivePath, options) {
76641
77001
  }
76642
77002
  }
76643
77003
  rmSync8(plaintext);
76644
- 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 });
76645
77005
  return { id: job.id, summary: manifest.summary };
76646
77006
  } catch (error51) {
76647
77007
  rmSync8(job.directory, { recursive: true, force: true });
@@ -76650,24 +77010,24 @@ async function stageWorkspaceBackup(dataDir, archivePath, options) {
76650
77010
  }
76651
77011
  function readStagedWorkspaceBackup(dataDir, id) {
76652
77012
  const directory = jobPath(dataDir, id);
76653
- const ready = privateJson(join61(directory, "ready.json"));
77013
+ const ready = privateJson(join62(directory, "ready.json"));
76654
77014
  if (!record2(ready) || ready.id !== id) throw new Error("This workspace backup is not ready to restore.");
76655
- const manifest = validateManifest(privateJson(join61(directory, "staged", "manifest.json")));
77015
+ const manifest = validateManifest(privateJson(join62(directory, "staged", "manifest.json")));
76656
77016
  return { id, ...privateMetadata(manifest) };
76657
77017
  }
76658
77018
  function commitPendingWorkspaceRestore(dataDir, id) {
76659
77019
  const root = backupRoot(dataDir);
76660
- if (existsSync28(join61(root, "pending-restore.json"))) throw new Error("A workspace restore is already waiting for restart.");
76661
- 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.");
76662
77022
  const metadata = readStagedWorkspaceBackup(dataDir, id);
76663
- const manifest = validateStaged(join61(jobPath(dataDir, id), "staged"));
77023
+ const manifest = validateStaged(join62(jobPath(dataDir, id), "staged"));
76664
77024
  prepareRestore(dataDir, id, manifest);
76665
- writeJson(join61(root, "pending-restore.json"), { id });
77025
+ writeJson(join62(root, "pending-restore.json"), { id });
76666
77026
  return { id, restartRequired: true, summary: metadata.summary };
76667
77027
  }
76668
77028
  function readPendingWorkspaceRestoreMetadata(dataDir) {
76669
77029
  const root = backupRoot(dataDir);
76670
- const path = join61(root, "pending-restore.json");
77030
+ const path = join62(root, "pending-restore.json");
76671
77031
  if (!existsSync28(path)) return null;
76672
77032
  const pending = privateJson(path);
76673
77033
  if (!record2(pending) || typeof pending.id !== "string") throw new Error("Invalid pending workspace restore.");
@@ -76682,24 +77042,24 @@ function readJournal(path) {
76682
77042
  return raw;
76683
77043
  }
76684
77044
  function rollback(dataDir, journal) {
76685
- const stage = join61(jobPath(dataDir, journal.id), "apply", "data");
76686
- 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");
76687
77047
  for (const name2 of journal.incoming) {
76688
- const live = join61(dataDir, name2);
76689
- const staged = join61(stage, name2);
76690
- 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));
76691
77051
  if (originalMoved && !entryExists(staged) && entryExists(live)) renameSync9(live, staged);
76692
77052
  }
76693
77053
  for (const name2 of journal.existing) {
76694
- const original = join61(saved, name2);
77054
+ const original = join62(saved, name2);
76695
77055
  if (!entryExists(original)) continue;
76696
- const live = join61(dataDir, name2);
77056
+ const live = join62(dataDir, name2);
76697
77057
  if (entryExists(live)) throw new Error("Restore rollback found conflicting files. Both copies are preserved for recovery.");
76698
77058
  renameSync9(original, live);
76699
77059
  }
76700
77060
  const root = backupRoot(dataDir);
76701
- const previousReceipt = join61(root, `safety-${journal.id}`, "previous-restore.json");
76702
- 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");
76703
77063
  if (existsSync28(previousReceipt)) writeJson(last, privateJson(previousReceipt));
76704
77064
  else if (existsSync28(last)) {
76705
77065
  const value = privateJson(last);
@@ -76712,7 +77072,7 @@ function rebasePath(value, source, destination) {
76712
77072
  if (normalized !== original && !normalized.startsWith(`${original}/`)) return value;
76713
77073
  const suffix = normalized.slice(original.length).replace(/^\//, "");
76714
77074
  if (suffix && !validRelative(suffix)) return value;
76715
- return suffix ? join61(destination, suffix) : destination;
77075
+ return suffix ? join62(destination, suffix) : destination;
76716
77076
  }
76717
77077
  var PATH_KEYS = /* @__PURE__ */ new Set(["cwd", "pinnedCwd", "workspace", "configDir", "profileDirectory", "agentDir", "dataDir", "home", "cli", "path", "filePath", "localPath"]);
76718
77078
  function rebaseFields(value, source, destination) {
@@ -76751,16 +77111,16 @@ function rebaseMessage(message4, source, destination) {
76751
77111
  function prepareRestore(dataDir, id, manifest) {
76752
77112
  assertLocalAuthOutsideSnapshot(dataDir);
76753
77113
  const job = jobPath(dataDir, id);
76754
- const prepared = join61(job, "apply", "data");
76755
- 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 });
76756
77116
  folder2(prepared);
76757
- 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));
76758
77118
  for (const entry of manifest.entries) {
76759
- const destination = join61(prepared, entry.path);
76760
- 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);
76761
77121
  }
76762
77122
  const changeJson = (name2, change) => {
76763
- const path = join61(prepared, name2);
77123
+ const path = join62(prepared, name2);
76764
77124
  if (!existsSync28(path)) return;
76765
77125
  const value = privateJson(path);
76766
77126
  change(value);
@@ -76773,25 +77133,25 @@ function prepareRestore(dataDir, id, manifest) {
76773
77133
  if (!Array.isArray(value)) throw new Error("Invalid bot definitions in workspace backup.");
76774
77134
  for (const bot of value) if (record2(bot)) delete bot.voice;
76775
77135
  });
76776
- const oldConfig = join61(dataDir, "config.json");
76777
- const newConfig = join61(prepared, "config.json");
77136
+ const oldConfig = join62(dataDir, "config.json");
77137
+ const newConfig = join62(prepared, "config.json");
76778
77138
  if (existsSync28(oldConfig) || existsSync28(newConfig)) {
76779
77139
  writeJson(newConfig, restoredWorkspaceConfig(existsSync28(newConfig) ? privateJson(newConfig) : {}, existsSync28(oldConfig) ? privateJson(oldConfig) : {}));
76780
77140
  }
76781
77141
  const browserHomes = ["vm-home"];
76782
- const vmHomes = join61(dataDir, "vm-homes");
77142
+ const vmHomes = join62(dataDir, "vm-homes");
76783
77143
  if (entryExists(vmHomes)) {
76784
77144
  if (!lstatSync8(vmHomes).isDirectory()) throw new Error("Stop managed desktops and repair the VM home directory before restoring.");
76785
77145
  for (const name2 of readdirSync10(vmHomes)) if (validRelative(name2)) browserHomes.push(`vm-homes/${name2}`);
76786
77146
  }
76787
77147
  for (const home of browserHomes) {
76788
- const homePath = join61(dataDir, home);
77148
+ const homePath = join62(dataDir, home);
76789
77149
  if (!entryExists(homePath)) continue;
76790
77150
  if (!lstatSync8(homePath).isDirectory()) throw new Error("Stop managed desktops and repair the VM home directory before restoring.");
76791
- const source = join61(dataDir, home, ".browser-profiles");
77151
+ const source = join62(dataDir, home, ".browser-profiles");
76792
77152
  if (!entryExists(source)) continue;
76793
- folder2(join61(prepared, home));
76794
- 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 });
76795
77155
  }
76796
77156
  changeJson("routines.json", (value) => {
76797
77157
  if (!record2(value)) throw new Error("Invalid routine definitions in workspace backup.");
@@ -76809,7 +77169,7 @@ function prepareRestore(dataDir, id, manifest) {
76809
77169
  });
76810
77170
  changeJson("webhooks.json", (value) => {
76811
77171
  if (!record2(value)) throw new Error("Invalid webhook definitions in workspace backup.");
76812
- 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")) : {};
76813
77173
  const oldHooks = record2(destination) && Array.isArray(destination.webhooks) ? destination.webhooks : [];
76814
77174
  if (Array.isArray(value.webhooks)) {
76815
77175
  for (const webhook of value.webhooks) if (record2(webhook)) {
@@ -76826,15 +77186,15 @@ function prepareRestore(dataDir, id, manifest) {
76826
77186
  for (const call of value.calls) if (record2(call)) call.nextRunAt = null;
76827
77187
  }
76828
77188
  });
76829
- if (existsSync28(join61(prepared, "delegations.json"))) writeJson(join61(prepared, "delegations.json"), {});
76830
- 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"), []);
76831
77191
  for (const entry of manifest.entries) {
76832
77192
  if (/^messages-[^/]+\.json$/.test(entry.path)) changeJson(entry.path, (value) => {
76833
77193
  if (Array.isArray(value)) for (const message4 of value) rebaseMessage(message4, manifest.sourceDataDir, resolve15(dataDir));
76834
77194
  else if (record2(value) && Array.isArray(value.messages)) for (const message4 of value.messages) rebaseMessage(message4, manifest.sourceDataDir, resolve15(dataDir));
76835
77195
  });
76836
77196
  }
76837
- const dbPath = join61(prepared, "messages.db");
77197
+ const dbPath = join62(prepared, "messages.db");
76838
77198
  if (existsSync28(dbPath)) {
76839
77199
  const db2 = new DatabaseSync2(dbPath);
76840
77200
  try {
@@ -76866,7 +77226,7 @@ function prepareRestore(dataDir, id, manifest) {
76866
77226
  }
76867
77227
  function readLastWorkspaceRestore(dataDir) {
76868
77228
  const root = backupRoot(dataDir);
76869
- const path = join61(root, "last-restore.json");
77229
+ const path = join62(root, "last-restore.json");
76870
77230
  if (!existsSync28(path)) return null;
76871
77231
  const raw = privateJson(path);
76872
77232
  if (!record2(raw) || raw.restored !== true || typeof raw.id !== "string" || !ID.test(raw.id)) throw new Error("Invalid workspace restore receipt.");
@@ -76879,28 +77239,28 @@ function finishRestore(dataDir, id, consumePending = true) {
76879
77239
  if (draftAttachments) {
76880
77240
  try {
76881
77241
  const value = JSON.parse(draftAttachments);
76882
- const manifest = validateManifest(privateJson(join61(jobPath(dataDir, id), "staged", "manifest.json")));
77242
+ const manifest = validateManifest(privateJson(join62(jobPath(dataDir, id), "staged", "manifest.json")));
76883
77243
  rebaseFields(value, manifest.sourceDataDir, resolve15(dataDir));
76884
77244
  clientState["omb-draft-attachments"] = JSON.stringify(value);
76885
77245
  } catch {
76886
77246
  }
76887
77247
  }
76888
- const result = { restored: true, id, safetyCopyPath: join61(root, `safety-${id}`), summary, clientState };
76889
- writeJson(join61(root, "last-restore.json"), result);
76890
- 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 });
76891
77251
  return result;
76892
77252
  }
76893
77253
  function applyPendingWorkspaceRestore(dataDir) {
76894
77254
  const root = backupRoot(dataDir);
76895
- const journalFile2 = join61(root, "restore-journal.json");
76896
- const pendingFile = join61(root, "pending-restore.json");
77255
+ const journalFile2 = join62(root, "restore-journal.json");
77256
+ const pendingFile = join62(root, "pending-restore.json");
76897
77257
  if (existsSync28(journalFile2)) {
76898
77258
  const previous2 = readJournal(journalFile2);
76899
77259
  if (previous2.phase === "applying") {
76900
77260
  rollback(dataDir, previous2);
76901
77261
  writeJson(journalFile2, { ...previous2, phase: "rolled-back" });
76902
77262
  rmSync8(pendingFile, { force: true });
76903
- 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}`) };
76904
77264
  }
76905
77265
  if (previous2.phase === "committed" && existsSync28(pendingFile)) {
76906
77266
  const pending2 = privateJson(pendingFile);
@@ -76911,12 +77271,12 @@ function applyPendingWorkspaceRestore(dataDir) {
76911
77271
  }
76912
77272
  const pending = readPendingWorkspaceRestoreMetadata(dataDir);
76913
77273
  if (!pending) return { restored: false };
76914
- const staged = join61(jobPath(dataDir, pending.id), "staged");
77274
+ const staged = join62(jobPath(dataDir, pending.id), "staged");
76915
77275
  const manifest = validateStaged(staged);
76916
77276
  const prepared = prepareRestore(dataDir, pending.id, manifest);
76917
- const safetyCopyPath = join61(root, `safety-${pending.id}`);
77277
+ const safetyCopyPath = join62(root, `safety-${pending.id}`);
76918
77278
  if (existsSync28(safetyCopyPath)) throw new Error("A safety copy already exists for this restore. Original files have not been changed.");
76919
- folder2(join61(safetyCopyPath, "data"));
77279
+ folder2(join62(safetyCopyPath, "data"));
76920
77280
  const journal = {
76921
77281
  id: pending.id,
76922
77282
  phase: "applying",
@@ -76926,12 +77286,12 @@ function applyPendingWorkspaceRestore(dataDir) {
76926
77286
  for (const name2 of [...journal.existing, ...journal.incoming]) {
76927
77287
  if (!validRelative(name2) || name2.includes("/") || preserved(name2)) throw new Error("Unsafe workspace restore target.");
76928
77288
  }
76929
- writeJson(join61(safetyCopyPath, "manifest.json"), { id: pending.id, createdAt: (/* @__PURE__ */ new Date()).toISOString(), originalEntries: journal.existing });
76930
- 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")));
76931
77291
  writeJson(journalFile2, journal);
76932
77292
  try {
76933
- for (const name2 of journal.existing) renameSync9(join61(dataDir, name2), join61(safetyCopyPath, "data", name2));
76934
- 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));
76935
77295
  finishRestore(dataDir, pending.id, false);
76936
77296
  writeJson(journalFile2, { ...journal, phase: "committed" });
76937
77297
  } catch (error51) {
@@ -76946,11 +77306,11 @@ function removeWorkspaceBackupJob(dataDir, id) {
76946
77306
  const path = jobPath(dataDir, id);
76947
77307
  const root = backupRoot(dataDir);
76948
77308
  for (const name2 of ["pending-restore.json", "restore-journal.json", "last-restore.json"]) {
76949
- if (!existsSync28(join61(root, name2))) continue;
76950
- const value = privateJson(join61(root, name2));
77309
+ if (!existsSync28(join62(root, name2))) continue;
77310
+ const value = privateJson(join62(root, name2));
76951
77311
  if (record2(value) && value.id === id) throw new Error("This backup is still needed for restore or recovery and cannot be removed.");
76952
77312
  }
76953
- 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.");
76954
77314
  if (!entryExists(path)) return;
76955
77315
  if (!lstatSync8(path).isDirectory() || lstatSync8(path).isSymbolicLink()) throw new Error("Unsafe workspace backup cleanup target.");
76956
77316
  rmSync8(path, { recursive: true, force: true });
@@ -76980,7 +77340,7 @@ function createWorkspaceBackupRoutes(options) {
76980
77340
  function remove(id, artifact) {
76981
77341
  if (artifact.path) {
76982
77342
  try {
76983
- unlinkSync12(artifact.path);
77343
+ unlinkSync13(artifact.path);
76984
77344
  } catch {
76985
77345
  }
76986
77346
  }
@@ -76993,24 +77353,24 @@ function createWorkspaceBackupRoutes(options) {
76993
77353
  artifacts.delete(id);
76994
77354
  }
76995
77355
  function reclaimAbandoned() {
76996
- const root = join62(options.dataDir, ".backups");
77356
+ const root = join63(options.dataDir, ".backups");
76997
77357
  if (!existsSync29(root) || lstatSync9(root).isSymbolicLink()) return;
76998
77358
  for (const entry of readdirSync11(root, { withFileTypes: true })) {
76999
77359
  if (!entry.isDirectory() || !/^[a-f0-9-]{36}$/.test(entry.name)) continue;
77000
- 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;
77001
77361
  try {
77002
77362
  removeWorkspaceBackupJob(options.dataDir, entry.name);
77003
77363
  } catch {
77004
77364
  }
77005
77365
  }
77006
- const uploads = join62(root, "uploads");
77366
+ const uploads = join63(root, "uploads");
77007
77367
  if (!existsSync29(uploads) || lstatSync9(uploads).isSymbolicLink()) return;
77008
77368
  for (const entry of readdirSync11(uploads, { withFileTypes: true })) {
77009
77369
  if (!entry.isFile() || !/^[a-f0-9-]{36}\.ombbackup$/.test(entry.name)) continue;
77010
- const file2 = join62(uploads, entry.name);
77370
+ const file2 = join63(uploads, entry.name);
77011
77371
  if (statSync12(file2).mtimeMs + EXPIRES_MS <= Date.now()) {
77012
77372
  try {
77013
- unlinkSync12(file2);
77373
+ unlinkSync13(file2);
77014
77374
  } catch {
77015
77375
  }
77016
77376
  }
@@ -77107,14 +77467,14 @@ function createWorkspaceBackupRoutes(options) {
77107
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);
77108
77468
  replacePrevious(auth, ["upload", "staged"]);
77109
77469
  if (artifacts.size >= 4) throw failure3("There are already four backup files in progress. Restart or wait an hour before uploading another.", 409);
77110
- const root = join62(options.dataDir, ".backups");
77470
+ const root = join63(options.dataDir, ".backups");
77111
77471
  mkdirSync32(root, { recursive: true, mode: 448 });
77112
77472
  if (lstatSync9(root).isSymbolicLink()) throw failure3("Backup storage must not be a symbolic link.");
77113
- const directory = join62(root, "uploads");
77473
+ const directory = join63(root, "uploads");
77114
77474
  mkdirSync32(directory, { recursive: true, mode: 448 });
77115
77475
  if (lstatSync9(directory).isSymbolicLink()) throw failure3("Backup storage must not be a symbolic link.");
77116
77476
  const id = randomUUID26();
77117
- const file2 = join62(directory, `${id}.ombbackup`);
77477
+ const file2 = join63(directory, `${id}.ombbackup`);
77118
77478
  let bytes = 0;
77119
77479
  try {
77120
77480
  const limit = new Transform2({ transform(chunk, _encoding, callback) {
@@ -77128,7 +77488,7 @@ function createWorkspaceBackupRoutes(options) {
77128
77488
  json4(res, 200, { id });
77129
77489
  } catch (error51) {
77130
77490
  try {
77131
- unlinkSync12(file2);
77491
+ unlinkSync13(file2);
77132
77492
  } catch {
77133
77493
  }
77134
77494
  throw error51;
@@ -77147,7 +77507,7 @@ function createWorkspaceBackupRoutes(options) {
77147
77507
  throw error51;
77148
77508
  }
77149
77509
  try {
77150
- unlinkSync12(artifact.path);
77510
+ unlinkSync13(artifact.path);
77151
77511
  } catch {
77152
77512
  }
77153
77513
  artifacts.delete(body.id);
@@ -78316,7 +78676,7 @@ function resolveRequestAuth(req, options) {
78316
78676
 
78317
78677
  // server/sessions.ts
78318
78678
  import { createHash as createHash29, randomBytes as randomBytes8, randomUUID as randomUUID27, timingSafeEqual as timingSafeEqual4 } from "node:crypto";
78319
- 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";
78320
78680
  import { dirname as dirname18 } from "node:path";
78321
78681
  var SCOPES = ["admin", "client"];
78322
78682
  var PAIRING_CODE_ALPHABET = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ";
@@ -78462,7 +78822,7 @@ var SessionRegistry = class {
78462
78822
  try {
78463
78823
  this.revalidateEmailSessions();
78464
78824
  this.persist();
78465
- unlinkSync13(this.openMarker);
78825
+ unlinkSync14(this.openMarker);
78466
78826
  this.closed = true;
78467
78827
  } catch {
78468
78828
  throw new Error("Could not safely close session storage.");
@@ -78746,7 +79106,7 @@ var SessionRegistry = class {
78746
79106
 
78747
79107
  // server/brand.ts
78748
79108
  import { existsSync as existsSync31, readFileSync as readFileSync44 } from "node:fs";
78749
- import { join as join63 } from "node:path";
79109
+ import { join as join64 } from "node:path";
78750
79110
  var HEX_COLOUR = /^#[0-9a-fA-F]{6}$/;
78751
79111
  var MAX_LOGO_CHARS = 3e5;
78752
79112
  var MAX_FAVICON_CHARS = 12e4;
@@ -78766,7 +79126,7 @@ var brandSchema = external_exports.object({
78766
79126
  }).strict();
78767
79127
  var DEFAULT_BRAND = { name: "OpenMausBot" };
78768
79128
  function brandFile() {
78769
- return process.env.OMB_BRAND_FILE || join63(DATA_DIR, "brand.json");
79129
+ return process.env.OMB_BRAND_FILE || join64(DATA_DIR, "brand.json");
78770
79130
  }
78771
79131
  function loadBrand(options = {}) {
78772
79132
  const file2 = options.file ?? brandFile();
@@ -81286,7 +81646,7 @@ function releaseDataDirLeaseAtExit() {
81286
81646
  }
81287
81647
  process.once("exit", releaseDataDirLeaseAtExit);
81288
81648
  var workspaceRestore = { restored: false };
81289
- if (existsSync32(join64(DATA_DIR, ".backups"))) {
81649
+ if (existsSync32(join65(DATA_DIR, ".backups"))) {
81290
81650
  workspaceRestore = applyPendingWorkspaceRestore(DATA_DIR);
81291
81651
  if (!workspaceRestore.restored && !workspaceRestore.rolledBack) {
81292
81652
  workspaceRestore = readLastWorkspaceRestore(DATA_DIR) ?? workspaceRestore;
@@ -81299,7 +81659,7 @@ function signInAllowList() {
81299
81659
  return { admins: parseAllowList(current2?.admins?.join(",")), members: parseAllowList(current2?.members?.join(",")) };
81300
81660
  }
81301
81661
  var sessions = new SessionRegistry({
81302
- file: join64(DATA_DIR, "sessions.json"),
81662
+ file: join65(DATA_DIR, "sessions.json"),
81303
81663
  emailScopesSnapshot: () => {
81304
81664
  const membership = signInAllowList();
81305
81665
  return (email3) => allowedScopes(email3, membership);
@@ -81355,7 +81715,7 @@ function noteTurnTrigger(threadId, auth) {
81355
81715
  var providerAuthSessions = new ProviderAuthSessions();
81356
81716
  await registry2.load(instanceConfigs(cfg));
81357
81717
  var bundledSkills = loadBundledSkills();
81358
- var availableSkills = () => mergeSkills(bundledSkills, loadUserSkills(join64(DATA_DIR, "skills")));
81718
+ var availableSkills = () => mergeSkills(bundledSkills, loadUserSkills(join65(DATA_DIR, "skills")));
81359
81719
  var utilityParentPort = process.parentPort;
81360
81720
  function postDesktopPrivateMessage(message4) {
81361
81721
  if (!utilityParentPort) return false;
@@ -81381,7 +81741,7 @@ function applyDesktopMutationTokenMessage(raw) {
81381
81741
  return true;
81382
81742
  }
81383
81743
  var browserCleanup = new BrowserCleanupCoordinator({
81384
- file: join64(DATA_DIR, "browser-cleanups.json"),
81744
+ file: join65(DATA_DIR, "browser-cleanups.json"),
81385
81745
  send: (request3) => {
81386
81746
  const status = browserEngineStatus();
81387
81747
  const sessions2 = request3.type === "openmausbot:browser-bot-deleted" && request3.botId ? [browserSessionId(request3.botId, "")] : request3.partitionId ? [browserSessionId("", request3.partitionId)] : [];
@@ -81617,6 +81977,7 @@ function claimTurnResource(owner2, resource) {
81617
81977
  }
81618
81978
  function releaseTurnResources(owner2) {
81619
81979
  if (!owner2) return;
81980
+ if (autoVmClaims.get(owner2.threadId)?.owner.generation === owner2.generation) autoVmClaims.delete(owner2.threadId);
81620
81981
  if (settlingResourceOwners.get(owner2.threadId) === owner2.generation) settlingResourceOwners.delete(owner2.threadId);
81621
81982
  turnResources.release(owner2);
81622
81983
  if (turnResourceOwners.get(owner2.threadId)?.generation === owner2.generation) turnResourceOwners.delete(owner2.threadId);
@@ -81627,9 +81988,33 @@ function releaseTurnResources(owner2) {
81627
81988
  teamComputerTurns.delete(owner2.threadId);
81628
81989
  }
81629
81990
  }
81630
- function bindTurnComputer(owner2, resource, exclusive = false) {
81631
- if (exclusive && !claimTurnResource(owner2, resource)) {
81632
- throw Object.assign(new Error("another thread is using this computer \u2014 wait for it to finish"), { status: 409, code: "computer_busy" });
81991
+ async function bindTurnComputer(owner2, resource, exclusive = false) {
81992
+ const active = () => activeInternalGenerationByThread.get(owner2.threadId) === owner2.generation && turnResourceOwners.get(owner2.threadId)?.generation === owner2.generation;
81993
+ let waitingMessage;
81994
+ const deadline = Date.now() + GROUP_GOAL_WAIT_MAX_MS;
81995
+ try {
81996
+ while (true) {
81997
+ if (!active()) throw new DirectTurnSetupCancelled("Computer wait cancelled");
81998
+ if (!exclusive || claimTurnResource(owner2, resource)) break;
81999
+ if (!waitingMessage) {
82000
+ const blocker = turnResources.blocker(resource, owner2);
82001
+ const holderBot = blocker && store.botByThread(blocker.threadId);
82002
+ const holderTask = holderBot && blocker && store.taskByThread(holderBot.id, blocker.threadId);
82003
+ const holder = holderBot ? `${holderBot.name}${holderTask?.title ? ` / ${holderTask.title}` : ""}` : blocker && store.groupByThread(blocker.threadId)?.name;
82004
+ waitingMessage = store.appendMessage(owner2.threadId, {
82005
+ role: "bot",
82006
+ kind: "activity",
82007
+ tool: { name: `Waiting for computer${holder ? ` \u2014 ${holder} is using it` : ""}; will continue automatically` },
82008
+ ...holderBot && holderTask ? { threadRef: { botId: holderBot.id, threadId: holderTask.threadId, title: holderTask.title } } : {}
82009
+ });
82010
+ }
82011
+ if (Date.now() >= deadline) throw new Error("Computer is still busy. Stop the turn using it, then retry.");
82012
+ await new Promise((resolve16) => setTimeout(resolve16, 100));
82013
+ }
82014
+ } finally {
82015
+ if (waitingMessage) store.patchMessage(owner2.threadId, waitingMessage.id, {
82016
+ tool: { name: active() && turnResources.owns(resource, owner2) ? "Computer available \u2014 continuing" : "Computer wait ended", ok: true }
82017
+ });
81633
82018
  }
81634
82019
  turnResourceOwners.set(owner2.threadId, owner2);
81635
82020
  turnComputerResources.set(owner2.threadId, { owner: owner2, resource });
@@ -81650,6 +82035,10 @@ function botAtThreadCapacity(botId) {
81650
82035
  function hasDirectDispatch(botId) {
81651
82036
  return [...directTurnDispatchClaims.values()].some((claim) => claim.botId === botId);
81652
82037
  }
82038
+ function unattendedDispatchState(botId) {
82039
+ const bot = store.bot(botId);
82040
+ return !bot ? "missing" : botAtThreadCapacity(botId) || activeGroupTurnForBot(botId) ? "busy" : "ready";
82041
+ }
81653
82042
  function requestedTaskBot(botId, rawThreadId) {
81654
82043
  const profile = store.bot(botId);
81655
82044
  if (!profile) throw Object.assign(new Error("no such bot"), { status: 404 });
@@ -82109,7 +82498,7 @@ function checkedMemberIds(value) {
82109
82498
  }
82110
82499
  var bootSelection = { instanceId: "", model: "" };
82111
82500
  var store = new Store(() => bootSelection);
82112
- 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);
82113
82502
  var followupsReady = false;
82114
82503
  var sendSequencer = new SendSequencer();
82115
82504
  bootSelection = await defaultSelection();
@@ -82130,7 +82519,7 @@ try {
82130
82519
  }
82131
82520
  if (browserCleanupReferencesReconciled) browserCleanup.startPending();
82132
82521
  var activeCoordinationForThread = (_threadId) => false;
82133
- 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);
82134
82523
  var wireBot = (bot) => {
82135
82524
  const { resumeCursors: _resumeCursors, tasks, approvalGrant, lastProfileRequestId: _lastProfileRequestId, lastTeamSetupReceipt: _lastTeamSetupReceipt, ...rest } = bot;
82136
82525
  const visible = approvalGrant && !approvalGrant.threadOnly ? { ...rest, approvalMode: "ask", autoApprove: false } : rest;
@@ -82273,6 +82662,30 @@ function fullAccessForSource(botId, threadId) {
82273
82662
  function peerReviewRequired(bot, threadId) {
82274
82663
  return Boolean(bot.approvePeerComms && !fullAccessForSource(bot.id, threadId));
82275
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
+ }
82276
82689
  function handleDesktopTrustedApprovalMessage(raw) {
82277
82690
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) return false;
82278
82691
  const message4 = raw;
@@ -82770,7 +83183,7 @@ function outstandingAssignmentsPrompt(threadId) {
82770
83183
  }));
82771
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.`;
82772
83185
  }
82773
- var roomHandoffs = new RoomHandoffs(join64(DATA_DIR, "room-handoffs.json"), {
83186
+ var roomHandoffs = new RoomHandoffs(join65(DATA_DIR, "room-handoffs.json"), {
82774
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),
82775
83188
  // A direct follow-up is owed to one conversation, so it waits for that
82776
83189
  // conversation, not for the whole bot. Bot-level busy aggregates every
@@ -82920,7 +83333,7 @@ var roomHandoffs = new RoomHandoffs(join64(DATA_DIR, "room-handoffs.json"), {
82920
83333
  await tracked;
82921
83334
  return { ok: result.outcome === "settled", text: result.stopReason || result.replyText || result.outcome || "The addressed agent could not run" };
82922
83335
  }
82923
- });
83336
+ }, Date.now, roomHandoffLimits(cfg));
82924
83337
  activeCoordinationForThread = (threadId) => roomHandoffs.activeDirect(threadId);
82925
83338
  function publicGroupState(group) {
82926
83339
  return { ...group, working: groupIsWorking(group) || [...roomHandoffs.nodes.values()].some((n) => n.groupId === group.id && !["completed", "failed", "cancelled"].includes(n.status)) };
@@ -83426,10 +83839,10 @@ var watchdog = new TurnWatchdog({
83426
83839
  groupSpeakers.delete(turn.threadId);
83427
83840
  store.patchGroup(group.id, { busyBotId: null, unread: true });
83428
83841
  }
83842
+ releaseTurnResources(stalledResourceOwner);
83429
83843
  const currentBot = store.bot(turn.botId);
83430
83844
  if (currentBot?.busy) {
83431
83845
  stopScreenPoller(currentBot.id, turn.threadId);
83432
- releaseTurnResources(stalledResourceOwner);
83433
83846
  if (activeVpsThreads.get(currentBot.id) === turn.threadId) activeVpsThreads.delete(currentBot.id);
83434
83847
  if (store.taskByThread(currentBot.id, turn.threadId)) store.setTaskActivity(currentBot.id, turn.threadId, "idle");
83435
83848
  else store.setActivity(currentBot.id, "idle");
@@ -83530,6 +83943,8 @@ var localVmLeases = new LocalVmLeasePool(30 * 6e4);
83530
83943
  var localVmLifecycleBusy = /* @__PURE__ */ new Set();
83531
83944
  var localVmThreadTargets = /* @__PURE__ */ new Map();
83532
83945
  var localVmActiveThreads = /* @__PURE__ */ new Map();
83946
+ var autoVmClaims = /* @__PURE__ */ new Map();
83947
+ var LAZY_VM_CLAIM_GRACE_MS = 5e3;
83533
83948
  var localVmSeen = /* @__PURE__ */ new Set();
83534
83949
  function noteLocalVmSeen(target, status) {
83535
83950
  if (status && autoLocalVmAttachable(status)) localVmSeen.add(target.key);
@@ -83611,11 +84026,11 @@ async function attachTeamBox(computer, botId, owner2, canMount, remoteAgent) {
83611
84026
  const ownerId = teamComputerOwner(computer.id);
83612
84027
  if (boxLifecycleBusyBots.has(ownerId)) throw new Error("The team computer is being changed; wait for it to finish");
83613
84028
  if (computerControl.snapshot(ownerId).held) throw new Error("Release human control of the team computer before starting another turn");
83614
- bindTurnComputer(owner2, `computer:box-bot:${ownerId}`, true);
84029
+ await bindTurnComputer(owner2, `computer:box-bot:${ownerId}`, true);
83615
84030
  teamComputerTurns.set(owner2.threadId, { owner: owner2, computerId: computer.id, botId, remoteAgent });
83616
84031
  let machine = await findBox(cfg, ownerId);
83617
84032
  if (!machine) throw new Error("The team's Box computer is missing; explicitly create or retry it from the Team map");
83618
- bindTurnComputer(owner2, `computer:box:${machine.id}`, true);
84033
+ await bindTurnComputer(owner2, `computer:box:${machine.id}`, true);
83619
84034
  const action = boxTurnLifecycleAction({ explicitCloud: true, canMount: true, state: typeof machine.state === "string" ? machine.state : null });
83620
84035
  if (action === "wake") machine = await readyBox(cfg, ownerId);
83621
84036
  if (!machine || boxTurnLifecycleAction({ explicitCloud: true, canMount: true, state: typeof machine.state === "string" ? machine.state : null }) !== "attach") {
@@ -83881,6 +84296,7 @@ function localVmIdleFor(target) {
83881
84296
  return idle;
83882
84297
  }
83883
84298
  function releaseLocalVmThread(threadId) {
84299
+ autoVmClaims.delete(threadId);
83884
84300
  const target = localVmThreadTargets.get(threadId);
83885
84301
  if (!target) return;
83886
84302
  localVmLeaseFor(target).release(threadId);
@@ -84890,10 +85306,15 @@ async function startTurn(botId, text8, opts) {
84890
85306
  }
84891
85307
  const skipTranscript = /* @__PURE__ */ new Set([userMessage.id, ...opts?.excludeMessageIds ?? []]);
84892
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
+ }
84893
85314
  const messagesById = new Map(store.messagesFor(threadId).map((message4) => [message4.id, message4]));
84894
85315
  const transcript = activeMessages.filter((m3) => (m3.kind === "text" && m3.text || m3.roomRequest?.phase === "result") && !skipTranscript.has(m3.id)).slice(-40).map((m3) => ({
84895
85316
  role: m3.role === "user" ? "user" : "assistant",
84896
- 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")
84897
85318
  }));
84898
85319
  const rewound = Boolean(task2.rewound);
84899
85320
  const externalContextMarker = isExternalContextMarker(task2.lastInstanceId) ? task2.lastInstanceId : void 0;
@@ -85000,45 +85421,99 @@ async function startTurn(botId, text8, opts) {
85000
85421
  let browserCapture = null;
85001
85422
  let computerKind = null;
85002
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
+ };
85003
85465
  const attachLocalVm = async (strict) => {
85004
85466
  if (!mountsComputerMcp || instance.driverKind === "boxAgent") {
85005
85467
  if (!strict) return false;
85006
85468
  throw new Error("this model engine cannot use the Local VM \u2014 choose Claude or an ACP engine, or select another computer destination");
85007
85469
  }
85008
85470
  const localVmTarget = localVmTargetForBot(bot.id);
85471
+ let lazyReadyVm = null;
85009
85472
  if (!strict) {
85010
85473
  if (!localVmSeen.has(localVmTarget.key) && !opts?.automationSource) return false;
85011
85474
  const seen = await containerComputerStatus(void 0, void 0, localVmTarget).catch(() => null);
85012
85475
  if (!seen || !autoLocalVmAttachable(seen)) return false;
85013
85476
  if (localVmImageBusy || localVmModeChangeBusy || localVmLifecycleBusy.has(localVmTarget.key)) return false;
85477
+ if (seen.ready && seen.runtime) lazyReadyVm = { runtime: seen.runtime };
85014
85478
  }
85015
85479
  try {
85016
- bindTurnComputer(resourceOwner, `computer:vm:${localVmTarget.key}`, true);
85017
- if (localVmImageBusy || localVmModeChangeBusy || localVmLifecycleBusy.has(localVmTarget.key)) {
85018
- throw new Error("this Local VM is being started, stopped, or replaced \u2014 wait for setup to finish");
85019
- }
85020
- if (!localVmLeaseFor(localVmTarget).claim(threadId, bot.id, localVmOwnerBusy)) {
85021
- throw new Error("this Local VM is already being used by another turn \u2014 wait for that turn to finish");
85022
- }
85023
- localVmThreadTargets.set(threadId, localVmTarget);
85024
- localVmActiveThreads.set(localVmTarget.key, threadId);
85025
- localVmIdleFor(localVmTarget).touch();
85026
- const localVm = await readyLocalVmForTurn(bot.id, localVmTarget);
85027
- if (!localVm.ready || !localVm.runtime) {
85028
- 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;
85029
85504
  }
85505
+ const claimed = await claimAutoLocalVm(threadId);
85030
85506
  integrations.localComputer = containerComputerMcp(
85031
- localVm.runtime,
85507
+ claimed.runtime,
85032
85508
  controlIntegration(bot.id, threadId, dispatchClaimId),
85033
- localVmTarget
85509
+ claimed.target
85034
85510
  );
85035
- previewCapture = () => {
85036
- const owner2 = localVmLeaseFor(localVmTarget).current(localVmOwnerBusy);
85037
- if (owner2 && owner2.threadId !== threadId) {
85038
- 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);
85039
85515
  }
85040
- return containerComputerFrame(void 0, void 0, localVmTarget);
85041
- };
85516
+ });
85042
85517
  return true;
85043
85518
  } catch (error51) {
85044
85519
  if (strict) throw error51;
@@ -85054,11 +85529,11 @@ async function startTurn(botId, text8, opts) {
85054
85529
  hostPlatform: process.platform,
85055
85530
  providerSupportsLocal: mountsLocalComputer
85056
85531
  })) {
85057
- 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");
85058
85533
  }
85059
85534
  const cua = readCuaConnection();
85060
85535
  if (!cua) throw new Error("CUA Driver is not ready for this computer \u2014 check permissions and restart OpenMausBot");
85061
- bindTurnComputer(resourceOwner, "computer:host");
85536
+ await bindTurnComputer(resourceOwner, "computer:host");
85062
85537
  integrations.localComputer = gatedLocalComputer(cua, controlIntegration(bot.id, threadId, dispatchClaimId));
85063
85538
  computerKind = "local";
85064
85539
  }
@@ -85067,7 +85542,7 @@ async function startTurn(botId, text8, opts) {
85067
85542
  if (unsupported && wants2 === "cloud") throw new Error(unsupported);
85068
85543
  if (unsupported && wants2 === void 0) autoVpsProblem = unsupported;
85069
85544
  if (!unsupported) {
85070
- bindTurnComputer(resourceOwner, `computer:vps:${vpsSshAlias(cfg)}:${bot.id}`, true);
85545
+ await bindTurnComputer(resourceOwner, `computer:vps:${vpsSshAlias(cfg)}:${bot.id}`, true);
85071
85546
  activeVpsThreads.set(bot.id, threadId);
85072
85547
  let remote;
85073
85548
  remote = vpsStartsForTurn({ wants: wants2, autoStartVps: bot.autoStartVps, automationSource: opts?.automationSource }) ? await vpsComputerAction("provision", cfg, bot.id) : await inspectVpsForAuto(cfg, bot.id);
@@ -85097,7 +85572,7 @@ async function startTurn(botId, text8, opts) {
85097
85572
  computerKind = "box";
85098
85573
  }
85099
85574
  if (!teamComputer && mountsCloudComputer && (wants2 === "cloud" || wants2 === void 0) && cloudBackend === "box" && boxConfigured(cfg)) {
85100
- if (wants2 === "cloud") bindTurnComputer(resourceOwner, `computer:box-bot:${bot.id}`, true);
85575
+ if (wants2 === "cloud") await bindTurnComputer(resourceOwner, `computer:box-bot:${bot.id}`, true);
85101
85576
  if (!mountsCloudComputer && wants2 === "cloud") {
85102
85577
  throw new Error("this model engine cannot use computer tools \u2014 choose Claude, an ACP engine, or the Computer engine");
85103
85578
  }
@@ -85133,7 +85608,7 @@ async function startTurn(botId, text8, opts) {
85133
85608
  });
85134
85609
  }
85135
85610
  if (b3 && lifecycle === "attach") {
85136
- bindTurnComputer(resourceOwner, `computer:box:${b3.id}`, instance.driverKind === "boxAgent");
85611
+ await bindTurnComputer(resourceOwner, `computer:box:${b3.id}`, instance.driverKind === "boxAgent");
85137
85612
  previewCapture = () => screenshotBox(cfg, bot.id, b3.id);
85138
85613
  if (mountsCloudComputer) {
85139
85614
  integrations.computer = {
@@ -85161,7 +85636,7 @@ async function startTurn(botId, text8, opts) {
85161
85636
  })) {
85162
85637
  const cua = readCuaConnection();
85163
85638
  if (cua) {
85164
- bindTurnComputer(resourceOwner, "computer:host");
85639
+ await bindTurnComputer(resourceOwner, "computer:host");
85165
85640
  integrations.localComputer = gatedLocalComputer(cua, controlIntegration(bot.id, threadId, dispatchClaimId));
85166
85641
  computerKind = "local";
85167
85642
  }
@@ -85415,6 +85890,7 @@ function routineRunCard(run3) {
85415
85890
  status: run3.status
85416
85891
  };
85417
85892
  if (run3.goalStatus) card.goalStatus = run3.goalStatus;
85893
+ if (run3.deferredAt != null && run3.status === "queued") card.deferredAt = run3.deferredAt;
85418
85894
  if (run3.threadId) card.executionThreadId = run3.threadId;
85419
85895
  if (summary) card.summary = summary;
85420
85896
  if (error51) card.error = error51;
@@ -85422,7 +85898,7 @@ function routineRunCard(run3) {
85422
85898
  }
85423
85899
  function routineRunFallbackText(card) {
85424
85900
  const goalState = card.goalStatus === "needs-input" ? "needs your input" : card.goalStatus === "blocked" ? "was blocked" : card.goalStatus === "limit-reached" ? "reached its limit" : card.goalStatus === "stopped" ? "was stopped" : card.goalStatus === "failed" ? "failed" : void 0;
85425
- const state = goalState ?? (card.status === "waiting" ? "needs your attention" : card.status === "completed" ? "completed" : card.status === "failed" ? "failed" : card.status === "cancelled" ? "was cancelled" : card.status === "missed" ? "was missed" : card.status);
85901
+ const state = goalState ?? (card.status === "waiting" ? "needs your attention" : card.status === "completed" ? "completed" : card.status === "failed" ? "failed" : card.status === "cancelled" ? "was cancelled" : card.status === "missed" ? "was missed" : card.status === "queued" && card.deferredAt != null ? "deferred: target busy" : card.status);
85426
85902
  return `Routine \u201C${card.routineName}\u201D ${state}`;
85427
85903
  }
85428
85904
  function syncRoutineRunToSource(run3) {
@@ -85518,10 +85994,7 @@ _loadPending();
85518
85994
  routines = new RoutineManager({
85519
85995
  emit: broadcast,
85520
85996
  hasPendingDelegations: (threadId) => pendingThreads().includes(threadId) || [...delegationWatch.values()].some((watch) => watch.sourceThreadId === threadId) || pendingDelegationWakes.has(threadId),
85521
- botState: (botId) => {
85522
- const bot = store.bot(botId);
85523
- return !bot ? "missing" : bot.busy ? "busy" : "ready";
85524
- },
85997
+ botState: unattendedDispatchState,
85525
85998
  goalState: (groupId, coordinatorBotId) => {
85526
85999
  const group = store.group(groupId);
85527
86000
  const coordinator = store.bot(coordinatorBotId);
@@ -85585,6 +86058,14 @@ routines = new RoutineManager({
85585
86058
  const detail = run3.error ? `${run3.routineName}: ${run3.error}` : run3.routineName;
85586
86059
  const notificationBot = routineSourceOwner(run3)?.bot ?? bot;
85587
86060
  notify(buildNotification("routine-failed", notificationBot, routineSourceThread(run3) ?? run3.threadId ?? bot.threadId, detail));
86061
+ },
86062
+ onRunDeferred: (run3) => {
86063
+ const bot = store.bot(run3.botId);
86064
+ if (!bot) return;
86065
+ const minutes = run3.deferredAt != null && run3.deferredNoticeAt != null ? Math.max(1, Math.round((run3.deferredNoticeAt - run3.deferredAt) / 6e4)) : null;
86066
+ const detail = `${redactSecretsInText(run3.routineName)}: target busy${minutes != null ? ` for ${minutes} minutes` : ""}`;
86067
+ const notificationBot = routineSourceOwner(run3)?.bot ?? bot;
86068
+ notify(buildNotification("routine-deferred", notificationBot, routineSourceThread(run3) ?? bot.threadId, detail));
85588
86069
  }
85589
86070
  });
85590
86071
  var recoveredRoutineGoalRuns = new Map(
@@ -85812,12 +86293,6 @@ async function deleteBotWithLifecycle(botId, revalidate = () => {
85812
86293
  const acknowledged = await browserCleanup.ensure(committedCleanup);
85813
86294
  requireBrowserCleanupAcknowledged(acknowledged, `Browser data for ${bot.name}`);
85814
86295
  }
85815
- for (const dir of [EVENTS_DIR, NATIVE_DIR]) {
85816
- try {
85817
- unlinkSync14(join64(dir, `${bot.threadId}.ndjson`));
85818
- } catch {
85819
- }
85820
- }
85821
86296
  return deletionResponse(200, { ok: true });
85822
86297
  } finally {
85823
86298
  if (claimedLocalVmTarget) localVmLifecycleBusy.delete(claimedLocalVmTarget.key);
@@ -86106,10 +86581,7 @@ function resolveAndSendProfile(res, args) {
86106
86581
  }
86107
86582
  var webhooks = new WebhookManager({
86108
86583
  emit: broadcast,
86109
- botState: (botId) => {
86110
- const bot = store.bot(botId);
86111
- return !bot ? "missing" : bot.busy ? "busy" : "ready";
86112
- },
86584
+ botState: unattendedDispatchState,
86113
86585
  enqueue: (input) => routines.enqueueWebhook(input),
86114
86586
  findRun: (webhookId, deliveryId2) => routines.webhookRunReceipt(webhookId, deliveryId2),
86115
86587
  cancelQueued: (webhookId, message4) => routines.cancelQueuedWebhook(webhookId, message4),
@@ -86194,7 +86666,7 @@ async function runGroupMemberTurn(groupId, threadId, botId, hop, spoken = /* @__
86194
86666
  }
86195
86667
  revokeInternalCapabilitiesForThread(threadId);
86196
86668
  spoken.add(botId);
86197
- const preparedApprovalMode = approvalModeForTurn(bot, Boolean(orchestration?.roomHandoffId));
86669
+ const preparedApprovalMode = roomTurnApprovalMode(bot, orchestration);
86198
86670
  const preparedSelection = { ...bot.modelSelection };
86199
86671
  const preparedComposio = bot.composio;
86200
86672
  const instance = turnInstance(bot);
@@ -86305,7 +86777,7 @@ async function runGroupMemberTurn(groupId, threadId, botId, hop, spoken = /* @__
86305
86777
  const readyGroup = store.group(group.id);
86306
86778
  const stillOwnsThread = readyGroup?.dm ? readyGroup.threadId === threadId : Boolean(readyGroup && store.groupTaskByThread(readyGroup.id, threadId));
86307
86779
  if (!readyGroup || !stillOwnsThread || !readyGroup.memberIds.includes(readyBot.id)) return false;
86308
- 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;
86309
86781
  if (setupChanged) {
86310
86782
  if (setupRetry === 0) {
86311
86783
  return runGroupMemberTurn(
@@ -86424,7 +86896,7 @@ async function runGroupMemberTurn(groupId, threadId, botId, hop, spoken = /* @__
86424
86896
  throw new Error("this model engine cannot use the Local VM");
86425
86897
  }
86426
86898
  const target = { ...localVmTargetForBot(readyBot.id) };
86427
- bindTurnComputer(resourceOwner, `computer:vm:${target.key}`, true);
86899
+ await bindTurnComputer(resourceOwner, `computer:vm:${target.key}`, true);
86428
86900
  if (localVmImageBusy || localVmModeChangeBusy || localVmLifecycleBusy.has(target.key)) {
86429
86901
  throw new Error("this Local VM is being started, stopped, or replaced");
86430
86902
  }
@@ -86609,7 +87081,7 @@ ${e.text}`;
86609
87081
  text: text8,
86610
87082
  refreshSystemPrompt: true,
86611
87083
  images: turnImages,
86612
- approvalMode: approvalModeForTurn(readyBot, Boolean(orchestration?.roomHandoffId)),
87084
+ approvalMode: roomTurnApprovalMode(readyBot, orchestration),
86613
87085
  system: roomSystem.text,
86614
87086
  systemStable: roomSystem.stable,
86615
87087
  systemVolatile: roomSystem.volatile,
@@ -86775,6 +87247,7 @@ ${e.text}`;
86775
87247
  return true;
86776
87248
  } catch (error51) {
86777
87249
  if (providerDispatched) throw error51;
87250
+ if (error51 instanceof DirectTurnSetupCancelled) return false;
86778
87251
  const isSpendCap = typeof error51 === "object" && error51 !== null && error51.code === "spend_cap";
86779
87252
  const isVariantError = typeof error51 === "object" && error51 !== null && error51.code === "unsupported_model_variant";
86780
87253
  if (!roomSpeaker && !isSpendCap && !isVariantError) throw error51;
@@ -88255,7 +88728,7 @@ var claudeUpdatesInFlight = /* @__PURE__ */ new Set();
88255
88728
  function serveStatic(res, path) {
88256
88729
  if (!STATIC_DIR) return false;
88257
88730
  const safe = path === "/" ? "/index.html" : path.replace(/\.\./g, "");
88258
- const file2 = join64(STATIC_DIR, safe);
88731
+ const file2 = join65(STATIC_DIR, safe);
88259
88732
  try {
88260
88733
  const data = readFileSync45(file2);
88261
88734
  res.writeHead(200, { "content-type": MIME[extname5(file2)] ?? "application/octet-stream" });
@@ -88263,7 +88736,7 @@ function serveStatic(res, path) {
88263
88736
  return true;
88264
88737
  } catch {
88265
88738
  try {
88266
- const data = readFileSync45(join64(STATIC_DIR, "index.html"));
88739
+ const data = readFileSync45(join65(STATIC_DIR, "index.html"));
88267
88740
  res.writeHead(200, { "content-type": "text/html" });
88268
88741
  res.end(data);
88269
88742
  return true;
@@ -89280,15 +89753,19 @@ var handleRequest = async (req, res) => {
89280
89753
  if (method === "POST" && path === "/api/internal/ask-bot") {
89281
89754
  const body = await readInternalBody();
89282
89755
  const fromBotId = internalSender.id;
89283
- const toBotId = String(body.toBotId ?? "");
89756
+ const toBotRef = String(body.toBotId ?? "");
89284
89757
  const message4 = String(body.message ?? "").trim();
89285
89758
  if (body.depth !== void 0 && (!Number.isInteger(body.depth) || body.depth < 0 || body.depth !== internalCapability.depth)) {
89286
89759
  return json3(res, 403, { error: "the recursion depth does not match this turn" });
89287
89760
  }
89288
89761
  const depth = internalCapability.depth;
89289
- if (!toBotId || !message4) return json3(res, 400, { error: "toBotId and message required" });
89290
- 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" });
89291
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;
89292
89769
  const target = store.bot(toBotId);
89293
89770
  if (!target) return json3(res, 404, { error: "no such bot" });
89294
89771
  const from = internalSender;
@@ -89443,15 +89920,18 @@ var handleRequest = async (req, res) => {
89443
89920
  }
89444
89921
  if (method === "POST" && path === "/api/internal/delegate-bot") {
89445
89922
  const body = await readInternalBody();
89446
- const toBotId = String(body.toBotId ?? "");
89923
+ const toBotRef = String(body.toBotId ?? "");
89447
89924
  const message4 = String(body.message ?? "").trim();
89448
89925
  const reason = typeof body.reason === "string" && body.reason.trim() ? body.reason.trim() : void 0;
89449
89926
  if (body.depth !== void 0 && (!Number.isInteger(body.depth) || body.depth < 0 || body.depth !== internalCapability.depth)) {
89450
89927
  return json3(res, 403, { error: "the recursion depth does not match this turn" });
89451
89928
  }
89452
89929
  const depth = internalCapability.depth;
89453
- 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" });
89454
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;
89455
89935
  const target = store.bot(toBotId);
89456
89936
  if (!target) return json3(res, 404, { error: "no such bot" });
89457
89937
  if (!canAccessTeam(from, target.section) || target.hidden) {
@@ -89524,13 +90004,21 @@ var handleRequest = async (req, res) => {
89524
90004
  const groupId = parsed.data.groupId ?? source?.id;
89525
90005
  const destination = groupId ? store.group(groupId) : void 0;
89526
90006
  if (groupId && !destination) return json3(res, 404, { error: "No such room; use list_room_targets." });
89527
- 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) => ({
89528
90015
  groupId: destination?.id,
89529
90016
  threadId: destination ? destination.id === source?.id ? address.threadId : destination.threadId : store.bot(botId)?.threadId ?? "",
89530
90017
  botId
89531
90018
  }));
89532
90019
  for (const target of targets) {
89533
- 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);
89534
90022
  if (eligibility) return json3(res, 403, { error: eligibility });
89535
90023
  }
89536
90024
  let approvalGranted = false;
@@ -89557,6 +90045,9 @@ var handleRequest = async (req, res) => {
89557
90045
  if (!resolved) throw new Error("The recipient no longer exists");
89558
90046
  target.threadId = resolved.task.threadId;
89559
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
+ }
89560
90051
  }
89561
90052
  const { node: node2, duplicate } = roomHandoffs.enqueue(
89562
90053
  address,
@@ -89755,6 +90246,7 @@ ${message4}`;
89755
90246
  }
89756
90247
  const task2 = store.createTask(target.id, title, false, projectId, { botId: from.id, name: from.name, at: Date.now() });
89757
90248
  if (!task2) return json3(res, 500, { error: "couldn't create that thread" });
90249
+ if (delegatedFullAccess(from, fromThreadId, target)) grantDelegatedFullAccess(from, target, task2.threadId);
89758
90250
  const queued = queueDelegation(
89759
90251
  commsBus,
89760
90252
  from,
@@ -89994,6 +90486,29 @@ ${message4}`;
89994
90486
  if (!bot) return json3(res, 404, { error: "no such bot" });
89995
90487
  if (method === "GET") {
89996
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
+ }
89997
90512
  const computer = turnComputerResources.get(internalCapability.threadId);
89998
90513
  if (!snapshot2.held && computer && computer.owner.generation === internalCapability.generation && !claimTurnResource(computer.owner, computer.resource)) {
89999
90514
  return json3(res, 200, {
@@ -91025,14 +91540,6 @@ data: ${JSON.stringify({ kind: "ping" })}
91025
91540
  routines.disableForGroup(group.id);
91026
91541
  store.deleteGroup(group.id);
91027
91542
  rejectDeletedThreadSkillStages(stagedSkillCleanups);
91028
- for (const threadId of threadIds) {
91029
- for (const dir of [EVENTS_DIR, NATIVE_DIR]) {
91030
- try {
91031
- unlinkSync14(join64(dir, `${threadId}.ndjson`));
91032
- } catch {
91033
- }
91034
- }
91035
- }
91036
91543
  return json3(res, 200, { ok: true });
91037
91544
  }
91038
91545
  m3 = path.match(/^\/api\/groups\/([\w-]+)\/messages$/);
@@ -91127,6 +91634,74 @@ data: ${JSON.stringify({ kind: "ping" })}
91127
91634
  }
91128
91635
  return json3(res, 200, { ok: true });
91129
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
+ }
91130
91705
  m3 = path.match(/^\/api\/groups\/([\w-]+)\/interrupt$/);
91131
91706
  if (m3 && method === "POST") {
91132
91707
  const group = store.group(m3[1]);
@@ -92161,18 +92736,19 @@ data: ${JSON.stringify({ kind: "ping" })}
92161
92736
  }
92162
92737
  if (currentAtStart.busy) {
92163
92738
  const instance = runningTurnInstance(currentAtStart, threadId);
92164
- let steered = false;
92739
+ let steered = "refused";
92165
92740
  const carriesImages = extractTurnImages(text8).images.length > 0;
92166
92741
  if (!carriesImages && !computerSelectionTurns.get(threadId)?.selected && instance?.adapter.capabilities.queueing && instance.adapter.steer) {
92167
- 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");
92168
92743
  }
92169
92744
  const current2 = store.projectBotForTask(bot.id, threadId);
92170
92745
  if (!current2) throw Object.assign(new Error("no such bot"), { status: 404 });
92171
92746
  if (!store.taskByThread(bot.id, threadId)) {
92172
92747
  throw Object.assign(new Error("the target task no longer exists"), { status: 409 });
92173
92748
  }
92174
- if (steered) {
92175
- if (!current2.busy) {
92749
+ const delivered = steered !== "refused";
92750
+ if (delivered) {
92751
+ if (steered === "steered" && !current2.busy) {
92176
92752
  throw Object.assign(
92177
92753
  new Error("the running turn ended before the steered message could be recorded"),
92178
92754
  { status: 409 }
@@ -92215,6 +92791,53 @@ data: ${JSON.stringify({ kind: "ping" })}
92215
92791
  }
92216
92792
  return json3(res, 200, { ok: true });
92217
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
+ }
92218
92841
  m3 = path.match(/^\/api\/bots\/([\w-]+)\/messages\/([\w-]+)\/edit$/);
92219
92842
  if (m3 && method === "POST") {
92220
92843
  const messageId = m3[2];
@@ -94083,6 +94706,27 @@ try {
94083
94706
  } catch (error51) {
94084
94707
  console.warn(`attachments: startup partial cleanup failed: ${error51 instanceof Error ? error51.message : String(error51)}`);
94085
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();
94086
94730
  for (const row of chatFollowups()) {
94087
94731
  if (row.status !== "dispatching" && row.status !== "interrupted") continue;
94088
94732
  const owned = row.kind === "bot" ? Boolean(store.taskByThread(row.ownerId, row.threadId)) : Boolean(store.groupByThread(row.threadId)?.id === row.ownerId);