instar 0.7.44 → 0.7.45

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/dist/cli.js CHANGED
File without changes
@@ -710,77 +710,6 @@ export async function startServer(options) {
710
710
  ...(config.evolution || {}),
711
711
  });
712
712
  console.log(pc.green(' Evolution system enabled'));
713
- // Start MemoryPressureMonitor (platform-aware memory tracking)
714
- const { MemoryPressureMonitor } = await import('../monitoring/MemoryPressureMonitor.js');
715
- const memoryMonitor = new MemoryPressureMonitor({});
716
- memoryMonitor.on('stateChange', ({ from, to, state: memState }) => {
717
- // Gate scheduler spawning on memory pressure
718
- if (scheduler && (to === 'elevated' || to === 'critical')) {
719
- console.log(`[MemoryPressure] ${from} -> ${to} — scheduler should respect canSpawnSession()`);
720
- }
721
- // Alert via Telegram attention topic
722
- if (telegram && to !== 'normal') {
723
- const attentionTopicId = state.get('agent-attention-topic');
724
- if (attentionTopicId) {
725
- telegram.sendToTopic(attentionTopicId, `Memory ${to}: ${memState.pressurePercent.toFixed(1)}% used, ${memState.freeGB.toFixed(1)}GB free (trend: ${memState.trend})`).catch(() => { });
726
- }
727
- }
728
- });
729
- memoryMonitor.start();
730
- // Wire memory gate into scheduler
731
- if (scheduler) {
732
- const originalCanRun = scheduler.canRunJob;
733
- scheduler.canRunJob = (priority) => {
734
- // Check memory first
735
- const memCheck = memoryMonitor.canSpawnSession();
736
- if (!memCheck.allowed) {
737
- return false;
738
- }
739
- // Then check original gate (quota, etc.)
740
- return originalCanRun(priority);
741
- };
742
- }
743
- // Start CaffeinateManager (prevents macOS system sleep)
744
- const { CaffeinateManager } = await import('../core/CaffeinateManager.js');
745
- const caffeinateManager = new CaffeinateManager({ stateDir: config.stateDir });
746
- caffeinateManager.start();
747
- // Start SleepWakeDetector (re-validate sessions on wake)
748
- const { SleepWakeDetector } = await import('../core/SleepWakeDetector.js');
749
- const sleepWakeDetector = new SleepWakeDetector();
750
- sleepWakeDetector.on('wake', async (event) => {
751
- console.log(`[SleepWake] Wake detected after ~${event.sleepDurationSeconds}s sleep`);
752
- // Re-validate tmux sessions
753
- try {
754
- const tmuxPath = detectTmuxPath();
755
- if (tmuxPath) {
756
- const { execFileSync } = await import('child_process');
757
- const result = execFileSync(tmuxPath, ['list-sessions'], { encoding: 'utf-8', timeout: 5000 }).trim();
758
- console.log(`[SleepWake] tmux sessions after wake: ${result.split('\n').length}`);
759
- }
760
- }
761
- catch {
762
- console.warn('[SleepWake] tmux check failed after wake');
763
- }
764
- // Restart tunnel if configured
765
- if (tunnel) {
766
- try {
767
- await tunnel.stop();
768
- const tunnelUrl = await tunnel.start();
769
- console.log(`[SleepWake] Tunnel restarted: ${tunnelUrl}`);
770
- }
771
- catch (err) {
772
- console.error(`[SleepWake] Tunnel restart failed:`, err);
773
- }
774
- }
775
- // Notify via Telegram attention topic
776
- if (telegram) {
777
- const attentionTopicId = state.get('agent-attention-topic');
778
- if (attentionTopicId) {
779
- telegram.sendToTopic(attentionTopicId, `Wake detected after ~${event.sleepDurationSeconds}s sleep. Sessions re-validated.`).catch(() => { });
780
- }
781
- }
782
- });
783
- sleepWakeDetector.start();
784
713
  const server = new AgentServer({ config, sessionManager, state, scheduler, telegram, relationships, feedback, dispatches, updateChecker, autoUpdater, autoDispatcher, quotaTracker, publisher, viewer, tunnel, evolution });
785
714
  await server.start();
786
715
  // Start tunnel AFTER server is listening
