openmausbot 0.1.89 → 0.1.90

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 (140) hide show
  1. package/dist/assets/{abnfDiagram-O67JEVCF-BwaMaZEm.js → abnfDiagram-O67JEVCF-DUyXd0kL.js} +1 -1
  2. package/dist/assets/{arc-Be-8GKxd.js → arc-q73qM-At.js} +1 -1
  3. package/dist/assets/{architectureDiagram-NJMV4G6O-Dp_U2Kv1.js → architectureDiagram-NJMV4G6O-D33O3Wur.js} +1 -1
  4. package/dist/assets/{blockDiagram-BEXU5L5S-DQjUjYUk.js → blockDiagram-BEXU5L5S-DLsJeoXC.js} +1 -1
  5. package/dist/assets/{c4Diagram-YGBWAQC7-Bd3aE4Kc.js → c4Diagram-YGBWAQC7-DnaljZnu.js} +1 -1
  6. package/dist/assets/channel-BCuyHjFV.js +1 -0
  7. package/dist/assets/{chunk-2Q5K7J3B-BijjTBeJ.js → chunk-2Q5K7J3B-Da0h2i79.js} +1 -1
  8. package/dist/assets/{chunk-3FUC2YCW-CMGdwvzW.js → chunk-3FUC2YCW-CfzHi54f.js} +2 -2
  9. package/dist/assets/{chunk-5VM5RSS4-0G09BfsZ.js → chunk-5VM5RSS4-LjE4d9Jf.js} +1 -1
  10. package/dist/assets/{chunk-GTNCS2PH-FOp3vSHe.js → chunk-GTNCS2PH-DY-UGmvc.js} +1 -1
  11. package/dist/assets/{chunk-GWA4HPMP-Dv-20VIn.js → chunk-GWA4HPMP-BqeGhyhP.js} +1 -1
  12. package/dist/assets/{chunk-JWPE2WC7-CRgrdFcz.js → chunk-JWPE2WC7-BYa9EBAg.js} +1 -1
  13. package/dist/assets/{chunk-NETBCI7D-3hP4f7QB.js → chunk-NETBCI7D-vTrDtWyb.js} +1 -1
  14. package/dist/assets/{chunk-WEXAMYUT-CG98IjlG.js → chunk-WEXAMYUT-ic5tXadM.js} +1 -1
  15. package/dist/assets/{chunk-XXDRQBXY-BrSTC9xG.js → chunk-XXDRQBXY-DvRCY-c6.js} +1 -1
  16. package/dist/assets/{classDiagram-v2-NBCMYWYE-rpQQQDFX.js → classDiagram-v2-NBCMYWYE-DVbmgz_L.js} +1 -1
  17. package/dist/assets/{cose-bilkent-JH36ORCC-G6RSafHA.js → cose-bilkent-JH36ORCC-vdUs2n57.js} +1 -1
  18. package/dist/assets/{cynefin-OW5HDTMX-CXjv-IM2.js → cynefin-OW5HDTMX-BvLGlV3U.js} +1 -1
  19. package/dist/assets/{cynefinDiagram-VND7K2PF-Dw07MR9f.js → cynefinDiagram-VND7K2PF-C4GcJQDt.js} +1 -1
  20. package/dist/assets/{dagre-6A5THRUB-BwPnlTg2.js → dagre-6A5THRUB-BMSaHpt_.js} +1 -1
  21. package/dist/assets/{diagram-22UHCM2B-Cm6xcMiz.js → diagram-22UHCM2B-D5b9UBhC.js} +1 -1
  22. package/dist/assets/{diagram-3UASUU5V-CDpogPbE.js → diagram-3UASUU5V-CIKLlB7Y.js} +1 -1
  23. package/dist/assets/{diagram-ATOU4E4O-BV8z3Pnc.js → diagram-ATOU4E4O-BzXb-7k7.js} +1 -1
  24. package/dist/assets/{diagram-CDSNMT55-CKCQhOOC.js → diagram-CDSNMT55-HsTvJ7uT.js} +1 -1
  25. package/dist/assets/{diagram-MLGK6HIB-FGvJg69C.js → diagram-MLGK6HIB-Db9ySNbQ.js} +1 -1
  26. package/dist/assets/{diagram-MPIPVDR6-CwATvgnS.js → diagram-MPIPVDR6-D6BKbpqY.js} +1 -1
  27. package/dist/assets/{ebnfDiagram-ZINNZB2B-CH_c3SSC.js → ebnfDiagram-ZINNZB2B-Bq067L0t.js} +1 -1
  28. package/dist/assets/{elk-276RUBZZ-3guZi71R.js → elk-276RUBZZ-DhaD7fNJ.js} +1 -1
  29. package/dist/assets/{erDiagram-OPXOYQCR-BMHphR2n.js → erDiagram-OPXOYQCR-fwIhfz36.js} +1 -1
  30. package/dist/assets/{flowDiagram-KWPJA3E3-Vm5DkW5x.js → flowDiagram-KWPJA3E3-DygIyu0V.js} +1 -1
  31. package/dist/assets/{ganttDiagram-FUAMR5RP-DO-1ct07.js → ganttDiagram-FUAMR5RP-DPYfSO96.js} +1 -1
  32. package/dist/assets/{gitGraphDiagram-X574FWY7-nxeksEIN.js → gitGraphDiagram-X574FWY7-DhkHeCle.js} +1 -1
  33. package/dist/assets/index-CRvo-DS2.css +1 -0
  34. package/dist/assets/index-CWYGlA63.js +588 -0
  35. package/dist/assets/{index-Oxk9x45i.js → index-G1PHKGyh.js} +1 -1
  36. package/dist/assets/{infoDiagram-VRGFBTTK-Dkcvr0p7.js → infoDiagram-VRGFBTTK-DstBLxME.js} +1 -1
  37. package/dist/assets/{ishikawaDiagram-OU5B5YK6-BwVibkoO.js → ishikawaDiagram-OU5B5YK6-CrIsGgch.js} +1 -1
  38. package/dist/assets/{journeyDiagram-ZHPQQLJL-ByiBZqgG.js → journeyDiagram-ZHPQQLJL-CYypGDbB.js} +1 -1
  39. package/dist/assets/{kanban-definition-PNTS6WVX-qqjgXBF3.js → kanban-definition-PNTS6WVX-DGjPt8E_.js} +1 -1
  40. package/dist/assets/{linear-CtJnKe6A.js → linear-Dwc3Hrru.js} +1 -1
  41. package/dist/assets/{mermaid.core-BEiDklrY.js → mermaid.core-Dc4OePBL.js} +5 -5
  42. package/dist/assets/{mindmap-definition-NLK3R4M7-DfIYHia9.js → mindmap-definition-NLK3R4M7-DV-vepj-.js} +1 -1
  43. package/dist/assets/{pegDiagram-GJSIUBJH-CYyRqa1a.js → pegDiagram-GJSIUBJH-lx5Kj-n5.js} +1 -1
  44. package/dist/assets/{pieDiagram-5QR66LMP-hOToy_zH.js → pieDiagram-5QR66LMP-Bq7Vmm15.js} +1 -1
  45. package/dist/assets/{quadrantDiagram-O4NWA36T-DKAsNN9I.js → quadrantDiagram-O4NWA36T-DeqeqOBj.js} +1 -1
  46. package/dist/assets/{railroadDiagram-XR7U4H2S-BSA-d8JU.js → railroadDiagram-XR7U4H2S-CXwRQzSC.js} +1 -1
  47. package/dist/assets/{requirementDiagram-PLB6GJNP-CSmlvb-p.js → requirementDiagram-PLB6GJNP-8tAeoB88.js} +1 -1
  48. package/dist/assets/{sankeyDiagram-IPEJSGJF-CD5F6fnG.js → sankeyDiagram-IPEJSGJF-BG5zjGWU.js} +1 -1
  49. package/dist/assets/{sequenceDiagram-PO4LG4MO-JUq5sx6d.js → sequenceDiagram-PO4LG4MO-DKF3iLcw.js} +1 -1
  50. package/dist/assets/{sizeCapture-INFHLROL-BjIS-Stk.js → sizeCapture-INFHLROL-B-y1qUgT.js} +1 -1
  51. package/dist/assets/{stateDiagram-v2-GCMORJYK-DklkUxOg.js → stateDiagram-v2-GCMORJYK-MkzPgxAc.js} +1 -1
  52. package/dist/assets/{swimlanes-2SLR337P-CsjIU6be.js → swimlanes-2SLR337P-DnbaOEwH.js} +1 -1
  53. package/dist/assets/swimlanesDiagram-TC7HE7FX-BZQ07DGE.js +8 -0
  54. package/dist/assets/{timeline-definition-EJHVYXUP-BkFqK7AB.js → timeline-definition-EJHVYXUP-Bb4xG_Bt.js} +1 -1
  55. package/dist/assets/{usecaseDiagram-POWQR4AR-70gJB1EU.js → usecaseDiagram-POWQR4AR-JgkR9pFf.js} +1 -1
  56. package/dist/assets/{vennDiagram-UO4OBE2U-83Tm62dN.js → vennDiagram-UO4OBE2U-CIPIrAYe.js} +1 -1
  57. package/dist/assets/{wardleyDiagram-VNRHLVJA-tTmseoX9.js → wardleyDiagram-VNRHLVJA-BjCLuUvJ.js} +1 -1
  58. package/dist/assets/{xychartDiagram-PMCCYNJV-R1LBA55a.js → xychartDiagram-PMCCYNJV-DKgiaYxT.js} +1 -1
  59. package/dist/index.html +2 -2
  60. package/dist-server/cloud-home-start.js +14795 -0
  61. package/dist-server/container-mcp.js +52 -4
  62. package/dist-server/drivers/agents-proxy.js +20 -10
  63. package/dist-server/index.js +8985 -6461
  64. package/dist-server/openmausbot.js +327 -145
  65. package/dist-server/pair-cli.js +327 -145
  66. package/dist-server/permission-proxy.js +1 -1
  67. package/dist-server/server/admission.js +46 -0
  68. package/dist-server/server/{box-create-idempotency.js → boat-create-idempotency.js} +32 -31
  69. package/dist-server/server/{box-delete-journal.js → boat-delete-journal.js} +26 -25
  70. package/dist-server/server/{box.js → boat.js} +330 -328
  71. package/dist-server/server/bot-profile.js +7 -1
  72. package/dist-server/server/channel-queue.js +58 -18
  73. package/dist-server/server/checkpoints.js +98 -52
  74. package/dist-server/server/claim-idle.js +33 -0
  75. package/dist-server/server/cloud-backend.js +5 -5
  76. package/dist-server/server/cloud-home-start.js +103 -0
  77. package/dist-server/server/cloud-home.js +218 -0
  78. package/dist-server/server/cloud-overflow.js +167 -0
  79. package/dist-server/server/composio.js +5 -0
  80. package/dist-server/server/computer-observation.js +2 -2
  81. package/dist-server/server/config.js +115 -6
  82. package/dist-server/server/container-computer.js +13 -0
  83. package/dist-server/server/control-client.js +2 -2
  84. package/dist-server/server/delegations.js +11 -3
  85. package/dist-server/server/drivers/acp/core.js +5 -4
  86. package/dist-server/server/drivers/agents-call.js +5 -1
  87. package/dist-server/server/drivers/agents-catalog.js +9 -3
  88. package/dist-server/server/drivers/{boxagent.js → boatagent.js} +13 -12
  89. package/dist-server/server/drivers/builtIn.js +2 -2
  90. package/dist-server/server/drivers/{chat-box-tools.js → chat-boat-tools.js} +3 -3
  91. package/dist-server/server/drivers/chat-mcp-tools.js +2 -2
  92. package/dist-server/server/drivers/claude.js +177 -13
  93. package/dist-server/server/drivers/native.js +1 -1
  94. package/dist-server/server/drivers/openai-chat.js +15 -4
  95. package/dist-server/server/drivers/pi.js +3 -3
  96. package/dist-server/server/hosted-models.js +6 -1
  97. package/dist-server/server/index.js +1029 -479
  98. package/dist-server/server/local-vm-inventory.js +11 -1
  99. package/dist-server/server/local-vm-seat-pool.js +141 -0
  100. package/dist-server/server/managed-policy.js +4 -1
  101. package/dist-server/server/memory-capture.js +194 -0
  102. package/dist-server/server/memory-entries.js +76 -0
  103. package/dist-server/server/memory-organize.js +102 -0
  104. package/dist-server/server/memory-tidy.js +144 -0
  105. package/dist-server/server/memory-topics.js +169 -0
  106. package/dist-server/server/memory-upkeep.js +452 -0
  107. package/dist-server/server/message-db.js +49 -5
  108. package/dist-server/server/permission-proxy.js +3 -3
  109. package/dist-server/server/profile-learned.js +98 -0
  110. package/dist-server/server/profile-versions.js +45 -11
  111. package/dist-server/server/recall.js +172 -0
  112. package/dist-server/server/redact.js +1 -1
  113. package/dist-server/server/room-cwd.js +1 -1
  114. package/dist-server/server/room-handoffs.js +61 -6
  115. package/dist-server/server/routes/bot-memory.js +215 -0
  116. package/dist-server/server/routine-requests.js +27 -16
  117. package/dist-server/server/routines.js +10 -6
  118. package/dist-server/server/steer-queue.js +41 -10
  119. package/dist-server/server/store.js +25 -1
  120. package/dist-server/server/surface.js +1 -1
  121. package/dist-server/server/system-prompt.js +5 -5
  122. package/dist-server/server/team-computers.js +2 -2
  123. package/dist-server/server/tightening-requests.js +34 -4
  124. package/dist-server/server/tool-summary.js +2 -2
  125. package/dist-server/server/tts/elevenlabs.js +1 -1
  126. package/dist-server/server/turn-resources.js +87 -11
  127. package/dist-server/server/vps-computer.js +1 -1
  128. package/dist-server/server/workspace.js +57 -22
  129. package/dist-server/shared/ask-question.js +7 -5
  130. package/dist-server/shared/bot-avatar.js +19 -0
  131. package/dist-server/shared/credential-request.js +3 -3
  132. package/dist-server/shared/tool-surface.js +1 -1
  133. package/dist-server/src/lib/composer-attachments.js +9 -0
  134. package/dist-server/vps-container-mcp.js +52 -4
  135. package/dist-server/workspace-backup.worker.js +52 -4
  136. package/package.json +1 -1
  137. package/dist/assets/channel-DqY_gMLg.js +0 -1
  138. package/dist/assets/index-BIMdIbaw.css +0 -1
  139. package/dist/assets/index-BVPdbq2r.js +0 -583
  140. package/dist/assets/swimlanesDiagram-TC7HE7FX-Bk14M0_B.js +0 -8
