instar 0.28.33 → 0.28.41
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/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +119 -47
- package/dist/commands/server.js.map +1 -1
- package/dist/core/CoherenceGate.d.ts +7 -0
- package/dist/core/CoherenceGate.d.ts.map +1 -1
- package/dist/core/CoherenceGate.js +7 -6
- package/dist/core/CoherenceGate.js.map +1 -1
- package/dist/core/CoherenceReviewer.d.ts +20 -5
- package/dist/core/CoherenceReviewer.d.ts.map +1 -1
- package/dist/core/CoherenceReviewer.js +36 -6
- package/dist/core/CoherenceReviewer.js.map +1 -1
- package/dist/core/MessagingToneGate.d.ts +42 -0
- package/dist/core/MessagingToneGate.d.ts.map +1 -0
- package/dist/core/MessagingToneGate.js +109 -0
- package/dist/core/MessagingToneGate.js.map +1 -0
- package/dist/core/SessionManager.d.ts +9 -0
- package/dist/core/SessionManager.d.ts.map +1 -1
- package/dist/core/SessionManager.js +18 -0
- package/dist/core/SessionManager.js.map +1 -1
- package/dist/messaging/TelegramAdapter.d.ts +8 -0
- package/dist/messaging/TelegramAdapter.d.ts.map +1 -1
- package/dist/messaging/TelegramAdapter.js +42 -3
- package/dist/messaging/TelegramAdapter.js.map +1 -1
- package/dist/monitoring/CompactionSentinel.d.ts +143 -0
- package/dist/monitoring/CompactionSentinel.d.ts.map +1 -0
- package/dist/monitoring/CompactionSentinel.js +262 -0
- package/dist/monitoring/CompactionSentinel.js.map +1 -0
- package/dist/monitoring/SessionWatchdog.d.ts +26 -0
- package/dist/monitoring/SessionWatchdog.d.ts.map +1 -1
- package/dist/monitoring/SessionWatchdog.js +204 -26
- package/dist/monitoring/SessionWatchdog.js.map +1 -1
- package/dist/monitoring/probes/MessagingProbe.d.ts +4 -0
- package/dist/monitoring/probes/MessagingProbe.d.ts.map +1 -1
- package/dist/monitoring/probes/MessagingProbe.js +24 -8
- package/dist/monitoring/probes/MessagingProbe.js.map +1 -1
- package/dist/server/AgentServer.d.ts +1 -0
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +1 -0
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/routes.d.ts +5 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +92 -2
- package/dist/server/routes.js.map +1 -1
- package/dist/threadline/adapters/RESTServer.d.ts +7 -1
- package/dist/threadline/adapters/RESTServer.d.ts.map +1 -1
- package/dist/threadline/adapters/RESTServer.js +62 -1
- package/dist/threadline/adapters/RESTServer.js.map +1 -1
- package/dist/threadline/relay/OfflineQueue.js +1 -1
- package/dist/threadline/relay/OfflineQueue.js.map +1 -1
- package/package.json +1 -1
- package/scripts/check-upgrade-guide.js +9 -115
- package/scripts/upgrade-guide-validator.mjs +221 -0
- package/src/data/builtin-manifest.json +52 -52
- package/src/templates/scripts/imessage-reply.sh +16 -3
- package/src/templates/scripts/slack-reply.sh +10 -0
- package/src/templates/scripts/telegram-reply.sh +11 -0
- package/src/templates/scripts/whatsapp-reply.sh +10 -0
- package/upgrades/0.28.34.md +17 -0
- package/upgrades/0.28.36.md +40 -0
- package/upgrades/0.28.37.md +69 -0
- package/upgrades/0.28.38.md +41 -0
- package/upgrades/0.28.39.md +29 -0
- package/upgrades/0.28.40.md +32 -0
- package/upgrades/0.28.41.md +41 -0
- package/upgrades/NEXT.md +18 -0
- package/upgrades/0.28.10.md +0 -19
- package/upgrades/0.28.11.md +0 -19
- package/upgrades/0.28.13.md +0 -11
- package/upgrades/0.28.15.md +0 -11
- package/upgrades/0.28.18.md +0 -12
- package/upgrades/0.28.20.md +0 -19
- package/upgrades/0.28.21.md +0 -23
- package/upgrades/0.28.22.md +0 -23
- package/upgrades/0.28.23.md +0 -19
- package/upgrades/0.28.24.md +0 -18
- package/upgrades/0.28.25.md +0 -21
- package/upgrades/0.28.26.md +0 -20
- package/upgrades/0.28.27.md +0 -20
- package/upgrades/0.28.28.md +0 -20
- package/upgrades/0.28.30.md +0 -25
- package/upgrades/0.28.31.md +0 -38
- package/upgrades/0.28.32.md +0 -22
- package/upgrades/0.28.33.md +0 -22
- package/upgrades/0.28.5.md +0 -17
- package/upgrades/0.28.7.md +0 -24
- package/upgrades/0.28.8.md +0 -21
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CompactionSentinel — Owns the full lifecycle of recovering a Claude session
|
|
3
|
+
* after context compaction.
|
|
4
|
+
*
|
|
5
|
+
* Prior behavior (before this class): three independent triggers —
|
|
6
|
+
* PreCompact hook event, SessionWatchdog 'compaction-idle' poll, and the
|
|
7
|
+
* compaction-recovery.sh endpoint — each called `recoverCompactedSession`
|
|
8
|
+
* fire-and-forget. If the injection didn't wake the session up (session
|
|
9
|
+
* genuinely hung, injection silently dropped, Claude process dead), nobody
|
|
10
|
+
* noticed. The idle-prompt zombie-killer then raced the recovery window
|
|
11
|
+
* and killed the session 15 minutes later, wiping the conversation.
|
|
12
|
+
*
|
|
13
|
+
* What this class adds:
|
|
14
|
+
* 1. Dedupe across triggers — once a session is in recovery, additional
|
|
15
|
+
* reports within the guard window are ignored.
|
|
16
|
+
* 2. Verification — after each inject attempt, watch the session's JSONL
|
|
17
|
+
* file for size/mtime growth, which is the cheapest reliable signal
|
|
18
|
+
* that Claude actually processed the prompt and produced output.
|
|
19
|
+
* 3. Retry with backoff — if verification fails within the window, try
|
|
20
|
+
* injecting again, up to `maxInjectAttempts`.
|
|
21
|
+
* 4. Zombie-kill veto — while a recovery is in flight, SessionManager's
|
|
22
|
+
* idle-prompt cleanup is told to leave the session alone via the
|
|
23
|
+
* activeRecoveryChecker hook.
|
|
24
|
+
* 5. Observable outcomes — emits `compaction:detected`,
|
|
25
|
+
* `compaction:inject-attempted`, `compaction:recovered`,
|
|
26
|
+
* `compaction:failed` events with a single `[Sentinel]` log prefix
|
|
27
|
+
* so the full lifecycle is greppable.
|
|
28
|
+
*
|
|
29
|
+
* Decoupling: the class takes a `recoverFn` in its deps rather than pulling
|
|
30
|
+
* in server.ts. The existing `recoverCompactedSession` helper is passed
|
|
31
|
+
* straight through, which keeps message-bus / topic-routing concerns out
|
|
32
|
+
* of this file.
|
|
33
|
+
*/
|
|
34
|
+
import { EventEmitter } from 'node:events';
|
|
35
|
+
export type CompactionTrigger = 'PreCompact' | 'watchdog-poll' | 'recovery-hook' | string;
|
|
36
|
+
export type CompactionStatus = 'pending-inject' | 'verifying' | 'retrying' | 'recovered' | 'failed';
|
|
37
|
+
export interface RecoveryState {
|
|
38
|
+
sessionName: string;
|
|
39
|
+
trigger: CompactionTrigger;
|
|
40
|
+
detectedAt: number;
|
|
41
|
+
attempts: number;
|
|
42
|
+
lastInjectAt: number;
|
|
43
|
+
baselineJsonlPath: string | null;
|
|
44
|
+
baselineJsonlSize: number | null;
|
|
45
|
+
baselineJsonlMtime: number | null;
|
|
46
|
+
status: CompactionStatus;
|
|
47
|
+
}
|
|
48
|
+
export interface CompactionSentinelDeps {
|
|
49
|
+
/**
|
|
50
|
+
* Actually perform a recovery injection on a session. The sentinel uses this
|
|
51
|
+
* rather than talking to SessionManager directly so topic/channel routing
|
|
52
|
+
* stays in server.ts. Returns whether the injection was accepted.
|
|
53
|
+
*/
|
|
54
|
+
recoverFn: (sessionName: string, triggerLabel: string) => Promise<boolean>;
|
|
55
|
+
/**
|
|
56
|
+
* Project directory, used to locate Claude Code's JSONL files for the
|
|
57
|
+
* verification check. Usually `config.projectDir`.
|
|
58
|
+
*/
|
|
59
|
+
projectDir: string;
|
|
60
|
+
/**
|
|
61
|
+
* Resolve a session's Claude Code session UUID (from hook events). When
|
|
62
|
+
* present, the sentinel reads the jsonl file named exactly `<uuid>.jsonl`
|
|
63
|
+
* instead of falling back to most-recently-modified — which prevents
|
|
64
|
+
* false positives where a sibling session's output makes this one look
|
|
65
|
+
* recovered. Return undefined for sessions whose uuid isn't known yet.
|
|
66
|
+
*/
|
|
67
|
+
getClaudeSessionId?: (sessionName: string) => string | undefined;
|
|
68
|
+
/**
|
|
69
|
+
* Override for the JSONL lookup root. Primarily for tests. Defaults to
|
|
70
|
+
* `$HOME/.claude/projects/<project-hash>`.
|
|
71
|
+
*/
|
|
72
|
+
jsonlRoot?: string;
|
|
73
|
+
/** Override for Date.now — for tests. */
|
|
74
|
+
now?: () => number;
|
|
75
|
+
/** Override timer setters — for tests. */
|
|
76
|
+
setTimer?: (fn: () => void, ms: number) => ReturnType<typeof setTimeout>;
|
|
77
|
+
clearTimer?: (handle: ReturnType<typeof setTimeout>) => void;
|
|
78
|
+
}
|
|
79
|
+
export interface CompactionSentinelConfig {
|
|
80
|
+
/** Ignore repeat reports for the same session within this window. */
|
|
81
|
+
dedupeWindowMs?: number;
|
|
82
|
+
/** How long to wait after injection before verifying output. */
|
|
83
|
+
verifyWindowMs?: number;
|
|
84
|
+
/** Max inject attempts before declaring failure. */
|
|
85
|
+
maxInjectAttempts?: number;
|
|
86
|
+
/** Max total time a recovery can block the zombie-killer. */
|
|
87
|
+
recoveryGuardMs?: number;
|
|
88
|
+
}
|
|
89
|
+
export interface CompactionSentinelEvents {
|
|
90
|
+
'compaction:detected': [RecoveryState];
|
|
91
|
+
'compaction:inject-attempted': [RecoveryState & {
|
|
92
|
+
accepted: boolean;
|
|
93
|
+
}];
|
|
94
|
+
'compaction:recovered': [RecoveryState & {
|
|
95
|
+
jsonlDelta: number;
|
|
96
|
+
}];
|
|
97
|
+
'compaction:failed': [RecoveryState & {
|
|
98
|
+
reason: string;
|
|
99
|
+
}];
|
|
100
|
+
}
|
|
101
|
+
export declare class CompactionSentinel extends EventEmitter {
|
|
102
|
+
private readonly deps;
|
|
103
|
+
private readonly cfg;
|
|
104
|
+
private readonly active;
|
|
105
|
+
private readonly timers;
|
|
106
|
+
private readonly recentReports;
|
|
107
|
+
constructor(deps: CompactionSentinelDeps, config?: CompactionSentinelConfig);
|
|
108
|
+
private now;
|
|
109
|
+
private setTimer;
|
|
110
|
+
private clearTimer;
|
|
111
|
+
/**
|
|
112
|
+
* Report a compaction event for a session. Called by all three trigger paths
|
|
113
|
+
* (PreCompact hook, watchdog poll, recovery-hook endpoint). Deduped — if the
|
|
114
|
+
* same session is already in active recovery or was reported within the
|
|
115
|
+
* dedupe window, this is a no-op.
|
|
116
|
+
*/
|
|
117
|
+
report(sessionName: string, trigger: CompactionTrigger): void;
|
|
118
|
+
/**
|
|
119
|
+
* Predicate for SessionManager's zombie-killer: is this session currently
|
|
120
|
+
* being recovered? If yes, the zombie-killer should skip it.
|
|
121
|
+
*/
|
|
122
|
+
isRecoveryActive(sessionName: string): boolean;
|
|
123
|
+
/**
|
|
124
|
+
* Manually clear recovery state. Used when the session completes or is
|
|
125
|
+
* explicitly reset. Exposed mainly for tests and edge cases.
|
|
126
|
+
*/
|
|
127
|
+
clear(sessionName: string): void;
|
|
128
|
+
/** Stop all pending timers. Safe to call multiple times. */
|
|
129
|
+
stop(): void;
|
|
130
|
+
/** Get current active recovery state (test introspection). */
|
|
131
|
+
getState(sessionName: string): RecoveryState | undefined;
|
|
132
|
+
private attemptInjection;
|
|
133
|
+
private verifyRecovery;
|
|
134
|
+
private finalize;
|
|
135
|
+
/**
|
|
136
|
+
* Look up the JSONL file for this session. Prefers the exact file by
|
|
137
|
+
* Claude Code session UUID when known (prevents a sibling session's
|
|
138
|
+
* activity from looking like this session recovered). Falls back to the
|
|
139
|
+
* most recently-modified file in the project's jsonl root.
|
|
140
|
+
*/
|
|
141
|
+
private readJsonlBaseline;
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=CompactionSentinel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CompactionSentinel.d.ts","sourceRoot":"","sources":["../../src/monitoring/CompactionSentinel.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CompactionSentinel — Owns the full lifecycle of recovering a Claude session
|
|
3
|
+
* after context compaction.
|
|
4
|
+
*
|
|
5
|
+
* Prior behavior (before this class): three independent triggers —
|
|
6
|
+
* PreCompact hook event, SessionWatchdog 'compaction-idle' poll, and the
|
|
7
|
+
* compaction-recovery.sh endpoint — each called `recoverCompactedSession`
|
|
8
|
+
* fire-and-forget. If the injection didn't wake the session up (session
|
|
9
|
+
* genuinely hung, injection silently dropped, Claude process dead), nobody
|
|
10
|
+
* noticed. The idle-prompt zombie-killer then raced the recovery window
|
|
11
|
+
* and killed the session 15 minutes later, wiping the conversation.
|
|
12
|
+
*
|
|
13
|
+
* What this class adds:
|
|
14
|
+
* 1. Dedupe across triggers — once a session is in recovery, additional
|
|
15
|
+
* reports within the guard window are ignored.
|
|
16
|
+
* 2. Verification — after each inject attempt, watch the session's JSONL
|
|
17
|
+
* file for size/mtime growth, which is the cheapest reliable signal
|
|
18
|
+
* that Claude actually processed the prompt and produced output.
|
|
19
|
+
* 3. Retry with backoff — if verification fails within the window, try
|
|
20
|
+
* injecting again, up to `maxInjectAttempts`.
|
|
21
|
+
* 4. Zombie-kill veto — while a recovery is in flight, SessionManager's
|
|
22
|
+
* idle-prompt cleanup is told to leave the session alone via the
|
|
23
|
+
* activeRecoveryChecker hook.
|
|
24
|
+
* 5. Observable outcomes — emits `compaction:detected`,
|
|
25
|
+
* `compaction:inject-attempted`, `compaction:recovered`,
|
|
26
|
+
* `compaction:failed` events with a single `[Sentinel]` log prefix
|
|
27
|
+
* so the full lifecycle is greppable.
|
|
28
|
+
*
|
|
29
|
+
* Decoupling: the class takes a `recoverFn` in its deps rather than pulling
|
|
30
|
+
* in server.ts. The existing `recoverCompactedSession` helper is passed
|
|
31
|
+
* straight through, which keeps message-bus / topic-routing concerns out
|
|
32
|
+
* of this file.
|
|
33
|
+
*/
|
|
34
|
+
import { EventEmitter } from 'node:events';
|
|
35
|
+
import fs from 'node:fs';
|
|
36
|
+
import path from 'node:path';
|
|
37
|
+
const DEFAULTS = {
|
|
38
|
+
dedupeWindowMs: 60_000, // 1 minute — catch overlapping triggers
|
|
39
|
+
verifyWindowMs: 25_000, // 25s — enough for claude to boot past recovery hook and emit output
|
|
40
|
+
maxInjectAttempts: 3,
|
|
41
|
+
recoveryGuardMs: 10 * 60_000, // 10 minutes — protection from zombie-killer
|
|
42
|
+
};
|
|
43
|
+
export class CompactionSentinel extends EventEmitter {
|
|
44
|
+
deps;
|
|
45
|
+
cfg;
|
|
46
|
+
active = new Map();
|
|
47
|
+
timers = new Map();
|
|
48
|
+
recentReports = new Map(); // sessionName → lastReportedAt
|
|
49
|
+
constructor(deps, config = {}) {
|
|
50
|
+
super();
|
|
51
|
+
this.deps = deps;
|
|
52
|
+
this.cfg = {
|
|
53
|
+
dedupeWindowMs: config.dedupeWindowMs ?? DEFAULTS.dedupeWindowMs,
|
|
54
|
+
verifyWindowMs: config.verifyWindowMs ?? DEFAULTS.verifyWindowMs,
|
|
55
|
+
maxInjectAttempts: config.maxInjectAttempts ?? DEFAULTS.maxInjectAttempts,
|
|
56
|
+
recoveryGuardMs: config.recoveryGuardMs ?? DEFAULTS.recoveryGuardMs,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
now() {
|
|
60
|
+
return this.deps.now ? this.deps.now() : Date.now();
|
|
61
|
+
}
|
|
62
|
+
setTimer(fn, ms) {
|
|
63
|
+
return this.deps.setTimer ? this.deps.setTimer(fn, ms) : setTimeout(fn, ms);
|
|
64
|
+
}
|
|
65
|
+
clearTimer(handle) {
|
|
66
|
+
if (this.deps.clearTimer)
|
|
67
|
+
this.deps.clearTimer(handle);
|
|
68
|
+
else
|
|
69
|
+
clearTimeout(handle);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Report a compaction event for a session. Called by all three trigger paths
|
|
73
|
+
* (PreCompact hook, watchdog poll, recovery-hook endpoint). Deduped — if the
|
|
74
|
+
* same session is already in active recovery or was reported within the
|
|
75
|
+
* dedupe window, this is a no-op.
|
|
76
|
+
*/
|
|
77
|
+
report(sessionName, trigger) {
|
|
78
|
+
const now = this.now();
|
|
79
|
+
if (this.active.has(sessionName)) {
|
|
80
|
+
return; // Recovery already in flight — ignore duplicate trigger.
|
|
81
|
+
}
|
|
82
|
+
const lastReport = this.recentReports.get(sessionName);
|
|
83
|
+
if (lastReport && now - lastReport < this.cfg.dedupeWindowMs) {
|
|
84
|
+
return; // Already reported recently.
|
|
85
|
+
}
|
|
86
|
+
this.recentReports.set(sessionName, now);
|
|
87
|
+
const baseline = this.readJsonlBaseline(sessionName);
|
|
88
|
+
const state = {
|
|
89
|
+
sessionName,
|
|
90
|
+
trigger,
|
|
91
|
+
detectedAt: now,
|
|
92
|
+
attempts: 0,
|
|
93
|
+
lastInjectAt: 0,
|
|
94
|
+
baselineJsonlPath: baseline?.path ?? null,
|
|
95
|
+
baselineJsonlSize: baseline?.size ?? null,
|
|
96
|
+
baselineJsonlMtime: baseline?.mtime ?? null,
|
|
97
|
+
status: 'pending-inject',
|
|
98
|
+
};
|
|
99
|
+
this.active.set(sessionName, state);
|
|
100
|
+
console.log(`[Sentinel] detected compaction on "${sessionName}" via ${trigger}; ` +
|
|
101
|
+
`baseline jsonl: ${state.baselineJsonlPath ? path.basename(state.baselineJsonlPath) : 'none'} ` +
|
|
102
|
+
`size=${state.baselineJsonlSize ?? 'n/a'}`);
|
|
103
|
+
this.emit('compaction:detected', state);
|
|
104
|
+
this.attemptInjection(state);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Predicate for SessionManager's zombie-killer: is this session currently
|
|
108
|
+
* being recovered? If yes, the zombie-killer should skip it.
|
|
109
|
+
*/
|
|
110
|
+
isRecoveryActive(sessionName) {
|
|
111
|
+
const state = this.active.get(sessionName);
|
|
112
|
+
if (!state)
|
|
113
|
+
return false;
|
|
114
|
+
// Don't claim active for terminal states (defense in depth).
|
|
115
|
+
return state.status !== 'recovered' && state.status !== 'failed';
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Manually clear recovery state. Used when the session completes or is
|
|
119
|
+
* explicitly reset. Exposed mainly for tests and edge cases.
|
|
120
|
+
*/
|
|
121
|
+
clear(sessionName) {
|
|
122
|
+
const timer = this.timers.get(sessionName);
|
|
123
|
+
if (timer) {
|
|
124
|
+
this.clearTimer(timer);
|
|
125
|
+
this.timers.delete(sessionName);
|
|
126
|
+
}
|
|
127
|
+
this.active.delete(sessionName);
|
|
128
|
+
}
|
|
129
|
+
/** Stop all pending timers. Safe to call multiple times. */
|
|
130
|
+
stop() {
|
|
131
|
+
for (const handle of this.timers.values())
|
|
132
|
+
this.clearTimer(handle);
|
|
133
|
+
this.timers.clear();
|
|
134
|
+
this.active.clear();
|
|
135
|
+
this.recentReports.clear();
|
|
136
|
+
}
|
|
137
|
+
/** Get current active recovery state (test introspection). */
|
|
138
|
+
getState(sessionName) {
|
|
139
|
+
return this.active.get(sessionName);
|
|
140
|
+
}
|
|
141
|
+
async attemptInjection(state) {
|
|
142
|
+
state.attempts += 1;
|
|
143
|
+
state.lastInjectAt = this.now();
|
|
144
|
+
state.status = 'pending-inject';
|
|
145
|
+
let accepted = false;
|
|
146
|
+
try {
|
|
147
|
+
accepted = await this.deps.recoverFn(state.sessionName, state.trigger);
|
|
148
|
+
}
|
|
149
|
+
catch (err) {
|
|
150
|
+
console.warn(`[Sentinel] recoverFn threw on "${state.sessionName}" (attempt ${state.attempts}):`, err);
|
|
151
|
+
}
|
|
152
|
+
console.log(`[Sentinel] inject-attempted on "${state.sessionName}" ` +
|
|
153
|
+
`(attempt ${state.attempts}/${this.cfg.maxInjectAttempts}, accepted=${accepted})`);
|
|
154
|
+
this.emit('compaction:inject-attempted', { ...state, accepted });
|
|
155
|
+
if (!accepted) {
|
|
156
|
+
// recoverFn returned false — typically means no unanswered user message,
|
|
157
|
+
// session not alive, or injection blocked. No point retrying; close out.
|
|
158
|
+
this.finalize(state, 'failed', 'recoverFn declined (no pending work or session gone)');
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
state.status = 'verifying';
|
|
162
|
+
const handle = this.setTimer(() => {
|
|
163
|
+
this.verifyRecovery(state).catch(err => {
|
|
164
|
+
console.warn(`[Sentinel] verify threw on "${state.sessionName}":`, err);
|
|
165
|
+
this.finalize(state, 'failed', `verify threw: ${String(err)}`);
|
|
166
|
+
});
|
|
167
|
+
}, this.cfg.verifyWindowMs);
|
|
168
|
+
this.timers.set(state.sessionName, handle);
|
|
169
|
+
}
|
|
170
|
+
async verifyRecovery(state) {
|
|
171
|
+
this.timers.delete(state.sessionName);
|
|
172
|
+
// Has the jsonl file grown? If so, claude processed the prompt and emitted.
|
|
173
|
+
const current = this.readJsonlBaseline(state.sessionName);
|
|
174
|
+
const grew = state.baselineJsonlSize !== null &&
|
|
175
|
+
current !== null &&
|
|
176
|
+
(current.size > state.baselineJsonlSize ||
|
|
177
|
+
(current.path === state.baselineJsonlPath &&
|
|
178
|
+
state.baselineJsonlMtime !== null &&
|
|
179
|
+
current.mtime > state.baselineJsonlMtime));
|
|
180
|
+
if (grew) {
|
|
181
|
+
const delta = (current?.size ?? 0) - (state.baselineJsonlSize ?? 0);
|
|
182
|
+
console.log(`[Sentinel] recovered "${state.sessionName}" after ${state.attempts} attempt(s) ` +
|
|
183
|
+
`(jsonl grew by ${delta} bytes)`);
|
|
184
|
+
this.emit('compaction:recovered', { ...state, jsonlDelta: delta });
|
|
185
|
+
this.finalize(state, 'recovered');
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
// No growth — session didn't respond within the window.
|
|
189
|
+
if (state.attempts >= this.cfg.maxInjectAttempts) {
|
|
190
|
+
this.finalize(state, 'failed', `no jsonl growth after ${state.attempts} attempts (${state.attempts * this.cfg.verifyWindowMs}ms total wait)`);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
// Retry.
|
|
194
|
+
state.status = 'retrying';
|
|
195
|
+
console.log(`[Sentinel] retry "${state.sessionName}" ` +
|
|
196
|
+
`(attempt ${state.attempts}/${this.cfg.maxInjectAttempts} produced no output, re-injecting)`);
|
|
197
|
+
await this.attemptInjection(state);
|
|
198
|
+
}
|
|
199
|
+
finalize(state, status, reason) {
|
|
200
|
+
state.status = status;
|
|
201
|
+
if (status === 'failed') {
|
|
202
|
+
console.warn(`[Sentinel] failed "${state.sessionName}": ${reason ?? 'unknown'}`);
|
|
203
|
+
this.emit('compaction:failed', { ...state, reason: reason ?? 'unknown' });
|
|
204
|
+
}
|
|
205
|
+
// Keep the state around just long enough to exit the recovery-guard window
|
|
206
|
+
// (so the zombie-killer won't race the next prompt). Then clean up.
|
|
207
|
+
// Also clear the recentReports entry so a *new* compaction on this session
|
|
208
|
+
// can trigger a fresh recovery, even if the second compaction happens
|
|
209
|
+
// within the dedupe window of the first. Without this, the second
|
|
210
|
+
// compaction would be silently suppressed.
|
|
211
|
+
const keepFor = status === 'recovered' ? 5_000 : 30_000;
|
|
212
|
+
const handle = this.setTimer(() => {
|
|
213
|
+
this.timers.delete(state.sessionName);
|
|
214
|
+
this.active.delete(state.sessionName);
|
|
215
|
+
this.recentReports.delete(state.sessionName);
|
|
216
|
+
}, keepFor);
|
|
217
|
+
this.timers.set(state.sessionName, handle);
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Look up the JSONL file for this session. Prefers the exact file by
|
|
221
|
+
* Claude Code session UUID when known (prevents a sibling session's
|
|
222
|
+
* activity from looking like this session recovered). Falls back to the
|
|
223
|
+
* most recently-modified file in the project's jsonl root.
|
|
224
|
+
*/
|
|
225
|
+
readJsonlBaseline(sessionName) {
|
|
226
|
+
try {
|
|
227
|
+
const root = this.deps.jsonlRoot
|
|
228
|
+
|| path.join(process.env.HOME || '/tmp', '.claude', 'projects', this.deps.projectDir.replace(/\//g, '-'));
|
|
229
|
+
if (!fs.existsSync(root))
|
|
230
|
+
return null;
|
|
231
|
+
// Prefer the exact file by claudeSessionId when available.
|
|
232
|
+
const uuid = this.deps.getClaudeSessionId?.(sessionName);
|
|
233
|
+
if (uuid) {
|
|
234
|
+
const exact = path.join(root, `${uuid}.jsonl`);
|
|
235
|
+
if (fs.existsSync(exact)) {
|
|
236
|
+
const st = fs.statSync(exact);
|
|
237
|
+
return { path: exact, size: st.size, mtime: st.mtimeMs };
|
|
238
|
+
}
|
|
239
|
+
// If uuid is known but file doesn't exist, return null rather than
|
|
240
|
+
// falling through — the session genuinely has no jsonl, and picking
|
|
241
|
+
// a sibling's file would be a false signal.
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
// Fallback: most-recently-modified jsonl in the project.
|
|
245
|
+
const entries = fs.readdirSync(root).filter(f => f.endsWith('.jsonl'));
|
|
246
|
+
if (entries.length === 0)
|
|
247
|
+
return null;
|
|
248
|
+
const latest = entries
|
|
249
|
+
.map(f => {
|
|
250
|
+
const full = path.join(root, f);
|
|
251
|
+
const st = fs.statSync(full);
|
|
252
|
+
return { path: full, size: st.size, mtime: st.mtimeMs };
|
|
253
|
+
})
|
|
254
|
+
.sort((a, b) => b.mtime - a.mtime)[0];
|
|
255
|
+
return latest;
|
|
256
|
+
}
|
|
257
|
+
catch {
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
//# sourceMappingURL=CompactionSentinel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CompactionSentinel.js","sourceRoot":"","sources":["../../src/monitoring/CompactionSentinel.ts"],"names":[],"mappings":"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"}
|
|
@@ -122,6 +122,32 @@ export declare class SessionWatchdog extends EventEmitter {
|
|
|
122
122
|
private isCommandStuck;
|
|
123
123
|
private getClaudePid;
|
|
124
124
|
private getChildProcesses;
|
|
125
|
+
/**
|
|
126
|
+
* Detects whether the given process is a pipeline stdin consumer (tail,
|
|
127
|
+
* grep, sort, etc.) whose process group still contains an active sibling
|
|
128
|
+
* producer. Such cases are false positives for the stuck-command detector:
|
|
129
|
+
* `zsh -c "python ... | tail -40"` execs tail into place, so the child
|
|
130
|
+
* of claude shows up as `tail -40` even though the real work is the
|
|
131
|
+
* python producer still running in the same pgid.
|
|
132
|
+
*
|
|
133
|
+
* Returns true if this PID looks like a waiting consumer in an active
|
|
134
|
+
* pipeline — in which case escalation should be skipped.
|
|
135
|
+
*/
|
|
136
|
+
private hasActivePipelineSibling;
|
|
137
|
+
/**
|
|
138
|
+
* Cheap check whether a command is a pure stdin consumer (tail, grep,
|
|
139
|
+
* sort, etc.) without needing pgid lookups. Used to apply extended
|
|
140
|
+
* grace periods and fail-closed escalation for this class of commands.
|
|
141
|
+
*/
|
|
142
|
+
private isStdinConsumerCommand;
|
|
143
|
+
/**
|
|
144
|
+
* Heuristic: does a stdin-consumer command have a file argument? If so,
|
|
145
|
+
* it's reading from the file (not a pipe) and the pipeline guard doesn't
|
|
146
|
+
* apply. We strip flags (tokens starting with `-`) and anything that
|
|
147
|
+
* looks like a flag value, then check if a non-flag token remains past
|
|
148
|
+
* the executable name.
|
|
149
|
+
*/
|
|
150
|
+
private hasFileArgument;
|
|
125
151
|
private isExcluded;
|
|
126
152
|
private parseElapsed;
|
|
127
153
|
private sendSignal;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SessionWatchdog.d.ts","sourceRoot":"","sources":["../../src/monitoring/SessionWatchdog.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAS3C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,KAAK,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAE3E,oBAAY,eAAe;IACzB,UAAU,IAAI;IACd,KAAK,IAAI;IACT,OAAO,IAAI;IACX,OAAO,IAAI;IACX,WAAW,IAAI;CAChB;AAQD,UAAU,eAAe;IACvB,KAAK,EAAE,eAAe,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,eAAe,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,iDAAiD;IACjD,OAAO,CAAC,EAAE,WAAW,GAAG,MAAM,GAAG,SAAS,CAAC;IAC3C,gEAAgE;IAChE,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,8CAA8C;AAC9C,MAAM,WAAW,aAAa;IAC5B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7C,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,EAAE,MAAM,CAAC;CAC1B;
|
|
1
|
+
{"version":3,"file":"SessionWatchdog.d.ts","sourceRoot":"","sources":["../../src/monitoring/SessionWatchdog.ts"],"names":[],"mappings":"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"}
|