@sentry/junior-scheduler 0.89.0 → 0.90.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.
@@ -0,0 +1,53 @@
1
+ import { definePluginTool } from "@sentry/junior-plugin-api";
2
+ import { z } from "zod";
3
+ import type { ScheduledTask } from "../types";
4
+ import {
5
+ compactTask,
6
+ getWritableTask,
7
+ scheduleTaskToolResult,
8
+ scheduleTaskToolResultSchema,
9
+ schedulerStore,
10
+ throwToolInputError,
11
+ type SchedulerToolContext,
12
+ } from "../tool-support";
13
+
14
+ /** Create a tool that marks an existing scheduled task due immediately. */
15
+ export function createSlackScheduleRunTaskNowTool(
16
+ context: SchedulerToolContext,
17
+ ) {
18
+ return definePluginTool({
19
+ description:
20
+ "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.",
21
+ executionMode: "sequential",
22
+ inputSchema: z.object({
23
+ task_id: z
24
+ .string()
25
+ .min(1)
26
+ .describe(
27
+ "ID of the active task to run now. Must be from this active Slack conversation.",
28
+ ),
29
+ }),
30
+ outputSchema: scheduleTaskToolResultSchema,
31
+ execute: async ({ task_id }) => {
32
+ const lookup = await getWritableTask({ context, taskId: task_id });
33
+ if (lookup.status !== "active") {
34
+ throwToolInputError(
35
+ "Scheduled task must be active before it can be run now. Resume the task first if you want it to run.",
36
+ );
37
+ }
38
+
39
+ const nowMs = Date.now();
40
+ const next: ScheduledTask = {
41
+ ...lookup,
42
+ updatedAtMs: nowMs,
43
+ runNowAtMs: nowMs,
44
+ };
45
+
46
+ await schedulerStore(context).saveTask(next);
47
+ return scheduleTaskToolResult(
48
+ "slackScheduleRunTaskNow",
49
+ compactTask(next),
50
+ );
51
+ },
52
+ });
53
+ }
@@ -0,0 +1,121 @@
1
+ import { definePluginTool } from "@sentry/junior-plugin-api";
2
+ import { z } from "zod";
3
+ import type { ScheduledTask } from "../types";
4
+ import {
5
+ buildRecurrence,
6
+ compactTask,
7
+ getWritableTask,
8
+ isValidTimeZone,
9
+ normalizeStatus,
10
+ parseNextRunAtMs,
11
+ scheduleTaskToolResult,
12
+ scheduleTaskToolResultSchema,
13
+ schedulerStore,
14
+ shouldRebuildRecurrence,
15
+ throwToolInputError,
16
+ validateRecurringFrequencyLimit,
17
+ type SchedulerToolContext,
18
+ } from "../tool-support";
19
+
20
+ /** Create a tool that edits a scheduled task in the active Slack conversation. */
21
+ export function createSlackScheduleUpdateTaskTool(
22
+ context: SchedulerToolContext,
23
+ ) {
24
+ return definePluginTool({
25
+ description:
26
+ "Edit, pause, resume, or reschedule an existing Junior scheduled task in the active Slack conversation. Use only task IDs returned for this conversation. Do not move scheduled tasks across conversations.",
27
+ executionMode: "sequential",
28
+ inputSchema: z.object({
29
+ task_id: z
30
+ .string()
31
+ .min(1)
32
+ .describe(
33
+ "ID of the task to update. Must be from this active Slack conversation.",
34
+ ),
35
+ task: z.string().min(1).max(4000).optional(),
36
+ schedule: z.string().min(1).max(300).optional(),
37
+ timezone: z.string().min(1).max(80).optional(),
38
+ next_run_at: z
39
+ .string()
40
+ .min(1)
41
+ .describe("Exact ISO timestamp when changing the next run time.")
42
+ .optional(),
43
+ recurrence: z
44
+ .enum(["daily", "weekly", "monthly", "yearly"])
45
+ .nullable()
46
+ .describe(
47
+ "Provide only for repeating schedules. Omit for one-time requests. Set to null to convert a recurring task to one-time.",
48
+ )
49
+ .optional(),
50
+ status: z
51
+ .enum(["active", "paused", "blocked"])
52
+ .describe(
53
+ "Set to active, paused, or blocked to resume, pause, or block the task.",
54
+ )
55
+ .optional(),
56
+ }),
57
+ outputSchema: scheduleTaskToolResultSchema,
58
+ execute: async (input) => {
59
+ const lookup = await getWritableTask({
60
+ context,
61
+ taskId: input.task_id,
62
+ });
63
+
64
+ const timezone = input.timezone ?? lookup.schedule.timezone;
65
+ validateRecurringFrequencyLimit(input);
66
+ if (!isValidTimeZone(timezone)) {
67
+ throwToolInputError("timezone must be a valid IANA time zone.");
68
+ }
69
+ const parsedNextRunAtMs = parseNextRunAtMs(input.next_run_at);
70
+ const nextRunAtMs = input.next_run_at
71
+ ? parsedNextRunAtMs
72
+ : lookup.nextRunAtMs;
73
+ if (input.next_run_at && !nextRunAtMs) {
74
+ throwToolInputError("Provide next_run_at as a valid ISO timestamp.");
75
+ }
76
+
77
+ const status = normalizeStatus(input.status);
78
+ if (input.status && !status) {
79
+ throwToolInputError("status must be active, paused, or blocked.");
80
+ }
81
+ if (status === "active" && !nextRunAtMs) {
82
+ throwToolInputError(
83
+ "Active scheduled tasks require next_run_at when no next run is stored.",
84
+ );
85
+ }
86
+ const recurrence = shouldRebuildRecurrence(input)
87
+ ? buildRecurrence({
88
+ existing: lookup.schedule.recurrence,
89
+ input,
90
+ nextRunAtMs,
91
+ timezone,
92
+ })
93
+ : lookup.schedule.recurrence;
94
+ const nextStatus = status ?? lookup.status;
95
+
96
+ const next: ScheduledTask = {
97
+ ...lookup,
98
+ updatedAtMs: Date.now(),
99
+ nextRunAtMs,
100
+ runNowAtMs: nextStatus === "active" ? lookup.runNowAtMs : undefined,
101
+ status: nextStatus,
102
+ statusReason:
103
+ nextStatus === "blocked" ? lookup.statusReason : undefined,
104
+ schedule: {
105
+ ...lookup.schedule,
106
+ description: input.schedule ?? lookup.schedule.description,
107
+ timezone,
108
+ kind: recurrence ? "recurring" : "one_off",
109
+ recurrence,
110
+ },
111
+ task: input.task ? { text: input.task } : lookup.task,
112
+ };
113
+
114
+ await schedulerStore(context).saveTask(next);
115
+ return scheduleTaskToolResult(
116
+ "slackScheduleUpdateTask",
117
+ compactTask(next),
118
+ );
119
+ },
120
+ });
121
+ }