openmausbot 0.1.91 → 0.1.93

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 (164) hide show
  1. package/dist/assets/DesktopViewer-CKNR3SEW.js +5 -0
  2. package/dist/assets/{abnfDiagram-O67JEVCF-yP-w8pRq.js → abnfDiagram-O67JEVCF-Ckrm1I1X.js} +1 -1
  3. package/dist/assets/{arc-DsivfZEh.js → arc-BBdBuCgB.js} +1 -1
  4. package/dist/assets/{architectureDiagram-NJMV4G6O-CILw7lzT.js → architectureDiagram-NJMV4G6O-CDdA3Hjs.js} +1 -1
  5. package/dist/assets/{blockDiagram-BEXU5L5S-DEQUV3HF.js → blockDiagram-BEXU5L5S-AUrsx1YR.js} +1 -1
  6. package/dist/assets/{c4Diagram-YGBWAQC7-lSXCZoKA.js → c4Diagram-YGBWAQC7-BEqQ0Unh.js} +1 -1
  7. package/dist/assets/channel-BU94VpxE.js +1 -0
  8. package/dist/assets/{chunk-2Q5K7J3B-QCD5bgWP.js → chunk-2Q5K7J3B-CEEuvd4C.js} +1 -1
  9. package/dist/assets/{chunk-3FUC2YCW-C5-iZJuL.js → chunk-3FUC2YCW-DyANCiHl.js} +2 -2
  10. package/dist/assets/{chunk-5VM5RSS4-cxsawMpY.js → chunk-5VM5RSS4-Dx_Slpw9.js} +1 -1
  11. package/dist/assets/{chunk-GTNCS2PH-DV8tV8H7.js → chunk-GTNCS2PH-Dl7h7iiD.js} +1 -1
  12. package/dist/assets/{chunk-GWA4HPMP-CJFZyfbW.js → chunk-GWA4HPMP-BLx07SPb.js} +1 -1
  13. package/dist/assets/{chunk-JWPE2WC7-BigbMXco.js → chunk-JWPE2WC7-BMxDj3n7.js} +1 -1
  14. package/dist/assets/{chunk-NETBCI7D-oVxTfpDy.js → chunk-NETBCI7D-CN66PT4H.js} +1 -1
  15. package/dist/assets/{chunk-WEXAMYUT-CH4BiroW.js → chunk-WEXAMYUT-6HwhsDKw.js} +1 -1
  16. package/dist/assets/{chunk-XXDRQBXY-KEDa6gax.js → chunk-XXDRQBXY-gWnLt7SN.js} +1 -1
  17. package/dist/assets/{classDiagram-v2-NBCMYWYE-BiPjfqVv.js → classDiagram-v2-NBCMYWYE-CMS5l2ea.js} +1 -1
  18. package/dist/assets/{cose-bilkent-JH36ORCC-Caxew3OY.js → cose-bilkent-JH36ORCC-CV7sQwIG.js} +1 -1
  19. package/dist/assets/{cynefin-OW5HDTMX-B_Pui369.js → cynefin-OW5HDTMX-CSyl-7vl.js} +1 -1
  20. package/dist/assets/{cynefinDiagram-VND7K2PF-X64ydIhP.js → cynefinDiagram-VND7K2PF-BCLKL6az.js} +1 -1
  21. package/dist/assets/{dagre-6A5THRUB-TqYCI1E1.js → dagre-6A5THRUB-DiaZs6S1.js} +1 -1
  22. package/dist/assets/{diagram-22UHCM2B-TeOt3rAQ.js → diagram-22UHCM2B-BeTtmt3I.js} +1 -1
  23. package/dist/assets/{diagram-3UASUU5V-D70BLhDA.js → diagram-3UASUU5V-CsdBG-QQ.js} +1 -1
  24. package/dist/assets/{diagram-ATOU4E4O-BbQDLwUC.js → diagram-ATOU4E4O-BczvdbR1.js} +1 -1
  25. package/dist/assets/{diagram-CDSNMT55-ChNJZ1u-.js → diagram-CDSNMT55-DHTTTMhi.js} +1 -1
  26. package/dist/assets/{diagram-MLGK6HIB-DMBl2APa.js → diagram-MLGK6HIB-RTjHyd8z.js} +1 -1
  27. package/dist/assets/{diagram-MPIPVDR6-CEqDchOp.js → diagram-MPIPVDR6-BDenHEH_.js} +1 -1
  28. package/dist/assets/{ebnfDiagram-ZINNZB2B-BVDKRp_-.js → ebnfDiagram-ZINNZB2B-BSiRSbtL.js} +1 -1
  29. package/dist/assets/{elk-276RUBZZ-CRkofO9B.js → elk-276RUBZZ-BCAElss-.js} +1 -1
  30. package/dist/assets/{erDiagram-OPXOYQCR-DruOMh5e.js → erDiagram-OPXOYQCR-C4_1ZPJe.js} +1 -1
  31. package/dist/assets/{flowDiagram-KWPJA3E3-8GMQeB7t.js → flowDiagram-KWPJA3E3-Bcb4Z75S.js} +1 -1
  32. package/dist/assets/{ganttDiagram-FUAMR5RP-ORE9jcsM.js → ganttDiagram-FUAMR5RP-BiPOzxzb.js} +1 -1
  33. package/dist/assets/{gitGraphDiagram-X574FWY7-BmsedsWh.js → gitGraphDiagram-X574FWY7-B1wtF3ZS.js} +1 -1
  34. package/dist/assets/index-Biji-bfL.css +1 -0
  35. package/dist/assets/{index-DKNEM8Q6.js → index-BkYwilkG.js} +1 -1
  36. package/dist/assets/index-DZlhoES0.js +597 -0
  37. package/dist/assets/{infoDiagram-VRGFBTTK-E_p7AooJ.js → infoDiagram-VRGFBTTK-A19No71U.js} +1 -1
  38. package/dist/assets/{ishikawaDiagram-OU5B5YK6-Y-B2mKda.js → ishikawaDiagram-OU5B5YK6-BQsqJ_cV.js} +1 -1
  39. package/dist/assets/{journeyDiagram-ZHPQQLJL-eA8RELcK.js → journeyDiagram-ZHPQQLJL-DAO0pVan.js} +1 -1
  40. package/dist/assets/{kanban-definition-PNTS6WVX-DqIL2e8e.js → kanban-definition-PNTS6WVX-NepmKEe2.js} +1 -1
  41. package/dist/assets/{linear-yC_29R6e.js → linear-bq1PTDTW.js} +1 -1
  42. package/dist/assets/{mermaid.core-VhEMkSND.js → mermaid.core-s4LTbQle.js} +5 -5
  43. package/dist/assets/{mindmap-definition-NLK3R4M7-oJ7kyQZy.js → mindmap-definition-NLK3R4M7-LtdbgHFH.js} +1 -1
  44. package/dist/assets/{pegDiagram-GJSIUBJH-BmfYcY0O.js → pegDiagram-GJSIUBJH-tdbaCxiK.js} +1 -1
  45. package/dist/assets/{pieDiagram-5QR66LMP-DKvgP7w9.js → pieDiagram-5QR66LMP-DvkbpW8l.js} +1 -1
  46. package/dist/assets/{quadrantDiagram-O4NWA36T-BEKL3W0L.js → quadrantDiagram-O4NWA36T-Do4KaH-E.js} +1 -1
  47. package/dist/assets/{railroadDiagram-XR7U4H2S-BEsiqR5s.js → railroadDiagram-XR7U4H2S-BQWULOAs.js} +1 -1
  48. package/dist/assets/{requirementDiagram-PLB6GJNP-DWBcWvAP.js → requirementDiagram-PLB6GJNP-w9kei1FU.js} +1 -1
  49. package/dist/assets/{sankeyDiagram-IPEJSGJF-rpZ2_O4s.js → sankeyDiagram-IPEJSGJF-D2gRy0e8.js} +1 -1
  50. package/dist/assets/{sequenceDiagram-PO4LG4MO-B99HQdJR.js → sequenceDiagram-PO4LG4MO-a2sO5_Eo.js} +1 -1
  51. package/dist/assets/{sizeCapture-INFHLROL-BjN0qT5T.js → sizeCapture-INFHLROL-CiPJmFEB.js} +1 -1
  52. package/dist/assets/{stateDiagram-v2-GCMORJYK-BbsVgGsC.js → stateDiagram-v2-GCMORJYK-DAkUUlL7.js} +1 -1
  53. package/dist/assets/{swimlanes-2SLR337P-DG3gKkeR.js → swimlanes-2SLR337P-DANf2R8Y.js} +1 -1
  54. package/dist/assets/swimlanesDiagram-TC7HE7FX-CdHxG8aM.js +8 -0
  55. package/dist/assets/{timeline-definition-EJHVYXUP-BTHOdtF_.js → timeline-definition-EJHVYXUP-BKpQmzrT.js} +1 -1
  56. package/dist/assets/{usecaseDiagram-POWQR4AR-acLSLFwc.js → usecaseDiagram-POWQR4AR-BtI7qLN-.js} +1 -1
  57. package/dist/assets/{vennDiagram-UO4OBE2U-C5JzNX9G.js → vennDiagram-UO4OBE2U-gXJXrAG_.js} +1 -1
  58. package/dist/assets/{wardleyDiagram-VNRHLVJA-DL1-PrR_.js → wardleyDiagram-VNRHLVJA-GU8Po5-n.js} +1 -1
  59. package/dist/assets/{xychartDiagram-PMCCYNJV-Bvr1477p.js → xychartDiagram-PMCCYNJV-BF4J30g6.js} +1 -1
  60. package/dist/index.html +2 -2
  61. package/dist/novnc-NOTICE.txt +118 -0
  62. package/dist-server/cloud-home-start.js +112 -14
  63. package/dist-server/companion/src/routes.js +24 -1
  64. package/dist-server/container-mcp.js +201 -16
  65. package/dist-server/drivers/agents-proxy.js +7 -4
  66. package/dist-server/enterprise/server/index.js +5 -1
  67. package/dist-server/index.js +10698 -4737
  68. package/dist-server/local-computer-proxy.js +133 -8
  69. package/dist-server/mcp-gate.js +16 -6
  70. package/dist-server/model-catalog/models-dev.snapshot.json +7299 -0
  71. package/dist-server/openmausbot.js +4656 -1258
  72. package/dist-server/pair-cli.js +4656 -1258
  73. package/dist-server/server/boat.js +5 -5
  74. package/dist-server/server/browser-engine.js +10 -1
  75. package/dist-server/server/browser-live.js +46 -2
  76. package/dist-server/server/browser-runtime.js +74 -8
  77. package/dist-server/server/browser-tool-shape.js +6 -4
  78. package/dist-server/server/chief-of-staff.js +1 -1
  79. package/dist-server/server/cloud-home-start.js +118 -26
  80. package/dist-server/server/cloud-home.js +39 -3
  81. package/dist-server/server/cloud-lending.js +74 -10
  82. package/dist-server/server/cloud-owner.js +196 -0
  83. package/dist-server/server/cloud-secrets-boot.js +6 -0
  84. package/dist-server/server/cloud-secrets.js +65 -0
  85. package/dist-server/server/computer-control.js +6 -0
  86. package/dist-server/server/config.js +187 -14
  87. package/dist-server/server/container-computer.js +46 -34
  88. package/dist-server/server/desktop-viewer-targets.js +26 -0
  89. package/dist-server/server/digest.js +12 -2
  90. package/dist-server/server/drivers/acp/core.js +339 -33
  91. package/dist-server/server/drivers/acp/cursor.js +13 -1
  92. package/dist-server/server/drivers/acp/opencode-go.js +614 -85
  93. package/dist-server/server/drivers/acp/quiet-status.js +257 -0
  94. package/dist-server/server/drivers/acp/qwen.js +18 -0
  95. package/dist-server/server/drivers/agents-catalog.js +6 -6
  96. package/dist-server/server/drivers/antigravity-acp.js +69 -11
  97. package/dist-server/server/drivers/antigravity-temp.js +360 -0
  98. package/dist-server/server/drivers/antigravity.js +21 -0
  99. package/dist-server/server/drivers/builtIn.js +2 -0
  100. package/dist-server/server/drivers/cerebras.js +108 -0
  101. package/dist-server/server/drivers/chat-mcp-tools.js +20 -3
  102. package/dist-server/server/drivers/chatgpt-plan-auth.js +481 -0
  103. package/dist-server/server/drivers/claude.js +329 -37
  104. package/dist-server/server/drivers/codex.js +228 -18
  105. package/dist-server/server/drivers/grok.js +2 -2
  106. package/dist-server/server/drivers/mistral.js +4 -3
  107. package/dist-server/server/drivers/openai-chat-protocol.js +4 -3
  108. package/dist-server/server/drivers/openai-chat.js +66 -4
  109. package/dist-server/server/drivers/openai-compat.js +73 -23
  110. package/dist-server/server/drivers/prompt-split.js +2 -0
  111. package/dist-server/server/engine-install.js +46 -12
  112. package/dist-server/server/env-path.js +21 -5
  113. package/dist-server/server/harness/registry.js +2 -2
  114. package/dist-server/server/index.js +1545 -238
  115. package/dist-server/server/lending-memory.js +479 -0
  116. package/dist-server/server/local-vm-idle.js +24 -6
  117. package/dist-server/server/local-vm-stop-reason.js +26 -0
  118. package/dist-server/server/managed-desktop.js +3 -0
  119. package/dist-server/server/mcp-bridge.js +5 -3
  120. package/dist-server/server/mcp-http.js +6 -3
  121. package/dist-server/server/mcp-oauth-discovery.js +133 -0
  122. package/dist-server/server/mcp-oauth-store.js +70 -0
  123. package/dist-server/server/mcp-oauth.js +550 -0
  124. package/dist-server/server/mcp-probe.js +14 -1
  125. package/dist-server/server/mcp-registry.js +43 -0
  126. package/dist-server/server/memory-upkeep.js +94 -84
  127. package/dist-server/server/model-catalog/catalog.js +374 -0
  128. package/dist-server/server/model-catalog/presets.js +176 -0
  129. package/dist-server/server/model-catalog/trim.js +219 -0
  130. package/dist-server/server/provider-key-check.js +5 -2
  131. package/dist-server/server/recent-work.js +1 -1
  132. package/dist-server/server/request-auth.js +23 -0
  133. package/dist-server/server/routes/antigravity-leftovers.js +45 -0
  134. package/dist-server/server/routes/bot-memory.js +31 -6
  135. package/dist-server/server/routes/desktop-viewer.js +238 -0
  136. package/dist-server/server/routine-requests.js +1 -1
  137. package/dist-server/server/routines.js +6 -3
  138. package/dist-server/server/sessions.js +56 -9
  139. package/dist-server/server/skills.js +25 -10
  140. package/dist-server/server/store.js +116 -122
  141. package/dist-server/server/thread-events.js +2 -0
  142. package/dist-server/server/thread-starters.js +33 -0
  143. package/dist-server/server/thread-title.js +46 -0
  144. package/dist-server/server/tts/fish.js +4 -5
  145. package/dist-server/server/tts/index.js +5 -3
  146. package/dist-server/server/vps-computer.js +80 -13
  147. package/dist-server/server/webhooks.js +18 -1
  148. package/dist-server/server/workspace-backup-policy.js +3 -0
  149. package/dist-server/server/workspace-backup.js +25 -0
  150. package/dist-server/server/workspace.js +66 -16
  151. package/dist-server/shared/approval-mode.js +1 -1
  152. package/dist-server/shared/redact.js +1 -0
  153. package/dist-server/shared/tool-name.js +22 -0
  154. package/dist-server/shared/workspace-backup-client.js +1 -0
  155. package/dist-server/src/lib/citations.js +177 -0
  156. package/dist-server/src/lib/composer-attachments.js +7 -1
  157. package/dist-server/vps-container-mcp.js +202 -16
  158. package/dist-server/workspace-backup.worker.js +87 -9
  159. package/enterprise/server/index.js +5 -1
  160. package/package.json +1 -1
  161. package/dist/assets/channel-DrfGMu7X.js +0 -1
  162. package/dist/assets/index-BlnCEcU6.js +0 -588
  163. package/dist/assets/index-DVxjOvID.css +0 -1
  164. package/dist/assets/swimlanesDiagram-TC7HE7FX-BCZhxl7e.js +0 -8
