openmausbot 0.1.76 → 0.1.77

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/assets/{index-C6blGSq5.js → index-DeK-z9ld.js} +1 -1
  2. package/dist/assets/index-XdHJ-9CF.css +1 -0
  3. package/dist/assets/index-lhqE1umn.js +305 -0
  4. package/dist/index.html +2 -2
  5. package/dist-server/container-mcp.js +5 -1
  6. package/dist-server/drivers/agents-proxy.js +52 -1
  7. package/dist-server/enterprise/server/index.js +387 -29
  8. package/dist-server/index.js +1377 -459
  9. package/dist-server/openmausbot.js +725 -522
  10. package/dist-server/pair-cli.js +725 -522
  11. package/dist-server/server/agent-tool-policy.js +1 -0
  12. package/dist-server/server/browser-live.js +3 -0
  13. package/dist-server/server/config.js +16 -0
  14. package/dist-server/server/drivers/acp/core.js +3 -1
  15. package/dist-server/server/drivers/agents-proxy.js +58 -2
  16. package/dist-server/server/drivers/boxagent.js +1 -1
  17. package/dist-server/server/drivers/claude.js +3 -2
  18. package/dist-server/server/drivers/codex.js +3 -1
  19. package/dist-server/server/drivers/pi.js +4 -0
  20. package/dist-server/server/enterprise.js +38 -0
  21. package/dist-server/server/environment.js +3 -0
  22. package/dist-server/server/fleet-agent.js +57 -13
  23. package/dist-server/server/fleet-cli.js +248 -31
  24. package/dist-server/server/fleet.js +88 -14
  25. package/dist-server/server/hosted-contract.js +16 -0
  26. package/dist-server/server/index.js +671 -100
  27. package/dist-server/server/message-file.js +4 -0
  28. package/dist-server/server/provider-key-check.js +18 -1
  29. package/dist-server/server/request-auth.js +14 -5
  30. package/dist-server/server/sessions.js +115 -8
  31. package/dist-server/server/shared-computer-control.js +24 -0
  32. package/dist-server/server/shared-computers.js +131 -0
  33. package/dist-server/server/steer-queue.js +2 -0
  34. package/dist-server/server/store.js +20 -7
  35. package/dist-server/server/team-computers.js +108 -0
  36. package/dist-server/server/tool-summary.js +53 -1
  37. package/dist-server/server/workspace-backup.js +1 -0
  38. package/dist-server/shared/approval-mode.js +4 -1
  39. package/dist-server/shared/team-computer.js +1 -0
  40. package/dist-server/vps-container-mcp.js +5 -1
  41. package/enterprise/README.md +21 -3
  42. package/enterprise/server/index.js +387 -29
  43. package/package.json +1 -1
  44. package/dist/assets/index-BtT047dk.css +0 -1
  45. package/dist/assets/index-fKXFpB_J.js +0 -305
@@ -5,8 +5,8 @@ var __export = (target, all2) => {
5
5
  };
6
6
 
7
7
  // server/index.ts
8
- import { createHash as createHash29, randomBytes as randomBytes9, randomUUID as randomUUID25, timingSafeEqual as timingSafeEqual4 } from "node:crypto";
9
- import { existsSync as existsSync30, readFileSync as readFileSync42, rmSync as rmSync8, unlinkSync as unlinkSync12 } from "node:fs";
8
+ import { createHash as createHash29, randomBytes as randomBytes9, randomUUID as randomUUID27, timingSafeEqual as timingSafeEqual5 } from "node:crypto";
9
+ import { existsSync as existsSync31, readFileSync as readFileSync43, rmSync as rmSync8, unlinkSync as unlinkSync13 } from "node:fs";
10
10
  import { createServer as createServer3 } from "node:http";
11
11
  import { extname as extname5, join as join61 } from "node:path";
12
12
 
@@ -14524,12 +14524,173 @@ function date4(params) {
14524
14524
  // node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
14525
14525
  config(en_default());
14526
14526
 
14527
+ // server/shared-computers.ts
14528
+ import { randomUUID, timingSafeEqual } from "node:crypto";
14529
+ var folder = external_exports.object({ id: external_exports.string().uuid(), name: external_exports.string().min(1).max(120), write: external_exports.boolean() }).strict();
14530
+ var sharedComputerRegistration = external_exports.object({
14531
+ id: external_exports.string().uuid(),
14532
+ name: external_exports.string().min(1).max(120),
14533
+ environmentId: external_exports.string().uuid(),
14534
+ folders: external_exports.array(folder).max(20),
14535
+ terminal: external_exports.boolean(),
14536
+ computer: external_exports.boolean()
14537
+ }).strict();
14538
+ var sharedComputerOperation = external_exports.object({
14539
+ computer_id: external_exports.string().uuid(),
14540
+ action: external_exports.enum(["list_files", "read_file", "write_file", "run_command", "computer_tools", "computer_call"]),
14541
+ folder_id: external_exports.string().uuid().optional(),
14542
+ path: external_exports.string().max(2048).optional(),
14543
+ content: external_exports.string().max(35e4).optional(),
14544
+ encoding: external_exports.enum(["utf8", "base64"]).optional(),
14545
+ expected_sha256: external_exports.string().regex(/^[a-f0-9]{64}$/).optional(),
14546
+ command: external_exports.string().max(8e3).optional(),
14547
+ tool_name: external_exports.string().max(100).optional(),
14548
+ arguments: external_exports.record(external_exports.string(), external_exports.unknown()).optional()
14549
+ }).strict();
14550
+ var failure = (message3, status = 409) => Object.assign(new Error(message3), { status });
14551
+ var SharedComputers = class {
14552
+ computers = /* @__PURE__ */ new Map();
14553
+ sessionLive;
14554
+ constructor(sessionLive) {
14555
+ this.sessionLive = sessionLive;
14556
+ }
14557
+ register(registration, owner2, secret) {
14558
+ if (!/^[a-f0-9]{64}$/.test(secret)) throw failure("Invalid computer credential", 400);
14559
+ const old = this.computers.get(registration.id);
14560
+ if (old) {
14561
+ this.authorize(registration.id, owner2, secret);
14562
+ old.registration = registration;
14563
+ old.seen = Date.now();
14564
+ } else {
14565
+ for (const [id, entry] of this.computers) if (!this.online(entry) && entry.jobs.size === 0) this.computers.delete(id);
14566
+ if (this.computers.size >= 20) throw failure("Too many shared computers");
14567
+ this.computers.set(registration.id, { registration, owner: owner2, secret, seen: Date.now(), jobs: /* @__PURE__ */ new Map() });
14568
+ }
14569
+ }
14570
+ online(entry) {
14571
+ return this.sessionLive(entry.owner) && Date.now() - entry.seen < 4e4;
14572
+ }
14573
+ authorize(id, owner2, secret) {
14574
+ const entry = this.computers.get(id);
14575
+ if (!entry || entry.owner !== owner2 || !this.sessionLive(owner2) || !/^[a-f0-9]{64}$/.test(secret) || !timingSafeEqual(Buffer.from(secret), Buffer.from(entry.secret))) throw failure("Shared computer is disconnected or not authorized", 403);
14576
+ return entry;
14577
+ }
14578
+ list() {
14579
+ return [...this.computers.values()].filter((entry) => this.online(entry)).map((entry) => entry.registration);
14580
+ }
14581
+ async poll(id, owner2, secret) {
14582
+ const entry = this.authorize(id, owner2, secret);
14583
+ if (entry.wake) throw failure("A computer poll is already running");
14584
+ entry.seen = Date.now();
14585
+ const next = () => {
14586
+ for (const job of entry.jobs.values()) {
14587
+ if (!job.active()) {
14588
+ this.finish(entry, job, failure("The requesting turn ended"));
14589
+ continue;
14590
+ }
14591
+ if (!job.sent) {
14592
+ job.sent = true;
14593
+ return { id: job.id, operation: job.operation };
14594
+ }
14595
+ }
14596
+ return null;
14597
+ };
14598
+ const queued = next();
14599
+ if (queued) return queued;
14600
+ await new Promise((resolve16) => {
14601
+ const timer = setTimeout(() => {
14602
+ entry.wake = void 0;
14603
+ resolve16();
14604
+ }, 2e4);
14605
+ entry.wake = () => {
14606
+ clearTimeout(timer);
14607
+ entry.wake = void 0;
14608
+ resolve16();
14609
+ };
14610
+ });
14611
+ this.authorize(id, owner2, secret);
14612
+ entry.seen = Date.now();
14613
+ return next();
14614
+ }
14615
+ liveJob(id, owner2, secret, jobId) {
14616
+ const entry = this.authorize(id, owner2, secret);
14617
+ entry.seen = Date.now();
14618
+ const job = entry.jobs.get(jobId);
14619
+ return job?.sent === true && job.active();
14620
+ }
14621
+ complete(id, owner2, secret, jobId, result) {
14622
+ const entry = this.authorize(id, owner2, secret);
14623
+ const job = entry.jobs.get(jobId);
14624
+ if (!job || !job.sent) throw failure("This computer operation expired; it will not be replayed");
14625
+ this.finish(entry, job, job.active() ? null : failure("The requesting turn ended"), result);
14626
+ }
14627
+ disconnect(id, owner2, secret) {
14628
+ const entry = this.authorize(id, owner2, secret);
14629
+ this.computers.delete(id);
14630
+ entry.wake?.();
14631
+ for (const job of entry.jobs.values()) this.finish(entry, job, failure("Computer disconnected. An in-flight action may have completed; inspect before retrying."));
14632
+ }
14633
+ request(operation, active) {
14634
+ const entry = this.computers.get(operation.computer_id);
14635
+ if (!entry || !this.online(entry)) return Promise.reject(failure("This shared computer is offline. Keep its desktop app open; do not substitute files on the server."));
14636
+ if (entry.jobs.size >= 1) return Promise.reject(failure("This shared computer is busy. Wait for its current action to finish."));
14637
+ if (!active()) return Promise.reject(failure("The requesting turn ended", 401));
14638
+ return new Promise((resolve16, reject) => {
14639
+ const id = randomUUID();
14640
+ const job = { id, operation, active, sent: false, resolve: resolve16, reject, timer: setTimeout(() => this.finish(entry, job, failure("Computer action timed out. Its outcome is unknown; inspect before retrying.")), 45e3) };
14641
+ entry.jobs.set(id, job);
14642
+ entry.wake?.();
14643
+ });
14644
+ }
14645
+ close() {
14646
+ for (const entry of this.computers.values()) {
14647
+ entry.wake?.();
14648
+ for (const job of entry.jobs.values()) this.finish(entry, job, failure("Workspace stopped. Inspect any in-flight action before retrying."));
14649
+ }
14650
+ this.computers.clear();
14651
+ }
14652
+ finish(entry, job, error51, result) {
14653
+ clearTimeout(job.timer);
14654
+ entry.jobs.delete(job.id);
14655
+ if (error51) job.reject(error51);
14656
+ else job.resolve(result);
14657
+ }
14658
+ };
14659
+
14660
+ // server/shared-computer-control.ts
14661
+ var SharedComputerControl = class {
14662
+ leases = /* @__PURE__ */ new Map();
14663
+ resources;
14664
+ held;
14665
+ constructor(resources, held) {
14666
+ this.resources = resources;
14667
+ this.held = held;
14668
+ }
14669
+ acquire(id) {
14670
+ const owner2 = { threadId: `shared:${id}`, generation: id };
14671
+ if (this.held() || !this.resources.claim("computer:host", owner2)) {
14672
+ this.release(id);
14673
+ throw Object.assign(new Error("This computer is in use locally or held by a person. Wait, then observe it again before acting."), { status: 409 });
14674
+ }
14675
+ clearTimeout(this.leases.get(id));
14676
+ this.leases.set(id, setTimeout(() => this.release(id), 4e4));
14677
+ }
14678
+ release(id) {
14679
+ clearTimeout(this.leases.get(id));
14680
+ this.leases.delete(id);
14681
+ this.resources.release({ threadId: `shared:${id}`, generation: id });
14682
+ }
14683
+ close() {
14684
+ for (const id of this.leases.keys()) this.release(id);
14685
+ }
14686
+ };
14687
+
14527
14688
  // server/room-handoffs.ts
14528
- import { randomUUID as randomUUID2 } from "node:crypto";
14689
+ import { randomUUID as randomUUID3 } from "node:crypto";
14529
14690
  import { readFileSync } from "node:fs";
14530
14691
 
14531
14692
  // server/atomic.ts
14532
- import { randomUUID } from "node:crypto";
14693
+ import { randomUUID as randomUUID2 } from "node:crypto";
14533
14694
  import { closeSync, fsyncSync, openSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
14534
14695
  var RENAME_RETRY_DELAYS_MS = [5, 10, 20, 40, 80];
14535
14696
  var RETRYABLE_RENAME_CODES = /* @__PURE__ */ new Set(["EPERM", "EACCES", "EBUSY"]);
@@ -14549,7 +14710,7 @@ function renameWithRetry(tmp, path, rename3 = renameSync) {
14549
14710
  }
14550
14711
  }
14551
14712
  function writeFileAtomic(path, data, options = {}) {
14552
- const tmp = `${path}.${process.pid}.${randomUUID()}.tmp`;
14713
+ const tmp = `${path}.${process.pid}.${randomUUID2()}.tmp`;
14553
14714
  let fd = null;
14554
14715
  try {
14555
14716
  fd = openSync(tmp, "w", options.mode);
@@ -14681,7 +14842,7 @@ var RoomHandoffs = class {
14681
14842
  }
14682
14843
  const node2 = {
14683
14844
  ...target,
14684
- id: randomUUID2(),
14845
+ id: randomUUID3(),
14685
14846
  rootId: parent.rootId,
14686
14847
  parentId: parent.id,
14687
14848
  key: key3,
@@ -14840,7 +15001,9 @@ function isApprovalMode(value) {
14840
15001
  return typeof value === "string" && APPROVAL_MODES.includes(value);
14841
15002
  }
14842
15003
  function approvalModeFor(bot) {
14843
- if (bot.approvalGrant) return "ask";
15004
+ const grant = bot.approvalGrant;
15005
+ const otherThread = grant && typeof grant === "object" && "threadOnly" in grant && grant.threadOnly === true && "threadId" in grant && typeof grant.threadId === "string" && typeof bot.threadId === "string" && grant.threadId !== bot.threadId;
15006
+ if (grant && !otherThread) return "ask";
14844
15007
  if (isApprovalMode(bot.approvalMode)) return bot.approvalMode;
14845
15008
  return bot.autoApprove === true ? "auto" : "ask";
14846
15009
  }
@@ -15644,7 +15807,7 @@ async function updateClaudeCli(cli, env, execute2 = execCli) {
15644
15807
  }
15645
15808
 
15646
15809
  // server/claude-accounts.ts
15647
- import { randomUUID as randomUUID4 } from "node:crypto";
15810
+ import { randomUUID as randomUUID5 } from "node:crypto";
15648
15811
  import { realpathSync as realpathSync2 } from "node:fs";
15649
15812
  import { join as join11 } from "node:path";
15650
15813
 
@@ -16019,7 +16182,11 @@ var featureConfigSchema = external_exports.object({
16019
16182
  showToolCalls: external_exports.boolean().optional(),
16020
16183
  /** Experimental built-in browser. Off until explicitly enabled; each bot
16021
16184
  * also has its own switch. */
16022
- browser: external_exports.boolean().optional()
16185
+ browser: external_exports.boolean().optional(),
16186
+ /** Opt-in computer sharing (a desktop lending folders, a terminal or
16187
+ * computer control to a workspace). Off until explicitly enabled; there is
16188
+ * no Settings toggle — see sharedComputersEnabled. */
16189
+ sharedComputers: external_exports.boolean().optional()
16023
16190
  });
16024
16191
  var onboardingConfigSchema = external_exports.object({
16025
16192
  /** ISO timestamp of finishing (or skipping to the end of) the welcome flow. */
@@ -16219,6 +16386,9 @@ function showToolCallsEnabled(cfg2) {
16219
16386
  function builtInBrowserEnabled(cfg2) {
16220
16387
  return cfg2.features?.browser === true;
16221
16388
  }
16389
+ function sharedComputersEnabled(cfg2) {
16390
+ return cfg2.features?.sharedComputers === true;
16391
+ }
16222
16392
  var FLEET_NEUTRAL_KEYS = /* @__PURE__ */ new Set([
16223
16393
  "profile",
16224
16394
  "language",
@@ -16571,8 +16741,8 @@ function classifyResumeFailure(state) {
16571
16741
  if (state.attempted && state.rejected) return "before-accept";
16572
16742
  return "unknown";
16573
16743
  }
16574
- function mayReplay(failure3) {
16575
- return failure3 === "before-accept";
16744
+ function mayReplay(failure5) {
16745
+ return failure5 === "before-accept";
16576
16746
  }
16577
16747
  function recoveryPromptFor(input) {
16578
16748
  if (!mayReplay(input.failure)) return { text: input.currentText, replayed: false };
@@ -16582,7 +16752,7 @@ function recoveryPromptFor(input) {
16582
16752
  }
16583
16753
 
16584
16754
  // server/drivers/claude-login-auth.ts
16585
- import { randomUUID as randomUUID3 } from "node:crypto";
16755
+ import { randomUUID as randomUUID4 } from "node:crypto";
16586
16756
  import { realpathSync } from "node:fs";
16587
16757
  import { homedir as homedir3 } from "node:os";
16588
16758
  import { basename as basename2, dirname as dirname2, isAbsolute, join as join4, resolve } from "node:path";
@@ -16653,7 +16823,7 @@ var ClaudeLoginController = class {
16653
16823
  const flow3 = {
16654
16824
  status: {
16655
16825
  phase: "waiting",
16656
- flowId: randomUUID3(),
16826
+ flowId: randomUUID4(),
16657
16827
  authorizationUrl: null,
16658
16828
  expiresAt: new Date(Date.now() + (this.options.lifetimeMs ?? 15 * 6e4)).toISOString()
16659
16829
  },
@@ -17823,6 +17993,41 @@ function redactSecrets(input, depth = 0) {
17823
17993
  }
17824
17994
 
17825
17995
  // server/tool-summary.ts
17996
+ function toolDetailPreview(value) {
17997
+ if (value === void 0) return void 0;
17998
+ let budget = 200;
17999
+ let textBudget = 256e3;
18000
+ const bounded2 = (item, depth = 0) => {
18001
+ if (--budget < 0 || depth > 6) return "[additional data omitted]";
18002
+ if (typeof item === "string") {
18003
+ if (item.length > textBudget) return "[large content omitted]";
18004
+ if (/^data:[^,\s]+;base64,/i.test(item)) return "[binary content omitted]";
18005
+ if (/^\s*[[{]/.test(item)) {
18006
+ try {
18007
+ return bounded2(JSON.parse(item), depth + 1);
18008
+ } catch {
18009
+ }
18010
+ }
18011
+ textBudget -= item.length;
18012
+ return item;
18013
+ }
18014
+ if (typeof item === "bigint") return String(item);
18015
+ if (item === null || typeof item !== "object") return item;
18016
+ if (Array.isArray(item)) return item.slice(0, 40).map((child) => bounded2(child, depth + 1)).concat(item.length > 40 ? ["[additional items omitted]"] : []);
18017
+ const entries = Object.entries(item);
18018
+ const result = /* @__PURE__ */ Object.create(null);
18019
+ for (const [key3, child] of entries.slice(0, 40)) {
18020
+ result[key3] = /token|secret|password|passwd|cookie|authorization|api.?key|(^|[_.-])keys?$/i.test(key3) ? "[redacted]" : /^(data|base64|blob)$/i.test(key3) && typeof child === "string" ? "[binary content omitted]" : bounded2(child, depth + 1);
18021
+ }
18022
+ if (entries.length > 40) result["\u2026"] = "[additional fields omitted]";
18023
+ return result;
18024
+ };
18025
+ const safe = redactSecrets(bounded2(value));
18026
+ const text8 = typeof safe === "string" ? safe : JSON.stringify(safe, null, 2);
18027
+ if (!text8?.trim() || text8 === "{}" || text8 === "[]") return void 0;
18028
+ return text8.length > 6e3 ? `${text8.slice(0, 6e3)}
18029
+ [\u2026 preview shortened]` : text8;
18030
+ }
17826
18031
  var QUESTION_LIMIT = 300;
17827
18032
  var LIMIT = 200;
17828
18033
  function fieldsOf(input) {
@@ -19246,7 +19451,8 @@ var ClaudeDriver = {
19246
19451
  itemType: "tool",
19247
19452
  itemId: b2.id,
19248
19453
  title: b2.name,
19249
- summary: commandSummary(b2.input)
19454
+ summary: commandSummary(b2.input),
19455
+ input: toolDetailPreview(b2.input)
19250
19456
  });
19251
19457
  }
19252
19458
  }
@@ -19264,7 +19470,7 @@ var ClaudeDriver = {
19264
19470
  case "user":
19265
19471
  for (const b2 of Array.isArray(o.message?.content) ? o.message.content : []) {
19266
19472
  if (b2.type === "tool_result") {
19267
- emit({ ...base2(threadId, currentTurnId()), type: "item.completed", itemType: "tool", itemId: b2.tool_use_id, ok: !b2.is_error });
19473
+ emit({ ...base2(threadId, currentTurnId()), type: "item.completed", itemType: "tool", itemId: b2.tool_use_id, ok: !b2.is_error, output: toolDetailPreview(b2.content) });
19268
19474
  }
19269
19475
  }
19270
19476
  break;
@@ -19662,7 +19868,7 @@ function assertSeparateClaudeAccount(map3, id, entry) {
19662
19868
  }
19663
19869
  function newClaudeAccount(cfg2, body) {
19664
19870
  const instances = persistableInstanceConfigs(cfg2);
19665
- const instanceId = `claude-${randomUUID4()}`;
19871
+ const instanceId = `claude-${randomUUID5()}`;
19666
19872
  const existing = Object.values(instances).find((entry2) => entry2.driver === "claudeAgent");
19667
19873
  const cli = existing ? rawConfig(existing).cli : void 0;
19668
19874
  const entry = {
@@ -19699,7 +19905,7 @@ function claudeAccountInfo(instanceId, entry, cli) {
19699
19905
  }
19700
19906
 
19701
19907
  // server/browser-lifecycle-cleanup.ts
19702
- import { randomUUID as randomUUID5 } from "node:crypto";
19908
+ import { randomUUID as randomUUID6 } from "node:crypto";
19703
19909
  import { readFileSync as readFileSync6 } from "node:fs";
19704
19910
  var BOT_ID = /^[A-Za-z0-9_-]{1,120}$/;
19705
19911
  var PROFILE_ID = /^[a-z0-9_-]{1,40}$/;
@@ -19829,7 +20035,7 @@ var BrowserCleanupCoordinator = class {
19829
20035
  const existing = [...this.#pending.values()].find((request3) => request3.kind === kind && request3.id === id);
19830
20036
  if (existing) return existing;
19831
20037
  if (this.#pending.size >= MAX_PENDING) throw new Error("too many pending browser data cleanups");
19832
- const request2 = kind === "bot" ? { requestId: randomUUID5(), kind, id, phase: "prepared" } : { requestId: randomUUID5(), kind, id, partitionId, phase: "prepared" };
20038
+ const request2 = kind === "bot" ? { requestId: randomUUID6(), kind, id, phase: "prepared" } : { requestId: randomUUID6(), kind, id, partitionId, phase: "prepared" };
19833
20039
  this.#pending.set(request2.requestId, request2);
19834
20040
  try {
19835
20041
  this.#save();
@@ -20366,7 +20572,7 @@ function validateBotCwd(input) {
20366
20572
  }
20367
20573
 
20368
20574
  // server/attachments.ts
20369
- import { createHash as createHash4, randomUUID as randomUUID6 } from "node:crypto";
20575
+ import { createHash as createHash4, randomUUID as randomUUID7 } from "node:crypto";
20370
20576
  import {
20371
20577
  createReadStream,
20372
20578
  linkSync,
@@ -20665,9 +20871,9 @@ async function saveFile(chunks, originalName, mime, options = {}) {
20665
20871
  }
20666
20872
  return { path: existing, name: name2, mime: normalized, bytes: bytes2 };
20667
20873
  }
20668
- const id = uploadId ?? randomUUID6();
20874
+ const id = uploadId ?? randomUUID7();
20669
20875
  const path = join14(ATTACHMENTS_DIR, `${id}${extension2}`);
20670
- const partialPath = join14(ATTACHMENTS_DIR, `.openmaus-upload-${id}-${randomUUID6()}.partial`);
20876
+ const partialPath = join14(ATTACHMENTS_DIR, `.openmaus-upload-${id}-${randomUUID7()}.partial`);
20671
20877
  const reservation = new AttachmentReservation();
20672
20878
  if (expectedBytes) reservation.reserve(expectedBytes);
20673
20879
  activePartials.add(partialPath);
@@ -20740,10 +20946,10 @@ function saveImage(bytes, mime, requestedUploadId) {
20740
20946
  }
20741
20947
  const reservation = new AttachmentReservation();
20742
20948
  reservation.reserve(bytes.byteLength);
20743
- const id = uploadId ?? randomUUID6();
20949
+ const id = uploadId ?? randomUUID7();
20744
20950
  const name2 = `${id}${ext}`;
20745
20951
  const path = join14(ATTACHMENTS_DIR, name2);
20746
- const partialPath = join14(ATTACHMENTS_DIR, `.openmaus-upload-${id}-${randomUUID6()}.partial`);
20952
+ const partialPath = join14(ATTACHMENTS_DIR, `.openmaus-upload-${id}-${randomUUID7()}.partial`);
20747
20953
  activePartials.add(partialPath);
20748
20954
  let partialCleanupFailed = false;
20749
20955
  try {
@@ -27993,6 +28199,14 @@ function mimeFor(path) {
27993
28199
  return "image/gif";
27994
28200
  case ".webp":
27995
28201
  return "image/webp";
28202
+ case ".mp4":
28203
+ return "video/mp4";
28204
+ case ".m4v":
28205
+ return "video/x-m4v";
28206
+ case ".webm":
28207
+ return "video/webm";
28208
+ case ".mov":
28209
+ return "video/quicktime";
27996
28210
  case ".doc":
27997
28211
  return "application/msword";
27998
28212
  case ".docx":
@@ -28440,7 +28654,7 @@ function roomTurnTimeoutMessage(botName, minutes) {
28440
28654
  import { createHash as createHash6 } from "node:crypto";
28441
28655
 
28442
28656
  // server/environment.ts
28443
- import { randomUUID as randomUUID7 } from "node:crypto";
28657
+ import { randomUUID as randomUUID8 } from "node:crypto";
28444
28658
  import {
28445
28659
  closeSync as closeSync3,
28446
28660
  fsyncSync as fsyncSync2,
@@ -28479,8 +28693,8 @@ function loadEnvironmentId(dataDir) {
28479
28693
  if (existing) return existing;
28480
28694
  const directory = dirname5(file2);
28481
28695
  mkdirSync4(directory, { recursive: true, mode: 448 });
28482
- const id = randomUUID7();
28483
- const temporaryFile = join15(directory, `.environment-id.${process.pid}.${randomUUID7()}.tmp`);
28696
+ const id = randomUUID8();
28697
+ const temporaryFile = join15(directory, `.environment-id.${process.pid}.${randomUUID8()}.tmp`);
28484
28698
  let descriptor;
28485
28699
  try {
28486
28700
  descriptor = openSync3(temporaryFile, "wx", 384);
@@ -28524,6 +28738,9 @@ function environmentDescriptor(input) {
28524
28738
  version: serverVersion(),
28525
28739
  capabilities: {
28526
28740
  remoteSessions: true,
28741
+ // Never advertise a protocol this server would refuse: the routes are
28742
+ // gone unless features.sharedComputers is on, so the capability is too.
28743
+ ...input.sharedComputers === true ? { sharedComputers: true } : {},
28527
28744
  selfUpdate: input.desktopManaged ? "desktop-managed" : "operator",
28528
28745
  emailSignIn: input.emailSignIn === true
28529
28746
  }
@@ -28531,7 +28748,7 @@ function environmentDescriptor(input) {
28531
28748
  }
28532
28749
 
28533
28750
  // server/box-create-idempotency.ts
28534
- import { createHash as createHash5, randomUUID as randomUUID8 } from "node:crypto";
28751
+ import { createHash as createHash5, randomUUID as randomUUID9 } from "node:crypto";
28535
28752
  import { linkSync as linkSync3, mkdirSync as mkdirSync5, readFileSync as readFileSync10, unlinkSync as unlinkSync5, writeFileSync as writeFileSync6 } from "node:fs";
28536
28753
  import { join as join16 } from "node:path";
28537
28754
  var FILE = join16(DATA_DIR, "box-create-requests.json");
@@ -28687,7 +28904,7 @@ function claimReaperAuthority(expected) {
28687
28904
  const candidate = {
28688
28905
  version: 1,
28689
28906
  pid: process.pid,
28690
- token: randomUUID8(),
28907
+ token: randomUUID9(),
28691
28908
  createdAt: Date.now(),
28692
28909
  targetToken: expected.token
28693
28910
  };
@@ -28723,7 +28940,7 @@ function acquireJournalLock() {
28723
28940
  const owner2 = {
28724
28941
  version: 1,
28725
28942
  pid: process.pid,
28726
- token: randomUUID8(),
28943
+ token: randomUUID9(),
28727
28944
  createdAt: Date.now()
28728
28945
  };
28729
28946
  const candidate = `${LOCK_FILE}.candidate-${owner2.pid}-${owner2.token}`;
@@ -28795,7 +29012,7 @@ function beginBoxCreate(botId, requestBody) {
28795
29012
  const request2 = {
28796
29013
  botId,
28797
29014
  requestBody,
28798
- idempotencyKey: randomUUID8(),
29015
+ idempotencyKey: randomUUID9(),
28799
29016
  createdAt: now
28800
29017
  };
28801
29018
  if (requests.length >= MAX_REQUESTS) throw recoveryStateError("full");
@@ -28842,7 +29059,7 @@ function adoptResolvedBox(botId, boxId) {
28842
29059
  const adopted = sameBox ? { ...sameBox, resolved: true } : {
28843
29060
  botId,
28844
29061
  requestBody: JSON.stringify({ adopted: "legacy-name", boxId }),
28845
- idempotencyKey: randomUUID8(),
29062
+ idempotencyKey: randomUUID9(),
28846
29063
  createdAt: Date.now(),
28847
29064
  boxId,
28848
29065
  resolved: true
@@ -29761,6 +29978,101 @@ async function screenshotBox(cfg2, botId, knownBoxId) {
29761
29978
  return { png: data, format: "jpeg" };
29762
29979
  }
29763
29980
 
29981
+ // server/team-computers.ts
29982
+ import { randomUUID as randomUUID10 } from "node:crypto";
29983
+ import { existsSync as existsSync5, lstatSync, readFileSync as readFileSync11 } from "node:fs";
29984
+ var teamComputerCreate = external_exports.object({
29985
+ name: external_exports.string().trim().min(1).max(60).refine((value) => [...value].every((character) => character.charCodeAt(0) >= 32 && character.charCodeAt(0) !== 127)),
29986
+ acknowledgeCost: external_exports.literal(true),
29987
+ // Clients may keep this id across a lost create response. It is not a Box id.
29988
+ requestId: external_exports.string().uuid()
29989
+ }).strict();
29990
+ var teamComputerAssignment = external_exports.object({
29991
+ section: external_exports.string().trim().max(60).nullable(),
29992
+ acknowledgeSharedAccess: external_exports.literal(true)
29993
+ }).strict();
29994
+ var entrySchema = external_exports.object({
29995
+ id: external_exports.string().uuid(),
29996
+ name: teamComputerCreate.shape.name,
29997
+ section: external_exports.string().trim().max(60).nullable(),
29998
+ createdAt: external_exports.number().finite().nonnegative(),
29999
+ problem: external_exports.string().max(500).optional()
30000
+ }).strict();
30001
+ var fileSchema = external_exports.object({ version: external_exports.literal(1), environmentId: external_exports.string().uuid(), computers: external_exports.array(entrySchema).max(100) }).strict();
30002
+ var teamComputerOwner = (id) => `computer_${id}`;
30003
+ var failure2 = (message3, status = 409) => Object.assign(new Error(message3), { status });
30004
+ var TeamComputers = class {
30005
+ entries = [];
30006
+ problem;
30007
+ file;
30008
+ environmentId;
30009
+ constructor(file2, environmentId) {
30010
+ this.file = file2;
30011
+ this.environmentId = environmentId;
30012
+ try {
30013
+ if (!existsSync5(file2)) return;
30014
+ const stat6 = lstatSync(file2);
30015
+ if (!stat6.isFile() || stat6.isSymbolicLink() || stat6.size > 1e5) throw new Error("unsafe registry file");
30016
+ const saved = fileSchema.parse(JSON.parse(readFileSync11(file2, "utf8")));
30017
+ if (saved.environmentId !== environmentId) throw new Error("registry belongs to another workspace; its computers were not transferred");
30018
+ if (new Set(saved.computers.map((entry) => entry.id)).size !== saved.computers.length) throw new Error("duplicate computer identity");
30019
+ const sections = saved.computers.flatMap((entry) => entry.section === null ? [] : [entry.section]);
30020
+ if (new Set(sections).size !== sections.length) throw new Error("a team has more than one computer");
30021
+ this.entries = saved.computers;
30022
+ } catch (error51) {
30023
+ this.problem = `Team computer registry could not be loaded: ${error51 instanceof Error ? error51.message : String(error51)}`;
30024
+ }
30025
+ }
30026
+ list() {
30027
+ if (this.problem) throw failure2(this.problem, 503);
30028
+ return this.entries.map((entry) => ({ ...entry }));
30029
+ }
30030
+ get(id) {
30031
+ return this.list().find((entry) => entry.id === id);
30032
+ }
30033
+ forSection(section2) {
30034
+ return this.list().find((entry) => entry.section !== null && entry.section === (section2?.trim() || ""));
30035
+ }
30036
+ forBot(bot) {
30037
+ return bot.computer === void 0 && bot.cloudBackend !== "vps" ? this.forSection(bot.section) : void 0;
30038
+ }
30039
+ create(name2, id = randomUUID10()) {
30040
+ const entries = this.list();
30041
+ const existing = entries.find((entry2) => entry2.id === id);
30042
+ if (existing) {
30043
+ if (existing.name !== name2) throw failure2("This creation id already names a different computer");
30044
+ return existing;
30045
+ }
30046
+ if (entries.length >= 100) throw failure2("This workspace has reached its team computer limit");
30047
+ const entry = entrySchema.parse({ id, name: name2, section: null, createdAt: Date.now() });
30048
+ this.save([...entries, entry]);
30049
+ return { ...entry };
30050
+ }
30051
+ assign(id, section2) {
30052
+ section2 = entrySchema.shape.section.parse(section2);
30053
+ const entry = this.get(id);
30054
+ if (!entry) throw failure2("No such team computer", 404);
30055
+ if (entry.section !== null && section2 !== null && section2 !== entry.section) throw failure2("Unassign this computer before moving it to another team");
30056
+ if (section2 !== null && this.list().some((other) => other.id !== id && other.section === section2)) throw failure2("This team already has a computer; unassign it first");
30057
+ return this.patch(id, { section: section2 });
30058
+ }
30059
+ setProblem(id, problem) {
30060
+ return this.patch(id, { problem: problem?.slice(0, 500) });
30061
+ }
30062
+ patch(id, patch) {
30063
+ const entries = this.list();
30064
+ const entry = entries.find((candidate) => candidate.id === id);
30065
+ if (!entry) throw failure2("No such team computer", 404);
30066
+ const next = entrySchema.parse({ ...entry, ...patch });
30067
+ this.save(entries.map((candidate) => candidate.id === id ? next : candidate));
30068
+ return { ...next };
30069
+ }
30070
+ save(entries) {
30071
+ writeFileAtomic(this.file, JSON.stringify({ version: 1, environmentId: this.environmentId, computers: entries }), { mode: 384 });
30072
+ this.entries = entries;
30073
+ }
30074
+ };
30075
+
29764
30076
  // server/cloud-backend.ts
29765
30077
  var CLOUD_BACKEND_CHANGE_ERROR = "stop the active turn before changing the cloud backend";
29766
30078
  var BOX_ACCOUNT_RESOURCES_ERROR = "remove this installation's cloud computers before changing or clearing the Box account";
@@ -29781,7 +30093,7 @@ function vpsAliasResourceChangeError(instanceCount) {
29781
30093
  }
29782
30094
 
29783
30095
  // server/composio.ts
29784
- import { createHash as createHash7, randomUUID as randomUUID9 } from "node:crypto";
30096
+ import { createHash as createHash7, randomUUID as randomUUID11 } from "node:crypto";
29785
30097
 
29786
30098
  // shared/connector-availability.ts
29787
30099
  var MANAGED_CUSTOM_AUTH_TOOLKITS = /* @__PURE__ */ new Set(["twitter", "x"]);
@@ -30076,13 +30388,13 @@ async function prepareProjectSession(apiKey, current2, knownAuthConfigs) {
30076
30388
  if (existing && supportsMultiAccount(existing) && (sessionCoversAuthConfigs(existing, authConfigs) || authConfigUpgradeAttempted.has(authConfigsKey(existing.session_id, authConfigs)))) {
30077
30389
  return {
30078
30390
  apiKey: trimmed,
30079
- userId: existing.config?.user_id ?? current2.userId ?? `openmausbot_${randomUUID9()}`,
30391
+ userId: existing.config?.user_id ?? current2.userId ?? `openmausbot_${randomUUID11()}`,
30080
30392
  sessionId: existing.session_id
30081
30393
  };
30082
30394
  }
30083
30395
  priorUserId = existing?.config?.user_id ?? priorUserId;
30084
30396
  }
30085
- const userId = priorUserId ?? `openmausbot_${randomUUID9()}`;
30397
+ const userId = priorUserId ?? `openmausbot_${randomUUID11()}`;
30086
30398
  const sessionRequest = {
30087
30399
  user_id: userId,
30088
30400
  manage_connections: {
@@ -30741,9 +31053,9 @@ import {
30741
31053
  closeSync as closeSync4,
30742
31054
  constants as constants2,
30743
31055
  fstatSync,
30744
- lstatSync,
31056
+ lstatSync as lstatSync2,
30745
31057
  openSync as openSync4,
30746
- readFileSync as readFileSync11
31058
+ readFileSync as readFileSync12
30747
31059
  } from "node:fs";
30748
31060
  import { homedir as homedir8 } from "node:os";
30749
31061
  import { dirname as dirname6, join as join17 } from "node:path";
@@ -30894,14 +31206,14 @@ function privateCache(path) {
30894
31206
  if (uid === void 0) return null;
30895
31207
  let descriptor = null;
30896
31208
  try {
30897
- const parentStatus = lstatSync(dirname6(path));
30898
- const fileStatus = lstatSync(path);
31209
+ const parentStatus = lstatSync2(dirname6(path));
31210
+ const fileStatus = lstatSync2(path);
30899
31211
  if (parentStatus.isSymbolicLink() || !parentStatus.isDirectory() || parentStatus.uid !== uid || (parentStatus.mode & 511) !== 448) return null;
30900
31212
  if (fileStatus.isSymbolicLink() || !fileStatus.isFile() || fileStatus.uid !== uid || (fileStatus.mode & 511) !== 384 || fileStatus.size > MAX_CACHE_BYTES) return null;
30901
31213
  descriptor = openSync4(path, constants2.O_RDONLY | (constants2.O_NOFOLLOW ?? 0));
30902
31214
  const openedStatus = fstatSync(descriptor);
30903
31215
  if (!openedStatus.isFile() || openedStatus.uid !== uid || (openedStatus.mode & 511) !== 384 || openedStatus.dev !== fileStatus.dev || openedStatus.ino !== fileStatus.ino || openedStatus.size > MAX_CACHE_BYTES) return null;
30904
- const contents = readFileSync11(descriptor);
31216
+ const contents = readFileSync12(descriptor);
30905
31217
  return contents.length <= MAX_CACHE_BYTES ? contents : null;
30906
31218
  } catch {
30907
31219
  return null;
@@ -31128,7 +31440,7 @@ var ComputerControl = class {
31128
31440
  };
31129
31441
 
31130
31442
  // server/engine-install.ts
31131
- import { existsSync as existsSync5, mkdirSync as mkdirSync6 } from "node:fs";
31443
+ import { existsSync as existsSync6, mkdirSync as mkdirSync6 } from "node:fs";
31132
31444
  import { join as join18 } from "node:path";
31133
31445
  import { stripVTControlCharacters as stripVTControlCharacters2 } from "node:util";
31134
31446
  var MAX_OUTPUT2 = 64 * 1024;
@@ -31235,7 +31547,7 @@ function runNpm(args, env, cwd, timeoutMs) {
31235
31547
  }
31236
31548
 
31237
31549
  // server/usage-ledger.ts
31238
- import { readFileSync as readFileSync12 } from "node:fs";
31550
+ import { readFileSync as readFileSync13 } from "node:fs";
31239
31551
  import { appendFile as appendFile2, mkdir as mkdir2 } from "node:fs/promises";
31240
31552
  import { join as join19 } from "node:path";
31241
31553
 
@@ -31310,7 +31622,7 @@ function readUsage(dataDir, range) {
31310
31622
  for (const key3 of monthsBetween(range.from, range.to)) {
31311
31623
  let text8;
31312
31624
  try {
31313
- text8 = readFileSync12(join19(dataDir, DIR, `${key3}.jsonl`), "utf8");
31625
+ text8 = readFileSync13(join19(dataDir, DIR, `${key3}.jsonl`), "utf8");
31314
31626
  } catch {
31315
31627
  continue;
31316
31628
  }
@@ -31474,6 +31786,7 @@ function modelIds(body) {
31474
31786
  return ids;
31475
31787
  }
31476
31788
  async function checkProviderKey(input, fetchImpl = fetch, timeoutMs = 1e4) {
31789
+ if (!input.key.trim()) return { ok: false, reason: "rejected" };
31477
31790
  let url2;
31478
31791
  try {
31479
31792
  url2 = new URL(providerModelsUrl(input.provider, input.url));
@@ -31483,6 +31796,8 @@ async function checkProviderKey(input, fetchImpl = fetch, timeoutMs = 1e4) {
31483
31796
  if (url2.protocol !== "https:" && !(url2.protocol === "http:" && isLoopback(url2.hostname))) {
31484
31797
  return { ok: false, reason: "unexpected" };
31485
31798
  }
31799
+ const authenticate = input.provider === "openaiCompat" && url2.origin === "https://openrouter.ai" && url2.pathname === "/api/v1/models";
31800
+ if (authenticate) url2.pathname = "/api/v1/key";
31486
31801
  const headers = input.provider === "anthropic" ? { "x-api-key": input.key, "anthropic-version": "2023-06-01" } : { authorization: `Bearer ${input.key}` };
31487
31802
  const controller = new AbortController();
31488
31803
  const timer = setTimeout(() => controller.abort(), timeoutMs);
@@ -31491,7 +31806,15 @@ async function checkProviderKey(input, fetchImpl = fetch, timeoutMs = 1e4) {
31491
31806
  if (response.status === 401 || response.status === 403) return { ok: false, reason: "rejected", status: response.status };
31492
31807
  if (!response.ok) return { ok: false, reason: "unexpected", status: response.status };
31493
31808
  const body = await response.json().catch(() => null);
31494
- return { ok: true, models: modelIds(body) };
31809
+ if (!body || typeof body !== "object") return { ok: false, reason: "unexpected", status: response.status };
31810
+ if (authenticate) {
31811
+ const data = body.data;
31812
+ if (!data || typeof data !== "object" || Array.isArray(data) || typeof data.is_free_tier !== "boolean") {
31813
+ return { ok: false, reason: "unexpected", status: response.status };
31814
+ }
31815
+ return { ok: true, check: "authentication", models: [] };
31816
+ }
31817
+ return { ok: true, check: "models", models: modelIds(body) };
31495
31818
  } catch {
31496
31819
  return { ok: false, reason: "unreachable" };
31497
31820
  } finally {
@@ -31500,7 +31823,7 @@ async function checkProviderKey(input, fetchImpl = fetch, timeoutMs = 1e4) {
31500
31823
  }
31501
31824
 
31502
31825
  // server/enterprise.ts
31503
- import { existsSync as existsSync6 } from "node:fs";
31826
+ import { existsSync as existsSync7 } from "node:fs";
31504
31827
  import { join as join20 } from "node:path";
31505
31828
  import { pathToFileURL } from "node:url";
31506
31829
  var layerSchema = external_exports.object({
@@ -31510,14 +31833,38 @@ var layerSchema = external_exports.object({
31510
31833
  });
31511
31834
  var DEFAULT_DIR = join20(SERVER_ROOT, "..", "enterprise");
31512
31835
  var current = { edition: "oss", features: [] };
31836
+ var workspaceAccessFactory;
31837
+ function hostedWorkspaceConfigured(env = process.env) {
31838
+ return env.OMB_ADMIN_URL !== void 0 || env.OMB_ADMIN_WORKSPACE !== void 0 || env.OMB_ADMIN_MEMBERSHIP !== void 0;
31839
+ }
31840
+ function hostedWorkspaceConfiguration(env = process.env) {
31841
+ try {
31842
+ const origin = (raw) => {
31843
+ if (!raw || raw !== raw.trim()) throw new Error("missing origin");
31844
+ const url2 = new URL(raw);
31845
+ if (url2.protocol !== "https:" || raw !== url2.origin && raw !== `${url2.origin}/`) throw new Error("invalid origin");
31846
+ return url2;
31847
+ };
31848
+ if (!/^[a-z][a-z0-9-]{1,30}$/.test(env.OMB_ADMIN_WORKSPACE ?? "")) return null;
31849
+ if (env.OMB_ADMIN_MEMBERSHIP !== void 0 && !["local", "portal"].includes(env.OMB_ADMIN_MEMBERSHIP)) return null;
31850
+ return { admin: origin(env.OMB_ADMIN_URL), tenant: origin(env.OMB_PUBLIC_URL), workspace: env.OMB_ADMIN_WORKSPACE, portalMembership: env.OMB_ADMIN_MEMBERSHIP === "portal" };
31851
+ } catch {
31852
+ return null;
31853
+ }
31854
+ }
31855
+ function createWorkspaceAccess(options) {
31856
+ if (!hostedWorkspaceConfigured(options.env)) return null;
31857
+ return workspaceAccessFactory?.({ ...options, entitled: () => entitled("admin") }) ?? null;
31858
+ }
31513
31859
  function oss(notice) {
31514
31860
  current = notice ? { edition: "oss", features: [], notice } : { edition: "oss", features: [] };
31515
31861
  return current;
31516
31862
  }
31517
31863
  async function loadEnterpriseLayer(options = {}) {
31864
+ workspaceAccessFactory = void 0;
31518
31865
  const dir = options.dir ?? process.env.OMB_ENTERPRISE_DIR ?? DEFAULT_DIR;
31519
31866
  const licenseKey = options.licenseKey ?? process.env.OMB_LICENSE_KEY;
31520
- const entry = [join20(dir, "server", "index.ts"), join20(dir, "server", "index.js")].find((candidate) => existsSync6(candidate));
31867
+ const entry = [join20(dir, "server", "index.ts"), join20(dir, "server", "index.js")].find((candidate) => existsSync7(candidate));
31521
31868
  if (!entry) {
31522
31869
  return oss(
31523
31870
  licenseKey ? `OMB_LICENSE_KEY is set but no enterprise layer exists at ${dir}` : void 0
@@ -31525,6 +31872,8 @@ async function loadEnterpriseLayer(options = {}) {
31525
31872
  }
31526
31873
  try {
31527
31874
  const loaded = await import(pathToFileURL(entry).href);
31875
+ const access2 = Reflect.get(Object(loaded), "createWorkspaceAccess");
31876
+ if (typeof access2 === "function") workspaceAccessFactory = access2;
31528
31877
  const register = Reflect.get(Object(loaded), "register");
31529
31878
  if (typeof register !== "function") throw new Error(`${entry} does not export register()`);
31530
31879
  if (!licenseKey) return oss("enterprise layer present but OMB_LICENSE_KEY is not set");
@@ -31618,13 +31967,13 @@ function assertWithinBudget(cfg2, dataDir, now = /* @__PURE__ */ new Date(), isE
31618
31967
  }
31619
31968
 
31620
31969
  // server/fleet-client.ts
31621
- import { existsSync as existsSync7 } from "node:fs";
31970
+ import { existsSync as existsSync8 } from "node:fs";
31622
31971
  import { request } from "node:http";
31623
31972
  function fleetSocketPath(env = process.env) {
31624
31973
  return env.OMB_FLEET_SOCKET?.trim() || "/run/openmausbot/fleet.sock";
31625
31974
  }
31626
31975
  function fleetAvailable(socketPath = fleetSocketPath()) {
31627
- return existsSync7(socketPath);
31976
+ return existsSync8(socketPath);
31628
31977
  }
31629
31978
  function fleetRequest(socketPath, method, path, body, timeoutMs = 15 * 6e4) {
31630
31979
  return new Promise((resolve16, reject) => {
@@ -31659,6 +32008,15 @@ function fleetRequest(socketPath, method, path, body, timeoutMs = 15 * 6e4) {
31659
32008
  });
31660
32009
  }
31661
32010
 
32011
+ // server/hosted-contract.ts
32012
+ var HOSTED_CONTRACT_VERSION = 1;
32013
+ var HOSTED_CONTRACT_HEADER = "x-omb-hosted-contract-version";
32014
+ var HOSTED_CONTRACT_METADATA = Object.freeze({
32015
+ contractVersion: HOSTED_CONTRACT_VERSION,
32016
+ supportedContractVersions: Object.freeze([HOSTED_CONTRACT_VERSION]),
32017
+ legacyPolicy: "legacy-v1"
32018
+ });
32019
+
31662
32020
  // server/notify.ts
31663
32021
  function summarize(text8, max = 140) {
31664
32022
  const line = text8.replace(/```[\s\S]*?```/g, " ").replace(/\s+/g, " ").trim();
@@ -32414,7 +32772,8 @@ function createAcpDriver(support11) {
32414
32772
  itemType: "tool",
32415
32773
  itemId: u2.toolCallId,
32416
32774
  title: String(u2.rawInput?.command ?? u2.title ?? "tool").slice(0, 80),
32417
- summary: commandSummary(u2.rawInput)
32775
+ summary: commandSummary(u2.rawInput),
32776
+ input: toolDetailPreview(u2.rawInput)
32418
32777
  });
32419
32778
  break;
32420
32779
  }
@@ -32425,7 +32784,8 @@ function createAcpDriver(support11) {
32425
32784
  type: "item.completed",
32426
32785
  itemType: "tool",
32427
32786
  itemId: u2.toolCallId,
32428
- ok: u2.status !== "failed"
32787
+ ok: u2.status !== "failed",
32788
+ output: toolDetailPreview(u2.rawOutput ?? u2.content)
32429
32789
  });
32430
32790
  }
32431
32791
  break;
@@ -32715,7 +33075,7 @@ ${turn.text}` : turn.text;
32715
33075
  }
32716
33076
 
32717
33077
  // server/drivers/antigravity-acp.ts
32718
- import { createHash as createHash9, randomUUID as randomUUID10 } from "node:crypto";
33078
+ import { createHash as createHash9, randomUUID as randomUUID12 } from "node:crypto";
32719
33079
  import { mkdir as mkdir4, mkdtemp as mkdtemp2, readFile as readFile2, rm as rm2, stat as stat4 } from "node:fs/promises";
32720
33080
  import { request as httpRequest } from "node:http";
32721
33081
  import { tmpdir as tmpdir3 } from "node:os";
@@ -33100,10 +33460,10 @@ async function validateAntigravityRuntime(runtime, expectedVersion) {
33100
33460
  const profileDirectory = await mkdtemp2(join21(tmpdir3(), "openmaus-antigravity-verify-"));
33101
33461
  let client;
33102
33462
  let failed = false;
33103
- let failure3;
33463
+ let failure5;
33104
33464
  try {
33105
33465
  const profile = await prepareAntigravityProfile({
33106
- instanceId: `verify-${randomUUID10()}`,
33466
+ instanceId: `verify-${randomUUID12()}`,
33107
33467
  runtime,
33108
33468
  profileDirectory,
33109
33469
  // Google's Windows one-file executable expands a large Python runtime
@@ -33118,7 +33478,7 @@ async function validateAntigravityRuntime(runtime, expectedVersion) {
33118
33478
  }
33119
33479
  } catch (error51) {
33120
33480
  failed = true;
33121
- failure3 = error51;
33481
+ failure5 = error51;
33122
33482
  }
33123
33483
  let stopped = !client;
33124
33484
  try {
@@ -33129,7 +33489,7 @@ async function validateAntigravityRuntime(runtime, expectedVersion) {
33129
33489
  } catch (error51) {
33130
33490
  if (!failed) {
33131
33491
  failed = true;
33132
- failure3 = error51;
33492
+ failure5 = error51;
33133
33493
  }
33134
33494
  }
33135
33495
  if (stopped) {
@@ -33137,7 +33497,7 @@ async function validateAntigravityRuntime(runtime, expectedVersion) {
33137
33497
  console.warn(`antigravity: could not remove verification profile ${profileDirectory}: ${error51 instanceof Error ? error51.message : String(error51)}`);
33138
33498
  });
33139
33499
  }
33140
- if (failed) throw failure3;
33500
+ if (failed) throw failure5;
33141
33501
  }
33142
33502
  var AntigravityAuthController = class {
33143
33503
  flow = null;
@@ -33176,7 +33536,7 @@ var AntigravityAuthController = class {
33176
33536
  return { phase: "succeeded", flowId: null, authorizationUrl: null, expiresAt: null };
33177
33537
  }
33178
33538
  const flow3 = {
33179
- id: randomUUID10(),
33539
+ id: randomUUID12(),
33180
33540
  client,
33181
33541
  completed: authenticated,
33182
33542
  pending: { redirectUri: outcome.request.redirectUri, state: outcome.request.state },
@@ -33214,7 +33574,7 @@ var AntigravityAuthController = class {
33214
33574
  };
33215
33575
 
33216
33576
  // server/drivers/antigravity-runtime.ts
33217
- import { createHash as createHash10, randomUUID as randomUUID11 } from "node:crypto";
33577
+ import { createHash as createHash10, randomUUID as randomUUID13 } from "node:crypto";
33218
33578
  import { createReadStream as createReadStream2, createWriteStream } from "node:fs";
33219
33579
  import {
33220
33580
  access,
@@ -33539,7 +33899,7 @@ async function installOnce2(options) {
33539
33899
  } catch (error51) {
33540
33900
  if (error51 instanceof Error && error51.message.includes("Antigravity needs")) throw error51;
33541
33901
  }
33542
- const staging = await mkdtemp3(join22(versions, `${STAGING_PREFIX}${randomUUID11()}-`));
33902
+ const staging = await mkdtemp3(join22(versions, `${STAGING_PREFIX}${randomUUID13()}-`));
33543
33903
  let failed = false;
33544
33904
  try {
33545
33905
  const archivePath = join22(staging, "download.zip");
@@ -33843,7 +34203,7 @@ var BoxAgentDriver = {
33843
34203
  const model = turn.model || MODELS.default;
33844
34204
  const prompt = [
33845
34205
  turn.system,
33846
- "You are working on your own cloud computer \u2014 its desktop, Chrome, and shell are yours.",
34206
+ "You are working on the assigned cloud computer \u2014 use its desktop, Chrome, and shell within the access described above.",
33847
34207
  "",
33848
34208
  turn.text
33849
34209
  ].filter((s3) => s3 !== void 0).join("\n");
@@ -33998,11 +34358,11 @@ var BoxAgentDriver = {
33998
34358
  };
33999
34359
 
34000
34360
  // server/drivers/codex.ts
34001
- import { existsSync as existsSync8 } from "node:fs";
34361
+ import { existsSync as existsSync9 } from "node:fs";
34002
34362
  import { homedir as homedir13 } from "node:os";
34003
34363
 
34004
34364
  // server/drivers/codex-catalog.ts
34005
- import { readdirSync as readdirSync3, readFileSync as readFileSync13 } from "node:fs";
34365
+ import { readdirSync as readdirSync3, readFileSync as readFileSync14 } from "node:fs";
34006
34366
  import { homedir as homedir10 } from "node:os";
34007
34367
  import { basename as basename5, join as join23 } from "node:path";
34008
34368
  var STATIC_CODEX_MODELS = {
@@ -34199,7 +34559,7 @@ function parseCodexToml(text8) {
34199
34559
  }
34200
34560
  function readText(path) {
34201
34561
  try {
34202
- return readFileSync13(path, "utf8");
34562
+ return readFileSync14(path, "utf8");
34203
34563
  } catch {
34204
34564
  return null;
34205
34565
  }
@@ -34335,7 +34695,7 @@ async function readCodexModelCatalog(env = process.env, fetchImpl = fetch, cli)
34335
34695
 
34336
34696
  // server/drivers/codex-instructions.ts
34337
34697
  import { createHash as createHash11 } from "node:crypto";
34338
- import { mkdirSync as mkdirSync7, readFileSync as readFileSync14 } from "node:fs";
34698
+ import { mkdirSync as mkdirSync7, readFileSync as readFileSync15 } from "node:fs";
34339
34699
  import { join as join24 } from "node:path";
34340
34700
  var digest = (value) => createHash11("sha256").update(value).digest("hex");
34341
34701
  function codexDeveloperInstructions(config2, botInstructions) {
@@ -34356,7 +34716,7 @@ async function syncCodexInstructions(key3, nativeThreadId, instructions, resumed
34356
34716
  const fingerprint = digest(instructions);
34357
34717
  let previous2;
34358
34718
  try {
34359
- previous2 = readFileSync14(path, "utf8");
34719
+ previous2 = readFileSync15(path, "utf8");
34360
34720
  } catch (error51) {
34361
34721
  if (error51.code !== "ENOENT") throw error51;
34362
34722
  }
@@ -34387,7 +34747,7 @@ async function syncCodexInstructions(key3, nativeThreadId, instructions, resumed
34387
34747
  }
34388
34748
 
34389
34749
  // server/drivers/codex-device-auth.ts
34390
- import { randomUUID as randomUUID12 } from "node:crypto";
34750
+ import { randomUUID as randomUUID14 } from "node:crypto";
34391
34751
  import { realpathSync as realpathSync4 } from "node:fs";
34392
34752
  import { homedir as homedir11 } from "node:os";
34393
34753
  import { basename as basename6, dirname as dirname9, isAbsolute as isAbsolute8, join as join25, resolve as resolve9 } from "node:path";
@@ -34452,7 +34812,7 @@ var CodexDeviceAuthController = class {
34452
34812
  const flow3 = {
34453
34813
  status: {
34454
34814
  phase: "waiting",
34455
- flowId: randomUUID12(),
34815
+ flowId: randomUUID14(),
34456
34816
  authorizationUrl: null,
34457
34817
  expiresAt: new Date(Date.now() + (this.options.lifetimeMs ?? 15 * 6e4)).toISOString()
34458
34818
  },
@@ -34802,7 +35162,7 @@ function codexPredatesAstra(version2) {
34802
35162
  function codexUpdateCommand(cli, platform = process.platform) {
34803
35163
  if (cli === "codex") return "codex update";
34804
35164
  const trimmed = cli.trim();
34805
- const tokens = trimmed.includes(" ") && existsSync8(trimmed) ? [trimmed] : splitCliString(trimmed);
35165
+ const tokens = trimmed.includes(" ") && existsSync9(trimmed) ? [trimmed] : splitCliString(trimmed);
34806
35166
  const quote = platform === "win32" ? (token) => `'${token.replaceAll("'", "''")}'` : (token) => `'${token.replaceAll("'", `'\\''`)}'`;
34807
35167
  const command = (tokens.length > 0 ? tokens : [trimmed]).map(quote).join(" ");
34808
35168
  return platform === "win32" ? `& ${command} update` : `${command} update`;
@@ -35378,7 +35738,8 @@ var CodexDriver = {
35378
35738
  itemType: "tool",
35379
35739
  itemId: item.id,
35380
35740
  title,
35381
- summary: item.type === "commandExecution" ? commandSummary({ command: item.command }) : void 0
35741
+ summary: item.type === "commandExecution" ? commandSummary({ command: item.command }) : void 0,
35742
+ input: toolDetailPreview(item.type === "commandExecution" ? { command: item.command, cwd: item.cwd } : item.type === "mcpToolCall" ? item.arguments : item.type === "fileChange" ? item.changes : item.query)
35382
35743
  });
35383
35744
  }
35384
35745
  break;
@@ -35411,7 +35772,8 @@ var CodexDriver = {
35411
35772
  type: "item.completed",
35412
35773
  itemType: "tool",
35413
35774
  itemId: item.id,
35414
- ok: item.status !== "failed" && item.status !== "declined"
35775
+ ok: item.status !== "failed" && item.status !== "declined",
35776
+ output: toolDetailPreview(item.type === "commandExecution" ? { output: item.aggregatedOutput, exitCode: item.exitCode } : item.type === "mcpToolCall" ? item.error ?? item.result : item.type === "fileChange" ? item.changes : item.action)
35415
35777
  });
35416
35778
  } else if (item.type === "reasoning") {
35417
35779
  emit({ ...base2(threadId, turnId), type: "item.updated", itemType: "reasoning", tokens: null });
@@ -35630,10 +35992,10 @@ var CodexDriver = {
35630
35992
  await startTurn2();
35631
35993
  }
35632
35994
  } catch (e) {
35633
- const failure3 = e instanceof Error ? e : { text: String(e) };
35995
+ const failure5 = e instanceof Error ? e : { text: String(e) };
35634
35996
  const message3 = e instanceof Error ? e.message : String(e);
35635
35997
  const needsAuth = /(?:\b401\b|unauthorized|missing bearer|authentication required)/i.test(message3);
35636
- const verdict = classifyError(failure3);
35998
+ const verdict = classifyError(failure5);
35637
35999
  if (!state.settled && !needsAuth && verdict.transient && attempt < RETRY_MAX_ATTEMPTS - 1 && state.sawStreamDelta === false) {
35638
36000
  const delayMs = computeBackoff(attempt);
35639
36001
  attempt++;
@@ -36041,7 +36403,7 @@ var GrokDriver = {
36041
36403
  };
36042
36404
 
36043
36405
  // server/drivers/acp/grok.ts
36044
- import { existsSync as existsSync9, readFileSync as readFileSync15, writeFileSync as writeFileSync7 } from "node:fs";
36406
+ import { existsSync as existsSync10, readFileSync as readFileSync16, writeFileSync as writeFileSync7 } from "node:fs";
36045
36407
  import { homedir as homedir14 } from "node:os";
36046
36408
  import { join as join27 } from "node:path";
36047
36409
  var STATIC_GROK_MODELS = {
@@ -36067,7 +36429,7 @@ function readGrokModelCatalog(env = process.env) {
36067
36429
  const path = join27(grokHome(env), "config.toml");
36068
36430
  let text8 = "";
36069
36431
  try {
36070
- text8 = readFileSync15(path, "utf8");
36432
+ text8 = readFileSync16(path, "utf8");
36071
36433
  } catch {
36072
36434
  return STATIC_GROK_MODELS;
36073
36435
  }
@@ -36140,7 +36502,7 @@ function ensureGrokInjectSlug(modelId, env = process.env) {
36140
36502
  const path = join27(grokHome(env), "config.toml");
36141
36503
  let text8 = "";
36142
36504
  try {
36143
- text8 = readFileSync15(path, "utf8");
36505
+ text8 = readFileSync16(path, "utf8");
36144
36506
  } catch {
36145
36507
  text8 = "";
36146
36508
  }
@@ -36269,7 +36631,7 @@ var support2 = {
36269
36631
  // an unauthenticated CLI is a user action, not something to paper over.
36270
36632
  pickAuthMethod: (methods) => methods.some((m2) => m2.id === "cached_token") ? "cached_token" : null,
36271
36633
  authFailure: "fail",
36272
- isAuthenticated: () => existsSync9(join27(homedir14(), ".grok", "auth.json")),
36634
+ isAuthenticated: () => existsSync10(join27(homedir14(), ".grok", "auth.json")),
36273
36635
  // `--append-system-prompt`/`--rules` are accepted by the CLI but do NOT
36274
36636
  // reach the agent-stdio system prompt (verified against 1.0.0), so the
36275
36637
  // persona is prepended codex-style.
@@ -36280,13 +36642,13 @@ ${turn.text}` : turn.text
36280
36642
  var GrokAgentDriver = createAcpDriver(support2);
36281
36643
 
36282
36644
  // server/drivers/acp/gemini.ts
36283
- import { existsSync as existsSync10, readFileSync as readFileSync16 } from "node:fs";
36645
+ import { existsSync as existsSync11, readFileSync as readFileSync17 } from "node:fs";
36284
36646
  import { homedir as homedir15 } from "node:os";
36285
36647
  import { join as join28 } from "node:path";
36286
36648
  var AUTH_PREFERENCE = ["gemini-api-key", "oauth-personal", "vertex-ai"];
36287
36649
  function liveOauthCredential() {
36288
36650
  try {
36289
- const creds = JSON.parse(readFileSync16(join28(homedir15(), ".gemini", "oauth_creds.json"), "utf8"));
36651
+ const creds = JSON.parse(readFileSync17(join28(homedir15(), ".gemini", "oauth_creds.json"), "utf8"));
36290
36652
  if (typeof creds.access_token !== "string" || creds.access_token.trim().length === 0) return false;
36291
36653
  const hasRefresh = typeof creds.refresh_token === "string" && creds.refresh_token.trim().length > 0;
36292
36654
  if (creds.expiry_date !== void 0) {
@@ -36300,7 +36662,7 @@ function liveOauthCredential() {
36300
36662
  }
36301
36663
  var nonBlank = (value) => Boolean(value?.trim());
36302
36664
  function geminiIsAuthenticated(env) {
36303
- return nonBlank(env.GEMINI_API_KEY) || nonBlank(env.GOOGLE_API_KEY) || existsSync10(join28(homedir15(), ".gemini", "oauth_creds.json")) && liveOauthCredential();
36665
+ return nonBlank(env.GEMINI_API_KEY) || nonBlank(env.GOOGLE_API_KEY) || existsSync11(join28(homedir15(), ".gemini", "oauth_creds.json")) && liveOauthCredential();
36304
36666
  }
36305
36667
  var support3 = {
36306
36668
  driverKind: "geminiAgent",
@@ -36333,7 +36695,7 @@ ${turn.text}` : turn.text
36333
36695
  var GeminiAgentDriver = createAcpDriver(support3);
36334
36696
 
36335
36697
  // server/drivers/acp/kimi.ts
36336
- import { existsSync as existsSync11, mkdirSync as mkdirSync8, readFileSync as readFileSync17, writeFileSync as writeFileSync8 } from "node:fs";
36698
+ import { existsSync as existsSync12, mkdirSync as mkdirSync8, readFileSync as readFileSync18, writeFileSync as writeFileSync8 } from "node:fs";
36337
36699
  import { homedir as homedir16 } from "node:os";
36338
36700
  import { join as join29 } from "node:path";
36339
36701
  var STATIC_KIMI_MODELS = {
@@ -36645,7 +37007,7 @@ function ensureKimiInjectAlias(modelId, env = process.env) {
36645
37007
  const path = join29(dataRoot, "config.toml");
36646
37008
  let text8 = "";
36647
37009
  try {
36648
- text8 = readFileSync17(path, "utf8");
37010
+ text8 = readFileSync18(path, "utf8");
36649
37011
  } catch {
36650
37012
  text8 = "";
36651
37013
  }
@@ -36714,7 +37076,7 @@ function readKimiModelCatalog(env) {
36714
37076
  const dataRoot = kimiDataRoot(env);
36715
37077
  let text8 = "";
36716
37078
  try {
36717
- text8 = readFileSync17(join29(dataRoot, "config.toml"), "utf8");
37079
+ text8 = readFileSync18(join29(dataRoot, "config.toml"), "utf8");
36718
37080
  } catch {
36719
37081
  return STATIC_KIMI_MODELS;
36720
37082
  }
@@ -36799,7 +37161,7 @@ var support4 = {
36799
37161
  authFailure: "continue",
36800
37162
  // Match the child CLI's own data-root precedence. A custom instance HOME or
36801
37163
  // KIMI_CODE_HOME must not be checked against the server user's home instead.
36802
- isAuthenticated: (env) => existsSync11(credentialsPath(env)),
37164
+ isAuthenticated: (env) => existsSync12(credentialsPath(env)),
36803
37165
  buildPromptText: (turn) => turn.system ? `${turn.system}
36804
37166
 
36805
37167
  ${turn.text}` : turn.text
@@ -36807,7 +37169,7 @@ ${turn.text}` : turn.text
36807
37169
  var KimiAgentDriver = createAcpDriver(support4);
36808
37170
 
36809
37171
  // server/drivers/acp/droid.ts
36810
- import { existsSync as existsSync12, mkdirSync as mkdirSync9, readFileSync as readFileSync18, writeFileSync as writeFileSync9 } from "node:fs";
37172
+ import { existsSync as existsSync13, mkdirSync as mkdirSync9, readFileSync as readFileSync19, writeFileSync as writeFileSync9 } from "node:fs";
36811
37173
  import { homedir as homedir17 } from "node:os";
36812
37174
  import { join as join30 } from "node:path";
36813
37175
  var AUTH_FILES = ["auth.v2.file", "auth.v2.loginkeychain", "auth.v2.keyring"];
@@ -36834,9 +37196,9 @@ function ensureDroidInjectModel(modelId, env = process.env) {
36834
37196
  const path = join30(dir, "settings.json");
36835
37197
  let settings = {};
36836
37198
  try {
36837
- settings = JSON.parse(readFileSync18(path, "utf8"));
37199
+ settings = JSON.parse(readFileSync19(path, "utf8"));
36838
37200
  } catch (error51) {
36839
- if (existsSync12(path)) throw error51;
37201
+ if (existsSync13(path)) throw error51;
36840
37202
  }
36841
37203
  const custom2 = Array.isArray(settings.customModels) ? [...settings.customModels] : [];
36842
37204
  const match = custom2.find(
@@ -36867,11 +37229,11 @@ function ensureDroidInjectModel(modelId, env = process.env) {
36867
37229
  function applyDroidLocalAuthEnv(env, modelId) {
36868
37230
  if (!decodeInjectId(modelId)) return;
36869
37231
  if (env.FACTORY_API_KEY?.trim()) return;
36870
- if (authFilePaths(env).some(existsSync12)) return;
37232
+ if (authFilePaths(env).some(existsSync13)) return;
36871
37233
  env.FACTORY_API_KEY = "openmausbot-local";
36872
37234
  }
36873
37235
  function readSettings(env) {
36874
- return JSON.parse(readFileSync18(join30(factoryHome(env), ".factory", "settings.json"), "utf8"));
37236
+ return JSON.parse(readFileSync19(join30(factoryHome(env), ".factory", "settings.json"), "utf8"));
36875
37237
  }
36876
37238
  async function resolveModels(env) {
36877
37239
  let catalog = MODELS3;
@@ -36950,7 +37312,7 @@ var support5 = {
36950
37312
  // The signed-in CLI is the primary source; FACTORY_API_KEY is the fallback
36951
37313
  // droid itself advertises, and it is checked last so an ambient key can
36952
37314
  // never be what makes an otherwise logged-out instance look ready.
36953
- isAuthenticated: (env) => authFilePaths(env).some(existsSync12) || Boolean(env.FACTORY_API_KEY),
37315
+ isAuthenticated: (env) => authFilePaths(env).some(existsSync13) || Boolean(env.FACTORY_API_KEY),
36954
37316
  resolveModels,
36955
37317
  resolveTurnModel: (model, env) => model ? ensureDroidInjectModel(model, env) : model,
36956
37318
  applyTurnEnv: (env, { requestedModel }) => {
@@ -37243,7 +37605,7 @@ function createCursorAgentDriver(run3 = execCli) {
37243
37605
  var CursorAgentDriver = createCursorAgentDriver();
37244
37606
 
37245
37607
  // server/drivers/acp/opencode-go.ts
37246
- import { existsSync as existsSync13, mkdirSync as mkdirSync10, readFileSync as readFileSync19, writeFileSync as writeFileSync10 } from "node:fs";
37608
+ import { existsSync as existsSync14, mkdirSync as mkdirSync10, readFileSync as readFileSync20, writeFileSync as writeFileSync10 } from "node:fs";
37247
37609
  import { homedir as homedir18 } from "node:os";
37248
37610
  import { join as join31 } from "node:path";
37249
37611
  var STATIC_MODELS = {
@@ -37382,9 +37744,9 @@ function ensureOpenCodeInjectModel(modelId, env = process.env) {
37382
37744
  mkdirSync10(dir, { recursive: true });
37383
37745
  const path = join31(dir, "opencode.json");
37384
37746
  let config2 = { $schema: "https://opencode.ai/config.json" };
37385
- if (existsSync13(path)) {
37747
+ if (existsSync14(path)) {
37386
37748
  try {
37387
- config2 = JSON.parse(readFileSync19(path, "utf8"));
37749
+ config2 = JSON.parse(readFileSync20(path, "utf8"));
37388
37750
  } catch {
37389
37751
  }
37390
37752
  }
@@ -37435,7 +37797,7 @@ function hasStoredOpenCodeAuth(env) {
37435
37797
  if (env.OPENCODE_AUTH_CONTENT) candidates.push(env.OPENCODE_AUTH_CONTENT);
37436
37798
  for (const path of storedAuthPaths(env)) {
37437
37799
  try {
37438
- candidates.push(readFileSync19(path, "utf8"));
37800
+ candidates.push(readFileSync20(path, "utf8"));
37439
37801
  } catch {
37440
37802
  }
37441
37803
  }
@@ -37518,7 +37880,7 @@ function createOpenCodeDriver(loadCatalog = discoverOpenCodeModels) {
37518
37880
  var OpenCodeDriver = createOpenCodeDriver();
37519
37881
 
37520
37882
  // server/drivers/acp/qwen.ts
37521
- import { chmodSync as chmodSync2, existsSync as existsSync14, mkdirSync as mkdirSync11, readFileSync as readFileSync20, writeFileSync as writeFileSync11 } from "node:fs";
37883
+ import { chmodSync as chmodSync2, existsSync as existsSync15, mkdirSync as mkdirSync11, readFileSync as readFileSync21, writeFileSync as writeFileSync11 } from "node:fs";
37522
37884
  import { createHash as createHash12 } from "node:crypto";
37523
37885
  import { homedir as homedir19 } from "node:os";
37524
37886
  import { join as join32 } from "node:path";
@@ -37538,7 +37900,7 @@ var PROTOCOLS = /* @__PURE__ */ new Set(["openai", "anthropic", "gemini", "verte
37538
37900
  function readQwenRoutes(env) {
37539
37901
  let settings;
37540
37902
  try {
37541
- settings = JSON.parse(readFileSync20(join32(qwenHome(env), "settings.json"), "utf8"));
37903
+ settings = JSON.parse(readFileSync21(join32(qwenHome(env), "settings.json"), "utf8"));
37542
37904
  } catch {
37543
37905
  return [];
37544
37906
  }
@@ -37624,9 +37986,9 @@ function ensureQwenInjectModel(modelId, env = process.env) {
37624
37986
  mkdirSync11(dir, { recursive: true });
37625
37987
  const path = join32(dir, "settings.json");
37626
37988
  let settings = {};
37627
- if (existsSync14(path)) {
37989
+ if (existsSync15(path)) {
37628
37990
  try {
37629
- settings = JSON.parse(readFileSync20(path, "utf8"));
37991
+ settings = JSON.parse(readFileSync21(path, "utf8"));
37630
37992
  } catch {
37631
37993
  }
37632
37994
  }
@@ -37761,7 +38123,7 @@ var CustomAcpDriver = {
37761
38123
 
37762
38124
  // server/drivers/acp/hermes.ts
37763
38125
  import { spawn as spawn2 } from "node:child_process";
37764
- import { mkdirSync as mkdirSync12, readFileSync as readFileSync21, writeFileSync as writeFileSync12 } from "node:fs";
38126
+ import { mkdirSync as mkdirSync12, readFileSync as readFileSync22, writeFileSync as writeFileSync12 } from "node:fs";
37765
38127
  import { homedir as homedir20 } from "node:os";
37766
38128
  import { join as join33 } from "node:path";
37767
38129
 
@@ -44112,7 +44474,7 @@ function ensureHermesInjectProvider(modelId, env = process.env) {
44112
44474
  const path = join33(dir, "config.yaml");
44113
44475
  let text8 = "";
44114
44476
  try {
44115
- text8 = readFileSync21(path, "utf8");
44477
+ text8 = readFileSync22(path, "utf8");
44116
44478
  } catch {
44117
44479
  text8 = "";
44118
44480
  }
@@ -44188,13 +44550,13 @@ function hermesConfiguredModel(env = process.env) {
44188
44550
  const dir = hermesHome(env);
44189
44551
  let secrets = "";
44190
44552
  try {
44191
- secrets = readFileSync21(join33(dir, ".env"), "utf8");
44553
+ secrets = readFileSync22(join33(dir, ".env"), "utf8");
44192
44554
  } catch {
44193
44555
  }
44194
44556
  const hasHostedProviderKey = HERMES_HOSTED_PROVIDER_KEYS.some((name2) => nonEmptyDotenvValue(secrets, name2));
44195
44557
  let configuredDefault = null;
44196
44558
  try {
44197
- configuredDefault = hermesConfigDefault(readFileSync21(join33(dir, "config.yaml"), "utf8"));
44559
+ configuredDefault = hermesConfigDefault(readFileSync22(join33(dir, "config.yaml"), "utf8"));
44198
44560
  } catch {
44199
44561
  }
44200
44562
  const configuredProvider = configuredDefault?.provider.toLowerCase() ?? "";
@@ -44503,7 +44865,7 @@ var OpenAICompatDriver = {
44503
44865
  };
44504
44866
 
44505
44867
  // server/drivers/pi.ts
44506
- import { chmodSync as chmodSync3, existsSync as existsSync15, mkdirSync as mkdirSync13, mkdtempSync as mkdtempSync2, readFileSync as readFileSync22, rmSync as rmSync2, writeFileSync as writeFileSync13 } from "node:fs";
44868
+ import { chmodSync as chmodSync3, existsSync as existsSync16, mkdirSync as mkdirSync13, mkdtempSync as mkdtempSync2, readFileSync as readFileSync23, rmSync as rmSync2, writeFileSync as writeFileSync13 } from "node:fs";
44507
44869
  import { homedir as homedir21, tmpdir as tmpdir4 } from "node:os";
44508
44870
  import { join as join34 } from "node:path";
44509
44871
  var DRIVER_KIND6 = "piAgent";
@@ -44513,7 +44875,7 @@ var NODE_ENV_FLAG2 = { ELECTRON_RUN_AS_NODE: "1" };
44513
44875
  function readPiPromptImages(turn) {
44514
44876
  return (turn.images ?? []).map((image) => ({
44515
44877
  type: "image",
44516
- data: readFileSync22(image.path).toString("base64"),
44878
+ data: readFileSync23(image.path).toString("base64"),
44517
44879
  mimeType: image.mime
44518
44880
  }));
44519
44881
  }
@@ -44629,9 +44991,9 @@ function ensurePiInjectModel(modelId, env = process.env) {
44629
44991
  mkdirSync13(dir, { recursive: true });
44630
44992
  const path = join34(dir, "models.json");
44631
44993
  let root = { providers: {} };
44632
- if (existsSync15(path)) {
44994
+ if (existsSync16(path)) {
44633
44995
  try {
44634
- const parsed = JSON.parse(readFileSync22(path, "utf8"));
44996
+ const parsed = JSON.parse(readFileSync23(path, "utf8"));
44635
44997
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
44636
44998
  root = parsed;
44637
44999
  } else {
@@ -44683,7 +45045,7 @@ function ensurePiInjectModel(modelId, env = process.env) {
44683
45045
  function readPiDefaultModel(env) {
44684
45046
  const home = env.HOME || env.USERPROFILE || homedir21();
44685
45047
  try {
44686
- const s3 = JSON.parse(readFileSync22(`${home}/.pi/agent/settings.json`, "utf8"));
45048
+ const s3 = JSON.parse(readFileSync23(`${home}/.pi/agent/settings.json`, "utf8"));
44687
45049
  return s3.defaultProvider && s3.defaultModel ? `${s3.defaultProvider}/${s3.defaultModel}` : "";
44688
45050
  } catch {
44689
45051
  return "";
@@ -44960,7 +45322,9 @@ var PiDriver = {
44960
45322
  type: "item.started",
44961
45323
  itemType: "tool",
44962
45324
  itemId: evt.toolCallId,
44963
- title: String(evt.toolName ?? "tool").slice(0, 80)
45325
+ title: String(evt.toolName ?? "tool").slice(0, 80),
45326
+ summary: commandSummary(evt.args),
45327
+ input: toolDetailPreview(evt.args)
44964
45328
  });
44965
45329
  return;
44966
45330
  }
@@ -44970,7 +45334,8 @@ var PiDriver = {
44970
45334
  type: "item.completed",
44971
45335
  itemType: "tool",
44972
45336
  itemId: evt.toolCallId,
44973
- ok: !evt.isError
45337
+ ok: !evt.isError,
45338
+ output: toolDetailPreview(evt.result)
44974
45339
  });
44975
45340
  return;
44976
45341
  }
@@ -45183,7 +45548,7 @@ ${turn.text}` : turn.text;
45183
45548
  };
45184
45549
 
45185
45550
  // server/drivers/minimax.ts
45186
- import { readFileSync as readFileSync23 } from "node:fs";
45551
+ import { readFileSync as readFileSync24 } from "node:fs";
45187
45552
  import { homedir as homedir22 } from "node:os";
45188
45553
  import { join as join35 } from "node:path";
45189
45554
  var DRIVER_KIND7 = "minimax";
@@ -45211,7 +45576,7 @@ function normalizedApiUrl(value) {
45211
45576
  }
45212
45577
  function loadLocalMiniMaxConfig(home = homedir22()) {
45213
45578
  try {
45214
- const raw = localConfigSchema.parse(JSON.parse(readFileSync23(join35(home, ".mmx", "config.json"), "utf8")));
45579
+ const raw = localConfigSchema.parse(JSON.parse(readFileSync24(join35(home, ".mmx", "config.json"), "utf8")));
45215
45580
  const configuredUrl = raw.base_url?.trim() ? raw.base_url : raw.region === "cn" ? CN_URL : DEFAULT_URL2;
45216
45581
  return {
45217
45582
  apiKey: raw.api_key?.trim() ?? "",
@@ -45302,11 +45667,11 @@ var BUILT_IN_DRIVERS = [
45302
45667
 
45303
45668
  // server/store.ts
45304
45669
  import { createHash as createHash14 } from "node:crypto";
45305
- import { existsSync as existsSync19, readFileSync as readFileSync27, mkdirSync as mkdirSync18, rmSync as rmSync4, unlinkSync as unlinkSync6 } from "node:fs";
45670
+ import { existsSync as existsSync20, readFileSync as readFileSync28, mkdirSync as mkdirSync18, rmSync as rmSync4, unlinkSync as unlinkSync6 } from "node:fs";
45306
45671
  import { join as join40 } from "node:path";
45307
45672
 
45308
45673
  // server/section-context.ts
45309
- import { existsSync as existsSync16, mkdirSync as mkdirSync14, readFileSync as readFileSync24 } from "node:fs";
45674
+ import { existsSync as existsSync17, mkdirSync as mkdirSync14, readFileSync as readFileSync25 } from "node:fs";
45310
45675
  import { join as join36 } from "node:path";
45311
45676
  var SECTION_CONTEXT_MAX_BYTES = 24e3;
45312
45677
  var SECTION_CONTEXTS_FILE = join36(DATA_DIR, "section-contexts.json");
@@ -45327,9 +45692,9 @@ function sectionContextLabel(section2) {
45327
45692
  return sectionContextKey(section2) || "General";
45328
45693
  }
45329
45694
  function loadFile(forWrite = false) {
45330
- if (!existsSync16(SECTION_CONTEXTS_FILE)) return emptyFile();
45695
+ if (!existsSync17(SECTION_CONTEXTS_FILE)) return emptyFile();
45331
45696
  try {
45332
- const input = JSON.parse(readFileSync24(SECTION_CONTEXTS_FILE, "utf8"));
45697
+ const input = JSON.parse(readFileSync25(SECTION_CONTEXTS_FILE, "utf8"));
45333
45698
  const candidate = sectionContextFileSchema.safeParse(input);
45334
45699
  if (!candidate.success) throw new Error("Invalid team instructions file");
45335
45700
  const contexts = /* @__PURE__ */ Object.create(null);
@@ -45487,7 +45852,7 @@ function peerAllowKey(action, targetId) {
45487
45852
  }
45488
45853
 
45489
45854
  // server/message-db.ts
45490
- import { chmodSync as chmodSync4, closeSync as closeSync6, existsSync as existsSync17, mkdirSync as mkdirSync16, openSync as openSync6, readFileSync as readFileSync25, renameSync as renameSync3 } from "node:fs";
45855
+ import { chmodSync as chmodSync4, closeSync as closeSync6, existsSync as existsSync18, mkdirSync as mkdirSync16, openSync as openSync6, readFileSync as readFileSync26, renameSync as renameSync3 } from "node:fs";
45491
45856
  import { join as join38 } from "node:path";
45492
45857
  import { DatabaseSync } from "node:sqlite";
45493
45858
 
@@ -45615,7 +45980,7 @@ function ensureRecallIndex(db2) {
45615
45980
  }
45616
45981
  var UPSERT_MESSAGE = "INSERT INTO messages (thread_id, id, at, role, kind, text, json) VALUES (?, ?, ?, ?, ?, ?, ?) ON CONFLICT(thread_id, id) DO UPDATE SET at = excluded.at, role = excluded.role, kind = excluded.kind, text = excluded.text, json = excluded.json";
45617
45982
  function db() {
45618
- if (handle && handlePath === DB_FILE() && existsSync17(DB_FILE())) return handle;
45983
+ if (handle && handlePath === DB_FILE() && existsSync18(DB_FILE())) return handle;
45619
45984
  try {
45620
45985
  handle?.close();
45621
45986
  } catch {
@@ -45696,7 +46061,7 @@ function importLegacy(threadId, legacyFile) {
45696
46061
  let activeLeafId = null;
45697
46062
  let raw;
45698
46063
  try {
45699
- raw = JSON.parse(readFileSync25(legacyFile, "utf8"));
46064
+ raw = JSON.parse(readFileSync26(legacyFile, "utf8"));
45700
46065
  } catch {
45701
46066
  return { messages, activeLeafId };
45702
46067
  }
@@ -45891,7 +46256,7 @@ function closeMessageDb() {
45891
46256
  }
45892
46257
 
45893
46258
  // server/workspace.ts
45894
- import { existsSync as existsSync18, mkdirSync as mkdirSync17, readdirSync as readdirSync4, readFileSync as readFileSync26, statSync as statSync5 } from "node:fs";
46259
+ import { existsSync as existsSync19, mkdirSync as mkdirSync17, readdirSync as readdirSync4, readFileSync as readFileSync27, statSync as statSync5 } from "node:fs";
45895
46260
  import { join as join39 } from "node:path";
45896
46261
  var WORKSPACES_DIR = join39(DATA_DIR, "workspaces");
45897
46262
  var TASK_WORKSPACES_DIR = join39(DATA_DIR, "task-workspaces");
@@ -45915,7 +46280,7 @@ function ensureWorkspace(botId) {
45915
46280
  const dir = join39(WORKSPACES_DIR, botId);
45916
46281
  mkdirSync17(join39(dir, "memory"), { recursive: true, mode: 448 });
45917
46282
  const memoryFile = join39(dir, "MEMORY.md");
45918
- if (!existsSync18(memoryFile)) writeFileAtomic(memoryFile, MEMORY_SEED, { mode: 384 });
46283
+ if (!existsSync19(memoryFile)) writeFileAtomic(memoryFile, MEMORY_SEED, { mode: 384 });
45919
46284
  return dir;
45920
46285
  }
45921
46286
  function workspaceDir(botId) {
@@ -45938,7 +46303,7 @@ function memoryOverBudget(text8) {
45938
46303
  function loadMemory(botId) {
45939
46304
  let raw;
45940
46305
  try {
45941
- raw = readFileSync26(join39(workspaceDir(botId), "MEMORY.md"), "utf8");
46306
+ raw = readFileSync27(join39(workspaceDir(botId), "MEMORY.md"), "utf8");
45942
46307
  } catch {
45943
46308
  return null;
45944
46309
  }
@@ -45961,7 +46326,7 @@ var MEMORY_FILE_MAX_BYTES = 256 * 1024;
45961
46326
  function readMemoryFile(botId) {
45962
46327
  let raw;
45963
46328
  try {
45964
- raw = readFileSync26(join39(workspaceDir(botId), "MEMORY.md"), "utf8");
46329
+ raw = readFileSync27(join39(workspaceDir(botId), "MEMORY.md"), "utf8");
45965
46330
  } catch {
45966
46331
  return { text: "", truncated: false };
45967
46332
  }
@@ -45977,7 +46342,7 @@ function indexWrittenMemoryFile(botId, relativePath) {
45977
46342
  try {
45978
46343
  const path = join39(workspaceDir(botId), relativePath);
45979
46344
  const stat6 = statSync5(path);
45980
- indexMemoryFile(botId, relativePath, readFileSync26(path, "utf8"), { mtimeMs: stat6.mtimeMs, bytes: stat6.size });
46345
+ indexMemoryFile(botId, relativePath, readFileSync27(path, "utf8"), { mtimeMs: stat6.mtimeMs, bytes: stat6.size });
45981
46346
  } catch {
45982
46347
  }
45983
46348
  }
@@ -46005,7 +46370,7 @@ function syncMemoryIndex(botId) {
46005
46370
  indexed.delete(file2.path);
46006
46371
  if (known && known.bytes === file2.bytes && known.mtimeMs === Math.trunc(file2.mtimeMs)) continue;
46007
46372
  try {
46008
- const text8 = readFileSync26(join39(workspaceDir(botId), file2.path), "utf8");
46373
+ const text8 = readFileSync27(join39(workspaceDir(botId), file2.path), "utf8");
46009
46374
  indexMemoryFile(botId, file2.path, file2.path === "MEMORY.md" && text8 === MEMORY_SEED ? "" : text8, file2);
46010
46375
  } catch {
46011
46376
  }
@@ -46066,7 +46431,7 @@ function updateMemory(botId, update, opts = {}) {
46066
46431
  }
46067
46432
  const text8 = update.text === void 0 ? void 0 : redactSecretsInText(update.text);
46068
46433
  const dir = ensureWorkspace(botId);
46069
- const raw = readFileSync26(join39(dir, "MEMORY.md"), "utf8");
46434
+ const raw = readFileSync27(join39(dir, "MEMORY.md"), "utf8");
46070
46435
  const current2 = raw === MEMORY_SEED ? "" : raw;
46071
46436
  const today = memoryDate(opts.now);
46072
46437
  const appendEntry = (base2, entry2) => `${base2}${base2 && !base2.endsWith("\n") ? "\n" : ""}${entry2}
@@ -46144,7 +46509,7 @@ function appendMemoryLog(botId, text8, opts = {}) {
46144
46509
  const path = join39(dir, file2);
46145
46510
  let current2 = "";
46146
46511
  try {
46147
- current2 = readFileSync26(path, "utf8");
46512
+ current2 = readFileSync27(path, "utf8");
46148
46513
  } catch {
46149
46514
  }
46150
46515
  writeFileAtomic(path, `${current2}${current2 && !current2.endsWith("\n") ? "\n" : ""}${line}
@@ -46170,7 +46535,7 @@ function listMemoryLogs(botId) {
46170
46535
  function readMemoryLog(botId, name2) {
46171
46536
  if (!LOG_FILE_NAME.test(name2)) return null;
46172
46537
  try {
46173
- return readFileSync26(join39(workspaceDir(botId), "memory", MEMORY_LOG_DIR, name2), "utf8");
46538
+ return readFileSync27(join39(workspaceDir(botId), "memory", MEMORY_LOG_DIR, name2), "utf8");
46174
46539
  } catch {
46175
46540
  return null;
46176
46541
  }
@@ -46204,7 +46569,7 @@ function writeMemoryTopic(botId, name2, text8) {
46204
46569
  function readMemoryTopic(botId, name2) {
46205
46570
  if (!isMemoryTopicName(name2)) return null;
46206
46571
  try {
46207
- return readFileSync26(join39(workspaceDir(botId), "memory", name2), "utf8");
46572
+ return readFileSync27(join39(workspaceDir(botId), "memory", name2), "utf8");
46208
46573
  } catch {
46209
46574
  return null;
46210
46575
  }
@@ -46519,12 +46884,12 @@ var Store = class {
46519
46884
  this.defaultSelection = defaultSelection2;
46520
46885
  mkdirSync18(DATA_DIR, { recursive: true });
46521
46886
  try {
46522
- this.bots = JSON.parse(readFileSync27(BOTS_FILE, "utf8"));
46887
+ this.bots = JSON.parse(readFileSync28(BOTS_FILE, "utf8"));
46523
46888
  } catch {
46524
46889
  this.bots = [];
46525
46890
  }
46526
46891
  try {
46527
- this.groups = JSON.parse(readFileSync27(GROUPS_FILE, "utf8"));
46892
+ this.groups = JSON.parse(readFileSync28(GROUPS_FILE, "utf8"));
46528
46893
  } catch {
46529
46894
  this.groups = [];
46530
46895
  }
@@ -46545,7 +46910,7 @@ var Store = class {
46545
46910
  b2.soulHash = soulHash(b2.soul);
46546
46911
  botsMigrated = true;
46547
46912
  }
46548
- if (!existsSync19(soulFile(b2.id))) {
46913
+ if (!existsSync20(soulFile(b2.id))) {
46549
46914
  try {
46550
46915
  writeSoulMirror(b2.id, b2.soul);
46551
46916
  } catch (e) {
@@ -46576,15 +46941,25 @@ var Store = class {
46576
46941
  botsMigrated = true;
46577
46942
  }
46578
46943
  if (b2.approvalGrant !== void 0) {
46579
- b2.approvalMode = "ask";
46580
- b2.autoApprove = false;
46581
- delete b2.approvalGrant;
46582
- for (const task2 of b2.tasks ?? []) {
46583
- if (task2.approvalMode === "full" || task2.approvalMode === "custom") {
46584
- task2.approvalMode = "ask";
46585
- task2.autoApprove = false;
46944
+ const threadOnly = b2.approvalGrant.threadOnly === true;
46945
+ if (threadOnly) {
46946
+ const target = b2.tasks?.find((task2) => task2.threadId === b2.approvalGrant?.threadId);
46947
+ if (target) {
46948
+ target.approvalMode = "ask";
46949
+ target.autoApprove = false;
46586
46950
  }
46587
46951
  }
46952
+ if (!threadOnly) {
46953
+ b2.approvalMode = "ask";
46954
+ b2.autoApprove = false;
46955
+ for (const task2 of b2.tasks ?? []) {
46956
+ if (task2.approvalMode === "full" || task2.approvalMode === "custom") {
46957
+ task2.approvalMode = "ask";
46958
+ task2.autoApprove = false;
46959
+ }
46960
+ }
46961
+ }
46962
+ delete b2.approvalGrant;
46588
46963
  botsMigrated = true;
46589
46964
  }
46590
46965
  const avatar = botAvatarProfile(b2);
@@ -46727,7 +47102,7 @@ var Store = class {
46727
47102
  ]);
46728
47103
  for (const threadId of knownThreads) {
46729
47104
  const legacyFile = messagesFile(threadId);
46730
- if (existsSync19(legacyFile)) readThread(threadId, legacyFile);
47105
+ if (existsSync20(legacyFile)) readThread(threadId, legacyFile);
46731
47106
  }
46732
47107
  this.registeringInitialSections = false;
46733
47108
  }
@@ -47683,6 +48058,7 @@ var Store = class {
47683
48058
  return {
47684
48059
  ...bot,
47685
48060
  threadId: task2.threadId,
48061
+ approvalGrant: bot.approvalGrant?.threadOnly && bot.approvalGrant.threadId !== threadId ? void 0 : bot.approvalGrant,
47686
48062
  modelSelection: structuredClone(task2.modelSelection ?? bot.modelSelection),
47687
48063
  resumeCursors: structuredClone(task2.resumeCursors),
47688
48064
  approvalMode: task2.approvalMode ?? (task2.autoApprove === void 0 ? bot.approvalMode : void 0),
@@ -47976,7 +48352,7 @@ ${message3.text}`;
47976
48352
  }
47977
48353
 
47978
48354
  // server/delegations.ts
47979
- import { readFileSync as readFileSync28 } from "node:fs";
48355
+ import { readFileSync as readFileSync29 } from "node:fs";
47980
48356
  import { join as join41 } from "node:path";
47981
48357
 
47982
48358
  // server/peer-approval.ts
@@ -48187,7 +48563,7 @@ function _loadPending() {
48187
48563
  pendingDelegations.clear();
48188
48564
  let backfilled = false;
48189
48565
  try {
48190
- const raw = JSON.parse(readFileSync28(DELEGATIONS_FILE, "utf8"));
48566
+ const raw = JSON.parse(readFileSync29(DELEGATIONS_FILE, "utf8"));
48191
48567
  const now = Date.now();
48192
48568
  for (const [threadId, list3] of Object.entries(raw)) {
48193
48569
  if (!Array.isArray(list3)) continue;
@@ -48226,7 +48602,7 @@ function _loadPending() {
48226
48602
  if (backfilled) savePending();
48227
48603
  receipts = [];
48228
48604
  try {
48229
- const rawReceipts = JSON.parse(readFileSync28(RECEIPTS_FILE, "utf8"));
48605
+ const rawReceipts = JSON.parse(readFileSync29(RECEIPTS_FILE, "utf8"));
48230
48606
  if (Array.isArray(rawReceipts)) {
48231
48607
  const now = Date.now();
48232
48608
  const loaded = [];
@@ -48731,7 +49107,8 @@ function queueSteeredMessage(botId, threadId, text8, options = {}) {
48731
49107
  replyToId: options.replyToId,
48732
49108
  sendId: options.sendId,
48733
49109
  reason: options.reason,
48734
- unattended: options.unattended
49110
+ unattended: options.unattended,
49111
+ peerAsk: options.peerAsk
48735
49112
  };
48736
49113
  saveChatFollowup({ id, kind: "bot", ownerId: botId, threadId, payload: item });
48737
49114
  entry.items.push(item);
@@ -48771,7 +49148,8 @@ function drainSteeredMessages(store2, run3, isBlocked) {
48771
49148
  text: item.text,
48772
49149
  replyToId: item.replyToId,
48773
49150
  sendId: item.sendId,
48774
- queueId: item.messageId
49151
+ queueId: item.messageId,
49152
+ peerAsk: item.peerAsk
48775
49153
  })
48776
49154
  );
48777
49155
  }
@@ -49657,7 +50035,7 @@ function buildTurnContext(input) {
49657
50035
  }
49658
50036
 
49659
50037
  // server/turn-images.ts
49660
- import { lstatSync as lstatSync2, realpathSync as realpathSync5, statSync as statSync6 } from "node:fs";
50038
+ import { lstatSync as lstatSync3, realpathSync as realpathSync5, statSync as statSync6 } from "node:fs";
49661
50039
  import { dirname as dirname10, extname as extname4, resolve as resolve11 } from "node:path";
49662
50040
  var TURN_IMAGE_MAX_COUNT = 4;
49663
50041
  var TURN_IMAGE_MAX_BYTES = TURN_IMAGE_MAX_COUNT * IMAGE_MAX_BYTES2;
@@ -49720,7 +50098,7 @@ function ownedImage(path, attachmentsDir) {
49720
50098
  const expected = resolve11(attachmentsDir, name2);
49721
50099
  if (resolve11(path) !== expected) return null;
49722
50100
  try {
49723
- const entry = lstatSync2(expected);
50101
+ const entry = lstatSync3(expected);
49724
50102
  if (!entry.isFile() || entry.isSymbolicLink() || entry.size <= 0 || entry.size > IMAGE_MAX_BYTES2) return null;
49725
50103
  const canonicalRoot = realpathSync5(attachmentsDir);
49726
50104
  const canonicalFile = realpathSync5(expected);
@@ -49857,7 +50235,7 @@ function overlaps(a, b2) {
49857
50235
  // server/memory-store.ts
49858
50236
  import { spawn as spawn3 } from "node:child_process";
49859
50237
  import { createHash as createHash15 } from "node:crypto";
49860
- import { existsSync as existsSync20, lstatSync as lstatSync3, mkdirSync as mkdirSync19, readdirSync as readdirSync5, readFileSync as readFileSync29, realpathSync as realpathSync7, unlinkSync as unlinkSync7 } from "node:fs";
50238
+ import { existsSync as existsSync21, lstatSync as lstatSync4, mkdirSync as mkdirSync19, readdirSync as readdirSync5, readFileSync as readFileSync30, realpathSync as realpathSync7, unlinkSync as unlinkSync7 } from "node:fs";
49861
50239
  import { dirname as dirname11, join as join44, sep as sep3 } from "node:path";
49862
50240
  var MEMORY_INDEX = "MEMORY.md";
49863
50241
  var MemoryStoreError = class extends Error {
@@ -49912,13 +50290,13 @@ function resolveMemoryPath(botId, path) {
49912
50290
  return absolute;
49913
50291
  }
49914
50292
  let ancestor = dirname11(absolute);
49915
- while (!existsSync20(ancestor) && ancestor !== root) ancestor = dirname11(ancestor);
50293
+ while (!existsSync21(ancestor) && ancestor !== root) ancestor = dirname11(ancestor);
49916
50294
  const realAncestor = realpathSync7(ancestor);
49917
50295
  if (realAncestor !== realRoot && !realAncestor.startsWith(realRoot + sep3)) {
49918
50296
  throw new MemoryStoreError("path", 400, `memory path escapes the workspace: ${JSON.stringify(path)}`);
49919
50297
  }
49920
50298
  try {
49921
- if (lstatSync3(absolute).isSymbolicLink()) {
50299
+ if (lstatSync4(absolute).isSymbolicLink()) {
49922
50300
  throw new MemoryStoreError("path", 400, `memory file is a link, not a file: ${JSON.stringify(path)}`);
49923
50301
  }
49924
50302
  } catch (error51) {
@@ -49963,7 +50341,7 @@ function listMarkdown(dir, prefix) {
49963
50341
  for (const name2 of names) {
49964
50342
  if (!isMemoryTopicName(name2)) continue;
49965
50343
  try {
49966
- const info = lstatSync3(join44(dir, name2));
50344
+ const info = lstatSync4(join44(dir, name2));
49967
50345
  if (!info.isFile()) continue;
49968
50346
  files.push({ path: `${prefix}/${name2}`, name: name2, bytes: info.size, modifiedAt: Math.round(info.mtimeMs) });
49969
50347
  } catch {
@@ -49976,7 +50354,7 @@ function memoryOverview(botId) {
49976
50354
  const root = workspaceDir(botId);
49977
50355
  let raw = "";
49978
50356
  try {
49979
- raw = readFileSync29(resolveMemoryPath(botId, MEMORY_INDEX), "utf8");
50357
+ raw = readFileSync30(resolveMemoryPath(botId, MEMORY_INDEX), "utf8");
49980
50358
  } catch (error51) {
49981
50359
  if (error51 instanceof MemoryStoreError) throw error51;
49982
50360
  raw = "";
@@ -49992,7 +50370,7 @@ function memoryOverview(botId) {
49992
50370
  function readMemoryDoc(botId, path) {
49993
50371
  const absolute = resolveMemoryPath(botId, path);
49994
50372
  try {
49995
- const text8 = readFileSync29(absolute, "utf8");
50373
+ const text8 = readFileSync30(absolute, "utf8");
49996
50374
  return { path, text: text8, hash: hashMemoryText(text8), exists: true };
49997
50375
  } catch {
49998
50376
  return { path, text: "", hash: hashMemoryText(""), exists: false };
@@ -50082,7 +50460,7 @@ async function openMemoryLocation(botId, target, platform = process.platform, ru
50082
50460
  }
50083
50461
 
50084
50462
  // server/memory-journal.ts
50085
- import { randomUUID as randomUUID13 } from "node:crypto";
50463
+ import { randomUUID as randomUUID15 } from "node:crypto";
50086
50464
  import { closeSync as closeSync7, fstatSync as fstatSync2, mkdirSync as mkdirSync20, openSync as openSync7, readSync as readSync3 } from "node:fs";
50087
50465
  import { appendFile as appendFile3 } from "node:fs/promises";
50088
50466
  import { join as join45 } from "node:path";
@@ -50280,7 +50658,7 @@ function recordMemoryChange(botId, change) {
50280
50658
  }
50281
50659
  }
50282
50660
  const entry = {
50283
- id: randomUUID13(),
50661
+ id: randomUUID15(),
50284
50662
  at: Date.now(),
50285
50663
  botId,
50286
50664
  path: change.path,
@@ -50396,17 +50774,17 @@ function endMemoryTurn(threadId) {
50396
50774
  }
50397
50775
 
50398
50776
  // server/skills.ts
50399
- import { createHash as createHash16, randomUUID as randomUUID14 } from "node:crypto";
50777
+ import { createHash as createHash16, randomUUID as randomUUID16 } from "node:crypto";
50400
50778
  import {
50401
50779
  closeSync as closeSync8,
50402
50780
  constants as constants4,
50403
- existsSync as existsSync21,
50781
+ existsSync as existsSync22,
50404
50782
  fstatSync as fstatSync3,
50405
- lstatSync as lstatSync4,
50783
+ lstatSync as lstatSync5,
50406
50784
  mkdirSync as mkdirSync21,
50407
50785
  openSync as openSync8,
50408
50786
  readdirSync as readdirSync6,
50409
- readFileSync as readFileSync30,
50787
+ readFileSync as readFileSync31,
50410
50788
  readlinkSync,
50411
50789
  realpathSync as realpathSync8,
50412
50790
  renameSync as renameSync4,
@@ -50550,7 +50928,7 @@ function skillsDir(botId) {
50550
50928
  }
50551
50929
  function directoryEntryState(path) {
50552
50930
  try {
50553
- const stat6 = lstatSync4(path);
50931
+ const stat6 = lstatSync5(path);
50554
50932
  return stat6.isDirectory() && !stat6.isSymbolicLink() ? "directory" : "unsafe";
50555
50933
  } catch {
50556
50934
  return "missing";
@@ -50593,7 +50971,7 @@ function skillTarget(root, name2, entry) {
50593
50971
  }
50594
50972
  function entryExistsWithoutFollowing(path) {
50595
50973
  try {
50596
- lstatSync4(path);
50974
+ lstatSync5(path);
50597
50975
  return true;
50598
50976
  } catch {
50599
50977
  return false;
@@ -50636,7 +51014,7 @@ function managedLinksPath(botId) {
50636
51014
  }
50637
51015
  function readManagedLinks(botId) {
50638
51016
  try {
50639
- const parsed = JSON.parse(readFileSync30(managedLinksPath(botId), "utf8"));
51017
+ const parsed = JSON.parse(readFileSync31(managedLinksPath(botId), "utf8"));
50640
51018
  const result = managedLinksSchema.safeParse(parsed);
50641
51019
  return result.success ? result.data.filter(isSkillName) : [];
50642
51020
  } catch {
@@ -50650,7 +51028,7 @@ function writeManagedLinks(botId, names) {
50650
51028
  }
50651
51029
  function manifestFromFile(path) {
50652
51030
  try {
50653
- const parsed = JSON.parse(readFileSync30(path, "utf8"));
51031
+ const parsed = JSON.parse(readFileSync31(path, "utf8"));
50654
51032
  const result = skillManifestSchema.safeParse(parsed);
50655
51033
  if (!result.success) return null;
50656
51034
  const manifest = {};
@@ -50664,11 +51042,11 @@ function manifestFromFile(path) {
50664
51042
  }
50665
51043
  function readManifest(botId) {
50666
51044
  const securePath = manifestPath(botId);
50667
- if (existsSync21(securePath)) return manifestFromFile(securePath) ?? {};
51045
+ if (existsSync22(securePath)) return manifestFromFile(securePath) ?? {};
50668
51046
  const legacyRoot = existingSkillsRoot(botId);
50669
51047
  if (!legacyRoot) return {};
50670
51048
  const legacyPath = join46(legacyRoot, "skills.json");
50671
- if (!existsSync21(legacyPath)) return {};
51049
+ if (!existsSync22(legacyPath)) return {};
50672
51050
  const legacy = manifestFromFile(legacyPath) ?? {};
50673
51051
  const migrated = {};
50674
51052
  for (const [name2, entry] of Object.entries(legacy)) {
@@ -50692,7 +51070,7 @@ function comparablePath(path) {
50692
51070
  }
50693
51071
  function nativeLinkPointsToSkill(link2, target) {
50694
51072
  try {
50695
- if (!lstatSync4(link2).isSymbolicLink()) return false;
51073
+ if (!lstatSync5(link2).isSymbolicLink()) return false;
50696
51074
  try {
50697
51075
  return comparablePath(realpathSync8(link2)) === comparablePath(realpathSync8(target));
50698
51076
  } catch {
@@ -50706,7 +51084,7 @@ function nativeLinkPointsToSkill(link2, target) {
50706
51084
  }
50707
51085
  function nativeLinkDirectlyTargetsOwnedSkill(link2, root, name2, revisionWasManaged, targetBaseDirectory = dirname12(link2)) {
50708
51086
  try {
50709
- if (!lstatSync4(link2).isSymbolicLink()) return false;
51087
+ if (!lstatSync5(link2).isSymbolicLink()) return false;
50710
51088
  const rawTarget = readlinkSync(link2);
50711
51089
  const resolvedTarget = resolve13(targetBaseDirectory, rawTarget.replace(/^\\\\\?\\/, ""));
50712
51090
  const insideSkills = relative4(join46(root, "skills"), resolvedTarget).replaceAll("\\", "/");
@@ -50722,7 +51100,7 @@ function removeOwnedNativeLink(botId, link2, root, name2, revisionWasManaged, be
50722
51100
  } catch {
50723
51101
  return "retry";
50724
51102
  }
50725
- const quarantined = join46(quarantineDir, randomUUID14());
51103
+ const quarantined = join46(quarantineDir, randomUUID16());
50726
51104
  beforeRemove?.(link2);
50727
51105
  try {
50728
51106
  renameSync4(link2, quarantined);
@@ -50860,8 +51238,8 @@ function skillContentMatches(botId, name2, entry) {
50860
51238
  const directory = skillDirectory(botId, name2, entry);
50861
51239
  if (!directory) return false;
50862
51240
  const file2 = join46(directory, "SKILL.md");
50863
- if (!lstatSync4(file2).isFile()) return false;
50864
- return createHash16("sha256").update(readFileSync30(file2)).digest("hex") === entry.sha256;
51241
+ if (!lstatSync5(file2).isFile()) return false;
51242
+ return createHash16("sha256").update(readFileSync31(file2)).digest("hex") === entry.sha256;
50865
51243
  } catch {
50866
51244
  return false;
50867
51245
  }
@@ -50890,12 +51268,12 @@ function readSkillFile(botId, name2) {
50890
51268
  let descriptor = null;
50891
51269
  try {
50892
51270
  const path = join46(directory, "SKILL.md");
50893
- const before = lstatSync4(path);
51271
+ const before = lstatSync5(path);
50894
51272
  if (!before.isFile() || before.size > SKILL_FILE_MAX_BYTES) return null;
50895
51273
  descriptor = openSync8(path, constants4.O_RDONLY | (process.platform === "win32" ? 0 : constants4.O_NOFOLLOW ?? 0));
50896
51274
  const opened = fstatSync3(descriptor);
50897
51275
  if (!opened.isFile() || opened.size > SKILL_FILE_MAX_BYTES || opened.dev !== before.dev || opened.ino !== before.ino) return null;
50898
- const text8 = readFileSync30(descriptor, "utf8");
51276
+ const text8 = readFileSync31(descriptor, "utf8");
50899
51277
  return createHash16("sha256").update(text8).digest("hex") === entry.sha256 ? text8 : null;
50900
51278
  } catch {
50901
51279
  return null;
@@ -50966,9 +51344,9 @@ function removeReviewedRevisionsNamed(botId, name2) {
50966
51344
  if (directoryEntryState(directory) !== "directory") continue;
50967
51345
  try {
50968
51346
  const file2 = join46(directory, "SKILL.md");
50969
- const stat6 = lstatSync4(file2);
51347
+ const stat6 = lstatSync5(file2);
50970
51348
  if (!stat6.isFile() || stat6.size > SKILL_FILE_MAX_BYTES) continue;
50971
- const parsed = parseSkillMd(readFileSync30(file2, "utf8"));
51349
+ const parsed = parseSkillMd(readFileSync31(file2, "utf8"));
50972
51350
  if ("error" in parsed || parsed.name !== name2) continue;
50973
51351
  rmSync5(directory, { recursive: true, force: true });
50974
51352
  } catch {
@@ -51021,9 +51399,9 @@ function stagedPath(botId) {
51021
51399
  }
51022
51400
  function readStaged(botId) {
51023
51401
  const securePath = stagedPath(botId);
51024
- if (existsSync21(securePath)) {
51402
+ if (existsSync22(securePath)) {
51025
51403
  try {
51026
- const parsed = JSON.parse(readFileSync30(securePath, "utf8"));
51404
+ const parsed = JSON.parse(readFileSync31(securePath, "utf8"));
51027
51405
  const result = stagedStoreSchema.safeParse(parsed);
51028
51406
  if (result.success) return result.data;
51029
51407
  } catch {
@@ -51033,7 +51411,7 @@ function readStaged(botId) {
51033
51411
  const legacyRoot = existingSkillsRoot(botId);
51034
51412
  if (!legacyRoot) return { writes: {} };
51035
51413
  const legacyPath = join46(legacyRoot, "staged.json");
51036
- if (!existsSync21(legacyPath)) return { writes: {} };
51414
+ if (!existsSync22(legacyPath)) return { writes: {} };
51037
51415
  const empty = { writes: {} };
51038
51416
  writeStaged(botId, empty);
51039
51417
  try {
@@ -51082,8 +51460,8 @@ function learnedSkillDirectoryMatches(directory, sha2564) {
51082
51460
  const entries = readdirSync6(directory);
51083
51461
  if (entries.length !== 1 || entries[0] !== "SKILL.md") return false;
51084
51462
  const file2 = join46(directory, "SKILL.md");
51085
- if (!lstatSync4(file2).isFile()) return false;
51086
- return createHash16("sha256").update(readFileSync30(file2)).digest("hex") === sha2564;
51463
+ if (!lstatSync5(file2).isFile()) return false;
51464
+ return createHash16("sha256").update(readFileSync31(file2)).digest("hex") === sha2564;
51087
51465
  } catch {
51088
51466
  return false;
51089
51467
  }
@@ -51094,7 +51472,7 @@ function installedLearnedSkillMatches(botId, name2, entry) {
51094
51472
  }
51095
51473
  function directoryIdentity2(path) {
51096
51474
  try {
51097
- const stat6 = lstatSync4(path);
51475
+ const stat6 = lstatSync5(path);
51098
51476
  return stat6.isDirectory() && !stat6.isSymbolicLink() ? { dev: stat6.dev, ino: stat6.ino } : null;
51099
51477
  } catch {
51100
51478
  return null;
@@ -51125,7 +51503,7 @@ function publishReviewedRevision(botId, stageId, skillMd, sha2564) {
51125
51503
  }
51126
51504
  rmSync5(prepared, { recursive: true, force: true });
51127
51505
  }
51128
- const temporary = join46(preparedRoot, `.prepare-${revision}-${randomUUID14()}`);
51506
+ const temporary = join46(preparedRoot, `.prepare-${revision}-${randomUUID16()}`);
51129
51507
  try {
51130
51508
  mkdirSync21(temporary, { mode: 448 });
51131
51509
  writeFileAtomic(join46(temporary, "SKILL.md"), skillMd, { mode: 384 });
@@ -51172,7 +51550,7 @@ function commitNewSkillFiles(botId, name2, files, commitManifest) {
51172
51550
  const root = ensureSkillsRoot(botId);
51173
51551
  if (!root) throw new Error("the workspace skills path must be a real directory, not a symlink or file");
51174
51552
  const target = join46(root, name2);
51175
- const staged = join46(root, `.install-${name2}-${randomUUID14()}`);
51553
+ const staged = join46(root, `.install-${name2}-${randomUUID16()}`);
51176
51554
  if (entryExistsWithoutFollowing(target)) throw new Error(`skill path already exists: ${name2}`);
51177
51555
  let published = false;
51178
51556
  try {
@@ -51355,7 +51733,7 @@ function stageSkillWrite(botId, input) {
51355
51733
  return { error: `skill "${parsed.name}" already matches the proposed SKILL.md` };
51356
51734
  }
51357
51735
  const entry = {
51358
- id: randomUUID14(),
51736
+ id: randomUUID16(),
51359
51737
  action: input.action,
51360
51738
  name: parsed.name,
51361
51739
  gist: gist || parsed.description.slice(0, STAGED_GIST_MAX),
@@ -51697,7 +52075,7 @@ function mentionPrompt(tagged) {
51697
52075
  }
51698
52076
 
51699
52077
  // server/local-computer.ts
51700
- import { lstatSync as lstatSync5, readFileSync as readFileSync31, realpathSync as realpathSync9, statSync as statSync7 } from "node:fs";
52078
+ import { lstatSync as lstatSync6, readFileSync as readFileSync32, realpathSync as realpathSync9, statSync as statSync7 } from "node:fs";
51701
52079
  import { homedir as homedir23 } from "node:os";
51702
52080
  import { dirname as dirname13, isAbsolute as isAbsolute10, join as join47 } from "node:path";
51703
52081
  var REQUIRED_LINUX_TOOLS = ["click", "get_window_state", "list_apps", "type_text"];
@@ -51857,8 +52235,8 @@ function validateLinuxDescriptorRuntime(descriptorFile, raw, {
51857
52235
  isProcessAlive = processAlive
51858
52236
  } = {}) {
51859
52237
  try {
51860
- const descriptorStat = lstatSync5(descriptorFile);
51861
- const descriptorDirectoryStat = lstatSync5(dirname13(descriptorFile));
52238
+ const descriptorStat = lstatSync6(descriptorFile);
52239
+ const descriptorDirectoryStat = lstatSync6(dirname13(descriptorFile));
51862
52240
  if (!descriptorStat.isFile() || descriptorStat.isSymbolicLink() || !ownedPrivate(descriptorStat, uid) || !descriptorDirectoryStat.isDirectory() || descriptorDirectoryStat.isSymbolicLink() || !ownedPrivate(descriptorDirectoryStat, uid)) {
51863
52241
  return false;
51864
52242
  }
@@ -51868,8 +52246,8 @@ function validateLinuxDescriptorRuntime(descriptorFile, raw, {
51868
52246
  const socketPath = daemon.socketPath;
51869
52247
  const binaryStat = statSync7(binaryPath);
51870
52248
  const currentFileIdentity = currentDriverFileIdentity(binaryPath);
51871
- const socketStat = lstatSync5(socketPath);
51872
- const socketDirectoryStat = lstatSync5(dirname13(socketPath));
52249
+ const socketStat = lstatSync6(socketPath);
52250
+ const socketDirectoryStat = lstatSync6(dirname13(socketPath));
51873
52251
  if (realpathSync9(binaryPath) !== binaryPath || !sameDriverFileIdentity(driver.fileIdentity, currentFileIdentity) || !binaryStat.isFile() || binaryStat.uid !== uid && binaryStat.uid !== 0 || (binaryStat.mode & 73) === 0 || (binaryStat.mode & 18) !== 0 || !socketStat.isSocket() || socketStat.isSymbolicLink() || !ownedPrivate(socketStat, uid) || !socketDirectoryStat.isDirectory() || socketDirectoryStat.isSymbolicLink() || !ownedPrivate(socketDirectoryStat, uid) || !isProcessAlive(raw.ownerPid) || !isProcessAlive(daemon.pid)) {
51874
52252
  return false;
51875
52253
  }
@@ -51892,7 +52270,7 @@ function readCuaConnection({
51892
52270
  }
51893
52271
  for (const file2 of new Set(candidates)) {
51894
52272
  try {
51895
- const raw = JSON.parse(readFileSync31(file2, "utf8"));
52273
+ const raw = JSON.parse(readFileSync32(file2, "utf8"));
51896
52274
  if (platform === "linux") {
51897
52275
  const decoded = decodeLinuxDescriptor(raw);
51898
52276
  if (decoded && validateLinuxRuntime(file2, raw)) return decoded;
@@ -52819,14 +53197,14 @@ async function vpsComputerJoin(cfg2, botId, runner = defaultRunner) {
52819
53197
  stdio: ["ignore", "ignore", "pipe"],
52820
53198
  windowsHide: true
52821
53199
  });
52822
- let failure3 = null;
53200
+ let failure5 = null;
52823
53201
  let stderr = "";
52824
53202
  child.stderr?.setEncoding("utf8");
52825
53203
  child.stderr?.on("data", (chunk) => {
52826
53204
  stderr = `${stderr}${chunk}`.slice(-1e3);
52827
53205
  });
52828
53206
  child.once("error", (error51) => {
52829
- failure3 = error51.message;
53207
+ failure5 = error51.message;
52830
53208
  });
52831
53209
  child.once("close", (code) => {
52832
53210
  const active = desktopTunnels.get(botId);
@@ -52834,16 +53212,16 @@ async function vpsComputerJoin(cfg2, botId, runner = defaultRunner) {
52834
53212
  clearTimeout(active.expiry);
52835
53213
  desktopTunnels.delete(botId);
52836
53214
  }
52837
- if (!failure3) failure3 = stderr.trim() || `SSH viewer tunnel exited ${code ?? "without a status"}`;
53215
+ if (!failure5) failure5 = stderr.trim() || `SSH viewer tunnel exited ${code ?? "without a status"}`;
52838
53216
  });
52839
53217
  const deadline = Date.now() + 1e4;
52840
- while (Date.now() < deadline && !failure3) {
53218
+ while (Date.now() < deadline && !failure5) {
52841
53219
  if (await loopbackAnswers(localPort)) break;
52842
53220
  await new Promise((resolve16) => setTimeout(resolve16, 100));
52843
53221
  }
52844
- if (failure3 || !await loopbackAnswers(localPort)) {
53222
+ if (failure5 || !await loopbackAnswers(localPort)) {
52845
53223
  if (child.exitCode === null && !child.killed) child.kill("SIGTERM");
52846
- const detail = failure3 || stderr.trim() || "the SSH port forward did not become ready";
53224
+ const detail = failure5 || stderr.trim() || "the SSH port forward did not become ready";
52847
53225
  throw Object.assign(new Error(`Could not open the VPS live desktop: ${detail}`), { status: 502 });
52848
53226
  }
52849
53227
  const joinUrl = `http://127.0.0.1:${localPort}/vnc.html#autoconnect=true&resize=scale&password=${encodeURIComponent(connection.password)}`;
@@ -52965,8 +53343,8 @@ async function vpsComputerScreenshot(cfg2, botId, runner = defaultRunner) {
52965
53343
  }
52966
53344
 
52967
53345
  // server/routines.ts
52968
- import { randomUUID as randomUUID15 } from "node:crypto";
52969
- import { mkdirSync as mkdirSync22, readFileSync as readFileSync32 } from "node:fs";
53346
+ import { randomUUID as randomUUID17 } from "node:crypto";
53347
+ import { mkdirSync as mkdirSync22, readFileSync as readFileSync33 } from "node:fs";
52970
53348
  import { dirname as dirname14, join as join48 } from "node:path";
52971
53349
  var persistedSourceThreadId = external_exports.string().trim().min(1).optional().catch(void 0);
52972
53350
  var webhookRunReceiptSchema = external_exports.object({
@@ -53352,7 +53730,7 @@ var RoutineManager = class {
53352
53730
  this.file = options.file ?? join48(DATA_DIR, "routines.json");
53353
53731
  this.now = options.now ?? Date.now;
53354
53732
  try {
53355
- const disk = JSON.parse(readFileSync32(this.file, "utf8"));
53733
+ const disk = JSON.parse(readFileSync33(this.file, "utf8"));
53356
53734
  this.routines = Array.isArray(disk.routines) ? disk.routines.flatMap((routine) => {
53357
53735
  const schedule2 = loadSchedule(routine.schedule);
53358
53736
  if (!schedule2) return [];
@@ -53522,7 +53900,7 @@ var RoutineManager = class {
53522
53900
  throw new Error("This interval has no future runs. Choose a later end date or turn it off.");
53523
53901
  }
53524
53902
  const routine = {
53525
- id: randomUUID15(),
53903
+ id: randomUUID17(),
53526
53904
  ...clean4,
53527
53905
  // Only a confirmed chat card supplies `request`; the public calendar
53528
53906
  // API cannot choose an arbitrary transcript as a reporting target.
@@ -53723,7 +54101,7 @@ var RoutineManager = class {
53723
54101
  throw Object.assign(new Error("The assigned MAUS no longer exists"), { status: 410 });
53724
54102
  }
53725
54103
  const run3 = {
53726
- id: randomUUID15(),
54104
+ id: randomUUID17(),
53727
54105
  routineId: input.webhookId,
53728
54106
  routineName: input.webhookName,
53729
54107
  prompt: input.prompt,
@@ -54107,7 +54485,7 @@ var RoutineManager = class {
54107
54485
  }
54108
54486
  }
54109
54487
  const run3 = {
54110
- id: randomUUID15(),
54488
+ id: randomUUID17(),
54111
54489
  routineId: routine.id,
54112
54490
  routineName: routine.name,
54113
54491
  prompt: routine.prompt,
@@ -54235,8 +54613,8 @@ var RoutineManager = class {
54235
54613
  };
54236
54614
 
54237
54615
  // server/calendar-calls.ts
54238
- import { randomUUID as randomUUID16 } from "node:crypto";
54239
- import { mkdirSync as mkdirSync23, readFileSync as readFileSync33 } from "node:fs";
54616
+ import { randomUUID as randomUUID18 } from "node:crypto";
54617
+ import { mkdirSync as mkdirSync23, readFileSync as readFileSync34 } from "node:fs";
54240
54618
  import { dirname as dirname15, join as join49 } from "node:path";
54241
54619
  var MAX_BOTS = 100;
54242
54620
  var MAX_ATTACHMENTS2 = 50;
@@ -54268,7 +54646,7 @@ var callSchema = external_exports.object({
54268
54646
  createdAt: external_exports.number().finite().nonnegative(),
54269
54647
  updatedAt: external_exports.number().finite().nonnegative()
54270
54648
  });
54271
- var fileSchema = external_exports.object({
54649
+ var fileSchema2 = external_exports.object({
54272
54650
  version: external_exports.literal(1),
54273
54651
  calls: external_exports.array(external_exports.unknown())
54274
54652
  });
@@ -54349,7 +54727,7 @@ var CalendarCallManager = class {
54349
54727
  this.onDue = options.onDue;
54350
54728
  let migrated = false;
54351
54729
  try {
54352
- const parsed = fileSchema.safeParse(JSON.parse(readFileSync33(this.file, "utf8")));
54730
+ const parsed = fileSchema2.safeParse(JSON.parse(readFileSync34(this.file, "utf8")));
54353
54731
  this.calls = parsed.success ? parsed.data.calls.flatMap((candidate) => {
54354
54732
  const call = callSchema.safeParse(candidate);
54355
54733
  if (!call.success) return [];
@@ -54379,7 +54757,7 @@ var CalendarCallManager = class {
54379
54757
  const clean4 = cleanInput(input, this.botExists);
54380
54758
  const timestamp4 = this.now();
54381
54759
  const call = {
54382
- id: randomUUID16(),
54760
+ id: randomUUID18(),
54383
54761
  ...clean4,
54384
54762
  nextRunAt: this.initialOccurrence(clean4.schedule, timestamp4),
54385
54763
  createdAt: timestamp4,
@@ -54512,8 +54890,8 @@ var CalendarCallManager = class {
54512
54890
 
54513
54891
  // server/browser-engine.ts
54514
54892
  import { spawn as spawn5 } from "node:child_process";
54515
- import { createHash as createHash18, randomBytes as randomBytes4, randomUUID as randomUUID17 } from "node:crypto";
54516
- import { chmodSync as chmodSync5, constants as constants5, copyFileSync, existsSync as existsSync22, mkdirSync as mkdirSync24, readFileSync as readFileSync34, readdirSync as readdirSync7, renameSync as renameSync5, rmSync as rmSync6, statSync as statSync8, unlinkSync as unlinkSync9, writeFileSync as writeFileSync15 } from "node:fs";
54893
+ import { createHash as createHash18, randomBytes as randomBytes4, randomUUID as randomUUID19 } from "node:crypto";
54894
+ import { chmodSync as chmodSync5, constants as constants5, copyFileSync, existsSync as existsSync23, mkdirSync as mkdirSync24, readFileSync as readFileSync35, readdirSync as readdirSync7, renameSync as renameSync5, rmSync as rmSync6, statSync as statSync8, unlinkSync as unlinkSync9, writeFileSync as writeFileSync15 } from "node:fs";
54517
54895
  import { homedir as homedir24, tmpdir as tmpdir6 } from "node:os";
54518
54896
  import { delimiter as delimiter4, dirname as dirname16, join as join51, resolve as resolve14 } from "node:path";
54519
54897
 
@@ -54972,7 +55350,7 @@ function ensureManagedBrowserConfig(env) {
54972
55350
  writeFileSync15(path, "{}\n", { flag: "wx", mode: 384 });
54973
55351
  } catch (error51) {
54974
55352
  if (error51.code !== "EEXIST") throw error51;
54975
- if (readFileSync34(path, "utf8").trim() !== "{}") throw new Error("The managed browser configuration was changed. Restore it to an empty JSON object before connecting.");
55353
+ if (readFileSync35(path, "utf8").trim() !== "{}") throw new Error("The managed browser configuration was changed. Restore it to an empty JSON object before connecting.");
54976
55354
  }
54977
55355
  return path;
54978
55356
  }
@@ -55056,7 +55434,7 @@ async function prepareBrowserSessionState(binaryPath, session, options = {}) {
55056
55434
  if (!pending) {
55057
55435
  pending = (async () => {
55058
55436
  const targetBase = join51(directory, `${restoreKey}-${session}`);
55059
- const hasTarget = () => existsSync22(`${targetBase}.json.enc`) || existsSync22(`${targetBase}.json`);
55437
+ const hasTarget = () => existsSync23(`${targetBase}.json.enc`) || existsSync23(`${targetBase}.json`);
55060
55438
  if (hasTarget()) return;
55061
55439
  check2();
55062
55440
  if (!await closeBrowserSession(binaryPath, { ...env, AGENT_BROWSER_RESTORE: session }, options.timeoutMs)) {
@@ -55095,7 +55473,7 @@ async function prepareBrowserSessionState(binaryPath, session, options = {}) {
55095
55473
  function executableName(platform = process.platform) {
55096
55474
  return platform === "win32" ? "agent-browser.exe" : "agent-browser";
55097
55475
  }
55098
- function isMusl(platform = process.platform, exists = existsSync22) {
55476
+ function isMusl(platform = process.platform, exists = existsSync23) {
55099
55477
  return platform === "linux" && (exists("/lib/ld-musl-x86_64.so.1") || exists("/lib/ld-musl-aarch64.so.1"));
55100
55478
  }
55101
55479
  function pinnedBinaryPath(dataDir = DATA_DIR, platform = process.platform, arch = process.arch) {
@@ -55127,7 +55505,7 @@ function completePackage(bundle, exists) {
55127
55505
  function resolveAgentBrowserBinary(options = {}) {
55128
55506
  const env = options.env ?? process.env;
55129
55507
  const platform = options.platform ?? process.platform;
55130
- const exists = options.exists ?? existsSync22;
55508
+ const exists = options.exists ?? existsSync23;
55131
55509
  const override = env.OMB_AGENT_BROWSER_PATH?.trim();
55132
55510
  if (override) return resolve14(override) && exists(resolve14(override)) ? resolve14(override) : null;
55133
55511
  const bundle = packagedBrowser(options);
@@ -55167,7 +55545,7 @@ async function installAgentBrowserBinary(options = {}) {
55167
55545
  }
55168
55546
  function ensureChrome(binaryPath, options = {}) {
55169
55547
  const bundle = packagedBrowser(options);
55170
- if (!options.withDeps && bundle && resolve14(binaryPath) === bundle.engine && completePackage(bundle, existsSync22)) {
55548
+ if (!options.withDeps && bundle && resolve14(binaryPath) === bundle.engine && completePackage(bundle, existsSync23)) {
55171
55549
  options.log?.("agent-browser: the bundled browser is ready; no download needed");
55172
55550
  return Promise.resolve();
55173
55551
  }
@@ -55195,7 +55573,7 @@ function ensureChrome(binaryPath, options = {}) {
55195
55573
  function browserEngineEncryptionKey(dataDir = DATA_DIR) {
55196
55574
  const file2 = join51(dataDir, KEY_FILE);
55197
55575
  try {
55198
- const existing = readFileSync34(file2, "utf8").trim();
55576
+ const existing = readFileSync35(file2, "utf8").trim();
55199
55577
  if (/^[0-9a-f]{64}$/u.test(existing)) return existing;
55200
55578
  } catch {
55201
55579
  }
@@ -55214,7 +55592,7 @@ function browserEngineStatus(options = {}) {
55214
55592
  const version2 = managed ? agentBrowserReleaseVersion(resolveAgentBrowserReleaseAsset(platform2, options.arch)) : AGENT_BROWSER_VERSION;
55215
55593
  return { kind: "ready", binaryPath, version: version2 };
55216
55594
  }
55217
- if (bundle && (options.exists ?? existsSync22)(bundle.directory)) {
55595
+ if (bundle && (options.exists ?? existsSync23)(bundle.directory)) {
55218
55596
  return { kind: "unavailable", reason: "The desktop browser bundle is incomplete. Reinstall or update OpenMausBot to repair it.", installable: false };
55219
55597
  }
55220
55598
  const platform = options.platform ?? process.platform;
@@ -55247,14 +55625,14 @@ function agentBrowserIntegration(input) {
55247
55625
  if (sourceEnv[name2]) env[name2] = sourceEnv[name2];
55248
55626
  }
55249
55627
  const bundle = packagedBrowser({ env: sourceEnv });
55250
- if (!env.AGENT_BROWSER_EXECUTABLE_PATH && bundle && resolve14(input.binaryPath) === bundle.engine && completePackage(bundle, existsSync22)) {
55628
+ if (!env.AGENT_BROWSER_EXECUTABLE_PATH && bundle && resolve14(input.binaryPath) === bundle.engine && completePackage(bundle, existsSync23)) {
55251
55629
  env.AGENT_BROWSER_EXECUTABLE_PATH = bundle.chrome;
55252
55630
  }
55253
55631
  return { command: input.binaryPath, args: ["mcp", "--tools", "core", "--no-webmcp"], env };
55254
55632
  }
55255
55633
  var FRAME_TIMEOUT_MS = 1e4;
55256
55634
  function agentBrowserFrame(input) {
55257
- const file2 = join51(tmpdir6(), `openmausbot-browser-${randomUUID17()}.png`);
55635
+ const file2 = join51(tmpdir6(), `openmausbot-browser-${randomUUID19()}.png`);
55258
55636
  return new Promise((settle, fail2) => {
55259
55637
  const child = spawn5(input.binaryPath, ["screenshot", file2], {
55260
55638
  env: browserRuntimeEnv(input.env),
@@ -55276,7 +55654,7 @@ function agentBrowserFrame(input) {
55276
55654
  fail2(error51);
55277
55655
  return;
55278
55656
  }
55279
- settle({ png: readFileSync34(file2).toString("base64"), format: "png" });
55657
+ settle({ png: readFileSync35(file2).toString("base64"), format: "png" });
55280
55658
  } catch {
55281
55659
  fail2(new Error("the browser reported a picture it did not write"));
55282
55660
  } finally {
@@ -55290,7 +55668,7 @@ function agentBrowserFrame(input) {
55290
55668
  });
55291
55669
  }
55292
55670
  function browserSessionId(botId, partitionId) {
55293
- if (partitionId === "guest") return `guest-${randomUUID17()}`;
55671
+ if (partitionId === "guest") return `guest-${randomUUID19()}`;
55294
55672
  const raw = partitionId || `bot-${botId}`;
55295
55673
  return raw.replace(/[^A-Za-z0-9_.-]/gu, "_").slice(0, 96);
55296
55674
  }
@@ -55337,7 +55715,7 @@ async function clearBrowserSessionState(binaryPath, session, options = {}) {
55337
55715
 
55338
55716
  // server/browser-live.ts
55339
55717
  import { execFile as execFile6 } from "node:child_process";
55340
- import { randomUUID as randomUUID18 } from "node:crypto";
55718
+ import { randomUUID as randomUUID20 } from "node:crypto";
55341
55719
  import { promisify as promisify3 } from "node:util";
55342
55720
  var execute = promisify3(execFile6);
55343
55721
  var MAX_FRAME = 3 * 1024 * 1024;
@@ -55678,7 +56056,7 @@ data: ${JSON.stringify(data)}
55678
56056
  if (this.viewers.size >= 8 || [...this.viewers.values()].filter((v2) => v2.session === options.session).length >= 2) {
55679
56057
  throw new BrowserLiveError("Too many browser views are open. Close another browser panel first.", 429);
55680
56058
  }
55681
- const viewer = { ...options, id: randomUUID18(), closed: false, blocked: false, pendingActions: 0, restarting: false, pressedKeys: /* @__PURE__ */ new Set(), pressedButtons: /* @__PURE__ */ new Set() };
56059
+ const viewer = { ...options, id: randomUUID20(), closed: false, blocked: false, pendingActions: 0, restarting: false, pressedKeys: /* @__PURE__ */ new Set(), pressedButtons: /* @__PURE__ */ new Set() };
55682
56060
  if (!this.current(viewer)) throw new BrowserLiveError("This browser view is no longer available.", 409);
55683
56061
  this.viewers.set(viewer.id, viewer);
55684
56062
  options.res.once("close", () => this.close(viewer));
@@ -55853,6 +56231,9 @@ data: ${JSON.stringify(data)}
55853
56231
  closeForSession(session) {
55854
56232
  for (const viewer of this.viewers.values()) if (viewer.session === session) this.close(viewer);
55855
56233
  }
56234
+ closeForOwner(owner2) {
56235
+ for (const viewer of this.viewers.values()) if (viewer.owner === owner2) this.close(viewer);
56236
+ }
55856
56237
  closeForBot(botId) {
55857
56238
  for (const viewer of this.viewers.values()) if (viewer.botId === botId) this.close(viewer);
55858
56239
  }
@@ -57228,8 +57609,8 @@ function profileRevision(bot) {
57228
57609
 
57229
57610
  // server/profile-versions.ts
57230
57611
  import { appendFile as appendFile4 } from "node:fs/promises";
57231
- import { createHash as createHash22, randomUUID as randomUUID19 } from "node:crypto";
57232
- import { closeSync as closeSync9, existsSync as existsSync23, fstatSync as fstatSync4, mkdirSync as mkdirSync25, openSync as openSync9, readSync as readSync4 } from "node:fs";
57612
+ import { createHash as createHash22, randomUUID as randomUUID21 } from "node:crypto";
57613
+ import { closeSync as closeSync9, existsSync as existsSync24, fstatSync as fstatSync4, mkdirSync as mkdirSync25, openSync as openSync9, readSync as readSync4 } from "node:fs";
57233
57614
  import { join as join52 } from "node:path";
57234
57615
  var FILE_NAME2 = "history.ndjson";
57235
57616
  var ONE_LINER = 200;
@@ -57245,11 +57626,11 @@ function rowFor(field, at2, actor, via, before, after) {
57245
57626
  if (field === "soul") {
57246
57627
  const b3 = Buffer.byteLength(before, "utf8");
57247
57628
  const a2 = Buffer.byteLength(after, "utf8");
57248
- return { id: randomUUID19(), at: at2, actor, via, field, summary: `soul: ${b3} \u2192 ${a2} bytes`, before, after };
57629
+ return { id: randomUUID21(), at: at2, actor, via, field, summary: `soul: ${b3} \u2192 ${a2} bytes`, before, after };
57249
57630
  }
57250
57631
  const b2 = oneLiner(before);
57251
57632
  const a = oneLiner(after);
57252
- return { id: randomUUID19(), at: at2, actor, via, field, summary: `${field}: ${JSON.stringify(b2)} \u2192 ${JSON.stringify(a)}`, before: b2, after: a };
57633
+ return { id: randomUUID21(), at: at2, actor, via, field, summary: `${field}: ${JSON.stringify(b2)} \u2192 ${JSON.stringify(a)}`, before: b2, after: a };
57253
57634
  }
57254
57635
  function withRestoreEligibility(row) {
57255
57636
  const redacted = redactSecrets(row);
@@ -57261,7 +57642,7 @@ function withRestoreEligibility(row) {
57261
57642
  };
57262
57643
  }
57263
57644
  async function writeRows(botId, rows) {
57264
- if (!existsSync23(botFolder(botId))) return;
57645
+ if (!existsSync24(botFolder(botId))) return;
57265
57646
  const file2 = historyFile(botId);
57266
57647
  mkdirSync25(botFolder(botId), { recursive: true, mode: 448 });
57267
57648
  const text8 = rows.map((row) => JSON.stringify(withRestoreEligibility(row))).join("\n") + "\n";
@@ -57971,7 +58352,7 @@ function matchDirectoryBots(profile, bots, limit = 5) {
57971
58352
  }
57972
58353
 
57973
58354
  // server/project-scout.ts
57974
- import { readFileSync as readFileSync35, readdirSync as readdirSync8, statSync as statSync9 } from "node:fs";
58355
+ import { readFileSync as readFileSync36, readdirSync as readdirSync8, statSync as statSync9 } from "node:fs";
57975
58356
  import { basename as basename7, join as join53 } from "node:path";
57976
58357
 
57977
58358
  // shared/import-name.ts
@@ -58156,7 +58537,7 @@ var MAX_DIR_ENTRIES = 400;
58156
58537
  function readText2(path) {
58157
58538
  try {
58158
58539
  if (statSync9(path).size > MAX_FILE_BYTES2) return null;
58159
- return readFileSync35(path, "utf8");
58540
+ return readFileSync36(path, "utf8");
58160
58541
  } catch {
58161
58542
  return null;
58162
58543
  }
@@ -59288,8 +59669,8 @@ function memberTurnSelection(selection) {
59288
59669
  }
59289
59670
 
59290
59671
  // server/webhooks.ts
59291
- import { createHash as createHash24, randomBytes as randomBytes5, randomUUID as randomUUID20, timingSafeEqual } from "node:crypto";
59292
- import { mkdirSync as mkdirSync26, readFileSync as readFileSync36 } from "node:fs";
59672
+ import { createHash as createHash24, randomBytes as randomBytes5, randomUUID as randomUUID22, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
59673
+ import { mkdirSync as mkdirSync26, readFileSync as readFileSync37 } from "node:fs";
59293
59674
  import { dirname as dirname17, join as join55 } from "node:path";
59294
59675
  var MAX_DELIVERIES = 2e3;
59295
59676
  var MAX_ATTEMPTS = 2e3;
@@ -59372,7 +59753,7 @@ function secretMatches(secret, expectedHex) {
59372
59753
  if (!secret) return false;
59373
59754
  const actual = Buffer.from(hashSecret(secret), "hex");
59374
59755
  const expected = Buffer.from(expectedHex, "hex");
59375
- return actual.length === expected.length && timingSafeEqual(actual, expected);
59756
+ return actual.length === expected.length && timingSafeEqual2(actual, expected);
59376
59757
  }
59377
59758
  function newEndpointId() {
59378
59759
  return `wh_${randomBytes5(12).toString("base64url")}`;
@@ -59480,7 +59861,7 @@ var WebhookManager = class {
59480
59861
  this.file = options.file ?? join55(DATA_DIR, "webhooks.json");
59481
59862
  this.now = options.now ?? Date.now;
59482
59863
  try {
59483
- const parsed = webhookFileSchema.safeParse(parseJson(readFileSync36(this.file, "utf8")));
59864
+ const parsed = webhookFileSchema.safeParse(parseJson(readFileSync37(this.file, "utf8")));
59484
59865
  if (!parsed.success) throw parsed.error;
59485
59866
  this.webhooks = parsed.data.webhooks;
59486
59867
  this.deliveries = parsed.data.deliveries.slice(-MAX_DELIVERIES);
@@ -59503,7 +59884,7 @@ var WebhookManager = class {
59503
59884
  const now = this.now();
59504
59885
  const secret = newSecret();
59505
59886
  const trigger = {
59506
- id: randomUUID20(),
59887
+ id: randomUUID22(),
59507
59888
  endpointId: newEndpointId(),
59508
59889
  ...clean4,
59509
59890
  secretHash: hashSecret(secret),
@@ -59599,7 +59980,7 @@ var WebhookManager = class {
59599
59980
  contentType: "application/json",
59600
59981
  eventName: eventName2,
59601
59982
  userAgent: "OpenMausBot webhook tester",
59602
- deliveryId: `test-${randomUUID20()}`
59983
+ deliveryId: `test-${randomUUID22()}`
59603
59984
  });
59604
59985
  }
59605
59986
  recordRejected(endpointId, statusCode, reason, event = {}) {
@@ -59612,7 +59993,7 @@ var WebhookManager = class {
59612
59993
  if (this.options.botState(trigger.botId) === "missing") fail(410, "The assigned MAUS no longer exists");
59613
59994
  const allowed = trigger.eventTypes ?? [];
59614
59995
  if (allowed.length > 0 && (!event.eventName || !allowed.includes(event.eventName))) {
59615
- const deliveryId3 = String(event.deliveryId ?? "").trim().slice(0, 200) || randomUUID20();
59996
+ const deliveryId3 = String(event.deliveryId ?? "").trim().slice(0, 200) || randomUUID22();
59616
59997
  this.appendAttempt(trigger, event, {
59617
59998
  outcome: "ignored",
59618
59999
  statusCode: 202,
@@ -59647,7 +60028,7 @@ var WebhookManager = class {
59647
60028
  if (recent.length >= RATE_LIMIT) fail(429, "Webhook rate limit exceeded");
59648
60029
  recent.push(now);
59649
60030
  this.rate.set(trigger.endpointId, recent);
59650
- const deliveryId2 = requestedDeliveryId || randomUUID20();
60031
+ const deliveryId2 = requestedDeliveryId || randomUUID22();
59651
60032
  const run3 = this.options.enqueue({
59652
60033
  webhookId: trigger.id,
59653
60034
  webhookName: trigger.name,
@@ -59677,7 +60058,7 @@ var WebhookManager = class {
59677
60058
  }
59678
60059
  captureVerification(trigger, event) {
59679
60060
  const receivedAt = this.now();
59680
- const deliveryId2 = String(event.deliveryId ?? "").trim().slice(0, 200) || randomUUID20();
60061
+ const deliveryId2 = String(event.deliveryId ?? "").trim().slice(0, 200) || randomUUID22();
59681
60062
  trigger.verificationPending = false;
59682
60063
  trigger.verifiedAt = receivedAt;
59683
60064
  trigger.lastReceivedAt = receivedAt;
@@ -59711,7 +60092,7 @@ var WebhookManager = class {
59711
60092
  }
59712
60093
  appendAttempt(trigger, event, details) {
59713
60094
  const attempt = {
59714
- id: randomUUID20(),
60095
+ id: randomUUID22(),
59715
60096
  webhookId: trigger.id,
59716
60097
  receivedAt: this.now(),
59717
60098
  outcome: details.outcome,
@@ -59741,7 +60122,7 @@ var WebhookManager = class {
59741
60122
  };
59742
60123
 
59743
60124
  // server/skill-library.ts
59744
- import { existsSync as existsSync24, readFileSync as readFileSync37, readdirSync as readdirSync9 } from "node:fs";
60125
+ import { existsSync as existsSync25, readFileSync as readFileSync38, readdirSync as readdirSync9 } from "node:fs";
59745
60126
  import { basename as basename8, join as join56 } from "node:path";
59746
60127
  var SAFE_ID = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
59747
60128
  function strings(value) {
@@ -59773,14 +60154,14 @@ function parseSkillManifest(value, directory) {
59773
60154
  function loadSkillDirectory(directory) {
59774
60155
  const manifestPath2 = join56(directory, "manifest.json");
59775
60156
  const skillPath = join56(directory, "SKILL.md");
59776
- if (!existsSync24(manifestPath2) || !existsSync24(skillPath)) return null;
59777
- const manifest = parseSkillManifest(JSON.parse(readFileSync37(manifestPath2, "utf8")), directory);
59778
- const instructions = readFileSync37(skillPath, "utf8").trim();
60157
+ if (!existsSync25(manifestPath2) || !existsSync25(skillPath)) return null;
60158
+ const manifest = parseSkillManifest(JSON.parse(readFileSync38(manifestPath2, "utf8")), directory);
60159
+ const instructions = readFileSync38(skillPath, "utf8").trim();
59779
60160
  if (!instructions.startsWith("---")) throw new Error(`${skillPath} has no skill frontmatter`);
59780
60161
  return { manifest, instructions, directory };
59781
60162
  }
59782
60163
  function loadBundledSkills(root = process.env.OMB_SKILLS_DIR || join56(process.cwd(), "skills")) {
59783
- if (!existsSync24(root)) return [];
60164
+ if (!existsSync25(root)) return [];
59784
60165
  const skills = [];
59785
60166
  for (const name2 of readdirSync9(root).sort()) {
59786
60167
  const directory = join56(root, name2);
@@ -59790,7 +60171,7 @@ function loadBundledSkills(root = process.env.OMB_SKILLS_DIR || join56(process.c
59790
60171
  return skills;
59791
60172
  }
59792
60173
  function loadUserSkills(root) {
59793
- if (!existsSync24(root)) return [];
60174
+ if (!existsSync25(root)) return [];
59794
60175
  let names;
59795
60176
  try {
59796
60177
  names = readdirSync9(root).sort();
@@ -60606,8 +60987,8 @@ function createGracefulShutdown({
60606
60987
 
60607
60988
  // electron/data-dir-lease.mjs
60608
60989
  import { execFileSync } from "node:child_process";
60609
- import { createHash as createHash25, randomUUID as randomUUID21 } from "node:crypto";
60610
- import { existsSync as existsSync25, linkSync as linkSync4, mkdirSync as mkdirSync27, readFileSync as readFileSync38, renameSync as renameSync6, unlinkSync as unlinkSync10, writeFileSync as writeFileSync16 } from "node:fs";
60990
+ import { createHash as createHash25, randomUUID as randomUUID23 } from "node:crypto";
60991
+ import { existsSync as existsSync26, linkSync as linkSync4, mkdirSync as mkdirSync27, readFileSync as readFileSync39, renameSync as renameSync6, unlinkSync as unlinkSync10, writeFileSync as writeFileSync16 } from "node:fs";
60611
60992
  import { hostname as hostname4, uptime } from "node:os";
60612
60993
  import { join as join57 } from "node:path";
60613
60994
  var LEASE_NAME = "openmausbot-server.lease";
@@ -60630,7 +61011,7 @@ var cachedBootSession;
60630
61011
  function readBootSession() {
60631
61012
  try {
60632
61013
  if (process.platform === "linux") {
60633
- return normalizeBootId(readFileSync38("/proc/sys/kernel/random/boot_id", "utf8"));
61014
+ return normalizeBootId(readFileSync39("/proc/sys/kernel/random/boot_id", "utf8"));
60634
61015
  }
60635
61016
  if (process.platform === "darwin") {
60636
61017
  return normalizeBootId(execFileSync("/usr/sbin/sysctl", ["-n", "kern.bootsessionuuid"], {
@@ -60677,7 +61058,7 @@ function isReaperOwner(value, targetToken) {
60677
61058
  function parseRecord(path, invalidMessage, validate) {
60678
61059
  let raw;
60679
61060
  try {
60680
- raw = readFileSync38(path, "utf8");
61061
+ raw = readFileSync39(path, "utf8");
60681
61062
  } catch (error51) {
60682
61063
  if (error51?.code === "ENOENT") return null;
60683
61064
  throw leaseError("OpenMausBot cannot read the data-directory lease; refusing to start to protect its state.", error51);
@@ -60753,7 +61134,7 @@ function claimReaperAuthority2(leasePath, expected) {
60753
61134
  version: 1,
60754
61135
  pid: process.pid,
60755
61136
  host: hostname4(),
60756
- token: randomUUID21(),
61137
+ token: randomUUID23(),
60757
61138
  createdAt: Date.now(),
60758
61139
  boot: bootSession(),
60759
61140
  uptime: uptimeMs(),
@@ -60800,7 +61181,7 @@ function prepareDataDir(dataDir, legacyDataDir) {
60800
61181
  validateDataDir(dataDir);
60801
61182
  if (legacyDataDir !== void 0) {
60802
61183
  validateDataDir(legacyDataDir);
60803
- if (!existsSync25(dataDir) && existsSync25(legacyDataDir)) {
61184
+ if (!existsSync26(dataDir) && existsSync26(legacyDataDir)) {
60804
61185
  try {
60805
61186
  renameSync6(legacyDataDir, dataDir);
60806
61187
  } catch {
@@ -60885,7 +61266,7 @@ function acquireDataDirLeaseInternal(dataDir, options = {}) {
60885
61266
  version: 1,
60886
61267
  pid: process.pid,
60887
61268
  host: hostname4(),
60888
- token: randomUUID21(),
61269
+ token: randomUUID23(),
60889
61270
  createdAt: Date.now(),
60890
61271
  boot: bootSession(),
60891
61272
  uptime: uptimeMs()
@@ -61000,8 +61381,8 @@ var WorkspaceBackupMaintenance = class {
61000
61381
  };
61001
61382
 
61002
61383
  // server/workspace-backup-http.ts
61003
- import { randomUUID as randomUUID23 } from "node:crypto";
61004
- import { createReadStream as createReadStream4, createWriteStream as createWriteStream3, existsSync as existsSync27, lstatSync as lstatSync7, mkdirSync as mkdirSync29, readdirSync as readdirSync11, statSync as statSync10, unlinkSync as unlinkSync11 } from "node:fs";
61384
+ import { randomUUID as randomUUID25 } from "node:crypto";
61385
+ import { createReadStream as createReadStream4, createWriteStream as createWriteStream3, existsSync as existsSync28, lstatSync as lstatSync8, mkdirSync as mkdirSync29, readdirSync as readdirSync11, statSync as statSync10, unlinkSync as unlinkSync11 } from "node:fs";
61005
61386
  import { join as join59 } from "node:path";
61006
61387
  import { Transform as Transform2 } from "node:stream";
61007
61388
  import { pipeline as pipeline3 } from "node:stream/promises";
@@ -61022,7 +61403,7 @@ var WORKSPACE_BACKUP_CLIENT_KEYS = [
61022
61403
  ];
61023
61404
 
61024
61405
  // server/workspace-backup.ts
61025
- import { createCipheriv, createDecipheriv, createHash as createHash26, randomBytes as randomBytes6, randomUUID as randomUUID22, scrypt } from "node:crypto";
61406
+ import { createCipheriv, createDecipheriv, createHash as createHash26, randomBytes as randomBytes6, randomUUID as randomUUID24, scrypt } from "node:crypto";
61026
61407
  import {
61027
61408
  chmodSync as chmodSync6,
61028
61409
  closeSync as closeSync11,
@@ -61030,13 +61411,13 @@ import {
61030
61411
  cpSync,
61031
61412
  createReadStream as createReadStream3,
61032
61413
  createWriteStream as createWriteStream2,
61033
- existsSync as existsSync26,
61414
+ existsSync as existsSync27,
61034
61415
  fstatSync as fstatSync6,
61035
61416
  fsyncSync as fsyncSync3,
61036
- lstatSync as lstatSync6,
61417
+ lstatSync as lstatSync7,
61037
61418
  mkdirSync as mkdirSync28,
61038
61419
  openSync as openSync11,
61039
- readFileSync as readFileSync39,
61420
+ readFileSync as readFileSync40,
61040
61421
  readdirSync as readdirSync10,
61041
61422
  readSync as readSync6,
61042
61423
  realpathSync as realpathSync10,
@@ -64068,6 +64449,7 @@ var EXCLUDED = /* @__PURE__ */ new Set([
64068
64449
  "environment-id",
64069
64450
  "sessions.json",
64070
64451
  "tunnel-account.json",
64452
+ "team-computers.json",
64071
64453
  "openmausbot-server.lease",
64072
64454
  "box-create-requests.lock",
64073
64455
  "messages.db-wal",
@@ -64092,32 +64474,32 @@ function preserved(name2) {
64092
64474
  const folded = name2.toLowerCase();
64093
64475
  return excluded(folded) && folded !== "messages.db-wal" && folded !== "messages.db-shm";
64094
64476
  }
64095
- function folder(path) {
64477
+ function folder2(path) {
64096
64478
  mkdirSync28(path, { recursive: true, mode: 448 });
64097
- const stat6 = lstatSync6(path);
64479
+ const stat6 = lstatSync7(path);
64098
64480
  if (!stat6.isDirectory() || stat6.isSymbolicLink()) throw new Error("The backup directory must be a real directory, not a symbolic link.");
64099
64481
  chmodSync6(path, 448);
64100
64482
  }
64101
64483
  function backupRoot(dataDir) {
64102
64484
  const root = resolve15(dataDir);
64103
64485
  if (root === parse6(root).root) throw new Error("A filesystem root cannot be a workspace backup target.");
64104
- const stat6 = lstatSync6(root);
64486
+ const stat6 = lstatSync7(root);
64105
64487
  if (!stat6.isDirectory() || stat6.isSymbolicLink()) throw new Error("The workspace data directory must not be a symbolic link.");
64106
64488
  const path = join58(root, ".backups");
64107
- folder(path);
64489
+ folder2(path);
64108
64490
  return path;
64109
64491
  }
64110
64492
  function jobPath(dataDir, id) {
64111
64493
  if (!ID.test(id)) throw new Error("Invalid workspace backup identifier.");
64112
64494
  const path = join58(backupRoot(dataDir), id);
64113
64495
  if (entryExists(path)) {
64114
- const stat6 = lstatSync6(path);
64496
+ const stat6 = lstatSync7(path);
64115
64497
  if (!stat6.isDirectory() || stat6.isSymbolicLink()) throw new Error("Unsafe workspace backup job directory.");
64116
64498
  }
64117
64499
  return path;
64118
64500
  }
64119
64501
  function newJob(dataDir) {
64120
- const id = randomUUID22();
64502
+ const id = randomUUID24();
64121
64503
  const directory = jobPath(dataDir, id);
64122
64504
  mkdirSync28(directory, { mode: 448 });
64123
64505
  return { id, directory };
@@ -64130,7 +64512,7 @@ function record2(value) {
64130
64512
  }
64131
64513
  function entryExists(path) {
64132
64514
  try {
64133
- lstatSync6(path);
64515
+ lstatSync7(path);
64134
64516
  return true;
64135
64517
  } catch (error51) {
64136
64518
  if (error51.code === "ENOENT") return false;
@@ -64142,7 +64524,7 @@ function privateJson(path) {
64142
64524
  try {
64143
64525
  const stat6 = fstatSync6(fd);
64144
64526
  if (!stat6.isFile() || stat6.size > MAX_METADATA_BYTES) throw new Error("Workspace backup metadata is too large or is not a regular file.");
64145
- return JSON.parse(readFileSync39(fd, "utf8"));
64527
+ return JSON.parse(readFileSync40(fd, "utf8"));
64146
64528
  } finally {
64147
64529
  closeSync11(fd);
64148
64530
  }
@@ -64157,7 +64539,7 @@ function privateMetadata(manifest) {
64157
64539
  }
64158
64540
  function assertLocalAuthOutsideSnapshot(dataDir) {
64159
64541
  const configPath = join58(dataDir, "config.json");
64160
- const config2 = existsSync26(configPath) ? privateJson(configPath) : {};
64542
+ const config2 = existsSync27(configPath) ? privateJson(configPath) : {};
64161
64543
  const instances = record2(config2) && record2(config2.instances) ? config2.instances : {};
64162
64544
  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"];
64163
64545
  const check2 = (value, field, home) => {
@@ -64214,7 +64596,7 @@ function copyRegular(source, destination) {
64214
64596
  while (offset < count) offset += writeSync(output, chunk, offset, count - offset);
64215
64597
  }
64216
64598
  const after = fstatSync6(input);
64217
- const current2 = lstatSync6(source);
64599
+ const current2 = lstatSync7(source);
64218
64600
  if (size !== before.size || after.mtimeMs !== before.mtimeMs || after.ctimeMs !== before.ctimeMs || current2.ino !== before.ino || current2.dev !== before.dev || current2.isSymbolicLink()) {
64219
64601
  throw new Error("A workspace file changed during backup. Stop its writer and retry.");
64220
64602
  }
@@ -64241,7 +64623,7 @@ function hashFile(path) {
64241
64623
  }
64242
64624
  }
64243
64625
  function countJsonArray(path) {
64244
- if (!existsSync26(path)) return 0;
64626
+ if (!existsSync27(path)) return 0;
64245
64627
  const value = privateJson(path);
64246
64628
  if (!Array.isArray(value)) throw new Error("A workspace roster is not a valid JSON array.");
64247
64629
  const ids = /* @__PURE__ */ new Set();
@@ -64254,7 +64636,7 @@ function countJsonArray(path) {
64254
64636
  return value.length;
64255
64637
  }
64256
64638
  function databaseCounts(path) {
64257
- if (!existsSync26(path)) return { threads: 0, messages: 0 };
64639
+ if (!existsSync27(path)) return { threads: 0, messages: 0 };
64258
64640
  const db2 = new DatabaseSync2(path, { readOnly: true });
64259
64641
  try {
64260
64642
  const check2 = db2.prepare("PRAGMA quick_check").all();
@@ -64274,7 +64656,7 @@ async function createWorkspaceBackup(dataDir, options) {
64274
64656
  const key3 = await passwordKey(options.password, salt);
64275
64657
  const job = newJob(dataDir);
64276
64658
  const snapshot2 = join58(job.directory, "snapshot");
64277
- folder(join58(snapshot2, "data"));
64659
+ folder2(join58(snapshot2, "data"));
64278
64660
  const entries = [];
64279
64661
  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];
64280
64662
  let bytes = 0;
@@ -64282,8 +64664,8 @@ async function createWorkspaceBackup(dataDir, options) {
64282
64664
  try {
64283
64665
  const root = realpathSync10(dataDir);
64284
64666
  const sourceDb = join58(root, "messages.db");
64285
- if (existsSync26(sourceDb)) {
64286
- if (lstatSync6(sourceDb).isSymbolicLink()) throw new Error("The message database must not be a symbolic link.");
64667
+ if (existsSync27(sourceDb)) {
64668
+ if (lstatSync7(sourceDb).isSymbolicLink()) throw new Error("The message database must not be a symbolic link.");
64287
64669
  const db2 = new DatabaseSync2(sourceDb, { readOnly: true });
64288
64670
  const completionPulse = setInterval(() => {
64289
64671
  }, 25);
@@ -64308,7 +64690,7 @@ async function createWorkspaceBackup(dataDir, options) {
64308
64690
  if (forbiddenArchivePath(path2)) throw new Error("A workspace filename conflicts with a protected authentication or runtime path.");
64309
64691
  if (!validRelative(path2)) throw new Error("A workspace filename cannot be safely restored on supported platforms.");
64310
64692
  const source = join58(directory, name2);
64311
- const stat6 = lstatSync6(source);
64693
+ const stat6 = lstatSync7(source);
64312
64694
  if (stat6.isSymbolicLink()) {
64313
64695
  let target;
64314
64696
  try {
@@ -64329,7 +64711,7 @@ async function createWorkspaceBackup(dataDir, options) {
64329
64711
  if (entries.length >= MAX_WORKSPACE_BACKUP_FILES) throw new Error("The workspace exceeds the 100,000-entry backup limit.");
64330
64712
  const destination = join58(snapshot2, "data", path2);
64331
64713
  if (stat6.isDirectory()) {
64332
- folder(destination);
64714
+ folder2(destination);
64333
64715
  entries.push({ path: path2, type: "directory", size: 0, mode: 448 });
64334
64716
  walk2(source, path2);
64335
64717
  } else if (stat6.isFile()) {
@@ -64344,7 +64726,7 @@ async function createWorkspaceBackup(dataDir, options) {
64344
64726
  }
64345
64727
  writeJson(destination, value);
64346
64728
  }
64347
- copied = { size: lstatSync6(destination).size, mode: 384, sha256: hashFile(destination) };
64729
+ copied = { size: lstatSync7(destination).size, mode: 384, sha256: hashFile(destination) };
64348
64730
  } else copied = copyRegular(path2 === "messages.db" ? join58(snapshot2, "messages-snapshot.db") : source, destination);
64349
64731
  bytes += copied.size;
64350
64732
  if (bytes > MAX_WORKSPACE_BACKUP_BYTES) throw new Error("The workspace exceeds the 10 GB backup limit.");
@@ -64499,7 +64881,7 @@ function validateStaged(directory, expected) {
64499
64881
  for (const name2 of readdirSync10(root)) {
64500
64882
  const path = prefix ? `${prefix}/${name2}` : name2;
64501
64883
  if (!declared.has(path)) throw new Error("Staged workspace contains files absent from its manifest.");
64502
- const stat6 = lstatSync6(join58(root, name2));
64884
+ const stat6 = lstatSync7(join58(root, name2));
64503
64885
  if (stat6.isSymbolicLink()) throw new Error("Staged workspace contains a symbolic link.");
64504
64886
  if (stat6.isDirectory()) inspect(join58(root, name2), path);
64505
64887
  }
@@ -64507,7 +64889,7 @@ function validateStaged(directory, expected) {
64507
64889
  inspect(join58(directory, "data"));
64508
64890
  for (const entry of manifest.entries) {
64509
64891
  const path = join58(directory, "data", entry.path);
64510
- const stat6 = lstatSync6(path);
64892
+ const stat6 = lstatSync7(path);
64511
64893
  if (stat6.isSymbolicLink() || (entry.type === "file" ? !stat6.isFile() || stat6.size !== entry.size : !stat6.isDirectory())) {
64512
64894
  throw new Error("Staged workspace files do not match the backup manifest.");
64513
64895
  }
@@ -64520,11 +64902,11 @@ function validateStaged(directory, expected) {
64520
64902
  }
64521
64903
  const data = join58(directory, "data");
64522
64904
  if (countJsonArray(join58(data, "bots.json")) !== manifest.summary.bots || countJsonArray(join58(data, "groups.json")) !== manifest.summary.groups) throw new Error("The backup roster does not match its summary.");
64523
- if (existsSync26(join58(data, "config.json"))) {
64905
+ if (existsSync27(join58(data, "config.json"))) {
64524
64906
  const config2 = privateJson(join58(data, "config.json"));
64525
64907
  if (JSON.stringify(config2) !== JSON.stringify(portableWorkspaceConfig(config2))) throw new Error("Workspace backup contains non-portable connection settings.");
64526
64908
  }
64527
- if (existsSync26(join58(data, "webhooks.json"))) {
64909
+ if (existsSync27(join58(data, "webhooks.json"))) {
64528
64910
  const value = privateJson(join58(data, "webhooks.json"));
64529
64911
  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.");
64530
64912
  }
@@ -64539,7 +64921,7 @@ async function stageWorkspaceBackup(dataDir, archivePath, options) {
64539
64921
  await decryptArchive(archivePath, plaintext, options.password);
64540
64922
  const expected = await inspectTar(plaintext);
64541
64923
  const staged = join58(job.directory, "staged");
64542
- folder(staged);
64924
+ folder2(staged);
64543
64925
  await So({ file: plaintext, cwd: staged, strict: true, preservePaths: false, umask: 63, noChmod: true });
64544
64926
  const manifest = validateStaged(staged, expected);
64545
64927
  const versions = [manifest.summary.appVersion, options.currentAppVersion ?? ""].map((version2) => /^(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/.exec(version2)?.slice(1).map(Number));
@@ -64566,8 +64948,8 @@ function readStagedWorkspaceBackup(dataDir, id) {
64566
64948
  }
64567
64949
  function commitPendingWorkspaceRestore(dataDir, id) {
64568
64950
  const root = backupRoot(dataDir);
64569
- if (existsSync26(join58(root, "pending-restore.json"))) throw new Error("A workspace restore is already waiting for restart.");
64570
- if (existsSync26(join58(root, `safety-${id}`))) throw new Error("This restore was already attempted. Preview the backup again to create a fresh restore attempt.");
64951
+ if (existsSync27(join58(root, "pending-restore.json"))) throw new Error("A workspace restore is already waiting for restart.");
64952
+ if (existsSync27(join58(root, `safety-${id}`))) throw new Error("This restore was already attempted. Preview the backup again to create a fresh restore attempt.");
64571
64953
  const metadata = readStagedWorkspaceBackup(dataDir, id);
64572
64954
  const manifest = validateStaged(join58(jobPath(dataDir, id), "staged"));
64573
64955
  prepareRestore(dataDir, id, manifest);
@@ -64577,7 +64959,7 @@ function commitPendingWorkspaceRestore(dataDir, id) {
64577
64959
  function readPendingWorkspaceRestoreMetadata(dataDir) {
64578
64960
  const root = backupRoot(dataDir);
64579
64961
  const path = join58(root, "pending-restore.json");
64580
- if (!existsSync26(path)) return null;
64962
+ if (!existsSync27(path)) return null;
64581
64963
  const pending = privateJson(path);
64582
64964
  if (!record2(pending) || typeof pending.id !== "string") throw new Error("Invalid pending workspace restore.");
64583
64965
  return readStagedWorkspaceBackup(dataDir, pending.id);
@@ -64609,8 +64991,8 @@ function rollback(dataDir, journal) {
64609
64991
  const root = backupRoot(dataDir);
64610
64992
  const previousReceipt = join58(root, `safety-${journal.id}`, "previous-restore.json");
64611
64993
  const last = join58(root, "last-restore.json");
64612
- if (existsSync26(previousReceipt)) writeJson(last, privateJson(previousReceipt));
64613
- else if (existsSync26(last)) {
64994
+ if (existsSync27(previousReceipt)) writeJson(last, privateJson(previousReceipt));
64995
+ else if (existsSync27(last)) {
64614
64996
  const value = privateJson(last);
64615
64997
  if (record2(value) && value.id === journal.id) rmSync7(last);
64616
64998
  }
@@ -64661,16 +65043,16 @@ function prepareRestore(dataDir, id, manifest) {
64661
65043
  assertLocalAuthOutsideSnapshot(dataDir);
64662
65044
  const job = jobPath(dataDir, id);
64663
65045
  const prepared = join58(job, "apply", "data");
64664
- if (existsSync26(join58(job, "apply"))) rmSync7(join58(job, "apply"), { recursive: true, force: true });
64665
- folder(prepared);
64666
- for (const entry of manifest.entries.filter((entry2) => entry2.type === "directory")) folder(join58(prepared, entry.path));
65046
+ if (existsSync27(join58(job, "apply"))) rmSync7(join58(job, "apply"), { recursive: true, force: true });
65047
+ folder2(prepared);
65048
+ for (const entry of manifest.entries.filter((entry2) => entry2.type === "directory")) folder2(join58(prepared, entry.path));
64667
65049
  for (const entry of manifest.entries) {
64668
65050
  const destination = join58(prepared, entry.path);
64669
65051
  if (entry.type === "file") copyRegular(join58(job, "staged", "data", entry.path), destination);
64670
65052
  }
64671
65053
  const changeJson = (name2, change) => {
64672
65054
  const path = join58(prepared, name2);
64673
- if (!existsSync26(path)) return;
65055
+ if (!existsSync27(path)) return;
64674
65056
  const value = privateJson(path);
64675
65057
  change(value);
64676
65058
  writeJson(path, value);
@@ -64680,22 +65062,22 @@ function prepareRestore(dataDir, id, manifest) {
64680
65062
  }
64681
65063
  const oldConfig = join58(dataDir, "config.json");
64682
65064
  const newConfig = join58(prepared, "config.json");
64683
- if (existsSync26(oldConfig) || existsSync26(newConfig)) {
64684
- writeJson(newConfig, restoredWorkspaceConfig(existsSync26(newConfig) ? privateJson(newConfig) : {}, existsSync26(oldConfig) ? privateJson(oldConfig) : {}));
65065
+ if (existsSync27(oldConfig) || existsSync27(newConfig)) {
65066
+ writeJson(newConfig, restoredWorkspaceConfig(existsSync27(newConfig) ? privateJson(newConfig) : {}, existsSync27(oldConfig) ? privateJson(oldConfig) : {}));
64685
65067
  }
64686
65068
  const browserHomes = ["vm-home"];
64687
65069
  const vmHomes = join58(dataDir, "vm-homes");
64688
65070
  if (entryExists(vmHomes)) {
64689
- if (!lstatSync6(vmHomes).isDirectory()) throw new Error("Stop managed desktops and repair the VM home directory before restoring.");
65071
+ if (!lstatSync7(vmHomes).isDirectory()) throw new Error("Stop managed desktops and repair the VM home directory before restoring.");
64690
65072
  for (const name2 of readdirSync10(vmHomes)) if (validRelative(name2)) browserHomes.push(`vm-homes/${name2}`);
64691
65073
  }
64692
65074
  for (const home of browserHomes) {
64693
65075
  const homePath = join58(dataDir, home);
64694
65076
  if (!entryExists(homePath)) continue;
64695
- if (!lstatSync6(homePath).isDirectory()) throw new Error("Stop managed desktops and repair the VM home directory before restoring.");
65077
+ if (!lstatSync7(homePath).isDirectory()) throw new Error("Stop managed desktops and repair the VM home directory before restoring.");
64696
65078
  const source = join58(dataDir, home, ".browser-profiles");
64697
65079
  if (!entryExists(source)) continue;
64698
- folder(join58(prepared, home));
65080
+ folder2(join58(prepared, home));
64699
65081
  cpSync(source, join58(prepared, home, ".browser-profiles"), { recursive: true, dereference: false, verbatimSymlinks: true, errorOnExist: true, force: false });
64700
65082
  }
64701
65083
  changeJson("routines.json", (value) => {
@@ -64714,7 +65096,7 @@ function prepareRestore(dataDir, id, manifest) {
64714
65096
  });
64715
65097
  changeJson("webhooks.json", (value) => {
64716
65098
  if (!record2(value)) throw new Error("Invalid webhook definitions in workspace backup.");
64717
- const destination = existsSync26(join58(dataDir, "webhooks.json")) ? privateJson(join58(dataDir, "webhooks.json")) : {};
65099
+ const destination = existsSync27(join58(dataDir, "webhooks.json")) ? privateJson(join58(dataDir, "webhooks.json")) : {};
64718
65100
  const oldHooks = record2(destination) && Array.isArray(destination.webhooks) ? destination.webhooks : [];
64719
65101
  if (Array.isArray(value.webhooks)) {
64720
65102
  for (const webhook of value.webhooks) if (record2(webhook)) {
@@ -64731,8 +65113,8 @@ function prepareRestore(dataDir, id, manifest) {
64731
65113
  for (const call of value.calls) if (record2(call)) call.nextRunAt = null;
64732
65114
  }
64733
65115
  });
64734
- if (existsSync26(join58(prepared, "delegations.json"))) writeJson(join58(prepared, "delegations.json"), {});
64735
- if (existsSync26(join58(prepared, "browser-cleanups.json"))) writeJson(join58(prepared, "browser-cleanups.json"), []);
65116
+ if (existsSync27(join58(prepared, "delegations.json"))) writeJson(join58(prepared, "delegations.json"), {});
65117
+ if (existsSync27(join58(prepared, "browser-cleanups.json"))) writeJson(join58(prepared, "browser-cleanups.json"), []);
64736
65118
  for (const entry of manifest.entries) {
64737
65119
  if (/^messages-[^/]+\.json$/.test(entry.path)) changeJson(entry.path, (value) => {
64738
65120
  if (Array.isArray(value)) for (const message3 of value) rebaseMessage(message3, manifest.sourceDataDir, resolve15(dataDir));
@@ -64740,7 +65122,7 @@ function prepareRestore(dataDir, id, manifest) {
64740
65122
  });
64741
65123
  }
64742
65124
  const dbPath = join58(prepared, "messages.db");
64743
- if (existsSync26(dbPath)) {
65125
+ if (existsSync27(dbPath)) {
64744
65126
  const db2 = new DatabaseSync2(dbPath);
64745
65127
  try {
64746
65128
  db2.exec("BEGIN");
@@ -64772,7 +65154,7 @@ function prepareRestore(dataDir, id, manifest) {
64772
65154
  function readLastWorkspaceRestore(dataDir) {
64773
65155
  const root = backupRoot(dataDir);
64774
65156
  const path = join58(root, "last-restore.json");
64775
- if (!existsSync26(path)) return null;
65157
+ if (!existsSync27(path)) return null;
64776
65158
  const raw = privateJson(path);
64777
65159
  if (!record2(raw) || raw.restored !== true || typeof raw.id !== "string" || !ID.test(raw.id)) throw new Error("Invalid workspace restore receipt.");
64778
65160
  return raw;
@@ -64799,7 +65181,7 @@ function applyPendingWorkspaceRestore(dataDir) {
64799
65181
  const root = backupRoot(dataDir);
64800
65182
  const journalFile2 = join58(root, "restore-journal.json");
64801
65183
  const pendingFile = join58(root, "pending-restore.json");
64802
- if (existsSync26(journalFile2)) {
65184
+ if (existsSync27(journalFile2)) {
64803
65185
  const previous2 = readJournal(journalFile2);
64804
65186
  if (previous2.phase === "applying") {
64805
65187
  rollback(dataDir, previous2);
@@ -64807,7 +65189,7 @@ function applyPendingWorkspaceRestore(dataDir) {
64807
65189
  rmSync7(pendingFile, { force: true });
64808
65190
  return { restored: false, rolledBack: true, id: previous2.id, safetyCopyPath: join58(root, `safety-${previous2.id}`) };
64809
65191
  }
64810
- if (previous2.phase === "committed" && existsSync26(pendingFile)) {
65192
+ if (previous2.phase === "committed" && existsSync27(pendingFile)) {
64811
65193
  const pending2 = privateJson(pendingFile);
64812
65194
  if (record2(pending2) && pending2.id === previous2.id) {
64813
65195
  return finishRestore(dataDir, previous2.id);
@@ -64820,8 +65202,8 @@ function applyPendingWorkspaceRestore(dataDir) {
64820
65202
  const manifest = validateStaged(staged);
64821
65203
  const prepared = prepareRestore(dataDir, pending.id, manifest);
64822
65204
  const safetyCopyPath = join58(root, `safety-${pending.id}`);
64823
- if (existsSync26(safetyCopyPath)) throw new Error("A safety copy already exists for this restore. Original files have not been changed.");
64824
- folder(join58(safetyCopyPath, "data"));
65205
+ if (existsSync27(safetyCopyPath)) throw new Error("A safety copy already exists for this restore. Original files have not been changed.");
65206
+ folder2(join58(safetyCopyPath, "data"));
64825
65207
  const journal = {
64826
65208
  id: pending.id,
64827
65209
  phase: "applying",
@@ -64832,7 +65214,7 @@ function applyPendingWorkspaceRestore(dataDir) {
64832
65214
  if (!validRelative(name2) || name2.includes("/") || preserved(name2)) throw new Error("Unsafe workspace restore target.");
64833
65215
  }
64834
65216
  writeJson(join58(safetyCopyPath, "manifest.json"), { id: pending.id, createdAt: (/* @__PURE__ */ new Date()).toISOString(), originalEntries: journal.existing });
64835
- if (existsSync26(join58(root, "last-restore.json"))) writeJson(join58(safetyCopyPath, "previous-restore.json"), privateJson(join58(root, "last-restore.json")));
65217
+ if (existsSync27(join58(root, "last-restore.json"))) writeJson(join58(safetyCopyPath, "previous-restore.json"), privateJson(join58(root, "last-restore.json")));
64836
65218
  writeJson(journalFile2, journal);
64837
65219
  try {
64838
65220
  for (const name2 of journal.existing) renameSync7(join58(dataDir, name2), join58(safetyCopyPath, "data", name2));
@@ -64851,13 +65233,13 @@ function removeWorkspaceBackupJob(dataDir, id) {
64851
65233
  const path = jobPath(dataDir, id);
64852
65234
  const root = backupRoot(dataDir);
64853
65235
  for (const name2 of ["pending-restore.json", "restore-journal.json", "last-restore.json"]) {
64854
- if (!existsSync26(join58(root, name2))) continue;
65236
+ if (!existsSync27(join58(root, name2))) continue;
64855
65237
  const value = privateJson(join58(root, name2));
64856
65238
  if (record2(value) && value.id === id) throw new Error("This backup is still needed for restore or recovery and cannot be removed.");
64857
65239
  }
64858
- if (existsSync26(join58(root, `safety-${id}`))) throw new Error("This backup has a recovery copy and cannot be removed.");
65240
+ if (existsSync27(join58(root, `safety-${id}`))) throw new Error("This backup has a recovery copy and cannot be removed.");
64859
65241
  if (!entryExists(path)) return;
64860
- if (!lstatSync6(path).isDirectory() || lstatSync6(path).isSymbolicLink()) throw new Error("Unsafe workspace backup cleanup target.");
65242
+ if (!lstatSync7(path).isDirectory() || lstatSync7(path).isSymbolicLink()) throw new Error("Unsafe workspace backup cleanup target.");
64861
65243
  rmSync7(path, { recursive: true, force: true });
64862
65244
  }
64863
65245
 
@@ -64867,7 +65249,7 @@ var MAX_UPLOAD_BYTES = MAX_WORKSPACE_BACKUP_UPLOAD_BYTES;
64867
65249
  var MAX_CLIENT_STATE_BYTES = 2 * 1024 ** 2;
64868
65250
  var EXPIRES_MS = 60 * 6e4;
64869
65251
  var passwordSchema = external_exports.string().min(12, "Use a password of at least 12 characters.").max(1024);
64870
- var failure = (message3, status = 400) => Object.assign(new Error(message3), { status });
65252
+ var failure3 = (message3, status = 400) => Object.assign(new Error(message3), { status });
64871
65253
  function isWorkspaceBackupSessionControl(method, path) {
64872
65254
  return method === "GET" && path === "/api/auth/sessions" || method === "POST" && path === "/api/auth/logout" || method === "DELETE" && /^\/api\/auth\/sessions\/[\w-]+$/.test(path);
64873
65255
  }
@@ -64880,7 +65262,7 @@ function createWorkspaceBackupRoutes(options) {
64880
65262
  };
64881
65263
  const owner2 = (auth) => auth.kind === "session" ? auth.session.id : "local-owner";
64882
65264
  const check2 = (req, auth) => {
64883
- if (!options.authorized(req, auth)) throw failure("Your session changed. Reopen Backups and try again.", 403);
65265
+ if (!options.authorized(req, auth)) throw failure3("Your session changed. Reopen Backups and try again.", 403);
64884
65266
  };
64885
65267
  function remove(id, artifact) {
64886
65268
  if (artifact.path) {
@@ -64899,7 +65281,7 @@ function createWorkspaceBackupRoutes(options) {
64899
65281
  }
64900
65282
  function reclaimAbandoned() {
64901
65283
  const root = join59(options.dataDir, ".backups");
64902
- if (!existsSync27(root) || lstatSync7(root).isSymbolicLink()) return;
65284
+ if (!existsSync28(root) || lstatSync8(root).isSymbolicLink()) return;
64903
65285
  for (const entry of readdirSync11(root, { withFileTypes: true })) {
64904
65286
  if (!entry.isDirectory() || !/^[a-f0-9-]{36}$/.test(entry.name)) continue;
64905
65287
  if (statSync10(join59(root, entry.name)).mtimeMs + EXPIRES_MS > Date.now()) continue;
@@ -64909,7 +65291,7 @@ function createWorkspaceBackupRoutes(options) {
64909
65291
  }
64910
65292
  }
64911
65293
  const uploads = join59(root, "uploads");
64912
- if (!existsSync27(uploads) || lstatSync7(uploads).isSymbolicLink()) return;
65294
+ if (!existsSync28(uploads) || lstatSync8(uploads).isSymbolicLink()) return;
64913
65295
  for (const entry of readdirSync11(uploads, { withFileTypes: true })) {
64914
65296
  if (!entry.isFile() || !/^[a-f0-9-]{36}\.ombbackup$/.test(entry.name)) continue;
64915
65297
  const file2 = join59(uploads, entry.name);
@@ -64939,12 +65321,12 @@ function createWorkspaceBackupRoutes(options) {
64939
65321
  expire();
64940
65322
  const artifact = typeof id === "string" ? artifacts.get(id) : void 0;
64941
65323
  if (!artifact || artifact.owner !== owner2(auth) || artifact.kind !== kind) {
64942
- throw failure("This backup has expired or belongs to another session. Select the file again.", 404);
65324
+ throw failure3("This backup has expired or belongs to another session. Select the file again.", 404);
64943
65325
  }
64944
65326
  return artifact;
64945
65327
  };
64946
65328
  async function operate(work) {
64947
- if (operating) throw failure("Another backup operation is in progress. Wait for it to finish.", 409);
65329
+ if (operating) throw failure3("Another backup operation is in progress. Wait for it to finish.", 409);
64948
65330
  operating = true;
64949
65331
  try {
64950
65332
  return await work();
@@ -64969,18 +65351,18 @@ function createWorkspaceBackupRoutes(options) {
64969
65351
  if (method === "POST" && path === `${PREFIX}/client-state`) {
64970
65352
  const body = external_exports.object({ restoreId: external_exports.string().uuid() }).parse(await options.readBody(req));
64971
65353
  check2(req, auth);
64972
- if (!options.restored.restored || body.restoreId !== options.restored.id) throw failure("This restore has not completed. Restart OpenMausBot first.", 409);
65354
+ if (!options.restored.restored || body.restoreId !== options.restored.id) throw failure3("This restore has not completed. Restart OpenMausBot first.", 409);
64973
65355
  json4(res, 200, { clientState: options.restored.clientState ?? {} });
64974
65356
  return true;
64975
65357
  }
64976
65358
  if (method === "POST" && path === `${PREFIX}/export`) {
64977
65359
  const body = external_exports.object({ password: passwordSchema, clientState: external_exports.record(external_exports.string(), external_exports.string()).default({}) }).parse(await options.readBody(req, MAX_CLIENT_STATE_BYTES + 8 * 1024));
64978
- if (Buffer.byteLength(JSON.stringify(body.clientState)) > MAX_CLIENT_STATE_BYTES) throw failure("Saved drafts exceed the 2 MB backup preference limit.", 413);
65360
+ if (Buffer.byteLength(JSON.stringify(body.clientState)) > MAX_CLIENT_STATE_BYTES) throw failure3("Saved drafts exceed the 2 MB backup preference limit.", 413);
64979
65361
  const clientState = Object.fromEntries(Object.entries(body.clientState).filter(([key3]) => WORKSPACE_BACKUP_CLIENT_KEYS.includes(key3)));
64980
65362
  const result = await operate(() => options.exclusive(async () => {
64981
65363
  check2(req, auth);
64982
65364
  replacePrevious(auth, ["download"]);
64983
- if (artifacts.size >= 4) throw failure("There are already four backup files in progress. Restart or wait an hour before creating another.", 409);
65365
+ if (artifacts.size >= 4) throw failure3("There are already four backup files in progress. Restart or wait an hour before creating another.", 409);
64984
65366
  return createWorkspaceBackup(options.dataDir, { password: body.password, clientState, appVersion: options.appVersion });
64985
65367
  }));
64986
65368
  try {
@@ -65009,25 +65391,25 @@ function createWorkspaceBackupRoutes(options) {
65009
65391
  if (method === "POST" && path === `${PREFIX}/upload`) {
65010
65392
  await operate(async () => {
65011
65393
  const length = Number(req.headers["content-length"]);
65012
- if (!Number.isSafeInteger(length) || length <= 0 || length > MAX_UPLOAD_BYTES) throw failure("Select a backup containing no more than 10 GB of workspace data.", 413);
65394
+ 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);
65013
65395
  replacePrevious(auth, ["upload", "staged"]);
65014
- if (artifacts.size >= 4) throw failure("There are already four backup files in progress. Restart or wait an hour before uploading another.", 409);
65396
+ if (artifacts.size >= 4) throw failure3("There are already four backup files in progress. Restart or wait an hour before uploading another.", 409);
65015
65397
  const root = join59(options.dataDir, ".backups");
65016
65398
  mkdirSync29(root, { recursive: true, mode: 448 });
65017
- if (lstatSync7(root).isSymbolicLink()) throw failure("Backup storage must not be a symbolic link.");
65399
+ if (lstatSync8(root).isSymbolicLink()) throw failure3("Backup storage must not be a symbolic link.");
65018
65400
  const directory = join59(root, "uploads");
65019
65401
  mkdirSync29(directory, { recursive: true, mode: 448 });
65020
- if (lstatSync7(directory).isSymbolicLink()) throw failure("Backup storage must not be a symbolic link.");
65021
- const id = randomUUID23();
65402
+ if (lstatSync8(directory).isSymbolicLink()) throw failure3("Backup storage must not be a symbolic link.");
65403
+ const id = randomUUID25();
65022
65404
  const file2 = join59(directory, `${id}.ombbackup`);
65023
65405
  let bytes = 0;
65024
65406
  try {
65025
65407
  const limit = new Transform2({ transform(chunk, _encoding, callback) {
65026
65408
  bytes += chunk.length;
65027
- callback(bytes > MAX_UPLOAD_BYTES ? failure("The backup is too large.", 413) : null, chunk);
65409
+ callback(bytes > MAX_UPLOAD_BYTES ? failure3("The backup is too large.", 413) : null, chunk);
65028
65410
  } });
65029
65411
  await pipeline3(req.iterator({ destroyOnReturn: false }), limit, createWriteStream3(file2, { flags: "wx", mode: 384 }));
65030
- if (bytes !== length) throw failure("The upload was incomplete. Select the file again.");
65412
+ if (bytes !== length) throw failure3("The upload was incomplete. Select the file again.");
65031
65413
  check2(req, auth);
65032
65414
  artifacts.set(id, { owner: owner2(auth), kind: "upload", path: file2, expires: Date.now() + EXPIRES_MS });
65033
65415
  json4(res, 200, { id });
@@ -65681,17 +66063,17 @@ function unavailable(error51) {
65681
66063
  }
65682
66064
 
65683
66065
  // server/provider-auth-sessions.ts
65684
- var failure2 = (message3, status) => Object.assign(new Error(message3), { status });
66066
+ var failure4 = (message3, status) => Object.assign(new Error(message3), { status });
65685
66067
  var ProviderAuthSessions = class {
65686
66068
  flows = /* @__PURE__ */ new Map();
65687
66069
  get active() {
65688
66070
  return [...this.flows.values()].some((flow3) => flow3.busy || !flow3.revoked && flow3.expiresAt > Date.now());
65689
66071
  }
65690
66072
  async start(instance, owner2) {
65691
- if (!instance.startAuthentication) throw failure2("Account setup is unavailable for this provider.", 404);
66073
+ if (!instance.startAuthentication) throw failure4("Account setup is unavailable for this provider.", 404);
65692
66074
  const existing = this.flows.get(instance.instanceId);
65693
66075
  if (existing && (existing.busy || existing.owner !== owner2 && existing.expiresAt > Date.now())) {
65694
- throw failure2("A sign-in is already in progress. Finish or cancel it in the browser that started it, or wait for it to expire.", 409);
66076
+ throw failure4("A sign-in is already in progress. Finish or cancel it in the browser that started it, or wait for it to expire.", 409);
65695
66077
  }
65696
66078
  const resuming = existing && existing.owner === owner2 && existing.expiresAt > Date.now() && !existing.revoked;
65697
66079
  const flow3 = resuming ? existing : { instance, owner: owner2, flowId: null, expiresAt: Date.now() + 15 * 6e4, busy: false, starting: false, revoked: false };
@@ -65702,7 +66084,7 @@ var ProviderAuthSessions = class {
65702
66084
  const result = await instance.startAuthentication();
65703
66085
  if (flow3.revoked || this.flows.get(instance.instanceId) !== flow3) {
65704
66086
  await instance.cancelAuthentication?.();
65705
- throw failure2("Sign-in cancelled because the session or provider changed.", 409);
66087
+ throw failure4("Sign-in cancelled because the session or provider changed.", 409);
65706
66088
  }
65707
66089
  flow3.flowId = result.flowId;
65708
66090
  const expiry = Date.parse(result.expiresAt ?? "");
@@ -65720,22 +66102,22 @@ var ProviderAuthSessions = class {
65720
66102
  owned(instanceId, owner2, flowId) {
65721
66103
  const flow3 = this.flows.get(instanceId);
65722
66104
  if (!flowId || !flow3 || flow3.revoked || flow3.owner !== owner2 || flow3.flowId !== flowId) {
65723
- throw failure2("This sign-in is no longer available in this browser. Start a new sign-in.", 404);
66105
+ throw failure4("This sign-in is no longer available in this browser. Start a new sign-in.", 404);
65724
66106
  }
65725
- if (flow3.busy) throw failure2("The sign-in is still being updated. Try again shortly.", 409);
66107
+ if (flow3.busy) throw failure4("The sign-in is still being updated. Try again shortly.", 409);
65726
66108
  return flow3;
65727
66109
  }
65728
66110
  async status(instanceId, owner2, flowId) {
65729
66111
  const flow3 = this.owned(instanceId, owner2, flowId);
65730
- if (!flow3.instance.getAuthentication) throw failure2("Sign-in status is unavailable for this provider.", 404);
66112
+ if (!flow3.instance.getAuthentication) throw failure4("Sign-in status is unavailable for this provider.", 404);
65731
66113
  const status = await flow3.instance.getAuthentication(flowId);
65732
- if (flow3.revoked) throw failure2("This sign-in session ended.", 404);
66114
+ if (flow3.revoked) throw failure4("This sign-in session ended.", 404);
65733
66115
  if (status.phase !== "waiting") flow3.expiresAt = 0;
65734
66116
  return status;
65735
66117
  }
65736
66118
  async complete(instanceId, owner2, flowId, callbackUrl) {
65737
66119
  const flow3 = this.owned(instanceId, owner2, flowId);
65738
- if (!flow3.instance.completeAuthentication) throw failure2("This provider does not use a callback URL.", 400);
66120
+ if (!flow3.instance.completeAuthentication) throw failure4("This provider does not use a callback URL.", 400);
65739
66121
  flow3.busy = true;
65740
66122
  try {
65741
66123
  await flow3.instance.completeAuthentication(flowId, callbackUrl);
@@ -65758,10 +66140,10 @@ var ProviderAuthSessions = class {
65758
66140
  * admin is still completing must not be pulled away underneath them, and
65759
66141
  * nobody may start one while the credential is being removed. */
65760
66142
  async signOut(instance, owner2) {
65761
- if (!instance.signOut) throw failure2("Sign-out is unavailable for this provider.", 404);
66143
+ if (!instance.signOut) throw failure4("Sign-out is unavailable for this provider.", 404);
65762
66144
  const existing = this.flows.get(instance.instanceId);
65763
66145
  if (existing && (existing.busy || existing.owner !== owner2 && existing.expiresAt > Date.now() && !existing.revoked)) {
65764
- throw failure2("A sign-in is in progress. Finish or cancel it in the browser that started it, or wait for it to expire.", 409);
66146
+ throw failure4("A sign-in is in progress. Finish or cancel it in the browser that started it, or wait for it to expire.", 409);
65765
66147
  }
65766
66148
  const flow3 = { instance, owner: owner2, flowId: null, expiresAt: Date.now() + 6e4, busy: true, starting: false, revoked: false };
65767
66149
  this.flows.set(instance.instanceId, flow3);
@@ -65796,7 +66178,7 @@ var ProviderAuthSessions = class {
65796
66178
  };
65797
66179
 
65798
66180
  // server/request-auth.ts
65799
- import { timingSafeEqual as timingSafeEqual2 } from "node:crypto";
66181
+ import { timingSafeEqual as timingSafeEqual3 } from "node:crypto";
65800
66182
  import { isIP as isIP2 } from "node:net";
65801
66183
 
65802
66184
  // companion/src/routes.ts
@@ -66063,6 +66445,11 @@ var CLIENT_ALLOW = [
66063
66445
  { methods: ["GET"], path: /^\/api\/auth\/session$/ },
66064
66446
  { methods: ["POST"], path: /^\/api\/auth\/stream-ticket$/ },
66065
66447
  { methods: ["POST"], path: /^\/api\/auth\/logout$/ },
66448
+ // Own outbound desktop connector, additionally bound to a private secret.
66449
+ // Only honoured while features.sharedComputers is on (see requiredScope):
66450
+ // with the feature off these paths are as unlisted as any other, so a
66451
+ // client session is refused exactly the way an unknown route refuses it.
66452
+ { methods: ["POST"], path: /^\/api\/shared-computers\/(?:connect|[\w-]+\/(?:poll|lease|result|disconnect))$/, feature: "sharedComputers" },
66066
66453
  // liveness, identity, the stream
66067
66454
  { methods: ["GET"], path: /^\/api\/health$/ },
66068
66455
  { methods: ["GET"], path: /^\/api\/edition$/ },
@@ -66127,9 +66514,10 @@ var CLIENT_ALLOW = [
66127
66514
  // configured-or-not booleans; the handler strips the few identifying fields for clients
66128
66515
  { methods: ["GET"], path: /^\/api\/config$/ }
66129
66516
  ];
66130
- function requiredScope(method, path) {
66517
+ function requiredScope(method, path, features = {}) {
66131
66518
  const upper = method.toUpperCase();
66132
66519
  for (const rule of CLIENT_ALLOW) {
66520
+ if (rule.feature && features[rule.feature] !== true) continue;
66133
66521
  if (rule.path.test(path) && rule.methods.includes(upper)) return "client";
66134
66522
  }
66135
66523
  return "admin";
@@ -66157,7 +66545,7 @@ function secureTokenMatch(actual, expected) {
66157
66545
  if (!actual || !expected) return false;
66158
66546
  const left = Buffer.from(actual);
66159
66547
  const right = Buffer.from(expected);
66160
- return left.length === right.length && timingSafeEqual2(left, right);
66548
+ return left.length === right.length && timingSafeEqual3(left, right);
66161
66549
  }
66162
66550
  function resolveRequestAuth(req, options) {
66163
66551
  const method = req.method ?? "GET";
@@ -66180,13 +66568,16 @@ function resolveRequestAuth(req, options) {
66180
66568
  }
66181
66569
  if (session && via) {
66182
66570
  if (via === "cookie" && !isSameOrigin(req)) return deny(403, "forbidden: cross-origin request");
66183
- const needed = requiredScope(method, path);
66571
+ const needed = requiredScope(method, path, options.features ?? {});
66184
66572
  if (!session.scopes.includes(needed)) {
66185
66573
  return deny(403, `forbidden: this session lacks the ${needed} scope`);
66186
66574
  }
66187
66575
  if (via !== "ticket") options.sessions.renew(session.id);
66188
66576
  return { auth: { kind: "session", session, via, scopes: session.scopes }, status: 401, error: "" };
66189
66577
  }
66578
+ if (via) {
66579
+ return deny(401, "unauthorized: this session has expired or was revoked; pair this device again");
66580
+ }
66190
66581
  const proxied = isProxied(req);
66191
66582
  const loopback2 = !proxied && isLoopbackHost(headerValue(req.headers.host)) && isAllowedOrigin(headerValue(req.headers.origin));
66192
66583
  if (loopback2) {
@@ -66200,9 +66591,6 @@ function resolveRequestAuth(req, options) {
66200
66591
  }
66201
66592
  return { auth: { kind: "loopback", scopes: LOOPBACK_SCOPES }, status: 401, error: "" };
66202
66593
  }
66203
- if (via) {
66204
- return deny(401, "unauthorized: this session has expired or was revoked; pair this device again");
66205
- }
66206
66594
  if (proxied) {
66207
66595
  return deny(403, "forbidden: this request came through a proxy (pair this device to use the server remotely)");
66208
66596
  }
@@ -66213,8 +66601,8 @@ function resolveRequestAuth(req, options) {
66213
66601
  }
66214
66602
 
66215
66603
  // server/sessions.ts
66216
- import { createHash as createHash27, randomBytes as randomBytes8, randomUUID as randomUUID24, timingSafeEqual as timingSafeEqual3 } from "node:crypto";
66217
- import { chmodSync as chmodSync7, existsSync as existsSync28, mkdirSync as mkdirSync30, readFileSync as readFileSync40, renameSync as renameSync8, writeFileSync as writeFileSync18 } from "node:fs";
66604
+ import { createHash as createHash27, randomBytes as randomBytes8, randomUUID as randomUUID26, timingSafeEqual as timingSafeEqual4 } from "node:crypto";
66605
+ import { closeSync as closeSync12, existsSync as existsSync29, fsyncSync as fsyncSync4, mkdirSync as mkdirSync30, openSync as openSync12, readFileSync as readFileSync41, unlinkSync as unlinkSync12 } from "node:fs";
66218
66606
  import { dirname as dirname18 } from "node:path";
66219
66607
  var SCOPES = ["admin", "client"];
66220
66608
  var PAIRING_CODE_ALPHABET = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ";
@@ -66247,14 +66635,16 @@ var sessionSchema = external_exports.object({
66247
66635
  expiresAt: external_exports.number(),
66248
66636
  /** Set when the session came from an account sign-in rather than a code. */
66249
66637
  userId: external_exports.string().max(256).optional(),
66250
- email: external_exports.string().max(320).optional()
66638
+ email: external_exports.string().max(320).optional(),
66639
+ /** Set only by the internal verified-portal issuance path, never by email or pairing input. */
66640
+ membershipAuthority: external_exports.literal("portal").optional()
66251
66641
  });
66252
- var fileSchema2 = external_exports.object({ version: external_exports.literal(1), sessions: external_exports.array(sessionSchema) });
66642
+ var fileSchema3 = external_exports.object({ version: external_exports.literal(1), sessions: external_exports.array(sessionSchema) });
66253
66643
  var sha2562 = (value) => createHash27("sha256").update(value).digest("hex");
66254
66644
  function sameDigest(a, b2) {
66255
66645
  const left = Buffer.from(a, "hex");
66256
66646
  const right = Buffer.from(b2, "hex");
66257
- return left.length === right.length && timingSafeEqual3(left, right);
66647
+ return left.length === right.length && timingSafeEqual4(left, right);
66258
66648
  }
66259
66649
  function generatePairingCode() {
66260
66650
  const limit = Math.floor(256 / PAIRING_CODE_ALPHABET.length) * PAIRING_CODE_ALPHABET.length;
@@ -66296,34 +66686,69 @@ var SessionRegistry = class {
66296
66686
  lastSeenWrites = /* @__PURE__ */ new Map();
66297
66687
  now;
66298
66688
  options;
66689
+ openMarker;
66690
+ closed = false;
66299
66691
  // No parameter properties: the server runs this file under Node's
66300
66692
  // strip-only TypeScript mode, which only erases types.
66301
66693
  constructor(options) {
66302
66694
  this.options = options;
66303
66695
  this.now = options.now ?? Date.now;
66304
- this.load();
66696
+ this.openMarker = `${options.file}.open`;
66697
+ const unclean = existsSync29(this.openMarker);
66698
+ try {
66699
+ mkdirSync30(dirname18(options.file), { recursive: true, mode: 448 });
66700
+ writeFileAtomic(this.openMarker, "Session registry is open.\n", { mode: 384 });
66701
+ this.syncDirectory();
66702
+ } catch {
66703
+ throw new Error("Could not establish safe session storage.");
66704
+ }
66705
+ this.load(!unclean);
66706
+ this.revalidateEmailSessions();
66305
66707
  }
66306
- load() {
66307
- if (!existsSync28(this.options.file)) return;
66708
+ load(restoreAccounts) {
66709
+ if (!existsSync29(this.options.file)) return;
66308
66710
  let raw;
66309
66711
  try {
66310
- raw = JSON.parse(readFileSync40(this.options.file, "utf8"));
66712
+ raw = JSON.parse(readFileSync41(this.options.file, "utf8"));
66311
66713
  } catch {
66312
66714
  return;
66313
66715
  }
66314
- const parsed = fileSchema2.safeParse(raw);
66315
- if (parsed.success) this.sessions = parsed.data.sessions;
66716
+ const parsed = fileSchema3.safeParse(raw);
66717
+ if (parsed.success) this.sessions = parsed.data.sessions.filter((session) => restoreAccounts || session.email === void 0 && session.userId === void 0);
66718
+ }
66719
+ syncDirectory() {
66720
+ if (process.platform === "win32") return;
66721
+ const fd = openSync12(dirname18(this.options.file), "r");
66722
+ try {
66723
+ fsyncSync4(fd);
66724
+ } finally {
66725
+ closeSync12(fd);
66726
+ }
66316
66727
  }
66317
66728
  /** Atomic write, owner-only, directory owner-only. */
66318
66729
  persist() {
66730
+ if (this.closed) throw new Error("Session registry is closed.");
66319
66731
  const dir = dirname18(this.options.file);
66320
66732
  mkdirSync30(dir, { recursive: true, mode: 448 });
66321
- const tmp = `${this.options.file}.${process.pid}.tmp`;
66322
- writeFileSync18(tmp, JSON.stringify({ version: 1, sessions: this.sessions }, null, 2) + "\n", { mode: 384 });
66323
- chmodSync7(tmp, 384);
66324
- renameSync8(tmp, this.options.file);
66733
+ writeFileAtomic(this.options.file, JSON.stringify({ version: 1, sessions: this.sessions }, null, 2) + "\n", { mode: 384 });
66734
+ this.syncDirectory();
66735
+ }
66736
+ /** Call after HTTP/stream shutdown. A failed close leaves the open marker,
66737
+ * so account sessions require fresh sign-in after restart. Paired devices
66738
+ * remain durable. Never remove the marker before the reduced set is saved. */
66739
+ close() {
66740
+ if (this.closed) return;
66741
+ try {
66742
+ this.revalidateEmailSessions();
66743
+ this.persist();
66744
+ unlinkSync12(this.openMarker);
66745
+ this.closed = true;
66746
+ } catch {
66747
+ throw new Error("Could not safely close session storage.");
66748
+ }
66325
66749
  }
66326
66750
  prune() {
66751
+ this.revalidateEmailSessions();
66327
66752
  const now = this.now();
66328
66753
  this.pairings = this.pairings.filter((p2) => p2.expiresAt > now);
66329
66754
  this.replays = this.replays.filter((r) => r.expiresAt > now);
@@ -66356,7 +66781,7 @@ var SessionRegistry = class {
66356
66781
  const code = generatePairingCode();
66357
66782
  const scopes = input.scopes?.length ? [...new Set(input.scopes)] : [...SCOPES];
66358
66783
  const pairing = {
66359
- id: randomUUID24(),
66784
+ id: randomUUID26(),
66360
66785
  codeHash: sha2562(code),
66361
66786
  scopes,
66362
66787
  label: (input.label ?? "").trim().slice(0, 80),
@@ -66428,7 +66853,7 @@ var SessionRegistry = class {
66428
66853
  this.failures.delete(input.source);
66429
66854
  const token = `omb_sess_${randomBytes8(32).toString("base64url")}`;
66430
66855
  const record3 = {
66431
- id: randomUUID24(),
66856
+ id: randomUUID26(),
66432
66857
  tokenHash: sha2562(token),
66433
66858
  label: (input.label.trim() || pairing.label || input.fallbackLabel?.trim() || "Unnamed device").slice(0, 80),
66434
66859
  scopes: [...pairing.scopes],
@@ -66448,11 +66873,20 @@ var SessionRegistry = class {
66448
66873
  /** A session from a verified account sign-in (server/account-signin.ts)
66449
66874
  * rather than a pairing code: same token, same term, same gates. */
66450
66875
  issue(input) {
66876
+ return this.issueAccount(input);
66877
+ }
66878
+ /** Internal hosted-bridge seam: call only after consuming a verified,
66879
+ * workspace-bound PKCE grant. No HTTP route accepts this marker as input. */
66880
+ issuePortal(input) {
66881
+ if (!/^[A-Za-z0-9_-]{43}$/.test(input.grant) || !input.email) throw new Error("A verified portal identity is required");
66882
+ return this.issueAccount({ label: "Hosted workspace", email: input.email, userId: `portal:${input.grant}`, scopes: input.scopes }, "portal");
66883
+ }
66884
+ issueAccount(input, membershipAuthority) {
66451
66885
  this.prune();
66452
66886
  const now = this.now();
66453
66887
  const token = `omb_sess_${randomBytes8(32).toString("base64url")}`;
66454
66888
  const record3 = {
66455
- id: randomUUID24(),
66889
+ id: randomUUID26(),
66456
66890
  tokenHash: sha2562(token),
66457
66891
  label: (input.label.trim() || "Unnamed device").slice(0, 80),
66458
66892
  scopes: [...new Set(input.scopes)],
@@ -66462,6 +66896,7 @@ var SessionRegistry = class {
66462
66896
  };
66463
66897
  if (input.userId) record3.userId = input.userId;
66464
66898
  if (input.email) record3.email = input.email;
66899
+ if (membershipAuthority) record3.membershipAuthority = membershipAuthority;
66465
66900
  this.sessions.push(record3);
66466
66901
  this.lastSeenWrites.set(record3.id, now);
66467
66902
  this.persist();
@@ -66479,8 +66914,41 @@ var SessionRegistry = class {
66479
66914
  this.failures.delete(source);
66480
66915
  }
66481
66916
  // ── sessions ───────────────────────────────────────────────────────────
66917
+ /** Email membership is live, not a thirty-day grant. Losing any issued
66918
+ * scope revokes the token and its streams; signing in again obtains the
66919
+ * new role. Promotions never widen existing credentials. Pairing sessions
66920
+ * have no email and remain independent of the hosted sign-in list. */
66921
+ revalidateEmailSessions() {
66922
+ if (this.closed) throw new Error("Session registry is closed.");
66923
+ const eligible = this.sessions.filter((session) => session.email !== void 0 && !(this.options.portalMembership && session.membershipAuthority === "portal"));
66924
+ if (!eligible.length) return;
66925
+ let resolveScopes = this.options.emailScopes;
66926
+ try {
66927
+ if (this.options.emailScopesSnapshot) resolveScopes = this.options.emailScopesSnapshot();
66928
+ } catch {
66929
+ resolveScopes = void 0;
66930
+ }
66931
+ const revoked = eligible.filter((session) => {
66932
+ try {
66933
+ const allowed = resolveScopes?.(session.email);
66934
+ return !allowed || session.scopes.some((scope) => !allowed.includes(scope));
66935
+ } catch {
66936
+ return true;
66937
+ }
66938
+ });
66939
+ if (!revoked.length) return;
66940
+ const ids = new Set(revoked.map((session) => session.id));
66941
+ this.sessions = this.sessions.filter((session) => !ids.has(session.id));
66942
+ for (const session of revoked) this.forget(session.id);
66943
+ try {
66944
+ this.persist();
66945
+ } catch {
66946
+ console.error("Could not persist email session revocation.");
66947
+ }
66948
+ }
66482
66949
  authenticate(token) {
66483
66950
  if (!token) return null;
66951
+ this.revalidateEmailSessions();
66484
66952
  const hash2 = sha2562(token);
66485
66953
  const now = this.now();
66486
66954
  const record3 = this.sessions.find((s3) => sameDigest(s3.tokenHash, hash2));
@@ -66500,6 +66968,7 @@ var SessionRegistry = class {
66500
66968
  * Nothing expired is revived. Writes at most once per half-term, so it
66501
66969
  * adds nothing to the last-seen traffic. Returns whether it renewed. */
66502
66970
  renew(sessionId) {
66971
+ this.revalidateEmailSessions();
66503
66972
  const record3 = this.sessions.find((s3) => s3.id === sessionId);
66504
66973
  const now = this.now();
66505
66974
  if (!record3 || record3.expiresAt <= now) return false;
@@ -66553,7 +67022,7 @@ var SessionRegistry = class {
66553
67022
  };
66554
67023
 
66555
67024
  // server/brand.ts
66556
- import { existsSync as existsSync29, readFileSync as readFileSync41 } from "node:fs";
67025
+ import { existsSync as existsSync30, readFileSync as readFileSync42 } from "node:fs";
66557
67026
  import { join as join60 } from "node:path";
66558
67027
  var HEX_COLOUR = /^#[0-9a-fA-F]{6}$/;
66559
67028
  var MAX_LOGO_CHARS = 3e5;
@@ -66580,10 +67049,10 @@ function loadBrand(options = {}) {
66580
67049
  const file2 = options.file ?? brandFile();
66581
67050
  const isEntitled = options.isEntitled ?? entitled;
66582
67051
  const fallback = (notice) => notice ? { brand: DEFAULT_BRAND, source: "default", file: file2, notice } : { brand: DEFAULT_BRAND, source: "default", file: file2 };
66583
- if (!existsSync29(file2)) return fallback();
67052
+ if (!existsSync30(file2)) return fallback();
66584
67053
  let raw;
66585
67054
  try {
66586
- raw = JSON.parse(readFileSync41(file2, "utf8"));
67055
+ raw = JSON.parse(readFileSync42(file2, "utf8"));
66587
67056
  } catch (error51) {
66588
67057
  return fallback(`${file2} is not valid JSON (${error51 instanceof Error ? error51.message : String(error51)}); using the default brand`);
66589
67058
  }
@@ -69094,7 +69563,7 @@ function releaseDataDirLeaseAtExit() {
69094
69563
  }
69095
69564
  process.once("exit", releaseDataDirLeaseAtExit);
69096
69565
  var workspaceRestore = { restored: false };
69097
- if (existsSync30(join61(DATA_DIR, ".backups"))) {
69566
+ if (existsSync31(join61(DATA_DIR, ".backups"))) {
69098
69567
  workspaceRestore = applyPendingWorkspaceRestore(DATA_DIR);
69099
69568
  if (!workspaceRestore.restored && !workspaceRestore.rolledBack) {
69100
69569
  workspaceRestore = readLastWorkspaceRestore(DATA_DIR) ?? workspaceRestore;
@@ -69102,8 +69571,22 @@ if (existsSync30(join61(DATA_DIR, ".backups"))) {
69102
69571
  }
69103
69572
  var workspaceMaintenance = new WorkspaceBackupMaintenance();
69104
69573
  var ENVIRONMENT_ID = loadEnvironmentId(DATA_DIR);
69105
- var sessions = new SessionRegistry({ file: join61(DATA_DIR, "sessions.json") });
69574
+ function signInAllowList() {
69575
+ const current2 = loadConfig().signIn;
69576
+ return { admins: parseAllowList(current2?.admins?.join(",")), members: parseAllowList(current2?.members?.join(",")) };
69577
+ }
69578
+ var sessions = new SessionRegistry({
69579
+ file: join61(DATA_DIR, "sessions.json"),
69580
+ emailScopesSnapshot: () => {
69581
+ const membership = signInAllowList();
69582
+ return (email3) => allowedScopes(email3, membership);
69583
+ },
69584
+ portalMembership: hostedWorkspaceConfiguration()?.portalMembership === true
69585
+ });
69586
+ var sharedComputers = new SharedComputers((id) => sessions.isLive(id));
69106
69587
  var SESSION_COOKIE = sessionCookieName(PORT, ENVIRONMENT_ID);
69588
+ var HOSTED_WORKSPACE = hostedWorkspaceConfigured();
69589
+ var workspaceAccess = null;
69107
69590
  var DESKTOP_MANAGED = process.env.OMB_DESKTOP_PARENT === "1";
69108
69591
  var desktopMutationToken = DESKTOP_MANAGED ? "" : void 0;
69109
69592
  var companionMutationToken = DESKTOP_MANAGED ? "" : void 0;
@@ -69111,10 +69594,7 @@ var FALLBACK_PUBLIC_URL = process.env.OMB_PUBLIC_URL?.trim().replace(/\/+$/, "")
69111
69594
  var cfg = loadConfig();
69112
69595
  var customDomainVerifier = createCustomDomainVerifier({ environmentId: ENVIRONMENT_ID });
69113
69596
  var emailSignIn = createEmailSignIn({
69114
- allow: () => {
69115
- const current2 = loadConfig().signIn;
69116
- return { admins: parseAllowList(current2?.admins?.join(",")), members: parseAllowList(current2?.members?.join(",")) };
69117
- }
69597
+ allow: signInAllowList
69118
69598
  });
69119
69599
  var customDomainRevision = 0;
69120
69600
  function savedCustomDomain() {
@@ -69216,7 +69696,7 @@ var INTERNAL_CAPABILITY_ORPHAN_MS = 30 * 24 * 60 * 6e4;
69216
69696
  var internalCapabilities = /* @__PURE__ */ new Map();
69217
69697
  var activeInternalGenerationByThread = /* @__PURE__ */ new Map();
69218
69698
  var internalGenerationByProviderTurn = new ProviderTurnGenerationRegistry();
69219
- function beginInternalCapabilityGeneration(threadId, generation = randomUUID25()) {
69699
+ function beginInternalCapabilityGeneration(threadId, generation = randomUUID27()) {
69220
69700
  const previous2 = activeInternalGenerationByThread.get(threadId);
69221
69701
  if (previous2) revokeInternalCapabilityGeneration(threadId, previous2);
69222
69702
  activeInternalGenerationByThread.set(threadId, generation);
@@ -69275,11 +69755,15 @@ function authorizedInternalCapability(header2) {
69275
69755
  continue;
69276
69756
  }
69277
69757
  const expected = Buffer.from(`Bearer ${token}`);
69278
- if (got.length === expected.length && timingSafeEqual4(got, expected)) return capability;
69758
+ if (got.length === expected.length && timingSafeEqual5(got, expected)) return capability;
69279
69759
  }
69280
69760
  return null;
69281
69761
  }
69282
69762
  function internalCapabilityIsActive(capability) {
69763
+ if (capability.teamComputerId) {
69764
+ const pinned = teamComputerTurns.get(capability.threadId);
69765
+ if (pinned?.computerId !== capability.teamComputerId || pinned.owner.generation !== capability.generation || pinned.botId !== capability.botId) return false;
69766
+ }
69283
69767
  if (capability.localVmTarget) {
69284
69768
  const owner2 = localVmLeaseFor(capability.localVmTarget).current(localVmOwnerBusy);
69285
69769
  if (localVmThreadTargets.get(capability.threadId) !== capability.localVmTarget || owner2?.threadId !== capability.threadId || owner2.botId !== capability.botId) return false;
@@ -69306,7 +69790,7 @@ var phoneSecretEnvelopeSchema = external_exports.object({
69306
69790
  var agentsProxyPath = SPAWNED_PROXIES.agents;
69307
69791
  var phoneProxyPath = SPAWNED_PROXIES.phone;
69308
69792
  var AGENTS_NODE_FLAG = { ELECTRON_RUN_AS_NODE: "1" };
69309
- function agentsIntegration(botId, threadId, depth, skillAuthoring, generation, roomHandoffId, roomCoordination = false) {
69793
+ function agentsIntegration(botId, threadId, depth, skillAuthoring, generation, roomHandoffId, roomCoordination = false, ownThreadCreation = false) {
69310
69794
  const token = mintInternalCapability({
69311
69795
  botId,
69312
69796
  threadId,
@@ -69317,7 +69801,8 @@ function agentsIntegration(botId, threadId, depth, skillAuthoring, generation, r
69317
69801
  createdBots: 0,
69318
69802
  openedThreads: 0,
69319
69803
  roomHandoffId,
69320
- roomCoordination
69804
+ roomCoordination,
69805
+ ownThreadCreation
69321
69806
  });
69322
69807
  return {
69323
69808
  command: process.execPath,
@@ -69330,7 +69815,11 @@ function agentsIntegration(botId, threadId, depth, skillAuthoring, generation, r
69330
69815
  OMB_COMMS_TOKEN: token,
69331
69816
  OMB_TURN_DEPTH: String(depth),
69332
69817
  OMB_ROOM_TURN: roomCoordination ? "1" : "0",
69333
- OMB_SKILL_AUTHORING_ENABLED: skillAuthoring ? "1" : "0"
69818
+ OMB_OWN_THREAD_CREATION: ownThreadCreation ? "1" : "0",
69819
+ OMB_SKILL_AUTHORING_ENABLED: skillAuthoring ? "1" : "0",
69820
+ // The shared-computer tools are advertised only while the workspace
69821
+ // gate is on; the routes behind them refuse regardless.
69822
+ OMB_SHARED_COMPUTERS_ENABLED: sharedComputersEnabled(cfg) ? "1" : "0"
69334
69823
  }
69335
69824
  };
69336
69825
  }
@@ -69360,8 +69849,10 @@ function settleDirectFollowup(generation) {
69360
69849
  var directTurnBots = /* @__PURE__ */ new Map();
69361
69850
  var providerFleetReloading = false;
69362
69851
  var turnResources = new TurnResources();
69852
+ var sharedComputerControl = new SharedComputerControl(turnResources, () => store.bots.some((bot) => botComputerControlSnapshot(bot.id).held));
69363
69853
  var turnResourceOwners = /* @__PURE__ */ new Map();
69364
69854
  var turnComputerResources = /* @__PURE__ */ new Map();
69855
+ var teamComputerTurns = /* @__PURE__ */ new Map();
69365
69856
  var settlingResourceOwners = /* @__PURE__ */ new Map();
69366
69857
  function claimTurnResource(owner2, resource) {
69367
69858
  if (!turnResources.claim(resource, owner2)) return false;
@@ -69374,6 +69865,11 @@ function releaseTurnResources(owner2) {
69374
69865
  turnResources.release(owner2);
69375
69866
  if (turnResourceOwners.get(owner2.threadId)?.generation === owner2.generation) turnResourceOwners.delete(owner2.threadId);
69376
69867
  if (turnComputerResources.get(owner2.threadId)?.owner.generation === owner2.generation) turnComputerResources.delete(owner2.threadId);
69868
+ const teamTurn = teamComputerTurns.get(owner2.threadId);
69869
+ if (teamTurn?.owner.generation === owner2.generation) {
69870
+ stopScreenPoller(teamTurn.botId, owner2.threadId);
69871
+ teamComputerTurns.delete(owner2.threadId);
69872
+ }
69377
69873
  }
69378
69874
  function bindTurnComputer(owner2, resource, exclusive = false) {
69379
69875
  if (exclusive && !claimTurnResource(owner2, resource)) {
@@ -69592,12 +70088,15 @@ function connectedAppsIntegration(botId, threadId, generation) {
69592
70088
  });
69593
70089
  }
69594
70090
  var computerControlRevision = /* @__PURE__ */ new Map();
69595
- var computerControl = new ComputerControl((botId, snapshot2) => {
69596
- computerControlRevision.set(botId, (computerControlRevision.get(botId) ?? 0) + 1);
69597
- if (snapshot2.held && /^[A-Za-z0-9_-]{1,120}$/.test(botId)) {
69598
- postDesktopPrivateMessage({ type: "openmausbot:browser-control", botId, held: true });
70091
+ var computerControl = new ComputerControl((key3, snapshot2) => {
70092
+ const members = key3.startsWith("computer_") ? store.bots.filter((bot) => inheritedTeamComputer(bot)?.id === key3.slice("computer_".length)).map((bot) => bot.id) : [key3];
70093
+ for (const botId of members) {
70094
+ computerControlRevision.set(botId, (computerControlRevision.get(botId) ?? 0) + 1);
70095
+ if (snapshot2.held && /^[A-Za-z0-9_-]{1,120}$/.test(botId)) {
70096
+ postDesktopPrivateMessage({ type: "openmausbot:browser-control", botId, held: true });
70097
+ }
70098
+ broadcast({ kind: "computer-control", botId, held: snapshot2.held, helpReason: snapshot2.helpReason });
69599
70099
  }
69600
- broadcast({ kind: "computer-control", botId, held: snapshot2.held, helpReason: snapshot2.helpReason });
69601
70100
  });
69602
70101
  var controlLeaseIdSchema = external_exports.string().min(16).max(120).regex(/^[A-Za-z0-9_-]+$/);
69603
70102
  var routineRequestSourceSchema = {
@@ -69626,6 +70125,7 @@ function controlIntegration(botId, threadId, generation, localVmTarget) {
69626
70125
  depth: 0,
69627
70126
  kind: "computer",
69628
70127
  ...localVmTarget ? { localVmTarget } : {},
70128
+ ...teamComputerTurns.get(threadId) ? { teamComputerId: teamComputerTurns.get(threadId).computerId } : {},
69629
70129
  skillAuthoring: false,
69630
70130
  createdBots: 0,
69631
70131
  openedThreads: 0
@@ -69823,6 +70323,7 @@ function checkedMemberIds(value) {
69823
70323
  }
69824
70324
  var bootSelection = { instanceId: "", model: "" };
69825
70325
  var store = new Store(() => bootSelection);
70326
+ var teamComputers = new TeamComputers(join61(DATA_DIR, "team-computers.json"), ENVIRONMENT_ID);
69826
70327
  var followupsReady = false;
69827
70328
  var sendSequencer = new SendSequencer();
69828
70329
  bootSelection = await defaultSelection();
@@ -69846,7 +70347,7 @@ var activeCoordinationForThread = (_threadId) => false;
69846
70347
  var wireTask = ({ resumeCursors: _resumeCursors, lastInstanceId: _lastInstanceId, ...task2 }) => activeCoordinationForThread(task2.threadId) && !task2.busy ? { ...task2, busy: true, activity: "working" } : task2;
69847
70348
  var wireBot = (bot) => {
69848
70349
  const { resumeCursors: _resumeCursors, tasks, approvalGrant, lastProfileRequestId: _lastProfileRequestId, lastTeamSetupReceipt: _lastTeamSetupReceipt, ...rest } = bot;
69849
- const visible = approvalGrant ? { ...rest, approvalMode: "ask", autoApprove: false } : rest;
70350
+ const visible = approvalGrant && !approvalGrant.threadOnly ? { ...rest, approvalMode: "ask", autoApprove: false } : rest;
69850
70351
  return {
69851
70352
  ...visible,
69852
70353
  ...activeCoordinationForThread(bot.threadId) && !visible.busy ? { busy: true, activity: "working" } : {},
@@ -69856,7 +70357,7 @@ var wireBot = (bot) => {
69856
70357
  };
69857
70358
  var wireTrustedApprovalBot = (bot) => {
69858
70359
  const { resumeCursors: _resumeCursors, tasks, approvalGrant: _approvalGrant, lastProfileRequestId: _lastProfileRequestId, lastTeamSetupReceipt: _lastTeamSetupReceipt, ...rest } = bot;
69859
- return { ...rest, avatarUrl: rest.avatarUrl ?? null, ...tasks ? { tasks: tasks.map(wireTask) } : {} };
70360
+ return { ...rest, approvalMode: approvalModeFor(rest), avatarUrl: rest.avatarUrl ?? null, ...tasks ? { tasks: tasks.map(wireTask) } : {} };
69860
70361
  };
69861
70362
  function previewSystemPrompt(bot) {
69862
70363
  const persona = [
@@ -69866,12 +70367,14 @@ function previewSystemPrompt(bot) {
69866
70367
  ].filter(Boolean).join(" ");
69867
70368
  const instance = registry2.get(bot.modelSelection.instanceId);
69868
70369
  const caps = instance?.adapter.capabilities;
69869
- const computerPromptKind = bot.computer === "vm" ? caps?.computerMcp ? localVmMode(cfg) === "per-bot" ? "vm-private" : "vm-shared" : null : bot.computer === "cloud" ? instance?.driverKind === "boxAgent" ? "box-agent" : caps?.computerMcp ? bot.cloudBackend === "vps" ? "vps" : "box" : null : bot.computer === "local" ? caps?.localComputerMcp ? "local" : null : null;
70370
+ const teamComputer = inheritedTeamComputer(bot);
70371
+ const previewComputer = teamComputer ? "cloud" : bot.computer;
70372
+ const computerPromptKind = previewComputer === "vm" ? caps?.computerMcp ? localVmMode(cfg) === "per-bot" ? "vm-private" : "vm-shared" : null : previewComputer === "cloud" ? instance?.driverKind === "boxAgent" ? "box-agent" : caps?.computerMcp ? bot.cloudBackend === "vps" ? "vps" : "box" : null : previewComputer === "local" ? caps?.localComputerMcp ? "local" : null : null;
69870
70373
  const peers = reachablePeers(store.bots, bot);
69871
70374
  const coordination = bot.chiefOfStaff ? chiefOfStaffSystemPrompt(bot.id, store.bots, true, openMausStatusSystemPrompt()) : peers.length > 0 ? peerRosterSystemPrompt(peers) : "";
69872
70375
  const agentsMounted = caps?.agentsMcp === true;
69873
70376
  const previewPlan = resolveSurface({
69874
- destination: bot.computer,
70377
+ destination: previewComputer,
69875
70378
  browserOn: caps?.browserMcp === true && builtInBrowserEnabled(cfg) && bot.browser !== false
69876
70379
  });
69877
70380
  const privateWorkspace = instance && !["grok", "boxAgent"].includes(instance.driverKind);
@@ -69885,6 +70388,7 @@ function previewSystemPrompt(bot) {
69885
70388
  })
69886
70389
  },
69887
70390
  { id: "computer", label: "Computer", text: computerPrompt(computerPromptKind) },
70391
+ { id: "team-computer", label: "Team computer", text: teamComputerPrompt(teamComputer) },
69888
70392
  { id: "plan", label: "Surface", text: previewPlan.note },
69889
70393
  { id: "composio", label: "Connected apps", text: caps?.composioMcp && bot.composio !== false && configured(cfg) ? COMPOSIO_PROMPT : "" },
69890
70394
  { id: "mcp", label: "MCP servers", text: caps?.customMcp ? customMcpPrompt(Object.keys(customMcpServers(cfg, bot.mcpServers))) : "" },
@@ -69972,11 +70476,21 @@ var approvalModeForTurn = (bot, peerInitiated = false) => {
69972
70476
  function handleDesktopTrustedApprovalMessage(raw) {
69973
70477
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) return false;
69974
70478
  const message3 = raw;
70479
+ const grantTarget = (bot) => bot.approvalGrant?.threadOnly ? store.projectBotForTask(bot.id, bot.approvalGrant.threadId) : bot;
70480
+ const grantBusy = (bot) => bot.approvalGrant?.threadOnly ? threadBusy(bot.id, bot.approvalGrant.threadId) : bot.busy;
70481
+ const grantSupported = (bot, mode2) => supportsApprovalMode(
70482
+ registry2.cliTarget(grantTarget(bot)?.modelSelection.instanceId ?? "")?.driverKind,
70483
+ mode2
70484
+ );
70485
+ const clearGrant = (bot) => store.patchBot(bot.id, {
70486
+ ...!bot.approvalGrant?.threadOnly ? { approvalMode: "ask", autoApprove: false } : {},
70487
+ approvalGrant: void 0
70488
+ });
69975
70489
  const threadCanReceiveGrant = (bot) => {
69976
70490
  const threadId2 = bot.approvalGrant?.threadId;
69977
70491
  if (!threadId2) return true;
69978
70492
  const target = store.projectBotForTask(bot.id, threadId2);
69979
- return Boolean(target && !threadBusy(bot.id, threadId2) && registry2.cliTarget(target.modelSelection.instanceId)?.driverKind === registry2.cliTarget(bot.modelSelection.instanceId)?.driverKind);
70493
+ return Boolean(target && !threadBusy(bot.id, threadId2) && (bot.approvalGrant?.threadOnly || registry2.cliTarget(target.modelSelection.instanceId)?.driverKind === registry2.cliTarget(bot.modelSelection.instanceId)?.driverKind));
69980
70494
  };
69981
70495
  if (message3.type === "approval-trusted-mode-commit") {
69982
70496
  const requestId2 = typeof message3.requestId === "string" && /^[A-Za-z0-9_-]{1,80}$/.test(message3.requestId) ? message3.requestId : null;
@@ -69985,13 +70499,17 @@ function handleDesktopTrustedApprovalMessage(raw) {
69985
70499
  if (!requestId2) return true;
69986
70500
  if (!botId2 || !mode2) return true;
69987
70501
  const bot = store.bot(botId2);
69988
- if (bot?.approvalGrant?.requestId === requestId2 && bot.approvalGrant.mode === mode2 && bot.approvalGrant.phase === "committed" && bot.approvalMode === mode2 && !bot.busy && threadCanReceiveGrant(bot) && supportsApprovalMode(registry2.cliTarget(bot.modelSelection.instanceId)?.driverKind, mode2)) {
70502
+ if (bot?.approvalGrant?.requestId === requestId2 && bot.approvalGrant.mode === mode2 && bot.approvalGrant.phase === "committed" && (bot.approvalGrant.threadOnly || bot.approvalMode === mode2) && !grantBusy(bot) && threadCanReceiveGrant(bot) && grantSupported(bot, mode2)) {
69989
70503
  if (bot.approvalGrant.threadId) {
69990
70504
  store.patchTask(botId2, bot.approvalGrant.threadId, { approvalMode: mode2, autoApprove: false });
69991
70505
  }
69992
70506
  store.patchBot(botId2, { approvalGrant: void 0 });
70507
+ postDesktopPrivateMessage({ type: "approval-trusted-mode-commit-result", requestId: requestId2, ok: true, bot: wireBot(store.bot(botId2)) });
69993
70508
  } else if (bot?.approvalGrant?.requestId === requestId2) {
69994
- store.patchBot(botId2, { approvalMode: "ask", autoApprove: false, approvalGrant: void 0 });
70509
+ clearGrant(bot);
70510
+ postDesktopPrivateMessage({ type: "approval-trusted-mode-commit-result", requestId: requestId2, ok: false });
70511
+ } else {
70512
+ postDesktopPrivateMessage({ type: "approval-trusted-mode-commit-result", requestId: requestId2, ok: false });
69995
70513
  }
69996
70514
  return true;
69997
70515
  }
@@ -70013,9 +70531,9 @@ function handleDesktopTrustedApprovalMessage(raw) {
70013
70531
  return true;
70014
70532
  }
70015
70533
  const bot = store.bot(botId2);
70016
- if (bot?.approvalGrant?.requestId === requestId2 && bot.approvalGrant.mode === mode2 && bot.approvalGrant.phase === "prepared" && bot.approvalMode === mode2) {
70017
- if (!supportsApprovalMode(registry2.cliTarget(bot.modelSelection.instanceId)?.driverKind, mode2)) {
70018
- store.patchBot(botId2, { approvalMode: "ask", autoApprove: false, approvalGrant: void 0 });
70534
+ if (bot?.approvalGrant?.requestId === requestId2 && bot.approvalGrant.mode === mode2 && bot.approvalGrant.phase === "prepared" && (bot.approvalGrant.threadOnly || bot.approvalMode === mode2)) {
70535
+ if (!grantSupported(bot, mode2)) {
70536
+ clearGrant(bot);
70019
70537
  confirm(false, "This provider does not support the selected approval level");
70020
70538
  return true;
70021
70539
  }
@@ -70026,7 +70544,7 @@ function handleDesktopTrustedApprovalMessage(raw) {
70026
70544
  return true;
70027
70545
  }
70028
70546
  if (bot?.approvalGrant?.requestId === requestId2) {
70029
- store.patchBot(botId2, { approvalMode: "ask", autoApprove: false, approvalGrant: void 0 });
70547
+ clearGrant(bot);
70030
70548
  }
70031
70549
  confirm(false, "The approval confirmation no longer matches this bot");
70032
70550
  return true;
@@ -70049,10 +70567,10 @@ function handleDesktopTrustedApprovalMessage(raw) {
70049
70567
  return true;
70050
70568
  }
70051
70569
  const bot = store.bot(botId2);
70052
- if (bot?.approvalGrant?.requestId === requestId2 && bot.approvalGrant.mode === mode2 && bot.approvalGrant.phase === "confirmed" && bot.approvalMode === mode2) {
70053
- if (bot.busy || !supportsApprovalMode(registry2.cliTarget(bot.modelSelection.instanceId)?.driverKind, mode2)) {
70054
- store.patchBot(botId2, { approvalMode: "ask", autoApprove: false, approvalGrant: void 0 });
70055
- activate(false, bot.busy ? "Stop this bot's turn before changing its approval level" : "This provider does not support the selected approval level");
70570
+ if (bot?.approvalGrant?.requestId === requestId2 && bot.approvalGrant.mode === mode2 && bot.approvalGrant.phase === "confirmed" && (bot.approvalGrant.threadOnly || bot.approvalMode === mode2)) {
70571
+ if (grantBusy(bot) || !grantSupported(bot, mode2)) {
70572
+ clearGrant(bot);
70573
+ activate(false, grantBusy(bot) ? "Stop this bot's turn before changing its approval level" : "This provider does not support the selected approval level");
70056
70574
  return true;
70057
70575
  }
70058
70576
  store.patchBot(botId2, { approvalGrant: { ...bot.approvalGrant, requestId: requestId2, mode: mode2, phase: "activated" } });
@@ -70060,7 +70578,7 @@ function handleDesktopTrustedApprovalMessage(raw) {
70060
70578
  return true;
70061
70579
  }
70062
70580
  if (bot?.approvalGrant?.requestId === requestId2) {
70063
- store.patchBot(botId2, { approvalMode: "ask", autoApprove: false, approvalGrant: void 0 });
70581
+ clearGrant(bot);
70064
70582
  }
70065
70583
  activate(false, "The approval activation no longer matches this bot");
70066
70584
  return true;
@@ -70083,13 +70601,13 @@ function handleDesktopTrustedApprovalMessage(raw) {
70083
70601
  return true;
70084
70602
  }
70085
70603
  const bot = store.bot(botId2);
70086
- if (bot?.approvalGrant?.requestId === requestId2 && bot.approvalGrant.mode === mode2 && bot.approvalGrant.phase === "activated" && bot.approvalMode === mode2 && !bot.busy && threadCanReceiveGrant(bot) && supportsApprovalMode(registry2.cliTarget(bot.modelSelection.instanceId)?.driverKind, mode2)) {
70604
+ if (bot?.approvalGrant?.requestId === requestId2 && bot.approvalGrant.mode === mode2 && bot.approvalGrant.phase === "activated" && (bot.approvalGrant.threadOnly || bot.approvalMode === mode2) && !grantBusy(bot) && threadCanReceiveGrant(bot) && grantSupported(bot, mode2)) {
70087
70605
  store.patchBot(botId2, { approvalGrant: { ...bot.approvalGrant, requestId: requestId2, mode: mode2, phase: "committed" } });
70088
70606
  finalize2(true);
70089
70607
  return true;
70090
70608
  }
70091
70609
  if (bot?.approvalGrant?.requestId === requestId2) {
70092
- store.patchBot(botId2, { approvalMode: "ask", autoApprove: false, approvalGrant: void 0 });
70610
+ clearGrant(bot);
70093
70611
  }
70094
70612
  finalize2(false, bot?.busy ? "Stop this bot's turn before changing its approval level" : "The approval finalization no longer matches this bot");
70095
70613
  return true;
@@ -70121,6 +70639,38 @@ function handleDesktopTrustedApprovalMessage(raw) {
70121
70639
  }
70122
70640
  const currentMode = approvalModeFor(existing);
70123
70641
  const threadId = message3.threadId;
70642
+ if (message3.threadOnly !== void 0 && typeof message3.threadOnly !== "boolean") {
70643
+ respond({ ok: false, error: "Invalid thread approval scope" });
70644
+ return true;
70645
+ }
70646
+ if (message3.threadOnly === true) {
70647
+ const target = typeof threadId === "string" ? store.projectBotForTask(botId, threadId) : null;
70648
+ if (!target || message3.modelSelection !== void 0 || message3.updateBotDefault !== void 0) {
70649
+ respond({ ok: false, error: "Choose an existing thread for this approval change" });
70650
+ return true;
70651
+ }
70652
+ const clearsOwnGrant = mode === "ask" && existing.approvalGrant?.threadOnly && existing.approvalGrant.threadId === threadId;
70653
+ if (existing.approvalGrant && !clearsOwnGrant || threadBusy(botId, target.threadId)) {
70654
+ respond({ ok: false, error: "Stop this thread and finish its pending approval change first" });
70655
+ return true;
70656
+ }
70657
+ if (!supportsApprovalMode(registry2.cliTarget(target.modelSelection.instanceId)?.driverKind, mode)) {
70658
+ respond({ ok: false, error: "This thread's provider does not support that approval level" });
70659
+ return true;
70660
+ }
70661
+ if (mode === "auto" && target.computer === "local" && approvalModeFor(target) !== "auto" && message3.acknowledgeLocalAuto !== true) {
70662
+ respond({ ok: false, error: "Auto mode on this computer requires confirming the warning" });
70663
+ return true;
70664
+ }
70665
+ if (mode === "full" || mode === "custom") {
70666
+ store.patchBot(botId, { approvalGrant: { requestId, mode, phase: "prepared", threadId: target.threadId, threadOnly: true } });
70667
+ } else {
70668
+ if (clearsOwnGrant) clearGrant(existing);
70669
+ store.patchTask(botId, target.threadId, { approvalMode: mode, autoApprove: mode === "auto", alwaysAllow: [] });
70670
+ }
70671
+ respond({ ok: true, bot: wireTrustedApprovalBot(store.bot(botId)) });
70672
+ return true;
70673
+ }
70124
70674
  if (message3.modelSelection !== void 0) {
70125
70675
  const target = typeof threadId === "string" ? store.projectBotForTask(botId, threadId) : null;
70126
70676
  if (mode !== "ask" || !target || typeof message3.updateBotDefault !== "boolean") {
@@ -70546,7 +71096,7 @@ function publicGroupState(group) {
70546
71096
  }
70547
71097
  function beginGroupTurnOperation(groupId, threadId, botIds = []) {
70548
71098
  const operation = {
70549
- id: randomUUID25(),
71099
+ id: randomUUID27(),
70550
71100
  threadId,
70551
71101
  botIds: new Set(botIds),
70552
71102
  cancelled: false,
@@ -70863,8 +71413,8 @@ function messageWindow(threadId, messageId, limit) {
70863
71413
  return { messages: all2.slice(start, stop).map(slimMessage), hasMore: start > 0 };
70864
71414
  }
70865
71415
  var sseClients = /* @__PURE__ */ new Set();
70866
- sessions.onSessionRevoked((sessionId) => {
70867
- providerAuthSessions.revokeOwner(sessionId);
71416
+ function closeSessionStreams(sessionId) {
71417
+ browserLive.closeForOwner(sessionId);
70868
71418
  for (const client of sseClients) {
70869
71419
  if (client.sessionId !== sessionId) continue;
70870
71420
  sseClients.delete(client);
@@ -70873,8 +71423,12 @@ sessions.onSessionRevoked((sessionId) => {
70873
71423
  } catch {
70874
71424
  }
70875
71425
  }
71426
+ }
71427
+ sessions.onSessionRevoked((sessionId) => {
71428
+ providerAuthSessions.revokeOwner(sessionId);
71429
+ closeSessionStreams(sessionId);
70876
71430
  });
70877
- var STREAM_ID = randomUUID25().slice(0, 8);
71431
+ var STREAM_ID = randomUUID27().slice(0, 8);
70878
71432
  var REPLAY_MAX = 500;
70879
71433
  var configuredSseHeartbeatMs = Number(process.env.OMB_SSE_HEARTBEAT_MS);
70880
71434
  var SSE_HEARTBEAT_MS = Number.isFinite(configuredSseHeartbeatMs) && configuredSseHeartbeatMs > 0 ? configuredSseHeartbeatMs : 15e3;
@@ -70890,6 +71444,7 @@ function cursorSeq(raw) {
70890
71444
  return Number.isSafeInteger(parsed) && parsed >= 0 ? parsed : null;
70891
71445
  }
70892
71446
  function broadcast(payload) {
71447
+ sessions.revalidateEmailSessions();
70893
71448
  const seq2 = ++lastSeq;
70894
71449
  const kind = String(payload.kind ?? "");
70895
71450
  const frame = `id: ${STREAM_ID}:${seq2}
@@ -71131,15 +71686,99 @@ var checkpointRestoreLeases = /* @__PURE__ */ new Set();
71131
71686
  var LOCAL_VM_IDLE_MS = 8 * 60 * 6e4;
71132
71687
  var LOCAL_VM_DESKTOP_WAIT_MS = 9e4;
71133
71688
  var localVmIdles = /* @__PURE__ */ new Map();
71689
+ function inheritedTeamComputer(bot) {
71690
+ return teamComputers.forBot(bot);
71691
+ }
71692
+ function teamComputerPrompt(computer) {
71693
+ return computer ? `Your team shares the Box computer ${JSON.stringify(computer.name)}. Its desktop files and desktop browser logins are shared with other Auto bots in your team; only one turn may drive it at a time. The separate built-in Browser is not this desktop and does not automatically share its logins.` : "";
71694
+ }
71695
+ function botComputerControlKey(bot) {
71696
+ const computer = inheritedTeamComputer(bot);
71697
+ return computer ? teamComputerOwner(computer.id) : bot.id;
71698
+ }
71699
+ function botComputerControlSnapshot(botId, pinnedComputerId) {
71700
+ const own3 = computerControl.snapshot(botId);
71701
+ const bot = store.bot(botId);
71702
+ const key3 = pinnedComputerId ? teamComputerOwner(pinnedComputerId) : bot ? botComputerControlKey(bot) : botId;
71703
+ const shared = computerControl.snapshot(key3);
71704
+ return own3.held ? own3 : shared.held || shared.helpReason ? shared : own3;
71705
+ }
71706
+ function teamComputerInUse(computer) {
71707
+ computer = teamComputers.get(computer.id) ?? computer;
71708
+ return computerControl.snapshot(teamComputerOwner(computer.id)).held || [...teamComputerTurns.values()].some((turn) => turn.computerId === computer.id) || store.bots.some((bot) => computer.section !== null && sectionKey3(bot.section) === computer.section && (botHasActiveTurn(bot.id) || Boolean(routines?.activeRunForBot(bot.id)) || computerControl.snapshot(bot.id).held));
71709
+ }
71710
+ function assertTeamControlCanBeTaken(computerId) {
71711
+ if ([...teamComputerTurns.values()].some((turn) => turn.computerId === computerId && turn.remoteAgent)) {
71712
+ throw Object.assign(new Error("Stop the Computer engine's active turn before taking control of this shared desktop"), { status: 409 });
71713
+ }
71714
+ }
71715
+ function claimTeamComputerLifecycle(computer) {
71716
+ if (computerProviderConfigTransitions.has("box")) throw Object.assign(new Error(providerTransitionMessage("box")), { status: 409 });
71717
+ if (teamComputerInUse(computer)) throw Object.assign(new Error("This team computer is in use; stop the team's work and release computer control first"), { status: 409 });
71718
+ return claimBotComputerLifecycle(teamComputerOwner(computer.id));
71719
+ }
71720
+ function assertTeamComputerChangeIdle(before, after) {
71721
+ const previous2 = inheritedTeamComputer(before);
71722
+ const next = inheritedTeamComputer(after);
71723
+ if (previous2?.id === next?.id) return;
71724
+ if (botHasActiveTurn(before.id) || routines?.activeRunForBot(before.id) || botComputerControlSnapshot(before.id).held || [previous2, next].some((computer) => computer && (teamComputerInUse(computer) || boxLifecycleBusyBots.has(teamComputerOwner(computer.id))))) {
71725
+ throw Object.assign(new Error("Stop the affected team's work and release computer control before changing its computer access"), { status: 409 });
71726
+ }
71727
+ }
71728
+ async function teamComputersPayload() {
71729
+ const entries = teamComputers.list();
71730
+ const inventory = await listManagedBoxes(cfg, managedBoxOwners());
71731
+ return {
71732
+ configured: inventory.configured,
71733
+ ...inventory.problem ? { problem: inventory.problem } : {},
71734
+ computers: entries.map((entry) => {
71735
+ const machine = inventory.instances.find((instance) => instance.ownerBotId === teamComputerOwner(entry.id));
71736
+ return {
71737
+ id: entry.id,
71738
+ name: entry.name,
71739
+ section: entry.section,
71740
+ held: computerControl.snapshot(teamComputerOwner(entry.id)).held,
71741
+ state: boxLifecycleBusyBots.has(teamComputerOwner(entry.id)) ? "working" : machine?.state ?? (inventory.available ? "missing" : "unavailable"),
71742
+ ...entry.problem || inventory.problem ? { problem: entry.problem || inventory.problem } : {}
71743
+ };
71744
+ })
71745
+ };
71746
+ }
71747
+ async function attachTeamBox(computer, botId, owner2, canMount, remoteAgent) {
71748
+ if (!canMount) throw new Error("This model engine cannot use the team's Box computer; choose an engine with computer tools or an explicit bot destination");
71749
+ if (!boxConfigured(cfg)) throw new Error("The team's Box account is not configured; reconnect it in Settings");
71750
+ const ownerId = teamComputerOwner(computer.id);
71751
+ if (boxLifecycleBusyBots.has(ownerId)) throw new Error("The team computer is being changed; wait for it to finish");
71752
+ if (computerControl.snapshot(ownerId).held) throw new Error("Release human control of the team computer before starting another turn");
71753
+ bindTurnComputer(owner2, `computer:box-bot:${ownerId}`, true);
71754
+ teamComputerTurns.set(owner2.threadId, { owner: owner2, computerId: computer.id, botId, remoteAgent });
71755
+ let machine = await findBox(cfg, ownerId);
71756
+ if (!machine) throw new Error("The team's Box computer is missing; explicitly create or retry it from the Team map");
71757
+ bindTurnComputer(owner2, `computer:box:${machine.id}`, true);
71758
+ const action = boxTurnLifecycleAction({ explicitCloud: true, canMount: true, state: typeof machine.state === "string" ? machine.state : null });
71759
+ if (action === "wake") machine = await readyBox(cfg, ownerId);
71760
+ if (!machine || boxTurnLifecycleAction({ explicitCloud: true, canMount: true, state: typeof machine.state === "string" ? machine.state : null }) !== "attach") {
71761
+ throw new Error("The team computer is not ready; check it in the Team map");
71762
+ }
71763
+ if (turnResourceOwners.get(owner2.threadId)?.generation !== owner2.generation || !turnResources.owns(`computer:box:${machine.id}`, owner2)) throw new Error("This computer turn ended while its machine was starting");
71764
+ return {
71765
+ integration: { kind: "box", boxId: machine.id, token: cfg.box.token, control: controlIntegration(botId, owner2.threadId, owner2.generation) },
71766
+ capture: () => screenshotBox(cfg, ownerId, machine.id)
71767
+ };
71768
+ }
71134
71769
  function managedBoxOwners() {
71135
- return store.bots.map((bot) => ({
71770
+ return [...store.bots.map((bot) => ({
71136
71771
  botId: bot.id,
71137
71772
  name: bot.name,
71138
71773
  // A machine is not safe to mutate while any app-level work or human
71139
71774
  // control lease still names its owner. This is deliberately conservative
71140
71775
  // across destination changes: an old Box may still contain valuable state.
71141
71776
  inUse: bot.busy === true || hasDirectDispatch(bot.id) || activeGroupTurnForBot(bot.id) !== null || Boolean(routines?.activeRunForBot(bot.id)) || activeVpsThreads.has(bot.id) || computerControl.snapshot(bot.id).held
71142
- }));
71777
+ })), ...teamComputers.list().map((computer) => ({
71778
+ botId: teamComputerOwner(computer.id),
71779
+ name: computer.name,
71780
+ inUse: teamComputerInUse(computer)
71781
+ }))];
71143
71782
  }
71144
71783
  function botHasActiveTurn(botId) {
71145
71784
  const bot = store.bot(botId);
@@ -71172,6 +71811,7 @@ function providerOperationConflict(provider) {
71172
71811
  }
71173
71812
  function turnProvider(bot, runOn2) {
71174
71813
  if (runOn2 === "cloud" || registry2.get(bot.modelSelection.instanceId)?.driverKind === "boxAgent") return "box";
71814
+ if (inheritedTeamComputer(bot)) return "box";
71175
71815
  if (bot.computer !== void 0 && bot.computer !== "cloud") return null;
71176
71816
  return bot.cloudBackend === "vps" ? "vps" : "box";
71177
71817
  }
@@ -71188,6 +71828,8 @@ function claimBoxInventoryRequest(boxId) {
71188
71828
  }
71189
71829
  function claimManagedBoxMutation(instance) {
71190
71830
  const ownerBotId = instance.ownerBotId;
71831
+ const teamComputer = teamComputers.list().find((computer) => teamComputerOwner(computer.id) === ownerBotId);
71832
+ if (teamComputer) return claimTeamComputerLifecycle(teamComputer);
71191
71833
  if (!ownerBotId) {
71192
71834
  if (orphanBoxLifecycleBusyIds.has(instance.boxId)) {
71193
71835
  throw Object.assign(new Error("this cloud computer is being changed \u2014 wait for it to finish"), { status: 409 });
@@ -71387,7 +72029,7 @@ bus.subscribe((event) => {
71387
72029
  const existing = store.messagesFor(event.threadId).find((m2) => m2.id === messageId)?.tool;
71388
72030
  toolName = existing?.name ?? "tool";
71389
72031
  store.patchMessage(event.threadId, messageId, {
71390
- tool: { name: toolName, ok: event.ok, spoken: existing?.spoken, summary: existing?.summary }
72032
+ tool: { ...existing, name: toolName, ok: event.ok, output: event.output }
71391
72033
  });
71392
72034
  toolMessageByItem.delete(itemKey);
71393
72035
  }
@@ -71406,7 +72048,7 @@ bus.subscribe((event) => {
71406
72048
  const message3 = pushMessage({
71407
72049
  role: "bot",
71408
72050
  kind: "activity",
71409
- tool: { name: name2, spoken: narrateTool(name2) ?? void 0, summary: event.summary }
72051
+ tool: { name: name2, spoken: narrateTool(name2) ?? void 0, summary: event.summary, input: event.input }
71410
72052
  });
71411
72053
  if (event.itemId) toolMessageByItem.set(`${event.threadId}:${event.itemId}`, message3.id);
71412
72054
  }
@@ -71784,7 +72426,7 @@ function markTaskContextExternallyUpdated(bot, threadId) {
71784
72426
  if (!task2) return;
71785
72427
  store.patchTask(bot.id, threadId, {
71786
72428
  resumeCursors: {},
71787
- lastInstanceId: `${EXTERNAL_CONTEXT_MARKER_PREFIX}${randomUUID25()}`,
72429
+ lastInstanceId: `${EXTERNAL_CONTEXT_MARKER_PREFIX}${randomUUID27()}`,
71788
72430
  unread: true
71789
72431
  });
71790
72432
  }
@@ -72010,9 +72652,9 @@ function drainQueuedSends() {
72010
72652
  store,
72011
72653
  (botId, threadId, prompt, userMessage, excludeIds, unattended) => (
72012
72654
  // A plain attended turn — no automationSource, no comms depth: exactly
72013
- // what typing the same words into an idle bot would run. The one
72014
- // exception is `unattended`: a thread a bot opened on itself while
72015
- // nobody was watching stays unwatched through the wait for a slot.
72655
+ // what typing the same words into an idle bot would run. Self-opened
72656
+ // work retains `unattended` and the message's bot-origin provenance
72657
+ // through the wait for a slot.
72016
72658
  // Drain just appended the held lines; userMessage keeps startTurn
72017
72659
  // from duplicating the last one, and excludeIds drops every drained
72018
72660
  // line from the transcript-replay so they are not also in `prompt`.
@@ -72058,12 +72700,13 @@ async function startOrQueueDirectMessage(botId, threadId, text8, replyTo, sendId
72058
72700
  }
72059
72701
  var MAX_THREADS_OPENED_PER_TURN = 5;
72060
72702
  async function startOrQueueOpenedThread(botId, threadId, text8, unattended) {
72703
+ const peerAsk = { botId, name: store.bot(botId).name, unattended: unattended || void 0 };
72061
72704
  if (botAtThreadCapacity(botId) || activeGroupTurnForBot(botId)) {
72062
- queueSteeredMessage(botId, threadId, text8, { reason: "capacity", unattended });
72705
+ queueSteeredMessage(botId, threadId, text8, { reason: "capacity", unattended, peerAsk });
72063
72706
  return { state: "queued", position: queuedThreadPosition(botId, threadId) ?? 1 };
72064
72707
  }
72065
72708
  try {
72066
- await startTurn(botId, text8, { threadId, unattended });
72709
+ await startTurn(botId, text8, { threadId, unattended, peerAsk });
72067
72710
  return { state: "running" };
72068
72711
  } catch (error51) {
72069
72712
  const why = error51 instanceof Error ? error51.message : String(error51);
@@ -72098,7 +72741,7 @@ function startScreenPoller(botId, threadId, captures, { screenIsTheWork = false
72098
72741
  browser: guarded(captures.browser, browserSession ? `browser:${browserSession}` : void 0)
72099
72742
  },
72100
72743
  control: () => ({
72101
- held: computerControl.snapshot(botId).held,
72744
+ held: botComputerControlSnapshot(botId, teamComputerTurns.get(threadId)?.computerId).held,
72102
72745
  revision: computerControlRevision.get(botId) ?? 0
72103
72746
  }),
72104
72747
  onFrame: (frame) => broadcast({ kind: "screen", botId, threadId, ...frame }),
@@ -72238,7 +72881,7 @@ async function startTurn(botId, text8, opts) {
72238
72881
  userMessage = edited;
72239
72882
  }
72240
72883
  if (!userMessage) {
72241
- userMessage = opts?.cardContinuation ? { id: `card-${randomUUID25()}`, at: Date.now(), role: "user", kind: "text", text: text8 } : store.appendMessage(threadId, {
72884
+ userMessage = opts?.cardContinuation ? { id: `card-${randomUUID27()}`, at: Date.now(), role: "user", kind: "text", text: text8 } : store.appendMessage(threadId, {
72242
72885
  role: "user",
72243
72886
  kind: "text",
72244
72887
  text: text8,
@@ -72248,7 +72891,7 @@ async function startTurn(botId, text8, opts) {
72248
72891
  });
72249
72892
  }
72250
72893
  if (!opts?.cardContinuation) {
72251
- if (commsDepth === 0 && opts?.automationSource === void 0 && !opts?.unattended) {
72894
+ if (commsDepth === 0 && opts?.automationSource === void 0 && !opts?.unattended && !userMessage.peerAsk) {
72252
72895
  personAskAt.set(threadId, userMessage.at);
72253
72896
  if (task2.routineRunId) store.patchTask(bot.id, threadId, { routineRunId: void 0 });
72254
72897
  } else {
@@ -72291,7 +72934,7 @@ async function startTurn(botId, text8, opts) {
72291
72934
  const drift = checkSoulDrift(bot.id, bot.soul ?? "", bot.soulHash ?? "");
72292
72935
  if (drift.drift !== Boolean(bot.soulDrift)) store.patchBot(bot.id, { soulDrift: drift.drift });
72293
72936
  }
72294
- const dispatchClaimId = randomUUID25();
72937
+ const dispatchClaimId = randomUUID27();
72295
72938
  const resourceOwner = { threadId, generation: dispatchClaimId };
72296
72939
  turnResourceOwners.set(threadId, resourceOwner);
72297
72940
  directTurnGenerationByThread.set(threadId, dispatchClaimId);
@@ -72347,13 +72990,14 @@ async function startTurn(botId, text8, opts) {
72347
72990
  const checkpointCwd = cwd && cwd !== privateWorkspace ? cwd : void 0;
72348
72991
  const dwebUrl = process.env.DWEB_URL?.trim();
72349
72992
  if (dwebUrl) integrations.dweb = { url: dwebUrl };
72350
- const cloudBackend = opts?.runOn === "cloud" || bot.cloudBackend !== "vps" ? "box" : "vps";
72993
+ const teamComputer = inheritedTeamComputer(bot);
72994
+ const cloudBackend = teamComputer || opts?.runOn === "cloud" || bot.cloudBackend !== "vps" ? "box" : "vps";
72351
72995
  const mountsComputerMcp = instance.adapter.capabilities.computerMcp === true;
72352
72996
  const mountsCloudComputer = mountsComputerMcp || instance.driverKind === "boxAgent";
72353
72997
  const mountsLocalComputer = instance.adapter.capabilities.localComputerMcp === true;
72354
72998
  const browserOn = builtInBrowserEnabled(cfg) && bot.browser !== false && instance.adapter.capabilities.browserMcp === true;
72355
72999
  const plan = resolveSurface({
72356
- destination: opts?.runOn === "cloud" ? "cloud" : bot.computer,
73000
+ destination: teamComputer || opts?.runOn === "cloud" ? "cloud" : bot.computer,
72357
73001
  // cloud routine overrides the MAUS default
72358
73002
  browserOn
72359
73003
  });
@@ -72438,7 +73082,13 @@ async function startTurn(botId, text8, opts) {
72438
73082
  }
72439
73083
  }
72440
73084
  }
72441
- if ((wants2 === "cloud" || wants2 === void 0) && cloudBackend === "box" && boxConfigured(cfg)) {
73085
+ if (teamComputer) {
73086
+ const attached = await attachTeamBox(teamComputer, bot.id, resourceOwner, mountsCloudComputer, instance.driverKind === "boxAgent");
73087
+ integrations.computer = attached.integration;
73088
+ previewCapture = attached.capture;
73089
+ computerKind = "box";
73090
+ }
73091
+ if (!teamComputer && (wants2 === "cloud" || wants2 === void 0) && cloudBackend === "box" && boxConfigured(cfg)) {
72442
73092
  if (wants2 === "cloud") bindTurnComputer(resourceOwner, `computer:box-bot:${bot.id}`, true);
72443
73093
  if (!mountsCloudComputer && wants2 === "cloud") {
72444
73094
  throw new Error("this model engine cannot use computer tools \u2014 choose Claude, an ACP engine, or the Computer engine");
@@ -72506,7 +73156,9 @@ async function startTurn(botId, text8, opts) {
72506
73156
  }
72507
73157
  const sectionPeers = reachablePeers(store.bots, bot);
72508
73158
  if (agentsMounted) {
72509
- integrations.agents = agentsIntegration(bot.id, threadId, commsDepth, skillAuthoring, dispatchClaimId, opts?.coordination?.id, boundedCoordination);
73159
+ const origin = opts?.cardContinuation ? store.activePath(threadId).findLast((message3) => message3.role === "user" && message3.kind === "text") : userMessage;
73160
+ const ownThreadCreation = boundedCoordination && !opts?.coordination && Boolean(origin && !origin.peerAsk);
73161
+ integrations.agents = agentsIntegration(bot.id, threadId, commsDepth, skillAuthoring, dispatchClaimId, opts?.coordination?.id, boundedCoordination, ownThreadCreation);
72510
73162
  }
72511
73163
  const tagged = integrations.agents ? mentionedBots(
72512
73164
  providerText,
@@ -72564,6 +73216,7 @@ async function startTurn(botId, text8, opts) {
72564
73216
  { id: "setup", label: "Setup", text: setupSystemPrompt(setupMode, { skills: skillAuthoring, cwd: liveBot?.cwd ?? bot.cwd }) },
72565
73217
  { id: "files", label: "File locations", text: worksInWorkspace && opts?.runOn !== "cloud" ? workspaceLocationsPrompt(bot.id, cwd, liveBot?.cwd ?? bot.cwd) : "" },
72566
73218
  { id: "computer", label: "Computer", text: computerPrompt(computerPromptKind) },
73219
+ { id: "team-computer", label: "Team computer", text: teamComputerPrompt(teamComputer) },
72567
73220
  { id: "plan", label: "Surface", text: plan.note },
72568
73221
  // gated on the integration, not the key: the hint only goes to a
72569
73222
  // bot whose driver actually mounted the tools
@@ -72821,8 +73474,8 @@ async function stopBotForEmergencyApprovalDowngrade(botId) {
72821
73474
  registry2.get(bot.modelSelection.instanceId)?.adapter.interruptTurn(groupTurn.threadId)
72822
73475
  ]);
72823
73476
  closeOpenApprovals(groupTurn.threadId);
72824
- const failure3 = results.find((result) => result.status === "rejected");
72825
- if (failure3) throw failure3.reason;
73477
+ const failure5 = results.find((result) => result.status === "rejected");
73478
+ if (failure5) throw failure5.reason;
72826
73479
  return;
72827
73480
  }
72828
73481
  await directStop;
@@ -73012,7 +73665,7 @@ async function deleteBotWithLifecycle(botId, revalidate = () => {
73012
73665
  return deletionResponse(409, { error: "stop this bot's Local VM turn or setup action before deleting the bot" });
73013
73666
  }
73014
73667
  const vm = await containerComputerStatus(void 0, void 0, target);
73015
- if (!vm.daemonUp && existsSync30(target.workspaceDir)) {
73668
+ if (!vm.daemonUp && existsSync31(target.workspaceDir)) {
73016
73669
  return deletionResponse(409, {
73017
73670
  error: "start the container runtime and delete this bot's Local VM before deleting the bot"
73018
73671
  });
@@ -73110,7 +73763,7 @@ async function deleteBotWithLifecycle(botId, revalidate = () => {
73110
73763
  }
73111
73764
  for (const dir of [EVENTS_DIR, NATIVE_DIR]) {
73112
73765
  try {
73113
- unlinkSync12(join61(dir, `${bot.threadId}.ndjson`));
73766
+ unlinkSync13(join61(dir, `${bot.threadId}.ndjson`));
73114
73767
  } catch {
73115
73768
  }
73116
73769
  }
@@ -73234,6 +73887,12 @@ async function resolveAndSendTeamSetup(res, args, ownerReview) {
73234
73887
  json3(res, 403, { error: "Approve team setup or deletion from the desktop app or a paired owner device. In a local browser, wait until every bot is idle." });
73235
73888
  return true;
73236
73889
  }
73890
+ if (args.behavior === "allow" && !card.answered && !card.dismissed) {
73891
+ for (const operation of card.teamSetupRequest.operations) {
73892
+ const before = store.bot(operation.botId);
73893
+ if (before && operation.action === "update") assertTeamComputerChangeIdle(before, { ...before, ...operation.fields });
73894
+ }
73895
+ }
73237
73896
  const resumeGeneration = teamSetupResumeGenerations.get(args.threadId) ?? 0;
73238
73897
  const resolved = await teamSetupRequests.resolve(args);
73239
73898
  if (!resolved) return false;
@@ -73670,8 +74329,9 @@ async function runGroupMemberTurn(groupId, threadId, botId, hop, spoken = /* @__
73670
74329
  groupSpeakers.set(threadId, roomSpeaker);
73671
74330
  store.patchGroup(readyGroup.id, { busyBotId: bot.id });
73672
74331
  orchestration?.onClaimed?.();
74332
+ const roomTeamComputer = inheritedTeamComputer(readyBot);
73673
74333
  const roomPlan = resolveSurface({
73674
- destination: readyBot.computer,
74334
+ destination: roomTeamComputer ? "cloud" : readyBot.computer,
73675
74335
  browserOn: builtInBrowserEnabled(cfg) && readyBot.browser !== false && instance.adapter.capabilities.browserMcp === true
73676
74336
  });
73677
74337
  if (roomPlan.browser) {
@@ -73683,6 +74343,18 @@ async function runGroupMemberTurn(groupId, threadId, botId, hop, spoken = /* @__
73683
74343
  if (isCancelled?.() || !browserReadyBot?.busy || groupSpeakers.get(threadId) !== roomSpeaker || activeInternalGenerationByThread.get(threadId) !== internalGeneration) {
73684
74344
  return false;
73685
74345
  }
74346
+ if (roomTeamComputer) {
74347
+ const attached = await attachTeamBox(
74348
+ roomTeamComputer,
74349
+ readyBot.id,
74350
+ resourceOwner,
74351
+ instance.adapter.capabilities.computerMcp === true || instance.driverKind === "boxAgent",
74352
+ instance.driverKind === "boxAgent"
74353
+ );
74354
+ if (isCancelled?.() || groupSpeakers.get(threadId) !== roomSpeaker || activeInternalGenerationByThread.get(threadId) !== internalGeneration) return false;
74355
+ integrations.computer = attached.integration;
74356
+ startScreenPoller(readyBot.id, threadId, { computer: attached.capture }, { screenIsTheWork: instance.driverKind === "boxAgent" });
74357
+ }
73686
74358
  if (readyBot.computer === "vm") {
73687
74359
  if (instance.adapter.capabilities.computerMcp !== true || instance.driverKind === "boxAgent") {
73688
74360
  throw new Error("this model engine cannot use the Local VM");
@@ -73754,7 +74426,8 @@ ${cardContinuation}` : ""}`;
73754
74426
  const roomSystem = buildSystemPrompt(system, store.bot(bot.id)?.soul ?? bot.soul ?? "", [
73755
74427
  { id: "files", label: "File locations", text: workspace ? workspaceLocationsPrompt(bot.id, cwd, readyBot.cwd) : "" },
73756
74428
  { id: "mcp", label: "MCP servers", text: customMcpPrompt(Object.keys(integrations.custom ?? {})) },
73757
- { id: "computer", label: "Computer", text: computerPrompt(roomVmTarget ? localVmMode(cfg) === "per-bot" ? "vm-private" : "vm-shared" : null) },
74429
+ { id: "computer", label: "Computer", text: computerPrompt(roomTeamComputer ? instance.driverKind === "boxAgent" ? "box-agent" : "box" : roomVmTarget ? localVmMode(cfg) === "per-bot" ? "vm-private" : "vm-shared" : null) },
74430
+ { id: "team-computer", label: "Team computer", text: teamComputerPrompt(roomTeamComputer) },
73758
74431
  { id: "plan", label: "Surface", text: roomPlan.note },
73759
74432
  { id: "browser", label: "Browser", text: integrations.browser ? BUILT_IN_BROWSER_SYSTEM_PROMPT : "" },
73760
74433
  { id: "recall", label: "Recall", text: integrations.agents ? SESSION_SEARCH_SYSTEM_PROMPT : "" },
@@ -73786,7 +74459,7 @@ ${memorySystemPrompt(bot.id, { managedWrites: Boolean(integrations.agents) }).tr
73786
74459
  let replyText = "";
73787
74460
  let providerTurnId;
73788
74461
  let abandoned = false;
73789
- const retirementOwner = `room-abandoned:${randomUUID25()}`;
74462
+ const retirementOwner = `room-abandoned:${randomUUID27()}`;
73790
74463
  const abandonProviderTurn = () => {
73791
74464
  if (abandoned) return;
73792
74465
  abandoned = true;
@@ -74391,7 +75064,7 @@ function startGroupTurn(groupId, text8, replyTo, sendId, channelMode = "chat", q
74391
75064
  goalCoordinator ? [] : responders.map((responder) => responder.id)
74392
75065
  );
74393
75066
  if (goalCoordinator) {
74394
- const runId = options.goalRunId?.trim() || `goal-${Date.now().toString(36)}-${randomUUID25()}`;
75067
+ const runId = options.goalRunId?.trim() || `goal-${Date.now().toString(36)}-${randomUUID27()}`;
74395
75068
  const startedAt = Date.now();
74396
75069
  const detail = `${goalCoordinator.name} is coordinating this goal.`;
74397
75070
  const card = store.appendMessage(threadId, {
@@ -74650,7 +75323,7 @@ Adds one line to the prompt index; the body is read only when used.${warnings}`,
74650
75323
  };
74651
75324
  }
74652
75325
  function appendSkillRequestCard(args) {
74653
- const requestId = randomUUID25();
75326
+ const requestId = randomUUID27();
74654
75327
  const copy = skillCardCopy(args.staged);
74655
75328
  const payload = {
74656
75329
  version: 1,
@@ -75235,7 +75908,7 @@ async function perBotLocalVmCountForModeChange() {
75235
75908
  if (targets.length === 0) return 0;
75236
75909
  const runtime = await containerRuntimeStatus();
75237
75910
  if (!runtime.runtime || !runtime.daemonUp) {
75238
- return targets.some((target) => existsSync30(target.workspaceDir)) ? null : 0;
75911
+ return targets.some((target) => existsSync31(target.workspaceDir)) ? null : 0;
75239
75912
  }
75240
75913
  return existingPerBotLocalVmCount(runtime.runtime);
75241
75914
  }
@@ -75279,7 +75952,11 @@ function configStatus() {
75279
75952
  features: {
75280
75953
  skillAuthoring: skillAuthoringEnabled(cfg),
75281
75954
  showToolCalls: showToolCallsEnabled(cfg),
75282
- browser: builtInBrowserEnabled(cfg)
75955
+ browser: builtInBrowserEnabled(cfg),
75956
+ // Maintainer-only escape hatch, not a Settings toggle: the desktop
75957
+ // shell and the Settings UI read it so they offer nothing this server
75958
+ // would refuse.
75959
+ sharedComputers: sharedComputersEnabled(cfg)
75283
75960
  },
75284
75961
  // first-run progress — not a secret; the app decides whether to show
75285
75962
  // the welcome tour from this, never from browser storage
@@ -75411,13 +76088,13 @@ function serveStatic(res, path) {
75411
76088
  const safe = path === "/" ? "/index.html" : path.replace(/\.\./g, "");
75412
76089
  const file2 = join61(STATIC_DIR, safe);
75413
76090
  try {
75414
- const data = readFileSync42(file2);
76091
+ const data = readFileSync43(file2);
75415
76092
  res.writeHead(200, { "content-type": MIME[extname5(file2)] ?? "application/octet-stream" });
75416
76093
  res.end(data);
75417
76094
  return true;
75418
76095
  } catch {
75419
76096
  try {
75420
- const data = readFileSync42(join61(STATIC_DIR, "index.html"));
76097
+ const data = readFileSync43(join61(STATIC_DIR, "index.html"));
75421
76098
  res.writeHead(200, { "content-type": "text/html" });
75422
76099
  res.end(data);
75423
76100
  return true;
@@ -75493,8 +76170,13 @@ var workspaceBackupRoutes = createWorkspaceBackupRoutes({
75493
76170
  }).auth;
75494
76171
  return Boolean(current2?.scopes.includes("admin") && current2.kind === original.kind && (current2.kind !== "session" || original.kind === "session" && current2.session.id === original.session.id));
75495
76172
  },
75496
- exclusive: (work, keepLocked) => workspaceMaintenance.run(work, {
75497
- idle: () => !providerFleetReloading && !providerAuthSessions.active && !browserEngineInstall && !routines?.isTicking && !calendarCalls?.isTicking && !localVmImageBusy && !localVmProvisionBusy && !localVmModeChangeBusy && !localVmLifecycleBusy.size && !boxLifecycleBusyBots.size && !vpsPreviewRequests.size && !orphanBoxLifecycleBusyIds.size && !computerProviderConfigTransitions.size && !checkpointRestoreLeases.size && store.bots.every((bot) => !botHasActiveTurn(bot.id) && !routines?.activeRunForBot(bot.id) && !computerControl.snapshot(bot.id).held) && store.groups.every((group) => !groupIsWorking(group)),
76173
+ exclusive: (work, keepLocked) => workspaceMaintenance.run(async () => {
76174
+ if (keepLocked && teamComputers.list().some((computer) => computer.section !== null)) {
76175
+ throw Object.assign(new Error("Unassign team computers before restoring this workspace"), { status: 409 });
76176
+ }
76177
+ return work();
76178
+ }, {
76179
+ idle: () => !providerFleetReloading && !providerAuthSessions.active && !browserEngineInstall && !routines?.isTicking && !calendarCalls?.isTicking && !localVmImageBusy && !localVmProvisionBusy && !localVmModeChangeBusy && !localVmLifecycleBusy.size && !boxLifecycleBusyBots.size && !vpsPreviewRequests.size && !orphanBoxLifecycleBusyIds.size && !computerProviderConfigTransitions.size && !checkpointRestoreLeases.size && teamComputers.list().every((computer) => !teamComputerInUse(computer)) && store.bots.every((bot) => !botHasActiveTurn(bot.id) && !routines?.activeRunForBot(bot.id) && !botComputerControlSnapshot(bot.id).held) && store.groups.every((group) => !groupIsWorking(group)),
75498
76180
  pause: () => {
75499
76181
  routines?.stop();
75500
76182
  calendarCalls?.stop();
@@ -75523,9 +76205,28 @@ var handleRequest = async (req, res) => {
75523
76205
  let m2 = null;
75524
76206
  let releaseWorkspaceRequest;
75525
76207
  try {
76208
+ if (method === "GET" && path === "/api/health/hosted") {
76209
+ res.setHeader("cache-control", "no-store");
76210
+ const hosted = hostedWorkspaceConfiguration();
76211
+ if (!hosted?.portalMembership || !workspaceAccess || !entitled("admin")) {
76212
+ return json3(res, 503, { error: "Hosted workspace readiness is unavailable." });
76213
+ }
76214
+ res.setHeader(HOSTED_CONTRACT_HEADER, String(HOSTED_CONTRACT_VERSION));
76215
+ return json3(res, 200, { ok: true, service: "openmausbot", membershipAuthority: "portal", workspace: hosted.workspace, ...HOSTED_CONTRACT_METADATA });
76216
+ }
76217
+ if (HOSTED_WORKSPACE) {
76218
+ if (method === "POST" && ["/api/auth/pair", "/api/auth/pairing", "/api/auth/email/start", "/api/auth/email/verify"].includes(path)) {
76219
+ return json3(res, 403, { error: "Sign in through the workspace portal." });
76220
+ }
76221
+ if (workspaceAccess) {
76222
+ if (await workspaceAccess.handlePublic(req, res, url2)) return;
76223
+ } else if (path.startsWith("/api/auth/hosted/") || path === "/pair" || path === "/" && (!isLoopbackHost(req.headers.host) || isProxied(req))) {
76224
+ return json3(res, 503, { error: "Workspace sign-in is unavailable." });
76225
+ }
76226
+ }
75526
76227
  if (method === "GET" && !path.startsWith("/api/") && !path.startsWith("/.well-known/") && serveStatic(res, path)) return;
75527
76228
  if (method === "GET" && path === "/.well-known/openmausbot/environment") {
75528
- return json3(res, 200, environmentDescriptor({ environmentId: ENVIRONMENT_ID, desktopManaged: DESKTOP_MANAGED, emailSignIn: emailSignIn.enabled() }));
76229
+ return json3(res, 200, environmentDescriptor({ environmentId: ENVIRONMENT_ID, desktopManaged: DESKTOP_MANAGED, emailSignIn: !HOSTED_WORKSPACE && emailSignIn.enabled(), sharedComputers: sharedComputersEnabled(cfg) }));
75529
76230
  }
75530
76231
  const domainCheck = /^\/\.well-known\/openmausbot\/domain-check\/([a-f0-9]{64})$/.exec(path);
75531
76232
  if (method === "GET" && domainCheck) {
@@ -75561,7 +76262,7 @@ var handleRequest = async (req, res) => {
75561
76262
  }
75562
76263
  sessions.clearFailures(source);
75563
76264
  const issued = sessions.issue({ label: label.trim() || labelFromUserAgent(req.headers["user-agent"]), scopes: verified.scopes, userId: verified.userId, email: verified.email });
75564
- const environment = environmentDescriptor({ environmentId: ENVIRONMENT_ID, desktopManaged: DESKTOP_MANAGED, emailSignIn: true });
76265
+ const environment = environmentDescriptor({ environmentId: ENVIRONMENT_ID, desktopManaged: DESKTOP_MANAGED, emailSignIn: true, sharedComputers: sharedComputersEnabled(cfg) });
75565
76266
  const secure = requestOrigin(req)?.startsWith("https://") === true;
75566
76267
  res.setHeader("set-cookie", serializeSessionCookie(SESSION_COOKIE, issued.token, { secure, maxAgeSeconds: cookieMaxAgeSeconds(issued.session) }));
75567
76268
  return json3(res, 200, { session: issued.session, environment });
@@ -75580,7 +76281,7 @@ var handleRequest = async (req, res) => {
75580
76281
  console.warn(`pairing refused from ${requestSource(req)}: ${result.error}`);
75581
76282
  return json3(res, result.status, { error: result.error });
75582
76283
  }
75583
- const environment = environmentDescriptor({ environmentId: ENVIRONMENT_ID, desktopManaged: DESKTOP_MANAGED, emailSignIn: emailSignIn.enabled() });
76284
+ const environment = environmentDescriptor({ environmentId: ENVIRONMENT_ID, desktopManaged: DESKTOP_MANAGED, emailSignIn: emailSignIn.enabled(), sharedComputers: sharedComputersEnabled(cfg) });
75584
76285
  if (wantsCookie) {
75585
76286
  const secure = requestOrigin(req)?.startsWith("https://") === true;
75586
76287
  res.setHeader("set-cookie", serializeSessionCookie(SESSION_COOKIE, result.token, { secure, maxAgeSeconds: cookieMaxAgeSeconds(result.session) }));
@@ -75594,12 +76295,13 @@ var handleRequest = async (req, res) => {
75594
76295
  streamPath: "/api/events",
75595
76296
  url: url2,
75596
76297
  loopbackMutationToken: desktopMutationToken,
75597
- companionMutationToken
76298
+ companionMutationToken,
76299
+ features: { sharedComputers: sharedComputersEnabled(cfg) }
75598
76300
  });
75599
76301
  if (gate.auth?.kind === "session" && gate.auth.via === "cookie") {
75600
76302
  const presented = parseCookies(req.headers.cookie).get(SESSION_COOKIE);
75601
76303
  if (presented) {
75602
- const secure = requestOrigin(req)?.startsWith("https://") === true;
76304
+ const secure = HOSTED_WORKSPACE || requestOrigin(req)?.startsWith("https://") === true;
75603
76305
  res.setHeader("set-cookie", serializeSessionCookie(SESSION_COOKIE, presented, { secure, maxAgeSeconds: cookieMaxAgeSeconds(gate.auth.session) }));
75604
76306
  }
75605
76307
  }
@@ -75611,8 +76313,15 @@ var handleRequest = async (req, res) => {
75611
76313
  }
75612
76314
  if (!gate.auth) return json3(res, gate.status, { error: gate.error });
75613
76315
  const auth = gate.auth;
76316
+ if (HOSTED_WORKSPACE && auth.kind === "session") {
76317
+ const failure5 = workspaceAccess ? await workspaceAccess.authorize(req, auth) : { status: 503, error: "Workspace sign-in is unavailable." };
76318
+ if (failure5) return json3(res, failure5.status, { error: failure5.error });
76319
+ }
76320
+ if (method === "POST" && path === "/api/workspace-backup/restore" && teamComputers.list().some((computer) => computer.section !== null)) {
76321
+ return json3(res, 409, { error: "Unassign team computers before restoring a workspace; restored team names must not gain access to existing desktops" });
76322
+ }
75614
76323
  if (await workspaceBackupRoutes(req, res, path, auth)) return;
75615
- if (path.startsWith("/api/") && path !== "/api/events" && path !== "/api/health" && !isWorkspaceBackupSessionControl(method, path)) {
76324
+ if (path.startsWith("/api/") && path !== "/api/events" && path !== "/api/health" && !path.startsWith("/api/shared-computers/") && !isWorkspaceBackupSessionControl(method, path)) {
75616
76325
  releaseWorkspaceRequest = workspaceMaintenance.request();
75617
76326
  }
75618
76327
  if (method === "GET" && path === "/api/auth/session") {
@@ -75694,6 +76403,39 @@ var handleRequest = async (req, res) => {
75694
76403
  const cancelled = sessions.cancelPairing(m2[1]);
75695
76404
  return json3(res, cancelled ? 200 : 404, cancelled ? { ok: true } : { error: "no such pairing code" });
75696
76405
  }
76406
+ if (method === "POST" && path === "/api/desktop/shared-computer-control" && sharedComputersEnabled(cfg)) {
76407
+ if (auth.kind !== "loopback") return json3(res, 403, { error: "Local desktop only" });
76408
+ const body = await readBody(req, 1024);
76409
+ if (!sharedComputersEnabled(cfg)) return json3(res, 404, { error: `no route: ${method} ${path}` });
76410
+ if (!external_exports.string().uuid().safeParse(body?.id).success || !["acquire", "release"].includes(body?.action)) return json3(res, 400, { error: "Invalid computer lease" });
76411
+ if (body.action === "release") sharedComputerControl.release(body.id);
76412
+ else sharedComputerControl.acquire(body.id);
76413
+ return json3(res, 200, { ok: true });
76414
+ }
76415
+ if (method === "POST" && path.startsWith("/api/shared-computers/") && sharedComputersEnabled(cfg)) {
76416
+ if (auth.kind !== "session") return json3(res, 403, { error: "Pair this desktop first" });
76417
+ if (!/^application\/json\b/i.test(String(req.headers["content-type"] ?? ""))) return json3(res, 415, { error: "JSON required" });
76418
+ const body = await readBody(req, 4e6);
76419
+ if (!sharedComputersEnabled(cfg)) return json3(res, 404, { error: `no route: ${method} ${path}` });
76420
+ if (!sessions.isLive(auth.session.id)) return json3(res, 401, { error: "Session ended" });
76421
+ const secret = String(req.headers["x-omb-computer-secret"] ?? "");
76422
+ if (path === "/api/shared-computers/connect") {
76423
+ const parsed = sharedComputerRegistration.safeParse(body);
76424
+ if (!parsed.success) return json3(res, 400, { error: "Invalid computer registration" });
76425
+ const registration = parsed.data;
76426
+ if (registration.environmentId !== ENVIRONMENT_ID) return json3(res, 409, { error: "Workspace identity changed. Pair again before sharing this computer." });
76427
+ sharedComputers.register(registration, auth.session.id, secret);
76428
+ return json3(res, 200, { ok: true });
76429
+ }
76430
+ const route = /^\/api\/shared-computers\/([\w-]+)\/(poll|lease|result|disconnect)$/.exec(path);
76431
+ if (!route) return json3(res, 404, { error: "not found" });
76432
+ const [, id, action] = route;
76433
+ if (action === "poll") return json3(res, 200, { job: await sharedComputers.poll(id, auth.session.id, secret) });
76434
+ if (action === "lease") return json3(res, 200, { active: sharedComputers.liveJob(id, auth.session.id, secret, String(body?.jobId)) });
76435
+ if (action === "result") sharedComputers.complete(id, auth.session.id, secret, String(body?.jobId), body?.result);
76436
+ if (action === "disconnect") sharedComputers.disconnect(id, auth.session.id, secret);
76437
+ return json3(res, 200, { ok: true });
76438
+ }
75697
76439
  if (method === "GET" && path === "/api/auth/sessions") {
75698
76440
  return json3(res, 200, { sessions: sessions.list(), current: auth.kind === "session" ? auth.session.id : null });
75699
76441
  }
@@ -75708,7 +76450,7 @@ var handleRequest = async (req, res) => {
75708
76450
  const actual = Array.isArray(req.headers["x-openmausbot-test-capability"]) ? "" : String(req.headers["x-openmausbot-test-capability"] ?? "");
75709
76451
  const expectedBytes = Buffer.from(expected);
75710
76452
  const actualBytes = Buffer.from(actual);
75711
- if (!expected || actualBytes.length !== expectedBytes.length || !timingSafeEqual4(actualBytes, expectedBytes)) return json3(res, 404, { error: "not found" });
76453
+ if (!expected || actualBytes.length !== expectedBytes.length || !timingSafeEqual5(actualBytes, expectedBytes)) return json3(res, 404, { error: "not found" });
75712
76454
  const parsed = external_exports.object({
75713
76455
  botId: external_exports.string().regex(/^[\w-]{1,128}$/),
75714
76456
  threadId: external_exports.string().regex(/^[\w-]{1,128}$/),
@@ -75741,7 +76483,7 @@ var handleRequest = async (req, res) => {
75741
76483
  if (internalCapability.kind !== requiredCapabilityKind) {
75742
76484
  return json3(res, 403, { error: "this internal capability cannot access that service" });
75743
76485
  }
75744
- if (internalCapability.roomCoordination && method === "POST" && ["/api/internal/ask-bot", "/api/internal/delegate-bot", "/api/internal/threads"].includes(path)) {
76486
+ if (internalCapability.roomCoordination && method === "POST" && ["/api/internal/ask-bot", "/api/internal/delegate-bot"].includes(path)) {
75745
76487
  return json3(res, 409, { error: "Use coordinate_bots for teamwork; it queues actual teammates and resumes you automatically." });
75746
76488
  }
75747
76489
  for (const key3 of ["self", "fromBotId", "botId"]) {
@@ -75819,6 +76561,13 @@ var handleRequest = async (req, res) => {
75819
76561
  requireActiveInternalCapability();
75820
76562
  return json3(res, 200, { result });
75821
76563
  }
76564
+ if (method === "GET" && path === "/api/internal/shared-computers" && sharedComputersEnabled(cfg)) return json3(res, 200, { computers: sharedComputers.list() });
76565
+ if (method === "POST" && path === "/api/internal/shared-computers" && sharedComputersEnabled(cfg)) {
76566
+ const parsed = sharedComputerOperation.safeParse(await readInternalBody());
76567
+ if (!sharedComputersEnabled(cfg)) return json3(res, 404, { error: "unknown internal endpoint" });
76568
+ if (!parsed.success) return json3(res, 400, { error: "Invalid shared computer operation" });
76569
+ return json3(res, 200, { result: await sharedComputers.request(parsed.data, () => sharedComputersEnabled(cfg) && internalCapabilityIsActive(internalCapability)) });
76570
+ }
75822
76571
  if (method === "GET" && path === "/api/internal/agents") {
75823
76572
  const sender = internalSender;
75824
76573
  const bots = reachablePeers(store.bots, sender).map((b2) => {
@@ -75848,6 +76597,7 @@ var handleRequest = async (req, res) => {
75848
76597
  if (task2.activity === "waiting-on-you") return "waiting-on-you";
75849
76598
  if (threadBusy(bot.id, task2.threadId)) return "running";
75850
76599
  if (queuedThreadPosition(bot.id, task2.threadId) !== null) return "queued";
76600
+ if (roomHandoffs.activeDirect(task2.threadId)) return "queued";
75851
76601
  if (task2.closedBy) return "closed";
75852
76602
  return "idle";
75853
76603
  };
@@ -75898,7 +76648,7 @@ var handleRequest = async (req, res) => {
75898
76648
  return json3(res, 403, { error: "that thread is not yours to close \u2014 only the bot that opened it, or its own bot, can" });
75899
76649
  }
75900
76650
  if (threadId === fromThreadId) return json3(res, 400, { error: "you cannot close the thread you are speaking in \u2014 finish your turn instead" });
75901
- if (threadBusy(owner2.id, threadId)) {
76651
+ if (threadBusy(owner2.id, threadId) || queuedThreadPosition(owner2.id, threadId) !== null || roomHandoffs.activeDirect(threadId)) {
75902
76652
  return json3(res, 409, { error: `#${task2.title} is still running \u2014 wait for it to finish (list_threads), or the person can stop it from the app` });
75903
76653
  }
75904
76654
  if (task2.closedBy) {
@@ -76518,7 +77268,13 @@ var handleRequest = async (req, res) => {
76518
77268
  const existing = roomHandoffs.children(internalCapability.roomHandoffId ?? internalCapability.generation).find((node3) => node3.key === parsed.data.requestKey + ":" + target.botId);
76519
77269
  if (existing) target.threadId = existing.threadId;
76520
77270
  else {
76521
- const task2 = store.createTask(target.botId, parsed.data.message, false);
77271
+ const task2 = store.createTask(
77272
+ target.botId,
77273
+ parsed.data.message,
77274
+ false,
77275
+ void 0,
77276
+ { botId: internalSender.id, name: internalSender.name, at: Date.now() }
77277
+ );
76522
77278
  if (!task2) throw new Error("The recipient no longer exists");
76523
77279
  target.threadId = createdThread = task2.threadId;
76524
77280
  }
@@ -76660,15 +77416,23 @@ var handleRequest = async (req, res) => {
76660
77416
  const toBotId = typeof body.toBotId === "string" && body.toBotId.trim() ? body.toBotId.trim() : from.id;
76661
77417
  const target = store.bot(toBotId);
76662
77418
  if (!target) return json3(res, 404, { error: "no such bot" });
76663
- const folder2 = typeof body.folder === "string" ? body.folder.trim() : "";
77419
+ if (internalCapability.roomCoordination) {
77420
+ if (target.id !== from.id) {
77421
+ return json3(res, 409, { error: "Use coordinate_bots for teamwork; it queues actual teammates and resumes you automatically." });
77422
+ }
77423
+ if (!internalCapability.ownThreadCreation) {
77424
+ return json3(res, 409, { error: "Only a direct user request can open separate self-owned threads. Finish this assigned work here; use coordinate_bots for teammates." });
77425
+ }
77426
+ }
77427
+ const folder3 = typeof body.folder === "string" ? body.folder.trim() : "";
76664
77428
  let projectId;
76665
- if (folder2) {
77429
+ if (folder3) {
76666
77430
  const project = (target.projects ?? []).find(
76667
- (candidate) => candidate.id === folder2 || candidate.name.trim().toLowerCase() === folder2.toLowerCase()
77431
+ (candidate) => candidate.id === folder3 || candidate.name.trim().toLowerCase() === folder3.toLowerCase()
76668
77432
  );
76669
77433
  if (!project) {
76670
77434
  const names = (target.projects ?? []).map((candidate) => candidate.name).join(", ");
76671
- return json3(res, 400, { error: `@${target.name} has no folder named "${folder2}"${names ? ` \u2014 the folders are: ${names}` : " \u2014 it has no folders; leave folder out"}` });
77435
+ return json3(res, 400, { error: `@${target.name} has no folder named "${folder3}"${names ? ` \u2014 the folders are: ${names}` : " \u2014 it has no folders; leave folder out"}` });
76672
77436
  }
76673
77437
  projectId = project.id;
76674
77438
  }
@@ -76920,7 +77684,7 @@ ${message3}`;
76920
77684
  description: `${reason ? `${target.description} ${reason}` : target.description} ${from.name} can use it but never read it back.`,
76921
77685
  placeholder: target.placeholder,
76922
77686
  helpUrl: target.helpUrl,
76923
- requestKey: randomUUID25()
77687
+ requestKey: randomUUID27()
76924
77688
  }
76925
77689
  });
76926
77690
  return json3(res, 201, { messageId: message3.id, label: target.label });
@@ -76949,7 +77713,7 @@ ${message3}`;
76949
77713
  const bot = store.bot(botId);
76950
77714
  if (!bot) return json3(res, 404, { error: "no such bot" });
76951
77715
  if (method === "GET") {
76952
- const snapshot2 = computerControl.snapshot(botId);
77716
+ const snapshot2 = botComputerControlSnapshot(botId, internalCapability.teamComputerId);
76953
77717
  const computer = turnComputerResources.get(internalCapability.threadId);
76954
77718
  if (!snapshot2.held && computer && computer.owner.generation === internalCapability.generation && !claimTurnResource(computer.owner, computer.resource)) {
76955
77719
  return json3(res, 200, {
@@ -76962,7 +77726,8 @@ ${message3}`;
76962
77726
  }
76963
77727
  if (method === "POST") {
76964
77728
  const body = await readInternalBody();
76965
- const { snapshot: snapshot2, requestId } = computerControl.requestHelpLease(botId, body.reason);
77729
+ const controlKey = internalCapability.teamComputerId ? teamComputerOwner(internalCapability.teamComputerId) : botId;
77730
+ const { snapshot: snapshot2, requestId } = computerControl.requestHelpLease(controlKey, body.reason);
76966
77731
  const roomTurn = activeGroupTurnForBot(bot.id);
76967
77732
  const target = blockedTarget({ ...bot, threadId: internalCapability.threadId }, roomTurn && { ...roomTurn.group, threadId: roomTurn.threadId });
76968
77733
  notify(
@@ -76974,7 +77739,7 @@ ${message3}`;
76974
77739
  }
76975
77740
  if (method === "DELETE") {
76976
77741
  const body = await readInternalBody();
76977
- const snapshot2 = computerControl.expireHelp(botId, body.requestId);
77742
+ const snapshot2 = computerControl.expireHelp(internalCapability.teamComputerId ? teamComputerOwner(internalCapability.teamComputerId) : botId, body.requestId);
76978
77743
  return json3(res, 200, { held: snapshot2.held, helpOpen: snapshot2.helpReason !== null });
76979
77744
  }
76980
77745
  return json3(res, 405, { error: "method not allowed" });
@@ -77173,6 +77938,10 @@ ${message3}`;
77173
77938
  }
77174
77939
  const browser = await browserIntegration(bot.id, bot.browserProfile);
77175
77940
  if (!browser) return json3(res, 503, { error: "Install the browser engine first." });
77941
+ if (HOSTED_WORKSPACE && auth.kind === "session") {
77942
+ const failure5 = workspaceAccess ? await workspaceAccess.authorize(req, auth) : { status: 503, error: "Workspace sign-in is unavailable." };
77943
+ if (failure5) return json3(res, failure5.status, { error: failure5.error });
77944
+ }
77176
77945
  const owner2 = auth.kind === "session" ? auth.session.id : "local-owner";
77177
77946
  const isCurrent = () => {
77178
77947
  const current2 = store.bot(bot.id);
@@ -77270,7 +78039,7 @@ data: ${JSON.stringify({ kind: "ping" })}
77270
78039
  groups: store.groups.map((g2) => ({ ...publicGroupState(g2), ...messagePage(g2.threadId, limit) })),
77271
78040
  computerControl: Object.fromEntries(
77272
78041
  store.bots.map((bot) => {
77273
- const snapshot2 = computerControl.snapshot(bot.id);
78042
+ const snapshot2 = botComputerControlSnapshot(bot.id);
77274
78043
  return [bot.id, { held: snapshot2.held, helpReason: snapshot2.helpReason }];
77275
78044
  })
77276
78045
  )
@@ -77974,7 +78743,7 @@ data: ${JSON.stringify({ kind: "ping" })}
77974
78743
  for (const threadId of threadIds) {
77975
78744
  for (const dir of [EVENTS_DIR, NATIVE_DIR]) {
77976
78745
  try {
77977
- unlinkSync12(join61(dir, `${threadId}.ndjson`));
78746
+ unlinkSync13(join61(dir, `${threadId}.ndjson`));
77978
78747
  } catch {
77979
78748
  }
77980
78749
  }
@@ -78128,6 +78897,7 @@ data: ${JSON.stringify({ kind: "ping" })}
78128
78897
  if (path === "/api/sidebar-sections" && (method === "PATCH" || method === "DELETE")) {
78129
78898
  const section2 = url2.searchParams.get("section")?.trim();
78130
78899
  if (!section2) return json3(res, 400, { error: "Choose a named team" });
78900
+ if (teamComputers.forSection(section2)) return json3(res, 409, { error: "Unassign this team's computer before renaming or deleting the team" });
78131
78901
  let nextName = null;
78132
78902
  if (method === "PATCH") {
78133
78903
  const parsed = external_exports.object({ name: external_exports.string().trim().min(1).max(60) }).strict().safeParse(await readBody(req));
@@ -78149,6 +78919,10 @@ data: ${JSON.stringify({ kind: "ping" })}
78149
78919
  }
78150
78920
  const botIds = [...new Set(parsed.data.botIds)];
78151
78921
  if (!name2 && !botIds.length) return json3(res, 400, { error: "Team name is required" });
78922
+ for (const botId of botIds) {
78923
+ const bot = store.bot(botId);
78924
+ if (bot) assertTeamComputerChangeIdle(bot, { ...bot, section: name2 || void 0 });
78925
+ }
78152
78926
  const result = store.setBotsSection(botIds, name2);
78153
78927
  if (!result.ok) {
78154
78928
  if (result.reason === "chief-conflict") {
@@ -78637,11 +79411,13 @@ data: ${JSON.stringify({ kind: "ping" })}
78637
79411
  return json3(res, 409, { error: "Stop the bot and release browser control before changing its profile." });
78638
79412
  }
78639
79413
  if (profile.patch.soul !== void 0) {
79414
+ if (freshBrowserBot) assertTeamComputerChangeIdle(freshBrowserBot, { ...freshBrowserBot, ...patch });
78640
79415
  const runtimePatch = { ...patch };
78641
79416
  for (const field of Object.keys(profile.patch)) delete runtimePatch[field];
78642
79417
  if (Object.keys(runtimePatch).length) store.patchBot(m2[1], runtimePatch);
78643
79418
  bot = store.patchBotProfile(m2[1], profile.patch);
78644
79419
  } else {
79420
+ if (freshBrowserBot) assertTeamComputerChangeIdle(freshBrowserBot, { ...freshBrowserBot, ...patch });
78645
79421
  bot = store.patchBot(m2[1], patch);
78646
79422
  }
78647
79423
  if (!bot) return json3(res, 404, { error: "no such bot" });
@@ -79523,6 +80299,104 @@ data: ${JSON.stringify({ kind: "ping" })}
79523
80299
  broadcast({ kind: "bot", bot: fresh });
79524
80300
  return json3(res, 200, { bot: fresh });
79525
80301
  }
80302
+ if (path === "/api/team-computers" && method === "GET") {
80303
+ res.setHeader("cache-control", "private, no-store");
80304
+ return json3(res, 200, await teamComputersPayload());
80305
+ }
80306
+ m2 = path.match(/^\/api\/team-computers(?:\/([\w-]+)(?:\/(provision|join|sleep|control))?)?$/);
80307
+ if (m2) {
80308
+ const computerId = m2[1];
80309
+ const action = m2[2];
80310
+ let found = computerId ? teamComputers.get(computerId) : void 0;
80311
+ if (computerId && !found) return json3(res, 404, { error: "No such team computer" });
80312
+ if (method === "GET" && action === "control" && found) {
80313
+ return json3(res, 200, computerControl.snapshot(teamComputerOwner(found.id)));
80314
+ }
80315
+ if (!String(req.headers["content-type"] ?? "").toLowerCase().startsWith("application/json")) return json3(res, 415, { error: "content-type must be application/json" });
80316
+ const body = await readBody(req);
80317
+ found = computerId ? teamComputers.get(computerId) : void 0;
80318
+ const assertCurrentOwner = () => {
80319
+ if (auth.kind === "session" && !sessions.isLive(auth.session.id)) throw Object.assign(new Error("Your session ended; sign in again"), { status: 401 });
80320
+ if (computerProviderConfigTransitions.has("box")) throw Object.assign(new Error(providerTransitionMessage("box")), { status: 409 });
80321
+ };
80322
+ assertCurrentOwner();
80323
+ if (action === "control" && method === "POST" && found) {
80324
+ const parsed = external_exports.object({ action: external_exports.enum(["take", "release"]), controlLeaseId: controlLeaseIdSchema.optional() }).strict().safeParse(body);
80325
+ if (!parsed.success) return json3(res, 400, { error: "Choose take or release with a valid optional controlLeaseId" });
80326
+ const key3 = teamComputerOwner(found.id);
80327
+ if (parsed.data.action === "take" && boxLifecycleBusyBots.has(key3)) return json3(res, 409, { error: "Wait for this computer's action to finish before taking control" });
80328
+ if (parsed.data.action === "take") assertTeamControlCanBeTaken(found.id);
80329
+ if (parsed.data.controlLeaseId) {
80330
+ const result = parsed.data.action === "take" ? computerControl.acquireLease(key3, parsed.data.controlLeaseId) : computerControl.releaseLease(key3, parsed.data.controlLeaseId);
80331
+ return json3(res, 200, { ...result.snapshot, ...result });
80332
+ }
80333
+ return json3(res, 200, parsed.data.action === "take" ? computerControl.take(key3) : computerControl.release(key3));
80334
+ }
80335
+ if (method === "PATCH" && found && !action) {
80336
+ const parsed = teamComputerAssignment.safeParse(body);
80337
+ if (!parsed.success) return json3(res, 400, { error: "Confirm shared desktop access with section and acknowledgeSharedAccess: true" });
80338
+ const section2 = parsed.data.section;
80339
+ const checkAssignment = () => {
80340
+ assertCurrentOwner();
80341
+ if (section2 !== null && section2 !== "" && !store.sections.includes(section2)) throw Object.assign(new Error("Create the team before assigning a computer"), { status: 404 });
80342
+ if (section2 !== null && store.bots.some((bot) => sectionKey3(bot.section) === section2 && (botHasActiveTurn(bot.id) || routines?.activeRunForBot(bot.id) || botComputerControlSnapshot(bot.id).held || boxLifecycleBusyBots.has(bot.id)))) throw Object.assign(new Error("Stop the target team's work and release computer control before assigning this computer"), { status: 409 });
80343
+ };
80344
+ checkAssignment();
80345
+ const release = claimTeamComputerLifecycle(found);
80346
+ try {
80347
+ if (section2 !== null && !await findBox(cfg, teamComputerOwner(found.id))) return json3(res, 409, { error: "Create or retry this computer before assigning it to a team" });
80348
+ checkAssignment();
80349
+ if (teamComputerInUse(found)) return json3(res, 409, { error: "This team computer became busy; stop its work before assigning it" });
80350
+ teamComputers.assign(found.id, section2);
80351
+ return json3(res, 200, { ok: true });
80352
+ } finally {
80353
+ release();
80354
+ }
80355
+ }
80356
+ if (method === "POST" && !computerId) {
80357
+ const parsed = teamComputerCreate.safeParse(body);
80358
+ if (!parsed.success) return json3(res, 400, { error: "Provide requestId (UUID), a name, and acknowledgeCost: true to create a paid Box" });
80359
+ if (!boxConfigured(cfg)) return json3(res, 409, { error: "Configure Box in Settings before creating a cloud computer" });
80360
+ const computer = teamComputers.create(parsed.data.name, parsed.data.requestId);
80361
+ const release = claimTeamComputerLifecycle(computer);
80362
+ try {
80363
+ await provisionBox(cfg, teamComputerOwner(computer.id), computer.name);
80364
+ teamComputers.setProblem(computer.id);
80365
+ return json3(res, 201, { id: computer.id });
80366
+ } catch (error51) {
80367
+ teamComputers.setProblem(computer.id, redactSecretsInText(error51 instanceof Error ? error51.message : String(error51)));
80368
+ throw error51;
80369
+ } finally {
80370
+ release();
80371
+ }
80372
+ }
80373
+ if (method === "POST" && found && action && action !== "control") {
80374
+ if (action === "provision" && body?.acknowledgeCost !== true) return json3(res, 400, { error: "Confirm Box creation or wake costs with acknowledgeCost: true" });
80375
+ if (action === "join") {
80376
+ const key3 = teamComputerOwner(found.id);
80377
+ if (boxLifecycleBusyBots.has(key3)) return json3(res, 409, { error: "Wait for this computer's action to finish" });
80378
+ if (!computerControl.snapshot(key3).held) return json3(res, 409, { error: "Take control before opening this shared desktop" });
80379
+ const release2 = claimBotComputerLifecycle(key3);
80380
+ try {
80381
+ return json3(res, 200, await joinReadyBox(cfg, key3));
80382
+ } finally {
80383
+ release2();
80384
+ }
80385
+ }
80386
+ const release = claimTeamComputerLifecycle(found);
80387
+ try {
80388
+ const result = action === "provision" ? await provisionBox(cfg, teamComputerOwner(found.id), found.name) : await sleepBox(cfg, teamComputerOwner(found.id));
80389
+ teamComputers.setProblem(found.id);
80390
+ return json3(res, 200, result);
80391
+ } catch (error51) {
80392
+ teamComputers.setProblem(found.id, redactSecretsInText(error51 instanceof Error ? error51.message : String(error51)));
80393
+ throw error51;
80394
+ } finally {
80395
+ release();
80396
+ }
80397
+ }
80398
+ return json3(res, 405, { error: "method not allowed" });
80399
+ }
79526
80400
  if (method === "GET" && path === "/api/computers/boxes") {
79527
80401
  res.setHeader("cache-control", "private, no-store");
79528
80402
  return json3(res, 200, await listManagedBoxes(cfg, managedBoxOwners()));
@@ -79804,8 +80678,10 @@ data: ${JSON.stringify({ kind: "ping" })}
79804
80678
  }
79805
80679
  const kind = provider;
79806
80680
  const saved = kind === "anthropic" ? cfg.anthropic : kind === "openaiCompat" ? cfg.openaiCompat : cfg.xai;
79807
- const pasted = typeof body?.key === "string" ? body.key.trim() : "";
79808
- const key3 = pasted || saved?.key || "";
80681
+ if (body?.key !== void 0 && typeof body.key !== "string") {
80682
+ return json3(res, 400, { error: "key must be a string" });
80683
+ }
80684
+ const key3 = typeof body?.key === "string" ? body.key.trim() : saved?.key?.trim() || "";
79809
80685
  if (!key3) return json3(res, 400, { error: "No key to test. Paste one or save one first." });
79810
80686
  if (key3.length > 512) return json3(res, 400, { error: "That does not look like an API key." });
79811
80687
  const url3 = typeof body?.url === "string" && body.url.trim() ? body.url.trim() : saved?.url;
@@ -80416,6 +81292,11 @@ data: ${JSON.stringify({ kind: "ping" })}
80416
81292
  throw error51;
80417
81293
  }
80418
81294
  let browserReferenceCleanupError = null;
81295
+ if (patch.signIn !== void 0) sessions.revalidateEmailSessions();
81296
+ if (!sharedComputersEnabled(cfg)) {
81297
+ sharedComputers.close();
81298
+ sharedComputerControl.close();
81299
+ }
80419
81300
  if (disablingBuiltInBrowser) browserLive.closeAll();
80420
81301
  for (const request2 of browserCleanupRequests) {
80421
81302
  if (request2.kind === "profile") browserLive.closeForSession(browserSessionId("", request2.partitionId));
@@ -80670,29 +81551,36 @@ data: ${JSON.stringify({ kind: "ping" })}
80670
81551
  if (m2 && method === "GET") {
80671
81552
  const bot = store.bot(m2[1]);
80672
81553
  if (!bot) return json3(res, 404, { error: "no such bot" });
81554
+ const teamComputer = inheritedTeamComputer(bot);
81555
+ if (teamComputer) return json3(res, 200, { backend: "box", teamComputer: { id: teamComputer.id, name: teamComputer.name }, ...await boxStatus(cfg, teamComputerOwner(teamComputer.id)) });
80673
81556
  return bot.cloudBackend === "vps" ? json3(res, 200, { backend: "vps", ...await vpsComputerStatus(cfg, bot.id) }) : json3(res, 200, { backend: "box", ...await boxStatus(cfg, bot.id) });
80674
81557
  }
80675
81558
  m2 = path.match(/^\/api\/bots\/([\w-]+)\/computer\/control$/);
80676
81559
  if (m2) {
80677
81560
  const bot = store.bot(m2[1]);
80678
81561
  if (!bot) return json3(res, 404, { error: "no such bot" });
80679
- if (method === "GET") return json3(res, 200, computerControl.snapshot(bot.id));
81562
+ if (method === "GET") return json3(res, 200, botComputerControlSnapshot(bot.id));
80680
81563
  if (method === "POST") {
80681
81564
  if (!String(req.headers["content-type"] ?? "").toLowerCase().startsWith("application/json")) {
80682
81565
  return json3(res, 415, { error: "content-type must be application/json" });
80683
81566
  }
80684
81567
  const body = await readBody(req);
80685
81568
  const action = String(body.action ?? "");
81569
+ const currentBot = store.bot(bot.id);
81570
+ if (!currentBot) return json3(res, 404, { error: "no such bot" });
81571
+ const controlKey = botComputerControlKey(currentBot);
81572
+ const teamComputer = inheritedTeamComputer(currentBot);
81573
+ if (action === "take" && teamComputer) assertTeamControlCanBeTaken(teamComputer.id);
80686
81574
  const leaseResult = body.controlLeaseId === void 0 ? null : controlLeaseIdSchema.safeParse(body.controlLeaseId);
80687
81575
  if (leaseResult && !leaseResult.success) {
80688
81576
  return json3(res, 400, { error: "controlLeaseId is invalid" });
80689
81577
  }
80690
81578
  const controlLeaseId = leaseResult?.data;
80691
- if (action === "take" && boxLifecycleBusyBots.has(bot.id)) {
81579
+ if (action === "take" && (boxLifecycleBusyBots.has(bot.id) || boxLifecycleBusyBots.has(controlKey))) {
80692
81580
  return json3(res, 409, { error: "this bot's cloud computer is being changed \u2014 wait before taking control" });
80693
81581
  }
80694
81582
  if (action === "take" && controlLeaseId) {
80695
- const result = computerControl.acquireLease(bot.id, controlLeaseId);
81583
+ const result = computerControl.acquireLease(controlKey, controlLeaseId);
80696
81584
  return json3(res, 200, {
80697
81585
  ...result.snapshot,
80698
81586
  owned: result.owned,
@@ -80700,12 +81588,12 @@ data: ${JSON.stringify({ kind: "ping" })}
80700
81588
  });
80701
81589
  }
80702
81590
  if (action === "release" && controlLeaseId) {
80703
- const result = computerControl.releaseLease(bot.id, controlLeaseId);
81591
+ const result = computerControl.releaseLease(controlKey, controlLeaseId);
80704
81592
  return json3(res, 200, { ...result.snapshot, released: result.released });
80705
81593
  }
80706
- if (action === "take") return json3(res, 200, computerControl.take(bot.id));
80707
- if (action === "release") return json3(res, 200, computerControl.release(bot.id));
80708
- if (action === "dismiss-help") return json3(res, 200, computerControl.dismissHelp(bot.id));
81594
+ if (action === "take") return json3(res, 200, computerControl.take(controlKey));
81595
+ if (action === "release") return json3(res, 200, computerControl.release(controlKey));
81596
+ if (action === "dismiss-help") return json3(res, 200, computerControl.dismissHelp(controlKey));
80709
81597
  return json3(res, 400, { error: "action must be take, release, or dismiss-help" });
80710
81598
  }
80711
81599
  return json3(res, 405, { error: "method not allowed" });
@@ -80734,6 +81622,22 @@ data: ${JSON.stringify({ kind: "ping" })}
80734
81622
  if (boxLifecycleBusyBots.has(botId)) {
80735
81623
  return json3(res, 409, { error: "this bot's cloud computer is being changed \u2014 wait for it to finish" });
80736
81624
  }
81625
+ const teamComputer = inheritedTeamComputer(bot);
81626
+ if (teamComputer) {
81627
+ const key3 = teamComputerOwner(teamComputer.id);
81628
+ if (boxLifecycleBusyBots.has(key3)) return json3(res, 409, { error: "This team computer is being changed; wait for it to finish" });
81629
+ if (m2[2] === "provision" || m2[2] === "remove") return json3(res, 409, { error: "Manage this shared computer from the Team map" });
81630
+ if (m2[2] === "exec") return json3(res, 409, { error: "Use the bot's scoped computer tools for this shared desktop" });
81631
+ if (m2[2] === "join" && !computerControl.snapshot(key3).held) return json3(res, 409, { error: "Take control before opening this shared desktop" });
81632
+ const release = m2[2] === "sleep" ? claimTeamComputerLifecycle(teamComputer) : claimBotComputerLifecycle(key3);
81633
+ try {
81634
+ if (m2[2] === "join") return json3(res, 200, await joinReadyBox(cfg, key3));
81635
+ if (m2[2] === "screenshot") return json3(res, 200, await screenshotBox(cfg, key3));
81636
+ return json3(res, 200, await sleepBox(cfg, key3));
81637
+ } finally {
81638
+ release();
81639
+ }
81640
+ }
80737
81641
  if (bot.cloudBackend === "vps") {
80738
81642
  if (m2[2] === "screenshot") {
80739
81643
  let preview = vpsPreviewRequests.get(botId);
@@ -80818,6 +81722,11 @@ data: ${JSON.stringify({ kind: "ping" })}
80818
81722
  var server = createServer3(handleRequest);
80819
81723
  calendarCalls.start();
80820
81724
  console.log(describeEdition(await loadEnterpriseLayer()));
81725
+ workspaceAccess = createWorkspaceAccess({ sessions, cookieName: SESSION_COOKIE, closeSessionStreams });
81726
+ var workspaceAccessTimer = workspaceAccess ? setInterval(() => {
81727
+ void workspaceAccess.revalidate().catch((error51) => console.warn("workspace access revalidation failed", error51));
81728
+ }, 1e4) : null;
81729
+ workspaceAccessTimer?.unref();
80821
81730
  console.log(describeBrand(loadBrand()));
80822
81731
  try {
80823
81732
  const reclaimedPartials = cleanupStaleAttachmentPartials();
@@ -80886,7 +81795,10 @@ var gracefulShutdown = createGracefulShutdown({
80886
81795
  cleanup: [
80887
81796
  () => {
80888
81797
  followupsReady = false;
81798
+ if (workspaceAccessTimer) clearInterval(workspaceAccessTimer);
80889
81799
  revokeAllInternalCapabilities();
81800
+ sharedComputers.close();
81801
+ sharedComputerControl.close();
80890
81802
  browserLive.closeAll();
80891
81803
  for (const idle of localVmIdles.values()) idle.cancel();
80892
81804
  closeAllVpsDesktopTunnels();
@@ -80910,6 +81822,12 @@ var gracefulShutdown = createGracefulShutdown({
80910
81822
  // the shutdown deadline), immediately before the process exits, so no new
80911
81823
  // server can overlap with a still-mutating old one.
80912
81824
  exit: (code) => {
81825
+ try {
81826
+ sessions.close();
81827
+ } catch {
81828
+ console.error("Session persistence failed during shutdown; account sign-in will be required again.");
81829
+ code = 1;
81830
+ }
80913
81831
  closeMessageDb();
80914
81832
  releaseDataDirLeaseAtExit();
80915
81833
  process.exit(code);