instar 1.3.61 → 1.3.63

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 (73) hide show
  1. package/dist/commands/server.d.ts.map +1 -1
  2. package/dist/commands/server.js +176 -10
  3. package/dist/commands/server.js.map +1 -1
  4. package/dist/config/ConfigDefaults.d.ts.map +1 -1
  5. package/dist/config/ConfigDefaults.js +36 -0
  6. package/dist/config/ConfigDefaults.js.map +1 -1
  7. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  8. package/dist/core/PostUpdateMigrator.js +45 -0
  9. package/dist/core/PostUpdateMigrator.js.map +1 -1
  10. package/dist/core/ReapGuard.d.ts +79 -0
  11. package/dist/core/ReapGuard.d.ts.map +1 -0
  12. package/dist/core/ReapGuard.js +114 -0
  13. package/dist/core/ReapGuard.js.map +1 -0
  14. package/dist/core/SessionLivenessOracle.d.ts +104 -0
  15. package/dist/core/SessionLivenessOracle.d.ts.map +1 -0
  16. package/dist/core/SessionLivenessOracle.js +201 -0
  17. package/dist/core/SessionLivenessOracle.js.map +1 -0
  18. package/dist/core/SessionManager.d.ts +90 -10
  19. package/dist/core/SessionManager.d.ts.map +1 -1
  20. package/dist/core/SessionManager.js +200 -40
  21. package/dist/core/SessionManager.js.map +1 -1
  22. package/dist/core/SessionRefresh.d.ts +10 -0
  23. package/dist/core/SessionRefresh.d.ts.map +1 -1
  24. package/dist/core/SessionRefresh.js +12 -2
  25. package/dist/core/SessionRefresh.js.map +1 -1
  26. package/dist/core/types.d.ts +70 -0
  27. package/dist/core/types.d.ts.map +1 -1
  28. package/dist/core/types.js.map +1 -1
  29. package/dist/monitoring/ContextWedgeSentinel.d.ts +118 -0
  30. package/dist/monitoring/ContextWedgeSentinel.d.ts.map +1 -0
  31. package/dist/monitoring/ContextWedgeSentinel.js +238 -0
  32. package/dist/monitoring/ContextWedgeSentinel.js.map +1 -0
  33. package/dist/monitoring/ReapLog.d.ts +51 -0
  34. package/dist/monitoring/ReapLog.d.ts.map +1 -0
  35. package/dist/monitoring/ReapLog.js +82 -0
  36. package/dist/monitoring/ReapLog.js.map +1 -0
  37. package/dist/monitoring/ReapNotifier.d.ts +66 -0
  38. package/dist/monitoring/ReapNotifier.d.ts.map +1 -0
  39. package/dist/monitoring/ReapNotifier.js +112 -0
  40. package/dist/monitoring/ReapNotifier.js.map +1 -0
  41. package/dist/monitoring/SentinelNotifier.d.ts +1 -1
  42. package/dist/monitoring/SentinelNotifier.d.ts.map +1 -1
  43. package/dist/monitoring/SentinelNotifier.js.map +1 -1
  44. package/dist/monitoring/SessionReaper.d.ts +4 -0
  45. package/dist/monitoring/SessionReaper.d.ts.map +1 -1
  46. package/dist/monitoring/SessionReaper.js +19 -42
  47. package/dist/monitoring/SessionReaper.js.map +1 -1
  48. package/dist/monitoring/StaleSessionBackstop.d.ts +89 -0
  49. package/dist/monitoring/StaleSessionBackstop.d.ts.map +1 -0
  50. package/dist/monitoring/StaleSessionBackstop.js +176 -0
  51. package/dist/monitoring/StaleSessionBackstop.js.map +1 -0
  52. package/dist/monitoring/sentinelWiring.d.ts +23 -0
  53. package/dist/monitoring/sentinelWiring.d.ts.map +1 -1
  54. package/dist/monitoring/sentinelWiring.js +36 -0
  55. package/dist/monitoring/sentinelWiring.js.map +1 -1
  56. package/dist/scaffold/templates.d.ts.map +1 -1
  57. package/dist/scaffold/templates.js +5 -0
  58. package/dist/scaffold/templates.js.map +1 -1
  59. package/dist/server/AgentServer.d.ts +3 -0
  60. package/dist/server/AgentServer.d.ts.map +1 -1
  61. package/dist/server/AgentServer.js +1 -0
  62. package/dist/server/AgentServer.js.map +1 -1
  63. package/dist/server/routes.d.ts +1 -0
  64. package/dist/server/routes.d.ts.map +1 -1
  65. package/dist/server/routes.js +33 -11
  66. package/dist/server/routes.js.map +1 -1
  67. package/package.json +1 -1
  68. package/src/data/builtin-manifest.json +64 -64
  69. package/src/scaffold/templates.ts +5 -0
  70. package/upgrades/1.3.62.md +87 -0
  71. package/upgrades/1.3.63.md +87 -0
  72. package/upgrades/side-effects/context-wedge-sentinel.md +69 -0
  73. package/upgrades/side-effects/unified-session-lifecycle-robustness.md +241 -0
