@sentry/junior-scheduler 0.107.0 → 0.108.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.js +557 -421
- package/dist/schedule-intent.d.ts +61 -0
- package/dist/store.d.ts +1 -0
- 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/schedule-intent.ts +372 -0
- package/src/store.ts +35 -12
- 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/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
|
|