nightytidy 0.2.6 → 0.2.8
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/package.json +1 -1
- package/src/agent/index.js +27 -4
- package/src/agent/keep-awake.js +60 -0
package/package.json
CHANGED
package/src/agent/index.js
CHANGED
|
@@ -12,6 +12,7 @@ import { WebhookDispatcher } from './webhook-dispatcher.js';
|
|
|
12
12
|
import { CliBridge } from './cli-bridge.js';
|
|
13
13
|
import { AgentGit } from './git-integration.js';
|
|
14
14
|
import { FirebaseAuth } from './firebase-auth.js';
|
|
15
|
+
import { acquireKeepAwake, releaseKeepAwake } from './keep-awake.js';
|
|
15
16
|
|
|
16
17
|
const FIREBASE_WEBHOOK_URL = 'https://webhookingest-24h6taciuq-uc.a.run.app';
|
|
17
18
|
|
|
@@ -371,6 +372,7 @@ export async function startAgent() {
|
|
|
371
372
|
run: { id: interrupted.id },
|
|
372
373
|
}, []);
|
|
373
374
|
reply({ type: 'interrupted-discarded', runId: interrupted.id });
|
|
375
|
+
if (!runQueue.getCurrent()) processQueue();
|
|
374
376
|
break;
|
|
375
377
|
}
|
|
376
378
|
|
|
@@ -479,7 +481,10 @@ export async function startAgent() {
|
|
|
479
481
|
|
|
480
482
|
async function processQueue() {
|
|
481
483
|
const run = runQueue.dequeue();
|
|
482
|
-
if (!run)
|
|
484
|
+
if (!run) {
|
|
485
|
+
releaseKeepAwake();
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
483
488
|
|
|
484
489
|
const project = projectManager.getProject(run.projectId);
|
|
485
490
|
if (!project) {
|
|
@@ -497,6 +502,7 @@ export async function startAgent() {
|
|
|
497
502
|
runOutputBuffer = '';
|
|
498
503
|
runProgress = { stepList: [], completedCount: 0, failedCount: 0, totalCost: 0, currentStepNum: null };
|
|
499
504
|
|
|
505
|
+
acquireKeepAwake();
|
|
500
506
|
info(`\n━━━ Run started: ${project.name} ━━━`);
|
|
501
507
|
info(` Steps: [${run.steps.join(', ')}] (${run.steps.length} total)`);
|
|
502
508
|
info(` Project: ${project.path}`);
|
|
@@ -712,7 +718,8 @@ export async function startAgent() {
|
|
|
712
718
|
|
|
713
719
|
stopHeartbeat();
|
|
714
720
|
info(` Finishing run (report + merge)...`);
|
|
715
|
-
await bridge.finishRun();
|
|
721
|
+
const finishResult = await bridge.finishRun();
|
|
722
|
+
const reportMarkdown = finishResult?.parsed?.reportContent || null;
|
|
716
723
|
projectManager.updateProject(run.projectId, { lastRunAt: Date.now() });
|
|
717
724
|
|
|
718
725
|
const elapsedMs = Date.now() - run.startedAt;
|
|
@@ -728,6 +735,7 @@ export async function startAgent() {
|
|
|
728
735
|
project: project.name,
|
|
729
736
|
projectId: project.id,
|
|
730
737
|
run: { id: run.id, totalSteps, completedSteps: run.steps.length, elapsedMs: Date.now() - run.startedAt },
|
|
738
|
+
reportMarkdown,
|
|
731
739
|
}, project.webhooks);
|
|
732
740
|
|
|
733
741
|
activeBridge = null;
|
|
@@ -922,7 +930,8 @@ export async function startAgent() {
|
|
|
922
930
|
// Finish the run
|
|
923
931
|
stopHeartbeat();
|
|
924
932
|
info(` Finishing resumed run (report + merge)...`);
|
|
925
|
-
await bridge.finishRun();
|
|
933
|
+
const finishResult = await bridge.finishRun();
|
|
934
|
+
const reportMarkdown = finishResult?.parsed?.reportContent || null;
|
|
926
935
|
projectManager.updateProject(interrupted.projectId, { lastRunAt: Date.now() });
|
|
927
936
|
|
|
928
937
|
wsServer.broadcast({ type: 'run-completed', runId: interrupted.id, results: {} });
|
|
@@ -931,6 +940,7 @@ export async function startAgent() {
|
|
|
931
940
|
dispatchWithQueue('run_completed', {
|
|
932
941
|
project: project.name, projectId: project.id,
|
|
933
942
|
run: { id: interrupted.id, totalSteps: interrupted.steps.length, completedSteps: runProgress.completedCount, elapsedMs: Date.now() - interrupted.startedAt },
|
|
943
|
+
reportMarkdown,
|
|
934
944
|
}, project.webhooks);
|
|
935
945
|
|
|
936
946
|
activeBridge = null;
|
|
@@ -951,11 +961,13 @@ export async function startAgent() {
|
|
|
951
961
|
interrupted.status = 'running';
|
|
952
962
|
runQueue._save();
|
|
953
963
|
|
|
964
|
+
let finishResult = null;
|
|
954
965
|
try {
|
|
955
|
-
await bridge.finishRun();
|
|
966
|
+
finishResult = await bridge.finishRun();
|
|
956
967
|
} catch (err) {
|
|
957
968
|
warn(` finishRun failed: ${err.message}`);
|
|
958
969
|
}
|
|
970
|
+
const reportMarkdown = finishResult?.parsed?.reportContent || null;
|
|
959
971
|
|
|
960
972
|
projectManager.updateProject(interrupted.projectId, { lastRunAt: Date.now() });
|
|
961
973
|
wsServer.broadcast({ type: 'run-completed', runId: interrupted.id, status: 'completed', results: {} });
|
|
@@ -964,6 +976,7 @@ export async function startAgent() {
|
|
|
964
976
|
dispatchWithQueue('run_completed', {
|
|
965
977
|
project: project.name, projectId: project.id,
|
|
966
978
|
run: { id: interrupted.id, totalSteps: interrupted.steps.length, completedSteps: interrupted.lastProgress?.completedCount || 0, elapsedMs: Date.now() - interrupted.startedAt },
|
|
979
|
+
reportMarkdown,
|
|
967
980
|
}, project.webhooks);
|
|
968
981
|
|
|
969
982
|
activeBridge = null;
|
|
@@ -1036,6 +1049,7 @@ export async function startAgent() {
|
|
|
1036
1049
|
// Graceful shutdown
|
|
1037
1050
|
const shutdown = async () => {
|
|
1038
1051
|
info('Agent shutting down...');
|
|
1052
|
+
releaseKeepAwake();
|
|
1039
1053
|
saveInterruptedState();
|
|
1040
1054
|
scheduler.stopAll();
|
|
1041
1055
|
await wsServer.stop();
|
|
@@ -1093,6 +1107,15 @@ export async function startAgent() {
|
|
|
1093
1107
|
runQueue.markInterrupted({ completedCount: 0, failedCount: 0, totalCost: 0, stepList: [], currentStepNum: null });
|
|
1094
1108
|
}
|
|
1095
1109
|
|
|
1110
|
+
// Process any queued runs left from a previous session
|
|
1111
|
+
if (!runQueue.getInterrupted() && !runQueue.getCurrent()) {
|
|
1112
|
+
const pending = runQueue.getQueue();
|
|
1113
|
+
if (pending.length > 0) {
|
|
1114
|
+
info(`Found ${pending.length} queued run(s) — starting queue processing`);
|
|
1115
|
+
processQueue();
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1096
1119
|
// Print startup info
|
|
1097
1120
|
console.log(`\nNightyTidy Agent v${AGENT_VERSION}`);
|
|
1098
1121
|
console.log(`WebSocket: ws://127.0.0.1:${actualPort}`);
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prevents the OS from sleeping while NightyTidy runs are active.
|
|
3
|
+
*
|
|
4
|
+
* Windows: Uses PowerShell to call SetThreadExecutionState with
|
|
5
|
+
* ES_CONTINUOUS | ES_SYSTEM_REQUIRED (0x80000001). This tells Windows
|
|
6
|
+
* "don't sleep, this process needs the system." The flag is automatically
|
|
7
|
+
* cleared when the process exits or when releaseKeepAwake() is called.
|
|
8
|
+
*
|
|
9
|
+
* macOS/Linux: Uses caffeinate / systemd-inhibit respectively.
|
|
10
|
+
*
|
|
11
|
+
* No admin privileges required on any platform.
|
|
12
|
+
*/
|
|
13
|
+
import { execSync, spawn } from 'node:child_process';
|
|
14
|
+
import { debug, warn } from '../logger.js';
|
|
15
|
+
|
|
16
|
+
let keepAwakeProcess = null;
|
|
17
|
+
|
|
18
|
+
export function acquireKeepAwake() {
|
|
19
|
+
if (keepAwakeProcess) return; // already held
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
if (process.platform === 'win32') {
|
|
23
|
+
// PowerShell script that sets ES_CONTINUOUS | ES_SYSTEM_REQUIRED
|
|
24
|
+
// and then sleeps forever. When we kill this process, the flag clears.
|
|
25
|
+
keepAwakeProcess = spawn('powershell', [
|
|
26
|
+
'-NoProfile', '-WindowStyle', 'Hidden', '-Command',
|
|
27
|
+
`Add-Type -TypeDefinition 'using System; using System.Runtime.InteropServices; public class SleepPreventer { [DllImport("kernel32.dll")] public static extern uint SetThreadExecutionState(uint esFlags); }'; [SleepPreventer]::SetThreadExecutionState(0x80000001); while($true) { Start-Sleep -Seconds 3600 }`,
|
|
28
|
+
], { stdio: 'ignore', detached: false });
|
|
29
|
+
keepAwakeProcess.unref();
|
|
30
|
+
keepAwakeProcess.on('error', () => { keepAwakeProcess = null; });
|
|
31
|
+
debug('Sleep prevention acquired (Windows SetThreadExecutionState)');
|
|
32
|
+
} else if (process.platform === 'darwin') {
|
|
33
|
+
// macOS: caffeinate prevents sleep, -i = idle sleep, -s = system sleep
|
|
34
|
+
keepAwakeProcess = spawn('caffeinate', ['-is'], { stdio: 'ignore' });
|
|
35
|
+
keepAwakeProcess.unref();
|
|
36
|
+
keepAwakeProcess.on('error', () => { keepAwakeProcess = null; });
|
|
37
|
+
debug('Sleep prevention acquired (macOS caffeinate)');
|
|
38
|
+
} else {
|
|
39
|
+
// Linux: systemd-inhibit (may not exist on all distros)
|
|
40
|
+
keepAwakeProcess = spawn('systemd-inhibit', [
|
|
41
|
+
'--what=idle:sleep', '--who=NightyTidy', '--why=Running codebase improvement',
|
|
42
|
+
'sleep', 'infinity',
|
|
43
|
+
], { stdio: 'ignore' });
|
|
44
|
+
keepAwakeProcess.unref();
|
|
45
|
+
keepAwakeProcess.on('error', () => { keepAwakeProcess = null; });
|
|
46
|
+
debug('Sleep prevention acquired (Linux systemd-inhibit)');
|
|
47
|
+
}
|
|
48
|
+
} catch {
|
|
49
|
+
warn('Could not acquire sleep prevention — system may sleep during runs');
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function releaseKeepAwake() {
|
|
54
|
+
if (!keepAwakeProcess) return;
|
|
55
|
+
try {
|
|
56
|
+
keepAwakeProcess.kill();
|
|
57
|
+
} catch { /* already dead */ }
|
|
58
|
+
keepAwakeProcess = null;
|
|
59
|
+
debug('Sleep prevention released');
|
|
60
|
+
}
|