@sentry/junior 0.128.1 → 0.129.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 (71) hide show
  1. package/dist/{agent-hooks-IWQNFJMS.js → agent-hooks-ZM4WMZCN.js} +9 -9
  2. package/dist/api/schema/task.d.ts +152 -0
  3. package/dist/api/schema.d.ts +2 -0
  4. package/dist/api/schema.js +13 -3
  5. package/dist/api/tasks/routes.d.ts +4 -0
  6. package/dist/api.js +425 -23
  7. package/dist/app.js +309 -27
  8. package/dist/{catalog-runtime-KLSYFS4A.js → catalog-runtime-TN2GPVEI.js} +5 -5
  9. package/dist/chat/agent-dispatch/context.d.ts +18 -0
  10. package/dist/chat/event-tasks/store.d.ts +7 -1
  11. package/dist/chat/event-tasks/tool-support.d.ts +1 -0
  12. package/dist/chat/resource-events/runtime-catalog.d.ts +3 -0
  13. package/dist/chat/scheduled-tasks/cadence.d.ts +27 -0
  14. package/dist/chat/scheduled-tasks/heartbeat.d.ts +6 -0
  15. package/dist/chat/scheduled-tasks/identity.d.ts +6 -0
  16. package/dist/chat/scheduled-tasks/personal.d.ts +26 -0
  17. package/dist/chat/scheduled-tasks/schedule-intent.d.ts +61 -0
  18. package/dist/chat/scheduled-tasks/store.d.ts +77 -0
  19. package/dist/chat/scheduled-tasks/tool-support.d.ts +203 -0
  20. package/dist/chat/scheduled-tasks/tools/create.d.ts +80 -0
  21. package/dist/chat/scheduled-tasks/tools/delete.d.ts +56 -0
  22. package/dist/chat/scheduled-tasks/tools/list.d.ts +54 -0
  23. package/dist/chat/scheduled-tasks/tools/run-now.d.ts +56 -0
  24. package/dist/chat/scheduled-tasks/tools/update.d.ts +82 -0
  25. package/dist/chat/scheduled-tasks/tools.d.ts +6 -0
  26. package/dist/chat/scheduled-tasks/types.d.ts +81 -0
  27. package/dist/chat/tasks/read.d.ts +9 -0
  28. package/dist/chat/tools/scheduled-tasks.d.ts +4 -0
  29. package/dist/{chunk-5IR7SGRO.js → chunk-2BCQKJTX.js} +2 -2
  30. package/dist/{chunk-MZ6ZOWMJ.js → chunk-3N4MWF3R.js} +1 -1
  31. package/dist/{chunk-ETGUGGO2.js → chunk-6CMYU62S.js} +7 -7
  32. package/dist/{chunk-AAMF2IRC.js → chunk-6QQOLIJJ.js} +1308 -804
  33. package/dist/{chunk-R2LXTX4J.js → chunk-A6QQPVSH.js} +1 -1
  34. package/dist/chunk-BDIEEZK7.js +1470 -0
  35. package/dist/{chunk-AFJF3RGU.js → chunk-BVKP37NE.js} +61 -13
  36. package/dist/{chunk-54KP2EAH.js → chunk-CZJUBH6O.js} +12 -12
  37. package/dist/{chunk-O3YROBH4.js → chunk-CZQ2DSQZ.js} +1 -1
  38. package/dist/{chunk-O3C5BJFD.js → chunk-E4UCPRYL.js} +1 -1
  39. package/dist/{chunk-S2O64VXP.js → chunk-ESYXDXNX.js} +6 -6
  40. package/dist/{chunk-EHEACYJ6.js → chunk-GPWHBHAG.js} +1 -1
  41. package/dist/{chunk-PLAMHS3D.js → chunk-GURSWCI4.js} +2 -2
  42. package/dist/{chunk-Q5TMMCBV.js → chunk-K3U5447S.js} +1 -1
  43. package/dist/{chunk-SHMHHXD5.js → chunk-KB2M27DL.js} +58 -0
  44. package/dist/{chunk-LIJ4APSS.js → chunk-LV2BQ26V.js} +1 -1
  45. package/dist/{chunk-MKM5552O.js → chunk-NNRV4T2C.js} +1 -2
  46. package/dist/{chunk-TONH5NXR.js → chunk-P6I7LFKC.js} +1 -1
  47. package/dist/{chunk-J3YIO3LE.js → chunk-PQFNTLZI.js} +3 -3
  48. package/dist/{chunk-H4HMCSOZ.js → chunk-QAOA4GUG.js} +5 -5
  49. package/dist/{chunk-EOIA25WC.js → chunk-R62DRNKB.js} +2 -2
  50. package/dist/{chunk-OIQCRZ67.js → chunk-U23X4ASF.js} +4 -4
  51. package/dist/{chunk-P7UUNWC4.js → chunk-XENYUOZG.js} +4 -4
  52. package/dist/cli/chat.js +24 -24
  53. package/dist/cli/check.js +6 -6
  54. package/dist/cli/plugins.js +12 -12
  55. package/dist/cli/snapshot-create.js +9 -9
  56. package/dist/cli/upgrade.js +9 -17
  57. package/dist/db/schema/scheduled-tasks.d.ts +257 -0
  58. package/dist/db/schema.d.ts +258 -1
  59. package/dist/{db-VOU5QNLY.js → db-ZI6TBXIS.js} +7 -7
  60. package/dist/instrumentation.js +4 -4
  61. package/dist/nitro.js +2 -2
  62. package/dist/{oauth-callback-server-GS7FBYBC.js → oauth-callback-server-TLA4GARF.js} +17 -17
  63. package/dist/{runner-GSJSBI3J.js → runner-23YFRWQJ.js} +11 -11
  64. package/dist/{skills-DSHG54LM.js → skills-QIUG4D6M.js} +6 -6
  65. package/dist/{validation-25XWT6TA.js → validation-3KGEBATS.js} +6 -6
  66. package/dist/version.js +1 -1
  67. package/migrations/0016_neat_the_anarchist.sql +171 -0
  68. package/migrations/meta/0016_snapshot.json +1617 -0
  69. package/migrations/meta/_journal.json +7 -0
  70. package/package.json +5 -6
  71. package/dist/chunk-REHOYCYS.js +0 -37
