@mndrk/agx 1.4.3 → 1.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +79 -48
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -333,6 +333,7 @@ function createSubtasks(splits, memInfo) {
|
|
|
333
333
|
|
|
334
334
|
const DAEMON_PID_FILE = path.join(process.env.HOME || process.env.USERPROFILE, '.agx', 'daemon.pid');
|
|
335
335
|
const DAEMON_LOG_FILE = path.join(process.env.HOME || process.env.USERPROFILE, '.agx', 'daemon.log');
|
|
336
|
+
const DAEMON_STATE_FILE = path.join(process.env.HOME || process.env.USERPROFILE, '.agx', 'daemon-state.json');
|
|
336
337
|
|
|
337
338
|
function isDaemonRunning() {
|
|
338
339
|
try {
|
|
@@ -414,82 +415,112 @@ async function runDaemon() {
|
|
|
414
415
|
console.log(`[${new Date().toISOString()}] Daemon starting...`);
|
|
415
416
|
|
|
416
417
|
const DEFAULT_WAKE_INTERVAL = 15 * 60 * 1000; // 15 minutes fallback
|
|
418
|
+
const TICK_INTERVAL = 60 * 1000; // Check every 1 minute
|
|
417
419
|
const memDir = path.join(process.env.HOME || process.env.USERPROFILE, '.mem');
|
|
418
420
|
|
|
419
|
-
//
|
|
420
|
-
function
|
|
421
|
+
// Load/save daemon state (last run times per task)
|
|
422
|
+
function loadState() {
|
|
421
423
|
try {
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
const index = JSON.parse(fs.readFileSync(indexFile, 'utf8'));
|
|
426
|
-
let minInterval = DEFAULT_WAKE_INTERVAL;
|
|
427
|
-
|
|
428
|
-
for (const taskBranch of Object.values(index)) {
|
|
429
|
-
try {
|
|
430
|
-
execSync(`git checkout ${taskBranch}`, { cwd: memDir, stdio: 'ignore' });
|
|
431
|
-
const state = fs.readFileSync(path.join(memDir, 'state.md'), 'utf8');
|
|
432
|
-
const wakeMatch = state.match(/^wake:\s*(.+)$/m);
|
|
433
|
-
if (wakeMatch) {
|
|
434
|
-
const interval = parseWakeInterval(wakeMatch[1]);
|
|
435
|
-
if (interval && interval < minInterval) minInterval = interval;
|
|
436
|
-
}
|
|
437
|
-
} catch {}
|
|
424
|
+
if (fs.existsSync(DAEMON_STATE_FILE)) {
|
|
425
|
+
return JSON.parse(fs.readFileSync(DAEMON_STATE_FILE, 'utf8'));
|
|
438
426
|
}
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
427
|
+
} catch {}
|
|
428
|
+
return { lastRun: {} };
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function saveState(state) {
|
|
432
|
+
const dir = path.dirname(DAEMON_STATE_FILE);
|
|
433
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
434
|
+
fs.writeFileSync(DAEMON_STATE_FILE, JSON.stringify(state, null, 2));
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Get wake interval for a specific task
|
|
438
|
+
function getTaskWakeInterval(projectDir) {
|
|
439
|
+
try {
|
|
440
|
+
const wake = execSync('mem wake', { cwd: projectDir, encoding: 'utf8' });
|
|
441
|
+
const wakeMatch = wake.match(/Wake:\s*(.+)/);
|
|
442
|
+
if (wakeMatch) {
|
|
443
|
+
const interval = parseWakeInterval(wakeMatch[1]);
|
|
444
|
+
if (interval) return interval;
|
|
445
|
+
}
|
|
446
|
+
} catch {}
|
|
447
|
+
return DEFAULT_WAKE_INTERVAL;
|
|
443
448
|
}
|
|
444
449
|
|
|
445
450
|
const tick = async () => {
|
|
446
|
-
|
|
451
|
+
const now = Date.now();
|
|
452
|
+
const state = loadState();
|
|
447
453
|
|
|
454
|
+
// Get task list from mem index
|
|
448
455
|
const indexFile = path.join(memDir, 'index.json');
|
|
449
|
-
|
|
450
|
-
if (!fs.existsSync(indexFile)) {
|
|
451
|
-
console.log(`[${new Date().toISOString()}] No tasks found`);
|
|
452
|
-
return;
|
|
453
|
-
}
|
|
456
|
+
if (!fs.existsSync(indexFile)) return;
|
|
454
457
|
|
|
455
458
|
const index = JSON.parse(fs.readFileSync(indexFile, 'utf8'));
|
|
459
|
+
let ranAny = false;
|
|
456
460
|
|
|
457
461
|
for (const [projectDir, taskBranch] of Object.entries(index)) {
|
|
458
462
|
if (!fs.existsSync(projectDir)) continue;
|
|
459
463
|
|
|
460
|
-
// Check if task is active
|
|
461
464
|
try {
|
|
462
|
-
|
|
463
|
-
const
|
|
465
|
+
// Check if task is due based on its wake interval
|
|
466
|
+
const wakeInterval = getTaskWakeInterval(projectDir);
|
|
467
|
+
const lastRun = state.lastRun[taskBranch] || 0;
|
|
468
|
+
const elapsed = now - lastRun;
|
|
464
469
|
|
|
465
|
-
if (
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
console.log(`[${new Date().toISOString()}] Task ${taskBranch} error: ${err.message}`);
|
|
477
|
-
}
|
|
470
|
+
if (elapsed < wakeInterval) {
|
|
471
|
+
continue; // Not due yet
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// Use mem to switch and check status
|
|
475
|
+
execSync(`mem switch ${taskBranch.replace('task/', '')}`, { cwd: projectDir, stdio: 'ignore' });
|
|
476
|
+
const status = execSync('mem status', { cwd: projectDir, encoding: 'utf8' });
|
|
477
|
+
|
|
478
|
+
// Check if task is active (not done/blocked)
|
|
479
|
+
if (status.includes('status: done') || status.includes('status: blocked')) {
|
|
480
|
+
continue; // Skip inactive tasks
|
|
478
481
|
}
|
|
482
|
+
|
|
483
|
+
console.log(`[${new Date().toISOString()}] Running task: ${taskBranch} (due after ${Math.round(wakeInterval/60000)}m)`);
|
|
484
|
+
ranAny = true;
|
|
485
|
+
|
|
486
|
+
// Update last run time before executing (in case it takes a while)
|
|
487
|
+
state.lastRun[taskBranch] = now;
|
|
488
|
+
saveState(state);
|
|
489
|
+
|
|
490
|
+
// Run agx continue
|
|
491
|
+
try {
|
|
492
|
+
execSync(`agx claude -y -p "continue"`, {
|
|
493
|
+
cwd: projectDir,
|
|
494
|
+
stdio: 'inherit',
|
|
495
|
+
timeout: 10 * 60 * 1000 // 10 min timeout
|
|
496
|
+
});
|
|
497
|
+
} catch (err) {
|
|
498
|
+
console.log(`[${new Date().toISOString()}] Task ${taskBranch} error: ${err.message}`);
|
|
499
|
+
}
|
|
500
|
+
|
|
479
501
|
} catch (err) {
|
|
480
502
|
console.log(`[${new Date().toISOString()}] Error checking ${taskBranch}: ${err.message}`);
|
|
481
503
|
}
|
|
482
504
|
}
|
|
505
|
+
|
|
506
|
+
if (!ranAny) {
|
|
507
|
+
// Only log periodically to avoid spam
|
|
508
|
+
const lastLog = state.lastLog || 0;
|
|
509
|
+
if (now - lastLog > 5 * 60 * 1000) { // Every 5 min
|
|
510
|
+
console.log(`[${new Date().toISOString()}] Daemon tick - no tasks due`);
|
|
511
|
+
state.lastLog = now;
|
|
512
|
+
saveState(state);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
483
515
|
};
|
|
484
516
|
|
|
485
517
|
// Initial tick
|
|
486
518
|
await tick();
|
|
487
519
|
|
|
488
|
-
//
|
|
489
|
-
|
|
490
|
-
setInterval(tick, wakeInterval);
|
|
520
|
+
// Check every minute, but only run tasks when their interval is due
|
|
521
|
+
setInterval(tick, TICK_INTERVAL);
|
|
491
522
|
|
|
492
|
-
console.log(`[${new Date().toISOString()}] Daemon running,
|
|
523
|
+
console.log(`[${new Date().toISOString()}] Daemon running, checking every ${TICK_INTERVAL / 1000}s`);
|
|
493
524
|
}
|
|
494
525
|
|
|
495
526
|
// Handle approval prompts
|