@@ -5,7 +5,7 @@ import { randomUUID } from "node:crypto";
5
5
  // shared/ask-question.ts
6
6
  var MAX_QUESTIONS = 6;
7
7
  var MAX_OPTIONS = 12;
8
- var MAX_QUESTION_TEXT = 400;
8
+ var MAX_QUESTION_TEXT = 8e3;
9
9
  var MAX_LABEL = 120;
10
10
  var MAX_DESCRIPTION = 400;
11
11
  var MAX_CUSTOM_ANSWER = 2e3;
@@ -7,6 +7,52 @@
7
7
  // gather state, call admit(), and apply the returned decision with their own
8
8
  // side effects. Anything that can settle a turn (an awaited steer, a slow
9
9
  // disk) belongs to the caller, which re-reads state and asks again.
10
+ // ── L2: ordering and batching on drain ─────────────────────────────────
11
+ // M2's drain rule: a sender's CONTIGUOUS burst inside a short window is one
12
+ // item; senders never merge, and a pause past the window reads as separate
13
+ // messages. The window is measured between consecutive queued items, so a
14
+ // rolling burst stays whole while hours-apart texts split.
15
+ /** How late one queued text may follow the previous one and still join the
16
+ * same drained turn. Two minutes covers a person typing a burst in pieces;
17
+ * anything slower is already a separate thought, not a continuation. */
18
+ export const DRAIN_COALESCE_WINDOW_MS = 120_000;
19
+ /** The most items one drained head group may carry. Room turns read the
20
+ * transcript through the room-context window (its last N messages), so a
21
+ * group larger than that window would append lines the responder never
22
+ * sees; the excess stays queued and drains with the next turn instead.
23
+ * The 1:1 drain carries every group item in its prompt directly, so it
24
+ * passes no cap. */
25
+ export const DRAIN_COALESCE_MAX_ITEMS = 30;
26
+ /** The leading run of queued items that drain together: consecutive items
27
+ * with the same merge identity, each arriving within the coalescing window
28
+ * of the one before it. Pure and shape-agnostic — each queue supplies its
29
+ * own identity (sender + provenance kind) and timestamp accessors, and the
30
+ * first item always drains, so an empty identity never strands a queue.
31
+ * `maxItems`, when given, stops the group before it outgrows a bound the
32
+ * consuming turn can actually carry. */
33
+ export function drainCoalesceHead(items, identityOf, queuedAtOf, maxItems) {
34
+ const head = [];
35
+ let previousIdentity;
36
+ let previousAt;
37
+ for (const item of items) {
38
+ if (maxItems !== undefined && head.length >= maxItems)
39
+ break;
40
+ const identity = identityOf(item);
41
+ const at = queuedAtOf(item);
42
+ // The gap must be non-negative AND inside the window: a regressed
43
+ // timestamp (clock skew, a rewritten row) is not "within the window".
44
+ const gap = at - (previousAt ?? at);
45
+ if (head.length === 0 || (identity === previousIdentity && gap >= 0 && gap <= DRAIN_COALESCE_WINDOW_MS)) {
46
+ head.push(item);
47
+ }
48
+ else {
49
+ break;
50
+ }
51
+ previousIdentity = identity;
52
+ previousAt = at;
53
+ }
54
+ return head;
55
+ }
10
56
  /** The shared admission decision. Pure: same surface, message, and state
11
57
  * always return the same decision, with no side effects. */
