4runr-os 2.10.9 → 2.10.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,188 @@
1
+ /**
2
+ * Boot sequence - ensures clean system state before launching 4r
3
+ * Handles stale processes, in-progress updates, and broken installs
4
+ */
5
+ import { execSync } from 'child_process';
6
+ import * as fs from 'fs';
7
+ import * as os from 'os';
8
+ import * as path from 'path';
9
+ /**
10
+ * Kill any stale 4r/Gateway processes that might lock files
11
+ */
12
+ export function killStaleProcesses() {
13
+ if (process.platform !== 'win32') {
14
+ // POSIX: killall is less aggressive than pkill -9
15
+ try {
16
+ execSync('pkill -f "4runr-os|mk3-tui" || true', { stdio: 'ignore' });
17
+ }
18
+ catch {
19
+ // ignore
20
+ }
21
+ return;
22
+ }
23
+ // Windows: kill node.exe processes running from global 4runr-os or Gateway paths
24
+ try {
25
+ const output = execSync('wmic process where "name=\'node.exe\'" get ProcessId,CommandLine /format:csv', { encoding: 'utf-8', windowsHide: true });
26
+ const lines = output.split(/\r?\n/);
27
+ for (const line of lines) {
28
+ if (line.includes('4runr-os') || line.includes('gateway')) {
29
+ const match = line.match(/,(\d+)$/);
30
+ if (match) {
31
+ const pid = parseInt(match[1], 10);
32
+ if (pid > 0 && pid !== process.pid) {
33
+ try {
34
+ process.kill(pid, 'SIGTERM');
35
+ }
36
+ catch {
37
+ // ignore
38
+ }
39
+ }
40
+ }
41
+ }
42
+ }
43
+ }
44
+ catch {
45
+ // Fallback: just kill all node.exe (aggressive but effective)
46
+ try {
47
+ execSync('taskkill /F /IM node.exe /FI "PID ne ' + process.pid + '"', {
48
+ stdio: 'ignore',
49
+ windowsHide: true,
50
+ });
51
+ }
52
+ catch {
53
+ // ignore
54
+ }
55
+ }
56
+ }
57
+ /**
58
+ * Wait for any in-progress npm install -g 4runr-os to complete
59
+ */
60
+ export async function waitForInProgressUpdate(maxWaitMs = 120000) {
61
+ const lockPath = path.join(os.tmpdir(), '4runr-os-gnpm-in-progress.lock');
62
+ const start = Date.now();
63
+ while (Date.now() - start < maxWaitMs) {
64
+ if (!fs.existsSync(lockPath)) {
65
+ return true;
66
+ }
67
+ // Check if lock is stale (>15 min)
68
+ try {
69
+ const { mtimeMs } = fs.statSync(lockPath);
70
+ if (Date.now() - mtimeMs > 15 * 60 * 1000) {
71
+ fs.unlinkSync(lockPath);
72
+ return true;
73
+ }
74
+ }
75
+ catch {
76
+ return true;
77
+ }
78
+ // Show progress every 5s
79
+ if ((Date.now() - start) % 5000 < 1000) {
80
+ process.stderr.write('.');
81
+ }
82
+ await new Promise(r => setTimeout(r, 1000));
83
+ }
84
+ return false;
85
+ }
86
+ /**
87
+ * Check if Prisma client is properly generated
88
+ */
89
+ function checkPrismaClient() {
90
+ try {
91
+ const globalRoot = execSync('npm root -g', { encoding: 'utf-8' }).trim();
92
+ const prismaPath = path.join(globalRoot, '4runr-os', 'apps', 'gateway', 'node_modules', '.prisma', 'client');
93
+ return fs.existsSync(prismaPath);
94
+ }
95
+ catch {
96
+ return false;
97
+ }
98
+ }
99
+ /**
100
+ * Check if Gateway bundle is intact
101
+ */
102
+ function checkGatewayBundle() {
103
+ try {
104
+ const globalRoot = execSync('npm root -g', { encoding: 'utf-8' }).trim();
105
+ const gatewayDist = path.join(globalRoot, '4runr-os', 'apps', 'gateway', 'dist', 'index.js');
106
+ return fs.existsSync(gatewayDist);
107
+ }
108
+ catch {
109
+ return false;
110
+ }
111
+ }
112
+ /**
113
+ * Repair broken install by regenerating Prisma
114
+ */
115
+ async function repairInstall() {
116
+ try {
117
+ const globalRoot = execSync('npm root -g', { encoding: 'utf-8' }).trim();
118
+ const gatewayDir = path.join(globalRoot, '4runr-os', 'apps', 'gateway');
119
+ if (!fs.existsSync(gatewayDir)) {
120
+ return false;
121
+ }
122
+ process.stderr.write('🔧 Repairing installation...\n');
123
+ // Run npm install + prisma generate in gateway
124
+ execSync('npm install --no-audit', {
125
+ cwd: gatewayDir,
126
+ stdio: 'pipe',
127
+ windowsHide: true,
128
+ });
129
+ execSync('npm run db:generate', {
130
+ cwd: gatewayDir,
131
+ stdio: 'pipe',
132
+ windowsHide: true,
133
+ });
134
+ process.stderr.write('✓ Repair complete\n');
135
+ return true;
136
+ }
137
+ catch (err) {
138
+ process.stderr.write(`⚠️ Repair failed: ${err instanceof Error ? err.message : String(err)}\n`);
139
+ return false;
140
+ }
141
+ }
142
+ /**
143
+ * Perform full boot sequence health check
144
+ */
145
+ export async function performBootSequence() {
146
+ const warnings = [];
147
+ // Step 1: Kill stale processes
148
+ try {
149
+ killStaleProcesses();
150
+ await new Promise(r => setTimeout(r, 500));
151
+ }
152
+ catch (err) {
153
+ warnings.push(`Could not kill stale processes: ${err instanceof Error ? err.message : String(err)}`);
154
+ }
155
+ // Step 2: Wait for in-progress updates
156
+ const lockPath = path.join(os.tmpdir(), '4runr-os-gnpm-in-progress.lock');
157
+ if (fs.existsSync(lockPath)) {
158
+ process.stderr.write('⏳ Waiting for background update to complete');
159
+ const ok = await waitForInProgressUpdate(60000);
160
+ process.stderr.write('\n');
161
+ if (!ok) {
162
+ // Force clear stale lock
163
+ try {
164
+ fs.unlinkSync(lockPath);
165
+ }
166
+ catch {
167
+ // ignore
168
+ }
169
+ }
170
+ }
171
+ // Step 3: Health checks
172
+ const prismaOk = checkPrismaClient();
173
+ const gatewayOk = checkGatewayBundle();
174
+ if (!prismaOk || !gatewayOk) {
175
+ warnings.push('Installation health check failed');
176
+ // Attempt repair
177
+ const repaired = await repairInstall();
178
+ if (!repaired) {
179
+ return {
180
+ ok: false,
181
+ error: 'Installation is broken and auto-repair failed. Run: npm install -g 4runr-os@latest',
182
+ warnings,
183
+ };
184
+ }
185
+ }
186
+ return { ok: true, warnings: warnings.length > 0 ? warnings : undefined };
187
+ }
188
+ //# sourceMappingURL=boot-sequence.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"boot-sequence.js","sourceRoot":"","sources":["../src/boot-sequence.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -2460,15 +2460,45 @@ async function startREPL() {
2460
2460
  process.exit(0);
