@0xmaxma/claude-gateway 1.3.25 → 1.3.31
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 +47 -1
- 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 +44 -0
- package/dist/agent/runner.d.ts.map +1 -1
- package/dist/agent/runner.js +272 -2
- 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/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/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/session/process.d.ts +18 -0
- package/dist/session/process.d.ts.map +1 -1
- package/dist/session/process.js +61 -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 +21 -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
package/README.md
CHANGED
|
@@ -293,6 +293,52 @@ Controls the Claude subprocess backend for all non-app agents.
|
|
|
293
293
|
|
|
294
294
|
This setting is hot-reloadable — new sessions pick it up without a restart.
|
|
295
295
|
|
|
296
|
+
### `gateway.selfHealing.autoRecover`
|
|
297
|
+
|
|
298
|
+
Opt-in self-healing for the turn-trace watchdog (Epic #195). When a turn stalls, the gateway always detects it, logs a scrubbed incident, and notifies the affected chat. This flag additionally controls whether the gateway may *act* on a stall.
|
|
299
|
+
|
|
300
|
+
| Value | Behaviour |
|
|
301
|
+
|-------|-----------|
|
|
302
|
+
| `false` *(default)* | Detection + incident logging + notification only — no automatic action |
|
|
303
|
+
| `true` | The watchdog may run a whitelisted recovery for a stalled turn: a keystroke into the TUI (esc / enter / arrow / menu selection), a session restart, a reversible safe-mode fallback to the headless backend, and — after a successful unblock — a guarded resend of the last message (only if the turn produced no output, so it is never double-submitted) |
|
|
304
|
+
|
|
305
|
+
Recovery actions are clamped to a per-stage whitelist and a per-turn budget, and any local triage treats the on-screen text as untrusted data validated against a closed schema. Safe-mode auto-fallback on a hard PTY failure is independent of this flag (it is always reversible and never presses keys). In-memory only — a gateway restart re-reads your real config.
|
|
306
|
+
|
|
307
|
+
```json
|
|
308
|
+
{
|
|
309
|
+
"gateway": {
|
|
310
|
+
"selfHealing": {
|
|
311
|
+
"autoRecover": true
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### `gateway.bind`
|
|
318
|
+
|
|
319
|
+
Network interface the HTTP/WebSocket server binds to. Defaults to `127.0.0.1` (localhost-only), so the dashboard and API are **not** exposed to the local network out of the box. Set to `0.0.0.0` to listen on all interfaces (for example when a containerized reverse proxy needs to reach the gateway). The `GATEWAY_BIND` environment variable, when set, takes precedence over this field.
|
|
320
|
+
|
|
321
|
+
```json
|
|
322
|
+
{
|
|
323
|
+
"gateway": {
|
|
324
|
+
"bind": "127.0.0.1"
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
> **⚠️ Upgrade note:** the default bind changed from `0.0.0.0` to `127.0.0.1` (configVersion 1.0.13). To avoid silently cutting off external access, the config migrator is **behavior-preserving**: whenever it upgrades a config that never set `gateway.bind`, it pins `bind` to `0.0.0.0` and logs a one-time warning, so a deployment that was reachable from another host stays reachable. This applies to *any* upgraded config with no `bind` key — including one already stamped `1.0.13` that never received a bind (an earlier version gated this on `< 1.0.13` and left such configs stuck on the `127.0.0.1` default). New installs (no prior config, so no migration runs) keep the secure `127.0.0.1` default. If you *want* localhost-only after upgrading, set `gateway.bind` to `127.0.0.1` explicitly (or the `GATEWAY_BIND` env var).
|
|
330
|
+
|
|
331
|
+
### Terminal Viewer — interactive terminal mode
|
|
332
|
+
|
|
333
|
+
The dashboard's **Terminal Viewer** opens read-only (a live mirror of the PTY). A toggle in the top-right of the viewer switches it into an **interactive terminal**: keystrokes typed into the panel — printable characters, Enter, arrows, Ctrl-combos, Esc — are streamed into the live PTY, and the panel title changes to reflect the active mode. This is a per-browser client-side choice (Issue #201); there is no server config flag to enable it.
|
|
334
|
+
|
|
335
|
+
Because interactive mode turns a read-only view into a remote-write surface, access is protected upstream rather than by a feature flag:
|
|
336
|
+
|
|
337
|
+
- **Authentication** — the WebSocket requires a valid dashboard ticket or API key.
|
|
338
|
+
- **`gateway.bind`** — the gateway binds to `127.0.0.1` (localhost) by default, so the dashboard is not reachable from the network out of the box. Expose a non-loopback bind (`0.0.0.0`) **only** behind a trusted authenticating reverse proxy.
|
|
339
|
+
|
|
340
|
+
Inbound frames are always bounded (text-only, size-capped) and are dropped for headless sessions (no PTY).
|
|
341
|
+
|
|
296
342
|
### `gateway.api.keys`
|
|
297
343
|
|
|
298
344
|
Each key has a `key` string (supports `${ENV_VAR}` interpolation), an optional `description`, and an `agents` field — either an array of agent IDs or `"*"` for full access. Keys support both `Authorization: Bearer` and `X-Api-Key` headers.
|
|
@@ -485,7 +531,7 @@ The gateway proxies `/app/:name/:portName/*` to the app containers. Two env vars
|
|
|
485
531
|
|
|
486
532
|
| Env var | Default | Description |
|
|
487
533
|
|---------|---------|-------------|
|
|
488
|
-
| `GATEWAY_BIND` | `
|
|
534
|
+
| `GATEWAY_BIND` | `127.0.0.1` | Gateway HTTP listen address. Overrides the `gateway.bind` config field when set. Defaults to localhost-only; set to `0.0.0.0` when a **containerized** reverse proxy (Caddy, nginx in Docker) needs to reach the gateway across container boundaries. A **host-network** proxy (Traefik on host) can keep the localhost default. |
|
|
489
535
|
| `DOCKER_HOST` | _(system default)_ | Docker socket/TCP address. When set to `tcp://host:port` (e.g. DinD), the gateway automatically uses the host extracted from `DOCKER_HOST` to proxy to app containers instead of `127.0.0.1`. |
|
|
490
536
|
|
|
491
537
|
Example Caddyfile for apps behind Caddy in Docker:
|
package/config.template.json
CHANGED
|
@@ -10,10 +10,11 @@
|
|
|
10
10
|
"claude.dangerouslySkipPermissions"
|
|
11
11
|
]
|
|
12
12
|
},
|
|
13
|
-
"configVersion": "1.0.
|
|
13
|
+
"configVersion": "1.0.14",
|
|
14
14
|
"gateway": {
|
|
15
15
|
"logDir": "~/.claude-gateway/logs",
|
|
16
16
|
"timezone": "Asia/Bangkok",
|
|
17
|
+
"bind": "127.0.0.1",
|
|
17
18
|
"models": [
|
|
18
19
|
{ "id": "claude-fable-5[1m]", "label": "Fable 5 (1M)", "alias": "fable[1m]", "contextWindow": 1000000 },
|
|
19
20
|
{ "id": "claude-opus-4-8[1m]", "label": "Opus 4.8 (1M)", "alias": "opus[1m]", "contextWindow": 1000000 },
|
|
@@ -44,7 +45,10 @@
|
|
|
44
45
|
"cleanupHour": 0,
|
|
45
46
|
"cleanupTimezone": "UTC"
|
|
46
47
|
},
|
|
47
|
-
"headless": true
|
|
48
|
+
"headless": true,
|
|
49
|
+
"selfHealing": {
|
|
50
|
+
"autoRecover": false
|
|
51
|
+
}
|
|
48
52
|
},
|
|
49
53
|
"agents": [
|
|
50
54
|
{
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Incident store (Epic #195, Phase 2).
|
|
3
|
+
*
|
|
4
|
+
* Persists turn-trace stall incidents as on-disk bundles under
|
|
5
|
+
* `<dir>/<id>/`, deduplicates repeats by fingerprint within a rolling window,
|
|
6
|
+
* escalates according to the pure rules in incident.ts, and prunes old bundles.
|
|
7
|
+
*
|
|
8
|
+
* All IO is funnelled through an injected `fs` and `now()` so the store is
|
|
9
|
+
* fully unit-testable against an in-memory filesystem (no real disk, no real
|
|
10
|
+
* clock). The pure decision logic (fingerprint, escalation, scrub, digest)
|
|
11
|
+
* lives in incident.ts; this file only orchestrates it and touches disk.
|
|
12
|
+
*
|
|
13
|
+
* A bundle is captured BEFORE any recovery mutates state (Phase 3 recovery runs
|
|
14
|
+
* after record() returns), so evidence reflects the wedged turn, not its
|
|
15
|
+
* aftermath. Every text artifact is scrubbed on the way in.
|
|
16
|
+
*/
|
|
17
|
+
import type { TurnIncident, TurnIncidentEvidence } from './turn-trace';
|
|
18
|
+
import { type EscalationConfig, type EscalationDecision, type EscalationLevel, type IncidentManifest, type DigestSummary, type RecoveryOutcome } from './incident';
|
|
19
|
+
/** Minimal filesystem surface the store needs. Node's `fs` satisfies it. */
|
|
20
|
+
export interface IncidentFsApi {
|
|
21
|
+
mkdirSync(path: string, opts: {
|
|
22
|
+
recursive: boolean;
|
|
23
|
+
}): void;
|
|
24
|
+
writeFileSync(path: string, data: string): void;
|
|
25
|
+
readFileSync(path: string, enc: 'utf8'): string;
|
|
26
|
+
existsSync(path: string): boolean;
|
|
27
|
+
readdirSync(path: string): string[];
|
|
28
|
+
rmSync(path: string, opts: {
|
|
29
|
+
recursive: boolean;
|
|
30
|
+
force: boolean;
|
|
31
|
+
}): void;
|
|
32
|
+
}
|
|
33
|
+
export interface IncidentStoreDeps {
|
|
34
|
+
/** Base directory for incident bundles (e.g. ~/.claude-gateway/incidents). */
|
|
35
|
+
dir: string;
|
|
36
|
+
fs: IncidentFsApi;
|
|
37
|
+
/** Clock injection — epoch ms. */
|
|
38
|
+
now: () => number;
|
|
39
|
+
/** Transport name recorded on each incident ('telegram' | 'discord' | ...). */
|
|
40
|
+
channel: string;
|
|
41
|
+
/** Gateway version, recorded for context (not part of the fingerprint). */
|
|
42
|
+
gatewayVersion: string;
|
|
43
|
+
/** Lazily resolves the Claude CLI version (part of the fingerprint). */
|
|
44
|
+
getCliVersion: () => string;
|
|
45
|
+
/** Retention: bundles whose lastAt is older than this are pruned (ms). */
|
|
46
|
+
retentionMs?: number;
|
|
47
|
+
/** Retention: hard cap on bundle count (oldest pruned first). */
|
|
48
|
+
maxIncidents?: number;
|
|
49
|
+
/** Escalation thresholds. */
|
|
50
|
+
escalation?: EscalationConfig;
|
|
51
|
+
/** Max occurrence samples retained per incident. */
|
|
52
|
+
maxSamples?: number;
|
|
53
|
+
}
|
|
54
|
+
/** Outcome of recording one stall occurrence. */
|
|
55
|
+
export interface RecordResult {
|
|
56
|
+
id: string;
|
|
57
|
+
fingerprint: string;
|
|
58
|
+
occurrences: number;
|
|
59
|
+
escalation: EscalationDecision;
|
|
60
|
+
/** True when this created a fresh bundle (vs. folding into an existing one). */
|
|
61
|
+
isNew: boolean;
|
|
62
|
+
manifest: IncidentManifest;
|
|
63
|
+
}
|
|
64
|
+
export interface IncidentStore {
|
|
65
|
+
record(incident: TurnIncident, evidence?: TurnIncidentEvidence): RecordResult;
|
|
66
|
+
/** Load a manifest by id, or null if absent/corrupt. */
|
|
67
|
+
get(id: string): IncidentManifest | null;
|
|
68
|
+
/** Record that the user was notified at a level (dedupes re-notifying). */
|
|
69
|
+
markNotified(id: string, level: EscalationLevel, at?: number): void;
|
|
70
|
+
/**
|
|
71
|
+
* Append a recovery outcome to an incident bundle (Phase 3b). The runner
|
|
72
|
+
* executes recovery and returns the outcome; the receiver persists it here so
|
|
73
|
+
* the bundle records what was attempted and whether it worked. Capped like
|
|
74
|
+
* samples so a flapping stall cannot grow the manifest unbounded.
|
|
75
|
+
*/
|
|
76
|
+
appendRecovery(id: string, outcome: RecoveryOutcome): void;
|
|
77
|
+
/** Link a filed GitHub issue to an incident fingerprint. */
|
|
78
|
+
linkIssue(id: string, issueNumber: number): void;
|
|
79
|
+
/** Mark an incident resolved (stops it folding new occurrences). */
|
|
80
|
+
resolve(id: string): void;
|
|
81
|
+
/** Prune bundles past retention / over the count cap. Returns pruned ids. */
|
|
82
|
+
prune(): string[];
|
|
83
|
+
/** Digest summary over the last `sinceMs`. */
|
|
84
|
+
digest(sinceMs: number): DigestSummary;
|
|
85
|
+
/** All manifests currently on disk (unsorted). */
|
|
86
|
+
list(): IncidentManifest[];
|
|
87
|
+
}
|
|
88
|
+
export declare function createIncidentStore(deps: IncidentStoreDeps): IncidentStore;
|
|
89
|
+
//# sourceMappingURL=incident-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"incident-store.d.ts","sourceRoot":"","sources":["../../src/agent/incident-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Incident store (Epic #195, Phase 2).
|
|
4
|
+
*
|
|
5
|
+
* Persists turn-trace stall incidents as on-disk bundles under
|
|
6
|
+
* `<dir>/<id>/`, deduplicates repeats by fingerprint within a rolling window,
|
|
7
|
+
* escalates according to the pure rules in incident.ts, and prunes old bundles.
|
|
8
|
+
*
|
|
9
|
+
* All IO is funnelled through an injected `fs` and `now()` so the store is
|
|
10
|
+
* fully unit-testable against an in-memory filesystem (no real disk, no real
|
|
11
|
+
* clock). The pure decision logic (fingerprint, escalation, scrub, digest)
|
|
12
|
+
* lives in incident.ts; this file only orchestrates it and touches disk.
|
|
13
|
+
*
|
|
14
|
+
* A bundle is captured BEFORE any recovery mutates state (Phase 3 recovery runs
|
|
15
|
+
* after record() returns), so evidence reflects the wedged turn, not its
|
|
16
|
+
* aftermath. Every text artifact is scrubbed on the way in.
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.createIncidentStore = createIncidentStore;
|
|
20
|
+
const incident_1 = require("./incident");
|
|
21
|
+
const DEFAULT_RETENTION_MS = 30 * 24 * 60 * 60 * 1000; // 30 days
|
|
22
|
+
const DEFAULT_MAX_INCIDENTS = 500;
|
|
23
|
+
const DEFAULT_MAX_SAMPLES = 20;
|
|
24
|
+
const MANIFEST = 'manifest.json';
|
|
25
|
+
function createIncidentStore(deps) {
|
|
26
|
+
const { dir, fs, now, channel, gatewayVersion, getCliVersion, retentionMs = DEFAULT_RETENTION_MS, maxIncidents = DEFAULT_MAX_INCIDENTS, escalation = incident_1.DEFAULT_ESCALATION, maxSamples = DEFAULT_MAX_SAMPLES, } = deps;
|
|
27
|
+
// In-memory index of currently-open incidents by fingerprint, so dedup does
|
|
28
|
+
// not rescan every bundle on each stall. Rebuilt from disk on construction so
|
|
29
|
+
// dedup survives a process restart.
|
|
30
|
+
const openByFingerprint = new Map(); // fingerprint → id
|
|
31
|
+
function bundleDir(id) {
|
|
32
|
+
return join(dir, id);
|
|
33
|
+
}
|
|
34
|
+
function manifestPath(id) {
|
|
35
|
+
return join(bundleDir(id), MANIFEST);
|
|
36
|
+
}
|
|
37
|
+
function ensureBaseDir() {
|
|
38
|
+
try {
|
|
39
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// Best-effort; a subsequent write will surface a real failure.
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function readManifest(id) {
|
|
46
|
+
try {
|
|
47
|
+
const raw = fs.readFileSync(manifestPath(id), 'utf8');
|
|
48
|
+
const m = JSON.parse(raw);
|
|
49
|
+
// Defensive: a manifest missing its id is treated as corrupt.
|
|
50
|
+
if (!m || typeof m.id !== 'string' || typeof m.fingerprint !== 'string') {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
return m;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function writeManifest(m) {
|
|
60
|
+
fs.mkdirSync(bundleDir(m.id), { recursive: true });
|
|
61
|
+
fs.writeFileSync(manifestPath(m.id), JSON.stringify(m, null, 2));
|
|
62
|
+
}
|
|
63
|
+
function listIds() {
|
|
64
|
+
try {
|
|
65
|
+
return fs.readdirSync(dir).filter((name) => {
|
|
66
|
+
// Only directories that contain a manifest count as bundles.
|
|
67
|
+
return fs.existsSync(manifestPath(name));
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// ── Rebuild the open-fingerprint index from disk on construction. ──────────
|
|
75
|
+
ensureBaseDir();
|
|
76
|
+
for (const id of listIds()) {
|
|
77
|
+
const m = readManifest(id);
|
|
78
|
+
if (m && m.status === 'open') {
|
|
79
|
+
// If two open bundles share a fingerprint (shouldn't happen), keep the
|
|
80
|
+
// most recent so folding continues on the freshest episode.
|
|
81
|
+
const existing = openByFingerprint.get(m.fingerprint);
|
|
82
|
+
if (!existing) {
|
|
83
|
+
openByFingerprint.set(m.fingerprint, m.id);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
const prev = readManifest(existing);
|
|
87
|
+
if (!prev || m.lastAt >= prev.lastAt) {
|
|
88
|
+
openByFingerprint.set(m.fingerprint, m.id);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function scrubEvidence(evidence, redactions) {
|
|
94
|
+
const files = {};
|
|
95
|
+
if (!evidence)
|
|
96
|
+
return files;
|
|
97
|
+
if (evidence.artifacts && evidence.artifacts.length > 0) {
|
|
98
|
+
files['typing-artifacts.txt'] = (0, incident_1.scrubText)(evidence.artifacts.join('\n'), redactions);
|
|
99
|
+
}
|
|
100
|
+
if (evidence.statusText) {
|
|
101
|
+
files['status.txt'] = (0, incident_1.scrubText)(evidence.statusText, redactions);
|
|
102
|
+
}
|
|
103
|
+
if (evidence.errorText) {
|
|
104
|
+
files['error.txt'] = (0, incident_1.scrubText)(evidence.errorText, redactions);
|
|
105
|
+
}
|
|
106
|
+
return files;
|
|
107
|
+
}
|
|
108
|
+
function record(incident, evidence) {
|
|
109
|
+
const at = incident.at || now();
|
|
110
|
+
const cliVersion = safe(getCliVersion) || 'unknown';
|
|
111
|
+
const fingerprint = (0, incident_1.computeFingerprint)({
|
|
112
|
+
stage: incident.stage,
|
|
113
|
+
failureClass: incident.failureClass,
|
|
114
|
+
cliVersion,
|
|
115
|
+
});
|
|
116
|
+
const sample = {
|
|
117
|
+
at,
|
|
118
|
+
sinceMs: incident.sinceMs,
|
|
119
|
+
budgetMs: incident.budgetMs,
|
|
120
|
+
midTurn: incident.midTurn,
|
|
121
|
+
};
|
|
122
|
+
// Try to fold into an existing open incident within the dedup window.
|
|
123
|
+
const existingId = openByFingerprint.get(fingerprint);
|
|
124
|
+
if (existingId) {
|
|
125
|
+
const existing = readManifest(existingId);
|
|
126
|
+
if (existing &&
|
|
127
|
+
existing.status === 'open' &&
|
|
128
|
+
at - existing.lastAt <= escalation.windowMs) {
|
|
129
|
+
existing.occurrences += 1;
|
|
130
|
+
existing.lastAt = at;
|
|
131
|
+
existing.samples = pushCapped(existing.samples, sample, maxSamples);
|
|
132
|
+
const escalated = (0, incident_1.decideEscalation)(existing.occurrences, existing.notifiedLevel === 'investigate', escalation);
|
|
133
|
+
existing.escalationLevel = (0, incident_1.maxEscalationLevel)(existing.escalationLevel, escalated.level);
|
|
134
|
+
writeManifest(existing);
|
|
135
|
+
return {
|
|
136
|
+
id: existing.id,
|
|
137
|
+
fingerprint,
|
|
138
|
+
occurrences: existing.occurrences,
|
|
139
|
+
escalation: escalated,
|
|
140
|
+
isNew: false,
|
|
141
|
+
manifest: existing,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
// Stale or resolved → drop the stale index entry and fall through to new.
|
|
145
|
+
openByFingerprint.delete(fingerprint);
|
|
146
|
+
}
|
|
147
|
+
// Fresh incident bundle.
|
|
148
|
+
const id = `${(0, incident_1.fingerprintHash)(fingerprint)}-${at}`;
|
|
149
|
+
const escalated = (0, incident_1.decideEscalation)(1, false, escalation);
|
|
150
|
+
const manifest = {
|
|
151
|
+
id,
|
|
152
|
+
fingerprint,
|
|
153
|
+
stage: incident.stage,
|
|
154
|
+
failureClass: incident.failureClass,
|
|
155
|
+
cliVersion,
|
|
156
|
+
gatewayVersion,
|
|
157
|
+
channel,
|
|
158
|
+
firstAt: at,
|
|
159
|
+
lastAt: at,
|
|
160
|
+
occurrences: 1,
|
|
161
|
+
escalationLevel: escalated.level,
|
|
162
|
+
status: 'open',
|
|
163
|
+
notifiedAt: null,
|
|
164
|
+
notifiedLevel: null,
|
|
165
|
+
githubIssue: null,
|
|
166
|
+
recovery: [],
|
|
167
|
+
samples: [sample],
|
|
168
|
+
};
|
|
169
|
+
// Redact the chat id and any known handle from all exported text.
|
|
170
|
+
const redactions = [incident.chatId];
|
|
171
|
+
writeManifest(manifest);
|
|
172
|
+
const files = scrubEvidence(evidence, redactions);
|
|
173
|
+
for (const [name, content] of Object.entries(files)) {
|
|
174
|
+
try {
|
|
175
|
+
fs.writeFileSync(join(bundleDir(id), name), content);
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
// A failed artifact write must not lose the incident itself.
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
openByFingerprint.set(fingerprint, id);
|
|
182
|
+
// Enforce the count cap opportunistically on creation.
|
|
183
|
+
prune();
|
|
184
|
+
return { id, fingerprint, occurrences: 1, escalation: escalated, isNew: true, manifest };
|
|
185
|
+
}
|
|
186
|
+
function get(id) {
|
|
187
|
+
return readManifest(id);
|
|
188
|
+
}
|
|
189
|
+
function markNotified(id, level, atArg) {
|
|
190
|
+
const m = readManifest(id);
|
|
191
|
+
if (!m)
|
|
192
|
+
return;
|
|
193
|
+
m.notifiedAt = atArg ?? now();
|
|
194
|
+
m.notifiedLevel = m.notifiedLevel
|
|
195
|
+
? (0, incident_1.maxEscalationLevel)(m.notifiedLevel, level)
|
|
196
|
+
: level;
|
|
197
|
+
writeManifest(m);
|
|
198
|
+
}
|
|
199
|
+
function appendRecovery(id, outcome) {
|
|
200
|
+
const m = readManifest(id);
|
|
201
|
+
if (!m)
|
|
202
|
+
return;
|
|
203
|
+
m.recovery = pushCapped(m.recovery ?? [], outcome, maxSamples);
|
|
204
|
+
writeManifest(m);
|
|
205
|
+
}
|
|
206
|
+
function linkIssue(id, issueNumber) {
|
|
207
|
+
const m = readManifest(id);
|
|
208
|
+
if (!m)
|
|
209
|
+
return;
|
|
210
|
+
m.githubIssue = issueNumber;
|
|
211
|
+
writeManifest(m);
|
|
212
|
+
}
|
|
213
|
+
function resolve(id) {
|
|
214
|
+
const m = readManifest(id);
|
|
215
|
+
if (!m)
|
|
216
|
+
return;
|
|
217
|
+
m.status = 'resolved';
|
|
218
|
+
writeManifest(m);
|
|
219
|
+
if (openByFingerprint.get(m.fingerprint) === id) {
|
|
220
|
+
openByFingerprint.delete(m.fingerprint);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
function list() {
|
|
224
|
+
const out = [];
|
|
225
|
+
for (const id of listIds()) {
|
|
226
|
+
const m = readManifest(id);
|
|
227
|
+
if (m)
|
|
228
|
+
out.push(m);
|
|
229
|
+
}
|
|
230
|
+
return out;
|
|
231
|
+
}
|
|
232
|
+
function prune() {
|
|
233
|
+
const manifests = list();
|
|
234
|
+
const cutoff = now() - retentionMs;
|
|
235
|
+
const pruned = [];
|
|
236
|
+
// Age-based prune.
|
|
237
|
+
const survivors = [];
|
|
238
|
+
for (const m of manifests) {
|
|
239
|
+
if (m.lastAt < cutoff) {
|
|
240
|
+
removeBundle(m.id);
|
|
241
|
+
pruned.push(m.id);
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
survivors.push(m);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
// Count-cap prune: drop oldest (by lastAt) beyond the cap.
|
|
248
|
+
if (survivors.length > maxIncidents) {
|
|
249
|
+
survivors.sort((a, b) => a.lastAt - b.lastAt); // oldest first
|
|
250
|
+
const overflow = survivors.length - maxIncidents;
|
|
251
|
+
for (let i = 0; i < overflow; i++) {
|
|
252
|
+
removeBundle(survivors[i].id);
|
|
253
|
+
pruned.push(survivors[i].id);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
// Keep the open-fingerprint index consistent with what remains.
|
|
257
|
+
for (const id of pruned) {
|
|
258
|
+
for (const [fp, mapped] of openByFingerprint) {
|
|
259
|
+
if (mapped === id)
|
|
260
|
+
openByFingerprint.delete(fp);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return pruned;
|
|
264
|
+
}
|
|
265
|
+
function removeBundle(id) {
|
|
266
|
+
try {
|
|
267
|
+
fs.rmSync(bundleDir(id), { recursive: true, force: true });
|
|
268
|
+
}
|
|
269
|
+
catch {
|
|
270
|
+
// Best-effort.
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
function digest(sinceMs) {
|
|
274
|
+
return (0, incident_1.summarizeIncidents)(list(), sinceMs, now());
|
|
275
|
+
}
|
|
276
|
+
return { record, get, markNotified, appendRecovery, linkIssue, resolve, prune, digest, list };
|
|
277
|
+
}
|
|
278
|
+
/** Path join without importing `path` (keeps the module dependency-light). */
|
|
279
|
+
function join(...parts) {
|
|
280
|
+
return parts
|
|
281
|
+
.map((p, i) => (i === 0 ? p.replace(/\/+$/, '') : p.replace(/^\/+|\/+$/g, '')))
|
|
282
|
+
.filter((p) => p.length > 0)
|
|
283
|
+
.join('/');
|
|
284
|
+
}
|
|
285
|
+
/** Push onto a capped ring, dropping the oldest when full. */
|
|
286
|
+
function pushCapped(arr, item, cap) {
|
|
287
|
+
const next = [...arr, item];
|
|
288
|
+
return next.length > cap ? next.slice(next.length - cap) : next;
|
|
289
|
+
}
|
|
290
|
+
/** Call a provider defensively; any throw collapses to ''. */
|
|
291
|
+
function safe(fn) {
|
|
292
|
+
try {
|
|
293
|
+
return fn();
|
|
294
|
+
}
|
|
295
|
+
catch {
|
|
296
|
+
return '';
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
//# sourceMappingURL=incident-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"incident-store.js","sourceRoot":"","sources":["../../src/agent/incident-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -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":"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"}
|