@sentry/junior 0.178.0 → 0.180.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 (138) hide show
  1. package/dist/{agent-hooks-PBT4NNBV.js → agent-hooks-DKDDRKZA.js} +16 -18
  2. package/dist/api/schema.js +0 -1
  3. package/dist/api.js +401 -366
  4. package/dist/app.d.ts +4 -0
  5. package/dist/app.js +783 -1367
  6. package/dist/catalog-runtime-WXKGUF3W.js +14 -0
  7. package/dist/chat/agent/sandbox.d.ts +0 -2
  8. package/dist/chat/api-turns/routing.d.ts +4 -0
  9. package/dist/chat/api-turns/work.d.ts +5 -0
  10. package/dist/chat/config.d.ts +5 -2
  11. package/dist/chat/configuration/defaults.d.ts +1 -1
  12. package/dist/chat/conversations/projection.d.ts +6 -4
  13. package/dist/chat/ingress/message-router.d.ts +3 -0
  14. package/dist/chat/model-profile.d.ts +8 -10
  15. package/dist/chat/pi/conversation-events.d.ts +3 -2
  16. package/dist/chat/plugins/task-queue.d.ts +40 -3
  17. package/dist/chat/plugins/task-runner.d.ts +1 -1
  18. package/dist/chat/queue/callback.d.ts +26 -0
  19. package/dist/chat/queue/sign.d.ts +30 -0
  20. package/dist/chat/sandbox/sandbox.d.ts +0 -2
  21. package/dist/chat/sandbox/session.d.ts +0 -2
  22. package/dist/chat/sandbox/skill-sync.d.ts +1 -1
  23. package/dist/chat/sandbox/snapshot/events.d.ts +23 -0
  24. package/dist/chat/sandbox/snapshot/job-callback.d.ts +5 -0
  25. package/dist/chat/sandbox/snapshot/job-queue.d.ts +26 -0
  26. package/dist/chat/sandbox/snapshot/job-runner.d.ts +11 -0
  27. package/dist/chat/sandbox/snapshot/not-ready-error.d.ts +6 -0
  28. package/dist/chat/sandbox/snapshot/workspace.d.ts +9 -3
  29. package/dist/chat/scheduled-tasks/personal.d.ts +5 -8
  30. package/dist/chat/scheduled-tasks/runs.d.ts +49 -0
  31. package/dist/chat/scheduled-tasks/schedule-intent.d.ts +1 -1
  32. package/dist/chat/scheduled-tasks/tasks.d.ts +27 -0
  33. package/dist/chat/scheduled-tasks/tool-support.d.ts +0 -4
  34. package/dist/chat/scheduled-tasks/tools/create.d.ts +1 -1
  35. package/dist/chat/scheduled-tasks/tools/update.d.ts +1 -1
  36. package/dist/chat/scheduled-tasks/types.d.ts +127 -78
  37. package/dist/chat/services/turn-router.d.ts +4 -3
  38. package/dist/chat/task-execution/queue-signing.d.ts +10 -21
  39. package/dist/chat/task-execution/state.d.ts +48 -0
  40. package/dist/chat/task-execution/store.d.ts +28 -2
  41. package/dist/chat/task-execution/worker.d.ts +4 -1
  42. package/dist/chat/tools/handoff/tool.d.ts +1 -1
  43. package/dist/{chunk-534BO42N.js → chunk-2AYEVN7K.js} +8 -8
  44. package/dist/{chunk-D3UJTX4U.js → chunk-2YIRH4E3.js} +46 -46
  45. package/dist/{chunk-4K6VNP3S.js → chunk-3ILJBWP7.js} +35 -227
  46. package/dist/{chunk-FQ6BMP5P.js → chunk-5PUMIMNQ.js} +2724 -1257
  47. package/dist/{chunk-G3CQEUHI.js → chunk-5TUR4M3M.js} +8 -61
  48. package/dist/{chunk-6ZWRNC4D.js → chunk-6Y5B5PEX.js} +1 -1
  49. package/dist/{chunk-KEQAA42D.js → chunk-6YCEWOLO.js} +4 -4
  50. package/dist/chunk-7H2VZTWN.js +103 -0
  51. package/dist/{chunk-22T3HB44.js → chunk-CCDXTDSY.js} +8 -8
  52. package/dist/{chunk-CGA4ERJ5.js → chunk-CEMMLV5L.js} +1 -1
  53. package/dist/chunk-CK6EINVZ.js +1181 -0
  54. package/dist/{chunk-FIANEE7Z.js → chunk-CM5HNV2W.js} +6 -6
  55. package/dist/{chunk-PRDKO3QB.js → chunk-CP73FKME.js} +144 -218
  56. package/dist/{chunk-XWUJWHFD.js → chunk-DHLOGWYH.js} +36 -36
  57. package/dist/chunk-DKIJ2RP5.js +68 -0
  58. package/dist/{chunk-ABLODWPO.js → chunk-DRHEGXTZ.js} +1 -1
  59. package/dist/{chunk-OOUNJNMR.js → chunk-DWPN6SCH.js} +1 -1
  60. package/dist/chunk-EWHP2IRG.js +209 -0
  61. package/dist/{chunk-ZCHHVTMF.js → chunk-FGC3PHNH.js} +3 -3
  62. package/dist/{chunk-H6ZXBFGX.js → chunk-GA5NS74S.js} +1 -1
  63. package/dist/{chunk-BFLMRTPD.js → chunk-GPXAHUQH.js} +84 -274
  64. package/dist/chunk-GUKXEPMX.js +218 -0
  65. package/dist/{chunk-3ZZEPLLB.js → chunk-I6IFTAPF.js} +108 -76
  66. package/dist/{chunk-RGXOLPWO.js → chunk-IANUX7HX.js} +5 -5
  67. package/dist/{chunk-UMG7M32G.js → chunk-KCL6AWYW.js} +74 -68
  68. package/dist/{chunk-BUGO42R6.js → chunk-L7LTGN4Z.js} +129 -31
  69. package/dist/{chunk-QCCLDJHB.js → chunk-LGVQLNLS.js} +42 -41
  70. package/dist/chunk-M3QYTBQ6.js +55 -0
  71. package/dist/{chunk-AVRWMQ4U.js → chunk-MXEUIGHO.js} +2 -2
  72. package/dist/{chunk-M63DETLL.js → chunk-N7TMSOZH.js} +6 -6
  73. package/dist/{chunk-JVXZ3WDH.js → chunk-O4QYZTDK.js} +33 -32
  74. package/dist/chunk-PEQXYSSE.js +45 -0
  75. package/dist/{chunk-OGRD5PFM.js → chunk-QSO7B5JO.js} +1 -1
  76. package/dist/{chunk-UGWBJAGN.js → chunk-RAZG3YHC.js} +100 -79
  77. package/dist/{chunk-XCNS3CXE.js → chunk-S7YRTOSQ.js} +69 -73
  78. package/dist/{chunk-LAOP4EPL.js → chunk-SDBKZ5LV.js} +108 -5
  79. package/dist/{chunk-RE4XWXBV.js → chunk-TC5DE5KJ.js} +22 -18
  80. package/dist/{chunk-H4HBKISO.js → chunk-TXKUKMVO.js} +1 -1
  81. package/dist/chunk-U5EUCUBH.js +164 -0
  82. package/dist/{chunk-BHHWCNXI.js → chunk-VG722ONZ.js} +18 -16
  83. package/dist/{chunk-U3UDPGZG.js → chunk-VGPPWWHF.js} +16 -16
  84. package/dist/{chunk-AQHSXA3J.js → chunk-WVAMRKYG.js} +9 -54
  85. package/dist/chunk-X6GDDWQH.js +60 -0
  86. package/dist/{chunk-6OYVH66J.js → chunk-ZE2KOV3G.js} +1 -1
  87. package/dist/{chunk-IQRPDKPJ.js → chunk-ZM7ZURJS.js} +4 -3
  88. package/dist/cli/chat.js +47 -43
  89. package/dist/cli/check.js +13 -15
  90. package/dist/cli/env.js +0 -1
  91. package/dist/cli/init.js +0 -1
  92. package/dist/cli/main.js +2 -3
  93. package/dist/cli/plugins.js +18 -20
  94. package/dist/cli/run.js +0 -1
  95. package/dist/cli/snapshot-create.js +14 -16
  96. package/dist/cli/upgrade.js +16 -18
  97. package/dist/{conversation-work-KATFHLG3.js → conversation-work-VIOQ5ZF3.js} +0 -2
  98. package/dist/db/schema/scheduled-tasks.d.ts +29 -5
  99. package/dist/db/schema.d.ts +28 -4
  100. package/dist/{db-QVYOWATH.js → db-6SBHIAJK.js} +8 -10
  101. package/dist/deployment.d.ts +1 -0
  102. package/dist/{discovery-GUFTFBTL.js → discovery-DWVDM7UK.js} +1 -2
  103. package/dist/{experimental-XVDOSRBS.js → experimental-SIJSRDJD.js} +0 -1
  104. package/dist/instrumentation.js +7 -9
  105. package/dist/nitro.js +39 -13
  106. package/dist/{oauth-callback-server-PLPAFXO6.js → oauth-callback-server-WSDQPZZS.js} +34 -32
  107. package/dist/{oauth-relay-3ZU72UGL.js → oauth-relay-5XPVRPYB.js} +0 -1
  108. package/dist/{plugins-HW4VWW6G.js → plugins-YTYJMMJF.js} +1 -2
  109. package/dist/{runner-AGNEIOZS.js → runner-GWTSWESR.js} +39 -33
  110. package/dist/{sandbox-egress-signals-DXOOBSBI.js → sandbox-egress-signals-FIL7EGPR.js} +5 -6
  111. package/dist/{sentry-JMWTS32N.js → sentry-MB3NL3IR.js} +1 -2
  112. package/dist/skills-F32BPFYW.js +27 -0
  113. package/dist/spawn-X5BPP5OE.js +37 -0
  114. package/dist/task-runner-2FPBV5PP.js +32 -0
  115. package/dist/validation-62WO524A.js +17 -0
  116. package/dist/vercel.js +0 -1
  117. package/dist/version.js +1 -2
  118. package/dist/work-3C3CMZ24.js +45 -0
  119. package/dist/worker-MFAI6ZEK.js +22 -0
  120. package/package.json +7 -6
  121. package/dist/catalog-runtime-M7QGKPFZ.js +0 -16
  122. package/dist/chat/plugins/task-callback.d.ts +0 -5
  123. package/dist/chat/plugins/task-message.d.ts +0 -23
  124. package/dist/chat/plugins/task-signing.d.ts +0 -31
  125. package/dist/chat/sandbox/snapshot/waiting-error.d.ts +0 -15
  126. package/dist/chat/scheduled-tasks/store.d.ts +0 -78
  127. package/dist/chat/services/workspace-snapshot-wait.d.ts +0 -27
  128. package/dist/chunk-5I6ORJUY.js +0 -127
  129. package/dist/chunk-6GWA276C.js +0 -72
  130. package/dist/chunk-AGJ4OQ2V.js +0 -51
  131. package/dist/chunk-MLKGABMK.js +0 -9
  132. package/dist/chunk-RGV6KHDM.js +0 -1944
  133. package/dist/chunk-WR76IVA5.js +0 -130
  134. package/dist/skills-U5KQ66EM.js +0 -29
  135. package/dist/spawn-ZRYYZP2B.js +0 -37
  136. package/dist/validation-QE4U45KS.js +0 -19
  137. package/dist/work-ATAXAHJJ.js +0 -45
  138. package/dist/worker-LIL5AOER.js +0 -24
