@crewx/cron 0.1.10-rc.24 → 0.1.10-rc.241

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,819 +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 = void 0;
37
- exports.getLocalTimestamp = getLocalTimestamp;
38
- exports.logUsage = logUsage;
39
- exports.getProjectRoot = getProjectRoot;
40
- exports.normalizeMode = normalizeMode;
41
- exports.loadSchedules = loadSchedules;
42
- exports.saveSchedules = saveSchedules;
43
- exports.acquireLock = acquireLock;
44
- exports.releaseLock = releaseLock;
45
- exports.shouldRunNow = shouldRunNow;
46
- exports.describeCron = describeCron;
47
- exports.calculateNextRun = calculateNextRun;
48
- exports.isDaemonRunning = isDaemonRunning;
49
- exports.startDaemon = startDaemon;
50
- exports.stopDaemon = stopDaemon;
51
- exports.ensureDaemonRunning = ensureDaemonRunning;
52
- exports.getDaemonStatus = getDaemonStatus;
53
- exports.executeSchedule = executeSchedule;
54
- exports.daemonTick = daemonTick;
55
- exports.runDaemonForeground = runDaemonForeground;
56
- exports.addSchedule = addSchedule;
57
- exports.removeSchedule = removeSchedule;
58
- exports.enableSchedule = enableSchedule;
59
- exports.disableSchedule = disableSchedule;
60
- exports.updateSchedule = updateSchedule;
61
- const child_process_1 = require("child_process");
62
- const path = __importStar(require("path"));
63
- const fs = __importStar(require("fs"));
64
- const sdk_1 = require("@crewx/sdk");
65
- const DATA_DIR = path.join(process.cwd(), '.crewx');
66
- const SCHEDULES_FILE = path.join(DATA_DIR, '.cron-data.json');
67
- const LOG_FILE = path.join(DATA_DIR, '.cron-daemon.log');
68
- const LOCK_FILE = path.join(DATA_DIR, '.cron-data.json.lock');
69
- const USAGE_LOG_FILE = path.join(DATA_DIR, 'cron-usage.log');
70
- const PID_FILE = path.join(DATA_DIR, '.cron-daemon.pid');
71
- function resolveDataDir(dir) {
72
- return dir ? path.join(dir, '.crewx') : DATA_DIR;
73
- }
74
- function resolveSchedulesFile(dir) {
75
- return path.join(resolveDataDir(dir), '.cron-data.json');
76
- }
77
- function resolvePidFile(dir) {
78
- return path.join(resolveDataDir(dir), '.cron-daemon.pid');
79
- }
80
- const DAEMON_INTERVAL_MS = 60 * 1000;
81
- const runningSchedules = new Set();
82
- function getLocalTimestamp() {
83
- const now = new Date();
84
- const y = now.getFullYear();
85
- const m = String(now.getMonth() + 1).padStart(2, '0');
86
- const d = String(now.getDate()).padStart(2, '0');
87
- const h = String(now.getHours()).padStart(2, '0');
88
- const min = String(now.getMinutes()).padStart(2, '0');
89
- const s = String(now.getSeconds()).padStart(2, '0');
90
- return `${y}-${m}-${d} ${h}:${min}:${s}`;
91
- }
92
- function logUsage(...args) {
93
- ensureDataDir();
94
- const timestamp = getLocalTimestamp();
95
- const message = args.join(' ');
96
- const logLine = `${timestamp} | [cron] ${message}\n`;
97
- fs.appendFileSync(USAGE_LOG_FILE, logLine);
98
- }
99
- function ensureDataDir(dir) {
100
- const dataDir = resolveDataDir(dir);
101
- if (!fs.existsSync(dataDir)) {
102
- fs.mkdirSync(dataDir, { recursive: true });
103
- }
104
- }
105
- function safeReadJsonFile(filePath) {
106
- try {
107
- return JSON.parse(fs.readFileSync(filePath, 'utf8'));
108
- }
109
- catch {
110
- return null;
111
- }
112
- }
113
- function getProjectRoot() {
114
- try {
115
- return (0, child_process_1.execSync)('git rev-parse --show-toplevel', { encoding: 'utf8' }).trim();
116
- }
117
- catch {
118
- return process.cwd();
119
- }
120
- }
121
- function getBuiltinMemoryCliPath() {
122
- return path.join(getProjectRoot(), 'packages', 'built-in', 'memory', 'dist', 'cli.js');
123
- }
124
- function maybeRewriteLegacyMemoryCommand(command) {
125
- if (!command.includes('/skills/memory-v2/memory-v2.js')) {
126
- return command;
127
- }
128
- const builtinMemoryCli = getBuiltinMemoryCliPath();
129
- return `node ${builtinMemoryCli} summarize-dirty`;
130
- }
131
- function migrateLegacyMemoryDebounceSchedule() {
132
- ensureDataDir();
133
- const legacyFile = path.join(getProjectRoot(), 'skills', 'cron', '.cron-data.json');
134
- if (!fs.existsSync(legacyFile)) {
135
- return;
136
- }
137
- const legacySchedules = safeReadJsonFile(legacyFile);
138
- if (!Array.isArray(legacySchedules)) {
139
- return;
140
- }
141
- const legacySchedule = legacySchedules.find(s => s.name === 'memory-debounce');
142
- if (!legacySchedule) {
143
- return;
144
- }
145
- const targetSchedules = fs.existsSync(SCHEDULES_FILE) ? safeReadJsonFile(SCHEDULES_FILE) ?? [] : [];
146
- if (targetSchedules.some(s => s.name === 'memory-debounce')) {
147
- return;
148
- }
149
- const migratedSchedule = {
150
- ...legacySchedule,
151
- command: maybeRewriteLegacyMemoryCommand(legacySchedule.command),
152
- updated_at: new Date().toISOString(),
153
- next_run: calculateNextRun(legacySchedule.cron, legacySchedule.timezone),
154
- };
155
- saveSchedules([...targetSchedules, migratedSchedule]);
156
- }
157
- function normalizeMode(mode) {
158
- if (mode === 'query')
159
- return 'query';
160
- if (mode === 'command')
161
- return 'command';
162
- return 'execute';
163
- }
164
- function loadSchedules(dir) {
165
- ensureDataDir(dir);
166
- if (!dir) {
167
- migrateLegacyMemoryDebounceSchedule();
168
- }
169
- const schedulesFile = resolveSchedulesFile(dir);
170
- if (!fs.existsSync(schedulesFile)) {
171
- return [];
172
- }
173
- try {
174
- const data = fs.readFileSync(schedulesFile, 'utf8');
175
- return JSON.parse(data);
176
- }
177
- catch (e) {
178
- const err = e;
179
- console.error('Failed to load schedules:', err.message);
180
- return [];
181
- }
182
- }
183
- function saveSchedules(schedules, dir) {
184
- ensureDataDir(dir);
185
- const schedulesFile = resolveSchedulesFile(dir);
186
- const tempFile = `${schedulesFile}.tmp`;
187
- fs.writeFileSync(tempFile, JSON.stringify(schedules, null, 2));
188
- fs.renameSync(tempFile, schedulesFile);
189
- }
190
- async function acquireLock(timeout = 5000) {
191
- ensureDataDir();
192
- const startTime = Date.now();
193
- while (Date.now() - startTime < timeout) {
194
- try {
195
- fs.writeFileSync(LOCK_FILE, String(process.pid), { flag: 'wx' });
196
- return true;
197
- }
198
- catch (e) {
199
- const err = e;
200
- if (err.code === 'EEXIST') {
201
- try {
202
- const stat = fs.statSync(LOCK_FILE);
203
- if (Date.now() - stat.mtimeMs > 30000) {
204
- fs.unlinkSync(LOCK_FILE);
205
- continue;
206
- }
207
- }
208
- catch {
209
- continue;
210
- }
211
- await new Promise(resolve => setTimeout(resolve, 100));
212
- }
213
- else {
214
- throw e;
215
- }
216
- }
217
- }
218
- return false;
219
- }
220
- function releaseLock() {
221
- try {
222
- fs.unlinkSync(LOCK_FILE);
223
- }
224
- catch {
225
- }
226
- }
227
- const DAY_NAMES = {
228
- SUN: 0, MON: 1, TUE: 2, WED: 3, THU: 4, FRI: 5, SAT: 6,
229
- };
230
- const MONTH_NAMES = {
231
- JAN: 1, FEB: 2, MAR: 3, APR: 4, MAY: 5, JUN: 6,
232
- JUL: 7, AUG: 8, SEP: 9, OCT: 10, NOV: 11, DEC: 12,
233
- };
234
- function resolveValue(token, fieldIdx) {
235
- const upper = token.toUpperCase();
236
- if (fieldIdx === 4) {
237
- const day = DAY_NAMES[upper];
238
- if (day !== undefined)
239
- return day;
240
- const num = parseInt(token, 10);
241
- return num === 7 ? 0 : num;
242
- }
243
- if (fieldIdx === 3) {
244
- const month = MONTH_NAMES[upper];
245
- if (month !== undefined)
246
- return month;
247
- }
248
- return parseInt(token, 10);
249
- }
250
- function matchSegment(segment, value, fieldIdx) {
251
- if (segment === '*')
252
- return true;
253
- if (segment.includes('/')) {
254
- const slashPos = segment.indexOf('/');
255
- const rangePart = segment.slice(0, slashPos);
256
- const step = parseInt(segment.slice(slashPos + 1), 10);
257
- if (isNaN(step) || step <= 0)
258
- return false;
259
- if (rangePart === '*') {
260
- return value % step === 0;
261
- }
262
- if (rangePart.includes('-')) {
263
- const dashPos = rangePart.indexOf('-');
264
- const start = resolveValue(rangePart.slice(0, dashPos), fieldIdx);
265
- const end = resolveValue(rangePart.slice(dashPos + 1), fieldIdx);
266
- return value >= start && value <= end && (value - start) % step === 0;
267
- }
268
- const base = resolveValue(rangePart, fieldIdx);
269
- return value >= base && (value - base) % step === 0;
270
- }
271
- if (segment.includes('-')) {
272
- const dashPos = segment.indexOf('-');
273
- const start = resolveValue(segment.slice(0, dashPos), fieldIdx);
274
- const end = resolveValue(segment.slice(dashPos + 1), fieldIdx);
275
- if (start <= end) {
276
- return value >= start && value <= end;
277
- }
278
- return value >= start || value <= end;
279
- }
280
- return resolveValue(segment, fieldIdx) === value;
281
- }
282
- function matchField(field, value, fieldIdx) {
283
- return field.split(',').some(seg => matchSegment(seg.trim(), value, fieldIdx));
284
- }
285
- function isValidTimezone(tz) {
286
- try {
287
- Intl.DateTimeFormat('en-US', { timeZone: tz });
288
- return true;
289
- }
290
- catch {
291
- return false;
292
- }
293
- }
294
- function shouldRunNow(cronExpr, now, timezone) {
295
- let checkDate = now;
296
- if (timezone) {
297
- if (!isValidTimezone(timezone)) {
298
- console.error(`Invalid timezone: ${timezone}, skipping execution`);
299
- return false;
300
- }
301
- else {
302
- const formatter = new Intl.DateTimeFormat('en-US', {
303
- timeZone: timezone,
304
- year: 'numeric',
305
- month: '2-digit',
306
- day: '2-digit',
307
- hour: '2-digit',
308
- minute: '2-digit',
309
- hour12: false,
310
- });
311
- const parts = formatter.formatToParts(now);
312
- const getPart = (type) => parseInt(parts.find(p => p.type === type)?.value ?? '0', 10);
313
- const year = getPart('year');
314
- const month = getPart('month');
315
- const day = getPart('day');
316
- checkDate = {
317
- getMinutes: () => getPart('minute'),
318
- getHours: () => getPart('hour'),
319
- getDate: () => getPart('day'),
320
- getMonth: () => getPart('month') - 1,
321
- getDay: () => new Date(year, month - 1, day).getDay(),
322
- };
323
- }
324
- }
325
- const fields = cronExpr.trim().split(/\s+/);
326
- if (fields.length !== 5) {
327
- console.error(`Invalid cron expression: ${cronExpr}`);
328
- return false;
329
- }
330
- const [minute, hour, dayOfMonth, month, dayOfWeek] = fields;
331
- const m = checkDate.getMinutes();
332
- const h = checkDate.getHours();
333
- const dom = checkDate.getDate();
334
- const mon = checkDate.getMonth() + 1;
335
- const dow = checkDate.getDay();
336
- return (matchField(minute, m, 0) &&
337
- matchField(hour, h, 1) &&
338
- matchField(dayOfMonth, dom, 2) &&
339
- matchField(month, mon, 3) &&
340
- matchField(dayOfWeek, dow, 4));
341
- }
342
- function describeCron(cronExpr) {
343
- const fields = cronExpr.trim().split(/\s+/);
344
- if (fields.length !== 5)
345
- return cronExpr;
346
- const [minute, hour, dayOfMonth, month, dayOfWeek] = fields;
347
- if (minute === '0' && hour !== '*' && dayOfMonth === '*' && month === '*' && dayOfWeek === '*') {
348
- return `Daily at ${hour}:00`;
349
- }
350
- if (minute !== '*' && hour !== '*' && dayOfMonth === '*' && month === '*' && dayOfWeek === '*') {
351
- return `Daily at ${hour}:${minute.padStart(2, '0')}`;
352
- }
353
- if (cronExpr === '* * * * *') {
354
- return 'Every minute';
355
- }
356
- if (minute === '*/5' && hour === '*' && dayOfMonth === '*' && month === '*' && dayOfWeek === '*') {
357
- return 'Every 5 minutes';
358
- }
359
- return cronExpr;
360
- }
361
- function getDateParts(d, timezone) {
362
- if (timezone && isValidTimezone(timezone)) {
363
- const formatter = new Intl.DateTimeFormat('en-US', {
364
- timeZone: timezone,
365
- year: 'numeric', month: '2-digit', day: '2-digit',
366
- hour: '2-digit', minute: '2-digit', hour12: false,
367
- weekday: 'short',
368
- });
369
- const parts = formatter.formatToParts(d);
370
- const getPart = (type) => parseInt(parts.find(p => p.type === type)?.value ?? '0', 10);
371
- const weekdayStr = parts.find(p => p.type === 'weekday')?.value ?? '';
372
- const weekdayMap = { Sun: 0, Mon: 1, Tue: 2, Wed: 3, Thu: 4, Fri: 5, Sat: 6 };
373
- return {
374
- minute: getPart('minute'),
375
- hour: getPart('hour'),
376
- day: getPart('day'),
377
- month: getPart('month'),
378
- weekday: weekdayMap[weekdayStr] ?? d.getDay(),
379
- };
380
- }
381
- return {
382
- minute: d.getMinutes(),
383
- hour: d.getHours(),
384
- day: d.getDate(),
385
- month: d.getMonth() + 1,
386
- weekday: d.getDay(),
387
- };
388
- }
389
- function getTimezoneOffsetMinutes(date, timezone) {
390
- const utcStr = date.toLocaleString('en-US', { timeZone: 'UTC' });
391
- const tzStr = date.toLocaleString('en-US', { timeZone: timezone });
392
- return (new Date(tzStr).getTime() - new Date(utcStr).getTime()) / 60000;
393
- }
394
- function calculateNextRun(cronExpr, timezone) {
395
- const fields = cronExpr.trim().split(/\s+/);
396
- if (fields.length !== 5)
397
- return null;
398
- const [minuteField, hourField, dayField, monthField, weekdayField] = fields;
399
- const matchingHours = [];
400
- for (let h = 0; h < 24; h++) {
401
- if (matchField(hourField, h, 1))
402
- matchingHours.push(h);
403
- }
404
- const matchingMinutes = [];
405
- for (let m = 0; m < 60; m++) {
406
- if (matchField(minuteField, m, 0))
407
- matchingMinutes.push(m);
408
- }
409
- if (matchingHours.length === 0 || matchingMinutes.length === 0)
410
- return null;
411
- const now = new Date();
412
- const useTz = timezone && isValidTimezone(timezone);
413
- const startParts = getDateParts(now, timezone);
414
- for (let dayOffset = 0; dayOffset <= 366; dayOffset++) {
415
- const refDate = new Date(now.getTime() + dayOffset * 86400000);
416
- const dp = getDateParts(refDate, timezone);
417
- if (!matchField(monthField, dp.month, 3))
418
- continue;
419
- if (!matchField(dayField, dp.day, 2))
420
- continue;
421
- if (!matchField(weekdayField, dp.weekday, 4))
422
- continue;
423
- for (const h of matchingHours) {
424
- for (const m of matchingMinutes) {
425
- if (dayOffset === 0 && (h < startParts.hour || (h === startParts.hour && m <= startParts.minute))) {
426
- continue;
427
- }
428
- let candidate;
429
- if (useTz) {
430
- const offsetMin = getTimezoneOffsetMinutes(refDate, timezone);
431
- candidate = new Date(refDate);
432
- candidate.setUTCHours(h - Math.floor(offsetMin / 60), m - (offsetMin % 60), 0, 0);
433
- }
434
- else {
435
- candidate = new Date(refDate);
436
- candidate.setHours(h, m, 0, 0);
437
- }
438
- if (candidate.getTime() <= now.getTime())
439
- continue;
440
- const cp = getDateParts(candidate, timezone);
441
- if (cp.hour === h && cp.minute === m &&
442
- matchField(dayField, cp.day, 2) &&
443
- matchField(monthField, cp.month, 3) &&
444
- matchField(weekdayField, cp.weekday, 4)) {
445
- return candidate.toISOString();
446
- }
447
- }
448
- }
449
- }
450
- return null;
451
- }
452
- function readDaemonPid(dir) {
453
- try {
454
- const raw = fs.readFileSync(resolvePidFile(dir), 'utf8').trim();
455
- const pid = Number.parseInt(raw, 10);
456
- return Number.isInteger(pid) && pid > 0 ? pid : null;
457
- }
458
- catch {
459
- return null;
460
- }
461
- }
462
- function writeDaemonPid(pid, dir) {
463
- ensureDataDir(dir);
464
- fs.writeFileSync(resolvePidFile(dir), `${pid}\n`);
465
- }
466
- function removeDaemonPidFile(dir) {
467
- try {
468
- fs.unlinkSync(resolvePidFile(dir));
469
- }
470
- catch {
471
- }
472
- }
473
- function isPidAlive(pid) {
474
- try {
475
- process.kill(pid, 0);
476
- return true;
477
- }
478
- catch (e) {
479
- const err = e;
480
- if (err.code === 'EPERM')
481
- return true;
482
- return false;
483
- }
484
- }
485
- function getDaemonCliPath() {
486
- const distCli = path.join(__dirname, '..', 'cli.js');
487
- if (fs.existsSync(distCli)) {
488
- return distCli;
489
- }
490
- throw new Error(`Cron CLI entrypoint not found: ${distCli}`);
491
- }
492
- function isDaemonRunning(dir) {
493
- const pid = readDaemonPid(dir);
494
- if (!pid) {
495
- return false;
496
- }
497
- const alive = isPidAlive(pid);
498
- if (!alive) {
499
- removeDaemonPidFile(dir);
500
- }
501
- return alive;
502
- }
503
- function startDaemon(dir) {
504
- if (isDaemonRunning(dir)) {
505
- const pid = readDaemonPid(dir);
506
- if (pid)
507
- return pid;
508
- throw new Error('Daemon is already running');
509
- }
510
- ensureDataDir(dir);
511
- const cliPath = getDaemonCliPath();
512
- const child = (0, child_process_1.spawn)('node', [cliPath, 'daemon', '--foreground'], {
513
- cwd: dir || undefined,
514
- detached: true,
515
- stdio: 'ignore',
516
- windowsHide: true,
517
- });
518
- if (!child.pid) {
519
- throw new Error('Failed to start daemon: no PID returned');
520
- }
521
- child.unref();
522
- writeDaemonPid(child.pid, dir);
523
- return child.pid;
524
- }
525
- function stopDaemon(dir) {
526
- const pid = readDaemonPid(dir);
527
- if (!pid) {
528
- removeDaemonPidFile(dir);
529
- return false;
530
- }
531
- if (!isPidAlive(pid)) {
532
- removeDaemonPidFile(dir);
533
- return false;
534
- }
535
- process.kill(pid, 'SIGTERM');
536
- return true;
537
- }
538
- function ensureDaemonRunning(dir) {
539
- if (isDaemonRunning(dir))
540
- return;
541
- console.log('⚡ cron 데몬이 꺼져 있어서 자동 시작합니다...');
542
- try {
543
- startDaemon(dir);
544
- }
545
- catch (e) {
546
- const err = e;
547
- console.error('Failed to auto-start daemon:', err.message);
548
- process.exit(1);
549
- }
550
- }
551
- function getDaemonStatus(dir) {
552
- const schedules = loadSchedules(dir);
553
- const enabled = schedules.filter(s => s.enabled).length;
554
- const pid = readDaemonPid(dir);
555
- const running = pid !== null && isDaemonRunning(dir);
556
- let started_at = null;
557
- const pidFile = resolvePidFile(dir);
558
- if (running && fs.existsSync(pidFile)) {
559
- try {
560
- started_at = fs.statSync(pidFile).mtime.toISOString();
561
- }
562
- catch {
563
- started_at = null;
564
- }
565
- }
566
- return {
567
- running,
568
- pid: running ? pid : null,
569
- started_at,
570
- schedules: {
571
- total: schedules.length,
572
- enabled,
573
- },
574
- };
575
- }
576
- async function executeSchedule(schedule, dir) {
577
- if (!schedule.allow_concurrent && runningSchedules.has(schedule.id)) {
578
- console.log(`[${getLocalTimestamp()}] Schedule ${schedule.id} already running, skipping`);
579
- return { success: false, error: 'Already running' };
580
- }
581
- runningSchedules.add(schedule.id);
582
- const workingDir = schedule.working_directory || getProjectRoot();
583
- const mode = schedule.mode;
584
- const modeFlag = mode === 'execute' ? 'x' : 'q';
585
- console.log(`[${getLocalTimestamp()}] Executing schedule ${schedule.id}: ${schedule.name ?? schedule.cron}`);
586
- console.log(`[${getLocalTimestamp()}] Command: ${schedule.command}`);
587
- return new Promise((resolve) => {
588
- const env = { ...process.env, ...schedule.env };
589
- delete env['CLAUDECODE'];
590
- const child = mode === 'command'
591
- ? (0, child_process_1.spawn)('sh', ['-c', schedule.command], {
592
- cwd: workingDir,
593
- stdio: 'inherit',
594
- windowsHide: true,
595
- env,
596
- })
597
- : (0, child_process_1.spawn)('crewx', [modeFlag, schedule.command], {
598
- cwd: workingDir,
599
- stdio: 'inherit',
600
- windowsHide: true,
601
- env,
602
- });
603
- const timeout = setTimeout(() => {
604
- console.log(`[${getLocalTimestamp()}] Schedule ${schedule.id} timeout, killing process`);
605
- try {
606
- child.kill('SIGTERM');
607
- }
608
- catch (e) {
609
- const err = e;
610
- console.error(`Failed to kill process: ${err.message}`);
611
- }
612
- runningSchedules.delete(schedule.id);
613
- resolve({ success: false, error: 'Timeout' });
614
- }, schedule.timeout_ms || 600000);
615
- child.on('close', (code) => {
616
- clearTimeout(timeout);
617
- runningSchedules.delete(schedule.id);
618
- const schedules = loadSchedules(dir);
619
- const idx = schedules.findIndex(s => s.id === schedule.id);
620
- if (idx !== -1) {
621
- const s = schedules[idx];
622
- s.last_run = new Date().toISOString();
623
- s.run_count = (s.run_count || 0) + 1;
624
- s.next_run = calculateNextRun(schedule.cron, schedule.timezone);
625
- saveSchedules(schedules, dir);
626
- }
627
- if (code === 0) {
628
- console.log(`[${getLocalTimestamp()}] Schedule ${schedule.id} completed successfully`);
629
- resolve({ success: true });
630
- }
631
- else {
632
- console.log(`[${getLocalTimestamp()}] Schedule ${schedule.id} failed with code ${code}`);
633
- resolve({ success: false, error: `Exit code: ${code}` });
634
- }
635
- });
636
- child.on('error', (err) => {
637
- clearTimeout(timeout);
638
- runningSchedules.delete(schedule.id);
639
- console.error(`[${getLocalTimestamp()}] Schedule ${schedule.id} error: ${err.message}`);
640
- resolve({ success: false, error: err.message });
641
- });
642
- });
643
- }
644
- function getLocalMinuteKey(now, timezone) {
645
- if (!timezone || !isValidTimezone(timezone)) {
646
- 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')}`;
647
- }
648
- const fmt = new Intl.DateTimeFormat('en-CA', {
649
- timeZone: timezone,
650
- year: 'numeric', month: '2-digit', day: '2-digit',
651
- hour: '2-digit', minute: '2-digit', hour12: false,
652
- });
653
- return fmt.format(now);
654
- }
655
- async function daemonTick(dir) {
656
- const now = new Date();
657
- const schedules = loadSchedules(dir);
658
- const enabledSchedules = schedules.filter(s => s.enabled);
659
- if (enabledSchedules.length === 0) {
660
- return;
661
- }
662
- for (const schedule of enabledSchedules) {
663
- if (shouldRunNow(schedule.cron, now, schedule.timezone)) {
664
- const minuteKey = getLocalMinuteKey(now, schedule.timezone);
665
- if (schedule.last_run) {
666
- const lastRunMinuteKey = getLocalMinuteKey(new Date(schedule.last_run), schedule.timezone);
667
- if (lastRunMinuteKey === minuteKey) {
668
- continue;
669
- }
670
- }
671
- await executeSchedule(schedule, dir);
672
- }
673
- }
674
- }
675
- function runDaemonForeground(dir) {
676
- writeDaemonPid(process.pid, dir);
677
- let cleanedUp = false;
678
- const cleanup = () => {
679
- if (cleanedUp)
680
- return;
681
- cleanedUp = true;
682
- const pid = readDaemonPid(dir);
683
- if (pid === process.pid) {
684
- removeDaemonPidFile(dir);
685
- }
686
- };
687
- process.on('SIGTERM', () => {
688
- cleanup();
689
- process.exit(0);
690
- });
691
- process.on('SIGINT', () => {
692
- cleanup();
693
- process.exit(0);
694
- });
695
- process.on('exit', cleanup);
696
- console.log(`[${getLocalTimestamp()}] Cron daemon starting in foreground mode`);
697
- console.log(`[${getLocalTimestamp()}] Check interval: 1 minute`);
698
- console.log(`[${getLocalTimestamp()}] Press Ctrl+C to stop\n`);
699
- const schedules = loadSchedules(dir);
700
- const enabled = schedules.filter(s => s.enabled).length;
701
- console.log(`[${getLocalTimestamp()}] Active schedules: ${enabled}/${schedules.length}\n`);
702
- void daemonTick(dir);
703
- setInterval(() => {
704
- void daemonTick(dir);
705
- }, DAEMON_INTERVAL_MS);
706
- }
707
- function addSchedule(cron, command, options, dataDir) {
708
- if (options.timezone && !isValidTimezone(options.timezone)) {
709
- throw new Error(`Invalid timezone: ${options.timezone}`);
710
- }
711
- const schedule = {
712
- id: (0, sdk_1.generateId)('cron'),
713
- cron,
714
- command,
715
- mode: normalizeMode(options.mode ?? 'execute'),
716
- enabled: true,
717
- name: options.name ?? null,
718
- working_directory: options.dir ?? null,
719
- timezone: options.timezone ?? null,
720
- timeout_ms: parseInt(options.timeout ?? '600000', 10),
721
- allow_concurrent: options.allowConcurrent ?? false,
722
- created_at: new Date().toISOString(),
723
- updated_at: new Date().toISOString(),
724
- run_count: 0,
725
- };
726
- schedule.next_run = calculateNextRun(cron, schedule.timezone);
727
- const schedules = loadSchedules(dataDir);
728
- schedules.push(schedule);
729
- saveSchedules(schedules, dataDir);
730
- return schedule;
731
- }
732
- function removeSchedule(id, dir) {
733
- const schedules = loadSchedules(dir);
734
- const idx = schedules.findIndex(s => s.id === id);
735
- if (idx === -1) {
736
- throw new Error(`Schedule not found: ${id}`);
737
- }
738
- const removed = schedules.splice(idx, 1)[0];
739
- saveSchedules(schedules, dir);
740
- return removed;
741
- }
742
- function enableSchedule(id, dir) {
743
- const schedules = loadSchedules(dir);
744
- const schedule = schedules.find(s => s.id === id);
745
- if (!schedule) {
746
- throw new Error(`Schedule not found: ${id}`);
747
- }
748
- schedule.enabled = true;
749
- schedule.updated_at = new Date().toISOString();
750
- schedule.next_run = calculateNextRun(schedule.cron, schedule.timezone);
751
- saveSchedules(schedules, dir);
752
- return schedule;
753
- }
754
- function disableSchedule(id, dir) {
755
- const schedules = loadSchedules(dir);
756
- const schedule = schedules.find(s => s.id === id);
757
- if (!schedule) {
758
- throw new Error(`Schedule not found: ${id}`);
759
- }
760
- schedule.enabled = false;
761
- schedule.updated_at = new Date().toISOString();
762
- saveSchedules(schedules, dir);
763
- return schedule;
764
- }
765
- function updateSchedule(id, options, dir) {
766
- const schedules = loadSchedules(dir);
767
- const schedule = schedules.find(s => s.id === id);
768
- if (!schedule) {
769
- throw new Error(`Schedule not found: ${id}`);
770
- }
771
- if (options.timezone !== undefined && options.timezone !== null && !isValidTimezone(options.timezone)) {
772
- throw new Error(`Invalid timezone: ${options.timezone}`);
773
- }
774
- const changes = [];
775
- if (options.cron !== undefined) {
776
- schedule.cron = options.cron;
777
- changes.push(`cron: ${options.cron}`);
778
- }
779
- if (options.command !== undefined) {
780
- schedule.command = options.command;
781
- changes.push(`command: ${options.command}`);
782
- }
783
- if (options.name !== undefined) {
784
- schedule.name = options.name;
785
- changes.push(`name: ${options.name}`);
786
- }
787
- if (options.mode !== undefined) {
788
- schedule.mode = normalizeMode(options.mode);
789
- changes.push(`mode: ${schedule.mode}`);
790
- }
791
- if (options.timezone !== undefined) {
792
- schedule.timezone = options.timezone;
793
- changes.push(`timezone: ${options.timezone}`);
794
- }
795
- if (options.working_directory !== undefined) {
796
- schedule.working_directory = options.working_directory;
797
- changes.push(`working_directory: ${options.working_directory}`);
798
- }
799
- if (options.timeout_ms !== undefined) {
800
- schedule.timeout_ms = options.timeout_ms;
801
- changes.push(`timeout: ${options.timeout_ms}ms`);
802
- }
803
- if (options.allow_concurrent !== undefined) {
804
- schedule.allow_concurrent = options.allow_concurrent;
805
- changes.push(`allow_concurrent: ${options.allow_concurrent}`);
806
- }
807
- schedule.updated_at = new Date().toISOString();
808
- schedule.next_run = calculateNextRun(schedule.cron, schedule.timezone);
809
- saveSchedules(schedules, dir);
810
- return { schedule, changes };
811
- }
812
- exports.DATA_PATHS = {
813
- DATA_DIR,
814
- SCHEDULES_FILE,
815
- LOG_FILE,
816
- USAGE_LOG_FILE,
817
- PID_FILE,
818
- };
819
- //# sourceMappingURL=engine.js.map
1
+ "use strict";var Rn=exports&&exports.__createBinding||(Object.create?(function(e,n,t,r){if(r===void 0)r=t;var i=Object.getOwnPropertyDescriptor(n,t);if(!i||("get"in i?!n.__esModule:i.writable||i.configurable)){i={enumerable:true,get:function(){return n[t]}}}Object.defineProperty(e,r,i)}):(function(e,n,t,r){if(r===void 0)r=t;e[r]=n[t]}));var On=exports&&exports.__setModuleDefault||(Object.create?(function(e,n){Object.defineProperty(e,"default",{enumerable:true,value:n})}):function(e,n){e["default"]=n});var Ve=exports&&exports.__importStar||(function(){var e=function(n){e=Object.getOwnPropertyNames||function(t){var r=[];for(var i in t)if(Object.prototype.hasOwnProperty.call(t,i))r[r.length]=i;return r};return e(n)};return function(n){if(n&&n.__esModule)return n;var t={};if(n!=null){for(var r=e(n),i=0;i<r.length;i++)if(r[i]!=="default")Rn(t,n,r[i])}On(t,n);return t}})();Object.defineProperty(exports,"__esModule",{value:true});exports.DATA_PATHS=exports.scheduledPeriodicAvailability=exports.CRON_RUNTIME_PROCESS_LEASE_FILENAME=exports.createCronRuntime=exports.SCHEDULE_RUN_SWEEP_INTERVAL_MS=exports.getProjectRoot=exports.BUILTIN_SCHEDULES=exports.CronStoreError=void 0;exports.recognizeBuiltinRow=ye;exports.getLocalTimestamp=S;exports.logUsage=jn;exports.acquireWorkspaceLock=ne;exports.releaseWorkspaceLock=Z;exports.migrateLegacyMemoryDebounceSchedule=zn;exports.normalizeMode=Oe;exports.effectiveEnabled=me;exports.createScheduleStore=A;exports.loadSchedules=tn;exports.saveSchedules=Zn;exports.getRemovedBuiltins=on;exports.seedBuiltinSchedules=et;exports.acquireLock=tt;exports.releaseLock=rt;exports.releaseRunLock=sn;exports.shouldRunNow=Ne;exports.describeCron=ft;exports.calculateNextRun=K;exports.isDaemonRunning=be;exports.startDaemon=_t;exports.stopDaemon=gt;exports.startDaemonSafe=_n;exports.ensureDaemonRunning=ht;exports.getDaemonStatus=wt;exports.executeSchedule=wn;exports.isHighFrequencyCron=En;exports.sweepScheduleRuns=kt;exports.getLocalMinuteKey=In;exports.buildOccurrenceKey=Cn;exports.daemonTick=xe;exports.runDaemonForeground=Ht;exports.addSchedule=Wt;exports.removeSchedule=Bt;exports.enableSchedule=zt;exports.disableSchedule=Kt;exports.updateSchedule=Gt;const V=require("child_process");const le=require("crypto");const m=Ve(require("path"));const f=Ve(require("fs"));const C=require("./types");Object.defineProperty(exports,"CronStoreError",{enumerable:true,get:function(){return C.CronStoreError}});const p=require("@crewx/sdk");const F=require("./builtins");Object.defineProperty(exports,"BUILTIN_SCHEDULES",{enumerable:true,get:function(){return F.BUILTIN_SCHEDULES}});Object.defineProperty(exports,"getProjectRoot",{enumerable:true,get:function(){return F.getProjectRoot}});const k=require("./run-record");const w=m.resolve(process.cwd());const J=m.join(w,".crewx");const Tn=m.join(J,".cron-data.json");const kn=m.join(J,".cron-daemon.log");const Pn=m.join(J,".cron-data.json.lock");const Ln=m.join(J,"cron-usage.log");const Nn=m.join(J,".cron-daemon.pid");const vn=m.join(J,".cron-builtins.json");function z(e){return m.join(m.resolve(e??w),".crewx")}function fe(e){return m.join(z(e),".cron-data.json")}function Je(e){return m.join(z(e),".cron-data.json.lock")}function $n(e){return m.join(z(e),"cron-usage.log")}function ee(e){return m.join(z(e),".cron-daemon.pid")}function Xe(e){return m.join(z(e),".cron-builtins.json")}function bn(e,n,t,r){const i=(0,p.reserveUpdateAdmission)(e,n,t,r);if(!i){console.error(`[${S()}] Failed to record update-admission reservation for task ${e}`)}return i}function Un(e){(0,p.releaseUpdateAdmissionReservation)(e)}function Ce(e,n){return m.join(z(n),`.cron-run-${e}.lock`)}const Fn=60*1e3;const Ze=5e3;const Mn=25;const xn=3e4;const De=5e3;function S(){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 i=String(e.getHours()).padStart(2,"0");const c=String(e.getMinutes()).padStart(2,"0");const o=String(e.getSeconds()).padStart(2,"0");return`${n}-${t}-${r} ${i}:${c}:${o}`}function jn(e,...n){const t=a=>a!==void 0&&(a==="."||a===".."||m.isAbsolute(a)||a.includes(m.sep)||a.includes("/")||a.includes("\\")||a.endsWith(".crewx")||(()=>{try{return f.statSync(a).isDirectory()}catch{return false}})());let r;let i;if(n.length>0&&(e===void 0||t(e))){r=e;i=n}else if(n.length===0&&t(e)){r=e;i=[]}else if(n.length===1&&t(n[0])){r=n[0];i=e===void 0?[]:[e]}else{r=void 0;i=e===void 0?n:[e,...n]}X(r);const c=S();const o=i.join(" ");const s=`${c} | [cron] ${o}
2
+ `;f.appendFileSync($n(r),s)}function X(e){const n=z(e);if(!f.existsSync(n)){f.mkdirSync(n,{recursive:true})}}function Ae(e){try{return JSON.parse(f.readFileSync(e,"utf8"))}catch{return null}}const Hn=new Int32Array(new SharedArrayBuffer(4));function Wn(){Atomics.wait(Hn,0,0,Mn)}function Ye(e){try{const n=JSON.parse(f.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 ne(e,n=Ze){const t=m.resolve(e);X(t);const r=Je(t);const i=(0,le.randomUUID)();const c=Date.now()+Math.max(0,n);const o={workspacePath:t,lockPath:r,token:i};while(true){const s={token:i,pid:process.pid,created_at:Date.now()};try{f.writeFileSync(r,JSON.stringify(s),{flag:"wx",mode:384});return o}catch(a){const l=a;if(l.code!=="EEXIST")throw a;const u=Ye(r);if(u&&Date.now()-u.created_at>xn){Z({...o,token:u.token});continue}if(Date.now()>=c){throw new C.CronStoreError("CRON_STORE_LOCK_TIMEOUT",`Timed out waiting for cron store lock: ${r}`,r)}Wn()}}}function Z(e){const n=Ye(e.lockPath);if(!n||n.token!==e.token)return false;try{f.unlinkSync(e.lockPath);return true}catch(t){const r=t;if(r.code==="ENOENT")return false;throw t}}const Re=(0,F.findBuiltinById)("memory-debounce");function Bn(e){if(!e.includes("/skills/memory-v2/memory-v2.js")){return e}return Re.buildCommand()}function zn(e=(0,F.getProjectRoot)()){const n=m.resolve(e);const t=m.join(n,"skills","cron",".cron-data.json");if(!f.existsSync(t)){return}const r=Ae(t);if(!Array.isArray(r)){return}const i=r.find(a=>a.name===Re.name);if(!i){return}const c=A(n);const o=c.list();if(o.some(a=>a.name===Re.name)){return}const s={...i,command:Bn(i.command),updated_at:new Date().toISOString(),next_run:K(i.cron,i.timezone)};c.create(s)}function Qe(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 D(e){return{...e,...e.env?{env:{...e.env}}:{}}}function te(e){let n;try{n=f.readFileSync(e,"utf8")}catch(r){const i=r;if(i.code==="ENOENT")return[];throw r}let t;try{t=JSON.parse(n)}catch(r){throw new C.CronStoreError("CRON_STORE_CORRUPT",`Cron schedule store is not valid JSON: ${e}`,e)}if(!Array.isArray(t)){throw new C.CronStoreError("CRON_STORE_CORRUPT",`Cron schedule store must contain a top-level array: ${e}`,e)}for(const r of t)Gn(r,e);return t}const Kn=["apps","skills","bundled"];function me(e){return e.app!==void 0?e.app.enabled===true:e.enabled===true}function Gn(e,n){if(typeof e!=="object"||e===null||Array.isArray(e)){throw new C.CronStoreError("CRON_STORE_CORRUPT",`Cron schedule store has a row that is not an object: ${n}`,n)}const t=e;if(t.mode==="app"){throw new C.CronStoreError("CRON_STORE_CORRUPT",`Cron schedule store has a row in the retired app mode: ${n}`,n)}if(t.app!==void 0&&!qn(t.app)){throw new C.CronStoreError("CRON_STORE_CORRUPT",`Cron schedule store has a malformed app binding: ${n}`,n)}if(t.last_error_code!==void 0&&(t.app===void 0||!(0,p.isAppScheduleCode)(t.last_error_code))){throw new C.CronStoreError("CRON_STORE_CORRUPT",`Cron schedule store has an error code that is not a managed row's stable code: ${n}`,n)}}function en(e){if(typeof e!=="object"||e===null)return false;const n=e;return typeof n.appId==="string"&&n.appId.length>0&&typeof n.actionId==="string"&&n.actionId.length>0&&typeof n.source==="string"&&Kn.includes(n.source)}function qn(e){return en(e)&&typeof e.enabled==="boolean"}function nn(e,n){if(n===void 0||n===null)delete e.last_error_code;else e.last_error_code=n}function Te(e){return new C.CronStoreError("CRON_SCHEDULE_MANAGED",`CRON_SCHEDULE_MANAGED: ${e.id} is managed by app ${e.app?.appId??"(unknown)"}; change it from the App`)}function Vn(e,n){if(e.app===void 0)return;const t=Object.entries(n).every(([r,i])=>i===void 0||r==="enabled"&&i===false);if(!t)throw Te(e)}function Jn(e){if(process.platform==="win32")return;try{const n=f.openSync(e,"r");try{f.fsyncSync(n)}finally{f.closeSync(n)}}catch{}}function ke(e,n){const t=m.dirname(e);const r=m.join(t,`.${m.basename(e)}.${process.pid}.${(0,le.randomUUID)()}.tmp`);const i=JSON.stringify(n,null,2);let c;try{c=f.openSync(r,"wx",384);f.writeFileSync(c,i);f.fsyncSync(c);f.closeSync(c);c=void 0;f.renameSync(r,e);Jn(t)}finally{if(c!==void 0){try{f.closeSync(c)}catch{}}try{f.unlinkSync(r)}catch{}}}function re(e,n){const t=e.find(r=>r.id===n);if(!t){throw new C.CronStoreError("CRON_SCHEDULE_NOT_FOUND",`Schedule not found: ${n}`)}return t}function M(e,n){const t=ne(e);try{const r=fe(e);const i=te(r);const c=n(i);ke(r,i);return c}finally{Z(t)}}function Xn(e,n){const t=ne(e);try{const r=fe(e);const i=te(r);const c=n(i);if(c)ke(r,i);return c}finally{Z(t)}}function A(e=w){const n=m.resolve(e);const t=z(n);const r=fe(n);const i=Je(n);return{workspacePath:n,dataDir:t,schedulesFile:r,lockFile:i,list(){return te(r).map(D)},get(c){const o=te(r).find(s=>s.id===c);return o?D(o):void 0},create(c){if(c.app!==void 0){throw new C.CronStoreError("CRON_SCHEDULE_MANAGED","CRON_SCHEDULE_MANAGED: an app schedule is created only by the App path")}return M(n,o=>{if(o.some(s=>s.id===c.id)){throw new C.CronStoreError("CRON_SCHEDULE_EXISTS",`Schedule already exists: ${c.id}`)}o.push(D(c));return D(c)})},update(c,o){return M(n,s=>{const a=re(s,c);Vn(a,o);if(o.cron!==void 0)a.cron=o.cron;if(o.command!==void 0)a.command=o.command;if(o.mode!==void 0)a.mode=o.mode;if(o.enabled!==void 0){if(a.app!==void 0)a.app={...a.app,enabled:o.enabled};else a.enabled=o.enabled}if(o.name!==void 0)a.name=o.name;if(o.working_directory!==void 0)a.working_directory=o.working_directory;if(o.timezone!==void 0)a.timezone=o.timezone;if(o.timeout_ms!==void 0)a.timeout_ms=o.timeout_ms;if(o.allow_concurrent!==void 0)a.allow_concurrent=o.allow_concurrent;if(o.env!==void 0){if(o.env===null)delete a.env;else a.env={...o.env}}if(o.model!==void 0){if(o.model===null)delete a.model;else a.model=o.model}if(o.provider!==void 0){if(o.provider===null)delete a.provider;else a.provider=o.provider}a.updated_at=new Date().toISOString();a.next_run=a.app!==void 0&&!a.app.enabled?null:K(a.cron,a.timezone);return D(a)})},remove(c){return M(n,o=>{const s=o.findIndex(l=>l.id===c);if(s===-1){throw new C.CronStoreError("CRON_SCHEDULE_NOT_FOUND",`Schedule not found: ${c}`)}if(o[s].app!==void 0)throw Te(o[s]);const[a]=o.splice(s,1);return D(a)})},toggle(c,o){return M(n,s=>{const a=re(s,c);const l=o??!me(a);const u=new Date().toISOString();if(a.app!==void 0){if(l)throw Te(a);a.app={...a.app,enabled:false};a.next_run=null;a.updated_at=u;return D(a)}a.enabled=l;a.updated_at=u;if(a.enabled){a.next_run=K(a.cron,a.timezone)}return D(a)})},setAppSchedule(c,o,s){if(!en(c))throw new TypeError("setAppSchedule needs an owner with appId, actionId and source");return M(n,a=>{const l=new Date().toISOString();let u=a.find(y=>y.app?.appId===c.appId);const R=u?.app?.enabled===true;if(u===void 0){u={id:(0,p.generateId)("cron"),cron:o.cron,command:p.APP_SCHEDULE_INERT_COMMAND,mode:"command",enabled:false,name:o.name,working_directory:null,timezone:null,timeout_ms:o.timeout_ms,allow_concurrent:false,created_at:l,updated_at:l,run_count:0};a.push(u)}const E=s??u.app?.enabled??false;u.mode="command";u.command=p.APP_SCHEDULE_INERT_COMMAND;u.enabled=false;u.app={appId:c.appId,actionId:c.actionId,source:c.source,enabled:E};u.cron=o.cron;u.timeout_ms=o.timeout_ms;u.name=o.name;u.allow_concurrent=false;u.next_run=E?K(u.cron,u.timezone):null;u.updated_at=l;return{schedule:D(u),started:!R&&E}})},recordSkipped(c,o){if(o.errorCode!==null&&!(0,p.isAppScheduleCode)(o.errorCode)){throw new TypeError("recordSkipped needs a stable error code")}return M(n,s=>{const a=re(s,c);if(a.app===void 0)throw new TypeError("recordSkipped closes only a managed row");const l=o.finishedAt??new Date().toISOString();a.last_run=l;a.last_outcome=o.outcome;nn(a,o.errorCode);a.updated_at=l;return D(a)})},removeAppSchedule(c){return M(n,o=>{const s=o.findIndex(l=>l.app?.appId===c);if(s===-1)return void 0;const[a]=o.splice(s,1);return D(a)})},claimOccurrence(c,o){return M(n,s=>{const a=re(s,typeof c==="string"?c:c.id);if(a.last_occurrence_key===o.occurrenceKey){return{claimed:false,schedule:D(a)}}const l=o.startedAt??new Date().toISOString();a.last_started_at=l;a.last_occurrence_key=o.occurrenceKey;a.updated_at=l;return{claimed:true,schedule:D(a)}})},recordStarted(c,o){return M(n,s=>{const a=re(s,typeof c==="string"?c:c.id);const l=typeof o==="string"?{startedAt:o}:o;const u=l?.startedAt??new Date().toISOString();a.last_started_at=u;if(l?.occurrenceKey!==void 0){a.last_occurrence_key=l.occurrenceKey}a.updated_at=u;return D(a)})},recordFinished(c,o){if(o?.errorCode!==void 0&&!(0,p.isAppScheduleCode)(o.errorCode)){throw new TypeError("recordFinished needs a stable error code")}return M(n,s=>{const a=re(s,typeof c==="string"?c:c.id);const l=o?.finishedAt??new Date().toISOString();a.last_run=l;a.run_count=(a.run_count||0)+1;a.next_run=o?.nextRun!==void 0?o.nextRun:K(a.cron,a.timezone);if(o?.outcome!==void 0)a.last_outcome=o.outcome;else if(o?.success!==void 0)a.last_outcome=o.success?"success":"failure";if(a.app!==void 0)nn(a,o?.errorCode);a.updated_at=l;return D(a)})}}}function tn(e){return A(e??w).list()}function Zn(e,n){const t=m.resolve(n??w);const r=ne(t);try{const i=fe(t);te(i);ke(i,e.map(D))}finally{Z(r)}}function rn(e){const n=Ae(Xe(e));return{removed_builtins:Array.isArray(n?.removed_builtins)?n.removed_builtins:[]}}function Yn(e,n){X(n);const t=Xe(n);const r=`${t}.tmp`;f.writeFileSync(r,JSON.stringify(e,null,2));f.renameSync(r,t)}function on(e){return rn(e).removed_builtins}function Qn(e,n){const t=rn(n);if(!t.removed_builtins.includes(e)){t.removed_builtins.push(e);Yn(t,n)}}function et(e){const n=A(e??w);const t=n.list();const r=new Set(t.map(o=>o.name));const i=new Set(on(e));const c=[];for(const o of F.BUILTIN_SCHEDULES){if(r.has(o.name))continue;if(i.has(o.builtin_id))continue;const s=new Date().toISOString();const a={id:(0,p.generateId)("cron"),cron:o.cron,command:o.buildCommand(),mode:o.mode,enabled:true,name:o.name,working_directory:null,timezone:null,timeout_ms:6e5,allow_concurrent:false,created_at:s,updated_at:s,run_count:0,next_run:K(o.cron,null)};n.create(a);r.add(o.name);c.push(o.builtin_id)}nt(m.resolve(e??w));return c}function ye(e){if(e.app!==void 0)return void 0;const n=(0,F.findBuiltinByName)(e.name);if(!n||e.mode!==n.mode||typeof e.command!=="string")return void 0;const t=(0,F.normalizeBuiltinCommand)(e.command);return t===n.buildCommand()||n.isLegacyCommand(t)?n:void 0}function cn(e){const n=ye(e);return n?.isFrozenPathCommand((0,F.normalizeBuiltinCommand)(e.command))?n:void 0}function nt(e){if(!te(fe(e)).some(n=>cn(n)!==void 0)){return}Xn(e,n=>{let t=false;for(const r of n){const i=cn(r);if(i===void 0)continue;r.command=i.buildCommand();t=true}return t})}const Pe=new Map;async function tt(e=Ze,n){const t=m.resolve(n??w);try{Pe.set(t,ne(t,e));return true}catch(r){if(r instanceof C.CronStoreError&&r.code==="CRON_STORE_LOCK_TIMEOUT")return false;throw r}}function rt(e){const n=m.resolve(e??w);const t=Pe.get(n);if(!t)return;Pe.delete(n);Z(t)}function oe(e){return Ae(e)}function ot(e,n){const t=m.dirname(e);const r=m.join(t,`.${m.basename(e)}.${process.pid}.${(0,le.randomUUID)()}.tmp`);let i;try{i=f.openSync(r,"wx",384);f.writeFileSync(i,JSON.stringify(n));f.fsyncSync(i);f.closeSync(i);i=void 0;f.renameSync(r,e)}finally{if(i!==void 0){try{f.closeSync(i)}catch{}}try{f.unlinkSync(r)}catch{}}}function Le(e,n,t){const r=ne(e);try{return t(Ce(n,e))}finally{Z(r)}}function it(e){try{return Math.max(0,Date.now()-f.statSync(e).mtimeMs)}catch{return null}}function an(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 ct(e,n,t){if(e.phase!=="child"){const s=it(n);if(s===null)return false;const a=e.hostPid??e.pid;const l=ae(a);return l===false||s>t+De}const r=e.childPid??e.pid;const i=ae(r);if(i===false)return true;if(i!==true)return false;if(!e.childIdentity)return false;const c=$e(r);if(!c)return false;const o=an(e.childIdentity,c);if(o===false)return true;return false}function at(e,n,t,r){const i=m.resolve(r??w);X(i);const c=Ce(e,i);const o=(0,le.randomUUID)();const s=(0,le.randomUUID)();const a={leaseId:o,ownerToken:s,phase:"admitting",hostPid:process.pid,pid:process.pid,runId:n,startedAt:new Date().toISOString()};return Le(i,e,()=>{for(let u=0;u<2;u++){try{f.writeFileSync(c,JSON.stringify(a),{flag:"wx",mode:384});return{acquired:true,lease:{scheduleId:e,workspacePath:i,lockFile:c,leaseId:o,ownerToken:s,runId:n}}}catch(R){const E=R;if(E.code!=="EEXIST")throw E;const y=oe(c);if(!y){return{acquired:false,runningTaskId:"",reason:"execution_lease"}}if(ct(y,c,t)){const L=oe(c);if(L?.leaseId!==void 0&&L.leaseId!==y.leaseId){continue}try{f.unlinkSync(c)}catch{}continue}return{acquired:false,runningTaskId:y.taskId??"",runningRunId:y.runId,reason:"execution_lease"}}}const l=oe(c);return{acquired:false,runningTaskId:l?.taskId??"",runningRunId:l?.runId,reason:"execution_lease"}})}function st(e,n,t,r){return Le(e.workspacePath,e.scheduleId,i=>{const c=oe(i);if(!c||c.leaseId!==e.leaseId||c.ownerToken!==e.ownerToken){return false}ot(i,{...c,phase:"child",pid:n,childPid:n,childIdentity:t,...r===void 0?{}:{taskId:r}});return true})}function sn(e,n,t){const r=m.resolve(t??w);X(r);return Le(r,e,i=>{const c=oe(i);if(!c||c.leaseId!==n)return false;try{f.unlinkSync(i);return true}catch(o){const s=o;if(s.code==="ENOENT")return false;throw o}})}const ut={SUN:0,MON:1,TUE:2,WED:3,THU:4,FRI:5,SAT:6};const dt={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 ie(e,n){const t=e.toUpperCase();if(n===4){const r=ut[t];if(r!==void 0)return r;const i=parseInt(e,10);return i===7?0:i}if(n===3){const r=dt[t];if(r!==void 0)return r}return parseInt(e,10)}function lt(e,n,t){if(e==="*")return true;if(e.includes("/")){const r=e.indexOf("/");const i=e.slice(0,r);const c=parseInt(e.slice(r+1),10);if(isNaN(c)||c<=0)return false;if(i==="*"){return n%c===0}if(i.includes("-")){const s=i.indexOf("-");const a=ie(i.slice(0,s),t);const l=ie(i.slice(s+1),t);return n>=a&&n<=l&&(n-a)%c===0}const o=ie(i,t);return n>=o&&(n-o)%c===0}if(e.includes("-")){const r=e.indexOf("-");const i=ie(e.slice(0,r),t);const c=ie(e.slice(r+1),t);if(i<=c){return n>=i&&n<=c}return n>=i||n<=c}return ie(e,t)===n}function P(e,n,t){return e.split(",").some(r=>lt(r.trim(),n,t))}const un=new Map;function ce(e){const n=un.get(e);if(n!==void 0)return n;let t;try{Intl.DateTimeFormat("en-US",{timeZone:e});t=true}catch{t=false}un.set(e,t);return t}const dn=new Map;function ln(e,n,t){const r=`${e}:${n}`;let i=dn.get(r);if(i===void 0){i=new Intl.DateTimeFormat("en-US",t);dn.set(r,i)}return i}function Ne(e,n,t){let r=n;if(t){if(!ce(t)){console.error(`Invalid timezone: ${t}, skipping execution`);return false}else{const O=ln("cron",t,{timeZone:t,year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",hour12:false});const $=O.formatToParts(n);const I=j=>parseInt($.find(H=>H.type===j)?.value??"0",10);const N=I("year");const b=I("month");const G=I("day");r={getMinutes:()=>I("minute"),getHours:()=>I("hour"),getDate:()=>I("day"),getMonth:()=>I("month")-1,getDay:()=>new Date(N,b-1,G).getDay()}}}const i=e.trim().split(/\s+/);if(i.length!==5){console.error(`Invalid cron expression: ${e}`);return false}const[c,o,s,a,l]=i;const u=r.getMinutes();const R=r.getHours();const E=r.getDate();const y=r.getMonth()+1;const L=r.getDay();return P(c,u,0)&&P(o,R,1)&&P(s,E,2)&&P(a,y,3)&&P(l,L,4)}function ft(e){const n=e.trim().split(/\s+/);if(n.length!==5)return e;const[t,r,i,c,o]=n;if(t==="0"&&r!=="*"&&i==="*"&&c==="*"&&o==="*"){return`Daily at ${r}:00`}if(t!=="*"&&r!=="*"&&i==="*"&&c==="*"&&o==="*"){return`Daily at ${r}:${t.padStart(2,"0")}`}if(e==="* * * * *"){return"Every minute"}if(t==="*/5"&&r==="*"&&i==="*"&&c==="*"&&o==="*"){return"Every 5 minutes"}return e}function ve(e,n){if(n&&ce(n)){const t=ln("parts",n,{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 i=s=>parseInt(r.find(a=>a.type===s)?.value??"0",10);const c=r.find(s=>s.type==="weekday")?.value??"";const o={Sun:0,Mon:1,Tue:2,Wed:3,Thu:4,Fri:5,Sat:6};return{minute:i("minute"),hour:i("hour"),day:i("day"),month:i("month"),weekday:o[c]??e.getDay()}}return{minute:e.getMinutes(),hour:e.getHours(),day:e.getDate(),month:e.getMonth()+1,weekday:e.getDay()}}function mt(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 K(e,n){const t=e.trim().split(/\s+/);if(t.length!==5)return null;const[r,i,c,o,s]=t;const a=[];for(let y=0;y<24;y++){if(P(i,y,1))a.push(y)}const l=[];for(let y=0;y<60;y++){if(P(r,y,0))l.push(y)}if(a.length===0||l.length===0)return null;const u=new Date;const R=n&&ce(n);const E=ve(u,n);for(let y=0;y<=366;y++){const L=new Date(u.getTime()+y*864e5);const O=ve(L,n);if(!P(o,O.month,3))continue;if(!P(c,O.day,2))continue;if(!P(s,O.weekday,4))continue;for(const $ of a){for(const I of l){if(y===0&&($<E.hour||$===E.hour&&I<=E.minute)){continue}let N;if(R){const G=mt(L,n);N=new Date(L);N.setUTCHours($-Math.floor(G/60),I-G%60,0,0)}else{N=new Date(L);N.setHours($,I,0,0)}if(N.getTime()<=u.getTime())continue;const b=ve(N,n);if(b.hour===$&&b.minute===I&&P(c,b.day,2)&&P(o,b.month,3)&&P(s,b.weekday,4)){return N.toISOString()}}}}return null}function Y(e){try{const n=f.readFileSync(ee(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 fn(e,n){X(n);f.writeFileSync(ee(n),`${e}
3
+ `)}function _e(e){try{f.unlinkSync(ee(e))}catch{}}function ae(e){try{process.kill(e,0);return true}catch(n){const t=n;return t.code==="ESRCH"?false:"unknown"}}function mn(e){try{const n=process.platform==="win32"?(0,V.execFileSync)("powershell.exe",["-NoLogo","-NoProfile","-NonInteractive","-Command",`(Get-CimInstance Win32_Process -Filter "ProcessId = ${e}").CommandLine`],{encoding:"utf8",stdio:["ignore","pipe","ignore"]}):(0,V.execFileSync)("ps",["-ww","-p",String(e),"-o","command="],{encoding:"utf8",stdio:["ignore","pipe","ignore"]});return n.trim()||null}catch{return null}}function pt(e){try{const n=process.platform==="win32"?(0,V.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,V.execFileSync)("ps",["-p",String(e),"-o","lstart="],{encoding:"utf8",stdio:["ignore","pipe","ignore"]});return n.trim()||null}catch{return null}}function $e(e){const n={commandLine:mn(e),startTime:pt(e)};return n.commandLine!==null||n.startTime!==null?n:null}const se={isAlive:ae,getCommandLine:mn};function pn(e){const n=e.replace(/\\/g,"/");return process.platform==="win32"?n.toLowerCase():n}function St(){try{const e=pn(Sn()).split("/").filter(Boolean);const n=e.slice(-3).join("/");return n||null}catch{return null}}function yt(e){if(!e?.trim())return"unknown";const n=St();if(!n)return"unknown";const t=pn(e);return t.includes("daemon --foreground")&&t.includes(n)?"running":"stale"}function Sn(){const e=[m.join(__dirname,"..","cli.js"),m.join(__dirname,"..","dist","cli.js")];const n=e.find(t=>f.existsSync(t));if(n){return n}throw new Error(`Cron CLI entrypoint not found: ${e[0]}`)}function ge(e,n=se){const t=Y(e);if(!t){_e(e);return"stale"}let r;try{r=n.isAlive(t)}catch{r="unknown"}if(!r){_e(e);return"stale"}let i;try{i=n.getCommandLine(t)}catch{return"unknown"}const c=yt(i);if(c==="stale"){_e(e)}return c}function be(e,n=se){return ge(e,n)==="running"}function yn(e,n=V.spawn){if((0,p.isDurablyQuiescing)()){throw new Error("Cannot start cron daemon: a desktop update is draining active work")}X(e);const t=Sn();const r={...process.env};if(process.versions.electron){r.ELECTRON_RUN_AS_NODE="1"}const i=n(process.execPath,[t,"daemon","--foreground"],{cwd:e||void 0,detached:true,stdio:"ignore",windowsHide:true,env:r});if(!i.pid){throw new Error("Failed to start daemon: no PID returned")}i.unref();fn(i.pid,e);return i.pid}function _t(e,n=se){const t=ge(e,n);if(t==="running"){const r=Y(e);if(r)return r;throw new Error("Daemon is already running")}if(t==="unknown"){const r=Y(e);throw new Error(`Unable to determine cron daemon liveness (pid=${r??"unknown"}, pidFile=${ee(e)}); refusing to start`)}return yn(e,n.spawnImpl)}function gt(e,n=se){if(ge(e,n)!=="running")return false;const t=Y(e);if(!t)return false;process.kill(t,"SIGTERM");return true}function _n(e,n=se){const t=ge(e,n);if(t==="running"){return{started:false}}if(t==="unknown"){return{started:false,error:`Unable to determine cron daemon liveness (pid=${Y(e)??"unknown"}, pidFile=${ee(e)}); refusing to start`}}try{const r=yn(e,n.spawnImpl);return{started:true,pid:r}}catch(r){const i=r;return{started:false,error:i.message}}}function ht(e,n=se){if(be(e,n))return;console.log("\u26A1 Cron daemon is not running; starting it automatically...");const t=_n(e,n);if(!t.started){console.error("Failed to auto-start daemon:",t.error);process.exit(1)}}function wt(e){const n=A(e??w).list();const t=n.filter(s=>me(s)).length;const r=be(e);const i=r?Y(e):null;let c=null;const o=ee(e);if(r&&f.existsSync(o)){try{c=f.statSync(o).mtime.toISOString()}catch{c=null}}return{running:r,pid:r?i:null,started_at:c,schedules:{total:n.length,enabled:t}}}function he(e,n){try{n()}catch(t){console.error(`[${S()}] ${e} failed: ${t.message}`)}}function gn(e,n){he(`Run ${e} record finish`,()=>{new p.ScheduleRunRepository().finish(e,n)})}function hn(e){try{return(0,k.readRunOutputTail)(e)}catch{return{tail:null,bytes:0}}}function Et(e){try{const n=new p.TaskRepository;let t=n.getTask(e);if(t===void 0)return{status:"failed",errorCode:"TASK_MISSING"};if((0,p.normalizeTaskStatus)(t)==="pending"){n.failPendingTask(e,"RUNNER_NOT_ADOPTED");return{status:"failed",errorCode:"RUNNER_NOT_ADOPTED"}}switch((0,p.normalizeTaskStatus)(t)){case"success":return{status:"success",errorCode:null};case"stopped":return{status:"failed",errorCode:"TASK_STOPPED"};case"failed":return{status:"failed",errorCode:"TASK_FAILED"};default:return{status:"failed",errorCode:"TASK_UNFINISHED"}}}catch{return{status:"failed",errorCode:"TASK_UNREADABLE"}}}function Ue(e,n){he(`Task ${e} close`,()=>{new p.TaskRepository().failPendingTask(e,n)})}function It(e){const{taskId:n,schedule:t,modeFlag:r,trigger:i,workingDir:c}=e;const o=f.existsSync(m.join(c,"crewx.yaml"))||f.existsSync(m.join(c,"crewx.yml"));const s=/^@(\S+)/.exec(t.command)?.[1]??"crewx";new p.TaskRepository().startTask({id:n,agentId:s,actorKind:"system",actorId:`cron:${t.id}`,prompt:t.command,mode:r==="x"?"execute":"query",status:"pending",startedAt:e.startedAt,pid:null,traceId:n,command:`crewx ${r} ${t.command}`,metadata:JSON.stringify({trigger:i,schedule_id:t.id}),workspaceId:o?(0,p.hashWorkspaceId)((0,p.resolveWorkspaceOwnerPath)(c)):null,cwd:(0,p.normalizeWorkspacePath)(c),platform:"cli",threadId:null,model:e.model??null})}function Ct(e,n){const t=(0,p.resolveCrewxExecutable)({env:e,cwd:n});if(!t.ok)return e;const r=Object.keys(e).find(c=>c.toUpperCase()==="PATH")??"PATH";const i=(0,p.createCrewxChildInvocation)(t,{baseEnv:e}).env[r];if(i===void 0)return e;return{...e,[r]:i}}async function wn(e,n){const t=n?.dir;const r=m.resolve(t??w);const i=n?.trigger??"cron";const c=e.timeout_ms||6e5;const o=(0,p.generateId)("crun");const s=(0,p.generateId)("tsk");const a=e.app!==void 0?"app":e.mode==="command"?"command":"agent";if(e.app!==void 0){if(e.mode!=="command"||e.command!==p.APP_SCHEDULE_INERT_COMMAND){return{status:"blocked",reason:"APP_ROW_INVALID"}}if(n?.launchApp===void 0)return{status:"blocked",reason:"APP_LAUNCHER_UNAVAILABLE"}}else{if(!Mt(e.mode))return{status:"blocked",reason:"MODE_UNSUPPORTED"};if(i==="app_enable")return{status:"blocked",reason:"TRIGGER_UNSUPPORTED"}}let l;if(!e.allow_concurrent){const d=at(e.id,o,c,t);if(!d.acquired){console.log(`[${S()}] Schedule ${e.id} already running (run ${d.runningRunId??"unknown"}), skipping`);return{status:"conflict",runningTaskId:d.runningTaskId,...d.runningRunId===void 0?{}:{runningRunId:d.runningRunId},reason:d.reason}}l=d.lease}const u=()=>{if(l){sn(e.id,l.leaseId,t);l=void 0}Un(s)};const R=d=>({id:o,scheduleId:e.id,workspaceId:(0,k.runWorkspaceId)(r),mode:a,trigger:i,occurrenceKey:n?.occurrenceKey??null,taskId:d??null,appId:e.app?.appId??null,scheduleName:e.name,builtinId:ye(e)?.builtin_id??null,command:a==="app"?(0,p.appScheduleCommandLabel)(e.app):e.command,highFrequency:En(e.cron,new Date,e.timezone)});const E=(d,g)=>{const _=new Date().toISOString();he(`Schedule ${e.id} run record`,()=>{new p.ScheduleRunRepository().insert({...R(),status:"failed",errorCode:d,startedAt:g,finishedAt:_,durationMs:Math.max(0,Date.parse(_)-Date.parse(g)),pid:null,hostPid:process.pid})})};if(!bn(s,e.id,c,o)){console.log(`[${S()}] Schedule ${e.id} blocked: could not durably record the update-admission reservation`);u();return{status:"blocked",reason:"ADMISSION_UNKNOWN"}}if((0,p.isDurablyQuiescing)()){console.log(`[${S()}] Schedule ${e.id} blocked: a desktop update is draining active work`);u();return{status:"blocked",reason:"UPDATE_QUIESCING"}}try{const d=A(r);const g=new Date().toISOString();if(n?.occurrenceKey!==void 0){const _=d.claimOccurrence(e.id,{occurrenceKey:n.occurrenceKey,startedAt:g});if(!_.claimed){u();console.log(`[${S()}] Schedule ${e.id} occurrence ${n.occurrenceKey} already claimed, skipping`);return{status:"conflict",runningTaskId:"",reason:"occurrence_claim"}}}else{if(d.get(e.id)){d.recordStarted(e.id,{startedAt:g})}}}catch(d){u();throw d}let y;let L;if(e.app!==void 0){let d;const g=new Date().toISOString();try{d=await n.launchApp(e);if(d.kind!=="spawn")jt(r,e,d)}catch(_){Me(r,e.id,"APP_LAUNCH_FAILED");E("APP_LAUNCH_FAILED",g);u();throw _}if(d.kind!=="spawn"){u();const _=d.kind==="refresh"?"APP_SCHEDULE_REFRESHED":d.code;console.log(`[${S()}] Schedule ${e.id} app row ${d.kind}: ${_}`);return{status:"skipped",reason:_,lifecycle:d.kind}}y=d.child;L=typeof d.settle==="function"?d.settle:void 0}const O=e.working_directory||n?.dir||(0,F.getProjectRoot)();const $=e.mode;const I=ye(e);const N=$==="execute"?"x":"q";const b=n?.overrides?.model??e.model;const G=n?.overrides?.provider??e.provider;const j=(0,k.cronRunLogPath)(r,o);const H=(0,p.withoutCrewxRunnerIdentity)((0,p.withoutCrewxRootContext)({...process.env,...e.env,CREWX_TRACE_ID:s}));delete H["CLAUDECODE"];if(!e.env||!("CREWX_CONFIG"in e.env)){delete H["CREWX_CONFIG"]}H["CREWX_WORKSPACE"]=O;const pe=I!==void 0?[...I.cliArgs]:[N,e.command];if(I===void 0){if(b){pe.push("--model",b)}if($!=="command"&&G){pe.push("--provider",G)}pe.push("--metadata",JSON.stringify({trigger:i,schedule_id:e.id}))}const Q=new Date().toISOString();let W=false;if(a==="agent"){try{It({taskId:s,schedule:e,modeFlag:N,trigger:i,workingDir:O,model:b,startedAt:Q});W=true}catch(d){console.error(`[${S()}] Schedule ${e.id} could not create its task row: ${d.message}`);E("TASK_CREATE_FAILED",Q);u();throw d}}console.log(`[${S()}] Executing schedule ${e.id}: ${e.name??e.cron} (run ${o}${W?`, task ${s}`:""})`);console.log(`[${S()}] Command: ${e.command}`);let h;let x;try{if(y!==void 0){h=y;if(h.pid===void 0)throw new Error("App launcher returned a child that did not start")}else if(e.app!==void 0){throw new Error("managed row has no launched child")}else{x=(0,k.openRunLog)(j);if($==="command"&&I===void 0){h=(0,V.spawn)("sh",["-c",e.command],{cwd:O,stdio:["ignore",x,x],windowsHide:true,detached:true,env:Ct(H,O)})}else{const d=(0,p.resolveCrewxExecutable)({env:H,cwd:O});if(!d.ok){const U=new Error((0,p.formatCrewxExecutableFailure)(d));Object.assign(U,{code:"CLI_UNRESOLVABLE"});throw U}const g=(0,p.createCrewxChildInvocation)(d,{baseEnv:H});const _=(0,p.resolveWindowsSpawnProgram)({command:g.command,env:g.env,cwd:O,packageName:"crewx",allowShellFallback:true});const v=(0,p.materializeWindowsSpawnProgram)(_,[...g.args,...pe]);h=(0,V.spawn)(v.command,v.argv,{cwd:O,stdio:["ignore",x,x],windowsHide:v.windowsHide,detached:true,env:W?{...g.env,CREWX_DETACHED_RUNNER:"1",CREWX_ASSIGNED_TASK_ID:s}:g.env,shell:v.shell??false})}f.closeSync(x);x=void 0}if(y===void 0){await new Promise((d,g)=>{h.once("spawn",()=>d());h.once("error",_=>g(_))})}}catch(d){const g=d;console.error(`[${S()}] Schedule ${e.id} failed to spawn: ${g.message}`);if(x!==void 0){try{f.closeSync(x)}catch{}}if(e.app===void 0)(0,k.removeRunLog)(j);if(e.app!==void 0)Me(r,e.id,"APP_SPAWN_FAILED");if(W)Ue(s,"SPAWN_FAILED");E(e.app!==void 0?"APP_SPAWN_FAILED":"SPAWN_FAILED",Q);u();throw g}he(`Schedule ${e.id} run record`,()=>{new p.ScheduleRunRepository().insert({...R(W?s:void 0),status:"running",startedAt:Q,pid:h.pid??null,hostPid:process.pid})});if(l&&h.pid){const d=st(l,h.pid,$e(h.pid),W?s:void 0);if(!d){const g=new Error("Cron execution lease was replaced before child ownership was established");console.error(`[${S()}] Schedule ${e.id} failed to hand off its execution lease: ${g.message}`);try{h.kill("SIGTERM")}catch{}if(e.app!==void 0)Me(r,e.id,"APP_HANDOFF_FAILED");if(W)Ue(s,"LEASE_HANDOFF_FAILED");const _=new Date().toISOString();gn(o,{status:"failed",errorCode:e.app!==void 0?"APP_HANDOFF_FAILED":"LEASE_HANDOFF_FAILED",finishedAt:_,durationMs:Math.max(0,Date.parse(_)-Date.parse(Q))});if(e.app===void 0)(0,k.removeRunLog)(j);u();throw g}}h.unref();const He=e.app===void 0?(0,k.watchRunLogLimit)(j):void 0;const Dn=new Promise(d=>{let g=false;let _=false;let v;let U;let We=false;const Be=()=>{if(U!==void 0||!h.stdout&&!h.stderr)return;U=setTimeout(()=>{We=true;h.stdout?.destroy();h.stderr?.destroy()},Lt);if(typeof U==="object"&&U!==null&&"unref"in U){U.unref()}};if(e.app!==void 0){h.once("exit",Be);if(h.exitCode!==null||h.signalCode!==null)Be()}const ze=setTimeout(()=>{g=true;console.log(`[${S()}] Schedule ${e.id} timeout, killing process`);try{h.kill("SIGTERM")}catch(T){const Se=T;console.error(`Failed to kill process: ${Se.message}`)}v=setTimeout(()=>{try{h.kill("SIGKILL")}catch{}},De);if(typeof v==="object"&&v!==null&&"unref"in v){v.unref()}if(!_){_=true;d({success:false,error:"Timeout"})}},c);h.on("close",(T,Se)=>{clearTimeout(ze);if(v)clearTimeout(v);if(U)clearTimeout(U);u();He?.stop();const B=e.app!==void 0&&!g?Nt({code:T,signal:Se},L,We):void 0;const Ee=new Date().toISOString();const Ke=g?"timeout":B!==void 0?B.outcome:T===0?"success":"failure";const Ge=e.app===void 0?void 0:g?Pt:B?.outcome==="failure"?B.code:void 0;try{A(r).recordFinished(e.id,{finishedAt:Ee,outcome:Ke,errorCode:Ge})}catch(de){const An=de;if(!(de instanceof C.CronStoreError&&de.code==="CRON_SCHEDULE_NOT_FOUND")){console.error(`[${S()}] Failed to record schedule ${e.id} completion: ${An.message}`)}}let q;if(e.app!==void 0){q={status:Ke==="success"?"success":"failed",errorCode:Ge??null}}else if(a==="agent"){const de=Et(s);q=g?{status:"failed",errorCode:"TIMEOUT"}:de}else if(g){q={status:"failed",errorCode:"TIMEOUT"}}else if(T===0){q={status:"success",errorCode:null}}else{q={status:"failed",errorCode:T===null?"SIGNALED":"EXIT_NONZERO"}}const Ie=e.app===void 0?hn(j):void 0;const qe=He?.state();gn(o,{status:q.status,errorCode:q.errorCode,exitCode:T,signal:Se,finishedAt:Ee,durationMs:Math.max(0,Date.parse(Ee)-Date.parse(Q)),outputTail:Ie?.tail??null,outputBytes:Ie===void 0?null:(qe?.droppedBytes??0)+Ie.bytes,outputTruncated:qe?.truncated??false});if(e.app===void 0&&!(0,k.removeRunLog)(j)){console.warn(`[${S()}] Run ${o} output file could not be removed; the sweep will retry`)}if(g){console.log(`[${S()}] Schedule ${e.id} terminated after timeout`);if(!_){_=true;d({success:false,error:"Timeout"})}}else if(B!==void 0){if(B.outcome==="success"){console.log(`[${S()}] Schedule ${e.id} completed successfully`);if(!_){_=true;d({success:true})}}else{console.log(`[${S()}] Schedule ${e.id} failed with ${B.code}`);if(!_){_=true;d({success:false,error:B.code})}}}else if(T===0){console.log(`[${S()}] Schedule ${e.id} completed successfully`);if(!_){_=true;d({success:true})}}else{console.log(`[${S()}] Schedule ${e.id} failed with code ${T}`);if(!_){_=true;d({success:false,error:`Exit code: ${T}`})}}});h.on("error",T=>{clearTimeout(ze);console.error(`[${S()}] Schedule ${e.id} error: ${T.message}`);if(!_){_=true;d({success:false,error:T.message})}})});return{status:"running",runId:o,...W?{taskId:s}:{},done:Dn}}const Dt=24;function En(e,n,t){const r=60*1e3;const i=n.getTime()+24*60*r;let c=0;try{for(let o=(Math.floor(n.getTime()/r)+1)*r;o<=i;o+=r){if(!Ne(e,new Date(o),t))continue;c++;if(c>Dt)return true}}catch{return false}return false}exports.SCHEDULE_RUN_SWEEP_INTERVAL_MS=60*60*1e3;function At(e,n){const t=oe(Ce(n.scheduleId,e));if(t!==null&&t.runId===n.id&&t.phase==="child"){const i=t.childPid??t.pid;const c=ae(i);if(c===false)return"dead";if(c!==true||!t.childIdentity)return"unknown";const o=$e(i);if(!o)return"unknown";const s=an(t.childIdentity,o);if(s===false)return"dead";return s===null?"unknown":"alive"}if(n.pid===null)return"unknown";const r=ae(n.pid);if(r===false)return"dead";return r===true?"alive":"unknown"}function Rt(e){try{return tn(e)}catch{return[]}}function Ot(e,n,t,r,i){const c=new Map(Rt(n).map(s=>[s.id,s.timeout_ms]));for(const s of e.listRunning(t)){const a=s.hostPid===null?"unknown":ae(s.hostPid);if(a!==false)continue;const l=At(n,s);const u=Math.max(0,r.getTime()-Date.parse(s.startedAt));const R=c.get(s.scheduleId)||6e5;if(l!=="dead"&&u<=R+De)continue;const E=s.mode==="app"?void 0:hn((0,k.cronRunLogPath)(n,s.id));const y=e.markLost(s.id,{finishedAt:r.toISOString(),durationMs:u,errorCode:"RUNNER_LOST",outputTail:E?.tail??null,outputBytes:E===void 0?null:E.bytes,outputTruncated:s.outputTruncated});if(!y)continue;i.lost++;if(s.taskId!==null)Ue(s.taskId,"RUNNER_LOST")}const o=e.prune(t,r);i.deleted=o.deleted;i.tailsCleared=o.tailsCleared}function Tt(e,n,t,r,i){const c=(0,k.cronRunLogDir)(n);let o;try{o=f.readdirSync(c)}catch{return}for(const s of o){if(!s.endsWith(".log"))continue;const a=m.join(c,s);const l=s.slice(0,-".log".length);let u;try{u=t?e.get(l):void 0}catch{continue}if(u?.status==="running")continue;if(u===void 0){let R;try{R=f.statSync(a).mtimeMs}catch{continue}if(r.getTime()-R<k.CRON_RUN_ORPHAN_LOG_GRACE_MS)continue}if((0,k.removeRunLog)(a))i.logsRemoved++}}function kt(e={}){const n=m.resolve(e.dir??w);const t=e.now??new Date;const r={lost:0,logsRemoved:0,deleted:0,tailsCleared:0};const i=new p.ScheduleRunRepository;let c=false;try{c=f.existsSync(i.resolveDbPath())}catch(o){console.warn(`[${S()}] Run record sweep: database unavailable: ${o.message}`)}if(c){try{Ot(i,n,(0,k.runWorkspaceId)(n),t,r)}catch(o){console.warn(`[${S()}] Run record sweep failed: ${o.message}`)}}try{Tt(i,n,c,t,r)}catch(o){console.warn(`[${S()}] Run output sweep failed: ${o.message}`)}return r}const Pt="APP_TIMEOUT";const Lt=5e3;function Nt(e,n,t){if(e.signal!==null)return ue("APP_SIGNALED");if(e.code!==0)return ue("APP_EXIT_NONZERO");if(t)return ue("APP_STDIO_NOT_DRAINED");if(n===void 0)return ue("APP_VERDICT_FAILED");let r;try{r=n(e)}catch{return ue("APP_VERDICT_FAILED")}return bt(r)}const vt=64;const $t=3;function bt(e){try{if(typeof e==="object"&&e!==null&&!Array.isArray(e)&&typeof e.then!=="function"){const n=Object.keys(e);const{outcome:t,code:r}=e;if(t==="success"&&n.length===1)return{outcome:"success"};if(t==="failure"&&n.length===2&&n.includes("code")){return(0,p.isAppScheduleCode)(r)?{outcome:"failure",code:r}:Fe(e,"APP_OUTPUT_INVALID")}}return Fe(e,"APP_VERDICT_FAILED")}catch{return Fe(e,"APP_VERDICT_FAILED")}}function Fe(e,n){Ut(e);return ue(n)}const we=()=>void 0;function Ut(e){const n=[{value:e,depth:0}];for(let t=0;t<vt&&n.length>0;t+=1){const{value:r,depth:i}=n.shift();if(typeof r!=="object"&&typeof r!=="function"||r===null)continue;try{if(Object.getPrototypeOf(r)===Promise.prototype){Promise.prototype.then.call(r,we,we);continue}const c=r.then;if(typeof c==="function"){c.call(r,we,we);continue}if(i<$t){for(const o of Object.keys(r)){const s=Object.getOwnPropertyDescriptor(r,o);if(s!==void 0&&"value"in s)n.push({value:s.value,depth:i+1})}}}catch{}}}function ue(e){return{outcome:"failure",code:e}}const Ft=["query","execute","command"];function Mt(e){return Ft.includes(e)}function xt(e){return{appId:e.appId,actionId:e.actionId,source:e.source}}function jt(e,n,t){const r=A(e);const i=xt(n.app);const c={cron:n.cron,timeout_ms:n.timeout_ms,name:n.name};switch(t.kind){case"skip":{const o=r.setAppSchedule(i,c).schedule;r.recordSkipped(o.id,{outcome:t.code,errorCode:t.code});return}case"disable":{const o=r.setAppSchedule(i,c,false).schedule;r.recordSkipped(o.id,{outcome:t.code,errorCode:t.code});return}case"remove":r.removeAppSchedule(i.appId);return;case"refresh":{const o=r.setAppSchedule(t.owner,t.template).schedule;r.recordSkipped(o.id,{outcome:"APP_SCHEDULE_REFRESHED",errorCode:null});return}}}function Me(e,n,t){try{A(e).recordSkipped(n,{outcome:t,errorCode:t})}catch(r){if(r instanceof C.CronStoreError&&r.code==="CRON_SCHEDULE_NOT_FOUND")return;console.error(`[${S()}] Schedule ${n} could not record its ${t} outcome: ${r.message}`)}}function In(e,n){if(!n||!ce(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 Cn(e,n){return`${e.trim()}|${n}`}async function xe(e){if((0,p.isDurablyQuiescing)()){console.log(`[${S()}] Daemon tick skipped: a desktop update is draining active work`);return}const n=new Date;const t=A(e??w).list();const r=t.filter(i=>me(i));if(r.length===0){return}for(const i of r){if(Ne(i.cron,n,i.timezone)){const c=In(n,i.timezone);const o=Cn(i.cron,c);try{await wn(i,{dir:e,occurrenceKey:o})}catch(s){const a=s;console.error(`[${S()}] Schedule ${i.id} failed to spawn: ${a.message}`)}}}}var je=require("./runtime");Object.defineProperty(exports,"createCronRuntime",{enumerable:true,get:function(){return je.createCronRuntime}});Object.defineProperty(exports,"CRON_RUNTIME_PROCESS_LEASE_FILENAME",{enumerable:true,get:function(){return je.CRON_RUNTIME_PROCESS_LEASE_FILENAME}});Object.defineProperty(exports,"scheduledPeriodicAvailability",{enumerable:true,get:function(){return je.scheduledPeriodicAvailability}});function Ht(e){fn(process.pid,e);let n=false;const t=()=>{if(n)return;n=true;const c=Y(e);if(c===process.pid){_e(e)}};process.on("SIGTERM",()=>{t();process.exit(0)});process.on("SIGINT",()=>{t();process.exit(0)});process.on("exit",t);console.log(`[${S()}] Cron daemon starting in foreground mode`);console.log(`[${S()}] Check interval: 1 minute`);console.log(`[${S()}] Press Ctrl+C to stop
4
+ `);const r=A(e??w).list();const i=r.filter(c=>me(c)).length;console.log(`[${S()}] Active schedules: ${i}/${r.length}
5
+ `);void xe(e);setInterval(()=>{void xe(e)},Fn)}function Wt(e,n,t,r){if(t.timezone&&!ce(t.timezone)){throw new Error(`Invalid timezone: ${t.timezone}`)}Qe(n);const i={id:(0,p.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){i.model=t.model}if(t.provider){i.provider=t.provider}i.next_run=K(e,i.timezone);return A(r??w).create(i)}function Bt(e,n){const t=A(n??w).remove(e);const r=(0,F.findBuiltinByName)(t.name);if(r){Qn(r.builtin_id,n)}return t}function zt(e,n){return A(n??w).toggle(e,true)}function Kt(e,n){return A(n??w).toggle(e,false)}function Gt(e,n,t){if(n.timezone!==void 0&&n.timezone!==null&&!ce(n.timezone)){throw new Error(`Invalid timezone: ${n.timezone}`)}const r=A(t??w);const i=r.get(e);if(!i){throw new Error(`Schedule not found: ${e}`)}const c=[];const o={};if(n.cron!==void 0){o.cron=n.cron;c.push(`cron: ${n.cron}`)}if(n.command!==void 0){Qe(n.command);o.command=n.command;c.push(`command: ${n.command}`)}if(n.name!==void 0){o.name=n.name;c.push(`name: ${n.name}`)}if(n.mode!==void 0){o.mode=Oe(n.mode);c.push(`mode: ${o.mode}`)}if(n.timezone!==void 0){o.timezone=n.timezone;c.push(`timezone: ${n.timezone}`)}if(n.working_directory!==void 0){o.working_directory=n.working_directory;c.push(`working_directory: ${n.working_directory}`)}if(n.timeout_ms!==void 0){o.timeout_ms=n.timeout_ms;c.push(`timeout: ${n.timeout_ms}ms`)}if(n.allow_concurrent!==void 0){o.allow_concurrent=n.allow_concurrent;c.push(`allow_concurrent: ${n.allow_concurrent}`)}if(n.model!==void 0){if(n.model===null){o.model=null;c.push("model: (cleared, reverts to agent default)")}else{o.model=n.model;c.push(`model: ${n.model}`)}}if(n.provider!==void 0){if(n.provider===null){o.provider=null;c.push("provider: (cleared, reverts to agent default)")}else{o.provider=n.provider;c.push(`provider: ${n.provider}`)}}return{schedule:r.update(i.id,o),changes:c}}exports.DATA_PATHS={DATA_DIR:J,SCHEDULES_FILE:Tn,LOG_FILE:kn,LOCK_FILE:Pn,USAGE_LOG_FILE:Ln,PID_FILE:Nn,BUILTINS_META_FILE:vn};