@sentry/junior 0.202.0 → 0.204.0

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 (174) hide show
  1. package/dist/agent-CMyTD4DD.js +17509 -0
  2. package/dist/agent-hooks-Bbcp9e33.js +4040 -0
  3. package/dist/agent-runner-DNb1A49M.js +678 -0
  4. package/dist/api/schema.d.ts +2728 -2639
  5. package/dist/api/schema.js +4 -169
  6. package/dist/api.d.ts +23 -17
  7. package/dist/api.js +3881 -5326
  8. package/dist/app-B1TOqBI8.d.ts +246 -0
  9. package/dist/app.d.ts +4 -520
  10. package/dist/app.js +9522 -11875
  11. package/dist/auth-pause-state-DosEalIG.js +168 -0
  12. package/dist/catalog-runtime-BReos-D4.js +7 -0
  13. package/dist/checkpoint-CNrPyRJN.js +2450 -0
  14. package/dist/cli/chat.d.ts +20 -0
  15. package/dist/cli/chat.js +229 -336
  16. package/dist/cli/check.d.ts +8 -0
  17. package/dist/cli/check.js +513 -806
  18. package/dist/cli/env.d.ts +13 -0
  19. package/dist/cli/env.js +63 -12
  20. package/dist/cli/init.d.ts +3 -0
  21. package/dist/cli/init.js +90 -159
  22. package/dist/cli/main.d.ts +6 -0
  23. package/dist/cli/main.js +43 -64
  24. package/dist/cli/plugins.d.ts +13 -0
  25. package/dist/cli/plugins.js +148 -227
  26. package/dist/cli/run.d.ts +16 -0
  27. package/dist/cli/run.js +52 -8
  28. package/dist/cli/snapshot-create.d.ts +4 -0
  29. package/dist/cli/snapshot-create.js +71 -125
  30. package/dist/cli/upgrade.d.ts +15 -0
  31. package/dist/cli/upgrade.js +232 -335
  32. package/dist/config-nGCW1OTG.js +941 -0
  33. package/dist/context-B45_WqVX.js +298 -0
  34. package/dist/conversation-privacy-B4voFRSm.js +182 -0
  35. package/dist/conversation-work-C6y3T5Hx.js +51 -0
  36. package/dist/credential-sync-kXr1hvO1.js +2660 -0
  37. package/dist/db-BeyXKJOI.d.ts +5 -0
  38. package/dist/destinations-BeBiJeEM.js +29 -0
  39. package/dist/execution-stats-DWfSihcH.js +373 -0
  40. package/dist/executor-DcMSlI5c.js +721 -0
  41. package/dist/experimental-CIXf7Uye.js +46 -0
  42. package/dist/history-ClE4toxe.d.ts +32 -0
  43. package/dist/history-CqTuSAdI.js +441 -0
  44. package/dist/instrumentation.d.ts +3 -3
  45. package/dist/instrumentation.js +130 -176
  46. package/dist/job-queue-Drn_LXlM.js +63 -0
  47. package/dist/logging-BzAk1S_s.js +1210 -0
  48. package/dist/model-profile-CZmVoGdX.js +49 -0
  49. package/dist/nitro.d.ts +31 -36
  50. package/dist/nitro.js +275 -456
  51. package/dist/oauth-callback-server-JiftokvA.js +130 -0
  52. package/dist/oauth-relay-CdCnu80W.js +62 -0
  53. package/dist/package-discovery-7a9YqLhE.js +455 -0
  54. package/dist/pending-auth-CWmSdZVg.js +185 -0
  55. package/dist/plugin-module-BKGOXca2.js +81 -0
  56. package/dist/plugins-BI9A1vKA.d.ts +95 -0
  57. package/dist/plugins-egKWjgue.js +99 -0
  58. package/dist/private-trace-result-BLvBjy_e.js +16 -0
  59. package/dist/profile-CrT5nXbw.js +861 -0
  60. package/dist/registry-DH1xUo2N.js +1419 -0
  61. package/dist/reporting-schema-DosTN__Q.js +105 -0
  62. package/dist/resolve-CZHnfxaS.js +546 -0
  63. package/dist/rolldown-runtime-B4FMCO8f.js +28 -0
  64. package/dist/runner-CZV2sC46.js +392 -0
  65. package/dist/sandbox-egress-signals-bHZfmhbd.js +46 -0
  66. package/dist/schema-Rn8-XEaN.js +1140 -0
  67. package/dist/sentry-CD3SdJl-.js +26 -0
  68. package/dist/skills-CIcDQSgM.js +316 -0
  69. package/dist/spawn-DdkUFRmq.js +29 -0
  70. package/dist/stats-DTUbAb88.js +2634 -0
  71. package/dist/store-C5qxF2ac.d.ts +208 -0
  72. package/dist/task-queue-CJIR4RF7.js +136 -0
  73. package/dist/task-runner-D-O3A-Cp.js +639 -0
  74. package/dist/thread-state-CRc9jTJS.js +431 -0
  75. package/dist/turn-execution-BkinIdJF.js +47 -0
  76. package/dist/turn-lifecycle-DQV19VTW.js +54 -0
  77. package/dist/usage-D3EbSO7w.js +296 -0
  78. package/dist/usage-schema-CKaDkH_j.js +20 -0
  79. package/dist/validation-C2CR-OX3.js +40 -0
  80. package/dist/vercel-queue-DIkVcfK4.js +167 -0
  81. package/dist/vercel.d.ts +6 -6
  82. package/dist/vercel.js +14 -6
  83. package/dist/version-CnXZ614E.d.ts +5 -0
  84. package/dist/version-DxcWdoKa.js +8 -0
  85. package/dist/version.d.ts +2 -4
  86. package/dist/version.js +2 -6
  87. package/dist/web-authorization-RMBxAR_t.js +76 -0
  88. package/dist/web-input-3EPfpf55.js +494 -0
  89. package/dist/work-fGofI7Op.js +509 -0
  90. package/dist/worker-CsOLq-mR.js +625 -0
  91. package/package.json +9 -9
  92. package/dist/agent-hooks-UXITLYOK.js +0 -51
  93. package/dist/catalog-runtime-BG3JLLK2.js +0 -14
  94. package/dist/chunk-2LYHWMR4.js +0 -46
  95. package/dist/chunk-2MK3QBGA.js +0 -321
  96. package/dist/chunk-3PGQSKJV.js +0 -692
  97. package/dist/chunk-3UI3WFTX.js +0 -519
  98. package/dist/chunk-3Y47VMHV.js +0 -6
  99. package/dist/chunk-4UO6FK4G.js +0 -64
  100. package/dist/chunk-5NOPO64K.js +0 -198
  101. package/dist/chunk-5PKWYPFT.js +0 -2943
  102. package/dist/chunk-63NKKFJL.js +0 -1228
  103. package/dist/chunk-6AYH6252.js +0 -787
  104. package/dist/chunk-6Y5B5PEX.js +0 -80
  105. package/dist/chunk-7DHZP5KJ.js +0 -333
  106. package/dist/chunk-7H2VZTWN.js +0 -103
  107. package/dist/chunk-7NL7T6VU.js +0 -406
  108. package/dist/chunk-A76OIMYN.js +0 -102
  109. package/dist/chunk-AWMS3SPP.js +0 -58
  110. package/dist/chunk-B3LY5RCL.js +0 -229
  111. package/dist/chunk-BD2KT3OB.js +0 -36
  112. package/dist/chunk-BXCX5QKA.js +0 -2086
  113. package/dist/chunk-C6VVF43U.js +0 -81
  114. package/dist/chunk-CEMMLV5L.js +0 -98
  115. package/dist/chunk-CM5HNV2W.js +0 -131
  116. package/dist/chunk-DA2KGZZI.js +0 -13
  117. package/dist/chunk-DBVY6HLX.js +0 -64
  118. package/dist/chunk-DIB4GX3G.js +0 -8
  119. package/dist/chunk-DKIJ2RP5.js +0 -68
  120. package/dist/chunk-DL2KLKVH.js +0 -931
  121. package/dist/chunk-DWPN6SCH.js +0 -172
  122. package/dist/chunk-ELWOYVOY.js +0 -206
  123. package/dist/chunk-EWT7H65X.js +0 -506
  124. package/dist/chunk-FA6POGLH.js +0 -70
  125. package/dist/chunk-HMREVIO2.js +0 -135
  126. package/dist/chunk-IIWUT4FO.js +0 -820
  127. package/dist/chunk-JAL3CH4G.js +0 -27
  128. package/dist/chunk-JSJQEWBJ.js +0 -3295
  129. package/dist/chunk-KHKKHDLD.js +0 -387
  130. package/dist/chunk-LA6R4A7Q.js +0 -706
  131. package/dist/chunk-LOOU2DUR.js +0 -20428
  132. package/dist/chunk-M6FHWXE3.js +0 -700
  133. package/dist/chunk-M76HMB7M.js +0 -1856
  134. package/dist/chunk-MAGNK5J4.js +0 -1520
  135. package/dist/chunk-MTM5IFCY.js +0 -1190
  136. package/dist/chunk-N7TMSOZH.js +0 -235
  137. package/dist/chunk-NHJU2ZAL.js +0 -56
  138. package/dist/chunk-NSXKRGRT.js +0 -717
  139. package/dist/chunk-OWMPFXJA.js +0 -1764
  140. package/dist/chunk-PDO5BLNM.js +0 -22
  141. package/dist/chunk-PEQXYSSE.js +0 -45
  142. package/dist/chunk-PW4P4JYF.js +0 -1135
  143. package/dist/chunk-QB54UQUS.js +0 -320
  144. package/dist/chunk-QP3OGJXF.js +0 -105
  145. package/dist/chunk-RC745GGZ.js +0 -122
  146. package/dist/chunk-REOIRYPR.js +0 -37
  147. package/dist/chunk-SCOZRLZ4.js +0 -164
  148. package/dist/chunk-TXJSXBV5.js +0 -1433
  149. package/dist/chunk-TXKUKMVO.js +0 -417
  150. package/dist/chunk-U2GPUYAL.js +0 -3656
  151. package/dist/chunk-UIE3R5XU.js +0 -17
  152. package/dist/chunk-UXPG6ZIN.js +0 -84
  153. package/dist/chunk-VFUK3X5B.js +0 -50
  154. package/dist/chunk-WQSHDTTI.js +0 -1163
  155. package/dist/chunk-X6GDDWQH.js +0 -60
  156. package/dist/chunk-Y2CM7HXH.js +0 -111
  157. package/dist/chunk-Y7X25LFY.js +0 -15
  158. package/dist/chunk-YDUR3ZYW.js +0 -10
  159. package/dist/conversation-work-VIOQ5ZF3.js +0 -54
  160. package/dist/db-3L2MCVRI.js +0 -26
  161. package/dist/discovery-DWVDM7UK.js +0 -44
  162. package/dist/experimental-P5FZM4CU.js +0 -12
  163. package/dist/oauth-callback-server-YVV6C6T2.js +0 -243
  164. package/dist/oauth-relay-5XPVRPYB.js +0 -8
  165. package/dist/plugins-YTYJMMJF.js +0 -14
  166. package/dist/runner-LQ7RETDP.js +0 -522
  167. package/dist/sandbox-egress-signals-FIL7EGPR.js +0 -73
  168. package/dist/sentry-MB3NL3IR.js +0 -42
  169. package/dist/skills-XRWYEDCD.js +0 -27
  170. package/dist/spawn-RC6SJUAI.js +0 -42
  171. package/dist/task-runner-7DY7ARYH.js +0 -36
  172. package/dist/validation-7MNQGCJA.js +0 -17
  173. package/dist/work-COALHUCG.js +0 -51
  174. package/dist/worker-6MURZIPD.js +0 -24
