@sentry/junior-scheduler 0.107.1 → 0.109.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.
- package/README.md +10 -1
- package/dist/cadence.d.ts +12 -10
- package/dist/index.d.ts +1 -1
- package/dist/index.js +553 -524
- package/dist/schedule-intent.d.ts +61 -0
- package/dist/store.d.ts +1 -10
- package/dist/tool-support.d.ts +8 -31
- package/dist/tools/create-task.d.ts +23 -5
- package/dist/tools/update-task.d.ts +23 -4
- package/package.json +2 -2
- package/src/cadence.ts +228 -304
- package/src/index.ts +0 -1
- package/src/plugin.ts +0 -7
- package/src/schedule-intent.ts +372 -0
- package/src/store.ts +35 -94
- package/src/tool-support.ts +25 -143
- package/src/tools/create-task.ts +66 -69
- package/src/tools/update-task.ts +59 -71
package/dist/index.js
CHANGED
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
|
|
7
7
|
// src/store.ts
|
|
8
8
|
import {
|
|
9
|
-
pluginCredentialSubjectSchema,
|
|
10
9
|
destinationSchema,
|
|
11
10
|
isSlackDestination,
|
|
12
11
|
slackActorSchema
|
|
@@ -25,26 +24,6 @@ import {
|
|
|
25
24
|
import { z as z2 } from "zod";
|
|
26
25
|
|
|
27
26
|
// src/cadence.ts
|
|
28
|
-
function parseScheduleTimestamp(value) {
|
|
29
|
-
const trimmed = value.trim();
|
|
30
|
-
const match = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2})(?:\.\d{1,9})?)?(Z|[+-]\d{2}:\d{2})$/.exec(
|
|
31
|
-
trimmed
|
|
32
|
-
);
|
|
33
|
-
if (!match) {
|
|
34
|
-
return void 0;
|
|
35
|
-
}
|
|
36
|
-
const year = Number(match[1]);
|
|
37
|
-
const month = Number(match[2]);
|
|
38
|
-
const day = Number(match[3]);
|
|
39
|
-
const hour = Number(match[4]);
|
|
40
|
-
const minute = Number(match[5]);
|
|
41
|
-
const second = match[6] ? Number(match[6]) : 0;
|
|
42
|
-
if (!Number.isInteger(year) || !Number.isInteger(month) || !Number.isInteger(day) || !Number.isInteger(hour) || !Number.isInteger(minute) || !Number.isInteger(second) || month < 1 || month > 12 || day < 1 || day > daysInMonth(year, month) || hour < 0 || hour > 23 || minute < 0 || minute > 59 || second < 0 || second > 59) {
|
|
43
|
-
return void 0;
|
|
44
|
-
}
|
|
45
|
-
const parsed = Date.parse(trimmed);
|
|
46
|
-
return Number.isFinite(parsed) ? parsed : void 0;
|
|
47
|
-
}
|
|
48
27
|
var FORMATTERS = /* @__PURE__ */ new Map();
|
|
49
28
|
function getFormatter(timezone) {
|
|
50
29
|
const existing = FORMATTERS.get(timezone);
|
|
@@ -70,6 +49,9 @@ function normalizeHour(hour) {
|
|
|
70
49
|
function getLocalDateWeekday(date) {
|
|
71
50
|
return new Date(Date.UTC(date.year, date.month - 1, date.day)).getUTCDay();
|
|
72
51
|
}
|
|
52
|
+
function getWeekStart(date) {
|
|
53
|
+
return addDays(date, -((getLocalDateWeekday(date) + 6) % 7));
|
|
54
|
+
}
|
|
73
55
|
function getZonedDateTimeParts(timestampMs, timezone) {
|
|
74
56
|
const parts = getFormatter(timezone).formatToParts(new Date(timestampMs));
|
|
75
57
|
const values = new Map(parts.map((part) => [part.type, part.value]));
|
|
@@ -109,15 +91,163 @@ function localDateTimeToTimestampMs(args) {
|
|
|
109
91
|
args.time.minute,
|
|
110
92
|
0
|
|
111
93
|
);
|
|
112
|
-
|
|
113
|
-
for (
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
94
|
+
const offsets = /* @__PURE__ */ new Set();
|
|
95
|
+
for (const probeDeltaMs of [
|
|
96
|
+
-36 * 60 * 60 * 1e3,
|
|
97
|
+
-12 * 60 * 60 * 1e3,
|
|
98
|
+
0,
|
|
99
|
+
12 * 60 * 60 * 1e3,
|
|
100
|
+
36 * 60 * 60 * 1e3
|
|
101
|
+
]) {
|
|
102
|
+
offsets.add(
|
|
103
|
+
getTimeZoneOffsetMs(localAsUtcMs + probeDeltaMs, args.timezone)
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
const matches = [...offsets].map((offsetMs) => localAsUtcMs - offsetMs).filter((timestampMs) => {
|
|
107
|
+
const parts = getZonedDateTimeParts(timestampMs, args.timezone);
|
|
108
|
+
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;
|
|
109
|
+
});
|
|
110
|
+
if (matches.length === 0) {
|
|
111
|
+
return void 0;
|
|
112
|
+
}
|
|
113
|
+
return Math.min(...matches);
|
|
114
|
+
}
|
|
115
|
+
function resolveLocalScheduleAtMs(args) {
|
|
116
|
+
const date = parseLocalDate(args.date);
|
|
117
|
+
if (!date) {
|
|
118
|
+
return void 0;
|
|
119
|
+
}
|
|
120
|
+
return localDateTimeToTimestampMs({
|
|
121
|
+
date,
|
|
122
|
+
time: args.time,
|
|
123
|
+
timezone: args.timezone
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
function daysBetween(left, right) {
|
|
127
|
+
return Math.floor(
|
|
128
|
+
(Date.UTC(right.year, right.month - 1, right.day) - Date.UTC(left.year, left.month - 1, left.day)) / (24 * 60 * 60 * 1e3)
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
function weeklyRecurrenceMatchesDate(date, start, recurrence) {
|
|
132
|
+
if (compareDate(date, start) < 0) {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
return normalizeWeekdays(recurrence.weekdays).includes(
|
|
136
|
+
getLocalDateWeekday(date)
|
|
137
|
+
) && Math.floor(daysBetween(getWeekStart(start), getWeekStart(date)) / 7) % recurrence.interval === 0;
|
|
138
|
+
}
|
|
139
|
+
function greatestCommonDivisor(left, right) {
|
|
140
|
+
let a = Math.abs(left);
|
|
141
|
+
let b = Math.abs(right);
|
|
142
|
+
while (b !== 0) {
|
|
143
|
+
[a, b] = [b, a % b];
|
|
144
|
+
}
|
|
145
|
+
return a;
|
|
146
|
+
}
|
|
147
|
+
function findNextRunAtMs(args) {
|
|
148
|
+
const start = parseLocalDate(args.recurrence.startDate);
|
|
149
|
+
const interval = args.recurrence.interval;
|
|
150
|
+
if (!start || !Number.isInteger(interval) || interval <= 0) {
|
|
151
|
+
return void 0;
|
|
152
|
+
}
|
|
153
|
+
const searchFrom = compareDate(args.searchFrom, start) < 0 ? start : args.searchFrom;
|
|
154
|
+
if (args.recurrence.frequency === "daily") {
|
|
155
|
+
const offsetDays = daysBetween(start, searchFrom);
|
|
156
|
+
let candidateDate = addDays(
|
|
157
|
+
start,
|
|
158
|
+
Math.ceil(offsetDays / interval) * interval
|
|
159
|
+
);
|
|
160
|
+
const gregorianCycleDays = 146097;
|
|
161
|
+
const candidateCount2 = gregorianCycleDays / greatestCommonDivisor(gregorianCycleDays, interval);
|
|
162
|
+
for (let attempts = 0; attempts < candidateCount2; attempts += 1) {
|
|
163
|
+
const candidate = buildCandidate({
|
|
164
|
+
date: candidateDate,
|
|
165
|
+
recurrence: args.recurrence,
|
|
166
|
+
timezone: args.timezone
|
|
167
|
+
});
|
|
168
|
+
if (candidate !== void 0 && candidate > args.afterMs) {
|
|
169
|
+
return candidate;
|
|
170
|
+
}
|
|
171
|
+
candidateDate = addDays(candidateDate, interval);
|
|
117
172
|
}
|
|
118
|
-
|
|
173
|
+
return void 0;
|
|
174
|
+
}
|
|
175
|
+
if (args.recurrence.frequency === "weekly") {
|
|
176
|
+
if (normalizeWeekdays(args.recurrence.weekdays).length === 0) {
|
|
177
|
+
return void 0;
|
|
178
|
+
}
|
|
179
|
+
let candidateDate = searchFrom;
|
|
180
|
+
const gregorianCycleDays = 146097;
|
|
181
|
+
const cadenceDays = interval * 7;
|
|
182
|
+
const searchDays = gregorianCycleDays * cadenceDays / greatestCommonDivisor(gregorianCycleDays, cadenceDays);
|
|
183
|
+
for (let attempts = 0; attempts < searchDays; attempts += 1) {
|
|
184
|
+
if (weeklyRecurrenceMatchesDate(candidateDate, start, args.recurrence)) {
|
|
185
|
+
const candidate = buildCandidate({
|
|
186
|
+
date: candidateDate,
|
|
187
|
+
recurrence: args.recurrence,
|
|
188
|
+
timezone: args.timezone
|
|
189
|
+
});
|
|
190
|
+
if (candidate !== void 0 && candidate > args.afterMs) {
|
|
191
|
+
return candidate;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
candidateDate = addDays(candidateDate, 1);
|
|
195
|
+
}
|
|
196
|
+
return void 0;
|
|
197
|
+
}
|
|
198
|
+
if (args.recurrence.frequency === "monthly") {
|
|
199
|
+
const startMonth = start.year * 12 + start.month - 1;
|
|
200
|
+
const searchMonth = searchFrom.year * 12 + searchFrom.month - 1;
|
|
201
|
+
let candidateMonth = startMonth + Math.ceil((searchMonth - startMonth) / interval) * interval;
|
|
202
|
+
const gregorianCycleMonths = 4800;
|
|
203
|
+
const candidateCount2 = gregorianCycleMonths / greatestCommonDivisor(gregorianCycleMonths, interval);
|
|
204
|
+
for (let attempts = 0; attempts < candidateCount2; attempts += 1) {
|
|
205
|
+
const candidateDate = {
|
|
206
|
+
year: Math.floor(candidateMonth / 12),
|
|
207
|
+
month: candidateMonth % 12 + 1,
|
|
208
|
+
day: args.recurrence.dayOfMonth ?? 0
|
|
209
|
+
};
|
|
210
|
+
if (compareDate(candidateDate, searchFrom) >= 0) {
|
|
211
|
+
const candidate = buildCandidate({
|
|
212
|
+
date: candidateDate,
|
|
213
|
+
recurrence: args.recurrence,
|
|
214
|
+
timezone: args.timezone
|
|
215
|
+
});
|
|
216
|
+
if (candidate !== void 0 && candidate > args.afterMs) {
|
|
217
|
+
return candidate;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
candidateMonth += interval;
|
|
221
|
+
}
|
|
222
|
+
return void 0;
|
|
223
|
+
}
|
|
224
|
+
let candidateYear = start.year + Math.ceil((searchFrom.year - start.year) / interval) * interval;
|
|
225
|
+
const candidateCount = 400 / greatestCommonDivisor(400, interval);
|
|
226
|
+
for (let attempts = 0; attempts < candidateCount; attempts += 1) {
|
|
227
|
+
const candidateDate = {
|
|
228
|
+
year: candidateYear,
|
|
229
|
+
month: args.recurrence.month ?? 0,
|
|
230
|
+
day: args.recurrence.dayOfMonth ?? 0
|
|
231
|
+
};
|
|
232
|
+
if (compareDate(candidateDate, searchFrom) >= 0) {
|
|
233
|
+
const candidate = buildCandidate({
|
|
234
|
+
date: candidateDate,
|
|
235
|
+
recurrence: args.recurrence,
|
|
236
|
+
timezone: args.timezone
|
|
237
|
+
});
|
|
238
|
+
if (candidate !== void 0 && candidate > args.afterMs) {
|
|
239
|
+
return candidate;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
candidateYear += interval;
|
|
119
243
|
}
|
|
120
|
-
return
|
|
244
|
+
return void 0;
|
|
245
|
+
}
|
|
246
|
+
function getFirstRunAtMs(args) {
|
|
247
|
+
return findNextRunAtMs({
|
|
248
|
+
...args,
|
|
249
|
+
searchFrom: getLocalDate(args.afterMs, args.timezone)
|
|
250
|
+
});
|
|
121
251
|
}
|
|
122
252
|
function compareDate(left, right) {
|
|
123
253
|
return Date.UTC(left.year, left.month - 1, left.day) - Date.UTC(right.year, right.month - 1, right.day);
|
|
@@ -146,13 +276,6 @@ function parseLocalDate(value) {
|
|
|
146
276
|
}
|
|
147
277
|
return { year, month, day };
|
|
148
278
|
}
|
|
149
|
-
function formatLocalDate(date) {
|
|
150
|
-
return [
|
|
151
|
-
String(date.year).padStart(4, "0"),
|
|
152
|
-
String(date.month).padStart(2, "0"),
|
|
153
|
-
String(date.day).padStart(2, "0")
|
|
154
|
-
].join("-");
|
|
155
|
-
}
|
|
156
279
|
function getLocalDate(timestampMs, timezone) {
|
|
157
280
|
const parts = getZonedDateTimeParts(timestampMs, timezone);
|
|
158
281
|
return { year: parts.year, month: parts.month, day: parts.day };
|
|
@@ -169,168 +292,6 @@ function buildCandidate(args) {
|
|
|
169
292
|
timezone: args.timezone
|
|
170
293
|
});
|
|
171
294
|
}
|
|
172
|
-
function getDailyNextRunAtMs(args) {
|
|
173
|
-
const start = parseLocalDate(args.recurrence.startDate);
|
|
174
|
-
if (!start) {
|
|
175
|
-
return void 0;
|
|
176
|
-
}
|
|
177
|
-
let candidateDate = addDays(
|
|
178
|
-
getLocalDate(args.scheduledForMs, args.timezone),
|
|
179
|
-
args.recurrence.interval
|
|
180
|
-
);
|
|
181
|
-
if (compareDate(candidateDate, start) < 0) {
|
|
182
|
-
candidateDate = start;
|
|
183
|
-
}
|
|
184
|
-
let candidate = buildCandidate({
|
|
185
|
-
date: candidateDate,
|
|
186
|
-
recurrence: args.recurrence,
|
|
187
|
-
timezone: args.timezone
|
|
188
|
-
});
|
|
189
|
-
while (candidate <= args.afterMs) {
|
|
190
|
-
candidateDate = addDays(candidateDate, args.recurrence.interval);
|
|
191
|
-
candidate = buildCandidate({
|
|
192
|
-
date: candidateDate,
|
|
193
|
-
recurrence: args.recurrence,
|
|
194
|
-
timezone: args.timezone
|
|
195
|
-
});
|
|
196
|
-
}
|
|
197
|
-
return candidate;
|
|
198
|
-
}
|
|
199
|
-
function getWeeklyNextRunAtMs(args) {
|
|
200
|
-
const start = parseLocalDate(args.recurrence.startDate);
|
|
201
|
-
if (!start) {
|
|
202
|
-
return void 0;
|
|
203
|
-
}
|
|
204
|
-
const weekdays = normalizeWeekdays(args.recurrence.weekdays);
|
|
205
|
-
if (weekdays.length === 0) {
|
|
206
|
-
return void 0;
|
|
207
|
-
}
|
|
208
|
-
let candidateDate = addDays(
|
|
209
|
-
getLocalDate(args.scheduledForMs, args.timezone),
|
|
210
|
-
1
|
|
211
|
-
);
|
|
212
|
-
for (let attempts = 0; attempts < 3660; attempts += 1) {
|
|
213
|
-
const weeksSinceStart = Math.floor(
|
|
214
|
-
(Date.UTC(
|
|
215
|
-
candidateDate.year,
|
|
216
|
-
candidateDate.month - 1,
|
|
217
|
-
candidateDate.day
|
|
218
|
-
) - Date.UTC(start.year, start.month - 1, start.day)) / (7 * 24 * 60 * 60 * 1e3)
|
|
219
|
-
);
|
|
220
|
-
const isInCycle = weeksSinceStart >= 0 && weeksSinceStart % args.recurrence.interval === 0;
|
|
221
|
-
if (isInCycle && weekdays.includes(getLocalDateWeekday(candidateDate))) {
|
|
222
|
-
const candidate = buildCandidate({
|
|
223
|
-
date: candidateDate,
|
|
224
|
-
recurrence: args.recurrence,
|
|
225
|
-
timezone: args.timezone
|
|
226
|
-
});
|
|
227
|
-
if (candidate > args.afterMs) {
|
|
228
|
-
return candidate;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
candidateDate = addDays(candidateDate, 1);
|
|
232
|
-
}
|
|
233
|
-
return void 0;
|
|
234
|
-
}
|
|
235
|
-
function getMonthlyNextRunAtMs(args) {
|
|
236
|
-
const start = parseLocalDate(args.recurrence.startDate);
|
|
237
|
-
const dayOfMonth = args.recurrence.dayOfMonth;
|
|
238
|
-
if (!start || !dayOfMonth) {
|
|
239
|
-
return void 0;
|
|
240
|
-
}
|
|
241
|
-
const scheduledDate = getLocalDate(args.scheduledForMs, args.timezone);
|
|
242
|
-
let monthIndex = scheduledDate.year * 12 + scheduledDate.month - 1;
|
|
243
|
-
const startMonthIndex = start.year * 12 + start.month - 1;
|
|
244
|
-
for (let attempts = 0; attempts < 1200; attempts += 1) {
|
|
245
|
-
monthIndex += args.recurrence.interval;
|
|
246
|
-
if (monthIndex < startMonthIndex) {
|
|
247
|
-
monthIndex = startMonthIndex;
|
|
248
|
-
}
|
|
249
|
-
const year = Math.floor(monthIndex / 12);
|
|
250
|
-
const month = monthIndex % 12 + 1;
|
|
251
|
-
if (dayOfMonth > daysInMonth(year, month)) {
|
|
252
|
-
continue;
|
|
253
|
-
}
|
|
254
|
-
const candidate = buildCandidate({
|
|
255
|
-
date: { year, month, day: dayOfMonth },
|
|
256
|
-
recurrence: args.recurrence,
|
|
257
|
-
timezone: args.timezone
|
|
258
|
-
});
|
|
259
|
-
if (candidate > args.afterMs) {
|
|
260
|
-
return candidate;
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
return void 0;
|
|
264
|
-
}
|
|
265
|
-
function getYearlyNextRunAtMs(args) {
|
|
266
|
-
const start = parseLocalDate(args.recurrence.startDate);
|
|
267
|
-
const month = args.recurrence.month;
|
|
268
|
-
const dayOfMonth = args.recurrence.dayOfMonth;
|
|
269
|
-
if (!start || !month || !dayOfMonth) {
|
|
270
|
-
return void 0;
|
|
271
|
-
}
|
|
272
|
-
const scheduledDate = getLocalDate(args.scheduledForMs, args.timezone);
|
|
273
|
-
let year = scheduledDate.year;
|
|
274
|
-
for (let attempts = 0; attempts < 100; attempts += 1) {
|
|
275
|
-
year += args.recurrence.interval;
|
|
276
|
-
if (year < start.year) {
|
|
277
|
-
year = start.year;
|
|
278
|
-
}
|
|
279
|
-
if (dayOfMonth > daysInMonth(year, month)) {
|
|
280
|
-
continue;
|
|
281
|
-
}
|
|
282
|
-
const candidate = buildCandidate({
|
|
283
|
-
date: { year, month, day: dayOfMonth },
|
|
284
|
-
recurrence: args.recurrence,
|
|
285
|
-
timezone: args.timezone
|
|
286
|
-
});
|
|
287
|
-
if (candidate > args.afterMs) {
|
|
288
|
-
return candidate;
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
return void 0;
|
|
292
|
-
}
|
|
293
|
-
function buildCalendarRecurrence(args) {
|
|
294
|
-
const interval = args.interval && args.interval > 0 ? args.interval : 1;
|
|
295
|
-
const parts = getZonedDateTimeParts(args.nextRunAtMs, args.timezone);
|
|
296
|
-
const time = { hour: parts.hour, minute: parts.minute };
|
|
297
|
-
const startDate = formatLocalDate(parts);
|
|
298
|
-
if (args.frequency === "weekly") {
|
|
299
|
-
const weekdays = normalizeWeekdays(args.weekdays);
|
|
300
|
-
return {
|
|
301
|
-
frequency: args.frequency,
|
|
302
|
-
interval,
|
|
303
|
-
startDate,
|
|
304
|
-
time,
|
|
305
|
-
weekdays: weekdays.length > 0 ? weekdays : [parts.weekday]
|
|
306
|
-
};
|
|
307
|
-
}
|
|
308
|
-
if (args.frequency === "monthly") {
|
|
309
|
-
return {
|
|
310
|
-
dayOfMonth: parts.day,
|
|
311
|
-
frequency: args.frequency,
|
|
312
|
-
interval,
|
|
313
|
-
startDate,
|
|
314
|
-
time
|
|
315
|
-
};
|
|
316
|
-
}
|
|
317
|
-
if (args.frequency === "yearly") {
|
|
318
|
-
return {
|
|
319
|
-
dayOfMonth: parts.day,
|
|
320
|
-
frequency: args.frequency,
|
|
321
|
-
interval,
|
|
322
|
-
month: parts.month,
|
|
323
|
-
startDate,
|
|
324
|
-
time
|
|
325
|
-
};
|
|
326
|
-
}
|
|
327
|
-
return {
|
|
328
|
-
frequency: args.frequency,
|
|
329
|
-
interval,
|
|
330
|
-
startDate,
|
|
331
|
-
time
|
|
332
|
-
};
|
|
333
|
-
}
|
|
334
295
|
function getNextRunAtMs(task, scheduledForMs, afterMs = scheduledForMs) {
|
|
335
296
|
if (task.schedule.kind !== "recurring") {
|
|
336
297
|
return void 0;
|
|
@@ -339,35 +300,14 @@ function getNextRunAtMs(task, scheduledForMs, afterMs = scheduledForMs) {
|
|
|
339
300
|
if (!recurrence || !Number.isFinite(recurrence.interval) || recurrence.interval <= 0) {
|
|
340
301
|
return void 0;
|
|
341
302
|
}
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
afterMs
|
|
348
|
-
});
|
|
349
|
-
}
|
|
350
|
-
if (recurrence.frequency === "weekly") {
|
|
351
|
-
return getWeeklyNextRunAtMs({
|
|
352
|
-
recurrence,
|
|
353
|
-
timezone: task.schedule.timezone,
|
|
354
|
-
scheduledForMs,
|
|
355
|
-
afterMs
|
|
356
|
-
});
|
|
357
|
-
}
|
|
358
|
-
if (recurrence.frequency === "monthly") {
|
|
359
|
-
return getMonthlyNextRunAtMs({
|
|
360
|
-
recurrence,
|
|
361
|
-
timezone: task.schedule.timezone,
|
|
362
|
-
scheduledForMs,
|
|
363
|
-
afterMs
|
|
364
|
-
});
|
|
365
|
-
}
|
|
366
|
-
return getYearlyNextRunAtMs({
|
|
303
|
+
const timezone = task.schedule.timezone;
|
|
304
|
+
const afterDate = getLocalDate(afterMs, timezone);
|
|
305
|
+
const nextScheduledDate = addDays(getLocalDate(scheduledForMs, timezone), 1);
|
|
306
|
+
return findNextRunAtMs({
|
|
307
|
+
afterMs,
|
|
367
308
|
recurrence,
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
afterMs
|
|
309
|
+
searchFrom: compareDate(afterDate, nextScheduledDate) < 0 ? nextScheduledDate : afterDate,
|
|
310
|
+
timezone
|
|
371
311
|
});
|
|
372
312
|
}
|
|
373
313
|
|
|
@@ -488,10 +428,6 @@ var taskRecordSchema = z2.object({
|
|
|
488
428
|
...taskRecordFields,
|
|
489
429
|
credentialMode: scheduledTaskCredentialModeSchema
|
|
490
430
|
}).strict();
|
|
491
|
-
var legacyTaskRecordSchema = z2.object({
|
|
492
|
-
...taskRecordFields,
|
|
493
|
-
credentialSubject: pluginCredentialSubjectSchema.optional()
|
|
494
|
-
}).strict();
|
|
495
431
|
var runRecordSchema = z2.object({
|
|
496
432
|
id: z2.string(),
|
|
497
433
|
attempt: z2.number(),
|
|
@@ -523,30 +459,13 @@ function taskLockKey(taskId) {
|
|
|
523
459
|
function runKey(runId) {
|
|
524
460
|
return `${SCHEDULER_KEY_PREFIX}:run:${runId}`;
|
|
525
461
|
}
|
|
526
|
-
function activeRunKey(taskId) {
|
|
527
|
-
return `${SCHEDULER_KEY_PREFIX}:active:${taskId}`;
|
|
528
|
-
}
|
|
529
|
-
function globalTaskIndexKey() {
|
|
530
|
-
return `${SCHEDULER_KEY_PREFIX}:tasks`;
|
|
531
|
-
}
|
|
532
462
|
function indexLockKey(indexKey) {
|
|
533
463
|
return `${indexKey}:lock`;
|
|
534
464
|
}
|
|
535
465
|
function buildRunId(taskId, scheduledForMs) {
|
|
536
466
|
return `${taskId}:${scheduledForMs}`;
|
|
537
467
|
}
|
|
538
|
-
|
|
539
|
-
return [...new Set(values.filter(Boolean))];
|
|
540
|
-
}
|
|
541
|
-
async function getIndex(state, key) {
|
|
542
|
-
const values = await state.get(key) ?? [];
|
|
543
|
-
return unique(
|
|
544
|
-
values.filter((value) => typeof value === "string")
|
|
545
|
-
);
|
|
546
|
-
}
|
|
547
|
-
function isFinishedRun(run) {
|
|
548
|
-
return run.status === "completed" || run.status === "failed" || run.status === "blocked" || run.status === "skipped";
|
|
549
|
-
}
|
|
468
|
+
var schedulerTaskIndexSchema = z2.array(z2.string().min(1));
|
|
550
469
|
function isStalePendingRun(run, nowMs) {
|
|
551
470
|
return run?.status === "pending" && run.claimedAtMs + PENDING_CLAIM_STALE_MS <= nowMs;
|
|
552
471
|
}
|
|
@@ -622,25 +541,6 @@ function parseStoredTask(value) {
|
|
|
622
541
|
const parsed = taskRecordSchema.safeParse(parseJsonRecord(value));
|
|
623
542
|
return parsed.success ? stripLegacyTaskFields(parsed.data) : void 0;
|
|
624
543
|
}
|
|
625
|
-
function parseLegacyStoredTaskForMigration(value) {
|
|
626
|
-
const parsed = legacyTaskRecordSchema.safeParse(parseJsonRecord(value));
|
|
627
|
-
if (!parsed.success) {
|
|
628
|
-
return void 0;
|
|
629
|
-
}
|
|
630
|
-
const {
|
|
631
|
-
credentialSubject: _credentialSubject,
|
|
632
|
-
version: _version,
|
|
633
|
-
...task
|
|
634
|
-
} = parsed.data;
|
|
635
|
-
return {
|
|
636
|
-
...task,
|
|
637
|
-
credentialMode: "system"
|
|
638
|
-
};
|
|
639
|
-
}
|
|
640
|
-
function parseStoredRun(value) {
|
|
641
|
-
const parsed = runRecordSchema.safeParse(parseJsonRecord(value));
|
|
642
|
-
return parsed.success ? stripLegacyRunFields(parsed.data) : void 0;
|
|
643
|
-
}
|
|
644
544
|
function stripLegacyTaskFields(task) {
|
|
645
545
|
const { version: _version, ...current } = task;
|
|
646
546
|
return current;
|
|
@@ -676,23 +576,6 @@ function requireStoredTask(task) {
|
|
|
676
576
|
}
|
|
677
577
|
return parsed;
|
|
678
578
|
}
|
|
679
|
-
async function getRunFromState(state, runId) {
|
|
680
|
-
return parseStoredRun(await state.get(runKey(runId)));
|
|
681
|
-
}
|
|
682
|
-
async function listIncompleteRunsForTasksFromState(state, tasks) {
|
|
683
|
-
const runs = [];
|
|
684
|
-
for (const task of tasks) {
|
|
685
|
-
const active = await state.get(activeRunKey(task.id));
|
|
686
|
-
if (typeof active?.runId !== "string") {
|
|
687
|
-
continue;
|
|
688
|
-
}
|
|
689
|
-
const run = await getRunFromState(state, active.runId);
|
|
690
|
-
if (run && !isFinishedRun(run)) {
|
|
691
|
-
runs.push(run);
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
return runs;
|
|
695
|
-
}
|
|
696
579
|
function parseSqlTaskRecord(value) {
|
|
697
580
|
const parsed = taskRecordSchema.safeParse(parseJsonRecord(value));
|
|
698
581
|
return parsed.success ? stripLegacyTaskFields(parsed.data) : void 0;
|
|
@@ -798,6 +681,17 @@ var SqlSchedulerStore = class {
|
|
|
798
681
|
this.db = db;
|
|
799
682
|
}
|
|
800
683
|
db;
|
|
684
|
+
async createTask(task) {
|
|
685
|
+
const next = requireStoredTask(task);
|
|
686
|
+
return await withSqlLock(this.db, taskLockKey(task.id), async (db) => {
|
|
687
|
+
const current = await getTaskFromSql(db, task.id);
|
|
688
|
+
if (current) {
|
|
689
|
+
return current;
|
|
690
|
+
}
|
|
691
|
+
await this.saveTaskRecord(db, next, void 0);
|
|
692
|
+
return next;
|
|
693
|
+
});
|
|
694
|
+
}
|
|
801
695
|
async saveTask(task) {
|
|
802
696
|
const next = requireStoredTask(task);
|
|
803
697
|
await withSqlLock(this.db, taskLockKey(task.id), async (db) => {
|
|
@@ -1093,47 +987,6 @@ function createSchedulerSqlStore(db) {
|
|
|
1093
987
|
function createSchedulerOperationalSqlStore(db) {
|
|
1094
988
|
return new SqlSchedulerStore(db);
|
|
1095
989
|
}
|
|
1096
|
-
async function migrateSchedulerStateToSql(args) {
|
|
1097
|
-
const store = createSchedulerSqlStore(args.db);
|
|
1098
|
-
const ids = await getIndex(args.state, globalTaskIndexKey());
|
|
1099
|
-
let existing = 0;
|
|
1100
|
-
let migrated = 0;
|
|
1101
|
-
let missing = 0;
|
|
1102
|
-
const migratedTasks = [];
|
|
1103
|
-
for (const id of ids) {
|
|
1104
|
-
const rawTask = await args.state.get(taskKey(id));
|
|
1105
|
-
const task = parseStoredTask(rawTask) ?? parseLegacyStoredTaskForMigration(rawTask);
|
|
1106
|
-
if (!task) {
|
|
1107
|
-
missing += 1;
|
|
1108
|
-
continue;
|
|
1109
|
-
}
|
|
1110
|
-
migratedTasks.push(task);
|
|
1111
|
-
if (await store.getTask(task.id)) {
|
|
1112
|
-
existing += 1;
|
|
1113
|
-
continue;
|
|
1114
|
-
}
|
|
1115
|
-
await store.saveTask(task);
|
|
1116
|
-
migrated += 1;
|
|
1117
|
-
}
|
|
1118
|
-
const runs = await listIncompleteRunsForTasksFromState(
|
|
1119
|
-
args.state,
|
|
1120
|
-
migratedTasks
|
|
1121
|
-
);
|
|
1122
|
-
for (const run of runs) {
|
|
1123
|
-
if (await store.getRun(run.id)) {
|
|
1124
|
-
existing += 1;
|
|
1125
|
-
continue;
|
|
1126
|
-
}
|
|
1127
|
-
await upsertSqlRun(args.db, run);
|
|
1128
|
-
migrated += 1;
|
|
1129
|
-
}
|
|
1130
|
-
return {
|
|
1131
|
-
existing,
|
|
1132
|
-
migrated,
|
|
1133
|
-
missing,
|
|
1134
|
-
scanned: ids.length + runs.length
|
|
1135
|
-
};
|
|
1136
|
-
}
|
|
1137
990
|
|
|
1138
991
|
// src/identity.ts
|
|
1139
992
|
var SLACK_USER_ID_PATTERN = /^[UW][A-Z0-9]{5,}$/;
|
|
@@ -1181,55 +1034,304 @@ function scheduledTaskPrincipalLabel(principal) {
|
|
|
1181
1034
|
|
|
1182
1035
|
// src/tools/create-task.ts
|
|
1183
1036
|
import { definePluginTool } from "@sentry/junior-plugin-api";
|
|
1184
|
-
import { z as
|
|
1037
|
+
import { z as z5 } from "zod";
|
|
1038
|
+
|
|
1039
|
+
// src/schedule-intent.ts
|
|
1040
|
+
import { z as z3 } from "zod";
|
|
1041
|
+
var localDateSchema = z3.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Use a local date in YYYY-MM-DD format.");
|
|
1042
|
+
var localTimeSchema = z3.string().regex(/^([01]\d|2[0-3]):[0-5]\d$/, "Use local time in HH:MM format.");
|
|
1043
|
+
var timezoneSchema = z3.string().min(1).max(80).describe(
|
|
1044
|
+
"IANA timezone, for example America/Los_Angeles. Omit or use null for the scheduler default."
|
|
1045
|
+
).nullable().optional();
|
|
1046
|
+
var weekdaySchema = z3.enum([
|
|
1047
|
+
"sunday",
|
|
1048
|
+
"monday",
|
|
1049
|
+
"tuesday",
|
|
1050
|
+
"wednesday",
|
|
1051
|
+
"thursday",
|
|
1052
|
+
"friday",
|
|
1053
|
+
"saturday"
|
|
1054
|
+
]);
|
|
1055
|
+
var oneOffScheduleIntentSchema = z3.object({
|
|
1056
|
+
kind: z3.literal("one_off").describe("A schedule that runs once."),
|
|
1057
|
+
timezone: timezoneSchema,
|
|
1058
|
+
timing: z3.discriminatedUnion("type", [
|
|
1059
|
+
z3.object({
|
|
1060
|
+
type: z3.literal("after").describe("Run after a relative delay from the server clock."),
|
|
1061
|
+
value: z3.number().int().positive().max(1e5),
|
|
1062
|
+
unit: z3.enum(["second", "minute", "hour", "day", "week"])
|
|
1063
|
+
}).strict(),
|
|
1064
|
+
z3.object({
|
|
1065
|
+
type: z3.literal("at").describe("Run at an explicit local calendar date and time."),
|
|
1066
|
+
date: localDateSchema.describe(
|
|
1067
|
+
"Requested local calendar date in YYYY-MM-DD format."
|
|
1068
|
+
),
|
|
1069
|
+
time: localTimeSchema.describe(
|
|
1070
|
+
"Requested local wall-clock time in HH:MM format."
|
|
1071
|
+
)
|
|
1072
|
+
}).strict()
|
|
1073
|
+
]).describe("Relative delay or explicit local time for the one-off run.")
|
|
1074
|
+
}).strict();
|
|
1075
|
+
var recurringScheduleIntentSchema = z3.object({
|
|
1076
|
+
kind: z3.literal("recurring").describe("A repeating calendar schedule."),
|
|
1077
|
+
frequency: z3.enum(["daily", "weekly", "monthly", "yearly"]).describe("Recurring tasks can run at most once per day."),
|
|
1078
|
+
interval: z3.number().int().positive().max(365).describe("Repeat every N frequency units. Defaults to 1.").nullable().optional(),
|
|
1079
|
+
time: localTimeSchema.describe(
|
|
1080
|
+
"Local wall-clock time for each occurrence in HH:MM format."
|
|
1081
|
+
),
|
|
1082
|
+
weekdays: z3.array(weekdaySchema).min(1).max(7).describe("Required for weekly schedules.").nullable().optional(),
|
|
1083
|
+
day_of_month: z3.number().int().min(1).max(31).describe("Required for monthly and yearly schedules.").nullable().optional(),
|
|
1084
|
+
month: z3.number().int().min(1).max(12).describe("Required for yearly schedules, where January is 1.").nullable().optional(),
|
|
1085
|
+
start_date: localDateSchema.describe(
|
|
1086
|
+
"Optional local calendar date that anchors the recurrence. Omit to start with the next matching occurrence."
|
|
1087
|
+
).nullable().optional(),
|
|
1088
|
+
timezone: timezoneSchema
|
|
1089
|
+
}).strict().superRefine((schedule, context) => {
|
|
1090
|
+
if (schedule.frequency === "weekly" && schedule.weekdays == null) {
|
|
1091
|
+
context.addIssue({
|
|
1092
|
+
code: "custom",
|
|
1093
|
+
message: "Weekly schedules require weekdays.",
|
|
1094
|
+
path: ["weekdays"]
|
|
1095
|
+
});
|
|
1096
|
+
}
|
|
1097
|
+
if ((schedule.frequency === "monthly" || schedule.frequency === "yearly") && schedule.day_of_month == null) {
|
|
1098
|
+
context.addIssue({
|
|
1099
|
+
code: "custom",
|
|
1100
|
+
message: `${schedule.frequency} schedules require day_of_month.`,
|
|
1101
|
+
path: ["day_of_month"]
|
|
1102
|
+
});
|
|
1103
|
+
}
|
|
1104
|
+
if (schedule.frequency === "yearly" && schedule.month == null) {
|
|
1105
|
+
context.addIssue({
|
|
1106
|
+
code: "custom",
|
|
1107
|
+
message: "Yearly schedules require month.",
|
|
1108
|
+
path: ["month"]
|
|
1109
|
+
});
|
|
1110
|
+
}
|
|
1111
|
+
if (schedule.frequency !== "weekly" && schedule.weekdays != null) {
|
|
1112
|
+
context.addIssue({
|
|
1113
|
+
code: "custom",
|
|
1114
|
+
message: "weekdays applies only to weekly schedules.",
|
|
1115
|
+
path: ["weekdays"]
|
|
1116
|
+
});
|
|
1117
|
+
}
|
|
1118
|
+
if (schedule.frequency !== "monthly" && schedule.frequency !== "yearly" && schedule.day_of_month != null) {
|
|
1119
|
+
context.addIssue({
|
|
1120
|
+
code: "custom",
|
|
1121
|
+
message: "day_of_month applies only to monthly or yearly schedules.",
|
|
1122
|
+
path: ["day_of_month"]
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1125
|
+
if (schedule.frequency !== "yearly" && schedule.month != null) {
|
|
1126
|
+
context.addIssue({
|
|
1127
|
+
code: "custom",
|
|
1128
|
+
message: "month applies only to yearly schedules.",
|
|
1129
|
+
path: ["month"]
|
|
1130
|
+
});
|
|
1131
|
+
}
|
|
1132
|
+
});
|
|
1133
|
+
var scheduleIntentSchema = z3.union([
|
|
1134
|
+
oneOffScheduleIntentSchema,
|
|
1135
|
+
recurringScheduleIntentSchema
|
|
1136
|
+
]);
|
|
1137
|
+
var WEEKDAY_INDEX = {
|
|
1138
|
+
sunday: 0,
|
|
1139
|
+
monday: 1,
|
|
1140
|
+
tuesday: 2,
|
|
1141
|
+
wednesday: 3,
|
|
1142
|
+
thursday: 4,
|
|
1143
|
+
friday: 5,
|
|
1144
|
+
saturday: 6
|
|
1145
|
+
};
|
|
1146
|
+
var DURATION_MS = {
|
|
1147
|
+
second: 1e3,
|
|
1148
|
+
minute: 60 * 1e3,
|
|
1149
|
+
hour: 60 * 60 * 1e3,
|
|
1150
|
+
day: 24 * 60 * 60 * 1e3,
|
|
1151
|
+
week: 7 * 24 * 60 * 60 * 1e3
|
|
1152
|
+
};
|
|
1153
|
+
function parseLocalTime(value) {
|
|
1154
|
+
const [hour, minute] = value.split(":").map(Number);
|
|
1155
|
+
return { hour, minute };
|
|
1156
|
+
}
|
|
1157
|
+
function localDateAt(timestampMs, timezone) {
|
|
1158
|
+
const parts = getZonedDateTimeParts(timestampMs, timezone);
|
|
1159
|
+
return [parts.year, parts.month, parts.day].map(
|
|
1160
|
+
(value, index2) => index2 === 0 ? String(value).padStart(4, "0") : String(value).padStart(2, "0")
|
|
1161
|
+
).join("-");
|
|
1162
|
+
}
|
|
1163
|
+
function isValidTimeZone(timezone) {
|
|
1164
|
+
try {
|
|
1165
|
+
new Intl.DateTimeFormat("en-US", { timeZone: timezone }).format();
|
|
1166
|
+
return true;
|
|
1167
|
+
} catch {
|
|
1168
|
+
return false;
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
function plural(value, unit) {
|
|
1172
|
+
return value === 1 ? unit : `${unit}s`;
|
|
1173
|
+
}
|
|
1174
|
+
function formatWeekdays(weekdays) {
|
|
1175
|
+
const labels = weekdays.map(
|
|
1176
|
+
(weekday) => `${weekday[0].toUpperCase()}${weekday.slice(1)}`
|
|
1177
|
+
);
|
|
1178
|
+
if (labels.length < 2) {
|
|
1179
|
+
return labels[0] ?? "";
|
|
1180
|
+
}
|
|
1181
|
+
return `${labels.slice(0, -1).join(", ")} and ${labels.at(-1)}`;
|
|
1182
|
+
}
|
|
1183
|
+
function recurringDescription(schedule, timezone) {
|
|
1184
|
+
const interval = schedule.interval ?? 1;
|
|
1185
|
+
const cadenceUnit = {
|
|
1186
|
+
daily: "day",
|
|
1187
|
+
weekly: "week",
|
|
1188
|
+
monthly: "month",
|
|
1189
|
+
yearly: "year"
|
|
1190
|
+
}[schedule.frequency];
|
|
1191
|
+
const cadence = interval === 1 ? cadenceUnit : `${interval} ${cadenceUnit}s`;
|
|
1192
|
+
let detail = "";
|
|
1193
|
+
if (schedule.frequency === "weekly") {
|
|
1194
|
+
detail = ` on ${formatWeekdays(schedule.weekdays)}`;
|
|
1195
|
+
} else if (schedule.frequency === "monthly") {
|
|
1196
|
+
detail = ` on day ${schedule.day_of_month}`;
|
|
1197
|
+
} else if (schedule.frequency === "yearly") {
|
|
1198
|
+
detail = ` on ${String(schedule.month).padStart(2, "0")}-${String(
|
|
1199
|
+
schedule.day_of_month
|
|
1200
|
+
).padStart(2, "0")}`;
|
|
1201
|
+
}
|
|
1202
|
+
return `Every ${cadence}${detail} at ${schedule.time} (${timezone})`;
|
|
1203
|
+
}
|
|
1204
|
+
var ScheduleIntentError = class extends Error {
|
|
1205
|
+
};
|
|
1206
|
+
function compileScheduleIntent(args) {
|
|
1207
|
+
const timezone = args.intent.timezone ?? args.defaultTimezone;
|
|
1208
|
+
if (!isValidTimeZone(timezone)) {
|
|
1209
|
+
throw new ScheduleIntentError("timezone must be a valid IANA time zone.");
|
|
1210
|
+
}
|
|
1211
|
+
if (args.intent.kind === "one_off") {
|
|
1212
|
+
if (args.intent.timing.type === "after") {
|
|
1213
|
+
const nextRunAtMs3 = args.nowMs + args.intent.timing.value * DURATION_MS[args.intent.timing.unit];
|
|
1214
|
+
return {
|
|
1215
|
+
nextRunAtMs: nextRunAtMs3,
|
|
1216
|
+
schedule: {
|
|
1217
|
+
description: `In ${args.intent.timing.value} ${plural(
|
|
1218
|
+
args.intent.timing.value,
|
|
1219
|
+
args.intent.timing.unit
|
|
1220
|
+
)}`,
|
|
1221
|
+
kind: "one_off",
|
|
1222
|
+
timezone
|
|
1223
|
+
}
|
|
1224
|
+
};
|
|
1225
|
+
}
|
|
1226
|
+
const nextRunAtMs2 = resolveLocalScheduleAtMs({
|
|
1227
|
+
date: args.intent.timing.date,
|
|
1228
|
+
time: parseLocalTime(args.intent.timing.time),
|
|
1229
|
+
timezone
|
|
1230
|
+
});
|
|
1231
|
+
if (nextRunAtMs2 === void 0) {
|
|
1232
|
+
throw new ScheduleIntentError(
|
|
1233
|
+
"The requested local date or time does not exist in that timezone."
|
|
1234
|
+
);
|
|
1235
|
+
}
|
|
1236
|
+
if (nextRunAtMs2 <= args.nowMs) {
|
|
1237
|
+
throw new ScheduleIntentError(
|
|
1238
|
+
"The requested one-off schedule must be in the future."
|
|
1239
|
+
);
|
|
1240
|
+
}
|
|
1241
|
+
return {
|
|
1242
|
+
nextRunAtMs: nextRunAtMs2,
|
|
1243
|
+
schedule: {
|
|
1244
|
+
description: `Once on ${args.intent.timing.date} at ${args.intent.timing.time} (${timezone})`,
|
|
1245
|
+
kind: "one_off",
|
|
1246
|
+
timezone
|
|
1247
|
+
}
|
|
1248
|
+
};
|
|
1249
|
+
}
|
|
1250
|
+
const recurrence = {
|
|
1251
|
+
frequency: args.intent.frequency,
|
|
1252
|
+
interval: args.intent.interval ?? 1,
|
|
1253
|
+
startDate: args.intent.start_date ?? localDateAt(args.nowMs, timezone),
|
|
1254
|
+
time: parseLocalTime(args.intent.time),
|
|
1255
|
+
...args.intent.frequency === "weekly" ? {
|
|
1256
|
+
weekdays: [
|
|
1257
|
+
...new Set(
|
|
1258
|
+
args.intent.weekdays.map((weekday) => WEEKDAY_INDEX[weekday])
|
|
1259
|
+
)
|
|
1260
|
+
].sort((left, right) => left - right)
|
|
1261
|
+
} : {},
|
|
1262
|
+
...args.intent.frequency === "monthly" || args.intent.frequency === "yearly" ? { dayOfMonth: args.intent.day_of_month ?? void 0 } : {},
|
|
1263
|
+
...args.intent.frequency === "yearly" ? { month: args.intent.month ?? void 0 } : {}
|
|
1264
|
+
};
|
|
1265
|
+
const searchRecurrence = args.intent.start_date ? recurrence : { ...recurrence, interval: 1 };
|
|
1266
|
+
const nextRunAtMs = getFirstRunAtMs({
|
|
1267
|
+
afterMs: args.nowMs,
|
|
1268
|
+
recurrence: searchRecurrence,
|
|
1269
|
+
timezone
|
|
1270
|
+
});
|
|
1271
|
+
if (nextRunAtMs === void 0) {
|
|
1272
|
+
throw new ScheduleIntentError(
|
|
1273
|
+
"The recurring schedule has no valid future occurrence."
|
|
1274
|
+
);
|
|
1275
|
+
}
|
|
1276
|
+
const materializedRecurrence = args.intent.start_date ? recurrence : { ...recurrence, startDate: localDateAt(nextRunAtMs, timezone) };
|
|
1277
|
+
return {
|
|
1278
|
+
nextRunAtMs,
|
|
1279
|
+
schedule: {
|
|
1280
|
+
description: recurringDescription(args.intent, timezone),
|
|
1281
|
+
kind: "recurring",
|
|
1282
|
+
recurrence: materializedRecurrence,
|
|
1283
|
+
timezone
|
|
1284
|
+
}
|
|
1285
|
+
};
|
|
1286
|
+
}
|
|
1185
1287
|
|
|
1186
1288
|
// src/tool-support.ts
|
|
1187
|
-
import {
|
|
1289
|
+
import { createHash } from "crypto";
|
|
1188
1290
|
import {
|
|
1189
1291
|
PluginToolInputError,
|
|
1190
1292
|
pluginToolResultSchema,
|
|
1191
1293
|
sourceSchema
|
|
1192
1294
|
} from "@sentry/junior-plugin-api";
|
|
1193
|
-
import { z as
|
|
1295
|
+
import { z as z4 } from "zod";
|
|
1194
1296
|
var TASK_ID_PREFIX = "sched";
|
|
1195
1297
|
var MAX_LISTED_TASKS = 50;
|
|
1196
1298
|
var DEFAULT_SCHEDULE_TIMEZONE = "America/Los_Angeles";
|
|
1197
|
-
var compactTaskResultSchema =
|
|
1198
|
-
id:
|
|
1199
|
-
status:
|
|
1200
|
-
task:
|
|
1201
|
-
schedule:
|
|
1202
|
-
timezone:
|
|
1203
|
-
recurrence:
|
|
1204
|
-
next_run_at:
|
|
1205
|
-
conversation_access:
|
|
1206
|
-
credential_mode:
|
|
1207
|
-
last_run_at:
|
|
1208
|
-
run_now_at:
|
|
1299
|
+
var compactTaskResultSchema = z4.object({
|
|
1300
|
+
id: z4.string(),
|
|
1301
|
+
status: z4.enum(["active", "paused", "blocked", "deleted"]),
|
|
1302
|
+
task: z4.string(),
|
|
1303
|
+
schedule: z4.string(),
|
|
1304
|
+
timezone: z4.string(),
|
|
1305
|
+
recurrence: z4.unknown().nullable(),
|
|
1306
|
+
next_run_at: z4.string().nullable(),
|
|
1307
|
+
conversation_access: z4.unknown().nullable(),
|
|
1308
|
+
credential_mode: z4.enum(["system", "creator"]),
|
|
1309
|
+
last_run_at: z4.string().nullable(),
|
|
1310
|
+
run_now_at: z4.string().nullable()
|
|
1209
1311
|
}).strict();
|
|
1210
|
-
var scheduleTaskResultDataSchema =
|
|
1211
|
-
ok:
|
|
1312
|
+
var scheduleTaskResultDataSchema = z4.object({
|
|
1313
|
+
ok: z4.literal(true),
|
|
1212
1314
|
task: compactTaskResultSchema
|
|
1213
1315
|
}).strict();
|
|
1214
1316
|
var scheduleTaskToolResultSchema = pluginToolResultSchema.extend({
|
|
1215
|
-
ok:
|
|
1216
|
-
status:
|
|
1217
|
-
target:
|
|
1317
|
+
ok: z4.literal(true),
|
|
1318
|
+
status: z4.literal("success"),
|
|
1319
|
+
target: z4.string(),
|
|
1218
1320
|
data: scheduleTaskResultDataSchema,
|
|
1219
1321
|
task: compactTaskResultSchema
|
|
1220
1322
|
});
|
|
1221
|
-
var scheduleListResultDataSchema =
|
|
1222
|
-
ok:
|
|
1223
|
-
tasks:
|
|
1224
|
-
truncated:
|
|
1323
|
+
var scheduleListResultDataSchema = z4.object({
|
|
1324
|
+
ok: z4.literal(true),
|
|
1325
|
+
tasks: z4.array(compactTaskResultSchema),
|
|
1326
|
+
truncated: z4.boolean()
|
|
1225
1327
|
}).strict();
|
|
1226
1328
|
var scheduleListToolResultSchema = pluginToolResultSchema.extend({
|
|
1227
|
-
ok:
|
|
1228
|
-
status:
|
|
1229
|
-
target:
|
|
1329
|
+
ok: z4.literal(true),
|
|
1330
|
+
status: z4.literal("success"),
|
|
1331
|
+
target: z4.string(),
|
|
1230
1332
|
data: scheduleListResultDataSchema,
|
|
1231
|
-
tasks:
|
|
1232
|
-
truncated:
|
|
1333
|
+
tasks: z4.array(compactTaskResultSchema),
|
|
1334
|
+
truncated: z4.boolean()
|
|
1233
1335
|
});
|
|
1234
1336
|
function throwToolInputError(error) {
|
|
1235
1337
|
throw new PluginToolInputError(error);
|
|
@@ -1363,8 +1465,21 @@ function scheduleListToolResult(args) {
|
|
|
1363
1465
|
truncated: args.truncated
|
|
1364
1466
|
};
|
|
1365
1467
|
}
|
|
1366
|
-
function buildTaskId() {
|
|
1367
|
-
|
|
1468
|
+
function buildTaskId(args) {
|
|
1469
|
+
const toolCallId = args.toolCallId?.trim();
|
|
1470
|
+
if (!toolCallId) {
|
|
1471
|
+
throw new Error("Scheduler task creation requires a tool-call identity.");
|
|
1472
|
+
}
|
|
1473
|
+
const digest = createHash("sha256").update(
|
|
1474
|
+
JSON.stringify({
|
|
1475
|
+
actor: args.actor.slackUserId,
|
|
1476
|
+
channel: args.destination.channelId,
|
|
1477
|
+
operation: toolCallId,
|
|
1478
|
+
platform: args.destination.platform,
|
|
1479
|
+
team: args.destination.teamId
|
|
1480
|
+
})
|
|
1481
|
+
).digest("hex").slice(0, 32);
|
|
1482
|
+
return `${TASK_ID_PREFIX}_${digest}`;
|
|
1368
1483
|
}
|
|
1369
1484
|
function schedulerStore(context) {
|
|
1370
1485
|
return context.store;
|
|
@@ -1375,133 +1490,64 @@ function normalizeStatus(value) {
|
|
|
1375
1490
|
}
|
|
1376
1491
|
return void 0;
|
|
1377
1492
|
}
|
|
1378
|
-
function normalizeFrequency(value) {
|
|
1379
|
-
if (value === "daily" || value === "weekly" || value === "monthly" || value === "yearly") {
|
|
1380
|
-
return value;
|
|
1381
|
-
}
|
|
1382
|
-
return void 0;
|
|
1383
|
-
}
|
|
1384
|
-
function buildRecurrence(args) {
|
|
1385
|
-
if (args.input.recurrence === null) {
|
|
1386
|
-
return void 0;
|
|
1387
|
-
}
|
|
1388
|
-
const frequency = normalizeFrequency(args.input.recurrence) ?? args.existing?.frequency;
|
|
1389
|
-
if (!frequency) {
|
|
1390
|
-
return void 0;
|
|
1391
|
-
}
|
|
1392
|
-
if (!args.nextRunAtMs) {
|
|
1393
|
-
throwToolInputError("Recurring scheduled tasks require next_run_at.");
|
|
1394
|
-
}
|
|
1395
|
-
try {
|
|
1396
|
-
return buildCalendarRecurrence({
|
|
1397
|
-
frequency,
|
|
1398
|
-
interval: args.existing?.interval,
|
|
1399
|
-
nextRunAtMs: args.nextRunAtMs,
|
|
1400
|
-
timezone: args.timezone,
|
|
1401
|
-
weekdays: frequency === "weekly" ? args.existing?.weekdays : void 0
|
|
1402
|
-
});
|
|
1403
|
-
} catch (error) {
|
|
1404
|
-
throwToolInputError(
|
|
1405
|
-
error instanceof RangeError ? "timezone must be a valid IANA time zone." : error instanceof Error ? error.message : String(error)
|
|
1406
|
-
);
|
|
1407
|
-
}
|
|
1408
|
-
}
|
|
1409
|
-
function validateRecurringFrequencyLimit(input) {
|
|
1410
|
-
if (input.recurrence !== void 0 && input.recurrence !== null && !normalizeFrequency(input.recurrence)) {
|
|
1411
|
-
throwToolInputError(
|
|
1412
|
-
"Recurring scheduled tasks can run at most once per day."
|
|
1413
|
-
);
|
|
1414
|
-
}
|
|
1415
|
-
}
|
|
1416
|
-
function validateCreateScheduleKind(input) {
|
|
1417
|
-
if (input.schedule_kind === void 0) {
|
|
1418
|
-
throwToolInputError("Provide schedule_kind as one_off or recurring.");
|
|
1419
|
-
}
|
|
1420
|
-
if (input.schedule_kind !== "one_off" && input.schedule_kind !== "recurring") {
|
|
1421
|
-
throwToolInputError("schedule_kind must be one_off or recurring.");
|
|
1422
|
-
}
|
|
1423
|
-
if (input.schedule_kind === "one_off" && input.recurrence !== void 0 && input.recurrence !== null) {
|
|
1424
|
-
throwToolInputError("Omit recurrence when schedule_kind is one_off.");
|
|
1425
|
-
}
|
|
1426
|
-
if (input.schedule_kind === "recurring" && (input.recurrence === void 0 || input.recurrence === null)) {
|
|
1427
|
-
throwToolInputError("Provide recurrence when schedule_kind is recurring.");
|
|
1428
|
-
}
|
|
1429
|
-
}
|
|
1430
|
-
function shouldRebuildRecurrence(input) {
|
|
1431
|
-
return input.next_run_at !== void 0 || input.recurrence !== void 0 || input.timezone !== void 0;
|
|
1432
|
-
}
|
|
1433
1493
|
function getDefaultScheduleTimezone() {
|
|
1434
1494
|
return process.env.JUNIOR_TIMEZONE?.trim() || DEFAULT_SCHEDULE_TIMEZONE;
|
|
1435
1495
|
}
|
|
1436
|
-
function isValidTimeZone(timezone) {
|
|
1437
|
-
try {
|
|
1438
|
-
new Intl.DateTimeFormat("en-US", { timeZone: timezone }).format();
|
|
1439
|
-
return true;
|
|
1440
|
-
} catch {
|
|
1441
|
-
return false;
|
|
1442
|
-
}
|
|
1443
|
-
}
|
|
1444
|
-
function parseNextRunAtMs(nextRunAtIso) {
|
|
1445
|
-
try {
|
|
1446
|
-
if (nextRunAtIso) {
|
|
1447
|
-
return parseScheduleTimestamp(nextRunAtIso);
|
|
1448
|
-
}
|
|
1449
|
-
} catch {
|
|
1450
|
-
return void 0;
|
|
1451
|
-
}
|
|
1452
|
-
return void 0;
|
|
1453
|
-
}
|
|
1454
1496
|
|
|
1455
1497
|
// src/tools/create-task.ts
|
|
1456
1498
|
function createSlackScheduleCreateTaskTool(context) {
|
|
1457
1499
|
return definePluginTool({
|
|
1458
|
-
description: "Create a one-time or recurring Junior task in the active Slack conversation
|
|
1500
|
+
description: "Create a one-time or recurring Junior task in the active Slack conversation when the user asks Junior to do work later or repeatedly.",
|
|
1459
1501
|
executionMode: "sequential",
|
|
1460
|
-
inputSchema:
|
|
1461
|
-
task:
|
|
1462
|
-
schedule:
|
|
1463
|
-
|
|
1464
|
-
"Required schedule classification. Use one_off for one-time reminders or one-time scheduled work. Use recurring only when the user explicitly asks for a repeating schedule."
|
|
1502
|
+
inputSchema: z5.object({
|
|
1503
|
+
task: z5.string().min(1).max(4e3),
|
|
1504
|
+
schedule: scheduleIntentSchema.describe(
|
|
1505
|
+
"When the task runs. The scheduler computes the exact next run from this intent and the server clock."
|
|
1465
1506
|
),
|
|
1466
|
-
|
|
1467
|
-
"IANA timezone, e.g. 'America/Los_Angeles'. Defaults to the channel's configured timezone."
|
|
1468
|
-
).optional(),
|
|
1469
|
-
next_run_at: z4.string().min(1).describe(
|
|
1470
|
-
"Exact next run time as an ISO timestamp, computed from the user's requested schedule."
|
|
1471
|
-
).optional(),
|
|
1472
|
-
recurrence: z4.enum(["daily", "weekly", "monthly", "yearly"]).nullable().describe(
|
|
1473
|
-
"Required when schedule_kind is recurring. Omit when schedule_kind is one_off. Recurring tasks run at most once per day: use daily, weekly, monthly, or yearly only."
|
|
1474
|
-
).optional(),
|
|
1475
|
-
credential_mode: z4.enum(["system", "creator"]).nullable().describe(
|
|
1507
|
+
credential_mode: z5.enum(["system", "creator"]).nullable().describe(
|
|
1476
1508
|
"Use creator only when the current user explicitly authorizes future scheduled use of their connected credentials. Omit or use system otherwise."
|
|
1477
1509
|
).optional()
|
|
1478
|
-
}),
|
|
1510
|
+
}).strict(),
|
|
1479
1511
|
outputSchema: scheduleTaskToolResultSchema,
|
|
1480
|
-
execute: async (input) => {
|
|
1512
|
+
execute: async (input, options) => {
|
|
1481
1513
|
const destination = requireActiveConversation(context);
|
|
1482
1514
|
const actor = requireActor(context, destination);
|
|
1483
|
-
const
|
|
1484
|
-
const
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1515
|
+
const store = schedulerStore(context);
|
|
1516
|
+
const id = buildTaskId({
|
|
1517
|
+
actor,
|
|
1518
|
+
destination,
|
|
1519
|
+
toolCallId: options.toolCallId
|
|
1520
|
+
});
|
|
1521
|
+
const existing = await store.getTask(id);
|
|
1522
|
+
if (existing) {
|
|
1523
|
+
if (!sameDestination(existing, destination) || existing.createdBy.slackUserId !== actor.slackUserId) {
|
|
1524
|
+
throwToolInputError("Scheduled task operation identity is invalid.");
|
|
1525
|
+
}
|
|
1526
|
+
return scheduleTaskToolResult(
|
|
1527
|
+
"slackScheduleCreateTask",
|
|
1528
|
+
compactTask(existing)
|
|
1529
|
+
);
|
|
1489
1530
|
}
|
|
1490
|
-
const
|
|
1491
|
-
|
|
1492
|
-
|
|
1531
|
+
const nowMs = context.now?.() ?? Date.now();
|
|
1532
|
+
let compiled;
|
|
1533
|
+
try {
|
|
1534
|
+
compiled = compileScheduleIntent({
|
|
1535
|
+
defaultTimezone: getDefaultScheduleTimezone(),
|
|
1536
|
+
intent: input.schedule,
|
|
1537
|
+
nowMs
|
|
1538
|
+
});
|
|
1539
|
+
} catch (error) {
|
|
1540
|
+
if (error instanceof ScheduleIntentError) {
|
|
1541
|
+
throwToolInputError(error.message);
|
|
1542
|
+
}
|
|
1543
|
+
throw error;
|
|
1493
1544
|
}
|
|
1494
|
-
const recurrence = buildRecurrence({
|
|
1495
|
-
input,
|
|
1496
|
-
nextRunAtMs,
|
|
1497
|
-
timezone
|
|
1498
|
-
});
|
|
1499
1545
|
const conversationAccess = getConversationAccess(
|
|
1500
1546
|
destination,
|
|
1501
1547
|
context.source
|
|
1502
1548
|
);
|
|
1503
1549
|
const task = {
|
|
1504
|
-
id
|
|
1550
|
+
id,
|
|
1505
1551
|
createdAtMs: nowMs,
|
|
1506
1552
|
updatedAtMs: nowMs,
|
|
1507
1553
|
createdBy: actor,
|
|
@@ -1509,23 +1555,21 @@ function createSlackScheduleCreateTaskTool(context) {
|
|
|
1509
1555
|
credentialMode: input.credential_mode ?? "system",
|
|
1510
1556
|
destination,
|
|
1511
1557
|
executionActor: SCHEDULED_TASK_SYSTEM_ACTOR,
|
|
1512
|
-
nextRunAtMs,
|
|
1558
|
+
nextRunAtMs: compiled.nextRunAtMs,
|
|
1513
1559
|
originalRequest: context.userText,
|
|
1514
|
-
schedule:
|
|
1515
|
-
description: input.schedule,
|
|
1516
|
-
timezone,
|
|
1517
|
-
kind: recurrence ? "recurring" : "one_off",
|
|
1518
|
-
recurrence
|
|
1519
|
-
},
|
|
1560
|
+
schedule: compiled.schedule,
|
|
1520
1561
|
status: "active",
|
|
1521
1562
|
task: {
|
|
1522
1563
|
text: input.task
|
|
1523
1564
|
}
|
|
1524
1565
|
};
|
|
1525
|
-
await
|
|
1566
|
+
const committed = await store.createTask(task);
|
|
1567
|
+
if (!sameDestination(committed, destination) || committed.createdBy.slackUserId !== actor.slackUserId) {
|
|
1568
|
+
throwToolInputError("Scheduled task operation identity is invalid.");
|
|
1569
|
+
}
|
|
1526
1570
|
return scheduleTaskToolResult(
|
|
1527
1571
|
"slackScheduleCreateTask",
|
|
1528
|
-
compactTask(
|
|
1572
|
+
compactTask(committed)
|
|
1529
1573
|
);
|
|
1530
1574
|
}
|
|
1531
1575
|
});
|
|
@@ -1533,13 +1577,13 @@ function createSlackScheduleCreateTaskTool(context) {
|
|
|
1533
1577
|
|
|
1534
1578
|
// src/tools/delete-task.ts
|
|
1535
1579
|
import { definePluginTool as definePluginTool2 } from "@sentry/junior-plugin-api";
|
|
1536
|
-
import { z as
|
|
1580
|
+
import { z as z6 } from "zod";
|
|
1537
1581
|
function createSlackScheduleDeleteTaskTool(context) {
|
|
1538
1582
|
return definePluginTool2({
|
|
1539
1583
|
description: "Delete one scheduled Junior task from the active Slack conversation. Use only task IDs returned for this conversation. Do not delete schedules from threads, other channels, or another user's DM.",
|
|
1540
1584
|
executionMode: "sequential",
|
|
1541
|
-
inputSchema:
|
|
1542
|
-
task_id:
|
|
1585
|
+
inputSchema: z6.object({
|
|
1586
|
+
task_id: z6.string().min(1).describe(
|
|
1543
1587
|
"ID of the task to delete. Must be from this active Slack conversation."
|
|
1544
1588
|
)
|
|
1545
1589
|
}),
|
|
@@ -1564,12 +1608,12 @@ function createSlackScheduleDeleteTaskTool(context) {
|
|
|
1564
1608
|
|
|
1565
1609
|
// src/tools/list-tasks.ts
|
|
1566
1610
|
import { definePluginTool as definePluginTool3 } from "@sentry/junior-plugin-api";
|
|
1567
|
-
import { z as
|
|
1611
|
+
import { z as z7 } from "zod";
|
|
1568
1612
|
function createSlackScheduleListTasksTool(context) {
|
|
1569
1613
|
return definePluginTool3({
|
|
1570
1614
|
description: "List scheduled Junior tasks for the active Slack conversation. Use when the user asks what is scheduled here, or when task IDs are needed before editing, deleting, or running schedules. Only manages tasks for the active Slack DM or channel.",
|
|
1571
1615
|
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
1572
|
-
inputSchema:
|
|
1616
|
+
inputSchema: z7.object({}),
|
|
1573
1617
|
outputSchema: scheduleListToolResultSchema,
|
|
1574
1618
|
execute: async () => {
|
|
1575
1619
|
const destination = requireActiveConversation(context);
|
|
@@ -1591,13 +1635,13 @@ function createSlackScheduleListTasksTool(context) {
|
|
|
1591
1635
|
|
|
1592
1636
|
// src/tools/run-task-now.ts
|
|
1593
1637
|
import { definePluginTool as definePluginTool4 } from "@sentry/junior-plugin-api";
|
|
1594
|
-
import { z as
|
|
1638
|
+
import { z as z8 } from "zod";
|
|
1595
1639
|
function createSlackScheduleRunTaskNowTool(context) {
|
|
1596
1640
|
return definePluginTool4({
|
|
1597
1641
|
description: "Queue an existing active scheduled Junior task to run as soon as possible, without changing its cadence. Use when the user asks to run an existing scheduled task now. Use only task IDs returned for this conversation.",
|
|
1598
1642
|
executionMode: "sequential",
|
|
1599
|
-
inputSchema:
|
|
1600
|
-
task_id:
|
|
1643
|
+
inputSchema: z8.object({
|
|
1644
|
+
task_id: z8.string().min(1).describe(
|
|
1601
1645
|
"ID of the active task to run now. Must be from this active Slack conversation."
|
|
1602
1646
|
)
|
|
1603
1647
|
}),
|
|
@@ -1626,29 +1670,26 @@ function createSlackScheduleRunTaskNowTool(context) {
|
|
|
1626
1670
|
|
|
1627
1671
|
// src/tools/update-task.ts
|
|
1628
1672
|
import { definePluginTool as definePluginTool5 } from "@sentry/junior-plugin-api";
|
|
1629
|
-
import { z as
|
|
1673
|
+
import { z as z9 } from "zod";
|
|
1630
1674
|
function createSlackScheduleUpdateTaskTool(context) {
|
|
1631
1675
|
return definePluginTool5({
|
|
1632
|
-
description: "Edit, pause, resume, reschedule, or change credential use for an existing Junior scheduled task in the active Slack conversation.
|
|
1676
|
+
description: "Edit, pause, resume, reschedule, or change credential use for an existing Junior scheduled task in the active Slack conversation.",
|
|
1633
1677
|
executionMode: "sequential",
|
|
1634
|
-
inputSchema:
|
|
1635
|
-
task_id:
|
|
1678
|
+
inputSchema: z9.object({
|
|
1679
|
+
task_id: z9.string().min(1).describe(
|
|
1636
1680
|
"ID of the task to update. Must be from this active Slack conversation."
|
|
1637
1681
|
),
|
|
1638
|
-
task:
|
|
1639
|
-
schedule:
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
"Provide only for repeating schedules. Omit for one-time requests. Set to null to convert a recurring task to one-time."
|
|
1644
|
-
).optional(),
|
|
1645
|
-
status: z8.enum(["active", "paused", "blocked"]).describe(
|
|
1682
|
+
task: z9.string().min(1).max(4e3).optional(),
|
|
1683
|
+
schedule: scheduleIntentSchema.describe(
|
|
1684
|
+
"Complete replacement schedule when rescheduling. Omit for task, status, or credential-only changes; the scheduler computes the next run."
|
|
1685
|
+
).nullable().optional(),
|
|
1686
|
+
status: z9.enum(["active", "paused", "blocked"]).describe(
|
|
1646
1687
|
"Set to active, paused, or blocked to resume, pause, or block the task."
|
|
1647
1688
|
).optional(),
|
|
1648
|
-
credential_mode:
|
|
1689
|
+
credential_mode: z9.enum(["system", "creator"]).nullable().describe(
|
|
1649
1690
|
"Set creator only when the current actor is the task creator and explicitly authorizes future scheduled credential use. Set system to disable delegation."
|
|
1650
1691
|
).optional()
|
|
1651
|
-
}),
|
|
1692
|
+
}).strict(),
|
|
1652
1693
|
outputSchema: scheduleTaskToolResultSchema,
|
|
1653
1694
|
execute: async (input) => {
|
|
1654
1695
|
const lookup = await getWritableTask({
|
|
@@ -1662,48 +1703,43 @@ function createSlackScheduleUpdateTaskTool(context) {
|
|
|
1662
1703
|
"Only the scheduled task creator can enable creator credential use."
|
|
1663
1704
|
);
|
|
1664
1705
|
}
|
|
1665
|
-
const
|
|
1666
|
-
|
|
1667
|
-
if (
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1706
|
+
const nowMs = context.now?.() ?? Date.now();
|
|
1707
|
+
let compiled;
|
|
1708
|
+
if (input.schedule) {
|
|
1709
|
+
try {
|
|
1710
|
+
compiled = compileScheduleIntent({
|
|
1711
|
+
defaultTimezone: lookup.schedule.timezone || getDefaultScheduleTimezone(),
|
|
1712
|
+
intent: input.schedule,
|
|
1713
|
+
nowMs
|
|
1714
|
+
});
|
|
1715
|
+
} catch (error) {
|
|
1716
|
+
if (error instanceof ScheduleIntentError) {
|
|
1717
|
+
throwToolInputError(error.message);
|
|
1718
|
+
}
|
|
1719
|
+
throw error;
|
|
1720
|
+
}
|
|
1674
1721
|
}
|
|
1722
|
+
const nextRunAtMs = compiled?.nextRunAtMs ?? lookup.nextRunAtMs;
|
|
1675
1723
|
const status = normalizeStatus(input.status);
|
|
1676
1724
|
if (input.status && !status) {
|
|
1677
1725
|
throwToolInputError("status must be active, paused, or blocked.");
|
|
1678
1726
|
}
|
|
1679
1727
|
if (status === "active" && !nextRunAtMs) {
|
|
1680
1728
|
throwToolInputError(
|
|
1681
|
-
"Active scheduled tasks require
|
|
1729
|
+
"Active scheduled tasks require a schedule with a future occurrence."
|
|
1682
1730
|
);
|
|
1683
1731
|
}
|
|
1684
|
-
const recurrence = shouldRebuildRecurrence(input) ? buildRecurrence({
|
|
1685
|
-
existing: lookup.schedule.recurrence,
|
|
1686
|
-
input,
|
|
1687
|
-
nextRunAtMs,
|
|
1688
|
-
timezone
|
|
1689
|
-
}) : lookup.schedule.recurrence;
|
|
1690
1732
|
const nextStatus = status ?? lookup.status;
|
|
1691
1733
|
const credentialMode = input.task !== void 0 && input.task !== lookup.task.text && !isCreator ? "system" : input.credential_mode ?? lookup.credentialMode;
|
|
1692
1734
|
const next = {
|
|
1693
1735
|
...lookup,
|
|
1694
1736
|
credentialMode,
|
|
1695
|
-
updatedAtMs:
|
|
1737
|
+
updatedAtMs: nowMs,
|
|
1696
1738
|
nextRunAtMs,
|
|
1697
|
-
runNowAtMs: nextStatus === "active" ? lookup.runNowAtMs : void 0,
|
|
1739
|
+
runNowAtMs: nextStatus === "active" && !compiled ? lookup.runNowAtMs : void 0,
|
|
1698
1740
|
status: nextStatus,
|
|
1699
1741
|
statusReason: nextStatus === "blocked" ? lookup.statusReason : void 0,
|
|
1700
|
-
schedule:
|
|
1701
|
-
...lookup.schedule,
|
|
1702
|
-
description: input.schedule ?? lookup.schedule.description,
|
|
1703
|
-
timezone,
|
|
1704
|
-
kind: recurrence ? "recurring" : "one_off",
|
|
1705
|
-
recurrence
|
|
1706
|
-
},
|
|
1742
|
+
schedule: compiled?.schedule ?? lookup.schedule,
|
|
1707
1743
|
task: input.task ? { text: input.task } : lookup.task
|
|
1708
1744
|
};
|
|
1709
1745
|
await schedulerStore(context).saveTask(next);
|
|
@@ -2156,12 +2192,6 @@ function createSchedulerPlugin() {
|
|
|
2156
2192
|
nowMs: ctx.nowMs,
|
|
2157
2193
|
store: schedulerOperationalStore(ctx)
|
|
2158
2194
|
});
|
|
2159
|
-
},
|
|
2160
|
-
async migrateStorage(ctx) {
|
|
2161
|
-
return await migrateSchedulerStateToSql({
|
|
2162
|
-
db: ctx.db,
|
|
2163
|
-
state: ctx.state
|
|
2164
|
-
});
|
|
2165
2195
|
}
|
|
2166
2196
|
}
|
|
2167
2197
|
});
|
|
@@ -2176,6 +2206,5 @@ export {
|
|
|
2176
2206
|
createSlackScheduleListTasksTool,
|
|
2177
2207
|
createSlackScheduleRunTaskNowTool,
|
|
2178
2208
|
createSlackScheduleUpdateTaskTool,
|
|
2179
|
-
migrateSchedulerStateToSql,
|
|
2180
2209
|
schedulerPlugin
|
|
2181
2210
|
};
|