12
58
  export function admit(surface, message = {}, state = {}) {
@@ -3,6 +3,7 @@ import { linkSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "no
3
3
  import { join } from "node:path";
4
4
  import { writeFileAtomic } from "./atomic.js";
5
5
  import { DATA_DIR } from "./config.js";
6
+ // Persisted journal filenames keep their historical box-* names.
6
7
  const FILE = join(DATA_DIR, "box-create-requests.json");
7
8
  const LOCK_FILE = join(DATA_DIR, "box-create-requests.lock");
8
9
  const KEY_RETENTION_MS = 24 * 60 * 60 * 1_000;
@@ -12,7 +13,7 @@ const LOCK_RETRY_MS = 20;
12
13
  const MAX_REAPER_GENERATIONS = 128;
13
14
  const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
14
15
  const BOT_ID = /^[A-Za-z0-9_-]{1,120}$/;
15
- const BOX_ID = /^bx_[23456789abcdefghjkmnpqrstuvwxyz]{8}$/;
16
+ const BOAT_ID = /^bx_[23456789abcdefghjkmnpqrstuvwxyz]{8}$/;
16
17
  const lockWait = new Int32Array(new SharedArrayBuffer(4));
17
18
  function isRequest(value) {
18
19
  if (!value || typeof value !== "object" || Array.isArray(value))
@@ -28,12 +29,12 @@ function isRequest(value) {
28
29
  && typeof request.createdAt === "number"
29
30
  && Number.isFinite(request.createdAt)
30
31
  && request.createdAt > 0
31
- && (request.boxId === undefined || (typeof request.boxId === "string" && BOX_ID.test(request.boxId)))
32
+ && (request.boxId === undefined || (typeof request.boxId === "string" && BOAT_ID.test(request.boxId)))
32
33
  && (request.resolved === undefined || (request.resolved === true && typeof request.boxId === "string")));
33
34
  }
34
35
  function recoveryStateError(detail, cause) {
35
36
  return Object.assign(new Error(`Cloud computer creation is paused because its recovery state is ${detail}. `
36
- + "Check boat.dev for an unnamed Box before repairing OpenMausBot's local state."), { status: 503, cause });
37
+ + "Check boat.dev for an unnamed Boat before repairing OpenMausBot's local state."), { status: 503, cause });
37
38
  }
