openmausbot 0.1.84 → 0.1.86

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 (230) hide show
  1. package/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  2. package/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  3. package/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  4. package/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  5. package/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  6. package/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  7. package/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  8. package/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  9. package/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  10. package/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  11. package/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  12. package/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  13. package/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  14. package/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  15. package/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  16. package/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  17. package/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  18. package/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  19. package/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  20. package/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  21. package/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  22. package/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  23. package/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  24. package/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  25. package/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  26. package/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  27. package/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  28. package/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  29. package/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  30. package/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  31. package/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  32. package/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  33. package/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  34. package/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  35. package/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  36. package/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  37. package/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  38. package/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  39. package/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  40. package/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  41. package/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  42. package/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  43. package/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  44. package/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  45. package/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  46. package/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  47. package/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  48. package/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  49. package/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  50. package/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  51. package/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  52. package/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  53. package/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  54. package/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  55. package/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  56. package/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  57. package/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  58. package/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  59. package/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  60. package/dist/assets/abnfDiagram-O67JEVCF-DpUZNnFD.js +1 -0
  61. package/dist/assets/arc-DW7mrI4p.js +1 -0
  62. package/dist/assets/architectureDiagram-NJMV4G6O-CyeZ5WLZ.js +36 -0
  63. package/dist/assets/blockDiagram-BEXU5L5S-CBVaNjO7.js +140 -0
  64. package/dist/assets/c4Diagram-YGBWAQC7-sf8d41uT.js +38 -0
  65. package/dist/assets/channel-BXFkgus9.js +1 -0
  66. package/dist/assets/chunk-2Q5K7J3B-BOGvy7Y_.js +1 -0
  67. package/dist/assets/chunk-3FUC2YCW-BiF9zyiz.js +2 -0
  68. package/dist/assets/chunk-5VM5RSS4-EFuZ1SBZ.js +15 -0
  69. package/dist/assets/chunk-GTNCS2PH-pJLNSAT7.js +88 -0
  70. package/dist/assets/chunk-GWA4HPMP-Cb-3_3IT.js +1 -0
  71. package/dist/assets/chunk-JWPE2WC7-DAf5aogn.js +1 -0
  72. package/dist/assets/chunk-NETBCI7D-DR3kiwZz.js +1 -0
  73. package/dist/assets/chunk-WEXAMYUT-BmU8PeyM.js +1 -0
  74. package/dist/assets/chunk-XXDRQBXY-Buh8g8OT.js +1 -0
  75. package/dist/assets/classDiagram-v2-NBCMYWYE-BurfmMWM.js +217 -0
  76. package/dist/assets/cose-bilkent-JH36ORCC-KkGZUEkU.js +1 -0
  77. package/dist/assets/cynefin-OW5HDTMX-C3PpV96d.js +166 -0
  78. package/dist/assets/cynefinDiagram-VND7K2PF-Dt8JwJaI.js +62 -0
  79. package/dist/assets/cytoscape.esm-BB4DxJjf.js +321 -0
  80. package/dist/assets/dagre-6A5THRUB-DkjN8266.js +4 -0
  81. package/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
  82. package/dist/assets/diagram-22UHCM2B-DUAJS0JV.js +200 -0
  83. package/dist/assets/diagram-3UASUU5V-Dw4KhF1a.js +24 -0
  84. package/dist/assets/diagram-ATOU4E4O-Dd1gmQqf.js +3 -0
  85. package/dist/assets/diagram-CDSNMT55-CjwGs0Yw.js +30 -0
  86. package/dist/assets/diagram-MLGK6HIB-BL-69Zrq.js +24 -0
  87. package/dist/assets/diagram-MPIPVDR6-5Zi2N7wo.js +41 -0
  88. package/dist/assets/ebnfDiagram-ZINNZB2B-D9NekWUX.js +1 -0
  89. package/dist/assets/elk-276RUBZZ-CkaSQjxw.js +27 -0
  90. package/dist/assets/erDiagram-OPXOYQCR-CdAVBV8-.js +99 -0
  91. package/dist/assets/flowDiagram-KWPJA3E3-Bm_lWWH7.js +213 -0
  92. package/dist/assets/ganttDiagram-FUAMR5RP-FZ8ejYWJ.js +292 -0
  93. package/dist/assets/gitGraphDiagram-X574FWY7-Dlv9rizZ.js +106 -0
  94. package/dist/assets/graph-D8UmR1vK.js +1 -0
  95. package/dist/assets/index-BGgqlWPJ.css +1 -0
  96. package/dist/assets/index-BtHo8ZKl.js +577 -0
  97. package/dist/assets/{index-DNa2umw-.js → index-h9FFNwTu.js} +1 -1
  98. package/dist/assets/infoDiagram-VRGFBTTK-BdQ_gW5B.js +2 -0
  99. package/dist/assets/init-Gi6I4Gst.js +1 -0
  100. package/dist/assets/ishikawaDiagram-OU5B5YK6-EwssEp2X.js +70 -0
  101. package/dist/assets/journeyDiagram-ZHPQQLJL-BX1G1vOd.js +139 -0
  102. package/dist/assets/kanban-definition-PNTS6WVX-BB4kAsyC.js +89 -0
  103. package/dist/assets/linear-3su2TG93.js +1 -0
  104. package/dist/assets/mermaid.core-CDD1E0s9.js +311 -0
  105. package/dist/assets/mindmap-definition-NLK3R4M7-Ba50MsNK.js +96 -0
  106. package/dist/assets/ordinal-Cboi1Yqb.js +1 -0
  107. package/dist/assets/pegDiagram-GJSIUBJH-DZLkkZlM.js +1 -0
  108. package/dist/assets/pieDiagram-5QR66LMP-DaTz77Uq.js +39 -0
  109. package/dist/assets/quadrantDiagram-O4NWA36T-CpaiidpR.js +7 -0
  110. package/dist/assets/railroadDiagram-XR7U4H2S-mN4JrMeI.js +1 -0
  111. package/dist/assets/requirementDiagram-PLB6GJNP-BSNDRpg7.js +84 -0
  112. package/dist/assets/sankeyDiagram-IPEJSGJF-FngzGNug.js +40 -0
  113. package/dist/assets/sequenceDiagram-PO4LG4MO-CM4pCC3L.js +169 -0
  114. package/dist/assets/sizeCapture-INFHLROL-CRICHXkw.js +1 -0
  115. package/dist/assets/stateDiagram-v2-GCMORJYK-oyTWpxH2.js +291 -0
  116. package/dist/assets/swimlanes-2SLR337P-D9h9mPBq.js +1 -0
  117. package/dist/assets/swimlanesDiagram-TC7HE7FX-BWrvB2ak.js +8 -0
  118. package/dist/assets/timeline-definition-EJHVYXUP-BJUlN9bW.js +120 -0
  119. package/dist/assets/usecaseDiagram-POWQR4AR-3uBNkbAb.js +370 -0
  120. package/dist/assets/vennDiagram-UO4OBE2U-D2odalzT.js +34 -0
  121. package/dist/assets/wardleyDiagram-VNRHLVJA-DHgFaB9S.js +78 -0
  122. package/dist/assets/xychartDiagram-PMCCYNJV-w5GYM2h0.js +7 -0
  123. package/dist/index.html +2 -2
  124. package/dist-server/companion/src/routes.js +1 -0
  125. package/dist-server/container-mcp.js +231 -17
  126. package/dist-server/drivers/agents-proxy.js +16097 -1411
  127. package/dist-server/enterprise/LICENSE +32 -0
  128. package/dist-server/enterprise/server/index.js +6 -5
  129. package/dist-server/hooks/omb-hook.js +74 -0
  130. package/dist-server/index.js +12495 -7584
  131. package/dist-server/local-computer-proxy.js +83 -1
  132. package/dist-server/local-computer.js +14 -2
  133. package/dist-server/mcp-server.js +3 -1
  134. package/dist-server/openmausbot.js +5458 -4587
  135. package/dist-server/pair-cli.js +5458 -4587
  136. package/dist-server/proxy-paths.js +1 -0
  137. package/dist-server/server/admin-activity.js +410 -0
  138. package/dist-server/server/agent-tool-policy.js +1 -0
  139. package/dist-server/server/bot-package.js +2 -0
  140. package/dist-server/server/bot-visibility.js +462 -0
  141. package/dist-server/server/box.js +25 -5
  142. package/dist-server/server/browser-engine.js +87 -10
  143. package/dist-server/server/browser-live.js +7 -5
  144. package/dist-server/server/browser-runtime.js +66 -11
  145. package/dist-server/server/channel-queue.js +2 -0
  146. package/dist-server/server/checkpoints.js +62 -18
  147. package/dist-server/server/cli-prompts.js +26 -2
  148. package/dist-server/server/cli.js +79 -10
  149. package/dist-server/server/commands.js +27 -0
  150. package/dist-server/server/compaction-summary.js +78 -0
  151. package/dist-server/server/composio.js +49 -11
  152. package/dist-server/server/config.js +137 -14
  153. package/dist-server/server/context-budget.js +29 -0
  154. package/dist-server/server/context-rebuild.js +72 -0
  155. package/dist-server/server/decision-log.js +209 -48
  156. package/dist-server/server/default-model-selection.js +20 -3
  157. package/dist-server/server/delegations.js +11 -9
  158. package/dist-server/server/digest.js +130 -0
  159. package/dist-server/server/drivers/acp/core.js +637 -238
  160. package/dist-server/server/drivers/acp/grok.js +4 -3
  161. package/dist-server/server/drivers/agents-call.js +1123 -0
  162. package/dist-server/server/drivers/agents-catalog.js +659 -0
  163. package/dist-server/server/drivers/agents-client.js +28 -0
  164. package/dist-server/server/drivers/agents-proxy.js +18 -1683
  165. package/dist-server/server/drivers/agents-result.js +23 -0
  166. package/dist-server/server/drivers/chat-mcp-tools.js +17 -2
  167. package/dist-server/server/drivers/claude.js +83 -16
  168. package/dist-server/server/drivers/codex-mcp-names.js +44 -0
  169. package/dist-server/server/drivers/codex.js +32 -4
  170. package/dist-server/server/drivers/grok.js +2 -1
  171. package/dist-server/server/drivers/openai-chat.js +66 -45
  172. package/dist-server/server/drivers/openai-compat.js +16 -8
  173. package/dist-server/server/enterprise.js +97 -16
  174. package/dist-server/server/external-runtime.js +83 -0
  175. package/dist-server/server/guarded-requests.js +60 -0
  176. package/dist-server/server/harness/http.js +61 -0
  177. package/dist-server/server/hooks/omb-hook.js +103 -0
  178. package/dist-server/server/hosted-models.js +151 -0
  179. package/dist-server/server/hosted-slack.js +11 -0
  180. package/dist-server/server/index.js +2810 -678
  181. package/dist-server/server/local-computer.js +21 -2
  182. package/dist-server/server/managed-desktop.js +79 -14
  183. package/dist-server/server/managed-policy.js +153 -0
  184. package/dist-server/server/mcp-bridge.js +10 -2
  185. package/dist-server/server/mcp-tool-images.js +37 -0
  186. package/dist-server/server/mcp-tool-schema.js +108 -0
  187. package/dist-server/server/message-db.js +108 -21
  188. package/dist-server/server/message-file.js +3 -2
  189. package/dist-server/server/model-context-window.js +7 -0
  190. package/dist-server/server/model-prices.js +121 -0
  191. package/dist-server/server/notify.js +10 -1
  192. package/dist-server/server/package-export.js +1 -0
  193. package/dist-server/server/peer-roster.js +17 -4
  194. package/dist-server/server/provider-icon.js +20 -0
  195. package/dist-server/server/proxy-paths.js +1 -0
  196. package/dist-server/server/recent-work.js +15 -0
  197. package/dist-server/server/request-auth.js +97 -0
  198. package/dist-server/server/room-handoffs.js +41 -13
  199. package/dist-server/server/routes/hosted-slack.js +20 -0
  200. package/dist-server/server/routes/table.js +14 -0
  201. package/dist-server/server/routine-requests.js +39 -1
  202. package/dist-server/server/routines.js +65 -9
  203. package/dist-server/server/shared-computer-control.js +31 -4
  204. package/dist-server/server/spend.js +111 -16
  205. package/dist-server/server/steer-queue.js +12 -1
  206. package/dist-server/server/store.js +384 -33
  207. package/dist-server/server/system-prompt.js +4 -4
  208. package/dist-server/server/team-backup.js +19 -2
  209. package/dist-server/server/thread-starters.js +53 -0
  210. package/dist-server/server/tool-results.js +72 -0
  211. package/dist-server/server/tts/grok.js +74 -0
  212. package/dist-server/server/tts/index.js +19 -1
  213. package/dist-server/server/usage-ledger.js +31 -8
  214. package/dist-server/server/webhooks.js +28 -0
  215. package/dist-server/server/workspace-backup-policy.js +9 -2
  216. package/dist-server/server/workspace-backup.js +7 -4
  217. package/dist-server/shared/approval-mode.js +7 -1
  218. package/dist-server/shared/digest.js +1 -0
  219. package/dist-server/shared/markdown-windows-paths.js +36 -0
  220. package/dist-server/shared/mention-boundary.js +15 -0
  221. package/dist-server/shared/provider-icon.js +110 -0
  222. package/dist-server/shared/routines.js +7 -1
  223. package/dist-server/shared/team-backup.js +5 -0
  224. package/dist-server/src/lib/composer-attachments.js +16 -0
  225. package/dist-server/vps-container-mcp.js +236 -22
  226. package/enterprise/README.md +33 -12
  227. package/enterprise/server/index.js +6 -5
  228. package/package.json +1 -1
  229. package/dist/assets/index-BbU5REzd.js +0 -310
  230. package/dist/assets/index-CLGfYlx_.css +0 -1
