@0xmaxma/claude-gateway 1.3.25 → 1.3.32
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/README.md +50 -2
- package/config.template.json +6 -2
- package/dist/agent/incident-store.d.ts +89 -0
- package/dist/agent/incident-store.d.ts.map +1 -0
- package/dist/agent/incident-store.js +299 -0
- package/dist/agent/incident-store.js.map +1 -0
- package/dist/agent/incident.d.ts +156 -0
- package/dist/agent/incident.d.ts.map +1 -0
- package/dist/agent/incident.js +177 -0
- package/dist/agent/incident.js.map +1 -0
- package/dist/agent/recovery-executor.d.ts +117 -0
- package/dist/agent/recovery-executor.d.ts.map +1 -0
- package/dist/agent/recovery-executor.js +168 -0
- package/dist/agent/recovery-executor.js.map +1 -0
- package/dist/agent/recovery-policy.d.ts +97 -0
- package/dist/agent/recovery-policy.d.ts.map +1 -0
- package/dist/agent/recovery-policy.js +164 -0
- package/dist/agent/recovery-policy.js.map +1 -0
- package/dist/agent/runner.d.ts +66 -6
- package/dist/agent/runner.d.ts.map +1 -1
- package/dist/agent/runner.js +332 -27
- package/dist/agent/runner.js.map +1 -1
- package/dist/agent/safe-mode.d.ts +61 -0
- package/dist/agent/safe-mode.d.ts.map +1 -0
- package/dist/agent/safe-mode.js +102 -0
- package/dist/agent/safe-mode.js.map +1 -0
- package/dist/agent/triage.d.ts +94 -0
- package/dist/agent/triage.d.ts.map +1 -0
- package/dist/agent/triage.js +209 -0
- package/dist/agent/triage.js.map +1 -0
- package/dist/agent/turn-trace.d.ts +120 -0
- package/dist/agent/turn-trace.d.ts.map +1 -0
- package/dist/agent/turn-trace.js +122 -0
- package/dist/agent/turn-trace.js.map +1 -0
- package/dist/api/gateway-router.d.ts +21 -0
- package/dist/api/gateway-router.d.ts.map +1 -1
- package/dist/api/gateway-router.js +58 -17
- package/dist/api/gateway-router.js.map +1 -1
- package/dist/api/router.d.ts.map +1 -1
- package/dist/api/router.js +104 -4
- package/dist/api/router.js.map +1 -1
- package/dist/config/migrator.d.ts +4 -0
- package/dist/config/migrator.d.ts.map +1 -1
- package/dist/config/migrator.js +60 -3
- package/dist/config/migrator.js.map +1 -1
- package/dist/history/db.d.ts +13 -1
- package/dist/history/db.d.ts.map +1 -1
- package/dist/history/db.js +67 -9
- package/dist/history/db.js.map +1 -1
- package/dist/history/types.d.ts +10 -0
- package/dist/history/types.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/session/process.d.ts +26 -0
- package/dist/session/process.d.ts.map +1 -1
- package/dist/session/process.js +77 -1
- package/dist/session/process.js.map +1 -1
- package/dist/shell/claude-pty-shell.js +59 -0
- package/dist/shell/claude-pty-shell.js.map +1 -1
- package/dist/shell/control-channel.d.ts +74 -0
- package/dist/shell/control-channel.d.ts.map +1 -0
- package/dist/shell/control-channel.js +114 -0
- package/dist/shell/control-channel.js.map +1 -0
- package/dist/types.d.ts +22 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/ui/web-ui.d.ts.map +1 -1
- package/dist/ui/web-ui.js +103 -2
- package/dist/ui/web-ui.js.map +1 -1
- package/mcp/tools/skills/handlers.ts +4 -2
- package/mcp/tools/telegram/receiver-server.ts +163 -0
- package/mcp/tools/telegram/typing.ts +124 -0
- package/package.json +3 -1
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Incident core (Epic #195, Phase 2).
|
|
3
|
+
*
|
|
4
|
+
* Pure decision logic for the incident-reporting layer that sits on top of the
|
|
5
|
+
* turn-trace watchdog (Phase 1). Given a stall incident, this module decides:
|
|
6
|
+
* - its fingerprint (what makes two stalls "the same problem"),
|
|
7
|
+
* - whether/how to escalate a repeat (quiet → repeat → recommend-investigate),
|
|
8
|
+
* - how to scrub evidence before any of it can leave the machine,
|
|
9
|
+
* - how to summarise a set of incidents into a digest line/report.
|
|
10
|
+
*
|
|
11
|
+
* Like turn-trace.ts and orphan-wake.ts, this file performs NO IO and imports
|
|
12
|
+
* no runtime code (only types, which are erased), so every rule here is
|
|
13
|
+
* unit-testable without a filesystem, a clock, or a live session. The store
|
|
14
|
+
* (incident-store.ts) owns persistence and calls into these functions.
|
|
15
|
+
*/
|
|
16
|
+
import type { TurnStage, TurnFailureClass } from './turn-trace';
|
|
17
|
+
/** Placeholder substituted for any redacted span in a scrubbed export. */
|
|
18
|
+
export declare const REDACTION = "\u2039redacted\u203A";
|
|
19
|
+
/** Escalation level reached for an incident fingerprint. */
|
|
20
|
+
export type EscalationLevel = 'quiet' | 'repeat' | 'investigate';
|
|
21
|
+
/** Lifecycle status of a persisted incident. */
|
|
22
|
+
export type IncidentStatus = 'open' | 'resolved';
|
|
23
|
+
/**
|
|
24
|
+
* A single occurrence of a stall, captured each time the watchdog re-raises the
|
|
25
|
+
* same fingerprint. Kept small — the store retains only a capped tail of these.
|
|
26
|
+
*/
|
|
27
|
+
export interface IncidentSample {
|
|
28
|
+
/** When this occurrence was raised (epoch ms). */
|
|
29
|
+
at: number;
|
|
30
|
+
/** How long the turn had sat in the stalled stage (ms). */
|
|
31
|
+
sinceMs: number;
|
|
32
|
+
/** The stage's timeout budget at the time (ms). */
|
|
33
|
+
budgetMs: number;
|
|
34
|
+
/** Whether a fresh `.processing` sentinel showed the turn was mid-work. */
|
|
35
|
+
midTurn: boolean;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The persisted record for one incident fingerprint. Written to
|
|
39
|
+
* `<dir>/<id>/manifest.json`. Fields are deliberately free of chat content;
|
|
40
|
+
* `channel` is the transport name only (never a chat id), and evidence with
|
|
41
|
+
* potentially sensitive text lives in scrubbed sibling artifact files.
|
|
42
|
+
*/
|
|
43
|
+
export interface IncidentManifest {
|
|
44
|
+
/** Filesystem-safe unique id: `<fingerprintHash>-<firstAt>`. */
|
|
45
|
+
id: string;
|
|
46
|
+
/** Stable dedup key: stage + failure class + CLI version. */
|
|
47
|
+
fingerprint: string;
|
|
48
|
+
stage: TurnStage;
|
|
49
|
+
failureClass: TurnFailureClass | null;
|
|
50
|
+
/** Claude CLI version at capture time, or 'unknown'. Part of the fingerprint. */
|
|
51
|
+
cliVersion: string;
|
|
52
|
+
/** Gateway version at capture time, or 'unknown'. Context only. */
|
|
53
|
+
gatewayVersion: string;
|
|
54
|
+
/** Transport name only: 'telegram' | 'discord' | 'line' | 'api'. */
|
|
55
|
+
channel: string;
|
|
56
|
+
/** First and most-recent occurrence (epoch ms). */
|
|
57
|
+
firstAt: number;
|
|
58
|
+
lastAt: number;
|
|
59
|
+
/** Total occurrences folded into this incident. */
|
|
60
|
+
occurrences: number;
|
|
61
|
+
/** Highest escalation level reached. */
|
|
62
|
+
escalationLevel: EscalationLevel;
|
|
63
|
+
status: IncidentStatus;
|
|
64
|
+
/** Last time the user was notified, and at what level (dedupes re-notifying). */
|
|
65
|
+
notifiedAt: number | null;
|
|
66
|
+
notifiedLevel: EscalationLevel | null;
|
|
67
|
+
/** Linked GitHub issue number, if one was filed for this fingerprint. */
|
|
68
|
+
githubIssue: number | null;
|
|
69
|
+
/** Recovery actions + outcomes — populated in Phase 3; [] in Phase 2. */
|
|
70
|
+
recovery: RecoveryOutcome[];
|
|
71
|
+
/** Capped tail of recent occurrences. */
|
|
72
|
+
samples: IncidentSample[];
|
|
73
|
+
}
|
|
74
|
+
/** A recovery action and its result (Phase 3 fills these; typed here for the schema). */
|
|
75
|
+
export interface RecoveryOutcome {
|
|
76
|
+
action: string;
|
|
77
|
+
at: number;
|
|
78
|
+
ok: boolean;
|
|
79
|
+
detail?: string;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Compute the dedup fingerprint. Two stalls are "the same problem" when they
|
|
83
|
+
* hit the same pipeline stage with the same failure attribution on the same CLI
|
|
84
|
+
* version — a new CLI version is treated as a distinct problem so a regression
|
|
85
|
+
* introduced by an upgrade does not silently fold into an old fingerprint.
|
|
86
|
+
*/
|
|
87
|
+
export declare function computeFingerprint(input: {
|
|
88
|
+
stage: TurnStage;
|
|
89
|
+
failureClass: TurnFailureClass | null;
|
|
90
|
+
cliVersion: string | null | undefined;
|
|
91
|
+
}): string;
|
|
92
|
+
/**
|
|
93
|
+
* Deterministic, dependency-free hash (FNV-1a → base36). Used to derive a short
|
|
94
|
+
* filesystem-safe prefix for an incident id from its fingerprint. Not used for
|
|
95
|
+
* anything security-sensitive — only stable naming/dedup.
|
|
96
|
+
*/
|
|
97
|
+
export declare function fingerprintHash(fingerprint: string): string;
|
|
98
|
+
/** Configurable thresholds for escalation. */
|
|
99
|
+
export interface EscalationConfig {
|
|
100
|
+
/** Dedup window — repeats within this of the last occurrence fold in (ms). */
|
|
101
|
+
windowMs: number;
|
|
102
|
+
/** Occurrence count at/after which we recommend investigation. */
|
|
103
|
+
investigateThreshold: number;
|
|
104
|
+
}
|
|
105
|
+
export declare const DEFAULT_ESCALATION: EscalationConfig;
|
|
106
|
+
/** The escalation verdict for a single (folded) occurrence. */
|
|
107
|
+
export interface EscalationDecision {
|
|
108
|
+
level: EscalationLevel;
|
|
109
|
+
/** Whether the user should be notified for this occurrence. */
|
|
110
|
+
notify: boolean;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Decide how to escalate given the running occurrence count for a fingerprint
|
|
114
|
+
* and whether the user was already notified at 'investigate' level.
|
|
115
|
+
*
|
|
116
|
+
* - 1st occurrence → quiet notify (one short heads-up)
|
|
117
|
+
* - repeats below threshold → silent increment (no re-notify)
|
|
118
|
+
* - Nth (N ≥ threshold), once → recommend investigation (louder notify)
|
|
119
|
+
* - after that → silent (already recommended)
|
|
120
|
+
*
|
|
121
|
+
* Pure: the caller supplies the count and prior-notify state.
|
|
122
|
+
*/
|
|
123
|
+
export declare function decideEscalation(occurrences: number, alreadyInvestigateNotified: boolean, cfg?: EscalationConfig): EscalationDecision;
|
|
124
|
+
/** The maximum of two escalation levels (for tracking the highest reached). */
|
|
125
|
+
export declare function maxEscalationLevel(a: EscalationLevel, b: EscalationLevel): EscalationLevel;
|
|
126
|
+
/**
|
|
127
|
+
* Scrub text destined for an exported/persisted artifact. Removes:
|
|
128
|
+
* 1. every caller-supplied literal (chat ids, usernames) — matched verbatim,
|
|
129
|
+
* 2. recognisable secret/PII shapes (tokens, keys, emails).
|
|
130
|
+
*
|
|
131
|
+
* Literals are escaped before use so a value like `a.b` cannot act as a regex.
|
|
132
|
+
* Pure and idempotent enough for repeated application (placeholder is inert).
|
|
133
|
+
*/
|
|
134
|
+
export declare function scrubText(text: string, redactions?: string[]): string;
|
|
135
|
+
/** A roll-up of incidents over a period, for the digest line / trend report. */
|
|
136
|
+
export interface DigestSummary {
|
|
137
|
+
/** Incidents (folded fingerprints) whose lastAt falls in the window. */
|
|
138
|
+
total: number;
|
|
139
|
+
/** Sum of occurrences across those incidents. */
|
|
140
|
+
occurrences: number;
|
|
141
|
+
openCount: number;
|
|
142
|
+
byStage: Record<string, number>;
|
|
143
|
+
byFailureClass: Record<string, number>;
|
|
144
|
+
byCliVersion: Record<string, number>;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Summarise the incidents whose most-recent occurrence lands in
|
|
148
|
+
* `[now - sinceMs, now]`. Pure — the store supplies the manifests it read.
|
|
149
|
+
*/
|
|
150
|
+
export declare function summarizeIncidents(manifests: IncidentManifest[], sinceMs: number, now: number): DigestSummary;
|
|
151
|
+
/**
|
|
152
|
+
* One-line, human-friendly digest. Zero-incident periods produce a short "all
|
|
153
|
+
* clear" line rather than nothing, so the digest itself is a liveness signal.
|
|
154
|
+
*/
|
|
155
|
+
export declare function formatDigestLine(summary: DigestSummary, label?: string): string;
|
|
156
|
+
//# sourceMappingURL=incident.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"incident.d.ts","sourceRoot":"","sources":["../../src/agent/incident.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAE/D,0EAA0E;AAC1E,eAAO,MAAM,SAAS,yBAAe,CAAA;AAErC,4DAA4D;AAC5D,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,QAAQ,GAAG,aAAa,CAAA;AAEhE,gDAAgD;AAChD,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,UAAU,CAAA;AAEhD;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,kDAAkD;IAClD,EAAE,EAAE,MAAM,CAAA;IACV,2DAA2D;IAC3D,OAAO,EAAE,MAAM,CAAA;IACf,mDAAmD;IACnD,QAAQ,EAAE,MAAM,CAAA;IAChB,2EAA2E;IAC3E,OAAO,EAAE,OAAO,CAAA;CACjB;AAED;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,gEAAgE;IAChE,EAAE,EAAE,MAAM,CAAA;IACV,6DAA6D;IAC7D,WAAW,EAAE,MAAM,CAAA;IACnB,KAAK,EAAE,SAAS,CAAA;IAChB,YAAY,EAAE,gBAAgB,GAAG,IAAI,CAAA;IACrC,iFAAiF;IACjF,UAAU,EAAE,MAAM,CAAA;IAClB,mEAAmE;IACnE,cAAc,EAAE,MAAM,CAAA;IACtB,oEAAoE;IACpE,OAAO,EAAE,MAAM,CAAA;IACf,mDAAmD;IACnD,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,mDAAmD;IACnD,WAAW,EAAE,MAAM,CAAA;IACnB,wCAAwC;IACxC,eAAe,EAAE,eAAe,CAAA;IAChC,MAAM,EAAE,cAAc,CAAA;IACtB,iFAAiF;IACjF,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,aAAa,EAAE,eAAe,GAAG,IAAI,CAAA;IACrC,yEAAyE;IACzE,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,yEAAyE;IACzE,QAAQ,EAAE,eAAe,EAAE,CAAA;IAC3B,yCAAyC;IACzC,OAAO,EAAE,cAAc,EAAE,CAAA;CAC1B;AAED,yFAAyF;AACzF,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAA;IACd,EAAE,EAAE,MAAM,CAAA;IACV,EAAE,EAAE,OAAO,CAAA;IACX,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE;IACxC,KAAK,EAAE,SAAS,CAAA;IAChB,YAAY,EAAE,gBAAgB,GAAG,IAAI,CAAA;IACrC,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;CACtC,GAAG,MAAM,CAIT;AAQD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAS3D;AAED,8CAA8C;AAC9C,MAAM,WAAW,gBAAgB;IAC/B,8EAA8E;IAC9E,QAAQ,EAAE,MAAM,CAAA;IAChB,kEAAkE;IAClE,oBAAoB,EAAE,MAAM,CAAA;CAC7B;AAED,eAAO,MAAM,kBAAkB,EAAE,gBAGhC,CAAA;AAED,+DAA+D;AAC/D,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,eAAe,CAAA;IACtB,+DAA+D;IAC/D,MAAM,EAAE,OAAO,CAAA;CAChB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,MAAM,EACnB,0BAA0B,EAAE,OAAO,EACnC,GAAG,GAAE,gBAAqC,GACzC,kBAAkB,CAQpB;AAED,+EAA+E;AAC/E,wBAAgB,kBAAkB,CAChC,CAAC,EAAE,eAAe,EAClB,CAAC,EAAE,eAAe,GACjB,eAAe,CAGjB;AAyBD;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,GAAE,MAAM,EAAO,GAAG,MAAM,CAezE;AAID,gFAAgF;AAChF,MAAM,WAAW,aAAa;IAC5B,wEAAwE;IACxE,KAAK,EAAE,MAAM,CAAA;IACb,iDAAiD;IACjD,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC/B,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACtC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACrC;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,GACV,aAAa,CAoBf;AAMD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,aAAa,EAAE,KAAK,SAAU,GAAG,MAAM,CAYhF"}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Incident core (Epic #195, Phase 2).
|
|
4
|
+
*
|
|
5
|
+
* Pure decision logic for the incident-reporting layer that sits on top of the
|
|
6
|
+
* turn-trace watchdog (Phase 1). Given a stall incident, this module decides:
|
|
7
|
+
* - its fingerprint (what makes two stalls "the same problem"),
|
|
8
|
+
* - whether/how to escalate a repeat (quiet → repeat → recommend-investigate),
|
|
9
|
+
* - how to scrub evidence before any of it can leave the machine,
|
|
10
|
+
* - how to summarise a set of incidents into a digest line/report.
|
|
11
|
+
*
|
|
12
|
+
* Like turn-trace.ts and orphan-wake.ts, this file performs NO IO and imports
|
|
13
|
+
* no runtime code (only types, which are erased), so every rule here is
|
|
14
|
+
* unit-testable without a filesystem, a clock, or a live session. The store
|
|
15
|
+
* (incident-store.ts) owns persistence and calls into these functions.
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.DEFAULT_ESCALATION = exports.REDACTION = void 0;
|
|
19
|
+
exports.computeFingerprint = computeFingerprint;
|
|
20
|
+
exports.fingerprintHash = fingerprintHash;
|
|
21
|
+
exports.decideEscalation = decideEscalation;
|
|
22
|
+
exports.maxEscalationLevel = maxEscalationLevel;
|
|
23
|
+
exports.scrubText = scrubText;
|
|
24
|
+
exports.summarizeIncidents = summarizeIncidents;
|
|
25
|
+
exports.formatDigestLine = formatDigestLine;
|
|
26
|
+
/** Placeholder substituted for any redacted span in a scrubbed export. */
|
|
27
|
+
exports.REDACTION = '‹redacted›';
|
|
28
|
+
/**
|
|
29
|
+
* Compute the dedup fingerprint. Two stalls are "the same problem" when they
|
|
30
|
+
* hit the same pipeline stage with the same failure attribution on the same CLI
|
|
31
|
+
* version — a new CLI version is treated as a distinct problem so a regression
|
|
32
|
+
* introduced by an upgrade does not silently fold into an old fingerprint.
|
|
33
|
+
*/
|
|
34
|
+
function computeFingerprint(input) {
|
|
35
|
+
const cls = input.failureClass ?? 'none';
|
|
36
|
+
const ver = normalizeVersion(input.cliVersion);
|
|
37
|
+
return `${input.stage}:${cls}:${ver}`;
|
|
38
|
+
}
|
|
39
|
+
/** Normalise a version string to a fingerprint-safe token; empty → 'unknown'. */
|
|
40
|
+
function normalizeVersion(v) {
|
|
41
|
+
const t = (v ?? '').trim();
|
|
42
|
+
return t.length > 0 ? t : 'unknown';
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Deterministic, dependency-free hash (FNV-1a → base36). Used to derive a short
|
|
46
|
+
* filesystem-safe prefix for an incident id from its fingerprint. Not used for
|
|
47
|
+
* anything security-sensitive — only stable naming/dedup.
|
|
48
|
+
*/
|
|
49
|
+
function fingerprintHash(fingerprint) {
|
|
50
|
+
let h = 0x811c9dc5;
|
|
51
|
+
for (let i = 0; i < fingerprint.length; i++) {
|
|
52
|
+
h ^= fingerprint.charCodeAt(i);
|
|
53
|
+
// FNV prime multiply, kept in 32-bit range via Math.imul.
|
|
54
|
+
h = Math.imul(h, 0x01000193);
|
|
55
|
+
}
|
|
56
|
+
// >>> 0 to interpret as unsigned before stringifying.
|
|
57
|
+
return (h >>> 0).toString(36);
|
|
58
|
+
}
|
|
59
|
+
exports.DEFAULT_ESCALATION = {
|
|
60
|
+
windowMs: 24 * 60 * 60 * 1000, // 24 h
|
|
61
|
+
investigateThreshold: 3,
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Decide how to escalate given the running occurrence count for a fingerprint
|
|
65
|
+
* and whether the user was already notified at 'investigate' level.
|
|
66
|
+
*
|
|
67
|
+
* - 1st occurrence → quiet notify (one short heads-up)
|
|
68
|
+
* - repeats below threshold → silent increment (no re-notify)
|
|
69
|
+
* - Nth (N ≥ threshold), once → recommend investigation (louder notify)
|
|
70
|
+
* - after that → silent (already recommended)
|
|
71
|
+
*
|
|
72
|
+
* Pure: the caller supplies the count and prior-notify state.
|
|
73
|
+
*/
|
|
74
|
+
function decideEscalation(occurrences, alreadyInvestigateNotified, cfg = exports.DEFAULT_ESCALATION) {
|
|
75
|
+
if (occurrences <= 1) {
|
|
76
|
+
return { level: 'quiet', notify: true };
|
|
77
|
+
}
|
|
78
|
+
if (occurrences >= cfg.investigateThreshold && !alreadyInvestigateNotified) {
|
|
79
|
+
return { level: 'investigate', notify: true };
|
|
80
|
+
}
|
|
81
|
+
return { level: 'repeat', notify: false };
|
|
82
|
+
}
|
|
83
|
+
/** The maximum of two escalation levels (for tracking the highest reached). */
|
|
84
|
+
function maxEscalationLevel(a, b) {
|
|
85
|
+
const rank = { quiet: 0, repeat: 1, investigate: 2 };
|
|
86
|
+
return rank[a] >= rank[b] ? a : b;
|
|
87
|
+
}
|
|
88
|
+
// ─── Scrubbing ──────────────────────────────────────────────────────────────
|
|
89
|
+
/**
|
|
90
|
+
* Patterns for content that must never leave the machine, independent of any
|
|
91
|
+
* caller-supplied literals. Conservative by design: each pattern targets a
|
|
92
|
+
* recognisable secret/PII shape, not free text, to avoid mangling useful
|
|
93
|
+
* diagnostic content. Order matters — more specific patterns run first.
|
|
94
|
+
*/
|
|
95
|
+
const SECRET_PATTERNS = [
|
|
96
|
+
// Telegram bot token: <digits>:<35+ token chars>
|
|
97
|
+
/\b\d{6,}:[A-Za-z0-9_-]{30,}\b/g,
|
|
98
|
+
// Anthropic / OpenAI style keys: sk-... (and sk-ant-...)
|
|
99
|
+
/\bsk-[A-Za-z0-9-]{16,}\b/g,
|
|
100
|
+
// Slack tokens: xox[baprs]-...
|
|
101
|
+
/\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g,
|
|
102
|
+
// GitHub tokens: ghp_/gho_/ghu_/ghs_/ghr_ + 36 chars
|
|
103
|
+
/\bgh[pousr]_[A-Za-z0-9]{20,}\b/g,
|
|
104
|
+
// Bearer tokens in headers
|
|
105
|
+
/\bBearer\s+[A-Za-z0-9._-]{16,}\b/gi,
|
|
106
|
+
// Email addresses
|
|
107
|
+
/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g,
|
|
108
|
+
];
|
|
109
|
+
/**
|
|
110
|
+
* Scrub text destined for an exported/persisted artifact. Removes:
|
|
111
|
+
* 1. every caller-supplied literal (chat ids, usernames) — matched verbatim,
|
|
112
|
+
* 2. recognisable secret/PII shapes (tokens, keys, emails).
|
|
113
|
+
*
|
|
114
|
+
* Literals are escaped before use so a value like `a.b` cannot act as a regex.
|
|
115
|
+
* Pure and idempotent enough for repeated application (placeholder is inert).
|
|
116
|
+
*/
|
|
117
|
+
function scrubText(text, redactions = []) {
|
|
118
|
+
if (!text)
|
|
119
|
+
return text;
|
|
120
|
+
let out = text;
|
|
121
|
+
// Caller literals first (longest first so a longer id containing a shorter
|
|
122
|
+
// one is fully removed rather than partially).
|
|
123
|
+
const literals = [...new Set(redactions.filter((r) => r && r.length >= 2))].sort((a, b) => b.length - a.length);
|
|
124
|
+
for (const lit of literals) {
|
|
125
|
+
out = out.split(lit).join(exports.REDACTION);
|
|
126
|
+
}
|
|
127
|
+
for (const re of SECRET_PATTERNS) {
|
|
128
|
+
out = out.replace(re, exports.REDACTION);
|
|
129
|
+
}
|
|
130
|
+
return out;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Summarise the incidents whose most-recent occurrence lands in
|
|
134
|
+
* `[now - sinceMs, now]`. Pure — the store supplies the manifests it read.
|
|
135
|
+
*/
|
|
136
|
+
function summarizeIncidents(manifests, sinceMs, now) {
|
|
137
|
+
const cutoff = now - sinceMs;
|
|
138
|
+
const summary = {
|
|
139
|
+
total: 0,
|
|
140
|
+
occurrences: 0,
|
|
141
|
+
openCount: 0,
|
|
142
|
+
byStage: {},
|
|
143
|
+
byFailureClass: {},
|
|
144
|
+
byCliVersion: {},
|
|
145
|
+
};
|
|
146
|
+
for (const m of manifests) {
|
|
147
|
+
if (m.lastAt < cutoff)
|
|
148
|
+
continue;
|
|
149
|
+
summary.total++;
|
|
150
|
+
summary.occurrences += m.occurrences;
|
|
151
|
+
if (m.status === 'open')
|
|
152
|
+
summary.openCount++;
|
|
153
|
+
bump(summary.byStage, m.stage);
|
|
154
|
+
bump(summary.byFailureClass, m.failureClass ?? 'none');
|
|
155
|
+
bump(summary.byCliVersion, m.cliVersion || 'unknown');
|
|
156
|
+
}
|
|
157
|
+
return summary;
|
|
158
|
+
}
|
|
159
|
+
function bump(rec, key) {
|
|
160
|
+
rec[key] = (rec[key] ?? 0) + 1;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* One-line, human-friendly digest. Zero-incident periods produce a short "all
|
|
164
|
+
* clear" line rather than nothing, so the digest itself is a liveness signal.
|
|
165
|
+
*/
|
|
166
|
+
function formatDigestLine(summary, label = 'daily') {
|
|
167
|
+
if (summary.total === 0) {
|
|
168
|
+
return `🩺 ${label} digest: no turn-trace incidents`;
|
|
169
|
+
}
|
|
170
|
+
const stages = Object.entries(summary.byStage)
|
|
171
|
+
.sort((a, b) => b[1] - a[1])
|
|
172
|
+
.map(([s, n]) => `${s}×${n}`)
|
|
173
|
+
.join(', ');
|
|
174
|
+
return (`🩺 ${label} digest: ${summary.total} incident(s), ` +
|
|
175
|
+
`${summary.occurrences} occurrence(s), ${summary.openCount} open — ${stages}`);
|
|
176
|
+
}
|
|
177
|
+
//# sourceMappingURL=incident.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"incident.js","sourceRoot":"","sources":["../../src/agent/incident.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;GAcG;;;AAgFH,gDAQC;AAaD,0CASC;AAiCD,4CAYC;AAGD,gDAMC;AAiCD,8BAeC;AAoBD,gDAwBC;AAUD,4CAYC;AAlRD,0EAA0E;AAC7D,QAAA,SAAS,GAAG,YAAY,CAAA;AAqErC;;;;;GAKG;AACH,SAAgB,kBAAkB,CAAC,KAIlC;IACC,MAAM,GAAG,GAAG,KAAK,CAAC,YAAY,IAAI,MAAM,CAAA;IACxC,MAAM,GAAG,GAAG,gBAAgB,CAAC,KAAK,CAAC,UAAU,CAAC,CAAA;IAC9C,OAAO,GAAG,KAAK,CAAC,KAAK,IAAI,GAAG,IAAI,GAAG,EAAE,CAAA;AACvC,CAAC;AAED,iFAAiF;AACjF,SAAS,gBAAgB,CAAC,CAA4B;IACpD,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;IAC1B,OAAO,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;AACrC,CAAC;AAED;;;;GAIG;AACH,SAAgB,eAAe,CAAC,WAAmB;IACjD,IAAI,CAAC,GAAG,UAAU,CAAA;IAClB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,WAAW,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAC5C,CAAC,IAAI,WAAW,CAAC,UAAU,CAAC,CAAC,CAAC,CAAA;QAC9B,0DAA0D;QAC1D,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,UAAU,CAAC,CAAA;IAC9B,CAAC;IACD,sDAAsD;IACtD,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAA;AAC/B,CAAC;AAUY,QAAA,kBAAkB,GAAqB;IAClD,QAAQ,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE,OAAO;IACtC,oBAAoB,EAAE,CAAC;CACxB,CAAA;AASD;;;;;;;;;;GAUG;AACH,SAAgB,gBAAgB,CAC9B,WAAmB,EACnB,0BAAmC,EACnC,MAAwB,0BAAkB;IAE1C,IAAI,WAAW,IAAI,CAAC,EAAE,CAAC;QACrB,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAA;IACzC,CAAC;IACD,IAAI,WAAW,IAAI,GAAG,CAAC,oBAAoB,IAAI,CAAC,0BAA0B,EAAE,CAAC;QAC3E,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,IAAI,EAAE,CAAA;IAC/C,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,CAAA;AAC3C,CAAC;AAED,+EAA+E;AAC/E,SAAgB,kBAAkB,CAChC,CAAkB,EAClB,CAAkB;IAElB,MAAM,IAAI,GAAoC,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,CAAA;IACrF,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;AACnC,CAAC;AAED,+EAA+E;AAE/E;;;;;GAKG;AACH,MAAM,eAAe,GAAa;IAChC,iDAAiD;IACjD,gCAAgC;IAChC,yDAAyD;IACzD,2BAA2B;IAC3B,+BAA+B;IAC/B,mCAAmC;IACnC,qDAAqD;IACrD,iCAAiC;IACjC,2BAA2B;IAC3B,oCAAoC;IACpC,kBAAkB;IAClB,qDAAqD;CACtD,CAAA;AAED;;;;;;;GAOG;AACH,SAAgB,SAAS,CAAC,IAAY,EAAE,aAAuB,EAAE;IAC/D,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAA;IACtB,IAAI,GAAG,GAAG,IAAI,CAAA;IACd,2EAA2E;IAC3E,+CAA+C;IAC/C,MAAM,QAAQ,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAC9E,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,CAC9B,CAAA;IACD,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;QAC3B,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,iBAAS,CAAC,CAAA;IACtC,CAAC;IACD,KAAK,MAAM,EAAE,IAAI,eAAe,EAAE,CAAC;QACjC,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,EAAE,EAAE,iBAAS,CAAC,CAAA;IAClC,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAgBD;;;GAGG;AACH,SAAgB,kBAAkB,CAChC,SAA6B,EAC7B,OAAe,EACf,GAAW;IAEX,MAAM,MAAM,GAAG,GAAG,GAAG,OAAO,CAAA;IAC5B,MAAM,OAAO,GAAkB;QAC7B,KAAK,EAAE,CAAC;QACR,WAAW,EAAE,CAAC;QACd,SAAS,EAAE,CAAC;QACZ,OAAO,EAAE,EAAE;QACX,cAAc,EAAE,EAAE;QAClB,YAAY,EAAE,EAAE;KACjB,CAAA;IACD,KAAK,MAAM,CAAC,IAAI,SAAS,EAAE,CAAC;QAC1B,IAAI,CAAC,CAAC,MAAM,GAAG,MAAM;YAAE,SAAQ;QAC/B,OAAO,CAAC,KAAK,EAAE,CAAA;QACf,OAAO,CAAC,WAAW,IAAI,CAAC,CAAC,WAAW,CAAA;QACpC,IAAI,CAAC,CAAC,MAAM,KAAK,MAAM;YAAE,OAAO,CAAC,SAAS,EAAE,CAAA;QAC5C,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,CAAA;QAC9B,IAAI,CAAC,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC,YAAY,IAAI,MAAM,CAAC,CAAA;QACtD,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC,UAAU,IAAI,SAAS,CAAC,CAAA;IACvD,CAAC;IACD,OAAO,OAAO,CAAA;AAChB,CAAC;AAED,SAAS,IAAI,CAAC,GAA2B,EAAE,GAAW;IACpD,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAA;AAChC,CAAC;AAED;;;GAGG;AACH,SAAgB,gBAAgB,CAAC,OAAsB,EAAE,KAAK,GAAG,OAAO;IACtE,IAAI,OAAO,CAAC,KAAK,KAAK,CAAC,EAAE,CAAC;QACxB,OAAO,MAAM,KAAK,kCAAkC,CAAA;IACtD,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC;SAC3C,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;SAC3B,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;SAC5B,IAAI,CAAC,IAAI,CAAC,CAAA;IACb,OAAO,CACL,MAAM,KAAK,YAAY,OAAO,CAAC,KAAK,gBAAgB;QACpD,GAAG,OAAO,CAAC,WAAW,mBAAmB,OAAO,CAAC,SAAS,WAAW,MAAM,EAAE,CAC9E,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recovery executor (Epic #195, Phase 3b).
|
|
3
|
+
*
|
|
4
|
+
* This is the orchestration core that turns a watchdog-detected stall into an
|
|
5
|
+
* actual recovery attempt. It runs in the AGENT RUNNER process — the only place
|
|
6
|
+
* that owns the live control surfaces (session stdin, restart, safe-mode) — but
|
|
7
|
+
* it is written as a PURE function with every side effect injected, so tests
|
|
8
|
+
* drive the full decision path without touching a real session or CLI.
|
|
9
|
+
*
|
|
10
|
+
* Trust + safety model (why the pieces are split the way they are):
|
|
11
|
+
* - The classify step (triage.ts) treats screen text as UNTRUSTED data and
|
|
12
|
+
* validates the model reply against a CLOSED schema.
|
|
13
|
+
* - The decide step (recovery-policy.ts) clamps the proposed action to a
|
|
14
|
+
* per-stage whitelist and enforces a per-turn budget + cooldown.
|
|
15
|
+
* - This execute step maps the *already-validated, already-whitelisted* action
|
|
16
|
+
* to an injected effect. A missing effect is a no-op failure, never a guess.
|
|
17
|
+
*
|
|
18
|
+
* Everything is gated by `autoRecover`: when it is off the executor does nothing
|
|
19
|
+
* but report that it was skipped, so the whole feature ships dark. Safe-mode
|
|
20
|
+
* auto-fallback is deliberately NOT routed through here — it is a reversible
|
|
21
|
+
* backend flip the runner applies on hard PTY failure regardless of this flag.
|
|
22
|
+
*/
|
|
23
|
+
import { type TriageSpawn } from './triage';
|
|
24
|
+
import { type RecoveryAction, type BudgetState, type BudgetConfig } from './recovery-policy';
|
|
25
|
+
import type { RecoveryOutcome } from './incident';
|
|
26
|
+
/**
|
|
27
|
+
* The concrete side effects the executor may invoke. Every method is optional:
|
|
28
|
+
* an action whose effect is not provided is reported as unsupported rather than
|
|
29
|
+
* silently succeeding. Keystroke effects (esc/enter/…) are delivered to the PTY
|
|
30
|
+
* wrapper via the control channel; the restart/backend effects act on the
|
|
31
|
+
* session/receiver. All may be async.
|
|
32
|
+
*/
|
|
33
|
+
export interface RecoveryEffects {
|
|
34
|
+
esc?(): Promise<void> | void;
|
|
35
|
+
escEsc?(): Promise<void> | void;
|
|
36
|
+
enter?(): Promise<void> | void;
|
|
37
|
+
selectOption?(option: number): Promise<void> | void;
|
|
38
|
+
bridgeMenu?(): Promise<void> | void;
|
|
39
|
+
redeliverForward?(): Promise<void> | void;
|
|
40
|
+
restartSession?(): Promise<void> | void;
|
|
41
|
+
restartReceiver?(): Promise<void> | void;
|
|
42
|
+
fallbackHeadless?(): Promise<void> | void;
|
|
43
|
+
/**
|
|
44
|
+
* C1: re-inject the last user message so the user does not have to retype it
|
|
45
|
+
* after a recovery. The implementation MUST guard against duplicates — resend
|
|
46
|
+
* only when the stalled turn produced no output — and returns whether it
|
|
47
|
+
* actually resent. The executor never forces a resend; it only asks.
|
|
48
|
+
*/
|
|
49
|
+
resendLast?(): Promise<boolean> | boolean;
|
|
50
|
+
}
|
|
51
|
+
/** What the watchdog hands the executor for one stalled turn. */
|
|
52
|
+
export interface RecoveryRequest {
|
|
53
|
+
/** Incident id the outcome is recorded against (opaque to the executor). */
|
|
54
|
+
incidentId: string;
|
|
55
|
+
agentId: string;
|
|
56
|
+
chatId: string;
|
|
57
|
+
sessionId: string;
|
|
58
|
+
/** Pipeline stage that stalled (drives the action whitelist). */
|
|
59
|
+
stage: string;
|
|
60
|
+
failureClass: string | null;
|
|
61
|
+
/** Identifies the turn for budget accounting (resets budget when it changes). */
|
|
62
|
+
turnKey: string;
|
|
63
|
+
}
|
|
64
|
+
export interface RecoveryDeps {
|
|
65
|
+
/** Master gate. When false the executor does nothing but report `skipped`. */
|
|
66
|
+
autoRecover: boolean;
|
|
67
|
+
effects: RecoveryEffects;
|
|
68
|
+
now: () => number;
|
|
69
|
+
/** Per-turn budget accounting, injected so it can persist across calls. */
|
|
70
|
+
budget: {
|
|
71
|
+
get(turnKey: string): BudgetState;
|
|
72
|
+
set(state: BudgetState): void;
|
|
73
|
+
config?: BudgetConfig;
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Optional local `claude -p` triage. When omitted, the executor falls back to
|
|
77
|
+
* the deterministic per-stage default action (still whitelist-checked).
|
|
78
|
+
*/
|
|
79
|
+
triageSpawn?: TriageSpawn;
|
|
80
|
+
/**
|
|
81
|
+
* Collect scrubbed evidence for triage (screen snapshot / status text). The
|
|
82
|
+
* caller is responsible for scrubbing; the executor passes it through as data.
|
|
83
|
+
*/
|
|
84
|
+
gatherEvidence?: () => Promise<{
|
|
85
|
+
screenText?: string;
|
|
86
|
+
statusText?: string;
|
|
87
|
+
} | null>;
|
|
88
|
+
/** C1 gate: attempt a guarded resend after a successful unblocking action. */
|
|
89
|
+
resendAfterRecover?: boolean;
|
|
90
|
+
/** Optional structured logger. */
|
|
91
|
+
log?: (msg: string, meta?: Record<string, unknown>) => void;
|
|
92
|
+
}
|
|
93
|
+
/** Full result of one recovery attempt (richer than the persisted schema). */
|
|
94
|
+
export interface RecoveryResult {
|
|
95
|
+
incidentId: string;
|
|
96
|
+
stage: string;
|
|
97
|
+
/** The action actually taken (may be clamped to notify-only). */
|
|
98
|
+
action: RecoveryAction;
|
|
99
|
+
option?: number;
|
|
100
|
+
/** True if an effect was invoked (notify-only / skip / clamp are false). */
|
|
101
|
+
executed: boolean;
|
|
102
|
+
/** True if the invoked effect completed without throwing. */
|
|
103
|
+
ok: boolean;
|
|
104
|
+
reason: string;
|
|
105
|
+
/** True if the last user message was resent (C1). */
|
|
106
|
+
resent: boolean;
|
|
107
|
+
at: number;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Run one recovery attempt. ALWAYS resolves (never throws): any failure degrades
|
|
111
|
+
* to a safe, recorded outcome. The returned RecoveryResult is what the caller
|
|
112
|
+
* persists to the incident bundle and may surface to the user.
|
|
113
|
+
*/
|
|
114
|
+
export declare function runRecovery(req: RecoveryRequest, deps: RecoveryDeps): Promise<RecoveryResult>;
|
|
115
|
+
/** Map a full result to the compact schema persisted in the incident bundle. */
|
|
116
|
+
export declare function toRecoveryOutcome(r: RecoveryResult): RecoveryOutcome;
|
|
117
|
+
//# sourceMappingURL=recovery-executor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recovery-executor.d.ts","sourceRoot":"","sources":["../../src/agent/recovery-executor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Recovery executor (Epic #195, Phase 3b).
|
|
4
|
+
*
|
|
5
|
+
* This is the orchestration core that turns a watchdog-detected stall into an
|
|
6
|
+
* actual recovery attempt. It runs in the AGENT RUNNER process — the only place
|
|
7
|
+
* that owns the live control surfaces (session stdin, restart, safe-mode) — but
|
|
8
|
+
* it is written as a PURE function with every side effect injected, so tests
|
|
9
|
+
* drive the full decision path without touching a real session or CLI.
|
|
10
|
+
*
|
|
11
|
+
* Trust + safety model (why the pieces are split the way they are):
|
|
12
|
+
* - The classify step (triage.ts) treats screen text as UNTRUSTED data and
|
|
13
|
+
* validates the model reply against a CLOSED schema.
|
|
14
|
+
* - The decide step (recovery-policy.ts) clamps the proposed action to a
|
|
15
|
+
* per-stage whitelist and enforces a per-turn budget + cooldown.
|
|
16
|
+
* - This execute step maps the *already-validated, already-whitelisted* action
|
|
17
|
+
* to an injected effect. A missing effect is a no-op failure, never a guess.
|
|
18
|
+
*
|
|
19
|
+
* Everything is gated by `autoRecover`: when it is off the executor does nothing
|
|
20
|
+
* but report that it was skipped, so the whole feature ships dark. Safe-mode
|
|
21
|
+
* auto-fallback is deliberately NOT routed through here — it is a reversible
|
|
22
|
+
* backend flip the runner applies on hard PTY failure regardless of this flag.
|
|
23
|
+
*/
|
|
24
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
+
exports.runRecovery = runRecovery;
|
|
26
|
+
exports.toRecoveryOutcome = toRecoveryOutcome;
|
|
27
|
+
const triage_1 = require("./triage");
|
|
28
|
+
const recovery_policy_1 = require("./recovery-policy");
|
|
29
|
+
const ACTION_EFFECT = {
|
|
30
|
+
esc: 'esc',
|
|
31
|
+
'esc-esc': 'escEsc',
|
|
32
|
+
enter: 'enter',
|
|
33
|
+
'select-option': 'selectOption',
|
|
34
|
+
'bridge-menu': 'bridgeMenu',
|
|
35
|
+
'redeliver-forward': 'redeliverForward',
|
|
36
|
+
'restart-session': 'restartSession',
|
|
37
|
+
'restart-receiver': 'restartReceiver',
|
|
38
|
+
'fallback-headless': 'fallbackHeadless',
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Actions after which a guarded resend of the last user message makes sense: the
|
|
42
|
+
* ones that unblock a claude turn which was still waiting for input. Delivery/
|
|
43
|
+
* transport actions (redeliver-forward, restart-receiver) already move the
|
|
44
|
+
* pending output themselves, so a resend there would double-submit.
|
|
45
|
+
*/
|
|
46
|
+
const RESEND_ELIGIBLE = new Set([
|
|
47
|
+
'esc',
|
|
48
|
+
'esc-esc',
|
|
49
|
+
'enter',
|
|
50
|
+
'select-option',
|
|
51
|
+
'bridge-menu',
|
|
52
|
+
'restart-session',
|
|
53
|
+
'fallback-headless',
|
|
54
|
+
]);
|
|
55
|
+
/**
|
|
56
|
+
* Run one recovery attempt. ALWAYS resolves (never throws): any failure degrades
|
|
57
|
+
* to a safe, recorded outcome. The returned RecoveryResult is what the caller
|
|
58
|
+
* persists to the incident bundle and may surface to the user.
|
|
59
|
+
*/
|
|
60
|
+
async function runRecovery(req, deps) {
|
|
61
|
+
const at = deps.now();
|
|
62
|
+
const base = { incidentId: req.incidentId, stage: req.stage, at };
|
|
63
|
+
// Master gate: feature ships dark. Detection/incident/notify already ran in
|
|
64
|
+
// the caller; here we simply do nothing and say so.
|
|
65
|
+
if (!deps.autoRecover) {
|
|
66
|
+
return { ...base, action: 'notify-only', executed: false, ok: true, reason: 'skipped: autoRecover disabled', resent: false };
|
|
67
|
+
}
|
|
68
|
+
// 1) Evidence → 2) triage (classify) → 3) policy (decide). None of these act.
|
|
69
|
+
let verdict = null;
|
|
70
|
+
if (deps.triageSpawn) {
|
|
71
|
+
let bundle = { stage: req.stage, failureClass: req.failureClass };
|
|
72
|
+
try {
|
|
73
|
+
const ev = deps.gatherEvidence ? await deps.gatherEvidence() : null;
|
|
74
|
+
if (ev)
|
|
75
|
+
bundle = { ...bundle, screenText: ev.screenText, statusText: ev.statusText };
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// Evidence gathering is best-effort; triage can still classify from stage.
|
|
79
|
+
}
|
|
80
|
+
verdict = await (0, triage_1.runTriage)({ spawn: deps.triageSpawn, bundle });
|
|
81
|
+
}
|
|
82
|
+
const plan = (0, recovery_policy_1.selectRecoveryAction)({ stage: req.stage, failureClass: req.failureClass, verdict });
|
|
83
|
+
// notify-only never consumes budget and invokes no effect — the caller owns
|
|
84
|
+
// the user-facing notice, so the executor just records the decision.
|
|
85
|
+
if (plan.action === 'notify-only' || plan.action === 'none') {
|
|
86
|
+
return { ...base, action: 'notify-only', executed: false, ok: true, reason: plan.reason, resent: false };
|
|
87
|
+
}
|
|
88
|
+
// 4) Budget + cooldown. An actionable plan blocked by budget clamps to
|
|
89
|
+
// notify-only (recorded with the action it wanted) and consumes nothing more.
|
|
90
|
+
const cfg = deps.budget.config ?? recovery_policy_1.DEFAULT_BUDGET;
|
|
91
|
+
const verdictBudget = (0, recovery_policy_1.checkBudget)(deps.budget.get(req.turnKey), req.turnKey, at, cfg);
|
|
92
|
+
if (!verdictBudget.allowed) {
|
|
93
|
+
deps.budget.set(verdictBudget.next);
|
|
94
|
+
return {
|
|
95
|
+
...base,
|
|
96
|
+
action: 'notify-only',
|
|
97
|
+
executed: false,
|
|
98
|
+
ok: true,
|
|
99
|
+
reason: `clamped: ${verdictBudget.reason} (wanted ${plan.action})`,
|
|
100
|
+
resent: false,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
deps.budget.set(verdictBudget.next);
|
|
104
|
+
// 5) Execute the whitelisted action via its injected effect.
|
|
105
|
+
const effectKey = ACTION_EFFECT[plan.action];
|
|
106
|
+
const effect = effectKey ? deps.effects[effectKey] : undefined;
|
|
107
|
+
const result = {
|
|
108
|
+
...base,
|
|
109
|
+
action: plan.action,
|
|
110
|
+
option: plan.option,
|
|
111
|
+
executed: false,
|
|
112
|
+
ok: false,
|
|
113
|
+
reason: plan.reason,
|
|
114
|
+
resent: false,
|
|
115
|
+
};
|
|
116
|
+
if (!effect) {
|
|
117
|
+
result.reason = `unsupported: no effect for ${plan.action}`;
|
|
118
|
+
deps.log?.('recovery: unsupported action', { action: plan.action, stage: req.stage });
|
|
119
|
+
return result;
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
if (plan.action === 'select-option' && typeof plan.option === 'number') {
|
|
123
|
+
await effect(plan.option);
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
await effect();
|
|
127
|
+
}
|
|
128
|
+
result.executed = true;
|
|
129
|
+
result.ok = true;
|
|
130
|
+
deps.log?.('recovery: executed', { action: plan.action, stage: req.stage, incidentId: req.incidentId });
|
|
131
|
+
}
|
|
132
|
+
catch (err) {
|
|
133
|
+
result.executed = true;
|
|
134
|
+
result.ok = false;
|
|
135
|
+
result.reason = `effect threw: ${err.message}`;
|
|
136
|
+
deps.log?.('recovery: effect failed', { action: plan.action, error: err.message });
|
|
137
|
+
return result;
|
|
138
|
+
}
|
|
139
|
+
// 6) C1 guarded resend. Only after a successful unblocking action, and only if
|
|
140
|
+
// the effect implementation confirms the turn produced no output.
|
|
141
|
+
if (deps.resendAfterRecover && RESEND_ELIGIBLE.has(plan.action) && deps.effects.resendLast) {
|
|
142
|
+
try {
|
|
143
|
+
result.resent = Boolean(await deps.effects.resendLast());
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
result.resent = false;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return result;
|
|
150
|
+
}
|
|
151
|
+
/** Map a full result to the compact schema persisted in the incident bundle. */
|
|
152
|
+
function toRecoveryOutcome(r) {
|
|
153
|
+
const bits = [r.reason];
|
|
154
|
+
if (r.resent)
|
|
155
|
+
bits.push('resent');
|
|
156
|
+
return {
|
|
157
|
+
action: r.option !== undefined ? `${r.action}:${r.option}` : r.action,
|
|
158
|
+
at: r.at,
|
|
159
|
+
// `ok` means "no failure occurred" — true for a successful execution AND for
|
|
160
|
+
// a benign non-action (skipped / notify-only / budget-clamp), false only when
|
|
161
|
+
// an effect was attempted and threw or was unsupported. Using r.ok directly
|
|
162
|
+
// (which is already false only on those failure paths) keeps notify-only
|
|
163
|
+
// decisions from being miscounted as failed recoveries in the digest.
|
|
164
|
+
ok: r.ok,
|
|
165
|
+
detail: bits.join('; '),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
//# sourceMappingURL=recovery-executor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recovery-executor.js","sourceRoot":"","sources":["../../src/agent/recovery-executor.ts"],"names":[],"mappings":"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"}
|