38
39
  function loadFresh() {
39
40
  let raw;
@@ -57,17 +58,17 @@ function loadFresh() {
57
58
  }
58
59
  const identities = new Set();
59
60
  const keys = new Set();
60
- const botsWithKnownBoxes = new Set();
61
+ const botsWithKnownBoats = new Set();
61
62
  for (const request of file.requests) {
62
63
  const identity = `${request.botId}\0${request.requestBody}`;
63
64
  if (identities.has(identity) || keys.has(request.idempotencyKey))
64
65
  throw recoveryStateError("invalid");
65
- if (request.boxId && botsWithKnownBoxes.has(request.botId))
66
+ if (request.boxId && botsWithKnownBoats.has(request.botId))
66
67
  throw recoveryStateError("invalid");
67
68
  identities.add(identity);
68
69
  keys.add(request.idempotencyKey);
69
70
  if (request.boxId)
70
- botsWithKnownBoxes.add(request.botId);
71
+ botsWithKnownBoats.add(request.botId);
71
72
  }
72
73
  return file.requests.map((request) => ({ ...request }));
73
74
  }
@@ -316,9 +317,9 @@ function withJournalLock(operation) {
316
317
  }
317
318
  }
318
319
  /** Record the exact provider request before it leaves the process. A known
319
- * Box wins over a changed body: once a provider resource exists, recovering
320
+ * Boat wins over a changed body: once a provider resource exists, recovering
320
321
  * and naming it is safer than issuing any second create request. */
