@mulmoclaude/core 0.11.0 → 0.12.1
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/assets/helps/index.md +1 -0
- package/assets/helps/remote-host.md +122 -0
- package/dist/remote-host/index.cjs +2 -2
- package/dist/remote-host/index.cjs.map +1 -1
- package/dist/remote-host/index.d.ts +1 -1
- package/dist/remote-host/index.js +2 -2
- package/dist/remote-host/index.js.map +1 -1
- package/dist/remote-host/server/hostRunner.d.ts +1 -1
- package/dist/remote-host/server/index.cjs +22 -8
- package/dist/remote-host/server/index.cjs.map +1 -1
- package/dist/remote-host/server/index.js +22 -8
- package/dist/remote-host/server/index.js.map +1 -1
- package/dist/remote-host/server/lifecycle.d.ts +1 -1
- package/dist/scheduler/adapter.d.ts +24 -1
- package/dist/scheduler/index.cjs +139 -74
- package/dist/scheduler/index.cjs.map +1 -1
- package/dist/scheduler/index.d.ts +2 -1
- package/dist/scheduler/index.js +135 -78
- package/dist/scheduler/index.js.map +1 -1
- package/dist/scheduler/task-manager.d.ts +14 -6
- package/dist/whisper/client-helpers.d.ts +22 -0
- package/dist/whisper/client.cjs +50 -18
- package/dist/whisper/client.cjs.map +1 -1
- package/dist/whisper/client.js +50 -18
- package/dist/whisper/client.js.map +1 -1
- package/dist/whisper/index.cjs +43 -21
- package/dist/whisper/index.cjs.map +1 -1
- package/dist/whisper/index.js +43 -21
- package/dist/whisper/index.js.map +1 -1
- package/dist/whisper/models.d.ts +6 -0
- package/dist/whisper/sidecar-helpers.d.ts +7 -0
- package/package.json +2 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TaskExecutionState, TaskLogEntry, MISSED_RUN_POLICIES } from '@receptron/task-scheduler';
|
|
1
|
+
import { TaskExecutionState, TaskLogEntry, TaskTrigger, MISSED_RUN_POLICIES } from '@receptron/task-scheduler';
|
|
2
2
|
import { ITaskManager, TaskDefinition, SchedulerLogger } from './task-manager.js';
|
|
3
3
|
export interface SchedulerConfig {
|
|
4
4
|
/** Absolute workspace root — state.json + logs hang off it. */
|
|
@@ -44,5 +44,28 @@ export declare function getSchedulerTasks(): {
|
|
|
44
44
|
missedRunPolicy: string;
|
|
45
45
|
state: TaskExecutionState;
|
|
46
46
|
}[];
|
|
47
|
+
/** Read the persisted execution state for any task id (system, user, or
|
|
48
|
+
* skill). Returns an empty state when the id has never run — used by the API
|
|
49
|
+
* route to attach last-run / next-run / history state to user + skill tasks
|
|
50
|
+
* it lists from other sources. */
|
|
51
|
+
export declare function getSchedulerTaskState(taskId: string): TaskExecutionState;
|
|
52
|
+
/** Record a run of an EXTERNAL (skill / user) task — one registered directly
|
|
53
|
+
* on the task-manager rather than through `initScheduler`. Persists a log
|
|
54
|
+
* entry + updates state so these tasks get the same history / last-run /
|
|
55
|
+
* next-run as system tasks. Because they are fire-and-forget (`startChat`
|
|
56
|
+
* spawns an async chat), `errorMessage` reflects whether the *dispatch*
|
|
57
|
+
* succeeded, not the chat's eventual outcome; `chatSessionId` links the run
|
|
58
|
+
* to the spawned session. */
|
|
59
|
+
export declare function recordExternalRun(params: {
|
|
60
|
+
id: string;
|
|
61
|
+
name: string;
|
|
62
|
+
schedule: TaskDefinition["schedule"];
|
|
63
|
+
scheduledFor: string;
|
|
64
|
+
startedAt: string;
|
|
65
|
+
durationMs: number;
|
|
66
|
+
trigger: TaskTrigger;
|
|
67
|
+
errorMessage: string | null;
|
|
68
|
+
chatSessionId?: string;
|
|
69
|
+
}): Promise<void>;
|
|
47
70
|
/** Test-only: clear config + in-memory state. */
|
|
48
71
|
export declare function resetSchedulerForTesting(): void;
|
package/dist/scheduler/index.cjs
CHANGED
|
@@ -27,65 +27,76 @@ function isDue(now, schedule, tickMs) {
|
|
|
27
27
|
}
|
|
28
28
|
return false;
|
|
29
29
|
}
|
|
30
|
+
/** Split the due tasks into those that may run immediately and those gated
|
|
31
|
+
* behind a `dependsOn` edge (resolved later in the same tick cycle). */
|
|
32
|
+
function collectDueTasks(currentTime, registry, tickMs) {
|
|
33
|
+
const independent = [];
|
|
34
|
+
const dependent = [];
|
|
35
|
+
for (const def of registry.values()) {
|
|
36
|
+
if (def.enabled === false) continue;
|
|
37
|
+
if (!isDue(currentTime, def.schedule, tickMs)) continue;
|
|
38
|
+
if (def.dependsOn) dependent.push(def);
|
|
39
|
+
else independent.push(def);
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
independent,
|
|
43
|
+
dependent
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
async function runAndTrack(def, currentTime, succeeded, log) {
|
|
47
|
+
try {
|
|
48
|
+
await def.run({
|
|
49
|
+
taskId: def.id,
|
|
50
|
+
now: currentTime
|
|
51
|
+
});
|
|
52
|
+
succeeded.add(def.id);
|
|
53
|
+
} catch (err) {
|
|
54
|
+
log.error("task failed", {
|
|
55
|
+
id: def.id,
|
|
56
|
+
error: String(err)
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async function runDependentChain(dependent, currentTime, succeeded, log) {
|
|
61
|
+
let remaining = [...dependent];
|
|
62
|
+
let progress = true;
|
|
63
|
+
while (remaining.length > 0 && progress) {
|
|
64
|
+
progress = false;
|
|
65
|
+
const next = [];
|
|
66
|
+
for (const def of remaining) {
|
|
67
|
+
const dep = def.dependsOn;
|
|
68
|
+
if (!dep || !succeeded.has(dep)) {
|
|
69
|
+
next.push(def);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
await runAndTrack(def, currentTime, succeeded, log);
|
|
73
|
+
progress = true;
|
|
74
|
+
}
|
|
75
|
+
remaining = next;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
async function runTick(now, registry, tickMs, log) {
|
|
79
|
+
const currentTime = now();
|
|
80
|
+
const { independent, dependent } = collectDueTasks(currentTime, registry, tickMs);
|
|
81
|
+
const succeeded = /* @__PURE__ */ new Set();
|
|
82
|
+
await Promise.all(independent.map((def) => runAndTrack(def, currentTime, succeeded, log)));
|
|
83
|
+
await runDependentChain(dependent, currentTime, succeeded, log);
|
|
84
|
+
}
|
|
85
|
+
function listTaskSummaries(registry) {
|
|
86
|
+
return [...registry.values()].map((taskDef) => ({
|
|
87
|
+
id: taskDef.id,
|
|
88
|
+
description: taskDef.description,
|
|
89
|
+
schedule: taskDef.schedule,
|
|
90
|
+
dependsOn: taskDef.dependsOn
|
|
91
|
+
}));
|
|
92
|
+
}
|
|
30
93
|
function createTaskManager(options) {
|
|
31
94
|
const tickMs = options?.tickMs ?? ONE_MINUTE_MS;
|
|
32
95
|
const now = options?.now ?? (() => /* @__PURE__ */ new Date());
|
|
33
96
|
const log = options?.log ?? NOOP_LOG$1;
|
|
34
97
|
const registry = /* @__PURE__ */ new Map();
|
|
35
98
|
let timer = null;
|
|
36
|
-
|
|
37
|
-
const independent = [];
|
|
38
|
-
const dependent = [];
|
|
39
|
-
for (const def of registry.values()) {
|
|
40
|
-
if (def.enabled === false) continue;
|
|
41
|
-
if (!isDue(currentTime, def.schedule, tickMs)) continue;
|
|
42
|
-
if (def.dependsOn) dependent.push(def);
|
|
43
|
-
else independent.push(def);
|
|
44
|
-
}
|
|
45
|
-
return {
|
|
46
|
-
independent,
|
|
47
|
-
dependent
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
async function runAndTrack(def, currentTime, succeeded) {
|
|
51
|
-
try {
|
|
52
|
-
await def.run({
|
|
53
|
-
taskId: def.id,
|
|
54
|
-
now: currentTime
|
|
55
|
-
});
|
|
56
|
-
succeeded.add(def.id);
|
|
57
|
-
} catch (err) {
|
|
58
|
-
log.error("task failed", {
|
|
59
|
-
id: def.id,
|
|
60
|
-
error: String(err)
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
async function runDependentChain(dependent, currentTime, succeeded) {
|
|
65
|
-
let remaining = [...dependent];
|
|
66
|
-
let progress = true;
|
|
67
|
-
while (remaining.length > 0 && progress) {
|
|
68
|
-
progress = false;
|
|
69
|
-
const next = [];
|
|
70
|
-
for (const def of remaining) {
|
|
71
|
-
const dep = def.dependsOn;
|
|
72
|
-
if (!dep || !succeeded.has(dep)) {
|
|
73
|
-
next.push(def);
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
await runAndTrack(def, currentTime, succeeded);
|
|
77
|
-
progress = true;
|
|
78
|
-
}
|
|
79
|
-
remaining = next;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
async function onTick() {
|
|
83
|
-
const currentTime = now();
|
|
84
|
-
const { independent, dependent } = collectDueTasks(currentTime);
|
|
85
|
-
const succeeded = /* @__PURE__ */ new Set();
|
|
86
|
-
await Promise.all(independent.map((def) => runAndTrack(def, currentTime, succeeded)));
|
|
87
|
-
await runDependentChain(dependent, currentTime, succeeded);
|
|
88
|
-
}
|
|
99
|
+
const onTick = () => runTick(now, registry, tickMs, log);
|
|
89
100
|
return {
|
|
90
101
|
async tick() {
|
|
91
102
|
await onTick();
|
|
@@ -118,12 +129,7 @@ function createTaskManager(options) {
|
|
|
118
129
|
}
|
|
119
130
|
},
|
|
120
131
|
listTasks() {
|
|
121
|
-
return
|
|
122
|
-
id: taskDef.id,
|
|
123
|
-
description: taskDef.description,
|
|
124
|
-
schedule: taskDef.schedule,
|
|
125
|
-
dependsOn: taskDef.dependsOn
|
|
126
|
-
}));
|
|
132
|
+
return listTaskSummaries(registry);
|
|
127
133
|
}
|
|
128
134
|
};
|
|
129
135
|
}
|
|
@@ -228,7 +234,7 @@ async function applyScheduleOverride(taskId, schedule) {
|
|
|
228
234
|
if (!task || !taskManagerRef) return false;
|
|
229
235
|
if (!taskManagerRef.updateSchedule(taskId, schedule)) return false;
|
|
230
236
|
task.schedule = schedule;
|
|
231
|
-
await safeUpdateState(taskId, { nextScheduledAt:
|
|
237
|
+
await safeUpdateState(taskId, { nextScheduledAt: computeNextScheduledFor(task.schedule) });
|
|
232
238
|
return true;
|
|
233
239
|
}
|
|
234
240
|
/** Query execution logs — used by API routes. */
|
|
@@ -246,6 +252,35 @@ function getSchedulerTasks() {
|
|
|
246
252
|
state: stateMap.get(taskDef.id) ?? (0, _receptron_task_scheduler.emptyState)(taskDef.id)
|
|
247
253
|
}));
|
|
248
254
|
}
|
|
255
|
+
/** Read the persisted execution state for any task id (system, user, or
|
|
256
|
+
* skill). Returns an empty state when the id has never run — used by the API
|
|
257
|
+
* route to attach last-run / next-run / history state to user + skill tasks
|
|
258
|
+
* it lists from other sources. */
|
|
259
|
+
function getSchedulerTaskState(taskId) {
|
|
260
|
+
return stateMap.get(taskId) ?? (0, _receptron_task_scheduler.emptyState)(taskId);
|
|
261
|
+
}
|
|
262
|
+
/** Record a run of an EXTERNAL (skill / user) task — one registered directly
|
|
263
|
+
* on the task-manager rather than through `initScheduler`. Persists a log
|
|
264
|
+
* entry + updates state so these tasks get the same history / last-run /
|
|
265
|
+
* next-run as system tasks. Because they are fire-and-forget (`startChat`
|
|
266
|
+
* spawns an async chat), `errorMessage` reflects whether the *dispatch*
|
|
267
|
+
* succeeded, not the chat's eventual outcome; `chatSessionId` links the run
|
|
268
|
+
* to the spawned session. */
|
|
269
|
+
async function recordExternalRun(params) {
|
|
270
|
+
await persistRun({
|
|
271
|
+
meta: {
|
|
272
|
+
id: params.id,
|
|
273
|
+
name: params.name,
|
|
274
|
+
schedule: params.schedule
|
|
275
|
+
},
|
|
276
|
+
scheduledFor: params.scheduledFor,
|
|
277
|
+
startedAt: params.startedAt,
|
|
278
|
+
durationMs: params.durationMs,
|
|
279
|
+
trigger: params.trigger,
|
|
280
|
+
errMsg: params.errorMessage,
|
|
281
|
+
chatSessionId: params.chatSessionId
|
|
282
|
+
});
|
|
283
|
+
}
|
|
249
284
|
/** Test-only: clear config + in-memory state. */
|
|
250
285
|
function resetSchedulerForTesting() {
|
|
251
286
|
config = null;
|
|
@@ -266,44 +301,66 @@ async function executeAndLog(task, scheduledFor, trigger) {
|
|
|
266
301
|
error: errMsg
|
|
267
302
|
});
|
|
268
303
|
}
|
|
269
|
-
|
|
304
|
+
const durationMs = Date.now() - startMs;
|
|
305
|
+
await persistRun({
|
|
306
|
+
meta: {
|
|
307
|
+
id: task.id,
|
|
308
|
+
name: task.name,
|
|
309
|
+
schedule: task.schedule
|
|
310
|
+
},
|
|
311
|
+
scheduledFor,
|
|
312
|
+
startedAt,
|
|
313
|
+
durationMs,
|
|
314
|
+
trigger,
|
|
315
|
+
errMsg
|
|
316
|
+
});
|
|
270
317
|
}
|
|
271
|
-
/** Best-effort persistence — state and log are independent
|
|
272
|
-
*
|
|
273
|
-
async function
|
|
318
|
+
/** Best-effort persistence — state and log are independent, so one failing
|
|
319
|
+
* never blocks the other and neither propagates upward. */
|
|
320
|
+
async function persistRun(run) {
|
|
321
|
+
await writeRunState(run);
|
|
322
|
+
await writeRunLog(run);
|
|
323
|
+
}
|
|
324
|
+
async function writeRunState(run) {
|
|
325
|
+
const { meta, scheduledFor, durationMs, errMsg } = run;
|
|
274
326
|
const isSuccess = errMsg === null;
|
|
275
|
-
const currentState = stateMap.get(
|
|
327
|
+
const currentState = stateMap.get(meta.id);
|
|
276
328
|
try {
|
|
277
|
-
await (0, _receptron_task_scheduler.updateAndSave)(stateFilePath(), stateMap,
|
|
329
|
+
await (0, _receptron_task_scheduler.updateAndSave)(stateFilePath(), stateMap, meta.id, {
|
|
278
330
|
lastRunAt: scheduledFor,
|
|
279
331
|
lastRunResult: isSuccess ? _receptron_task_scheduler.TASK_RESULTS.success : _receptron_task_scheduler.TASK_RESULTS.error,
|
|
280
332
|
lastRunDurationMs: durationMs,
|
|
281
333
|
lastErrorMessage: errMsg,
|
|
282
334
|
consecutiveFailures: isSuccess ? 0 : (currentState?.consecutiveFailures ?? 0) + 1,
|
|
283
335
|
totalRuns: (currentState?.totalRuns ?? 0) + 1,
|
|
284
|
-
nextScheduledAt:
|
|
336
|
+
nextScheduledAt: computeNextScheduledFor(meta.schedule)
|
|
285
337
|
}, stateDeps());
|
|
286
338
|
} catch (err) {
|
|
287
339
|
logger().warn("state persistence failed", {
|
|
288
|
-
taskId:
|
|
340
|
+
taskId: meta.id,
|
|
289
341
|
error: String(err)
|
|
290
342
|
});
|
|
291
343
|
}
|
|
344
|
+
}
|
|
345
|
+
async function writeRunLog(run) {
|
|
346
|
+
const { meta, scheduledFor, startedAt, durationMs, trigger, errMsg, chatSessionId } = run;
|
|
347
|
+
const isSuccess = errMsg === null;
|
|
292
348
|
try {
|
|
293
349
|
await (0, _receptron_task_scheduler.appendLogEntry)(logsDir(), {
|
|
294
|
-
taskId:
|
|
295
|
-
taskName:
|
|
350
|
+
taskId: meta.id,
|
|
351
|
+
taskName: meta.name,
|
|
296
352
|
scheduledFor,
|
|
297
353
|
startedAt,
|
|
298
354
|
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
299
355
|
result: isSuccess ? _receptron_task_scheduler.TASK_RESULTS.success : _receptron_task_scheduler.TASK_RESULTS.error,
|
|
300
356
|
durationMs,
|
|
301
357
|
trigger,
|
|
302
|
-
...errMsg !== null && { errorMessage: errMsg }
|
|
358
|
+
...errMsg !== null && { errorMessage: errMsg },
|
|
359
|
+
...chatSessionId !== void 0 && { chatSessionId }
|
|
303
360
|
}, logDeps);
|
|
304
361
|
} catch (err) {
|
|
305
362
|
logger().warn("log persistence failed", {
|
|
306
|
-
taskId:
|
|
363
|
+
taskId: meta.id,
|
|
307
364
|
error: String(err)
|
|
308
365
|
});
|
|
309
366
|
}
|
|
@@ -329,8 +386,8 @@ function computeCurrentWindow(task) {
|
|
|
329
386
|
const windowMs = (0, _receptron_task_scheduler.nextWindowAfter)(coreSchedule, nowMs - (coreSchedule.type === _receptron_task_scheduler.SCHEDULE_TYPES.interval ? coreSchedule.intervalSec * ONE_SECOND_MS : 0));
|
|
330
387
|
return windowMs !== null && windowMs <= nowMs ? new Date(windowMs).toISOString() : new Date(nowMs).toISOString();
|
|
331
388
|
}
|
|
332
|
-
function
|
|
333
|
-
const next = (0, _receptron_task_scheduler.nextWindowAfter)(toCoreSchedule(
|
|
389
|
+
function computeNextScheduledFor(schedule) {
|
|
390
|
+
const next = (0, _receptron_task_scheduler.nextWindowAfter)(toCoreSchedule(schedule), Date.now() + 1);
|
|
334
391
|
return next !== null ? new Date(next).toISOString() : null;
|
|
335
392
|
}
|
|
336
393
|
function toCoreSchedule(schedule) {
|
|
@@ -341,12 +398,20 @@ function toCoreSchedule(schedule) {
|
|
|
341
398
|
return schedule;
|
|
342
399
|
}
|
|
343
400
|
//#endregion
|
|
401
|
+
Object.defineProperty(exports, "TASK_TRIGGERS", {
|
|
402
|
+
enumerable: true,
|
|
403
|
+
get: function() {
|
|
404
|
+
return _receptron_task_scheduler.TASK_TRIGGERS;
|
|
405
|
+
}
|
|
406
|
+
});
|
|
344
407
|
exports.applyScheduleOverride = applyScheduleOverride;
|
|
345
408
|
exports.configureScheduler = configureScheduler;
|
|
346
409
|
exports.createTaskManager = createTaskManager;
|
|
347
410
|
exports.getSchedulerLogs = getSchedulerLogs;
|
|
411
|
+
exports.getSchedulerTaskState = getSchedulerTaskState;
|
|
348
412
|
exports.getSchedulerTasks = getSchedulerTasks;
|
|
349
413
|
exports.initScheduler = initScheduler;
|
|
414
|
+
exports.recordExternalRun = recordExternalRun;
|
|
350
415
|
exports.resetSchedulerForTesting = resetSchedulerForTesting;
|
|
351
416
|
|
|
352
417
|
//# sourceMappingURL=index.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":[],"sources":["../../src/scheduler/task-manager.ts","../../src/scheduler/adapter.ts"],"sourcesContent":["// Generic dependency-ordered cron tick engine. Host-agnostic: the only\n// host coupling (a logger) is injected via options. Schedules are either\n// fixed intervals or a daily UTC time; tasks may declare a `dependsOn`\n// edge so an ordering like \"news fetch → journal → memory extraction\"\n// runs in sequence within one tick.\n\nimport { SCHEDULE_TYPES } from \"@receptron/task-scheduler\";\n\nconst ONE_SECOND_MS = 1000;\nconst ONE_MINUTE_MS = 60 * ONE_SECOND_MS;\nconst ONE_HOUR_MS = 60 * ONE_MINUTE_MS;\n\n/** Minimal logger the engine logs through. Absent one, runs silent. */\nexport interface SchedulerLogger {\n info: (message: string, data?: Record<string, unknown>) => void;\n warn: (message: string, data?: Record<string, unknown>) => void;\n error: (message: string, data?: Record<string, unknown>) => void;\n}\n\nconst NOOP_LOG: SchedulerLogger = { info: () => {}, warn: () => {}, error: () => {} };\n\nexport type TaskSchedule = { type: typeof SCHEDULE_TYPES.interval; intervalMs: number } | { type: typeof SCHEDULE_TYPES.daily; time: string }; // time: \"HH:MM\" in UTC\n\nexport interface TaskRunContext {\n taskId: string;\n now: Date;\n}\n\nexport interface TaskDefinition {\n id: string;\n description?: string;\n schedule: TaskSchedule;\n enabled?: boolean; // default: true\n /** If set, this task only fires after the named task has completed\n * successfully in the current tick cycle. Enforces ordering like\n * \"news fetch → journal → memory extraction\". */\n dependsOn?: string;\n run: (ctx: TaskRunContext) => Promise<void>;\n}\n\nexport interface ITaskManager {\n registerTask: (def: TaskDefinition) => void;\n removeTask: (taskId: string) => void;\n /** Update the schedule of an existing task. Returns false if not found. */\n updateSchedule: (taskId: string, schedule: TaskSchedule) => boolean;\n start: () => void;\n stop: () => void;\n /** Run one tick manually (for testing). */\n tick: () => Promise<void>;\n listTasks: () => {\n id: string;\n description?: string;\n schedule: TaskSchedule;\n dependsOn?: string;\n }[];\n}\n\nexport interface TaskManagerOptions {\n tickMs?: number; // default: ONE_MINUTE_MS\n now?: () => Date; // default: () => new Date()\n log?: SchedulerLogger; // default: noop\n}\n\nfunction isDue(now: Date, schedule: TaskSchedule, tickMs: number): boolean {\n if (schedule.type === SCHEDULE_TYPES.interval) {\n const msSinceMidnight = now.getUTCHours() * ONE_HOUR_MS + now.getUTCMinutes() * ONE_MINUTE_MS + now.getUTCSeconds() * ONE_SECOND_MS;\n // Round down to tick boundary, then check if it aligns with the interval\n const rounded = Math.floor(msSinceMidnight / tickMs) * tickMs;\n return rounded % schedule.intervalMs === 0;\n }\n\n if (schedule.type === SCHEDULE_TYPES.daily) {\n const [hours, minutes] = schedule.time.split(\":\").map(Number);\n const targetMs = hours * ONE_HOUR_MS + minutes * ONE_MINUTE_MS;\n const msSinceMidnight = now.getUTCHours() * ONE_HOUR_MS + now.getUTCMinutes() * ONE_MINUTE_MS + now.getUTCSeconds() * ONE_SECOND_MS;\n const rounded = Math.floor(msSinceMidnight / tickMs) * tickMs;\n return rounded === targetMs;\n }\n\n return false;\n}\n\nexport function createTaskManager(options?: TaskManagerOptions): ITaskManager {\n const tickMs = options?.tickMs ?? ONE_MINUTE_MS;\n const now = options?.now ?? (() => new Date());\n const log = options?.log ?? NOOP_LOG;\n const registry = new Map<string, TaskDefinition>();\n let timer: ReturnType<typeof setInterval> | null = null;\n\n function collectDueTasks(currentTime: Date): {\n independent: TaskDefinition[];\n dependent: TaskDefinition[];\n } {\n const independent: TaskDefinition[] = [];\n const dependent: TaskDefinition[] = [];\n for (const def of registry.values()) {\n if (def.enabled === false) continue;\n if (!isDue(currentTime, def.schedule, tickMs)) continue;\n if (def.dependsOn) {\n dependent.push(def);\n } else {\n independent.push(def);\n }\n }\n return { independent, dependent };\n }\n\n async function runAndTrack(def: TaskDefinition, currentTime: Date, succeeded: Set<string>): Promise<void> {\n try {\n await def.run({ taskId: def.id, now: currentTime });\n succeeded.add(def.id);\n } catch (err) {\n log.error(\"task failed\", {\n id: def.id,\n error: String(err),\n });\n }\n }\n\n async function runDependentChain(dependent: TaskDefinition[], currentTime: Date, succeeded: Set<string>): Promise<void> {\n let remaining = [...dependent];\n let progress = true;\n while (remaining.length > 0 && progress) {\n progress = false;\n const next: TaskDefinition[] = [];\n for (const def of remaining) {\n const dep = def.dependsOn;\n if (!dep || !succeeded.has(dep)) {\n next.push(def);\n continue;\n }\n await runAndTrack(def, currentTime, succeeded);\n progress = true;\n }\n remaining = next;\n }\n }\n\n async function onTick(): Promise<void> {\n const currentTime = now();\n const { independent, dependent } = collectDueTasks(currentTime);\n\n // Per-invocation set — success does not leak across tick() calls.\n const succeeded = new Set<string>();\n\n await Promise.all(independent.map((def) => runAndTrack(def, currentTime, succeeded)));\n\n await runDependentChain(dependent, currentTime, succeeded);\n }\n\n return {\n async tick() {\n await onTick();\n },\n\n registerTask(def: TaskDefinition) {\n if (registry.has(def.id)) {\n throw new Error(`[task-manager] Task \"${def.id}\" is already registered`);\n }\n registry.set(def.id, def);\n log.info(\"registered\", { id: def.id });\n },\n\n updateSchedule(taskId: string, schedule: TaskSchedule): boolean {\n const def = registry.get(taskId);\n if (!def) return false;\n def.schedule = schedule;\n log.info(\"schedule updated\", { id: taskId });\n return true;\n },\n\n removeTask(taskId: string) {\n if (registry.delete(taskId)) {\n log.info(\"removed\", { id: taskId });\n }\n },\n\n start() {\n if (timer) return;\n timer = setInterval(onTick, tickMs);\n log.info(\"started\", { tickMs });\n },\n\n stop() {\n if (timer) {\n clearInterval(timer);\n timer = null;\n log.info(\"stopped\");\n }\n },\n\n listTasks() {\n return [...registry.values()].map((taskDef) => ({\n id: taskDef.id,\n description: taskDef.description,\n schedule: taskDef.schedule,\n dependsOn: taskDef.dependsOn,\n }));\n },\n };\n}\n","// Adapter that wires the pure scheduler library (@receptron/task-scheduler)\n// to a host's task-manager + workspace. Registers system tasks, runs\n// catch-up on startup, and persists execution state + logs.\n//\n// Host-agnostic: the workspace root, the atomic file writer, and the\n// logger are injected via `configureScheduler`. The host supplies its OWN\n// system tasks (journal / feeds / user-cron in MulmoClaude) to\n// `initScheduler` — the package owns no task definitions. Deliberately\n// thin: all complex scheduling logic lives in @receptron/task-scheduler.\n\nimport { existsSync } from \"node:fs\";\nimport { readFile, appendFile, mkdir } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport {\n type TaskSchedule,\n type TaskExecutionState,\n type TaskLogEntry,\n type CatchUpTask,\n type TaskTrigger,\n emptyState,\n computeCatchUpPlan,\n nextWindowAfter,\n loadState,\n updateAndSave,\n appendLogEntry,\n queryLog,\n SCHEDULE_TYPES,\n TASK_RESULTS,\n TASK_TRIGGERS,\n type MISSED_RUN_POLICIES,\n type StateMap,\n type StateDeps,\n type LogDeps,\n} from \"@receptron/task-scheduler\";\nimport type { ITaskManager, TaskDefinition, SchedulerLogger } from \"./task-manager.js\";\n\nconst ONE_SECOND_MS = 1000;\nconst SCHEDULER_CONFIG_DIR = \"config/scheduler\";\nconst SCHEDULER_DATA_DIR = \"data/scheduler/logs\";\n\n// ── Host injection ────────────────────────────────────────────────\n\nexport interface SchedulerConfig {\n /** Absolute workspace root — state.json + logs hang off it. */\n workspaceRoot: string;\n /** Host atomic file writer (used with `uniqueTmp` for the state file). */\n writeFileAtomic: (filePath: string, content: string, opts: { uniqueTmp: boolean }) => Promise<void>;\n /** Optional logger. */\n log?: SchedulerLogger;\n}\n\nconst NOOP_LOG: SchedulerLogger = { info: () => {}, warn: () => {}, error: () => {} };\n\nlet config: SchedulerConfig | null = null;\n\n/** Wire the adapter to a host. Call once at startup, before `initScheduler`. */\nexport function configureScheduler(injected: SchedulerConfig): void {\n config = injected;\n}\n\nfunction requireConfig(): SchedulerConfig {\n if (!config) throw new Error(\"scheduler: configureScheduler() not called\");\n return config;\n}\n\nfunction logger(): SchedulerLogger {\n return config?.log ?? NOOP_LOG;\n}\n\nfunction errorMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\n// ── Paths ─────────────────────────────────────────────────────────\n\nfunction stateFilePath(): string {\n return path.join(requireConfig().workspaceRoot, SCHEDULER_CONFIG_DIR, \"state.json\");\n}\n\nfunction logsDir(): string {\n return path.join(requireConfig().workspaceRoot, SCHEDULER_DATA_DIR);\n}\n\n// ── I/O deps (real filesystem) ────────────────────────────────────\n\nfunction stateDeps(): StateDeps {\n return {\n readFile: (filePath: string) => readFile(filePath, \"utf-8\"),\n writeFileAtomic: (filePath: string, content: string) => requireConfig().writeFileAtomic(filePath, content, { uniqueTmp: true }),\n exists: existsSync,\n };\n}\n\nconst logDeps: LogDeps = {\n appendFile: (filePath: string, content: string) => appendFile(filePath, content),\n readFile: (filePath: string) => readFile(filePath, \"utf-8\"),\n exists: existsSync,\n ensureDir: (directoryPath: string) => mkdir(directoryPath, { recursive: true }).then(() => {}),\n};\n\n// ── System task registry ──────────────────────────────────────────\n\nexport interface SystemTaskDef {\n id: string;\n name: string;\n description: string;\n schedule: TaskDefinition[\"schedule\"];\n missedRunPolicy: typeof MISSED_RUN_POLICIES.skip | typeof MISSED_RUN_POLICIES.runOnce | typeof MISSED_RUN_POLICIES.runAll;\n run: () => Promise<void>;\n}\n\n// ── Public API ────────────────────────────────────────────────────\n\nlet stateMap: StateMap = new Map();\nconst systemTasks: SystemTaskDef[] = [];\nlet taskManagerRef: ITaskManager | null = null;\n\n/**\n * Initialize the scheduler adapter. Call once at server startup AFTER the\n * task-manager is created but BEFORE `taskManager.start()`.\n */\nexport async function initScheduler(taskManager: ITaskManager, tasks: SystemTaskDef[]): Promise<void> {\n await mkdir(path.dirname(stateFilePath()), { recursive: true });\n await mkdir(logsDir(), { recursive: true });\n\n stateMap = await loadState(stateFilePath(), stateDeps());\n systemTasks.length = 0;\n systemTasks.push(...tasks);\n taskManagerRef = taskManager;\n\n // Run catch-up\n const catchUpTasks: CatchUpTask[] = tasks.map((taskDef) => ({\n id: taskDef.id,\n name: taskDef.name,\n schedule: toCoreSchedule(taskDef.schedule),\n missedRunPolicy: taskDef.missedRunPolicy,\n enabled: true,\n }));\n const plan = computeCatchUpPlan(catchUpTasks, stateMap, Date.now());\n\n for (const skip of plan.skipped) {\n logger().info(\"catch-up skipped\", { taskId: skip.taskId, windows: skip.windowCount });\n await safeUpdateState(skip.taskId, { lastRunAt: skip.lastWindow });\n }\n\n if (plan.runs.length > 0) {\n logger().info(\"catch-up enqueued\", { runs: plan.runs.length });\n for (const run of plan.runs) {\n const task = tasks.find((taskDef) => taskDef.id === run.taskId);\n if (!task) continue;\n await executeAndLog(task, run.context.scheduledFor, TASK_TRIGGERS.catchUp);\n }\n }\n\n // Register with task-manager for ongoing ticks\n for (const task of tasks) {\n taskManager.registerTask({\n id: task.id,\n description: task.description,\n schedule: task.schedule,\n run: async () => {\n const windowIso = computeCurrentWindow(task);\n await executeAndLog(task, windowIso, TASK_TRIGGERS.scheduled);\n },\n });\n }\n\n logger().info(\"initialized\", { tasks: tasks.map((taskDef) => taskDef.id), stateEntries: stateMap.size });\n}\n\n/** Apply a schedule override to a running system task. Updates the\n * in-memory task definition, the task-manager, and recalculates\n * nextScheduledAt in persisted state. */\nexport async function applyScheduleOverride(taskId: string, schedule: SystemTaskDef[\"schedule\"]): Promise<boolean> {\n const task = systemTasks.find((taskDef) => taskDef.id === taskId);\n if (!task || !taskManagerRef) return false;\n if (!taskManagerRef.updateSchedule(taskId, schedule)) return false;\n task.schedule = schedule;\n\n // Recalculate next window so the UI reflects the new schedule\n const nextScheduledAt = computeNextScheduled(task);\n await safeUpdateState(taskId, { nextScheduledAt });\n\n return true;\n}\n\n/** Query execution logs — used by API routes. */\nexport async function getSchedulerLogs(opts: { since?: string; taskId?: string; limit?: number }): Promise<TaskLogEntry[]> {\n return queryLog(logsDir(), opts, logDeps);\n}\n\n/** Get all task states — used by API routes. */\nexport function getSchedulerTasks(): {\n id: string;\n name: string;\n description: string;\n schedule: TaskDefinition[\"schedule\"];\n missedRunPolicy: string;\n state: TaskExecutionState;\n}[] {\n return systemTasks.map((taskDef) => ({\n id: taskDef.id,\n name: taskDef.name,\n description: taskDef.description,\n schedule: taskDef.schedule,\n missedRunPolicy: taskDef.missedRunPolicy,\n state: stateMap.get(taskDef.id) ?? emptyState(taskDef.id),\n }));\n}\n\n/** Test-only: clear config + in-memory state. */\nexport function resetSchedulerForTesting(): void {\n config = null;\n stateMap = new Map();\n systemTasks.length = 0;\n taskManagerRef = null;\n}\n\n// ── Internal ──────────────────────────────────────────────────────\n\nasync function executeAndLog(task: SystemTaskDef, scheduledFor: string, trigger: TaskTrigger): Promise<void> {\n const startedAt = new Date().toISOString();\n const startMs = Date.now();\n let errMsg: string | null = null;\n try {\n await task.run();\n } catch (err) {\n errMsg = errorMessage(err);\n logger().error(\"task failed\", { taskId: task.id, error: errMsg });\n }\n const durationMs = Date.now() - startMs;\n // Persistence is best-effort — never let disk failures propagate to the\n // tick loop or abort startup catch-up.\n await safePersist(task, scheduledFor, startedAt, durationMs, trigger, errMsg);\n}\n\n/** Best-effort persistence — state and log are independent. A failure in\n * one does not block the other, and neither propagates upward. */\nasync function safePersist(\n task: SystemTaskDef,\n scheduledFor: string,\n startedAt: string,\n durationMs: number,\n trigger: TaskTrigger,\n errMsg: string | null,\n): Promise<void> {\n const isSuccess = errMsg === null;\n const currentState = stateMap.get(task.id);\n try {\n await updateAndSave(\n stateFilePath(),\n stateMap,\n task.id,\n {\n lastRunAt: scheduledFor,\n lastRunResult: isSuccess ? TASK_RESULTS.success : TASK_RESULTS.error,\n lastRunDurationMs: durationMs,\n lastErrorMessage: errMsg,\n consecutiveFailures: isSuccess ? 0 : (currentState?.consecutiveFailures ?? 0) + 1,\n totalRuns: (currentState?.totalRuns ?? 0) + 1,\n nextScheduledAt: computeNextScheduled(task),\n },\n stateDeps(),\n );\n } catch (err) {\n logger().warn(\"state persistence failed\", { taskId: task.id, error: String(err) });\n }\n try {\n await appendLogEntry(\n logsDir(),\n {\n taskId: task.id,\n taskName: task.name,\n scheduledFor,\n startedAt,\n completedAt: new Date().toISOString(),\n result: isSuccess ? TASK_RESULTS.success : TASK_RESULTS.error,\n durationMs,\n trigger,\n ...(errMsg !== null && { errorMessage: errMsg }),\n },\n logDeps,\n );\n } catch (err) {\n logger().warn(\"log persistence failed\", { taskId: task.id, error: String(err) });\n }\n}\n\n/** Safe state update — swallows errors. */\nasync function safeUpdateState(taskId: string, patch: Partial<TaskExecutionState>): Promise<void> {\n try {\n await updateAndSave(stateFilePath(), stateMap, taskId, patch, stateDeps());\n } catch (err) {\n logger().warn(\"state update failed\", { taskId, error: String(err) });\n }\n}\n\n/** Compute the window boundary that the current tick belongs to. For\n * scheduled runs, this is the epoch-aligned window — not the wall-clock\n * time of execution. This keeps lastRunAt consistent with catch-up's\n * window-based accounting. */\nfunction computeCurrentWindow(task: SystemTaskDef): string {\n const coreSchedule = toCoreSchedule(task.schedule);\n // The window that just fired is the latest one at or before now.\n const nowMs = Date.now();\n const windowMs = nextWindowAfter(coreSchedule, nowMs - (coreSchedule.type === SCHEDULE_TYPES.interval ? coreSchedule.intervalSec * ONE_SECOND_MS : 0));\n return windowMs !== null && windowMs <= nowMs ? new Date(windowMs).toISOString() : new Date(nowMs).toISOString();\n}\n\nfunction computeNextScheduled(task: SystemTaskDef): string | null {\n const coreSchedule = toCoreSchedule(task.schedule);\n const next = nextWindowAfter(coreSchedule, Date.now() + 1);\n return next !== null ? new Date(next).toISOString() : null;\n}\n\nfunction toCoreSchedule(schedule: TaskDefinition[\"schedule\"]): TaskSchedule {\n if (schedule.type === SCHEDULE_TYPES.interval) {\n return {\n type: SCHEDULE_TYPES.interval,\n intervalSec: Math.round(schedule.intervalMs / ONE_SECOND_MS),\n };\n }\n return schedule;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":[],"sources":["../../src/scheduler/task-manager.ts","../../src/scheduler/adapter.ts"],"sourcesContent":["// Generic dependency-ordered cron tick engine. Host-agnostic: the only\n// host coupling (a logger) is injected via options. Schedules are either\n// fixed intervals or a daily UTC time; tasks may declare a `dependsOn`\n// edge so an ordering like \"news fetch → journal → memory extraction\"\n// runs in sequence within one tick.\n\nimport { SCHEDULE_TYPES } from \"@receptron/task-scheduler\";\n\nconst ONE_SECOND_MS = 1000;\nconst ONE_MINUTE_MS = 60 * ONE_SECOND_MS;\nconst ONE_HOUR_MS = 60 * ONE_MINUTE_MS;\n\n/** Minimal logger the engine logs through. Absent one, runs silent. */\nexport interface SchedulerLogger {\n info: (message: string, data?: Record<string, unknown>) => void;\n warn: (message: string, data?: Record<string, unknown>) => void;\n error: (message: string, data?: Record<string, unknown>) => void;\n}\n\nconst NOOP_LOG: SchedulerLogger = { info: () => {}, warn: () => {}, error: () => {} };\n\nexport type TaskSchedule = { type: typeof SCHEDULE_TYPES.interval; intervalMs: number } | { type: typeof SCHEDULE_TYPES.daily; time: string }; // time: \"HH:MM\" in UTC\n\nexport interface TaskRunContext {\n taskId: string;\n now: Date;\n}\n\nexport interface TaskDefinition {\n id: string;\n description?: string;\n schedule: TaskSchedule;\n enabled?: boolean; // default: true\n /** If set, this task only fires after the named task has completed\n * successfully in the current tick cycle. Enforces ordering like\n * \"news fetch → journal → memory extraction\". */\n dependsOn?: string;\n run: (ctx: TaskRunContext) => Promise<void>;\n}\n\nexport interface TaskSummary {\n id: string;\n description?: string;\n schedule: TaskSchedule;\n dependsOn?: string;\n}\n\nexport interface ITaskManager {\n registerTask: (def: TaskDefinition) => void;\n removeTask: (taskId: string) => void;\n /** Update the schedule of an existing task. Returns false if not found. */\n updateSchedule: (taskId: string, schedule: TaskSchedule) => boolean;\n start: () => void;\n stop: () => void;\n /** Run one tick manually (for testing). */\n tick: () => Promise<void>;\n listTasks: () => TaskSummary[];\n}\n\nexport interface TaskManagerOptions {\n tickMs?: number; // default: ONE_MINUTE_MS\n now?: () => Date; // default: () => new Date()\n log?: SchedulerLogger; // default: noop\n}\n\nfunction isDue(now: Date, schedule: TaskSchedule, tickMs: number): boolean {\n if (schedule.type === SCHEDULE_TYPES.interval) {\n const msSinceMidnight = now.getUTCHours() * ONE_HOUR_MS + now.getUTCMinutes() * ONE_MINUTE_MS + now.getUTCSeconds() * ONE_SECOND_MS;\n // Round down to tick boundary, then check if it aligns with the interval\n const rounded = Math.floor(msSinceMidnight / tickMs) * tickMs;\n return rounded % schedule.intervalMs === 0;\n }\n\n if (schedule.type === SCHEDULE_TYPES.daily) {\n const [hours, minutes] = schedule.time.split(\":\").map(Number);\n const targetMs = hours * ONE_HOUR_MS + minutes * ONE_MINUTE_MS;\n const msSinceMidnight = now.getUTCHours() * ONE_HOUR_MS + now.getUTCMinutes() * ONE_MINUTE_MS + now.getUTCSeconds() * ONE_SECOND_MS;\n const rounded = Math.floor(msSinceMidnight / tickMs) * tickMs;\n return rounded === targetMs;\n }\n\n return false;\n}\n\n/** Split the due tasks into those that may run immediately and those gated\n * behind a `dependsOn` edge (resolved later in the same tick cycle). */\nexport function collectDueTasks(\n currentTime: Date,\n registry: Map<string, TaskDefinition>,\n tickMs: number,\n): { independent: TaskDefinition[]; dependent: TaskDefinition[] } {\n const independent: TaskDefinition[] = [];\n const dependent: TaskDefinition[] = [];\n for (const def of registry.values()) {\n if (def.enabled === false) continue;\n if (!isDue(currentTime, def.schedule, tickMs)) continue;\n if (def.dependsOn) {\n dependent.push(def);\n } else {\n independent.push(def);\n }\n }\n return { independent, dependent };\n}\n\nasync function runAndTrack(def: TaskDefinition, currentTime: Date, succeeded: Set<string>, log: SchedulerLogger): Promise<void> {\n try {\n await def.run({ taskId: def.id, now: currentTime });\n succeeded.add(def.id);\n } catch (err) {\n log.error(\"task failed\", {\n id: def.id,\n error: String(err),\n });\n }\n}\n\nasync function runDependentChain(dependent: TaskDefinition[], currentTime: Date, succeeded: Set<string>, log: SchedulerLogger): Promise<void> {\n let remaining = [...dependent];\n let progress = true;\n while (remaining.length > 0 && progress) {\n progress = false;\n const next: TaskDefinition[] = [];\n for (const def of remaining) {\n const dep = def.dependsOn;\n if (!dep || !succeeded.has(dep)) {\n next.push(def);\n continue;\n }\n await runAndTrack(def, currentTime, succeeded, log);\n progress = true;\n }\n remaining = next;\n }\n}\n\nasync function runTick(now: () => Date, registry: Map<string, TaskDefinition>, tickMs: number, log: SchedulerLogger): Promise<void> {\n const currentTime = now();\n const { independent, dependent } = collectDueTasks(currentTime, registry, tickMs);\n\n // Per-invocation set — success does not leak across tick() calls.\n const succeeded = new Set<string>();\n\n await Promise.all(independent.map((def) => runAndTrack(def, currentTime, succeeded, log)));\n\n await runDependentChain(dependent, currentTime, succeeded, log);\n}\n\nexport function listTaskSummaries(registry: Map<string, TaskDefinition>): TaskSummary[] {\n return [...registry.values()].map((taskDef) => ({\n id: taskDef.id,\n description: taskDef.description,\n schedule: taskDef.schedule,\n dependsOn: taskDef.dependsOn,\n }));\n}\n\nexport function createTaskManager(options?: TaskManagerOptions): ITaskManager {\n const tickMs = options?.tickMs ?? ONE_MINUTE_MS;\n const now = options?.now ?? (() => new Date());\n const log = options?.log ?? NOOP_LOG;\n const registry = new Map<string, TaskDefinition>();\n let timer: ReturnType<typeof setInterval> | null = null;\n\n const onTick = () => runTick(now, registry, tickMs, log);\n\n return {\n async tick() {\n await onTick();\n },\n\n registerTask(def: TaskDefinition) {\n if (registry.has(def.id)) {\n throw new Error(`[task-manager] Task \"${def.id}\" is already registered`);\n }\n registry.set(def.id, def);\n log.info(\"registered\", { id: def.id });\n },\n\n updateSchedule(taskId: string, schedule: TaskSchedule): boolean {\n const def = registry.get(taskId);\n if (!def) return false;\n def.schedule = schedule;\n log.info(\"schedule updated\", { id: taskId });\n return true;\n },\n\n removeTask(taskId: string) {\n if (registry.delete(taskId)) {\n log.info(\"removed\", { id: taskId });\n }\n },\n\n start() {\n if (timer) return;\n timer = setInterval(onTick, tickMs);\n log.info(\"started\", { tickMs });\n },\n\n stop() {\n if (timer) {\n clearInterval(timer);\n timer = null;\n log.info(\"stopped\");\n }\n },\n\n listTasks() {\n return listTaskSummaries(registry);\n },\n };\n}\n","// Adapter that wires the pure scheduler library (@receptron/task-scheduler)\n// to a host's task-manager + workspace. Registers system tasks, runs\n// catch-up on startup, and persists execution state + logs.\n//\n// Host-agnostic: the workspace root, the atomic file writer, and the\n// logger are injected via `configureScheduler`. The host supplies its OWN\n// system tasks (journal / feeds / user-cron in MulmoClaude) to\n// `initScheduler` — the package owns no task definitions. Deliberately\n// thin: all complex scheduling logic lives in @receptron/task-scheduler.\n\nimport { existsSync } from \"node:fs\";\nimport { readFile, appendFile, mkdir } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport {\n type TaskSchedule,\n type TaskExecutionState,\n type TaskLogEntry,\n type CatchUpTask,\n type TaskTrigger,\n emptyState,\n computeCatchUpPlan,\n nextWindowAfter,\n loadState,\n updateAndSave,\n appendLogEntry,\n queryLog,\n SCHEDULE_TYPES,\n TASK_RESULTS,\n TASK_TRIGGERS,\n type MISSED_RUN_POLICIES,\n type StateMap,\n type StateDeps,\n type LogDeps,\n} from \"@receptron/task-scheduler\";\nimport type { ITaskManager, TaskDefinition, SchedulerLogger } from \"./task-manager.js\";\n\nconst ONE_SECOND_MS = 1000;\nconst SCHEDULER_CONFIG_DIR = \"config/scheduler\";\nconst SCHEDULER_DATA_DIR = \"data/scheduler/logs\";\n\n// ── Host injection ────────────────────────────────────────────────\n\nexport interface SchedulerConfig {\n /** Absolute workspace root — state.json + logs hang off it. */\n workspaceRoot: string;\n /** Host atomic file writer (used with `uniqueTmp` for the state file). */\n writeFileAtomic: (filePath: string, content: string, opts: { uniqueTmp: boolean }) => Promise<void>;\n /** Optional logger. */\n log?: SchedulerLogger;\n}\n\nconst NOOP_LOG: SchedulerLogger = { info: () => {}, warn: () => {}, error: () => {} };\n\nlet config: SchedulerConfig | null = null;\n\n/** Wire the adapter to a host. Call once at startup, before `initScheduler`. */\nexport function configureScheduler(injected: SchedulerConfig): void {\n config = injected;\n}\n\nfunction requireConfig(): SchedulerConfig {\n if (!config) throw new Error(\"scheduler: configureScheduler() not called\");\n return config;\n}\n\nfunction logger(): SchedulerLogger {\n return config?.log ?? NOOP_LOG;\n}\n\nfunction errorMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\n// ── Paths ─────────────────────────────────────────────────────────\n\nfunction stateFilePath(): string {\n return path.join(requireConfig().workspaceRoot, SCHEDULER_CONFIG_DIR, \"state.json\");\n}\n\nfunction logsDir(): string {\n return path.join(requireConfig().workspaceRoot, SCHEDULER_DATA_DIR);\n}\n\n// ── I/O deps (real filesystem) ────────────────────────────────────\n\nfunction stateDeps(): StateDeps {\n return {\n readFile: (filePath: string) => readFile(filePath, \"utf-8\"),\n writeFileAtomic: (filePath: string, content: string) => requireConfig().writeFileAtomic(filePath, content, { uniqueTmp: true }),\n exists: existsSync,\n };\n}\n\nconst logDeps: LogDeps = {\n appendFile: (filePath: string, content: string) => appendFile(filePath, content),\n readFile: (filePath: string) => readFile(filePath, \"utf-8\"),\n exists: existsSync,\n ensureDir: (directoryPath: string) => mkdir(directoryPath, { recursive: true }).then(() => {}),\n};\n\n// ── System task registry ──────────────────────────────────────────\n\nexport interface SystemTaskDef {\n id: string;\n name: string;\n description: string;\n schedule: TaskDefinition[\"schedule\"];\n missedRunPolicy: typeof MISSED_RUN_POLICIES.skip | typeof MISSED_RUN_POLICIES.runOnce | typeof MISSED_RUN_POLICIES.runAll;\n run: () => Promise<void>;\n}\n\n// ── Public API ────────────────────────────────────────────────────\n\nlet stateMap: StateMap = new Map();\nconst systemTasks: SystemTaskDef[] = [];\nlet taskManagerRef: ITaskManager | null = null;\n\n/**\n * Initialize the scheduler adapter. Call once at server startup AFTER the\n * task-manager is created but BEFORE `taskManager.start()`.\n */\nexport async function initScheduler(taskManager: ITaskManager, tasks: SystemTaskDef[]): Promise<void> {\n await mkdir(path.dirname(stateFilePath()), { recursive: true });\n await mkdir(logsDir(), { recursive: true });\n\n stateMap = await loadState(stateFilePath(), stateDeps());\n systemTasks.length = 0;\n systemTasks.push(...tasks);\n taskManagerRef = taskManager;\n\n // Run catch-up\n const catchUpTasks: CatchUpTask[] = tasks.map((taskDef) => ({\n id: taskDef.id,\n name: taskDef.name,\n schedule: toCoreSchedule(taskDef.schedule),\n missedRunPolicy: taskDef.missedRunPolicy,\n enabled: true,\n }));\n const plan = computeCatchUpPlan(catchUpTasks, stateMap, Date.now());\n\n for (const skip of plan.skipped) {\n logger().info(\"catch-up skipped\", { taskId: skip.taskId, windows: skip.windowCount });\n await safeUpdateState(skip.taskId, { lastRunAt: skip.lastWindow });\n }\n\n if (plan.runs.length > 0) {\n logger().info(\"catch-up enqueued\", { runs: plan.runs.length });\n for (const run of plan.runs) {\n const task = tasks.find((taskDef) => taskDef.id === run.taskId);\n if (!task) continue;\n await executeAndLog(task, run.context.scheduledFor, TASK_TRIGGERS.catchUp);\n }\n }\n\n // Register with task-manager for ongoing ticks\n for (const task of tasks) {\n taskManager.registerTask({\n id: task.id,\n description: task.description,\n schedule: task.schedule,\n run: async () => {\n const windowIso = computeCurrentWindow(task);\n await executeAndLog(task, windowIso, TASK_TRIGGERS.scheduled);\n },\n });\n }\n\n logger().info(\"initialized\", { tasks: tasks.map((taskDef) => taskDef.id), stateEntries: stateMap.size });\n}\n\n/** Apply a schedule override to a running system task. Updates the\n * in-memory task definition, the task-manager, and recalculates\n * nextScheduledAt in persisted state. */\nexport async function applyScheduleOverride(taskId: string, schedule: SystemTaskDef[\"schedule\"]): Promise<boolean> {\n const task = systemTasks.find((taskDef) => taskDef.id === taskId);\n if (!task || !taskManagerRef) return false;\n if (!taskManagerRef.updateSchedule(taskId, schedule)) return false;\n task.schedule = schedule;\n\n // Recalculate next window so the UI reflects the new schedule\n const nextScheduledAt = computeNextScheduledFor(task.schedule);\n await safeUpdateState(taskId, { nextScheduledAt });\n\n return true;\n}\n\n/** Query execution logs — used by API routes. */\nexport async function getSchedulerLogs(opts: { since?: string; taskId?: string; limit?: number }): Promise<TaskLogEntry[]> {\n return queryLog(logsDir(), opts, logDeps);\n}\n\n/** Get all task states — used by API routes. */\nexport function getSchedulerTasks(): {\n id: string;\n name: string;\n description: string;\n schedule: TaskDefinition[\"schedule\"];\n missedRunPolicy: string;\n state: TaskExecutionState;\n}[] {\n return systemTasks.map((taskDef) => ({\n id: taskDef.id,\n name: taskDef.name,\n description: taskDef.description,\n schedule: taskDef.schedule,\n missedRunPolicy: taskDef.missedRunPolicy,\n state: stateMap.get(taskDef.id) ?? emptyState(taskDef.id),\n }));\n}\n\n/** Read the persisted execution state for any task id (system, user, or\n * skill). Returns an empty state when the id has never run — used by the API\n * route to attach last-run / next-run / history state to user + skill tasks\n * it lists from other sources. */\nexport function getSchedulerTaskState(taskId: string): TaskExecutionState {\n return stateMap.get(taskId) ?? emptyState(taskId);\n}\n\n/** Record a run of an EXTERNAL (skill / user) task — one registered directly\n * on the task-manager rather than through `initScheduler`. Persists a log\n * entry + updates state so these tasks get the same history / last-run /\n * next-run as system tasks. Because they are fire-and-forget (`startChat`\n * spawns an async chat), `errorMessage` reflects whether the *dispatch*\n * succeeded, not the chat's eventual outcome; `chatSessionId` links the run\n * to the spawned session. */\nexport async function recordExternalRun(params: {\n id: string;\n name: string;\n schedule: TaskDefinition[\"schedule\"];\n scheduledFor: string;\n startedAt: string;\n durationMs: number;\n trigger: TaskTrigger;\n errorMessage: string | null;\n chatSessionId?: string;\n}): Promise<void> {\n await persistRun({\n meta: { id: params.id, name: params.name, schedule: params.schedule },\n scheduledFor: params.scheduledFor,\n startedAt: params.startedAt,\n durationMs: params.durationMs,\n trigger: params.trigger,\n errMsg: params.errorMessage,\n chatSessionId: params.chatSessionId,\n });\n}\n\n/** Test-only: clear config + in-memory state. */\nexport function resetSchedulerForTesting(): void {\n config = null;\n stateMap = new Map();\n systemTasks.length = 0;\n taskManagerRef = null;\n}\n\n// ── Internal ──────────────────────────────────────────────────────\n\nasync function executeAndLog(task: SystemTaskDef, scheduledFor: string, trigger: TaskTrigger): Promise<void> {\n const startedAt = new Date().toISOString();\n const startMs = Date.now();\n let errMsg: string | null = null;\n try {\n await task.run();\n } catch (err) {\n errMsg = errorMessage(err);\n logger().error(\"task failed\", { taskId: task.id, error: errMsg });\n }\n const durationMs = Date.now() - startMs;\n // Persistence is best-effort — never let disk failures propagate to the\n // tick loop or abort startup catch-up.\n await persistRun({ meta: { id: task.id, name: task.name, schedule: task.schedule }, scheduledFor, startedAt, durationMs, trigger, errMsg });\n}\n\n/** The minimum a run needs to persist state + a log entry — shared by system\n * tasks (`executeAndLog`) and external skill/user runs (`recordExternalRun`). */\ninterface TaskRunMeta {\n id: string;\n name: string;\n schedule: TaskDefinition[\"schedule\"];\n}\n\ninterface RunRecord {\n meta: TaskRunMeta;\n scheduledFor: string;\n startedAt: string;\n durationMs: number;\n trigger: TaskTrigger;\n errMsg: string | null;\n chatSessionId?: string;\n}\n\n/** Best-effort persistence — state and log are independent, so one failing\n * never blocks the other and neither propagates upward. */\nasync function persistRun(run: RunRecord): Promise<void> {\n await writeRunState(run);\n await writeRunLog(run);\n}\n\nasync function writeRunState(run: RunRecord): Promise<void> {\n const { meta, scheduledFor, durationMs, errMsg } = run;\n const isSuccess = errMsg === null;\n const currentState = stateMap.get(meta.id);\n try {\n await updateAndSave(\n stateFilePath(),\n stateMap,\n meta.id,\n {\n lastRunAt: scheduledFor,\n lastRunResult: isSuccess ? TASK_RESULTS.success : TASK_RESULTS.error,\n lastRunDurationMs: durationMs,\n lastErrorMessage: errMsg,\n consecutiveFailures: isSuccess ? 0 : (currentState?.consecutiveFailures ?? 0) + 1,\n totalRuns: (currentState?.totalRuns ?? 0) + 1,\n nextScheduledAt: computeNextScheduledFor(meta.schedule),\n },\n stateDeps(),\n );\n } catch (err) {\n logger().warn(\"state persistence failed\", { taskId: meta.id, error: String(err) });\n }\n}\n\nasync function writeRunLog(run: RunRecord): Promise<void> {\n const { meta, scheduledFor, startedAt, durationMs, trigger, errMsg, chatSessionId } = run;\n const isSuccess = errMsg === null;\n try {\n await appendLogEntry(\n logsDir(),\n {\n taskId: meta.id,\n taskName: meta.name,\n scheduledFor,\n startedAt,\n completedAt: new Date().toISOString(),\n result: isSuccess ? TASK_RESULTS.success : TASK_RESULTS.error,\n durationMs,\n trigger,\n ...(errMsg !== null && { errorMessage: errMsg }),\n ...(chatSessionId !== undefined && { chatSessionId }),\n },\n logDeps,\n );\n } catch (err) {\n logger().warn(\"log persistence failed\", { taskId: meta.id, error: String(err) });\n }\n}\n\n/** Safe state update — swallows errors. */\nasync function safeUpdateState(taskId: string, patch: Partial<TaskExecutionState>): Promise<void> {\n try {\n await updateAndSave(stateFilePath(), stateMap, taskId, patch, stateDeps());\n } catch (err) {\n logger().warn(\"state update failed\", { taskId, error: String(err) });\n }\n}\n\n/** Compute the window boundary that the current tick belongs to. For\n * scheduled runs, this is the epoch-aligned window — not the wall-clock\n * time of execution. This keeps lastRunAt consistent with catch-up's\n * window-based accounting. */\nfunction computeCurrentWindow(task: SystemTaskDef): string {\n const coreSchedule = toCoreSchedule(task.schedule);\n // The window that just fired is the latest one at or before now.\n const nowMs = Date.now();\n const windowMs = nextWindowAfter(coreSchedule, nowMs - (coreSchedule.type === SCHEDULE_TYPES.interval ? coreSchedule.intervalSec * ONE_SECOND_MS : 0));\n return windowMs !== null && windowMs <= nowMs ? new Date(windowMs).toISOString() : new Date(nowMs).toISOString();\n}\n\nfunction computeNextScheduledFor(schedule: TaskDefinition[\"schedule\"]): string | null {\n const coreSchedule = toCoreSchedule(schedule);\n const next = nextWindowAfter(coreSchedule, Date.now() + 1);\n return next !== null ? new Date(next).toISOString() : null;\n}\n\nfunction toCoreSchedule(schedule: TaskDefinition[\"schedule\"]): TaskSchedule {\n if (schedule.type === SCHEDULE_TYPES.interval) {\n return {\n type: SCHEDULE_TYPES.interval,\n intervalSec: Math.round(schedule.intervalMs / ONE_SECOND_MS),\n };\n }\n return schedule;\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
export { createTaskManager, type ITaskManager, type TaskDefinition, type TaskSchedule, type TaskRunContext, type TaskManagerOptions, type SchedulerLogger, } from './task-manager.js';
|
|
2
|
-
export { configureScheduler, initScheduler, applyScheduleOverride, getSchedulerLogs, getSchedulerTasks, resetSchedulerForTesting, type SchedulerConfig, type SystemTaskDef, } from './adapter.js';
|
|
2
|
+
export { configureScheduler, initScheduler, applyScheduleOverride, getSchedulerLogs, getSchedulerTasks, getSchedulerTaskState, recordExternalRun, resetSchedulerForTesting, type SchedulerConfig, type SystemTaskDef, } from './adapter.js';
|
|
3
|
+
export { TASK_TRIGGERS, type TaskTrigger } from '@receptron/task-scheduler';
|