All notable changes to ripple are documented here. Format based on Keep a Changelog, versioning follows Semantic Versioning.
Single fix for a history-dependent pwsh worker output corruption. When ripple's worker pwsh ran on a box with populated command history, PSReadLine's Predictive IntelliSense rendered grey ghost text (SGR 38;2;68;68;68) over the line while a long encoded-scriptblock payload was streaming in; that redraw interleaved with the input echo and intermittently corrupted captured command output — enough to fail the pwsh adapter probe outright on some machines. Reported in #9.
- PSReadLine Predictive IntelliSense disabled in the pwsh worker shell.
ShellIntegration/integration.ps1now runsSet-PSReadLineOption -PredictionSource Nonein ripple's worker pwsh. Only the predictor is turned off — history recall (Up/Down, Ctrl-R), Tab completion, and syntax highlighting are unaffected — and it touches ripple's automation shell only, never the user's interactive pwsh session. No human reads the inline prediction in an AI-driven worker, so the change loses nothing there.
AI-ergonomics polish + a correctness/robustness sweep across file-tools, OSC parsing, console routing, and worker lifecycle. peek_console drops the PSReadLine "screen reader detected" warning ConPTY-launched pwsh reprints on every snapshot. LastExit: N no longer surfaces for bare $LASTEXITCODE = N assignments — only when AST analysis confirms a native exe actually ran. EditFile streams in one pass and emits ±2 lines of context per replacement so the AI doesn't have to re-read the file to confirm the change. File-tools moved to Microsoft.Extensions.FileSystemGlobbing.Matcher (path-segment globs now work correctly on Windows), gained symlink/junction cycle detection, and retry on transient AV/indexer sharing violations. Drift handling bails with a "Pipeline NOT executed" notice instead of silently re-injecting a cd preamble — the AI re-issues to accept the user's cwd or prepends the revert hint.
-
find_files/search_filesglob backend switched toMicrosoft.Extensions.FileSystemGlobbing.Matcher. The hand-rolledMatchGlob(regex fromRegex.Escape+ chained Replace) matched against either the file basename or the absolute path with/as the only path separator, so path-segment globs (src/**/*.cs) never engaged on Windows because absolute paths use\. Each file entry is now matched as a forward-slash root-relative path against anAddInclude(pattern)rule. Bare-basename ergonomics preserved — patterns without/or\are auto-rewritten to**/<pattern>so*.csstill finds files at every depth. Our recursive walker is kept (Matcher's built-in walker doesn't honourSkipDirs,max_results,CancellationToken, or our symlink-cycle visited tracking);ctis now threaded throughFindFilesRecursiveso a deep tree is cancellable. Six new test scenarios cover top-level / one-level / multi-level matches, sibling exclusion, basename-anywhere ergonomics, andSkipDirsprune. -
Adapter schema canonicalized on
prompt.primary. DroppedPromptSpec.PrimaryRegexand the worker'sPrimary ?? PrimaryRegexfallback. SCHEMA.md was internally inconsistent (§6 listedprompt.primary_regexwhile the example block usedprimary); all 18 embedded YAML adapters declareprimary:, andIgnoreUnmatchedProperties()would have silently dropped a hand-rolled external adapter using the alias anyway. SCHEMA.md row corrected to match the example block. -
strip_ansidefault flipped totrueonexecute_command/wait_for_completion. AI callers almost never want raw SGR bytes mixed into the text they reason about — leaving the default off meant every pwsh tool response carried the colorized prompt / status / error spans verbatim, costing tokens. Visible console output is unaffected (ripple's shared-console model deliberately preserves color for human viewing). Description updated to call out when settingfalseis the right move (you'll be quoting colored output back to the user, or inspecting compiler diagnostics where color carries semantic information). -
EditFilestreams in one pass and returns ±2 lines of context per replacement. Single-lineold_string(the common shape) reads with aStreamReader, writes to a same-volume temp file with aStreamWriter, and atomically renames into place — no more loading the entire file into memory. A 2-slot rotating buffer holds recent lines so the response can show the lines before / on / after each match without buffering everything. Multi-lineold_stringkeeps the whole-file path but also picks up ±2 line context around the first replacement so the response shape is uniform. Existing test contracts (Replaced N occurrence,Error: old_string not found,Error: old_string found N times) preserved.
-
Drift bails with notice instead of silent
cdpreamble. When live cwd ≠ AI's last known cwd in the same console, ripple now returns a "Pipeline NOT executed" early result with prev → new cwd and a single-quote-escaped revert hint, then updatesLastAiCwdto the live cwd so user-cd state is cleared. Pre-fix, an injected cd-preamble silently overrode the user's navigation intent — brittle if the user typedcdto set up a workspace they wanted the AI to use. The bail surrenders one round-trip but gives the AI full agency: re-issue to accept the user's cwd, or prepend the suggested revert. Mirrors PowerShell.MCP's safety-first drift policy (commit 32f0f56 there). -
OSC parser caps inter-chunk carry at 256 KB.
OscParser.Parsecarries unterminated OSC payloads (no BEL/ST yet) across chunks via_buffer. A malformed PTY emitter, a torn frame, or an integration script that crashed mid-write would grow the buffer one chunk at a time without bound, eventually pinning worker memory. Cap leaves a ~60× margin over legitimate OSC 633 payloads (worst case OSC R: 1000-char base64-encoded error message ~1.4 KB). Once the cap trips, the parser flushes the buffered bytes as cleaned text (visible console may briefly show\x1b]633;smear, no data loss) and resets_bufferso subsequent valid OSCs parse normally. Two new tests cover both branches (16 KB under-cap round-trip + 300 KB unterminated bail). -
Recursive file-walk detects symlink/junction cycles. A self-referential symlink (
ln -s . loop) or a Windows junction pointing at an ancestor would sendfind_files/search_filesspinning until eithermax_resultsclipped it short or the call stack overflowed.SkipDirsonly catches named conventions like.git/node_modules; hand-crafted loops elsewhere went undetected. Visited directories are now tracked by their resolved real path (DirectoryInfo.ResolveLinkTarget(returnFinalTarget: true)); the walker bails before re-entering one. Set usesOrdinalIgnoreCaseon Windows to match NTFS path semantics. -
Orphan console claim threads the discovering agent's id.
FindStandbyConsoleAsyncis called fromPlanExecutionAsyncwith the caller'sagentId, but the second hop intoTryFindInPipesAsyncdropped that context — the claim payload and new pipe name were both built with a hardcoded"default". An orphan adopted by agent X ended up atRP.{proxy}.default.{pid}instead ofRP.{proxy}.X.{pid}, breaking the per-agent routing partition documented indocs/ARCHITECTURE.md. In a multi-agent setup, agent A discovering and adopting an orphan effectively published it to the"default"partition where any other agent would route through it on the next tool call. Wire contract unchanged — purely a proxy-side correctness fix; the worker already readsagent_idfrom the claim payload and rebuilds its pipe name from it. -
WriteFile/EditFileretry on transient sharing violations. On Windows with on-access antivirus, Defender, or the search indexer scanning a just-closed file in the window between StreamReader/StreamWriter dispose and the next write,File.Move(temp, path, overwrite: true)andFile.WriteAllTextintermittently raisedUnauthorizedAccessException. Three write paths now wrap in a 5-attempt exponential backoff (50/100/200/400 ms, ~750 ms worst case before re-throw). Real permission errors raise the same exception types but don't go away on retry, so the cap stays tight; the AV-scan race typically clears in well under 100 ms. Verified by running the full test suite 5 times consecutively after a temp-dir clean — flake gone. -
Worker drops broken termios
ProcessExithandler (use-after-free).InputForwardLoopUnixregistered a "belt-and-braces" termios restore for signal/crash exits, but the handler was broken on both paths it claimed to cover. On graceful exit, the thread'sfinallyalready restores termios and callsMarshal.FreeHGlobal(saved); theProcessExithandler thentcsetattr(savedSnapshot)'d freed memory. On the signal/crash exits the comment claimed to cover (SIGTERM, SIGKILL, segfault),AppDomain.ProcessExitdoesn't fire at all, so the handler wasn't catching what it advertised. Drop it. Thefinally-block restore is sufficient on graceful shutdown; a real signal-time restore would have to come fromPosixSignalRegistrationon a worker-lifetime buffer — left as a comment for whoever picks that up. -
ReadFilevalidatesoffset/limit. Negative parameters were silently mishandled —offset = -1bypassed the start-index gate (every line passeslineNum >= -1) and read from line 0 anyway;limit = -5madelines.Count < limituniformly false and returned an empty string with no diagnostic. Both come from MCP tool-call integers, so a clear validation error at the entry point beats silent empty / silent full responses. -
VT
InsertLines/DeleteLinesrotateSoftWrapalongsideGrid. Both ops shifted Grid rows but never touched the parallelSoftWraparray, so a freshly-blank inserted/displaced row inherited whatever soft-wrap flag was sitting at that index from the previous occupant. A subsequent renderer that consultsIsRowSoftWrappedwould treat the blank row as a wrap continuation of the row above, producing ghost merged-line artefacts on output that exercises IL/DL (full-screen editors, multi-line readline redraws). Walk both arrays in lockstep and explicitly clear the flag on the row that becomes blank. -
Cached file-tools regexes carry the 1 s match timeout.
SearchFiles's pattern is user-supplied; a pathological(a+)+b-shape regex against a long line would have run unbounded under the cache introduced this release. Apply the same 1 s per-match timeout already used byModeDetectorandRegexPromptDetectorso a bad pattern surfaces as a cleanRegexMatchTimeoutException(translated by MCP into a tool-call error) rather than a hung response. Glob patterns can't realistically backtrack catastrophically after the canned-replacement transform, but the cap is applied uniformly so every cached regex in the file shares one policy. -
peek_consolestrips the PSReadLine "screen reader detected" startup banner. Every pwsh started under non-interactive ConPTY — i.e. every ripple-spawned pwsh — prints this multi-line warning at the top of the screen buffer, and it persists in the rolling peek window for a long time because nothing scrolls it out. The AI seeing the same banner on every peek call was pure noise.FilterStartupBannersdetects the banner start, drops continuation lines until the closing marker (or a blank terminator), and swallows the trailing blank so the trimmed output starts cleanly with the prompt. Conservative — only activates on the banner prefix; capped at 5 dropped lines so an unusual buffer state can't eat real output. -
$LASTEXITCODEreporting gated on AST-detected native invocation. A bare$LASTEXITCODE = 7in pure PowerShell was indistinguishable fromcmd /c exit 7to the resolver — both updated the variable, both flipped$lecChanged, so the resolver emitted OSC L and the status line showedLastExit: 7as if cmd.exe had returned non-zero. Phantom failure signal.__rp_pipeline_uses_nativenow AST-parses the last history command line, walks forCommandAstnodes, and resolves each name throughInvokeCommand.GetCommandfiltered toApplication— only native exes return a hit. Without a native sighting,$LASTEXITCODEis treated as a stray variable whose value carries no exit-code meaning, the D code falls through to$?, and OSC L stays silent. Aliases pointing at natives miss (resolve as Alias) and functions wrapping natives miss (resolve as Function) — known static-analysis limitations, accepted as the conservative direction (fewer false positives is the goal). The check runs in the prompt fn, not inPreCommandLookupAction, so theGetCommandresolution can't recurse through the engine and re-fire the action mid-resolution (an earlier version of the patch did exactly that and hung worker startup). -
UTF-16 trailing-newline detection works.
FileMetadataHelper.DetectNewlineFromByteschecked the last raw byte for0x0A/0x0D, which works for UTF-8 but in UTF-16 LE the last byte of a trailing\nis0x00(the high byte) — the check missed it and the streamingEditFilewriter would then drop the trailing newline on round-trip. Now the tail bytes are decoded through the detected encoding and the last character is checked. Uncovered by the existingFileToolsTestsUTF-16 round-trip suite onceEditFileswitched to the streaming-write path.
-
Audit-pass mechanical hardening. Three locality-bound changes from a comprehensive code-review pass, all non-behavioural on the happy path, verified with
--test(68/68): drop two dead schema properties (InitSpec.WaitAfterMs,OutputSpec.StripPromptEcho) — neither is read anywhere andIgnoreUnmatchedProperties()would silently drop them today; cap regex match runtime at 1 s inModeDetector.DetectandRegexPromptDetector(constructor + Scan, with the Scan loop wrappingMatchesenumeration so a mid-scan timeout surfaces whatever was already collected) so a hand-edited adapter with a catastrophic-backtracking pattern can't pin a worker thread on every PTY chunk; add a 64 MiB ceiling toConsoleWorker.ReadMessageAsync's pipe-frame length prefix using(uint)len > capso a torn frame or buggy proxy with a negative or multi-GiB length stops OOM-ing the worker. -
Per-pattern compiled
Regexcache in the hot paths.FileTools.MatchGlob(per file entry inside the recursive walk),FileTools.SearchFiles(user-supplied pattern across iterative edits), andModeDetector.Detect(per-command poll loop) were rebuildingRegexobjects per invocation despite stable pattern lifetimes.Regex.IsMatch's process-wide static cache only holds 15 patterns; on a tree with thousands of files this was N compilations per glob. Caches areConcurrentDictionary<string, Regex>keyed by raw source pattern (or null sentinel for proven-invalid patterns), unbounded — distinct patterns per session are tens at most, each compiledRegexon the order of KB. If memory pressure ever shows up, swap for an LRU. -
RotateBuffer<T>ported from PowerShell.MCP. Fixed-capacity rotating buffer. Used by streamingEditFileto keep the most recent N lines as pre-match context without buffering the entire file. Same shape and oldest-first iteration order as the upstream copy. -
expect_no_last_exitAdapterTest field. Asserts the worker did NOT emit a non-zerolastExitCodefor the eval. Strictly distinct fromexpect_exit_code: 0(= the OSC D code, the overall pipeline outcome): a manual-assignment leak shows D=0 ($?stays true through a plain assignment) but L=N (phantom native exit), and only the new assertion catches the L side. Added the matching pwsh regression test (manual_lastexitcode_does_not_leak_in_pipeline); verified it fails on the pre-fix baseline before keeping it. -
FileToolsTestsstrengthened with 12 streaming-context assertions. SJIS CRLF round-trip asserts theN:/N-marker shape survives Shift-JIS decoding;replace_allasserts every replaced line shows up; a 7-line fixture asserts the ±2 window (lines outside it are absent); a two-match fixture with a 4-line gap asserts middle lines fall outside both windows. Unique tokens (MID4/MID5) where needed so an incidental letter in the path can't pollute the assertion. -
ShellToolsTests(new) coversFilterStartupBannersend-to-end: no-op when banner absent, empty / null passthrough, full banner with wrap-broken word stripped (the common ConPTY case), banner-only snapshot reduces to empty, 5-line cap prevents runaway when the closing marker never arrives, banner appearing mid-snapshot with surrounding real content preserved on both sides.
Structured PowerShell error visibility. AI can now read PowerShell errors as typed fields instead of parsing SGR-coloured text. Three new OSC 633 extensions surface error messages, truncation count, and silent native exit codes; an opt-in strip_ansi flag drops SGR bytes when callers don't need them.
Refuse and rescue paths show what the AI needs. First-execute refuse messages include Live cwd and Resolved shell so the AI can confirm the target before re-sending. Busy-timeout hint reordered to peek → send → wait. CLI gains --version / --help. PAGER / GIT_PAGER / MANPAGER pinned so external CLIs don't freeze the console waiting for q.
-
Structured error messages from PowerShell. OSC 633;R surfaces each
$Errorentry as a separate field, base64-utf8 encoded. Proxy renders an--- errors (N of total) ---section after the main output, with cmdlet provenance prefixes (Get-Item: ...). Caps at 20 records, oldest first (root causes are usually the first errors, later cascades drop on overflow). Each message capped at 1000 chars. -
Truncation count as its own field. OSC 633;T reports how many error records were dropped, separate from the R event stream. Header reads
--- errors (20 of 25) ---matching the status badgeErrors: 25. Under-cap cases keep the bare(N)header. -
Native non-zero exit code surfaced when pipeline succeeds. OSC 633;L emits
LastExit: Non the status line forcmd /c exit 7; "after"-style pipelines where$?is True but a native exe exited non-zero. The green ✓ badge no longer hides silent native failures. -
strip_ansiflag onexecute_command/wait_for_completion. Defaultfalse. Whentrue, response output runs through a narrow SGR-only regex (\x1b\[[0-9;]*m) — saves tokens for callers that don't reference colour cues. Visible console output is never touched. -
First-execute refuse shows Live cwd and Resolved shell. AI can confirm the target without an extra
pwdround-trip. Sub-agent isolation boundaries probe the target console's cwd directly so the line is never blank. -
CLI:
--version,--help, unknown-arg rejection. No more silent hangs in MCP server mode when a flag is mistyped.--versioncarries the git commit hash viaSourceRevisionId. -
Pager suppression in every console.
PAGER/GIT_PAGER/MANPAGER=catinjected atPtyFactory.Start.git log(~8700 lines) returns in 243 ms instead of waiting onq.
-
Busy-timeout hint reordered: peek → send → wait. Matches the order an AI should reach for the rescue tools when a command looks stuck.
peek_consolewas previously missing from the hint entirely. -
GHA actions on actually-Node-24 runtimes.
upload-artifact@v5/download-artifact@v5carried preliminary Node 24 but defaulted to Node 20; the default moved to Node 24 in v6+.azure/login@v2 → v3(also Node 20 → 24). -
README and npm metadata refreshed. README leads with descriptor
# Ripple — REPL-sharing MCP for AI Co-Driving. "Why ripple?" promotes Sensitive operations as the lead sub-section (the most novel differentiator). 19-adapter list bullet-formatted by family. Rename history moved to## Migrationnear the bottom. npmdescriptionrewritten to lead with co-driving + secret-safety; adapter count corrected (18 → 19, sbcl was missing); keywords expanded 17 → 31 to cover discovery vectors. -
list_shellsdocumented in README and tool descriptions inAdapterTools.cs/ShellTools.cssynced with the actual shipping set.
-
DECAWM duplicate-char wrap continuation. ConPTY re-emits the wrapping char on auto-margin, so on long lines (~100+ chars, e.g. git CRLF warnings) the continuation's first byte duplicated the prefix's last — visible as
CRLLF the next time.... The cursor redirect now lands at_lastLfPreCol - 1so the duplicate overwrites itself harmlessly. SeeHANDOFF_GARBLING.mdfor the dogfood report. -
Drift detection switched to direct cwd comparison. The previous OSC-C-event-counter heuristic miscounted under wrapped scripts, command composition, and during AI command execution. Now asks the actual question: does the current cwd match what the AI thinks it should be?
_userCmdsSinceLastAiand itsget_statusfield are removed.
ShellPathResolverextracted to fix a layer-violating reverse dependency fromAdapter(data model) intoConsoleManager(runtime). PATH resolution now lives in its own utility.StatusLineFormatterconsolidates two near-identical implementations (ConsoleWorker.BuildStatusLine+ShellTools.FormatStatusLine). Status-line extensions now have one source of truth instead of two sites that had to be updated in lockstep.- OSC 633 extension field (de)serialization centralized in
WriteOscExtensionFields/ReadOscExtensionFields. Adding the next extension is one line per call site instead of four. - pwsh exit-code resolution centralized in
__rp_resolve_exit_code. OSC D and OSC L can no longer disagree on pipeline success. AssertOscExttest helper. OSC R / T payload tests collapsed: 90 lines removed, 19 added, same coverage.- Inline comments on two intentionally-bare catches in
ConsoleWorker(no behavior change). docs/ARCHITECTURE.mdCache / drain row corrected (file attributions and stale symbol name)..gitignoreadds.stash-next-release/for work-in-progress adapters parked between releases.
pwsh output fidelity. PowerShell error visibility upgraded across the board: cmdlet failures show on the status line as ⚠ Completed with errors | Errors: N instead of hiding behind a green ✓ on exit 0. Multi-line bodies now ship as base64-encoded scriptblock by default — faster than tempfile, no disk I/O, same dot-source scope semantics. Stale $LASTEXITCODE from a preceding native pipeline no longer leaks into the next pure-PS command's reported exit.
Output rendering hardened at the MCP boundary. OSC 8 hyperlinks preserved as <URI> cells instead of dropped. Dangling SGR at output edges cleaned up (leading no-op reset stripped, trailing reset added when output ends mid-colour). Stale SGR no longer bleeds into cells overwritten with different characters. ripple-exec-<pid>-<guid>.ps1 tempfile paths stripped from PowerShell ConciseView error summaries.
Plus: a queryable list_shells MCP tool, two-tier startup stderr (silent modes only surface user-actionable load issues), AI-initiated cd no longer misattributed as user drift, PTY width tracks the visible ConHost width so \r-based progress bars repaint in place, and a try/finally safety net guarantees HandleExecuteAsync releases the user-input hold gate on every exception path.
- @doraemonkeys — PR #7 (JSON-native escape recommendations in
send_inputdocs) and PR #8 (surfacesend_inputin busy-timeout hint).
-
Errors: Non the status line for PowerShell. OSC 633;E carries$Error.Countdelta from the integration script; status line prepends| Errors: Nwhen N > 0. Other shells emit no E and stay at zero.Write-Warning/Write-Informationare not tracked — cmdlets bypass any user proxy via$PSCmdlet.WriteWarning, so they can't be reliably counted from an integration script. -
OSC 8 hyperlinks preserved as
<URI>. Build tools (dotnet, linters),Write-Information, IDE integrations emit\e]8;;<URI>\a link-text \e]8;;\a. Previously dropped with every other OSC; nowCommandOutputRendererinjects the captured URI as<URI>cells right after the link-text ("click here<https://example.com/>"). Both BEL and ST terminators handled. -
MCP tool hint when output spills to disk. When output exceeds the 15 KB threshold the spill-file preview now points at ripple's own
search_files/read_filetools — instead of leaving the AI to reach for shellgrep/cat(which round-trips through the same PTY). Bare tool names so the hint stays valid no matter which client prefix wraps ripple. -
encoded_scriptblockmulti-line delivery for pwsh. Base64-encoded. ([ScriptBlock]::Create(...))invocation. Same dot-source scope as the tempfile path but no disk I/O and no history-filter bookkeeping. ~0.3–0.5 s faster on the warm path. SharesBuildMultiLineTempfileBodyso both deliveries produce identical echo + OSC-C semantics — only the wrapper differs. -
⚠ Completed with errorsstatus badge. Three outcomes instead of two:exit != 0→ ✗ Failed,exit == 0, errors > 0→ ⚠ Completed with errors,exit == 0, errors == 0→ ✓ Completed. PowerShell commands hittingWrite-Erroror non-terminating cmdlet failures exit 0 but bump$Error— the plain green ✓ understated what the AI needed to look at. Pairs with the newErrors: Ntag. -
list_shellsMCP tool. Discovery endpoint for the validshellargument values. Returns name, aliases, family (shell/repl/debugger), source (embedded/external), and the resolved executable pathstart_consolewould launch (ornull+executable_notewhen not on PATH). Any absolute path can also be passed asshellto launch an unregistered REPL — with the caveat that without a matching adapter ripple runs it in minimal mode (no prompt detection / exit code / cwd tracking). Aload_issuesobject surfaces every parse error / collision / override at startup with auser_actionableflag.
-
Startup stderr two-tier. CLI modes (
--test,--list-adapters, ...) still print full[ripple adapters info]summary — a human is reading the terminal. Silent modes (MCP stdio, ConPTY worker) now suppress the info-level roll-up and print only user-actionable issues (parse errors / collisions on YAMLs in~/.ripple/adapters/). Embedded-only failures are ripple bugs the user can't fix; firing them every prompt was noise. AI consumers needing the full picture calllist_shells. -
pwsh multi-line delivery defaults to
encoded_scriptblock. The baseline-bleed race that previously tripped encoded delivery is fixed (see Fixed). 20+ consecutive burn-in runs of the original repro produce clean output.tempfileremains selectable for bodies exceeding PSReadLine's input line cap. -
Busy-timeout hint mentions
send_input. Previously pointed only atwait_for_completion, which never returns when the command is stuck on an interactive prompt (pager,Read-Host, y/n). Hint now lists both with role labels. PR #8 by @doraemonkeys. -
send_inputdocs use JSON-native escapes. Recommended form is now\u0003/\u001b[Ainstead of\x03/\x1b[A. JSON does not accept\xNN— strict parsers rejected the input, lenient ones passed through four literal chars, so the advertised "send Ctrl+C" behavior didn't reach the PTY. Runtime accepts both forms; docs show the JSON-native one. PR #7 by @doraemonkeys. -
GHA actions bumped to v5 ahead of GitHub's forced Node 24 switch on 2026-06-02 and Node 20 removal on 2026-09-16.
-
Dangling SGR at output boundaries. Two boundary cases: (a) leading
\e[mreset that attached as the SgrPrefix of the first non-bar character (Write-Progresscleanup →[maftergarbage); (b) missing trailing reset when output ends mid-colour, leaving the next prompt stuck red.CleanStringnow strips leading reset-only SGR and appends\e[0mwhen end-of-output state is non-default. -
Tempfile path leakage in multi-line errors. PowerShell's
ConciseViewprefixed error summaries with{Cmdlet}: {TempfilePath}:{Line}, exposing ripple's.ripple-exec-<pid>-<guid>.ps1wrapper. Now stripped; theLine | N | <source>diagnostic block below is preserved. -
Stale SGR bleeding into overwriting characters.
Write-Progress's reverse-video bar cells leaked\e[7monto subsequent normal text —Write-Progress ... ; "after"produced garbled[mafter [7mprogress.WriteCharnow treats same-char repaint differently from genuine content change: same-char keeps SGR (ConPTY idempotence), different-char drops stale SGR. -
Stale
$LASTEXITCODEleaking into pure-PS pipelines.$LASTEXITCODEis only updated by native exes; aftercmd /c "exit 7"the value stayed 7 and innocent PS pipelines (Get-Date) reportedFailed (exit 7). Fixed by capturing$?first in the prompt fn, snapshotting$LASTEXITCODEatPreCommandLookupAction, and resolving via priority:$?true → 0;$?false + LEC changed non-zero → use it;$?false otherwise → 1. -
AI's own
cdno longer misattributed as user drift. Cwd-snapshot drift detection produced spurious "moved by user" notices when standby rotation orRecordShellCwdrace lagged the snapshot. Replaced with a provenance counter (UserCmdsSinceLastAi) that increments at OSC A close-of-user-busy and resets onRegisterCommand. -
Explicit LineFeed clears stale soft-wrap flag on baseline rows. Pre-command rows that were wrap continuations kept their
ContinuedFromAboveflag, chaining command-emitted lines onto erased predecessors (abiter 1instead ofa\nb\niter 1). Now cleared when LineFeed targets a row in the baseline range. -
PTY width tracks the visible ConHost width. Old floor-at-200 broke
Write-Progressself-repaint — bars padded to the declared width physically wrapped past visible cols, so\rreturned only to the wrapped row and updates stacked vertically. Matching visible width fixes in-place repainting; mid-word splits on narrow terminals remain a theoretical edge the AI can read around (matches what the user is looking at). -
_vtStatefed in OSC-aligned slices. A single PTY read straddling OSC C and command output had the renderer baseline include cells from the very output it was about to replay (writing"a"onto a row whose stale tail was"ter 3"produced"ater 3", or dropped the line when the cursor landed on an already-written row). Slice-by-offset feed so the snapshot atCommandExecutedreflects state at the OSC C byte exactly. Unblocksencoded_scriptblockmulti-line as the new default. -
HandleExecuteAsyncuser-input hold gate guaranteed to release. Previously leaked on any unhandled exception path — flag stayed true, queued keystrokes never drained, nextexecute_commandstarted with a poisoned shell. try/finally added; release is idempotent so duplicate calls on normal paths are no-ops.
Cross-platform shipping. Native binaries for Linux x64 and macOS arm64 ship alongside Windows. npm i -g @ytsuda/ripple resolves the right platform automatically via optionalDependencies; a small Node launcher (bin/cli.mjs) spawns the matching binary with stdio inherited and SIGTERM / SIGINT / SIGHUP / SIGQUIT forwarded.
The release workflow is now a three-runner build matrix (windows / ubuntu / macos) followed by a single Linux publish job that Authenticode-signs the Windows binary via AzureSignTool, publishes three subpackages + a meta package with SLSA provenance, and attaches all three binaries to the GitHub Release.
-
Linux x64 and macOS arm64 native binaries. NativeAOT-compiled from the same source as the Windows binary; identical
--testsuite gates publish. macOS Intel (osx-x64) deferred — GHA's macos-13 runner pool capacity made it unshippable. -
@ytsuda/ripple-<platform>subpackages. Each carriesos+cpufilters; npm installs only the matching one and skips the rest silently viaoptionalDependencies. -
npm/bin/cli.mjsdispatcher. Resolves<platform>-<arch>against an allow-list, locates the subpackage binary, spawns withstdio: 'inherit', forwards SIGTERM / SIGINT / SIGHUP / SIGQUIT, and re-raises the child's exit signal so$?/%ERRORLEVEL%/$statusmatch a direct invocation.
-
Release workflow split into matrix build + single-runner publish.
buildruns over{win-x64, linux-x64, osx-arm64}and uploads artifacts;publish(Linux,environment: release) downloads all three, signs the Windows binary, then publishes subpackages sequentially (win32-x64 → linux-x64 → darwin-arm64) before the meta package — a mid-sequence failure halts before the meta points at a missing subpackage. -
npm/package.jsonis now a meta package. Ships onlybin/cli.mjs, README, LICENSE;optionalDependenciespin the three subpackages to the exact current version. Theosrestriction is lifted — Node installs everywhere; the native binary requirement is enforced by the subpackage filter. -
Version cross-check verifies five fields: csproj
<Version>, metanpm/package.json, and each of the three platformpackage.jsonmust match the pushed tag; publish aborts fast on disagreement.
Three parallel rounds land in one release.
(1) Live VT cursor tracking. ripple now keeps an authoritative VT-100 interpreter advanced from every PTY chunk and answers DSR (\x1b[6n) cursor-position queries from real state instead of the static "near the bottom of the screen" heuristic it shipped with. Closes the Unix drift where PSReadLine's up-arrow history recall painted over the active prompt and bash readline wrapped long input into the wrong column after a few AI commands had scrolled by.
(2) Oversized output spilled to a temp file (closes #1, PR #5). Outputs over 15,000 chars are written to a worker-owned spill file (%TEMP%\ripple.output\ on Windows, ${TMPDIR:-/tmp}/ripple.output/ on Unix) and the MCP response returns a head + tail preview embedding the spill path. Inline execute_command and deferred wait_for_completion flow through a shared finalize-once boundary so both delivery modes return the same CommandResult shape.
(3) Command-output extraction rebuilt as a per-command renderer fork (closes #4). At OSC C the worker snapshots the session-wide _vtState and hands the snapshot to a new CommandOutputRenderer initialised from it. ConPTY's post-prompt redraw bursts target cells whose baseline values match what's being rewritten, so a per-cell change detector recognises them as idempotent overwrites and they stay out of the AI-facing response. Alt-screen entry / exit collapses to an [interactive screen session] placeholder; soft-wrapped logical lines re-join at render time so a narrow PTY can't fragment a single git log --oneline entry.
- @doraemonkeys — reported the oversized-output overflow as #1 and contributed PR #5 (round 2's spill-to-tempfile fix).
- @luchezarno — reported #4 with a detailed Git Bash log that pinpointed the ConPTY post-prompt redraw burst as the cause of the dropped grep matches.
-
Services/VtLiteState.cs— VT-100 interpreter formerly embedded inCommandTracker, now a public class withFeed(ReadOnlySpan<char>). A 16 KB pending-escape buffer stitches CSI / OSC sequences split across PTY reads. The staticVtLite(...)one-shot helper is preserved for compatibility. -
CSI catalog growth.
ECH,DCH,ICH,IL,DLhandlers added — readline / PSReadLine emit these for in-line editing; previously dropped to the silent default branch. -
ConsoleWorker.AnswerAndStripDsr— pure helper carrying up to 3 partial DSR prefix bytes across PTY reads viaref string pendingPrefix. Fires the reply callback once per detected DSR (was once per chunk regardless of count) and strips the partial prefix from output so it never leaks downstream. -
Services/CommandOutputCapture.cs— bounded raw-capture store (small hot char buffer + scratch-file spill, offset-based slice readers + bounded snapshot for timeoutpartialOutput). Worker-private; distinct from the publicripple.outputspill directory. -
Services/CompletedCommandSnapshot.cs— lightweight record the tracker emits on primary completion: capture handle, command-window offsets, exit metadata, cwd, shell family, settle policy, and the exactptyPayloadbaseline. -
Services/CommandOutputFinalizer.cs— slice-reader-driven cleaner +EchoStripperfordeterministic_byte_matchadapters. Reads from offset-based capture slices instead of rebuilding tracker state from one monolithic in-memory output buffer. -
Services/OutputTruncationHelper.cs— preview + spill-file creation, DI-friendly (IOutputSpillFileSystem,IClock). Threshold 15,000 chars, head ~1,000, tail ~2,000, newline scan ±200, retention 120 min. Files referenced by undrained cached results are never cleaned. -
Services/CommandOutputRenderer.cs— cell-based per-command fork ofVtLiteState. OptionalVtLiteSnapshotbaseline; rows pre-populated from the snapshot grid, cursor / saved cursor / scroll region / SGR / alt-screen state carry over, baseline rows stash aBaselineCellscopy for the per-cell change detector. Implements CUU/CUD/CUF/CUB/CNL/CPL/CHA/VPA, CUP/HVP, EL/ED, ECH, DCH/ICH, IL/DL, save/restore, alt-screen save/restore, viewport-top tracking. -
VtLiteState.Snapshot()+ per-row soft-wrap + active-SGR tracking. Snapshot deep-copies primary + alternate grids, soft-wrap flags, cursor, saved cursor, scroll region, alt-screen flag, and accumulated active SGR.WriteChar's auto-wrap flips a per-rowContinuedFromAboveflag (set on wrap, cleared on hard LF / EraseLine mode 2 / EraseDisplay; shifted with grid rows on scroll).RecordSgraccumulates non-reset SGR since the last\e[mso the snapshot'sActiveSgrcan seed the renderer's first-cell prefix. -
CompletedCommandSnapshot.VtBaseline— optional snapshot field threaded throughCommandTracker↔ConsoleWorker↔CommandOutputFinalizer.Clean. The worker snapshots_vtState(session-wide, not the tracker's per-OSC-C-reset one) right before forwarding the OSC C event so the renderer receives the screen state ConPTY has at command start. -
Soft-wrap re-joining at render time. Rows with the
ContinuedFromAboveflag append to the previous emitted line without an inter-row newline, so a longgit log --onelineentry reaches the AI as one logical line. -
Alt-screen as placeholder. Entry switches to a separate row list for alt-buffer writes; exit restores the main-buffer cursor and inserts a single
[interactive screen session]line. ConPTY's post-exit redraw of the saved main buffer is naturally absorbed by the per-cell baseline diff. -
Regex-prompt cap.
RegexPromptDetector.Scanreturns(Start, End)per match. Worker fires syntheticCommandFinished+PromptStartatStartso visible prompt characters are excluded from the[commandStart, OSC A]window — fixes trailing(Pdb)/DB<N>/>leak for pdb / perldb / python / any regex-prompt REPL.Startalso backs past contiguous non-reset SGR immediately preceding the prompt match (REPL prompt decoration). -
Test coverage: 34
VtLiteStateTestsasserts (split-at-every-byte property test, alt-screen save/restore, SGR no-shift, bracketed paste, pending-buffer overflow safety); 29 newConsoleWorker Unit Testsfor DSR splits; spill / finalize coverage inOutputTruncationHelper Tests(32),CommandOutputCapture Tests(20),CommandOutputFinalizer Tests(22),ConsoleWorker Cache Unit Tests(59); end-to-endSpillIntegrationTests(41 under--test --e2e); 23 new renderer tests. 728 tests pass. -
1 MiB Feed throughput bench (informational, not pass/fail). Baseline on Win11 AOT release: 174 MiB/s — the
<5%overhead bar is cleared with three orders of magnitude of headroom.
-
DSR reply uses live cursor state. Reads
_vtState.Row+1/Col+1instead of static row + heuristic column. Heuristic retained only as fallback during the pre-first-chunk shell-startup window. Windows path is dormant in practice — ConPTY intercepts DSR before ripple sees it. -
peek_consolesnapshot routes through live state.GetRecentOutputSnapshotreturns_vtState.Render()directly instead of re-parsing the recent-output ring through a freshVtLite()on every call. Tracker keeps its ownVtLiteStatefed fromFeedOutput, reset on the same triggers as the ring. Raw ring buffer is retained forGetRawRecentBytes()—ModeDetectorreads bytes pre-VT-reshape. -
Allocation-minimal
Feedhot path.Feed/ParseEscape/ApplyCsioperate onReadOnlySpan<char>directly.paramsStr.Split(';')replaced with a zero-allocGetParamhelper; pending merges use a 512-charstackallocbuffer withArrayPool<char>fallback. With allocation pressure removed, live tracking runs on every platform — the earlier!OperatingSystem.IsWindows()gate (added when GC pressure caused deno adapter-test flakes) is gone. -
Finalize ownership moved from
CommandTrackertoConsoleWorker. Tracker now only emits aCompletedCommandSnapshoton primary completion; worker runs cleaning, echo-stripping, truncation, and cache insertion in one place.ConsoleManagerno longer reassembles output viadrain_post_output— it forwards the worker's finalizedCommandResultdirectly. Inline and deferred always read from the same finalized result shape. -
Bare
\ris now cursor-reset only (no row clear). Matches what the human sees in the live terminal. Spec semantics produce identical results for properly-formed progress bars (\r\x1b[Kor full rewrites); slightly truthier results for short rewrites that match the live-terminal residue. -
Node REPL integration script emits OSC bytes BEFORE the visible prompt instead of after — same pattern pwsh's prompt fn has always used. Eliminates the trailing
>on every node REPL command. -
Build.ps1gains-Sign. Optional Authenticode signing ofdist/ripple.exebefore npm/dist deploy. Defaults preserve unsigned dev workflow; pass-Signfor publish builds. PFX password read interactively viaRead-Host -AsSecureString.
-
#4 —
echo … | grepon Git Bash via ConPTY no longer drops trailing match lines. ConPTY's screen-redraw burst around prompts contains real grep output (cherry,elderberry) plus absolute cursor positioning that the legacyStripAnsisilently dropped — leaving only the first match (banana). The renderer processes cursor positioning correctly and per-cell baseline diff treats matching repaint as idempotent — all matches survive. -
Cross-chunk DSR queries no longer leak downstream. Old
text.Contains("\x1b[6n")substring check missed DSR queries straddling two PTY reads. Partial ESC bytes flowed into parser / mirror / output; shell sat indefinitely waiting for a reply that never fired. NewAnswerAndStripDsrbuffers the partial prefix, completes on next chunk, replies once. -
Orphaned inline
TaskCompletionSourceonHandleExecuteAsynctimeout / shell-exit. Previously the inline TCS was not detached on those branches, breakingwait_for_completion's ability to drain timed-out commands. Per-id routing through_inlineDeliveriesByIdcloses the race. -
OSC stripping no longer swallows past a prior ST terminator. OSC alternative
\x1b\][^\x07]*\x07previously matched across an earlierESC \\(ST) terminator to a later bare BEL when input mixed ST-terminated title OSCs with subsequent BELs. Tightened so each OSC stops at its own terminator. -
Unix spill file permissions. Spill directory is
0700, files0600viaUnixCreateModeon .NET 9. Command output (which can contain secrets) is no longer world-readable on multi-user hosts. -
Progress-bar redraws no longer flood AI-visible output.
StripAnsirewritten as a line + cursor-row tracker: bare\roverwrites in place, CSI cursor-up rewinds row index, EL/ED clears,\bundoes one char. Previously stripped as raw bytes; a./Build.ps1invocation filled output with dozens of stale frames. Visible mirror is untouched — human still sees a live progress bar. -
Trailing
\e[mreset SGR is no longer lost when output endstext\r\n\e[m.Renderflushes pending SGR to the last non-empty row'sTrailingSgrso end-of-output color resets reach downstream consumers instead of leaving color stuck on.
- First alt-screen run on a fresh console after non-alt commands may include ConPTY's post-exit redraw of the visible session history (typically 3–6 prior prompts). Subsequent alt-screen runs in the same console produce clean output. Cause: subtle divergence between
ConsoleWorker._vtStateincremental session state and ConPTY's screen view; the first ConPTY full redraw syncs them. Mitigation: ignore the first noisy response or discard one warm-up command. Affects only alt-screen workflows (vim, less, htop) on the very first such command of a session.
.github/workflows/release.yml triggers on v* tag pushes. NativeAOT publish, unit-test gate, tag/version cross-check, then Azure OIDC login → Authenticode sign via Azure Key Vault (kv-yotsuda-sign) → npm publish --access public --provenance → gh release create with the per-version CHANGELOG section as release body and dist\ripple.exe attached. The release environment requires reviewer approval and locks deploys to v* tags. Federated credential is repo+environment scoped; no client secret stored in the repo. The npm publish carries an SLSA build provenance attestation verifiable back to the workflow run.
Debuggers as a first-class adapter type. The schema gains three additive fields — process.executable_candidates, modes.advance_commands, commands.debugger — so a single YAML can describe any debugger's step / print / breakpoint vocabulary in a vendor-agnostic way. AI agents drive perldb and jdb using the same operation names, no per-debugger knowledge required. perldb / jdb / pdb are the first family: debugger adapters.
Three new debugger adapters plus three new REPLs (sqlite3, lua, deno) bring the embedded set to 18 adapters. Plus two root-cause fixes that close out the user-input-contamination class of flakes: a hold-gate buffers user keystrokes during AI command execution, and --adapter-tests worker windows launch fully hidden (SW_HIDE) so a long test run no longer disrupts the user's other windows. 100 adapter assertions green.
-
family: debuggeradapter framework with three instances.perldb— Perlperl -d -e 0scriptless debugger. PromptDB<N>, nestedDB<<N>>on breakpoint pause. 8 tests including end-to-end breakpoint-hit / inspect / continue / verify-return chain.jdb— Java Debugger detached mode (jdbwith no target class). Prompt>. 5 tests covering deferred breakpoint registration, meta-command dispatch, detached-mode restrictions.pdb— Python's built-in debugger viapython -c "import pdb; pdb.Pdb().set_trace()". Prompt(Pdb). 6 tests.
-
process.executable_candidates(schema §3). Ordered launcher list tried left-to-right with%VAR%env expansion; first existing file wins. Solves "single absolute path doesn't port across distributions" for interpreters with multiple install locations (Strawberry / ActivePerl / Git-bundled perl; Microsoft OpenJDK / Temurin / Corretto / Zulu; python.org / Windows Store / Anaconda). Falls back toexecutable, then the adapter name. -
commands.debugger(schema §10). Structured debugger vocabulary: navigation (step_in,step_over,step_out,continue,run), inspection (print,dump,backtrace,source_list,locals,where,args), breakpoints (breakpoint_set,breakpoint_set_line,breakpoint_list,breakpoint_clear_all). Each field is a template with{expr}/{target}/{line}/{file}placeholders, ornullwhen unsupported. AI agents discover the syntax from the adapter instead of parsing help text. -
modes.advance_commands(schema §9). Distinct fromexit_commands: advance commands (step_in / step_over / step_out) change position within the same paused mode without leaving it. Lets AI agents distinguish "stepped one line, still paused" from "resumed and left the breakpoint". -
sqlite3REPL adapter.sqlite3 :memory:with.mode list+.headers offforced at startup so query output is pipe-separated and regex-friendly. 6 tests. -
luaREPL adapter. Lua 5.4 interactive interpreter with classic>prompt. Probes use the=prefix shortcut (Lua 5.3+) to return values withoutprint(). 6 tests. -
denoREPL adapter. Deno 2.x for JavaScript / TypeScript. Distinct from node — Deno evaluates TypeScript directly (no ts-node), supports top-level await, has built-in Web Platform APIs.NO_COLOR=1for clean regex matching. 6 tests. -
--no-user-inputworker flag. Test workers permanently hold user keystrokes viaInputForwardLoop. Prevents typing in unrelated windows from leaking into test PTYs — the root cause of the intermittent jshell / node / fsi / jdb-hello flakes on the 0.7 release train. -
OSC sequence stripping in
RegexPromptDetector.StripCsiWithMapnow consumesESC ] ... BELandESC ] ... ESC \in addition to CSI. Fixes failures where ConPTY's window-title setter (ESC ] 0 ; <path> BEL) sat between banner and prompt, preventing^anchoring.
-
User input held during AI command execution. New
_holdUserInputgate buffers keystrokes into_heldUserInputinstead of forwarding to the PTY whileHandleExecuteAsyncis in flight. Held bytes replay after the command completes (success / timeout / error). Ctrl+C passes through even when held so the user can always interrupt. Operates at ripple's forwarding layer (above the shell), universal across adapters regardless of line-editor presence.The hold gate complements the pre-existing
input.clear_linefield — they cover different windows. Hold gate protects during-command keystrokes (between AI command arrival and output drain).clear_lineprotects between-command keystrokes (user-typed bytes already in the shell's line editor before the next AI command), by issuing line-editor-kill bytes (Ctrl-A + Ctrl-K for readline) right before submitting. Both remain in use. -
--adapter-testsworker windows hidden (SW_HIDE). Normal usage keepsSW_SHOWNOACTIVATE(visible-but-inactive). Test runs gate onnoUserInputto switch to fully invisible windows. Rapid window creation during a full test suite (15+ workers) previously caused focus churn that disrupted the user's other windows — now the entire test run is silent.
-
RegexPromptDetectormissed prompts behind OSC window-title sequences. Pre-0.8 the stripper handled CSI only, leaving OSC sequences intact. When ConPTY emittedESC ] 0 ; <path> BELright before the first prompt, the title bytes sat between banner newline and prompt and a regex like^> $couldn't anchor. The jdb adapter's initial-prompt detection blocked on this exact pattern.StripCsiWithMapnow also consumes OSC via BEL or ST terminator. -
CS8600warning inHandleExecuteAsync.ModeMatchis a record (reference type), soModeMatch match = defaultgave null and triggered flow-analysis warnings. Replaceddefaultwith an explicitnew ModeMatch(Name: null, Level: null)sentinel so the variable is never null. -
jdb
next-stepping test was state-dependent. Relied on a previous test leaving the VM paused at a breakpoint. The fragile design and thecontasync race (jdb returns the post-resume prompt while the VM is still running) are documented in the adapter YAML for futureasync_interleavework. -
CCL / ABCL
execute_commandresponses leaked the next?prompt.CleanDelta's trailing-prompt suppressor recognised$ # % > ❯ λbut not?. Fix: addline.EndsWith('?')toIsShellPrompt. Nested break-loop prompts (1 >/2 >) were already matched via>.
dotnet-dump analyzeis post-mortem only. Adapter shipping deferred — has the same fixture-dependency problem asjdb-hello(a dump file must exist at adapter-launch time).- IPython under ConPTY.
--simple-promptstill emits absolute cursor positioning (\x1b[5;1H) when it detects a TTY-like environment.TERM=dumbandPROMPT_TOOLKIT_NO_CPR=1don't override — it's a ConPTY-specific codepath inside IPython. The stdlibpythonadapter remains the recommended Python REPL. - perldb
b subnameondo-loaded subs. Setting a function-name breakpoint on a sub loaded viado 'file.pl'may silently not fire — address resolution doesn't always match. Workaround inperldb.yaml: use line-number breakpoints (b {line}) afterl subnamefinds the target lines.
Polish round + Clozure Common Lisp ships embedded. Seven bug fixes from adversarial testing of the v0.6.0 surface — window title split-chunk leak, nested datum comments, node / groovy signals.interrupt mis-declaration, console focus theft, line-editor buffer flush, mode detection against the wrong input source. Each fix started with a complaint or a suspected weakness, pinned the broken behaviour with a test, then replaced the implementation.
12 embedded adapters for the first time: CCL moves out of the local gitignore after empirical confirmation that the corporate AppLocker block which motivated the exclusion has been relaxed. 528 / 528 assertions pass — --test (458 unit) + --adapter-tests (70 declared, 12 adapters). The two pre-existing ConsoleWorkerTests.Run flakes (Ctrl+C standby, obsolete PTY alive) predate 0.6 and are tracked separately — invisible to release binaries.
-
Owned console window titles got clobbered by split-chunk OSC.
ReplaceOscTitlewas stateless and couldn't handle an OSC 0/1/2 title sequence straddling a PTY read-chunk boundary — the partial opener leaked to the visible terminal and the terminal interpreted following bytes as the shell's title until whatever terminator arrived. Now uses aref string pendingTailto buffer unterminated openers across chunks. 37 new unit asserts cover splits at every byte boundary plus non-title OSCs (633, 7, 112) flowing through untouched. -
#;#;(a)reported submit-ready when it needed a second datum.BalancedParensCounter's atom-run-consume branch decrementedpendingDatumCommentson atoms inside a datum-commented list — atoms already being skipped by the list's own bracket accounting. Stacked datum-comment prefixes with only one following datum therefore silently resolved. Fix: gate the atom-run branch ondatumCommentAnchorDepths.Count == 0. 32 new stress asserts cover reader-macro pathologies: char literals of quote / semicolon / pipe / backslash, multi-line strings, 200-deep nesting, quasi-quote, CL#+nilpassthrough. -
Node / groovy mis-declared
signals.interrupt. Live verification of all 10 adapters found two lies: Node's REPL can't handle Ctrl-C while its event loop is blocked (signal handler runs on the same thread), and groovysh's Ctrl-C terminates the JVM outright. Both flipcapabilities.interrupttofalse. Groovy'ssignals.interruptis nownullso MCP clients don't even try the send. SCHEMA §11 extended with nullable-interrupt semantics;SignalsSpec.Interruptis nowstring?. -
New console windows stole keyboard focus.
CreateProcessWwith onlyCREATE_NEW_CONSOLEactivates the new window by default — starting a ripple shell while the user types in their editor dropped keystrokes into ripple's buffer. Now setsSTARTF_USESHOWWINDOW | wShowWindow = SW_SHOWNOACTIVATE: window displays but isn't activated, editor keeps focus. -
User-typed bytes prepended to next AI command. Even with the focus fix, users occasionally click into a ripple console and type before noticing. Those bytes sit in the line editor and get submitted together with the next AI command as one garbled line. New
input.clear_lineschema field carries bytes to write before each execute to wipe the current line. Defaultnull(opt-in per adapter) because Python basic REPL / fsi--readline-/ Racket-i/ CCL / ABCL run without a line editor and parse\x01\x0b(Ctrl-A + Ctrl-K) as literal input. Opted in for bash and zsh where readline / ZLE emacs defaults treat the bytes as no-ops on an empty buffer. -
ModeDetectornever saw the post-OSC-A prompt. Auto_enter + nested + level_capture scannedcleanedOutput(the OSC-C..D slice), but the mode-transition signal lives in the NEXT prompt (1 >for CCL's break loop,(Pdb)for Python,N]for SBCL) which arrives AFTER OSC A fires Resolve.GetRecentOutputSnapshot()was the obvious alternative but routes the ring through VtLite, which reshapes the trailing prompt into cell-addressed coordinates that break^<prompt>$anchors. Fix: scanGetRawRecentBytes()in a short 150 ms poll loop, breaking out as soon as a non-default auto_enter mode matches. Verified against a 4-test chain walking CCL's break loop. Schema §18 Q2 is now backed by runtime evidence.
-
Clozure Common Lisp (CCL) ships embedded. Through v0.6.0
adapters/ccl.yaml+ShellIntegration/integration.lisplived locally-only because corporate AppLocker on the dev box blocked user-dir PE files under ConPTY spawn (CreateProcessW failed: 5). On 2026-04-15 that block is empirically gone:--adapter-tests --only cclruns 10 / 10 green covering probe, five expression-level tests (arithmetic,defparameterpersistence, block-comment / char-literal reader macros, default mode), and a four-test debugger-mode chain verifying §18 Q2's auto_enter + nested + level_capture path. CCL is the first native-binary Lisp in the embedded set alongside the JVM-hosted ABCL. On boxes where AppLocker persists the probe still soft-fails — same class as a missing zsh on Windows, not a regression. -
--adapter-tests [--only <name>]CLI flag — shipped in 0.6.0; now documented as the canonical way to exercise adapter-declared tests without the pre-existingConsoleWorkerTests.Runflakes hard-exiting the process. Useful for verifying a new adapter in isolation. -
input.clear_lineschema field documented in SCHEMA.md §8 alongside the empirical-verification requirement ("walk the adapter in ripple, type into its console, confirm the clear bytes wipe the buffer without syntax errors, then add the field"). Bash and zsh opt-in; everything else null by default. -
ABCL 1.9.2 gotcha in HANDOFF.md —
--add-opens java.base/java.lang=ALL-UNNAMEDon the Groovy-pattern command template silences the JDK 21 virtual-threading warning ABCL 1.9.2 prints on every cold start. -
Pre-existing E2E flake documentation in HANDOFF.md — the two
ConsoleWorkerTests.Runtests (Ctrl+C post-interrupt standby, obsolete PTY alive) and the--adapter-testsstandalone workaround. Invisible to release binaries; only affects--test --e2eduring development.
-
SignalsSpec.Interruptis nowstring?. YAML default stays"\x03"; adapters with destructive Ctrl-C handlers (groovy today, future hosts) can setinterrupt: nullto signal "no safe interrupt byte available". -
Mode detection poll window.
HandleExecuteAsyncnow waits up to 150 ms for a non-default auto_enter mode to appear in the raw ring after a command resolves. Happy path (default mode) returns immediately; transitions get up to 150 ms of headroom. -
capabilities.interruptflipped tofalsefor node and groovy. MCP clients querying the flag now see the honest story: sending Ctrl-C will NOT rescue a runaway command.
Cache-on-busy-receive salvage layer. When a command is in flight and the MCP client silently drops the response channel — ESC cancel, the MCP protocol's 3-minute ceiling, or a fresh tool call sneaking in on the same console — the worker flips the in-flight command to cache-on-complete mode so its eventual result lands in a per-console list instead of being silently discarded. The next tool call — any tool call, not just execute_command — drains the list and surfaces the result to the AI. Mirrors the PowerShell.MCP pattern, then closes three implementation holes observed in its reference.
Armed Bear Common Lisp (ABCL) joins the embedded adapter set, giving ripple a JVM-hosted Lisp reference for the balanced_parens counter and proving the Groovy pattern (java.exe from a whitelisted Program Files path loading a jar payload from %LocalAppData%) works for any future JVM-hosted REPL. 536 / 536 assertions pass (408 unit + 79 pre-existing E2E + 49 adapter-declared).
-
CommandTracker.FlipToCacheMode()detaches the in-flight TCS with aTimeoutExceptionand marks_shouldCacheOnCompleteso the eventual OSC-drivenResolve()appends to_cachedResultsinstead of delivering to the original caller. Invoked by two paths: the 170 s preemptive deadline firing, andHandleExecuteAsynccatching a freshexecute_commandon a busy console (proof the prior caller stopped listening). -
Multi-entry cached results per console.
_cachedResults: List<CommandResult>replaces the old single-slot so sequential flipped commands accumulate without racing to overwrite.ConsumeCachedOutputs()drains the whole list atomically;get_cached_outputreturns aresultsarray. -
170 s preemptive timeout cap.
CommandTracker.PreemptiveTimeoutMs = 170_000andConsoleManager.MaxExecuteTimeoutSeconds = 170clamp both worker-side timer and proxy-side pipe wait, soexecute_commandalways returns within the MCP 3-minute window even when the command keeps running in the background. -
Worker-baked status line on cached results.
SetDisplayContext(displayName, shellFamily)lets the worker compute a self-describing status line atResolvetime, threaded throughCommandResult.StatusLine→ExecuteResult.StatusLine.AppendCachedOutputsprefers it over proxy-side reformatting so drained output reads identically to inline results — even if the console has since been reused. -
84 new cache / drain test assertions — 76
CommandTrackerTests(flip semantics, list accumulation, atomic drain, status-line formatting per shell, cache survival acrossRegisterCommand, 170 s cap, wall-clock preemptive-timer path) + 8ConsoleWorkerTestsE2E covering the multi-entry wire protocol (back-to-back short-timeout commands stack and drain in one RPC). -
Armed Bear Common Lisp (ABCL) adapter —
adapters/abcl.yaml+ShellIntegration/integration.abcl.lisp. Second CL adapter, first JVM-hosted Lisp embedded. Runs ABCL 1.9.2 from%LocalAppData%\ripple-deps\abcl-bin-1.9.2\viajava.exefrom Program Files — the Groovy pattern that bypasses AppLocker's user-dir PE block. Integration scriptsetfstop-level::*repl-prompt-fun*to an OSC-emitting wrapper — simpler than CCL'sccl::print-listener-promptoverride. Prompt overridden fromCL-USER(N):to literal?so both CL adapters share mode regexes. Probe + 5 tests pass on--adapter-tests --only abcl. Debugger mode deferred (ABCL'ssystem::debug-loopuses a separate prompt mechanism). -
--adapter-tests [--only <name>]CLI flag — runs each adapter's declaredtests:block standalone, without theConsoleWorkerTests.Runharness whose pre-existing flakes hard-exit on failure. Useful for verifying a new adapter in isolation.
execute_commandtimeout cap — the tool'stimeout_secondsstill defaults to 30 but now hard-caps at 170 s; larger values are silently clamped. Worker-sideRegisterCommandapplies the same cap internally so the pipe wait and worker timer both unwind inside the MCP 3-minute window.CollectCachedOutputsAsync/WaitForCompletionAsyncdrain loops — both consume the newresultsarray fromget_cached_outputand emit oneExecuteResultper cached entry. A console that accumulated three flipped results surfaces as three entries in the next tool response.AppendCachedOutputsin every MCP tool —send_inputis now wrapped like the rest, so its response also drains any cached results on its target console and reports other consoles' busy / finished / closed state. Closes the last gap where a tool response could omit freshly-ready cached output.
- Drain hole: read-only MCP tools didn't surface stale cache — PowerShell.MCP's
CollectAllCachedOutputsAsyncis only called from execute / wait_for_completion handlers, so tools likeget_current_locationleave other consoles' caches sitting until the next execute. ripple now drains from every tool response (execute, wait_for_completion, start_console, peek_console, send_input), matching the user's "any MCP tool response" requirement. - Drain hole: older cache hidden behind timeout / flipped branches — PS.MCP's
invoke_expressiontimeout /shouldCachebranches don't consume older cache entries, so they sit until the next normal completion. ripple's atomicConsumeCachedOutputspicks up everything in one call regardless of which branch fires. CancellationTokenRegistrationself-disposal deadlock —FlipToCacheModeoriginally disposed_timeoutReginline, but when called FROM the token's own callback viaRegister(FlipToCacheMode),CTR.Disposeblocks until the callback finishes — which was the same thread currently inside the callback. Disposal now happens inResolve's cache branch andAbortPending, where the command has already finished running.
- Silent fast-completing ESC cancel — if
execute_commandcompletes normally in well under 170 s but the client already stopped listening (ESC fired before anything triggered a flip), the result is delivered via_tcs.TrySetResultand never enters the cache. ripple has no way to detect client-side cancel without a protocol extension. Commands taking more than a few hundred milliseconds are covered via the flip-on-busy-receive trigger as soon as the next tool call arrives. - Cross-agent salvage not attempted — the drain walks only consoles owned by the current agent. A sub-agent's flipped cache is not visible to the parent agent's tool calls, by design (agent isolation is a first-class ripple concept).
cmd.exe and bash polish driven by systematic shell-by-shell testing. cmd is now usable for AI commands instead of hanging indefinitely. bash subshells and command substitutions resolve correctly. pwsh integration tolerates a missing PSReadLine module without crashing the worker. 240 / 240 tests pass.
- cmd.exe AI command support — multi-line cmd commands (heredoc-equivalent batch blocks,
if/else,for /l,setlocal enabledelayedexpansionwith!VAR!) now work via a tempfile.cmdwrapper called from a single-line PTY input. ConPTY's input echo is stripped from the captured slice viaStripCmdInputEchoso AI output mirrors what pwsh and bash produce. - cmd.exe user-busy detection — a side-channel polling loop (cmd worker only, Windows only) samples the cmd process's CPU time delta and child-process count every 500 ms. CPU > 50 ms / 500 ms catches builtins like
dir /s; child detection viaCreateToolhelp32Snapshotcatches external commands likenotepad,git,xcopy,timeout /t. Either signal flips the tracker to busy soexecute_commandauto-routes around the user. Suppressed during AI command execution. - Multi-shell E2E test suite —
RunMultiShellexercises pwsh / Windows PowerShell 5.1 / cmd / bash through the worker pipe protocol with shell-specificShellProfiles. Covers ready/standby state, simple echo with input-echo strip assertion, session variable persistence, multi-line block syntax, and (bash) subshell capture + exit-code propagation.RunIntegrationScriptGuardTestverifiesintegration.ps1doesn't crash when PSReadLine is unloaded. - Additional E2E tests for pwsh — session variable persistence across execute calls, multi-line foreach, slow-command timeout + busy-state probe, cached output retrieval after timeout,
send_inputrejection on idle consoles,send_inputCtrl+C interrupt with standby recovery.
- bash integration rewritten from DEBUG trap to PS0 —
PS0=$'\\e]633;C\\a'fires OSC 633 C exactly once per command-line submit in the parent shell, working for subshells ((echo foo)), command substitutions ($(date)), pipelines, brace groups, and multi-statement lines. The old DEBUG trap approach couldn't fire for compound commands withoutset -T, and even with functrace had recursive emission issues inside__rp_precmd. The__rp_in_commandflag and DEBUG trap are deleted entirely;__rp_precmdnow emits OSC D unconditionally. - bash multi-line / multi-statement command capture — multi-line bodies route through a tempfile
.shdot-source with WSL/MSYS path translation. Multi-statement single-line commands (cmd1; cmd2; cmd3) capture all output in order — previously only the last statement was reported because each sub-command's DEBUG firing reset the OSC C marker. - cmd.exe status line — now renders as
○ Finished (exit code unavailable)instead of a misleading✓ Completed. cmd's PROMPT can't expand%ERRORLEVEL%at display time, so the worker reports a fake exit 0 for every command; the new status text makes that limitation visible to the AI instead of silently lying. - Long status-line commands truncated — the pipeline column in status lines now caps at 60 characters with
...to keep multi-line responses readable.
- cmd.exe AI commands hung forever — cmd has no preexec hook to fire OSC 633 C, so the proxy tracker waited on a
_commandStartthat never advanced. The worker now callsSkipCommandStartMarker()afterRegisterCommandfor cmd, and cmd's PROMPT emits a fakeOSC 633 D;0so the resolve path completes. - bash subshell commands hung forever —
(echo foo),(exit N), command substitutions all blocked the AI tracker indefinitely (and the multi-statement gate before that fix only captured the last sub-command's output). The PS0 rewrite resolves both issues. - bash multi-line newlines lost in PTY echo — embedded
\nin execute payloads got submitted as Enter and dropped subsequent lines into the continuation prompt. The tempfile-dot-source path preserves the body as a single source file. - pwsh integration crash when PSReadLine missing —
Set-PSReadLineKeyHandlerandSet-PSReadLineOptioncalls are now guarded byGet-Module PSReadLineplus per-calltry/catchso a screen-reader fallback orRemove-Moduledoesn't throwCommandNotFoundExceptionat integration-load time.
- cmd.exe exit codes are always reported as 0. cmd's PROMPT can't expand
%ERRORLEVEL%at display time, so the worker emits a fakeOSC 633 D;0after every command. AI commands show asFinished (exit code unavailable)to make the limitation visible. Use pwsh or bash if you need exit-code-aware execution. (The visible terminal still has the real%ERRORLEVEL%; only the AI-side capture is affected.) Remove-Module PSReadLinemid-session breaks the pwsh worker. PSReadLine spawns persistent reader threads that survive module unload (.NET can't fully unload binary modules), so the orphaned threads keep consuming console input bytes and the next AI command hangs forever. ripple can't recover from this state. Documented in README.- cmd.exe builtin interactive prompts are not detected as user-busy (
pause,set /p). Zero CPU + zero children leave both polling signals silent. Uncommon enough to leave undetected.
Visible-console rescue tools. New peek_console (read-only snapshot of what a console is displaying) and send_input (raw keystrokes to a busy console's PTY) let the AI diagnose and respond to stuck commands without waiting for completion. execute_command timeouts now include a partialOutput snapshot for immediate diagnosis. Plus multi-line PowerShell support via tempfile dot-sourcing, and a 4 KB recent-output ring fed through a multi-row VT interpreter so the snapshot reads as the human sees the screen.
peek_consoletool — read-only snapshot of what a console is currently displaying. On Windows, reads the console screen buffer directly viaReadConsoleOutputCharacterWfor an exact match with the visible terminal. On Linux/macOS, falls back to a built-in VT-medium terminal interpreter with fixed viewport, scrolling, alternate screen buffer, and save/restore cursor. Accepts a console selector (PID or display-name substring) or defaults to the active console.send_inputtool — send raw keystrokes to a busy console's PTY input. Supports C-style escape sequences (\rfor Enter,\x03for Ctrl+C,\x1b[Afor arrow up,\\for literal backslash). Rejected when the console is idle (useexecute_commandinstead). Console must be specified explicitly for safety. Max 256 chars per call.partialOutputon execute timeout — whenexecute_commandtimes out, the response now includes a snapshot of what the console has been printing so far, so the AI can immediately diagnose stuck commands (watch mode, interactive prompts, stalled progress) without callingwait_for_completion.- Multi-line pwsh command support — multi-line PowerShell commands (heredocs, foreach, try/catch, nested scriptblocks, comments) are handled via tempfile dot-sourcing. The command body is written to a temp
.ps1file and dot-sourced, so session state (variables, functions) persists. A synthetic colorized echo replaces the dot-source line in the visible console, and PSReadLine history skips the internal tempfile path viaAddToHistoryHandler. - Console selector for peek/send_input — both tools accept a PID number or display-name substring (e.g. "Reggae" matches "#43060 Reggae"). Ambiguous matches are rejected.
peek_consoleallows omitting the selector (defaults to active console);send_inputrequires it for safety. - Busy console workflow —
execute_commandtimeout responses now include apartialOutputsnapshot for immediate diagnosis. From there the AI cansend_input(respond or Ctrl+C),wait_for_completion(wait), orpeek_console(get a fresher snapshot later).
- Recent-output ring buffer —
CommandTrackernow maintains a 4 KB circular buffer fed from every PTY byte unconditionally (AI and user commands alike), with OSC C clearing to drop PSReadLine typing noise and claim-handshake clearing to drop prior-session residue. - VT-medium terminal interpreter — the ring buffer snapshot is processed through a multi-row VT state machine handling CR/LF/BS/HT, CSI cursor positioning (CUU/CUD/CUF/CUB/CHA/CUP/HVP/VPA/CNL/CPL), EL/ED erasure, scroll regions (DECSTBM), alternate screen buffer (
\e[?1049h/l), save/restore cursor (\e7/\e8,\e[s/\e[u), reverse index (\eM), SGR/OSC as no-ops, and DEC window manipulation (\e[<params>t) as a full-grid clear trigger. Fixed viewport with soft line wrap and vertical scrolling. _outputrenamed to_aiOutputto disambiguate from the new ring buffer. AI command result slicing via OSC C/D markers is unchanged.- Cache drain in
peek_console— everypeek_consolecall now also drains cached outputs and detects closed consoles, matchingexecute_commandandwait_for_completionbehavior.
- Dot-source line visible in console — the
\e[<N>F\e[0Jerase sequence now dynamically calculates wrap row count based on terminal width, so the full dot-source input (which can exceed 200 chars and wrap to 2-3 rows) is erased completely. - Multi-line command cursor position — the colorized echo is now emitted from inside the tempfile via
[Console]::OpenStandardOutput(), bypassing pwsh's host TextWriter layer that was rewriting cursor-control escapes into absolute positioning. This keeps the child's virtual buffer cursor in sync with the visible terminal. - PSReadLine history pollution —
.ripple-exec-*.ps1dot-source lines are excluded from PSReadLine history viaAddToHistoryHandlerinintegration.ps1.
peek_consoleon Linux/macOS uses the VT-medium interpreter which may not perfectly match the real terminal for complex TUI applications. Windows uses native screen buffer reads for exact fidelity.send_inputescape sequences are interpreted by the worker; if the MCP client pre-processes backslashes (e.g. JSON\r→ CR), the worker passes them through unchanged — both paths produce correct results.
Quality-focused polish on the v0.2.0 foundation. pwsh is now stable and polished. bash / zsh / cmd are functional but lag on a few items. Drop-in upgrade from v0.2.0. Headline additions: PSReadLine-equivalent syntax-highlighted echo of AI commands, background busy / finished / closed reports on every tool response, source-cwd drift handling when auto-routing around a busy console, and a NativeAOT publish path that drops cold start from ~1 s to ~130 ms.
- Syntax-highlighted AI command echo — pwsh and Windows PowerShell 5.1 both render the echoed command with PSReadLine-equivalent colors: cmdlets, keywords (
foreach,in,if,else, ...), scriptblock bodies (Write-Hostinside{ ... }), double-quoted string interpolation ("- $i"), parameters, variables, numbers and comments. Hand-rolled state machine inServices/PwshColorizer.cswith unit tests. - Background busy / finished / closed reports — every tool response now prepends a one-line summary of any other console's state, discovered on demand via a get_status pass. Includes a
✓ #N Name | Status: User command finishedline fired exactly once when a user-typed command likepausecompletes. - Source-cwd drift handling when auto-routing — if the human user manually
cd'd in the busy source console since your last command, ripple preserves your last known cwd by using it as the cd preamble target on the routed-to console and attaches a one-lineNote: source #N was moved by user to '...'; ran in #M at your last known cwd '...'to the response. Source'sLastAiCwdis intentionally not updated, so later returns to that console still prompt a verify-and-retry warning. - Same-console drift warning — if the user manually
cd'd in the idle active console, the nextexecute_commandreturns a "verify cwd and re-execute" warning instead of running in the wrong place. wait_for_completionthree-state contract — distinguishes "no commands pending" (nothing to wait for, stop calling), "completed" (one or more drained results included), and "still busy" (call again to keep waiting).
- NativeAOT publish —
ripple.execold start dropped from ~1 s (R2R) to ~130 ms, eliminating the race between Claude Code's first MCP call and ripple warm-up. - Two concurrent owned pipe listeners — a long-running
execute_commandno longer stallsget_status/get_cached_output; the second instance stays free for status queries. - 500 ms fixed settle removed — fast commands return without the old delay; trailing output is drained adaptively via the new
drain_post_outputpipe command. - Stream capture rewritten as OSC C/D position slicing (
_commandStart/_commandEnd), replacing the layered AcceptLine noise filters and first-newline heuristics. - start_console banner / reason survive ConPTY startup — they used to flash for ~0.5 s before ConPTY's initial
\e[?25l\e[2J\e[m\e[Hwiped them. For pwsh / powershell.exe the banner is now emitted from inside the shell via the generated integration tempfile, so it sticks. - Unowned window title changed from
#PID ____to#PID ~~~~so ripple's idle state visually differentiates from PowerShell.MCP's identical____. - AI command echo blank line removed —
cmdDisplayno longer ends with\r\n, so it no longer doubles up with PSReadLine's AcceptLine newline. - Same-shell-family pinning when auto-switching away from a busy console, so bash users don't get silently bounced into pwsh.
powershell.exefallback whenpwsh.exeis absent on the host.- Tool descriptions refreshed to reflect routing, cwd preservation, busy reports and
wait_for_completionstates — AI clients now see the new behavior in their tool list.
- PSReadLine prediction and AcceptLine noise leaking into pwsh command capture — pre-existing in v0.2.0, now cleanly avoided by moving OSC C emission to
PreCommandLookupActionand slicing captured output between OSC C and OSC D. - First-OSC-B emission race that stripped the first line of output on certain pwsh commands — the Enter handler now emits OSC B before delegating to
AcceptLine.
Multi-line commands break in ConPTY— fixed in 0.4.0 via tempfile dot-sourcing.- bash / zsh / cmd still use the pre-banner-fix
start_consolepath, so banners flash briefly there. Colorization is pwsh-only. - Routing / drift logic has no automated end-to-end test coverage yet.
- Worker re-claim across proxy restarts loses
LastAiCwd/LastAiCommandstate (expected).
Claim-handshake version check + npx install path. A strictly newer proxy attaching to an older worker is refused; the old worker marks itself obsolete and stops serving pipes while keeping the PTY alive for the human user, so the MCP session disconnects cleanly without killing the shell.
- Claim-handshake version check. Old worker refuses newer proxy, marks itself obsolete, stops serving pipes, keeps the PTY alive for the human.
- npx-based install docs. README documents
npx splashshellas the primary install path.
First published release, rebranded from the internal shellpilot codename. ConPTY backend with OSC 633 shell integration, multi-shell support (bash / pwsh / powershell.exe / cmd.exe in parallel workers), console re-claim across proxy restarts, per-console cwd tracking, and the core MCP tool set (start_console, execute_command, wait_for_completion, plus file primitives).
- ConPTY backend with shell integration via OSC 633 sequences (PromptStart / CommandInputStart / CommandExecuted / CommandFinished / Cwd).
- Multi-shell support — bash, pwsh, powershell.exe, cmd.exe run simultaneously, each in its own worker process with its own Named Pipe.
- Console re-claim — worker survives proxy death; a new proxy can pick it up on the unowned pipe so the human never loses their shell when Claude Code restarts.
- Per-console window titles —
#PID Namewhen owned,#PID ____when unowned. - Per-console cwd tracking —
LastAiCwd, auto cd on switch, detection of busy active console. - Banner and reason display on
start_consolewith format-aware layout. - MCP tools —
start_console,execute_command,wait_for_completion, plus file primitives (read_file,write_file,edit_file,find_files,search_files). - Cached output drain on every MCP tool call so timed-out AI commands surface their result automatically.
- Closed-console notifications so the AI learns when a console has been closed since the last call.
- User input forwarding from the visible console to ConPTY, so the human can still type in the shared terminal (Ctrl+C, interactive prompts).
- OSC 0 window title preservation against shell overrides.
- Shell type + cwd in
start_consoleresponse and status lines.