321
- export function beginBoxCreate(botId, requestBody) {
322
+ export function beginBoatCreate(botId, requestBody) {
322
323
  if (!BOT_ID.test(botId))
323
324
  throw new Error("invalid bot id for cloud computer creation");
324
325
  if (!requestBody || requestBody.length > 1_024)
@@ -335,7 +336,7 @@ export function beginBoxCreate(botId, requestBody) {
335
336
  }
336
337
  if (now - pending.createdAt >= KEY_RETENTION_MS) {
337
338
  // Once the provider forgets the idempotency key, retrying it (or using
338
- // a new key) may create a second billable Box. Absence cannot be
339
+ // a new key) may create a second billable Boat. Absence cannot be
339
340
  // inferred from a lost response, so stop for manual reconciliation.
340
341
  throw recoveryStateError("older than boat.dev's 24-hour retry window");
341
342
  }
@@ -354,8 +355,8 @@ export function beginBoxCreate(botId, requestBody) {
354
355
  });
355
356
  }
356
357
  /** Persist the returned identity before the caller attempts to rename it. */
357
- export function rememberCreatedBox(request, boxId) {
358
- if (!BOX_ID.test(boxId))
358
+ export function rememberCreatedBoat(request, boxId) {
359
+ if (!BOAT_ID.test(boxId))
359
360
  throw new Error("boat.dev returned an invalid cloud computer id");
360
361
  return withJournalLock((requests) => {
361
362
  const current = requests.find((candidate) => (candidate.botId === request.botId
@@ -365,16 +366,16 @@ export function rememberCreatedBox(request, boxId) {
365
366
  throw recoveryStateError("out of date");
366
367
  }
367
368
  const completed = { ...current, boxId };
368
- // There can be an older rejected-TTL request for this bot. Once a Box is
369
+ // There can be an older rejected-TTL request for this bot. Once a Boat is
369
370
  // known, it is the only recovery authority we need to retain.
370
371
  save([...requests.filter((candidate) => candidate.botId !== request.botId), completed]);
371
372
  return { ...completed };
372
373
  });
373
374
  }
374
375
  /** Mark the recovery record safe only after the deterministic provider rename
375
- * succeeds. Keeping the resolved Box ID still lets a later retry recover from
376
+ * succeeds. Keeping the resolved Boat ID still lets a later retry recover from
376
377
  * an eventually-consistent account listing without blocking bot deletion. */
377
- export function resolveBoxCreate(request) {
378
+ export function resolveBoatCreate(request) {
378
379
  return withJournalLock((requests) => {
379
380
  const current = requests.find((candidate) => (candidate.botId === request.botId
380
381
  && candidate.requestBody === request.requestBody
@@ -388,8 +389,8 @@ export function resolveBoxCreate(request) {
388
389
  });
389
390
  }
390
391
  /** Read-only deletion guard. Both a key-only request with an ambiguous
391
- * provider outcome and a known-but-not-yet-named Box must keep its bot owner. */
392
- export function hasUnresolvedBoxCreate(botId) {
392
+ * provider outcome and a known-but-not-yet-named Boat must keep its bot owner. */
393
+ export function hasUnresolvedBoatCreate(botId) {
393
394
  if (!BOT_ID.test(botId))
394
395
  throw new Error("invalid bot id for cloud computer creation");
395
396
  return withJournalLock((requests) => (requests.some((request) => request.botId === botId && request.resolved !== true)));
@@ -398,30 +399,30 @@ export function hasUnresolvedBoxCreate(botId) {
398
399
  * lock-protected read can prove a remembered provider id even while the
399
400
  * account-wide LIST endpoint is eventually consistent. Provider request
400
401
  * bodies and idempotency keys never cross this boundary. */
401
- export function boxCreateRecoverySnapshot() {
402
+ export function boatCreateRecoverySnapshot() {
402
403
  return withJournalLock((requests) => requests.map((request) => ({
403
404
  botId: request.botId,
404
405
  ...(request.boxId ? { boxId: request.boxId } : {}),
405
406
  resolved: request.resolved === true,
406
407
  })));
407
408
  }
408
- /** Persist ownership discovered from a pre-journal deterministic Box name.
409
+ /** Persist ownership discovered from a pre-journal deterministic Boat name.
409
410
  * The provider listing proves which live bot owns the exact immutable id, but
410
411
  * it must never steal an identity remembered for another bot. It supersedes
411
412
  * stale attempts for the same bot, is idempotent, and fails closed on a true
412
413
  * cross-bot conflict. */
413
- export function adoptResolvedBox(botId, boxId) {
414
+ export function adoptResolvedBoat(botId, boxId) {
414
415
  if (!BOT_ID.test(botId))
415
416
  throw new Error("invalid bot id for cloud computer ownership");
416
- if (!BOX_ID.test(boxId))
417
+ if (!BOAT_ID.test(boxId))
417
418
  throw new Error("invalid cloud computer id for ownership");
418
419
  withJournalLock((requests) => {
419
- const sameBox = requests.find((request) => request.boxId === boxId);
420
- if (sameBox && sameBox.botId !== botId) {
420
+ const sameBoat = requests.find((request) => request.boxId === boxId);
421
+ if (sameBoat && sameBoat.botId !== botId) {
421
422
  throw recoveryStateError("conflicted with another bot's remembered cloud computer");
422
423
  }
423
- const adopted = sameBox
424
- ? { ...sameBox, resolved: true }
424
+ const adopted = sameBoat
425
+ ? { ...sameBoat, resolved: true }
425
426
  : {
426
427
  botId,
427
428
  requestBody: JSON.stringify({ adopted: "legacy-name", boxId }),
@@ -434,19 +435,19 @@ export function adoptResolvedBox(botId, boxId) {
434
435
  if (retained.length >= MAX_REQUESTS)
435
436
  throw recoveryStateError("full");
436
437
  if (requests.filter((request) => request.botId === botId).length === 1
437
- && sameBox?.resolved === true
438
- && sameBox.botId === botId)
438
+ && sameBoat?.resolved === true
439
+ && sameBoat.botId === botId)
439
440
  return;
440
441
  // A successfully listed deterministic legacy name resolves any older
441
442
  // key-only/remembered attempt for this bot. Keeping those stale rows would
442
- // leave deletion permanently blocked after the adopted Box is removed.
443
+ // leave deletion permanently blocked after the adopted Boat is removed.
443
444
  save([...retained, adopted]);
444
445
  });
445
446
  }
446
- /** Retire only the durable identity for a Box the provider has confirmed was
447
+ /** Retire only the durable identity for a Boat the provider has confirmed was
447
448
  * deleted. Callers must never use this for a failed or ambiguous deletion. */
448
- export function retireDeletedBoxCreate(boxId) {
449
- if (!BOX_ID.test(boxId))
449
+ export function retireDeletedBoatCreate(boxId) {
450
+ if (!BOAT_ID.test(boxId))
450
451
  throw new Error("invalid deleted cloud computer id");
451
452
  withJournalLock((requests) => {
452
453
  const next = requests.filter((request) => request.boxId !== boxId);
@@ -454,7 +455,7 @@ export function retireDeletedBoxCreate(boxId) {
454
455
  save(next);
455
456
  });
456
457
  }
457
- export function discardBoxCreate(request) {
458
+ export function discardBoatCreate(request) {
458
459
  withJournalLock((requests) => {
459
460
  const next = requests.filter((candidate) => candidate.idempotencyKey !== request.idempotencyKey);
460
461
  if (next.length !== requests.length)
@@ -3,6 +3,7 @@ import { linkSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "no
3
3
  import { join } from "node:path";
4
4
  import { writeFileAtomic } from "./atomic.js";
5
5
  import { DATA_DIR } from "./config.js";
6
+ // Persisted journal filenames keep their historical box-* names.
6
7
  const FILE = join(DATA_DIR, "box-delete-requests.json");
7
8
  const LOCK_FILE = join(DATA_DIR, "box-delete-requests.lock");
8
9
  const MAX_RECORDS = 4_096;
@@ -11,7 +12,7 @@ const LOCK_RETRY_MS = 20;
11
12
  const MAX_REAPER_GENERATIONS = 128;
12
13
  const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
13
14
  const BOT_ID = /^[A-Za-z0-9_-]{1,120}$/;
14
- const BOX_ID = /^bx_[23456789abcdefghjkmnpqrstuvwxyz]{8}$/;
15
+ const BOAT_ID = /^bx_[23456789abcdefghjkmnpqrstuvwxyz]{8}$/;
15
16
  const OPERATION_ID = /^bdop_[a-f0-9]{32}$/;
16
17
  const PROVIDER_STATUSES = new Set(["pending", "processing", "blocked", "completed"]);
17
18
  const lockWait = new Int32Array(new SharedArrayBuffer(4));
@@ -29,7 +30,7 @@ function isRecord(value) {
29
30
  return false;
30
31
  const record = value;
31
32
  if (typeof record.boxId !== "string"
32
- || !BOX_ID.test(record.boxId)
33
+ || !BOAT_ID.test(record.boxId)
33
34
  || !validName(record.name)
34
35
  || !(record.ownerBotId === null || (typeof record.ownerBotId === "string" && BOT_ID.test(record.ownerBotId)))
35
36
  || !["prepared", "accepted", "blocked"].includes(record.phase)
@@ -49,7 +50,7 @@ function isRecord(value) {
49
50
  }
50
51
  function stateError(detail, cause) {
51
52
  return Object.assign(new Error(`Cloud computer deletion is paused because its recovery state is ${detail}. `
52
- + "Check the Box provider before repairing OpenMausBot's local state."), { status: 503, cause });
53
+ + "Check the Boat provider before repairing OpenMausBot's local state."), { status: 503, cause });
53
54
  }
54
55
  function clone(record) {
55
56
  return { ...record };
@@ -76,12 +77,12 @@ function loadFresh() {
76
77
  || journal.records.length > MAX_RECORDS
77
78
  || !journal.records.every(isRecord))
78
79
  throw stateError("invalid");
79
- const boxIds = new Set();
80
+ const boatIds = new Set();
80
81
  const operationIds = new Set();
81
82
  for (const record of journal.records) {
82
- if (boxIds.has(record.boxId))
83
+ if (boatIds.has(record.boxId))
83
84
  throw stateError("invalid");
84
- boxIds.add(record.boxId);
85
+ boatIds.add(record.boxId);
85
86
  if (record.operationId) {
86
87
  if (operationIds.has(record.operationId))
87
88
  throw stateError("invalid");
@@ -319,7 +320,7 @@ function withJournalLock(operation) {
319
320
  }
320
321
  }
321
322
  function validateIdentity(identity) {
322
- if (!BOX_ID.test(identity.boxId))
323
+ if (!BOAT_ID.test(identity.boxId))
323
324
  throw new Error("invalid cloud computer id for deletion");
324
325
  if (!validName(identity.name))
325
326
  throw new Error("invalid cloud computer name for deletion");
@@ -344,7 +345,7 @@ function parseOperation(boxId, value, expectedStatus) {
344
345
  }
345
346
  /** Write the immutable target before sending DELETE to the provider. Existing
346
347
  * in-flight work wins; a blocked operation may be explicitly retried. */
347
- export function prepareBoxDeletion(identity) {
348
+ export function prepareBoatDeletion(identity) {
348
349
  validateIdentity(identity);
349
350
  return withJournalLock((records) => {
350
351
  const existing = records.find((record) => record.boxId === identity.boxId);
@@ -368,14 +369,14 @@ export function prepareBoxDeletion(identity) {
368
369
  return clone(prepared);
369
370
  });
370
371
  }
371
- /** Bind a provider receipt to the exact immutable Box target. A caller cannot
372
- * accidentally attach another Box's operation to this deletion fence. */
373
- export function markBoxDeletionAccepted(boxId, value) {
374
- if (!BOX_ID.test(boxId))
372
+ /** Bind a provider receipt to the exact immutable Boat target. A caller cannot
373
+ * accidentally attach another Boat's operation to this deletion fence. */
374
+ export function markBoatDeletionAccepted(boxId, value) {
375
+ if (!BOAT_ID.test(boxId))
375
376
  throw new Error("invalid cloud computer id for deletion");
376
377
  const operation = parseOperation(boxId, value);
377
378
  if (operation.status === "blocked") {
378
- throw new Error("blocked cloud computer deletion must use markBoxDeletionBlocked");
379
+ throw new Error("blocked cloud computer deletion must use markBoatDeletionBlocked");
379
380
  }
380
381
  const acceptedStatus = operation.status;
381
382
  return withJournalLock((records) => {
@@ -412,8 +413,8 @@ export function markBoxDeletionAccepted(boxId, value) {
412
413
  /** Record an explicit provider block without discarding the target. The
413
414
  * target remains fenced from normal use; only a deliberate deletion retry
414
415
  * may prepare the same immutable target again. */
415
- export function markBoxDeletionBlocked(boxId, value) {
416
- if (!BOX_ID.test(boxId))
416
+ export function markBoatDeletionBlocked(boxId, value) {
417
+ if (!BOAT_ID.test(boxId))
417
418
  throw new Error("invalid cloud computer id for deletion");
418
419
  const operation = value === undefined ? null : parseOperation(boxId, value, "blocked");
419
420
  return withJournalLock((records) => {
@@ -438,30 +439,30 @@ export function markBoxDeletionBlocked(boxId, value) {
438
439
  });
439
440
  }
440
441
  /** A fresh, lock-protected copy suitable for server reconciliation. */
441
- export function boxDeletionSnapshot() {
442
+ export function boatDeletionSnapshot() {
442
443
  return withJournalLock((records) => records.map(clone));
443
444
  }
444
- export const listBoxDeletions = boxDeletionSnapshot;
445
- export function getBoxDeletion(boxId) {
446
- if (!BOX_ID.test(boxId))
445
+ export const listBoatDeletions = boatDeletionSnapshot;
446
+ export function getBoatDeletion(boxId) {
447
+ if (!BOAT_ID.test(boxId))
447
448
  throw new Error("invalid cloud computer id for deletion");
448
449
  return withJournalLock((records) => {
449
450
  const record = records.find((candidate) => candidate.boxId === boxId);
450
451
  return record ? clone(record) : null;
451
452
  });
452
453
  }
453
- export function isBoxDeletionPending(boxId) {
454
- return getBoxDeletion(boxId) !== null;
454
+ export function isBoatDeletionPending(boxId) {
455
+ return getBoatDeletion(boxId) !== null;
455
456
  }
456
- export function hasPendingBoxDeletionForBot(botId) {
457
+ export function hasPendingBoatDeletionForBot(botId) {
457
458
  if (!BOT_ID.test(botId))
458
459
  throw new Error("invalid bot id for cloud computer deletion");
459
460
  return withJournalLock((records) => records.some((record) => record.ownerBotId === botId));
460
461
  }
461
462
  /** Remove a record only after direct absence or a completed target-bound
462
- * provider operation proves the immutable Box identity is gone. */
463
- export function retireBoxDeletion(boxId) {
464
- if (!BOX_ID.test(boxId))
463
+ * provider operation proves the immutable Boat identity is gone. */
464
+ export function retireBoatDeletion(boxId) {
465
+ if (!BOAT_ID.test(boxId))
465
466
  throw new Error("invalid deleted cloud computer id");
466
467
  withJournalLock((records) => {
467
468
  const next = records.filter((record) => record.boxId !== boxId);