@crewx/cron 0.1.10-rc.181 → 0.1.10-rc.182

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.
@@ -1,1702 +1,5 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.DATA_PATHS = exports.CRON_RUNTIME_PROCESS_LEASE_FILENAME = exports.createCronRuntime = exports.getProjectRoot = exports.BUILTIN_SCHEDULES = exports.CronStoreError = void 0;
37
- exports.getLocalTimestamp = getLocalTimestamp;
38
- exports.logUsage = logUsage;
39
- exports.acquireWorkspaceLock = acquireWorkspaceLock;
40
- exports.releaseWorkspaceLock = releaseWorkspaceLock;
41
- exports.migrateLegacyMemoryDebounceSchedule = migrateLegacyMemoryDebounceSchedule;
42
- exports.normalizeMode = normalizeMode;
43
- exports.createScheduleStore = createScheduleStore;
44
- exports.loadSchedules = loadSchedules;
45
- exports.saveSchedules = saveSchedules;
46
- exports.getRemovedBuiltins = getRemovedBuiltins;
47
- exports.seedBuiltinSchedules = seedBuiltinSchedules;
48
- exports.acquireLock = acquireLock;
49
- exports.releaseLock = releaseLock;
50
- exports.releaseRunLock = releaseRunLock;
51
- exports.shouldRunNow = shouldRunNow;
52
- exports.describeCron = describeCron;
53
- exports.calculateNextRun = calculateNextRun;
54
- exports.isDaemonRunning = isDaemonRunning;
55
- exports.startDaemon = startDaemon;
56
- exports.stopDaemon = stopDaemon;
57
- exports.startDaemonSafe = startDaemonSafe;
58
- exports.ensureDaemonRunning = ensureDaemonRunning;
59
- exports.getDaemonStatus = getDaemonStatus;
60
- exports.executeSchedule = executeSchedule;
61
- exports.getLocalMinuteKey = getLocalMinuteKey;
62
- exports.buildOccurrenceKey = buildOccurrenceKey;
63
- exports.daemonTick = daemonTick;
64
- exports.runDaemonForeground = runDaemonForeground;
65
- exports.addSchedule = addSchedule;
66
- exports.removeSchedule = removeSchedule;
67
- exports.enableSchedule = enableSchedule;
68
- exports.disableSchedule = disableSchedule;
69
- exports.updateSchedule = updateSchedule;
70
- const child_process_1 = require("child_process");
71
- const crypto_1 = require("crypto");
72
- const path = __importStar(require("path"));
73
- const fs = __importStar(require("fs"));
74
- const types_1 = require("./types");
75
- Object.defineProperty(exports, "CronStoreError", { enumerable: true, get: function () { return types_1.CronStoreError; } });
76
- const sdk_1 = require("@crewx/sdk");
77
- const builtins_1 = require("./builtins");
78
- Object.defineProperty(exports, "BUILTIN_SCHEDULES", { enumerable: true, get: function () { return builtins_1.BUILTIN_SCHEDULES; } });
79
- Object.defineProperty(exports, "getProjectRoot", { enumerable: true, get: function () { return builtins_1.getProjectRoot; } });
80
- const DEFAULT_WORKSPACE_PATH = path.resolve(process.cwd());
81
- const DATA_DIR = path.join(DEFAULT_WORKSPACE_PATH, '.crewx');
82
- const SCHEDULES_FILE = path.join(DATA_DIR, '.cron-data.json');
83
- const LOG_FILE = path.join(DATA_DIR, '.cron-daemon.log');
84
- const LOCK_FILE = path.join(DATA_DIR, '.cron-data.json.lock');
85
- const USAGE_LOG_FILE = path.join(DATA_DIR, 'cron-usage.log');
86
- const PID_FILE = path.join(DATA_DIR, '.cron-daemon.pid');
87
- const BUILTINS_META_FILE = path.join(DATA_DIR, '.cron-builtins.json');
88
- function resolveDataDir(dir) {
89
- return path.join(path.resolve(dir ?? DEFAULT_WORKSPACE_PATH), '.crewx');
90
- }
91
- function resolveSchedulesFile(dir) {
92
- return path.join(resolveDataDir(dir), '.cron-data.json');
93
- }
94
- function resolveLockFile(dir) {
95
- return path.join(resolveDataDir(dir), '.cron-data.json.lock');
96
- }
97
- function resolveUsageLogFile(dir) {
98
- return path.join(resolveDataDir(dir), 'cron-usage.log');
99
- }
100
- function resolvePidFile(dir) {
101
- return path.join(resolveDataDir(dir), '.cron-daemon.pid');
102
- }
103
- function resolveBuiltinsMetaFile(dir) {
104
- return path.join(resolveDataDir(dir), '.cron-builtins.json');
105
- }
106
- function reserveAdmission(taskId, scheduleId, timeoutMs) {
107
- const reserved = (0, sdk_1.reserveUpdateAdmission)(taskId, scheduleId, timeoutMs);
108
- if (!reserved) {
109
- console.error(`[${getLocalTimestamp()}] Failed to record update-admission reservation for task ${taskId}`);
110
- }
111
- return reserved;
112
- }
113
- function releaseAdmissionReservation(taskId) {
114
- (0, sdk_1.releaseUpdateAdmissionReservation)(taskId);
115
- }
116
- function resolveRunLockFile(scheduleId, dir) {
117
- return path.join(resolveDataDir(dir), `.cron-run-${scheduleId}.lock`);
118
- }
119
- const DAEMON_INTERVAL_MS = 60 * 1000;
120
- const STORE_LOCK_TIMEOUT_MS = 5000;
121
- const STORE_LOCK_POLL_MS = 25;
122
- const STORE_LOCK_STALE_MS = 30000;
123
- const RUN_LOCK_STALE_GRACE_MS = 5000;
124
- function getLocalTimestamp() {
125
- const now = new Date();
126
- const y = now.getFullYear();
127
- const m = String(now.getMonth() + 1).padStart(2, '0');
128
- const d = String(now.getDate()).padStart(2, '0');
129
- const h = String(now.getHours()).padStart(2, '0');
130
- const min = String(now.getMinutes()).padStart(2, '0');
131
- const s = String(now.getSeconds()).padStart(2, '0');
132
- return `${y}-${m}-${d} ${h}:${min}:${s}`;
133
- }
134
- function logUsage(dirOrMessage, ...args) {
135
- const isWorkspacePath = (value) => value !== undefined && (value === '.' ||
136
- value === '..' ||
137
- path.isAbsolute(value) ||
138
- value.includes(path.sep) ||
139
- value.includes('/') ||
140
- value.includes('\\') ||
141
- value.endsWith('.crewx') ||
142
- (() => {
143
- try {
144
- return fs.statSync(value).isDirectory();
145
- }
146
- catch {
147
- return false;
148
- }
149
- })());
150
- let workspacePath;
151
- let messageParts;
152
- if (args.length > 0 && (dirOrMessage === undefined || isWorkspacePath(dirOrMessage))) {
153
- workspacePath = dirOrMessage;
154
- messageParts = args;
155
- }
156
- else if (args.length === 0 && isWorkspacePath(dirOrMessage)) {
157
- workspacePath = dirOrMessage;
158
- messageParts = [];
159
- }
160
- else if (args.length === 1 && isWorkspacePath(args[0])) {
161
- workspacePath = args[0];
162
- messageParts = dirOrMessage === undefined ? [] : [dirOrMessage];
163
- }
164
- else {
165
- workspacePath = undefined;
166
- messageParts = dirOrMessage === undefined ? args : [dirOrMessage, ...args];
167
- }
168
- ensureDataDir(workspacePath);
169
- const timestamp = getLocalTimestamp();
170
- const message = messageParts.join(' ');
171
- const logLine = `${timestamp} | [cron] ${message}\n`;
172
- fs.appendFileSync(resolveUsageLogFile(workspacePath), logLine);
173
- }
174
- function ensureDataDir(dir) {
175
- const dataDir = resolveDataDir(dir);
176
- if (!fs.existsSync(dataDir)) {
177
- fs.mkdirSync(dataDir, { recursive: true });
178
- }
179
- }
180
- function safeReadJsonFile(filePath) {
181
- try {
182
- return JSON.parse(fs.readFileSync(filePath, 'utf8'));
183
- }
184
- catch {
185
- return null;
186
- }
187
- }
188
- const STORE_LOCK_WAIT_BUFFER = new Int32Array(new SharedArrayBuffer(4));
189
- function waitForStoreLock() {
190
- Atomics.wait(STORE_LOCK_WAIT_BUFFER, 0, 0, STORE_LOCK_POLL_MS);
191
- }
192
- function readStoreLock(lockPath) {
193
- try {
194
- const parsed = JSON.parse(fs.readFileSync(lockPath, 'utf8'));
195
- if (typeof parsed.token !== 'string' ||
196
- typeof parsed.pid !== 'number' ||
197
- typeof parsed.created_at !== 'number') {
198
- return null;
199
- }
200
- return parsed;
201
- }
202
- catch {
203
- return null;
204
- }
205
- }
206
- function acquireWorkspaceLock(workspacePath, timeout = STORE_LOCK_TIMEOUT_MS) {
207
- const normalizedWorkspacePath = path.resolve(workspacePath);
208
- ensureDataDir(normalizedWorkspacePath);
209
- const lockPath = resolveLockFile(normalizedWorkspacePath);
210
- const token = (0, crypto_1.randomUUID)();
211
- const deadline = Date.now() + Math.max(0, timeout);
212
- const owner = {
213
- workspacePath: normalizedWorkspacePath,
214
- lockPath,
215
- token,
216
- };
217
- while (true) {
218
- const payload = {
219
- token,
220
- pid: process.pid,
221
- created_at: Date.now(),
222
- };
223
- try {
224
- fs.writeFileSync(lockPath, JSON.stringify(payload), { flag: 'wx', mode: 0o600 });
225
- return owner;
226
- }
227
- catch (error) {
228
- const err = error;
229
- if (err.code !== 'EEXIST')
230
- throw error;
231
- const existing = readStoreLock(lockPath);
232
- if (existing && Date.now() - existing.created_at > STORE_LOCK_STALE_MS) {
233
- releaseWorkspaceLock({ ...owner, token: existing.token });
234
- continue;
235
- }
236
- if (Date.now() >= deadline) {
237
- throw new types_1.CronStoreError('CRON_STORE_LOCK_TIMEOUT', `Timed out waiting for cron store lock: ${lockPath}`, lockPath);
238
- }
239
- waitForStoreLock();
240
- }
241
- }
242
- }
243
- function releaseWorkspaceLock(owner) {
244
- const current = readStoreLock(owner.lockPath);
245
- if (!current || current.token !== owner.token)
246
- return false;
247
- try {
248
- fs.unlinkSync(owner.lockPath);
249
- return true;
250
- }
251
- catch (error) {
252
- const err = error;
253
- if (err.code === 'ENOENT')
254
- return false;
255
- throw error;
256
- }
257
- }
258
- const MEMORY_DEBOUNCE_BUILTIN = (0, builtins_1.findBuiltinById)('memory-debounce');
259
- function maybeRewriteLegacyMemoryCommand(command) {
260
- if (!command.includes('/skills/memory-v2/memory-v2.js')) {
261
- return command;
262
- }
263
- return MEMORY_DEBOUNCE_BUILTIN.buildCommand();
264
- }
265
- function migrateLegacyMemoryDebounceSchedule(workspacePath = (0, builtins_1.getProjectRoot)()) {
266
- const normalizedWorkspacePath = path.resolve(workspacePath);
267
- const legacyFile = path.join(normalizedWorkspacePath, 'skills', 'cron', '.cron-data.json');
268
- if (!fs.existsSync(legacyFile)) {
269
- return;
270
- }
271
- const legacySchedules = safeReadJsonFile(legacyFile);
272
- if (!Array.isArray(legacySchedules)) {
273
- return;
274
- }
275
- const legacySchedule = legacySchedules.find(s => s.name === MEMORY_DEBOUNCE_BUILTIN.name);
276
- if (!legacySchedule) {
277
- return;
278
- }
279
- const store = createScheduleStore(normalizedWorkspacePath);
280
- const targetSchedules = store.list();
281
- if (targetSchedules.some(s => s.name === MEMORY_DEBOUNCE_BUILTIN.name)) {
282
- return;
283
- }
284
- const migratedSchedule = {
285
- ...legacySchedule,
286
- command: maybeRewriteLegacyMemoryCommand(legacySchedule.command),
287
- updated_at: new Date().toISOString(),
288
- next_run: calculateNextRun(legacySchedule.cron, legacySchedule.timezone),
289
- };
290
- store.create(migratedSchedule);
291
- }
292
- function warnIfOverdriveInCommand(command) {
293
- if (command.includes('--overdrive')) {
294
- console.warn('warning: --overdrive in cron command is ignored and may silently override --model');
295
- }
296
- }
297
- function normalizeMode(mode) {
298
- if (mode === 'query')
299
- return 'query';
300
- if (mode === 'command')
301
- return 'command';
302
- return 'execute';
303
- }
304
- function cloneSchedule(schedule) {
305
- return {
306
- ...schedule,
307
- ...(schedule.env ? { env: { ...schedule.env } } : {}),
308
- };
309
- }
310
- function readSchedulesFile(schedulesFile) {
311
- let data;
312
- try {
313
- data = fs.readFileSync(schedulesFile, 'utf8');
314
- }
315
- catch (error) {
316
- const err = error;
317
- if (err.code === 'ENOENT')
318
- return [];
319
- throw error;
320
- }
321
- let parsed;
322
- try {
323
- parsed = JSON.parse(data);
324
- }
325
- catch (error) {
326
- throw new types_1.CronStoreError('CRON_STORE_CORRUPT', `Cron schedule store is not valid JSON: ${schedulesFile}`, schedulesFile);
327
- }
328
- if (!Array.isArray(parsed)) {
329
- throw new types_1.CronStoreError('CRON_STORE_CORRUPT', `Cron schedule store must contain a top-level array: ${schedulesFile}`, schedulesFile);
330
- }
331
- return parsed;
332
- }
333
- function syncDirectory(directory) {
334
- if (process.platform === 'win32')
335
- return;
336
- try {
337
- const fd = fs.openSync(directory, 'r');
338
- try {
339
- fs.fsyncSync(fd);
340
- }
341
- finally {
342
- fs.closeSync(fd);
343
- }
344
- }
345
- catch {
346
- }
347
- }
348
- function writeSchedulesAtomic(schedulesFile, schedules) {
349
- const directory = path.dirname(schedulesFile);
350
- const tempFile = path.join(directory, `.${path.basename(schedulesFile)}.${process.pid}.${(0, crypto_1.randomUUID)()}.tmp`);
351
- const contents = JSON.stringify(schedules, null, 2);
352
- let fd;
353
- try {
354
- fd = fs.openSync(tempFile, 'wx', 0o600);
355
- fs.writeFileSync(fd, contents);
356
- fs.fsyncSync(fd);
357
- fs.closeSync(fd);
358
- fd = undefined;
359
- fs.renameSync(tempFile, schedulesFile);
360
- syncDirectory(directory);
361
- }
362
- finally {
363
- if (fd !== undefined) {
364
- try {
365
- fs.closeSync(fd);
366
- }
367
- catch {
368
- }
369
- }
370
- try {
371
- fs.unlinkSync(tempFile);
372
- }
373
- catch {
374
- }
375
- }
376
- }
377
- function requireSchedule(schedules, id) {
378
- const schedule = schedules.find(item => item.id === id);
379
- if (!schedule) {
380
- throw new types_1.CronStoreError('CRON_SCHEDULE_NOT_FOUND', `Schedule not found: ${id}`);
381
- }
382
- return schedule;
383
- }
384
- function withStoreLock(workspacePath, operation) {
385
- const lock = acquireWorkspaceLock(workspacePath);
386
- try {
387
- const schedulesFile = resolveSchedulesFile(workspacePath);
388
- const schedules = readSchedulesFile(schedulesFile);
389
- const result = operation(schedules);
390
- writeSchedulesAtomic(schedulesFile, schedules);
391
- return result;
392
- }
393
- finally {
394
- releaseWorkspaceLock(lock);
395
- }
396
- }
397
- function createScheduleStore(workspacePath = DEFAULT_WORKSPACE_PATH) {
398
- const normalizedWorkspacePath = path.resolve(workspacePath);
399
- const dataDir = resolveDataDir(normalizedWorkspacePath);
400
- const schedulesFile = resolveSchedulesFile(normalizedWorkspacePath);
401
- const lockFile = resolveLockFile(normalizedWorkspacePath);
402
- return {
403
- workspacePath: normalizedWorkspacePath,
404
- dataDir,
405
- schedulesFile,
406
- lockFile,
407
- list() {
408
- return readSchedulesFile(schedulesFile).map(cloneSchedule);
409
- },
410
- get(id) {
411
- const schedule = readSchedulesFile(schedulesFile).find(item => item.id === id);
412
- return schedule ? cloneSchedule(schedule) : undefined;
413
- },
414
- create(schedule) {
415
- return withStoreLock(normalizedWorkspacePath, schedules => {
416
- if (schedules.some(item => item.id === schedule.id)) {
417
- throw new types_1.CronStoreError('CRON_SCHEDULE_EXISTS', `Schedule already exists: ${schedule.id}`);
418
- }
419
- schedules.push(cloneSchedule(schedule));
420
- return cloneSchedule(schedule);
421
- });
422
- },
423
- update(id, changes) {
424
- return withStoreLock(normalizedWorkspacePath, schedules => {
425
- const schedule = requireSchedule(schedules, id);
426
- if (changes.cron !== undefined)
427
- schedule.cron = changes.cron;
428
- if (changes.command !== undefined)
429
- schedule.command = changes.command;
430
- if (changes.mode !== undefined)
431
- schedule.mode = changes.mode;
432
- if (changes.enabled !== undefined)
433
- schedule.enabled = changes.enabled;
434
- if (changes.name !== undefined)
435
- schedule.name = changes.name;
436
- if (changes.working_directory !== undefined)
437
- schedule.working_directory = changes.working_directory;
438
- if (changes.timezone !== undefined)
439
- schedule.timezone = changes.timezone;
440
- if (changes.timeout_ms !== undefined)
441
- schedule.timeout_ms = changes.timeout_ms;
442
- if (changes.allow_concurrent !== undefined)
443
- schedule.allow_concurrent = changes.allow_concurrent;
444
- if (changes.env !== undefined) {
445
- if (changes.env === null)
446
- delete schedule.env;
447
- else
448
- schedule.env = { ...changes.env };
449
- }
450
- if (changes.model !== undefined) {
451
- if (changes.model === null)
452
- delete schedule.model;
453
- else
454
- schedule.model = changes.model;
455
- }
456
- if (changes.provider !== undefined) {
457
- if (changes.provider === null)
458
- delete schedule.provider;
459
- else
460
- schedule.provider = changes.provider;
461
- }
462
- schedule.updated_at = new Date().toISOString();
463
- schedule.next_run = calculateNextRun(schedule.cron, schedule.timezone);
464
- return cloneSchedule(schedule);
465
- });
466
- },
467
- remove(id) {
468
- return withStoreLock(normalizedWorkspacePath, schedules => {
469
- const index = schedules.findIndex(item => item.id === id);
470
- if (index === -1) {
471
- throw new types_1.CronStoreError('CRON_SCHEDULE_NOT_FOUND', `Schedule not found: ${id}`);
472
- }
473
- const [removed] = schedules.splice(index, 1);
474
- return cloneSchedule(removed);
475
- });
476
- },
477
- toggle(id, enabled) {
478
- return withStoreLock(normalizedWorkspacePath, schedules => {
479
- const schedule = requireSchedule(schedules, id);
480
- schedule.enabled = enabled ?? !schedule.enabled;
481
- schedule.updated_at = new Date().toISOString();
482
- if (schedule.enabled) {
483
- schedule.next_run = calculateNextRun(schedule.cron, schedule.timezone);
484
- }
485
- return cloneSchedule(schedule);
486
- });
487
- },
488
- claimOccurrence(id, options) {
489
- return withStoreLock(normalizedWorkspacePath, schedules => {
490
- const schedule = requireSchedule(schedules, typeof id === 'string' ? id : id.id);
491
- if (schedule.last_occurrence_key === options.occurrenceKey) {
492
- return {
493
- claimed: false,
494
- schedule: cloneSchedule(schedule),
495
- };
496
- }
497
- const startedAt = options.startedAt ?? new Date().toISOString();
498
- schedule.last_started_at = startedAt;
499
- schedule.last_occurrence_key = options.occurrenceKey;
500
- schedule.updated_at = startedAt;
501
- return {
502
- claimed: true,
503
- schedule: cloneSchedule(schedule),
504
- };
505
- });
506
- },
507
- recordStarted(id, options) {
508
- return withStoreLock(normalizedWorkspacePath, schedules => {
509
- const schedule = requireSchedule(schedules, typeof id === 'string' ? id : id.id);
510
- const normalizedOptions = typeof options === 'string' ? { startedAt: options } : options;
511
- const startedAt = normalizedOptions?.startedAt ?? new Date().toISOString();
512
- schedule.last_started_at = startedAt;
513
- if (normalizedOptions?.occurrenceKey !== undefined) {
514
- schedule.last_occurrence_key = normalizedOptions.occurrenceKey;
515
- }
516
- schedule.updated_at = startedAt;
517
- return cloneSchedule(schedule);
518
- });
519
- },
520
- recordFinished(id, options) {
521
- return withStoreLock(normalizedWorkspacePath, schedules => {
522
- const schedule = requireSchedule(schedules, typeof id === 'string' ? id : id.id);
523
- const finishedAt = options?.finishedAt ?? new Date().toISOString();
524
- schedule.last_run = finishedAt;
525
- schedule.run_count = (schedule.run_count || 0) + 1;
526
- schedule.next_run = options?.nextRun !== undefined
527
- ? options.nextRun
528
- : calculateNextRun(schedule.cron, schedule.timezone);
529
- if (options?.outcome !== undefined)
530
- schedule.last_outcome = options.outcome;
531
- else if (options?.success !== undefined)
532
- schedule.last_outcome = options.success ? 'success' : 'failure';
533
- schedule.updated_at = finishedAt;
534
- return cloneSchedule(schedule);
535
- });
536
- },
537
- };
538
- }
539
- function loadSchedules(dir) {
540
- return createScheduleStore(dir ?? DEFAULT_WORKSPACE_PATH).list();
541
- }
542
- function saveSchedules(schedules, dir) {
543
- const workspacePath = path.resolve(dir ?? DEFAULT_WORKSPACE_PATH);
544
- const lock = acquireWorkspaceLock(workspacePath);
545
- try {
546
- const schedulesFile = resolveSchedulesFile(workspacePath);
547
- readSchedulesFile(schedulesFile);
548
- writeSchedulesAtomic(schedulesFile, schedules.map(cloneSchedule));
549
- }
550
- finally {
551
- releaseWorkspaceLock(lock);
552
- }
553
- }
554
- function loadBuiltinsMeta(dir) {
555
- const data = safeReadJsonFile(resolveBuiltinsMetaFile(dir));
556
- return { removed_builtins: Array.isArray(data?.removed_builtins) ? data.removed_builtins : [] };
557
- }
558
- function saveBuiltinsMeta(meta, dir) {
559
- ensureDataDir(dir);
560
- const metaFile = resolveBuiltinsMetaFile(dir);
561
- const tempFile = `${metaFile}.tmp`;
562
- fs.writeFileSync(tempFile, JSON.stringify(meta, null, 2));
563
- fs.renameSync(tempFile, metaFile);
564
- }
565
- function getRemovedBuiltins(dir) {
566
- return loadBuiltinsMeta(dir).removed_builtins;
567
- }
568
- function recordRemovedBuiltin(builtinId, dir) {
569
- const meta = loadBuiltinsMeta(dir);
570
- if (!meta.removed_builtins.includes(builtinId)) {
571
- meta.removed_builtins.push(builtinId);
572
- saveBuiltinsMeta(meta, dir);
573
- }
574
- }
575
- function seedBuiltinSchedules(dir) {
576
- const store = createScheduleStore(dir ?? DEFAULT_WORKSPACE_PATH);
577
- const schedules = store.list();
578
- const existingNames = new Set(schedules.map(s => s.name));
579
- const removedBuiltins = new Set(getRemovedBuiltins(dir));
580
- const seeded = [];
581
- for (const def of builtins_1.BUILTIN_SCHEDULES) {
582
- if (existingNames.has(def.name))
583
- continue;
584
- if (removedBuiltins.has(def.builtin_id))
585
- continue;
586
- const now = new Date().toISOString();
587
- const schedule = {
588
- id: (0, sdk_1.generateId)('cron'),
589
- cron: def.cron,
590
- command: def.buildCommand(),
591
- mode: def.mode,
592
- enabled: true,
593
- name: def.name,
594
- working_directory: null,
595
- timezone: null,
596
- timeout_ms: 600000,
597
- allow_concurrent: false,
598
- created_at: now,
599
- updated_at: now,
600
- run_count: 0,
601
- next_run: calculateNextRun(def.cron, null),
602
- };
603
- store.create(schedule);
604
- existingNames.add(def.name);
605
- seeded.push(def.builtin_id);
606
- }
607
- return seeded;
608
- }
609
- const legacyLockOwners = new Map();
610
- async function acquireLock(timeout = STORE_LOCK_TIMEOUT_MS, dir) {
611
- const workspacePath = path.resolve(dir ?? DEFAULT_WORKSPACE_PATH);
612
- try {
613
- legacyLockOwners.set(workspacePath, acquireWorkspaceLock(workspacePath, timeout));
614
- return true;
615
- }
616
- catch (error) {
617
- if (error instanceof types_1.CronStoreError && error.code === 'CRON_STORE_LOCK_TIMEOUT')
618
- return false;
619
- throw error;
620
- }
621
- }
622
- function releaseLock(dir) {
623
- const workspacePath = path.resolve(dir ?? DEFAULT_WORKSPACE_PATH);
624
- const owner = legacyLockOwners.get(workspacePath);
625
- if (!owner)
626
- return;
627
- legacyLockOwners.delete(workspacePath);
628
- releaseWorkspaceLock(owner);
629
- }
630
- function readRunLease(lockFile) {
631
- return safeReadJsonFile(lockFile);
632
- }
633
- function writeRunLeaseAtomic(lockFile, lease) {
634
- const directory = path.dirname(lockFile);
635
- const tempFile = path.join(directory, `.${path.basename(lockFile)}.${process.pid}.${(0, crypto_1.randomUUID)()}.tmp`);
636
- let fd;
637
- try {
638
- fd = fs.openSync(tempFile, 'wx', 0o600);
639
- fs.writeFileSync(fd, JSON.stringify(lease));
640
- fs.fsyncSync(fd);
641
- fs.closeSync(fd);
642
- fd = undefined;
643
- fs.renameSync(tempFile, lockFile);
644
- }
645
- finally {
646
- if (fd !== undefined) {
647
- try {
648
- fs.closeSync(fd);
649
- }
650
- catch { }
651
- }
652
- try {
653
- fs.unlinkSync(tempFile);
654
- }
655
- catch { }
656
- }
657
- }
658
- function withRunLeaseStoreLock(workspacePath, scheduleId, operation) {
659
- const owner = acquireWorkspaceLock(workspacePath);
660
- try {
661
- return operation(resolveRunLockFile(scheduleId, workspacePath));
662
- }
663
- finally {
664
- releaseWorkspaceLock(owner);
665
- }
666
- }
667
- function getLeaseAgeMs(lockFile) {
668
- try {
669
- return Math.max(0, Date.now() - fs.statSync(lockFile).mtimeMs);
670
- }
671
- catch {
672
- return null;
673
- }
674
- }
675
- function compareProcessIdentity(expected, actual) {
676
- if (expected.startTime !== null && actual.startTime !== null) {
677
- return expected.startTime === actual.startTime;
678
- }
679
- if (expected.commandLine !== null && actual.commandLine !== null) {
680
- return expected.commandLine === actual.commandLine;
681
- }
682
- return null;
683
- }
684
- function isRunLeaseStale(lease, lockFile, timeoutMs) {
685
- if (lease.phase !== 'child') {
686
- const ageMs = getLeaseAgeMs(lockFile);
687
- if (ageMs === null)
688
- return false;
689
- const ownerPid = lease.hostPid ?? lease.pid;
690
- const ownerAlive = isPidAlive(ownerPid);
691
- return ownerAlive === false || ageMs > timeoutMs + RUN_LOCK_STALE_GRACE_MS;
692
- }
693
- const childPid = lease.childPid ?? lease.pid;
694
- const childAlive = isPidAlive(childPid);
695
- if (childAlive === false)
696
- return true;
697
- if (childAlive !== true)
698
- return false;
699
- if (!lease.childIdentity)
700
- return false;
701
- const currentIdentity = captureProcessIdentity(childPid);
702
- if (!currentIdentity)
703
- return false;
704
- const identityMatch = compareProcessIdentity(lease.childIdentity, currentIdentity);
705
- if (identityMatch === false)
706
- return true;
707
- return false;
708
- }
709
- function acquireRunLock(scheduleId, taskId, timeoutMs, dir) {
710
- const workspacePath = path.resolve(dir ?? DEFAULT_WORKSPACE_PATH);
711
- ensureDataDir(workspacePath);
712
- const lockFile = resolveRunLockFile(scheduleId, workspacePath);
713
- const leaseId = (0, crypto_1.randomUUID)();
714
- const ownerToken = (0, crypto_1.randomUUID)();
715
- const lease = {
716
- leaseId,
717
- ownerToken,
718
- phase: 'admitting',
719
- hostPid: process.pid,
720
- pid: process.pid,
721
- taskId,
722
- startedAt: new Date().toISOString(),
723
- };
724
- return withRunLeaseStoreLock(workspacePath, scheduleId, () => {
725
- for (let attempt = 0; attempt < 2; attempt++) {
726
- try {
727
- fs.writeFileSync(lockFile, JSON.stringify(lease), { flag: 'wx', mode: 0o600 });
728
- return {
729
- acquired: true,
730
- lease: { scheduleId, workspacePath, lockFile, leaseId, ownerToken, taskId },
731
- };
732
- }
733
- catch (e) {
734
- const err = e;
735
- if (err.code !== 'EEXIST')
736
- throw err;
737
- const existing = readRunLease(lockFile);
738
- if (!existing) {
739
- return { acquired: false, runningTaskId: 'unknown', reason: 'execution_lease' };
740
- }
741
- if (isRunLeaseStale(existing, lockFile, timeoutMs)) {
742
- const current = readRunLease(lockFile);
743
- if (current?.leaseId !== undefined && current.leaseId !== existing.leaseId) {
744
- continue;
745
- }
746
- try {
747
- fs.unlinkSync(lockFile);
748
- }
749
- catch { }
750
- continue;
751
- }
752
- return {
753
- acquired: false,
754
- runningTaskId: existing.taskId,
755
- reason: 'execution_lease',
756
- };
757
- }
758
- }
759
- const existing = readRunLease(lockFile);
760
- return {
761
- acquired: false,
762
- runningTaskId: existing?.taskId ?? 'unknown',
763
- reason: 'execution_lease',
764
- };
765
- });
766
- }
767
- function handoffRunLock(lease, childPid, childIdentity) {
768
- return withRunLeaseStoreLock(lease.workspacePath, lease.scheduleId, lockFile => {
769
- const current = readRunLease(lockFile);
770
- if (!current ||
771
- current.leaseId !== lease.leaseId ||
772
- current.ownerToken !== lease.ownerToken) {
773
- return false;
774
- }
775
- writeRunLeaseAtomic(lockFile, {
776
- ...current,
777
- phase: 'child',
778
- pid: childPid,
779
- childPid,
780
- childIdentity,
781
- });
782
- return true;
783
- });
784
- }
785
- function releaseRunLock(scheduleId, leaseId, dir) {
786
- const workspacePath = path.resolve(dir ?? DEFAULT_WORKSPACE_PATH);
787
- ensureDataDir(workspacePath);
788
- return withRunLeaseStoreLock(workspacePath, scheduleId, lockFile => {
789
- const current = readRunLease(lockFile);
790
- if (!current || current.leaseId !== leaseId)
791
- return false;
792
- try {
793
- fs.unlinkSync(lockFile);
794
- return true;
795
- }
796
- catch (error) {
797
- const err = error;
798
- if (err.code === 'ENOENT')
799
- return false;
800
- throw error;
801
- }
802
- });
803
- }
804
- const DAY_NAMES = {
805
- SUN: 0, MON: 1, TUE: 2, WED: 3, THU: 4, FRI: 5, SAT: 6,
806
- };
807
- const MONTH_NAMES = {
808
- JAN: 1, FEB: 2, MAR: 3, APR: 4, MAY: 5, JUN: 6,
809
- JUL: 7, AUG: 8, SEP: 9, OCT: 10, NOV: 11, DEC: 12,
810
- };
811
- function resolveValue(token, fieldIdx) {
812
- const upper = token.toUpperCase();
813
- if (fieldIdx === 4) {
814
- const day = DAY_NAMES[upper];
815
- if (day !== undefined)
816
- return day;
817
- const num = parseInt(token, 10);
818
- return num === 7 ? 0 : num;
819
- }
820
- if (fieldIdx === 3) {
821
- const month = MONTH_NAMES[upper];
822
- if (month !== undefined)
823
- return month;
824
- }
825
- return parseInt(token, 10);
826
- }
827
- function matchSegment(segment, value, fieldIdx) {
828
- if (segment === '*')
829
- return true;
830
- if (segment.includes('/')) {
831
- const slashPos = segment.indexOf('/');
832
- const rangePart = segment.slice(0, slashPos);
833
- const step = parseInt(segment.slice(slashPos + 1), 10);
834
- if (isNaN(step) || step <= 0)
835
- return false;
836
- if (rangePart === '*') {
837
- return value % step === 0;
838
- }
839
- if (rangePart.includes('-')) {
840
- const dashPos = rangePart.indexOf('-');
841
- const start = resolveValue(rangePart.slice(0, dashPos), fieldIdx);
842
- const end = resolveValue(rangePart.slice(dashPos + 1), fieldIdx);
843
- return value >= start && value <= end && (value - start) % step === 0;
844
- }
845
- const base = resolveValue(rangePart, fieldIdx);
846
- return value >= base && (value - base) % step === 0;
847
- }
848
- if (segment.includes('-')) {
849
- const dashPos = segment.indexOf('-');
850
- const start = resolveValue(segment.slice(0, dashPos), fieldIdx);
851
- const end = resolveValue(segment.slice(dashPos + 1), fieldIdx);
852
- if (start <= end) {
853
- return value >= start && value <= end;
854
- }
855
- return value >= start || value <= end;
856
- }
857
- return resolveValue(segment, fieldIdx) === value;
858
- }
859
- function matchField(field, value, fieldIdx) {
860
- return field.split(',').some(seg => matchSegment(seg.trim(), value, fieldIdx));
861
- }
862
- function isValidTimezone(tz) {
863
- try {
864
- Intl.DateTimeFormat('en-US', { timeZone: tz });
865
- return true;
866
- }
867
- catch {
868
- return false;
869
- }
870
- }
871
- function shouldRunNow(cronExpr, now, timezone) {
872
- let checkDate = now;
873
- if (timezone) {
874
- if (!isValidTimezone(timezone)) {
875
- console.error(`Invalid timezone: ${timezone}, skipping execution`);
876
- return false;
877
- }
878
- else {
879
- const formatter = new Intl.DateTimeFormat('en-US', {
880
- timeZone: timezone,
881
- year: 'numeric',
882
- month: '2-digit',
883
- day: '2-digit',
884
- hour: '2-digit',
885
- minute: '2-digit',
886
- hour12: false,
887
- });
888
- const parts = formatter.formatToParts(now);
889
- const getPart = (type) => parseInt(parts.find(p => p.type === type)?.value ?? '0', 10);
890
- const year = getPart('year');
891
- const month = getPart('month');
892
- const day = getPart('day');
893
- checkDate = {
894
- getMinutes: () => getPart('minute'),
895
- getHours: () => getPart('hour'),
896
- getDate: () => getPart('day'),
897
- getMonth: () => getPart('month') - 1,
898
- getDay: () => new Date(year, month - 1, day).getDay(),
899
- };
900
- }
901
- }
902
- const fields = cronExpr.trim().split(/\s+/);
903
- if (fields.length !== 5) {
904
- console.error(`Invalid cron expression: ${cronExpr}`);
905
- return false;
906
- }
907
- const [minute, hour, dayOfMonth, month, dayOfWeek] = fields;
908
- const m = checkDate.getMinutes();
909
- const h = checkDate.getHours();
910
- const dom = checkDate.getDate();
911
- const mon = checkDate.getMonth() + 1;
912
- const dow = checkDate.getDay();
913
- return (matchField(minute, m, 0) &&
914
- matchField(hour, h, 1) &&
915
- matchField(dayOfMonth, dom, 2) &&
916
- matchField(month, mon, 3) &&
917
- matchField(dayOfWeek, dow, 4));
918
- }
919
- function describeCron(cronExpr) {
920
- const fields = cronExpr.trim().split(/\s+/);
921
- if (fields.length !== 5)
922
- return cronExpr;
923
- const [minute, hour, dayOfMonth, month, dayOfWeek] = fields;
924
- if (minute === '0' && hour !== '*' && dayOfMonth === '*' && month === '*' && dayOfWeek === '*') {
925
- return `Daily at ${hour}:00`;
926
- }
927
- if (minute !== '*' && hour !== '*' && dayOfMonth === '*' && month === '*' && dayOfWeek === '*') {
928
- return `Daily at ${hour}:${minute.padStart(2, '0')}`;
929
- }
930
- if (cronExpr === '* * * * *') {
931
- return 'Every minute';
932
- }
933
- if (minute === '*/5' && hour === '*' && dayOfMonth === '*' && month === '*' && dayOfWeek === '*') {
934
- return 'Every 5 minutes';
935
- }
936
- return cronExpr;
937
- }
938
- function getDateParts(d, timezone) {
939
- if (timezone && isValidTimezone(timezone)) {
940
- const formatter = new Intl.DateTimeFormat('en-US', {
941
- timeZone: timezone,
942
- year: 'numeric', month: '2-digit', day: '2-digit',
943
- hour: '2-digit', minute: '2-digit', hour12: false,
944
- weekday: 'short',
945
- });
946
- const parts = formatter.formatToParts(d);
947
- const getPart = (type) => parseInt(parts.find(p => p.type === type)?.value ?? '0', 10);
948
- const weekdayStr = parts.find(p => p.type === 'weekday')?.value ?? '';
949
- const weekdayMap = { Sun: 0, Mon: 1, Tue: 2, Wed: 3, Thu: 4, Fri: 5, Sat: 6 };
950
- return {
951
- minute: getPart('minute'),
952
- hour: getPart('hour'),
953
- day: getPart('day'),
954
- month: getPart('month'),
955
- weekday: weekdayMap[weekdayStr] ?? d.getDay(),
956
- };
957
- }
958
- return {
959
- minute: d.getMinutes(),
960
- hour: d.getHours(),
961
- day: d.getDate(),
962
- month: d.getMonth() + 1,
963
- weekday: d.getDay(),
964
- };
965
- }
966
- function getTimezoneOffsetMinutes(date, timezone) {
967
- const utcStr = date.toLocaleString('en-US', { timeZone: 'UTC' });
968
- const tzStr = date.toLocaleString('en-US', { timeZone: timezone });
969
- return (new Date(tzStr).getTime() - new Date(utcStr).getTime()) / 60000;
970
- }
971
- function calculateNextRun(cronExpr, timezone) {
972
- const fields = cronExpr.trim().split(/\s+/);
973
- if (fields.length !== 5)
974
- return null;
975
- const [minuteField, hourField, dayField, monthField, weekdayField] = fields;
976
- const matchingHours = [];
977
- for (let h = 0; h < 24; h++) {
978
- if (matchField(hourField, h, 1))
979
- matchingHours.push(h);
980
- }
981
- const matchingMinutes = [];
982
- for (let m = 0; m < 60; m++) {
983
- if (matchField(minuteField, m, 0))
984
- matchingMinutes.push(m);
985
- }
986
- if (matchingHours.length === 0 || matchingMinutes.length === 0)
987
- return null;
988
- const now = new Date();
989
- const useTz = timezone && isValidTimezone(timezone);
990
- const startParts = getDateParts(now, timezone);
991
- for (let dayOffset = 0; dayOffset <= 366; dayOffset++) {
992
- const refDate = new Date(now.getTime() + dayOffset * 86400000);
993
- const dp = getDateParts(refDate, timezone);
994
- if (!matchField(monthField, dp.month, 3))
995
- continue;
996
- if (!matchField(dayField, dp.day, 2))
997
- continue;
998
- if (!matchField(weekdayField, dp.weekday, 4))
999
- continue;
1000
- for (const h of matchingHours) {
1001
- for (const m of matchingMinutes) {
1002
- if (dayOffset === 0 && (h < startParts.hour || (h === startParts.hour && m <= startParts.minute))) {
1003
- continue;
1004
- }
1005
- let candidate;
1006
- if (useTz) {
1007
- const offsetMin = getTimezoneOffsetMinutes(refDate, timezone);
1008
- candidate = new Date(refDate);
1009
- candidate.setUTCHours(h - Math.floor(offsetMin / 60), m - (offsetMin % 60), 0, 0);
1010
- }
1011
- else {
1012
- candidate = new Date(refDate);
1013
- candidate.setHours(h, m, 0, 0);
1014
- }
1015
- if (candidate.getTime() <= now.getTime())
1016
- continue;
1017
- const cp = getDateParts(candidate, timezone);
1018
- if (cp.hour === h && cp.minute === m &&
1019
- matchField(dayField, cp.day, 2) &&
1020
- matchField(monthField, cp.month, 3) &&
1021
- matchField(weekdayField, cp.weekday, 4)) {
1022
- return candidate.toISOString();
1023
- }
1024
- }
1025
- }
1026
- }
1027
- return null;
1028
- }
1029
- function readDaemonPid(dir) {
1030
- try {
1031
- const raw = fs.readFileSync(resolvePidFile(dir), 'utf8').trim();
1032
- if (!/^\d+$/.test(raw))
1033
- return null;
1034
- const pid = Number(raw);
1035
- return Number.isSafeInteger(pid) && pid > 0 ? pid : null;
1036
- }
1037
- catch {
1038
- return null;
1039
- }
1040
- }
1041
- function writeDaemonPid(pid, dir) {
1042
- ensureDataDir(dir);
1043
- fs.writeFileSync(resolvePidFile(dir), `${pid}\n`);
1044
- }
1045
- function removeDaemonPidFile(dir) {
1046
- try {
1047
- fs.unlinkSync(resolvePidFile(dir));
1048
- }
1049
- catch {
1050
- }
1051
- }
1052
- function isPidAlive(pid) {
1053
- try {
1054
- process.kill(pid, 0);
1055
- return true;
1056
- }
1057
- catch (e) {
1058
- const error = e;
1059
- return error.code === 'ESRCH' ? false : 'unknown';
1060
- }
1061
- }
1062
- function getProcessCommandLine(pid) {
1063
- try {
1064
- const output = process.platform === 'win32'
1065
- ? (0, child_process_1.execFileSync)('powershell.exe', [
1066
- '-NoLogo',
1067
- '-NoProfile',
1068
- '-NonInteractive',
1069
- '-Command',
1070
- `(Get-CimInstance Win32_Process -Filter "ProcessId = ${pid}").CommandLine`,
1071
- ], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] })
1072
- : (0, child_process_1.execFileSync)('ps', ['-ww', '-p', String(pid), '-o', 'command='], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
1073
- return output.trim() || null;
1074
- }
1075
- catch {
1076
- return null;
1077
- }
1078
- }
1079
- function getProcessStartTime(pid) {
1080
- try {
1081
- const output = process.platform === 'win32'
1082
- ? (0, child_process_1.execFileSync)('powershell.exe', [
1083
- '-NoLogo',
1084
- '-NoProfile',
1085
- '-NonInteractive',
1086
- '-Command',
1087
- `(Get-CimInstance Win32_Process -Filter "ProcessId = ${pid}").CreationDate.ToUniversalTime().ToString("o")`,
1088
- ], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] })
1089
- : (0, child_process_1.execFileSync)('ps', ['-p', String(pid), '-o', 'lstart='], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
1090
- return output.trim() || null;
1091
- }
1092
- catch {
1093
- return null;
1094
- }
1095
- }
1096
- function captureProcessIdentity(pid) {
1097
- const identity = {
1098
- commandLine: getProcessCommandLine(pid),
1099
- startTime: getProcessStartTime(pid),
1100
- };
1101
- return identity.commandLine !== null || identity.startTime !== null ? identity : null;
1102
- }
1103
- const DEFAULT_DAEMON_PROCESS_PROBE = {
1104
- isAlive: isPidAlive,
1105
- getCommandLine: getProcessCommandLine,
1106
- };
1107
- function normalizeCommandLine(value) {
1108
- const normalized = value.replace(/\\/g, '/');
1109
- return process.platform === 'win32' ? normalized.toLowerCase() : normalized;
1110
- }
1111
- function getDaemonCliPathTail() {
1112
- try {
1113
- const segments = normalizeCommandLine(getDaemonCliPath()).split('/').filter(Boolean);
1114
- const tail = segments.slice(-3).join('/');
1115
- return tail || null;
1116
- }
1117
- catch {
1118
- return null;
1119
- }
1120
- }
1121
- function classifyDaemonIdentity(commandLine) {
1122
- if (!commandLine?.trim())
1123
- return 'unknown';
1124
- const cliPathTail = getDaemonCliPathTail();
1125
- if (!cliPathTail)
1126
- return 'unknown';
1127
- const normalizedCommandLine = normalizeCommandLine(commandLine);
1128
- return normalizedCommandLine.includes('daemon --foreground')
1129
- && normalizedCommandLine.includes(cliPathTail)
1130
- ? 'running'
1131
- : 'stale';
1132
- }
1133
- function getDaemonCliPath() {
1134
- const candidates = [
1135
- path.join(__dirname, '..', 'cli.js'),
1136
- path.join(__dirname, '..', 'dist', 'cli.js'),
1137
- ];
1138
- const cliPath = candidates.find(candidate => fs.existsSync(candidate));
1139
- if (cliPath) {
1140
- return cliPath;
1141
- }
1142
- throw new Error(`Cron CLI entrypoint not found: ${candidates[0]}`);
1143
- }
1144
- function inspectDaemon(dir, probe = DEFAULT_DAEMON_PROCESS_PROBE) {
1145
- const pid = readDaemonPid(dir);
1146
- if (!pid) {
1147
- removeDaemonPidFile(dir);
1148
- return 'stale';
1149
- }
1150
- let alive;
1151
- try {
1152
- alive = probe.isAlive(pid);
1153
- }
1154
- catch {
1155
- alive = 'unknown';
1156
- }
1157
- if (!alive) {
1158
- removeDaemonPidFile(dir);
1159
- return 'stale';
1160
- }
1161
- let commandLine;
1162
- try {
1163
- commandLine = probe.getCommandLine(pid);
1164
- }
1165
- catch {
1166
- return 'unknown';
1167
- }
1168
- const liveness = classifyDaemonIdentity(commandLine);
1169
- if (liveness === 'stale') {
1170
- removeDaemonPidFile(dir);
1171
- }
1172
- return liveness;
1173
- }
1174
- function isDaemonRunning(dir, probe = DEFAULT_DAEMON_PROCESS_PROBE) {
1175
- return inspectDaemon(dir, probe) === 'running';
1176
- }
1177
- function spawnDaemon(dir, spawnImpl = child_process_1.spawn) {
1178
- if ((0, sdk_1.isDurablyQuiescing)()) {
1179
- throw new Error('Cannot start cron daemon: a desktop update is draining active work');
1180
- }
1181
- ensureDataDir(dir);
1182
- const cliPath = getDaemonCliPath();
1183
- const daemonEnv = { ...process.env };
1184
- if (process.versions.electron) {
1185
- daemonEnv.ELECTRON_RUN_AS_NODE = '1';
1186
- }
1187
- const child = spawnImpl(process.execPath, [cliPath, 'daemon', '--foreground'], {
1188
- cwd: dir || undefined,
1189
- detached: true,
1190
- stdio: 'ignore',
1191
- windowsHide: true,
1192
- env: daemonEnv,
1193
- });
1194
- if (!child.pid) {
1195
- throw new Error('Failed to start daemon: no PID returned');
1196
- }
1197
- child.unref();
1198
- writeDaemonPid(child.pid, dir);
1199
- return child.pid;
1200
- }
1201
- function startDaemon(dir, probe = DEFAULT_DAEMON_PROCESS_PROBE) {
1202
- const liveness = inspectDaemon(dir, probe);
1203
- if (liveness === 'running') {
1204
- const pid = readDaemonPid(dir);
1205
- if (pid)
1206
- return pid;
1207
- throw new Error('Daemon is already running');
1208
- }
1209
- if (liveness === 'unknown') {
1210
- const pid = readDaemonPid(dir);
1211
- throw new Error(`Unable to determine cron daemon liveness (pid=${pid ?? 'unknown'}, pidFile=${resolvePidFile(dir)}); refusing to start`);
1212
- }
1213
- return spawnDaemon(dir, probe.spawnImpl);
1214
- }
1215
- function stopDaemon(dir, probe = DEFAULT_DAEMON_PROCESS_PROBE) {
1216
- if (inspectDaemon(dir, probe) !== 'running')
1217
- return false;
1218
- const pid = readDaemonPid(dir);
1219
- if (!pid)
1220
- return false;
1221
- process.kill(pid, 'SIGTERM');
1222
- return true;
1223
- }
1224
- function startDaemonSafe(dir, probe = DEFAULT_DAEMON_PROCESS_PROBE) {
1225
- const liveness = inspectDaemon(dir, probe);
1226
- if (liveness === 'running') {
1227
- return { started: false };
1228
- }
1229
- if (liveness === 'unknown') {
1230
- return {
1231
- started: false,
1232
- error: `Unable to determine cron daemon liveness (pid=${readDaemonPid(dir) ?? 'unknown'}, pidFile=${resolvePidFile(dir)}); refusing to start`,
1233
- };
1234
- }
1235
- try {
1236
- const pid = spawnDaemon(dir, probe.spawnImpl);
1237
- return { started: true, pid };
1238
- }
1239
- catch (e) {
1240
- const err = e;
1241
- return { started: false, error: err.message };
1242
- }
1243
- }
1244
- function ensureDaemonRunning(dir, probe = DEFAULT_DAEMON_PROCESS_PROBE) {
1245
- if (isDaemonRunning(dir, probe))
1246
- return;
1247
- console.log('⚡ Cron daemon is not running; starting it automatically...');
1248
- const result = startDaemonSafe(dir, probe);
1249
- if (!result.started) {
1250
- console.error('Failed to auto-start daemon:', result.error);
1251
- process.exit(1);
1252
- }
1253
- }
1254
- function getDaemonStatus(dir) {
1255
- const schedules = createScheduleStore(dir ?? DEFAULT_WORKSPACE_PATH).list();
1256
- const enabled = schedules.filter(s => s.enabled).length;
1257
- const running = isDaemonRunning(dir);
1258
- const pid = running ? readDaemonPid(dir) : null;
1259
- let started_at = null;
1260
- const pidFile = resolvePidFile(dir);
1261
- if (running && fs.existsSync(pidFile)) {
1262
- try {
1263
- started_at = fs.statSync(pidFile).mtime.toISOString();
1264
- }
1265
- catch {
1266
- started_at = null;
1267
- }
1268
- }
1269
- return {
1270
- running,
1271
- pid: running ? pid : null,
1272
- started_at,
1273
- schedules: {
1274
- total: schedules.length,
1275
- enabled,
1276
- },
1277
- };
1278
- }
1279
- async function executeSchedule(schedule, options) {
1280
- const dir = options?.dir;
1281
- const workspacePath = path.resolve(dir ?? DEFAULT_WORKSPACE_PATH);
1282
- const trigger = options?.trigger ?? 'cron';
1283
- const timeoutMs = schedule.timeout_ms || 600000;
1284
- const taskId = (0, sdk_1.generateId)('tsk');
1285
- let runLease;
1286
- if (!schedule.allow_concurrent) {
1287
- const lock = acquireRunLock(schedule.id, taskId, timeoutMs, dir);
1288
- if (!lock.acquired) {
1289
- console.log(`[${getLocalTimestamp()}] Schedule ${schedule.id} already running (task ${lock.runningTaskId}), skipping`);
1290
- return {
1291
- status: 'conflict',
1292
- runningTaskId: lock.runningTaskId,
1293
- reason: lock.reason,
1294
- };
1295
- }
1296
- runLease = lock.lease;
1297
- }
1298
- const releaseLease = () => {
1299
- if (runLease) {
1300
- releaseRunLock(schedule.id, runLease.leaseId, dir);
1301
- runLease = undefined;
1302
- }
1303
- releaseAdmissionReservation(taskId);
1304
- };
1305
- if (!reserveAdmission(taskId, schedule.id, timeoutMs)) {
1306
- console.log(`[${getLocalTimestamp()}] Schedule ${schedule.id} blocked: could not durably record the update-admission reservation`);
1307
- releaseLease();
1308
- return { status: 'blocked', reason: 'ADMISSION_UNKNOWN' };
1309
- }
1310
- if ((0, sdk_1.isDurablyQuiescing)()) {
1311
- console.log(`[${getLocalTimestamp()}] Schedule ${schedule.id} blocked: a desktop update is draining active work`);
1312
- releaseLease();
1313
- return { status: 'blocked', reason: 'UPDATE_QUIESCING' };
1314
- }
1315
- try {
1316
- const store = createScheduleStore(workspacePath);
1317
- const startedAt = new Date().toISOString();
1318
- if (options?.occurrenceKey !== undefined) {
1319
- const claim = store.claimOccurrence(schedule.id, {
1320
- occurrenceKey: options.occurrenceKey,
1321
- startedAt,
1322
- });
1323
- if (!claim.claimed) {
1324
- releaseLease();
1325
- console.log(`[${getLocalTimestamp()}] Schedule ${schedule.id} occurrence ${options.occurrenceKey} already claimed, skipping`);
1326
- return {
1327
- status: 'conflict',
1328
- runningTaskId: '',
1329
- reason: 'occurrence_claim',
1330
- };
1331
- }
1332
- }
1333
- else {
1334
- if (store.get(schedule.id)) {
1335
- store.recordStarted(schedule.id, { startedAt });
1336
- }
1337
- }
1338
- }
1339
- catch (error) {
1340
- releaseLease();
1341
- throw error;
1342
- }
1343
- const workingDir = schedule.working_directory || options?.dir || (0, builtins_1.getProjectRoot)();
1344
- const mode = schedule.mode;
1345
- const modeFlag = mode === 'execute' ? 'x' : 'q';
1346
- const effectiveModel = options?.overrides?.model ?? schedule.model;
1347
- const effectiveProvider = options?.overrides?.provider ?? schedule.provider;
1348
- console.log(`[${getLocalTimestamp()}] Executing schedule ${schedule.id}: ${schedule.name ?? schedule.cron} (task ${taskId})`);
1349
- console.log(`[${getLocalTimestamp()}] Command: ${schedule.command}`);
1350
- const env = (0, sdk_1.withoutCrewxRootContext)({ ...process.env, ...schedule.env, CREWX_TRACE_ID: taskId });
1351
- delete env['CLAUDECODE'];
1352
- if (!schedule.env || !('CREWX_CONFIG' in schedule.env)) {
1353
- delete env['CREWX_CONFIG'];
1354
- }
1355
- env['CREWX_WORKSPACE'] = workingDir;
1356
- const cliArgs = [modeFlag, schedule.command];
1357
- if (effectiveModel) {
1358
- cliArgs.push('--model', effectiveModel);
1359
- }
1360
- if (mode !== 'command' && effectiveProvider) {
1361
- cliArgs.push('--provider', effectiveProvider);
1362
- }
1363
- cliArgs.push('--metadata', JSON.stringify({ trigger, schedule_id: schedule.id }));
1364
- let child;
1365
- try {
1366
- if (mode === 'command') {
1367
- child = (0, child_process_1.spawn)('sh', ['-c', schedule.command], {
1368
- cwd: workingDir,
1369
- stdio: 'ignore',
1370
- windowsHide: true,
1371
- detached: true,
1372
- env,
1373
- });
1374
- }
1375
- else {
1376
- const resolution = (0, sdk_1.resolveCrewxExecutable)({ env, cwd: workingDir });
1377
- if (!resolution.ok) {
1378
- const error = new Error((0, sdk_1.formatCrewxExecutableFailure)(resolution));
1379
- Object.assign(error, { code: 'CLI_UNRESOLVABLE' });
1380
- throw error;
1381
- }
1382
- const childInvocation = (0, sdk_1.createCrewxChildInvocation)(resolution, { baseEnv: env });
1383
- const program = (0, sdk_1.resolveWindowsSpawnProgram)({
1384
- command: childInvocation.command,
1385
- env: childInvocation.env,
1386
- cwd: workingDir,
1387
- packageName: 'crewx',
1388
- allowShellFallback: true,
1389
- });
1390
- const invocation = (0, sdk_1.materializeWindowsSpawnProgram)(program, [
1391
- ...childInvocation.args,
1392
- ...cliArgs,
1393
- ]);
1394
- child = (0, child_process_1.spawn)(invocation.command, invocation.argv, {
1395
- cwd: workingDir,
1396
- stdio: 'ignore',
1397
- windowsHide: invocation.windowsHide,
1398
- detached: true,
1399
- env: childInvocation.env,
1400
- shell: invocation.shell ?? false,
1401
- });
1402
- }
1403
- await new Promise((resolve, reject) => {
1404
- child.once('spawn', () => resolve());
1405
- child.once('error', (err) => reject(err));
1406
- });
1407
- }
1408
- catch (e) {
1409
- const err = e;
1410
- console.error(`[${getLocalTimestamp()}] Schedule ${schedule.id} failed to spawn: ${err.message}`);
1411
- releaseLease();
1412
- throw err;
1413
- }
1414
- if (runLease && child.pid) {
1415
- const handedOff = handoffRunLock(runLease, child.pid, captureProcessIdentity(child.pid));
1416
- if (!handedOff) {
1417
- const error = new Error('Cron execution lease was replaced before child ownership was established');
1418
- console.error(`[${getLocalTimestamp()}] Schedule ${schedule.id} failed to hand off its execution lease: ${error.message}`);
1419
- try {
1420
- child.kill('SIGTERM');
1421
- }
1422
- catch {
1423
- }
1424
- releaseLease();
1425
- throw error;
1426
- }
1427
- }
1428
- child.unref();
1429
- const done = new Promise((resolve) => {
1430
- let timedOut = false;
1431
- let settled = false;
1432
- let graceTimer;
1433
- const timeout = setTimeout(() => {
1434
- timedOut = true;
1435
- console.log(`[${getLocalTimestamp()}] Schedule ${schedule.id} timeout, killing process`);
1436
- try {
1437
- child.kill('SIGTERM');
1438
- }
1439
- catch (e) {
1440
- const err = e;
1441
- console.error(`Failed to kill process: ${err.message}`);
1442
- }
1443
- graceTimer = setTimeout(() => {
1444
- try {
1445
- child.kill('SIGKILL');
1446
- }
1447
- catch {
1448
- }
1449
- }, RUN_LOCK_STALE_GRACE_MS);
1450
- if (typeof graceTimer === 'object' && graceTimer !== null && 'unref' in graceTimer) {
1451
- graceTimer.unref();
1452
- }
1453
- if (!settled) {
1454
- settled = true;
1455
- resolve({ success: false, error: 'Timeout' });
1456
- }
1457
- }, timeoutMs);
1458
- child.on('close', (code) => {
1459
- clearTimeout(timeout);
1460
- if (graceTimer)
1461
- clearTimeout(graceTimer);
1462
- releaseLease();
1463
- const finishedAt = new Date().toISOString();
1464
- const outcome = timedOut ? 'timeout' : code === 0 ? 'success' : 'failure';
1465
- try {
1466
- createScheduleStore(workspacePath).recordFinished(schedule.id, {
1467
- finishedAt,
1468
- outcome,
1469
- });
1470
- }
1471
- catch (error) {
1472
- const err = error;
1473
- if (!(error instanceof types_1.CronStoreError && error.code === 'CRON_SCHEDULE_NOT_FOUND')) {
1474
- console.error(`[${getLocalTimestamp()}] Failed to record schedule ${schedule.id} completion: ${err.message}`);
1475
- }
1476
- }
1477
- if (timedOut) {
1478
- console.log(`[${getLocalTimestamp()}] Schedule ${schedule.id} terminated after timeout`);
1479
- if (!settled) {
1480
- settled = true;
1481
- resolve({ success: false, error: 'Timeout' });
1482
- }
1483
- }
1484
- else if (code === 0) {
1485
- console.log(`[${getLocalTimestamp()}] Schedule ${schedule.id} completed successfully`);
1486
- if (!settled) {
1487
- settled = true;
1488
- resolve({ success: true });
1489
- }
1490
- }
1491
- else {
1492
- console.log(`[${getLocalTimestamp()}] Schedule ${schedule.id} failed with code ${code}`);
1493
- if (!settled) {
1494
- settled = true;
1495
- resolve({ success: false, error: `Exit code: ${code}` });
1496
- }
1497
- }
1498
- });
1499
- child.on('error', (err) => {
1500
- clearTimeout(timeout);
1501
- console.error(`[${getLocalTimestamp()}] Schedule ${schedule.id} error: ${err.message}`);
1502
- if (!settled) {
1503
- settled = true;
1504
- resolve({ success: false, error: err.message });
1505
- }
1506
- });
1507
- });
1508
- return { status: 'running', taskId, done };
1509
- }
1510
- function getLocalMinuteKey(now, timezone) {
1511
- if (!timezone || !isValidTimezone(timezone)) {
1512
- return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`;
1513
- }
1514
- const fmt = new Intl.DateTimeFormat('en-CA', {
1515
- timeZone: timezone,
1516
- year: 'numeric', month: '2-digit', day: '2-digit',
1517
- hour: '2-digit', minute: '2-digit', hour12: false,
1518
- });
1519
- return fmt.format(now);
1520
- }
1521
- function buildOccurrenceKey(cronExpression, scheduledMinute) {
1522
- return `${cronExpression.trim()}|${scheduledMinute}`;
1523
- }
1524
- async function daemonTick(dir) {
1525
- if ((0, sdk_1.isDurablyQuiescing)()) {
1526
- console.log(`[${getLocalTimestamp()}] Daemon tick skipped: a desktop update is draining active work`);
1527
- return;
1528
- }
1529
- const now = new Date();
1530
- const schedules = createScheduleStore(dir ?? DEFAULT_WORKSPACE_PATH).list();
1531
- const enabledSchedules = schedules.filter(s => s.enabled);
1532
- if (enabledSchedules.length === 0) {
1533
- return;
1534
- }
1535
- for (const schedule of enabledSchedules) {
1536
- if (shouldRunNow(schedule.cron, now, schedule.timezone)) {
1537
- const minuteKey = getLocalMinuteKey(now, schedule.timezone);
1538
- const occurrenceKey = buildOccurrenceKey(schedule.cron, minuteKey);
1539
- try {
1540
- await executeSchedule(schedule, { dir, occurrenceKey });
1541
- }
1542
- catch (e) {
1543
- const err = e;
1544
- console.error(`[${getLocalTimestamp()}] Schedule ${schedule.id} failed to spawn: ${err.message}`);
1545
- }
1546
- }
1547
- }
1548
- }
1549
- var runtime_1 = require("./runtime");
1550
- Object.defineProperty(exports, "createCronRuntime", { enumerable: true, get: function () { return runtime_1.createCronRuntime; } });
1551
- Object.defineProperty(exports, "CRON_RUNTIME_PROCESS_LEASE_FILENAME", { enumerable: true, get: function () { return runtime_1.CRON_RUNTIME_PROCESS_LEASE_FILENAME; } });
1552
- function runDaemonForeground(dir) {
1553
- writeDaemonPid(process.pid, dir);
1554
- let cleanedUp = false;
1555
- const cleanup = () => {
1556
- if (cleanedUp)
1557
- return;
1558
- cleanedUp = true;
1559
- const pid = readDaemonPid(dir);
1560
- if (pid === process.pid) {
1561
- removeDaemonPidFile(dir);
1562
- }
1563
- };
1564
- process.on('SIGTERM', () => {
1565
- cleanup();
1566
- process.exit(0);
1567
- });
1568
- process.on('SIGINT', () => {
1569
- cleanup();
1570
- process.exit(0);
1571
- });
1572
- process.on('exit', cleanup);
1573
- console.log(`[${getLocalTimestamp()}] Cron daemon starting in foreground mode`);
1574
- console.log(`[${getLocalTimestamp()}] Check interval: 1 minute`);
1575
- console.log(`[${getLocalTimestamp()}] Press Ctrl+C to stop\n`);
1576
- const schedules = createScheduleStore(dir ?? DEFAULT_WORKSPACE_PATH).list();
1577
- const enabled = schedules.filter(s => s.enabled).length;
1578
- console.log(`[${getLocalTimestamp()}] Active schedules: ${enabled}/${schedules.length}\n`);
1579
- void daemonTick(dir);
1580
- setInterval(() => {
1581
- void daemonTick(dir);
1582
- }, DAEMON_INTERVAL_MS);
1583
- }
1584
- function addSchedule(cron, command, options, dataDir) {
1585
- if (options.timezone && !isValidTimezone(options.timezone)) {
1586
- throw new Error(`Invalid timezone: ${options.timezone}`);
1587
- }
1588
- warnIfOverdriveInCommand(command);
1589
- const schedule = {
1590
- id: (0, sdk_1.generateId)('cron'),
1591
- cron,
1592
- command,
1593
- mode: normalizeMode(options.mode ?? 'execute'),
1594
- enabled: true,
1595
- name: options.name ?? null,
1596
- working_directory: options.dir ?? null,
1597
- timezone: options.timezone ?? null,
1598
- timeout_ms: parseInt(options.timeout ?? '600000', 10),
1599
- allow_concurrent: options.allowConcurrent ?? false,
1600
- created_at: new Date().toISOString(),
1601
- updated_at: new Date().toISOString(),
1602
- run_count: 0,
1603
- };
1604
- if (options.model) {
1605
- schedule.model = options.model;
1606
- }
1607
- if (options.provider) {
1608
- schedule.provider = options.provider;
1609
- }
1610
- schedule.next_run = calculateNextRun(cron, schedule.timezone);
1611
- return createScheduleStore(dataDir ?? DEFAULT_WORKSPACE_PATH).create(schedule);
1612
- }
1613
- function removeSchedule(id, dir) {
1614
- const removed = createScheduleStore(dir ?? DEFAULT_WORKSPACE_PATH).remove(id);
1615
- const matchedBuiltin = (0, builtins_1.findBuiltinByName)(removed.name);
1616
- if (matchedBuiltin) {
1617
- recordRemovedBuiltin(matchedBuiltin.builtin_id, dir);
1618
- }
1619
- return removed;
1620
- }
1621
- function enableSchedule(id, dir) {
1622
- return createScheduleStore(dir ?? DEFAULT_WORKSPACE_PATH).toggle(id, true);
1623
- }
1624
- function disableSchedule(id, dir) {
1625
- return createScheduleStore(dir ?? DEFAULT_WORKSPACE_PATH).toggle(id, false);
1626
- }
1627
- function updateSchedule(id, options, dir) {
1628
- if (options.timezone !== undefined && options.timezone !== null && !isValidTimezone(options.timezone)) {
1629
- throw new Error(`Invalid timezone: ${options.timezone}`);
1630
- }
1631
- const store = createScheduleStore(dir ?? DEFAULT_WORKSPACE_PATH);
1632
- const current = store.get(id);
1633
- if (!current) {
1634
- throw new Error(`Schedule not found: ${id}`);
1635
- }
1636
- const changes = [];
1637
- const patch = {};
1638
- if (options.cron !== undefined) {
1639
- patch.cron = options.cron;
1640
- changes.push(`cron: ${options.cron}`);
1641
- }
1642
- if (options.command !== undefined) {
1643
- warnIfOverdriveInCommand(options.command);
1644
- patch.command = options.command;
1645
- changes.push(`command: ${options.command}`);
1646
- }
1647
- if (options.name !== undefined) {
1648
- patch.name = options.name;
1649
- changes.push(`name: ${options.name}`);
1650
- }
1651
- if (options.mode !== undefined) {
1652
- patch.mode = normalizeMode(options.mode);
1653
- changes.push(`mode: ${patch.mode}`);
1654
- }
1655
- if (options.timezone !== undefined) {
1656
- patch.timezone = options.timezone;
1657
- changes.push(`timezone: ${options.timezone}`);
1658
- }
1659
- if (options.working_directory !== undefined) {
1660
- patch.working_directory = options.working_directory;
1661
- changes.push(`working_directory: ${options.working_directory}`);
1662
- }
1663
- if (options.timeout_ms !== undefined) {
1664
- patch.timeout_ms = options.timeout_ms;
1665
- changes.push(`timeout: ${options.timeout_ms}ms`);
1666
- }
1667
- if (options.allow_concurrent !== undefined) {
1668
- patch.allow_concurrent = options.allow_concurrent;
1669
- changes.push(`allow_concurrent: ${options.allow_concurrent}`);
1670
- }
1671
- if (options.model !== undefined) {
1672
- if (options.model === null) {
1673
- patch.model = null;
1674
- changes.push('model: (cleared, reverts to agent default)');
1675
- }
1676
- else {
1677
- patch.model = options.model;
1678
- changes.push(`model: ${options.model}`);
1679
- }
1680
- }
1681
- if (options.provider !== undefined) {
1682
- if (options.provider === null) {
1683
- patch.provider = null;
1684
- changes.push('provider: (cleared, reverts to agent default)');
1685
- }
1686
- else {
1687
- patch.provider = options.provider;
1688
- changes.push(`provider: ${options.provider}`);
1689
- }
1690
- }
1691
- return { schedule: store.update(current.id, patch), changes };
1692
- }
1693
- exports.DATA_PATHS = {
1694
- DATA_DIR,
1695
- SCHEDULES_FILE,
1696
- LOG_FILE,
1697
- LOCK_FILE,
1698
- USAGE_LOG_FILE,
1699
- PID_FILE,
1700
- BUILTINS_META_FILE,
1701
- };
1702
- //# sourceMappingURL=engine.js.map
1
+ "use strict";var Ue=exports&&exports.__createBinding||(Object.create?(function(e,n,t,r){if(r===void 0)r=t;var o=Object.getOwnPropertyDescriptor(n,t);if(!o||("get"in o?!n.__esModule:o.writable||o.configurable)){o={enumerable:true,get:function(){return n[t]}}}Object.defineProperty(e,r,o)}):(function(e,n,t,r){if(r===void 0)r=t;e[r]=n[t]}));var Me=exports&&exports.__setModuleDefault||(Object.create?(function(e,n){Object.defineProperty(e,"default",{enumerable:true,value:n})}):function(e,n){e["default"]=n});var de=exports&&exports.__importStar||(function(){var e=function(n){e=Object.getOwnPropertyNames||function(t){var r=[];for(var o in t)if(Object.prototype.hasOwnProperty.call(t,o))r[r.length]=o;return r};return e(n)};return function(n){if(n&&n.__esModule)return n;var t={};if(n!=null){for(var r=e(n),o=0;o<r.length;o++)if(r[o]!=="default")Ue(t,n,r[o])}Me(t,n);return t}})();Object.defineProperty(exports,"__esModule",{value:true});exports.DATA_PATHS=exports.CRON_RUNTIME_PROCESS_LEASE_FILENAME=exports.createCronRuntime=exports.getProjectRoot=exports.BUILTIN_SCHEDULES=exports.CronStoreError=void 0;exports.getLocalTimestamp=g;exports.logUsage=Ve;exports.acquireWorkspaceLock=G;exports.releaseWorkspaceLock=B;exports.migrateLegacyMemoryDebounceSchedule=nn;exports.normalizeMode=oe;exports.createScheduleStore=D;exports.loadSchedules=rn;exports.saveSchedules=on;exports.getRemovedBuiltins=ve;exports.seedBuiltinSchedules=an;exports.acquireLock=un;exports.releaseLock=ln;exports.releaseRunLock=Ee;exports.shouldRunNow=ke;exports.describeCron=_n;exports.calculateNextRun=j;exports.isDaemonRunning=ue;exports.startDaemon=In;exports.stopDaemon=On;exports.startDaemonSafe=Le;exports.ensureDaemonRunning=Tn;exports.getDaemonStatus=Cn;exports.executeSchedule=Pe;exports.getLocalMinuteKey=Re;exports.buildOccurrenceKey=Ne;exports.daemonTick=le;exports.runDaemonForeground=$n;exports.addSchedule=Ln;exports.removeSchedule=Pn;exports.enableSchedule=Rn;exports.disableSchedule=Nn;exports.updateSchedule=bn;const b=require("child_process");const q=require("crypto");const d=de(require("path"));const u=de(require("fs"));const L=require("./types");Object.defineProperty(exports,"CronStoreError",{enumerable:true,get:function(){return L.CronStoreError}});const k=require("@crewx/sdk");const A=require("./builtins");Object.defineProperty(exports,"BUILTIN_SCHEDULES",{enumerable:true,get:function(){return A.BUILTIN_SCHEDULES}});Object.defineProperty(exports,"getProjectRoot",{enumerable:true,get:function(){return A.getProjectRoot}});const p=d.resolve(process.cwd());const F=d.join(p,".crewx");const je=d.join(F,".cron-data.json");const xe=d.join(F,".cron-daemon.log");const ze=d.join(F,".cron-data.json.lock");const Be=d.join(F,"cron-usage.log");const He=d.join(F,".cron-daemon.pid");const Ke=d.join(F,".cron-builtins.json");function P(e){return d.join(d.resolve(e??p),".crewx")}function ne(e){return d.join(P(e),".cron-data.json")}function fe(e){return d.join(P(e),".cron-data.json.lock")}function We(e){return d.join(P(e),"cron-usage.log")}function z(e){return d.join(P(e),".cron-daemon.pid")}function me(e){return d.join(P(e),".cron-builtins.json")}function qe(e,n,t){const r=(0,k.reserveUpdateAdmission)(e,n,t);if(!r){console.error(`[${g()}] Failed to record update-admission reservation for task ${e}`)}return r}function Ge(e){(0,k.releaseUpdateAdmissionReservation)(e)}function Se(e,n){return d.join(P(n),`.cron-run-${e}.lock`)}const Je=60*1e3;const ge=5e3;const Xe=25;const Ze=3e4;const ye=5e3;function g(){const e=new Date;const n=e.getFullYear();const t=String(e.getMonth()+1).padStart(2,"0");const r=String(e.getDate()).padStart(2,"0");const o=String(e.getHours()).padStart(2,"0");const c=String(e.getMinutes()).padStart(2,"0");const i=String(e.getSeconds()).padStart(2,"0");return`${n}-${t}-${r} ${o}:${c}:${i}`}function Ve(e,...n){const t=s=>s!==void 0&&(s==="."||s===".."||d.isAbsolute(s)||s.includes(d.sep)||s.includes("/")||s.includes("\\")||s.endsWith(".crewx")||(()=>{try{return u.statSync(s).isDirectory()}catch{return false}})());let r;let o;if(n.length>0&&(e===void 0||t(e))){r=e;o=n}else if(n.length===0&&t(e)){r=e;o=[]}else if(n.length===1&&t(n[0])){r=n[0];o=e===void 0?[]:[e]}else{r=void 0;o=e===void 0?n:[e,...n]}U(r);const c=g();const i=o.join(" ");const a=`${c} | [cron] ${i}
2
+ `;u.appendFileSync(We(r),a)}function U(e){const n=P(e);if(!u.existsSync(n)){u.mkdirSync(n,{recursive:true})}}function te(e){try{return JSON.parse(u.readFileSync(e,"utf8"))}catch{return null}}const Ye=new Int32Array(new SharedArrayBuffer(4));function Qe(){Atomics.wait(Ye,0,0,Xe)}function he(e){try{const n=JSON.parse(u.readFileSync(e,"utf8"));if(typeof n.token!=="string"||typeof n.pid!=="number"||typeof n.created_at!=="number"){return null}return n}catch{return null}}function G(e,n=ge){const t=d.resolve(e);U(t);const r=fe(t);const o=(0,q.randomUUID)();const c=Date.now()+Math.max(0,n);const i={workspacePath:t,lockPath:r,token:o};while(true){const a={token:o,pid:process.pid,created_at:Date.now()};try{u.writeFileSync(r,JSON.stringify(a),{flag:"wx",mode:384});return i}catch(s){const f=s;if(f.code!=="EEXIST")throw s;const w=he(r);if(w&&Date.now()-w.created_at>Ze){B({...i,token:w.token});continue}if(Date.now()>=c){throw new L.CronStoreError("CRON_STORE_LOCK_TIMEOUT",`Timed out waiting for cron store lock: ${r}`,r)}Qe()}}}function B(e){const n=he(e.lockPath);if(!n||n.token!==e.token)return false;try{u.unlinkSync(e.lockPath);return true}catch(t){const r=t;if(r.code==="ENOENT")return false;throw t}}const re=(0,A.findBuiltinById)("memory-debounce");function en(e){if(!e.includes("/skills/memory-v2/memory-v2.js")){return e}return re.buildCommand()}function nn(e=(0,A.getProjectRoot)()){const n=d.resolve(e);const t=d.join(n,"skills","cron",".cron-data.json");if(!u.existsSync(t)){return}const r=te(t);if(!Array.isArray(r)){return}const o=r.find(s=>s.name===re.name);if(!o){return}const c=D(n);const i=c.list();if(i.some(s=>s.name===re.name)){return}const a={...o,command:en(o.command),updated_at:new Date().toISOString(),next_run:j(o.cron,o.timezone)};c.create(a)}function we(e){if(e.includes("--overdrive")){console.warn("warning: --overdrive in cron command is ignored and may silently override --model")}}function oe(e){if(e==="query")return"query";if(e==="command")return"command";return"execute"}function O(e){return{...e,...e.env?{env:{...e.env}}:{}}}function Z(e){let n;try{n=u.readFileSync(e,"utf8")}catch(r){const o=r;if(o.code==="ENOENT")return[];throw r}let t;try{t=JSON.parse(n)}catch(r){throw new L.CronStoreError("CRON_STORE_CORRUPT",`Cron schedule store is not valid JSON: ${e}`,e)}if(!Array.isArray(t)){throw new L.CronStoreError("CRON_STORE_CORRUPT",`Cron schedule store must contain a top-level array: ${e}`,e)}return t}function tn(e){if(process.platform==="win32")return;try{const n=u.openSync(e,"r");try{u.fsyncSync(n)}finally{u.closeSync(n)}}catch{}}function pe(e,n){const t=d.dirname(e);const r=d.join(t,`.${d.basename(e)}.${process.pid}.${(0,q.randomUUID)()}.tmp`);const o=JSON.stringify(n,null,2);let c;try{c=u.openSync(r,"wx",384);u.writeFileSync(c,o);u.fsyncSync(c);u.closeSync(c);c=void 0;u.renameSync(r,e);tn(t)}finally{if(c!==void 0){try{u.closeSync(c)}catch{}}try{u.unlinkSync(r)}catch{}}}function J(e,n){const t=e.find(r=>r.id===n);if(!t){throw new L.CronStoreError("CRON_SCHEDULE_NOT_FOUND",`Schedule not found: ${n}`)}return t}function M(e,n){const t=G(e);try{const r=ne(e);const o=Z(r);const c=n(o);pe(r,o);return c}finally{B(t)}}function D(e=p){const n=d.resolve(e);const t=P(n);const r=ne(n);const o=fe(n);return{workspacePath:n,dataDir:t,schedulesFile:r,lockFile:o,list(){return Z(r).map(O)},get(c){const i=Z(r).find(a=>a.id===c);return i?O(i):void 0},create(c){return M(n,i=>{if(i.some(a=>a.id===c.id)){throw new L.CronStoreError("CRON_SCHEDULE_EXISTS",`Schedule already exists: ${c.id}`)}i.push(O(c));return O(c)})},update(c,i){return M(n,a=>{const s=J(a,c);if(i.cron!==void 0)s.cron=i.cron;if(i.command!==void 0)s.command=i.command;if(i.mode!==void 0)s.mode=i.mode;if(i.enabled!==void 0)s.enabled=i.enabled;if(i.name!==void 0)s.name=i.name;if(i.working_directory!==void 0)s.working_directory=i.working_directory;if(i.timezone!==void 0)s.timezone=i.timezone;if(i.timeout_ms!==void 0)s.timeout_ms=i.timeout_ms;if(i.allow_concurrent!==void 0)s.allow_concurrent=i.allow_concurrent;if(i.env!==void 0){if(i.env===null)delete s.env;else s.env={...i.env}}if(i.model!==void 0){if(i.model===null)delete s.model;else s.model=i.model}if(i.provider!==void 0){if(i.provider===null)delete s.provider;else s.provider=i.provider}s.updated_at=new Date().toISOString();s.next_run=j(s.cron,s.timezone);return O(s)})},remove(c){return M(n,i=>{const a=i.findIndex(f=>f.id===c);if(a===-1){throw new L.CronStoreError("CRON_SCHEDULE_NOT_FOUND",`Schedule not found: ${c}`)}const[s]=i.splice(a,1);return O(s)})},toggle(c,i){return M(n,a=>{const s=J(a,c);s.enabled=i??!s.enabled;s.updated_at=new Date().toISOString();if(s.enabled){s.next_run=j(s.cron,s.timezone)}return O(s)})},claimOccurrence(c,i){return M(n,a=>{const s=J(a,typeof c==="string"?c:c.id);if(s.last_occurrence_key===i.occurrenceKey){return{claimed:false,schedule:O(s)}}const f=i.startedAt??new Date().toISOString();s.last_started_at=f;s.last_occurrence_key=i.occurrenceKey;s.updated_at=f;return{claimed:true,schedule:O(s)}})},recordStarted(c,i){return M(n,a=>{const s=J(a,typeof c==="string"?c:c.id);const f=typeof i==="string"?{startedAt:i}:i;const w=f?.startedAt??new Date().toISOString();s.last_started_at=w;if(f?.occurrenceKey!==void 0){s.last_occurrence_key=f.occurrenceKey}s.updated_at=w;return O(s)})},recordFinished(c,i){return M(n,a=>{const s=J(a,typeof c==="string"?c:c.id);const f=i?.finishedAt??new Date().toISOString();s.last_run=f;s.run_count=(s.run_count||0)+1;s.next_run=i?.nextRun!==void 0?i.nextRun:j(s.cron,s.timezone);if(i?.outcome!==void 0)s.last_outcome=i.outcome;else if(i?.success!==void 0)s.last_outcome=i.success?"success":"failure";s.updated_at=f;return O(s)})}}}function rn(e){return D(e??p).list()}function on(e,n){const t=d.resolve(n??p);const r=G(t);try{const o=ne(t);Z(o);pe(o,e.map(O))}finally{B(r)}}function _e(e){const n=te(me(e));return{removed_builtins:Array.isArray(n?.removed_builtins)?n.removed_builtins:[]}}function cn(e,n){U(n);const t=me(n);const r=`${t}.tmp`;u.writeFileSync(r,JSON.stringify(e,null,2));u.renameSync(r,t)}function ve(e){return _e(e).removed_builtins}function sn(e,n){const t=_e(n);if(!t.removed_builtins.includes(e)){t.removed_builtins.push(e);cn(t,n)}}function an(e){const n=D(e??p);const t=n.list();const r=new Set(t.map(i=>i.name));const o=new Set(ve(e));const c=[];for(const i of A.BUILTIN_SCHEDULES){if(r.has(i.name))continue;if(o.has(i.builtin_id))continue;const a=new Date().toISOString();const s={id:(0,k.generateId)("cron"),cron:i.cron,command:i.buildCommand(),mode:i.mode,enabled:true,name:i.name,working_directory:null,timezone:null,timeout_ms:6e5,allow_concurrent:false,created_at:a,updated_at:a,run_count:0,next_run:j(i.cron,null)};n.create(s);r.add(i.name);c.push(i.builtin_id)}return c}const ie=new Map;async function un(e=ge,n){const t=d.resolve(n??p);try{ie.set(t,G(t,e));return true}catch(r){if(r instanceof L.CronStoreError&&r.code==="CRON_STORE_LOCK_TIMEOUT")return false;throw r}}function ln(e){const n=d.resolve(e??p);const t=ie.get(n);if(!t)return;ie.delete(n);B(t)}function X(e){return te(e)}function dn(e,n){const t=d.dirname(e);const r=d.join(t,`.${d.basename(e)}.${process.pid}.${(0,q.randomUUID)()}.tmp`);let o;try{o=u.openSync(r,"wx",384);u.writeFileSync(o,JSON.stringify(n));u.fsyncSync(o);u.closeSync(o);o=void 0;u.renameSync(r,e)}finally{if(o!==void 0){try{u.closeSync(o)}catch{}}try{u.unlinkSync(r)}catch{}}}function ce(e,n,t){const r=G(e);try{return t(Se(n,e))}finally{B(r)}}function fn(e){try{return Math.max(0,Date.now()-u.statSync(e).mtimeMs)}catch{return null}}function mn(e,n){if(e.startTime!==null&&n.startTime!==null){return e.startTime===n.startTime}if(e.commandLine!==null&&n.commandLine!==null){return e.commandLine===n.commandLine}return null}function Sn(e,n,t){if(e.phase!=="child"){const a=fn(n);if(a===null)return false;const s=e.hostPid??e.pid;const f=ae(s);return f===false||a>t+ye}const r=e.childPid??e.pid;const o=ae(r);if(o===false)return true;if(o!==true)return false;if(!e.childIdentity)return false;const c=Oe(r);if(!c)return false;const i=mn(e.childIdentity,c);if(i===false)return true;return false}function gn(e,n,t,r){const o=d.resolve(r??p);U(o);const c=Se(e,o);const i=(0,q.randomUUID)();const a=(0,q.randomUUID)();const s={leaseId:i,ownerToken:a,phase:"admitting",hostPid:process.pid,pid:process.pid,taskId:n,startedAt:new Date().toISOString()};return ce(o,e,()=>{for(let w=0;w<2;w++){try{u.writeFileSync(c,JSON.stringify(s),{flag:"wx",mode:384});return{acquired:true,lease:{scheduleId:e,workspacePath:o,lockFile:c,leaseId:i,ownerToken:a,taskId:n}}}catch(R){const T=R;if(T.code!=="EEXIST")throw T;const S=X(c);if(!S){return{acquired:false,runningTaskId:"unknown",reason:"execution_lease"}}if(Sn(S,c,t)){const _=X(c);if(_?.leaseId!==void 0&&_.leaseId!==S.leaseId){continue}try{u.unlinkSync(c)}catch{}continue}return{acquired:false,runningTaskId:S.taskId,reason:"execution_lease"}}}const f=X(c);return{acquired:false,runningTaskId:f?.taskId??"unknown",reason:"execution_lease"}})}function yn(e,n,t){return ce(e.workspacePath,e.scheduleId,r=>{const o=X(r);if(!o||o.leaseId!==e.leaseId||o.ownerToken!==e.ownerToken){return false}dn(r,{...o,phase:"child",pid:n,childPid:n,childIdentity:t});return true})}function Ee(e,n,t){const r=d.resolve(t??p);U(r);return ce(r,e,o=>{const c=X(o);if(!c||c.leaseId!==n)return false;try{u.unlinkSync(o);return true}catch(i){const a=i;if(a.code==="ENOENT")return false;throw i}})}const hn={SUN:0,MON:1,TUE:2,WED:3,THU:4,FRI:5,SAT:6};const wn={JAN:1,FEB:2,MAR:3,APR:4,MAY:5,JUN:6,JUL:7,AUG:8,SEP:9,OCT:10,NOV:11,DEC:12};function H(e,n){const t=e.toUpperCase();if(n===4){const r=hn[t];if(r!==void 0)return r;const o=parseInt(e,10);return o===7?0:o}if(n===3){const r=wn[t];if(r!==void 0)return r}return parseInt(e,10)}function pn(e,n,t){if(e==="*")return true;if(e.includes("/")){const r=e.indexOf("/");const o=e.slice(0,r);const c=parseInt(e.slice(r+1),10);if(isNaN(c)||c<=0)return false;if(o==="*"){return n%c===0}if(o.includes("-")){const a=o.indexOf("-");const s=H(o.slice(0,a),t);const f=H(o.slice(a+1),t);return n>=s&&n<=f&&(n-s)%c===0}const i=H(o,t);return n>=i&&(n-i)%c===0}if(e.includes("-")){const r=e.indexOf("-");const o=H(e.slice(0,r),t);const c=H(e.slice(r+1),t);if(o<=c){return n>=o&&n<=c}return n>=o||n<=c}return H(e,t)===n}function I(e,n,t){return e.split(",").some(r=>pn(r.trim(),n,t))}function K(e){try{Intl.DateTimeFormat("en-US",{timeZone:e});return true}catch{return false}}function ke(e,n,t){let r=n;if(t){if(!K(t)){console.error(`Invalid timezone: ${t}, skipping execution`);return false}else{const C=new Intl.DateTimeFormat("en-US",{timeZone:t,year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",hour12:false});const y=C.formatToParts(n);const v=E=>parseInt(y.find(N=>N.type===E)?.value??"0",10);const l=v("year");const m=v("month");const h=v("day");r={getMinutes:()=>v("minute"),getHours:()=>v("hour"),getDate:()=>v("day"),getMonth:()=>v("month")-1,getDay:()=>new Date(l,m-1,h).getDay()}}}const o=e.trim().split(/\s+/);if(o.length!==5){console.error(`Invalid cron expression: ${e}`);return false}const[c,i,a,s,f]=o;const w=r.getMinutes();const R=r.getHours();const T=r.getDate();const S=r.getMonth()+1;const _=r.getDay();return I(c,w,0)&&I(i,R,1)&&I(a,T,2)&&I(s,S,3)&&I(f,_,4)}function _n(e){const n=e.trim().split(/\s+/);if(n.length!==5)return e;const[t,r,o,c,i]=n;if(t==="0"&&r!=="*"&&o==="*"&&c==="*"&&i==="*"){return`Daily at ${r}:00`}if(t!=="*"&&r!=="*"&&o==="*"&&c==="*"&&i==="*"){return`Daily at ${r}:${t.padStart(2,"0")}`}if(e==="* * * * *"){return"Every minute"}if(t==="*/5"&&r==="*"&&o==="*"&&c==="*"&&i==="*"){return"Every 5 minutes"}return e}function se(e,n){if(n&&K(n)){const t=new Intl.DateTimeFormat("en-US",{timeZone:n,year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",hour12:false,weekday:"short"});const r=t.formatToParts(e);const o=a=>parseInt(r.find(s=>s.type===a)?.value??"0",10);const c=r.find(a=>a.type==="weekday")?.value??"";const i={Sun:0,Mon:1,Tue:2,Wed:3,Thu:4,Fri:5,Sat:6};return{minute:o("minute"),hour:o("hour"),day:o("day"),month:o("month"),weekday:i[c]??e.getDay()}}return{minute:e.getMinutes(),hour:e.getHours(),day:e.getDate(),month:e.getMonth()+1,weekday:e.getDay()}}function vn(e,n){const t=e.toLocaleString("en-US",{timeZone:"UTC"});const r=e.toLocaleString("en-US",{timeZone:n});return(new Date(r).getTime()-new Date(t).getTime())/6e4}function j(e,n){const t=e.trim().split(/\s+/);if(t.length!==5)return null;const[r,o,c,i,a]=t;const s=[];for(let S=0;S<24;S++){if(I(o,S,1))s.push(S)}const f=[];for(let S=0;S<60;S++){if(I(r,S,0))f.push(S)}if(s.length===0||f.length===0)return null;const w=new Date;const R=n&&K(n);const T=se(w,n);for(let S=0;S<=366;S++){const _=new Date(w.getTime()+S*864e5);const C=se(_,n);if(!I(i,C.month,3))continue;if(!I(c,C.day,2))continue;if(!I(a,C.weekday,4))continue;for(const y of s){for(const v of f){if(S===0&&(y<T.hour||y===T.hour&&v<=T.minute)){continue}let l;if(R){const h=vn(_,n);l=new Date(_);l.setUTCHours(y-Math.floor(h/60),v-h%60,0,0)}else{l=new Date(_);l.setHours(y,v,0,0)}if(l.getTime()<=w.getTime())continue;const m=se(l,n);if(m.hour===y&&m.minute===v&&I(c,m.day,2)&&I(i,m.month,3)&&I(a,m.weekday,4)){return l.toISOString()}}}}return null}function x(e){try{const n=u.readFileSync(z(e),"utf8").trim();if(!/^\d+$/.test(n))return null;const t=Number(n);return Number.isSafeInteger(t)&&t>0?t:null}catch{return null}}function De(e,n){U(n);u.writeFileSync(z(n),`${e}
3
+ `)}function V(e){try{u.unlinkSync(z(e))}catch{}}function ae(e){try{process.kill(e,0);return true}catch(n){const t=n;return t.code==="ESRCH"?false:"unknown"}}function Ie(e){try{const n=process.platform==="win32"?(0,b.execFileSync)("powershell.exe",["-NoLogo","-NoProfile","-NonInteractive","-Command",`(Get-CimInstance Win32_Process -Filter "ProcessId = ${e}").CommandLine`],{encoding:"utf8",stdio:["ignore","pipe","ignore"]}):(0,b.execFileSync)("ps",["-ww","-p",String(e),"-o","command="],{encoding:"utf8",stdio:["ignore","pipe","ignore"]});return n.trim()||null}catch{return null}}function En(e){try{const n=process.platform==="win32"?(0,b.execFileSync)("powershell.exe",["-NoLogo","-NoProfile","-NonInteractive","-Command",`(Get-CimInstance Win32_Process -Filter "ProcessId = ${e}").CreationDate.ToUniversalTime().ToString("o")`],{encoding:"utf8",stdio:["ignore","pipe","ignore"]}):(0,b.execFileSync)("ps",["-p",String(e),"-o","lstart="],{encoding:"utf8",stdio:["ignore","pipe","ignore"]});return n.trim()||null}catch{return null}}function Oe(e){const n={commandLine:Ie(e),startTime:En(e)};return n.commandLine!==null||n.startTime!==null?n:null}const W={isAlive:ae,getCommandLine:Ie};function Te(e){const n=e.replace(/\\/g,"/");return process.platform==="win32"?n.toLowerCase():n}function kn(){try{const e=Te(Ce()).split("/").filter(Boolean);const n=e.slice(-3).join("/");return n||null}catch{return null}}function Dn(e){if(!e?.trim())return"unknown";const n=kn();if(!n)return"unknown";const t=Te(e);return t.includes("daemon --foreground")&&t.includes(n)?"running":"stale"}function Ce(){const e=[d.join(__dirname,"..","cli.js"),d.join(__dirname,"..","dist","cli.js")];const n=e.find(t=>u.existsSync(t));if(n){return n}throw new Error(`Cron CLI entrypoint not found: ${e[0]}`)}function Y(e,n=W){const t=x(e);if(!t){V(e);return"stale"}let r;try{r=n.isAlive(t)}catch{r="unknown"}if(!r){V(e);return"stale"}let o;try{o=n.getCommandLine(t)}catch{return"unknown"}const c=Dn(o);if(c==="stale"){V(e)}return c}function ue(e,n=W){return Y(e,n)==="running"}function $e(e,n=b.spawn){if((0,k.isDurablyQuiescing)()){throw new Error("Cannot start cron daemon: a desktop update is draining active work")}U(e);const t=Ce();const r={...process.env};if(process.versions.electron){r.ELECTRON_RUN_AS_NODE="1"}const o=n(process.execPath,[t,"daemon","--foreground"],{cwd:e||void 0,detached:true,stdio:"ignore",windowsHide:true,env:r});if(!o.pid){throw new Error("Failed to start daemon: no PID returned")}o.unref();De(o.pid,e);return o.pid}function In(e,n=W){const t=Y(e,n);if(t==="running"){const r=x(e);if(r)return r;throw new Error("Daemon is already running")}if(t==="unknown"){const r=x(e);throw new Error(`Unable to determine cron daemon liveness (pid=${r??"unknown"}, pidFile=${z(e)}); refusing to start`)}return $e(e,n.spawnImpl)}function On(e,n=W){if(Y(e,n)!=="running")return false;const t=x(e);if(!t)return false;process.kill(t,"SIGTERM");return true}function Le(e,n=W){const t=Y(e,n);if(t==="running"){return{started:false}}if(t==="unknown"){return{started:false,error:`Unable to determine cron daemon liveness (pid=${x(e)??"unknown"}, pidFile=${z(e)}); refusing to start`}}try{const r=$e(e,n.spawnImpl);return{started:true,pid:r}}catch(r){const o=r;return{started:false,error:o.message}}}function Tn(e,n=W){if(ue(e,n))return;console.log("\u26A1 Cron daemon is not running; starting it automatically...");const t=Le(e,n);if(!t.started){console.error("Failed to auto-start daemon:",t.error);process.exit(1)}}function Cn(e){const n=D(e??p).list();const t=n.filter(a=>a.enabled).length;const r=ue(e);const o=r?x(e):null;let c=null;const i=z(e);if(r&&u.existsSync(i)){try{c=u.statSync(i).mtime.toISOString()}catch{c=null}}return{running:r,pid:r?o:null,started_at:c,schedules:{total:n.length,enabled:t}}}async function Pe(e,n){const t=n?.dir;const r=d.resolve(t??p);const o=n?.trigger??"cron";const c=e.timeout_ms||6e5;const i=(0,k.generateId)("tsk");let a;if(!e.allow_concurrent){const l=gn(e.id,i,c,t);if(!l.acquired){console.log(`[${g()}] Schedule ${e.id} already running (task ${l.runningTaskId}), skipping`);return{status:"conflict",runningTaskId:l.runningTaskId,reason:l.reason}}a=l.lease}const s=()=>{if(a){Ee(e.id,a.leaseId,t);a=void 0}Ge(i)};if(!qe(i,e.id,c)){console.log(`[${g()}] Schedule ${e.id} blocked: could not durably record the update-admission reservation`);s();return{status:"blocked",reason:"ADMISSION_UNKNOWN"}}if((0,k.isDurablyQuiescing)()){console.log(`[${g()}] Schedule ${e.id} blocked: a desktop update is draining active work`);s();return{status:"blocked",reason:"UPDATE_QUIESCING"}}try{const l=D(r);const m=new Date().toISOString();if(n?.occurrenceKey!==void 0){const h=l.claimOccurrence(e.id,{occurrenceKey:n.occurrenceKey,startedAt:m});if(!h.claimed){s();console.log(`[${g()}] Schedule ${e.id} occurrence ${n.occurrenceKey} already claimed, skipping`);return{status:"conflict",runningTaskId:"",reason:"occurrence_claim"}}}else{if(l.get(e.id)){l.recordStarted(e.id,{startedAt:m})}}}catch(l){s();throw l}const f=e.working_directory||n?.dir||(0,A.getProjectRoot)();const w=e.mode;const R=w==="execute"?"x":"q";const T=n?.overrides?.model??e.model;const S=n?.overrides?.provider??e.provider;console.log(`[${g()}] Executing schedule ${e.id}: ${e.name??e.cron} (task ${i})`);console.log(`[${g()}] Command: ${e.command}`);const _=(0,k.withoutCrewxRootContext)({...process.env,...e.env,CREWX_TRACE_ID:i});delete _["CLAUDECODE"];if(!e.env||!("CREWX_CONFIG"in e.env)){delete _["CREWX_CONFIG"]}_["CREWX_WORKSPACE"]=f;const C=[R,e.command];if(T){C.push("--model",T)}if(w!=="command"&&S){C.push("--provider",S)}C.push("--metadata",JSON.stringify({trigger:o,schedule_id:e.id}));let y;try{if(w==="command"){y=(0,b.spawn)("sh",["-c",e.command],{cwd:f,stdio:"ignore",windowsHide:true,detached:true,env:_})}else{const l=(0,k.resolveCrewxExecutable)({env:_,cwd:f});if(!l.ok){const N=new Error((0,k.formatCrewxExecutableFailure)(l));Object.assign(N,{code:"CLI_UNRESOLVABLE"});throw N}const m=(0,k.createCrewxChildInvocation)(l,{baseEnv:_});const h=(0,k.resolveWindowsSpawnProgram)({command:m.command,env:m.env,cwd:f,packageName:"crewx",allowShellFallback:true});const E=(0,k.materializeWindowsSpawnProgram)(h,[...m.args,...C]);y=(0,b.spawn)(E.command,E.argv,{cwd:f,stdio:"ignore",windowsHide:E.windowsHide,detached:true,env:m.env,shell:E.shell??false})}await new Promise((l,m)=>{y.once("spawn",()=>l());y.once("error",h=>m(h))})}catch(l){const m=l;console.error(`[${g()}] Schedule ${e.id} failed to spawn: ${m.message}`);s();throw m}if(a&&y.pid){const l=yn(a,y.pid,Oe(y.pid));if(!l){const m=new Error("Cron execution lease was replaced before child ownership was established");console.error(`[${g()}] Schedule ${e.id} failed to hand off its execution lease: ${m.message}`);try{y.kill("SIGTERM")}catch{}s();throw m}}y.unref();const v=new Promise(l=>{let m=false;let h=false;let E;const N=setTimeout(()=>{m=true;console.log(`[${g()}] Schedule ${e.id} timeout, killing process`);try{y.kill("SIGTERM")}catch($){const Q=$;console.error(`Failed to kill process: ${Q.message}`)}E=setTimeout(()=>{try{y.kill("SIGKILL")}catch{}},ye);if(typeof E==="object"&&E!==null&&"unref"in E){E.unref()}if(!h){h=true;l({success:false,error:"Timeout"})}},c);y.on("close",$=>{clearTimeout(N);if(E)clearTimeout(E);s();const Q=new Date().toISOString();const Ae=m?"timeout":$===0?"success":"failure";try{D(r).recordFinished(e.id,{finishedAt:Q,outcome:Ae})}catch(ee){const Fe=ee;if(!(ee instanceof L.CronStoreError&&ee.code==="CRON_SCHEDULE_NOT_FOUND")){console.error(`[${g()}] Failed to record schedule ${e.id} completion: ${Fe.message}`)}}if(m){console.log(`[${g()}] Schedule ${e.id} terminated after timeout`);if(!h){h=true;l({success:false,error:"Timeout"})}}else if($===0){console.log(`[${g()}] Schedule ${e.id} completed successfully`);if(!h){h=true;l({success:true})}}else{console.log(`[${g()}] Schedule ${e.id} failed with code ${$}`);if(!h){h=true;l({success:false,error:`Exit code: ${$}`})}}});y.on("error",$=>{clearTimeout(N);console.error(`[${g()}] Schedule ${e.id} error: ${$.message}`);if(!h){h=true;l({success:false,error:$.message})}})});return{status:"running",taskId:i,done:v}}function Re(e,n){if(!n||!K(n)){return`${e.getFullYear()}-${String(e.getMonth()+1).padStart(2,"0")}-${String(e.getDate()).padStart(2,"0")} ${String(e.getHours()).padStart(2,"0")}:${String(e.getMinutes()).padStart(2,"0")}`}const t=new Intl.DateTimeFormat("en-CA",{timeZone:n,year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",hour12:false});return t.format(e)}function Ne(e,n){return`${e.trim()}|${n}`}async function le(e){if((0,k.isDurablyQuiescing)()){console.log(`[${g()}] Daemon tick skipped: a desktop update is draining active work`);return}const n=new Date;const t=D(e??p).list();const r=t.filter(o=>o.enabled);if(r.length===0){return}for(const o of r){if(ke(o.cron,n,o.timezone)){const c=Re(n,o.timezone);const i=Ne(o.cron,c);try{await Pe(o,{dir:e,occurrenceKey:i})}catch(a){const s=a;console.error(`[${g()}] Schedule ${o.id} failed to spawn: ${s.message}`)}}}}var be=require("./runtime");Object.defineProperty(exports,"createCronRuntime",{enumerable:true,get:function(){return be.createCronRuntime}});Object.defineProperty(exports,"CRON_RUNTIME_PROCESS_LEASE_FILENAME",{enumerable:true,get:function(){return be.CRON_RUNTIME_PROCESS_LEASE_FILENAME}});function $n(e){De(process.pid,e);let n=false;const t=()=>{if(n)return;n=true;const c=x(e);if(c===process.pid){V(e)}};process.on("SIGTERM",()=>{t();process.exit(0)});process.on("SIGINT",()=>{t();process.exit(0)});process.on("exit",t);console.log(`[${g()}] Cron daemon starting in foreground mode`);console.log(`[${g()}] Check interval: 1 minute`);console.log(`[${g()}] Press Ctrl+C to stop
4
+ `);const r=D(e??p).list();const o=r.filter(c=>c.enabled).length;console.log(`[${g()}] Active schedules: ${o}/${r.length}
5
+ `);void le(e);setInterval(()=>{void le(e)},Je)}function Ln(e,n,t,r){if(t.timezone&&!K(t.timezone)){throw new Error(`Invalid timezone: ${t.timezone}`)}we(n);const o={id:(0,k.generateId)("cron"),cron:e,command:n,mode:oe(t.mode??"execute"),enabled:true,name:t.name??null,working_directory:t.dir??null,timezone:t.timezone??null,timeout_ms:parseInt(t.timeout??"600000",10),allow_concurrent:t.allowConcurrent??false,created_at:new Date().toISOString(),updated_at:new Date().toISOString(),run_count:0};if(t.model){o.model=t.model}if(t.provider){o.provider=t.provider}o.next_run=j(e,o.timezone);return D(r??p).create(o)}function Pn(e,n){const t=D(n??p).remove(e);const r=(0,A.findBuiltinByName)(t.name);if(r){sn(r.builtin_id,n)}return t}function Rn(e,n){return D(n??p).toggle(e,true)}function Nn(e,n){return D(n??p).toggle(e,false)}function bn(e,n,t){if(n.timezone!==void 0&&n.timezone!==null&&!K(n.timezone)){throw new Error(`Invalid timezone: ${n.timezone}`)}const r=D(t??p);const o=r.get(e);if(!o){throw new Error(`Schedule not found: ${e}`)}const c=[];const i={};if(n.cron!==void 0){i.cron=n.cron;c.push(`cron: ${n.cron}`)}if(n.command!==void 0){we(n.command);i.command=n.command;c.push(`command: ${n.command}`)}if(n.name!==void 0){i.name=n.name;c.push(`name: ${n.name}`)}if(n.mode!==void 0){i.mode=oe(n.mode);c.push(`mode: ${i.mode}`)}if(n.timezone!==void 0){i.timezone=n.timezone;c.push(`timezone: ${n.timezone}`)}if(n.working_directory!==void 0){i.working_directory=n.working_directory;c.push(`working_directory: ${n.working_directory}`)}if(n.timeout_ms!==void 0){i.timeout_ms=n.timeout_ms;c.push(`timeout: ${n.timeout_ms}ms`)}if(n.allow_concurrent!==void 0){i.allow_concurrent=n.allow_concurrent;c.push(`allow_concurrent: ${n.allow_concurrent}`)}if(n.model!==void 0){if(n.model===null){i.model=null;c.push("model: (cleared, reverts to agent default)")}else{i.model=n.model;c.push(`model: ${n.model}`)}}if(n.provider!==void 0){if(n.provider===null){i.provider=null;c.push("provider: (cleared, reverts to agent default)")}else{i.provider=n.provider;c.push(`provider: ${n.provider}`)}}return{schedule:r.update(o.id,i),changes:c}}exports.DATA_PATHS={DATA_DIR:F,SCHEDULES_FILE:je,LOG_FILE:xe,LOCK_FILE:ze,USAGE_LOG_FILE:Be,PID_FILE:He,BUILTINS_META_FILE:Ke};