@@ -0,0 +1,462 @@
1
+ // Who may see a bot on a workspace several people share.
2
+ //
3
+ // A bot's optional `visibility` is set by an admin:
4
+ // - absent or "everyone": every signed-in person, today's behaviour;
5
+ // - "admins": admin sessions only;
6
+ // - { people: [...] }: the listed addresses (or @domain entries) plus admins.
7
+ //
8
+ // This is access control, not an approval gate. It decides what a member's
9
+ // session is shown and may touch — the bot, its threads, their messages,
10
+ // attachments, exports, search hits, routines, webhooks and live frames —
11
+ // and nothing else. The owner on this machine, a session-less local service
12
+ // (the Slack worker), and admin sessions see everything. A desktop has no
13
+ // member sessions, so nothing there changes.
14
+ //
15
+ // Rooms follow their bots: a member sees a room only when the room has at
16
+ // least one bot and they can see every bot in it. A room is one shared
17
+ // transcript, so a restricted bot's words there would otherwise reach people
18
+ // who cannot see the bot. Teams (sidebar sections) are listed to a member
19
+ // only when they hold a bot or room that member can see.
20
+ //
21
+ // Bots reach each other only when the same people can see both (see
22
+ // sameAudience): an ask or a delegation carries one bot's words into the
23
+ // other's thread, so a teammate with a different audience could surface a
24
+ // restricted bot's answers to people who cannot see it.
25
+ //
26
+ // Pure: no store, no server. server/index.ts supplies the records.
27
+ import { z } from "zod";
28
+ export const SEES_EVERYTHING = { kind: "all" };
29
+ export const MAX_VISIBILITY_PEOPLE = 500;
30
+ const ENTRY = /^(?:[^\s@,;<>"]+@[^\s@,;<>"]+\.[^\s@,;<>"]+|@[^\s@,;<>"]+\.[^\s@,;<>"]+)$/;
31
+ const peopleSchema = z.object({ people: z.array(z.string().max(320)).max(MAX_VISIBILITY_PEOPLE) }).strict();
32
+ /** Read an admin's input. null and "everyone" both mean the default, kept as
33
+ * "everyone" so every client's copy of the bot changes too (an absent field
34
+ * would leave a client's old value in place). An empty people list means
35
+ * admins only: a list that names nobody must not quietly mean everyone. */
36
+ export function parseVisibility(value) {
37
+ if (value === null || value === "everyone")
38
+ return { ok: true, visibility: "everyone" };
39
+ if (value === "admins")
40
+ return { ok: true, visibility: "admins" };
41
+ const parsed = peopleSchema.safeParse(value);
42
+ if (!parsed.success) {
43
+ return { ok: false, error: `visibility must be "everyone", "admins", or { "people": [email addresses] } with at most ${MAX_VISIBILITY_PEOPLE} entries` };
44
+ }
45
+ const people = [];
46
+ for (const raw of parsed.data.people) {
47
+ const entry = raw.trim().toLowerCase();
48
+ if (!entry)
49
+ continue;
50
+ if (!ENTRY.test(entry))
51
+ return { ok: false, error: `"${raw.slice(0, 80)}" is not an email address or @domain` };
52
+ if (!people.includes(entry))
53
+ people.push(entry);
54
+ }
55
+ return { ok: true, visibility: people.length ? { people } : "admins" };
56
+ }
57
+ /** A value read back from bots.json. Anything unrecognised fails closed to
58
+ * admins only: a hand-edited or damaged field must never widen access. */
59
+ export function storedVisibility(value) {
60
+ if (value === undefined || value === null || value === "everyone")
61
+ return "everyone";
62
+ if (value === "admins")
63
+ return "admins";
64
+ const parsed = parseVisibility(value);
65
+ return parsed.ok ? parsed.visibility : "admins";
66
+ }
67
+ export function isRestricted(value) {
68
+ return storedVisibility(value) !== "everyone";
69
+ }
70
+ function entryMatches(entry, email) {
71
+ return entry.startsWith("@") ? email.endsWith(entry) && email.length > entry.length : entry === email;
72
+ }
73
+ export function viewerSees(viewer, visibility) {
74
+ if (viewer.kind === "all")
75
+ return true;
76
+ const stored = storedVisibility(visibility);
77
+ if (stored === "everyone")
78
+ return true;
79
+ if (stored === "admins")
80
+ return false;
81
+ const email = viewer.email?.trim().toLowerCase();
82
+ return Boolean(email && email.includes("@") && stored.people.some((entry) => entryMatches(entry, email)));
83
+ }
84
+ /** True when exactly the same people can see both bots. */
85
+ export function sameAudience(a, b) {
86
+ const left = storedVisibility(a);
87
+ const right = storedVisibility(b);
88
+ if (typeof left === "string" || typeof right === "string")
89
+ return left === right;
90
+ if (left.people.length !== right.people.length)
91
+ return false;
92
+ const set = new Set(left.people);
93
+ return right.people.every((entry) => set.has(entry));
94
+ }
95
+ /** True when everyone who can see `inner` can also see `outer`. Admins see
96
+ * every bot, so an admins-only audience is inside any other. */
97
+ export function audienceWithin(inner, outer) {
98
+ const small = storedVisibility(inner);
99
+ const large = storedVisibility(outer);
100
+ if (large === "everyone" || small === "admins")
101
+ return true;
102
+ if (small === "everyone" || large === "admins")
103
+ return false;
104
+ return small.people.every((entry) => large.people.includes(entry) || (!entry.startsWith("@") && large.people.some((other) => other.startsWith("@") && entryMatches(other, entry))));
105
+ }
106
+ /** The people who can see both: everyone ∩ X is X, admins ∩ X is admins,
107
+ * and two lists keep each entry the other also admits. An intersection that
108
+ * names nobody is admins only. */
109
+ export function intersectAudience(a, b) {
110
+ const left = storedVisibility(a);
111
+ const right = storedVisibility(b);
112
+ if (left === "everyone")
113
+ return right;
114
+ if (right === "everyone")
115
+ return left;
116
+ if (left === "admins" || right === "admins")
117
+ return "admins";
118
+ const kept = [
119
+ ...left.people.filter((entry) => audienceWithin({ people: [entry] }, right)),
120
+ ...right.people.filter((entry) => audienceWithin({ people: [entry] }, left)),
121
+ ];
122
+ const people = [...new Set(kept)];
123
+ return people.length ? { people } : "admins";
124
+ }
125
+ /** The narrowest of several audiences (a room's bots, and its floor). */
126
+ export function narrowestAudience(values) {
127
+ return values.reduce((floor, value) => intersectAudience(floor, value), "everyone");
128
+ }
129
+ /** Whether two readings name the same audience. */
130
+ export function audienceEquals(a, b) {
131
+ return sameAudience(a, b);
132
+ }
133
+ /** Whether a room's conversation may feed a bot's recall and its brief of
134
+ * recent work: only when everyone who can see the bot can see every bot in
135
+ * the room. Otherwise a member chatting with a bot everyone sees would get
136
+ * back what a restricted bot said in a room they cannot open. */
137
+ export function roomFeeds(memberVisibilities, botVisibility) {
138
+ return memberVisibilities.every((visibility) => audienceWithin(botVisibility, visibility));
139
+ }
140
+ /** What one viewer may see of the current fleet, built from the store's
141
+ * records. When no bot is restricted, or the viewer sees everything, every
142
+ * check is `true` and nothing is filtered — the records are not even read.
143
+ * `ownerOf` resolves a thread to the bot or room that owns it; without one
144
+ * the set indexes the records itself. */
145
+ export class VisibleSet {
146
+ /** The viewer sees everything: nothing to filter. */
147
+ everything;
148
+ bots = new Map();
149
+ groups = new Map();
150
+ viewer;
151
+ ownerOf;
152
+ threads = null;
153
+ botMemo = new Map();
154
+ constructor(bots, groups, viewer, ownerOf) {
155
+ this.viewer = viewer;
156
+ this.everything = viewer.kind === "all" ||
157
+ (!bots.some((bot) => isRestricted(bot.visibility)) && !groups.some((group) => isRestricted(group.audienceFloor)));
158
+ this.ownerOf = ownerOf ?? ((threadId) => this.indexedOwner(threadId));
159
+ if (this.everything)
160
+ return;
161
+ for (const bot of bots)
162
+ this.bots.set(bot.id, bot);
163
+ for (const group of groups)
164
+ this.groups.set(group.id, group);
165
+ }
166
+ bot(id) {
167
+ if (this.everything)
168
+ return true;
169
+ let seen = this.botMemo.get(id);
170
+ if (seen === undefined) {
171
+ const bot = this.bots.get(id);
172
+ seen = Boolean(bot && viewerSees(this.viewer, bot.visibility));
173
+ this.botMemo.set(id, seen);
174
+ }
175
+ return seen;
176
+ }
177
+ /** At least one bot, every bot in it visible, and the room's floor — the
178
+ * narrowest audience it has had — admits the viewer. */
179
+ group(id) {
180
+ if (this.everything)
181
+ return true;
182
+ const group = this.groups.get(id);
183
+ if (!group)
184
+ return false;
185
+ const members = group.memberIds.filter((member) => this.bots.has(member));
186
+ return members.length > 0 && members.every((member) => this.bot(member)) && viewerSees(this.viewer, group.audienceFloor);
187
+ }
188
+ /** The bot or room that owns the thread is visible. An unknown thread is not. */
189
+ thread(threadId) {
190
+ if (this.everything)
191
+ return true;
192
+ const owner = this.ownerOf(threadId);
193
+ if (!owner)
194
+ return false;
195
+ return owner.bot !== undefined ? this.bot(owner.bot) : this.group(owner.group);
196
+ }
197
+ indexedOwner(threadId) {
198
+ if (!this.threads) {
199
+ const threads = new Map();
200
+ for (const bot of this.bots.values()) {
201
+ threads.set(bot.threadId, { bot: bot.id });
202
+ for (const task of bot.tasks ?? [])
203
+ threads.set(task.threadId, { bot: bot.id });
204
+ }
205
+ for (const group of this.groups.values()) {
206
+ if (!threads.has(group.threadId))
207
+ threads.set(group.threadId, { group: group.id });
208
+ for (const task of group.tasks ?? [])
209
+ if (!threads.has(task.threadId))
210
+ threads.set(task.threadId, { group: group.id });
211
+ }
212
+ this.threads = threads;
213
+ }
214
+ return this.threads.get(threadId);
215
+ }
216
+ /** Sections holding at least one visible bot or room. */
217
+ sections(all) {
218
+ if (this.everything)
219
+ return [...all];
220
+ const key = (value) => value?.trim() || "";
221
+ const shown = new Set();
222
+ for (const bot of this.bots.values())
223
+ if (this.bot(bot.id))
224
+ shown.add(key(bot.section));
225
+ for (const group of this.groups.values())
226
+ if (this.group(group.id))
227
+ shown.add(key(group.section));
228
+ return all.filter((section) => shown.has(key(section)));
229
+ }
230
+ /** An attachment is refused only when everything that references it is
231
+ * hidden: an image in a restricted bot's thread, or its avatar. One that
232
+ * nothing references yet (a member's own upload before sending) is served;
233
+ * its name is random. `threadsReferencing` is only called when needed. */
234
+ attachment(name, threadsReferencing) {
235
+ if (this.everything)
236
+ return true;
237
+ const url = `/api/attachments/${name}`;
238
+ let referenced = false;
239
+ for (const bot of this.bots.values()) {
240
+ if (bot.avatarUrl !== url)
241
+ continue;
242
+ if (this.bot(bot.id))
243
+ return true;
244
+ referenced = true;
245
+ }
246
+ for (const threadId of threadsReferencing(name)) {
247
+ if (this.thread(threadId))
248
+ return true;
249
+ referenced = true;
250
+ }
251
+ return !referenced;
252
+ }
253
+ }
254
+ export function pathSubject(path) {
255
+ let m = /^\/api\/bots\/([\w-]+)(?:\/|$)/.exec(path);
256
+ if (m)
257
+ return { kind: "bot", id: m[1] };
258
+ m = /^\/api\/threads\/([\w-]+)(?:\/|$)/.exec(path);
259
+ if (m)
260
+ return { kind: "thread", id: m[1] };
261
+ m = /^\/api\/groups\/([\w-]+)(?:\/|$)/.exec(path);
262
+ if (m)
263
+ return { kind: "group", id: m[1] };
264
+ m = /^\/api\/routines\/([\w-]+)(?:\/|$)/.exec(path);
265
+ if (m && m[1] !== "wake")
266
+ return { kind: "routine", id: m[1] };
267
+ m = /^\/api\/routine-runs\/([\w-]+)\//.exec(path);
268
+ if (m && m[1] !== "seen-all")
269
+ return { kind: "routine-run", id: m[1] };
270
+ m = /^\/api\/attachments\/([\w.-]+)$/.exec(path);
271
+ if (m)
272
+ return { kind: "attachment", name: m[1] };
273
+ return null;
274
+ }
275
+ /** The refusal a hidden subject gets: the same "not found" an unknown id
276
+ * gets, so a member cannot tell a restricted bot from a missing one. */
277
+ export function notFoundFor(subject) {
278
+ switch (subject.kind) {
279
+ case "bot": return "no such bot";
280
+ case "thread": return "no such conversation";
281
+ case "group": return "no such channel";
282
+ case "routine": return "no such routine";
283
+ case "routine-run": return "no such active run";
284
+ case "attachment": return "no such attachment";
285
+ }
286
+ }
287
+ /** The bot as a member receives it: no audience list (who else may see a bot
288
+ * is an admin's business), and no teammate ids the member cannot see. */
289
+ export function memberBot(bot, visible) {
290
+ if (visible.everything)
291
+ return bot;
292
+ const { visibility: _visibility, ...rest } = bot;
293
+ const peers = Array.isArray(rest.peers) ? rest.peers.filter((id) => typeof id === "string" && visible.bot(id)) : undefined;
294
+ return { ...rest, ...(peers ? { peers } : {}) };
295
+ }
296
+ /** A room as a member receives it: without its audience floor. */
297
+ export function memberGroup(group) {
298
+ if (!("audienceFloor" in group))
299
+ return group;
300
+ const { audienceFloor: _floor, ...rest } = group;
301
+ return rest;
302
+ }
303
+ /** A JSON response as a member receives it: every bot it carries (under
304
+ * `bot`, or in `bots`, at the top or one level down) through memberBot. */
305
+ export function memberBody(body, visible) {
306
+ if (visible.everything || !body || typeof body !== "object" || Array.isArray(body))
307
+ return body;
308
+ const narrow = (value, depth) => {
309
+ if (!value || typeof value !== "object" || Array.isArray(value))
310
+ return value;
311
+ const record = value;
312
+ let out = null;
313
+ for (const [key, child] of Object.entries(record)) {
314
+ let next = child;
315
+ if (key === "bot" && child && typeof child === "object" && !Array.isArray(child))
316
+ next = memberBot(child, visible);
317
+ else if (key === "bots" && Array.isArray(child)) {
318
+ next = child.map((bot) => (bot && typeof bot === "object" ? memberBot(bot, visible) : bot));
319
+ }
320
+ else if (key === "group" && child && typeof child === "object" && !Array.isArray(child))
321
+ next = memberGroup(child);
322
+ else if (key === "groups" && Array.isArray(child)) {
323
+ next = child.map((group) => (group && typeof group === "object" ? memberGroup(group) : group));
324
+ }
325
+ else if (depth > 0 && key !== "messages" && child && typeof child === "object" && !Array.isArray(child))
326
+ next = narrow(child, depth - 1);
327
+ if (next !== child)
328
+ (out ??= { ...record })[key] = next;
329
+ }
330
+ return out ?? record;
331
+ };
332
+ return narrow(body, 1);
333
+ }
334
+ /** `undefined`: send nothing. Otherwise the payload to send, which is the
335
+ * original object when nothing needed changing (the caller then reuses the
336
+ * shared serialized frame). */
337
+ export function frameForMember(payload, ctx, seen) {
338
+ const { visible } = ctx;
339
+ const kind = String(payload.kind ?? "");
340
+ const str = (value) => (typeof value === "string" ? value : "");
341
+ const field = (key) => (payload[key] && typeof payload[key] === "object" ? payload[key] : {});
342
+ switch (kind) {
343
+ case "sections": {
344
+ const sections = Array.isArray(payload.sections) ? payload.sections.filter((s) => typeof s === "string") : [];
345
+ return { ...payload, sections: visible.sections(sections) };
346
+ }
347
+ case "bot.queued": {
348
+ const queues = field("queues");
349
+ return { ...payload, queues: Object.fromEntries(Object.entries(queues).filter(([threadId]) => visible.thread(threadId))) };
350
+ }
351
+ case "message":
352
+ case "message.patch":
353
+ case "thread":
354
+ return visible.thread(str(payload.threadId)) ? payload : undefined;
355
+ case "bot": {
356
+ const bot = field("bot");
357
+ const id = str(bot.id);
358
+ if (!visible.bot(id)) {
359
+ if (!seen.bots.delete(id))
360
+ return undefined;
361
+ return { kind: "bot.deleted", botId: id };
362
+ }
363
+ if (!seen.bots.has(id)) {
364
+ seen.bots.add(id);
365
+ const fresh = ctx.freshBot(id);
366
+ if (fresh)
367
+ return { ...payload, bot: memberBot({ ...fresh, ...bot }, visible) };
368
+ }
369
+ return { ...payload, bot: memberBot(bot, visible) };
370
+ }
371
+ case "bot.deleted":
372
+ return seen.bots.delete(str(payload.botId)) ? payload : undefined;
373
+ case "group": {
374
+ const group = field("group");
375
+ const id = str(group.id);
376
+ if (!visible.group(id)) {
377
+ if (!seen.groups.delete(id))
378
+ return undefined;
379
+ return { kind: "group.deleted", groupId: id };
380
+ }
381
+ if (!seen.groups.has(id)) {
382
+ seen.groups.add(id);
383
+ const fresh = ctx.freshGroup(id);
384
+ if (fresh)
385
+ return { ...payload, group: memberGroup({ ...fresh, ...group }) };
386
+ }
387
+ return "audienceFloor" in group ? { ...payload, group: memberGroup(group) } : payload;
388
+ }
389
+ case "group.deleted":
390
+ return seen.groups.delete(str(payload.groupId)) ? payload : undefined;
391
+ case "notify": {
392
+ const notification = field("notification");
393
+ return visible.thread(str(notification.threadId)) && (!notification.botId || visible.bot(str(notification.botId))) ? payload : undefined;
394
+ }
395
+ case "routine": {
396
+ const routine = field("routine");
397
+ return routineVisible(routine, visible) ? payload : undefined;
398
+ }
399
+ case "routine.run": {
400
+ const run = field("run");
401
+ return routineVisible(run, visible) ? payload : undefined;
402
+ }
403
+ case "webhook":
404
+ return visible.bot(str(field("webhook").botId)) ? payload : undefined;
405
+ case "webhook.attempt": {
406
+ const botId = ctx.webhookBot(str(field("attempt").webhookId));
407
+ return botId && visible.bot(botId) ? payload : undefined;
408
+ }
409
+ case "runtime":
410
+ return visible.thread(str(field("event").threadId)) ? payload : undefined;
411
+ case "screen":
412
+ return visible.bot(str(payload.botId)) && visible.thread(str(payload.threadId)) ? payload : undefined;
413
+ case "computer":
414
+ case "computer-control":
415
+ return visible.bot(str(payload.botId)) ? payload : undefined;
416
+ case "config":
417
+ case "routine.deleted":
418
+ case "webhook.deleted":
419
+ return payload;
420
+ default:
421
+ // A frame kind added later: judge it by the ids it carries, and send it
422
+ // only when it names nothing hidden.
423
+ if (typeof payload.threadId === "string" && !visible.thread(payload.threadId))
424
+ return undefined;
425
+ if (typeof payload.botId === "string" && !visible.bot(payload.botId))
426
+ return undefined;
427
+ if (typeof payload.groupId === "string" && !visible.group(payload.groupId))
428
+ return undefined;
429
+ return payload;
430
+ }
431
+ }
432
+ /** Keep a member stream's record of what it was shown current while nothing
433
+ * is restricted, so restricting a bot later withdraws it from that stream. */
434
+ export function noteSeen(payload, seen) {
435
+ const id = (value) => (value && typeof value === "object" && typeof value.id === "string" ? value.id : "");
436
+ switch (payload.kind) {
437
+ case "bot":
438
+ if (id(payload.bot))
439
+ seen.bots.add(id(payload.bot));
440
+ break;
441
+ case "bot.deleted":
442
+ if (typeof payload.botId === "string")
443
+ seen.bots.delete(payload.botId);
444
+ break;
445
+ case "group":
446
+ if (id(payload.group))
447
+ seen.groups.add(id(payload.group));
448
+ break;
449
+ case "group.deleted":
450
+ if (typeof payload.groupId === "string")
451
+ seen.groups.delete(payload.groupId);
452
+ break;
453
+ }
454
+ }
455
+ /** A routine or run targets a bot, and may run in a room. */
456
+ export function routineVisible(value, visible) {
457
+ if (visible.everything)
458
+ return true;
459
+ if (typeof value.groupId === "string" && value.groupId)
460
+ return visible.group(value.groupId);
461
+ return typeof value.botId === "string" && visible.bot(value.botId);
462
+ }
@@ -394,7 +394,7 @@ async function listBoxPages(cfg) {
394
394
  return { ok: false, problem: "Could not reach ascii.dev to list cloud computers — check your connection and refresh" };
395
395
  }
396
396
  if (!listed.ok || !Array.isArray(listed.body?.boxes)) {
397
- return { ok: false, problem: boxInventoryProblem(listed.status, listed.body) };
397
+ return { ok: false, problem: boxInventoryProblem(listed.status, listed.body), credentialRejected: listed.status === 401 || listed.status === 403 };
398
398
  }
399
399
  boxes.push(...listed.body.boxes);
400
400
  const next = listed.body?.pageInfo?.nextCursor;
@@ -431,6 +431,7 @@ export async function listManagedBoxes(cfg, owners, options = {}) {
431
431
  configured: true,
432
432
  available: false,
433
433
  problem: listed.problem,
434
+ credentialRejected: listed.credentialRejected,
434
435
  instances: [],
435
436
  };
436
437
  }
@@ -1229,23 +1230,42 @@ export function panelShotCommand({ width = PANEL_FRAME_WIDTH, quality = PANEL_FR
1229
1230
  ].join("; ");
1230
1231
  }
1231
1232
  const SHOT_CMD = panelShotCommand();
1233
+ // A compromised box can answer with an arbitrarily large "frame"; cap what
1234
+ // the server ever buffers for one (raw bytes, before base64) so a single
1235
+ // response cannot exhaust memory.
1236
+ const MAX_FRAME_BYTES = 8 * 1024 * 1024;
1237
+ const FRAME_TOO_LARGE = "the box frame exceeds the 8 MB limit";
1232
1238
  /** Read a file off the box as base64 — raw artifact bytes when the API
1233
1239
  * supports it (33% less transfer, no JSON envelope), else the files API. */
1234
1240
  async function readFileBase64(cfg, boxId, path) {
1241
+ let bytes = null;
1242
+ let tooLarge = false;
1235
1243
  try {
1236
1244
  const res = await boxFetch(cfg, `/boxes/${boxId}/artifacts?path=${encodeURIComponent(path)}`);
1237
1245
  if (res.ok) {
1238
- const bytes = Buffer.from(await res.arrayBuffer());
1239
- if (bytes.length)
1240
- return bytes.toString("base64");
1246
+ const declaredLength = res.headers.get("content-length");
1247
+ if (declaredLength !== null && Number(declaredLength) > MAX_FRAME_BYTES)
1248
+ tooLarge = true;
1249
+ else
1250
+ bytes = Buffer.from(await res.arrayBuffer());
1241
1251
  }
1242
1252
  }
1243
1253
  catch {
1244
1254
  /* fall through */
1245
1255
  }
1256
+ if (tooLarge || (bytes !== null && bytes.length > MAX_FRAME_BYTES)) {
1257
+ throw new Error(FRAME_TOO_LARGE);
1258
+ }
1259
+ if (bytes?.length)
1260
+ return bytes.toString("base64");
1246
1261
  const { ok, body } = await boxJson(cfg, `/boxes/${boxId}/files?path=${encodeURIComponent(path)}&encoding=base64`);
1247
1262
  const content = body?.content;
1248
- return ok && typeof content === "string" && content ? content : null;
1263
+ if (ok && typeof content === "string" && content) {
1264
+ if (Buffer.byteLength(content, "base64") > MAX_FRAME_BYTES)
1265
+ throw new Error(FRAME_TOO_LARGE);
1266
+ return content;
1267
+ }
1268
+ return null;
1249
1269
  }
1250
1270
  /** `knownBoxId` skips box resolution entirely — the screen poller holds
1251
1271
  * the id for the whole turn and must not re-resolve it every frame. */