openmausbot 0.1.85 → 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 (193) 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-CTNNoqSJ.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/container-mcp.js +19 -10
  125. package/dist-server/drivers/agents-proxy.js +16159 -1596
  126. package/dist-server/enterprise/LICENSE +32 -0
  127. package/dist-server/enterprise/server/index.js +5 -5
  128. package/dist-server/index.js +4779 -1667
  129. package/dist-server/mcp-server.js +3 -1
  130. package/dist-server/openmausbot.js +4679 -4266
  131. package/dist-server/pair-cli.js +4679 -4266
  132. package/dist-server/server/admin-activity.js +410 -0
  133. package/dist-server/server/bot-visibility.js +462 -0
  134. package/dist-server/server/box.js +2 -1
  135. package/dist-server/server/browser-runtime.js +1 -1
  136. package/dist-server/server/channel-queue.js +2 -0
  137. package/dist-server/server/cli-prompts.js +24 -2
  138. package/dist-server/server/cli.js +79 -10
  139. package/dist-server/server/composio.js +49 -11
  140. package/dist-server/server/config.js +79 -13
  141. package/dist-server/server/decision-log.js +209 -48
  142. package/dist-server/server/default-model-selection.js +20 -3
  143. package/dist-server/server/delegations.js +11 -9
  144. package/dist-server/server/drivers/acp/core.js +110 -22
  145. package/dist-server/server/drivers/acp/grok.js +4 -3
  146. package/dist-server/server/drivers/agents-call.js +1123 -0
  147. package/dist-server/server/drivers/agents-catalog.js +659 -0
  148. package/dist-server/server/drivers/agents-client.js +28 -0
  149. package/dist-server/server/drivers/agents-proxy.js +18 -1722
  150. package/dist-server/server/drivers/chat-mcp-tools.js +17 -2
  151. package/dist-server/server/drivers/claude.js +21 -6
  152. package/dist-server/server/drivers/codex-mcp-names.js +44 -0
  153. package/dist-server/server/drivers/codex.js +26 -4
  154. package/dist-server/server/drivers/grok.js +2 -1
  155. package/dist-server/server/drivers/openai-compat.js +15 -8
  156. package/dist-server/server/enterprise.js +97 -16
  157. package/dist-server/server/external-runtime.js +83 -0
  158. package/dist-server/server/guarded-requests.js +60 -0
  159. package/dist-server/server/harness/http.js +61 -0
  160. package/dist-server/server/hosted-models.js +151 -0
  161. package/dist-server/server/hosted-slack.js +11 -0
  162. package/dist-server/server/index.js +1718 -348
  163. package/dist-server/server/managed-desktop.js +79 -14
  164. package/dist-server/server/managed-policy.js +153 -0
  165. package/dist-server/server/message-db.js +28 -0
  166. package/dist-server/server/model-context-window.js +7 -0
  167. package/dist-server/server/model-prices.js +121 -0
  168. package/dist-server/server/notify.js +10 -1
  169. package/dist-server/server/peer-roster.js +13 -2
  170. package/dist-server/server/recent-work.js +15 -0
  171. package/dist-server/server/request-auth.js +96 -0
  172. package/dist-server/server/room-handoffs.js +25 -11
  173. package/dist-server/server/routes/hosted-slack.js +20 -0
  174. package/dist-server/server/routes/table.js +14 -0
  175. package/dist-server/server/routines.js +22 -0
  176. package/dist-server/server/spend.js +111 -16
  177. package/dist-server/server/steer-queue.js +6 -1
  178. package/dist-server/server/store.js +292 -21
  179. package/dist-server/server/team-backup.js +10 -1
  180. package/dist-server/server/thread-starters.js +53 -0
  181. package/dist-server/server/usage-ledger.js +31 -8
  182. package/dist-server/server/workspace-backup-policy.js +9 -2
  183. package/dist-server/server/workspace-backup.js +7 -4
  184. package/dist-server/shared/mention-boundary.js +15 -0
  185. package/dist-server/shared/routines.js +7 -1
  186. package/dist-server/shared/team-backup.js +2 -0
  187. package/dist-server/src/lib/composer-attachments.js +16 -0
  188. package/dist-server/vps-container-mcp.js +19 -10
  189. package/enterprise/README.md +33 -12
  190. package/enterprise/server/index.js +5 -5
  191. package/package.json +1 -1
  192. package/dist/assets/index-Ba9G44HI.js +0 -310
  193. package/dist/assets/index-CkRNp7wX.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
  }
