openmausbot 0.1.92 → 0.1.94

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 (163) hide show
  1. package/dist/assets/{DesktopViewer-Do6lBa4C.js → DesktopViewer-bxM7NV3N.js} +1 -1
  2. package/dist/assets/{abnfDiagram-O67JEVCF-Cd3X7Hxc.js → abnfDiagram-O67JEVCF-BhMdJhRJ.js} +1 -1
  3. package/dist/assets/{arc-DIYc7Ymx.js → arc-B5itKDcx.js} +1 -1
  4. package/dist/assets/{architectureDiagram-NJMV4G6O-DBmRGVyP.js → architectureDiagram-NJMV4G6O-C5d7UY6U.js} +1 -1
  5. package/dist/assets/{blockDiagram-BEXU5L5S-CPFePSEE.js → blockDiagram-BEXU5L5S-BrZIhMmu.js} +1 -1
  6. package/dist/assets/{c4Diagram-YGBWAQC7-DD_DO1qy.js → c4Diagram-YGBWAQC7-7I0ArfbD.js} +1 -1
  7. package/dist/assets/channel-CdJX6i8k.js +1 -0
  8. package/dist/assets/{chunk-2Q5K7J3B-BNEeNSjX.js → chunk-2Q5K7J3B-BnEsyN36.js} +1 -1
  9. package/dist/assets/{chunk-3FUC2YCW-BbinLaqH.js → chunk-3FUC2YCW-BXATQ52s.js} +2 -2
  10. package/dist/assets/{chunk-5VM5RSS4-BBgYrgDR.js → chunk-5VM5RSS4-B0BpTkEx.js} +1 -1
  11. package/dist/assets/{chunk-GTNCS2PH-ezXj-kA-.js → chunk-GTNCS2PH-BtLTz6FR.js} +1 -1
  12. package/dist/assets/{chunk-GWA4HPMP-9FxZ3diV.js → chunk-GWA4HPMP-D6gcZOYt.js} +1 -1
  13. package/dist/assets/{chunk-JWPE2WC7-CO-40JpI.js → chunk-JWPE2WC7-DCLrosvf.js} +1 -1
  14. package/dist/assets/{chunk-NETBCI7D-BEhjslYE.js → chunk-NETBCI7D-B9JsknV9.js} +1 -1
  15. package/dist/assets/{chunk-WEXAMYUT-CNeq4n7_.js → chunk-WEXAMYUT-CsoTxw7z.js} +1 -1
  16. package/dist/assets/{chunk-XXDRQBXY-OsCY7KSi.js → chunk-XXDRQBXY-0F_frB0c.js} +1 -1
  17. package/dist/assets/{classDiagram-v2-NBCMYWYE-CloRUEtk.js → classDiagram-v2-NBCMYWYE-DkrpnflX.js} +1 -1
  18. package/dist/assets/{cose-bilkent-JH36ORCC-D1BGZhp4.js → cose-bilkent-JH36ORCC-BNDwYBt9.js} +1 -1
  19. package/dist/assets/{cynefin-OW5HDTMX-CUf4T2CN.js → cynefin-OW5HDTMX-JUkNr1Mk.js} +1 -1
  20. package/dist/assets/{cynefinDiagram-VND7K2PF-DPEZrFGA.js → cynefinDiagram-VND7K2PF-DKeh9N4M.js} +1 -1
  21. package/dist/assets/{dagre-6A5THRUB-bDe6GIv5.js → dagre-6A5THRUB-ve-6T_wX.js} +1 -1
  22. package/dist/assets/{diagram-22UHCM2B-CdcUQz55.js → diagram-22UHCM2B-BuJ2d8rT.js} +1 -1
  23. package/dist/assets/{diagram-3UASUU5V-BZpqGg7c.js → diagram-3UASUU5V-BgOs_Ni-.js} +1 -1
  24. package/dist/assets/{diagram-ATOU4E4O-DvGM6ugp.js → diagram-ATOU4E4O-DMK6VmcP.js} +1 -1
  25. package/dist/assets/{diagram-CDSNMT55-DSTmMm0s.js → diagram-CDSNMT55-DksASlcY.js} +1 -1
  26. package/dist/assets/{diagram-MLGK6HIB-15cucnMn.js → diagram-MLGK6HIB-Bgn8I5k6.js} +1 -1
  27. package/dist/assets/{diagram-MPIPVDR6-tMbuaE6n.js → diagram-MPIPVDR6-zK0jqO8N.js} +1 -1
  28. package/dist/assets/{ebnfDiagram-ZINNZB2B-BYxYzjHI.js → ebnfDiagram-ZINNZB2B-vKrbzTwr.js} +1 -1
  29. package/dist/assets/{elk-276RUBZZ-v6bj852W.js → elk-276RUBZZ-Bzv2mPNf.js} +1 -1
  30. package/dist/assets/{erDiagram-OPXOYQCR-5lBBlnd2.js → erDiagram-OPXOYQCR-CvOyYuxu.js} +1 -1
  31. package/dist/assets/{flowDiagram-KWPJA3E3-CZyl1zIj.js → flowDiagram-KWPJA3E3-CtbnDbc0.js} +1 -1
  32. package/dist/assets/{ganttDiagram-FUAMR5RP-BiAI-ltZ.js → ganttDiagram-FUAMR5RP-c4dMRYyG.js} +1 -1
  33. package/dist/assets/{gitGraphDiagram-X574FWY7-BAZISkj3.js → gitGraphDiagram-X574FWY7-B34uaQho.js} +1 -1
  34. package/dist/assets/index-CPYrGsv1.js +610 -0
  35. package/dist/assets/{index-DGBaEXrM.js → index-CPiqc_DN.js} +1 -1
  36. package/dist/assets/index-CzT42GR8.css +1 -0
  37. package/dist/assets/{infoDiagram-VRGFBTTK-B6ooOy-Z.js → infoDiagram-VRGFBTTK-o64ma4CI.js} +1 -1
  38. package/dist/assets/{ishikawaDiagram-OU5B5YK6-BZ9ksi-B.js → ishikawaDiagram-OU5B5YK6-DV4C9BAm.js} +1 -1
  39. package/dist/assets/{journeyDiagram-ZHPQQLJL-DWqlzIEe.js → journeyDiagram-ZHPQQLJL-Bf0AHk1H.js} +1 -1
  40. package/dist/assets/{kanban-definition-PNTS6WVX-JX6piDwL.js → kanban-definition-PNTS6WVX-B1UMSKmE.js} +1 -1
  41. package/dist/assets/{linear-CmOWzX0P.js → linear-BpKhuyLm.js} +1 -1
  42. package/dist/assets/{mermaid.core-CF1zwoQ-.js → mermaid.core-B_BKNCw5.js} +5 -5
  43. package/dist/assets/{mindmap-definition-NLK3R4M7-CGh2K4fW.js → mindmap-definition-NLK3R4M7-DeARruT1.js} +1 -1
  44. package/dist/assets/{pegDiagram-GJSIUBJH-CDwYN0s7.js → pegDiagram-GJSIUBJH-BOQEk_1D.js} +1 -1
  45. package/dist/assets/{pieDiagram-5QR66LMP-1rfjJocB.js → pieDiagram-5QR66LMP-EIfoiTZo.js} +1 -1
  46. package/dist/assets/{quadrantDiagram-O4NWA36T-tKPvPiok.js → quadrantDiagram-O4NWA36T-GgZwT-fi.js} +1 -1
  47. package/dist/assets/{railroadDiagram-XR7U4H2S-BHLm5vXR.js → railroadDiagram-XR7U4H2S-D9rDQomj.js} +1 -1
  48. package/dist/assets/{requirementDiagram-PLB6GJNP-CB45STp7.js → requirementDiagram-PLB6GJNP-DAh1MIeo.js} +1 -1
  49. package/dist/assets/{sankeyDiagram-IPEJSGJF-55QA_huC.js → sankeyDiagram-IPEJSGJF-CPr6Uz64.js} +1 -1
  50. package/dist/assets/{sequenceDiagram-PO4LG4MO-BckFmQHn.js → sequenceDiagram-PO4LG4MO-BU6VIdcm.js} +1 -1
  51. package/dist/assets/{sizeCapture-INFHLROL-BFCOJF11.js → sizeCapture-INFHLROL-qF2HO41r.js} +1 -1
  52. package/dist/assets/{stateDiagram-v2-GCMORJYK-CZvmiVVN.js → stateDiagram-v2-GCMORJYK-DlJ8G4MD.js} +1 -1
  53. package/dist/assets/{swimlanes-2SLR337P-BpDUCEKy.js → swimlanes-2SLR337P-DzYE8I54.js} +1 -1
  54. package/dist/assets/swimlanesDiagram-TC7HE7FX-D-U4GtLQ.js +8 -0
  55. package/dist/assets/{timeline-definition-EJHVYXUP-BpxHq6IK.js → timeline-definition-EJHVYXUP-B7wzEQ0A.js} +1 -1
  56. package/dist/assets/{usecaseDiagram-POWQR4AR-Cj66vm02.js → usecaseDiagram-POWQR4AR-BvGgrxIP.js} +1 -1
  57. package/dist/assets/{vennDiagram-UO4OBE2U-C9nRkWE_.js → vennDiagram-UO4OBE2U-CEECdgQg.js} +1 -1
  58. package/dist/assets/{wardleyDiagram-VNRHLVJA-D-me_nWj.js → wardleyDiagram-VNRHLVJA-XQHA0P_P.js} +1 -1
  59. package/dist/assets/{xychartDiagram-PMCCYNJV-Dc_fL4mk.js → xychartDiagram-PMCCYNJV-B5mLcZI2.js} +1 -1
  60. package/dist/index.html +2 -2
  61. package/dist-server/cloud-home-start.js +1 -0
  62. package/dist-server/companion/src/routes.js +45 -1
  63. package/dist-server/container-mcp.js +99 -77
  64. package/dist-server/drivers/agents-proxy.js +40 -1
  65. package/dist-server/drivers/pi-mcp-extension.ts +245 -299
  66. package/dist-server/enterprise/server/index.js +1 -0
  67. package/dist-server/index.js +18361 -13794
  68. package/dist-server/local-computer-proxy.js +5 -1
  69. package/dist-server/local-computer.js +1 -0
  70. package/dist-server/mcp-gate.js +152 -20
  71. package/dist-server/mcp-remote-proxy.js +437 -0
  72. package/dist-server/openmausbot.js +8872 -7620
  73. package/dist-server/pair-cli.js +8872 -7620
  74. package/dist-server/permission-proxy.js +65 -1
  75. package/dist-server/proxy-paths.js +1 -0
  76. package/dist-server/server/auto-approve.js +33 -11
  77. package/dist-server/server/boat.js +3 -3
  78. package/dist-server/server/browser-live.js +14 -5
  79. package/dist-server/server/browser-runtime.js +6 -0
  80. package/dist-server/server/channel-queue.js +4 -1
  81. package/dist-server/server/cli.js +32 -12
  82. package/dist-server/server/cloud-move-http.js +9 -3
  83. package/dist-server/server/cloud-move.js +18 -0
  84. package/dist-server/server/cloud-owner.js +1 -1
  85. package/dist-server/server/computer-control.js +6 -0
  86. package/dist-server/server/computer-wait.js +33 -13
  87. package/dist-server/server/config.js +57 -10
  88. package/dist-server/server/container-computer.js +43 -32
  89. package/dist-server/server/decision-log.js +9 -6
  90. package/dist-server/server/delegations.js +47 -22
  91. package/dist-server/server/digest.js +12 -2
  92. package/dist-server/server/drivers/acp/core.js +229 -20
  93. package/dist-server/server/drivers/acp/cursor.js +13 -1
  94. package/dist-server/server/drivers/acp/grok.js +208 -3
  95. package/dist-server/server/drivers/acp/quiet-status.js +257 -0
  96. package/dist-server/server/drivers/acp/qwen.js +18 -0
  97. package/dist-server/server/drivers/agents-call.js +28 -0
  98. package/dist-server/server/drivers/agents-catalog.js +10 -1
  99. package/dist-server/server/drivers/boatagent.js +2 -0
  100. package/dist-server/server/drivers/builtIn.js +2 -0
  101. package/dist-server/server/drivers/cerebras.js +108 -0
  102. package/dist-server/server/drivers/chat-mcp-tools.js +45 -15
  103. package/dist-server/server/drivers/claude.js +35 -9
  104. package/dist-server/server/drivers/codex.js +84 -10
  105. package/dist-server/server/drivers/openai-chat.js +37 -5
  106. package/dist-server/server/drivers/pi.js +48 -4
  107. package/dist-server/server/drivers/prompt-split.js +2 -0
  108. package/dist-server/server/env-path.js +6 -2
  109. package/dist-server/server/environment.js +3 -0
  110. package/dist-server/server/guarded-requests.js +2 -1
  111. package/dist-server/server/index.js +1353 -311
  112. package/dist-server/server/live-call-controller.js +868 -0
  113. package/dist-server/server/live-call.js +216 -0
  114. package/dist-server/server/local-vm-stop-reason.js +26 -0
  115. package/dist-server/server/mcp-gate-config.js +43 -12
  116. package/dist-server/server/mcp-gate.js +127 -25
  117. package/dist-server/server/mcp-http.js +46 -8
  118. package/dist-server/server/mcp-remote-proxy.js +108 -0
  119. package/dist-server/server/new-bot-defaults.js +3 -0
  120. package/dist-server/server/peer-provenance.js +4 -2
  121. package/dist-server/server/permission-proxy.js +15 -1
  122. package/dist-server/server/plan-usage.js +797 -0
  123. package/dist-server/server/provider-key-check.js +2 -1
  124. package/dist-server/server/proxy-paths.js +1 -0
  125. package/dist-server/server/redact.js +2 -0
  126. package/dist-server/server/request-auth.js +3 -2
  127. package/dist-server/server/routes/desktop-viewer.js +50 -6
  128. package/dist-server/server/routes/live.js +98 -0
  129. package/dist-server/server/routines.js +6 -3
  130. package/dist-server/server/skill-library.js +207 -0
  131. package/dist-server/server/skills-library-migration.js +235 -0
  132. package/dist-server/server/skills.js +66 -3
  133. package/dist-server/server/steer-queue.js +13 -1
  134. package/dist-server/server/store.js +86 -4
  135. package/dist-server/server/surface.js +9 -3
  136. package/dist-server/server/team-backup.js +2 -1
  137. package/dist-server/server/thread-events.js +1 -1
  138. package/dist-server/server/tunnel.js +20 -1
  139. package/dist-server/server/turn-log.js +10 -0
  140. package/dist-server/server/turn-resources.js +124 -0
  141. package/dist-server/server/vps-computer.js +1 -1
  142. package/dist-server/server/workspace-backup.js +22 -0
  143. package/dist-server/server/workspace.js +1 -1
  144. package/dist-server/shared/approval-mode.js +1 -1
  145. package/dist-server/shared/ask-question.js +13 -0
  146. package/dist-server/shared/call-consent.js +28 -0
  147. package/dist-server/shared/live-approval.js +124 -0
  148. package/dist-server/shared/live-call.js +176 -0
  149. package/dist-server/shared/redact.js +1 -0
  150. package/dist-server/shared/skill-md.js +10 -0
  151. package/dist-server/shared/team-backup.js +3 -0
  152. package/dist-server/shared/tool-name.js +22 -0
  153. package/dist-server/shared/tool-scope-support.js +25 -0
  154. package/dist-server/shared/tool-scope.js +136 -0
  155. package/dist-server/src/lib/thread-refs.js +406 -0
  156. package/dist-server/vps-container-mcp.js +99 -77
  157. package/dist-server/workspace-backup.worker.js +103 -100
  158. package/enterprise/server/index.js +1 -0
  159. package/package.json +1 -1
  160. package/dist/assets/channel-Dtk5Mtic.js +0 -1
  161. package/dist/assets/index-Cdmjrl28.css +0 -1
  162. package/dist/assets/index-DR7YPMkW.js +0 -597
  163. package/dist/assets/swimlanesDiagram-TC7HE7FX-M0GYW9Y3.js +0 -8
