@mulmoclaude/core 0.1.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/assets/helps/billing-clients-worklog.md +215 -0
- package/assets/helps/billing-invoice.md +458 -0
- package/assets/helps/business.md +104 -0
- package/assets/helps/collection-skills.md +810 -0
- package/assets/helps/custom-view.md +433 -0
- package/assets/helps/feeds.md +114 -0
- package/assets/helps/gemini.md +57 -0
- package/assets/helps/github.md +23 -0
- package/assets/helps/guide.md +61 -0
- package/assets/helps/index.md +89 -0
- package/assets/helps/lessons-collection.md +400 -0
- package/assets/helps/mulmoscript.md +249 -0
- package/assets/helps/portfolio-tracker.md +211 -0
- package/assets/helps/presentation-deck.md +828 -0
- package/assets/helps/presenthtml.md +89 -0
- package/assets/helps/sandbox.md +97 -0
- package/assets/helps/spreadsheet.md +43 -0
- package/assets/helps/storyteller.md +101 -0
- package/assets/helps/telegram.md +136 -0
- package/assets/helps/todo-collection.md +140 -0
- package/assets/helps/vocabulary.md +109 -0
- package/assets/helps/wiki.md +168 -0
- package/assets/skills-preset/mc-cooking-coach/SKILL.md +217 -0
- package/assets/skills-preset/mc-library/SKILL.md +188 -0
- package/assets/skills-preset/mc-manage-automations/SKILL.md +119 -0
- package/assets/skills-preset/mc-manage-skills/SKILL.md +141 -0
- package/assets/skills-preset/mc-wiki-deep-lint/SKILL.md +108 -0
- package/assets/skills-preset/mc-wiki-health-check/SKILL.md +61 -0
- package/assets/skills-preset/mc-wiki-ingest/SKILL.md +182 -0
- package/assets/skills-preset/mc-wiki-promote/SKILL.md +175 -0
- package/assets/skills-preset/mc-zenn/SKILL.md +136 -0
- package/dist/chunk-CKQMccvm.cjs +28 -0
- package/dist/collection/core/actionVisible.d.ts +34 -0
- package/dist/collection/core/calendarGrid.d.ts +120 -0
- package/dist/collection/core/deriveAll.d.ts +38 -0
- package/dist/collection/core/derivedFormula.d.ts +18 -0
- package/dist/collection/core/draft.d.ts +18 -0
- package/dist/collection/core/enumColors.d.ts +33 -0
- package/dist/collection/core/errorMessage.d.ts +4 -0
- package/dist/collection/core/itemLabel.d.ts +12 -0
- package/dist/collection/core/presentCollection.d.ts +13 -0
- package/dist/collection/core/promptSafety.d.ts +1 -0
- package/dist/collection/core/schema.d.ts +355 -0
- package/dist/collection/core/shortHexId.d.ts +8 -0
- package/dist/collection/core/sortItems.d.ts +29 -0
- package/dist/collection/core/uiTypes.d.ts +106 -0
- package/dist/collection/index.cjs +793 -0
- package/dist/collection/index.cjs.map +1 -0
- package/dist/collection/index.d.ts +14 -0
- package/dist/collection/index.js +740 -0
- package/dist/collection/index.js.map +1 -0
- package/dist/collection/paths.cjs +44 -0
- package/dist/collection/paths.cjs.map +1 -0
- package/dist/collection/paths.js +41 -0
- package/dist/collection/paths.js.map +1 -0
- package/dist/collection/server/atomic.d.ts +1 -0
- package/dist/collection/server/delete.d.ts +38 -0
- package/dist/collection/server/derive.d.ts +8 -0
- package/dist/collection/server/discoveredCollection.d.ts +18 -0
- package/dist/collection/server/discovery.d.ts +227 -0
- package/dist/collection/server/host.d.ts +77 -0
- package/dist/collection/server/index.cjs +1721 -0
- package/dist/collection/server/index.cjs.map +1 -0
- package/dist/collection/server/index.d.ts +11 -0
- package/dist/collection/server/index.js +1671 -0
- package/dist/collection/server/index.js.map +1 -0
- package/dist/collection/server/io.d.ts +114 -0
- package/dist/collection/server/paths.d.ts +52 -0
- package/dist/collection/server/spawn.d.ts +55 -0
- package/dist/collection/server/templatePath.d.ts +25 -0
- package/dist/collection/server/util.d.ts +3 -0
- package/dist/collection/server/validate.d.ts +19 -0
- package/dist/collection/server/views.d.ts +20 -0
- package/dist/deriveAll-C15OpM3K.cjs +399 -0
- package/dist/deriveAll-C15OpM3K.cjs.map +1 -0
- package/dist/deriveAll-C6BYnpBL.js +364 -0
- package/dist/deriveAll-C6BYnpBL.js.map +1 -0
- package/dist/file-change/index.cjs +72 -0
- package/dist/file-change/index.cjs.map +1 -0
- package/dist/file-change/index.d.ts +43 -0
- package/dist/file-change/index.js +66 -0
- package/dist/file-change/index.js.map +1 -0
- package/dist/notifier/engine.d.ts +72 -0
- package/dist/notifier/index.cjs +484 -0
- package/dist/notifier/index.cjs.map +1 -0
- package/dist/notifier/index.d.ts +3 -0
- package/dist/notifier/index.js +464 -0
- package/dist/notifier/index.js.map +1 -0
- package/dist/notifier/store.d.ts +18 -0
- package/dist/notifier/types.d.ts +118 -0
- package/dist/notifier/validate.d.ts +17 -0
- package/dist/scheduler/adapter.d.ts +48 -0
- package/dist/scheduler/index.cjs +352 -0
- package/dist/scheduler/index.cjs.map +1 -0
- package/dist/scheduler/index.d.ts +2 -0
- package/dist/scheduler/index.js +343 -0
- package/dist/scheduler/index.js.map +1 -0
- package/dist/scheduler/task-manager.d.ts +51 -0
- package/dist/whisper/client.cjs +241 -0
- package/dist/whisper/client.cjs.map +1 -0
- package/dist/whisper/client.d.ts +35 -0
- package/dist/whisper/client.js +239 -0
- package/dist/whisper/client.js.map +1 -0
- package/dist/whisper/ffmpeg.d.ts +6 -0
- package/dist/whisper/index.cjs +433 -0
- package/dist/whisper/index.cjs.map +1 -0
- package/dist/whisper/index.d.ts +5 -0
- package/dist/whisper/index.js +425 -0
- package/dist/whisper/index.js.map +1 -0
- package/dist/whisper/internal.d.ts +11 -0
- package/dist/whisper/models.d.ts +49 -0
- package/dist/whisper/sidecar.d.ts +8 -0
- package/dist/whisper/whisper.d.ts +28 -0
- package/dist/workspace-setup/assets.d.ts +10 -0
- package/dist/workspace-setup/index.d.ts +3 -0
- package/dist/workspace-setup/index.js +556 -0
- package/dist/workspace-setup/index.js.map +1 -0
- package/dist/workspace-setup/slug.d.ts +6 -0
- package/dist/workspace-setup/slug.js +13 -0
- package/dist/workspace-setup/slug.js.map +1 -0
- package/dist/workspace-setup/sync.d.ts +94 -0
- package/package.json +95 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { NotifierFile, NotifierHistoryFile } from './types.js';
|
|
2
|
+
/** Injected atomic JSON writer — the host's `writeJsonAtomic`. */
|
|
3
|
+
export type WriteJson = (filePath: string, data: unknown) => Promise<void>;
|
|
4
|
+
/** Read the active-entries file. Returns an empty store when the file
|
|
5
|
+
* doesn't exist yet (first ever call on a fresh workspace). Any other
|
|
6
|
+
* read or parse failure throws — the caller has to decide whether to
|
|
7
|
+
* surface or recover, since silently treating "malformed file" as
|
|
8
|
+
* "no entries" would lose data. */
|
|
9
|
+
export declare function loadActive(filePath: string): Promise<NotifierFile>;
|
|
10
|
+
/** Write the active-entries file via the injected atomic writer so a
|
|
11
|
+
* half-written file is never visible to readers. The caller serialises
|
|
12
|
+
* writes (engine.ts queues mutations) — this function makes no
|
|
13
|
+
* concurrency guarantees of its own. */
|
|
14
|
+
export declare function saveActive(writeJson: WriteJson, filePath: string, state: NotifierFile): Promise<void>;
|
|
15
|
+
/** Read the history file. Empty array on first run. Same parse-error
|
|
16
|
+
* policy as `loadActive`. */
|
|
17
|
+
export declare function loadHistory(filePath: string): Promise<NotifierHistoryFile>;
|
|
18
|
+
export declare function saveHistory(writeJson: WriteJson, filePath: string, state: NotifierHistoryFile): Promise<void>;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/** Two notification shapes, distinguished by who fires the close call:
|
|
2
|
+
*
|
|
3
|
+
* `fyi` — informational. The host (bell panel) clears it when the
|
|
4
|
+
* user dismisses the row. No deep-link target.
|
|
5
|
+
* `action` — pending obligation. The plugin clears it when the
|
|
6
|
+
* underlying domain state changes (the user paid the tax,
|
|
7
|
+
* viewed the digest, etc.). The bell row navigates to
|
|
8
|
+
* `navigateTarget` on click.
|
|
9
|
+
*
|
|
10
|
+
* The engine reads `lifecycle` only to enforce two publish-time rules
|
|
11
|
+
* (everything downstream — pubsub fan-out, persistence, history — is
|
|
12
|
+
* lifecycle-blind):
|
|
13
|
+
*
|
|
14
|
+
* 1. `action` requires a non-empty `navigateTarget`. Without one,
|
|
15
|
+
* clicking the row does nothing and the entry is a degraded fyi.
|
|
16
|
+
* 2. `action` cannot use `info` severity. A low-priority obligation
|
|
17
|
+
* is incoherent — fyi if it's a ping, `nudge`/`urgent` if it's a
|
|
18
|
+
* real obligation worth a landing page.
|
|
19
|
+
*
|
|
20
|
+
* Both rules are mirrored in the HTTP layer so plugin-runtime callers
|
|
21
|
+
* and HTTP callers hit the same wall. */
|
|
22
|
+
export declare const NOTIFIER_LIFECYCLES: readonly ["fyi", "action"];
|
|
23
|
+
export type NotifierLifecycle = (typeof NOTIFIER_LIFECYCLES)[number];
|
|
24
|
+
/** Severity drives badge color (gray / amber / red, worst-wins) and
|
|
25
|
+
* in a future iteration channel routing. Mostly stored verbatim by
|
|
26
|
+
* the engine; the one engine-visible interaction is the rule that
|
|
27
|
+
* `action` lifecycle cannot pair with `info` severity (see
|
|
28
|
+
* `NotifierLifecycle` above). */
|
|
29
|
+
export declare const NOTIFIER_SEVERITIES: readonly ["info", "nudge", "urgent"];
|
|
30
|
+
export type NotifierSeverity = (typeof NOTIFIER_SEVERITIES)[number];
|
|
31
|
+
export interface NotifierEntry<TPluginData = unknown> {
|
|
32
|
+
/** Engine-assigned UUID. Generated synchronously inside `publish()`
|
|
33
|
+
* so the caller can use it before persistence completes. */
|
|
34
|
+
id: string;
|
|
35
|
+
/** Plugin namespace (e.g. `"encore"`, `"debug__system"`). The
|
|
36
|
+
* engine never inspects it — used only for `listFor()` filtering
|
|
37
|
+
* and as a UI grouping key. */
|
|
38
|
+
pluginPkg: string;
|
|
39
|
+
severity: NotifierSeverity;
|
|
40
|
+
lifecycle?: NotifierLifecycle;
|
|
41
|
+
title: string;
|
|
42
|
+
body?: string;
|
|
43
|
+
/** Optional in-app deep-link target (relative URL). The bell popup
|
|
44
|
+
* routes here on row click, with `¬ificationId=<id>` appended
|
|
45
|
+
* so the landing page can identify which entry to clear. The
|
|
46
|
+
* engine doesn't read this — it's a UI hint stored on the entry. */
|
|
47
|
+
navigateTarget?: string;
|
|
48
|
+
/** Opaque to the engine. Round-trips through JSON unchanged; only
|
|
49
|
+
* the originating plugin's UI knows the shape. */
|
|
50
|
+
pluginData?: TPluginData;
|
|
51
|
+
/** ISO-8601 timestamp set at `publish()` time. */
|
|
52
|
+
createdAt: string;
|
|
53
|
+
}
|
|
54
|
+
/** A history entry — a `NotifierEntry` after it has been cleared or
|
|
55
|
+
* cancelled, with the terminal type and timestamp recorded. The
|
|
56
|
+
* bell popup's "History" section renders these read-only. */
|
|
57
|
+
export interface NotifierHistoryEntry<TPluginData = unknown> extends NotifierEntry<TPluginData> {
|
|
58
|
+
terminalType: "cleared" | "cancelled";
|
|
59
|
+
terminalAt: string;
|
|
60
|
+
}
|
|
61
|
+
/** Caller-supplied input for `publish()`. The engine fills in `id`
|
|
62
|
+
* and `createdAt`; everything else flows through verbatim.
|
|
63
|
+
*
|
|
64
|
+
* Two publish-time rules apply to `action` lifecycle (see
|
|
65
|
+
* `NotifierLifecycle`):
|
|
66
|
+
*
|
|
67
|
+
* - `navigateTarget` MUST be a non-empty string.
|
|
68
|
+
* - `severity` MUST NOT be `"info"`.
|
|
69
|
+
*
|
|
70
|
+
* Violations cause `publish()` to throw. Currently expressed as
|
|
71
|
+
* runtime validation rather than a discriminated-union type, so the
|
|
72
|
+
* fields below are all individually optional / loose at the
|
|
73
|
+
* type-level. */
|
|
74
|
+
export interface PublishInput<TPluginData = unknown> {
|
|
75
|
+
pluginPkg: string;
|
|
76
|
+
severity: NotifierSeverity;
|
|
77
|
+
title: string;
|
|
78
|
+
body?: string;
|
|
79
|
+
lifecycle?: NotifierLifecycle;
|
|
80
|
+
navigateTarget?: string;
|
|
81
|
+
pluginData?: TPluginData;
|
|
82
|
+
}
|
|
83
|
+
/** On-disk shape of `~/mulmoclaude/data/notifier/active.json`. Holds
|
|
84
|
+
* only entries that haven't been cleared or cancelled — the file is
|
|
85
|
+
* a snapshot, not an event log. */
|
|
86
|
+
export interface NotifierFile {
|
|
87
|
+
entries: Record<string, NotifierEntry>;
|
|
88
|
+
}
|
|
89
|
+
/** On-disk shape of `~/mulmoclaude/data/notifier/history.json`. Array
|
|
90
|
+
* of terminated entries newest-first, capped at `HISTORY_CAP` with
|
|
91
|
+
* FIFO eviction (push at index 0, slice from the tail). */
|
|
92
|
+
export interface NotifierHistoryFile {
|
|
93
|
+
entries: NotifierHistoryEntry[];
|
|
94
|
+
}
|
|
95
|
+
/** History size cap. The bell popup's History section renders this
|
|
96
|
+
* many entries; older ones fall off when new terminations land. */
|
|
97
|
+
export declare const HISTORY_CAP = 50;
|
|
98
|
+
/** Pub-sub event published on the host's notifier channel after every
|
|
99
|
+
* successful state change. Discriminated union — subscribers switch
|
|
100
|
+
* on `type` to keep TypeScript narrowing the rest of the payload.
|
|
101
|
+
*
|
|
102
|
+
* `updated` carries the post-mutation entry — the receiver swaps
|
|
103
|
+
* the matching `id` in their local active set. Reserved for in-
|
|
104
|
+
* place edits via `updateForPlugin`; no history record is written
|
|
105
|
+
* because the entry is still active, just with refreshed content. */
|
|
106
|
+
export type NotifierEvent = {
|
|
107
|
+
type: "published";
|
|
108
|
+
entry: NotifierEntry;
|
|
109
|
+
} | {
|
|
110
|
+
type: "cleared";
|
|
111
|
+
id: string;
|
|
112
|
+
} | {
|
|
113
|
+
type: "cancelled";
|
|
114
|
+
id: string;
|
|
115
|
+
} | {
|
|
116
|
+
type: "updated";
|
|
117
|
+
entry: NotifierEntry;
|
|
118
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { PublishInput } from './types.js';
|
|
2
|
+
/** Hard caps on publish-input fields. The engine reads each entry on
|
|
3
|
+
* every list/get call (no in-memory cache), so unbounded fields hurt
|
|
4
|
+
* every reader. Caps chosen to be generous for legitimate UX copy
|
|
5
|
+
* while bounding active.json growth: a notification fundamentally is
|
|
6
|
+
* a short blurb, not a document. */
|
|
7
|
+
export declare const NOTIFIER_LIMITS: {
|
|
8
|
+
readonly titleMax: 200;
|
|
9
|
+
readonly bodyMax: 4000;
|
|
10
|
+
readonly navigateTargetMax: 1000;
|
|
11
|
+
readonly pluginDataMaxBytes: number;
|
|
12
|
+
};
|
|
13
|
+
/** Validate a `PublishInput`. Returns `null` if OK, or a
|
|
14
|
+
* human-readable error string. Order matters — shape/size errors are
|
|
15
|
+
* reported before lifecycle/severity coherence errors so the message
|
|
16
|
+
* the caller sees points at the most fundamental problem first. */
|
|
17
|
+
export declare function validatePublishInput(input: PublishInput): string | null;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { TaskExecutionState, TaskLogEntry, MISSED_RUN_POLICIES } from '@receptron/task-scheduler';
|
|
2
|
+
import { ITaskManager, TaskDefinition, SchedulerLogger } from './task-manager.js';
|
|
3
|
+
export interface SchedulerConfig {
|
|
4
|
+
/** Absolute workspace root — state.json + logs hang off it. */
|
|
5
|
+
workspaceRoot: string;
|
|
6
|
+
/** Host atomic file writer (used with `uniqueTmp` for the state file). */
|
|
7
|
+
writeFileAtomic: (filePath: string, content: string, opts: {
|
|
8
|
+
uniqueTmp: boolean;
|
|
9
|
+
}) => Promise<void>;
|
|
10
|
+
/** Optional logger. */
|
|
11
|
+
log?: SchedulerLogger;
|
|
12
|
+
}
|
|
13
|
+
/** Wire the adapter to a host. Call once at startup, before `initScheduler`. */
|
|
14
|
+
export declare function configureScheduler(injected: SchedulerConfig): void;
|
|
15
|
+
export interface SystemTaskDef {
|
|
16
|
+
id: string;
|
|
17
|
+
name: string;
|
|
18
|
+
description: string;
|
|
19
|
+
schedule: TaskDefinition["schedule"];
|
|
20
|
+
missedRunPolicy: typeof MISSED_RUN_POLICIES.skip | typeof MISSED_RUN_POLICIES.runOnce | typeof MISSED_RUN_POLICIES.runAll;
|
|
21
|
+
run: () => Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Initialize the scheduler adapter. Call once at server startup AFTER the
|
|
25
|
+
* task-manager is created but BEFORE `taskManager.start()`.
|
|
26
|
+
*/
|
|
27
|
+
export declare function initScheduler(taskManager: ITaskManager, tasks: SystemTaskDef[]): Promise<void>;
|
|
28
|
+
/** Apply a schedule override to a running system task. Updates the
|
|
29
|
+
* in-memory task definition, the task-manager, and recalculates
|
|
30
|
+
* nextScheduledAt in persisted state. */
|
|
31
|
+
export declare function applyScheduleOverride(taskId: string, schedule: SystemTaskDef["schedule"]): Promise<boolean>;
|
|
32
|
+
/** Query execution logs — used by API routes. */
|
|
33
|
+
export declare function getSchedulerLogs(opts: {
|
|
34
|
+
since?: string;
|
|
35
|
+
taskId?: string;
|
|
36
|
+
limit?: number;
|
|
37
|
+
}): Promise<TaskLogEntry[]>;
|
|
38
|
+
/** Get all task states — used by API routes. */
|
|
39
|
+
export declare function getSchedulerTasks(): {
|
|
40
|
+
id: string;
|
|
41
|
+
name: string;
|
|
42
|
+
description: string;
|
|
43
|
+
schedule: TaskDefinition["schedule"];
|
|
44
|
+
missedRunPolicy: string;
|
|
45
|
+
state: TaskExecutionState;
|
|
46
|
+
}[];
|
|
47
|
+
/** Test-only: clear config + in-memory state. */
|
|
48
|
+
export declare function resetSchedulerForTesting(): void;
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_chunk = require("../chunk-CKQMccvm.cjs");
|
|
3
|
+
let node_path = require("node:path");
|
|
4
|
+
node_path = require_chunk.__toESM(node_path, 1);
|
|
5
|
+
let node_fs = require("node:fs");
|
|
6
|
+
let node_fs_promises = require("node:fs/promises");
|
|
7
|
+
let _receptron_task_scheduler = require("@receptron/task-scheduler");
|
|
8
|
+
//#region src/scheduler/task-manager.ts
|
|
9
|
+
var ONE_SECOND_MS$1 = 1e3;
|
|
10
|
+
var ONE_MINUTE_MS = 60 * ONE_SECOND_MS$1;
|
|
11
|
+
var ONE_HOUR_MS = 60 * ONE_MINUTE_MS;
|
|
12
|
+
var NOOP_LOG$1 = {
|
|
13
|
+
info: () => {},
|
|
14
|
+
warn: () => {},
|
|
15
|
+
error: () => {}
|
|
16
|
+
};
|
|
17
|
+
function isDue(now, schedule, tickMs) {
|
|
18
|
+
if (schedule.type === _receptron_task_scheduler.SCHEDULE_TYPES.interval) {
|
|
19
|
+
const msSinceMidnight = now.getUTCHours() * ONE_HOUR_MS + now.getUTCMinutes() * ONE_MINUTE_MS + now.getUTCSeconds() * ONE_SECOND_MS$1;
|
|
20
|
+
return Math.floor(msSinceMidnight / tickMs) * tickMs % schedule.intervalMs === 0;
|
|
21
|
+
}
|
|
22
|
+
if (schedule.type === _receptron_task_scheduler.SCHEDULE_TYPES.daily) {
|
|
23
|
+
const [hours, minutes] = schedule.time.split(":").map(Number);
|
|
24
|
+
const targetMs = hours * ONE_HOUR_MS + minutes * ONE_MINUTE_MS;
|
|
25
|
+
const msSinceMidnight = now.getUTCHours() * ONE_HOUR_MS + now.getUTCMinutes() * ONE_MINUTE_MS + now.getUTCSeconds() * ONE_SECOND_MS$1;
|
|
26
|
+
return Math.floor(msSinceMidnight / tickMs) * tickMs === targetMs;
|
|
27
|
+
}
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
function createTaskManager(options) {
|
|
31
|
+
const tickMs = options?.tickMs ?? ONE_MINUTE_MS;
|
|
32
|
+
const now = options?.now ?? (() => /* @__PURE__ */ new Date());
|
|
33
|
+
const log = options?.log ?? NOOP_LOG$1;
|
|
34
|
+
const registry = /* @__PURE__ */ new Map();
|
|
35
|
+
let timer = null;
|
|
36
|
+
function collectDueTasks(currentTime) {
|
|
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
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
async tick() {
|
|
91
|
+
await onTick();
|
|
92
|
+
},
|
|
93
|
+
registerTask(def) {
|
|
94
|
+
if (registry.has(def.id)) throw new Error(`[task-manager] Task "${def.id}" is already registered`);
|
|
95
|
+
registry.set(def.id, def);
|
|
96
|
+
log.info("registered", { id: def.id });
|
|
97
|
+
},
|
|
98
|
+
updateSchedule(taskId, schedule) {
|
|
99
|
+
const def = registry.get(taskId);
|
|
100
|
+
if (!def) return false;
|
|
101
|
+
def.schedule = schedule;
|
|
102
|
+
log.info("schedule updated", { id: taskId });
|
|
103
|
+
return true;
|
|
104
|
+
},
|
|
105
|
+
removeTask(taskId) {
|
|
106
|
+
if (registry.delete(taskId)) log.info("removed", { id: taskId });
|
|
107
|
+
},
|
|
108
|
+
start() {
|
|
109
|
+
if (timer) return;
|
|
110
|
+
timer = setInterval(onTick, tickMs);
|
|
111
|
+
log.info("started", { tickMs });
|
|
112
|
+
},
|
|
113
|
+
stop() {
|
|
114
|
+
if (timer) {
|
|
115
|
+
clearInterval(timer);
|
|
116
|
+
timer = null;
|
|
117
|
+
log.info("stopped");
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
listTasks() {
|
|
121
|
+
return [...registry.values()].map((taskDef) => ({
|
|
122
|
+
id: taskDef.id,
|
|
123
|
+
description: taskDef.description,
|
|
124
|
+
schedule: taskDef.schedule,
|
|
125
|
+
dependsOn: taskDef.dependsOn
|
|
126
|
+
}));
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
//#endregion
|
|
131
|
+
//#region src/scheduler/adapter.ts
|
|
132
|
+
var ONE_SECOND_MS = 1e3;
|
|
133
|
+
var SCHEDULER_CONFIG_DIR = "config/scheduler";
|
|
134
|
+
var SCHEDULER_DATA_DIR = "data/scheduler/logs";
|
|
135
|
+
var NOOP_LOG = {
|
|
136
|
+
info: () => {},
|
|
137
|
+
warn: () => {},
|
|
138
|
+
error: () => {}
|
|
139
|
+
};
|
|
140
|
+
var config = null;
|
|
141
|
+
/** Wire the adapter to a host. Call once at startup, before `initScheduler`. */
|
|
142
|
+
function configureScheduler(injected) {
|
|
143
|
+
config = injected;
|
|
144
|
+
}
|
|
145
|
+
function requireConfig() {
|
|
146
|
+
if (!config) throw new Error("scheduler: configureScheduler() not called");
|
|
147
|
+
return config;
|
|
148
|
+
}
|
|
149
|
+
function logger() {
|
|
150
|
+
return config?.log ?? NOOP_LOG;
|
|
151
|
+
}
|
|
152
|
+
function errorMessage(err) {
|
|
153
|
+
return err instanceof Error ? err.message : String(err);
|
|
154
|
+
}
|
|
155
|
+
function stateFilePath() {
|
|
156
|
+
return node_path.default.join(requireConfig().workspaceRoot, SCHEDULER_CONFIG_DIR, "state.json");
|
|
157
|
+
}
|
|
158
|
+
function logsDir() {
|
|
159
|
+
return node_path.default.join(requireConfig().workspaceRoot, SCHEDULER_DATA_DIR);
|
|
160
|
+
}
|
|
161
|
+
function stateDeps() {
|
|
162
|
+
return {
|
|
163
|
+
readFile: (filePath) => (0, node_fs_promises.readFile)(filePath, "utf-8"),
|
|
164
|
+
writeFileAtomic: (filePath, content) => requireConfig().writeFileAtomic(filePath, content, { uniqueTmp: true }),
|
|
165
|
+
exists: node_fs.existsSync
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
var logDeps = {
|
|
169
|
+
appendFile: (filePath, content) => (0, node_fs_promises.appendFile)(filePath, content),
|
|
170
|
+
readFile: (filePath) => (0, node_fs_promises.readFile)(filePath, "utf-8"),
|
|
171
|
+
exists: node_fs.existsSync,
|
|
172
|
+
ensureDir: (directoryPath) => (0, node_fs_promises.mkdir)(directoryPath, { recursive: true }).then(() => {})
|
|
173
|
+
};
|
|
174
|
+
var stateMap = /* @__PURE__ */ new Map();
|
|
175
|
+
var systemTasks = [];
|
|
176
|
+
var taskManagerRef = null;
|
|
177
|
+
/**
|
|
178
|
+
* Initialize the scheduler adapter. Call once at server startup AFTER the
|
|
179
|
+
* task-manager is created but BEFORE `taskManager.start()`.
|
|
180
|
+
*/
|
|
181
|
+
async function initScheduler(taskManager, tasks) {
|
|
182
|
+
await (0, node_fs_promises.mkdir)(node_path.default.dirname(stateFilePath()), { recursive: true });
|
|
183
|
+
await (0, node_fs_promises.mkdir)(logsDir(), { recursive: true });
|
|
184
|
+
stateMap = await (0, _receptron_task_scheduler.loadState)(stateFilePath(), stateDeps());
|
|
185
|
+
systemTasks.length = 0;
|
|
186
|
+
systemTasks.push(...tasks);
|
|
187
|
+
taskManagerRef = taskManager;
|
|
188
|
+
const plan = (0, _receptron_task_scheduler.computeCatchUpPlan)(tasks.map((taskDef) => ({
|
|
189
|
+
id: taskDef.id,
|
|
190
|
+
name: taskDef.name,
|
|
191
|
+
schedule: toCoreSchedule(taskDef.schedule),
|
|
192
|
+
missedRunPolicy: taskDef.missedRunPolicy,
|
|
193
|
+
enabled: true
|
|
194
|
+
})), stateMap, Date.now());
|
|
195
|
+
for (const skip of plan.skipped) {
|
|
196
|
+
logger().info("catch-up skipped", {
|
|
197
|
+
taskId: skip.taskId,
|
|
198
|
+
windows: skip.windowCount
|
|
199
|
+
});
|
|
200
|
+
await safeUpdateState(skip.taskId, { lastRunAt: skip.lastWindow });
|
|
201
|
+
}
|
|
202
|
+
if (plan.runs.length > 0) {
|
|
203
|
+
logger().info("catch-up enqueued", { runs: plan.runs.length });
|
|
204
|
+
for (const run of plan.runs) {
|
|
205
|
+
const task = tasks.find((taskDef) => taskDef.id === run.taskId);
|
|
206
|
+
if (!task) continue;
|
|
207
|
+
await executeAndLog(task, run.context.scheduledFor, _receptron_task_scheduler.TASK_TRIGGERS.catchUp);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
for (const task of tasks) taskManager.registerTask({
|
|
211
|
+
id: task.id,
|
|
212
|
+
description: task.description,
|
|
213
|
+
schedule: task.schedule,
|
|
214
|
+
run: async () => {
|
|
215
|
+
await executeAndLog(task, computeCurrentWindow(task), _receptron_task_scheduler.TASK_TRIGGERS.scheduled);
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
logger().info("initialized", {
|
|
219
|
+
tasks: tasks.map((taskDef) => taskDef.id),
|
|
220
|
+
stateEntries: stateMap.size
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
/** Apply a schedule override to a running system task. Updates the
|
|
224
|
+
* in-memory task definition, the task-manager, and recalculates
|
|
225
|
+
* nextScheduledAt in persisted state. */
|
|
226
|
+
async function applyScheduleOverride(taskId, schedule) {
|
|
227
|
+
const task = systemTasks.find((taskDef) => taskDef.id === taskId);
|
|
228
|
+
if (!task || !taskManagerRef) return false;
|
|
229
|
+
if (!taskManagerRef.updateSchedule(taskId, schedule)) return false;
|
|
230
|
+
task.schedule = schedule;
|
|
231
|
+
await safeUpdateState(taskId, { nextScheduledAt: computeNextScheduled(task) });
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
234
|
+
/** Query execution logs — used by API routes. */
|
|
235
|
+
async function getSchedulerLogs(opts) {
|
|
236
|
+
return (0, _receptron_task_scheduler.queryLog)(logsDir(), opts, logDeps);
|
|
237
|
+
}
|
|
238
|
+
/** Get all task states — used by API routes. */
|
|
239
|
+
function getSchedulerTasks() {
|
|
240
|
+
return systemTasks.map((taskDef) => ({
|
|
241
|
+
id: taskDef.id,
|
|
242
|
+
name: taskDef.name,
|
|
243
|
+
description: taskDef.description,
|
|
244
|
+
schedule: taskDef.schedule,
|
|
245
|
+
missedRunPolicy: taskDef.missedRunPolicy,
|
|
246
|
+
state: stateMap.get(taskDef.id) ?? (0, _receptron_task_scheduler.emptyState)(taskDef.id)
|
|
247
|
+
}));
|
|
248
|
+
}
|
|
249
|
+
/** Test-only: clear config + in-memory state. */
|
|
250
|
+
function resetSchedulerForTesting() {
|
|
251
|
+
config = null;
|
|
252
|
+
stateMap = /* @__PURE__ */ new Map();
|
|
253
|
+
systemTasks.length = 0;
|
|
254
|
+
taskManagerRef = null;
|
|
255
|
+
}
|
|
256
|
+
async function executeAndLog(task, scheduledFor, trigger) {
|
|
257
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
258
|
+
const startMs = Date.now();
|
|
259
|
+
let errMsg = null;
|
|
260
|
+
try {
|
|
261
|
+
await task.run();
|
|
262
|
+
} catch (err) {
|
|
263
|
+
errMsg = errorMessage(err);
|
|
264
|
+
logger().error("task failed", {
|
|
265
|
+
taskId: task.id,
|
|
266
|
+
error: errMsg
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
await safePersist(task, scheduledFor, startedAt, Date.now() - startMs, trigger, errMsg);
|
|
270
|
+
}
|
|
271
|
+
/** Best-effort persistence — state and log are independent. A failure in
|
|
272
|
+
* one does not block the other, and neither propagates upward. */
|
|
273
|
+
async function safePersist(task, scheduledFor, startedAt, durationMs, trigger, errMsg) {
|
|
274
|
+
const isSuccess = errMsg === null;
|
|
275
|
+
const currentState = stateMap.get(task.id);
|
|
276
|
+
try {
|
|
277
|
+
await (0, _receptron_task_scheduler.updateAndSave)(stateFilePath(), stateMap, task.id, {
|
|
278
|
+
lastRunAt: scheduledFor,
|
|
279
|
+
lastRunResult: isSuccess ? _receptron_task_scheduler.TASK_RESULTS.success : _receptron_task_scheduler.TASK_RESULTS.error,
|
|
280
|
+
lastRunDurationMs: durationMs,
|
|
281
|
+
lastErrorMessage: errMsg,
|
|
282
|
+
consecutiveFailures: isSuccess ? 0 : (currentState?.consecutiveFailures ?? 0) + 1,
|
|
283
|
+
totalRuns: (currentState?.totalRuns ?? 0) + 1,
|
|
284
|
+
nextScheduledAt: computeNextScheduled(task)
|
|
285
|
+
}, stateDeps());
|
|
286
|
+
} catch (err) {
|
|
287
|
+
logger().warn("state persistence failed", {
|
|
288
|
+
taskId: task.id,
|
|
289
|
+
error: String(err)
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
try {
|
|
293
|
+
await (0, _receptron_task_scheduler.appendLogEntry)(logsDir(), {
|
|
294
|
+
taskId: task.id,
|
|
295
|
+
taskName: task.name,
|
|
296
|
+
scheduledFor,
|
|
297
|
+
startedAt,
|
|
298
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
299
|
+
result: isSuccess ? _receptron_task_scheduler.TASK_RESULTS.success : _receptron_task_scheduler.TASK_RESULTS.error,
|
|
300
|
+
durationMs,
|
|
301
|
+
trigger,
|
|
302
|
+
...errMsg !== null && { errorMessage: errMsg }
|
|
303
|
+
}, logDeps);
|
|
304
|
+
} catch (err) {
|
|
305
|
+
logger().warn("log persistence failed", {
|
|
306
|
+
taskId: task.id,
|
|
307
|
+
error: String(err)
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
/** Safe state update — swallows errors. */
|
|
312
|
+
async function safeUpdateState(taskId, patch) {
|
|
313
|
+
try {
|
|
314
|
+
await (0, _receptron_task_scheduler.updateAndSave)(stateFilePath(), stateMap, taskId, patch, stateDeps());
|
|
315
|
+
} catch (err) {
|
|
316
|
+
logger().warn("state update failed", {
|
|
317
|
+
taskId,
|
|
318
|
+
error: String(err)
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
/** Compute the window boundary that the current tick belongs to. For
|
|
323
|
+
* scheduled runs, this is the epoch-aligned window — not the wall-clock
|
|
324
|
+
* time of execution. This keeps lastRunAt consistent with catch-up's
|
|
325
|
+
* window-based accounting. */
|
|
326
|
+
function computeCurrentWindow(task) {
|
|
327
|
+
const coreSchedule = toCoreSchedule(task.schedule);
|
|
328
|
+
const nowMs = Date.now();
|
|
329
|
+
const windowMs = (0, _receptron_task_scheduler.nextWindowAfter)(coreSchedule, nowMs - (coreSchedule.type === _receptron_task_scheduler.SCHEDULE_TYPES.interval ? coreSchedule.intervalSec * ONE_SECOND_MS : 0));
|
|
330
|
+
return windowMs !== null && windowMs <= nowMs ? new Date(windowMs).toISOString() : new Date(nowMs).toISOString();
|
|
331
|
+
}
|
|
332
|
+
function computeNextScheduled(task) {
|
|
333
|
+
const next = (0, _receptron_task_scheduler.nextWindowAfter)(toCoreSchedule(task.schedule), Date.now() + 1);
|
|
334
|
+
return next !== null ? new Date(next).toISOString() : null;
|
|
335
|
+
}
|
|
336
|
+
function toCoreSchedule(schedule) {
|
|
337
|
+
if (schedule.type === _receptron_task_scheduler.SCHEDULE_TYPES.interval) return {
|
|
338
|
+
type: _receptron_task_scheduler.SCHEDULE_TYPES.interval,
|
|
339
|
+
intervalSec: Math.round(schedule.intervalMs / ONE_SECOND_MS)
|
|
340
|
+
};
|
|
341
|
+
return schedule;
|
|
342
|
+
}
|
|
343
|
+
//#endregion
|
|
344
|
+
exports.applyScheduleOverride = applyScheduleOverride;
|
|
345
|
+
exports.configureScheduler = configureScheduler;
|
|
346
|
+
exports.createTaskManager = createTaskManager;
|
|
347
|
+
exports.getSchedulerLogs = getSchedulerLogs;
|
|
348
|
+
exports.getSchedulerTasks = getSchedulerTasks;
|
|
349
|
+
exports.initScheduler = initScheduler;
|
|
350
|
+
exports.resetSchedulerForTesting = resetSchedulerForTesting;
|
|
351
|
+
|
|
352
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +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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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
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';
|