@sentry/junior 0.188.0 → 0.189.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 (109) hide show
  1. package/dist/{agent-hooks-5RQEVLQD.js → agent-hooks-6QRYH6PX.js} +11 -11
  2. package/dist/api/acp/conversations.d.ts +2 -2
  3. package/dist/api/conversations/list.d.ts +7 -0
  4. package/dist/api/people/shared.d.ts +54 -0
  5. package/dist/api.js +41 -52
  6. package/dist/app.d.ts +1 -1
  7. package/dist/app.js +1003 -346
  8. package/dist/{catalog-runtime-L4A6BJ3U.js → catalog-runtime-IETJLJFG.js} +4 -4
  9. package/dist/chat/agent/resume.d.ts +0 -2
  10. package/dist/chat/agent/types.d.ts +8 -9
  11. package/dist/chat/agent-dispatch/work.d.ts +1 -10
  12. package/dist/chat/agent-invocations/spawn.d.ts +1 -1
  13. package/dist/chat/agent-invocations/types.d.ts +296 -96
  14. package/dist/chat/app/conversation-work.d.ts +16 -8
  15. package/dist/chat/conversation-privacy.d.ts +7 -7
  16. package/dist/chat/conversations/destination.d.ts +12 -0
  17. package/dist/chat/conversations/projection.d.ts +2 -2
  18. package/dist/chat/conversations/sql/location.d.ts +13 -4
  19. package/dist/chat/{api-turns → conversations}/stop.d.ts +2 -2
  20. package/dist/chat/conversations/store.d.ts +4 -4
  21. package/dist/chat/conversations/structured-events.d.ts +1 -1
  22. package/dist/chat/{api-turns/authorization.d.ts → conversations/web-authorization.d.ts} +1 -0
  23. package/dist/chat/conversations/web-input.d.ts +98 -0
  24. package/dist/chat/conversations/web-mailbox.d.ts +12 -0
  25. package/dist/chat/event-tasks/tool-support.d.ts +0 -6
  26. package/dist/chat/providers/slack/runtime.d.ts +1 -0
  27. package/dist/chat/providers/slack/system-turn.d.ts +8 -0
  28. package/dist/chat/resource-events/actor.d.ts +13 -12
  29. package/dist/chat/resource-events/notification.d.ts +1 -1
  30. package/dist/chat/services/guardian-action-policy.d.ts +1 -1
  31. package/dist/chat/services/turn-session-routing.d.ts +2 -2
  32. package/dist/chat/task-execution/assistant-message.d.ts +32 -0
  33. package/dist/chat/task-execution/conversation-turn.d.ts +8 -0
  34. package/dist/chat/{api-turns → task-execution}/mailbox-input.d.ts +1 -2
  35. package/dist/chat/task-execution/mailbox-turn.d.ts +42 -0
  36. package/dist/chat/task-execution/paused-turn.d.ts +1 -1
  37. package/dist/chat/task-execution/slack-work.d.ts +0 -2
  38. package/dist/{chunk-AW77HH54.js → chunk-4RHJ3CXR.js} +14 -295
  39. package/dist/{chunk-ZX7RSBQ6.js → chunk-5CXCGDIL.js} +1 -1
  40. package/dist/{chunk-QVH6FW3T.js → chunk-5VWAY27R.js} +7 -3
  41. package/dist/{chunk-XJIPMHKI.js → chunk-6AUR6PWR.js} +3 -3
  42. package/dist/{chunk-QDHJTKPE.js → chunk-7R5JRPCY.js} +8 -6
  43. package/dist/{chunk-6JZGXLL2.js → chunk-AC7MDOJW.js} +5 -7
  44. package/dist/{chunk-Q7O74WOO.js → chunk-BYBSI5TI.js} +2 -2
  45. package/dist/{chunk-VFZAUQ5V.js → chunk-CMWIMFM6.js} +4 -182
  46. package/dist/{chunk-IUUQWIG2.js → chunk-CUYFCI4G.js} +59 -11
  47. package/dist/chunk-DA2KGZZI.js +13 -0
  48. package/dist/{chunk-FIUCHYT6.js → chunk-DYXVAO2E.js} +1 -1
  49. package/dist/{chunk-NZLZ4KYZ.js → chunk-E3PDZPGV.js} +1 -1
  50. package/dist/{chunk-Z5SKZE5B.js → chunk-GP32KD2J.js} +4 -12
  51. package/dist/{chunk-XI4PVY2T.js → chunk-KN32OWL2.js} +5 -5
  52. package/dist/chunk-LHKZKAZ4.js +285 -0
  53. package/dist/{chunk-6YGFBU3A.js → chunk-MFSET7Q7.js} +44 -334
  54. package/dist/{chunk-QCGNHSMA.js → chunk-MI6ZBDTS.js} +6 -6
  55. package/dist/{chunk-CIG6WI7Q.js → chunk-MRXMFB4L.js} +5 -5
  56. package/dist/{chunk-CVBDVIF4.js → chunk-MVPLEPSI.js} +1 -1
  57. package/dist/{chunk-32XSSFZV.js → chunk-NY2F7BZA.js} +1 -1
  58. package/dist/chunk-O35GZCKL.js +311 -0
  59. package/dist/{chunk-HCIVBCP5.js → chunk-OEIITG5F.js} +1 -1
  60. package/dist/{chunk-UVUXFRYN.js → chunk-PCPSX4ES.js} +209 -7
  61. package/dist/{chunk-QIMN3FXL.js → chunk-PY3EIEAF.js} +1 -1
  62. package/dist/{chunk-RMB7CERI.js → chunk-QNIOBNOY.js} +282 -121
  63. package/dist/{chunk-WGZJ5SU4.js → chunk-S3SGZUOI.js} +1 -1
  64. package/dist/chunk-SR6OMO5C.js +703 -0
  65. package/dist/{chunk-CDSLOYOR.js → chunk-UCWO5MKP.js} +6 -6
  66. package/dist/{chunk-PSHMPRIU.js → chunk-VKQWU4PO.js} +2 -2
  67. package/dist/{chunk-X55FOVZO.js → chunk-WDHPN2GH.js} +3 -3
  68. package/dist/{chunk-WW2Y7JIA.js → chunk-WMUAKAIF.js} +43 -44
  69. package/dist/{chunk-MQDIIFZM.js → chunk-WN6VLHOU.js} +12 -5
  70. package/dist/{chunk-W42ENMJW.js → chunk-WWYUYKKV.js} +1 -1
  71. package/dist/{chunk-ERZMACYA.js → chunk-WYDF6PGK.js} +3 -3
  72. package/dist/{chunk-SC67CH3C.js → chunk-YNZPOFF3.js} +14 -17
  73. package/dist/{chunk-3HEZWDS5.js → chunk-YYSEH2WC.js} +2 -2
  74. package/dist/{chunk-W2DDJCPS.js → chunk-YYZNESC3.js} +8 -8
  75. package/dist/{chunk-CZRWU4ZM.js → chunk-ZUPDSJUE.js} +2 -5
  76. package/dist/{chunk-QIOUTXTZ.js → chunk-ZV6PFUA4.js} +1 -1
  77. package/dist/cli/chat.js +34 -33
  78. package/dist/cli/check.js +5 -5
  79. package/dist/cli/plugins.js +12 -12
  80. package/dist/cli/snapshot-create.js +9 -9
  81. package/dist/cli/upgrade.js +6 -6
  82. package/dist/db/schema/agent-invocations.d.ts +0 -36
  83. package/dist/db/schema/conversations.d.ts +62 -0
  84. package/dist/db/schema.d.ts +31 -36
  85. package/dist/{db-YPRZAA2T.js → db-LPJKR2EO.js} +6 -6
  86. package/dist/instrumentation.js +3 -3
  87. package/dist/nitro.js +6 -6
  88. package/dist/{oauth-callback-server-DBBIR3XG.js → oauth-callback-server-DMXF4TTQ.js} +30 -28
  89. package/dist/{runner-FKVFFPLO.js → runner-L5DPVYCN.js} +25 -21
  90. package/dist/{skills-4RBAZEMV.js → skills-NXG3LBCK.js} +5 -5
  91. package/dist/spawn-VRUIGKWW.js +42 -0
  92. package/dist/task-runner-7YTBSPXW.js +36 -0
  93. package/dist/{validation-E6Z5LHZM.js → validation-CH36LTOS.js} +5 -5
  94. package/dist/version.js +1 -1
  95. package/dist/{work-OFTXWOK4.js → work-HBC5Y5VZ.js} +22 -21
  96. package/dist/{worker-OMWNOKTJ.js → worker-5RDZHLXH.js} +10 -10
  97. package/migrations/0037_conversation_location.sql +23 -0
  98. package/migrations/meta/0037_snapshot.json +3192 -0
  99. package/migrations/meta/_journal.json +7 -0
  100. package/package.json +5 -5
  101. package/dist/chat/api-turns/assistant-message.d.ts +0 -12
  102. package/dist/chat/api-turns/cancellation.d.ts +0 -24
  103. package/dist/chat/api-turns/routing.d.ts +0 -51
  104. package/dist/chat/api-turns/work.d.ts +0 -99
  105. package/dist/chat/task-execution/slack-resource-event.d.ts +0 -55
  106. package/dist/chunk-6FGN7ZEG.js +0 -221
  107. package/dist/chunk-LODJKINF.js +0 -1502
  108. package/dist/spawn-CO7QFCNJ.js +0 -41
  109. package/dist/task-runner-DUGZGI7V.js +0 -34
