openmausbot 0.1.90 → 0.1.91

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 (112) hide show
  1. package/dist/assets/{abnfDiagram-O67JEVCF-DUyXd0kL.js → abnfDiagram-O67JEVCF-yP-w8pRq.js} +1 -1
  2. package/dist/assets/{arc-q73qM-At.js → arc-DsivfZEh.js} +1 -1
  3. package/dist/assets/{architectureDiagram-NJMV4G6O-D33O3Wur.js → architectureDiagram-NJMV4G6O-CILw7lzT.js} +1 -1
  4. package/dist/assets/{blockDiagram-BEXU5L5S-DLsJeoXC.js → blockDiagram-BEXU5L5S-DEQUV3HF.js} +1 -1
  5. package/dist/assets/{c4Diagram-YGBWAQC7-DnaljZnu.js → c4Diagram-YGBWAQC7-lSXCZoKA.js} +1 -1
  6. package/dist/assets/channel-DrfGMu7X.js +1 -0
  7. package/dist/assets/{chunk-2Q5K7J3B-Da0h2i79.js → chunk-2Q5K7J3B-QCD5bgWP.js} +1 -1
  8. package/dist/assets/{chunk-3FUC2YCW-CfzHi54f.js → chunk-3FUC2YCW-C5-iZJuL.js} +2 -2
  9. package/dist/assets/{chunk-5VM5RSS4-LjE4d9Jf.js → chunk-5VM5RSS4-cxsawMpY.js} +1 -1
  10. package/dist/assets/{chunk-GTNCS2PH-DY-UGmvc.js → chunk-GTNCS2PH-DV8tV8H7.js} +1 -1
  11. package/dist/assets/{chunk-GWA4HPMP-BqeGhyhP.js → chunk-GWA4HPMP-CJFZyfbW.js} +1 -1
  12. package/dist/assets/{chunk-JWPE2WC7-BYa9EBAg.js → chunk-JWPE2WC7-BigbMXco.js} +1 -1
  13. package/dist/assets/{chunk-NETBCI7D-vTrDtWyb.js → chunk-NETBCI7D-oVxTfpDy.js} +1 -1
  14. package/dist/assets/{chunk-WEXAMYUT-ic5tXadM.js → chunk-WEXAMYUT-CH4BiroW.js} +1 -1
  15. package/dist/assets/{chunk-XXDRQBXY-DvRCY-c6.js → chunk-XXDRQBXY-KEDa6gax.js} +1 -1
  16. package/dist/assets/{classDiagram-v2-NBCMYWYE-DVbmgz_L.js → classDiagram-v2-NBCMYWYE-BiPjfqVv.js} +1 -1
  17. package/dist/assets/{cose-bilkent-JH36ORCC-vdUs2n57.js → cose-bilkent-JH36ORCC-Caxew3OY.js} +1 -1
  18. package/dist/assets/{cynefin-OW5HDTMX-BvLGlV3U.js → cynefin-OW5HDTMX-B_Pui369.js} +1 -1
  19. package/dist/assets/{cynefinDiagram-VND7K2PF-C4GcJQDt.js → cynefinDiagram-VND7K2PF-X64ydIhP.js} +1 -1
  20. package/dist/assets/{dagre-6A5THRUB-BMSaHpt_.js → dagre-6A5THRUB-TqYCI1E1.js} +1 -1
  21. package/dist/assets/{diagram-22UHCM2B-D5b9UBhC.js → diagram-22UHCM2B-TeOt3rAQ.js} +1 -1
  22. package/dist/assets/{diagram-3UASUU5V-CIKLlB7Y.js → diagram-3UASUU5V-D70BLhDA.js} +1 -1
  23. package/dist/assets/{diagram-ATOU4E4O-BzXb-7k7.js → diagram-ATOU4E4O-BbQDLwUC.js} +1 -1
  24. package/dist/assets/{diagram-CDSNMT55-HsTvJ7uT.js → diagram-CDSNMT55-ChNJZ1u-.js} +1 -1
  25. package/dist/assets/{diagram-MLGK6HIB-Db9ySNbQ.js → diagram-MLGK6HIB-DMBl2APa.js} +1 -1
  26. package/dist/assets/{diagram-MPIPVDR6-D6BKbpqY.js → diagram-MPIPVDR6-CEqDchOp.js} +1 -1
  27. package/dist/assets/{ebnfDiagram-ZINNZB2B-Bq067L0t.js → ebnfDiagram-ZINNZB2B-BVDKRp_-.js} +1 -1
  28. package/dist/assets/{elk-276RUBZZ-DhaD7fNJ.js → elk-276RUBZZ-CRkofO9B.js} +1 -1
  29. package/dist/assets/{erDiagram-OPXOYQCR-fwIhfz36.js → erDiagram-OPXOYQCR-DruOMh5e.js} +1 -1
  30. package/dist/assets/{flowDiagram-KWPJA3E3-DygIyu0V.js → flowDiagram-KWPJA3E3-8GMQeB7t.js} +1 -1
  31. package/dist/assets/{ganttDiagram-FUAMR5RP-DPYfSO96.js → ganttDiagram-FUAMR5RP-ORE9jcsM.js} +1 -1
  32. package/dist/assets/{gitGraphDiagram-X574FWY7-DhkHeCle.js → gitGraphDiagram-X574FWY7-BmsedsWh.js} +1 -1
  33. package/dist/assets/index-BlnCEcU6.js +588 -0
  34. package/dist/assets/{index-G1PHKGyh.js → index-DKNEM8Q6.js} +1 -1
  35. package/dist/assets/index-DVxjOvID.css +1 -0
  36. package/dist/assets/{infoDiagram-VRGFBTTK-DstBLxME.js → infoDiagram-VRGFBTTK-E_p7AooJ.js} +1 -1
  37. package/dist/assets/{ishikawaDiagram-OU5B5YK6-CrIsGgch.js → ishikawaDiagram-OU5B5YK6-Y-B2mKda.js} +1 -1
  38. package/dist/assets/{journeyDiagram-ZHPQQLJL-CYypGDbB.js → journeyDiagram-ZHPQQLJL-eA8RELcK.js} +1 -1
  39. package/dist/assets/{kanban-definition-PNTS6WVX-DGjPt8E_.js → kanban-definition-PNTS6WVX-DqIL2e8e.js} +1 -1
  40. package/dist/assets/{linear-Dwc3Hrru.js → linear-yC_29R6e.js} +1 -1
  41. package/dist/assets/{mermaid.core-Dc4OePBL.js → mermaid.core-VhEMkSND.js} +5 -5
  42. package/dist/assets/{mindmap-definition-NLK3R4M7-DV-vepj-.js → mindmap-definition-NLK3R4M7-oJ7kyQZy.js} +1 -1
  43. package/dist/assets/{pegDiagram-GJSIUBJH-lx5Kj-n5.js → pegDiagram-GJSIUBJH-BmfYcY0O.js} +1 -1
  44. package/dist/assets/{pieDiagram-5QR66LMP-Bq7Vmm15.js → pieDiagram-5QR66LMP-DKvgP7w9.js} +1 -1
  45. package/dist/assets/{quadrantDiagram-O4NWA36T-DeqeqOBj.js → quadrantDiagram-O4NWA36T-BEKL3W0L.js} +1 -1
  46. package/dist/assets/{railroadDiagram-XR7U4H2S-CXwRQzSC.js → railroadDiagram-XR7U4H2S-BEsiqR5s.js} +1 -1
  47. package/dist/assets/{requirementDiagram-PLB6GJNP-8tAeoB88.js → requirementDiagram-PLB6GJNP-DWBcWvAP.js} +1 -1
  48. package/dist/assets/{sankeyDiagram-IPEJSGJF-BG5zjGWU.js → sankeyDiagram-IPEJSGJF-rpZ2_O4s.js} +1 -1
  49. package/dist/assets/{sequenceDiagram-PO4LG4MO-DKF3iLcw.js → sequenceDiagram-PO4LG4MO-B99HQdJR.js} +1 -1
  50. package/dist/assets/{sizeCapture-INFHLROL-B-y1qUgT.js → sizeCapture-INFHLROL-BjN0qT5T.js} +1 -1
  51. package/dist/assets/{stateDiagram-v2-GCMORJYK-MkzPgxAc.js → stateDiagram-v2-GCMORJYK-BbsVgGsC.js} +1 -1
  52. package/dist/assets/{swimlanes-2SLR337P-DnbaOEwH.js → swimlanes-2SLR337P-DG3gKkeR.js} +1 -1
  53. package/dist/assets/swimlanesDiagram-TC7HE7FX-BCZhxl7e.js +8 -0
  54. package/dist/assets/{timeline-definition-EJHVYXUP-Bb4xG_Bt.js → timeline-definition-EJHVYXUP-BTHOdtF_.js} +1 -1
  55. package/dist/assets/{usecaseDiagram-POWQR4AR-JgkR9pFf.js → usecaseDiagram-POWQR4AR-acLSLFwc.js} +1 -1
  56. package/dist/assets/{vennDiagram-UO4OBE2U-CIPIrAYe.js → vennDiagram-UO4OBE2U-C5JzNX9G.js} +1 -1
  57. package/dist/assets/{wardleyDiagram-VNRHLVJA-BjCLuUvJ.js → wardleyDiagram-VNRHLVJA-DL1-PrR_.js} +1 -1
  58. package/dist/assets/{xychartDiagram-PMCCYNJV-DKgiaYxT.js → xychartDiagram-PMCCYNJV-Bvr1477p.js} +1 -1
  59. package/dist/index.html +2 -2
  60. package/dist-server/cloud-home-start.js +18 -3
  61. package/dist-server/container-mcp.js +84 -32
  62. package/dist-server/drivers/agents-proxy.js +11 -0
  63. package/dist-server/index.js +25183 -23580
  64. package/dist-server/openmausbot.js +313 -159
  65. package/dist-server/pair-cli.js +313 -159
  66. package/dist-server/server/boat.js +66 -21
  67. package/dist-server/server/bot-overview.js +7 -4
  68. package/dist-server/server/cloud-home-start.js +23 -5
  69. package/dist-server/server/cloud-home.js +25 -2
  70. package/dist-server/server/cloud-lending.js +103 -0
  71. package/dist-server/server/cloud-move-http.js +268 -0
  72. package/dist-server/server/cloud-move.js +418 -0
  73. package/dist-server/server/config.js +42 -4
  74. package/dist-server/server/decider/index.js +238 -0
  75. package/dist-server/server/decider/jev.js +183 -0
  76. package/dist-server/server/decider/log.js +62 -0
  77. package/dist-server/server/decider/relay.js +52 -0
  78. package/dist-server/server/decider/room-routing.js +88 -0
  79. package/dist-server/server/decider/types.js +5 -0
  80. package/dist-server/server/drivers/agents-catalog.js +16 -0
  81. package/dist-server/server/drivers/boatagent.js +20 -12
  82. package/dist-server/server/drivers/claude.js +1 -0
  83. package/dist-server/server/group-goal-run.js +6 -4
  84. package/dist-server/server/harness-capabilities.js +2 -0
  85. package/dist-server/server/included-services.js +70 -0
  86. package/dist-server/server/index.js +438 -102
  87. package/dist-server/server/model-context-window.js +4 -4
  88. package/dist-server/server/package-export.js +17 -10
  89. package/dist-server/server/package-import.js +6 -3
  90. package/dist-server/server/package-parts.js +3 -1
  91. package/dist-server/server/request-auth.js +2 -0
  92. package/dist-server/server/routes/decider.js +19 -0
  93. package/dist-server/server/shared-computers.js +116 -30
  94. package/dist-server/server/store.js +17 -2
  95. package/dist-server/server/surface.js +13 -0
  96. package/dist-server/server/system-prompt.js +11 -0
  97. package/dist-server/server/team-backup.js +6 -2
  98. package/dist-server/server/team-manifest.js +11 -3
  99. package/dist-server/server/tts/elevenlabs.js +11 -6
  100. package/dist-server/server/tts/index.js +17 -10
  101. package/dist-server/server/workspace-backup-policy.js +22 -3
  102. package/dist-server/server/workspace-backup.js +25 -8
  103. package/dist-server/shared/cloud-home.js +4 -0
  104. package/dist-server/shared/package-format.js +30 -4
  105. package/dist-server/shared/team-backup.js +20 -3
  106. package/dist-server/vps-container-mcp.js +84 -32
  107. package/dist-server/workspace-backup.worker.js +113 -31
  108. package/package.json +1 -1
  109. package/dist/assets/channel-BCuyHjFV.js +0 -1
  110. package/dist/assets/index-CRvo-DS2.css +0 -1
  111. package/dist/assets/index-CWYGlA63.js +0 -588
  112. package/dist/assets/swimlanesDiagram-TC7HE7FX-BZQ07DGE.js +0 -8
