instar 1.3.664 → 1.3.665
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/core/IdleErrorClassifier.d.ts +44 -0
- package/dist/core/IdleErrorClassifier.d.ts.map +1 -0
- package/dist/core/IdleErrorClassifier.js +87 -0
- package/dist/core/IdleErrorClassifier.js.map +1 -0
- package/dist/core/SessionManager.d.ts +10 -0
- package/dist/core/SessionManager.d.ts.map +1 -1
- package/dist/core/SessionManager.js +40 -3
- package/dist/core/SessionManager.js.map +1 -1
- package/dist/core/paneTail.d.ts +29 -0
- package/dist/core/paneTail.d.ts.map +1 -0
- package/dist/core/paneTail.js +52 -0
- package/dist/core/paneTail.js.map +1 -0
- package/dist/monitoring/StuckSignatureClassifier.d.ts.map +1 -1
- package/dist/monitoring/StuckSignatureClassifier.js +5 -3
- package/dist/monitoring/StuckSignatureClassifier.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +3 -3
- package/upgrades/1.3.665.md +68 -0
- package/upgrades/side-effects/idle-error-tailgate-corroboration.md +65 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* paneTail — ONE definition of "the live tail" of a tmux pane capture, shared by
|
|
3
|
+
* every pane-signal consumer (StuckSignatureClassifier, IdleErrorClassifier, …).
|
|
4
|
+
*
|
|
5
|
+
* Extracted (CMT-1785) so the tail-gating + line-lead-stripping logic is not COPIED
|
|
6
|
+
* per consumer (the copies were already drifting: tailLines 12 / 20 / 8). A single
|
|
7
|
+
* definition is the only way "what counts as the live tail" stays consistent.
|
|
8
|
+
*
|
|
9
|
+
* Pure, dependency-free, allocation-cheap. No tmux, no I/O — it operates on an
|
|
10
|
+
* already-captured string.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* The last `tailLines` NON-EMPTY (non-whitespace-after-trim) lines of a capture,
|
|
14
|
+
* AS LINES (trimmed). The "live tail" — the region right above the prompt. Callers
|
|
15
|
+
* that need a joined string do `.join('\n')`.
|
|
16
|
+
*/
|
|
17
|
+
export declare function liveTail(text: string, tailLines: number): string[];
|
|
18
|
+
/**
|
|
19
|
+
* Strip a leading run of ANSI SGR escapes + Claude Code lead glyphs + whitespace
|
|
20
|
+
* (any interleaving) so the first *content* token of the line is exposed. Idempotent.
|
|
21
|
+
*/
|
|
22
|
+
export declare function stripLineLead(line: string): string;
|
|
23
|
+
/**
|
|
24
|
+
* True iff the line's stripped lead contained at least one known Claude lead glyph —
|
|
25
|
+
* i.e. the line was rendered as a Claude TUI line-lead (the discriminator that
|
|
26
|
+
* separates a Claude-emitted frame from raw column-0 content / prose).
|
|
27
|
+
*/
|
|
28
|
+
export declare function wasGlyphLed(line: string): boolean;
|
|
29
|
+
//# sourceMappingURL=paneTail.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paneTail.d.ts","sourceRoot":"","sources":["../../src/core/paneTail.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAqBH;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,CAGlE;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGlD;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAGjD"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* paneTail — ONE definition of "the live tail" of a tmux pane capture, shared by
|
|
3
|
+
* every pane-signal consumer (StuckSignatureClassifier, IdleErrorClassifier, …).
|
|
4
|
+
*
|
|
5
|
+
* Extracted (CMT-1785) so the tail-gating + line-lead-stripping logic is not COPIED
|
|
6
|
+
* per consumer (the copies were already drifting: tailLines 12 / 20 / 8). A single
|
|
7
|
+
* definition is the only way "what counts as the live tail" stays consistent.
|
|
8
|
+
*
|
|
9
|
+
* Pure, dependency-free, allocation-cheap. No tmux, no I/O — it operates on an
|
|
10
|
+
* already-captured string.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Claude Code line-lead glyphs (the bullet / tree-branch / box-drawing characters
|
|
14
|
+
* the TUI prints at the start of a rendered line) plus the box-drawing range.
|
|
15
|
+
* These are CONTENT (Unicode), not ANSI escapes — tmux `capture-pane -p` preserves
|
|
16
|
+
* them. The set is the static contract pinned by IdleErrorClassifier's fixture tests.
|
|
17
|
+
*/
|
|
18
|
+
const LEAD_GLYPH_CLASS = '\\u23FA\\u23BF\\u273B\\u25CF\\u2502\\u00B7\\u276F\\u203A\\u2570\\u256D\\u2717\\u2500-\\u257F';
|
|
19
|
+
/** A leading ANSI SGR colour run (absent under `-p`, stripped defensively in case a
|
|
20
|
+
* future caller captures with `-e`). */
|
|
21
|
+
const ANSI_SGR = '\\x1b\\[[0-9;]*m';
|
|
22
|
+
/** Matches the leading run of (ANSI SGR | whitespace | lead-glyph), in any interleaving. */
|
|
23
|
+
const LEAD_RE = new RegExp(`^(?:${ANSI_SGR}|[\\s${LEAD_GLYPH_CLASS}])+`);
|
|
24
|
+
/** True iff the (single) char is one of the known lead glyphs. */
|
|
25
|
+
const GLYPH_RE = new RegExp(`[${LEAD_GLYPH_CLASS}]`);
|
|
26
|
+
/**
|
|
27
|
+
* The last `tailLines` NON-EMPTY (non-whitespace-after-trim) lines of a capture,
|
|
28
|
+
* AS LINES (trimmed). The "live tail" — the region right above the prompt. Callers
|
|
29
|
+
* that need a joined string do `.join('\n')`.
|
|
30
|
+
*/
|
|
31
|
+
export function liveTail(text, tailLines) {
|
|
32
|
+
const lines = text.split('\n').map((l) => l.trim()).filter(Boolean);
|
|
33
|
+
return tailLines >= 0 ? lines.slice(-tailLines) : lines;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Strip a leading run of ANSI SGR escapes + Claude Code lead glyphs + whitespace
|
|
37
|
+
* (any interleaving) so the first *content* token of the line is exposed. Idempotent.
|
|
38
|
+
*/
|
|
39
|
+
export function stripLineLead(line) {
|
|
40
|
+
const m = LEAD_RE.exec(line);
|
|
41
|
+
return m ? line.slice(m[0].length) : line;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* True iff the line's stripped lead contained at least one known Claude lead glyph —
|
|
45
|
+
* i.e. the line was rendered as a Claude TUI line-lead (the discriminator that
|
|
46
|
+
* separates a Claude-emitted frame from raw column-0 content / prose).
|
|
47
|
+
*/
|
|
48
|
+
export function wasGlyphLed(line) {
|
|
49
|
+
const m = LEAD_RE.exec(line);
|
|
50
|
+
return m ? GLYPH_RE.test(m[0]) : false;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=paneTail.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paneTail.js","sourceRoot":"","sources":["../../src/core/paneTail.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH;;;;;GAKG;AACH,MAAM,gBAAgB,GACpB,8FAA8F,CAAC;AAEjG;yCACyC;AACzC,MAAM,QAAQ,GAAG,kBAAkB,CAAC;AAEpC,4FAA4F;AAC5F,MAAM,OAAO,GAAG,IAAI,MAAM,CAAC,OAAO,QAAQ,QAAQ,gBAAgB,KAAK,CAAC,CAAC;AAEzE,kEAAkE;AAClE,MAAM,QAAQ,GAAG,IAAI,MAAM,CAAC,IAAI,gBAAgB,GAAG,CAAC,CAAC;AAErD;;;;GAIG;AACH,MAAM,UAAU,QAAQ,CAAC,IAAY,EAAE,SAAiB;IACtD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACpE,OAAO,SAAS,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;AAC1D,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC7B,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAC5C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC7B,OAAO,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;AACzC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StuckSignatureClassifier.d.ts","sourceRoot":"","sources":["../../src/monitoring/StuckSignatureClassifier.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;
|
|
1
|
+
{"version":3,"file":"StuckSignatureClassifier.d.ts","sourceRoot":"","sources":["../../src/monitoring/StuckSignatureClassifier.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAKH,MAAM,MAAM,SAAS,GACjB,cAAc,GACd,cAAc,GACd,eAAe,GACf,kBAAkB,CAAC;AAEvB,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,SAAS,CAAC;IAChB,+EAA+E;IAC/E,OAAO,EAAE,MAAM,CAAC;IAChB,qEAAqE;IACrE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AA4CD,gFAAgF;AAChF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAKjE;AAED;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,MAAM,EACf,SAAS,SAAK,GACb,mBAAmB,GAAG,IAAI,CAqD5B"}
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
* incident, and adds tail-gated rate-limit + context-too-long detection.
|
|
31
31
|
*/
|
|
32
32
|
import { classifyWedgeTail } from './ContextWedgeSentinel.js';
|
|
33
|
+
import { liveTail as sharedLiveTail } from '../core/paneTail.js';
|
|
33
34
|
/** Rate-limit / usage-limit pane signatures (the Claude usage-limit form the
|
|
34
35
|
* RateLimitSentinel does NOT auto-handle — it only owns the capacity throttle).
|
|
35
36
|
* Anchored on the STATIVE/blocking forms Claude Code prints when it actually
|
|
@@ -57,10 +58,11 @@ const NORMAL_COMPACTION_TAIL_PATTERNS = [
|
|
|
57
58
|
/compaction[^.\n]{0,20}resumed/i,
|
|
58
59
|
/compaction recovery/i,
|
|
59
60
|
];
|
|
60
|
-
/** The last `tailLines` non-empty, trimmed lines of a capture, joined.
|
|
61
|
+
/** The last `tailLines` non-empty, trimmed lines of a capture, joined.
|
|
62
|
+
* Delegates to the shared paneTail.liveTail (CMT-1785: one definition of the live
|
|
63
|
+
* tail) and joins to preserve this module's byte-identical string-matching behavior. */
|
|
61
64
|
function liveTail(text, tailLines) {
|
|
62
|
-
|
|
63
|
-
return lines.slice(-tailLines).join('\n');
|
|
65
|
+
return sharedLiveTail(text, tailLines).join('\n');
|
|
64
66
|
}
|
|
65
67
|
function anyMatch(text, patterns) {
|
|
66
68
|
for (const p of patterns)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StuckSignatureClassifier.js","sourceRoot":"","sources":["../../src/monitoring/StuckSignatureClassifier.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"StuckSignatureClassifier.js","sourceRoot":"","sources":["../../src/monitoring/StuckSignatureClassifier.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "./builtin-manifest.schema.json",
|
|
3
3
|
"schemaVersion": 1,
|
|
4
|
-
"generatedAt": "2026-06-
|
|
5
|
-
"instarVersion": "1.3.
|
|
4
|
+
"generatedAt": "2026-06-25T18:41:44.708Z",
|
|
5
|
+
"instarVersion": "1.3.665",
|
|
6
6
|
"entryCount": 202,
|
|
7
7
|
"entries": {
|
|
8
8
|
"hook:session-start": {
|
|
@@ -1538,7 +1538,7 @@
|
|
|
1538
1538
|
"type": "subsystem",
|
|
1539
1539
|
"domain": "sessions",
|
|
1540
1540
|
"sourcePath": "src/core/SessionManager.ts",
|
|
1541
|
-
"contentHash": "
|
|
1541
|
+
"contentHash": "d6bacb543ff87514a6a650bce3269c4d6377b1b411ef0f651fdb88d196d11717",
|
|
1542
1542
|
"since": "2025-01-01"
|
|
1543
1543
|
},
|
|
1544
1544
|
"subsystem:auto-updater": {
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: patch -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
CMT-1785: the idle-error detector that decides "did this paused session stall on a transient API
|
|
9
|
+
error (nudge it) or stop normally (leave it)?" is migrated from a brittle bare-substring scan to a
|
|
10
|
+
tail-gated, frame-discriminated SIGNAL.
|
|
11
|
+
|
|
12
|
+
- New pure modules `src/core/paneTail.ts` (shared `liveTail`/`stripLineLead`/`wasGlyphLed` — ONE
|
|
13
|
+
definition of "the live tail", extracted so it is no longer copied per consumer) and
|
|
14
|
+
`src/core/IdleErrorClassifier.ts` (`classifyIdleError`).
|
|
15
|
+
- The old gate `TERMINAL_ERROR_PATTERNS.some(p => recentOutput.includes(p))` (a bare `.includes()`
|
|
16
|
+
over the whole capture) is replaced by `classifyIdleError`, which fires only when a terminal-error
|
|
17
|
+
token sits in the live tail (last 20 non-empty lines) on a Claude-EMITTED error frame — a two-tier
|
|
18
|
+
begins-with rule: **Tier A** the line begins with `API Error:`; **Tier B** the line is glyph-led
|
|
19
|
+
(`⏺`/`⎿`/…) and begins with one of the 11 patterns. The match set is a strict subset of the old
|
|
20
|
+
buffer-wide match, so the change can only SUPPRESS spurious fires, never add one.
|
|
21
|
+
- The idle-error capture is widened 30→45 rows (`RATE_LIMIT_SETTLED_CAPTURE_LINES`): Claude Code's
|
|
22
|
+
input box + footer render 15-25 rows BELOW the error, so a 30-row capture could miss it entirely.
|
|
23
|
+
- `StuckSignatureClassifier` is migrated onto the shared `liveTail` (behavior-preserving via
|
|
24
|
+
`.join('\n')`; its existing test corpus is the characterization guard).
|
|
25
|
+
- A once-per-idle-episode structured record (`{event:'idle-error-classify', result:'fired'|'suppressed', …}`)
|
|
26
|
+
lands in `logs/server.log` so a wave of suppressions on genuine errors (the under-fire risk) is
|
|
27
|
+
observable. Cleared in both the re-arm block and `sessionComplete` (no leak on the death path).
|
|
28
|
+
|
|
29
|
+
The signal still feeds the existing `apiErrorAtIdle` → `rateLimitSentinel` recovery actuator and gains
|
|
30
|
+
no blocking authority (Signal vs Authority).
|
|
31
|
+
|
|
32
|
+
## What to Tell Your User
|
|
33
|
+
|
|
34
|
+
Nothing you need to do. Under the hood, the agent got more reliable at telling a session that genuinely
|
|
35
|
+
stalled on an API error (which it nudges back to life) apart from one that's merely *showing* an old or
|
|
36
|
+
quoted error on screen. The old check was a blunt text search that sometimes kicked off a needless
|
|
37
|
+
recovery on a healthy session, and — more importantly — sometimes read too little of the screen and
|
|
38
|
+
missed a real stall. The new check looks at the live bottom of the screen, reads enough of it to clear
|
|
39
|
+
the input box, and only counts a line the tool actually emitted as an error. Net: fewer false recoveries
|
|
40
|
+
AND fewer missed real ones, so paused sessions get the right treatment faster. No new setting; behavior
|
|
41
|
+
is otherwise unchanged, and it's a one-line revert if ever needed.
|
|
42
|
+
|
|
43
|
+
## Summary of New Capabilities
|
|
44
|
+
|
|
45
|
+
- `paneTail` + `IdleErrorClassifier` — a precise, tail-gated, frame-discriminated idle-error signal
|
|
46
|
+
replacing the bare buffer-wide substring scan; widened capture (45 rows) so the input-box chrome can
|
|
47
|
+
no longer hide a real error; structured once-per-episode observability of fired-vs-suppressed
|
|
48
|
+
decisions. Internal robustness improvement to session-stall recovery; no user-facing surface to
|
|
49
|
+
configure.
|
|
50
|
+
|
|
51
|
+
## Evidence
|
|
52
|
+
|
|
53
|
+
- `tests/unit/IdleErrorClassifier.test.ts` (28) — both sides of every boundary, pinned to REAL captured
|
|
54
|
+
render fixtures: fires on `⏺ API Error:` / ` ⎿ API Error:` / glyph-led network tokens / wrapped
|
|
55
|
+
errors; suppresses stale-scrollback, prose mentions, quoted source literals (the self-collision case),
|
|
56
|
+
a tool's own `Error: …ECONNREFUSED`; the 45-vs-30 input-box-chrome capture test; parametrized over all
|
|
57
|
+
11 patterns; bounded audit fields; paneTail helpers.
|
|
58
|
+
- `tests/integration/idle-error-classifier-production-wiring.test.ts` (7) — the REAL exported
|
|
59
|
+
`TERMINAL_ERROR_PATTERNS` produce correct decisions on real panes; the SessionManager call-site is
|
|
60
|
+
wired to the classifier, the 45-row capture, the dual once-per-episode clears, and the structured record.
|
|
61
|
+
- `tests/e2e/idle-error-classifier-lifecycle.test.ts` (2) — the real SessionManager constructs and the
|
|
62
|
+
`apiErrorAtIdle` recovery handoff is attachable; the production-pattern live decision is correct.
|
|
63
|
+
- `tests/unit/monitoring/StuckSignatureClassifier.test.ts` (13) — unchanged, the characterization guard
|
|
64
|
+
proving the shared-`liveTail` migration is behavior-preserving.
|
|
65
|
+
- `npm run build` + `tsc --noEmit` clean; full unit suite green.
|
|
66
|
+
- Driven by the converged + approved spec; convergence report at
|
|
67
|
+
`docs/specs/reports/idle-error-tailgate-corroboration-convergence.md` (4 rounds; the review caught a
|
|
68
|
+
CRITICAL under-fire-on-the-common-error-form flaw before any code).
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Side-Effects Review — Idle-Error Detection: Tail-Gated, Frame-Discriminated Signal (CMT-1785)
|
|
2
|
+
|
|
3
|
+
Spec: `docs/specs/idle-error-tailgate-corroboration.md` (converged 4 rounds, approved).
|
|
4
|
+
Change: replace the bare `TERMINAL_ERROR_PATTERNS.some(includes)` idle-error detector at the
|
|
5
|
+
`SessionManager` idle path with a tail-gated, frame-discriminated classifier (`IdleErrorClassifier`)
|
|
6
|
+
fed by a shared `paneTail` helper; widen the idle-error capture 30→45 rows; add a once-per-episode
|
|
7
|
+
structured observability record.
|
|
8
|
+
|
|
9
|
+
## 1. Over-block (what legitimate inputs does this reject that it shouldn't?)
|
|
10
|
+
The classifier can SUPPRESS a fire the old bare match would have made — that is the whole point —
|
|
11
|
+
but the risk is suppressing a GENUINE error (under-fire). Mitigated: (a) the two-tier begins-with
|
|
12
|
+
grammar fires on real Claude render forms (`⏺ API Error:` / ` ⎿ API Error:`), pinned by tests using
|
|
13
|
+
the actual captured fixtures; (b) the 45-row capture clears the input-box chrome that pushes the
|
|
14
|
+
error up; (c) the 20-line non-empty tail clears realistic chrome with margin; (d) a missed error
|
|
15
|
+
falls through to the pre-existing 15m idle-kill (slower recovery, never silent loss); (e) the
|
|
16
|
+
`result:'suppressed'` structured record makes a wave of real misses observable.
|
|
17
|
+
|
|
18
|
+
## 2. Under-block (what failure modes does it still miss?)
|
|
19
|
+
- A genuine Claude error rendered in a form that is neither `API Error:`-led nor a glyph-led
|
|
20
|
+
begins-with-pattern line (unknown future render) → missed → 15m idle-kill fallback. The drift
|
|
21
|
+
test fails loudly if the known render fixtures stop matching.
|
|
22
|
+
- A residual false-POSITIVE (accepted): a tool result rendered glyph-led whose line begins with a
|
|
23
|
+
terminal token (e.g. `⎿ fetch failed`) fires. Bounded: the actuator is non-destructive (one wasted
|
|
24
|
+
nudge the verify step proves a no-op). Documented in the spec §5.
|
|
25
|
+
|
|
26
|
+
## 3. Level-of-abstraction fit
|
|
27
|
+
Correct layer: this is a deterministic SIGNAL-emitter (Signal vs Authority), feeding the existing
|
|
28
|
+
`apiErrorAtIdle` → `rateLimitSentinel` recovery actuator. It gains no blocking authority. The shared
|
|
29
|
+
`paneTail` helper is the right home for the tail-gating logic (was being copied per consumer). The
|
|
30
|
+
recovery actuator it feeds is honestly named a deterministic Tier-0 non-destructive self-heal (nudge
|
|
31
|
+
→ verify → escalate-to-notice + zombie-kill veto; no respawn).
|
|
32
|
+
|
|
33
|
+
## 4. Signal vs authority compliance
|
|
34
|
+
Compliant. The classifier returns a boolean SIGNAL; the actuator decides and verifies before any
|
|
35
|
+
action. The match set is a strict subset of the old buffer-wide match (tail + frame ⊂ buffer-wide),
|
|
36
|
+
so the change is monotonic-suppressing — it can only reduce spurious actuations, never add one.
|
|
37
|
+
|
|
38
|
+
## 5. Interactions
|
|
39
|
+
- The `detectRateLimited` branch runs FIRST and is untouched — a server throttle still routes to
|
|
40
|
+
`rateLimitedAtIdle` and never reaches the classifier (pinned by the wiring test's intent; the
|
|
41
|
+
branch order is preserved). The wider 45-row capture is a no-op for `detectRateLimited` (it slices
|
|
42
|
+
its own last ~20 lines).
|
|
43
|
+
- `errorNudgedSessions` (the nudge episode guard) is unchanged; the new `idleErrorClassified` set
|
|
44
|
+
mirrors its full lifecycle (armed once/episode; cleared in BOTH the re-arm block AND
|
|
45
|
+
`sessionComplete` — no leak on the session-death path).
|
|
46
|
+
- `StuckSignatureClassifier` migrates to the shared `liveTail` via `.join('\n')` — behavior-preserving
|
|
47
|
+
(its existing 13-test corpus passes unchanged = the characterization guard).
|
|
48
|
+
|
|
49
|
+
## 6. External surfaces
|
|
50
|
+
None new. No HTTP route, no config, no agent-installed file, no user-facing message. The only new
|
|
51
|
+
output is structured `console.log` records to `logs/server.log` (operator log), once per idle episode,
|
|
52
|
+
credential-free (matchedPattern is a fixed token; no raw pane content is logged).
|
|
53
|
+
|
|
54
|
+
## 7. Multi-machine posture (Cross-Machine Coherence)
|
|
55
|
+
Machine-local BY DESIGN. The classifier reads one local tmux pane on the host running the session —
|
|
56
|
+
no cross-machine state, no generated URL, no replicated surface. The downstream `rateLimitSentinel`
|
|
57
|
+
has a Telegram-notify surface, but the change is monotonic-suppressing, so it can only REDUCE
|
|
58
|
+
user-facing cross-machine notices, never add one. Correct posture, declared explicitly.
|
|
59
|
+
|
|
60
|
+
## 8. Rollback cost
|
|
61
|
+
Single-commit revert: restore the bare `.some(includes)` gate + the 30-row capture, delete
|
|
62
|
+
`IdleErrorClassifier.ts` + `paneTail.ts` + tests + the `idleErrorClassified` field/clears + the
|
|
63
|
+
structured record, and revert the `StuckSignatureClassifier` import (behavior-preserving, mechanical).
|
|
64
|
+
No migration, no durable state, no config. The revert IS the immediate operator action if a future
|
|
65
|
+
Claude render change is ever suspected of under-firing.
|