instar 1.3.665 → 1.3.667
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +64 -9
- package/dist/commands/server.js.map +1 -1
- package/dist/core/MessageSentinel.d.ts +62 -1
- package/dist/core/MessageSentinel.d.ts.map +1 -1
- package/dist/core/MessageSentinel.js +123 -1
- package/dist/core/MessageSentinel.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +9 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/SessionManager.d.ts +9 -0
- package/dist/core/SessionManager.d.ts.map +1 -1
- package/dist/core/SessionManager.js +31 -0
- package/dist/core/SessionManager.js.map +1 -1
- package/dist/core/paneTail.d.ts +9 -0
- package/dist/core/paneTail.d.ts.map +1 -1
- package/dist/core/paneTail.js +12 -0
- package/dist/core/paneTail.js.map +1 -1
- package/dist/core/types.d.ts +11 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/monitoring/PermissionPromptAutoResolver.d.ts +215 -0
- package/dist/monitoring/PermissionPromptAutoResolver.d.ts.map +1 -0
- package/dist/monitoring/PermissionPromptAutoResolver.js +475 -0
- package/dist/monitoring/PermissionPromptAutoResolver.js.map +1 -0
- package/dist/monitoring/PresenceProxy.d.ts.map +1 -1
- package/dist/monitoring/PresenceProxy.js +18 -0
- package/dist/monitoring/PresenceProxy.js.map +1 -1
- package/dist/monitoring/StuckSignatureClassifier.d.ts +1 -1
- package/dist/monitoring/StuckSignatureClassifier.d.ts.map +1 -1
- package/dist/monitoring/StuckSignatureClassifier.js +24 -0
- package/dist/monitoring/StuckSignatureClassifier.js.map +1 -1
- package/dist/monitoring/guardManifest.d.ts.map +1 -1
- package/dist/monitoring/guardManifest.js +15 -0
- package/dist/monitoring/guardManifest.js.map +1 -1
- package/dist/monitoring/guardPosture.d.ts.map +1 -1
- package/dist/monitoring/guardPosture.js +19 -0
- package/dist/monitoring/guardPosture.js.map +1 -1
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +10 -5
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/scripts/lint-guard-manifest.js +1 -0
- package/src/data/builtin-manifest.json +64 -64
- package/upgrades/1.3.666.md +131 -0
- package/upgrades/1.3.667.md +32 -0
- package/upgrades/side-effects/operator-channel-sacred.md +31 -0
- package/upgrades/1.3.665.md +0 -68
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: patch -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
An Instar session could be silently, permanently wedged by an agent-framework approval
|
|
9
|
+
prompt it cannot answer. Claude Code 2.1.176-177 added a hardcoded Bash safety prompt —
|
|
10
|
+
`cd <dir> && <cmd>` combined with a redirect/pipe pops "Do you want to proceed? ❯ 1. Yes
|
|
11
|
+
/ 2. No / Esc to cancel" — that runs BEFORE all permission rules and PermissionRequest
|
|
12
|
+
hooks, so `--dangerously-skip-permissions` and `permissions.allow` cannot suppress it.
|
|
13
|
+
A remote-driven session (Telegram/dashboard) cannot press a terminal key, so it freezes
|
|
14
|
+
while still *looking* busy (PresenceProxy could even report "actively working"). The
|
|
15
|
+
empirically-confirmed reason it bit the fleet: the existing PromptGate/AutoApprover
|
|
16
|
+
pipeline was persisted `enabled:false` on deployed agents (a stale-`false` config wins
|
|
17
|
+
over a newer `true` default), so nothing answered the prompt.
|
|
18
|
+
|
|
19
|
+
- **New `src/monitoring/PermissionPromptAutoResolver.ts`** — an always-on, deterministic,
|
|
20
|
+
zero-LLM safety floor driven once per monitoring tick. It detects a live, focused
|
|
21
|
+
approval menu in a session's tmux tail and sends the approve keystroke itself.
|
|
22
|
+
- **Layer 2 (auto-answer):** requires ≥2 distinct registered prose patterns AND a live
|
|
23
|
+
glyph-led `❯`-cursor-on-an-approve-option line at the genuine bottom of the tail
|
|
24
|
+
while not generating; sends **`Enter`** only (confirm the highlighted "Yes"), with a
|
|
25
|
+
re-capture-before-send check. Because a text capture cannot *perfectly* distinguish a
|
|
26
|
+
real TUI selector from displayed content containing the same characters, this is
|
|
27
|
+
bar-raising mitigation (stated honestly), bounded by Enter-only (a false match is a
|
|
28
|
+
benign empty submit), a per-episode `MAX_ATTEMPTS=3` (reset-on-clear so distinct
|
|
29
|
+
prompts are independent), and a terminal defect.
|
|
30
|
+
- **Layer 3 (broad detector):** a prose-agnostic `detectPersistingMenu` surfaces ANY
|
|
31
|
+
persisting glyph-led menu the auto-answer declines (cursor-not-on-approve, or a future
|
|
32
|
+
prompt whose wording drifts) as a single deduped Attention defect — so nothing is ever
|
|
33
|
+
silently stranded, and it doubles as the prompt-string drift detector.
|
|
34
|
+
- **`src/core/paneTail.ts`** gains a pure `leadGlyphsOf(line)` helper so the detector can
|
|
35
|
+
test for the `❯` selector glyph (U+276F) specifically.
|
|
36
|
+
- **`StuckSignatureClassifier`** gains an `approval-prompt-waiting` kind; **`PresenceProxy`**
|
|
37
|
+
never surfaces it as a user message (the sole user-facing surface is the terminal
|
|
38
|
+
defect), killing the false "actively working" report for a prompt-wedged session.
|
|
39
|
+
- **The floor is unconditional — no persisted `enabled:true` to rot to `false`** (the
|
|
40
|
+
exact trap that caused the bug). The only opt-out is `monitoring.permissionPromptAutoResolver.emergencyDisable`
|
|
41
|
+
(absent ⇒ on). A computed guard-posture key (`guardPosture.ts`) + a runtime
|
|
42
|
+
`guardStatus()` + a `GUARD_MANIFEST` entry make a disabled floor a visible incident in
|
|
43
|
+
`GET /guards`.
|
|
44
|
+
- Existing wedged sessions recover automatically: the server restart that deploys the
|
|
45
|
+
update restarts the monitor loop, which auto-clears any session currently parked on the
|
|
46
|
+
prompt. No new setting, no manual step.
|
|
47
|
+
|
|
48
|
+
The resolver answers a UI-flow prompt only; it never widens what commands are allowed
|
|
49
|
+
(the destructive `dangerous-command-guard` denylist remains an independent guard). The
|
|
50
|
+
auto-approve is accepted under the operator full-machine-access trust model. Converged
|
|
51
|
+
through 6 spec-review rounds (6 internal reviewers + codex/gemini cross-model).
|
|
52
|
+
|
|
53
|
+
CMT-1785: the idle-error detector that decides "did this paused session stall on a transient API
|
|
54
|
+
error (nudge it) or stop normally (leave it)?" is migrated from a brittle bare-substring scan to a
|
|
55
|
+
tail-gated, frame-discriminated SIGNAL.
|
|
56
|
+
|
|
57
|
+
- New pure modules `src/core/paneTail.ts` (shared `liveTail`/`stripLineLead`/`wasGlyphLed` — ONE
|
|
58
|
+
definition of "the live tail", extracted so it is no longer copied per consumer) and
|
|
59
|
+
`src/core/IdleErrorClassifier.ts` (`classifyIdleError`).
|
|
60
|
+
- The old gate `TERMINAL_ERROR_PATTERNS.some(p => recentOutput.includes(p))` (a bare `.includes()`
|
|
61
|
+
over the whole capture) is replaced by `classifyIdleError`, which fires only when a terminal-error
|
|
62
|
+
token sits in the live tail (last 20 non-empty lines) on a Claude-EMITTED error frame — a two-tier
|
|
63
|
+
begins-with rule: **Tier A** the line begins with `API Error:`; **Tier B** the line is glyph-led
|
|
64
|
+
(`⏺`/`⎿`/…) and begins with one of the 11 patterns. The match set is a strict subset of the old
|
|
65
|
+
buffer-wide match, so the change can only SUPPRESS spurious fires, never add one.
|
|
66
|
+
- The idle-error capture is widened 30→45 rows (`RATE_LIMIT_SETTLED_CAPTURE_LINES`): Claude Code's
|
|
67
|
+
input box + footer render 15-25 rows BELOW the error, so a 30-row capture could miss it entirely.
|
|
68
|
+
- `StuckSignatureClassifier` is migrated onto the shared `liveTail` (behavior-preserving via
|
|
69
|
+
`.join('\n')`; its existing test corpus is the characterization guard).
|
|
70
|
+
- A once-per-idle-episode structured record (`{event:'idle-error-classify', result:'fired'|'suppressed', …}`)
|
|
71
|
+
lands in `logs/server.log` so a wave of suppressions on genuine errors (the under-fire risk) is
|
|
72
|
+
observable. Cleared in both the re-arm block and `sessionComplete` (no leak on the death path).
|
|
73
|
+
|
|
74
|
+
The signal still feeds the existing `apiErrorAtIdle` → `rateLimitSentinel` recovery actuator and gains
|
|
75
|
+
no blocking authority (Signal vs Authority).
|
|
76
|
+
|
|
77
|
+
## What to Tell Your User
|
|
78
|
+
|
|
79
|
+
Your agent will no longer get silently stuck on a pop-up it can't answer. A recent
|
|
80
|
+
update to the coding tool added a safety pop-up ("Do you want to proceed? Yes / No") that
|
|
81
|
+
freezes the session, because nobody can press a key on it from your phone — and there's no
|
|
82
|
+
setting to turn it off. Now a small always-on layer answers that pop-up itself, only ever
|
|
83
|
+
pressing the harmless "Yes", and it's built into the code with no on/off switch that could
|
|
84
|
+
get stuck "off" (a stuck-off setting is exactly what caused the freeze). If it ever hits a
|
|
85
|
+
pop-up it genuinely can't clear (say a future version changes the wording), it stops and
|
|
86
|
+
sends you one "please take a look" notice instead of freezing silently. Nothing for you to
|
|
87
|
+
do; your agent just stays reachable.
|
|
88
|
+
|
|
89
|
+
Nothing you need to do. Under the hood, the agent got more reliable at telling a session that genuinely
|
|
90
|
+
stalled on an API error (which it nudges back to life) apart from one that's merely *showing* an old or
|
|
91
|
+
quoted error on screen. The old check was a blunt text search that sometimes kicked off a needless
|
|
92
|
+
recovery on a healthy session, and — more importantly — sometimes read too little of the screen and
|
|
93
|
+
missed a real stall. The new check looks at the live bottom of the screen, reads enough of it to clear
|
|
94
|
+
the input box, and only counts a line the tool actually emitted as an error. Net: fewer false recoveries
|
|
95
|
+
AND fewer missed real ones, so paused sessions get the right treatment faster. No new setting; behavior
|
|
96
|
+
is otherwise unchanged, and it's a one-line revert if ever needed.
|
|
97
|
+
|
|
98
|
+
## Summary of New Capabilities
|
|
99
|
+
|
|
100
|
+
- An always-on safety floor that auto-answers an un-answerable framework approval prompt,
|
|
101
|
+
so a remote-driven session is never silently wedged.
|
|
102
|
+
- Honest, bounded detection (live glyph-led menu, Enter-only, re-capture, attempt cap) +
|
|
103
|
+
a prose-agnostic backstop so a drifted/unrecognized prompt surfaces rather than strands.
|
|
104
|
+
- Visible in `GET /guards` (`on-confirmed`); an audit trail at
|
|
105
|
+
`logs/permission-prompt-resolver.jsonl` (matched-pattern names only, never raw tail);
|
|
106
|
+
emergency opt-out via `monitoring.permissionPromptAutoResolver.emergencyDisable`.
|
|
107
|
+
|
|
108
|
+
- `paneTail` + `IdleErrorClassifier` — a precise, tail-gated, frame-discriminated idle-error signal
|
|
109
|
+
replacing the bare buffer-wide substring scan; widened capture (45 rows) so the input-box chrome can
|
|
110
|
+
no longer hide a real error; structured once-per-episode observability of fired-vs-suppressed
|
|
111
|
+
decisions. Internal robustness improvement to session-stall recovery; no user-facing surface to
|
|
112
|
+
configure.
|
|
113
|
+
|
|
114
|
+
## Evidence
|
|
115
|
+
|
|
116
|
+
- `tests/unit/IdleErrorClassifier.test.ts` (28) — both sides of every boundary, pinned to REAL captured
|
|
117
|
+
render fixtures: fires on `⏺ API Error:` / ` ⎿ API Error:` / glyph-led network tokens / wrapped
|
|
118
|
+
errors; suppresses stale-scrollback, prose mentions, quoted source literals (the self-collision case),
|
|
119
|
+
a tool's own `Error: …ECONNREFUSED`; the 45-vs-30 input-box-chrome capture test; parametrized over all
|
|
120
|
+
11 patterns; bounded audit fields; paneTail helpers.
|
|
121
|
+
- `tests/integration/idle-error-classifier-production-wiring.test.ts` (7) — the REAL exported
|
|
122
|
+
`TERMINAL_ERROR_PATTERNS` produce correct decisions on real panes; the SessionManager call-site is
|
|
123
|
+
wired to the classifier, the 45-row capture, the dual once-per-episode clears, and the structured record.
|
|
124
|
+
- `tests/e2e/idle-error-classifier-lifecycle.test.ts` (2) — the real SessionManager constructs and the
|
|
125
|
+
`apiErrorAtIdle` recovery handoff is attachable; the production-pattern live decision is correct.
|
|
126
|
+
- `tests/unit/monitoring/StuckSignatureClassifier.test.ts` (13) — unchanged, the characterization guard
|
|
127
|
+
proving the shared-`liveTail` migration is behavior-preserving.
|
|
128
|
+
- `npm run build` + `tsc --noEmit` clean; full unit suite green.
|
|
129
|
+
- Driven by the converged + approved spec; convergence report at
|
|
130
|
+
`docs/specs/reports/idle-error-tailgate-corroboration-convergence.md` (4 rounds; the review caught a
|
|
131
|
+
CRITICAL under-fire-on-the-common-error-form flaw before any code).
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: patch -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
Fixed the "session paused" lockout (topic 28130): `MessageSentinel` was consuming the operator's benign inbound messages whenever its LLM classifier returned — or capacity-shed to — `'pause'`, and the "send a message to resume" recovery routed back through the same failing gate, an inescapable loop. New constitutional standard **"The Operator Channel Is Sacred — Critical-Path Gates Fail Toward Delivery"** (in `STANDARDS-REGISTRY.md`) + its exemplar fix:
|
|
9
|
+
|
|
10
|
+
- `'pause'` now consumes a message ONLY on a DETERMINISTIC fast-path match; a bare-LLM or capacity-shed `'pause'` routes THROUGH (delivered to the agent), never consumed.
|
|
11
|
+
- Before routing a non-deterministic result through, a non-word-count-gated stop-token scan rescues a long-form genuine "stop" to a kill (so a real stop is never dropped).
|
|
12
|
+
- A per-topic circuit-breaker bounds blast radius (a misclassification stream can't permanently lock the operator out).
|
|
13
|
+
- Both inbound consume paths (`onSentinelIntercept` + `/internal/telegram-forward`) decide via one shared `MessageSentinel.decideInboundDisposition`, so policy can't diverge.
|
|
14
|
+
- `/sentinel/stats` now exposes disposition counters (consumed / routed-through / breaker-recovered).
|
|
15
|
+
|
|
16
|
+
## What to Tell Your User
|
|
17
|
+
|
|
18
|
+
If your agent ever appeared "paused" and ignored your messages — replying "Session paused. Send a message to resume." in a loop — that's fixed. A safety check could mislabel an ordinary message as a "pause" command and silently eat it (worse, under load it defaulted to pausing). Now your messages are delivered unless you *unambiguously* typed a pause/stop command, and a genuine "stop" is always honored. The channel to your agent can no longer be sealed by a wrong guess.
|
|
19
|
+
|
|
20
|
+
## Summary of New Capabilities
|
|
21
|
+
|
|
22
|
+
- Standard "The Operator Channel Is Sacred" — a gate on the operator's inbound channel must fail toward DELIVERY; a brittle signal may not consume/block a message; recovery must not route through the failing gate; decision-gates have bounded blast radius.
|
|
23
|
+
- `MessageSentinel.decideInboundDisposition` + `hasStopToken` + disposition observability counters.
|
|
24
|
+
|
|
25
|
+
## Evidence
|
|
26
|
+
|
|
27
|
+
- `tests/unit/MessageSentinel-operator-channel-sacred.test.ts` (13) — deterministic pause consumes; benign LLM-pause + capacity-shed route through; long-form stop rescued to kill; circuit-breaker auto-recovers; per-topic isolation; counters; hasStopToken both-sides.
|
|
28
|
+
- `tests/integration/operator-channel-sacred-wiring.test.ts` (3) — both consume sites use the shared disposition helper; old consume-on-any-pause gone.
|
|
29
|
+
- `tests/integration/telegram-forward-sentinel-intercept.test.ts` — updated to the disposition contract incl. a new route-through-delivers test.
|
|
30
|
+
- `tests/e2e/operator-channel-lockout-regression.test.ts` (4) — no benign stream (even with the LLM mislabeling every message, or sustained capacity-shed) can lock the channel; recovery is escapable; a genuine stop still honored.
|
|
31
|
+
- `npm run build` + `tsc --noEmit` clean; consume-path + MessageSentinel suites green.
|
|
32
|
+
- Converged + approved spec; convergence report at docs/specs/reports/operator-channel-sacred-convergence.md.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Side-Effects Review — Operator Channel Is Sacred (MessageSentinel pause-lockout fix)
|
|
2
|
+
|
|
3
|
+
Spec: docs/specs/operator-channel-sacred.md (converged + approved). Standard integrated into docs/STANDARDS-REGISTRY.md.
|
|
4
|
+
Change: MessageSentinel `'pause'` consumes a message ONLY on a deterministic match; a bare-LLM or capacity-shed `'pause'` routes THROUGH (with a stop-token rescue); a per-topic circuit-breaker bounds blast radius; both inbound consume paths (server.ts onSentinelIntercept + routes.ts /internal/telegram-forward) decide via one shared `decideInboundDisposition`.
|
|
5
|
+
|
|
6
|
+
## 1. Over-block
|
|
7
|
+
The change can ONLY reduce consumption (more messages delivered). The one residual: `hasStopToken` is intentionally conservative (a whole-word "stop" anywhere → rescue to kill), so a benign message that the classifier already called 'pause' AND contains a whole-word stop ("non-stop") would be killed. Accepted: a kill is RECOVERABLE ("send a new message to start fresh"), unlike the pause lockout; and it only triggers on an already-pause-classified message, not normal traffic.
|
|
8
|
+
|
|
9
|
+
## 2. Under-block
|
|
10
|
+
A genuine pause directive that is non-deterministic (LLM-only) is now NOT honored (routes through). Accepted per the standard + the code's own comment ("pause's value is politeness, not safety"). A genuine STOP is preserved: deterministic fast-path stop fires instantly; a long-form/capacity-shed stop is rescued by `hasStopToken`.
|
|
11
|
+
|
|
12
|
+
## 3. Level-of-abstraction fit
|
|
13
|
+
The standard lives in ONE place (`MessageSentinel.decideInboundDisposition`); both consume sites call it, so policy cannot diverge between the poll path and the lifeline-forward path. Signal-vs-Authority: the classifier signals; the deterministic gate holds consume authority.
|
|
14
|
+
|
|
15
|
+
## 4. Signal vs authority compliance
|
|
16
|
+
Compliant + the exemplar of the new standard. A brittle (LLM/capacity-shed) signal no longer has authority to consume the operator's message; only a deterministic match (or a rescued stop) actuates.
|
|
17
|
+
|
|
18
|
+
## 5. Interactions
|
|
19
|
+
- detectRateLimited / throttle path: unchanged (separate).
|
|
20
|
+
- The capacity-shed return (llmClassify catch, confidence 0.4) now routes through instead of consuming — reconciled with the fork-bomb/No-Silent-Degradation fail-closed posture as a NAMED inbound exception (the deterministic emergency-stop pre-check + the stop-token rescue keep a real stop from ever being dropped).
|
|
21
|
+
- Emergency-stop kill path + resume-queue stop custody: unchanged for `disposition: 'kill'`.
|
|
22
|
+
- Circuit-breaker is in-memory shared across both paths (same MessageSentinel instance); in-memory is safe because the PRIMARY guard is deterministic-only-consume — a restart resetting the breaker cannot reintroduce the lockout. (Reasoned deviation from the spec's "durable" FD2: the build showed deterministic-only-consume makes breaker durability non-load-bearing; documented here.)
|
|
23
|
+
|
|
24
|
+
## 6. External surfaces
|
|
25
|
+
The user-facing change: benign messages that were being eaten as "Session paused" now reach the agent. `getStats()` (/sentinel/stats) now also exposes `disposition` counters (pause.consumed / .routed-through / breaker.recovered) — additive. No new route, no config schema change (the existing `externalOperations.sentinel.enabled` still gates the whole sentinel).
|
|
26
|
+
|
|
27
|
+
## 7. Multi-machine posture
|
|
28
|
+
Machine-local: each machine's MessageSentinel decides for its own inbound; both consume paths on a machine share its one instance + breaker. No cross-machine state. (Lifeline-owned agents hit the routes.ts path; poll-mode hits onSentinelIntercept — both on the same machine, same sentinel.)
|
|
29
|
+
|
|
30
|
+
## 8. Rollback cost
|
|
31
|
+
Single-commit revert restores the prior classify-then-consume logic. No migration, no durable state. The interim safeguard (`externalOperations.sentinel.enabled:false`) can stay until this merges, then be re-enabled (this fix makes the sentinel safe to re-enable).
|
package/upgrades/1.3.665.md
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
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).
|