@@ -0,0 +1,177 @@
1
+ // Selection matching is adapted from T3 Code's assistantTextSelection.ts
2
+ // (Copyright 2026 T3 Tools Inc., MIT License; see third_party/t3-code/LICENSE).
3
+ export const CITATION_MAX_QUOTE_LENGTH = 12_000;
4
+ export const CITATION_MAX_COMMENT_LENGTH = 4_000;
5
+ const CITATION_CONTEXT_LENGTH = 64;
6
+ const CITATION_MARKER = "<!--omb-citation-v1:";
7
+ const CITATION_MARKER_RE = /<!--omb-citation-v1:([A-Za-z0-9_-]{1,100000})-->/g;
8
+ function boundedString(value, max, allowEmpty = false) {
9
+ return typeof value === "string" && value.length <= max && (allowEmpty || value.length > 0);
10
+ }
11
+ export function isCitationAttachment(value) {
12
+ if (!value || typeof value !== "object")
13
+ return false;
14
+ const citation = value;
15
+ const source = citation.source;
16
+ return citation.kind === "citation" &&
17
+ citation.version === 1 &&
18
+ boundedString(citation.id, 200) &&
19
+ boundedString(citation.quote, CITATION_MAX_QUOTE_LENGTH) &&
20
+ (citation.comment === undefined || boundedString(citation.comment, CITATION_MAX_COMMENT_LENGTH, true)) &&
21
+ typeof citation.size === "number" && Number.isFinite(citation.size) && citation.size >= 0 &&
22
+ Boolean(source) &&
23
+ (source.ownerType === "bot" || source.ownerType === "group") &&
24
+ boundedString(source.ownerId, 200) &&
25
+ boundedString(source.threadId, 200) &&
26
+ boundedString(source.messageId, 200) &&
27
+ Number.isSafeInteger(source.start) && Number(source.start) >= 0 &&
28
+ Number.isSafeInteger(source.end) && Number(source.end) >= Number(source.start) &&
29
+ boundedString(source.prefix, CITATION_CONTEXT_LENGTH, true) &&
30
+ boundedString(source.suffix, CITATION_CONTEXT_LENGTH, true);
31
+ }
32
+ function newId() {
33
+ return globalThis.crypto?.randomUUID?.() ?? `c${Math.random().toString(36).slice(2)}`;
34
+ }
35
+ function byteLength(text) {
36
+ return new TextEncoder().encode(text).length;
37
+ }
38
+ export function citationAttachment(source, selector, comment = "") {
39
+ const trimmedComment = comment.trim();
40
+ if (!selector.text.trim() || selector.text.length > CITATION_MAX_QUOTE_LENGTH) {
41
+ throw new Error("Citation quote is empty or too long");
42
+ }
43
+ if (trimmedComment.length > CITATION_MAX_COMMENT_LENGTH)
44
+ throw new Error("Citation comment is too long");
45
+ return {
46
+ kind: "citation",
47
+ version: 1,
48
+ id: newId(),
49
+ quote: selector.text,
50
+ ...(trimmedComment ? { comment: trimmedComment } : {}),
51
+ source: { ...source, start: selector.start, end: selector.end, prefix: selector.prefix, suffix: selector.suffix },
52
+ size: byteLength(selector.text) + byteLength(trimmedComment),
53
+ };
54
+ }
55
+ export function withCitationComment(citation, comment) {
56
+ const trimmed = comment.trim();
57
+ if (trimmed.length > CITATION_MAX_COMMENT_LENGTH)
58
+ throw new Error("Citation comment is too long");
59
+ const { comment: _old, ...rest } = citation;
60
+ return { ...rest, ...(trimmed ? { comment: trimmed } : {}), size: byteLength(citation.quote) + byteLength(trimmed) };
61
+ }
62
+ function base64UrlEncode(value) {
63
+ const bytes = new TextEncoder().encode(JSON.stringify(value));
64
+ let binary = "";
65
+ for (const byte of bytes)
66
+ binary += String.fromCharCode(byte);
67
+ return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
68
+ }
69
+ function base64UrlDecode(value) {
70
+ const padded = value.replaceAll("-", "+").replaceAll("_", "/").padEnd(Math.ceil(value.length / 4) * 4, "=");
71
+ const binary = atob(padded);
72
+ return JSON.parse(new TextDecoder().decode(Uint8Array.from(binary, (character) => character.charCodeAt(0))));
73
+ }
74
+ /** Readable in clients without citation UI and unambiguous to model providers. */
75
+ export function citationFallback(citation) {
76
+ const quote = citation.quote.split("\n").map((line) => `> ${line}`).join("\n");
77
+ return `> Quoted message:\n${quote}${citation.comment ? `\n\nComment:\n${citation.comment}` : ""}`;
78
+ }
79
+ export function serializeCitation(citation) {
80
+ return `${CITATION_MARKER}${base64UrlEncode(citation)}-->\n${citationFallback(citation)}`;
81
+ }
82
+ export function splitTranscriptCitations(text) {
83
+ const citations = [];
84
+ let display = "";
85
+ let cursor = 0;
86
+ for (const match of text.matchAll(CITATION_MARKER_RE)) {
87
+ if (match.index < cursor)
88
+ continue;
89
+ let citation;
90
+ try {
91
+ const decoded = base64UrlDecode(match[1]);
92
+ if (!isCitationAttachment(decoded))
93
+ continue;
94
+ citation = decoded;
95
+ }
96
+ catch {
97
+ continue;
98
+ }
99
+ const block = `${match[0]}\n${citationFallback(citation)}`;
100
+ if (!text.startsWith(block, match.index))
101
+ continue;
102
+ display += text.slice(cursor, match.index);
103
+ cursor = match.index + block.length;
104
+ citations.push(citation);
105
+ }
106
+ return { display: `${display}${text.slice(cursor)}`.trim(), citations };
107
+ }
108
+ export function citationPreviewText(text) {
109
+ const cited = splitTranscriptCitations(text);
110
+ if (!cited.citations.length)
111
+ return text;
112
+ return [
113
+ cited.display,
114
+ ...cited.citations.map((citation) => citation.comment
115
+ ? `${citation.quote} — ${citation.comment}`
116
+ : citation.quote),
117
+ ].filter(Boolean).join(" ");
118
+ }
119
+ function normalizeWhitespace(text) {
120
+ return text.replace(/\s+/g, " ");
121
+ }
122
+ function splitsSurrogatePair(text, offset) {
123
+ const before = text.charCodeAt(offset - 1);
124
+ const after = text.charCodeAt(offset);
125
+ return before >= 0xd800 && before <= 0xdbff && after >= 0xdc00 && after <= 0xdfff;
126
+ }
127
+ export function createCitationTextSelector(text, rawStart, rawEnd) {
128
+ const quote = text.slice(rawStart, rawEnd);
129
+ if (!quote.trim())
130
+ return null;
131
+ const normalized = normalizeWhitespace(text);
132
+ let start = normalizeWhitespace(text.slice(0, rawStart)).length;
133
+ if (rawStart > 0 && /\s/.test(text[rawStart - 1]) && /\s/.test(text[rawStart]))
134
+ start -= 1;
135
+ const end = normalizeWhitespace(text.slice(0, rawEnd)).length;
136
+ let prefixStart = Math.max(0, start - CITATION_CONTEXT_LENGTH);
137
+ let suffixEnd = Math.min(normalized.length, end + CITATION_CONTEXT_LENGTH);
138
+ if (splitsSurrogatePair(normalized, prefixStart))
139
+ prefixStart += 1;
140
+ if (splitsSurrogatePair(normalized, suffixEnd))
141
+ suffixEnd -= 1;
142
+ return {
143
+ text: quote,
144
+ start,
145
+ end,
146
+ prefix: normalized.slice(prefixStart, start),
147
+ suffix: normalized.slice(end, suffixEnd),
148
+ };
149
+ }
150
+ export function findCitationText(text, selector) {
151
+ const normalized = normalizeWhitespace(text);
152
+ const quote = normalizeWhitespace(selector.text);
153
+ if (!quote.trim())
154
+ return null;
155
+ const prefix = normalizeWhitespace(selector.prefix);
156
+ const suffix = normalizeWhitespace(selector.suffix);
157
+ const matchesContext = (start, end) => normalized.slice(Math.max(0, start - prefix.length), start) === prefix &&
158
+ normalized.slice(end, end + suffix.length) === suffix;
159
+ let match = Number.isSafeInteger(selector.start) && Number.isSafeInteger(selector.end) &&
160
+ selector.start >= 0 && selector.end - selector.start === quote.length &&
161
+ normalized.slice(selector.start, selector.end) === quote && matchesContext(selector.start, selector.end)
162
+ ? { start: selector.start, end: selector.end }
163
+ : null;
164
+ let onlyQuote = null;
165
+ let quoteCount = 0;
166
+ for (let start = normalized.indexOf(quote); start !== -1; start = normalized.indexOf(quote, start + 1)) {
167
+ const end = start + quote.length;
168
+ quoteCount += 1;
169
+ onlyQuote = { start, end };
170
+ if (!matchesContext(start, end))
171
+ continue;
172
+ if (match && match.start !== start)
173
+ return null;
174
+ match = { start, end };
175
+ }
176
+ return match ?? (quoteCount === 1 ? onlyQuote : null);
177
+ }
@@ -1,7 +1,10 @@
1
+ import { isCitationAttachment, serializeCitation } from "./citations.js";
1
2
  export function isAttachment(value) {
2
3
  if (!value || typeof value !== "object")
3
4
  return false;
4
5
  const attachment = value;
6
+ if (attachment.kind === "citation")
7
+ return isCitationAttachment(value);
5
8
  if (typeof attachment.id !== "string" || !validSize(attachment.size))
6
9
  return false;
7
10
  if (attachment.kind === "paste") {
@@ -362,7 +365,10 @@ export function formatSize(bytes) {
362
365
  export function composeMessage(text, attachments) {
363
366
  const parts = [text.trim()];
364
367
  attachments.forEach((a, i) => {
365
- if (a.kind === "paste") {
368
+ if (a.kind === "citation") {
369
+ parts.push(serializeCitation(a));
370
+ }
371
+ else if (a.kind === "paste") {
366
372
  parts.push(`<pasted-text index="${i + 1}">\n${a.text}\n</pasted-text>`);
367
373
  }
368
374
  else if (a.kind === "image") {
@@ -90,6 +90,9 @@ function nvmBinDirs() {
90
90
  return [];
91
91
  }
92
92
  }
93
+ function sealedFixture() {
94
+ return process.env.OMB_TEST_SEALED_PATH === "1";
95
+ }
93
96
  function knownDirs() {
94
97
  const home = homedir();
95
98
  return [
@@ -105,10 +108,13 @@ function knownDirs() {
105
108
  // opencode installer
106
109
  join(home, ".claude", "local"),
107
110
  // claude "local install"
108
- "/opt/homebrew/bin",
109
- // brew, Apple silicon
110
- "/usr/local/bin",
111
- // brew Intel / classic installs
111
+ // Machine-wide rather than under the (possibly temporary) home.
112
+ ...sealedFixture() ? [] : [
113
+ "/opt/homebrew/bin",
114
+ // brew, Apple silicon
115
+ "/usr/local/bin"
116
+ // brew Intel / classic installs
117
+ ],
112
118
  join(home, ".volta", "bin"),
113
119
  join(home, ".bun", "bin"),
114
120
  join(home, ".asdf", "shims"),
@@ -130,6 +136,8 @@ function windowsKnownDirs() {
130
136
  // x.ai installer
131
137
  join(localAppData, "agy", "bin"),
132
138
  // Antigravity installer
139
+ // Cursor installer: cursor-agent.* and its `agent` copies (MOCA-272)
140
+ join(localAppData, "cursor-agent"),
133
141
  join(home, ".local", "bin"),
134
142
  // claude native installer
135
143
  join(home, ".claude", "local"),
@@ -137,7 +145,9 @@ function windowsKnownDirs() {
137
145
  // Factory droid installer (%USERPROFILE%\bin)
138
146
  join(home, ".bun", "bin"),
139
147
  join(home, ".deno", "bin"),
140
- join(home, "go", "bin")
148
+ join(home, "go", "bin"),
149
+ join(process.env.ProgramFiles ?? "C:\\Program Files", "Docker", "Docker", "resources", "bin")
150
+ // Docker Desktop installer
141
151
  ];
142
152
  }
143
153
  var cached = null;
@@ -160,7 +170,7 @@ function augmentedPath() {
160
170
  ...(process.platform === "win32" ? windowsKnownDirs() : knownDirs()).filter((d) => existsSync(d))
161
171
  ]);
162
172
  }
163
- if (!probed && !process.env.VITEST && process.platform !== "win32") {
173
+ if (!probed && !process.env.VITEST && !sealedFixture() && process.platform !== "win32") {
164
174
  probed = true;
165
175
  probeLoginShellPath();
166
176
  }
@@ -184,6 +194,119 @@ function probeLoginShellPath() {
184
194
  }
185
195
  );
186
196
  }
197
+ function splitCliString(cli) {
198
+ const out = [];
199
+ let cur = "";
200
+ let quote = null;
201
+ for (const ch of cli.trim()) {
202
+ if (quote) {
203
+ if (ch === quote) quote = null;
204
+ else cur += ch;
205
+ } else if (ch === '"' || ch === "'") {
206
+ quote = ch;
207
+ } else if (/\s/.test(ch)) {
208
+ if (cur) out.push(cur);
209
+ cur = "";
210
+ } else {
211
+ cur += ch;
212
+ }
213
+ }
214
+ if (cur) out.push(cur);
215
+ return out;
216
+ }
217
+ function isFile(p2) {
218
+ try {
219
+ return statSync(p2, { throwIfNoEntry: false })?.isFile() ?? false;
220
+ } catch {
221
+ return false;
222
+ }
223
+ }
224
+ function whichWin(cli, env) {
225
+ const exts = ((env ?? process.env).PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean);
226
+ const probe = (base) => {
227
+ const order = extname(base) ? [base, ...exts.map((e) => base + e)] : [...exts.map((e) => base + e), base];
228
+ return order.find(isFile) ?? null;
229
+ };
230
+ if (/[\\/]/.test(cli) || /^[a-zA-Z]:/.test(cli)) return probe(cli);
231
+ const path = env ? env.PATH ?? env.Path ?? "" : augmentedPath();
232
+ for (const dir of path.split(delimiter)) {
233
+ if (!dir) continue;
234
+ const hit = probe(join(dir, cli));
235
+ if (hit) return hit;
236
+ }
237
+ return null;
238
+ }
239
+ function nodeExe(near, env) {
240
+ const local = join(near, "node.exe");
241
+ if (isFile(local)) return local;
242
+ const onPath = whichWin("node.exe", env);
243
+ if (onPath && extname(onPath).toLowerCase() === ".exe") return onPath;
244
+ return process.versions.electron ? null : process.execPath;
245
+ }
246
+ function parseCmdShim(shim, env) {
247
+ let text;
248
+ try {
249
+ text = readFileSync(shim, "utf8");
250
+ } catch {
251
+ return null;
252
+ }
253
+ const dir = dirname(shim);
254
+ const npmEntry = /^SET "NP[MX]_CLI_JS=%~dp0\\([^"]+)"/im.exec(text);
255
+ const targets = [...npmEntry ? [npmEntry] : [], ...text.matchAll(/"%~?dp0%?\\?([^"]+)"/g)].map((m2) => join(dir, m2[1])).filter((p2) => isFile(p2) && basename(p2).toLowerCase() !== "node.exe");
256
+ const script = targets.find((p2) => /\.[cm]?js$/i.test(p2));
257
+ if (script) {
258
+ const node = nodeExe(dir, env);
259
+ if (node) return { command: node, args: [script] };
260
+ }
261
+ const exe = targets.find((p2) => extname(p2).toLowerCase() === ".exe");
262
+ return exe ? { command: exe, args: [] } : null;
263
+ }
264
+ function parseNodeShebang(file2, env) {
265
+ let head = "";
266
+ let fd = null;
267
+ try {
268
+ fd = openSync(file2, "r");
269
+ const buf = Buffer.alloc(128);
270
+ const n = readSync(fd, buf, 0, buf.length, 0);
271
+ head = buf.subarray(0, n).toString("utf8").split("\n", 1)[0];
272
+ } catch {
273
+ return null;
274
+ } finally {
275
+ if (fd !== null) {
276
+ try {
277
+ closeSync(fd);
278
+ } catch {
279
+ }
280
+ }
281
+ }
282
+ if (!/^#!.*\bnode(\.exe)?\b/.test(head)) return null;
283
+ const node = nodeExe(dirname(file2), env);
284
+ return node ? { command: node, args: [file2] } : null;
285
+ }
286
+ function resolveWord(cli, args2, env) {
287
+ if (process.platform !== "win32") return { command: cli, args: args2 };
288
+ const file2 = whichWin(cli, env);
289
+ if (!file2) return { command: cli, args: args2 };
290
+ const ext = extname(file2).toLowerCase();
291
+ if (ext === ".cmd" || ext === ".bat") {
292
+ const direct = parseCmdShim(file2, env);
293
+ return direct ? { command: direct.command, args: [...direct.args, ...args2] } : { command: file2, args: args2 };
294
+ }
295
+ if (ext === ".exe" || ext === ".com") return { command: file2, args: args2 };
296
+ const viaNode = parseNodeShebang(file2, env);
297
+ return viaNode ? { command: viaNode.command, args: [...viaNode.args, ...args2] } : { command: file2, args: args2 };
298
+ }
299
+ function resolveCliSpawn(cli, args2, env) {
300
+ const trimmed = cli.trim();
301
+ if (trimmed.includes(" ") && existsSync(trimmed)) return resolveWord(trimmed, args2, env);
302
+ const split = splitCliString(cli);
303
+ if (split.length > 1 || split[0] !== trimmed) {
304
+ const [head, ...fixed] = split;
305
+ if (!head) return { command: cli, args: args2 };
306
+ return resolveWord(head, [...fixed, ...args2], env);
307
+ }
308
+ return resolveWord(cli, args2, env);
309
+ }
187
310
 
188
311
  // server/mcp-tool-schema.ts
189
312
  var ROOT_COMBINATORS = ["anyOf", "oneOf", "allOf", "not"];
@@ -407,9 +530,11 @@ function createMcpBridgeInterceptor(options) {
407
530
  };
408
531
  }
409
532
  function runMcpBridge(options) {
410
- const child = spawn(options.command, options.args, {
533
+ const env = options.env ?? { ...process.env, PATH: augmentedPath() };
534
+ const { command, args: args2 } = resolveCliSpawn(options.command, options.args, env);
535
+ const child = spawn(command, args2, {
411
536
  shell: false,
412
- env: options.env ?? { ...process.env, PATH: augmentedPath() },
537
+ env,
413
538
  stdio: ["pipe", "pipe", "pipe"]
414
539
  });
415
540
  child.stdin.on("error", () => {
@@ -16008,15 +16133,27 @@ var stdioEntrySchema = external_exports.object({
16008
16133
  var stdioMutationSchema = stdioEntrySchema.extend({
16009
16134
  env: external_exports.record(external_exports.string(), external_exports.union([external_exports.string().max(16384), external_exports.literal(true)])).optional()
16010
16135
  });
16136
+ var CLIENT_ID = /^[\x20-\x7e]+$/;
16137
+ var SCOPE = /^[\x21\x23-\x5b\x5d-\x7e]+$/;
16138
+ var CLIENT_SECRET_MAX = 4096;
16139
+ var oauthClientSchema = external_exports.object({
16140
+ clientId: external_exports.string().trim().min(1, "Enter the client ID of the app registered with this server's sign-in provider.").max(512).regex(CLIENT_ID, "A client ID is printable text on one line."),
16141
+ clientSecret: external_exports.string().min(1).max(CLIENT_SECRET_MAX).regex(CLIENT_ID, "A client secret is printable text on one line.").optional(),
16142
+ scopes: external_exports.array(external_exports.string().max(256).regex(SCOPE, "Scopes are single words, like offline_access or api://my-app/mcp.read.")).max(32).optional()
16143
+ }).strict();
16011
16144
  var remoteEntrySchema = external_exports.object({
16012
16145
  /** Streamable HTTP unless the entry says the older SSE transport. */
16013
16146
  type: external_exports.enum(["http", "sse"]).optional(),
16014
16147
  url: external_exports.string().trim().min(1).max(2048),
16015
16148
  headers: external_exports.record(external_exports.string(), external_exports.string().max(16384)).optional(),
16149
+ oauth: oauthClientSchema.optional(),
16016
16150
  enabled: external_exports.boolean().optional()
16017
16151
  }).strict();
16018
16152
  var remoteMutationSchema = remoteEntrySchema.extend({
16019
- headers: external_exports.record(external_exports.string(), external_exports.union([external_exports.string().max(16384), external_exports.literal(true)])).optional()
16153
+ headers: external_exports.record(external_exports.string(), external_exports.union([external_exports.string().max(16384), external_exports.literal(true)])).optional(),
16154
+ oauth: oauthClientSchema.extend({
16155
+ clientSecret: external_exports.union([external_exports.string().min(1).max(CLIENT_SECRET_MAX).regex(CLIENT_ID, "A client secret is printable text on one line."), external_exports.literal(true)]).optional()
16156
+ }).optional()
16020
16157
  });
16021
16158
 
16022
16159
  // server/cloud-overflow.ts
@@ -16082,7 +16219,11 @@ var sessionSchema = external_exports.object({
16082
16219
  userId: external_exports.string().max(256).optional(),
16083
16220
  email: external_exports.string().max(320).optional(),
16084
16221
  /** Set only by the internal verified-portal issuance path, never by email or pairing input. */
16085
- membershipAuthority: external_exports.literal("portal").optional()
16222
+ membershipAuthority: external_exports.literal("portal").optional(),
16223
+ /** A browser sign-in (PairingCode `browser`): accepted only as its browser's cookie, never as a bearer token. */
16224
+ cookieOnly: external_exports.literal(true).optional(),
16225
+ /** Whose OMB Cloud a browser sign-in signed in to (its PairingCode `owner`). */
16226
+ owner: external_exports.string().max(254).optional()
16086
16227
  });
16087
16228
  var fileSchema = external_exports.object({ version: external_exports.literal(1), sessions: external_exports.array(sessionSchema) });
16088
16229
 
@@ -16094,6 +16235,7 @@ var optionalText = external_exports.string().optional();
16094
16235
  var SSH_ALIAS = /^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/;
16095
16236
  var LEGACY_BROWSER_PROFILE_ID = /^[A-Za-z0-9_-]{1,40}$/;
16096
16237
  var BROWSER_PROFILE_ID = /^[a-z0-9_-]{1,40}$/;
16238
+ var FISH_TTS_MODELS = ["s2.1-pro", "s2.1-pro-free"];
16097
16239
  var MIN_ROOM_TURN_TIMEOUT_MINUTES = 1;
16098
16240
  var MAX_ROOM_TURN_TIMEOUT_MINUTES = 1440;
16099
16241
  var DEFAULT_ROOM_HANDOFF_LIFETIME_MINUTES = 30;
@@ -16104,6 +16246,8 @@ var MIN_THREAD_EVENT_LOG_BYTES = 256 * 1024;
16104
16246
  var MAX_THREAD_EVENT_LOG_BYTES = 4 * 1024 * 1024 * 1024;
16105
16247
  var MIN_LOCAL_VM_MAX_INSTANCES = 1;
16106
16248
  var MAX_LOCAL_VM_MAX_INSTANCES = 8;
16249
+ var MIN_LOCAL_VM_IDLE_TIMEOUT_MINUTES = 5;
16250
+ var MAX_LOCAL_VM_IDLE_TIMEOUT_MINUTES = 1440;
16107
16251
  function isValidSshAlias(value) {
16108
16252
  return typeof value === "string" && SSH_ALIAS.test(value);
16109
16253
  }
@@ -16141,7 +16285,8 @@ var roomConfigSchema = external_exports.object({
16141
16285
  );
16142
16286
  var localVmConfigSchema = external_exports.object({
16143
16287
  mode: external_exports.enum(["shared", "per-bot", "pool"]).optional(),
16144
- maxInstances: external_exports.number().int().min(MIN_LOCAL_VM_MAX_INSTANCES).max(MAX_LOCAL_VM_MAX_INSTANCES).optional()
16288
+ maxInstances: external_exports.number().int().min(MIN_LOCAL_VM_MAX_INSTANCES).max(MAX_LOCAL_VM_MAX_INSTANCES).optional(),
16289
+ idleTimeoutMinutes: external_exports.number().int().min(MIN_LOCAL_VM_IDLE_TIMEOUT_MINUTES).max(MAX_LOCAL_VM_IDLE_TIMEOUT_MINUTES).optional()
16145
16290
  });
16146
16291
  var browserProfileSchema = external_exports.object({
16147
16292
  // "guest" is the throwaway session's reserved id, never a saved profile
@@ -16300,7 +16445,11 @@ var onboardingConfigSchema = external_exports.object({
16300
16445
  /** Which welcome flow was completed; a newer flow may re-show itself. */
16301
16446
  version: external_exports.number().int().min(0).max(1e3).optional(),
16302
16447
  reelSeen: external_exports.boolean().optional(),
16303
- hintsSeen: external_exports.array(external_exports.string().trim().min(1).max(60)).max(100).optional()
16448
+ hintsSeen: external_exports.array(external_exports.string().trim().min(1).max(60)).max(100).optional(),
16449
+ /** OMB Cloud home only: when a bot's turn first finished on this machine
16450
+ * (cloud-home.ts firstCloudTurnPatch). Written by the server, read by the
16451
+ * Cloud's setup checklist. */
16452
+ firstTurnAt: external_exports.string().trim().max(40).optional()
16304
16453
  }).strict();
16305
16454
  var instanceConfigSchema = external_exports.object({
16306
16455
  driver: external_exports.string().min(1),
@@ -16312,6 +16461,7 @@ var instanceConfigSchema = external_exports.object({
16312
16461
  ]).optional(),
16313
16462
  environment: external_exports.record(external_exports.string(), external_exports.string()).optional(),
16314
16463
  enabled: external_exports.boolean().optional(),
16464
+ access: external_exports.enum(["subscription", "custom", "api"]).optional(),
16315
16465
  config: external_exports.json().optional()
16316
16466
  });
16317
16467
  var instanceConfigMapSchema = external_exports.record(external_exports.string(), instanceConfigSchema);
@@ -16369,11 +16519,17 @@ var appConfigSchema = external_exports.object({
16369
16519
  phone: external_exports.enum(["ios", "android"]).optional()
16370
16520
  }).optional(),
16371
16521
  mistral: external_exports.object({ key: optionalText }).optional(),
16522
+ cerebras: external_exports.object({ key: optionalText }).optional(),
16372
16523
  xai: external_exports.object({ key: optionalText, url: optionalText }).optional(),
16373
16524
  /** Anthropic API key for Claude Code billed per token, handed only to
16374
16525
  * Claude instances; `url` only for a proxy or a test double. Never a
16375
16526
  * personal-login OAuth token. */
16376
- anthropic: external_exports.object({ key: optionalText, url: optionalText }).optional(),
16527
+ anthropic: external_exports.object({ key: optionalText, url: optionalText, everyClaudeBot: external_exports.boolean().optional() }).optional(),
16528
+ /** OpenAI's own API key, for the `openai` instance only. Codex keeps its
16529
+ * own ChatGPT or API login and never reads this. */
16530
+ openai: external_exports.object({ key: optionalText }).optional(),
16531
+ /** OpenRouter key, for the `openrouter` instance only. */
16532
+ openrouter: external_exports.object({ key: optionalText }).optional(),
16377
16533
  /** Monthly spend limit for the whole workspace, against the cost engines
16378
16534
  * report to the usage ledger. Enforced only with the `budgets` entitlement. */
16379
16535
  budgets: external_exports.object({
@@ -16407,8 +16563,8 @@ var appConfigSchema = external_exports.object({
16407
16563
  opencodeGo: external_exports.object({ apiKey: optionalText }).optional(),
16408
16564
  /** Voice settings and the selected voice id. `provider` picks the
16409
16565
  * engine: "elevenlabs" (default; needs `key`), "fish" (needs its own
16410
- * `fishKey`), "system" (the Mac's built-in voices, no key), or
16411
- * "xai" (Grok TTS, reusing `xai.key`), or
16566
+ * `fishKey`; `fishModel` picks its speech model), "system" (the Mac's
16567
+ * built-in voices, no key), "xai" (Grok TTS, reusing `xai.key`), or
16412
16568
  * "chatterbox" (a local OpenAI-compatible Chatterbox server; `baseUrl`
16413
16569
  * and `model` are settings, not secrets). Cloud keys stay separate so
16414
16570
  * switching providers never overwrites or misuses the other key. */
@@ -16418,7 +16574,8 @@ var appConfigSchema = external_exports.object({
16418
16574
  voice: optionalText,
16419
16575
  provider: external_exports.enum(["elevenlabs", "fish", "system", "chatterbox", "xai"]).optional(),
16420
16576
  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(),
16421
- model: optionalText
16577
+ model: optionalText,
16578
+ fishModel: external_exports.enum(FISH_TTS_MODELS).optional()
16422
16579
  }).optional(),
16423
16580
  /** The decision model (server/decider): a fast classifier that picks
16424
16581
  * things for bots, starting with who answers a room message. `key` is
@@ -16506,6 +16663,34 @@ var DATA_DIR = process.env.OMB_DATA_DIR ?? join3(homedir2(), ".openmausbot");
16506
16663
  var LEGACY_DATA_DIR = join3(homedir2(), ".opengrokbot");
16507
16664
  var EVENTS_DIR = join3(DATA_DIR, "events");
16508
16665
  var NATIVE_DIR = join3(DATA_DIR, "native");
16666
+ var OPENAI_API_INSTANCE = "openai";
16667
+ var OPENROUTER_API_INSTANCE = "openrouter";
16668
+ var XAI_API_INSTANCE = "xaiApi";
16669
+ var CLAUDE_API_INSTANCE = "claudeApi";
16670
+ var API_KEY_FLEET = {
16671
+ [OPENAI_API_INSTANCE]: {
16672
+ driver: "openai-compat",
16673
+ displayName: "OpenAI",
16674
+ access: "api",
16675
+ icon: { kind: "preset", preset: "openai" },
16676
+ config: { url: "https://api.openai.com/v1", apiKeyEnv: "OMB_OPENAI_API_KEY", catalog: "openai" }
16677
+ },
16678
+ [CLAUDE_API_INSTANCE]: {
16679
+ driver: "claudeAgent",
16680
+ displayName: "Claude (API key)",
16681
+ access: "api",
16682
+ icon: { kind: "preset", preset: "anthropic" },
16683
+ config: { requireApiKey: true }
16684
+ },
16685
+ [XAI_API_INSTANCE]: { driver: "grok", displayName: "xAI", access: "api", icon: { kind: "preset", preset: "xai" } },
16686
+ [OPENROUTER_API_INSTANCE]: {
16687
+ driver: "openai-compat",
16688
+ displayName: "OpenRouter",
16689
+ access: "api",
16690
+ icon: { kind: "preset", preset: "openrouter" },
16691
+ config: { url: "https://openrouter.ai/api/v1", apiKeyEnv: "OMB_OPENROUTER_API_KEY" }
16692
+ }
16693
+ };
16509
16694
 
16510
16695
  // server/container-computer.ts
16511
16696
  var run = promisify(execFile2);
@@ -16637,6 +16822,7 @@ var CONTAINER_ID = /^[a-f0-9]{12,64}$/i;
16637
16822
  var LOCK_ACQUIRE_TIMEOUT_MS = 5e3;
16638
16823
  var SCREENSHOT_BUDGET_MS = 45e3;
16639
16824
  var PREPARATION_LOCK_TIMEOUT_MS = SCREENSHOT_BUDGET_MS + LOCK_ACQUIRE_TIMEOUT_MS;
16825
+ var NATIVE_VIEWER_LIFETIME_MS = 8 * 60 * 6e4;
16640
16826
  function vpsDockerArgs(alias2, args2) {
16641
16827
  if (!isValidSshAlias(alias2)) {
16642
16828
  throw new Error("invalid VPS SSH config alias");