@@ -0,0 +1,494 @@
1
+ import { o as getSlackBotToken } from "./config-nGCW1OTG.js";
2
+ import { f as logWarn } from "./logging-BzAk1S_s.js";
3
+ import { t as canExposeConversationPayload } from "./conversation-privacy-B4voFRSm.js";
4
+ import "./context-B45_WqVX.js";
5
+ import { Et as getDb, Tt as getConversationStore, U as normalizeSlackConversationId } from "./agent-hooks-Bbcp9e33.js";
6
+ import { n as juniorDestinations } from "./destinations-BeBiJeEM.js";
7
+ import { C as juniorConversations, T as juniorUsers, m as juniorConversationParticipants, w as juniorIdentities } from "./executor-DcMSlI5c.js";
8
+ import { Gt as buildDeterministicTurnId, c as parseSlackThreadId, dt as appendAndEnqueueExclusiveInboundMessage, ft as appendAndEnqueueInboundMessage } from "./stats-DTUbAb88.js";
9
+ import { t as healthReportSchema } from "./reporting-schema-DosTN__Q.js";
10
+ import { z } from "zod";
11
+ import { createHash } from "node:crypto";
12
+ import { and, eq, exists, gte, inArray, isNotNull, notExists, or } from "drizzle-orm";
13
+ import { createWebSource, localDestinationSchema } from "@sentry/junior-plugin-api";
14
+ import { alias } from "drizzle-orm/pg-core";
15
+ import { del, get, put } from "@vercel/blob";
16
+ //#region src/handlers/health.ts
17
+ /** Build Junior's process health value for HTTP and reporting consumers. */
18
+ function readHealthReport() {
19
+ return healthReportSchema.parse({
20
+ status: "ok",
21
+ service: "junior",
22
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
23
+ });
24
+ }
25
+ /**
26
+ * Returns a minimal JSON health response for runtime health checks.
27
+ */
28
+ function GET() {
29
+ return Response.json(readHealthReport());
30
+ }
31
+ //#endregion
32
+ //#region src/chat/attachments/vercel.ts
33
+ /** Store private conversation attachments in Vercel Blob. */
34
+ function createVercelAttachmentStorage() {
35
+ return {
36
+ provider: "vercel-blob",
37
+ async put(input) {
38
+ await put(input.key, input.body, {
39
+ access: "private",
40
+ addRandomSuffix: false,
41
+ allowOverwrite: true,
42
+ contentType: input.contentType
43
+ });
44
+ },
45
+ async get(key) {
46
+ const result = await get(key, {
47
+ access: "private",
48
+ useCache: false
49
+ });
50
+ return result?.statusCode === 200 ? result.stream : null;
51
+ },
52
+ async delete(keys) {
53
+ if (keys.length === 0) return;
54
+ await del(keys);
55
+ }
56
+ };
57
+ }
58
+ //#endregion
59
+ //#region src/chat/conversations/web-mailbox.ts
60
+ /** Parse web mailbox metadata written before Source and Actor were complete. */
61
+ const legacyWebMailboxMetadataSchema = z.object({
62
+ authorEmail: z.string().email(),
63
+ authorFullName: z.string().min(1).optional(),
64
+ authorUserId: z.string().min(1),
65
+ authorUserName: z.string().min(1).optional(),
66
+ kind: z.literal("api_turn"),
67
+ messageId: z.string().min(1)
68
+ }).strict();
69
+ //#endregion
70
+ //#region src/chat/slack/user.ts
71
+ const slackUserInfoSchema = z.object({
72
+ ok: z.literal(true),
73
+ user: z.object({
74
+ name: z.string().nullish(),
75
+ profile: z.object({
76
+ email: z.string().nullish(),
77
+ real_name: z.string().nullish()
78
+ }).optional()
79
+ })
80
+ });
81
+ const USER_CACHE_TTL_MS = 3e5;
82
+ const userCache = /* @__PURE__ */ new Map();
83
+ function userCacheKey(teamId, userId) {
84
+ return `${teamId}:${userId}`;
85
+ }
86
+ function readFromCache(teamId, userId) {
87
+ const hit = userCache.get(userCacheKey(teamId, userId));
88
+ if (!hit) return null;
89
+ if (hit.expiresAt < Date.now()) {
90
+ userCache.delete(userCacheKey(teamId, userId));
91
+ return null;
92
+ }
93
+ return hit.value;
94
+ }
95
+ function writeToCache(teamId, userId, value) {
96
+ userCache.set(userCacheKey(teamId, userId), {
97
+ value,
98
+ expiresAt: Date.now() + USER_CACHE_TTL_MS
99
+ });
100
+ }
101
+ /** Fetch Slack user profile info with in-memory TTL cache to avoid repeated API calls. */
102
+ async function lookupSlackUser(teamId, userId) {
103
+ if (!teamId || !userId) return null;
104
+ const cached = readFromCache(teamId, userId);
105
+ if (cached) return cached;
106
+ const token = getSlackBotToken();
107
+ if (!token) return null;
108
+ try {
109
+ const response = await fetch(`https://slack.com/api/users.info?user=${encodeURIComponent(userId)}`, { headers: { authorization: `Bearer ${token}` } });
110
+ if (!response.ok) {
111
+ logWarn("slack.user.lookup.failed", {
112
+ "enduser.id": userId,
113
+ "app.slack.team_id": teamId,
114
+ "http.response.status_code": response.status
115
+ });
116
+ return null;
117
+ }
118
+ const parsed = slackUserInfoSchema.safeParse(await response.json());
119
+ if (!parsed.success) return null;
120
+ const payload = parsed.data;
121
+ const result = {
122
+ userName: payload.user.name?.trim() || void 0,
123
+ fullName: payload.user.profile?.real_name?.trim() || void 0,
124
+ email: payload.user.profile?.email?.trim() || void 0
125
+ };
126
+ writeToCache(teamId, userId, result);
127
+ return result;
128
+ } catch (error) {
129
+ logWarn("slack.user.lookup.failed", {
130
+ "enduser.id": userId,
131
+ "app.slack.team_id": teamId,
132
+ "exception.message": error instanceof Error ? error.message : String(error)
133
+ });
134
+ return null;
135
+ }
136
+ }
137
+ //#endregion
138
+ //#region src/chat/slack/conversation-context.ts
139
+ function normalizeConversationName(type, channelName) {
140
+ const trimmed = channelName?.trim();
141
+ if (!trimmed) return void 0;
142
+ if (type === "public_channel" || type === "private_channel" || type === "private_channel_or_group_dm") return trimmed.startsWith("#") ? trimmed : `#${trimmed}`;
143
+ return trimmed;
144
+ }
145
+ function typeFromSlackChannelType(channelType) {
146
+ if (channelType === "channel") return "public_channel";
147
+ if (channelType === "group") return "private_channel";
148
+ if (channelType === "mpim") return "group_dm";
149
+ if (channelType === "im") return "direct_message";
150
+ }
151
+ function typeFromChannelId(channelId, channelName) {
152
+ const normalized = normalizeSlackConversationId(channelId);
153
+ if (!normalized) return void 0;
154
+ if (normalized.startsWith("C")) return "public_channel";
155
+ if (normalized.startsWith("D")) return "direct_message";
156
+ if (normalized.startsWith("G")) return channelName?.trim().startsWith("mpdm-") ? "group_dm" : "private_channel_or_group_dm";
157
+ }
158
+ function toSlackEventChannelType(channelType) {
159
+ if (channelType === "channel" || channelType === "group" || channelType === "mpim" || channelType === "im") return channelType;
160
+ }
161
+ /**
162
+ * Map Slack's Events API channel_type to a source-confirmed visibility.
163
+ *
164
+ * Slack's event metadata is the only visibility authority; channel IDs never
165
+ * classify visibility.
166
+ */
167
+ function conversationVisibilityFromSlackChannelType(channelType) {
168
+ if (!channelType) return void 0;
169
+ return channelType === "channel" ? "public" : "private";
170
+ }
171
+ /** Resolve Slack's raw event channel type from a Chat SDK message-like object. */
172
+ function resolveSlackChannelTypeFromMessage(message) {
173
+ const raw = message.raw;
174
+ if (!raw || typeof raw !== "object") return;
175
+ const channelType = raw.channel_type;
176
+ return typeof channelType === "string" ? toSlackEventChannelType(channelType.trim()) : void 0;
177
+ }
178
+ /** Build Slack conversation facts available to runtime consumers. */
179
+ function resolveSlackConversationContext(input) {
180
+ const type = typeFromSlackChannelType(input.channelType) ?? typeFromChannelId(input.channelId, input.channelName);
181
+ if (!type) return void 0;
182
+ const name = normalizeConversationName(type, input.channelName);
183
+ const visibility = conversationVisibilityFromSlackChannelType(input.channelType);
184
+ return {
185
+ type,
186
+ ...name ? { name } : void 0,
187
+ ...visibility ? { visibility } : void 0
188
+ };
189
+ }
190
+ /** Build Slack conversation facts from Junior's persisted Slack thread id. */
191
+ function resolveSlackConversationContextFromThreadId(input) {
192
+ const context = resolveSlackConversationContext({
193
+ channelId: parseSlackThreadId(input.threadId)?.channelId,
194
+ channelName: input.channelName
195
+ });
196
+ if (!context || !input.visibility) return context;
197
+ return {
198
+ ...context,
199
+ type: input.visibility === "private" && context.type === "public_channel" ? "private_channel" : context.type,
200
+ visibility: input.visibility
201
+ };
202
+ }
203
+ /** Render a human label for a privacy-preserving Slack conversation type. */
204
+ function formatSlackConversationTypeLabel(type) {
205
+ if (type === "public_channel") return "Public Channel";
206
+ if (type === "private_channel") return "Private Channel";
207
+ if (type === "group_dm") return "Group DM";
208
+ if (type === "direct_message") return "Direct Message";
209
+ return "Private Channel or Group DM";
210
+ }
211
+ /** Render a Slack conversation label without exposing conversation names. */
212
+ function formatSlackConversationRedactedLabel(context) {
213
+ if (!context) return void 0;
214
+ return formatSlackConversationTypeLabel(context.type);
215
+ }
216
+ //#endregion
217
+ //#region src/chat/conversations/destination.ts
218
+ function requireLocalDestination(conversationId) {
219
+ const parsed = localDestinationSchema.safeParse({
220
+ platform: "local",
221
+ conversationId
222
+ });
223
+ if (!parsed.success) throw new Error(`Invalid local conversation id: ${conversationId}`);
224
+ return parsed.data;
225
+ }
226
+ /**
227
+ * Keep a stored Destination, or create the temporary local Destination.
228
+ *
229
+ * TODO(dcramer): Delete this module after Conversation and AgentRun use
230
+ * Location, and web and resource-event Turns no longer need a local
231
+ * Destination.
232
+ */
233
+ function resolveConversationDestination(args) {
234
+ return args.existing ?? requireLocalDestination(args.conversationId);
235
+ }
236
+ //#endregion
237
+ //#region src/api/conversations/membership.ts
238
+ /** True when the viewer is a materialized participant on this root conversation. */
239
+ function conversationHasParticipantUser(userId) {
240
+ const participant = getDb().select({ rootConversationId: juniorConversationParticipants.rootConversationId }).from(juniorConversationParticipants).where(and(eq(juniorConversationParticipants.rootConversationId, juniorConversations.conversationId), eq(juniorConversationParticipants.userId, userId)));
241
+ return exists(participant);
242
+ }
243
+ /**
244
+ * True when a verified primary-email user participates on this root
245
+ * conversation. Used only for the actorEmail feed fallback.
246
+ */
247
+ function conversationHasParticipantEmail(email) {
248
+ const participant = getDb().select({ rootConversationId: juniorConversationParticipants.rootConversationId }).from(juniorConversationParticipants).innerJoin(juniorUsers, eq(juniorUsers.id, juniorConversationParticipants.userId)).where(and(eq(juniorConversationParticipants.rootConversationId, juniorConversations.conversationId), eq(juniorUsers.primaryEmailNormalized, email)));
249
+ return exists(participant);
250
+ }
251
+ /** True when one user's personal feed has archived this root after the cutoff. */
252
+ function conversationArchivedForUser(userId, archivedAfter) {
253
+ const archive = getDb().select({ rootConversationId: juniorConversationParticipants.rootConversationId }).from(juniorConversationParticipants).where(and(eq(juniorConversationParticipants.rootConversationId, juniorConversations.conversationId), eq(juniorConversationParticipants.userId, userId), isNotNull(juniorConversationParticipants.archivedAt), archivedAfter ? gte(juniorConversationParticipants.archivedAt, archivedAfter) : void 0));
254
+ return exists(archive);
255
+ }
256
+ /** True when one user's personal feed has not archived this root. */
257
+ function conversationNotArchivedForUser(userId) {
258
+ const archive = getDb().select({ rootConversationId: juniorConversationParticipants.rootConversationId }).from(juniorConversationParticipants).where(and(eq(juniorConversationParticipants.rootConversationId, juniorConversations.conversationId), eq(juniorConversationParticipants.userId, userId), isNotNull(juniorConversationParticipants.archivedAt)));
259
+ return notExists(archive);
260
+ }
261
+ /** True when one primary-email user's feed archived this root after the cutoff. */
262
+ function conversationArchivedForEmail(email, archivedAfter) {
263
+ const archive = getDb().select({ rootConversationId: juniorConversationParticipants.rootConversationId }).from(juniorConversationParticipants).innerJoin(juniorUsers, eq(juniorUsers.id, juniorConversationParticipants.userId)).where(and(eq(juniorConversationParticipants.rootConversationId, juniorConversations.conversationId), eq(juniorUsers.primaryEmailNormalized, email), isNotNull(juniorConversationParticipants.archivedAt), archivedAfter ? gte(juniorConversationParticipants.archivedAt, archivedAfter) : void 0));
264
+ return exists(archive);
265
+ }
266
+ /** True when one primary-email user's personal feed has not archived this root. */
267
+ function conversationNotArchivedForEmail(email) {
268
+ const archive = getDb().select({ rootConversationId: juniorConversationParticipants.rootConversationId }).from(juniorConversationParticipants).innerJoin(juniorUsers, eq(juniorUsers.id, juniorConversationParticipants.userId)).where(and(eq(juniorConversationParticipants.rootConversationId, juniorConversations.conversationId), eq(juniorUsers.primaryEmailNormalized, email), isNotNull(juniorConversationParticipants.archivedAt)));
269
+ return notExists(archive);
270
+ }
271
+ /** Root-actor ownership or a materialized participant row for the same person. */
272
+ function viewerConversationMembership(args) {
273
+ if (args.actorMatch && args.participantMatch) return or(args.actorMatch, args.participantMatch);
274
+ return args.actorMatch ?? args.participantMatch;
275
+ }
276
+ /** Read personal archive timestamps for a bounded set of conversation roots. */
277
+ async function conversationArchiveTimes(db, conversationIds, subject) {
278
+ if (!subject || conversationIds.length === 0) return /* @__PURE__ */ new Map();
279
+ const query = db.select({
280
+ archivedAt: juniorConversationParticipants.archivedAt,
281
+ rootConversationId: juniorConversationParticipants.rootConversationId
282
+ }).from(juniorConversationParticipants);
283
+ const rows = "userId" in subject ? await query.where(and(eq(juniorConversationParticipants.userId, subject.userId), inArray(juniorConversationParticipants.rootConversationId, [...conversationIds]), isNotNull(juniorConversationParticipants.archivedAt))) : await query.innerJoin(juniorUsers, eq(juniorUsers.id, juniorConversationParticipants.userId)).where(and(eq(juniorUsers.primaryEmailNormalized, subject.email), inArray(juniorConversationParticipants.rootConversationId, [...conversationIds]), isNotNull(juniorConversationParticipants.archivedAt)));
284
+ return new Map(rows.flatMap((row) => row.archivedAt ? [[row.rootConversationId, row.archivedAt.getTime()]] : []));
285
+ }
286
+ /** Find which of the supplied roots include the linked viewer as a participant. */
287
+ async function conversationIdsWithParticipantUser(db, conversationIds, userId) {
288
+ if (conversationIds.length === 0) return /* @__PURE__ */ new Set();
289
+ const rows = await db.select({ rootConversationId: juniorConversationParticipants.rootConversationId }).from(juniorConversationParticipants).where(and(eq(juniorConversationParticipants.userId, userId), inArray(juniorConversationParticipants.rootConversationId, [...conversationIds])));
290
+ return new Set(rows.map((row) => row.rootConversationId));
291
+ }
292
+ //#endregion
293
+ //#region src/api/conversations/access.ts
294
+ const rootConversation = alias(juniorConversations, "access_root_conversation");
295
+ const rootDestination = alias(juniorDestinations, "access_root_destination");
296
+ const rootIdentity = alias(juniorIdentities, "access_root_identity");
297
+ /** Resolve viewer access for a bounded set of persisted conversations. */
298
+ async function readConversationAccessFromSql(db, conversationIds, viewer) {
299
+ if (conversationIds.length === 0) return /* @__PURE__ */ new Map();
300
+ const normalizedViewerEmail = viewer?.email.trim().toLowerCase() || void 0;
301
+ const rows = await db.select({
302
+ conversationId: juniorConversations.conversationId,
303
+ parentConversationId: juniorConversations.parentConversationId,
304
+ storedRootConversationId: juniorConversations.rootConversationId,
305
+ rootConversationId: rootConversation.conversationId,
306
+ rootParentConversationId: rootConversation.parentConversationId,
307
+ rootRootConversationId: rootConversation.rootConversationId,
308
+ visibility: rootDestination.visibility,
309
+ rootEmailNormalized: rootIdentity.emailNormalized,
310
+ rootEmailVerified: rootIdentity.emailVerified,
311
+ rootUserId: rootIdentity.userId
312
+ }).from(juniorConversations).leftJoin(rootConversation, eq(rootConversation.conversationId, juniorConversations.rootConversationId)).leftJoin(rootDestination, eq(rootDestination.id, rootConversation.destinationId)).leftJoin(rootIdentity, eq(rootIdentity.id, rootConversation.actorIdentityId)).where(inArray(juniorConversations.conversationId, [...conversationIds]));
313
+ const membershipConversationIds = /* @__PURE__ */ new Set();
314
+ for (const row of rows) {
315
+ membershipConversationIds.add(row.conversationId);
316
+ if (row.storedRootConversationId) membershipConversationIds.add(row.storedRootConversationId);
317
+ }
318
+ const participantRootIds = viewer !== void 0 ? await conversationIdsWithParticipantUser(db, [...membershipConversationIds], viewer.id) : /* @__PURE__ */ new Set();
319
+ return new Map(rows.map((row) => {
320
+ const validRootConversationId = row.rootConversationId !== null && row.rootConversationId === row.storedRootConversationId && row.rootRootConversationId === row.rootConversationId && row.rootParentConversationId === null && (row.parentConversationId !== null || row.storedRootConversationId === row.conversationId) ? row.rootConversationId ?? void 0 : void 0;
321
+ const visibility = validRootConversationId === void 0 ? null : row.visibility;
322
+ const isRootActor = validRootConversationId !== void 0 && viewer !== void 0 && (row.rootUserId === viewer.id || normalizedViewerEmail !== void 0 && row.rootEmailVerified === true && row.rootEmailNormalized === normalizedViewerEmail);
323
+ const isMaterializedParticipant = participantRootIds.has(row.conversationId) || validRootConversationId !== void 0 && participantRootIds.has(validRootConversationId);
324
+ const isParticipant = isRootActor || isMaterializedParticipant;
325
+ const canViewPrivateContent = isParticipant || validRootConversationId !== void 0 && canExposeConversationPayload({ visibility: visibility ?? void 0 });
326
+ return [row.conversationId, {
327
+ canViewPrivateContent,
328
+ isParticipant,
329
+ visibility
330
+ }];
331
+ }));
332
+ }
333
+ //#endregion
334
+ //#region src/chat/conversations/web-input.ts
335
+ /** Store web input in a Conversation mailbox. */
336
+ function normalizeEmail(email) {
337
+ return email.trim().toLowerCase();
338
+ }
339
+ function stableHex(...parts) {
340
+ return createHash("sha256").update(parts.join("\0")).digest("hex").slice(0, 24);
341
+ }
342
+ /**
343
+ * Build a durable Conversation id for one web Actor and create key.
344
+ *
345
+ * Retries with the same key must address the same Conversation before the
346
+ * mailbox Message id is derived.
347
+ */
348
+ function createConversationId(args) {
349
+ return `local:web:${stableHex(normalizeEmail(args.actorEmail), args.idempotencyKey)}`;
350
+ }
351
+ /**
352
+ * Build the retry-stable id for one web Message.
353
+ *
354
+ * TODO(dcramer): Replace the `api-msg` prefix after deployed request retries
355
+ * no longer need to derive ids written by the old web input code.
356
+ */
357
+ function webMessageId(args) {
358
+ return `api-msg:${stableHex(args.conversationId, args.idempotencyKey)}`;
359
+ }
360
+ /** Return the stable Turn id for one mailbox Message. */
361
+ function conversationTurnIdForMessage(messageId) {
362
+ return buildDeterministicTurnId(messageId);
363
+ }
364
+ /**
365
+ * Store the dashboard participant in the current Conversation actor field.
366
+ *
367
+ * TODO(dcramer): Remove this StoredSlackActor conversion after Conversation
368
+ * actor storage accepts web Actors directly.
369
+ */
370
+ function storedActorFromWeb(actor) {
371
+ return {
372
+ ...actor.email ? { email: normalizeEmail(actor.email) } : void 0,
373
+ ...actor.fullName ? { fullName: actor.fullName } : void 0
374
+ };
375
+ }
376
+ /** Rebuild the web Actor from durable Conversation identity. */
377
+ function webActorFromEmail(email, profile) {
378
+ const normalized = normalizeEmail(email);
379
+ return {
380
+ platform: "web",
381
+ userId: `dashboard:${stableHex(normalized)}`,
382
+ email: normalized,
383
+ ...profile?.fullName ? { fullName: profile.fullName } : void 0,
384
+ ...profile?.userName ? { userName: profile.userName } : void 0
385
+ };
386
+ }
387
+ /** Build one web mailbox entry. */
388
+ function buildWebInboundMessage(args) {
389
+ const text = args.message.trim();
390
+ if (!text) throw new Error("Web Message must not be empty");
391
+ if (!args.actor.email) throw new Error("Web Actor requires a verified email");
392
+ const destination = resolveConversationDestination({
393
+ conversationId: args.conversationId,
394
+ existing: args.destination
395
+ });
396
+ const nowMs = args.nowMs ?? Date.now();
397
+ return {
398
+ conversationId: args.conversationId,
399
+ createdAtMs: args.createdAtMs ?? nowMs,
400
+ delivery: "defer",
401
+ destination,
402
+ inboundMessageId: args.messageId,
403
+ input: {
404
+ authorId: args.actor.userId,
405
+ text,
406
+ metadata: {
407
+ authorEmail: normalizeEmail(args.actor.email),
408
+ ...args.actor.fullName && { authorFullName: args.actor.fullName },
409
+ authorUserId: args.actor.userId,
410
+ ...args.actor.userName && { authorUserName: args.actor.userName },
411
+ kind: "api_turn",
412
+ messageId: args.messageId
413
+ }
414
+ },
415
+ receivedAtMs: nowMs,
416
+ source: "web"
417
+ };
418
+ }
419
+ /** Record web activity and create a Conversation root when needed. */
420
+ async function recordWebConversationActivity(args) {
421
+ const store = args.conversationStore ?? getConversationStore();
422
+ const existing = await store.get({ conversationId: args.conversationId });
423
+ const destination = resolveConversationDestination({
424
+ conversationId: args.conversationId,
425
+ existing: existing?.destination
426
+ });
427
+ const isNewRoot = !existing;
428
+ const visibility = args.rootVisibility === "private" ? "private" : "public";
429
+ const source = isNewRoot ? createWebSource(args.conversationId, visibility) : void 0;
430
+ await store.recordActivity({
431
+ conversationId: args.conversationId,
432
+ destination,
433
+ nowMs: args.nowMs,
434
+ actor: storedActorFromWeb(args.actor),
435
+ ...isNewRoot ? { source: "web" } : void 0,
436
+ ...source ? { sessionSource: source } : void 0,
437
+ ...isNewRoot ? { visibility } : void 0
438
+ });
439
+ return destination;
440
+ }
441
+ /** Create a Conversation from web input and enqueue its first Message. */
442
+ async function createAndEnqueueConversation(input, options) {
443
+ if (!input.actor.email) throw new Error("Web Actor requires a verified email");
444
+ const conversationId = createConversationId({
445
+ actorEmail: input.actor.email,
446
+ idempotencyKey: input.idempotencyKey
447
+ });
448
+ return await appendAndEnqueueWebMessage({
449
+ actor: input.actor,
450
+ conversationId,
451
+ idempotencyKey: input.idempotencyKey,
452
+ message: input.message,
453
+ rootVisibility: input.visibility === "private" ? "private" : "public"
454
+ }, options);
455
+ }
456
+ async function appendAndEnqueueWebMessage(input, options) {
457
+ const text = input.message.trim();
458
+ if (!text) throw new Error("Web Message must not be empty");
459
+ if (!input.actor.email) throw new Error("Web Actor requires a verified email");
460
+ const nowMs = options.nowMs ?? Date.now();
461
+ const messageId = webMessageId({
462
+ conversationId: input.conversationId,
463
+ idempotencyKey: input.idempotencyKey
464
+ });
465
+ const destination = await recordWebConversationActivity({
466
+ actor: input.actor,
467
+ conversationId: input.conversationId,
468
+ conversationStore: options.conversationStore,
469
+ nowMs,
470
+ ...input.rootVisibility && { rootVisibility: input.rootVisibility }
471
+ });
472
+ const result = await (options.exclusive ? appendAndEnqueueExclusiveInboundMessage : appendAndEnqueueInboundMessage)({
473
+ message: buildWebInboundMessage({
474
+ actor: input.actor,
475
+ conversationId: input.conversationId,
476
+ destination,
477
+ message: text,
478
+ messageId,
479
+ nowMs
480
+ }),
481
+ conversationStore: options.conversationStore,
482
+ nowMs,
483
+ queue: options.queue,
484
+ state: options.state
485
+ });
486
+ const status = result.status === "appended" ? "accepted" : result.status;
487
+ return {
488
+ conversationId: input.conversationId,
489
+ messageId,
490
+ status
491
+ };
492
+ }
493
+ //#endregion
494
+ export { createVercelAttachmentStorage as C, legacyWebMailboxMetadataSchema as S, readHealthReport as T, formatSlackConversationRedactedLabel as _, webActorFromEmail as a, resolveSlackConversationContextFromThreadId as b, conversationArchivedForEmail as c, conversationHasParticipantUser as d, conversationNotArchivedForEmail as f, conversationVisibilityFromSlackChannelType as g, resolveConversationDestination as h, recordWebConversationActivity as i, conversationArchivedForUser as l, viewerConversationMembership as m, conversationTurnIdForMessage as n, readConversationAccessFromSql as o, conversationNotArchivedForUser as p, createAndEnqueueConversation as r, conversationArchiveTimes as s, appendAndEnqueueWebMessage as t, conversationHasParticipantEmail as u, resolveSlackChannelTypeFromMessage as v, GET as w, lookupSlackUser as x, resolveSlackConversationContext as y };