@@ -0,0 +1,1181 @@
1
+ import {
2
+ ToolInputError,
3
+ canRouteResourceEvents,
4
+ getPlugins
5
+ } from "./chunk-DHLOGWYH.js";
6
+ import {
7
+ getDb
8
+ } from "./chunk-S7YRTOSQ.js";
9
+ import {
10
+ juniorAttachments,
11
+ juniorConversations,
12
+ juniorEventTasks,
13
+ juniorSchedulerRuns,
14
+ juniorSchedulerTasks,
15
+ juniorStats
16
+ } from "./chunk-FGC3PHNH.js";
17
+ import {
18
+ juniorDestinations
19
+ } from "./chunk-VFUK3X5B.js";
20
+ import {
21
+ getSlackBotToken
22
+ } from "./chunk-I6IFTAPF.js";
23
+ import {
24
+ logWarn
25
+ } from "./chunk-RAZG3YHC.js";
26
+
27
+ // src/chat/resource-events/catalog.ts
28
+ import {
29
+ normalizeResourceEventIdentifier
30
+ } from "@sentry/junior-plugin-api";
31
+ import { z } from "zod";
32
+ function enumSchema(values, unavailableMessage) {
33
+ if (values.length === 0) {
34
+ return z.string().refine(() => false, unavailableMessage);
35
+ }
36
+ return z.enum(values);
37
+ }
38
+ function eventNamespaceSchema(catalog) {
39
+ return enumSchema(
40
+ Object.keys(catalog).sort(),
41
+ "No resource event namespaces are enabled."
42
+ );
43
+ }
44
+ function registeredEventTypeSchema(catalog) {
45
+ return enumSchema(
46
+ [
47
+ ...new Set(
48
+ Object.values(catalog).flatMap(
49
+ (registration) => registration.resourceTypes.flatMap(
50
+ (resourceType) => resourceType.supportedEvents
51
+ )
52
+ )
53
+ )
54
+ ].sort(),
55
+ "No resource event types are enabled."
56
+ );
57
+ }
58
+ function registeredResourceTypeSchema(catalog) {
59
+ return enumSchema(
60
+ [
61
+ ...new Set(
62
+ Object.values(catalog).flatMap(
63
+ (registration) => registration.resourceTypes.map((resourceType) => resourceType.type)
64
+ )
65
+ )
66
+ ].sort(),
67
+ "No resource types are enabled."
68
+ );
69
+ }
70
+ function pluginSupportsEvent(catalog, namespace, resourceType, eventType) {
71
+ return catalog[namespace]?.resourceTypes.find((candidate) => candidate.type === resourceType)?.supportedEvents.includes(eventType) ?? false;
72
+ }
73
+ function resourceEventGuidance(catalog, namespace, resourceType, eventType) {
74
+ return catalog[namespace]?.resourceTypes.find(
75
+ (candidate) => candidate.type === resourceType
76
+ )?.guidance?.[eventType];
77
+ }
78
+ function normalizeEventIdentifier(catalog, namespace, identifier) {
79
+ return normalizeResourceEventIdentifier(catalog[namespace], identifier);
80
+ }
81
+
82
+ // src/chat/resource-events/runtime-catalog.ts
83
+ function getResourceEventCatalog() {
84
+ if (!canRouteResourceEvents()) return {};
85
+ return Object.fromEntries(
86
+ getPlugins().flatMap((plugin) => {
87
+ const registration = plugin.resourceEvents;
88
+ if (!registration || registration.isEnabled?.() === false) {
89
+ return [];
90
+ }
91
+ return [[plugin.manifest.name, registration]];
92
+ })
93
+ );
94
+ }
95
+
96
+ // src/chat/event-tasks/store.ts
97
+ import { and, asc, desc, eq, inArray, or, sql } from "drizzle-orm";
98
+
99
+ // src/chat/event-tasks/types.ts
100
+ import {
101
+ actorUserIdSchema,
102
+ destinationVisibilitySchema,
103
+ resourceEventTypeSchema,
104
+ slackDestinationSchema
105
+ } from "@sentry/junior-plugin-api";
106
+ import { z as z2 } from "zod";
107
+ var EVENT_TASK_IDENTIFIER_MAX_LENGTH = 300;
108
+ var eventTaskPrincipalSchema = z2.object({
109
+ slackUserId: actorUserIdSchema,
110
+ fullName: z2.string().optional(),
111
+ userName: z2.string().optional()
112
+ }).strict();
113
+ var eventTaskTriggerSchema = z2.object({
114
+ events: z2.array(resourceEventTypeSchema).min(1),
115
+ label: z2.string().min(1),
116
+ namespace: z2.string().min(1),
117
+ identifier: z2.string().min(1).max(EVENT_TASK_IDENTIFIER_MAX_LENGTH),
118
+ resourceType: z2.string().min(1)
119
+ }).strict();
120
+ var eventTaskSchema = z2.object({
121
+ id: z2.string().min(1),
122
+ createdAtMs: z2.number().finite(),
123
+ createdBy: eventTaskPrincipalSchema,
124
+ credentialMode: z2.enum(["system", "creator"]),
125
+ destination: slackDestinationSchema,
126
+ destinationVisibility: destinationVisibilitySchema,
127
+ task: z2.object({ text: z2.string().min(1) }).strict(),
128
+ trigger: eventTaskTriggerSchema
129
+ }).strict();
130
+
131
+ // src/chat/event-tasks/store.ts
132
+ function eventTaskJsonPayload(task) {
133
+ const { title: _title, ...payload } = task;
134
+ return payload;
135
+ }
136
+ function parseTask(row) {
137
+ const raw = row.task && typeof row.task === "object" ? { ...row.task } : row.task;
138
+ if (raw && typeof raw === "object" && "title" in raw) {
139
+ delete raw.title;
140
+ }
141
+ const payload = eventTaskSchema.parse(raw);
142
+ const title = row.title?.trim();
143
+ return title ? { ...payload, title } : payload;
144
+ }
145
+ async function getEventTask(db, id) {
146
+ const rows = await db.select({ task: juniorEventTasks.task, title: juniorEventTasks.title }).from(juniorEventTasks).where(eq(juniorEventTasks.id, id)).limit(1);
147
+ return rows[0] ? parseTask(rows[0]) : void 0;
148
+ }
149
+ async function createEventTask(db, task) {
150
+ const parsed = eventTaskSchema.parse(eventTaskJsonPayload(task));
151
+ const title = task.title?.trim() || null;
152
+ await db.insert(juniorEventTasks).values({
153
+ id: parsed.id,
154
+ teamId: parsed.destination.teamId,
155
+ namespace: parsed.trigger.namespace,
156
+ identifier: parsed.trigger.identifier,
157
+ createdAtMs: parsed.createdAtMs,
158
+ title,
159
+ task: parsed
160
+ }).onConflictDoNothing();
161
+ return await getEventTask(db, parsed.id) ?? { ...parsed, ...title ? { title } : void 0 };
162
+ }
163
+ async function saveEventTask(db, task) {
164
+ const parsed = eventTaskSchema.parse(eventTaskJsonPayload(task));
165
+ const title = task.title?.trim() || null;
166
+ const rows = await db.update(juniorEventTasks).set({
167
+ namespace: parsed.trigger.namespace,
168
+ identifier: parsed.trigger.identifier,
169
+ title,
170
+ task: parsed
171
+ }).where(eq(juniorEventTasks.id, parsed.id)).returning({ task: juniorEventTasks.task, title: juniorEventTasks.title });
172
+ return rows[0] ? parseTask(rows[0]) : void 0;
173
+ }
174
+ async function deleteEventTask(db, id) {
175
+ const rows = await db.delete(juniorEventTasks).where(eq(juniorEventTasks.id, id)).returning({ task: juniorEventTasks.task, title: juniorEventTasks.title });
176
+ return rows[0] ? parseTask(rows[0]) : void 0;
177
+ }
178
+ async function listEventTasksForTeam(db, teamId) {
179
+ const rows = await db.select({ task: juniorEventTasks.task, title: juniorEventTasks.title }).from(juniorEventTasks).where(eq(juniorEventTasks.teamId, teamId)).orderBy(asc(juniorEventTasks.createdAtMs), asc(juniorEventTasks.id));
180
+ return rows.map(parseTask);
181
+ }
182
+ function viewerSlackIdentities(user) {
183
+ return user.identities.filter(
184
+ (identity) => identity.provider === "slack" && Boolean(identity.providerTenantId) && Boolean(identity.providerSubjectId)
185
+ );
186
+ }
187
+ function eventTaskBelongsToUser(task, user) {
188
+ return viewerSlackIdentities(user).some(
189
+ (identity) => identity.providerTenantId === task.destination.teamId && identity.providerSubjectId === task.createdBy.slackUserId
190
+ );
191
+ }
192
+ async function listEventTasksCreatedBy(db, user, limit) {
193
+ const identities = viewerSlackIdentities(user);
194
+ const ownership = or(
195
+ ...identities.map(
196
+ (identity) => and(
197
+ eq(juniorEventTasks.teamId, identity.providerTenantId),
198
+ sql`${juniorEventTasks.task}->'createdBy'->>'slackUserId' = ${identity.providerSubjectId}`
199
+ )
200
+ )
201
+ );
202
+ if (!ownership) return [];
203
+ const rows = await db.select({ task: juniorEventTasks.task, title: juniorEventTasks.title }).from(juniorEventTasks).where(ownership).orderBy(desc(juniorEventTasks.createdAtMs), desc(juniorEventTasks.id)).limit(limit);
204
+ return rows.map(parseTask);
205
+ }
206
+ async function listPublicEventTasksForTeams(db, teamIds, limit) {
207
+ if (teamIds.length === 0) return [];
208
+ const rows = await db.select({ task: juniorEventTasks.task, title: juniorEventTasks.title }).from(juniorEventTasks).innerJoin(
209
+ juniorDestinations,
210
+ and(
211
+ eq(juniorDestinations.provider, "slack"),
212
+ eq(juniorDestinations.providerTenantId, juniorEventTasks.teamId),
213
+ sql`${juniorDestinations.providerDestinationId} = ${juniorEventTasks.task}->'destination'->>'channelId'`
214
+ )
215
+ ).where(
216
+ and(
217
+ inArray(juniorEventTasks.teamId, teamIds),
218
+ eq(juniorDestinations.visibility, "public")
219
+ )
220
+ ).orderBy(desc(juniorEventTasks.createdAtMs), desc(juniorEventTasks.id)).limit(limit);
221
+ return rows.map(parseTask);
222
+ }
223
+ async function findMatchingEventTasks(db, event, teamId) {
224
+ const rows = await db.select({ task: juniorEventTasks.task, title: juniorEventTasks.title }).from(juniorEventTasks).where(
225
+ and(
226
+ eq(juniorEventTasks.teamId, teamId),
227
+ eq(juniorEventTasks.namespace, event.namespace),
228
+ eq(juniorEventTasks.identifier, event.identifier)
229
+ )
230
+ ).orderBy(asc(juniorEventTasks.createdAtMs), asc(juniorEventTasks.id));
231
+ return rows.map(parseTask).filter((task) => task.trigger.events.includes(event.eventType));
232
+ }
233
+
234
+ // src/stats.ts
235
+ import { and as and2, asc as asc2, gte, lte, sql as sql2 } from "drizzle-orm";
236
+ function utcDate(nowMs) {
237
+ return new Date(nowMs).toISOString().slice(0, 10);
238
+ }
239
+ async function incrementStat(key, options = {}) {
240
+ const date = utcDate(options.nowMs ?? Date.now());
241
+ await getDb().insert(juniorStats).values({ ...key, date, count: 1 }).onConflictDoUpdate({
242
+ target: [
243
+ juniorStats.date,
244
+ juniorStats.namespace,
245
+ juniorStats.metric,
246
+ juniorStats.name
247
+ ],
248
+ set: { count: sql2`${juniorStats.count} + 1` }
249
+ });
250
+ }
251
+ async function readStats(start, end) {
252
+ return await getDb().select().from(juniorStats).where(and2(gte(juniorStats.date, start), lte(juniorStats.date, end))).orderBy(
253
+ asc2(juniorStats.date),
254
+ asc2(juniorStats.namespace),
255
+ asc2(juniorStats.metric),
256
+ asc2(juniorStats.name)
257
+ );
258
+ }
259
+
260
+ // src/chat/tool-support/structured-result.ts
261
+ import { z as z3 } from "zod";
262
+ var juniorToolContinuationSchema = z3.object({
263
+ arguments: z3.record(z3.string(), z3.unknown()),
264
+ reason: z3.string().min(1).optional()
265
+ }).strict();
266
+ var juniorToolBoundContinuationSchema = juniorToolContinuationSchema.extend({
267
+ tool_name: z3.string().min(1)
268
+ });
269
+ var juniorToolOutputSchema = z3.object({
270
+ target: z3.string().min(1).optional(),
271
+ truncated: z3.boolean().optional(),
272
+ /** True when this attempt did not finish before its time budget. */
273
+ timed_out: z3.boolean().optional(),
274
+ continuation: juniorToolContinuationSchema.optional()
275
+ }).passthrough();
276
+ function sortJsonValue(value) {
277
+ if (Array.isArray(value)) {
278
+ return value.map(sortJsonValue);
279
+ }
280
+ if (!value || typeof value !== "object") {
281
+ return value;
282
+ }
283
+ return Object.fromEntries(
284
+ Object.entries(value).filter(([, item]) => item !== void 0).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => [key, sortJsonValue(item)])
285
+ );
286
+ }
287
+ function isRecord(value) {
288
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
289
+ }
290
+ function injectContinuationToolName(details, toolName) {
291
+ if (!isRecord(details) || !isRecord(details.continuation)) {
292
+ return details;
293
+ }
294
+ return {
295
+ ...details,
296
+ continuation: juniorToolBoundContinuationSchema.parse({
297
+ ...details.continuation,
298
+ tool_name: toolName
299
+ })
300
+ };
301
+ }
302
+ function makeStructuredToolOutput(details, options = {}) {
303
+ return {
304
+ content: [
305
+ {
306
+ type: "text",
307
+ text: JSON.stringify(sortJsonValue(details))
308
+ },
309
+ ...options.content ?? []
310
+ ],
311
+ details
312
+ };
313
+ }
314
+
315
+ // src/chat/slack/user.ts
316
+ import { z as z4 } from "zod";
317
+ var slackUserInfoSchema = z4.object({
318
+ ok: z4.literal(true),
319
+ user: z4.object({
320
+ name: z4.string().nullish(),
321
+ profile: z4.object({
322
+ email: z4.string().nullish(),
323
+ real_name: z4.string().nullish()
324
+ }).optional()
325
+ })
326
+ });
327
+ var USER_CACHE_TTL_MS = 5 * 60 * 1e3;
328
+ var userCache = /* @__PURE__ */ new Map();
329
+ function userCacheKey(teamId, userId) {
330
+ return `${teamId}:${userId}`;
331
+ }
332
+ function readFromCache(teamId, userId) {
333
+ const hit = userCache.get(userCacheKey(teamId, userId));
334
+ if (!hit) return null;
335
+ if (hit.expiresAt < Date.now()) {
336
+ userCache.delete(userCacheKey(teamId, userId));
337
+ return null;
338
+ }
339
+ return hit.value;
340
+ }
341
+ function writeToCache(teamId, userId, value) {
342
+ userCache.set(userCacheKey(teamId, userId), {
343
+ value,
344
+ expiresAt: Date.now() + USER_CACHE_TTL_MS
345
+ });
346
+ }
347
+ async function lookupSlackUser(teamId, userId) {
348
+ if (!teamId || !userId) {
349
+ return null;
350
+ }
351
+ const cached = readFromCache(teamId, userId);
352
+ if (cached) {
353
+ return cached;
354
+ }
355
+ const token = getSlackBotToken();
356
+ if (!token) {
357
+ return null;
358
+ }
359
+ try {
360
+ const response = await fetch(
361
+ `https://slack.com/api/users.info?user=${encodeURIComponent(userId)}`,
362
+ {
363
+ headers: {
364
+ authorization: `Bearer ${token}`
365
+ }
366
+ }
367
+ );
368
+ if (!response.ok) {
369
+ logWarn("slack.user.lookup.failed", {
370
+ "enduser.id": userId,
371
+ "app.slack.team_id": teamId,
372
+ "http.response.status_code": response.status
373
+ });
374
+ return null;
375
+ }
376
+ const parsed = slackUserInfoSchema.safeParse(await response.json());
377
+ if (!parsed.success) {
378
+ return null;
379
+ }
380
+ const payload = parsed.data;
381
+ const userName = payload.user.name?.trim() || void 0;
382
+ const fullName = payload.user.profile?.real_name?.trim() || void 0;
383
+ const result = {
384
+ userName,
385
+ fullName,
386
+ email: payload.user.profile?.email?.trim() || void 0
387
+ };
388
+ writeToCache(teamId, userId, result);
389
+ return result;
390
+ } catch (error) {
391
+ logWarn("slack.user.lookup.failed", {
392
+ "enduser.id": userId,
393
+ "app.slack.team_id": teamId,
394
+ "exception.message": error instanceof Error ? error.message : String(error)
395
+ });
396
+ return null;
397
+ }
398
+ }
399
+
400
+ // src/chat/event-tasks/tool-support.ts
401
+ import { z as z5 } from "zod";
402
+ var compactEventTaskResultSchema = z5.object({
403
+ id: z5.string().min(1),
404
+ task: z5.string().min(1),
405
+ namespace: z5.string().min(1),
406
+ identifier: z5.string().min(1),
407
+ resourceType: z5.string().min(1),
408
+ label: z5.string().min(1),
409
+ events: z5.array(z5.string().min(1)).min(1),
410
+ credentialMode: z5.enum(["system", "creator"]),
411
+ createdBy: eventTaskPrincipalSchema,
412
+ triggerAvailable: z5.boolean()
413
+ }).strict();
414
+ var eventTaskToolResultSchema = juniorToolOutputSchema.extend({
415
+ task: compactEventTaskResultSchema
416
+ }).strict();
417
+ var eventTaskListToolResultSchema = juniorToolOutputSchema.extend({
418
+ tasks: z5.array(compactEventTaskResultSchema),
419
+ truncated: z5.boolean()
420
+ }).strict();
421
+ function registeredEventTaskTriggerSchema(catalog) {
422
+ return z5.object({
423
+ namespace: eventNamespaceSchema(catalog),
424
+ identifier: z5.string().trim().min(1).max(EVENT_TASK_IDENTIFIER_MAX_LENGTH),
425
+ resourceType: registeredResourceTypeSchema(catalog),
426
+ label: z5.string().trim().min(1).max(500),
427
+ events: z5.array(registeredEventTypeSchema(catalog)).min(1)
428
+ }).strict();
429
+ }
430
+ function requireSupportedEventTaskTrigger(catalog, trigger) {
431
+ for (const eventType of trigger.events) {
432
+ if (!pluginSupportsEvent(
433
+ catalog,
434
+ trigger.namespace,
435
+ trigger.resourceType,
436
+ eventType
437
+ )) {
438
+ throw new ToolInputError(
439
+ `Resource type "${trigger.namespace}:${trigger.resourceType}" does not support event "${eventType}".`
440
+ );
441
+ }
442
+ }
443
+ }
444
+ function requireEventTaskSlackContext(context) {
445
+ if (context.source.platform !== "slack" || context.destination.platform !== "slack" || context.actor?.platform !== "slack" || context.actor.teamId !== context.source.teamId) {
446
+ throw new ToolInputError(
447
+ "Event tasks require an active Slack channel or DM and actor."
448
+ );
449
+ }
450
+ return {
451
+ actor: context.actor,
452
+ destination: {
453
+ platform: "slack",
454
+ teamId: context.source.teamId,
455
+ channelId: context.source.channelId
456
+ },
457
+ source: context.source
458
+ };
459
+ }
460
+ function eventTaskMatchesDestination(task, destination) {
461
+ return task.destination.teamId === destination.teamId && task.destination.channelId === destination.channelId;
462
+ }
463
+ async function writableEventTask(context, id) {
464
+ const { destination } = requireEventTaskSlackContext(context);
465
+ const task = await getEventTask(getDb(), id);
466
+ if (!task || !eventTaskMatchesDestination(task, destination)) {
467
+ throw new ToolInputError(
468
+ "Event task was not found in this Slack channel or DM."
469
+ );
470
+ }
471
+ return task;
472
+ }
473
+ function eventTaskTriggerAvailable(task, catalog) {
474
+ const registration = catalog[task.trigger.namespace];
475
+ return Boolean(
476
+ registration && task.trigger.events.every(
477
+ (eventType) => registration.resourceTypes.some(
478
+ (resourceType) => resourceType.supportedEvents.includes(eventType)
479
+ )
480
+ )
481
+ );
482
+ }
483
+ function compactEventTask(task, catalog) {
484
+ return compactEventTaskResultSchema.parse({
485
+ id: task.id,
486
+ task: task.task.text,
487
+ namespace: task.trigger.namespace,
488
+ identifier: task.trigger.identifier,
489
+ resourceType: task.trigger.resourceType,
490
+ label: task.trigger.label,
491
+ events: task.trigger.events,
492
+ credentialMode: task.credentialMode,
493
+ createdBy: task.createdBy,
494
+ triggerAvailable: eventTaskTriggerAvailable(task, catalog)
495
+ });
496
+ }
497
+ function eventTaskSuccess(task, catalog) {
498
+ return {
499
+ task: compactEventTask(task, catalog)
500
+ };
501
+ }
502
+
503
+ // src/chat/scheduled-tasks/tasks.ts
504
+ import { and as and3, asc as asc3, desc as desc2, eq as eq2, inArray as inArray2, notInArray, sql as sql3 } from "drizzle-orm";
505
+ import { z as z7 } from "zod";
506
+
507
+ // src/chat/scheduled-tasks/types.ts
508
+ import {
509
+ actorUserIdSchema as actorUserIdSchema2,
510
+ slackDestinationSchema as slackDestinationSchema2
511
+ } from "@sentry/junior-plugin-api";
512
+ import { z as z6 } from "zod";
513
+ var scheduledTaskStatusSchema = z6.enum([
514
+ "active",
515
+ "blocked",
516
+ "completed",
517
+ "deleted"
518
+ ]);
519
+ var scheduledTaskCredentialModeSchema = z6.enum(["system", "creator"]);
520
+ var scheduledRunStatusSchema = z6.enum([
521
+ "pending",
522
+ "running",
523
+ "completed",
524
+ "failed",
525
+ "blocked",
526
+ "skipped"
527
+ ]);
528
+ var scheduledTaskPrincipalSchema = z6.object({
529
+ slackUserId: actorUserIdSchema2,
530
+ fullName: z6.string().optional(),
531
+ userName: z6.string().optional()
532
+ }).strict();
533
+ var scheduledTaskRecurrenceSchema = z6.object({
534
+ dayOfMonth: z6.number().optional(),
535
+ frequency: z6.enum(["daily", "weekly", "monthly", "yearly"]),
536
+ interval: z6.number(),
537
+ month: z6.number().optional(),
538
+ startDate: z6.string(),
539
+ time: z6.object({
540
+ hour: z6.number(),
541
+ minute: z6.number()
542
+ }).strict(),
543
+ weekdays: z6.array(z6.number()).optional()
544
+ }).strict();
545
+ var scheduledTaskScheduleSchema = z6.object({
546
+ description: z6.string(),
547
+ kind: z6.enum(["one_off", "recurring"]),
548
+ recurrence: scheduledTaskRecurrenceSchema.optional(),
549
+ timezone: z6.string()
550
+ }).strict();
551
+ var scheduledTaskExecutionActorSchema = z6.object({
552
+ platform: z6.literal("system"),
553
+ name: z6.string()
554
+ }).strict();
555
+ var scheduledTaskSchema = z6.object({
556
+ id: z6.string(),
557
+ conversationAccess: z6.object({
558
+ audience: z6.enum(["direct", "group", "channel"]),
559
+ visibility: z6.enum(["private", "public"])
560
+ }).strict(),
561
+ createdAtMs: z6.number(),
562
+ createdBy: scheduledTaskPrincipalSchema,
563
+ /** Authoritative provider identity that created the task. */
564
+ creatorIdentityId: z6.string(),
565
+ /** Selects system credentials or task-bound creator credential delegation. */
566
+ credentialMode: scheduledTaskCredentialModeSchema,
567
+ destination: slackDestinationSchema2,
568
+ executionActor: scheduledTaskExecutionActorSchema.optional(),
569
+ lastRunAtMs: z6.number().optional(),
570
+ nextRunAtMs: z6.number().optional(),
571
+ originalRequest: z6.string().optional(),
572
+ runNowAtMs: z6.number().optional(),
573
+ schedule: scheduledTaskScheduleSchema,
574
+ status: scheduledTaskStatusSchema,
575
+ statusReason: z6.string().optional(),
576
+ task: z6.object({ text: z6.string() }).strict(),
577
+ /** SQL-backed short display title generated from the task instruction. */
578
+ title: z6.string().optional(),
579
+ updatedAtMs: z6.number()
580
+ }).strict();
581
+ var scheduledRunSchema = z6.object({
582
+ id: z6.string(),
583
+ attempt: z6.number(),
584
+ claimedAtMs: z6.number(),
585
+ completedAtMs: z6.number().optional(),
586
+ dispatchId: z6.string().optional(),
587
+ errorMessage: z6.string().optional(),
588
+ resultMessageTs: z6.string().optional(),
589
+ scheduledForMs: z6.number(),
590
+ startedAtMs: z6.number().optional(),
591
+ status: scheduledRunStatusSchema,
592
+ taskId: z6.string()
593
+ }).strict();
594
+ var SCHEDULED_TASK_SYSTEM_ACTOR = Object.freeze({
595
+ platform: "system",
596
+ name: "scheduled-task"
597
+ });
598
+
599
+ // src/chat/scheduled-tasks/tasks.ts
600
+ var SCHEDULER_KEY_PREFIX = "junior:scheduler";
601
+ var retainedScheduledTaskSchema = scheduledTaskSchema.omit({ creatorIdentityId: true, title: true }).extend({
602
+ // TODO(v0.131.0): Remove paused decoding and SQL list filtering after
603
+ // v0.129.x workers can no longer overlap upgrades.
604
+ status: z7.enum(["active", "blocked", "completed", "deleted", "paused"]),
605
+ version: z7.number().optional()
606
+ }).strict();
607
+ function parseScheduledTaskRow(row) {
608
+ const record = z7.record(z7.string(), z7.unknown()).safeParse(row.record);
609
+ if (!record.success) return void 0;
610
+ const {
611
+ creatorIdentityId: legacyCreatorIdentityId,
612
+ title: legacyTitle,
613
+ ...retained
614
+ } = record.data;
615
+ const parsed = retainedScheduledTaskSchema.safeParse(retained);
616
+ if (!parsed.success) return void 0;
617
+ const { status, version: _version, ...task } = parsed.data;
618
+ const fallbackIdentity = row.creatorIdentityId === null ? z7.string().safeParse(legacyCreatorIdentityId) : void 0;
619
+ const creatorIdentityId = row.creatorIdentityId ?? (fallbackIdentity?.success ? fallbackIdentity.data : void 0);
620
+ if (creatorIdentityId === void 0) return void 0;
621
+ const fallbackTitle = row.title === null ? z7.string().safeParse(legacyTitle) : void 0;
622
+ const titleSource = row.title !== null ? row.title : fallbackTitle?.success ? fallbackTitle.data : void 0;
623
+ const title = titleSource?.trim() || void 0;
624
+ if (status === "paused") {
625
+ const {
626
+ nextRunAtMs: _nextRunAtMs,
627
+ runNowAtMs: _runNowAtMs,
628
+ ...retained2
629
+ } = task;
630
+ return {
631
+ ...retained2,
632
+ creatorIdentityId,
633
+ status: "deleted",
634
+ ...title ? { title } : void 0
635
+ };
636
+ }
637
+ return {
638
+ ...task,
639
+ creatorIdentityId,
640
+ status,
641
+ ...title ? { title } : void 0
642
+ };
643
+ }
644
+ function isListedScheduledTask(task) {
645
+ return task !== void 0 && task.status !== "deleted";
646
+ }
647
+ function scheduledRunId(taskId, scheduledForMs) {
648
+ return `${taskId}:${scheduledForMs}`;
649
+ }
650
+ function withScheduledTaskLock(db, taskId, callback) {
651
+ return db.transaction(async (tx) => {
652
+ await tx.execute(
653
+ sql3`SELECT pg_advisory_xact_lock(hashtext(${`${SCHEDULER_KEY_PREFIX}:task:${taskId}:lock`}))`
654
+ );
655
+ return await callback(tx);
656
+ });
657
+ }
658
+ function requireStoredTask(task) {
659
+ const parsed = scheduledTaskSchema.safeParse(task);
660
+ if (!parsed.success) {
661
+ throw new Error("Scheduled task routing context is invalid.");
662
+ }
663
+ const { title, ...current } = parsed.data;
664
+ const normalizedTitle = title?.trim() || void 0;
665
+ return { ...current, ...normalizedTitle ? { title: normalizedTitle } : void 0 };
666
+ }
667
+ function scheduledTaskJsonRecord(task) {
668
+ const { title: _title, ...record } = task;
669
+ return record;
670
+ }
671
+ async function upsertScheduledTask(db, task) {
672
+ const title = task.title?.trim() || null;
673
+ await db.insert(juniorSchedulerTasks).values({
674
+ createdAtMs: task.createdAtMs,
675
+ creatorIdentityId: task.creatorIdentityId,
676
+ // TODO(v0.128.0): Remove after v0.127.x runtimes no longer read this column.
677
+ creatorSlackUserId: task.createdBy.slackUserId,
678
+ id: task.id,
679
+ nextRunAtMs: task.nextRunAtMs,
680
+ record: scheduledTaskJsonRecord(task),
681
+ runNowAtMs: task.runNowAtMs,
682
+ status: task.status,
683
+ teamId: task.destination.teamId,
684
+ title
685
+ }).onConflictDoUpdate({
686
+ target: juniorSchedulerTasks.id,
687
+ set: {
688
+ createdAtMs: sql3`excluded.created_at_ms`,
689
+ creatorIdentityId: sql3`excluded.creator_identity_id`,
690
+ creatorSlackUserId: sql3`excluded.creator_slack_user_id`,
691
+ nextRunAtMs: sql3`excluded.next_run_at_ms`,
692
+ record: sql3`excluded.record`,
693
+ runNowAtMs: sql3`excluded.run_now_at_ms`,
694
+ status: sql3`excluded.status`,
695
+ teamId: sql3`excluded.team_id`,
696
+ title: sql3`excluded.title`
697
+ }
698
+ });
699
+ }
700
+ async function readScheduledTask(db, taskId) {
701
+ const rows = await db.select({
702
+ creatorIdentityId: juniorSchedulerTasks.creatorIdentityId,
703
+ record: juniorSchedulerTasks.record,
704
+ title: juniorSchedulerTasks.title
705
+ }).from(juniorSchedulerTasks).where(eq2(juniorSchedulerTasks.id, taskId)).limit(1);
706
+ return rows[0] ? parseScheduledTaskRow(rows[0]) : void 0;
707
+ }
708
+ async function readListedScheduledTasks(db, teamId) {
709
+ const rows = await db.select({
710
+ creatorIdentityId: juniorSchedulerTasks.creatorIdentityId,
711
+ record: juniorSchedulerTasks.record,
712
+ title: juniorSchedulerTasks.title
713
+ }).from(juniorSchedulerTasks).where(
714
+ and3(
715
+ notInArray(juniorSchedulerTasks.status, ["deleted", "paused"]),
716
+ teamId === void 0 ? void 0 : eq2(juniorSchedulerTasks.teamId, teamId)
717
+ )
718
+ ).orderBy(
719
+ asc3(juniorSchedulerTasks.createdAtMs),
720
+ asc3(juniorSchedulerTasks.id)
721
+ );
722
+ return rows.map(parseScheduledTaskRow).filter(isListedScheduledTask);
723
+ }
724
+ async function listScheduledTasks(db) {
725
+ return await readListedScheduledTasks(db);
726
+ }
727
+ async function listScheduledTasksForTeam(db, teamId) {
728
+ return await readListedScheduledTasks(db, teamId);
729
+ }
730
+ async function writeScheduledTask(db, task, current) {
731
+ if (current?.status === "blocked" && task.status === "active" && typeof task.nextRunAtMs === "number" && Number.isFinite(task.nextRunAtMs)) {
732
+ await db.delete(juniorSchedulerRuns).where(
733
+ and3(
734
+ eq2(juniorSchedulerRuns.id, scheduledRunId(task.id, task.nextRunAtMs)),
735
+ eq2(juniorSchedulerRuns.status, "blocked")
736
+ )
737
+ );
738
+ }
739
+ await upsertScheduledTask(db, task);
740
+ }
741
+ async function createScheduledTask(db, task) {
742
+ const next = requireStoredTask(task);
743
+ return await withScheduledTaskLock(db, task.id, async (tx) => {
744
+ const current = await readScheduledTask(tx, task.id);
745
+ if (current) return current;
746
+ await writeScheduledTask(tx, next, void 0);
747
+ return next;
748
+ });
749
+ }
750
+ async function saveScheduledTask(db, task) {
751
+ const next = requireStoredTask(task);
752
+ await withScheduledTaskLock(db, task.id, async (tx) => {
753
+ const current = await readScheduledTask(tx, task.id);
754
+ await writeScheduledTask(tx, next, current);
755
+ });
756
+ return next;
757
+ }
758
+ async function saveScheduledTaskInLock(db, task, current) {
759
+ await writeScheduledTask(db, requireStoredTask(task), current);
760
+ }
761
+ async function listPublicScheduledTasksForTeams(db, teamIds, limit) {
762
+ if (teamIds.length === 0) return [];
763
+ const rows = await db.select({
764
+ creatorIdentityId: juniorSchedulerTasks.creatorIdentityId,
765
+ record: juniorSchedulerTasks.record,
766
+ title: juniorSchedulerTasks.title
767
+ }).from(juniorSchedulerTasks).innerJoin(
768
+ juniorDestinations,
769
+ and3(
770
+ eq2(juniorDestinations.provider, "slack"),
771
+ eq2(juniorDestinations.providerTenantId, juniorSchedulerTasks.teamId),
772
+ sql3`${juniorDestinations.providerDestinationId} = ${juniorSchedulerTasks.record}->'destination'->>'channelId'`
773
+ )
774
+ ).where(
775
+ and3(
776
+ inArray2(juniorSchedulerTasks.teamId, teamIds),
777
+ notInArray(juniorSchedulerTasks.status, [
778
+ "completed",
779
+ "deleted",
780
+ "paused"
781
+ ]),
782
+ eq2(juniorDestinations.visibility, "public")
783
+ )
784
+ ).orderBy(
785
+ desc2(juniorSchedulerTasks.createdAtMs),
786
+ desc2(juniorSchedulerTasks.id)
787
+ ).limit(limit);
788
+ return rows.map(parseScheduledTaskRow).filter(isListedScheduledTask);
789
+ }
790
+
791
+ // src/chat/attachments/store.ts
792
+ import { createHash, randomUUID } from "crypto";
793
+ import { and as and4, asc as asc4, eq as eq3, inArray as inArray3, isNotNull, isNull, or as or2 } from "drizzle-orm";
794
+ var ATTACHMENT_GC_BATCH_LIMIT = 200;
795
+ function attachmentDigest(file) {
796
+ return createHash("sha256").update(file.data).digest("hex");
797
+ }
798
+ function attachmentId(args) {
799
+ return createHash("sha256").update(
800
+ [
801
+ args.conversationId,
802
+ args.provider,
803
+ args.sha256,
804
+ args.filename,
805
+ args.contentType
806
+ ].join("\0")
807
+ ).digest("hex");
808
+ }
809
+ function attachmentKey(args) {
810
+ return `conversations/${args.conversationId}/attachments/${args.attachmentId}/${args.writeId}/${args.filename}`;
811
+ }
812
+ async function bestEffortDelete(storage, key) {
813
+ try {
814
+ await storage.delete([key]);
815
+ } catch {
816
+ }
817
+ }
818
+ async function isConversationPurged(db, conversationId) {
819
+ const [row] = await db.db().select({
820
+ transcriptPurgedAt: juniorConversations.transcriptPurgedAt
821
+ }).from(juniorConversations).where(eq3(juniorConversations.conversationId, conversationId));
822
+ return row?.transcriptPurgedAt != null;
823
+ }
824
+ function purgedConversationError(conversationId) {
825
+ return new Error(
826
+ `Cannot store attachment for purged conversation ${conversationId}`
827
+ );
828
+ }
829
+ async function assertConversationNotPurged(db, conversationId) {
830
+ if (await isConversationPurged(db, conversationId)) {
831
+ throw purgedConversationError(conversationId);
832
+ }
833
+ }
834
+ async function rejectPurgedAttachmentWrite(args) {
835
+ await args.db.db().update(juniorAttachments).set({ deleteRequestedAt: args.now }).where(eq3(juniorAttachments.id, args.id));
836
+ await bestEffortDelete(args.storage, args.storageKey);
837
+ throw purgedConversationError(args.conversationId);
838
+ }
839
+ async function reuseLiveAttachment(args) {
840
+ await bestEffortDelete(args.storage, args.storageKey);
841
+ await assertConversationNotPurged(args.db, args.conversationId);
842
+ return { id: args.id };
843
+ }
844
+ async function storeAttachment(args) {
845
+ const now = new Date(args.nowMs ?? Date.now());
846
+ const sha256 = attachmentDigest(args.file);
847
+ const id = attachmentId({
848
+ conversationId: args.conversationId,
849
+ contentType: args.file.mimeType,
850
+ filename: args.file.filename,
851
+ provider: args.storage.provider,
852
+ sha256
853
+ });
854
+ const [existing] = await args.db.db().select({
855
+ contentType: juniorAttachments.contentType,
856
+ conversationId: juniorAttachments.conversationId,
857
+ deleteRequestedAt: juniorAttachments.deleteRequestedAt,
858
+ filename: juniorAttachments.filename,
859
+ id: juniorAttachments.id,
860
+ provider: juniorAttachments.provider,
861
+ sha256: juniorAttachments.sha256,
862
+ storageKey: juniorAttachments.storageKey
863
+ }).from(juniorAttachments).where(eq3(juniorAttachments.id, id));
864
+ if (existing) {
865
+ if (existing.contentType !== args.file.mimeType || existing.conversationId !== args.conversationId || existing.filename !== args.file.filename || existing.provider !== args.storage.provider || existing.sha256 !== sha256) {
866
+ throw new Error(`Attachment write conflicts with ${existing.id}`);
867
+ }
868
+ if (existing.deleteRequestedAt === null) {
869
+ await assertConversationNotPurged(args.db, args.conversationId);
870
+ return { id: existing.id };
871
+ }
872
+ }
873
+ await assertConversationNotPurged(args.db, args.conversationId);
874
+ const storageKey = attachmentKey({
875
+ attachmentId: id,
876
+ conversationId: args.conversationId,
877
+ filename: args.file.filename,
878
+ writeId: randomUUID()
879
+ });
880
+ await args.storage.put({
881
+ body: args.file.data,
882
+ contentType: args.file.mimeType,
883
+ key: storageKey
884
+ });
885
+ if (existing?.deleteRequestedAt != null) {
886
+ if (await isConversationPurged(args.db, args.conversationId)) {
887
+ await bestEffortDelete(args.storage, storageKey);
888
+ throw purgedConversationError(args.conversationId);
889
+ }
890
+ const oldStorageKey = existing.storageKey;
891
+ const revived2 = await args.db.db().update(juniorAttachments).set({
892
+ deleteRequestedAt: null,
893
+ storageKey
894
+ }).where(
895
+ and4(
896
+ eq3(juniorAttachments.id, id),
897
+ isNotNull(juniorAttachments.deleteRequestedAt)
898
+ )
899
+ ).returning({ id: juniorAttachments.id });
900
+ if (revived2.length > 0) {
901
+ if (await isConversationPurged(args.db, args.conversationId)) {
902
+ await rejectPurgedAttachmentWrite({
903
+ conversationId: args.conversationId,
904
+ db: args.db,
905
+ id,
906
+ now,
907
+ storage: args.storage,
908
+ storageKey
909
+ });
910
+ }
911
+ if (oldStorageKey !== storageKey) {
912
+ await bestEffortDelete(args.storage, oldStorageKey);
913
+ }
914
+ return { id };
915
+ }
916
+ const [afterRace2] = await args.db.db().select({
917
+ deleteRequestedAt: juniorAttachments.deleteRequestedAt,
918
+ id: juniorAttachments.id,
919
+ storageKey: juniorAttachments.storageKey
920
+ }).from(juniorAttachments).where(eq3(juniorAttachments.id, id));
921
+ if (afterRace2?.deleteRequestedAt === null) {
922
+ return reuseLiveAttachment({
923
+ conversationId: args.conversationId,
924
+ db: args.db,
925
+ id: afterRace2.id,
926
+ storage: args.storage,
927
+ storageKey
928
+ });
929
+ }
930
+ }
931
+ let inserted = [];
932
+ try {
933
+ inserted = await args.db.db().insert(juniorAttachments).values({
934
+ id,
935
+ conversationId: args.conversationId,
936
+ provider: args.storage.provider,
937
+ storageKey,
938
+ filename: args.file.filename,
939
+ contentType: args.file.mimeType,
940
+ bytes: args.file.bytes,
941
+ sha256,
942
+ createdAt: now
943
+ }).onConflictDoNothing({ target: juniorAttachments.id }).returning({ id: juniorAttachments.id });
944
+ } catch (error) {
945
+ await bestEffortDelete(args.storage, storageKey);
946
+ throw error;
947
+ }
948
+ if (inserted.length > 0) {
949
+ if (await isConversationPurged(args.db, args.conversationId)) {
950
+ await rejectPurgedAttachmentWrite({
951
+ conversationId: args.conversationId,
952
+ db: args.db,
953
+ id,
954
+ now,
955
+ storage: args.storage,
956
+ storageKey
957
+ });
958
+ }
959
+ return { id };
960
+ }
961
+ const [row] = await args.db.db().select({
962
+ deleteRequestedAt: juniorAttachments.deleteRequestedAt,
963
+ id: juniorAttachments.id,
964
+ storageKey: juniorAttachments.storageKey
965
+ }).from(juniorAttachments).where(eq3(juniorAttachments.id, id));
966
+ if (!row) {
967
+ await bestEffortDelete(args.storage, storageKey);
968
+ throw new Error(`Attachment row missing after write for ${id}`);
969
+ }
970
+ if (row.deleteRequestedAt === null) {
971
+ return reuseLiveAttachment({
972
+ conversationId: args.conversationId,
973
+ db: args.db,
974
+ id: row.id,
975
+ storage: args.storage,
976
+ storageKey
977
+ });
978
+ }
979
+ if (await isConversationPurged(args.db, args.conversationId)) {
980
+ await bestEffortDelete(args.storage, storageKey);
981
+ throw purgedConversationError(args.conversationId);
982
+ }
983
+ const revived = await args.db.db().update(juniorAttachments).set({
984
+ deleteRequestedAt: null,
985
+ storageKey
986
+ }).where(
987
+ and4(
988
+ eq3(juniorAttachments.id, id),
989
+ isNotNull(juniorAttachments.deleteRequestedAt)
990
+ )
991
+ ).returning({ id: juniorAttachments.id });
992
+ if (revived.length > 0) {
993
+ if (await isConversationPurged(args.db, args.conversationId)) {
994
+ await rejectPurgedAttachmentWrite({
995
+ conversationId: args.conversationId,
996
+ db: args.db,
997
+ id,
998
+ now,
999
+ storage: args.storage,
1000
+ storageKey
1001
+ });
1002
+ }
1003
+ if (row.storageKey !== storageKey) {
1004
+ await bestEffortDelete(args.storage, row.storageKey);
1005
+ }
1006
+ return { id };
1007
+ }
1008
+ const [afterRace] = await args.db.db().select({
1009
+ deleteRequestedAt: juniorAttachments.deleteRequestedAt,
1010
+ id: juniorAttachments.id
1011
+ }).from(juniorAttachments).where(eq3(juniorAttachments.id, id));
1012
+ if (afterRace?.deleteRequestedAt === null) {
1013
+ return reuseLiveAttachment({
1014
+ conversationId: args.conversationId,
1015
+ db: args.db,
1016
+ id: afterRace.id,
1017
+ storage: args.storage,
1018
+ storageKey
1019
+ });
1020
+ }
1021
+ await bestEffortDelete(args.storage, storageKey);
1022
+ throw new Error(`Attachment ${id} disappeared while reviving`);
1023
+ }
1024
+ async function readLiveAttachment(args) {
1025
+ const [row] = await args.db.db().select({
1026
+ bytes: juniorAttachments.bytes,
1027
+ contentType: juniorAttachments.contentType,
1028
+ conversationId: juniorAttachments.conversationId,
1029
+ deleteRequestedAt: juniorAttachments.deleteRequestedAt,
1030
+ filename: juniorAttachments.filename,
1031
+ id: juniorAttachments.id,
1032
+ provider: juniorAttachments.provider,
1033
+ storageKey: juniorAttachments.storageKey
1034
+ }).from(juniorAttachments).where(
1035
+ and4(
1036
+ eq3(juniorAttachments.id, args.attachmentId),
1037
+ eq3(juniorAttachments.conversationId, args.conversationId),
1038
+ isNull(juniorAttachments.deleteRequestedAt)
1039
+ )
1040
+ );
1041
+ if (!row) return null;
1042
+ if (await isConversationPurged(args.db, args.conversationId)) {
1043
+ return null;
1044
+ }
1045
+ return {
1046
+ bytes: row.bytes,
1047
+ contentType: row.contentType,
1048
+ conversationId: row.conversationId,
1049
+ filename: row.filename,
1050
+ id: row.id,
1051
+ provider: row.provider,
1052
+ storageKey: row.storageKey
1053
+ };
1054
+ }
1055
+ async function storeAttachments(args) {
1056
+ const stored = [];
1057
+ for (const file of args.files) {
1058
+ stored.push(
1059
+ await storeAttachment({
1060
+ conversationId: args.conversationId,
1061
+ db: args.db,
1062
+ file,
1063
+ ...args.nowMs !== void 0 ? { nowMs: args.nowMs } : void 0,
1064
+ storage: args.storage
1065
+ })
1066
+ );
1067
+ }
1068
+ return stored;
1069
+ }
1070
+ async function requestAttachmentDeletion(db, conversationIds, nowMs) {
1071
+ if (conversationIds.length === 0) return;
1072
+ await db.db().update(juniorAttachments).set({ deleteRequestedAt: new Date(nowMs) }).where(
1073
+ and4(
1074
+ inArray3(juniorAttachments.conversationId, conversationIds),
1075
+ isNull(juniorAttachments.deleteRequestedAt)
1076
+ )
1077
+ );
1078
+ }
1079
+ async function collectAttachmentGarbage(args) {
1080
+ void args.nowMs;
1081
+ const rows = await args.db.db().select({
1082
+ id: juniorAttachments.id,
1083
+ storageKey: juniorAttachments.storageKey
1084
+ }).from(juniorAttachments).innerJoin(
1085
+ juniorConversations,
1086
+ eq3(
1087
+ juniorAttachments.conversationId,
1088
+ juniorConversations.conversationId
1089
+ )
1090
+ ).where(
1091
+ and4(
1092
+ eq3(juniorAttachments.provider, args.storage.provider),
1093
+ or2(
1094
+ isNotNull(juniorAttachments.deleteRequestedAt),
1095
+ isNotNull(juniorConversations.transcriptPurgedAt)
1096
+ )
1097
+ )
1098
+ ).orderBy(asc4(juniorAttachments.createdAt), asc4(juniorAttachments.id)).limit(args.limit ?? ATTACHMENT_GC_BATCH_LIMIT);
1099
+ if (rows.length === 0) return { deleted: 0 };
1100
+ await args.storage.delete(rows.map((row) => row.storageKey));
1101
+ const stillEligible = await args.db.db().select({ id: juniorAttachments.id }).from(juniorAttachments).innerJoin(
1102
+ juniorConversations,
1103
+ eq3(
1104
+ juniorAttachments.conversationId,
1105
+ juniorConversations.conversationId
1106
+ )
1107
+ ).where(
1108
+ and4(
1109
+ inArray3(
1110
+ juniorAttachments.id,
1111
+ rows.map((row) => row.id)
1112
+ ),
1113
+ or2(
1114
+ isNotNull(juniorAttachments.deleteRequestedAt),
1115
+ isNotNull(juniorConversations.transcriptPurgedAt)
1116
+ )
1117
+ )
1118
+ );
1119
+ if (stillEligible.length === 0) return { deleted: 0 };
1120
+ const removed = await args.db.db().delete(juniorAttachments).where(
1121
+ inArray3(
1122
+ juniorAttachments.id,
1123
+ stillEligible.map((row) => row.id)
1124
+ )
1125
+ ).returning({
1126
+ id: juniorAttachments.id
1127
+ });
1128
+ return { deleted: removed.length };
1129
+ }
1130
+
1131
+ export {
1132
+ juniorToolOutputSchema,
1133
+ injectContinuationToolName,
1134
+ makeStructuredToolOutput,
1135
+ lookupSlackUser,
1136
+ eventNamespaceSchema,
1137
+ registeredEventTypeSchema,
1138
+ registeredResourceTypeSchema,
1139
+ pluginSupportsEvent,
1140
+ resourceEventGuidance,
1141
+ normalizeEventIdentifier,
1142
+ getResourceEventCatalog,
1143
+ getEventTask,
1144
+ createEventTask,
1145
+ saveEventTask,
1146
+ deleteEventTask,
1147
+ listEventTasksForTeam,
1148
+ eventTaskBelongsToUser,
1149
+ listEventTasksCreatedBy,
1150
+ listPublicEventTasksForTeams,
1151
+ findMatchingEventTasks,
1152
+ eventTaskToolResultSchema,
1153
+ eventTaskListToolResultSchema,
1154
+ registeredEventTaskTriggerSchema,
1155
+ requireSupportedEventTaskTrigger,
1156
+ requireEventTaskSlackContext,
1157
+ eventTaskMatchesDestination,
1158
+ writableEventTask,
1159
+ eventTaskTriggerAvailable,
1160
+ compactEventTask,
1161
+ eventTaskSuccess,
1162
+ scheduledRunSchema,
1163
+ SCHEDULED_TASK_SYSTEM_ACTOR,
1164
+ parseScheduledTaskRow,
1165
+ isListedScheduledTask,
1166
+ scheduledRunId,
1167
+ withScheduledTaskLock,
1168
+ readScheduledTask,
1169
+ listScheduledTasks,
1170
+ listScheduledTasksForTeam,
1171
+ createScheduledTask,
1172
+ saveScheduledTask,
1173
+ saveScheduledTaskInLock,
1174
+ listPublicScheduledTasksForTeams,
1175
+ readLiveAttachment,
1176
+ storeAttachments,
1177
+ requestAttachmentDeletion,
1178
+ collectAttachmentGarbage,
1179
+ incrementStat,
1180
+ readStats
1181
+ };