@@ -12,13 +12,29 @@ export function portableWorkspaceConfig(value) {
12
12
  const config = parseStoredConfig(value);
13
13
  return Object.fromEntries(PORTABLE_CONFIG_KEYS.filter((key) => config[key] !== undefined).map((key) => [key, config[key]]));
14
14
  }
15
+ // Switches that belong to the machine even inside a portable section.
16
+ // Computer sharing (lending a computer to this server) opens routes and a
17
+ // desktop connector here, so a restored workspace never turns it on or off.
18
+ const DESTINATION_FEATURES = ["sharedComputers"];
15
19
  export function restoredWorkspaceConfig(portable, destination) {
16
20
  if (!destination || typeof destination !== "object" || Array.isArray(destination))
17
21
  throw new Error("Invalid destination workspace configuration; connections were not changed.");
18
22
  const merged = { ...destination };
19
23
  for (const key of PORTABLE_CONFIG_KEYS)
20
24
  delete merged[key];
21
- return { ...merged, ...portableWorkspaceConfig(portable) };
25
+ const restored = { ...merged, ...portableWorkspaceConfig(portable) };
26
+ const own = destination.features ?? {};
27
+ const features = { ...restored.features };
28
+ for (const key of DESTINATION_FEATURES) {
29
+ delete features[key];
30
+ if (own[key] !== undefined)
31
+ features[key] = own[key];
32
+ }
33
+ if (Object.keys(features).length)
34
+ restored.features = features;
35
+ else
36
+ delete restored.features;
37
+ return restored;
22
38
  }