@@ -797,9 +726,6 @@ export async function startServer(options) {
797
726
  // Graceful shutdown
798
727
  const shutdown = async () => {
799
728
  console.log('\nShutting down...');
800
- memoryMonitor.stop();
801
- caffeinateManager.stop();
802
- sleepWakeDetector.stop();
803
729
  autoUpdater.stop();
804
730
  autoDispatcher?.stop();
805
731
  if (tunnel)
@@ -33,7 +33,6 @@ export declare class HealthChecker {
33
33
  private checkTmux;
34
34
  private checkSessions;
35
35
  private checkScheduler;
36
- private checkMemory;
37
36
  private checkStateDir;
38
37
  }
39
38
  //# sourceMappingURL=HealthChecker.d.ts.map
@@ -26,7 +26,6 @@ export class HealthChecker {
26
26
  components.tmux = this.checkTmux();
27
27
  components.sessions = this.checkSessions();
28
28
  components.stateDir = this.checkStateDir();
29
- components.memory = this.checkMemory();
30
29
  if (this.scheduler) {
31
30
  components.scheduler = this.checkScheduler();
32
31
  }
@@ -136,27 +135,6 @@ export class HealthChecker {
136
135
  lastCheck: now,
137
136
  };
138
137
  }
139
- checkMemory() {
140
- const now = new Date().toISOString();
141
- try {
142
- const os = require('node:os');
143
- const totalBytes = os.totalmem();
144
- const freeBytes = os.freemem();
145
- const totalGB = totalBytes / (1024 ** 3);
146
- const freeGB = freeBytes / (1024 ** 3);
147
- const usedPercent = ((totalBytes - freeBytes) / totalBytes) * 100;
148
- if (usedPercent >= 90) {
149
- return { status: 'unhealthy', message: `Memory critical: ${usedPercent.toFixed(0)}% used (${freeGB.toFixed(1)}GB free)`, lastCheck: now };
150
- }
151
- if (usedPercent >= 75) {
152
- return { status: 'degraded', message: `Memory elevated: ${usedPercent.toFixed(0)}% used (${freeGB.toFixed(1)}GB free)`, lastCheck: now };
153
- }
154
- return { status: 'healthy', message: `${usedPercent.toFixed(0)}% used (${freeGB.toFixed(1)}GB free / ${totalGB.toFixed(0)}GB total)`, lastCheck: now };
155
- }
156
- catch (err) {
157
- return { status: 'degraded', message: `Memory check failed: ${err instanceof Error ? err.message : String(err)}`, lastCheck: now };
158
- }
159
- }
160
138
  checkStateDir() {
161
139
  const now = new Date().toISOString();
162
140
  try {
@@ -63,15 +63,6 @@ export function createRoutes(ctx) {
63
63
  heapUsed: Math.round(mem.heapUsed / 1024 / 1024),
64
64
  heapTotal: Math.round(mem.heapTotal / 1024 / 1024),
65
65
  };
66
- // System-wide memory state
67
- const os = require('node:os');
68
- const totalMem = os.totalmem();
69
- const freeMem = os.freemem();
70
- base.systemMemory = {
71
- totalGB: Math.round(totalMem / (1024 ** 3) * 10) / 10,
72
- freeGB: Math.round(freeMem / (1024 ** 3) * 10) / 10,
73
- usedPercent: Math.round(((totalMem - freeMem) / totalMem) * 1000) / 10,
74
- };
75
66
  // Job health summary
76
67
  if (ctx.scheduler) {
77
68
  const jobs = ctx.scheduler.getJobs();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instar",
3
- "version": "0.7.44",
3
+ "version": "0.7.45",
4
4
  "description": "Persistent autonomy infrastructure for AI agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,11 +0,0 @@
1
- > Why do I have a folder named ".vercel" in my project?
2
- The ".vercel" folder is created when you link a directory to a Vercel project.
3
-
4
- > What does the "project.json" file contain?
5
- The "project.json" file contains:
6
- - The ID of the Vercel project that you linked ("projectId")
7
- - The ID of the user or team your Vercel project is owned by ("orgId")
8
-
9
- > Should I commit the ".vercel" folder?
10
- No, you should not share the ".vercel" folder with anyone.
11
- Upon creation, it will be automatically added to your ".gitignore" file.
@@ -1 +0,0 @@
1
- {"projectId":"prj_evM5LcItYL3IAmw8zNvEPGrHeaya","orgId":"team_dHctwIDcV3X9ydapQlCPHFGI","projectName":"claude-agent-kit"}
@@ -1,50 +0,0 @@
1
- /**
2
- * CaffeinateManager - Prevent macOS system sleep for Instar server lifetime.
3
- *
4
- * Maintains a `caffeinate -s` child process that prevents system sleep.
5
- * A watchdog verifies it's alive every 30s and restarts if dead.
6
- * PID is written to <stateDir>/caffeinate.pid for crash recovery.
7
- *
8
- * Only activates on macOS (process.platform === 'darwin').
9
- * Uses EventEmitter pattern consistent with Instar conventions.
10
- */
11
- import { EventEmitter } from 'node:events';
12
- export interface CaffeinateManagerConfig {
13
- /** State directory for PID file storage */
14
- stateDir: string;
15
- }
16
- export interface CaffeinateStatus {
17
- running: boolean;
18
- pid: number | null;
19
- startedAt: string | null;
20
- restartCount: number;
21
- lastWatchdogCheck: string;
22
- }
23
- export declare class CaffeinateManager extends EventEmitter {
24
- private process;
25
- private watchdogInterval;
26
- private pid;
27
- private startedAt;
28
- private restartCount;
29
- private lastWatchdogCheck;
30
- private stopping;
31
- private pidFile;
32
- constructor(config: CaffeinateManagerConfig);
33
- /**
34
- * Start caffeinate and the watchdog.
35
- * Only activates on macOS.
36
- */
37
- start(): void;
38
- /**
39
- * Stop caffeinate and the watchdog cleanly.
40
- */
41
- stop(): void;
42
- getStatus(): CaffeinateStatus;
43
- private spawnCaffeinate;
44
- private killCaffeinate;
45
- private watchdog;
46
- private cleanupStale;
47
- private writePidFile;
48
- private removePidFile;
49
- }
50
- //# sourceMappingURL=CaffeinateManager.d.ts.map
@@ -1,180 +0,0 @@
1
- /**
2
- * CaffeinateManager - Prevent macOS system sleep for Instar server lifetime.
3
- *
4
- * Maintains a `caffeinate -s` child process that prevents system sleep.
5
- * A watchdog verifies it's alive every 30s and restarts if dead.
6
- * PID is written to <stateDir>/caffeinate.pid for crash recovery.
7
- *
8
- * Only activates on macOS (process.platform === 'darwin').
9
- * Uses EventEmitter pattern consistent with Instar conventions.
10
- */
11
- import { EventEmitter } from 'node:events';
12
- import { spawn, execSync } from 'child_process';
13
- import * as fs from 'fs';
14
- import * as path from 'path';
15
- const WATCHDOG_INTERVAL_MS = 30_000; // 30 seconds
16
- export class CaffeinateManager extends EventEmitter {
17
- process = null;
18
- watchdogInterval = null;
19
- pid = null;
20
- startedAt = null;
21
- restartCount = 0;
22
- lastWatchdogCheck = new Date().toISOString();
23
- stopping = false;
24
- pidFile;
25
- constructor(config) {
26
- super();
27
- this.pidFile = path.join(config.stateDir, 'caffeinate.pid');
28
- }
29
- /**
30
- * Start caffeinate and the watchdog.
31
- * Only activates on macOS.
32
- */
33
- start() {
34
- if (this.watchdogInterval)
35
- return;
36
- if (process.platform !== 'darwin') {
37
- console.log('[CaffeinateManager] Not macOS — skipping sleep prevention');
38
- return;
39
- }
40
- this.stopping = false;
41
- this.cleanupStale();
42
- this.spawnCaffeinate();
43
- this.watchdogInterval = setInterval(() => this.watchdog(), WATCHDOG_INTERVAL_MS);
44
- this.watchdogInterval.unref(); // Don't prevent process exit
45
- console.log(`[CaffeinateManager] Started (watchdog: ${WATCHDOG_INTERVAL_MS / 1000}s)`);
46
- }
47
- /**
48
- * Stop caffeinate and the watchdog cleanly.
49
- */
50
- stop() {
51
- this.stopping = true;
52
- if (this.watchdogInterval) {
53
- clearInterval(this.watchdogInterval);
54
- this.watchdogInterval = null;
55
- }
56
- this.killCaffeinate();
57
- this.removePidFile();
58
- console.log('[CaffeinateManager] Stopped');
59
- }
60
- getStatus() {
61
- return {
62
- running: this.process !== null && this.pid !== null,
63
- pid: this.pid,
64
- startedAt: this.startedAt,
65
- restartCount: this.restartCount,
66
- lastWatchdogCheck: this.lastWatchdogCheck,
67
- };
68
- }
69
- spawnCaffeinate() {
70
- try {
71
- const proc = spawn('caffeinate', ['-s'], {
72
- detached: true,
73
- stdio: 'ignore',
74
- });
75
- proc.unref();
76
- this.process = proc;
77
- this.pid = proc.pid ?? null;
78
- this.startedAt = new Date().toISOString();
79
- this.writePidFile();
80
- proc.on('exit', (code, signal) => {
81
- if (!this.stopping) {
82
- console.warn(`[CaffeinateManager] caffeinate exited (code: ${code}, signal: ${signal})`);
83
- this.emit('died', { code, signal });
84
- }
85
- this.process = null;
86
- this.pid = null;
87
- });
88
- proc.on('error', (err) => {
89
- console.error('[CaffeinateManager] caffeinate spawn error:', err.message);
90
- this.process = null;
91
- this.pid = null;
92
- });
93
- console.log(`[CaffeinateManager] caffeinate spawned (PID: ${this.pid})`);
94
- this.emit('started', { pid: this.pid });
95
- }
96
- catch (err) {
97
- console.error('[CaffeinateManager] Failed to spawn caffeinate:', err);
98
- }
99
- }
100
- killCaffeinate() {
101
- if (this.pid) {
102
- try {
103
- process.kill(this.pid, 'SIGTERM');
104
- }
105
- catch {
106
- // Already dead
107
- }
108
- }
109
- this.process = null;
110
- this.pid = null;
111
- }
112
- watchdog() {
113
- this.lastWatchdogCheck = new Date().toISOString();
114
- if (this.stopping)
115
- return;
116
- if (this.pid) {
117
- try {
118
- process.kill(this.pid, 0);
119
- return; // Still alive
120
- }
121
- catch {
122
- console.warn(`[CaffeinateManager] caffeinate PID ${this.pid} is dead`);
123
- this.process = null;
124
- this.pid = null;
125
- }
126
- }
127
- this.restartCount++;
128
- console.log(`[CaffeinateManager] Restarting caffeinate (restart #${this.restartCount})`);
129
- this.spawnCaffeinate();
130
- this.emit('restarted', { restartCount: this.restartCount });
131
- }
132
- cleanupStale() {
133
- try {
134
- if (fs.existsSync(this.pidFile)) {
135
- const stalePid = parseInt(fs.readFileSync(this.pidFile, 'utf-8').trim(), 10);
136
- if (!isNaN(stalePid) && stalePid > 0) {
137
- try {
138
- const cmdline = execSync(`ps -p ${stalePid} -o comm= 2>/dev/null`, {
139
- encoding: 'utf-8',
140
- timeout: 3000,
141
- }).trim();
142
- if (cmdline.includes('caffeinate')) {
143
- process.kill(stalePid, 'SIGTERM');
144
- console.log(`[CaffeinateManager] Killed stale caffeinate (PID: ${stalePid})`);
145
- }
146
- }
147
- catch {
148
- // Process doesn't exist
149
- }
150
- }
151
- this.removePidFile();
152
- }
153
- }
154
- catch {
155
- // PID file doesn't exist or can't be read
156
- }
157
- }
158
- writePidFile() {
159
- if (!this.pid)
160
- return;
161
- try {
162
- fs.mkdirSync(path.dirname(this.pidFile), { recursive: true });
163
- fs.writeFileSync(this.pidFile, String(this.pid));
164
- }
165
- catch (err) {
166
- console.error('[CaffeinateManager] Failed to write PID file:', err);
167
- }
168
- }
169
- removePidFile() {
170
- try {
171
- if (fs.existsSync(this.pidFile)) {
172
- fs.unlinkSync(this.pidFile);
173
- }
174
- }
175
- catch {
176
- // Not critical
177
- }
178
- }
179
- }
180
- //# sourceMappingURL=CaffeinateManager.js.map
@@ -1,83 +0,0 @@
1
- /**
2
- * MemoryPressureMonitor - Detect and respond to system memory pressure.
3
- *
4
- * Platform-aware: uses macOS `vm_stat` or Linux `/proc/meminfo`.
5
- * EventEmitter pattern consistent with Instar conventions.
6
- *
7
- * Thresholds:
8
- * - normal (< 60%): all operations allowed
9
- * - warning (60-75%): log trend, notify
10
- * - elevated (75-90%): restrict session spawning
11
- * - critical (90%+): block all spawns, alert
12
- *
13
- * Includes trend tracking via ring buffer + linear regression.
14
- */
15
- import { EventEmitter } from 'node:events';
16
- export type MemoryPressureState = 'normal' | 'warning' | 'elevated' | 'critical';
17
- export type MemoryTrend = 'rising' | 'stable' | 'falling';
18
- export interface MemoryState {
19
- pressurePercent: number;
20
- freeGB: number;
21
- totalGB: number;
22
- state: MemoryPressureState;
23
- trend: MemoryTrend;
24
- ratePerMin: number;
25
- lastChecked: string;
26
- stateChangedAt: string;
27
- platform: string;
28
- }
29
- export interface MemoryPressureMonitorConfig {
30
- /** Thresholds (percent). Defaults: warning=60, elevated=75, critical=90 */
31
- thresholds?: {
32
- warning?: number;
33
- elevated?: number;
34
- critical?: number;
35
- };
36
- /** Base check interval in ms. Default: 30000 */
37
- checkIntervalMs?: number;
38
- }
39
- export declare class MemoryPressureMonitor extends EventEmitter {
40
- private timeout;
41
- private currentState;
42
- private stateChangedAt;
43
- private lastChecked;
44
- private lastPressurePercent;
45
- private lastFreeGB;
46
- private lastTotalGB;
47
- private ringBuffer;
48
- private currentTrend;
49
- private currentRatePerMin;
50
- private thresholds;
51
- private baseIntervalMs;
52
- constructor(config?: MemoryPressureMonitorConfig);
53
- start(): void;
54
- stop(): void;
55
- getState(): MemoryState;
56
- /**
57
- * Can a new session be spawned?
58
- */
59
- canSpawnSession(): {
60
- allowed: boolean;
61
- reason?: string;
62
- };
63
- private scheduleNext;
64
- private check;
65
- private classifyState;
66
- /**
67
- * Read system memory — platform-aware.
68
- */
69
- private readSystemMemory;
70
- /**
71
- * macOS: parse vm_stat
72
- */
73
- private parseVmStat;
74
- /**
75
- * Linux: parse /proc/meminfo
76
- */
77
- private parseProcMeminfo;
78
- /**
79
- * Linear regression over recent readings.
80
- */
81
- private detectTrend;
82
- }
83
- //# sourceMappingURL=MemoryPressureMonitor.d.ts.map
@@ -1,242 +0,0 @@
1
- /**
2
- * MemoryPressureMonitor - Detect and respond to system memory pressure.
3
- *
4
- * Platform-aware: uses macOS `vm_stat` or Linux `/proc/meminfo`.
5
- * EventEmitter pattern consistent with Instar conventions.
6
- *
7
- * Thresholds:
8
- * - normal (< 60%): all operations allowed
9
- * - warning (60-75%): log trend, notify
10
- * - elevated (75-90%): restrict session spawning
11
- * - critical (90%+): block all spawns, alert
12
- *
13
- * Includes trend tracking via ring buffer + linear regression.
14
- */
15
- import { EventEmitter } from 'node:events';
16
- import { execSync } from 'node:child_process';
17
- import * as fs from 'node:fs';
18
- const DEFAULT_THRESHOLDS = {
19
- warning: 60,
20
- elevated: 75,
21
- critical: 90,
22
- };
23
- const RING_BUFFER_SIZE = 20;
24
- const TREND_WINDOW = 6;
25
- const PAGE_SIZE_BYTES = 16384; // macOS Apple Silicon
26
- // Adaptive intervals
27
- const INTERVALS = {
28
- normal: 30_000,
29
- warning: 15_000,
30
- elevated: 10_000,
31
- critical: 5_000,
32
- };
33
- export class MemoryPressureMonitor extends EventEmitter {
34
- timeout = null;
35
- currentState = 'normal';
36
- stateChangedAt = new Date().toISOString();
37
- lastChecked = new Date().toISOString();
38
- lastPressurePercent = 0;
39
- lastFreeGB = 0;
40
- lastTotalGB = 0;
41
- ringBuffer = [];
42
- currentTrend = 'stable';
43
- currentRatePerMin = 0;
44
- thresholds;
45
- baseIntervalMs;
46
- constructor(config = {}) {
47
- super();
48
- this.thresholds = {
49
- ...DEFAULT_THRESHOLDS,
50
- ...config.thresholds,
51
- };
52
- this.baseIntervalMs = config.checkIntervalMs ?? 30_000;
53
- }
54
- start() {
55
- if (this.timeout)
56
- return;
57
- this.check();
58
- this.scheduleNext();
59
- console.log(`[MemoryPressureMonitor] Started (platform: ${process.platform}, thresholds: ${JSON.stringify(this.thresholds)})`);
60
- }
61
- stop() {
62
- if (this.timeout) {
63
- clearTimeout(this.timeout);
64
- this.timeout = null;
65
- }
66
- }
67
- getState() {
68
- return {
69
- pressurePercent: this.lastPressurePercent,
70
- freeGB: this.lastFreeGB,
71
- totalGB: this.lastTotalGB,
72
- state: this.currentState,
73
- trend: this.currentTrend,
74
- ratePerMin: this.currentRatePerMin,
75
- lastChecked: this.lastChecked,
76
- stateChangedAt: this.stateChangedAt,
77
- platform: process.platform,
78
- };
79
- }
80
- /**
81
- * Can a new session be spawned?
82
- */
83
- canSpawnSession() {
84
- switch (this.currentState) {
85
- case 'normal':
86
- case 'warning':
87
- return { allowed: true };
88
- case 'elevated':
89
- return {
90
- allowed: false,
91
- reason: `Memory pressure elevated (${this.lastPressurePercent.toFixed(1)}%) — session spawn blocked`,
92
- };
93
- case 'critical':
94
- return {
95
- allowed: false,
96
- reason: `Memory pressure critical (${this.lastPressurePercent.toFixed(1)}%) — all spawns blocked`,
97
- };
98
- }
99
- }
100
- scheduleNext() {
101
- const intervalMs = INTERVALS[this.currentState] || this.baseIntervalMs;
102
- this.timeout = setTimeout(() => {
103
- this.check();
104
- this.scheduleNext();
105
- }, intervalMs);
106
- this.timeout.unref(); // Don't prevent process exit
107
- }
108
- check() {
109
- try {
110
- const { pressurePercent, freeGB, totalGB } = this.readSystemMemory();
111
- this.lastPressurePercent = pressurePercent;
112
- this.lastFreeGB = freeGB;
113
- this.lastTotalGB = totalGB;
114
- this.lastChecked = new Date().toISOString();
115
- // Ring buffer
116
- this.ringBuffer.push({ timestamp: Date.now(), pressurePercent });
117
- if (this.ringBuffer.length > RING_BUFFER_SIZE) {
118
- this.ringBuffer.shift();
119
- }
120
- // Trend
121
- const { trend, ratePerMin } = this.detectTrend();
122
- this.currentTrend = trend;
123
- this.currentRatePerMin = ratePerMin;
124
- const newState = this.classifyState(pressurePercent);
125
- if (newState !== this.currentState) {
126
- const from = this.currentState;
127
- this.currentState = newState;
128
- this.stateChangedAt = new Date().toISOString();
129
- console.log(`[MemoryPressureMonitor] ${from} -> ${newState} (${pressurePercent.toFixed(1)}%, ${freeGB.toFixed(1)}GB free, trend: ${trend})`);
130
- this.emit('stateChange', { from, to: newState, state: this.getState() });
131
- }
132
- }
133
- catch (error) {
134
- console.error('[MemoryPressureMonitor] Check failed:', error);
135
- }
136
- }
137
- classifyState(pressurePercent) {
138
- if (pressurePercent >= this.thresholds.critical)
139
- return 'critical';
140
- if (pressurePercent >= this.thresholds.elevated)
141
- return 'elevated';
142
- if (pressurePercent >= this.thresholds.warning)
143
- return 'warning';
144
- return 'normal';
145
- }
146
- /**
147
- * Read system memory — platform-aware.
148
- */
149
- readSystemMemory() {
150
- if (process.platform === 'darwin') {
151
- return this.parseVmStat();
152
- }
153
- else if (process.platform === 'linux') {
154
- return this.parseProcMeminfo();
155
- }
156
- else {
157
- // Fallback: use Node's process.memoryUsage (very rough)
158
- const mem = process.memoryUsage();
159
- const totalGB = require('os').totalmem() / (1024 ** 3);
160
- const usedGB = mem.rss / (1024 ** 3);
161
- return {
162
- pressurePercent: (usedGB / totalGB) * 100,
163
- freeGB: totalGB - usedGB,
164
- totalGB,
165
- };
166
- }
167
- }
168
- /**
169
- * macOS: parse vm_stat
170
- */
171
- parseVmStat() {
172
- const output = execSync('vm_stat', { encoding: 'utf-8', timeout: 5000 });
173
- const pageSizeMatch = output.match(/page size of (\d+) bytes/);
174
- const pageSize = pageSizeMatch ? parseInt(pageSizeMatch[1], 10) : PAGE_SIZE_BYTES;
175
- const parsePages = (label) => {
176
- const match = output.match(new RegExp(`${label}:\\s+(\\d+)`));
177
- return match ? parseInt(match[1], 10) : 0;
178
- };
179
- const freePages = parsePages('Pages free');
180
- const activePages = parsePages('Pages active');
181
- const inactivePages = parsePages('Pages inactive');
182
- const wiredPages = parsePages('Pages wired down');
183
- const compressorPages = parsePages('Pages occupied by compressor');
184
- const purgeablePages = parsePages('Pages purgeable');
185
- const totalPages = freePages + activePages + inactivePages + wiredPages + compressorPages;
186
- const totalBytes = totalPages * pageSize;
187
- const totalGB = totalBytes / (1024 ** 3);
188
- const availablePages = freePages + inactivePages + purgeablePages;
189
- const availableBytes = availablePages * pageSize;
190
- const freeGB = availableBytes / (1024 ** 3);
191
- const usedPages = totalPages - availablePages;
192
- const pressurePercent = totalPages > 0 ? (usedPages / totalPages) * 100 : 0;
193
- return { pressurePercent, freeGB, totalGB };
194
- }
195
- /**
196
- * Linux: parse /proc/meminfo
197
- */
198
- parseProcMeminfo() {
199
- const content = fs.readFileSync('/proc/meminfo', 'utf-8');
200
- const parseKB = (key) => {
201
- const match = content.match(new RegExp(`${key}:\\s+(\\d+)`));
202
- return match ? parseInt(match[1], 10) : 0;
203
- };
204
- const totalKB = parseKB('MemTotal');
205
- const availableKB = parseKB('MemAvailable') || (parseKB('MemFree') + parseKB('Buffers') + parseKB('Cached'));
206
- const totalGB = totalKB / (1024 * 1024);
207
- const freeGB = availableKB / (1024 * 1024);
208
- const pressurePercent = totalKB > 0 ? ((totalKB - availableKB) / totalKB) * 100 : 0;
209
- return { pressurePercent, freeGB, totalGB };
210
- }
211
- /**
212
- * Linear regression over recent readings.
213
- */
214
- detectTrend() {
215
- if (this.ringBuffer.length < 3) {
216
- return { trend: 'stable', ratePerMin: 0 };
217
- }
218
- const readings = this.ringBuffer.slice(-TREND_WINDOW);
219
- const n = readings.length;
220
- const firstTs = readings[0].timestamp;
221
- const xs = readings.map(r => (r.timestamp - firstTs) / 1000);
222
- const ys = readings.map(r => r.pressurePercent);
223
- const sumX = xs.reduce((a, b) => a + b, 0);
224
- const sumY = ys.reduce((a, b) => a + b, 0);
225
- const sumXY = xs.reduce((a, x, i) => a + x * ys[i], 0);
226
- const sumX2 = xs.reduce((a, x) => a + x * x, 0);
227
- const denom = n * sumX2 - sumX * sumX;
228
- if (denom === 0)
229
- return { trend: 'stable', ratePerMin: 0 };
230
- const slope = (n * sumXY - sumX * sumY) / denom;
231
- const ratePerMin = slope * 60;
232
- let trend;
233
- if (ratePerMin > 0.5)
234
- trend = 'rising';
235
- else if (ratePerMin < -0.5)
236
- trend = 'falling';
237
- else
238
- trend = 'stable';
239
- return { trend, ratePerMin };
240
- }
241
- }
242
- //# sourceMappingURL=MemoryPressureMonitor.js.map