instar 1.3.72 → 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.
Files changed (38) hide show
  1. package/dist/commands/server.d.ts.map +1 -1
  2. package/dist/commands/server.js +24 -4
  3. package/dist/commands/server.js.map +1 -1
  4. package/dist/core/SleepWakeDetector.d.ts +83 -5
  5. package/dist/core/SleepWakeDetector.d.ts.map +1 -1
  6. package/dist/core/SleepWakeDetector.js +117 -11
  7. package/dist/core/SleepWakeDetector.js.map +1 -1
  8. package/dist/core/UsherActedCorrelator.d.ts +96 -0
  9. package/dist/core/UsherActedCorrelator.d.ts.map +1 -0
  10. package/dist/core/UsherActedCorrelator.js +175 -0
  11. package/dist/core/UsherActedCorrelator.js.map +1 -0
  12. package/dist/core/UsherSignalStore.d.ts +26 -2
  13. package/dist/core/UsherSignalStore.d.ts.map +1 -1
  14. package/dist/core/UsherSignalStore.js +17 -2
  15. package/dist/core/UsherSignalStore.js.map +1 -1
  16. package/dist/core/types.d.ts +17 -0
  17. package/dist/core/types.d.ts.map +1 -1
  18. package/dist/core/types.js.map +1 -1
  19. package/dist/scaffold/templates.d.ts.map +1 -1
  20. package/dist/scaffold/templates.js +6 -0
  21. package/dist/scaffold/templates.js.map +1 -1
  22. package/dist/server/AgentServer.d.ts +3 -0
  23. package/dist/server/AgentServer.d.ts.map +1 -1
  24. package/dist/server/AgentServer.js +2 -0
  25. package/dist/server/AgentServer.js.map +1 -1
  26. package/dist/server/routes.d.ts +8 -0
  27. package/dist/server/routes.d.ts.map +1 -1
  28. package/dist/server/routes.js +26 -0
  29. package/dist/server/routes.js.map +1 -1
  30. package/dist/server/usherRoutes.d.ts.map +1 -1
  31. package/dist/server/usherRoutes.js +7 -1
  32. package/dist/server/usherRoutes.js.map +1 -1
  33. package/package.json +1 -1
  34. package/src/data/builtin-manifest.json +47 -47
  35. package/src/scaffold/templates.ts +6 -0
  36. package/upgrades/1.3.73.md +73 -0
  37. package/upgrades/1.3.74.md +95 -0
  38. package/upgrades/side-effects/usher-precision-loop.md +131 -0
