@dexto/tools-scheduler 1.6.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/LICENSE +44 -0
- package/dist/error-codes.cjs +44 -0
- package/dist/error-codes.d.cts +21 -0
- package/dist/error-codes.d.ts +21 -0
- package/dist/error-codes.js +20 -0
- package/dist/errors.cjs +163 -0
- package/dist/errors.d.cts +64 -0
- package/dist/errors.d.ts +64 -0
- package/dist/errors.js +138 -0
- package/dist/executor.cjs +161 -0
- package/dist/executor.d.cts +46 -0
- package/dist/executor.d.ts +46 -0
- package/dist/executor.js +137 -0
- package/dist/index.cjs +89 -0
- package/dist/index.d.cts +19 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +56 -0
- package/dist/manager.cjs +461 -0
- package/dist/manager.d.cts +113 -0
- package/dist/manager.d.ts +113 -0
- package/dist/manager.js +430 -0
- package/dist/schemas.cjs +138 -0
- package/dist/schemas.d.cts +263 -0
- package/dist/schemas.d.ts +263 -0
- package/dist/schemas.js +105 -0
- package/dist/storage.cjs +249 -0
- package/dist/storage.d.cts +62 -0
- package/dist/storage.d.ts +62 -0
- package/dist/storage.js +225 -0
- package/dist/tool-provider.cjs +239 -0
- package/dist/tool-provider.d.cts +34 -0
- package/dist/tool-provider.d.ts +34 -0
- package/dist/tool-provider.js +212 -0
- package/dist/tool-types.cjs +16 -0
- package/dist/tool-types.d.cts +9 -0
- package/dist/tool-types.d.ts +9 -0
- package/dist/tool-types.js +0 -0
- package/dist/tools/create-schedule.cjs +75 -0
- package/dist/tools/create-schedule.d.cts +14 -0
- package/dist/tools/create-schedule.d.ts +14 -0
- package/dist/tools/create-schedule.js +51 -0
- package/dist/tools/delete-schedule.cjs +45 -0
- package/dist/tools/delete-schedule.d.cts +14 -0
- package/dist/tools/delete-schedule.d.ts +14 -0
- package/dist/tools/delete-schedule.js +21 -0
- package/dist/tools/get-history.cjs +63 -0
- package/dist/tools/get-history.d.cts +14 -0
- package/dist/tools/get-history.d.ts +14 -0
- package/dist/tools/get-history.js +39 -0
- package/dist/tools/get-schedule.cjs +68 -0
- package/dist/tools/get-schedule.d.cts +14 -0
- package/dist/tools/get-schedule.d.ts +14 -0
- package/dist/tools/get-schedule.js +44 -0
- package/dist/tools/list-schedules.cjs +67 -0
- package/dist/tools/list-schedules.d.cts +14 -0
- package/dist/tools/list-schedules.d.ts +14 -0
- package/dist/tools/list-schedules.js +43 -0
- package/dist/tools/trigger-schedule.cjs +56 -0
- package/dist/tools/trigger-schedule.d.cts +14 -0
- package/dist/tools/trigger-schedule.d.ts +14 -0
- package/dist/tools/trigger-schedule.js +32 -0
- package/dist/tools/update-schedule.cjs +53 -0
- package/dist/tools/update-schedule.d.cts +14 -0
- package/dist/tools/update-schedule.d.ts +14 -0
- package/dist/tools/update-schedule.js +29 -0
- package/dist/types.cjs +16 -0
- package/dist/types.d.cts +72 -0
- package/dist/types.d.ts +72 -0
- package/dist/types.js +0 -0
- package/package.json +41 -0
package/dist/manager.cjs
ADDED
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var manager_exports = {};
|
|
30
|
+
__export(manager_exports, {
|
|
31
|
+
SchedulerManager: () => SchedulerManager
|
|
32
|
+
});
|
|
33
|
+
module.exports = __toCommonJS(manager_exports);
|
|
34
|
+
var import_node_cron = __toESM(require("node-cron"), 1);
|
|
35
|
+
var cronParser = __toESM(require("cron-parser"), 1);
|
|
36
|
+
var import_crypto = require("crypto");
|
|
37
|
+
var import_core = require("@dexto/core");
|
|
38
|
+
var import_schemas = require("./schemas.js");
|
|
39
|
+
var import_storage = require("./storage.js");
|
|
40
|
+
var import_executor = require("./executor.js");
|
|
41
|
+
var import_errors = require("./errors.js");
|
|
42
|
+
class SchedulerManager {
|
|
43
|
+
constructor(storageManager, config, logger) {
|
|
44
|
+
this.config = config;
|
|
45
|
+
this.logger = logger;
|
|
46
|
+
this.storage = new import_storage.ScheduleStorage(storageManager, config.maxExecutionHistory, logger);
|
|
47
|
+
this.executor = new import_executor.ScheduleExecutor(config.executionTimeout, logger);
|
|
48
|
+
}
|
|
49
|
+
cronTasks = /* @__PURE__ */ new Map();
|
|
50
|
+
storage;
|
|
51
|
+
executor;
|
|
52
|
+
executionChain = Promise.resolve();
|
|
53
|
+
initialized = false;
|
|
54
|
+
started = false;
|
|
55
|
+
cachedSchedules;
|
|
56
|
+
/**
|
|
57
|
+
* Set the executor function (called to run the agent with a prompt)
|
|
58
|
+
*/
|
|
59
|
+
setExecutor(fn) {
|
|
60
|
+
this.executor.setExecutor(fn);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Initialize the scheduler (load schedules from storage)
|
|
64
|
+
*/
|
|
65
|
+
async init() {
|
|
66
|
+
if (this.initialized) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
const storedSchedules = await this.storage.listSchedules();
|
|
71
|
+
this.cachedSchedules = storedSchedules;
|
|
72
|
+
this.logger.info(`Loaded ${storedSchedules.length} schedules from storage`);
|
|
73
|
+
this.initialized = true;
|
|
74
|
+
} catch (error) {
|
|
75
|
+
throw import_errors.SchedulerError.invalidConfig(
|
|
76
|
+
`Failed to initialize scheduler: ${error instanceof Error ? error.message : String(error)}`
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Start the scheduler (begin executing schedules)
|
|
82
|
+
*/
|
|
83
|
+
async start() {
|
|
84
|
+
if (!this.initialized) {
|
|
85
|
+
await this.init();
|
|
86
|
+
}
|
|
87
|
+
if (this.started) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
const schedules = this.cachedSchedules ?? await this.storage.listSchedules();
|
|
92
|
+
this.cachedSchedules = void 0;
|
|
93
|
+
for (const schedule of schedules) {
|
|
94
|
+
if (schedule.enabled) {
|
|
95
|
+
await this.scheduleTask(schedule);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
this.started = true;
|
|
99
|
+
this.logger.info(`Scheduler started with ${this.cronTasks.size} active schedules`);
|
|
100
|
+
} catch (error) {
|
|
101
|
+
throw import_errors.SchedulerError.invalidConfig(
|
|
102
|
+
`Failed to start scheduler: ${error instanceof Error ? error.message : String(error)}`
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Stop the scheduler (stop all running tasks)
|
|
108
|
+
*/
|
|
109
|
+
async stop() {
|
|
110
|
+
if (!this.started) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
for (const task of this.cronTasks.values()) {
|
|
114
|
+
task.stop();
|
|
115
|
+
}
|
|
116
|
+
this.cronTasks.clear();
|
|
117
|
+
this.started = false;
|
|
118
|
+
this.logger.info("Scheduler stopped");
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Create a new schedule
|
|
122
|
+
*
|
|
123
|
+
* @param input - Schedule creation input
|
|
124
|
+
* @param currentSessionId - The current session ID (used for 'inherit' mode)
|
|
125
|
+
*/
|
|
126
|
+
async createSchedule(input, currentSessionId) {
|
|
127
|
+
const validated = import_schemas.CreateScheduleInputSchema.parse(input);
|
|
128
|
+
const existingSchedules = await this.storage.listSchedules();
|
|
129
|
+
if (existingSchedules.length >= this.config.maxSchedules) {
|
|
130
|
+
throw import_errors.SchedulerError.limitReached(existingSchedules.length, this.config.maxSchedules);
|
|
131
|
+
}
|
|
132
|
+
if (!import_node_cron.default.validate(validated.cronExpression)) {
|
|
133
|
+
throw import_errors.SchedulerError.invalidCron(validated.cronExpression, "Invalid cron format");
|
|
134
|
+
}
|
|
135
|
+
let resolvedSessionId;
|
|
136
|
+
const sessionMode = validated.sessionMode ?? "ephemeral";
|
|
137
|
+
if (sessionMode === "inherit") {
|
|
138
|
+
if (!currentSessionId) {
|
|
139
|
+
throw import_errors.SchedulerError.invalidInput(
|
|
140
|
+
'sessionMode "inherit" requires a current session context'
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
resolvedSessionId = currentSessionId;
|
|
144
|
+
} else if (sessionMode === "fixed") {
|
|
145
|
+
resolvedSessionId = validated.sessionId;
|
|
146
|
+
}
|
|
147
|
+
const now = Date.now();
|
|
148
|
+
const taskMetadata = {
|
|
149
|
+
...validated.metadata
|
|
150
|
+
};
|
|
151
|
+
if (validated.targetAgentId) {
|
|
152
|
+
taskMetadata.__os_targetAgentId = validated.targetAgentId;
|
|
153
|
+
}
|
|
154
|
+
const workspacePath = validated.workspacePath && validated.workspacePath.trim().length > 0 ? validated.workspacePath : void 0;
|
|
155
|
+
const schedule = {
|
|
156
|
+
id: (0, import_crypto.randomUUID)(),
|
|
157
|
+
name: validated.name,
|
|
158
|
+
cronExpression: validated.cronExpression,
|
|
159
|
+
timezone: validated.timezone || this.config.timezone,
|
|
160
|
+
enabled: validated.enabled,
|
|
161
|
+
task: {
|
|
162
|
+
instruction: validated.instruction,
|
|
163
|
+
...Object.keys(taskMetadata).length > 0 && { metadata: taskMetadata }
|
|
164
|
+
},
|
|
165
|
+
sessionMode,
|
|
166
|
+
...resolvedSessionId && { sessionId: resolvedSessionId },
|
|
167
|
+
...workspacePath ? { workspacePath } : {},
|
|
168
|
+
createdAt: now,
|
|
169
|
+
updatedAt: now,
|
|
170
|
+
runCount: 0,
|
|
171
|
+
successCount: 0,
|
|
172
|
+
failureCount: 0
|
|
173
|
+
};
|
|
174
|
+
const nextRun = this.calculateNextRun(schedule);
|
|
175
|
+
if (nextRun !== void 0) {
|
|
176
|
+
schedule.nextRunAt = nextRun;
|
|
177
|
+
}
|
|
178
|
+
await this.storage.saveSchedule(schedule);
|
|
179
|
+
if (schedule.enabled && this.started) {
|
|
180
|
+
await this.scheduleTask(schedule);
|
|
181
|
+
}
|
|
182
|
+
this.logger.info(`Schedule created: ${schedule.name}`, {
|
|
183
|
+
scheduleId: schedule.id,
|
|
184
|
+
sessionMode,
|
|
185
|
+
cronExpression: schedule.cronExpression
|
|
186
|
+
});
|
|
187
|
+
return schedule;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Update an existing schedule
|
|
191
|
+
*
|
|
192
|
+
* @param scheduleId - The schedule ID to update
|
|
193
|
+
* @param updates - The updates to apply
|
|
194
|
+
* @param currentSessionId - The current session ID (used if changing to 'inherit' mode)
|
|
195
|
+
*/
|
|
196
|
+
async updateSchedule(scheduleId, updates, currentSessionId) {
|
|
197
|
+
const validated = import_schemas.UpdateScheduleFieldsOnlySchema.parse(updates);
|
|
198
|
+
const existing = await this.storage.loadSchedule(scheduleId);
|
|
199
|
+
if (!existing) {
|
|
200
|
+
throw import_errors.SchedulerError.notFound(scheduleId);
|
|
201
|
+
}
|
|
202
|
+
if (validated.cronExpression && !import_node_cron.default.validate(validated.cronExpression)) {
|
|
203
|
+
throw import_errors.SchedulerError.invalidCron(validated.cronExpression, "Invalid cron format");
|
|
204
|
+
}
|
|
205
|
+
let updatedSessionId = existing.sessionId;
|
|
206
|
+
const newSessionMode = validated.sessionMode ?? existing.sessionMode;
|
|
207
|
+
if (validated.sessionMode !== void 0) {
|
|
208
|
+
if (newSessionMode === "inherit") {
|
|
209
|
+
if (!currentSessionId) {
|
|
210
|
+
throw import_errors.SchedulerError.invalidInput(
|
|
211
|
+
'sessionMode "inherit" requires a current session context'
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
updatedSessionId = currentSessionId;
|
|
215
|
+
} else if (newSessionMode === "fixed") {
|
|
216
|
+
updatedSessionId = validated.sessionId;
|
|
217
|
+
} else {
|
|
218
|
+
updatedSessionId = void 0;
|
|
219
|
+
}
|
|
220
|
+
} else if (validated.sessionId !== void 0) {
|
|
221
|
+
updatedSessionId = validated.sessionId;
|
|
222
|
+
}
|
|
223
|
+
const existingTaskMetadata = existing.task.metadata ?? {};
|
|
224
|
+
const updatedTaskMetadata = validated.metadata !== void 0 ? { ...validated.metadata } : { ...existingTaskMetadata };
|
|
225
|
+
for (const [key, value] of Object.entries(existingTaskMetadata)) {
|
|
226
|
+
if (key.startsWith("__os_")) {
|
|
227
|
+
updatedTaskMetadata[key] = value;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (validated.targetAgentId !== void 0) {
|
|
231
|
+
if (validated.targetAgentId) {
|
|
232
|
+
updatedTaskMetadata.__os_targetAgentId = validated.targetAgentId;
|
|
233
|
+
} else {
|
|
234
|
+
delete updatedTaskMetadata.__os_targetAgentId;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
const resolvedWorkspacePath = validated.workspacePath === void 0 ? existing.workspacePath : validated.workspacePath && validated.workspacePath.trim().length > 0 ? validated.workspacePath : void 0;
|
|
238
|
+
const updated = {
|
|
239
|
+
...existing,
|
|
240
|
+
...validated.name !== void 0 && { name: validated.name },
|
|
241
|
+
...validated.cronExpression !== void 0 && {
|
|
242
|
+
cronExpression: validated.cronExpression
|
|
243
|
+
},
|
|
244
|
+
...validated.timezone !== void 0 && { timezone: validated.timezone },
|
|
245
|
+
...validated.enabled !== void 0 && { enabled: validated.enabled },
|
|
246
|
+
task: {
|
|
247
|
+
...existing.task,
|
|
248
|
+
...validated.instruction !== void 0 && { instruction: validated.instruction },
|
|
249
|
+
...Object.keys(updatedTaskMetadata).length > 0 ? { metadata: updatedTaskMetadata } : {}
|
|
250
|
+
},
|
|
251
|
+
sessionMode: newSessionMode,
|
|
252
|
+
...updatedSessionId !== void 0 ? { sessionId: updatedSessionId } : {},
|
|
253
|
+
updatedAt: Date.now()
|
|
254
|
+
};
|
|
255
|
+
if (resolvedWorkspacePath) {
|
|
256
|
+
updated.workspacePath = resolvedWorkspacePath;
|
|
257
|
+
} else {
|
|
258
|
+
delete updated.workspacePath;
|
|
259
|
+
}
|
|
260
|
+
if (newSessionMode === "ephemeral" || newSessionMode === "dedicated") {
|
|
261
|
+
delete updated.sessionId;
|
|
262
|
+
}
|
|
263
|
+
const nextRunTime = this.calculateNextRun(updated);
|
|
264
|
+
if (nextRunTime !== void 0) {
|
|
265
|
+
updated.nextRunAt = nextRunTime;
|
|
266
|
+
}
|
|
267
|
+
await this.storage.saveSchedule(updated);
|
|
268
|
+
if (this.started) {
|
|
269
|
+
this.unscheduleTask(scheduleId);
|
|
270
|
+
if (updated.enabled) {
|
|
271
|
+
await this.scheduleTask(updated);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
this.logger.info(`Schedule updated: ${updated.name}`, {
|
|
275
|
+
scheduleId,
|
|
276
|
+
sessionMode: newSessionMode
|
|
277
|
+
});
|
|
278
|
+
return updated;
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Delete a schedule
|
|
282
|
+
*/
|
|
283
|
+
async deleteSchedule(scheduleId) {
|
|
284
|
+
const schedule = await this.storage.loadSchedule(scheduleId);
|
|
285
|
+
if (!schedule) {
|
|
286
|
+
throw import_errors.SchedulerError.notFound(scheduleId);
|
|
287
|
+
}
|
|
288
|
+
if (this.started) {
|
|
289
|
+
this.unscheduleTask(scheduleId);
|
|
290
|
+
}
|
|
291
|
+
await this.storage.deleteSchedule(scheduleId);
|
|
292
|
+
this.logger.info(`Schedule deleted: ${schedule.name}`, { scheduleId });
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Get a schedule by ID
|
|
296
|
+
*/
|
|
297
|
+
async getSchedule(scheduleId) {
|
|
298
|
+
return this.storage.loadSchedule(scheduleId);
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* List schedules with optional filters
|
|
302
|
+
*/
|
|
303
|
+
async listSchedules(filters) {
|
|
304
|
+
const schedules = await this.storage.listSchedules();
|
|
305
|
+
if (!filters) {
|
|
306
|
+
return schedules;
|
|
307
|
+
}
|
|
308
|
+
return schedules.filter((schedule) => {
|
|
309
|
+
if (filters.enabled !== void 0 && schedule.enabled !== filters.enabled) {
|
|
310
|
+
return false;
|
|
311
|
+
}
|
|
312
|
+
return true;
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Trigger a schedule immediately (manual execution)
|
|
317
|
+
*/
|
|
318
|
+
async triggerScheduleNow(scheduleId) {
|
|
319
|
+
const schedule = await this.storage.loadSchedule(scheduleId);
|
|
320
|
+
if (!schedule) {
|
|
321
|
+
throw import_errors.SchedulerError.notFound(scheduleId);
|
|
322
|
+
}
|
|
323
|
+
this.logger.info(`Manually triggering schedule: ${schedule.name}`, { scheduleId });
|
|
324
|
+
return this.executeSchedule(schedule);
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Get execution history for a schedule
|
|
328
|
+
*/
|
|
329
|
+
async getExecutionHistory(scheduleId, limit) {
|
|
330
|
+
return this.storage.getExecutionLogs(scheduleId, limit);
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Get scheduler status
|
|
334
|
+
*/
|
|
335
|
+
getStatus() {
|
|
336
|
+
return {
|
|
337
|
+
initialized: this.initialized,
|
|
338
|
+
started: this.started,
|
|
339
|
+
activeSchedules: this.cronTasks.size
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Schedule a task using cron
|
|
344
|
+
*/
|
|
345
|
+
async scheduleTask(schedule) {
|
|
346
|
+
this.unscheduleTask(schedule.id);
|
|
347
|
+
if (!schedule.enabled) {
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
try {
|
|
351
|
+
const task = import_node_cron.default.schedule(
|
|
352
|
+
schedule.cronExpression,
|
|
353
|
+
() => {
|
|
354
|
+
this.executeSchedule(schedule).catch((error) => {
|
|
355
|
+
this.logger.error(
|
|
356
|
+
`Failed to execute schedule ${schedule.id}: ${error instanceof Error ? error.message : String(error)}`
|
|
357
|
+
);
|
|
358
|
+
});
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
timezone: schedule.timezone,
|
|
362
|
+
name: schedule.id
|
|
363
|
+
}
|
|
364
|
+
);
|
|
365
|
+
task.start();
|
|
366
|
+
this.cronTasks.set(schedule.id, task);
|
|
367
|
+
this.logger.debug(`Schedule task registered: ${schedule.name}`, {
|
|
368
|
+
scheduleId: schedule.id,
|
|
369
|
+
cronExpression: schedule.cronExpression
|
|
370
|
+
});
|
|
371
|
+
} catch (error) {
|
|
372
|
+
throw import_errors.SchedulerError.createFailed(
|
|
373
|
+
`Failed to schedule task: ${error instanceof Error ? error.message : String(error)}`
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Unschedule a task
|
|
379
|
+
*/
|
|
380
|
+
unscheduleTask(scheduleId) {
|
|
381
|
+
const task = this.cronTasks.get(scheduleId);
|
|
382
|
+
if (task) {
|
|
383
|
+
task.stop();
|
|
384
|
+
this.cronTasks.delete(scheduleId);
|
|
385
|
+
this.logger.debug(`Schedule task unregistered: ${scheduleId}`);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Execute a schedule
|
|
390
|
+
*/
|
|
391
|
+
async executeSchedule(schedule) {
|
|
392
|
+
return await this.queueExecution(() => this.executeScheduleInternal(schedule));
|
|
393
|
+
}
|
|
394
|
+
async executeScheduleInternal(schedule) {
|
|
395
|
+
try {
|
|
396
|
+
const current = await this.storage.loadSchedule(schedule.id);
|
|
397
|
+
if (!current) {
|
|
398
|
+
throw import_errors.SchedulerError.notFound(schedule.id);
|
|
399
|
+
}
|
|
400
|
+
const log = await this.executor.execute(current);
|
|
401
|
+
await this.storage.saveExecutionLog(log);
|
|
402
|
+
const updates = {
|
|
403
|
+
lastRunAt: log.triggeredAt,
|
|
404
|
+
runCount: current.runCount + 1,
|
|
405
|
+
updatedAt: Date.now()
|
|
406
|
+
};
|
|
407
|
+
if (log.status === "success") {
|
|
408
|
+
updates.successCount = current.successCount + 1;
|
|
409
|
+
} else if (log.status === "failed" || log.status === "timeout") {
|
|
410
|
+
updates.failureCount = current.failureCount + 1;
|
|
411
|
+
if (log.error) {
|
|
412
|
+
updates.lastError = log.error;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
const nextRun = this.calculateNextRun(current);
|
|
416
|
+
if (nextRun !== void 0) {
|
|
417
|
+
updates.nextRunAt = nextRun;
|
|
418
|
+
}
|
|
419
|
+
const updated = { ...current, ...updates };
|
|
420
|
+
await this.storage.saveSchedule(updated);
|
|
421
|
+
return log;
|
|
422
|
+
} catch (error) {
|
|
423
|
+
if (error instanceof import_core.DextoRuntimeError) {
|
|
424
|
+
throw error;
|
|
425
|
+
}
|
|
426
|
+
throw import_errors.SchedulerError.executionFailed(
|
|
427
|
+
schedule.id,
|
|
428
|
+
error instanceof Error ? error.message : String(error)
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
queueExecution(fn) {
|
|
433
|
+
const run = this.executionChain.then(fn);
|
|
434
|
+
this.executionChain = run.then(
|
|
435
|
+
() => void 0,
|
|
436
|
+
() => void 0
|
|
437
|
+
);
|
|
438
|
+
return run;
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Calculate next run time for a schedule
|
|
442
|
+
*/
|
|
443
|
+
calculateNextRun(schedule) {
|
|
444
|
+
try {
|
|
445
|
+
const interval = cronParser.parseExpression(schedule.cronExpression, {
|
|
446
|
+
tz: schedule.timezone
|
|
447
|
+
});
|
|
448
|
+
const next = interval.next();
|
|
449
|
+
return next.toDate().getTime();
|
|
450
|
+
} catch (error) {
|
|
451
|
+
this.logger.error(
|
|
452
|
+
`Failed to calculate next run: ${error instanceof Error ? error.message : String(error)}`
|
|
453
|
+
);
|
|
454
|
+
return void 0;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
459
|
+
0 && (module.exports = {
|
|
460
|
+
SchedulerManager
|
|
461
|
+
});
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { StorageManager, Logger } from '@dexto/core';
|
|
2
|
+
import { SchedulerToolsConfig, CreateScheduleInput, UpdateScheduleInput } from './schemas.cjs';
|
|
3
|
+
import { ScheduleExecutorFn, Schedule, ScheduleFilters, ExecutionLog } from './types.cjs';
|
|
4
|
+
import 'zod';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Scheduler Manager - Core service for internal task scheduling
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Scheduler Manager
|
|
12
|
+
*
|
|
13
|
+
* Core service for internal task scheduling. Manages cron-based schedules
|
|
14
|
+
* that execute tasks via the agent.
|
|
15
|
+
*
|
|
16
|
+
* Key features:
|
|
17
|
+
* - Cron-based scheduling with timezone support
|
|
18
|
+
* - Persistent schedules (survive agent restarts)
|
|
19
|
+
* - Direct agent execution (zero HTTP overhead)
|
|
20
|
+
* - Logging-based observability
|
|
21
|
+
* - Self-configurable via tools
|
|
22
|
+
*/
|
|
23
|
+
declare class SchedulerManager {
|
|
24
|
+
private config;
|
|
25
|
+
private logger;
|
|
26
|
+
private cronTasks;
|
|
27
|
+
private storage;
|
|
28
|
+
private executor;
|
|
29
|
+
private executionChain;
|
|
30
|
+
private initialized;
|
|
31
|
+
private started;
|
|
32
|
+
private cachedSchedules;
|
|
33
|
+
constructor(storageManager: StorageManager, config: SchedulerToolsConfig, logger: Logger);
|
|
34
|
+
/**
|
|
35
|
+
* Set the executor function (called to run the agent with a prompt)
|
|
36
|
+
*/
|
|
37
|
+
setExecutor(fn: ScheduleExecutorFn): void;
|
|
38
|
+
/**
|
|
39
|
+
* Initialize the scheduler (load schedules from storage)
|
|
40
|
+
*/
|
|
41
|
+
init(): Promise<void>;
|
|
42
|
+
/**
|
|
43
|
+
* Start the scheduler (begin executing schedules)
|
|
44
|
+
*/
|
|
45
|
+
start(): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* Stop the scheduler (stop all running tasks)
|
|
48
|
+
*/
|
|
49
|
+
stop(): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Create a new schedule
|
|
52
|
+
*
|
|
53
|
+
* @param input - Schedule creation input
|
|
54
|
+
* @param currentSessionId - The current session ID (used for 'inherit' mode)
|
|
55
|
+
*/
|
|
56
|
+
createSchedule(input: CreateScheduleInput, currentSessionId?: string): Promise<Schedule>;
|
|
57
|
+
/**
|
|
58
|
+
* Update an existing schedule
|
|
59
|
+
*
|
|
60
|
+
* @param scheduleId - The schedule ID to update
|
|
61
|
+
* @param updates - The updates to apply
|
|
62
|
+
* @param currentSessionId - The current session ID (used if changing to 'inherit' mode)
|
|
63
|
+
*/
|
|
64
|
+
updateSchedule(scheduleId: string, updates: Omit<UpdateScheduleInput, 'scheduleId'>, currentSessionId?: string): Promise<Schedule>;
|
|
65
|
+
/**
|
|
66
|
+
* Delete a schedule
|
|
67
|
+
*/
|
|
68
|
+
deleteSchedule(scheduleId: string): Promise<void>;
|
|
69
|
+
/**
|
|
70
|
+
* Get a schedule by ID
|
|
71
|
+
*/
|
|
72
|
+
getSchedule(scheduleId: string): Promise<Schedule | null>;
|
|
73
|
+
/**
|
|
74
|
+
* List schedules with optional filters
|
|
75
|
+
*/
|
|
76
|
+
listSchedules(filters?: ScheduleFilters): Promise<Schedule[]>;
|
|
77
|
+
/**
|
|
78
|
+
* Trigger a schedule immediately (manual execution)
|
|
79
|
+
*/
|
|
80
|
+
triggerScheduleNow(scheduleId: string): Promise<ExecutionLog>;
|
|
81
|
+
/**
|
|
82
|
+
* Get execution history for a schedule
|
|
83
|
+
*/
|
|
84
|
+
getExecutionHistory(scheduleId: string, limit?: number): Promise<ExecutionLog[]>;
|
|
85
|
+
/**
|
|
86
|
+
* Get scheduler status
|
|
87
|
+
*/
|
|
88
|
+
getStatus(): {
|
|
89
|
+
initialized: boolean;
|
|
90
|
+
started: boolean;
|
|
91
|
+
activeSchedules: number;
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* Schedule a task using cron
|
|
95
|
+
*/
|
|
96
|
+
private scheduleTask;
|
|
97
|
+
/**
|
|
98
|
+
* Unschedule a task
|
|
99
|
+
*/
|
|
100
|
+
private unscheduleTask;
|
|
101
|
+
/**
|
|
102
|
+
* Execute a schedule
|
|
103
|
+
*/
|
|
104
|
+
private executeSchedule;
|
|
105
|
+
private executeScheduleInternal;
|
|
106
|
+
private queueExecution;
|
|
107
|
+
/**
|
|
108
|
+
* Calculate next run time for a schedule
|
|
109
|
+
*/
|
|
110
|
+
private calculateNextRun;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export { SchedulerManager };
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { StorageManager, Logger } from '@dexto/core';
|
|
2
|
+
import { SchedulerToolsConfig, CreateScheduleInput, UpdateScheduleInput } from './schemas.js';
|
|
3
|
+
import { ScheduleExecutorFn, Schedule, ScheduleFilters, ExecutionLog } from './types.js';
|
|
4
|
+
import 'zod';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Scheduler Manager - Core service for internal task scheduling
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Scheduler Manager
|
|
12
|
+
*
|
|
13
|
+
* Core service for internal task scheduling. Manages cron-based schedules
|
|
14
|
+
* that execute tasks via the agent.
|
|
15
|
+
*
|
|
16
|
+
* Key features:
|
|
17
|
+
* - Cron-based scheduling with timezone support
|
|
18
|
+
* - Persistent schedules (survive agent restarts)
|
|
19
|
+
* - Direct agent execution (zero HTTP overhead)
|
|
20
|
+
* - Logging-based observability
|
|
21
|
+
* - Self-configurable via tools
|
|
22
|
+
*/
|
|
23
|
+
declare class SchedulerManager {
|
|
24
|
+
private config;
|
|
25
|
+
private logger;
|
|
26
|
+
private cronTasks;
|
|
27
|
+
private storage;
|
|
28
|
+
private executor;
|
|
29
|
+
private executionChain;
|
|
30
|
+
private initialized;
|
|
31
|
+
private started;
|
|
32
|
+
private cachedSchedules;
|
|
33
|
+
constructor(storageManager: StorageManager, config: SchedulerToolsConfig, logger: Logger);
|
|
34
|
+
/**
|
|
35
|
+
* Set the executor function (called to run the agent with a prompt)
|
|
36
|
+
*/
|
|
37
|
+
setExecutor(fn: ScheduleExecutorFn): void;
|
|
38
|
+
/**
|
|
39
|
+
* Initialize the scheduler (load schedules from storage)
|
|
40
|
+
*/
|
|
41
|
+
init(): Promise<void>;
|
|
42
|
+
/**
|
|
43
|
+
* Start the scheduler (begin executing schedules)
|
|
44
|
+
*/
|
|
45
|
+
start(): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* Stop the scheduler (stop all running tasks)
|
|
48
|
+
*/
|
|
49
|
+
stop(): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Create a new schedule
|
|
52
|
+
*
|
|
53
|
+
* @param input - Schedule creation input
|
|
54
|
+
* @param currentSessionId - The current session ID (used for 'inherit' mode)
|
|
55
|
+
*/
|
|
56
|
+
createSchedule(input: CreateScheduleInput, currentSessionId?: string): Promise<Schedule>;
|
|
57
|
+
/**
|
|
58
|
+
* Update an existing schedule
|
|
59
|
+
*
|
|
60
|
+
* @param scheduleId - The schedule ID to update
|
|
61
|
+
* @param updates - The updates to apply
|
|
62
|
+
* @param currentSessionId - The current session ID (used if changing to 'inherit' mode)
|
|
63
|
+
*/
|
|
64
|
+
updateSchedule(scheduleId: string, updates: Omit<UpdateScheduleInput, 'scheduleId'>, currentSessionId?: string): Promise<Schedule>;
|
|
65
|
+
/**
|
|
66
|
+
* Delete a schedule
|
|
67
|
+
*/
|
|
68
|
+
deleteSchedule(scheduleId: string): Promise<void>;
|
|
69
|
+
/**
|
|
70
|
+
* Get a schedule by ID
|
|
71
|
+
*/
|
|
72
|
+
getSchedule(scheduleId: string): Promise<Schedule | null>;
|
|
73
|
+
/**
|
|
74
|
+
* List schedules with optional filters
|
|
75
|
+
*/
|
|
76
|
+
listSchedules(filters?: ScheduleFilters): Promise<Schedule[]>;
|
|
77
|
+
/**
|
|
78
|
+
* Trigger a schedule immediately (manual execution)
|
|
79
|
+
*/
|
|
80
|
+
triggerScheduleNow(scheduleId: string): Promise<ExecutionLog>;
|
|
81
|
+
/**
|
|
82
|
+
* Get execution history for a schedule
|
|
83
|
+
*/
|
|
84
|
+
getExecutionHistory(scheduleId: string, limit?: number): Promise<ExecutionLog[]>;
|
|
85
|
+
/**
|
|
86
|
+
* Get scheduler status
|
|
87
|
+
*/
|
|
88
|
+
getStatus(): {
|
|
89
|
+
initialized: boolean;
|
|
90
|
+
started: boolean;
|
|
91
|
+
activeSchedules: number;
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* Schedule a task using cron
|
|
95
|
+
*/
|
|
96
|
+
private scheduleTask;
|
|
97
|
+
/**
|
|
98
|
+
* Unschedule a task
|
|
99
|
+
*/
|
|
100
|
+
private unscheduleTask;
|
|
101
|
+
/**
|
|
102
|
+
* Execute a schedule
|
|
103
|
+
*/
|
|
104
|
+
private executeSchedule;
|
|
105
|
+
private executeScheduleInternal;
|
|
106
|
+
private queueExecution;
|
|
107
|
+
/**
|
|
108
|
+
* Calculate next run time for a schedule
|
|
109
|
+
*/
|
|
110
|
+
private calculateNextRun;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export { SchedulerManager };
|