@@ -0,0 +1,1470 @@
1
+ import {
2
+ canRouteResourceEvents,
3
+ getPlugins
4
+ } from "./chunk-QAOA4GUG.js";
5
+ import {
6
+ getDb
7
+ } from "./chunk-PQFNTLZI.js";
8
+ import {
9
+ juniorEventTasks,
10
+ juniorSchedulerRuns,
11
+ juniorSchedulerTasks,
12
+ juniorStats
13
+ } from "./chunk-KB2M27DL.js";
14
+ import {
15
+ juniorDestinations
16
+ } from "./chunk-VFUK3X5B.js";
17
+
18
+ // src/chat/event-tasks/store.ts
19
+ import { and, asc, desc, eq, inArray, or, sql } from "drizzle-orm";
20
+
21
+ // src/chat/event-tasks/types.ts
22
+ import {
23
+ destinationVisibilitySchema,
24
+ resourceEventTypeSchema,
25
+ slackActorSchema,
26
+ slackDestinationSchema
27
+ } from "@sentry/junior-plugin-api";
28
+ import { z } from "zod";
29
+ var EVENT_TASK_IDENTIFIER_MAX_LENGTH = 300;
30
+ var eventTaskPrincipalSchema = z.object({
31
+ slackUserId: slackActorSchema.shape.userId,
32
+ fullName: z.string().optional(),
33
+ userName: z.string().optional()
34
+ }).strict();
35
+ var eventTaskTriggerSchema = z.object({
36
+ events: z.array(resourceEventTypeSchema).min(1),
37
+ label: z.string().min(1),
38
+ namespace: z.string().min(1),
39
+ identifier: z.string().min(1).max(EVENT_TASK_IDENTIFIER_MAX_LENGTH),
40
+ resourceType: z.string().min(1)
41
+ }).strict();
42
+ var eventTaskSchema = z.object({
43
+ id: z.string().min(1),
44
+ createdAtMs: z.number().finite(),
45
+ createdBy: eventTaskPrincipalSchema,
46
+ credentialMode: z.enum(["system", "creator"]),
47
+ destination: slackDestinationSchema,
48
+ destinationVisibility: destinationVisibilitySchema,
49
+ task: z.object({ text: z.string().min(1) }).strict(),
50
+ trigger: eventTaskTriggerSchema
51
+ }).strict();
52
+
53
+ // src/chat/event-tasks/store.ts
54
+ function parseTask(value) {
55
+ return eventTaskSchema.parse(value);
56
+ }
57
+ async function getEventTask(db, id) {
58
+ const rows = await db.select({ task: juniorEventTasks.task }).from(juniorEventTasks).where(eq(juniorEventTasks.id, id)).limit(1);
59
+ return rows[0] ? parseTask(rows[0].task) : void 0;
60
+ }
61
+ async function createEventTask(db, task) {
62
+ const parsed = eventTaskSchema.parse(task);
63
+ await db.insert(juniorEventTasks).values({
64
+ id: parsed.id,
65
+ teamId: parsed.destination.teamId,
66
+ namespace: parsed.trigger.namespace,
67
+ identifier: parsed.trigger.identifier,
68
+ createdAtMs: parsed.createdAtMs,
69
+ task: parsed
70
+ }).onConflictDoNothing();
71
+ return await getEventTask(db, parsed.id) ?? parsed;
72
+ }
73
+ async function saveEventTask(db, task) {
74
+ const parsed = eventTaskSchema.parse(task);
75
+ const rows = await db.update(juniorEventTasks).set({
76
+ namespace: parsed.trigger.namespace,
77
+ identifier: parsed.trigger.identifier,
78
+ task: parsed
79
+ }).where(eq(juniorEventTasks.id, parsed.id)).returning({ task: juniorEventTasks.task });
80
+ return rows[0] ? parseTask(rows[0].task) : void 0;
81
+ }
82
+ async function deleteEventTask(db, id) {
83
+ const rows = await db.delete(juniorEventTasks).where(eq(juniorEventTasks.id, id)).returning({ task: juniorEventTasks.task });
84
+ return rows[0] ? parseTask(rows[0].task) : void 0;
85
+ }
86
+ async function listEventTasksForTeam(db, teamId) {
87
+ const rows = await db.select({ task: juniorEventTasks.task }).from(juniorEventTasks).where(eq(juniorEventTasks.teamId, teamId)).orderBy(asc(juniorEventTasks.createdAtMs), asc(juniorEventTasks.id));
88
+ return rows.map((row) => parseTask(row.task));
89
+ }
90
+ function viewerSlackIdentities(user) {
91
+ return user.identities.filter(
92
+ (identity) => identity.provider === "slack" && Boolean(identity.providerTenantId) && Boolean(identity.providerSubjectId)
93
+ );
94
+ }
95
+ function eventTaskBelongsToUser(task, user) {
96
+ return viewerSlackIdentities(user).some(
97
+ (identity) => identity.providerTenantId === task.destination.teamId && identity.providerSubjectId === task.createdBy.slackUserId
98
+ );
99
+ }
100
+ async function listEventTasksCreatedBy(db, user, limit) {
101
+ const identities = viewerSlackIdentities(user);
102
+ const ownership = or(
103
+ ...identities.map(
104
+ (identity) => and(
105
+ eq(juniorEventTasks.teamId, identity.providerTenantId),
106
+ sql`${juniorEventTasks.task}->'createdBy'->>'slackUserId' = ${identity.providerSubjectId}`
107
+ )
108
+ )
109
+ );
110
+ if (!ownership) return [];
111
+ const rows = await db.select({ task: juniorEventTasks.task }).from(juniorEventTasks).where(ownership).orderBy(desc(juniorEventTasks.createdAtMs), desc(juniorEventTasks.id)).limit(limit);
112
+ return rows.map((row) => parseTask(row.task));
113
+ }
114
+ async function listPublicEventTasksForTeams(db, teamIds, limit) {
115
+ if (teamIds.length === 0) return [];
116
+ const rows = await db.select({ task: juniorEventTasks.task }).from(juniorEventTasks).innerJoin(
117
+ juniorDestinations,
118
+ and(
119
+ eq(juniorDestinations.provider, "slack"),
120
+ eq(juniorDestinations.providerTenantId, juniorEventTasks.teamId),
121
+ sql`${juniorDestinations.providerDestinationId} = ${juniorEventTasks.task}->'destination'->>'channelId'`
122
+ )
123
+ ).where(
124
+ and(
125
+ inArray(juniorEventTasks.teamId, teamIds),
126
+ eq(juniorDestinations.visibility, "public")
127
+ )
128
+ ).orderBy(desc(juniorEventTasks.createdAtMs), desc(juniorEventTasks.id)).limit(limit);
129
+ return rows.map((row) => parseTask(row.task));
130
+ }
131
+ async function findMatchingEventTasks(db, event, teamId) {
132
+ const rows = await db.select({ task: juniorEventTasks.task }).from(juniorEventTasks).where(
133
+ and(
134
+ eq(juniorEventTasks.teamId, teamId),
135
+ eq(juniorEventTasks.namespace, event.namespace),
136
+ eq(juniorEventTasks.identifier, event.identifier)
137
+ )
138
+ ).orderBy(asc(juniorEventTasks.createdAtMs), asc(juniorEventTasks.id));
139
+ return rows.map((row) => parseTask(row.task)).filter((task) => task.trigger.events.includes(event.eventType));
140
+ }
141
+
142
+ // src/stats.ts
143
+ import { and as and2, asc as asc2, gte, lte, sql as sql2 } from "drizzle-orm";
144
+ function utcDate(nowMs) {
145
+ return new Date(nowMs).toISOString().slice(0, 10);
146
+ }
147
+ async function incrementStat(key, options = {}) {
148
+ const date = utcDate(options.nowMs ?? Date.now());
149
+ await getDb().insert(juniorStats).values({ ...key, date, count: 1 }).onConflictDoUpdate({
150
+ target: [
151
+ juniorStats.date,
152
+ juniorStats.namespace,
153
+ juniorStats.metric,
154
+ juniorStats.name
155
+ ],
156
+ set: { count: sql2`${juniorStats.count} + 1` }
157
+ });
158
+ }
159
+ async function readStats(start, end) {
160
+ return await getDb().select().from(juniorStats).where(and2(gte(juniorStats.date, start), lte(juniorStats.date, end))).orderBy(
161
+ asc2(juniorStats.date),
162
+ asc2(juniorStats.namespace),
163
+ asc2(juniorStats.metric),
164
+ asc2(juniorStats.name)
165
+ );
166
+ }
167
+
168
+ // src/chat/tool-support/structured-result.ts
169
+ import { z as z2 } from "zod";
170
+ var juniorToolContinuationSchema = z2.object({
171
+ arguments: z2.record(z2.string(), z2.unknown()),
172
+ reason: z2.string().min(1).optional()
173
+ }).strict();
174
+ var juniorToolBoundContinuationSchema = juniorToolContinuationSchema.extend({
175
+ tool_name: z2.string().min(1)
176
+ });
177
+ var juniorToolOutputSchema = z2.object({
178
+ target: z2.string().min(1).optional(),
179
+ truncated: z2.boolean().optional(),
180
+ continuation: juniorToolContinuationSchema.optional()
181
+ }).passthrough();
182
+ function sortJsonValue(value) {
183
+ if (Array.isArray(value)) {
184
+ return value.map(sortJsonValue);
185
+ }
186
+ if (!value || typeof value !== "object") {
187
+ return value;
188
+ }
189
+ return Object.fromEntries(
190
+ Object.entries(value).filter(([, item]) => item !== void 0).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => [key, sortJsonValue(item)])
191
+ );
192
+ }
193
+ function isRecord(value) {
194
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
195
+ }
196
+ function injectContinuationToolName(details, toolName) {
197
+ if (!isRecord(details) || !isRecord(details.continuation)) {
198
+ return details;
199
+ }
200
+ return {
201
+ ...details,
202
+ continuation: juniorToolBoundContinuationSchema.parse({
203
+ ...details.continuation,
204
+ tool_name: toolName
205
+ })
206
+ };
207
+ }
208
+ function makeStructuredToolOutput(details, options = {}) {
209
+ return {
210
+ content: [
211
+ {
212
+ type: "text",
213
+ text: JSON.stringify(sortJsonValue(details))
214
+ },
215
+ ...options.content ?? []
216
+ ],
217
+ details
218
+ };
219
+ }
220
+
221
+ // src/chat/tools/execution/tool-input-error.ts
222
+ var ToolInputError = class extends Error {
223
+ constructor(message, options) {
224
+ super(message, options);
225
+ this.name = "ToolInputError";
226
+ }
227
+ };
228
+
229
+ // src/chat/resource-events/runtime-catalog.ts
230
+ function getResourceEventCatalog() {
231
+ return Object.fromEntries(
232
+ canRouteResourceEvents() ? getPlugins().flatMap((plugin) => {
233
+ const registration = plugin.resourceEvents;
234
+ if (!registration || registration.isEnabled?.() === false) {
235
+ return [];
236
+ }
237
+ return [[plugin.manifest.name, registration]];
238
+ }) : []
239
+ );
240
+ }
241
+
242
+ // src/chat/event-tasks/tool-support.ts
243
+ import { z as z4 } from "zod";
244
+
245
+ // src/chat/resource-events/catalog.ts
246
+ import {
247
+ normalizeResourceEventIdentifier
248
+ } from "@sentry/junior-plugin-api";
249
+ import { z as z3 } from "zod";
250
+ function enumSchema(values, unavailableMessage) {
251
+ if (values.length === 0) {
252
+ return z3.string().refine(() => false, unavailableMessage);
253
+ }
254
+ return z3.enum(values);
255
+ }
256
+ function eventNamespaceSchema(catalog) {
257
+ return enumSchema(
258
+ Object.keys(catalog).sort(),
259
+ "No resource event namespaces are enabled."
260
+ );
261
+ }
262
+ function registeredEventTypeSchema(catalog) {
263
+ return enumSchema(
264
+ [
265
+ ...new Set(
266
+ Object.values(catalog).flatMap(
267
+ (registration) => registration.resourceTypes.flatMap(
268
+ (resourceType) => resourceType.supportedEvents
269
+ )
270
+ )
271
+ )
272
+ ].sort(),
273
+ "No resource event types are enabled."
274
+ );
275
+ }
276
+ function registeredResourceTypeSchema(catalog) {
277
+ return enumSchema(
278
+ [
279
+ ...new Set(
280
+ Object.values(catalog).flatMap(
281
+ (registration) => registration.resourceTypes.map((resourceType) => resourceType.type)
282
+ )
283
+ )
284
+ ].sort(),
285
+ "No resource types are enabled."
286
+ );
287
+ }
288
+ function pluginSupportsEvent(catalog, namespace, resourceType, eventType) {
289
+ return catalog[namespace]?.resourceTypes.find((candidate) => candidate.type === resourceType)?.supportedEvents.includes(eventType) ?? false;
290
+ }
291
+ function normalizeEventIdentifier(catalog, namespace, identifier) {
292
+ return normalizeResourceEventIdentifier(catalog[namespace], identifier);
293
+ }
294
+
295
+ // src/chat/event-tasks/tool-support.ts
296
+ var compactEventTaskResultSchema = z4.object({
297
+ id: z4.string().min(1),
298
+ task: z4.string().min(1),
299
+ namespace: z4.string().min(1),
300
+ identifier: z4.string().min(1),
301
+ resourceType: z4.string().min(1),
302
+ label: z4.string().min(1),
303
+ events: z4.array(z4.string().min(1)).min(1),
304
+ credentialMode: z4.enum(["system", "creator"]),
305
+ createdBy: eventTaskPrincipalSchema,
306
+ triggerAvailable: z4.boolean()
307
+ }).strict();
308
+ var eventTaskToolResultSchema = juniorToolOutputSchema.extend({
309
+ task: compactEventTaskResultSchema
310
+ }).strict();
311
+ var eventTaskListToolResultSchema = juniorToolOutputSchema.extend({
312
+ tasks: z4.array(compactEventTaskResultSchema),
313
+ truncated: z4.boolean()
314
+ }).strict();
315
+ function registeredEventTaskTriggerSchema(catalog) {
316
+ return z4.object({
317
+ namespace: eventNamespaceSchema(catalog),
318
+ identifier: z4.string().trim().min(1).max(EVENT_TASK_IDENTIFIER_MAX_LENGTH),
319
+ resourceType: registeredResourceTypeSchema(catalog),
320
+ label: z4.string().trim().min(1).max(500),
321
+ events: z4.array(registeredEventTypeSchema(catalog)).min(1)
322
+ }).strict();
323
+ }
324
+ function requireSupportedEventTaskTrigger(catalog, trigger) {
325
+ for (const eventType of trigger.events) {
326
+ if (!pluginSupportsEvent(
327
+ catalog,
328
+ trigger.namespace,
329
+ trigger.resourceType,
330
+ eventType
331
+ )) {
332
+ throw new ToolInputError(
333
+ `Resource type "${trigger.namespace}:${trigger.resourceType}" does not support event "${eventType}".`
334
+ );
335
+ }
336
+ }
337
+ }
338
+ function requireEventTaskSlackContext(context) {
339
+ if (context.source.platform !== "slack" || context.destination.platform !== "slack" || context.actor?.platform !== "slack" || context.actor.teamId !== context.source.teamId) {
340
+ throw new ToolInputError(
341
+ "Event tasks require an active Slack channel or DM and actor."
342
+ );
343
+ }
344
+ return {
345
+ actor: context.actor,
346
+ destination: {
347
+ platform: "slack",
348
+ teamId: context.source.teamId,
349
+ channelId: context.source.channelId
350
+ },
351
+ source: context.source
352
+ };
353
+ }
354
+ function eventTaskMatchesDestination(task, destination) {
355
+ return task.destination.teamId === destination.teamId && task.destination.channelId === destination.channelId;
356
+ }
357
+ async function writableEventTask(context, id) {
358
+ const { destination } = requireEventTaskSlackContext(context);
359
+ const task = await getEventTask(getDb(), id);
360
+ if (!task || !eventTaskMatchesDestination(task, destination)) {
361
+ throw new ToolInputError(
362
+ "Event task was not found in this Slack channel or DM."
363
+ );
364
+ }
365
+ return task;
366
+ }
367
+ function eventTaskTriggerAvailable(task, catalog) {
368
+ const registration = catalog[task.trigger.namespace];
369
+ return Boolean(
370
+ registration && task.trigger.events.every(
371
+ (eventType) => registration.resourceTypes.some(
372
+ (resourceType) => resourceType.supportedEvents.includes(eventType)
373
+ )
374
+ )
375
+ );
376
+ }
377
+ function compactEventTask(task, catalog) {
378
+ return compactEventTaskResultSchema.parse({
379
+ id: task.id,
380
+ task: task.task.text,
381
+ namespace: task.trigger.namespace,
382
+ identifier: task.trigger.identifier,
383
+ resourceType: task.trigger.resourceType,
384
+ label: task.trigger.label,
385
+ events: task.trigger.events,
386
+ credentialMode: task.credentialMode,
387
+ createdBy: task.createdBy,
388
+ triggerAvailable: eventTaskTriggerAvailable(task, catalog)
389
+ });
390
+ }
391
+ function eventTaskSuccess(task, catalog) {
392
+ return {
393
+ task: compactEventTask(task, catalog)
394
+ };
395
+ }
396
+
397
+ // src/chat/scheduled-tasks/store.ts
398
+ import {
399
+ destinationSchema,
400
+ isSlackDestination,
401
+ slackActorSchema as slackActorSchema2
402
+ } from "@sentry/junior-plugin-api";
403
+ import {
404
+ and as and3,
405
+ asc as asc3,
406
+ desc as desc2,
407
+ eq as eq2,
408
+ inArray as inArray2,
409
+ isNotNull,
410
+ lt,
411
+ lte as lte2,
412
+ ne,
413
+ or as or2,
414
+ sql as sql3
415
+ } from "drizzle-orm";
416
+ import { z as z6 } from "zod";
417
+
418
+ // src/chat/scheduled-tasks/cadence.ts
419
+ var FORMATTERS = /* @__PURE__ */ new Map();
420
+ function getFormatter(timezone) {
421
+ const existing = FORMATTERS.get(timezone);
422
+ if (existing) {
423
+ return existing;
424
+ }
425
+ const formatter = new Intl.DateTimeFormat("en-US", {
426
+ timeZone: timezone,
427
+ hour12: false,
428
+ year: "numeric",
429
+ month: "2-digit",
430
+ day: "2-digit",
431
+ hour: "2-digit",
432
+ minute: "2-digit",
433
+ second: "2-digit"
434
+ });
435
+ FORMATTERS.set(timezone, formatter);
436
+ return formatter;
437
+ }
438
+ function normalizeHour(hour) {
439
+ return hour === 24 ? 0 : hour;
440
+ }
441
+ function getLocalDateWeekday(date) {
442
+ return new Date(Date.UTC(date.year, date.month - 1, date.day)).getUTCDay();
443
+ }
444
+ function getWeekStart(date) {
445
+ return addDays(date, -((getLocalDateWeekday(date) + 6) % 7));
446
+ }
447
+ function getZonedDateTimeParts(timestampMs, timezone) {
448
+ const parts = getFormatter(timezone).formatToParts(new Date(timestampMs));
449
+ const values = new Map(parts.map((part) => [part.type, part.value]));
450
+ const year = Number(values.get("year"));
451
+ const month = Number(values.get("month"));
452
+ const day = Number(values.get("day"));
453
+ const hour = normalizeHour(Number(values.get("hour")));
454
+ const minute = Number(values.get("minute"));
455
+ const second = Number(values.get("second"));
456
+ return {
457
+ year,
458
+ month,
459
+ day,
460
+ hour,
461
+ minute,
462
+ second,
463
+ weekday: getLocalDateWeekday({ year, month, day })
464
+ };
465
+ }
466
+ function getTimeZoneOffsetMs(timestampMs, timezone) {
467
+ const parts = getZonedDateTimeParts(timestampMs, timezone);
468
+ return Date.UTC(
469
+ parts.year,
470
+ parts.month - 1,
471
+ parts.day,
472
+ parts.hour,
473
+ parts.minute,
474
+ parts.second
475
+ ) - timestampMs;
476
+ }
477
+ function localDateTimeToTimestampMs(args) {
478
+ const localAsUtcMs = Date.UTC(
479
+ args.date.year,
480
+ args.date.month - 1,
481
+ args.date.day,
482
+ args.time.hour,
483
+ args.time.minute,
484
+ 0
485
+ );
486
+ const offsets = /* @__PURE__ */ new Set();
487
+ for (const probeDeltaMs of [
488
+ -36 * 60 * 60 * 1e3,
489
+ -12 * 60 * 60 * 1e3,
490
+ 0,
491
+ 12 * 60 * 60 * 1e3,
492
+ 36 * 60 * 60 * 1e3
493
+ ]) {
494
+ offsets.add(
495
+ getTimeZoneOffsetMs(localAsUtcMs + probeDeltaMs, args.timezone)
496
+ );
497
+ }
498
+ const matches = [...offsets].map((offsetMs) => localAsUtcMs - offsetMs).filter((timestampMs) => {
499
+ const parts = getZonedDateTimeParts(timestampMs, args.timezone);
500
+ return parts.year === args.date.year && parts.month === args.date.month && parts.day === args.date.day && parts.hour === args.time.hour && parts.minute === args.time.minute;
501
+ });
502
+ if (matches.length === 0) {
503
+ return void 0;
504
+ }
505
+ return Math.min(...matches);
506
+ }
507
+ function resolveLocalScheduleAtMs(args) {
508
+ const date = parseLocalDate(args.date);
509
+ if (!date) {
510
+ return void 0;
511
+ }
512
+ return localDateTimeToTimestampMs({
513
+ date,
514
+ time: args.time,
515
+ timezone: args.timezone
516
+ });
517
+ }
518
+ function daysBetween(left, right) {
519
+ return Math.floor(
520
+ (Date.UTC(right.year, right.month - 1, right.day) - Date.UTC(left.year, left.month - 1, left.day)) / (24 * 60 * 60 * 1e3)
521
+ );
522
+ }
523
+ function weeklyRecurrenceMatchesDate(date, start, recurrence) {
524
+ if (compareDate(date, start) < 0) {
525
+ return false;
526
+ }
527
+ return normalizeWeekdays(recurrence.weekdays).includes(
528
+ getLocalDateWeekday(date)
529
+ ) && Math.floor(daysBetween(getWeekStart(start), getWeekStart(date)) / 7) % recurrence.interval === 0;
530
+ }
531
+ function greatestCommonDivisor(left, right) {
532
+ let a = Math.abs(left);
533
+ let b = Math.abs(right);
534
+ while (b !== 0) {
535
+ [a, b] = [b, a % b];
536
+ }
537
+ return a;
538
+ }
539
+ function findNextRunAtMs(args) {
540
+ const start = parseLocalDate(args.recurrence.startDate);
541
+ const interval = args.recurrence.interval;
542
+ if (!start || !Number.isInteger(interval) || interval <= 0) {
543
+ return void 0;
544
+ }
545
+ const searchFrom = compareDate(args.searchFrom, start) < 0 ? start : args.searchFrom;
546
+ if (args.recurrence.frequency === "daily") {
547
+ const offsetDays = daysBetween(start, searchFrom);
548
+ let candidateDate = addDays(
549
+ start,
550
+ Math.ceil(offsetDays / interval) * interval
551
+ );
552
+ const gregorianCycleDays = 146097;
553
+ const candidateCount2 = gregorianCycleDays / greatestCommonDivisor(gregorianCycleDays, interval);
554
+ for (let attempts = 0; attempts < candidateCount2; attempts += 1) {
555
+ const candidate = buildCandidate({
556
+ date: candidateDate,
557
+ recurrence: args.recurrence,
558
+ timezone: args.timezone
559
+ });
560
+ if (candidate !== void 0 && candidate > args.afterMs) {
561
+ return candidate;
562
+ }
563
+ candidateDate = addDays(candidateDate, interval);
564
+ }
565
+ return void 0;
566
+ }
567
+ if (args.recurrence.frequency === "weekly") {
568
+ if (normalizeWeekdays(args.recurrence.weekdays).length === 0) {
569
+ return void 0;
570
+ }
571
+ let candidateDate = searchFrom;
572
+ const gregorianCycleDays = 146097;
573
+ const cadenceDays = interval * 7;
574
+ const searchDays = gregorianCycleDays * cadenceDays / greatestCommonDivisor(gregorianCycleDays, cadenceDays);
575
+ for (let attempts = 0; attempts < searchDays; attempts += 1) {
576
+ if (weeklyRecurrenceMatchesDate(candidateDate, start, args.recurrence)) {
577
+ const candidate = buildCandidate({
578
+ date: candidateDate,
579
+ recurrence: args.recurrence,
580
+ timezone: args.timezone
581
+ });
582
+ if (candidate !== void 0 && candidate > args.afterMs) {
583
+ return candidate;
584
+ }
585
+ }
586
+ candidateDate = addDays(candidateDate, 1);
587
+ }
588
+ return void 0;
589
+ }
590
+ if (args.recurrence.frequency === "monthly") {
591
+ const startMonth = start.year * 12 + start.month - 1;
592
+ const searchMonth = searchFrom.year * 12 + searchFrom.month - 1;
593
+ let candidateMonth = startMonth + Math.ceil((searchMonth - startMonth) / interval) * interval;
594
+ const gregorianCycleMonths = 4800;
595
+ const candidateCount2 = gregorianCycleMonths / greatestCommonDivisor(gregorianCycleMonths, interval);
596
+ for (let attempts = 0; attempts < candidateCount2; attempts += 1) {
597
+ const candidateDate = {
598
+ year: Math.floor(candidateMonth / 12),
599
+ month: candidateMonth % 12 + 1,
600
+ day: args.recurrence.dayOfMonth ?? 0
601
+ };
602
+ if (compareDate(candidateDate, searchFrom) >= 0) {
603
+ const candidate = buildCandidate({
604
+ date: candidateDate,
605
+ recurrence: args.recurrence,
606
+ timezone: args.timezone
607
+ });
608
+ if (candidate !== void 0 && candidate > args.afterMs) {
609
+ return candidate;
610
+ }
611
+ }
612
+ candidateMonth += interval;
613
+ }
614
+ return void 0;
615
+ }
616
+ let candidateYear = start.year + Math.ceil((searchFrom.year - start.year) / interval) * interval;
617
+ const candidateCount = 400 / greatestCommonDivisor(400, interval);
618
+ for (let attempts = 0; attempts < candidateCount; attempts += 1) {
619
+ const candidateDate = {
620
+ year: candidateYear,
621
+ month: args.recurrence.month ?? 0,
622
+ day: args.recurrence.dayOfMonth ?? 0
623
+ };
624
+ if (compareDate(candidateDate, searchFrom) >= 0) {
625
+ const candidate = buildCandidate({
626
+ date: candidateDate,
627
+ recurrence: args.recurrence,
628
+ timezone: args.timezone
629
+ });
630
+ if (candidate !== void 0 && candidate > args.afterMs) {
631
+ return candidate;
632
+ }
633
+ }
634
+ candidateYear += interval;
635
+ }
636
+ return void 0;
637
+ }
638
+ function getFirstRunAtMs(args) {
639
+ return findNextRunAtMs({
640
+ ...args,
641
+ searchFrom: getLocalDate(args.afterMs, args.timezone)
642
+ });
643
+ }
644
+ function compareDate(left, right) {
645
+ return Date.UTC(left.year, left.month - 1, left.day) - Date.UTC(right.year, right.month - 1, right.day);
646
+ }
647
+ function addDays(date, days) {
648
+ const next = new Date(Date.UTC(date.year, date.month - 1, date.day + days));
649
+ return {
650
+ year: next.getUTCFullYear(),
651
+ month: next.getUTCMonth() + 1,
652
+ day: next.getUTCDate()
653
+ };
654
+ }
655
+ function daysInMonth(year, month) {
656
+ return new Date(Date.UTC(year, month, 0)).getUTCDate();
657
+ }
658
+ function parseLocalDate(value) {
659
+ const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
660
+ if (!match) {
661
+ return void 0;
662
+ }
663
+ const year = Number(match[1]);
664
+ const month = Number(match[2]);
665
+ const day = Number(match[3]);
666
+ if (!Number.isInteger(year) || !Number.isInteger(month) || !Number.isInteger(day) || month < 1 || month > 12 || day < 1 || day > daysInMonth(year, month)) {
667
+ return void 0;
668
+ }
669
+ return { year, month, day };
670
+ }
671
+ function getLocalDate(timestampMs, timezone) {
672
+ const parts = getZonedDateTimeParts(timestampMs, timezone);
673
+ return { year: parts.year, month: parts.month, day: parts.day };
674
+ }
675
+ function normalizeWeekdays(values) {
676
+ return [
677
+ ...new Set((values ?? []).filter((value) => value >= 0 && value <= 6))
678
+ ].sort((a, b) => a - b);
679
+ }
680
+ function buildCandidate(args) {
681
+ return localDateTimeToTimestampMs({
682
+ date: args.date,
683
+ time: args.recurrence.time,
684
+ timezone: args.timezone
685
+ });
686
+ }
687
+ function getNextRunAtMs(task, scheduledForMs, afterMs = scheduledForMs) {
688
+ if (task.schedule.kind !== "recurring") {
689
+ return void 0;
690
+ }
691
+ const recurrence = task.schedule.recurrence;
692
+ if (!recurrence || !Number.isFinite(recurrence.interval) || recurrence.interval <= 0) {
693
+ return void 0;
694
+ }
695
+ const timezone = task.schedule.timezone;
696
+ const afterDate = getLocalDate(afterMs, timezone);
697
+ const nextScheduledDate = addDays(getLocalDate(scheduledForMs, timezone), 1);
698
+ return findNextRunAtMs({
699
+ afterMs,
700
+ recurrence,
701
+ searchFrom: compareDate(afterDate, nextScheduledDate) < 0 ? nextScheduledDate : afterDate,
702
+ timezone
703
+ });
704
+ }
705
+
706
+ // src/chat/scheduled-tasks/types.ts
707
+ import { z as z5 } from "zod";
708
+ var scheduledTaskCredentialModeSchema = z5.enum(["system", "creator"]);
709
+ var SCHEDULED_TASK_SYSTEM_ACTOR = Object.freeze({
710
+ platform: "system",
711
+ name: "scheduled-task"
712
+ });
713
+
714
+ // src/chat/scheduled-tasks/store.ts
715
+ var SCHEDULER_KEY_PREFIX = "junior:scheduler";
716
+ var SCHEDULER_RECORD_TTL_MS = 5 * 365 * 24 * 60 * 60 * 1e3;
717
+ var SCHEDULED_RUN_TTL_MS = 90 * 24 * 60 * 60 * 1e3;
718
+ var CLAIM_TTL_MS = 6 * 60 * 60 * 1e3;
719
+ var PENDING_CLAIM_STALE_MS = 6e4;
720
+ var MISSED_RUN_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
721
+ var SQL_INCOMPLETE_RUN_STATUSES = ["pending", "running"];
722
+ var slackDestinationSchema2 = destinationSchema.refine(isSlackDestination);
723
+ var taskPrincipalSchema = z6.object({
724
+ slackUserId: slackActorSchema2.shape.userId,
725
+ fullName: z6.string().optional(),
726
+ userName: z6.string().optional()
727
+ }).strict();
728
+ var recurrenceSchema = z6.object({
729
+ dayOfMonth: z6.number().optional(),
730
+ frequency: z6.enum(["daily", "weekly", "monthly", "yearly"]),
731
+ interval: z6.number(),
732
+ month: z6.number().optional(),
733
+ startDate: z6.string(),
734
+ time: z6.object({
735
+ hour: z6.number(),
736
+ minute: z6.number()
737
+ }).strict(),
738
+ weekdays: z6.array(z6.number()).optional()
739
+ }).strict();
740
+ var taskScheduleSchema = z6.object({
741
+ description: z6.string(),
742
+ kind: z6.enum(["one_off", "recurring"]),
743
+ recurrence: recurrenceSchema.optional(),
744
+ timezone: z6.string()
745
+ }).strict();
746
+ var taskSpecSchema = z6.object({
747
+ text: z6.string()
748
+ }).strict();
749
+ var taskRecordFields = {
750
+ id: z6.string(),
751
+ conversationAccess: z6.object({
752
+ audience: z6.enum(["direct", "group", "channel"]),
753
+ visibility: z6.enum(["private", "public"])
754
+ }).strict(),
755
+ createdAtMs: z6.number(),
756
+ createdBy: taskPrincipalSchema,
757
+ creatorIdentityId: z6.string(),
758
+ destination: slackDestinationSchema2,
759
+ executionActor: z6.object({
760
+ platform: z6.literal("system"),
761
+ name: z6.string()
762
+ }).strict().optional(),
763
+ lastRunAtMs: z6.number().optional(),
764
+ nextRunAtMs: z6.number().optional(),
765
+ originalRequest: z6.string().optional(),
766
+ runNowAtMs: z6.number().optional(),
767
+ schedule: taskScheduleSchema,
768
+ status: z6.enum(["active", "paused", "blocked", "deleted"]),
769
+ statusReason: z6.string().optional(),
770
+ task: taskSpecSchema,
771
+ updatedAtMs: z6.number(),
772
+ version: z6.number().optional()
773
+ };
774
+ var taskRecordSchema = z6.object({
775
+ ...taskRecordFields,
776
+ credentialMode: scheduledTaskCredentialModeSchema
777
+ }).strict();
778
+ var runRecordSchema = z6.object({
779
+ id: z6.string(),
780
+ attempt: z6.number(),
781
+ claimedAtMs: z6.number(),
782
+ completedAtMs: z6.number().optional(),
783
+ dispatchId: z6.string().optional(),
784
+ errorMessage: z6.string().optional(),
785
+ idempotencyKey: z6.string().optional(),
786
+ resultMessageTs: z6.string().optional(),
787
+ scheduledForMs: z6.number(),
788
+ startedAtMs: z6.number().optional(),
789
+ status: z6.enum([
790
+ "pending",
791
+ "running",
792
+ "completed",
793
+ "failed",
794
+ "blocked",
795
+ "skipped"
796
+ ]),
797
+ taskId: z6.string(),
798
+ taskVersion: z6.number().optional()
799
+ }).strict();
800
+ function normalizedTaskQuery(query) {
801
+ return query?.trim().toLowerCase() || void 0;
802
+ }
803
+ function taskKey(taskId) {
804
+ return `${SCHEDULER_KEY_PREFIX}:task:${taskId}`;
805
+ }
806
+ function taskLockKey(taskId) {
807
+ return `${taskKey(taskId)}:lock`;
808
+ }
809
+ function runKey(runId) {
810
+ return `${SCHEDULER_KEY_PREFIX}:run:${runId}`;
811
+ }
812
+ function indexLockKey(indexKey) {
813
+ return `${indexKey}:lock`;
814
+ }
815
+ function buildRunId(taskId, scheduledForMs) {
816
+ return `${taskId}:${scheduledForMs}`;
817
+ }
818
+ var schedulerTaskIndexSchema = z6.array(z6.string().min(1));
819
+ function isStalePendingRun(run, nowMs) {
820
+ return run?.status === "pending" && run.claimedAtMs + PENDING_CLAIM_STALE_MS <= nowMs;
821
+ }
822
+ function getDueRunAtMs(task, nowMs) {
823
+ if (typeof task.runNowAtMs === "number" && Number.isFinite(task.runNowAtMs) && task.runNowAtMs <= nowMs) {
824
+ return task.runNowAtMs;
825
+ }
826
+ if (typeof task.nextRunAtMs === "number" && Number.isFinite(task.nextRunAtMs) && task.nextRunAtMs <= nowMs) {
827
+ return task.nextRunAtMs;
828
+ }
829
+ return void 0;
830
+ }
831
+ function buildScheduledRun(args) {
832
+ return {
833
+ id: buildRunId(args.task.id, args.scheduledForMs),
834
+ attempt: 1,
835
+ claimedAtMs: args.claimedAtMs,
836
+ scheduledForMs: args.scheduledForMs,
837
+ status: "pending",
838
+ taskId: args.task.id
839
+ };
840
+ }
841
+ function buildSkippedScheduledRun(args) {
842
+ return {
843
+ ...buildScheduledRun({
844
+ claimedAtMs: args.completedAtMs,
845
+ scheduledForMs: args.scheduledForMs,
846
+ task: args.task
847
+ }),
848
+ completedAtMs: args.completedAtMs,
849
+ errorMessage: args.errorMessage,
850
+ status: "skipped"
851
+ };
852
+ }
853
+ function isMissedRunTooOld(args) {
854
+ return args.scheduledForMs + MISSED_RUN_MAX_AGE_MS < args.nowMs;
855
+ }
856
+ function normalizedText(value) {
857
+ return value?.trim().replace(/\s+/g, " ").toLowerCase() ?? "";
858
+ }
859
+ function taskDedupeFingerprint(task) {
860
+ return JSON.stringify({
861
+ credentialMode: task.credentialMode,
862
+ credentialUserId: task.credentialMode === "creator" ? task.createdBy.slackUserId : null,
863
+ destination: task.destination,
864
+ schedule: {
865
+ kind: task.schedule.kind,
866
+ oneOffAtMs: task.schedule.kind === "one_off" ? task.nextRunAtMs : null,
867
+ recurrence: task.schedule.recurrence ? {
868
+ dayOfMonth: task.schedule.recurrence.dayOfMonth ?? null,
869
+ frequency: task.schedule.recurrence.frequency,
870
+ interval: task.schedule.recurrence.interval,
871
+ month: task.schedule.recurrence.month ?? null,
872
+ startDate: task.schedule.recurrence.startDate,
873
+ time: task.schedule.recurrence.time,
874
+ weekdays: [...task.schedule.recurrence.weekdays ?? []].sort()
875
+ } : null,
876
+ timezone: task.schedule.timezone
877
+ },
878
+ task: normalizedText(task.task.text)
879
+ });
880
+ }
881
+ function isEarlierTask(left, right) {
882
+ return left.createdAtMs < right.createdAtMs || left.createdAtMs === right.createdAtMs && left.id < right.id;
883
+ }
884
+ function canFinishRun(run, startedAtMs) {
885
+ if (run.status === "pending") {
886
+ return startedAtMs === void 0;
887
+ }
888
+ return run.status === "running" && run.startedAtMs === startedAtMs;
889
+ }
890
+ function parseStoredTask(value) {
891
+ const parsed = taskRecordSchema.safeParse(parseJsonRecord(value));
892
+ return parsed.success ? stripLegacyTaskFields(parsed.data) : void 0;
893
+ }
894
+ function stripLegacyTaskFields(task) {
895
+ const { version: _version, ...current } = task;
896
+ return current;
897
+ }
898
+ function stripLegacyRunFields(run) {
899
+ const {
900
+ idempotencyKey: _idempotencyKey,
901
+ taskVersion: _taskVersion,
902
+ ...current
903
+ } = run;
904
+ return current;
905
+ }
906
+ function parseJsonRecord(value) {
907
+ if (typeof value === "string") {
908
+ try {
909
+ return JSON.parse(value);
910
+ } catch {
911
+ return void 0;
912
+ }
913
+ }
914
+ if (value && typeof value === "object") {
915
+ return value;
916
+ }
917
+ return void 0;
918
+ }
919
+ function present(value) {
920
+ return value !== void 0;
921
+ }
922
+ function requireStoredTask(task) {
923
+ const parsed = parseStoredTask(task);
924
+ if (!parsed) {
925
+ throw new Error("Scheduled task routing context is invalid.");
926
+ }
927
+ return parsed;
928
+ }
929
+ function parseSqlTaskRecord(value, creatorIdentityId) {
930
+ const record = parseJsonRecord(value);
931
+ const parsed = taskRecordSchema.safeParse(
932
+ record && creatorIdentityId ? { ...record, creatorIdentityId } : record
933
+ );
934
+ return parsed.success ? stripLegacyTaskFields(parsed.data) : void 0;
935
+ }
936
+ function parseSqlTaskRow(row) {
937
+ return parseSqlTaskRecord(row.record, row.creatorIdentityId);
938
+ }
939
+ function parseSqlRunRow(row) {
940
+ const parsed = runRecordSchema.safeParse(parseJsonRecord(row.record));
941
+ return parsed.success ? stripLegacyRunFields(parsed.data) : void 0;
942
+ }
943
+ async function withSqlLock(db, key, callback) {
944
+ return await db.transaction(async (tx) => {
945
+ await tx.execute(sql3`SELECT pg_advisory_xact_lock(hashtext(${key}))`);
946
+ return await callback(tx);
947
+ });
948
+ }
949
+ async function upsertSqlTask(db, task) {
950
+ await db.insert(juniorSchedulerTasks).values({
951
+ createdAtMs: task.createdAtMs,
952
+ creatorIdentityId: task.creatorIdentityId,
953
+ // TODO(v0.128.0): Remove after v0.127.x runtimes no longer read this column.
954
+ creatorSlackUserId: task.createdBy.slackUserId,
955
+ id: task.id,
956
+ nextRunAtMs: task.nextRunAtMs,
957
+ record: task,
958
+ runNowAtMs: task.runNowAtMs,
959
+ status: task.status,
960
+ teamId: task.destination.teamId
961
+ }).onConflictDoUpdate({
962
+ target: juniorSchedulerTasks.id,
963
+ set: {
964
+ createdAtMs: sql3`excluded.created_at_ms`,
965
+ creatorIdentityId: sql3`excluded.creator_identity_id`,
966
+ creatorSlackUserId: sql3`excluded.creator_slack_user_id`,
967
+ nextRunAtMs: sql3`excluded.next_run_at_ms`,
968
+ record: sql3`excluded.record`,
969
+ runNowAtMs: sql3`excluded.run_now_at_ms`,
970
+ status: sql3`excluded.status`,
971
+ teamId: sql3`excluded.team_id`
972
+ }
973
+ });
974
+ }
975
+ async function upsertSqlRun(db, run) {
976
+ await db.insert(juniorSchedulerRuns).values({
977
+ id: run.id,
978
+ record: run,
979
+ scheduledForMs: run.scheduledForMs,
980
+ status: run.status,
981
+ taskId: run.taskId
982
+ }).onConflictDoUpdate({
983
+ target: juniorSchedulerRuns.id,
984
+ set: {
985
+ record: sql3`excluded.record`,
986
+ scheduledForMs: sql3`excluded.scheduled_for_ms`,
987
+ status: sql3`excluded.status`,
988
+ taskId: sql3`excluded.task_id`
989
+ }
990
+ });
991
+ }
992
+ async function getTaskFromSql(db, taskId) {
993
+ const rows = await db.select({
994
+ creatorIdentityId: juniorSchedulerTasks.creatorIdentityId,
995
+ record: juniorSchedulerTasks.record
996
+ }).from(juniorSchedulerTasks).where(eq2(juniorSchedulerTasks.id, taskId)).limit(1);
997
+ return rows[0] ? parseSqlTaskRow(rows[0]) : void 0;
998
+ }
999
+ async function getRunFromSql(db, runId) {
1000
+ const rows = await db.select({ record: juniorSchedulerRuns.record }).from(juniorSchedulerRuns).where(eq2(juniorSchedulerRuns.id, runId)).limit(1);
1001
+ return rows[0] ? parseSqlRunRow(rows[0]) : void 0;
1002
+ }
1003
+ async function listTasksFromSql(db) {
1004
+ const rows = await db.select({
1005
+ creatorIdentityId: juniorSchedulerTasks.creatorIdentityId,
1006
+ record: juniorSchedulerTasks.record
1007
+ }).from(juniorSchedulerTasks).where(ne(juniorSchedulerTasks.status, "deleted")).orderBy(
1008
+ asc3(juniorSchedulerTasks.createdAtMs),
1009
+ asc3(juniorSchedulerTasks.id)
1010
+ );
1011
+ return rows.map(parseSqlTaskRow).filter(present);
1012
+ }
1013
+ async function listTasksCreatedByFromSql(db, input) {
1014
+ if (input.identityIds.length === 0) return [];
1015
+ const query = normalizedTaskQuery(input.query);
1016
+ const tasks = [];
1017
+ let before = input.before;
1018
+ while (tasks.length < input.limit) {
1019
+ const cursor = before ? or2(
1020
+ lt(juniorSchedulerTasks.createdAtMs, before.createdAtMs),
1021
+ and3(
1022
+ eq2(juniorSchedulerTasks.createdAtMs, before.createdAtMs),
1023
+ lt(juniorSchedulerTasks.id, before.id)
1024
+ )
1025
+ ) : void 0;
1026
+ const search = query ? or2(
1027
+ sql3`strpos(lower(${juniorSchedulerTasks.record}->'task'->>'text'), ${query}) > 0`,
1028
+ sql3`strpos(lower(${juniorSchedulerTasks.record}->'schedule'->>'description'), ${query}) > 0`,
1029
+ sql3`strpos(lower(${juniorSchedulerTasks.record}->'schedule'->>'timezone'), ${query}) > 0`,
1030
+ sql3`strpos(lower(${juniorSchedulerTasks.status}), ${query}) > 0`
1031
+ ) : void 0;
1032
+ const rows = await db.select({
1033
+ createdAtMs: juniorSchedulerTasks.createdAtMs,
1034
+ creatorIdentityId: juniorSchedulerTasks.creatorIdentityId,
1035
+ id: juniorSchedulerTasks.id,
1036
+ record: juniorSchedulerTasks.record
1037
+ }).from(juniorSchedulerTasks).where(
1038
+ and3(
1039
+ ne(juniorSchedulerTasks.status, "deleted"),
1040
+ inArray2(juniorSchedulerTasks.creatorIdentityId, input.identityIds),
1041
+ cursor,
1042
+ search
1043
+ )
1044
+ ).orderBy(
1045
+ desc2(juniorSchedulerTasks.createdAtMs),
1046
+ desc2(juniorSchedulerTasks.id)
1047
+ ).limit(input.limit);
1048
+ tasks.push(...rows.map(parseSqlTaskRow).filter(present));
1049
+ if (rows.length < input.limit) {
1050
+ break;
1051
+ }
1052
+ const last = rows.at(-1);
1053
+ before = {
1054
+ createdAtMs: last.createdAtMs,
1055
+ id: last.id
1056
+ };
1057
+ }
1058
+ return tasks.slice(0, input.limit);
1059
+ }
1060
+ async function listTasksForTeamFromSql(db, teamId) {
1061
+ const rows = await db.select({
1062
+ creatorIdentityId: juniorSchedulerTasks.creatorIdentityId,
1063
+ record: juniorSchedulerTasks.record
1064
+ }).from(juniorSchedulerTasks).where(
1065
+ and3(
1066
+ eq2(juniorSchedulerTasks.teamId, teamId),
1067
+ ne(juniorSchedulerTasks.status, "deleted")
1068
+ )
1069
+ ).orderBy(
1070
+ asc3(juniorSchedulerTasks.createdAtMs),
1071
+ asc3(juniorSchedulerTasks.id)
1072
+ );
1073
+ return rows.map(parseSqlTaskRow).filter(present);
1074
+ }
1075
+ async function listPublicScheduledTasksForTeams(db, teamIds, limit) {
1076
+ if (teamIds.length === 0) return [];
1077
+ const rows = await db.select({
1078
+ creatorIdentityId: juniorSchedulerTasks.creatorIdentityId,
1079
+ record: juniorSchedulerTasks.record
1080
+ }).from(juniorSchedulerTasks).innerJoin(
1081
+ juniorDestinations,
1082
+ and3(
1083
+ eq2(juniorDestinations.provider, "slack"),
1084
+ eq2(juniorDestinations.providerTenantId, juniorSchedulerTasks.teamId),
1085
+ sql3`${juniorDestinations.providerDestinationId} = ${juniorSchedulerTasks.record}->'destination'->>'channelId'`
1086
+ )
1087
+ ).where(
1088
+ and3(
1089
+ inArray2(juniorSchedulerTasks.teamId, teamIds),
1090
+ ne(juniorSchedulerTasks.status, "deleted"),
1091
+ eq2(juniorDestinations.visibility, "public")
1092
+ )
1093
+ ).orderBy(
1094
+ desc2(juniorSchedulerTasks.createdAtMs),
1095
+ desc2(juniorSchedulerTasks.id)
1096
+ ).limit(limit);
1097
+ return rows.map(parseSqlTaskRow).filter(present);
1098
+ }
1099
+ async function listIncompleteRunsForTasksFromSql(db, tasks) {
1100
+ if (tasks.length === 0) {
1101
+ return [];
1102
+ }
1103
+ const rows = await db.select({ record: juniorSchedulerRuns.record }).from(juniorSchedulerRuns).where(
1104
+ and3(
1105
+ inArray2(
1106
+ juniorSchedulerRuns.taskId,
1107
+ tasks.map((task) => task.id)
1108
+ ),
1109
+ inArray2(juniorSchedulerRuns.status, [...SQL_INCOMPLETE_RUN_STATUSES])
1110
+ )
1111
+ ).orderBy(
1112
+ asc3(juniorSchedulerRuns.scheduledForMs),
1113
+ asc3(juniorSchedulerRuns.id)
1114
+ );
1115
+ return rows.map(parseSqlRunRow).filter(present);
1116
+ }
1117
+ var SqlSchedulerStore = class {
1118
+ constructor(db) {
1119
+ this.db = db;
1120
+ }
1121
+ db;
1122
+ async createTask(task) {
1123
+ const next = requireStoredTask(task);
1124
+ return await withSqlLock(this.db, taskLockKey(task.id), async (db) => {
1125
+ const current = await getTaskFromSql(db, task.id);
1126
+ if (current) {
1127
+ return current;
1128
+ }
1129
+ await this.saveTaskRecord(db, next, void 0);
1130
+ return next;
1131
+ });
1132
+ }
1133
+ async saveTask(task) {
1134
+ const next = requireStoredTask(task);
1135
+ await withSqlLock(this.db, taskLockKey(task.id), async (db) => {
1136
+ const current = await getTaskFromSql(db, task.id);
1137
+ await this.saveTaskRecord(db, next, current);
1138
+ });
1139
+ }
1140
+ async saveTaskRecord(db, task, current) {
1141
+ if (current?.status === "blocked" && task.status === "active" && typeof task.nextRunAtMs === "number" && Number.isFinite(task.nextRunAtMs)) {
1142
+ await db.delete(juniorSchedulerRuns).where(
1143
+ and3(
1144
+ eq2(juniorSchedulerRuns.id, buildRunId(task.id, task.nextRunAtMs)),
1145
+ eq2(juniorSchedulerRuns.status, "blocked")
1146
+ )
1147
+ );
1148
+ }
1149
+ await upsertSqlTask(db, task);
1150
+ }
1151
+ async getTask(taskId) {
1152
+ return await getTaskFromSql(this.db, taskId);
1153
+ }
1154
+ async listTasks() {
1155
+ return await listTasksFromSql(this.db);
1156
+ }
1157
+ async listTasksCreatedBy(input) {
1158
+ return await listTasksCreatedByFromSql(this.db, input);
1159
+ }
1160
+ async listTasksForTeam(teamId) {
1161
+ return await listTasksForTeamFromSql(this.db, teamId);
1162
+ }
1163
+ async claimDueRun(args) {
1164
+ return await withSqlLock(this.db, "junior:scheduler:claim", async (db) => {
1165
+ const rows = await db.select({
1166
+ creatorIdentityId: juniorSchedulerTasks.creatorIdentityId,
1167
+ record: juniorSchedulerTasks.record
1168
+ }).from(juniorSchedulerTasks).where(
1169
+ and3(
1170
+ eq2(juniorSchedulerTasks.status, "active"),
1171
+ or2(
1172
+ and3(
1173
+ isNotNull(juniorSchedulerTasks.runNowAtMs),
1174
+ lte2(juniorSchedulerTasks.runNowAtMs, args.nowMs)
1175
+ ),
1176
+ and3(
1177
+ isNotNull(juniorSchedulerTasks.nextRunAtMs),
1178
+ lte2(juniorSchedulerTasks.nextRunAtMs, args.nowMs)
1179
+ )
1180
+ )
1181
+ )
1182
+ ).orderBy(
1183
+ asc3(juniorSchedulerTasks.createdAtMs),
1184
+ asc3(juniorSchedulerTasks.id)
1185
+ );
1186
+ for (const row of rows) {
1187
+ const task = parseSqlTaskRow(row);
1188
+ if (!task) {
1189
+ continue;
1190
+ }
1191
+ const scheduledForMs = getDueRunAtMs(task, args.nowMs);
1192
+ if (scheduledForMs === void 0) {
1193
+ continue;
1194
+ }
1195
+ const runId = buildRunId(task.id, scheduledForMs);
1196
+ const incompleteRuns = await listIncompleteRunsForTasksFromSql(db, [
1197
+ task
1198
+ ]);
1199
+ const incompleteRun = incompleteRuns.find((run2) => run2.id === runId);
1200
+ const blockingRun = incompleteRuns.find(
1201
+ (run2) => run2.id !== runId && !isStalePendingRun(run2, args.nowMs)
1202
+ );
1203
+ if (blockingRun) {
1204
+ continue;
1205
+ }
1206
+ if (incompleteRun) {
1207
+ if (!isStalePendingRun(incompleteRun, args.nowMs)) {
1208
+ continue;
1209
+ }
1210
+ const reclaimed = {
1211
+ ...incompleteRun,
1212
+ attempt: incompleteRun.attempt + 1,
1213
+ claimedAtMs: args.nowMs
1214
+ };
1215
+ await upsertSqlRun(db, reclaimed);
1216
+ return reclaimed;
1217
+ }
1218
+ const existingRun = await getRunFromSql(db, runId);
1219
+ if (existingRun) {
1220
+ continue;
1221
+ }
1222
+ if (isMissedRunTooOld({ nowMs: args.nowMs, scheduledForMs })) {
1223
+ await this.skipMissedRun(db, {
1224
+ nowMs: args.nowMs,
1225
+ scheduledForMs,
1226
+ task
1227
+ });
1228
+ continue;
1229
+ }
1230
+ const run = buildScheduledRun({
1231
+ claimedAtMs: args.nowMs,
1232
+ scheduledForMs,
1233
+ task
1234
+ });
1235
+ await upsertSqlRun(db, run);
1236
+ return run;
1237
+ }
1238
+ return void 0;
1239
+ });
1240
+ }
1241
+ async skipMissedRun(db, args) {
1242
+ const current = await getTaskFromSql(db, args.task.id);
1243
+ if (!current || current.status !== "active" || getDueRunAtMs(current, args.nowMs) !== args.scheduledForMs) {
1244
+ return;
1245
+ }
1246
+ const duplicateOf = await this.findStaleRecoveryCanonicalTask(db, current);
1247
+ const errorMessage = duplicateOf ? `Duplicate stale scheduled task was skipped without dispatch. Canonical task: ${duplicateOf.id}.` : "Scheduled occurrence was more than 24 hours late and was skipped without dispatch.";
1248
+ await upsertSqlRun(
1249
+ db,
1250
+ buildSkippedScheduledRun({
1251
+ completedAtMs: args.nowMs,
1252
+ errorMessage,
1253
+ scheduledForMs: args.scheduledForMs,
1254
+ task: current
1255
+ })
1256
+ );
1257
+ const isRunNow = current.runNowAtMs === args.scheduledForMs;
1258
+ let nextRunAtMs;
1259
+ if (!duplicateOf) {
1260
+ nextRunAtMs = isRunNow && current.nextRunAtMs !== args.scheduledForMs ? current.nextRunAtMs : current.schedule.kind === "recurring" ? getNextRunAtMs(current, args.scheduledForMs, args.nowMs) : void 0;
1261
+ }
1262
+ const nextStatus = nextRunAtMs ? "active" : "paused";
1263
+ await this.saveTaskRecord(
1264
+ db,
1265
+ {
1266
+ ...current,
1267
+ nextRunAtMs,
1268
+ runNowAtMs: isRunNow ? void 0 : current.runNowAtMs,
1269
+ status: nextStatus,
1270
+ statusReason: nextStatus === "paused" ? errorMessage : void 0,
1271
+ updatedAtMs: args.nowMs
1272
+ },
1273
+ current
1274
+ );
1275
+ }
1276
+ async findStaleRecoveryCanonicalTask(db, task) {
1277
+ const fingerprint = taskDedupeFingerprint(task);
1278
+ const tasks = await listTasksForTeamFromSql(db, task.destination.teamId);
1279
+ return tasks.filter((candidate) => candidate.id !== task.id).filter(
1280
+ (candidate) => candidate.status === "active" && isEarlierTask(candidate, task) && taskDedupeFingerprint(candidate) === fingerprint
1281
+ ).sort((a, b) => a.createdAtMs - b.createdAtMs || a.id.localeCompare(b.id)).at(0);
1282
+ }
1283
+ async getRun(runId) {
1284
+ return await getRunFromSql(this.db, runId);
1285
+ }
1286
+ async listIncompleteRuns() {
1287
+ return await listIncompleteRunsForTasksFromSql(
1288
+ this.db,
1289
+ await this.listTasks()
1290
+ );
1291
+ }
1292
+ async listIncompleteRunsForTasks(tasks) {
1293
+ return await listIncompleteRunsForTasksFromSql(this.db, tasks);
1294
+ }
1295
+ async markRunDispatched(args) {
1296
+ return await this.updateRun(
1297
+ args.runId,
1298
+ (run) => run.status === "pending" && run.claimedAtMs === args.claimedAtMs ? {
1299
+ ...run,
1300
+ dispatchId: args.dispatchId,
1301
+ startedAtMs: args.nowMs,
1302
+ status: "running"
1303
+ } : void 0
1304
+ );
1305
+ }
1306
+ async markRunCompleted(args) {
1307
+ const next = await this.updateRun(
1308
+ args.runId,
1309
+ (run) => canFinishRun(run, args.startedAtMs) ? {
1310
+ ...run,
1311
+ completedAtMs: args.completedAtMs,
1312
+ resultMessageTs: args.resultMessageTs,
1313
+ status: "completed"
1314
+ } : void 0
1315
+ );
1316
+ return next;
1317
+ }
1318
+ async markRunFailed(args) {
1319
+ return await this.updateRun(
1320
+ args.runId,
1321
+ (run) => canFinishRun(run, args.startedAtMs) ? {
1322
+ ...run,
1323
+ completedAtMs: args.completedAtMs,
1324
+ errorMessage: args.errorMessage,
1325
+ status: "failed"
1326
+ } : void 0
1327
+ );
1328
+ }
1329
+ async markRunSkipped(args) {
1330
+ return await this.updateRun(
1331
+ args.runId,
1332
+ (run) => run.status === "pending" ? {
1333
+ ...run,
1334
+ completedAtMs: args.completedAtMs,
1335
+ errorMessage: args.errorMessage,
1336
+ status: "skipped"
1337
+ } : void 0
1338
+ );
1339
+ }
1340
+ async markRunBlocked(args) {
1341
+ return await this.updateRun(
1342
+ args.runId,
1343
+ (run) => canFinishRun(run, args.startedAtMs) ? {
1344
+ ...run,
1345
+ completedAtMs: args.completedAtMs,
1346
+ errorMessage: args.errorMessage,
1347
+ status: "blocked"
1348
+ } : void 0
1349
+ );
1350
+ }
1351
+ async updateTaskAfterRun(args) {
1352
+ await withSqlLock(this.db, taskLockKey(args.run.taskId), async (db) => {
1353
+ const current = await getTaskFromSql(db, args.run.taskId);
1354
+ if (!current || current.status === "deleted") {
1355
+ return;
1356
+ }
1357
+ const isRunNow = current.runNowAtMs === args.run.scheduledForMs;
1358
+ if (isRunNow) {
1359
+ let nextRunAtMs2 = current.nextRunAtMs;
1360
+ if (args.status !== "blocked" && typeof current.nextRunAtMs === "number" && current.nextRunAtMs <= args.run.scheduledForMs) {
1361
+ nextRunAtMs2 = getNextRunAtMs(
1362
+ current,
1363
+ current.nextRunAtMs,
1364
+ args.nowMs
1365
+ );
1366
+ }
1367
+ await this.saveTaskRecord(
1368
+ db,
1369
+ {
1370
+ ...current,
1371
+ lastRunAtMs: args.run.scheduledForMs,
1372
+ nextRunAtMs: nextRunAtMs2,
1373
+ runNowAtMs: void 0,
1374
+ status: args.status === "blocked" ? "blocked" : nextRunAtMs2 ? current.status : "paused",
1375
+ statusReason: args.status === "blocked" ? args.errorMessage : void 0,
1376
+ updatedAtMs: args.nowMs
1377
+ },
1378
+ current
1379
+ );
1380
+ return;
1381
+ }
1382
+ if (current.status !== "active" || current.nextRunAtMs !== args.run.scheduledForMs) {
1383
+ await this.saveTaskRecord(
1384
+ db,
1385
+ {
1386
+ ...current,
1387
+ lastRunAtMs: args.run.scheduledForMs,
1388
+ updatedAtMs: args.nowMs
1389
+ },
1390
+ current
1391
+ );
1392
+ return;
1393
+ }
1394
+ const nextRunAtMs = args.status === "blocked" ? void 0 : getNextRunAtMs(current, args.run.scheduledForMs, args.nowMs);
1395
+ await this.saveTaskRecord(
1396
+ db,
1397
+ {
1398
+ ...current,
1399
+ lastRunAtMs: args.run.scheduledForMs,
1400
+ nextRunAtMs,
1401
+ status: args.status === "blocked" ? "blocked" : nextRunAtMs ? "active" : "paused",
1402
+ statusReason: args.status === "blocked" ? args.errorMessage : void 0,
1403
+ updatedAtMs: args.nowMs
1404
+ },
1405
+ current
1406
+ );
1407
+ });
1408
+ }
1409
+ async updateRun(runId, update) {
1410
+ return await withSqlLock(
1411
+ this.db,
1412
+ indexLockKey(runKey(runId)),
1413
+ async (db) => {
1414
+ const current = await getRunFromSql(db, runId);
1415
+ if (!current) {
1416
+ return void 0;
1417
+ }
1418
+ const next = update(current);
1419
+ if (!next) {
1420
+ return void 0;
1421
+ }
1422
+ await upsertSqlRun(db, next);
1423
+ return next;
1424
+ }
1425
+ );
1426
+ }
1427
+ };
1428
+ function createSchedulerSqlStore(db) {
1429
+ return new SqlSchedulerStore(db);
1430
+ }
1431
+
1432
+ export {
1433
+ juniorToolOutputSchema,
1434
+ injectContinuationToolName,
1435
+ makeStructuredToolOutput,
1436
+ ToolInputError,
1437
+ eventNamespaceSchema,
1438
+ registeredEventTypeSchema,
1439
+ registeredResourceTypeSchema,
1440
+ pluginSupportsEvent,
1441
+ normalizeEventIdentifier,
1442
+ getResourceEventCatalog,
1443
+ getEventTask,
1444
+ createEventTask,
1445
+ saveEventTask,
1446
+ deleteEventTask,
1447
+ listEventTasksForTeam,
1448
+ eventTaskBelongsToUser,
1449
+ listEventTasksCreatedBy,
1450
+ listPublicEventTasksForTeams,
1451
+ findMatchingEventTasks,
1452
+ eventTaskToolResultSchema,
1453
+ eventTaskListToolResultSchema,
1454
+ registeredEventTaskTriggerSchema,
1455
+ requireSupportedEventTaskTrigger,
1456
+ requireEventTaskSlackContext,
1457
+ eventTaskMatchesDestination,
1458
+ writableEventTask,
1459
+ eventTaskTriggerAvailable,
1460
+ compactEventTask,
1461
+ eventTaskSuccess,
1462
+ getZonedDateTimeParts,
1463
+ resolveLocalScheduleAtMs,
1464
+ getFirstRunAtMs,
1465
+ SCHEDULED_TASK_SYSTEM_ACTOR,
1466
+ listPublicScheduledTasksForTeams,
1467
+ createSchedulerSqlStore,
1468
+ incrementStat,
1469
+ readStats
1470
+ };