* fix(daemon): pause producers when stream backlogs grow
* fix(daemon): reset stream backpressure on socket replacement
* docs(daemon): point retention audit at current reproducer
* test(daemon): validate stream retention audit outcomes
* fix(daemon): bound the stream producer stall and leave a visible gap
Stream backpressure pauses a session's PTY with no deadline: the only
un-pause comes from the consumer draining, so a half-open peer that stops
reading without closing freezes the shell for the rest of the session.
Arm a 60s watchdog on the false->true stream-pause transition (not on the
re-assertions refresh() makes for neighbouring sessions). On fire, mark the
session stall-released: it becomes keep-tail droppable, its backlog is
thinned behind a dataGap, and the producer runs again. The existing dataGap
path makes the renderer restore that pane from the daemon's snapshot, so the
user sees the terminal jump to current rather than sit frozen. The mark
clears once the session's last byte leaves the daemon, restoring ordinary
pausing. Nothing here reports a process exit - loss of contact with a
consumer is not evidence about the child.
Also enable TCP keepalive on the stream socket so a genuinely dead peer
closes and onStreamDisconnected clears the pause.
* test(daemon): put each casting SAFETY: directive on one line
`oxlint-disable-next-line` covers only the line directly after it, so a
rationale wrapped onto a second comment line suppressed nothing and the
casts failed the changed-code quality gate. Drop the remaining JSON.parse
cast for an annotated binding.
---------
Co-authored-by: m4air <m4air@Mac.localdomain>
Co-authored-by: Neil <neil@stably.ai>
* fix: retire closed editor models from the app shell
* test(editor): use checked Monaco attachment calls
* Preserve bounded editor view caches when retiring closed models
* docs(editor): describe batched model retirement
* fix(editor): preserve cleanup work across registry replacement
* fix(editor): build editor model URIs with the file scheme
Monaco keys its model registry by `uri.toString()`, and both
`@monaco-editor/react` (via the `path` prop) and the closed-tab disposal
path built that key with `Uri.parse`. On Windows a raw path such as
`C:\repo\a.ts` parses as scheme `c`, which fails the scheme gate in
`modelService._schemaShouldMaintainUndoRedoElements`, so closed-file undo
history was dropped for every file at any size — not only the large files
the tradeoff note covers.
Add `toEditorModelUri`, the one filesystem-path -> model-key function,
built on `Uri.file` so the result always carries the `file:` scheme and
re-parses to itself. Route model creation, disposal lookup and the
still-open ownership comparison through it so all three agree; a
divergence there would dispose a model an open editor is still editing.
---------
Co-authored-by: m4air <m4air@Mac.localdomain>
Co-authored-by: Neil <neil@stably.ai>
* fix(ai-vault): bound incremental transcript record assembly
* fix(ai-vault): skip one oversized record instead of dropping the session
An agent transcript record over the 10 MiB budget threw out of the JSONL
fold, so the whole session vanished from Agent Session History and from
search. A 10 MiB base64 image or a runaway tool result is ordinary.
The reader now discards the offending record up to its newline and keeps
folding. The in-progress record always starts at `consumedThrough`, which
is what makes both its running size and the resume offset past a discarded
span exact; an unterminated oversized tail leaves the cursor at the
record's start so a still-growing record is re-read rather than guessed at.
Skips accumulate on the resume point keyed by start offset, and the scanner
reports them as a per-session `notice` so nothing is silently lost.
The budget itself is unchanged.
---------
Co-authored-by: m4air <m4air@Mac.localdomain>
Co-authored-by: Neil <neil@stably.ai>
* fix(runtime): persist acknowledged terminal tab retirement
* test(runtime): drain tab retirement fixture writes before teardown
* fix(runtime): explain a refused workspace terminal close
The Sleep-workspace path threw the raw refusal enum ("stale-terminal") as an
Error message, which reaches a CLI user verbatim and a Sleep toast via
describeSleepFailure. Map each refusal reason to a sentence instead.
Also pins two behaviours that had no coverage: the user-visible outcome of a
republished stale-terminal refusal on the web client (the caller cannot tell it
from a real close), and the one-call-per-close invariant that keeps a successor
terminal alive.
The bounded close retry was NOT implemented: notifier.closeTerminalTab carries
only a tab id, so a second call destroys whatever successor took that id.
* test(runtime): build refusal fixtures without type assertions
The changed-code quality gate rejects new `as` casts. Replace the
branded-outcome cast with refusedMobileSessionTabClose, and model the
wire-skew reason as a decoded host answer instead of `as never`.
---------
Co-authored-by: m4air <m4air@Mac.localdomain>
Co-authored-by: Neil <neil@stably.ai>
* fix(claude): enforce history window quota while reading
* test: repair history quota audit dependency and CI import
* fix(native-chat): record why restart reconciliation leaves work unconfirmed
Two silent paths hid the cause of an unconfirmed submission. The reconciler's
bare `continue` on an `unknown` outcome dropped the reason it already carried,
and the transcript read swallowed its error, collapsing an oversize file and a
genuine read failure into the same verdict.
Log both. No control flow changes.
---------
Co-authored-by: m4air <m4air@Mac.localdomain>
Co-authored-by: Neil <neil@stably.ai>
* fix(browser): bound CDP output for stalled clients
* fix(browser): log CDP outbound overflow before terminating the client
The outbound queue terminated the automation client silently on overflow, so
the client saw a socket close indistinguishable from a crash. Surface the cap
that tripped and the backlog held when it did.
The queue dropped its backlog before invoking onOverflow, so the counters were
already zero at the callback. Snapshot them first and pass them through.
---------
Co-authored-by: m4air <m4air@Mac.localdomain>
Co-authored-by: Neil <neil@stably.ai>
* fix(terminal): align IME preedit to terminal cell grid
* fix(terminal): preserve native shaping and reuse IME preedit on repaint
* fix(terminal): preserve native shaping with bounded IME spacing runs
* test(terminal): account for inline preedit subpixel rounding
* test(terminal): keep the IME grid fixture wide at every DPI
* chore: regenerate xterm patch after rebase
* test(terminal): remove IME assertion lint findings
* test(terminal): avoid reflective IME fixture access
* test(e2e): run IME renderer matrix with WebGL available
* fix(ci): restore editor line budget
* perf(terminal): skip kitty scans for plain output
* fix(terminal): keep the kitty scan fast path total for absent chunks
The new escape-byte fast path dereferences the chunk before the string
concatenation that used to coerce a nullish value, so an unchecked
caller now throws instead of no-opping. Normalize once at the top.
Also type the AgentTerminalPreview connect mock against the real preload
signature, which turns the stale bare-string replay fixture that tripped
this into a compile error.
* perf(terminal): skip background SGR scan without escapes
* perf(terminal): avoid duplicate renderer risk scans
* test(terminal): pin the carried renderer risk scan tail
The foreground renderer-risk scan splices the carried tail onto the
incoming chunk before classifying it, and nothing covered that ordering:
a pre-gate moved back above the concatenation would silently drop the
refresh for a background SGR split across ConPTY chunks.
Also pins the escape-free ASCII path and the shared global SGR pattern's
statelessness across calls, since the background hit returns mid-loop.