@integrity-labs/agt-cli 0.6.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,181 @@
1
+ // src/lib/claude-scheduler.ts
2
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
3
+ import { join } from "path";
4
+ import { homedir } from "os";
5
+ import { Cron } from "croner";
6
+ function parseIntervalMs(scheduleEvery) {
7
+ if (!scheduleEvery) return 60 * 6e4;
8
+ const match = scheduleEvery.match(/^(\d+)\s*(m|min|h|hr|d)$/i);
9
+ if (!match) return 60 * 6e4;
10
+ const value = parseInt(match[1], 10);
11
+ const unit = match[2].toLowerCase();
12
+ if (unit === "h" || unit === "hr") return value * 60 * 6e4;
13
+ if (unit === "d") return value * 24 * 60 * 6e4;
14
+ return value * 6e4;
15
+ }
16
+ function computeNextFire(kind, expr, every, at, timezone, afterMs) {
17
+ const now = afterMs ?? Date.now();
18
+ if (kind === "cron" && expr) {
19
+ try {
20
+ const cron = new Cron(expr, { timezone: timezone || void 0 });
21
+ const next = cron.nextRun(new Date(now));
22
+ return next ? next.getTime() : null;
23
+ } catch {
24
+ return null;
25
+ }
26
+ }
27
+ if (kind === "every") {
28
+ const intervalMs = parseIntervalMs(every);
29
+ return now + intervalMs;
30
+ }
31
+ if (kind === "at" && at) {
32
+ const ts = new Date(at).getTime();
33
+ return isNaN(ts) ? null : ts;
34
+ }
35
+ return null;
36
+ }
37
+ function getStateDir(codeName) {
38
+ return join(homedir(), ".augmented", codeName, "claudecode");
39
+ }
40
+ function getStatePath(codeName) {
41
+ return join(getStateDir(codeName), "scheduler-state.json");
42
+ }
43
+ function loadSchedulerState(codeName) {
44
+ const path = getStatePath(codeName);
45
+ if (existsSync(path)) {
46
+ try {
47
+ return JSON.parse(readFileSync(path, "utf-8"));
48
+ } catch {
49
+ }
50
+ }
51
+ return { version: 1, tasks: {}, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
52
+ }
53
+ function saveSchedulerState(codeName, state) {
54
+ const dir = getStateDir(codeName);
55
+ mkdirSync(dir, { recursive: true });
56
+ state.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
57
+ const path = getStatePath(codeName);
58
+ const tmpPath = path + ".tmp";
59
+ writeFileSync(tmpPath, JSON.stringify(state, null, 2));
60
+ renameSync(tmpPath, path);
61
+ }
62
+ function syncTasksToScheduler(codeName, agentId, tasks) {
63
+ const state = loadSchedulerState(codeName);
64
+ const desiredIds = new Set(tasks.map((t) => t.id));
65
+ for (const id of Object.keys(state.tasks)) {
66
+ if (!desiredIds.has(id)) {
67
+ delete state.tasks[id];
68
+ }
69
+ }
70
+ for (const t of tasks) {
71
+ const existing = state.tasks[t.id];
72
+ if (existing) {
73
+ existing.name = t.name;
74
+ existing.templateId = t.template_id;
75
+ existing.scheduleKind = t.schedule_kind;
76
+ existing.scheduleExpr = t.schedule_expr;
77
+ existing.scheduleEvery = t.schedule_every;
78
+ existing.scheduleAt = t.schedule_at;
79
+ existing.timezone = t.timezone;
80
+ existing.prompt = t.prompt;
81
+ existing.sessionTarget = t.session_target;
82
+ existing.deliveryMode = t.delivery_mode;
83
+ existing.deliveryChannel = t.delivery_channel;
84
+ existing.deliveryTo = t.delivery_to;
85
+ existing.enabled = t.enabled;
86
+ existing.nextFireAt = computeNextFire(
87
+ t.schedule_kind,
88
+ t.schedule_expr,
89
+ t.schedule_every,
90
+ t.schedule_at,
91
+ t.timezone,
92
+ existing.lastFireAt ?? void 0
93
+ );
94
+ } else {
95
+ state.tasks[t.id] = {
96
+ taskId: t.id,
97
+ templateId: t.template_id,
98
+ name: t.name,
99
+ agentCodeName: codeName,
100
+ agentId,
101
+ scheduleKind: t.schedule_kind,
102
+ scheduleExpr: t.schedule_expr,
103
+ scheduleEvery: t.schedule_every,
104
+ scheduleAt: t.schedule_at,
105
+ timezone: t.timezone,
106
+ prompt: t.prompt,
107
+ sessionTarget: t.session_target,
108
+ deliveryMode: t.delivery_mode,
109
+ deliveryChannel: t.delivery_channel,
110
+ deliveryTo: t.delivery_to,
111
+ enabled: t.enabled,
112
+ nextFireAt: computeNextFire(
113
+ t.schedule_kind,
114
+ t.schedule_expr,
115
+ t.schedule_every,
116
+ t.schedule_at,
117
+ t.timezone
118
+ ),
119
+ lastFireAt: null,
120
+ lastStatus: null,
121
+ firedCount: 0
122
+ };
123
+ }
124
+ }
125
+ saveSchedulerState(codeName, state);
126
+ return state;
127
+ }
128
+ function getReadyTasks(state) {
129
+ const now = Date.now();
130
+ const ready = Object.values(state.tasks).filter(
131
+ (t) => t.enabled && t.nextFireAt !== null && t.nextFireAt <= now
132
+ );
133
+ const seen = /* @__PURE__ */ new Set();
134
+ return ready.filter((t) => {
135
+ if (seen.has(t.templateId)) return false;
136
+ seen.add(t.templateId);
137
+ return true;
138
+ });
139
+ }
140
+ function markTaskFired(codeName, taskId, status) {
141
+ const state = loadSchedulerState(codeName);
142
+ const task = state.tasks[taskId];
143
+ if (!task) return state;
144
+ task.lastFireAt = Date.now();
145
+ task.lastStatus = status;
146
+ task.firedCount++;
147
+ if (task.scheduleKind === "at") {
148
+ task.nextFireAt = null;
149
+ } else {
150
+ task.nextFireAt = computeNextFire(
151
+ task.scheduleKind,
152
+ task.scheduleExpr,
153
+ task.scheduleEvery,
154
+ task.scheduleAt,
155
+ task.timezone,
156
+ task.lastFireAt
157
+ );
158
+ }
159
+ saveSchedulerState(codeName, state);
160
+ return state;
161
+ }
162
+ function findTaskByTemplate(state, templateId) {
163
+ return Object.values(state.tasks).find(
164
+ (t) => t.templateId === templateId && t.enabled
165
+ );
166
+ }
167
+ function getProjectDir(codeName) {
168
+ return join(homedir(), ".augmented", codeName, "project");
169
+ }
170
+
171
+ export {
172
+ computeNextFire,
173
+ loadSchedulerState,
174
+ saveSchedulerState,
175
+ syncTasksToScheduler,
176
+ getReadyTasks,
177
+ markTaskFired,
178
+ findTaskByTemplate,
179
+ getProjectDir
180
+ };
181
+ //# sourceMappingURL=chunk-4I4QZRBQ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/claude-scheduler.ts"],"sourcesContent":["/**\n * In-process scheduler for Claude Code agents.\n *\n * On each manager poll cycle, checks if any tasks are due and fires them\n * via `claude -p`. State persists to disk so nextFireAt/lastFireAt survive\n * manager restarts.\n */\n\nimport { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\nimport { Cron } from 'croner';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface SchedulerTaskState {\n taskId: string;\n templateId: string;\n name: string;\n agentCodeName: string;\n agentId: string;\n scheduleKind: 'cron' | 'every' | 'at';\n scheduleExpr: string | null;\n scheduleEvery: string | null;\n scheduleAt: string | null;\n timezone: string;\n prompt: string;\n sessionTarget: string;\n deliveryMode: string;\n deliveryChannel: string | null;\n deliveryTo: string | null;\n enabled: boolean;\n nextFireAt: number | null; // epoch ms, null = completed one-shot\n lastFireAt: number | null;\n lastStatus: 'ok' | 'error' | null;\n firedCount: number;\n}\n\nexport interface SchedulerState {\n version: 1;\n tasks: Record<string, SchedulerTaskState>;\n updatedAt: string;\n}\n\nexport interface SchedulerTaskInput {\n id: string;\n template_id: string;\n name: string;\n schedule_kind: 'cron' | 'every' | 'at';\n schedule_expr: string | null;\n schedule_every: string | null;\n schedule_at: string | null;\n timezone: string;\n prompt: string;\n session_target: string;\n delivery_mode: string;\n delivery_channel: string | null;\n delivery_to: string | null;\n enabled: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Interval parsing (reused from Claude Code adapter)\n// ---------------------------------------------------------------------------\n\nfunction parseIntervalMs(scheduleEvery: string | null): number {\n if (!scheduleEvery) return 60 * 60_000; // 1hr default\n const match = scheduleEvery.match(/^(\\d+)\\s*(m|min|h|hr|d)$/i);\n if (!match) return 60 * 60_000;\n const value = parseInt(match[1]!, 10);\n const unit = match[2]!.toLowerCase();\n if (unit === 'h' || unit === 'hr') return value * 60 * 60_000;\n if (unit === 'd') return value * 24 * 60 * 60_000;\n return value * 60_000; // minutes\n}\n\n// ---------------------------------------------------------------------------\n// Next-fire computation\n// ---------------------------------------------------------------------------\n\nexport function computeNextFire(\n kind: 'cron' | 'every' | 'at',\n expr: string | null,\n every: string | null,\n at: string | null,\n timezone: string,\n afterMs?: number,\n): number | null {\n const now = afterMs ?? Date.now();\n\n if (kind === 'cron' && expr) {\n try {\n const cron = new Cron(expr, { timezone: timezone || undefined });\n const next = cron.nextRun(new Date(now));\n return next ? next.getTime() : null;\n } catch {\n return null;\n }\n }\n\n if (kind === 'every') {\n const intervalMs = parseIntervalMs(every);\n return now + intervalMs;\n }\n\n if (kind === 'at' && at) {\n const ts = new Date(at).getTime();\n return isNaN(ts) ? null : ts;\n }\n\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// State persistence\n// ---------------------------------------------------------------------------\n\nfunction getStateDir(codeName: string): string {\n return join(homedir(), '.augmented', codeName, 'claudecode');\n}\n\nfunction getStatePath(codeName: string): string {\n return join(getStateDir(codeName), 'scheduler-state.json');\n}\n\nexport function loadSchedulerState(codeName: string): SchedulerState {\n const path = getStatePath(codeName);\n if (existsSync(path)) {\n try {\n return JSON.parse(readFileSync(path, 'utf-8'));\n } catch { /* corrupted — start fresh */ }\n }\n return { version: 1, tasks: {}, updatedAt: new Date().toISOString() };\n}\n\nexport function saveSchedulerState(codeName: string, state: SchedulerState): void {\n const dir = getStateDir(codeName);\n mkdirSync(dir, { recursive: true });\n state.updatedAt = new Date().toISOString();\n const path = getStatePath(codeName);\n const tmpPath = path + '.tmp';\n writeFileSync(tmpPath, JSON.stringify(state, null, 2));\n renameSync(tmpPath, path);\n}\n\n// ---------------------------------------------------------------------------\n// Sync API tasks → scheduler state\n// ---------------------------------------------------------------------------\n\nexport function syncTasksToScheduler(\n codeName: string,\n agentId: string,\n tasks: SchedulerTaskInput[],\n): SchedulerState {\n const state = loadSchedulerState(codeName);\n const desiredIds = new Set(tasks.map((t) => t.id));\n\n // Remove tasks no longer in API\n for (const id of Object.keys(state.tasks)) {\n if (!desiredIds.has(id)) {\n delete state.tasks[id];\n }\n }\n\n // Add or update tasks\n for (const t of tasks) {\n const existing = state.tasks[t.id];\n if (existing) {\n // Update mutable fields, preserve fire history\n existing.name = t.name;\n existing.templateId = t.template_id;\n existing.scheduleKind = t.schedule_kind;\n existing.scheduleExpr = t.schedule_expr;\n existing.scheduleEvery = t.schedule_every;\n existing.scheduleAt = t.schedule_at;\n existing.timezone = t.timezone;\n existing.prompt = t.prompt;\n existing.sessionTarget = t.session_target;\n existing.deliveryMode = t.delivery_mode;\n existing.deliveryChannel = t.delivery_channel;\n existing.deliveryTo = t.delivery_to;\n existing.enabled = t.enabled;\n // Recompute next fire from last fire (or now if never fired)\n existing.nextFireAt = computeNextFire(\n t.schedule_kind, t.schedule_expr, t.schedule_every, t.schedule_at,\n t.timezone, existing.lastFireAt ?? undefined,\n );\n } else {\n // New task\n state.tasks[t.id] = {\n taskId: t.id,\n templateId: t.template_id,\n name: t.name,\n agentCodeName: codeName,\n agentId,\n scheduleKind: t.schedule_kind,\n scheduleExpr: t.schedule_expr,\n scheduleEvery: t.schedule_every,\n scheduleAt: t.schedule_at,\n timezone: t.timezone,\n prompt: t.prompt,\n sessionTarget: t.session_target,\n deliveryMode: t.delivery_mode,\n deliveryChannel: t.delivery_channel,\n deliveryTo: t.delivery_to,\n enabled: t.enabled,\n nextFireAt: computeNextFire(\n t.schedule_kind, t.schedule_expr, t.schedule_every, t.schedule_at, t.timezone,\n ),\n lastFireAt: null,\n lastStatus: null,\n firedCount: 0,\n };\n }\n }\n\n saveSchedulerState(codeName, state);\n return state;\n}\n\n// ---------------------------------------------------------------------------\n// Ready-task detection\n// ---------------------------------------------------------------------------\n\nexport function getReadyTasks(state: SchedulerState): SchedulerTaskState[] {\n const now = Date.now();\n const ready = Object.values(state.tasks).filter(\n (t) => t.enabled && t.nextFireAt !== null && t.nextFireAt <= now,\n );\n // Deduplicate by templateId — only fire one task per template per cycle\n const seen = new Set<string>();\n return ready.filter((t) => {\n if (seen.has(t.templateId)) return false;\n seen.add(t.templateId);\n return true;\n });\n}\n\n// ---------------------------------------------------------------------------\n// Post-execution state update\n// ---------------------------------------------------------------------------\n\nexport function markTaskFired(\n codeName: string,\n taskId: string,\n status: 'ok' | 'error',\n): SchedulerState {\n const state = loadSchedulerState(codeName);\n const task = state.tasks[taskId];\n if (!task) return state;\n\n task.lastFireAt = Date.now();\n task.lastStatus = status;\n task.firedCount++;\n\n // Compute next fire\n if (task.scheduleKind === 'at') {\n // One-shot — mark as completed\n task.nextFireAt = null;\n } else {\n task.nextFireAt = computeNextFire(\n task.scheduleKind, task.scheduleExpr, task.scheduleEvery, task.scheduleAt,\n task.timezone, task.lastFireAt,\n );\n }\n\n saveSchedulerState(codeName, state);\n return state;\n}\n\n// ---------------------------------------------------------------------------\n// Find a task by template ID (for work triggers)\n// ---------------------------------------------------------------------------\n\nexport function findTaskByTemplate(state: SchedulerState, templateId: string): SchedulerTaskState | undefined {\n return Object.values(state.tasks).find(\n (t) => t.templateId === templateId && t.enabled,\n );\n}\n\nexport function getProjectDir(codeName: string): string {\n return join(homedir(), '.augmented', codeName, 'project');\n}\n"],"mappings":"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","names":[]}