@@ -13,7 +13,7 @@ export function browserRuntimeEnv(overrides) {
13
13
  const env = Object.fromEntries(HOST_ENV.flatMap((key) => process.env[key] === undefined ? [] : [[key, process.env[key]]]));
14
14
  return { ...env, ...overrides };
15
15
  }
16
- class TransportError extends Error {
16
+ export class TransportError extends Error {
17
17
  }
18
18
  /** A server-owned JSONL client. Neither child stderr nor its environment is
19
19
  * returned to the agent. Reuse the existing cross-platform spawn/kill rules. */
@@ -31,6 +31,8 @@ export function queueChannelMessage(groupId, threadId, text, options = {}) {
31
31
  sendId: options.sendId,
32
32
  mode: options.mode ?? "chat",
33
33
  via: options.via,
34
+ sender: options.sender,
35
+ trigger: options.trigger,
34
36
  };
35
37
  saveChatFollowup({ id: item.id, kind: "channel", ownerId: groupId, threadId, payload: item });
36
38
  entry.items.push(item);
@@ -9,10 +9,32 @@ export class SetupCancelled extends Error {
9
9
  }
10
10
  }
11
11
  function displayText(value, multiline = false) {
12
- const plain = stripVTControlCharacters(value);
12
+ // OSC 8 hyperlinks slip past stripVTControlCharacters on supported Node
13
+ // versions whenever the URI carries a character RFC 3986 allows unencoded
14
+ // ((, ), +, and friends): the failed match strands most of the URI and its
15
+ // BEL as printable text (nodejs/node#64313). Strip whole OSC sequences
16
+ // here, from the raw value while their openers are still intact; the
17
+ // label between a link's opening and closing sequences survives, and an
18
+ // unterminated sequence is removed outright. Both introducer forms
19
+ // match: ESC ] and the lone C1 byte, which carries no closing bracket.
20
+ // eslint-disable-next-line no-control-regex
21
+ const withoutOsc = value.replace(/(?:\u001b\]|\u009d)[^\u0007\u001b\u009c]*(?:\u0007|\u001b\\|\u009c)?/g, "");
22
+ const plain = stripVTControlCharacters(withoutOsc);
13
23
  // Provider-supplied labels must not issue terminal control commands.
24
+ // stripVTControlCharacters glues a trailing BEL onto the escape sequence
25
+ // before it on some Node versions and leaves it bare on others, so the
26
+ // fallback below must be version-independent: controls that render no
27
+ // glyph (BEL, DEL, C1, …) are removed outright — padding them would
28
+ // counterfeit whitespace — while tab and carriage return collapse to one
29
+ // space and a newline survives only in multiline mode.
14
30
  // eslint-disable-next-line no-control-regex
15
- const withoutControls = plain.replace(/[\u0000-\u001f\u007f-\u009f]/g, (character) => character === "\n" && multiline ? "\n" : " ");
31
+ const withoutControls = plain.replace(/[\u0000-\u001f\u007f-\u009f]/g, (character) => {
32
+ if (character === "\n")
33
+ return multiline ? "\n" : " ";
34
+ if (character === "\t" || character === "\r")
35
+ return " ";
36
+ return "";
37
+ });
16
38
  // Log lines must not end in trailing spaces; question prompts keep spacing like "Key: ".
17
39
  return multiline ? withoutControls.replace(/[ \t]+(?=\n|$)/g, "") : withoutControls;
18
40
  }