2461
2461
  });
2462
2462
  }
2463
- // Cleanup on process termination
2464
- process.on('SIGINT', () => {
2465
- stopAllAnimations();
2466
- console.log(`\n${C_MUTED}Terminated${RESET}\n`);
2463
+ // Graceful shutdown handlers
2464
+ let isShuttingDown = false;
2465
+ async function gracefulShutdown(signal) {
2466
+ if (isShuttingDown)
2467
+ return;
2468
+ isShuttingDown = true;
2469
+ process.stderr.write(`\n\n[4Runr] Received ${signal}, shutting down gracefully...\n`);
2470
+ // Stop watchdog and let it do cleanup
2471
+ try {
2472
+ const { stopWatchdog } = await import('./watchdog.js');
2473
+ stopWatchdog();
2474
+ }
2475
+ catch {
2476
+ // Fallback manual cleanup
2477
+ try {
2478
+ const { killRecordedLocalGateway } = await import('./local-gateway-pid.js');
2479
+ killRecordedLocalGateway();
2480
+ }
2481
+ catch {
2482
+ // ignore
2483
+ }
2484
+ }
2485
+ process.stderr.write('[4Runr] Shutdown complete\n');
2467
2486
  process.exit(0);
2487
+ }
2488
+ process.on('SIGINT', () => {
2489
+ gracefulShutdown('SIGINT');
2468
2490
  });
2469
2491
  process.on('SIGTERM', () => {
2470
- stopAllAnimations();
2471
- process.exit(0);
2492
+ gracefulShutdown('SIGTERM');
2493
+ });
2494
+ // Critical: cleanup on uncaught exceptions/unhandled rejections
2495
+ process.on('uncaughtException', async (err) => {
2496
+ process.stderr.write(`\n[4Runr] Uncaught exception: ${err.message}\n`);
2497
+ await gracefulShutdown('uncaughtException');
2498
+ });
2499
+ process.on('unhandledRejection', async (reason) => {
2500
+ process.stderr.write(`\n[4Runr] Unhandled rejection: ${reason}\n`);
2501
+ await gracefulShutdown('unhandledRejection');
2472
2502
  });
2473
2503
  // main() function removed - all routing now at top level
2474
2504
  /**
@@ -2648,9 +2678,25 @@ else {
2648
2678
  // Default mode - Launch terminal interface + WebSocket server
2649
2679
  // Launch Rust binary with WebSocket server embedded
2650
2680
  (async () => {
2681
+ // === BOOT SEQUENCE: Ensure clean system state ===
2682
+ const { performBootSequence } = await import('./boot-sequence.js');
2683
+ const bootResult = await performBootSequence();
2684
+ if (!bootResult.ok) {
2685
+ process.stderr.write(`\n❌ Boot check failed: ${bootResult.error}\n\n`);
2686
+ process.exit(1);
2687
+ }
2688
+ if (bootResult.warnings && bootResult.warnings.length > 0) {
2689
+ for (const w of bootResult.warnings) {
2690
+ process.stderr.write(`⚠️ ${w}\n`);
2691
+ }
2692
+ }
2693
+ // === START WATCHDOG: Monitors this process and cleans up Docker on ANY exit ===
2694
+ const { startWatchdog } = await import('./watchdog.js');
2695
+ startWatchdog(process.pid);
2651
2696
  // Start WebSocket server for TUI communication
2652
2697
  const { startTUIServer } = await import('./tui-server.js');
2653
2698
  const { initializeTUIHandlers } = await import('./tui-handlers.js');
2699
+ process.stderr.write('[4Runr] WebSocket server listening on ws://localhost:' + tuiPort + '\n');
2654
2700
  const tuiServer = await startTUIServer(tuiPort);
2655
2701
  initializeTUIHandlers(tuiServer, client, localMode);
2656
2702
  process.stderr.write(`[4Runr] TUI WebSocket server started on port ${tuiPort}\n`);