@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/src/tool-support.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
2
|
import {
|
|
3
3
|
PluginToolInputError,
|
|
4
4
|
pluginToolResultSchema,
|
|
@@ -8,20 +8,18 @@ import {
|
|
|
8
8
|
type SlackSource,
|
|
9
9
|
} from "@sentry/junior-plugin-api";
|
|
10
10
|
import { z } from "zod";
|
|
11
|
-
import { buildCalendarRecurrence, parseScheduleTimestamp } from "./cadence";
|
|
12
11
|
import { sanitizeScheduledTaskPrincipal } from "./identity";
|
|
13
12
|
import { type SchedulerStore } from "./store";
|
|
14
13
|
import type {
|
|
15
|
-
ScheduledCalendarFrequency,
|
|
16
14
|
ScheduledTask,
|
|
17
15
|
ScheduledTaskConversationAccess,
|
|
18
16
|
ScheduledTaskPrincipal,
|
|
19
|
-
ScheduledTaskRecurrence,
|
|
20
17
|
ScheduledTaskStatus,
|
|
21
18
|
} from "./types";
|
|
22
19
|
|
|
23
20
|
export interface SchedulerToolContext {
|
|
24
21
|
actor?: SlackActor;
|
|
22
|
+
now?: () => number;
|
|
25
23
|
source?: SlackSource;
|
|
26
24
|
store: SchedulerStore;
|
|
27
25
|
userText?: string;
|
|
@@ -277,9 +275,29 @@ export function scheduleListToolResult(args: {
|
|
|
277
275
|
} as const;
|
|
278
276
|
}
|
|
279
277
|
|
|
280
|
-
/**
|
|
281
|
-
export function buildTaskId(
|
|
282
|
-
|
|
278
|
+
/** Build a retry-stable scheduler id scoped to the creating actor and destination. */
|
|
279
|
+
export function buildTaskId(args: {
|
|
280
|
+
actor: ScheduledTaskPrincipal;
|
|
281
|
+
destination: SlackDestination;
|
|
282
|
+
toolCallId: string | undefined;
|
|
283
|
+
}): string {
|
|
284
|
+
const toolCallId = args.toolCallId?.trim();
|
|
285
|
+
if (!toolCallId) {
|
|
286
|
+
throw new Error("Scheduler task creation requires a tool-call identity.");
|
|
287
|
+
}
|
|
288
|
+
const digest = createHash("sha256")
|
|
289
|
+
.update(
|
|
290
|
+
JSON.stringify({
|
|
291
|
+
actor: args.actor.slackUserId,
|
|
292
|
+
channel: args.destination.channelId,
|
|
293
|
+
operation: toolCallId,
|
|
294
|
+
platform: args.destination.platform,
|
|
295
|
+
team: args.destination.teamId,
|
|
296
|
+
}),
|
|
297
|
+
)
|
|
298
|
+
.digest("hex")
|
|
299
|
+
.slice(0, 32);
|
|
300
|
+
return `${TASK_ID_PREFIX}_${digest}`;
|
|
283
301
|
}
|
|
284
302
|
|
|
285
303
|
/** Keep concrete scheduler tools coupled to the injected store, not global state. */
|
|
@@ -297,143 +315,7 @@ export function normalizeStatus(
|
|
|
297
315
|
return undefined;
|
|
298
316
|
}
|
|
299
317
|
|
|
300
|
-
function normalizeFrequency(
|
|
301
|
-
value: unknown,
|
|
302
|
-
): ScheduledCalendarFrequency | undefined {
|
|
303
|
-
if (
|
|
304
|
-
value === "daily" ||
|
|
305
|
-
value === "weekly" ||
|
|
306
|
-
value === "monthly" ||
|
|
307
|
-
value === "yearly"
|
|
308
|
-
) {
|
|
309
|
-
return value;
|
|
310
|
-
}
|
|
311
|
-
return undefined;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
/** Rebuild recurrence only from validated daily-or-slower schedule requests. */
|
|
315
|
-
export function buildRecurrence(args: {
|
|
316
|
-
existing?: ScheduledTaskRecurrence;
|
|
317
|
-
input: {
|
|
318
|
-
recurrence?: unknown;
|
|
319
|
-
};
|
|
320
|
-
nextRunAtMs: number | undefined;
|
|
321
|
-
timezone: string;
|
|
322
|
-
}): ScheduledTaskRecurrence | undefined {
|
|
323
|
-
if (args.input.recurrence === null) {
|
|
324
|
-
return undefined;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
const frequency =
|
|
328
|
-
normalizeFrequency(args.input.recurrence) ?? args.existing?.frequency;
|
|
329
|
-
if (!frequency) {
|
|
330
|
-
return undefined;
|
|
331
|
-
}
|
|
332
|
-
if (!args.nextRunAtMs) {
|
|
333
|
-
throwToolInputError("Recurring scheduled tasks require next_run_at.");
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
try {
|
|
337
|
-
return buildCalendarRecurrence({
|
|
338
|
-
frequency,
|
|
339
|
-
interval: args.existing?.interval,
|
|
340
|
-
nextRunAtMs: args.nextRunAtMs,
|
|
341
|
-
timezone: args.timezone,
|
|
342
|
-
weekdays: frequency === "weekly" ? args.existing?.weekdays : undefined,
|
|
343
|
-
});
|
|
344
|
-
} catch (error) {
|
|
345
|
-
throwToolInputError(
|
|
346
|
-
error instanceof RangeError
|
|
347
|
-
? "timezone must be a valid IANA time zone."
|
|
348
|
-
: error instanceof Error
|
|
349
|
-
? error.message
|
|
350
|
-
: String(error),
|
|
351
|
-
);
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
/** Reject recurrence values that would exceed the scheduler cadence policy. */
|
|
356
|
-
export function validateRecurringFrequencyLimit(input: {
|
|
357
|
-
recurrence?: unknown;
|
|
358
|
-
}) {
|
|
359
|
-
if (
|
|
360
|
-
input.recurrence !== undefined &&
|
|
361
|
-
input.recurrence !== null &&
|
|
362
|
-
!normalizeFrequency(input.recurrence)
|
|
363
|
-
) {
|
|
364
|
-
throwToolInputError(
|
|
365
|
-
"Recurring scheduled tasks can run at most once per day.",
|
|
366
|
-
);
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
/** Force create-tool callers to explicitly choose one-off versus recurring semantics. */
|
|
371
|
-
export function validateCreateScheduleKind(input: {
|
|
372
|
-
recurrence?: unknown;
|
|
373
|
-
schedule_kind?: unknown;
|
|
374
|
-
}) {
|
|
375
|
-
if (input.schedule_kind === undefined) {
|
|
376
|
-
throwToolInputError("Provide schedule_kind as one_off or recurring.");
|
|
377
|
-
}
|
|
378
|
-
if (
|
|
379
|
-
input.schedule_kind !== "one_off" &&
|
|
380
|
-
input.schedule_kind !== "recurring"
|
|
381
|
-
) {
|
|
382
|
-
throwToolInputError("schedule_kind must be one_off or recurring.");
|
|
383
|
-
}
|
|
384
|
-
if (
|
|
385
|
-
input.schedule_kind === "one_off" &&
|
|
386
|
-
input.recurrence !== undefined &&
|
|
387
|
-
input.recurrence !== null
|
|
388
|
-
) {
|
|
389
|
-
throwToolInputError("Omit recurrence when schedule_kind is one_off.");
|
|
390
|
-
}
|
|
391
|
-
if (
|
|
392
|
-
input.schedule_kind === "recurring" &&
|
|
393
|
-
(input.recurrence === undefined || input.recurrence === null)
|
|
394
|
-
) {
|
|
395
|
-
throwToolInputError("Provide recurrence when schedule_kind is recurring.");
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
/** Detect update inputs that affect calendar recurrence materialization. */
|
|
400
|
-
export function shouldRebuildRecurrence(input: {
|
|
401
|
-
next_run_at?: string;
|
|
402
|
-
recurrence?: unknown;
|
|
403
|
-
timezone?: string;
|
|
404
|
-
}): boolean {
|
|
405
|
-
return (
|
|
406
|
-
input.next_run_at !== undefined ||
|
|
407
|
-
input.recurrence !== undefined ||
|
|
408
|
-
input.timezone !== undefined
|
|
409
|
-
);
|
|
410
|
-
}
|
|
411
|
-
|
|
412
318
|
/** Centralize scheduler timezone defaulting for all concrete tool entry points. */
|
|
413
319
|
export function getDefaultScheduleTimezone(): string {
|
|
414
320
|
return process.env.JUNIOR_TIMEZONE?.trim() || DEFAULT_SCHEDULE_TIMEZONE;
|
|
415
321
|
}
|
|
416
|
-
|
|
417
|
-
/** Validate IANA timezone names before persisting scheduler cadence state. */
|
|
418
|
-
export function isValidTimeZone(timezone: string): boolean {
|
|
419
|
-
try {
|
|
420
|
-
new Intl.DateTimeFormat("en-US", { timeZone: timezone }).format();
|
|
421
|
-
return true;
|
|
422
|
-
} catch {
|
|
423
|
-
return false;
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
/** Parse model-supplied timestamps without leaking date parser details to tools. */
|
|
428
|
-
export function parseNextRunAtMs(
|
|
429
|
-
nextRunAtIso: string | undefined,
|
|
430
|
-
): number | undefined {
|
|
431
|
-
try {
|
|
432
|
-
if (nextRunAtIso) {
|
|
433
|
-
return parseScheduleTimestamp(nextRunAtIso);
|
|
434
|
-
}
|
|
435
|
-
} catch {
|
|
436
|
-
return undefined;
|
|
437
|
-
}
|
|
438
|
-
return undefined;
|
|
439
|
-
}
|
package/src/tools/create-task.ts
CHANGED
|
@@ -1,23 +1,24 @@
|
|
|
1
1
|
import { definePluginTool } from "@sentry/junior-plugin-api";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
+
import {
|
|
4
|
+
compileScheduleIntent,
|
|
5
|
+
ScheduleIntentError,
|
|
6
|
+
scheduleIntentSchema,
|
|
7
|
+
} from "../schedule-intent";
|
|
3
8
|
import { SCHEDULED_TASK_SYSTEM_ACTOR } from "../types";
|
|
4
9
|
import type { ScheduledTask } from "../types";
|
|
5
10
|
import {
|
|
6
|
-
buildRecurrence,
|
|
7
11
|
buildTaskId,
|
|
8
12
|
compactTask,
|
|
9
13
|
getConversationAccess,
|
|
10
14
|
getDefaultScheduleTimezone,
|
|
11
|
-
isValidTimeZone,
|
|
12
|
-
parseNextRunAtMs,
|
|
13
15
|
requireActiveConversation,
|
|
14
16
|
requireActor,
|
|
17
|
+
sameDestination,
|
|
15
18
|
scheduleTaskToolResult,
|
|
16
19
|
scheduleTaskToolResultSchema,
|
|
17
20
|
schedulerStore,
|
|
18
21
|
throwToolInputError,
|
|
19
|
-
validateCreateScheduleKind,
|
|
20
|
-
validateRecurringFrequencyLimit,
|
|
21
22
|
type SchedulerToolContext,
|
|
22
23
|
} from "../tool-support";
|
|
23
24
|
|
|
@@ -27,74 +28,69 @@ export function createSlackScheduleCreateTaskTool(
|
|
|
27
28
|
) {
|
|
28
29
|
return definePluginTool({
|
|
29
30
|
description:
|
|
30
|
-
"Create a one-time or recurring Junior task in the active Slack conversation
|
|
31
|
+
"Create a one-time or recurring Junior task in the active Slack conversation when the user asks Junior to do work later or repeatedly.",
|
|
31
32
|
executionMode: "sequential",
|
|
32
|
-
inputSchema: z
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
.describe(
|
|
38
|
-
"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.",
|
|
33
|
+
inputSchema: z
|
|
34
|
+
.object({
|
|
35
|
+
task: z.string().min(1).max(4000),
|
|
36
|
+
schedule: scheduleIntentSchema.describe(
|
|
37
|
+
"When the task runs. The scheduler computes the exact next run from this intent and the server clock.",
|
|
39
38
|
),
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
.string()
|
|
50
|
-
.min(1)
|
|
51
|
-
.describe(
|
|
52
|
-
"Exact next run time as an ISO timestamp, computed from the user's requested schedule.",
|
|
53
|
-
)
|
|
54
|
-
.optional(),
|
|
55
|
-
recurrence: z
|
|
56
|
-
.enum(["daily", "weekly", "monthly", "yearly"])
|
|
57
|
-
.nullable()
|
|
58
|
-
.describe(
|
|
59
|
-
"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.",
|
|
60
|
-
)
|
|
61
|
-
.optional(),
|
|
62
|
-
credential_mode: z
|
|
63
|
-
.enum(["system", "creator"])
|
|
64
|
-
.nullable()
|
|
65
|
-
.describe(
|
|
66
|
-
"Use creator only when the current user explicitly authorizes future scheduled use of their connected credentials. Omit or use system otherwise.",
|
|
67
|
-
)
|
|
68
|
-
.optional(),
|
|
69
|
-
}),
|
|
39
|
+
credential_mode: z
|
|
40
|
+
.enum(["system", "creator"])
|
|
41
|
+
.nullable()
|
|
42
|
+
.describe(
|
|
43
|
+
"Use creator only when the current user explicitly authorizes future scheduled use of their connected credentials. Omit or use system otherwise.",
|
|
44
|
+
)
|
|
45
|
+
.optional(),
|
|
46
|
+
})
|
|
47
|
+
.strict(),
|
|
70
48
|
outputSchema: scheduleTaskToolResultSchema,
|
|
71
|
-
execute: async (input) => {
|
|
49
|
+
execute: async (input, options) => {
|
|
72
50
|
const destination = requireActiveConversation(context);
|
|
73
51
|
const actor = requireActor(context, destination);
|
|
74
|
-
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
52
|
+
const store = schedulerStore(context);
|
|
53
|
+
const id = buildTaskId({
|
|
54
|
+
actor,
|
|
55
|
+
destination,
|
|
56
|
+
toolCallId: options.toolCallId,
|
|
57
|
+
});
|
|
58
|
+
// Replaying a durable tool call returns its original task instead of duplicating it.
|
|
59
|
+
const existing = await store.getTask(id);
|
|
60
|
+
if (existing) {
|
|
61
|
+
if (
|
|
62
|
+
!sameDestination(existing, destination) ||
|
|
63
|
+
existing.createdBy.slackUserId !== actor.slackUserId
|
|
64
|
+
) {
|
|
65
|
+
throwToolInputError("Scheduled task operation identity is invalid.");
|
|
66
|
+
}
|
|
67
|
+
return scheduleTaskToolResult(
|
|
68
|
+
"slackScheduleCreateTask",
|
|
69
|
+
compactTask(existing),
|
|
70
|
+
);
|
|
81
71
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
72
|
+
|
|
73
|
+
const nowMs = context.now?.() ?? Date.now();
|
|
74
|
+
let compiled;
|
|
75
|
+
try {
|
|
76
|
+
compiled = compileScheduleIntent({
|
|
77
|
+
defaultTimezone: getDefaultScheduleTimezone(),
|
|
78
|
+
intent: input.schedule,
|
|
79
|
+
nowMs,
|
|
80
|
+
});
|
|
81
|
+
} catch (error) {
|
|
82
|
+
if (error instanceof ScheduleIntentError) {
|
|
83
|
+
throwToolInputError(error.message);
|
|
84
|
+
}
|
|
85
|
+
throw error;
|
|
85
86
|
}
|
|
86
|
-
const recurrence = buildRecurrence({
|
|
87
|
-
input,
|
|
88
|
-
nextRunAtMs,
|
|
89
|
-
timezone,
|
|
90
|
-
});
|
|
91
87
|
const conversationAccess = getConversationAccess(
|
|
92
88
|
destination,
|
|
93
89
|
context.source,
|
|
94
90
|
);
|
|
95
91
|
|
|
96
92
|
const task: ScheduledTask = {
|
|
97
|
-
id
|
|
93
|
+
id,
|
|
98
94
|
createdAtMs: nowMs,
|
|
99
95
|
updatedAtMs: nowMs,
|
|
100
96
|
createdBy: actor,
|
|
@@ -102,24 +98,25 @@ export function createSlackScheduleCreateTaskTool(
|
|
|
102
98
|
credentialMode: input.credential_mode ?? "system",
|
|
103
99
|
destination,
|
|
104
100
|
executionActor: SCHEDULED_TASK_SYSTEM_ACTOR,
|
|
105
|
-
nextRunAtMs,
|
|
101
|
+
nextRunAtMs: compiled.nextRunAtMs,
|
|
106
102
|
originalRequest: context.userText,
|
|
107
|
-
schedule:
|
|
108
|
-
description: input.schedule,
|
|
109
|
-
timezone,
|
|
110
|
-
kind: recurrence ? "recurring" : "one_off",
|
|
111
|
-
recurrence,
|
|
112
|
-
},
|
|
103
|
+
schedule: compiled.schedule,
|
|
113
104
|
status: "active",
|
|
114
105
|
task: {
|
|
115
106
|
text: input.task,
|
|
116
107
|
},
|
|
117
108
|
};
|
|
118
109
|
|
|
119
|
-
await
|
|
110
|
+
const committed = await store.createTask(task);
|
|
111
|
+
if (
|
|
112
|
+
!sameDestination(committed, destination) ||
|
|
113
|
+
committed.createdBy.slackUserId !== actor.slackUserId
|
|
114
|
+
) {
|
|
115
|
+
throwToolInputError("Scheduled task operation identity is invalid.");
|
|
116
|
+
}
|
|
120
117
|
return scheduleTaskToolResult(
|
|
121
118
|
"slackScheduleCreateTask",
|
|
122
|
-
compactTask(
|
|
119
|
+
compactTask(committed),
|
|
123
120
|
);
|
|
124
121
|
},
|
|
125
122
|
});
|
package/src/tools/update-task.ts
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
import { definePluginTool } from "@sentry/junior-plugin-api";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
+
import {
|
|
4
|
+
compileScheduleIntent,
|
|
5
|
+
ScheduleIntentError,
|
|
6
|
+
scheduleIntentSchema,
|
|
7
|
+
} from "../schedule-intent";
|
|
3
8
|
import type { ScheduledTask } from "../types";
|
|
4
9
|
import {
|
|
5
|
-
buildRecurrence,
|
|
6
10
|
compactTask,
|
|
11
|
+
getDefaultScheduleTimezone,
|
|
7
12
|
getWritableTask,
|
|
8
|
-
isValidTimeZone,
|
|
9
13
|
normalizeStatus,
|
|
10
|
-
parseNextRunAtMs,
|
|
11
14
|
requireActor,
|
|
12
15
|
scheduleTaskToolResult,
|
|
13
16
|
scheduleTaskToolResultSchema,
|
|
14
17
|
schedulerStore,
|
|
15
|
-
shouldRebuildRecurrence,
|
|
16
18
|
throwToolInputError,
|
|
17
|
-
validateRecurringFrequencyLimit,
|
|
18
19
|
type SchedulerToolContext,
|
|
19
20
|
} from "../tool-support";
|
|
20
21
|
|
|
@@ -24,44 +25,38 @@ export function createSlackScheduleUpdateTaskTool(
|
|
|
24
25
|
) {
|
|
25
26
|
return definePluginTool({
|
|
26
27
|
description:
|
|
27
|
-
"Edit, pause, resume, reschedule, or change credential use for an existing Junior scheduled task in the active Slack conversation.
|
|
28
|
+
"Edit, pause, resume, reschedule, or change credential use for an existing Junior scheduled task in the active Slack conversation.",
|
|
28
29
|
executionMode: "sequential",
|
|
29
|
-
inputSchema: z
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
.nullable()
|
|
60
|
-
.describe(
|
|
61
|
-
"Set creator only when the current actor is the task creator and explicitly authorizes future scheduled credential use. Set system to disable delegation.",
|
|
62
|
-
)
|
|
63
|
-
.optional(),
|
|
64
|
-
}),
|
|
30
|
+
inputSchema: z
|
|
31
|
+
.object({
|
|
32
|
+
task_id: z
|
|
33
|
+
.string()
|
|
34
|
+
.min(1)
|
|
35
|
+
.describe(
|
|
36
|
+
"ID of the task to update. Must be from this active Slack conversation.",
|
|
37
|
+
),
|
|
38
|
+
task: z.string().min(1).max(4000).optional(),
|
|
39
|
+
schedule: scheduleIntentSchema
|
|
40
|
+
.describe(
|
|
41
|
+
"Complete replacement schedule when rescheduling. Omit for task, status, or credential-only changes; the scheduler computes the next run.",
|
|
42
|
+
)
|
|
43
|
+
.nullable()
|
|
44
|
+
.optional(),
|
|
45
|
+
status: z
|
|
46
|
+
.enum(["active", "paused", "blocked"])
|
|
47
|
+
.describe(
|
|
48
|
+
"Set to active, paused, or blocked to resume, pause, or block the task.",
|
|
49
|
+
)
|
|
50
|
+
.optional(),
|
|
51
|
+
credential_mode: z
|
|
52
|
+
.enum(["system", "creator"])
|
|
53
|
+
.nullable()
|
|
54
|
+
.describe(
|
|
55
|
+
"Set creator only when the current actor is the task creator and explicitly authorizes future scheduled credential use. Set system to disable delegation.",
|
|
56
|
+
)
|
|
57
|
+
.optional(),
|
|
58
|
+
})
|
|
59
|
+
.strict(),
|
|
65
60
|
outputSchema: scheduleTaskToolResultSchema,
|
|
66
61
|
execute: async (input) => {
|
|
67
62
|
const lookup = await getWritableTask({
|
|
@@ -76,18 +71,24 @@ export function createSlackScheduleUpdateTaskTool(
|
|
|
76
71
|
);
|
|
77
72
|
}
|
|
78
73
|
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
if (
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
74
|
+
const nowMs = context.now?.() ?? Date.now();
|
|
75
|
+
let compiled;
|
|
76
|
+
if (input.schedule) {
|
|
77
|
+
try {
|
|
78
|
+
compiled = compileScheduleIntent({
|
|
79
|
+
defaultTimezone:
|
|
80
|
+
lookup.schedule.timezone || getDefaultScheduleTimezone(),
|
|
81
|
+
intent: input.schedule,
|
|
82
|
+
nowMs,
|
|
83
|
+
});
|
|
84
|
+
} catch (error) {
|
|
85
|
+
if (error instanceof ScheduleIntentError) {
|
|
86
|
+
throwToolInputError(error.message);
|
|
87
|
+
}
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
90
|
}
|
|
91
|
+
const nextRunAtMs = compiled?.nextRunAtMs ?? lookup.nextRunAtMs;
|
|
91
92
|
|
|
92
93
|
const status = normalizeStatus(input.status);
|
|
93
94
|
if (input.status && !status) {
|
|
@@ -95,17 +96,9 @@ export function createSlackScheduleUpdateTaskTool(
|
|
|
95
96
|
}
|
|
96
97
|
if (status === "active" && !nextRunAtMs) {
|
|
97
98
|
throwToolInputError(
|
|
98
|
-
"Active scheduled tasks require
|
|
99
|
+
"Active scheduled tasks require a schedule with a future occurrence.",
|
|
99
100
|
);
|
|
100
101
|
}
|
|
101
|
-
const recurrence = shouldRebuildRecurrence(input)
|
|
102
|
-
? buildRecurrence({
|
|
103
|
-
existing: lookup.schedule.recurrence,
|
|
104
|
-
input,
|
|
105
|
-
nextRunAtMs,
|
|
106
|
-
timezone,
|
|
107
|
-
})
|
|
108
|
-
: lookup.schedule.recurrence;
|
|
109
102
|
const nextStatus = status ?? lookup.status;
|
|
110
103
|
// Another actor changing executable text revokes creator delegation.
|
|
111
104
|
const credentialMode =
|
|
@@ -118,19 +111,14 @@ export function createSlackScheduleUpdateTaskTool(
|
|
|
118
111
|
const next: ScheduledTask = {
|
|
119
112
|
...lookup,
|
|
120
113
|
credentialMode,
|
|
121
|
-
updatedAtMs:
|
|
114
|
+
updatedAtMs: nowMs,
|
|
122
115
|
nextRunAtMs,
|
|
123
|
-
runNowAtMs:
|
|
116
|
+
runNowAtMs:
|
|
117
|
+
nextStatus === "active" && !compiled ? lookup.runNowAtMs : undefined,
|
|
124
118
|
status: nextStatus,
|
|
125
119
|
statusReason:
|
|
126
120
|
nextStatus === "blocked" ? lookup.statusReason : undefined,
|
|
127
|
-
schedule:
|
|
128
|
-
...lookup.schedule,
|
|
129
|
-
description: input.schedule ?? lookup.schedule.description,
|
|
130
|
-
timezone,
|
|
131
|
-
kind: recurrence ? "recurring" : "one_off",
|
|
132
|
-
recurrence,
|
|
133
|
-
},
|
|
121
|
+
schedule: compiled?.schedule ?? lookup.schedule,
|
|
134
122
|
task: input.task ? { text: input.task } : lookup.task,
|
|
135
123
|
};
|
|
136
124
|
|