@@ -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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"}
@@ -0,0 +1,96 @@
1
+ /**
2
+ * UsherActedCorrelator — the precision-numerator wiring for the Usher (rung 4 of
3
+ * continuous-working-awareness).
4
+ *
5
+ * The Usher FIRES re-surface signals (UsherSignalStore.recordSignal). Until this
6
+ * module, nothing ever called markActed — so `precision = acted/fired` was pinned
7
+ * at 0 and the rung-5 (mid-task injection) gate could never be satisfied by data.
8
+ * This closes that loop via TWO correlation paths, both reducing to "does some
9
+ * probe text COVER a fired signal's contextText?":
10
+ *
11
+ * (a) auto-use — the agent's next reply on the topic uses the re-surfaced
12
+ * context. creditUsherOnOutbound(). The nudge was useful: the
13
+ * agent acted on it.
14
+ * (b) miss-map — the user later had to CORRECT the agent (a HumanAsDetector
15
+ * signal) on that same context. creditUsherOnMiss(). The nudge
16
+ * was a genuine catch the agent ignored — still a true positive
17
+ * for the Usher's precision.
18
+ *
19
+ * Matching is deliberately PRECISION-OVER-RECALL: precision gates whether the
20
+ * Usher earns the right to interrupt (rung 5), so a falsely-HIGH precision is the
21
+ * dangerous direction. We require a meaningful salient-term overlap (≥ MIN_SHARED
22
+ * shared content words AND ≥ MIN_COVERAGE of the context's terms) and only credit
23
+ * recently-fired signals. Under-crediting a fast reply is acceptable; inflating
24
+ * the gate is not.
25
+ *
26
+ * All exported integration fns are best-effort and NEVER throw into the
27
+ * message/delivery path — mirroring the Usher's own degrade-safety invariants.
28
+ */
29
+ import type { UsherSignal, UsherSignalStore } from './UsherSignalStore.js';
30
+ /** Minimal inbound-entry shape the miss path reads (matches InboundMessageEntry). */
31
+ export interface MissEntry {
32
+ topicId?: number | null;
33
+ text?: string;
34
+ }
35
+ export declare const MIN_SHARED = 2;
36
+ export declare const MIN_COVERAGE = 0.5;
37
+ /** Window in which the agent's reply still counts as "using" a just-fired nudge. */
38
+ export declare const USE_WINDOW_MS: number;
39
+ /** Window in which a later user correction still credits a prior nudge. */
40
+ export declare const MISS_WINDOW_MS: number;
41
+ /**
42
+ * Salient content tokens of a text: lowercased, alphanumeric-split, ≥3 chars,
43
+ * stopwords removed. Pure + deterministic.
44
+ */
45
+ export declare function salientTerms(text: string): Set<string>;
46
+ /**
47
+ * Does `probeText` cover enough of `contextText`'s salient terms to count as a
48
+ * genuine reference to it? Requires ≥ minShared shared terms AND ≥ minCoverage
49
+ * fraction of the context's terms (so single-coincidental-word matches fail, and
50
+ * a long context needs proportionally more overlap).
51
+ */
52
+ export declare function contextCoveredBy(contextText: string, probeText: string, opts?: {
53
+ minShared?: number;
54
+ minCoverage?: number;
55
+ }): boolean;
56
+ /**
57
+ * Pure: among NOT-yet-acted signals, which ones does `probeText` cover? Returns
58
+ * their ids. `maxAgeMs`+`nowMs` optionally drop signals older than the window.
59
+ */
60
+ export declare function findCoveredSignalIds(signals: UsherSignal[], probeText: string, opts?: {
61
+ minShared?: number;
62
+ minCoverage?: number;
63
+ maxAgeMs?: number;
64
+ nowMs?: number;
65
+ }): string[];
66
+ /**
67
+ * Core integration: correlate `probeText` against a topic's fired signals and
68
+ * mark the matches acted (stamped with `via`). Best-effort, never throws.
69
+ * Returns the ids actually marked.
70
+ */
71
+ export declare function markActedByCoverage(store: UsherSignalStore, topicId: number, probeText: string, via: 'use' | 'miss', opts?: {
72
+ now?: () => number;
73
+ maxAgeMs?: number;
74
+ minShared?: number;
75
+ minCoverage?: number;
76
+ onMarked?: (id: string) => void;
77
+ }): string[];
78
+ /**
79
+ * Path (a): the agent's reply just went out on `topicId`. Credit any recent
80
+ * faded-context nudge the reply actually used. Wire on the outbound-reply path.
81
+ */
82
+ export declare function creditUsherOnOutbound(store: UsherSignalStore | null | undefined, topicId: number, replyText: string, opts?: {
83
+ now?: () => number;
84
+ maxAgeMs?: number;
85
+ }): string[];
86
+ /**
87
+ * Path (b): the user just had to correct the agent (`missSignal` non-null). If a
88
+ * recent nudge on this topic covers what they're correcting, that nudge was a
89
+ * real catch the agent ignored → credit it. Wire on the inbound human-detector
90
+ * seam. No-op when there was no miss, no topic, or no text.
91
+ */
92
+ export declare function creditUsherOnMiss(store: UsherSignalStore | null | undefined, missSignal: unknown, entry: MissEntry, opts?: {
93
+ now?: () => number;
94
+ maxAgeMs?: number;
95
+ }): string[];
96
+ //# sourceMappingURL=UsherActedCorrelator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UsherActedCorrelator.d.ts","sourceRoot":"","sources":["../../src/core/UsherActedCorrelator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAE3E,qFAAqF;AACrF,MAAM,WAAW,SAAS;IACxB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAGD,eAAO,MAAM,UAAU,IAAI,CAAC;AAC5B,eAAO,MAAM,YAAY,MAAM,CAAC;AAChC,oFAAoF;AACpF,eAAO,MAAM,aAAa,QAAqB,CAAC;AAChD,2EAA2E;AAC3E,eAAO,MAAM,cAAc,QAAsB,CAAC;AAgBlD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAStD;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,IAAI,CAAC,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GAClD,OAAO,CAST;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,WAAW,EAAE,EACtB,SAAS,EAAE,MAAM,EACjB,IAAI,CAAC,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GACrF,MAAM,EAAE,CAaV;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,gBAAgB,EACvB,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,KAAK,GAAG,MAAM,EACnB,IAAI,CAAC,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,GAC1H,MAAM,EAAE,CAyBV;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,gBAAgB,GAAG,IAAI,GAAG,SAAS,EAC1C,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,IAAI,CAAC,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAC/C,MAAM,EAAE,CAMV;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,gBAAgB,GAAG,IAAI,GAAG,SAAS,EAC1C,UAAU,EAAE,OAAO,EACnB,KAAK,EAAE,SAAS,EAChB,IAAI,CAAC,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAC/C,MAAM,EAAE,CASV"}
@@ -0,0 +1,175 @@
1
+ /**
2
+ * UsherActedCorrelator — the precision-numerator wiring for the Usher (rung 4 of
3
+ * continuous-working-awareness).
4
+ *
5
+ * The Usher FIRES re-surface signals (UsherSignalStore.recordSignal). Until this
6
+ * module, nothing ever called markActed — so `precision = acted/fired` was pinned
7
+ * at 0 and the rung-5 (mid-task injection) gate could never be satisfied by data.
8
+ * This closes that loop via TWO correlation paths, both reducing to "does some
9
+ * probe text COVER a fired signal's contextText?":
10
+ *
11
+ * (a) auto-use — the agent's next reply on the topic uses the re-surfaced
12
+ * context. creditUsherOnOutbound(). The nudge was useful: the
13
+ * agent acted on it.
14
+ * (b) miss-map — the user later had to CORRECT the agent (a HumanAsDetector
15
+ * signal) on that same context. creditUsherOnMiss(). The nudge
16
+ * was a genuine catch the agent ignored — still a true positive
17
+ * for the Usher's precision.
18
+ *
19
+ * Matching is deliberately PRECISION-OVER-RECALL: precision gates whether the
20
+ * Usher earns the right to interrupt (rung 5), so a falsely-HIGH precision is the
21
+ * dangerous direction. We require a meaningful salient-term overlap (≥ MIN_SHARED
22
+ * shared content words AND ≥ MIN_COVERAGE of the context's terms) and only credit
23
+ * recently-fired signals. Under-crediting a fast reply is acceptable; inflating
24
+ * the gate is not.
25
+ *
26
+ * All exported integration fns are best-effort and NEVER throw into the
27
+ * message/delivery path — mirroring the Usher's own degrade-safety invariants.
28
+ */
29
+ // ── Matching knobs (precision-over-recall) ───────────────────────────────────
30
+ export const MIN_SHARED = 2; // a single coincidental word can't credit a signal
31
+ export const MIN_COVERAGE = 0.5; // must cover at least half the context's salient terms
32
+ /** Window in which the agent's reply still counts as "using" a just-fired nudge. */
33
+ export const USE_WINDOW_MS = 6 * 60 * 60 * 1000; // 6h
34
+ /** Window in which a later user correction still credits a prior nudge. */
35
+ export const MISS_WINDOW_MS = 24 * 60 * 60 * 1000; // 24h
36
+ // English stopwords + a few instar-conversation fillers. Kept small and stable:
37
+ // the goal is to drop function words so overlap reflects real subject matter.
38
+ const STOPWORDS = new Set([
39
+ 'the', 'and', 'for', 'are', 'but', 'not', 'you', 'your', 'with', 'that', 'this',
40
+ 'have', 'has', 'had', 'was', 'were', 'will', 'would', 'should', 'could', 'can',
41
+ 'its', 'it', 'is', 'be', 'been', 'being', 'from', 'into', 'over', 'under', 'about',
42
+ 'they', 'them', 'their', 'there', 'here', 'what', 'when', 'where', 'which', 'who',
43
+ 'how', 'why', 'all', 'any', 'some', 'one', 'two', 'now', 'then', 'than', 'just',
44
+ 'out', 'off', 'too', 'very', 'our', 'his', 'her', 'him', 'she', 'his', 'we',
45
+ 'get', 'got', 'let', 'see', 'use', 'used', 'make', 'made', 'want', 'need',
46
+ 'yes', 'yeah', 'okay', 'sure', 'thing', 'things', 'stuff', 'like', 'also',
47
+ 'going', 'gonna', 'really', 'much', 'more', 'most', 'still', 'back', 'done',
48
+ ]);
49
+ /**
50
+ * Salient content tokens of a text: lowercased, alphanumeric-split, ≥3 chars,
51
+ * stopwords removed. Pure + deterministic.
52
+ */
53
+ export function salientTerms(text) {
54
+ const out = new Set();
55
+ if (!text || typeof text !== 'string')
56
+ return out;
57
+ for (const raw of text.toLowerCase().split(/[^a-z0-9]+/)) {
58
+ if (raw.length < 3)
59
+ continue;
60
+ if (STOPWORDS.has(raw))
61
+ continue;
62
+ out.add(raw);
63
+ }
64
+ return out;
65
+ }
66
+ /**
67
+ * Does `probeText` cover enough of `contextText`'s salient terms to count as a
68
+ * genuine reference to it? Requires ≥ minShared shared terms AND ≥ minCoverage
69
+ * fraction of the context's terms (so single-coincidental-word matches fail, and
70
+ * a long context needs proportionally more overlap).
71
+ */
72
+ export function contextCoveredBy(contextText, probeText, opts) {
73
+ const ctx = salientTerms(contextText);
74
+ if (ctx.size === 0)
75
+ return false;
76
+ const probe = salientTerms(probeText);
77
+ if (probe.size === 0)
78
+ return false;
79
+ let shared = 0;
80
+ for (const t of ctx)
81
+ if (probe.has(t))
82
+ shared++;
83
+ const coverage = shared / ctx.size;
84
+ return shared >= (opts?.minShared ?? MIN_SHARED) && coverage >= (opts?.minCoverage ?? MIN_COVERAGE);
85
+ }
86
+ /**
87
+ * Pure: among NOT-yet-acted signals, which ones does `probeText` cover? Returns
88
+ * their ids. `maxAgeMs`+`nowMs` optionally drop signals older than the window.
89
+ */
90
+ export function findCoveredSignalIds(signals, probeText, opts) {
91
+ const ids = [];
92
+ const maxAgeMs = opts?.maxAgeMs;
93
+ const nowMs = opts?.nowMs ?? Date.now();
94
+ for (const s of signals) {
95
+ if (!s || s.acted)
96
+ continue;
97
+ if (maxAgeMs !== undefined) {
98
+ const firedMs = Date.parse(s.at);
99
+ if (!Number.isNaN(firedMs) && nowMs - firedMs > maxAgeMs)
100
+ continue;
101
+ }
102
+ if (contextCoveredBy(s.contextText, probeText, opts))
103
+ ids.push(s.id);
104
+ }
105
+ return ids;
106
+ }
107
+ /**
108
+ * Core integration: correlate `probeText` against a topic's fired signals and
109
+ * mark the matches acted (stamped with `via`). Best-effort, never throws.
110
+ * Returns the ids actually marked.
111
+ */
112
+ export function markActedByCoverage(store, topicId, probeText, via, opts) {
113
+ try {
114
+ if (!probeText || typeof probeText !== 'string')
115
+ return [];
116
+ if (typeof topicId !== 'number' || !Number.isFinite(topicId))
117
+ return [];
118
+ const nowMs = (opts?.now ?? (() => Date.now()))();
119
+ const signals = store.getSignals(topicId, 50);
120
+ const ids = findCoveredSignalIds(signals, probeText, {
121
+ minShared: opts?.minShared,
122
+ minCoverage: opts?.minCoverage,
123
+ maxAgeMs: opts?.maxAgeMs,
124
+ nowMs,
125
+ });
126
+ if (ids.length === 0)
127
+ return [];
128
+ const at = new Date(nowMs).toISOString();
129
+ const marked = [];
130
+ for (const id of ids) {
131
+ if (store.markActed(topicId, id, { via, at })) {
132
+ marked.push(id);
133
+ try {
134
+ opts?.onMarked?.(id);
135
+ }
136
+ catch { /* observability hook must not break */ }
137
+ }
138
+ }
139
+ return marked;
140
+ }
141
+ catch {
142
+ return [];
143
+ }
144
+ }
145
+ /**
146
+ * Path (a): the agent's reply just went out on `topicId`. Credit any recent
147
+ * faded-context nudge the reply actually used. Wire on the outbound-reply path.
148
+ */
149
+ export function creditUsherOnOutbound(store, topicId, replyText, opts) {
150
+ if (!store)
151
+ return [];
152
+ return markActedByCoverage(store, topicId, replyText, 'use', {
153
+ now: opts?.now,
154
+ maxAgeMs: opts?.maxAgeMs ?? USE_WINDOW_MS,
155
+ });
156
+ }
157
+ /**
158
+ * Path (b): the user just had to correct the agent (`missSignal` non-null). If a
159
+ * recent nudge on this topic covers what they're correcting, that nudge was a
160
+ * real catch the agent ignored → credit it. Wire on the inbound human-detector
161
+ * seam. No-op when there was no miss, no topic, or no text.
162
+ */
163
+ export function creditUsherOnMiss(store, missSignal, entry, opts) {
164
+ if (!store || !missSignal)
165
+ return [];
166
+ const topicId = entry?.topicId;
167
+ const text = entry?.text;
168
+ if (typeof topicId !== 'number' || !text)
169
+ return [];
170
+ return markActedByCoverage(store, topicId, text, 'miss', {
171
+ now: opts?.now,
172
+ maxAgeMs: opts?.maxAgeMs ?? MISS_WINDOW_MS,
173
+ });
174
+ }
175
+ //# sourceMappingURL=UsherActedCorrelator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UsherActedCorrelator.js","sourceRoot":"","sources":["../../src/core/UsherActedCorrelator.ts"],"names":[],"mappings":"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"}