23
39
  /** Exact app-owned authentication paths, not a scan of user document text. */
24
40
  export function excludedWorkspaceAuthPath(path) {
@@ -41,7 +57,10 @@ export function redownloadedOrgLibraryPath(path) {
41
57
  /** Per-turn engine hook bearers (`hook-tokens/<digest>.token`, written by the
42
58
  * Claude driver). They are dead once their turn settles, so they are never
43
59
  * exported. Unlike the saved auth paths above they are not refused on import:
44
- * v0.1.85 did export them, and such a backup must still restore, without them. */
60
+ * v0.1.85 did export them, and such a backup must still restore, without them.
61
+ * The same holds for the session registry's open marker: it describes the
62
+ * process that wrote it, so it is never exported or installed, and a restore
63
+ * leaves the destination's own marker where it is (workspace-backup.ts). */
45
64
  export function ephemeralWorkspaceTokenPath(path) {
46
- return /^hook-tokens(?:\/|$)/.test(path);
65
+ return /^(?:hook-tokens(?:\/|$)|sessions\.json\.open$)/.test(path);
47
66
  }
@@ -26,6 +26,9 @@ const EXCLUDED = new Set([
26
26
  ".openmausbot-server-child", "environment-id", "sessions.json", "tunnel-account.json",
27
27
  "team-computers.json",
28
28
  "openmausbot-server.lease", "box-create-requests.lock", "messages.db-wal", "messages.db-shm",
29
+ // This machine's decision-model log (server/decider/log.ts): local
30
+ // measurement of what the classifier picked, not workspace data.
31
+ "decider-log",
29
32
  ]);
30
33
  const EXCLUSION_NOTES = [
31
34
  "Device pairing, server identity, live leases and runtime files (existing destination identities are preserved).",
@@ -49,6 +52,12 @@ function preserved(name) {
49
52
  const folded = name.toLowerCase();
50
53
  return excluded(folded) && folded !== "messages.db-wal" && folded !== "messages.db-shm";
51
54
  }
55
+ /** Left where it is by a restore. The session registry's open marker is this
56
+ * machine's own (never exported or installed): a crash before the restore
57
+ * still costs account sign-ins, as it would have without one. */
58
+ function keptInPlace(name) {
59
+ return preserved(name) || name.toLowerCase() === "sessions.json.open";
60
+ }
52
61
  function folder(path) {
53
62
  mkdirSync(path, { recursive: true, mode: 0o700 });
54
63
  const stat = lstatSync(path);
@@ -544,22 +553,24 @@ async function decryptArchive(inputPath, plaintext, password) {
544
553
  }
545
554
  async function inspectTar(path) {
546
555
  const fd = openSync(path, "r");
547
- const signature = Buffer.alloc(2);
556
+ const signature = Buffer.alloc(4);
548
557
  try {
549
- readSync(fd, signature, 0, 2, 0);
558
+ readSync(fd, signature, 0, 4, 0);
550
559
  }
551
560
  finally {
552
561
  closeSync(fd);
553
562
  }
554
- // This format is uncompressed tar. Reject auto-detected gzip before tar's
555
- // parser can inflate an authenticated but malicious decompression bomb.
556
- if (signature[0] === 0x1f && signature[1] === 0x8b)
563
+ // This format is uncompressed tar. Reject auto-detected gzip and zstd
564
+ // before tar's parser can inflate an authenticated but malicious
565
+ // decompression bomb; tar is also told never to decompress (below).
566
+ if ((signature[0] === 0x1f && signature[1] === 0x8b) || signature.equals(Buffer.from([0x28, 0xb5, 0x2f, 0xfd]))) {
557
567
  throw new Error("Compressed payloads are not supported in this workspace backup version.");
568
+ }
558
569
  const entries = new Map();
559
570
  const names = new Set();
560
571
  let bytes = 0;
561
572
  let problem = "";
562
- await tar.t({ file: path, strict: true, onReadEntry(entry) {
573
+ await tar.t({ file: path, strict: true, brotli: false, zstd: false, onReadEntry(entry) {
563
574
  const name = entry.type === "Directory" ? entry.path.replace(/\/$/, "") : entry.path;
564
575
  const expectedRoot = name === "manifest.json" || name === "data" || name.startsWith("data/");
565
576
  // ReadEntry normalizes backslashes on Windows; inspect the raw header too
@@ -651,7 +662,7 @@ export async function stageWorkspaceBackup(dataDir, archivePath, options) {
651
662
  const expected = await inspectTar(plaintext);
652
663
  const staged = join(job.directory, "staged");
653
664
  folder(staged);
654
- await tar.x({ file: plaintext, cwd: staged, strict: true, preservePaths: false, umask: 0o077, noChmod: true });
665
+ await tar.x({ file: plaintext, cwd: staged, strict: true, preservePaths: false, umask: 0o077, noChmod: true, brotli: false, zstd: false });
655
666
  const manifest = validateStaged(staged, expected);
656
667
  const versions = [manifest.summary.appVersion, options.currentAppVersion ?? ""].map((version) => /^(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/.exec(version)?.slice(1).map(Number));
657
668
  if (versions[0] && versions[1]) {
@@ -1037,7 +1048,7 @@ export function applyPendingWorkspaceRestore(dataDir) {
1037
1048
  folder(join(safetyCopyPath, "data"));
1038
1049
  const journal = {
1039
1050
  id: pending.id, phase: "applying",
1040
- existing: readdirSync(dataDir).filter((name) => !preserved(name)).sort(),
1051
+ existing: readdirSync(dataDir).filter((name) => !keptInPlace(name)).sort(),
1041
1052
  incoming: readdirSync(prepared).sort(),
1042
1053
  };
1043
1054
  // Top-level source and destination names are checked before recording any
@@ -1087,3 +1098,9 @@ export function removeWorkspaceBackupJob(dataDir, id) {
1087
1098
  throw new Error("Unsafe workspace backup cleanup target.");
1088
1099
  rmSync(path, { recursive: true, force: true });
1089
1100
  }
1101
+ /** Whether a snapshot leaves out this workspace-relative path, by the same
1102
+ * rules as the export walk (cloud-move.ts sizes a move with it). Symbolic
1103
+ * links are the caller's to skip. */
1104
+ export function omittedFromWorkspaceBackup(path) {
1105
+ return excluded(path.split("/")[0]) || excludedWorkspaceAuthPath(path) || ephemeralWorkspaceTokenPath(path) || redownloadedOrgLibraryPath(path);
1106
+ }
@@ -0,0 +1,4 @@
1
+ /** Whether a Cloud home offers this place at all. */
2
+ export function cloudHomeOffersPlace(place) {
3
+ return place !== "local" && place !== "vm";
4
+ }
@@ -161,7 +161,24 @@ const requirementsSchema = z.object({
161
161
  capabilities: z.array(requiredText(80)).max(20),
162
162
  platforms: z.array(requiredText(80)).max(10).optional(),
163
163
  });
164
- const roomSchema = z.object({
164
+ const ROOM_RESPONDER_KINDS = new Set(["agent", "everyone", "mentions", "auto"]);
165
+ /** A responder kind from a newer release degrades to the room's first member
166
+ * as lead, which is what a room without a responder does, instead of
167
+ * refusing the whole package. Malformed values still fail below. */
168
+ function knownRoomResponder(value) {
169
+ if (!value || typeof value !== "object" || Array.isArray(value))
170
+ return value;
171
+ const room = value;
172
+ const responder = room.defaultResponder;
173
+ if (!responder || typeof responder !== "object" || Array.isArray(responder))
174
+ return value;
175
+ const kind = responder.kind;
176
+ if (typeof kind !== "string" || ROOM_RESPONDER_KINDS.has(kind))
177
+ return value;
178
+ const first = Array.isArray(room.members) && typeof room.members[0] === "string" ? room.members[0] : undefined;
179
+ return { ...room, defaultResponder: first ? { kind: "agent", agent: first } : { kind: "mentions" } };
180
+ }
181
+ const roomSchema = z.preprocess(knownRoomResponder, z.object({
165
182
  key,
166
183
  name: requiredText(100),
167
184
  members: z.array(key).min(1).max(200),
@@ -170,8 +187,11 @@ const roomSchema = z.object({
170
187
  z.object({ kind: z.literal("agent"), agent: key }),
171
188
  z.object({ kind: z.literal("everyone") }),
172
189
  z.object({ kind: z.literal("mentions") }),
190
+ // Read, not written: exports spell an Auto room as its lead (see
191
+ // server/package-export.ts) so every older app can still open them.
192
+ z.object({ kind: z.literal("auto"), agent: key.optional() }),
173
193
  ]),
174
- });
194
+ }));
175
195
  const exampleSchema = z.object({
176
196
  title: requiredText(120),
177
197
  input: requiredText(4_000),
@@ -580,7 +600,8 @@ function checkV1References(pkg) {
580
600
  if (!agents.has(member))
581
601
  throw invalid(`Room ${room.key} references unknown agent: ${member}`);
582
602
  }
583
- if (room.defaultResponder.kind === "agent" && !members.has(room.defaultResponder.agent)) {
603
+ if ((room.defaultResponder.kind === "agent" || room.defaultResponder.kind === "auto") && room.defaultResponder.agent !== undefined &&
604
+ !members.has(room.defaultResponder.agent)) {
584
605
  throw invalid(`Room ${room.key} has an unknown default responder`);
585
606
  }
586
607
  }
@@ -637,7 +658,8 @@ function checkV2References(pkg) {
637
658
  if (!agents.has(member))
638
659
  throw invalid(`Room ${room.key} references unknown agent: ${member}`);
639
660
  }
640
- if (room.defaultResponder.kind === "agent" && !members.has(room.defaultResponder.agent)) {
661
+ if ((room.defaultResponder.kind === "agent" || room.defaultResponder.kind === "auto") && room.defaultResponder.agent !== undefined &&
662
+ !members.has(room.defaultResponder.agent)) {
641
663
  throw invalid(`Room ${room.key} has a default responder who is not a member: ${room.defaultResponder.agent}`);
642
664
  }
643
665
  roomMembers.set(room.key, members);
@@ -821,8 +843,12 @@ export function downgradeToV1(document) {
821
843
  return [{ ...kept, enabledAfterInstall: false }];
822
844
  });
823
845
  const { team, publisher: _publisher, notes: _notes, presets: _presets, connections: _connections, skills: _skills, agents: _agents, routines: _routines, ...rest } = pkg;
846
+ // Older apps know no Auto room: spell one as its lead, as exports do.
847
+ const rooms = rest.rooms?.map((room) => room.defaultResponder.kind !== "auto" ? room
848
+ : { ...room, defaultResponder: { kind: "agent", agent: room.defaultResponder.agent ?? room.members[0] } });
824
849
  const v1 = {
825
850
  ...rest,
851
+ ...(rooms ? { rooms } : {}),
826
852
  agents,
827
853
  ...(team.leader ? { chiefOfStaff: team.leader } : {}),
828
854
  ...(routines.length ? { routines } : {}),
@@ -63,6 +63,20 @@ const memory = z.object({
63
63
  * against. Shareable exports never carry grants at all, and an import
64
64
  * always lands bots grant-less whatever the file says. */
65
65
  const connectorTools = z.record(z.string().regex(CONNECTOR_SLUG_PATTERN), z.object({ tools: z.union([z.literal("*"), z.array(z.string().regex(CONNECTOR_TOOL_NAME_PATTERN)).min(1).max(500)]) }));
66
+ /** A responder kind from a newer release restores as the room's first
67
+ * member as lead (what a room without one does) instead of refusing the
68
+ * whole backup. Malformed values still fail the schema. */
69
+ function knownRoomResponder(value) {
70
+ if (!value || typeof value !== "object" || Array.isArray(value))
71
+ return value;
72
+ const group = value;
73
+ const kind = group.defaultResponder && typeof group.defaultResponder === "object"
74
+ ? group.defaultResponder.kind : undefined;
75
+ if (typeof kind !== "string" || ["member", "everyone", "mentions", "auto"].includes(kind))
76
+ return value;
77
+ const first = Array.isArray(group.memberIds) && typeof group.memberIds[0] === "string" ? group.memberIds[0] : undefined;
78
+ return { ...group, defaultResponder: first ? { kind: "member", botId: first } : { kind: "mentions" } };
79
+ }
66
80
  const backupSchema = z.object({
67
81
  format: z.literal("openmaus.backup"),
68
82
  version: z.literal(1),
@@ -86,7 +100,7 @@ const backupSchema = z.object({
86
100
  connectorTools: connectorTools.optional(),
87
101
  memory: memory.optional(),
88
102
  })).min(1).max(200),
89
- groups: z.array(z.object({
103
+ groups: z.array(z.preprocess(knownRoomResponder, z.object({
90
104
  ...owner,
91
105
  memberIds: z.array(key).max(200),
92
106
  dm: z.boolean(),
@@ -95,8 +109,9 @@ const backupSchema = z.object({
95
109
  z.object({ kind: z.literal("member"), botId: key }),
96
110
  z.object({ kind: z.literal("everyone") }),
97
111
  z.object({ kind: z.literal("mentions") }),
112
+ z.object({ kind: z.literal("auto"), fallbackBotId: key.optional() }),
98
113
  ]),
99
- })).max(2_000),
114
+ }))).max(2_000),
100
115
  routines: z.array(z.object({
101
116
  name, prompt: z.string().trim().min(1).max(200_000),
102
117
  target: z.enum(["bot", "room-goal"]), botId: key, groupId: key.optional(),
@@ -136,7 +151,9 @@ export function parseTeamBackup(input) {
136
151
  if (group.memberIds.some((id) => !bots.has(id)))
137
152
  throw new Error("Invalid backup: unknown room member");
138
153
  unique(group.memberIds, "room member");
139
- if (group.defaultResponder.kind === "member" && !group.memberIds.includes(group.defaultResponder.botId)) {
154
+ const lead = group.defaultResponder.kind === "member" ? group.defaultResponder.botId
155
+ : group.defaultResponder.kind === "auto" ? group.defaultResponder.fallbackBotId : undefined;
156
+ if (lead !== undefined && !group.memberIds.includes(lead)) {
140
157
  throw new Error("Invalid backup: room responder is not a member");
141
158
  }
142
159
  if (group.dm && (group.memberIds.length !== 2 || group.tasks.length !== 1))
@@ -503,7 +503,7 @@ import { promisify } from "node:util";
503
503
 
504
504
  // server/config.ts
505
505
  import { homedir as homedir2 } from "node:os";
506
- import { join as join2 } from "node:path";
506
+ import { join as join3 } from "node:path";
507
507
 
508
508
  // node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
509
509
  var external_exports = {};
@@ -16023,6 +16023,72 @@ var remoteMutationSchema = remoteEntrySchema.extend({
16023
16023
  var CLOUD_SEAT_IDLE_STOP_MS = 5 * 6e4;
16024
16024
  var CLOUD_SEAT_SLEEP_RETRY_MAX_MS = 10 * 6e4;
16025
16025
 
16026
+ // server/proxy-paths.ts
16027
+ import { existsSync as existsSync2 } from "node:fs";
16028
+ import { dirname as dirname2, join as join2 } from "node:path";
16029
+ import { fileURLToPath } from "node:url";
16030
+ var SERVER_ROOT = dirname2(fileURLToPath(import.meta.url));
16031
+ function resolveProxy(relative) {
16032
+ const source = join2(SERVER_ROOT, `${relative}.ts`);
16033
+ return existsSync2(source) ? source : join2(SERVER_ROOT, `${relative}.js`);
16034
+ }
16035
+ var SPAWNED_PROXIES = {
16036
+ browser: resolveProxy("browser-proxy"),
16037
+ localComputer: resolveProxy("local-computer-proxy"),
16038
+ permission: resolveProxy("permission-proxy"),
16039
+ containerMcp: resolveProxy("container-mcp"),
16040
+ vpsContainerMcp: resolveProxy("vps-container-mcp"),
16041
+ agents: resolveProxy("drivers/agents-proxy"),
16042
+ dweb: resolveProxy("drivers/dweb-proxy"),
16043
+ connectors: resolveProxy("connector-proxy"),
16044
+ mcpGate: resolveProxy("mcp-gate"),
16045
+ phone: resolveProxy("drivers/phone-proxy"),
16046
+ hook: resolveProxy("hooks/omb-hook"),
16047
+ // Loaded by the external `pi` process via `-e`, not by this server — but
16048
+ // resolved through the same single source of truth so the packaged layout
16049
+ // check can assert it ships.
16050
+ piMcpExtension: resolveProxy("drivers/pi-mcp-extension")
16051
+ };
16052
+
16053
+ // server/enterprise.ts
16054
+ var layerSchema = external_exports.object({
16055
+ customer: external_exports.string().min(1),
16056
+ features: external_exports.array(external_exports.string().min(1)),
16057
+ expiresAt: external_exports.string().nullable()
16058
+ });
16059
+ var DAY_MS = 24 * 60 * 6e4;
16060
+
16061
+ // server/sessions.ts
16062
+ var PAIRING_CODE_TTL_MS = 5 * 6e4;
16063
+ function daysMs(value, fallbackDays) {
16064
+ const days = Number(value);
16065
+ const ok = Number.isInteger(days) && days >= 1 && days <= 3650;
16066
+ return (ok ? days : fallbackDays) * 24 * 60 * 6e4;
16067
+ }
16068
+ var SESSION_TTL_MS = daysMs(process.env.OMB_SESSION_TTL_DAYS, 30);
16069
+ var SESSION_MAX_AGE_MS = daysMs(process.env.OMB_SESSION_MAX_DAYS, 180);
16070
+ var SESSION_RENEW_WHEN_LEFT_MS = SESSION_TTL_MS / 2;
16071
+ var STREAM_TICKET_TTL_MS = 5 * 6e4;
16072
+ var scopeSchema = external_exports.enum(["admin", "client"]);
16073
+ var sessionSchema = external_exports.object({
16074
+ id: external_exports.string().min(1),
16075
+ tokenHash: external_exports.string().length(64),
16076
+ label: external_exports.string().max(80),
16077
+ scopes: external_exports.array(scopeSchema).min(1),
16078
+ createdAt: external_exports.number(),
16079
+ lastSeenAt: external_exports.number(),
16080
+ expiresAt: external_exports.number(),
16081
+ /** Set when the session came from an account sign-in rather than a code. */
16082
+ userId: external_exports.string().max(256).optional(),
16083
+ email: external_exports.string().max(320).optional(),
16084
+ /** Set only by the internal verified-portal issuance path, never by email or pairing input. */
16085
+ membershipAuthority: external_exports.literal("portal").optional()
16086
+ });
16087
+ var fileSchema = external_exports.object({ version: external_exports.literal(1), sessions: external_exports.array(sessionSchema) });
16088
+
16089
+ // server/cloud-home.ts
16090
+ var CLOUD_PAIRING_NONCE_MS = 10 * 6e4;
16091
+
16026
16092
  // server/config.ts
16027
16093
  var optionalText = external_exports.string().optional();
16028
16094
  var SSH_ALIAS = /^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/;
@@ -16354,6 +16420,19 @@ var appConfigSchema = external_exports.object({
16354
16420
  baseUrl: external_exports.string().trim().max(2048).refine((value) => !value || /^https?:\/\//i.test(value), "the Chatterbox server address must start with http:// or https://").optional(),
16355
16421
  model: optionalText
16356
16422
  }).optional(),
16423
+ /** The decision model (server/decider): a fast classifier that picks
16424
+ * things for bots, starting with who answers a room message. `key` is
16425
+ * write-only like every credential; `baseUrl` points at a Jev-compatible
16426
+ * server instead of TypeSafe's (an operator setting with no UI); `jobs`
16427
+ * switches each decision on or off. Not `decisions`: that section is the
16428
+ * authorization log's retention. */
16429
+ decider: external_exports.object({
16430
+ enabled: external_exports.boolean().optional(),
16431
+ provider: external_exports.enum(["jev", "off"]).optional(),
16432
+ key: optionalText,
16433
+ baseUrl: external_exports.string().trim().max(2048).refine((value) => !value || /^https?:\/\//i.test(value), "the decision model address must start with http:// or https://").optional(),
16434
+ jobs: external_exports.object({ roomRouting: external_exports.boolean().optional() }).optional()
16435
+ }).optional(),
16357
16436
  /** Avatar provider credentials stay separate; choosing a router never reuses a cloud key. */
16358
16437
  imageGen: external_exports.object({
16359
16438
  provider: external_exports.enum(["openai", "xai", "custom"]).optional(),
@@ -16423,37 +16502,10 @@ var storedAppConfigSchema = appConfigSchema.extend({
16423
16502
  var appConfigPatchSchema = appConfigSchema.omit({ instances: true, mcpServers: true, cliStartup: true, customDomain: true }).extend({ threads: threadsPatchSchema.optional(), newBots: newBotsPatchSchema.optional() });
16424
16503
  var jsonObjectSchema = external_exports.record(external_exports.string(), external_exports.json());
16425
16504
  var DEFAULT_CAPTURE_QUIET_MS = 2 * 6e4;
16426
- var DATA_DIR = process.env.OMB_DATA_DIR ?? join2(homedir2(), ".openmausbot");
16427
- var LEGACY_DATA_DIR = join2(homedir2(), ".opengrokbot");
16428
- var EVENTS_DIR = join2(DATA_DIR, "events");
16429
- var NATIVE_DIR = join2(DATA_DIR, "native");
16430
-
16431
- // server/proxy-paths.ts
16432
- import { existsSync as existsSync2 } from "node:fs";
16433
- import { dirname as dirname2, join as join3 } from "node:path";
16434
- import { fileURLToPath } from "node:url";
16435
- var SERVER_ROOT = dirname2(fileURLToPath(import.meta.url));
16436
- function resolveProxy(relative) {
16437
- const source = join3(SERVER_ROOT, `${relative}.ts`);
16438
- return existsSync2(source) ? source : join3(SERVER_ROOT, `${relative}.js`);
16439
- }
16440
- var SPAWNED_PROXIES = {
16441
- browser: resolveProxy("browser-proxy"),
16442
- localComputer: resolveProxy("local-computer-proxy"),
16443
- permission: resolveProxy("permission-proxy"),
16444
- containerMcp: resolveProxy("container-mcp"),
16445
- vpsContainerMcp: resolveProxy("vps-container-mcp"),
16446
- agents: resolveProxy("drivers/agents-proxy"),
16447
- dweb: resolveProxy("drivers/dweb-proxy"),
16448
- connectors: resolveProxy("connector-proxy"),
16449
- mcpGate: resolveProxy("mcp-gate"),
16450
- phone: resolveProxy("drivers/phone-proxy"),
16451
- hook: resolveProxy("hooks/omb-hook"),
16452
- // Loaded by the external `pi` process via `-e`, not by this server — but
16453
- // resolved through the same single source of truth so the packaged layout
16454
- // check can assert it ships.
16455
- piMcpExtension: resolveProxy("drivers/pi-mcp-extension")
16456
- };
16505
+ var DATA_DIR = process.env.OMB_DATA_DIR ?? join3(homedir2(), ".openmausbot");
16506
+ var LEGACY_DATA_DIR = join3(homedir2(), ".opengrokbot");
16507
+ var EVENTS_DIR = join3(DATA_DIR, "events");
16508
+ var NATIVE_DIR = join3(DATA_DIR, "native");
16457
16509
 
16458
16510
  // server/container-computer.ts
16459
16511
  var run = promisify(execFile2);