@@ -0,0 +1,238 @@
1
+ /**
2
+ * ContextWedgeSentinel — detects Claude Code's "thinking/redacted_thinking
3
+ * blocks in the latest assistant message cannot be modified" 400 wedge and
4
+ * recovers via a FRESH respawn.
5
+ *
6
+ * The failure (diagnosed 2026-05-28): a tool call is cancelled inside a
7
+ * parallel tool batch while extended thinking is on. Claude Code cancels every
8
+ * sibling call, and that cancellation corrupts the thinking block on the latest
9
+ * assistant turn. The Anthropic API then rejects EVERY resume of that session
10
+ * with `400 ... thinking blocks in the latest assistant message cannot be
11
+ * modified`. The session fast-fails instantly on every inbound message ("Cooked
12
+ * for 0s"); it is permanently dead yet still emitting output, so neither the
13
+ * silence sentinel (output never goes quiet) nor the socket sentinel (no
14
+ * disconnect string) catches it.
15
+ *
16
+ * Critical difference from the other silently-stopped sentinels: a send-keys
17
+ * nudge CANNOT recover this — re-engaging just re-sends the corrupted turn and
18
+ * hits the same 400. The only recovery is a clean respawn that does NOT
19
+ * `--resume` the corrupted transcript. That is the injected `recoverFn`
20
+ * (wired to SessionRefresh fresh-mode, which clears the topic's resume UUID
21
+ * before respawning).
22
+ *
23
+ * Because recovery is destructive (kills + respawns a session), detection is
24
+ * gated by a CONFIRM WINDOW: the signature must still be the non-progressing
25
+ * session tail after `confirmWindowMs` before a wedge is confirmed. This is the
26
+ * defense against false-positives — most importantly a session merely
27
+ * *discussing* this very error (its tail keeps changing as it works, so the
28
+ * signature does not persist as the tail). And the destructive recovery itself
29
+ * is opt-in (autoRecovery, default off) and rides the Graduated Feature Rollout
30
+ * track; in the default detect-only mode the sentinel only audits + escalates.
31
+ *
32
+ * Spec: docs/specs/context-wedge-sentinel.md (companion to silently-stopped-trio.md)
33
+ *
34
+ * Signal-vs-authority: the regex + confirm-window are detectors. The recoverFn
35
+ * is a bounded recovery primitive (SessionRefresh rate-guards respawns). User-
36
+ * facing escalation routes through the existing MessagingToneGate via the
37
+ * SentinelNotifier path. No new blocking authority.
38
+ */
39
+ import { EventEmitter } from 'node:events';
40
+ const DEFAULT_CONFIG = {
41
+ enabled: true,
42
+ tickIntervalMs: 20_000,
43
+ confirmWindowMs: 45_000,
44
+ };
45
+ /**
46
+ * Patterns for the thinking-block 400. Intentionally anchored on the unusual,
47
+ * API-specific phrase — natural prose almost never contains it, and the
48
+ * confirm-window + opt-in recovery cover the one case that does (a session
49
+ * discussing this very bug).
50
+ */
51
+ export const CONTEXT_WEDGE_PATTERNS = [
52
+ /blocks in the latest assistant message cannot be modified/i,
53
+ /`?(?:thinking|redacted_thinking)`?\s+blocks.{0,80}cannot be modified/i,
54
+ ];
55
+ /** Detector — true if `text` contains the thinking-block-400 signature. */
56
+ export function detectContextWedge(text) {
57
+ if (!text)
58
+ return false;
59
+ for (const pat of CONTEXT_WEDGE_PATTERNS) {
60
+ if (pat.test(text))
61
+ return true;
62
+ }
63
+ return false;
64
+ }
65
+ /**
66
+ * Tail detector — is the signature present within the last `tailLines`
67
+ * non-empty lines of the capture? A genuinely wedged session shows the error as
68
+ * its live tail; a session that merely mentioned the error earlier and then
69
+ * kept working has scrolled it out of the tail.
70
+ */
71
+ export function signatureIsTail(text, tailLines = 10) {
72
+ if (!text)
73
+ return false;
74
+ const lines = text.split('\n').map(l => l.trim()).filter(Boolean);
75
+ const tail = lines.slice(-tailLines).join('\n');
76
+ return detectContextWedge(tail);
77
+ }
78
+ export class ContextWedgeSentinel extends EventEmitter {
79
+ deps;
80
+ cfg;
81
+ states = new Map();
82
+ timers = new Map();
83
+ tickHandle = null;
84
+ constructor(deps, cfg = {}) {
85
+ super();
86
+ this.deps = deps;
87
+ this.cfg = { ...DEFAULT_CONFIG, ...cfg };
88
+ }
89
+ /** Begin the self-driving scan loop. No-op without listSessionNames. */
90
+ start() {
91
+ if (!this.cfg.enabled || this.tickHandle)
92
+ return;
93
+ if (!this.deps.listSessionNames)
94
+ return;
95
+ this.tickHandle = setInterval(() => this.tick(), this.cfg.tickIntervalMs);
96
+ if (typeof this.tickHandle.unref === 'function')
97
+ this.tickHandle.unref();
98
+ }
99
+ stop() {
100
+ if (this.tickHandle) {
101
+ clearInterval(this.tickHandle);
102
+ this.tickHandle = null;
103
+ }
104
+ this.shutdown();
105
+ }
106
+ /** One scan pass over every tracked session. */
107
+ tick() {
108
+ if (!this.cfg.enabled)
109
+ return;
110
+ const names = this.deps.listSessionNames?.() ?? [];
111
+ for (const name of names)
112
+ this.scanSession(name);
113
+ }
114
+ /** Scan one session for the wedge signature. */
115
+ scanSession(sessionName) {
116
+ if (!this.cfg.enabled)
117
+ return;
118
+ if (this.states.has(sessionName))
119
+ return; // already handling
120
+ const output = this.deps.getRecentOutput(sessionName);
121
+ // Require the signature to be the live TAIL, not merely present somewhere in
122
+ // the scrollback — the discriminator against a session discussing the bug.
123
+ if (!signatureIsTail(output))
124
+ return;
125
+ this.report(sessionName);
126
+ }
127
+ /** Public entry: report a detected (not yet confirmed) wedge. Idempotent. */
128
+ report(sessionName) {
129
+ if (!this.cfg.enabled)
130
+ return;
131
+ if (this.states.has(sessionName))
132
+ return;
133
+ const now = (this.deps.now ?? Date.now)();
134
+ const state = {
135
+ sessionName,
136
+ detectedAt: now,
137
+ status: 'detected',
138
+ };
139
+ this.states.set(sessionName, state);
140
+ this.emit('detected', { sessionName });
141
+ // Enter the confirm window before any destructive action.
142
+ state.status = 'confirming';
143
+ const handle = this.setTimer(() => void this.confirm(sessionName), this.cfg.confirmWindowMs);
144
+ this.timers.set(sessionName, handle);
145
+ }
146
+ isRecoveryActive(sessionName) {
147
+ const s = this.states.get(sessionName);
148
+ return !!s && s.status !== 'recovered' && s.status !== 'escalated';
149
+ }
150
+ listActive() {
151
+ return Array.from(this.states.values());
152
+ }
153
+ clear(sessionName) {
154
+ const t = this.timers.get(sessionName);
155
+ if (t)
156
+ this.clearTimer(t);
157
+ this.timers.delete(sessionName);
158
+ this.states.delete(sessionName);
159
+ }
160
+ shutdown() {
161
+ for (const t of this.timers.values())
162
+ this.clearTimer(t);
163
+ this.timers.clear();
164
+ this.states.clear();
165
+ }
166
+ // ── Confirm + recover ────────────────────────────────────────────────
167
+ async confirm(sessionName) {
168
+ const state = this.states.get(sessionName);
169
+ if (!state)
170
+ return;
171
+ const output = this.deps.getRecentOutput(sessionName);
172
+ // Confirmation gate: the signature must STILL be the tail. If it scrolled
173
+ // out (the session progressed past the error), this was not a wedge — a
174
+ // false alarm (e.g. a session that quoted the error then kept working).
175
+ if (!signatureIsTail(output)) {
176
+ this.emit('false-alarm', { sessionName });
177
+ this.clear(sessionName);
178
+ return;
179
+ }
180
+ // Confirmed wedge → hand to the recovery policy.
181
+ let outcome = 'failed';
182
+ try {
183
+ outcome = await this.deps.recoverFn(sessionName);
184
+ }
185
+ catch (err) {
186
+ this.emit('recovery-error', { sessionName, err });
187
+ outcome = 'failed';
188
+ }
189
+ switch (outcome) {
190
+ case 'respawned':
191
+ state.status = 'recovered';
192
+ this.emit('recovered', { sessionName });
193
+ this.clear(sessionName);
194
+ return;
195
+ case 'dry-run':
196
+ // Housekeeping only — would-have-respawned. Keep state so we don't
197
+ // re-confirm the same wedge every tick; the dry-run log is the signal.
198
+ state.status = 'escalated';
199
+ this.emit('dry-run', { sessionName });
200
+ return;
201
+ case 'detect-only':
202
+ case 'failed':
203
+ default:
204
+ this.escalate(sessionName, outcome);
205
+ return;
206
+ }
207
+ }
208
+ escalate(sessionName, outcome) {
209
+ const state = this.states.get(sessionName);
210
+ if (!state)
211
+ return;
212
+ state.status = 'escalated';
213
+ const detail = outcome === 'failed'
214
+ ? `${friendlyName(sessionName)} is wedged on a stuck-context error and my respawn attempt did not clear it. Want me to dig in?`
215
+ : `${friendlyName(sessionName)} is wedged on a stuck-context error (it can only be fixed by a fresh restart, which is currently off). Want me to restart it?`;
216
+ void this.notify(sessionName, detail);
217
+ this.emit('escalated', { sessionName, outcome });
218
+ // Keep state so a subsequent scan doesn't re-report the same wedge.
219
+ }
220
+ async notify(sessionName, text) {
221
+ try {
222
+ await this.deps.notifyFn(sessionName, text);
223
+ }
224
+ catch (err) {
225
+ this.emit('notify-error', { sessionName, err });
226
+ }
227
+ }
228
+ setTimer(fn, ms) {
229
+ return (this.deps.setTimer ?? setTimeout)(fn, ms);
230
+ }
231
+ clearTimer(handle) {
232
+ (this.deps.clearTimer ?? clearTimeout)(handle);
233
+ }
234
+ }
235
+ function friendlyName(sessionName) {
236
+ return sessionName.replace(/^ai\.instar\./, '').replace(/-server$/, '').replace(/-lifeline$/, '');
237
+ }
238
+ //# sourceMappingURL=ContextWedgeSentinel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ContextWedgeSentinel.js","sourceRoot":"","sources":["../../src/monitoring/ContextWedgeSentinel.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * ReapLog — durable, JSON-encoded audit of every session reap and every
3
+ * skipped-reap (UNIFIED-SESSION-LIFECYCLE §P4).
4
+ *
5
+ * The pull-surface answer to "why did my session vanish?": one line per reap
6
+ * (`sessionReaped`) and one per refused/skipped terminate (`reapBlocked`), so a
7
+ * dropped kill (not-lease-holder / protected / a KEEP / in-flight) is never
8
+ * invisible. Mirrors `sentinel-events.jsonl`: append-only JSONL under
9
+ * `logs/`, written with JSON.stringify (never raw concat — closes
10
+ * newline-injection of user-controlled session names / reasons).
11
+ *
12
+ * Read-only from the API (`GET /sessions/reap-log`, Bearer-auth). The log never
13
+ * gates or mutates a session — it only records.
14
+ */
15
+ export interface ReapLogEntry {
16
+ ts: string;
17
+ /** 'reaped' = a kill happened; 'skipped' = a terminate was refused/no-op. */
18
+ type: 'reaped' | 'skipped';
19
+ session: string;
20
+ tmuxSession: string;
21
+ /** The reason the killer requested (e.g. 'idle-zombie', 'age-limit'). */
22
+ reason: string;
23
+ disposition?: 'terminal' | 'recovery-bounce';
24
+ origin?: 'operator' | 'autonomous';
25
+ /** For 'skipped': why the authority refused (protected / not-lease-holder / a KEEP / in-flight). */
26
+ skipped?: string;
27
+ machine?: string;
28
+ }
29
+ export declare class ReapLog {
30
+ private readonly logPath;
31
+ private readonly machineId?;
32
+ constructor(stateDir: string, machineId?: () => string | undefined);
33
+ recordReaped(e: {
34
+ session: string;
35
+ tmuxSession: string;
36
+ reason: string;
37
+ disposition?: 'terminal' | 'recovery-bounce';
38
+ origin?: 'operator' | 'autonomous';
39
+ }): void;
40
+ recordSkipped(e: {
41
+ session: string;
42
+ tmuxSession: string;
43
+ reason: string;
44
+ skipped: string;
45
+ origin?: 'operator' | 'autonomous';
46
+ }): void;
47
+ private append;
48
+ /** Read the most-recent `limit` entries (newest last), tolerating partial/corrupt lines. */
49
+ read(limit?: number): ReapLogEntry[];
50
+ }
51
+ //# sourceMappingURL=ReapLog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReapLog.d.ts","sourceRoot":"","sources":["../../src/monitoring/ReapLog.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAKH,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,6EAA6E;IAC7E,IAAI,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,yEAAyE;IACzE,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,UAAU,GAAG,iBAAiB,CAAC;IAC7C,MAAM,CAAC,EAAE,UAAU,GAAG,YAAY,CAAC;IACnC,oGAAoG;IACpG,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,OAAO;IAClB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,CAA2B;gBAE1C,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,MAAM,GAAG,SAAS;IAKlE,YAAY,CAAC,CAAC,EAAE;QACd,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,EAAE,MAAM,CAAC;QACpB,MAAM,EAAE,MAAM,CAAC;QACf,WAAW,CAAC,EAAE,UAAU,GAAG,iBAAiB,CAAC;QAC7C,MAAM,CAAC,EAAE,UAAU,GAAG,YAAY,CAAC;KACpC,GAAG,IAAI;IAaR,aAAa,CAAC,CAAC,EAAE;QACf,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,EAAE,MAAM,CAAC;QACpB,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,UAAU,GAAG,YAAY,CAAC;KACpC,GAAG,IAAI;IAaR,OAAO,CAAC,MAAM;IASd,4FAA4F;IAC5F,IAAI,CAAC,KAAK,SAAM,GAAG,YAAY,EAAE;CAqBlC"}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * ReapLog — durable, JSON-encoded audit of every session reap and every
3
+ * skipped-reap (UNIFIED-SESSION-LIFECYCLE §P4).
4
+ *
5
+ * The pull-surface answer to "why did my session vanish?": one line per reap
6
+ * (`sessionReaped`) and one per refused/skipped terminate (`reapBlocked`), so a
7
+ * dropped kill (not-lease-holder / protected / a KEEP / in-flight) is never
8
+ * invisible. Mirrors `sentinel-events.jsonl`: append-only JSONL under
9
+ * `logs/`, written with JSON.stringify (never raw concat — closes
10
+ * newline-injection of user-controlled session names / reasons).
11
+ *
12
+ * Read-only from the API (`GET /sessions/reap-log`, Bearer-auth). The log never
13
+ * gates or mutates a session — it only records.
14
+ */
15
+ import fs from 'node:fs';
16
+ import path from 'node:path';
17
+ export class ReapLog {
18
+ logPath;
19
+ machineId;
20
+ constructor(stateDir, machineId) {
21
+ this.logPath = path.join(stateDir, '..', 'logs', 'reap-log.jsonl');
22
+ this.machineId = machineId;
23
+ }
24
+ recordReaped(e) {
25
+ this.append({
26
+ ts: new Date().toISOString(),
27
+ type: 'reaped',
28
+ session: e.session,
29
+ tmuxSession: e.tmuxSession,
30
+ reason: e.reason,
31
+ disposition: e.disposition,
32
+ origin: e.origin,
33
+ machine: this.machineId?.(),
34
+ });
35
+ }
36
+ recordSkipped(e) {
37
+ this.append({
38
+ ts: new Date().toISOString(),
39
+ type: 'skipped',
40
+ session: e.session,
41
+ tmuxSession: e.tmuxSession,
42
+ reason: e.reason,
43
+ skipped: e.skipped,
44
+ origin: e.origin,
45
+ machine: this.machineId?.(),
46
+ });
47
+ }
48
+ append(entry) {
49
+ try {
50
+ fs.mkdirSync(path.dirname(this.logPath), { recursive: true });
51
+ fs.appendFileSync(this.logPath, JSON.stringify(entry) + '\n');
52
+ }
53
+ catch {
54
+ // never throw from the audit sink
55
+ }
56
+ }
57
+ /** Read the most-recent `limit` entries (newest last), tolerating partial/corrupt lines. */
58
+ read(limit = 200) {
59
+ let raw;
60
+ try {
61
+ raw = fs.readFileSync(this.logPath, 'utf-8');
62
+ }
63
+ catch {
64
+ // @silent-fallback-ok — no log file yet means no reaps recorded; an empty
65
+ // list is the correct answer, not a degraded one.
66
+ return [];
67
+ }
68
+ const lines = raw.split('\n').filter((l) => l.trim().length > 0);
69
+ const tail = limit > 0 ? lines.slice(-limit) : lines;
70
+ const out = [];
71
+ for (const line of tail) {
72
+ try {
73
+ out.push(JSON.parse(line));
74
+ }
75
+ catch {
76
+ // skip a corrupt/partial line rather than failing the whole read
77
+ }
78
+ }
79
+ return out;
80
+ }
81
+ }
82
+ //# sourceMappingURL=ReapLog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReapLog.js","sourceRoot":"","sources":["../../src/monitoring/ReapLog.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAiB7B,MAAM,OAAO,OAAO;IACD,OAAO,CAAS;IAChB,SAAS,CAA4B;IAEtD,YAAY,QAAgB,EAAE,SAAoC;QAChE,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,gBAAgB,CAAC,CAAC;QACnE,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC7B,CAAC;IAED,YAAY,CAAC,CAMZ;QACC,IAAI,CAAC,MAAM,CAAC;YACV,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YAC5B,IAAI,EAAE,QAAQ;YACd,OAAO,EAAE,CAAC,CAAC,OAAO;YAClB,WAAW,EAAE,CAAC,CAAC,WAAW;YAC1B,MAAM,EAAE,CAAC,CAAC,MAAM;YAChB,WAAW,EAAE,CAAC,CAAC,WAAW;YAC1B,MAAM,EAAE,CAAC,CAAC,MAAM;YAChB,OAAO,EAAE,IAAI,CAAC,SAAS,EAAE,EAAE;SAC5B,CAAC,CAAC;IACL,CAAC;IAED,aAAa,CAAC,CAMb;QACC,IAAI,CAAC,MAAM,CAAC;YACV,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YAC5B,IAAI,EAAE,SAAS;YACf,OAAO,EAAE,CAAC,CAAC,OAAO;YAClB,WAAW,EAAE,CAAC,CAAC,WAAW;YAC1B,MAAM,EAAE,CAAC,CAAC,MAAM;YAChB,OAAO,EAAE,CAAC,CAAC,OAAO;YAClB,MAAM,EAAE,CAAC,CAAC,MAAM;YAChB,OAAO,EAAE,IAAI,CAAC,SAAS,EAAE,EAAE;SAC5B,CAAC,CAAC;IACL,CAAC;IAEO,MAAM,CAAC,KAAmB;QAChC,IAAI,CAAC;YACH,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YAC9D,EAAE,CAAC,cAAc,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;QAChE,CAAC;QAAC,MAAM,CAAC;YACP,kCAAkC;QACpC,CAAC;IACH,CAAC;IAED,4FAA4F;IAC5F,IAAI,CAAC,KAAK,GAAG,GAAG;QACd,IAAI,GAAW,CAAC;QAChB,IAAI,CAAC;YACH,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC/C,CAAC;QAAC,MAAM,CAAC;YACP,0EAA0E;YAC1E,kDAAkD;YAClD,OAAO,EAAE,CAAC;QACZ,CAAC;QACD,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QACjE,MAAM,IAAI,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;QACrD,MAAM,GAAG,GAAmB,EAAE,CAAC;QAC/B,KAAK,MAAM,IAAI,IAAI,IAAI,EAAE,CAAC;YACxB,IAAI,CAAC;gBACH,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAiB,CAAC,CAAC;YAC7C,CAAC;YAAC,MAAM,CAAC;gBACP,iEAAiE;YACnE,CAAC;QACH,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;CACF"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * ReapNotifier — the single coalescing listener for `sessionReaped`
3
+ * (UNIFIED-SESSION-LIFECYCLE §P3).
4
+ *
5
+ * `SessionManager.terminateSession` emits `sessionReaped` exactly once per kill
6
+ * at the one chokepoint. This listener turns a TERMINAL reap of a user-facing
7
+ * session into a "your session was shut down" notice so a session never silently
8
+ * vanishes (the 2026-05-27 incident). It stays SILENT for:
9
+ * - `recovery-bounce` reaps (a kill-to-respawn is a bounce, not a disappearance),
10
+ * - `origin:'operator'` reaps (the user clicked kill — telling them is noise).
11
+ *
12
+ * Coalescing (SE-7): terminal reaps within a short rolling window collapse onto a
13
+ * single shared timer. On flush:
14
+ * - exactly ONE reap in the window → a per-session notice routed to its bound
15
+ * topic (or the lifeline topic if unbound),
16
+ * - MORE than one → ONE consolidated lifeline message stating the TOTAL count,
17
+ * so a mass-reap burst that overflows the bounded buffer is never
18
+ * under-reported (the reap-log P4 has the complete record regardless).
19
+ *
20
+ * Sanitization: session names follow user-controlled topic renames, so the
21
+ * dynamic fields (name, reason) are wrapped as inline-code spans — the downstream
22
+ * Telegram formatter renders code spans as literal, HTML-escaped text, never
23
+ * markup. The notifier never emits raw markup around user-controlled values.
24
+ */
25
+ import type { Session } from '../core/types.js';
26
+ export interface ReapEvent {
27
+ session: Pick<Session, 'name' | 'tmuxSession'>;
28
+ reason: string;
29
+ disposition?: 'terminal' | 'recovery-bounce';
30
+ origin?: 'operator' | 'autonomous';
31
+ }
32
+ export interface ReapNotifierDeps {
33
+ /** Bound messaging topic for a session, or null if unbound. */
34
+ resolveTopic: (tmuxSession: string) => number | null;
35
+ /** The always-on system/lifeline topic, or null if none is configured. */
36
+ lifelineTopic: () => number | null;
37
+ /** Deliver a (GFM) notice to a topic. Wiring decides the transport/tier. */
38
+ send: (topicId: number, text: string) => void | Promise<void>;
39
+ now?: () => number;
40
+ }
41
+ export interface ReapNotifierOptions {
42
+ enabled: boolean;
43
+ coalesceWindowMs: number;
44
+ /** Max reaps retained for the consolidated-message detail list (count is exact regardless). */
45
+ maxBuffer: number;
46
+ }
47
+ export declare const DEFAULT_REAP_NOTIFIER_OPTIONS: ReapNotifierOptions;
48
+ export declare class ReapNotifier {
49
+ private readonly deps;
50
+ private readonly opts;
51
+ private readonly now;
52
+ private timer;
53
+ private buffer;
54
+ private windowCount;
55
+ constructor(deps: ReapNotifierDeps, opts?: Partial<ReapNotifierOptions>);
56
+ /** The `sessionReaped` event handler. */
57
+ onReaped(event: ReapEvent): void;
58
+ /**
59
+ * Emit the coalesced notice(s) for the closed window and reset. Public so the
60
+ * lifecycle (and tests) can drive it deterministically.
61
+ */
62
+ flush(): Promise<void>;
63
+ private formatSingle;
64
+ private formatBurst;
65
+ }
66
+ //# sourceMappingURL=ReapNotifier.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReapNotifier.d.ts","sourceRoot":"","sources":["../../src/monitoring/ReapNotifier.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAEhD,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,CAAC,CAAC;IAC/C,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,UAAU,GAAG,iBAAiB,CAAC;IAC7C,MAAM,CAAC,EAAE,UAAU,GAAG,YAAY,CAAC;CACpC;AAED,MAAM,WAAW,gBAAgB;IAC/B,+DAA+D;IAC/D,YAAY,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;IACrD,0EAA0E;IAC1E,aAAa,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;IACnC,4EAA4E;IAC5E,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9D,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,gBAAgB,EAAE,MAAM,CAAC;IACzB,+FAA+F;IAC/F,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,eAAO,MAAM,6BAA6B,EAAE,mBAI3C,CAAC;AAQF,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAmB;IACxC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAsB;IAC3C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,KAAK,CAA+B;IAC5C,OAAO,CAAC,MAAM,CAAmB;IACjC,OAAO,CAAC,WAAW,CAAK;gBAEZ,IAAI,EAAE,gBAAgB,EAAE,IAAI,CAAC,EAAE,OAAO,CAAC,mBAAmB,CAAC;IAMvE,yCAAyC;IACzC,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI;IAiBhC;;;OAGG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAsB5B,OAAO,CAAC,YAAY;IAKpB,OAAO,CAAC,WAAW;CAWpB"}
@@ -0,0 +1,112 @@
1
+ /**
2
+ * ReapNotifier — the single coalescing listener for `sessionReaped`
3
+ * (UNIFIED-SESSION-LIFECYCLE §P3).
4
+ *
5
+ * `SessionManager.terminateSession` emits `sessionReaped` exactly once per kill
6
+ * at the one chokepoint. This listener turns a TERMINAL reap of a user-facing
7
+ * session into a "your session was shut down" notice so a session never silently
8
+ * vanishes (the 2026-05-27 incident). It stays SILENT for:
9
+ * - `recovery-bounce` reaps (a kill-to-respawn is a bounce, not a disappearance),
10
+ * - `origin:'operator'` reaps (the user clicked kill — telling them is noise).
11
+ *
12
+ * Coalescing (SE-7): terminal reaps within a short rolling window collapse onto a
13
+ * single shared timer. On flush:
14
+ * - exactly ONE reap in the window → a per-session notice routed to its bound
15
+ * topic (or the lifeline topic if unbound),
16
+ * - MORE than one → ONE consolidated lifeline message stating the TOTAL count,
17
+ * so a mass-reap burst that overflows the bounded buffer is never
18
+ * under-reported (the reap-log P4 has the complete record regardless).
19
+ *
20
+ * Sanitization: session names follow user-controlled topic renames, so the
21
+ * dynamic fields (name, reason) are wrapped as inline-code spans — the downstream
22
+ * Telegram formatter renders code spans as literal, HTML-escaped text, never
23
+ * markup. The notifier never emits raw markup around user-controlled values.
24
+ */
25
+ export const DEFAULT_REAP_NOTIFIER_OPTIONS = {
26
+ enabled: true,
27
+ coalesceWindowMs: 60_000,
28
+ maxBuffer: 100,
29
+ };
30
+ /** Wrap a user-controlled value as a literal inline-code span (never markup). */
31
+ function literal(value) {
32
+ // Neutralize backticks so the code span can't be broken out of.
33
+ return '`' + String(value).replace(/`/g, "'") + '`';
34
+ }
35
+ export class ReapNotifier {
36
+ deps;
37
+ opts;
38
+ now;
39
+ timer = null;
40
+ buffer = [];
41
+ windowCount = 0;
42
+ constructor(deps, opts) {
43
+ this.deps = deps;
44
+ this.opts = { ...DEFAULT_REAP_NOTIFIER_OPTIONS, ...(opts ?? {}) };
45
+ this.now = deps.now ?? (() => Date.now());
46
+ }
47
+ /** The `sessionReaped` event handler. */
48
+ onReaped(event) {
49
+ if (!this.opts.enabled)
50
+ return;
51
+ // Silent dispositions/origins: a bounce is not a disappearance, and the user
52
+ // already knows about their own operator kill.
53
+ if ((event.disposition ?? 'terminal') !== 'terminal')
54
+ return;
55
+ if (event.origin === 'operator')
56
+ return;
57
+ this.windowCount++;
58
+ this.buffer.push(event);
59
+ if (this.buffer.length > this.opts.maxBuffer)
60
+ this.buffer.shift(); // drop-oldest detail
61
+ if (!this.timer) {
62
+ this.timer = setTimeout(() => { void this.flush(); }, this.opts.coalesceWindowMs);
63
+ if (typeof this.timer.unref === 'function')
64
+ this.timer.unref();
65
+ }
66
+ }
67
+ /**
68
+ * Emit the coalesced notice(s) for the closed window and reset. Public so the
69
+ * lifecycle (and tests) can drive it deterministically.
70
+ */
71
+ async flush() {
72
+ if (this.timer) {
73
+ clearTimeout(this.timer);
74
+ this.timer = null;
75
+ }
76
+ const count = this.windowCount;
77
+ const detail = this.buffer;
78
+ this.windowCount = 0;
79
+ this.buffer = [];
80
+ if (count === 0)
81
+ return;
82
+ if (count === 1) {
83
+ const ev = detail[0];
84
+ const topic = this.deps.resolveTopic(ev.session.tmuxSession) ?? this.deps.lifelineTopic();
85
+ if (topic == null)
86
+ return; // unreachable channel — reap-log (P4) still has it
87
+ await this.deps.send(topic, this.formatSingle(ev));
88
+ return;
89
+ }
90
+ // Burst → ONE consolidated lifeline message stating the exact total count.
91
+ const topic = this.deps.lifelineTopic();
92
+ if (topic == null)
93
+ return;
94
+ await this.deps.send(topic, this.formatBurst(count, detail));
95
+ }
96
+ formatSingle(ev) {
97
+ return `🪦 Session ${literal(ev.session.name)} was shut down — ${literal(ev.reason)}. `
98
+ + `See the reap-log (\`GET /sessions/reap-log\`) for the full record.`;
99
+ }
100
+ formatBurst(count, detail) {
101
+ const lines = detail.map((e) => `• ${literal(e.session.name)} — ${literal(e.reason)}`);
102
+ const shownNote = count > detail.length
103
+ ? `\n\n(showing the latest ${detail.length}; full list in the reap-log)`
104
+ : '';
105
+ return `🪦 ${count} session${count === 1 ? '' : 's'} shut down in the last `
106
+ + `${Math.round(this.opts.coalesceWindowMs / 1000)}s:\n\n`
107
+ + lines.join('\n')
108
+ + shownNote
109
+ + `\n\nFull record: \`GET /sessions/reap-log\`.`;
110
+ }
111
+ }
112
+ //# sourceMappingURL=ReapNotifier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReapNotifier.js","sourceRoot":"","sources":["../../src/monitoring/ReapNotifier.ts"],"names":[],"mappings":"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"}
@@ -28,7 +28,7 @@
28
28
  *
29
29
  * Spec: docs/specs/silently-stopped-trio.md
30
30
  */
31
- export type SentinelEventKind = 'detected' | 'nudged' | 'recovered' | 'escalated' | 'escalation-sent' | 'escalation-suppressed' | 'nudge-error' | 'recovery-error' | 'notify-error';
31
+ export type SentinelEventKind = 'detected' | 'nudged' | 'recovered' | 'escalated' | 'escalation-sent' | 'escalation-suppressed' | 'nudge-error' | 'recovery-error' | 'notify-error' | 'dry-run' | 'false-alarm';
32
32
  export interface SentinelLogEntry {
33
33
  ts: string;
34
34
  kind: SentinelEventKind;
@@ -1 +1 @@
1
- {"version":3,"file":"SentinelNotifier.d.ts","sourceRoot":"","sources":["../../src/monitoring/SentinelNotifier.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,MAAM,MAAM,iBAAiB,GACzB,UAAU,GACV,QAAQ,GACR,WAAW,GACX,WAAW,GACX,iBAAiB,GACjB,uBAAuB,GACvB,aAAa,GACb,gBAAgB,GAChB,cAAc,CAAC;AAEnB,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,iBAAiB,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,oBAAoB;IACnC;;;OAGG;IACH,GAAG,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,CAAC;IACvC;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACtD,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,KAAK,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;IACzE,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,KAAK,IAAI,CAAC;CAC9D;AAED,MAAM,WAAW,sBAAsB;IACrC;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAOD,qBAAa,gBAAgB;IAMzB,OAAO,CAAC,QAAQ,CAAC,IAAI;IALvB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAmC;IACvD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA6B;IACrD,OAAO,CAAC,WAAW,CAA8C;gBAG9C,IAAI,EAAE,oBAAoB,EAC3C,GAAG,GAAE,sBAA2B;IAKlC,wDAAwD;IACxD,IAAI,eAAe,IAAI,OAAO,CAE7B;IAED;;;OAGG;IACH,MAAM,CAAC,IAAI,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI;IAI7F;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAUnE,yDAAyD;IACnD,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAK/B,oDAAoD;IACpD,IAAI,IAAI,IAAI;IAKZ,OAAO,CAAC,aAAa;IAYrB,OAAO,CAAC,UAAU;YAOJ,KAAK;IA2BnB,+EAA+E;IAC/E,OAAO,CAAC,cAAc;IAYtB,OAAO,CAAC,KAAK;CAcd"}
1
+ {"version":3,"file":"SentinelNotifier.d.ts","sourceRoot":"","sources":["../../src/monitoring/SentinelNotifier.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,MAAM,MAAM,iBAAiB,GACzB,UAAU,GACV,QAAQ,GACR,WAAW,GACX,WAAW,GACX,iBAAiB,GACjB,uBAAuB,GACvB,aAAa,GACb,gBAAgB,GAChB,cAAc,GAEd,SAAS,GACT,aAAa,CAAC;AAElB,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,iBAAiB,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,oBAAoB;IACnC;;;OAGG;IACH,GAAG,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,CAAC;IACvC;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACtD,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,KAAK,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;IACzE,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,KAAK,IAAI,CAAC;CAC9D;AAED,MAAM,WAAW,sBAAsB;IACrC;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAOD,qBAAa,gBAAgB;IAMzB,OAAO,CAAC,QAAQ,CAAC,IAAI;IALvB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAmC;IACvD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA6B;IACrD,OAAO,CAAC,WAAW,CAA8C;gBAG9C,IAAI,EAAE,oBAAoB,EAC3C,GAAG,GAAE,sBAA2B;IAKlC,wDAAwD;IACxD,IAAI,eAAe,IAAI,OAAO,CAE7B;IAED;;;OAGG;IACH,MAAM,CAAC,IAAI,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI;IAI7F;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAUnE,yDAAyD;IACnD,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAK/B,oDAAoD;IACpD,IAAI,IAAI,IAAI;IAKZ,OAAO,CAAC,aAAa;IAYrB,OAAO,CAAC,UAAU;YAOJ,KAAK;IA2BnB,+EAA+E;IAC/E,OAAO,CAAC,cAAc;IAYtB,OAAO,CAAC,KAAK;CAcd"}
@@ -1 +1 @@
1
- {"version":3,"file":"SentinelNotifier.js","sourceRoot":"","sources":["../../src/monitoring/SentinelNotifier.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"SentinelNotifier.js","sourceRoot":"","sources":["../../src/monitoring/SentinelNotifier.ts"],"names":[],"mappings":"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"}
@@ -103,6 +103,10 @@ export declare class SessionReaper extends EventEmitter {
103
103
  /** Flips to true (forcing dry-run) after any ambiguous/failed reap. */
104
104
  private autoDisabled;
105
105
  private lastTickAt;
106
+ /** Shared stateless KEEP-guards (UNIFIED-SESSION-LIFECYCLE §P2). The reaper
107
+ * consults this first, then layers its stateful transcript-growth + positive-idle
108
+ * checks — so the same guard backs both the reaper and terminateSession(). */
109
+ private readonly guard;
106
110
  constructor(deps: SessionReaperDeps, cfg?: Partial<SessionReaperConfig>);
107
111
  start(): void;
108
112
  stop(): void;