@@ -0,0 +1,703 @@
1
+ import {
2
+ healthReportSchema
3
+ } from "./chunk-UTL3MHPC.js";
4
+ import {
5
+ parseSlackThreadId
6
+ } from "./chunk-MVPLEPSI.js";
7
+ import {
8
+ appendAndEnqueueExclusiveInboundMessage,
9
+ appendAndEnqueueInboundMessage
10
+ } from "./chunk-WN6VLHOU.js";
11
+ import {
12
+ buildDeterministicTurnId
13
+ } from "./chunk-DA2KGZZI.js";
14
+ import {
15
+ normalizeSlackConversationId
16
+ } from "./chunk-MI6ZBDTS.js";
17
+ import {
18
+ getConversationStore,
19
+ getDb
20
+ } from "./chunk-CUYFCI4G.js";
21
+ import {
22
+ juniorConversationParticipants,
23
+ juniorConversations,
24
+ juniorIdentities,
25
+ juniorUsers
26
+ } from "./chunk-5VWAY27R.js";
27
+ import {
28
+ juniorDestinations
29
+ } from "./chunk-VFUK3X5B.js";
30
+ import {
31
+ getSlackBotToken
32
+ } from "./chunk-BYBSI5TI.js";
33
+ import {
34
+ canExposeConversationPayload
35
+ } from "./chunk-DYXVAO2E.js";
36
+ import {
37
+ logWarn
38
+ } from "./chunk-PY3EIEAF.js";
39
+
40
+ // src/handlers/health.ts
41
+ function readHealthReport() {
42
+ return healthReportSchema.parse({
43
+ status: "ok",
44
+ service: "junior",
45
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
46
+ });
47
+ }
48
+ function GET() {
49
+ return Response.json(readHealthReport());
50
+ }
51
+
52
+ // src/chat/attachments/vercel.ts
53
+ import { del, get, put } from "@vercel/blob";
54
+ function createVercelAttachmentStorage() {
55
+ return {
56
+ provider: "vercel-blob",
57
+ async put(input) {
58
+ await put(input.key, input.body, {
59
+ access: "private",
60
+ addRandomSuffix: false,
61
+ allowOverwrite: true,
62
+ contentType: input.contentType
63
+ });
64
+ },
65
+ async get(key) {
66
+ const result = await get(key, { access: "private" });
67
+ return result?.statusCode === 200 ? result.stream : null;
68
+ },
69
+ async delete(keys) {
70
+ if (keys.length === 0) return;
71
+ await del(keys);
72
+ }
73
+ };
74
+ }
75
+
76
+ // src/api/conversations/access.ts
77
+ import { eq as eq2, inArray as inArray2 } from "drizzle-orm";
78
+ import { alias } from "drizzle-orm/pg-core";
79
+
80
+ // src/api/conversations/membership.ts
81
+ import {
82
+ and,
83
+ eq,
84
+ exists,
85
+ inArray,
86
+ isNotNull,
87
+ notExists,
88
+ or
89
+ } from "drizzle-orm";
90
+ function conversationHasParticipantUser(userId) {
91
+ const participant = getDb().select({
92
+ rootConversationId: juniorConversationParticipants.rootConversationId
93
+ }).from(juniorConversationParticipants).where(
94
+ and(
95
+ eq(
96
+ juniorConversationParticipants.rootConversationId,
97
+ juniorConversations.conversationId
98
+ ),
99
+ eq(juniorConversationParticipants.userId, userId)
100
+ )
101
+ );
102
+ return exists(participant);
103
+ }
104
+ function conversationHasParticipantEmail(email) {
105
+ const participant = getDb().select({
106
+ rootConversationId: juniorConversationParticipants.rootConversationId
107
+ }).from(juniorConversationParticipants).innerJoin(
108
+ juniorUsers,
109
+ eq(juniorUsers.id, juniorConversationParticipants.userId)
110
+ ).where(
111
+ and(
112
+ eq(
113
+ juniorConversationParticipants.rootConversationId,
114
+ juniorConversations.conversationId
115
+ ),
116
+ eq(juniorUsers.primaryEmailNormalized, email)
117
+ )
118
+ );
119
+ return exists(participant);
120
+ }
121
+ function conversationArchivedForUser(userId) {
122
+ const archive = getDb().select({
123
+ rootConversationId: juniorConversationParticipants.rootConversationId
124
+ }).from(juniorConversationParticipants).where(
125
+ and(
126
+ eq(
127
+ juniorConversationParticipants.rootConversationId,
128
+ juniorConversations.conversationId
129
+ ),
130
+ eq(juniorConversationParticipants.userId, userId),
131
+ isNotNull(juniorConversationParticipants.archivedAt)
132
+ )
133
+ );
134
+ return exists(archive);
135
+ }
136
+ function conversationNotArchivedForUser(userId) {
137
+ const archive = getDb().select({
138
+ rootConversationId: juniorConversationParticipants.rootConversationId
139
+ }).from(juniorConversationParticipants).where(
140
+ and(
141
+ eq(
142
+ juniorConversationParticipants.rootConversationId,
143
+ juniorConversations.conversationId
144
+ ),
145
+ eq(juniorConversationParticipants.userId, userId),
146
+ isNotNull(juniorConversationParticipants.archivedAt)
147
+ )
148
+ );
149
+ return notExists(archive);
150
+ }
151
+ function conversationArchivedForEmail(email) {
152
+ const archive = getDb().select({
153
+ rootConversationId: juniorConversationParticipants.rootConversationId
154
+ }).from(juniorConversationParticipants).innerJoin(
155
+ juniorUsers,
156
+ eq(juniorUsers.id, juniorConversationParticipants.userId)
157
+ ).where(
158
+ and(
159
+ eq(
160
+ juniorConversationParticipants.rootConversationId,
161
+ juniorConversations.conversationId
162
+ ),
163
+ eq(juniorUsers.primaryEmailNormalized, email),
164
+ isNotNull(juniorConversationParticipants.archivedAt)
165
+ )
166
+ );
167
+ return exists(archive);
168
+ }
169
+ function conversationNotArchivedForEmail(email) {
170
+ const archive = getDb().select({
171
+ rootConversationId: juniorConversationParticipants.rootConversationId
172
+ }).from(juniorConversationParticipants).innerJoin(
173
+ juniorUsers,
174
+ eq(juniorUsers.id, juniorConversationParticipants.userId)
175
+ ).where(
176
+ and(
177
+ eq(
178
+ juniorConversationParticipants.rootConversationId,
179
+ juniorConversations.conversationId
180
+ ),
181
+ eq(juniorUsers.primaryEmailNormalized, email),
182
+ isNotNull(juniorConversationParticipants.archivedAt)
183
+ )
184
+ );
185
+ return notExists(archive);
186
+ }
187
+ function viewerConversationMembership(args) {
188
+ if (args.actorMatch && args.participantMatch) {
189
+ return or(args.actorMatch, args.participantMatch);
190
+ }
191
+ return args.actorMatch ?? args.participantMatch;
192
+ }
193
+ async function conversationArchiveTimes(db, conversationIds, subject) {
194
+ if (!subject || conversationIds.length === 0) return /* @__PURE__ */ new Map();
195
+ const query = db.select({
196
+ archivedAt: juniorConversationParticipants.archivedAt,
197
+ rootConversationId: juniorConversationParticipants.rootConversationId
198
+ }).from(juniorConversationParticipants);
199
+ const rows = "userId" in subject ? await query.where(
200
+ and(
201
+ eq(juniorConversationParticipants.userId, subject.userId),
202
+ inArray(juniorConversationParticipants.rootConversationId, [
203
+ ...conversationIds
204
+ ]),
205
+ isNotNull(juniorConversationParticipants.archivedAt)
206
+ )
207
+ ) : await query.innerJoin(
208
+ juniorUsers,
209
+ eq(juniorUsers.id, juniorConversationParticipants.userId)
210
+ ).where(
211
+ and(
212
+ eq(juniorUsers.primaryEmailNormalized, subject.email),
213
+ inArray(juniorConversationParticipants.rootConversationId, [
214
+ ...conversationIds
215
+ ]),
216
+ isNotNull(juniorConversationParticipants.archivedAt)
217
+ )
218
+ );
219
+ return new Map(
220
+ rows.flatMap(
221
+ (row) => row.archivedAt ? [[row.rootConversationId, row.archivedAt.getTime()]] : []
222
+ )
223
+ );
224
+ }
225
+ async function conversationIdsWithParticipantUser(db, conversationIds, userId) {
226
+ if (conversationIds.length === 0) {
227
+ return /* @__PURE__ */ new Set();
228
+ }
229
+ const rows = await db.select({
230
+ rootConversationId: juniorConversationParticipants.rootConversationId
231
+ }).from(juniorConversationParticipants).where(
232
+ and(
233
+ eq(juniorConversationParticipants.userId, userId),
234
+ inArray(juniorConversationParticipants.rootConversationId, [
235
+ ...conversationIds
236
+ ])
237
+ )
238
+ );
239
+ return new Set(rows.map((row) => row.rootConversationId));
240
+ }
241
+
242
+ // src/api/conversations/access.ts
243
+ var rootConversation = alias(juniorConversations, "access_root_conversation");
244
+ var rootDestination = alias(juniorDestinations, "access_root_destination");
245
+ var rootIdentity = alias(juniorIdentities, "access_root_identity");
246
+ async function readConversationAccessFromSql(db, conversationIds, viewer) {
247
+ if (conversationIds.length === 0) return /* @__PURE__ */ new Map();
248
+ const normalizedViewerEmail = viewer?.email.trim().toLowerCase() || void 0;
249
+ const rows = await db.select({
250
+ conversationId: juniorConversations.conversationId,
251
+ parentConversationId: juniorConversations.parentConversationId,
252
+ storedRootConversationId: juniorConversations.rootConversationId,
253
+ rootConversationId: rootConversation.conversationId,
254
+ rootParentConversationId: rootConversation.parentConversationId,
255
+ rootRootConversationId: rootConversation.rootConversationId,
256
+ visibility: rootDestination.visibility,
257
+ rootEmailNormalized: rootIdentity.emailNormalized,
258
+ rootEmailVerified: rootIdentity.emailVerified,
259
+ rootUserId: rootIdentity.userId
260
+ }).from(juniorConversations).leftJoin(
261
+ rootConversation,
262
+ eq2(
263
+ rootConversation.conversationId,
264
+ juniorConversations.rootConversationId
265
+ )
266
+ ).leftJoin(
267
+ rootDestination,
268
+ eq2(rootDestination.id, rootConversation.destinationId)
269
+ ).leftJoin(
270
+ rootIdentity,
271
+ eq2(rootIdentity.id, rootConversation.actorIdentityId)
272
+ ).where(inArray2(juniorConversations.conversationId, [...conversationIds]));
273
+ const membershipConversationIds = /* @__PURE__ */ new Set();
274
+ for (const row of rows) {
275
+ membershipConversationIds.add(row.conversationId);
276
+ if (row.storedRootConversationId) {
277
+ membershipConversationIds.add(row.storedRootConversationId);
278
+ }
279
+ }
280
+ const participantRootIds = viewer !== void 0 ? await conversationIdsWithParticipantUser(
281
+ db,
282
+ [...membershipConversationIds],
283
+ viewer.id
284
+ ) : /* @__PURE__ */ new Set();
285
+ return new Map(
286
+ rows.map((row) => {
287
+ const hasValidRoot = row.rootConversationId !== null && row.rootConversationId === row.storedRootConversationId && row.rootRootConversationId === row.rootConversationId && row.rootParentConversationId === null && (row.parentConversationId !== null || row.storedRootConversationId === row.conversationId);
288
+ const validRootConversationId = hasValidRoot ? row.rootConversationId ?? void 0 : void 0;
289
+ const visibility = validRootConversationId === void 0 ? null : row.visibility;
290
+ const isRootActor = validRootConversationId !== void 0 && viewer !== void 0 && (row.rootUserId === viewer.id || normalizedViewerEmail !== void 0 && row.rootEmailVerified === true && row.rootEmailNormalized === normalizedViewerEmail);
291
+ const isMaterializedParticipant = participantRootIds.has(row.conversationId) || validRootConversationId !== void 0 && participantRootIds.has(validRootConversationId);
292
+ const isParticipant = isRootActor || isMaterializedParticipant;
293
+ const canViewPrivateContent = isParticipant || validRootConversationId !== void 0 && canExposeConversationPayload({
294
+ visibility: visibility ?? void 0
295
+ });
296
+ return [
297
+ row.conversationId,
298
+ {
299
+ canViewPrivateContent,
300
+ isParticipant,
301
+ visibility
302
+ }
303
+ ];
304
+ })
305
+ );
306
+ }
307
+
308
+ // src/chat/conversations/web-input.ts
309
+ import { createHash } from "crypto";
310
+ import { createWebSource } from "@sentry/junior-plugin-api";
311
+
312
+ // src/chat/conversations/destination.ts
313
+ import {
314
+ localDestinationSchema
315
+ } from "@sentry/junior-plugin-api";
316
+ function requireLocalDestination(conversationId) {
317
+ const parsed = localDestinationSchema.safeParse({
318
+ platform: "local",
319
+ conversationId
320
+ });
321
+ if (!parsed.success) {
322
+ throw new Error(`Invalid local conversation id: ${conversationId}`);
323
+ }
324
+ return parsed.data;
325
+ }
326
+ function resolveConversationDestination(args) {
327
+ return args.existing ?? requireLocalDestination(args.conversationId);
328
+ }
329
+
330
+ // src/chat/conversations/web-input.ts
331
+ function normalizeEmail(email) {
332
+ return email.trim().toLowerCase();
333
+ }
334
+ function stableHex(...parts) {
335
+ return createHash("sha256").update(parts.join("\0")).digest("hex").slice(0, 24);
336
+ }
337
+ function createConversationId(args) {
338
+ return `local:web:${stableHex(
339
+ normalizeEmail(args.actorEmail),
340
+ args.idempotencyKey
341
+ )}`;
342
+ }
343
+ function webMessageId(args) {
344
+ return `api-msg:${stableHex(args.conversationId, args.idempotencyKey)}`;
345
+ }
346
+ function conversationTurnIdForMessage(messageId) {
347
+ return buildDeterministicTurnId(messageId);
348
+ }
349
+ function storedActorFromWeb(actor) {
350
+ return {
351
+ ...actor.email ? { email: normalizeEmail(actor.email) } : void 0,
352
+ ...actor.fullName ? { fullName: actor.fullName } : void 0
353
+ };
354
+ }
355
+ function webActorFromEmail(email, profile) {
356
+ const normalized = normalizeEmail(email);
357
+ return {
358
+ platform: "web",
359
+ userId: `dashboard:${stableHex(normalized)}`,
360
+ email: normalized,
361
+ ...profile?.fullName ? { fullName: profile.fullName } : void 0,
362
+ ...profile?.userName ? { userName: profile.userName } : void 0
363
+ };
364
+ }
365
+ function buildWebInboundMessage(args) {
366
+ const text = args.message.trim();
367
+ if (!text) {
368
+ throw new Error("Web Message must not be empty");
369
+ }
370
+ if (!args.actor.email) {
371
+ throw new Error("Web Actor requires a verified email");
372
+ }
373
+ const destination = resolveConversationDestination({
374
+ conversationId: args.conversationId,
375
+ existing: args.destination
376
+ });
377
+ const nowMs = args.nowMs ?? Date.now();
378
+ return {
379
+ conversationId: args.conversationId,
380
+ createdAtMs: args.createdAtMs ?? nowMs,
381
+ delivery: "defer",
382
+ // TODO(dcramer): Remove InboundMessage.destination after workers read the
383
+ // Conversation Location and no mailbox reader requires Destination.
384
+ destination,
385
+ inboundMessageId: args.messageId,
386
+ input: {
387
+ authorId: args.actor.userId,
388
+ text,
389
+ metadata: {
390
+ authorEmail: normalizeEmail(args.actor.email),
391
+ ...args.actor.fullName && { authorFullName: args.actor.fullName },
392
+ authorUserId: args.actor.userId,
393
+ ...args.actor.userName && { authorUserName: args.actor.userName },
394
+ // TODO(dcramer): Remove api_turn after deployed mailbox readers use
395
+ // Inbound message Source.kind to identify web input.
396
+ kind: "api_turn",
397
+ messageId: args.messageId
398
+ }
399
+ },
400
+ receivedAtMs: nowMs,
401
+ // TODO(dcramer): Rename this stored field to publish after deployed
402
+ // mailbox readers and writers use the new name.
403
+ publishExternally: false,
404
+ // TODO(dcramer): Replace this string after deployed mailbox readers and
405
+ // writers use a complete web Source.
406
+ source: "web"
407
+ };
408
+ }
409
+ async function recordWebConversationActivity(args) {
410
+ const store = args.conversationStore ?? getConversationStore();
411
+ const existing = await store.get({ conversationId: args.conversationId });
412
+ const destination = resolveConversationDestination({
413
+ conversationId: args.conversationId,
414
+ existing: existing?.destination
415
+ });
416
+ const isNewRoot = !existing;
417
+ const visibility = args.rootVisibility === "private" ? "private" : "public";
418
+ const source = isNewRoot ? createWebSource(args.conversationId, visibility) : void 0;
419
+ await store.recordActivity({
420
+ conversationId: args.conversationId,
421
+ // TODO(dcramer): Remove the Conversation destination write after all
422
+ // readers use its stored Location.
423
+ destination,
424
+ nowMs: args.nowMs,
425
+ actor: storedActorFromWeb(args.actor),
426
+ // A dashboard continuation does not replace the root Source.
427
+ // TODO(dcramer): Remove the Conversation source and sessionSource writes
428
+ // after every Turn stores Source and all resume readers use that Turn fact.
429
+ ...isNewRoot ? { source: "web" } : void 0,
430
+ ...source ? { sessionSource: source } : void 0,
431
+ ...isNewRoot ? { visibility } : void 0
432
+ });
433
+ return destination;
434
+ }
435
+ async function createAndEnqueueConversation(input, options) {
436
+ if (!input.actor.email) {
437
+ throw new Error("Web Actor requires a verified email");
438
+ }
439
+ const conversationId = createConversationId({
440
+ actorEmail: input.actor.email,
441
+ idempotencyKey: input.idempotencyKey
442
+ });
443
+ return await appendAndEnqueueWebMessage(
444
+ {
445
+ actor: input.actor,
446
+ conversationId,
447
+ idempotencyKey: input.idempotencyKey,
448
+ message: input.message,
449
+ rootVisibility: input.visibility === "private" ? "private" : "public"
450
+ },
451
+ options
452
+ );
453
+ }
454
+ async function appendAndEnqueueWebMessage(input, options) {
455
+ const text = input.message.trim();
456
+ if (!text) {
457
+ throw new Error("Web Message must not be empty");
458
+ }
459
+ if (!input.actor.email) {
460
+ throw new Error("Web Actor requires a verified email");
461
+ }
462
+ const nowMs = options.nowMs ?? Date.now();
463
+ const messageId = webMessageId({
464
+ conversationId: input.conversationId,
465
+ idempotencyKey: input.idempotencyKey
466
+ });
467
+ const destination = await recordWebConversationActivity({
468
+ actor: input.actor,
469
+ conversationId: input.conversationId,
470
+ conversationStore: options.conversationStore,
471
+ nowMs,
472
+ ...input.rootVisibility && { rootVisibility: input.rootVisibility }
473
+ });
474
+ const enqueue = options.exclusive ? appendAndEnqueueExclusiveInboundMessage : appendAndEnqueueInboundMessage;
475
+ const result = await enqueue({
476
+ message: buildWebInboundMessage({
477
+ actor: input.actor,
478
+ conversationId: input.conversationId,
479
+ destination,
480
+ message: text,
481
+ messageId,
482
+ nowMs
483
+ }),
484
+ conversationStore: options.conversationStore,
485
+ nowMs,
486
+ queue: options.queue,
487
+ state: options.state
488
+ });
489
+ const status = result.status === "appended" ? "accepted" : result.status;
490
+ return {
491
+ conversationId: input.conversationId,
492
+ messageId,
493
+ status
494
+ };
495
+ }
496
+
497
+ // src/chat/slack/user.ts
498
+ import { z } from "zod";
499
+ var slackUserInfoSchema = z.object({
500
+ ok: z.literal(true),
501
+ user: z.object({
502
+ name: z.string().nullish(),
503
+ profile: z.object({
504
+ email: z.string().nullish(),
505
+ real_name: z.string().nullish()
506
+ }).optional()
507
+ })
508
+ });
509
+ var USER_CACHE_TTL_MS = 5 * 60 * 1e3;
510
+ var userCache = /* @__PURE__ */ new Map();
511
+ function userCacheKey(teamId, userId) {
512
+ return `${teamId}:${userId}`;
513
+ }
514
+ function readFromCache(teamId, userId) {
515
+ const hit = userCache.get(userCacheKey(teamId, userId));
516
+ if (!hit) return null;
517
+ if (hit.expiresAt < Date.now()) {
518
+ userCache.delete(userCacheKey(teamId, userId));
519
+ return null;
520
+ }
521
+ return hit.value;
522
+ }
523
+ function writeToCache(teamId, userId, value) {
524
+ userCache.set(userCacheKey(teamId, userId), {
525
+ value,
526
+ expiresAt: Date.now() + USER_CACHE_TTL_MS
527
+ });
528
+ }
529
+ async function lookupSlackUser(teamId, userId) {
530
+ if (!teamId || !userId) {
531
+ return null;
532
+ }
533
+ const cached = readFromCache(teamId, userId);
534
+ if (cached) {
535
+ return cached;
536
+ }
537
+ const token = getSlackBotToken();
538
+ if (!token) {
539
+ return null;
540
+ }
541
+ try {
542
+ const response = await fetch(
543
+ `https://slack.com/api/users.info?user=${encodeURIComponent(userId)}`,
544
+ {
545
+ headers: {
546
+ authorization: `Bearer ${token}`
547
+ }
548
+ }
549
+ );
550
+ if (!response.ok) {
551
+ logWarn("slack.user.lookup.failed", {
552
+ "enduser.id": userId,
553
+ "app.slack.team_id": teamId,
554
+ "http.response.status_code": response.status
555
+ });
556
+ return null;
557
+ }
558
+ const parsed = slackUserInfoSchema.safeParse(await response.json());
559
+ if (!parsed.success) {
560
+ return null;
561
+ }
562
+ const payload = parsed.data;
563
+ const userName = payload.user.name?.trim() || void 0;
564
+ const fullName = payload.user.profile?.real_name?.trim() || void 0;
565
+ const result = {
566
+ userName,
567
+ fullName,
568
+ email: payload.user.profile?.email?.trim() || void 0
569
+ };
570
+ writeToCache(teamId, userId, result);
571
+ return result;
572
+ } catch (error) {
573
+ logWarn("slack.user.lookup.failed", {
574
+ "enduser.id": userId,
575
+ "app.slack.team_id": teamId,
576
+ "exception.message": error instanceof Error ? error.message : String(error)
577
+ });
578
+ return null;
579
+ }
580
+ }
581
+
582
+ // src/chat/conversations/web-mailbox.ts
583
+ import { z as z2 } from "zod";
584
+ var legacyWebMailboxMetadataSchema = z2.object({
585
+ authorEmail: z2.string().email(),
586
+ authorFullName: z2.string().min(1).optional(),
587
+ authorUserId: z2.string().min(1),
588
+ authorUserName: z2.string().min(1).optional(),
589
+ kind: z2.literal("api_turn"),
590
+ messageId: z2.string().min(1)
591
+ }).strict();
592
+
593
+ // src/chat/slack/conversation-context.ts
594
+ function normalizeConversationName(type, channelName) {
595
+ const trimmed = channelName?.trim();
596
+ if (!trimmed) return void 0;
597
+ if (type === "public_channel" || type === "private_channel" || type === "private_channel_or_group_dm") {
598
+ return trimmed.startsWith("#") ? trimmed : `#${trimmed}`;
599
+ }
600
+ return trimmed;
601
+ }
602
+ function typeFromSlackChannelType(channelType) {
603
+ if (channelType === "channel") return "public_channel";
604
+ if (channelType === "group") return "private_channel";
605
+ if (channelType === "mpim") return "group_dm";
606
+ if (channelType === "im") return "direct_message";
607
+ return void 0;
608
+ }
609
+ function typeFromChannelId(channelId, channelName) {
610
+ const normalized = normalizeSlackConversationId(channelId);
611
+ if (!normalized) return void 0;
612
+ if (normalized.startsWith("C")) return "public_channel";
613
+ if (normalized.startsWith("D")) return "direct_message";
614
+ if (normalized.startsWith("G")) {
615
+ return channelName?.trim().startsWith("mpdm-") ? "group_dm" : "private_channel_or_group_dm";
616
+ }
617
+ return void 0;
618
+ }
619
+ function toSlackEventChannelType(channelType) {
620
+ if (channelType === "channel" || channelType === "group" || channelType === "mpim" || channelType === "im") {
621
+ return channelType;
622
+ }
623
+ return void 0;
624
+ }
625
+ function conversationVisibilityFromSlackChannelType(channelType) {
626
+ if (!channelType) return void 0;
627
+ return channelType === "channel" ? "public" : "private";
628
+ }
629
+ function resolveSlackChannelTypeFromMessage(message) {
630
+ const raw = message.raw;
631
+ if (!raw || typeof raw !== "object") {
632
+ return void 0;
633
+ }
634
+ const channelType = raw.channel_type;
635
+ return typeof channelType === "string" ? toSlackEventChannelType(channelType.trim()) : void 0;
636
+ }
637
+ function resolveSlackConversationContext(input) {
638
+ const type = typeFromSlackChannelType(input.channelType) ?? typeFromChannelId(input.channelId, input.channelName);
639
+ if (!type) return void 0;
640
+ const name = normalizeConversationName(type, input.channelName);
641
+ const visibility = conversationVisibilityFromSlackChannelType(
642
+ input.channelType
643
+ );
644
+ return {
645
+ type,
646
+ ...name ? { name } : void 0,
647
+ ...visibility ? { visibility } : void 0
648
+ };
649
+ }
650
+ function resolveSlackConversationContextFromThreadId(input) {
651
+ const slackThread = parseSlackThreadId(input.threadId);
652
+ const context = resolveSlackConversationContext({
653
+ channelId: slackThread?.channelId,
654
+ channelName: input.channelName
655
+ });
656
+ if (!context || !input.visibility) {
657
+ return context;
658
+ }
659
+ return {
660
+ ...context,
661
+ type: input.visibility === "private" && context.type === "public_channel" ? "private_channel" : context.type,
662
+ visibility: input.visibility
663
+ };
664
+ }
665
+ function formatSlackConversationTypeLabel(type) {
666
+ if (type === "public_channel") return "Public Channel";
667
+ if (type === "private_channel") return "Private Channel";
668
+ if (type === "group_dm") return "Group DM";
669
+ if (type === "direct_message") return "Direct Message";
670
+ return "Private Channel or Group DM";
671
+ }
672
+ function formatSlackConversationRedactedLabel(context) {
673
+ if (!context) return void 0;
674
+ return formatSlackConversationTypeLabel(context.type);
675
+ }
676
+
677
+ export {
678
+ readHealthReport,
679
+ GET,
680
+ createVercelAttachmentStorage,
681
+ lookupSlackUser,
682
+ conversationVisibilityFromSlackChannelType,
683
+ resolveSlackChannelTypeFromMessage,
684
+ resolveSlackConversationContext,
685
+ resolveSlackConversationContextFromThreadId,
686
+ formatSlackConversationRedactedLabel,
687
+ legacyWebMailboxMetadataSchema,
688
+ resolveConversationDestination,
689
+ conversationHasParticipantUser,
690
+ conversationHasParticipantEmail,
691
+ conversationArchivedForUser,
692
+ conversationNotArchivedForUser,
693
+ conversationArchivedForEmail,
694
+ conversationNotArchivedForEmail,
695
+ viewerConversationMembership,
696
+ conversationArchiveTimes,
697
+ readConversationAccessFromSql,
698
+ conversationTurnIdForMessage,
699
+ webActorFromEmail,
700
+ recordWebConversationActivity,
701
+ createAndEnqueueConversation,
702
+ appendAndEnqueueWebMessage
703
+ };