@@ -0,0 +1,406 @@
1
+ // "#QA PR 245" in prose becomes a link to that thread. Bots are asked to
2
+ // write a thread's title as #Title when they mention one, and the person
3
+ // may type the same. Resolution mirrors @mention resolution for bots:
4
+ // word-start, longest known title first, case-insensitive — titles hold
5
+ // spaces, so the longest known title following the "#" wins. Nothing else
6
+ // is a link: "#123" issue numbers, "# Heading" markers (the parser never
7
+ // hands those here) and unknown titles all stay plain text.
8
+ const WORD_CHAR = /[\p{L}\p{N}_]/u;
9
+ /** a "#" glued to one of these is not a word start: C#Title, ', ##x */
10
+ const NOT_WORD_START = /[\p{L}\p{N}_#&]/u;
11
+ /** Spacing a pasted thread token needs on each side: a lead space after
12
+ * any char the resolver refuses as a word start — word chars, but also #
13
+ * and &, so a paste after them cannot glue into ##Title — and a trail
14
+ * space before a plain word char. */
15
+ export function threadTokenSpacing(text, start, end) {
16
+ return {
17
+ lead: start > 0 && NOT_WORD_START.test(text[start - 1] ?? "") ? " " : "",
18
+ trail: end < text.length && WORD_CHAR.test(text[end] ?? "") ? " " : "",
19
+ };
20
+ }
21
+ /** Only a title that is a real name can be linked: "123" would turn every
22
+ * issue number into a thread link. */
23
+ function linkable(title) {
24
+ return title.length > 0 && !/^\d+$/.test(title);
25
+ }
26
+ /** Known titles, longest first, each with every thread that carries it in
27
+ * the caller's (recency) order. */
28
+ function groupTitles(threads) {
29
+ const groups = new Map();
30
+ const seen = new Set();
31
+ for (const thread of threads) {
32
+ const title = thread.title.trim();
33
+ const key = `${thread.botId}/${thread.threadId}`;
34
+ if (!linkable(title) || seen.has(key))
35
+ continue;
36
+ seen.add(key);
37
+ const lower = title.toLowerCase();
38
+ const group = groups.get(lower);
39
+ if (group)
40
+ group.candidates.push({ ...thread, title });
41
+ else
42
+ groups.set(lower, { lower, candidates: [{ ...thread, title }] });
43
+ }
44
+ return [...groups.values()].sort((a, b) => b.lower.length - a.lower.length);
45
+ }
46
+ /** Which of several same-titled threads a mention means: the current
47
+ * bot's, else the most recently active, else the first — flagged so the
48
+ * link can say which bot it landed on. */
49
+ function pick(candidates, currentBotId) {
50
+ const resolved = (thread, ambiguous) => ({
51
+ botId: thread.botId, botName: thread.botName, threadId: thread.threadId, title: thread.title, ambiguous,
52
+ });
53
+ if (candidates.length === 1)
54
+ return resolved(candidates[0], false);
55
+ const own = candidates.filter((thread) => thread.botId === currentBotId);
56
+ if (own.length === 1)
57
+ return resolved(own[0], false);
58
+ const pool = own.length > 1 ? own : candidates;
59
+ const stamps = pool.map((thread) => thread.activeAt);
60
+ if (stamps.every((stamp) => typeof stamp === "number")) {
61
+ const newest = Math.max(...stamps);
62
+ const latest = pool.filter((thread) => thread.activeAt === newest);
63
+ if (latest.length === 1)
64
+ return resolved(latest[0], false);
65
+ }
66
+ return resolved(pool[0], true);
67
+ }
68
+ /** Split `text` into plain runs and linked "#Title" runs. The linked run
69
+ * keeps the person's own spelling ("#qa pr 245" stays as typed). */
70
+ export function resolveThreadRefs(text, threads, currentBotId) {
71
+ const groups = groupTitles(threads);
72
+ if (!groups.length || !text.includes("#"))
73
+ return [{ text }];
74
+ const spans = [];
75
+ let last = 0;
76
+ let at = text.indexOf("#");
77
+ while (at !== -1) {
78
+ const before = at > 0 ? text[at - 1] : "";
79
+ const start = at + 1;
80
+ let matched = null;
81
+ if (!before || !NOT_WORD_START.test(before)) {
82
+ for (const group of groups) {
83
+ const end = start + group.lower.length;
84
+ if (text.slice(start, end).toLowerCase() !== group.lower)
85
+ continue;
86
+ const after = text[end] ?? "";
87
+ if (after && WORD_CHAR.test(after))
88
+ continue;
89
+ matched = { end, ref: pick(group.candidates, currentBotId) };
90
+ break;
91
+ }
92
+ }
93
+ if (matched) {
94
+ if (at > last)
95
+ spans.push({ text: text.slice(last, at) });
96
+ spans.push({ text: text.slice(at, matched.end), ref: matched.ref });
97
+ last = matched.end;
98
+ at = text.indexOf("#", matched.end);
99
+ }
100
+ else {
101
+ at = text.indexOf("#", at + 1);
102
+ }
103
+ }
104
+ if (last < text.length)
105
+ spans.push({ text: text.slice(last) });
106
+ return spans.length ? spans : [{ text }];
107
+ }
108
+ const THREAD_URL_PREFIX = "openmausbot://thread/";
109
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
110
+ const ID_LIMIT = 256;
111
+ const plainId = (value) => {
112
+ // oxlint-disable-next-line no-control-regex -- a thread id never carries control characters
113
+ if (!value || value.length > ID_LIMIT || /[\u0000-\u001f]/.test(value))
114
+ return null;
115
+ return value;
116
+ };
117
+ /** The canonical link for a thread a person can copy. */
118
+ export function threadRefUrl(ref) {
119
+ return THREAD_URL_PREFIX + encodeURIComponent(ref.threadId) + "?bot=" + encodeURIComponent(ref.botId);
120
+ }
121
+ /** Parse a canonical thread link; null for anything else, including
122
+ * near-misses with extra path, extra query or a foreign scheme. */
123
+ export function parseThreadRefUrl(value) {
124
+ const raw = value.trim();
125
+ if (!raw.toLowerCase().startsWith(THREAD_URL_PREFIX))
126
+ return null;
127
+ let url;
128
+ try {
129
+ url = new URL(raw);
130
+ }
131
+ catch {
132
+ return null;
133
+ }
134
+ if (url.username || url.password || url.port || url.hash)
135
+ return null;
136
+ if (url.hostname.toLowerCase() !== "thread")
137
+ return null;
138
+ let threadId;
139
+ try {
140
+ threadId = decodeURIComponent(url.pathname.slice(1));
141
+ }
142
+ catch {
143
+ return null;
144
+ }
145
+ if (!threadId || threadId.includes("/") || url.pathname.slice(1) !== encodeURIComponent(threadId))
146
+ return null;
147
+ const keys = [...url.searchParams.keys()];
148
+ if (keys.some((key) => key !== "bot"))
149
+ return null;
150
+ const bot = url.searchParams.get("bot");
151
+ if (keys.length > 1 || (bot !== null && !bot))
152
+ return null;
153
+ const thread = plainId(threadId);
154
+ const owner = bot === null ? undefined : plainId(bot);
155
+ if (!thread || owner === null)
156
+ return null;
157
+ return owner ? { threadId: thread, botId: owner } : { threadId: thread };
158
+ }
159
+ /** Whether a value is shaped like a thread link at all — even one that
160
+ * fails strict parsing. Markdown must never hand such a URL to the shell
161
+ * as an external link, live or dead. */
162
+ export function looksLikeThreadRefUrl(value) {
163
+ return value.trim().toLowerCase().startsWith(THREAD_URL_PREFIX);
164
+ }
165
+ /** A raw thread id on its own: the UUID form the server mints. */
166
+ export function isThreadUuid(value) {
167
+ return UUID.test(value.trim());
168
+ }
169
+ /** Resolve an address (or bare UUID) against the visible threads. The
170
+ * link's own bot wins when it names one; otherwise the same preference a
171
+ * #Title mention uses: the open bot's thread, then the newest, then a
172
+ * deterministic first — flagged ambiguous so the chip can name the bot. */
173
+ export function resolveThreadRefAddress(threads, address, currentBotId) {
174
+ const candidates = threads.filter((thread) => thread.threadId === address.threadId);
175
+ if (!candidates.length)
176
+ return null;
177
+ const pinned = address.botId ? candidates.filter((thread) => thread.botId === address.botId) : [];
178
+ if (pinned.length === 1)
179
+ return { ...pinned[0], title: pinned[0].title.trim(), ambiguous: false };
180
+ if (pinned.length > 1)
181
+ return pick(pinned, currentBotId);
182
+ // a pinned owner missing from the visible threads is a dead reference:
183
+ // never fall through to another bot's same-id thread
184
+ if (address.botId)
185
+ return null;
186
+ return pick(candidates, currentBotId);
187
+ }
188
+ // ── sent-message serialization ─────────────────────────────────────────
189
+ // A resolvable #Title becomes a markdown link whose URL carries the thread
190
+ // id, so bots and API consumers keep the machine-readable reference. The
191
+ // label keeps the title readable; escapes keep brackets in titles parseable.
192
+ const escapeThreadLabel = (title) => title.replace(/[[\]\\]/g, "\\$&");
193
+ const unescapeThreadLabel = (label) => label.replace(/\\(.)/g, "$1");
194
+ /** The markdown shape a thread reference is sent as. */
195
+ export function threadRefMarkdown(ref) {
196
+ return "[" + escapeThreadLabel(ref.title) + "](" + threadRefUrl(ref) + ")";
197
+ }
198
+ const MARKDOWN_LINK_LABEL = /\[((?:\\.|[^\\\]])*)\]\(/y;
199
+ const INLINE_LINK_SPACE = " \t\n\r";
200
+ /** The CommonMark inline-link tail after "[label](": optional whitespace,
201
+ * an angle-bracket destination (spaces allowed) or a bare destination with
202
+ * balanced parentheses, optional whitespace, an optional quoted title, and
203
+ * the closing ")". Returns the tail length plus the bare destination for
204
+ * resolution, or null when no complete link closes here. */
205
+ function inlineLinkTail(rest, from) {
206
+ let at = from;
207
+ while (at < rest.length && INLINE_LINK_SPACE.includes(rest.charAt(at)))
208
+ at += 1;
209
+ const destStart = at;
210
+ let destination = "";
211
+ if (rest.charAt(at) === "<") {
212
+ const close = rest.indexOf(">", at + 1);
213
+ if (close === -1)
214
+ return null;
215
+ destination = rest.slice(at + 1, close);
216
+ at = close + 1;
217
+ }
218
+ else {
219
+ let depth = 0;
220
+ while (at < rest.length) {
221
+ const ch = rest.charAt(at);
222
+ if (ch === "(")
223
+ depth += 1;
224
+ else if (ch === ")") {
225
+ if (depth === 0)
226
+ break;
227
+ depth -= 1;
228
+ }
229
+ else if (INLINE_LINK_SPACE.includes(ch))
230
+ break;
231
+ at += 1;
232
+ }
233
+ if (depth !== 0)
234
+ return null;
235
+ destination = rest.slice(destStart, at);
236
+ }
237
+ while (at < rest.length && INLINE_LINK_SPACE.includes(rest.charAt(at)))
238
+ at += 1;
239
+ const titleQuote = rest.charAt(at);
240
+ if (titleQuote === '"' || titleQuote === "'") {
241
+ const close = rest.indexOf(titleQuote, at + 1);
242
+ if (close === -1)
243
+ return null;
244
+ at = close + 1;
245
+ }
246
+ else if (titleQuote === "(") {
247
+ let depth = 1;
248
+ at += 1;
249
+ while (at < rest.length && depth > 0) {
250
+ const ch = rest.charAt(at);
251
+ if (ch === "(")
252
+ depth += 1;
253
+ else if (ch === ")")
254
+ depth -= 1;
255
+ at += 1;
256
+ }
257
+ if (depth !== 0)
258
+ return null;
259
+ }
260
+ while (at < rest.length && INLINE_LINK_SPACE.includes(rest.charAt(at)))
261
+ at += 1;
262
+ if (rest.charAt(at) !== ")")
263
+ return null;
264
+ return { tail: at + 1 - from, destination };
265
+ }
266
+ /** Split text into plain runs and the spans resolution must never touch.
267
+ * Every markdown link is kept verbatim — a #Title inside a label would
268
+ * nest links — and so is code: inline backticks and fenced blocks quote
269
+ * their contents, so only plain runs carry #Title mentions. */
270
+ function splitThreadLinkMarkdown(text) {
271
+ if (!text.includes("](") && !text.includes("`"))
272
+ return [{ raw: text }];
273
+ const spans = [];
274
+ let plain = "";
275
+ const flush = () => {
276
+ if (plain)
277
+ spans.push({ raw: plain });
278
+ plain = "";
279
+ };
280
+ let at = 0;
281
+ while (at < text.length) {
282
+ const rest = text.slice(at);
283
+ const guarded = (end) => {
284
+ flush();
285
+ spans.push({ raw: text.slice(at, end), code: true });
286
+ at = end;
287
+ };
288
+ if (rest.startsWith("```")) {
289
+ const close = rest.indexOf("```", 3);
290
+ guarded(close === -1 ? text.length : at + close + 3);
291
+ continue;
292
+ }
293
+ const ticks = /^`+/.exec(rest)?.[0];
294
+ if (ticks) {
295
+ const close = rest.indexOf(ticks, ticks.length);
296
+ guarded(close === -1 ? text.length : at + close + ticks.length);
297
+ continue;
298
+ }
299
+ MARKDOWN_LINK_LABEL.lastIndex = 0;
300
+ const open = MARKDOWN_LINK_LABEL.exec(rest);
301
+ const tail = open && inlineLinkTail(rest, open[0].length);
302
+ if (open && tail) {
303
+ flush();
304
+ const raw = rest.slice(0, open[0].length + tail.tail);
305
+ spans.push({ raw, link: { label: open[1], address: parseThreadRefUrl(tail.destination) } });
306
+ at += raw.length;
307
+ continue;
308
+ }
309
+ plain += text[at];
310
+ at += 1;
311
+ }
312
+ flush();
313
+ return spans.length ? spans : [{ raw: text }];
314
+ }
315
+ /** The text a send carries: resolvable #Title runs become canonical links;
316
+ * existing links and unknown titles pass through untouched. */
317
+ export function serializeThreadRefs(text, threads, currentBotId) {
318
+ if (!groupTitles(threads).length)
319
+ return text;
320
+ return splitThreadLinkMarkdown(text)
321
+ .map((span) => (span.link || span.code) ? span.raw : resolveThreadRefs(span.raw, threads, currentBotId)
322
+ .map((run) => run.ref ? threadRefMarkdown(run.ref) : run.text)
323
+ .join(""))
324
+ .join("");
325
+ }
326
+ export function splitThreadRefsForDisplay(text, threads, currentBotId) {
327
+ const spans = [];
328
+ for (const span of splitThreadLinkMarkdown(text)) {
329
+ if (span.link) {
330
+ const ref = span.link.address ? resolveThreadRefAddress(threads, span.link.address, currentBotId) : null;
331
+ spans.push(ref ? { text: unescapeThreadLabel(span.link.label), ref } : { text: span.raw });
332
+ }
333
+ else if (span.code) {
334
+ spans.push({ text: span.raw });
335
+ }
336
+ else {
337
+ spans.push(...resolveThreadRefs(span.raw, threads, currentBotId));
338
+ }
339
+ }
340
+ return spans.length ? spans : [{ text }];
341
+ }
342
+ /** The whole paste is one thread reference — a canonical link, the markdown
343
+ * link shape messages carry, or a raw UUID — and it resolves to a visible
344
+ * thread: the #Title token the composer holds, plus what it resolved to.
345
+ * null when the paste is not a reference or names no known thread, so an
346
+ * unknown id stays plain text. */
347
+ export function threadTokenFromPaste(pasted, threads, currentBotId) {
348
+ const trimmed = pasted.trim();
349
+ if (!trimmed)
350
+ return null;
351
+ const wrapped = /^\[((?:\\.|[^\\\]])*)\]\((openmausbot:\/\/thread\/[^()\s]*)\)$/.exec(trimmed);
352
+ const address = wrapped
353
+ ? parseThreadRefUrl(wrapped[2])
354
+ : parseThreadRefUrl(trimmed) ?? (isThreadUuid(trimmed) ? { threadId: trimmed } : null);
355
+ if (!address)
356
+ return null;
357
+ const ref = resolveThreadRefAddress(threads, address, currentBotId);
358
+ if (!ref)
359
+ return null;
360
+ // '#Title' is the token of choice, but a numeric or '#' prefixed title
361
+ // can never re-resolve through that spelling — those pastes hold the
362
+ // canonical markdown link instead, which sends and displays the same.
363
+ const title = ref.title.trim();
364
+ const token = linkable(title) && !title.startsWith("#") ? `#${title}` : threadRefMarkdown(ref);
365
+ return { token, ref };
366
+ }
367
+ const OPAQUE = new Set(["link", "linkReference", "image", "imageReference", "definition", "html", "mention"]);
368
+ export const THREAD_REF_NODE = "threadRef";
369
+ function linkNode(text, ref) {
370
+ return {
371
+ type: THREAD_REF_NODE,
372
+ data: {
373
+ hName: "span",
374
+ hProperties: {
375
+ "data-thread-bot": ref.botId,
376
+ "data-thread-bot-name": ref.botName,
377
+ "data-thread-id": ref.threadId,
378
+ "data-thread-title": ref.title,
379
+ "data-thread-ambiguous": ref.ambiguous ? "true" : "false",
380
+ },
381
+ },
382
+ children: [{ type: "text", value: text }],
383
+ };
384
+ }
385
+ /** A unified attacher: `remarkPlugins={[remarkThreadRefs(threads, botId)]}`. */
386
+ export function remarkThreadRefs(threads, currentBotId) {
387
+ return () => (tree) => {
388
+ if (!threads.length)
389
+ return;
390
+ const visit = (node) => {
391
+ if (!node.children || OPAQUE.has(node.type))
392
+ return;
393
+ node.children = node.children.flatMap((child) => {
394
+ if (child.type !== "text" || typeof child.value !== "string") {
395
+ visit(child);
396
+ return [child];
397
+ }
398
+ const spans = resolveThreadRefs(child.value, threads, currentBotId);
399
+ if (!spans.some((span) => span.ref))
400
+ return [child];
401
+ return spans.map((span) => (span.ref ? linkNode(span.text, span.ref) : { type: "text", value: span.text }));
402
+ });
403
+ };
404
+ visit(tree);
405
+ };
406
+ }
@@ -136,6 +136,8 @@ function windowsKnownDirs() {
136
136
  // x.ai installer
137
137
  join(localAppData, "agy", "bin"),
138
138
  // Antigravity installer
139
+ // Cursor installer: cursor-agent.* and its `agent` copies (MOCA-272)
140
+ join(localAppData, "cursor-agent"),
139
141
  join(home, ".local", "bin"),
140
142
  // claude native installer
141
143
  join(home, ".claude", "local"),
@@ -143,7 +145,9 @@ function windowsKnownDirs() {
143
145
  // Factory droid installer (%USERPROFILE%\bin)
144
146
  join(home, ".bun", "bin"),
145
147
  join(home, ".deno", "bin"),
146
- 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
147
151
  ];
148
152
  }
149
153
  var cached = null;
@@ -624,7 +628,7 @@ import { promisify } from "node:util";
624
628
 
625
629
  // server/config.ts
626
630
  import { homedir as homedir2 } from "node:os";
627
- import { join as join3 } from "node:path";
631
+ import { join as join2 } from "node:path";
628
632
 
629
633
  // node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
630
634
  var external_exports = {};
@@ -15926,6 +15930,48 @@ function normalizeCronSchedule(value, after = Date.now()) {
15926
15930
  return schedule;
15927
15931
  }
15928
15932
 
15933
+ // shared/tool-scope.ts
15934
+ var SERVER_NAME = /^[a-z][a-z0-9_-]{0,31}$/;
15935
+ var CONTROL_CHARACTER = /[\x00-\x1f\x7f]/;
15936
+ var MAX_SELECTORS = 256;
15937
+ var MAX_SELECTOR_LENGTH = 1024;
15938
+ function validName(name) {
15939
+ return typeof name === "string" && name.trim().length > 0 && name.length <= MAX_SELECTOR_LENGTH && !CONTROL_CHARACTER.test(name) && !name.includes("*");
15940
+ }
15941
+ function selectorIdentity(selector) {
15942
+ if (selector.length > MAX_SELECTOR_LENGTH || CONTROL_CHARACTER.test(selector)) return;
15943
+ if (selector.startsWith("native:")) {
15944
+ const name = selector.slice(7);
15945
+ if (name === "*" || validName(name)) return { kind: "native", name };
15946
+ } else if (selector.startsWith("mcp:")) {
15947
+ const separator = selector.indexOf(":", 4);
15948
+ if (separator < 0) return;
15949
+ const server = selector.slice(4, separator);
15950
+ const name = selector.slice(separator + 1);
15951
+ if (SERVER_NAME.test(server) && (name === "*" || validName(name))) {
15952
+ return { kind: "mcp", server, name };
15953
+ }
15954
+ }
15955
+ }
15956
+ function parseToolScope(value) {
15957
+ if (value === void 0) return { ok: true, scope: void 0 };
15958
+ const invalid = { ok: false, error: "Tool selection must contain only valid allow and deny lists." };
15959
+ if (!value || typeof value !== "object" || Array.isArray(value)) return invalid;
15960
+ const prototype = Object.getPrototypeOf(value);
15961
+ if (prototype !== Object.prototype && prototype !== null) return invalid;
15962
+ const scope = {};
15963
+ for (const key of Reflect.ownKeys(value)) {
15964
+ if (key !== "allow" && key !== "deny") return invalid;
15965
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
15966
+ if (!descriptor || !("value" in descriptor)) return invalid;
15967
+ const list = descriptor.value;
15968
+ if (!Array.isArray(list) || list.length > MAX_SELECTORS) return invalid;
15969
+ if (!Array.from(list).every((entry) => typeof entry === "string" && selectorIdentity(entry))) return invalid;
15970
+ scope[key] = [...new Set(list)];
15971
+ }
15972
+ return { ok: true, scope };
15973
+ }
15974
+
15929
15975
  // server/new-bot-defaults.ts
15930
15976
  var strings = (max, length) => external_exports.array(external_exports.string().max(length)).max(max);
15931
15977
  var clock = external_exports.string().regex(/^(?:[01]\d|2[0-3]):[0-5]\d$/);
@@ -15960,6 +16006,10 @@ var botDefaultsProfileSchema = profilePatchSchema.extend({
15960
16006
  browser: external_exports.boolean().optional(),
15961
16007
  browserProfile: external_exports.string().max(100).nullable().optional(),
15962
16008
  mcpServers: strings(100, 100).nullable().optional(),
16009
+ toolScope: external_exports.custom((value) => value !== void 0 && parseToolScope(value).ok, "Invalid tool selection").transform((value) => {
16010
+ const parsed = parseToolScope(value);
16011
+ return parsed.ok ? parsed.scope : value;
16012
+ }).nullable().optional(),
15963
16013
  parkDirectMessages: external_exports.boolean().optional()
15964
16014
  }).strict();
15965
16015
  var scheduleSchema = external_exports.discriminatedUnion("type", [
@@ -16156,76 +16206,6 @@ var remoteMutationSchema = remoteEntrySchema.extend({
16156
16206
  var CLOUD_SEAT_IDLE_STOP_MS = 5 * 6e4;
16157
16207
  var CLOUD_SEAT_SLEEP_RETRY_MAX_MS = 10 * 6e4;
16158
16208
 
16159
- // server/proxy-paths.ts
16160
- import { existsSync as existsSync2 } from "node:fs";
16161
- import { dirname as dirname2, join as join2 } from "node:path";
16162
- import { fileURLToPath } from "node:url";
16163
- var SERVER_ROOT = dirname2(fileURLToPath(import.meta.url));
16164
- function resolveProxy(relative) {
16165
- const source = join2(SERVER_ROOT, `${relative}.ts`);
16166
- return existsSync2(source) ? source : join2(SERVER_ROOT, `${relative}.js`);
16167
- }
16168
- var SPAWNED_PROXIES = {
16169
- browser: resolveProxy("browser-proxy"),
16170
- localComputer: resolveProxy("local-computer-proxy"),
16171
- permission: resolveProxy("permission-proxy"),
16172
- containerMcp: resolveProxy("container-mcp"),
16173
- vpsContainerMcp: resolveProxy("vps-container-mcp"),
16174
- agents: resolveProxy("drivers/agents-proxy"),
16175
- dweb: resolveProxy("drivers/dweb-proxy"),
16176
- connectors: resolveProxy("connector-proxy"),
16177
- mcpGate: resolveProxy("mcp-gate"),
16178
- phone: resolveProxy("drivers/phone-proxy"),
16179
- hook: resolveProxy("hooks/omb-hook"),
16180
- // Loaded by the external `pi` process via `-e`, not by this server — but
16181
- // resolved through the same single source of truth so the packaged layout
16182
- // check can assert it ships.
16183
- piMcpExtension: resolveProxy("drivers/pi-mcp-extension")
16184
- };
16185
-
16186
- // server/enterprise.ts
16187
- var layerSchema = external_exports.object({
16188
- customer: external_exports.string().min(1),
16189
- features: external_exports.array(external_exports.string().min(1)),
16190
- expiresAt: external_exports.string().nullable()
16191
- });
16192
- var DAY_MS = 24 * 60 * 6e4;
16193
-
16194
- // server/sessions.ts
16195
- var PAIRING_CODE_TTL_MS = 5 * 6e4;
16196
- function daysMs(value, fallbackDays) {
16197
- const days = Number(value);
16198
- const ok = Number.isInteger(days) && days >= 1 && days <= 3650;
16199
- return (ok ? days : fallbackDays) * 24 * 60 * 6e4;
16200
- }
16201
- var SESSION_TTL_MS = daysMs(process.env.OMB_SESSION_TTL_DAYS, 30);
16202
- var SESSION_MAX_AGE_MS = daysMs(process.env.OMB_SESSION_MAX_DAYS, 180);
16203
- var SESSION_RENEW_WHEN_LEFT_MS = SESSION_TTL_MS / 2;
16204
- var STREAM_TICKET_TTL_MS = 5 * 6e4;
16205
- var scopeSchema = external_exports.enum(["admin", "client"]);
16206
- var sessionSchema = external_exports.object({
16207
- id: external_exports.string().min(1),
16208
- tokenHash: external_exports.string().length(64),
16209
- label: external_exports.string().max(80),
16210
- scopes: external_exports.array(scopeSchema).min(1),
16211
- createdAt: external_exports.number(),
16212
- lastSeenAt: external_exports.number(),
16213
- expiresAt: external_exports.number(),
16214
- /** Set when the session came from an account sign-in rather than a code. */
16215
- userId: external_exports.string().max(256).optional(),
16216
- email: external_exports.string().max(320).optional(),
16217
- /** Set only by the internal verified-portal issuance path, never by email or pairing input. */
16218
- membershipAuthority: external_exports.literal("portal").optional(),
16219
- /** A browser sign-in (PairingCode `browser`): accepted only as its browser's cookie, never as a bearer token. */
16220
- cookieOnly: external_exports.literal(true).optional(),
16221
- /** Whose OMB Cloud a browser sign-in signed in to (its PairingCode `owner`). */
16222
- owner: external_exports.string().max(254).optional()
16223
- });
16224
- var fileSchema = external_exports.object({ version: external_exports.literal(1), sessions: external_exports.array(sessionSchema) });
16225
-
16226
- // server/cloud-home.ts
16227
- var CLOUD_PAIRING_NONCE_MS = 10 * 6e4;
16228
-
16229
16209
  // server/config.ts
16230
16210
  var optionalText = external_exports.string().optional();
16231
16211
  var SSH_ALIAS = /^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/;
@@ -16433,7 +16413,11 @@ var featureConfigSchema = external_exports.object({
16433
16413
  * may offer a per-second-billed cloud seat, but only through an explicit
16434
16414
  * consent card or a configured allowlist — never a silent Auto default.
16435
16415
  * Off until baked; see cloudOverflowEnabled for how to enable it by hand. */
16436
- cloudOverflow: external_exports.boolean().optional()
16416
+ cloudOverflow: external_exports.boolean().optional(),
16417
+ /** Opt-in shared skills library: one store at the data dir that bots
16418
+ * read by assignment instead of per-workspace copies. Off until
16419
+ * explicitly enabled — see skillsLibraryEnabled. */
16420
+ skillsLibrary: external_exports.boolean().optional()
16437
16421
  });
16438
16422
  var onboardingConfigSchema = external_exports.object({
16439
16423
  /** ISO timestamp of finishing (or skipping to the end of) the welcome flow. */
@@ -16515,6 +16499,7 @@ var appConfigSchema = external_exports.object({
16515
16499
  phone: external_exports.enum(["ios", "android"]).optional()
16516
16500
  }).optional(),
16517
16501
  mistral: external_exports.object({ key: optionalText }).optional(),
16502
+ cerebras: external_exports.object({ key: optionalText }).optional(),
16518
16503
  xai: external_exports.object({ key: optionalText, url: optionalText }).optional(),
16519
16504
  /** Anthropic API key for Claude Code billed per token, handed only to
16520
16505
  * Claude instances; `url` only for a proxy or a test double. Never a
@@ -16585,6 +16570,15 @@ var appConfigSchema = external_exports.object({
16585
16570
  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(),
16586
16571
  jobs: external_exports.object({ roomRouting: external_exports.boolean().optional() }).optional()
16587
16572
  }).optional(),
16573
+ /** Live calls: an OpenAI project key for GPT-Live, kept apart from every
16574
+ * other OpenAI credential so a Live call never bills an image or engine key
16575
+ * the user did not hand to it. `voice` is a GPT-Live built-in voice name. */
16576
+ live: external_exports.object({
16577
+ key: optionalText,
16578
+ voice: external_exports.string().trim().max(40).regex(/^[a-z]*$/, "a Live voice is a lowercase built-in voice name").optional(),
16579
+ readTypedReplies: external_exports.boolean().optional(),
16580
+ idleMinutes: external_exports.number().int().min(1).max(60).optional()
16581
+ }).optional(),
16588
16582
  /** Avatar provider credentials stay separate; choosing a router never reuses a cloud key. */
16589
16583
  imageGen: external_exports.object({
16590
16584
  provider: external_exports.enum(["openai", "xai", "custom"]).optional(),
@@ -16654,10 +16648,10 @@ var storedAppConfigSchema = appConfigSchema.extend({
16654
16648
  var appConfigPatchSchema = appConfigSchema.omit({ instances: true, mcpServers: true, cliStartup: true, customDomain: true }).extend({ threads: threadsPatchSchema.optional(), newBots: newBotsPatchSchema.optional() });
16655
16649
  var jsonObjectSchema = external_exports.record(external_exports.string(), external_exports.json());
16656
16650
  var DEFAULT_CAPTURE_QUIET_MS = 2 * 6e4;
16657
- var DATA_DIR = process.env.OMB_DATA_DIR ?? join3(homedir2(), ".openmausbot");
16658
- var LEGACY_DATA_DIR = join3(homedir2(), ".opengrokbot");
16659
- var EVENTS_DIR = join3(DATA_DIR, "events");
16660
- var NATIVE_DIR = join3(DATA_DIR, "native");
16651
+ var DATA_DIR = process.env.OMB_DATA_DIR ?? join2(homedir2(), ".openmausbot");
16652
+ var LEGACY_DATA_DIR = join2(homedir2(), ".opengrokbot");
16653
+ var EVENTS_DIR = join2(DATA_DIR, "events");
16654
+ var NATIVE_DIR = join2(DATA_DIR, "native");
16661
16655
  var OPENAI_API_INSTANCE = "openai";
16662
16656
  var OPENROUTER_API_INSTANCE = "openrouter";
16663
16657
  var XAI_API_INSTANCE = "xaiApi";
@@ -16687,6 +16681,34 @@ var API_KEY_FLEET = {
16687
16681
  }
16688
16682
  };
16689
16683
 
16684
+ // server/proxy-paths.ts
16685
+ import { existsSync as existsSync2 } from "node:fs";
16686
+ import { dirname as dirname2, join as join3 } from "node:path";
16687
+ import { fileURLToPath } from "node:url";
16688
+ var SERVER_ROOT = dirname2(fileURLToPath(import.meta.url));
16689
+ function resolveProxy(relative) {
16690
+ const source = join3(SERVER_ROOT, `${relative}.ts`);
16691
+ return existsSync2(source) ? source : join3(SERVER_ROOT, `${relative}.js`);
16692
+ }
16693
+ var SPAWNED_PROXIES = {
16694
+ browser: resolveProxy("browser-proxy"),
16695
+ localComputer: resolveProxy("local-computer-proxy"),
16696
+ permission: resolveProxy("permission-proxy"),
16697
+ containerMcp: resolveProxy("container-mcp"),
16698
+ vpsContainerMcp: resolveProxy("vps-container-mcp"),
16699
+ agents: resolveProxy("drivers/agents-proxy"),
16700
+ dweb: resolveProxy("drivers/dweb-proxy"),
16701
+ connectors: resolveProxy("connector-proxy"),
16702
+ mcpGate: resolveProxy("mcp-gate"),
16703
+ mcpRemote: resolveProxy("mcp-remote-proxy"),
16704
+ phone: resolveProxy("drivers/phone-proxy"),
16705
+ hook: resolveProxy("hooks/omb-hook"),
16706
+ // Loaded by the external `pi` process via `-e`, not by this server — but
16707
+ // resolved through the same single source of truth so the packaged layout
16708
+ // check can assert it ships.
16709
+ piMcpExtension: resolveProxy("drivers/pi-mcp-extension")
16710
+ };
16711
+
16690
16712
  // server/container-computer.ts
16691
16713
  var run = promisify(execFile2);
16692
16714
  var CUA_DRIVER_VERSION = "0.20.0";