instar 1.3.73 → 1.3.74

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.
@@ -6,6 +6,32 @@
6
6
  * We detect this drift and fire a callback.
7
7
  *
8
8
  * Ported from Dawn's infrastructure — battle-tested in production.
9
+ *
10
+ * ## CPU-starvation guard (2026-05-28)
11
+ *
12
+ * Timer drift has TWO causes, not one:
13
+ * 1. Real sleep — the OS suspends the process; the wall clock jumps forward.
14
+ * 2. CPU starvation — the machine is so oversubscribed (load >> cores) that the
15
+ * event loop can't service the `checkIntervalMs` timer on time. The wall
16
+ * clock advances normally, but the callback fires seconds late.
17
+ *
18
+ * The original detector could not tell these apart, so on a heavily-loaded box
19
+ * (e.g. many concurrent agent sessions) it fired hundreds of false "wake" events
20
+ * — each triggering expensive wake-recovery (tunnel restart, re-registration,
21
+ * failure-counter resets) that piled MORE load on, a self-reinforcing storm.
22
+ *
23
+ * The guard distinguishes the two:
24
+ * - A **long** drift (>= `longSleepFloorSeconds`) is unambiguously real sleep —
25
+ * a live machine never starves its own event loop for minutes (a watchdog
26
+ * would declare it dead first). Always emitted, regardless of load.
27
+ * - A **short** drift under high system load (`loadavg[0] / cpuCount >
28
+ * maxLoadRatio`) is treated as CPU starvation and SUPPRESSED — no `wake`.
29
+ * - A short drift under normal load is a brief real sleep — emitted.
30
+ * On top of classification, a `minWakeIntervalMs` cooldown caps the emit rate so
31
+ * even a misclassified burst can't trigger a recovery storm.
32
+ *
33
+ * Suppressed events are NOT added to `wakeHistory`, so `getCumulativeSleepMsBetween`
34
+ * (the wake-reaper's sleep-credit source) only ever counts genuine sleep.
9
35
  */
10
36
  import { EventEmitter } from 'node:events';
11
37
  export interface SleepWakeDetectorConfig {
@@ -13,20 +39,73 @@ export interface SleepWakeDetectorConfig {
13
39
  checkIntervalMs?: number;
14
40
  /** How much drift (ms) indicates a sleep event. Default: 10000 */
15
41
  driftThresholdMs?: number;
42
+ /**
43
+ * Above this `loadavg[0] / cpuCount` ratio, a SHORT drift is classified as CPU
44
+ * starvation and suppressed rather than emitted as a wake. Default: 1.5.
45
+ * Set to `Infinity` to disable the load guard (always trust the drift).
46
+ */
47
+ maxLoadRatio?: number;
48
+ /**
49
+ * A drift at least this long (seconds) is always treated as real sleep,
50
+ * regardless of load — a live event loop never starves for this long.
51
+ * Default: 300 (5 minutes).
52
+ */
53
+ longSleepFloorSeconds?: number;
54
+ /**
55
+ * Minimum gap (ms) between EMITTED wake events. A short drift that would emit
56
+ * within this cooldown of the previous emitted wake is suppressed, bounding
57
+ * recovery storms. Long sleeps bypass the cooldown. Default: 60000 (1 min).
58
+ */
59
+ minWakeIntervalMs?: number;
60
+ /** Injectable system-load source (testing). Default: os.loadavg. */
61
+ loadAvgProvider?: () => number[];
62
+ /** Injectable CPU-count source (testing). Default: os.cpus().length. */
63
+ cpuCountProvider?: () => number;
64
+ /** Injectable wall-clock source (testing). Default: Date.now. */
65
+ nowProvider?: () => number;
16
66
  }
17
67
  export interface WakeEvent {
18
68
  sleepDurationSeconds: number;
19
69
  timestamp: string;
20
70
  }
71
+ export type WakeSuppressionReason = 'cpu-starvation' | 'cooldown';
72
+ export interface SuppressedWakeEvent {
73
+ reason: WakeSuppressionReason;
74
+ driftSeconds: number;
75
+ loadRatio: number;
76
+ timestamp: string;
77
+ }
78
+ export interface SleepWakeStats {
79
+ wakeCount: number;
80
+ totalSleepSeconds: number;
81
+ longestSleepSeconds: number;
82
+ /** Drifts classified as CPU starvation / rate-limited and NOT emitted. */
83
+ suppressedCount: number;
84
+ suppressedByReason: Record<WakeSuppressionReason, number>;
85
+ /** ISO timestamp of the most recent suppression, or null. */
86
+ lastSuppressedAt: string | null;
87
+ }
21
88
  export declare class SleepWakeDetector extends EventEmitter {
22
89
  private interval;
23
90
  private lastTick;
91
+ private lastEmittedWakeAtMs;
24
92
  private checkIntervalMs;
25
93
  private driftThresholdMs;
94
+ private maxLoadRatio;
95
+ private longSleepFloorSeconds;
96
+ private minWakeIntervalMs;
97
+ private loadAvgProvider;
98
+ private cpuCountProvider;
99
+ private now;
26
100
  private wakeHistory;
101
+ private suppressionHistory;
27
102
  constructor(config?: SleepWakeDetectorConfig);
28
103
  start(): void;
29
104
  stop(): void;
105
+ /** Current `loadavg[0] / cpuCount` ratio. Returns 0 when load is unavailable
106
+ * (e.g. Windows reports [0,0,0]), which disables the starvation guard there. */
107
+ private currentLoadRatio;
108
+ private recordSuppression;
30
109
  /**
31
110
  * Cumulative wall-time-asleep during the half-open window [startMs, endMs).
32
111
  * Used by the wake-reaper (UNIFIED-SESSION-LIFECYCLE §P0 #9 / SE-8) to subtract
@@ -38,13 +117,12 @@ export declare class SleepWakeDetector extends EventEmitter {
38
117
  * [wakeTimestamp − sleepDurationSeconds, wakeTimestamp].
39
118
  * Returns the sum of overlap with the query window in milliseconds. Returns 0
40
119
  * when the history is empty or no event overlaps.
120
+ *
121
+ * Only EMITTED wakes are in `wakeHistory`; suppressed CPU-starvation drifts are
122
+ * deliberately excluded so starvation is never credited as real sleep.
41
123
  */
42
124
  getCumulativeSleepMsBetween(startMs: number, endMs: number): number;
43
125
  /** Get wake event stats for telemetry reporting. */
44
- getStats(sinceMs?: number): {
45
- wakeCount: number;
46
- totalSleepSeconds: number;
47
- longestSleepSeconds: number;
48
- };
126
+ getStats(sinceMs?: number): SleepWakeStats;
49
127
  }
50
128
  //# sourceMappingURL=SleepWakeDetector.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SleepWakeDetector.d.ts","sourceRoot":"","sources":["../../src/core/SleepWakeDetector.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,WAAW,uBAAuB;IACtC,uDAAuD;IACvD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,kEAAkE;IAClE,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,SAAS;IACxB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,qBAAa,iBAAkB,SAAQ,YAAY;IACjD,OAAO,CAAC,QAAQ,CAA+C;IAC/D,OAAO,CAAC,QAAQ,CAAsB;IACtC,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,WAAW,CAAmB;gBAE1B,MAAM,GAAE,uBAA4B;IAMhD,KAAK,IAAI,IAAI;IAqBb,IAAI,IAAI,IAAI;IAOZ;;;;;;;;;;;OAWG;IACH,2BAA2B,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM;IAanE,oDAAoD;IACpD,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,iBAAiB,EAAE,MAAM,CAAC;QAAC,mBAAmB,EAAE,MAAM,CAAA;KAAE;CAS1G"}
1
+ {"version":3,"file":"SleepWakeDetector.d.ts","sourceRoot":"","sources":["../../src/core/SleepWakeDetector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3C,MAAM,WAAW,uBAAuB;IACtC,uDAAuD;IACvD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,kEAAkE;IAClE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,oEAAoE;IACpE,eAAe,CAAC,EAAE,MAAM,MAAM,EAAE,CAAC;IACjC,wEAAwE;IACxE,gBAAgB,CAAC,EAAE,MAAM,MAAM,CAAC;IAChC,iEAAiE;IACjE,WAAW,CAAC,EAAE,MAAM,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,SAAS;IACxB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,qBAAqB,GAAG,gBAAgB,GAAG,UAAU,CAAC;AAElE,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,qBAAqB,CAAC;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,0EAA0E;IAC1E,eAAe,EAAE,MAAM,CAAC;IACxB,kBAAkB,EAAE,MAAM,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC;IAC1D,6DAA6D;IAC7D,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAED,qBAAa,iBAAkB,SAAQ,YAAY;IACjD,OAAO,CAAC,QAAQ,CAA+C;IAC/D,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,mBAAmB,CAAuB;IAClD,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,qBAAqB,CAAS;IACtC,OAAO,CAAC,iBAAiB,CAAS;IAClC,OAAO,CAAC,eAAe,CAAiB;IACxC,OAAO,CAAC,gBAAgB,CAAe;IACvC,OAAO,CAAC,GAAG,CAAe;IAC1B,OAAO,CAAC,WAAW,CAAmB;IACtC,OAAO,CAAC,kBAAkB,CAA6B;gBAE3C,MAAM,GAAE,uBAA4B;IAahD,KAAK,IAAI,IAAI;IAkDb,IAAI,IAAI,IAAI;IAOZ;qFACiF;IACjF,OAAO,CAAC,gBAAgB;IAiBxB,OAAO,CAAC,iBAAiB;IAezB;;;;;;;;;;;;;;OAcG;IACH,2BAA2B,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM;IAanE,oDAAoD;IACpD,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,cAAc;CAkB3C"}
@@ -6,36 +6,98 @@
6
6
  * We detect this drift and fire a callback.
7
7
  *
8
8
  * Ported from Dawn's infrastructure — battle-tested in production.
9
+ *
10
+ * ## CPU-starvation guard (2026-05-28)
11
+ *
12
+ * Timer drift has TWO causes, not one:
13
+ * 1. Real sleep — the OS suspends the process; the wall clock jumps forward.
14
+ * 2. CPU starvation — the machine is so oversubscribed (load >> cores) that the
15
+ * event loop can't service the `checkIntervalMs` timer on time. The wall
16
+ * clock advances normally, but the callback fires seconds late.
17
+ *
18
+ * The original detector could not tell these apart, so on a heavily-loaded box
19
+ * (e.g. many concurrent agent sessions) it fired hundreds of false "wake" events
20
+ * — each triggering expensive wake-recovery (tunnel restart, re-registration,
21
+ * failure-counter resets) that piled MORE load on, a self-reinforcing storm.
22
+ *
23
+ * The guard distinguishes the two:
24
+ * - A **long** drift (>= `longSleepFloorSeconds`) is unambiguously real sleep —
25
+ * a live machine never starves its own event loop for minutes (a watchdog
26
+ * would declare it dead first). Always emitted, regardless of load.
27
+ * - A **short** drift under high system load (`loadavg[0] / cpuCount >
28
+ * maxLoadRatio`) is treated as CPU starvation and SUPPRESSED — no `wake`.
29
+ * - A short drift under normal load is a brief real sleep — emitted.
30
+ * On top of classification, a `minWakeIntervalMs` cooldown caps the emit rate so
31
+ * even a misclassified burst can't trigger a recovery storm.
32
+ *
33
+ * Suppressed events are NOT added to `wakeHistory`, so `getCumulativeSleepMsBetween`
34
+ * (the wake-reaper's sleep-credit source) only ever counts genuine sleep.
9
35
  */
10
36
  import { EventEmitter } from 'node:events';
37
+ import os from 'node:os';
11
38
  export class SleepWakeDetector extends EventEmitter {
12
39
  interval = null;
13
- lastTick = Date.now();
40
+ lastTick;
41
+ lastEmittedWakeAtMs = null;
14
42
  checkIntervalMs;
15
43
  driftThresholdMs;
44
+ maxLoadRatio;
45
+ longSleepFloorSeconds;
46
+ minWakeIntervalMs;
47
+ loadAvgProvider;
48
+ cpuCountProvider;
49
+ now;
16
50
  wakeHistory = [];
51
+ suppressionHistory = [];
17
52
  constructor(config = {}) {
18
53
  super();
19
54
  this.checkIntervalMs = config.checkIntervalMs ?? 2000;
20
55
  this.driftThresholdMs = config.driftThresholdMs ?? 10000;
56
+ this.maxLoadRatio = config.maxLoadRatio ?? 1.5;
57
+ this.longSleepFloorSeconds = config.longSleepFloorSeconds ?? 300;
58
+ this.minWakeIntervalMs = config.minWakeIntervalMs ?? 60000;
59
+ this.loadAvgProvider = config.loadAvgProvider ?? (() => os.loadavg());
60
+ this.cpuCountProvider = config.cpuCountProvider ?? (() => os.cpus().length);
61
+ this.now = config.nowProvider ?? (() => Date.now());
62
+ this.lastTick = this.now();
21
63
  }
22
64
  start() {
23
65
  if (this.interval)
24
66
  return;
25
- this.lastTick = Date.now();
67
+ this.lastTick = this.now();
26
68
  this.interval = setInterval(() => {
27
- const now = Date.now();
69
+ const now = this.now();
28
70
  const elapsed = now - this.lastTick;
29
71
  this.lastTick = now;
30
- if (elapsed > this.driftThresholdMs) {
31
- const sleepDuration = Math.round((elapsed - this.checkIntervalMs) / 1000);
32
- console.log(`[SleepWakeDetector] Wake detected after ~${sleepDuration}s sleep`);
33
- const event = { sleepDurationSeconds: sleepDuration, timestamp: new Date().toISOString() };
34
- this.wakeHistory.push(event);
35
- if (this.wakeHistory.length > 100)
36
- this.wakeHistory.shift();
37
- this.emit('wake', event);
72
+ if (elapsed <= this.driftThresholdMs)
73
+ return;
74
+ const sleepDuration = Math.round((elapsed - this.checkIntervalMs) / 1000);
75
+ const isLongSleep = sleepDuration >= this.longSleepFloorSeconds;
76
+ const loadRatio = this.currentLoadRatio();
77
+ // Short drift under heavy CPU load = event-loop starvation, not sleep.
78
+ if (!isLongSleep && loadRatio > this.maxLoadRatio) {
79
+ this.recordSuppression('cpu-starvation', sleepDuration, loadRatio, now);
80
+ console.warn(`[SleepWakeDetector] Drift ~${sleepDuration}s under load ratio ${loadRatio.toFixed(2)} ` +
81
+ `(> ${this.maxLoadRatio}) — treating as CPU starvation, suppressing wake`);
82
+ return;
83
+ }
84
+ // Rate-limit emitted wakes so even a misclassified burst can't storm
85
+ // recovery. Long sleeps bypass the cooldown — recovery there is essential.
86
+ if (!isLongSleep &&
87
+ this.lastEmittedWakeAtMs !== null &&
88
+ now - this.lastEmittedWakeAtMs < this.minWakeIntervalMs) {
89
+ this.recordSuppression('cooldown', sleepDuration, loadRatio, now);
90
+ console.warn(`[SleepWakeDetector] Wake within cooldown ` +
91
+ `(${now - this.lastEmittedWakeAtMs}ms < ${this.minWakeIntervalMs}ms) — suppressing duplicate recovery`);
92
+ return;
38
93
  }
94
+ console.log(`[SleepWakeDetector] Wake detected after ~${sleepDuration}s sleep`);
95
+ const event = { sleepDurationSeconds: sleepDuration, timestamp: new Date(now).toISOString() };
96
+ this.wakeHistory.push(event);
97
+ if (this.wakeHistory.length > 100)
98
+ this.wakeHistory.shift();
99
+ this.lastEmittedWakeAtMs = now;
100
+ this.emit('wake', event);
39
101
  }, this.checkIntervalMs);
40
102
  this.interval.unref(); // Don't prevent process exit in CLI contexts
41
103
  }
@@ -45,6 +107,37 @@ export class SleepWakeDetector extends EventEmitter {
45
107
  this.interval = null;
46
108
  }
47
109
  }
110
+ /** Current `loadavg[0] / cpuCount` ratio. Returns 0 when load is unavailable
111
+ * (e.g. Windows reports [0,0,0]), which disables the starvation guard there. */
112
+ currentLoadRatio() {
113
+ let cpuCount = 1;
114
+ try {
115
+ cpuCount = Math.max(1, this.cpuCountProvider());
116
+ }
117
+ catch {
118
+ cpuCount = 1;
119
+ }
120
+ let load1 = 0;
121
+ try {
122
+ load1 = this.loadAvgProvider()[0] ?? 0;
123
+ }
124
+ catch {
125
+ load1 = 0;
126
+ }
127
+ if (!Number.isFinite(load1) || load1 <= 0)
128
+ return 0;
129
+ return load1 / cpuCount;
130
+ }
131
+ recordSuppression(reason, driftSeconds, loadRatio, nowMs) {
132
+ this.suppressionHistory.push({
133
+ reason,
134
+ driftSeconds,
135
+ loadRatio: Math.round(loadRatio * 100) / 100,
136
+ timestamp: new Date(nowMs).toISOString(),
137
+ });
138
+ if (this.suppressionHistory.length > 100)
139
+ this.suppressionHistory.shift();
140
+ }
48
141
  /**
49
142
  * Cumulative wall-time-asleep during the half-open window [startMs, endMs).
50
143
  * Used by the wake-reaper (UNIFIED-SESSION-LIFECYCLE §P0 #9 / SE-8) to subtract
@@ -56,6 +149,9 @@ export class SleepWakeDetector extends EventEmitter {
56
149
  * [wakeTimestamp − sleepDurationSeconds, wakeTimestamp].
57
150
  * Returns the sum of overlap with the query window in milliseconds. Returns 0
58
151
  * when the history is empty or no event overlaps.
152
+ *
153
+ * Only EMITTED wakes are in `wakeHistory`; suppressed CPU-starvation drifts are
154
+ * deliberately excluded so starvation is never credited as real sleep.
59
155
  */
60
156
  getCumulativeSleepMsBetween(startMs, endMs) {
61
157
  if (endMs <= startMs)
@@ -75,10 +171,20 @@ export class SleepWakeDetector extends EventEmitter {
75
171
  getStats(sinceMs) {
76
172
  const since = sinceMs ?? 0;
77
173
  const relevant = this.wakeHistory.filter(e => new Date(e.timestamp).getTime() >= since);
174
+ const suppressed = this.suppressionHistory.filter(e => new Date(e.timestamp).getTime() >= since);
175
+ const suppressedByReason = {
176
+ 'cpu-starvation': 0,
177
+ cooldown: 0,
178
+ };
179
+ for (const e of suppressed)
180
+ suppressedByReason[e.reason]++;
78
181
  return {
79
182
  wakeCount: relevant.length,
80
183
  totalSleepSeconds: relevant.reduce((sum, e) => sum + e.sleepDurationSeconds, 0),
81
184
  longestSleepSeconds: relevant.length > 0 ? Math.max(...relevant.map(e => e.sleepDurationSeconds)) : 0,
185
+ suppressedCount: suppressed.length,
186
+ suppressedByReason,
187
+ lastSuppressedAt: suppressed.length > 0 ? suppressed[suppressed.length - 1].timestamp : null,
82
188
  };
83
189
  }
84
190
  }
@@ -1 +1 @@
1
- {"version":3,"file":"SleepWakeDetector.js","sourceRoot":"","sources":["../../src/core/SleepWakeDetector.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"SleepWakeDetector.js","sourceRoot":"","sources":["../../src/core/SleepWakeDetector.ts"],"names":[],"mappings":"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"}
@@ -2542,6 +2542,23 @@ export interface MonitoringConfig {
2542
2542
  dryRun?: boolean;
2543
2543
  };
2544
2544
  };
2545
+ /**
2546
+ * SleepWakeDetector CPU-starvation guard tuning. All optional — the class ships
2547
+ * sane defaults, so absence of this block (every existing agent) still gets the
2548
+ * fix on update with no config migration. Read at runtime as a fallback against
2549
+ * the shipped defaults.
2550
+ */
2551
+ sleepWake?: {
2552
+ /** loadavg[0]/cpuCount above which a SHORT drift is treated as CPU starvation
2553
+ * (suppressed, not a wake). Default: 1.5. Set high to disable the guard. */
2554
+ maxLoadRatio?: number;
2555
+ /** A drift at least this long (seconds) is always treated as real sleep,
2556
+ * regardless of load. Default: 300. */
2557
+ longSleepFloorSeconds?: number;
2558
+ /** Minimum gap (ms) between EMITTED wakes; short drifts within it are
2559
+ * rate-limited. Long sleeps bypass it. Default: 60000. */
2560
+ minWakeIntervalMs?: number;
2561
+ };
2545
2562
  /**
2546
2563
  * SessionReaper — pressure-aware reaper of idle-but-alive sessions. The only
2547
2564
  * monitor that *kills* on a heuristic, so it ships OFF + dry-run by default.