@oh-my-pi/pi-agent-core
Added
- Added optional
promptCacheKeysupport toAgentOptionsandAgentvia a newpromptCacheKeyproperty so providers can receive a caller-provided prompt cache key - Added optional
ApiKeyResolveContextparameter togetApiKeyinAgentOptionsandAgentLoopConfigso key resolvers can receive retry context
Changed
- Enabled streaming API calls to re-resolve credentials through the
getApiKeycallback when retries occur after authentication-related errors Agent.abort(reason?)now forwardsreasonto the underlyingAbortController, and the synthesized aborted assistant message carries that reason onerrorMessage(string or non-AbortErrorErrormessage) instead of always defaulting to"Request was aborted". Bareabort()is unchanged.
Fixed
- Fixed handling of short-lived API keys so that expired tokens are retried with a refreshed value during 401/usage-limit failures
- Ensured fallback API key resolution uses the initially configured static
apiKeywhengetApiKeyis present - Wrapped oneshot LLM completions (
instrumentedCompleteSimple: handoff, compaction/branch summaries) in anEventLoopKeepalive. These run outside the agent#runLoop, so without the keepalive Bun's event loop stopped servicing timers while parked on the completion promise — freezing host spinners (e.g. the/handoffloader) until an unrelated terminal resize poked the loop into rendering again.
@oh-my-pi/pi-ai
Breaking Changes
- Removed the
onAuthErroroption from stream request options and shifted auth retry handling to resolver-basedapiKeybehavior, requiring callers using custom auth-retry hooks to migrate
Added
- Added
ApiKeyResolverandApiKeyauth helpers, includingisApiKeyResolver,isAuthRetryableError,resolveApiKeyOnce, andwithAuth, and exported them from the package root - Added support for a function-valued
apiKeyinSimpleStreamOptionsso a single stream request can refresh or rotate credentials during retry - Added
forceRefreshcredential option toAuthStorage.getApiKeyandrotateSessionCredentialsupport for session-level credential rotation after auth failures - Added
AuthStorage.resolver(provider, options)method that builds anApiKeyResolverimplementing the a/b/c auth-retry policy directly on the storage instance
Changed
- Changed gateway and stream auth flows to share the a/b/c retry policy, refreshing the same session credential first and then switching to a sibling credential on repeated auth failures
Fixed
- Fixed streaming auth retries to handle
401and usage-limit errors before replay-unsafe content is emitted, including failures surfaced only viaerrorStatus - Fixed tool argument validation to coerce singleton non-string values into arrays when the schema expects an array, preventing Anthropic-compatible models that emit
todo.opsas an object from getting stuck in repeated validation-error loops. (#2026) - Fixed streaming retries to buffer and suppress partial
startevents from failed auth attempts so only clean retried events are delivered - Fixed the HTTP 400 raw-request dumper (
appendRawHttpRequestDumpFor400) littering the real~/.omp/logs/http-400-requestsdirectory during tests. Provider suites exercise the 400 error path with mockedfetchresponses, which the dumper could not distinguish from genuine failures; it now skips persistence under the Bun test runner (isBunTestRuntime()). - Fixed Anthropic Opus requests unnecessarily forcing
tool_choice.disable_parallel_tool_use, allowing Claude Opus to use the provider's default parallel tool-calling behavior again. - Fixed parallel
function_callitems losing arguments against llama.cpp's OpenAI Responses endpoint (/v1/responses), where every call but the last finalized with{}and the agent rejected them withpath: Invalid input: expected string, received undefined. llama.cpp'sto_json_oaicompat_respemitsoutput_item.addedwith onlyitem.call_id(noitem.id, nooutput_index) while the matchingfunction_call_arguments.deltacarriesitem_id: "fc_<call_id>".processResponsesStreamnow registers function-call and custom-tool-call items underitem.call_idas a secondary lookup key (alongsideitem.id/output_index) so identifier-deviant hosts route deltas and done events to the right block. (#2015) - Fixed
PI_REQ_DEBUGresponse recording truncating the captured body when a streamed response was cancelled mid-flight. The response tee inwrapResponsecould callFileRequestDebugResponseLog.close()from both thecancelcallback and the resumedpull(which observesdoneonce the source reader is cancelled); the second caller saw the handle already nulled and returned before the first caller's pending write flushed, so the.res.loglost the already-buffered chunk.close()now memoizes its flush-and-close promise so every caller awaits the same completion.
@oh-my-pi/pi-coding-agent
Added
- Added
display.smoothStreamingsetting (defaulttrue) to let users enable or disable smooth assistant-stream text reveal - Added
/tan <work>slash command to fork the current conversation into a background agent so tangential work can continue asynchronously while your main session stays active - Added a background
/tandispatch message that records the handoff in the transcript and marks the delegated work as non-blocking - Added
providerPromptCacheKeysupport toCreateAgentSessionOptionsso/tanbackground sessions can reuse the parent session’s prompt-cache lineage - Added session cloning for
/tanruns with copied artifacts and shared MCP proxy tools - Added
SessionManager.forkFrom’s optionalsuppressBreadcrumbmode to avoid breadcrumb updates when forking background/tansessions - Added OSC 5522 enhanced paste handling in
InputController, so terminal clipboard events are decoded as image or text payloads and inserted without passing raw paste sequences to the editor - Added bracketed image-path paste support in
CustomEditorso a single pasted image file path (PNG/JPEG/GIF/WEBP) is loaded from disk and inserted as an image candidate - Added direct support for
Image #Ninsertion from pasted local image paths by routing successful image-path pastes through the same image normalization and resize flow as clipboard image pastes - Added
/freshto rotate the provider-facing session id and clear in-memory provider stream/cache state without changing the local session file. - Added a
ChatBlocktranscript primitive (modes/components/chat-block.ts) and a singlectx.present(...)sink (withctx.resetTranscript()) so chat output is mounted in one place instead of the repeatedchatContainer.addChild(...)+ui.requestRender()pattern scattered across controllers.ChatBlockcarries a React/Svelte-style lifecycle —onMountstarts effects,onCleanupregisters teardown,finish()self-completes (stops timers and freezes the block at its final content), anddispose()/resetTranscript()tears everything down — so animated blocks own their own resources instead of leakingsetInterval/requestRenderbookkeeping into callers. The MCP "Connecting…" spinner is now such a block. - Added a
framedBlockoutput-block helper (tui/output-block.ts) plus aborderColoroverride andapplyBg: false(no background fill) on output blocks, arenderStatusLineiconOverride, and anicon.search(magnifier) theme symbol — so tool renderers can draw self-contained muted-outline frames and search-family tools can show a magnifier instead of a checkmark.
Changed
-
Changed the bash tool frame to use a plain top rule instead of repeating "Bash" in the title bar, and folded minimizer raw-output artifact links into the status footer as
Artifact: <id>. -
Changed grouped
readoutput to use a white filled-circle mark for the group/single-read success state and omit duplicate per-file success marks inside multi-read groups. -
Changed assistant streaming output to reveal text incrementally at 30 FPS with grapheme-safe adaptive catch-up, instead of replacing the whole message chunk-by-chunk
-
Changed shimmer-driven TUI animations (working text, pending bash/eval borders, and theme activity-spinner documentation) to render at 30fps instead of 60fps.
-
Changed running
tasktool agent rows to use a static•marker and shimmer only the subagent name, leaving descriptions, stats, and nested tool detail text solid while removing the rotating status glyph from those rows. -
Changed settings singleton method access to reuse bound methods for the active instance instead of allocating a new bound function on every
settings.getlookup. -
Changed plan-mode approval to keep the drafted
local://<slug>-plan.mdfile at its original name as the canonical plan path, so approved plans are no longer renamed when leaving plan mode -
Changed plan-mode write enforcement so only
local://artifact files are writable during planning, blocking working-tree edits and allowing scratch or draft plan files in the local artifact area -
Changed the
todotool result renderer to stop redrawing every phase's full task list on each update: when a multi-phase list is rendered collapsed (the default, not manually expanded), only phases the latest update touched — the phase holding the in_progress task, any phase with a just-completed task, and phases named by the ops that ran (initcounts as touching all) — render their tasks; untouched phases collapse to a one-lineN. Name done/totalsummary. When call args are unavailable (e.g. transcript rebuilds) it falls back to the in_progress/completed-transition signals, and the manual expand toggle still shows every task. Also dropped the blank separator line previously inserted between phases. -
Changed non-agent API operations (title and commit-message generation, image generation, web search, eval
llm(), auto-thinking classifier, memory consolidation) to use session-aware API key resolution with auth retries viaregistry.resolver()/authStorage.resolver(), refreshing the active credential before rotating to another account -
Changed image generation to wrap every provider fetch branch in
withAuth, so 401 / usage-limit errors trigger credential force-refresh and rotation for authStorage-backed providers (OpenAI-hosted, antigravity, xai-oauth) while env-only providers (openrouter, gemini) stay single-attempt -
Changed web-search providers using
authStorage.getApiKey(anthropic, exa, tavily, parallel, synthetic, zai, kimi) to wrap HTTP calls inwithAuthfor automatic credential rotation on 401 / usage-limit errors -
Changed the directory grouping for
find,search,ast_grep,ast_edit, andlspdiagnostics from a single flat# dir/heading per immediate directory to a multi-level tree that folds the common path prefix into one heading. Previously every group repeated the full directory path — so results rooted outside cwd printed the absolute prefix (e.g./Users/me/proj/) on every heading and nested directories were never collapsed. Now a single-child directory chain folds into one heading (# packages/pkg/src/, including an absolute root for out-of-cwd results), subdirectories nest one#deeper (## nested/→### child.ts), and each directory's own files are listed before its subdirectories. TUI hyperlink reconstruction tracks the nested directory stack across the whole output so file and code-frame links keep resolving to the correct absolute paths. -
Changed the plan-mode approval surface from an inline transcript block plus a separate bottom selector into a single fullscreen overlay (like
/copy) and overhauled its navigation. The overlay now renders the plan per-section throughScrollView(line-level ↑/↓ scroll, Shift+↑/↓ to scroll faster, PgUp/PgDn, g/G) with no stray per-line…, and — when the terminal is wide enough and the plan has ≥2 headings — shows a compact VS Code-style section sidebar (the redundant plan-title heading and any "Contents" label are omitted). Focus moves between regions with Tab/Shift+Tab (and flows at the edges: Down past the last section or the bottom of the body drops into the approval options; Up steps back), while the sidebar glows to track the scrolled section. The sidebar can fast-jump between sections, delete a section (withuundo), and annotate sections with feedback (a); deletions and annotations are collected into refinement feedback that is submitted back to the model when the operator picks "Refine plan". Mouse works too: clicking an approval option activates it, clicking a sidebar section jumps to it, and the wheel scrolls the plan. ←/→ always drive the model-tier slider, Enter confirms, the external-editor key opens the plan, and Esc cancels. The overlay borrows the terminal's alternate screen buffer for its lifetime (fullscreenoverlay), so the transcript stays put on the normal screen instead of bleeding through scrollback behind the modal. -
Changed the interactive controllers (command, MCP, selector, extension-UI, event), debug panels, and the status/error/warning helpers to render chat output through
ctx.present(...)instead of appending tochatContainerand callingui.requestRender()directly; transcript rebuilds dispose live blocks viactx.resetTranscript()so animated blocks' timers stop on reset. -
Changed tool-execution block rendering so the container (
ToolExecutionComponent) is a transparent passthrough — it no longer inserts a top/bottom blank line, adds left/right padding, or paints a state-colored background behind tool output. Tools with substantial body now self-frame with a muted outline and the tool title in the frame's top bar (edit/apply_patch,write,ask,todo,github,goal,inspect_image,search_tool_bm25,task), matching the already-framedbash/read/eval/debug/web_search/lspblocks, while streaming/in-progress and trivial results collapse to a clean status line. The search-family list tools (find,search,ast_grep) andjobrender frameless/minimal;find/search/ast_grepshow a magnifier on success instead of a checkmark, andjobdrops itsJob:label prefix (the per-job rows are self-describing). Thesearch_tool_bm25,github, andinspect_imageframes draw with no background fill, andinspect_image's label was shortened toInspect. -
Changed the plan-mode active prompt (
prompts/system/plan-mode-active.md) to make plans decision-complete and cut filler. Added an Objective framing ("another engineer can execute end-to-end without making a single design decision"), a shared "Resolving Unknowns" section (explore discoverable facts before asking; reserveaskfor non-derivable preferences/tradeoffs with 2–4 options + a recommended default), and a single shared "The Plan" structure (Context / Approach grouped by behavior not file-by-file / ≤5 Critical files / Verification / Assumptions) that replaces the per-branch structure guidance previously duplicated across the iterative and parallel workflows. Added explicit prohibitions on sections that decide nothing (Non-Goals, Out of Scope, Alternatives Considered, Risks/Mitigations boilerplate, Future Work), on enumerating every file/line, and on inventing schema/validation/precedence policy the request never established. -
Changed completion notifications (
completion.notify) to fire whenever the agent yields its turn, including in the foreground. Theagent_endnotification was previously gated behind background mode (isBackgrounded), so an ordinary foreground turn never emitted one; the gate is gone and the desktop toast now fires on every normal turn completion (still skipped for aborted/error turns and whencompletion.notifyisoff). -
Changed the in-progress
tasktool block to keep the sharedcontextbrief (# Goal/# Constraintsbackground) visible after the first progress snapshot arrives, instead of dropping it the moment the streaming call view was replaced by the result frame, and to stop animating a spinner/clock next to theTaskframe header while running — the per-agent body lines already carry their own running spinner, so the header now shows a static state icon (matching the completed/failed header icons). The context is rendered through a sharedbuildContextSectionhelper that also undoes per-field double-encoding, so the brief reads cleanly in the result frame even thoughrenderResultreceives the raw (un-repaired) tool args. -
Changed the messaging shown when you press Esc to interrupt a streaming turn from the ambiguous
Operation aborted/Tool execution was aborted: Request was abortedtoInterrupted by user, so a deliberate user interrupt no longer reads like an internal failure. Every Esc/flush interrupt path (onEscapewhile streaming, the queued-message restore-and-abort path, and the empty-submit queue flush) threads the reason throughAgentSession.abort({ reason })→Agent.abort(reason)so it rides theAbortControlleronto the aborted assistant message'serrorMessage; the turn label renders it verbatim on both the live and replay paths, and the synthetic placeholder results paired with in-flight tool calls now readTool execution was aborted: Interrupted by user. Aborts that carry no reason still fall back to the retry-awareOperation abortedgeneric. Transcript label resolution is centralized inresolveAbortLabel(session/messages.ts).
Removed
- Removed the
/background(and/bg) slash command and the background-mode subsystem it was the sole entry point for —InteractiveMode.isBackgrounded,createBackgroundUiContext,handleBackgroundEvent, and everyisBackgroundedguard across the input/event/extension-UI controllers and UI helpers. The command suspended the whole process group viaSIGTSTP(a leftover testing shortcut) instead of detaching the running agent, which is not the expected workflow — use terminal panes or a multiplexer instead.
Fixed
-
Fixed inline
findandsearchresult blocks to align with groupedreadoutput and render their success headers with the normal tool-title color instead of accent blue. -
Fixed the working-status shimmer to opt into the loader's 30fps animated-message repaint path while keeping both the status spinner and pending bash/eval tool spinners on their normal 80 ms glyph cadence.
-
Fixed consecutive
readtool calls failing to collapse into a single grouped block when a reasoning model emits one read per completion ([thinking, read]). The read group was reset on every assistantmessage_start, so each read rendered as its own one-entryRead …line; now a read run accretes across completions and is broken only by a rendered non-empty text/thinking block, a non-read tool, or a user/IRC message — matching the transcript-rebuild path.ReadToolGroupComponentnow reports its live/finalized state so the growingRead (N)header repaints correctly on native-scrollback (risk) terminals. -
Fixed the
tasktool shared-context brief rendering raw Markdown headings (# Goal,# Constraints) inside framed call/result blocks instead of using the normal Markdown renderer. -
Fixed the animated pending border on
bash/evalblocks leaving a frozen dark "bar" segment behind after a backgrounded command finalized through the async update path. Once a command is auto-backgrounded (details.async.state === "running") the block stays "partial" in the TUI until the async job-manager delivers the final result, but it also gets committed to native scrollback — so a mid-sweep shimmer frame baked a stray darkened border segment into the committed copy. The border now stops animating (and the 60fps redraw loop stops) the moment a block enters the backgrounded state, so the committed frame is a clean static border. -
Fixed cold
omplaunch to clear native terminal history on the first paint, avoiding a once-per-launch duplicate welcome/transcript copy before the normal session replay. -
Fixed plan approval resolution so
resolvewithaction: "apply"can still find the plan file whenextra.titleis missing or stale by falling back to the current plan path and most-recent local plan artifacts -
Fixed the search-family tool magnifier glyph (
find,search,ast_grep,search_tool_bm25) to use theaccenttitle color instead ofsuccessgreen, so the icon matches the tool title in the status header instead of standing out -
Fixed TTSR stream interrupts to pass the matched rule name through the abort reason, so aborted in-flight tool placeholders say why they were stopped instead of
Request was aborted. -
Fixed URL reads for binary/special payloads to reuse local readers: remote archives list their root entries, SQLite databases show their table overview, notebooks render as editable cells, and unrenderable binary returns a metadata notice instead of decoded byte garbage.
-
Fixed pasted image-file paths that cannot be loaded to fall back to normal text paste with status feedback instead of disappearing.
-
Fixed tool-output file paths not being clickable OSC 8
file://hyperlinks in several renderers.readtitles for plain text and image files (the common case) emitted no link at all because the renderer only linked when aresolvedPathwas recorded — which the ordinary file/image read paths never set, keeping the absolute path only inmeta.source; the renderer now falls back to that source path.writeheaders were never wrapped in a hyperlink and now link to the absolute path written (file, archive entry, SQLite, and conflict resolutions).edit/apply_patchheaders wrapped the model-supplied (often cwd-relative) argument path, producing a root-anchoredfile:///rel/pathURI; they now link the absolutedetails.pathinstead. Finally,search,ast_grep, andast_editproduced doubled link targets (/proj/src/src/file.ts) for searches scoped to a subdirectory, because the renderer resolved the cwd-relative display paths against the scope directory rather than cwd — the scoped-search base is now the session cwd (with the scoped file's absolute path still seeding single-file body lines). -
Fixed
omp dry-balance --benchto recover from 401 token failures by re-minting the failing OAuth credential in place before switching accounts -
Fixed the bash tool corrupting commands that embed multi-byte UTF-8 (e.g.
✓/×inside agrep -Epattern) ahead of a trailing| head/| tail. Thebash.stripTrailingHeadTailrewrite cut at char-offset positions reported bybrush-parserwhile slicing the command by byte offset, so the trailing-pipe strip landed mid-pattern and dropped the closing quote — turning… |✓|×|XCTAssert" | tail -80into… |✓|×-80and making execution fail withpi-natives:command: unterminated double quote. Fixed inpi_shell::fixup(@oh-my-pi/pi-natives). -
Fixed
omp dry-balance --benchto recover from 401 token failures by re-minting the failing OAuth credential in place before switching accounts -
Fixed duplicate file entries in grouped outputs for
find,search,ast_grep,ast_edit, andlspdiagnostics when the same path appeared multiple times -
Fixed search, grep, and edit output rendering so repeated directory group blank-line boundaries no longer break nested path/link reconstruction
-
Fixed
omp dry-balance --benchflooding the terminal with staircased, duplicated spinner/status lines (and an indented summary) when the tty has ONLCR/OPOST disabled (raw mode). The interactive progress region separated rows with a bare LF and repositioned with a column-preserving\x1b[<n>Acursor-up, both of which only land at column 0 when the terminal translates LF→CRLF; with that translation off, every 80 ms redraw cascaded down and to the right into scrollback. The live region now carriage-returns before every cleared row, terminates each row with CRLF, and caps each row to the terminal width so a wrapped line cannot desync the cursor-up from the logical line count. -
Fixed inconsistent vertical spacing between transcript blocks: some blocks (tool results from
search/findand other renderer-backed tools) rendered with a doubled gap (a leadingSpacerplus the content box's ownpaddingY), while others (the groupedreadcard, file-mention lists, IRC cards) rendered with no gap at all. Vertical spacing is now owned entirely by the chat renderer:TranscriptContainerstrips each block's plain-blank top/bottom edges and inserts exactly one blank line between consecutive blocks, so every block is separated by a single consistent gap regardless of which component produced it. Individual components (assistant/user/tool/read-group/bash/eval/skill/custom/hook/compaction/branch/todo-reminder/plan-review messages) no longer emit their own leadingSpacer/paddingYfor separation, and multi-row groups (IRC cards, file-mention lists, completed-job batches, and the bordered command//changelog//context/version/OAuth/debug panels) are wrapped as singleTranscriptBlockchildren so the renderer spaces them as one unit. Background-colored box padding is preserved as block-internal design. -
Fixed
resolvewithaction: "discard"surfacing a hardisError"No pending action to resolve" failure to the model when the agent asked to cancel a staged action (e.g. anast_editpreview) but nothing was pending. A discard is a request to reach the "no staged change" end-state, which already holds in that case, so it is now honored as a successful cancellation ("Nothing to discard; no pending action remains."withdetails.action: "discard") instead of an error.action: "apply"with no pending action still errors. -
Fixed the collapsed tool-output expand hint rendering double brackets (e.g.
((Ctrl+O for more))) — theEXPAND_HINTtext already carried its own parentheses and thenformatExpandHintwrapped it again with the theme's bracket glyphs. The hint now resolves the key actually bound toapp.tools.expandat render time and reads⟨<key>: Expand⟩(e.g.⟨Ctrl+O: Expand⟩), so a single bracket pair surrounds it and a user remap of the expand keybinding is reflected instead of a hard-codedCtrl+O. -
Fixed the
edit/apply_patchtool dropping its outlined frame while streaming/in-progress (only the final result was framed); the in-progress diff preview now renders inside the same muted frame as the completed result. -
Fixed the
todoandjobtools rendering a success icon and success styling on a failed/error result; error results now show the error icon and a red frame border. -
Fixed
debugtool refusing everydlvlaunch on Go modules. The launch handler ranvalidateLaunchProgrambefore adapter selection and rejected any directory program withlaunch program resolves to a directory, while dlv's defaultmode=debugrequires a Go package path (a directory or.gosource file). Adapter resolution now precedes validation, directory programs prefer adapters that advertiseacceptsDirectoryProgrambefore falling back to native extensionless debuggers, the rejection only fires when the resolved adapter does not advertise that flag (set ondlvindap/defaults.json), and dlv'smodeis derived from the program shape — directories and.gofiles launch asmode=debug, other files asmode=exec— soompcan debug both Go packages and pre-built binaries (#2020).
@oh-my-pi/pi-natives
Fixed
- Fixed
applyBashFixupscorrupting commands that contain multi-byte UTF-8 before a trailing| head/| tail(or2>&1).brush-parserreports source positions as Unicode-scalar (char) offsets, butpi_shell::fixupsliced the command&strby those numbers as if they were byte offsets, so each multi-byte char (e.g.✓/×in agrep -Epattern) shifted the cut earlier and left a mangled command — e.g.… |✓|×|XCTAssert" | tail -80became… |✓|×-80, orphaning the closing quote and making the shell reject the whole pipeline withunterminated double quote. Positions are now translated to byte offsets before slicing.
@oh-my-pi/pi-tui
Breaking Changes
- Removed Kitty temp-file image transmission, its startup support probe, the
PI_KITTY_IMAGE_TRANSMISSIONoverride, and the temp-file helper exports. Kitty/Ghostty image payloads now stay on in-band base64 before placeholder/direct placement, avoiding blank first renders from temp-file load races. - Renamed
RenderRequestOptions.allowUnknownViewportMutation→allowUnknownViewportTransientRepaint. The option only permits a transient live-viewport repaint (autocomplete/IME/focused-editor chrome) on hosts that cannot report viewport position; it never authorizes a settled transcript commit. The old name implied any offscreen mutation was safe to push into native scrollback, which led callers to emit duplicate transcript copies.
Added
- Added
TUI.addStartListener()so feature hooks can re-enable terminal modes after temporary stop/start cycles such as external-editor handoffs. - Added
Editor.pasteText()to apply terminal-style paste handling for text inserted from non-bracketed paste transports - Added an optional
dispose()lifecycle method toComponentso components can release timers and subscriptions during permanent teardown - Added
Container.dispose()to propagate teardown to child components when a component tree is permanently discarded - Added
Loader.dispose()to stop the loader animation timer when the component is disposed - Added a
ScrollViewellipsisoption (defaults toEllipsis.Unicode) so callers that pre-wrap content to width can passEllipsis.Omitand suppress the stray per-line…that lands on trailing padding. - Added
ScrollView.handleScrollKey()plus afastScrollLinesoption so every scroll view gets shared navigation keys, including Shift+Arrow to scroll faster. - Added
OverlayOptions.fullscreen: while the topmost visible overlay sets it, the engine borrows the terminal's alternate screen buffer for the overlay's lifetime and paints only the modal there — no ED3, no transcript re-commit — so the transcript stays untouched on the normal screen and is not scrollable behind the modal. Mouse tracking (?1000h/?1006h) is enabled for the modal's lifetime and disabled on exit, so the rest of the app keeps the terminal's native text selection. - Added the
submitPinsViewportToTailterminal capability anddetectSubmitPinsViewportToTail(): genuine local terminals where a submit keystroke scrolls the host to its tail reconcile deferred native scrollback at the prompt-submit checkpoint even when the viewport position is unprobeable (Ghostty/kitty/iTerm/WezTerm/Alacritty). Restores the pre-regression submit reconciliation without re-enabling it for Windows Terminal/ConPTY, SSH, or multiplexers, where a submit is not proof the host is at the tail.
Changed
- Changed static
Loadermessages to repaint only at the spinner's 80 ms cadence; time-dependent message colorizers can opt into 16 ms redraws withanimated: true. - Changed keybinding matching to precompute canonical key sets so each input sequence is parsed once per binding check instead of once per candidate key.
- Made
Component.invalidate()optional so leaf components without render caches no longer need no-op invalidation hooks. TERMINALis now aRuntimeTerminalwhose post-construction capabilities (image protocol and the probe-driven flags) are writable, replacing theas unknown as MutableTerminalInfocast pattern and the positionalwithTerminalOverridesrebuild with a prototype-preservingclone().
Fixed
-
Fixed
Loadertext updates to skip identical messages and preserve the renderedTextcache instead of invalidating it every timer tick. -
Fixed fullscreen overlay alt-frame rendering to reuse the current line-preparation path instead of calling removed fitting helpers.
-
Reduced TUI render-path line fitting by deferring overlay base-frame fitting until an overlay rebuild and by reusing already-fitted lines in emitters.
-
Reduced live-region pinned repaint output by diffing unchanged viewport rows when no sealed rows are being committed to native scrollback.
-
Fixed no-append live-region pinned repaints to re-anchor the hardware cursor when the logical viewport shifts.
-
Fixed keybinding matching so printable uppercase input preserves
Shiftfor bindings such asshift+a. -
Optimized terminal image-line detection and Thai/Lao AM normalization checks to avoid hot-path regex scans and substring allocations.
-
Fixed
Markdown.render()cache hits returning the cache's mutable backing array, which let callers that append extra rows corrupt cached Markdown and duplicate those rows on every redraw. -
Fixed first-paint full replays for callers that intentionally replace terminal history by allowing
TUI.start({ clearScrollback: true }), so they do not briefly append an entire initial frame before the first clean replay. -
Fixed ED3-risk streaming cap accounting to preserve the native scrollback high-water mark for rows that were already physically committed before transient frames were viewport-capped.
-
Fixed terminal stop and restore cleanup to disable enhanced paste mode so it does not remain enabled after shutdown
-
Removed the per-frame line-fit
Mapcache from the render timer path to avoid forcing JSC rope-string hashing during scheduled viewport repaints. -
Fixed
visibleWidth()so terminal column measurements for ANSI and OSC text now match the native truncation/wrapping helpers, including OSC 66 text-sizing spans being counted at their scaled payload width -
Fixed cursor, padding, and line-fit behavior when strings contain tabs or OSC escapes by aligning
visibleWidth()with the native text-width model -
Fixed the transcript — or a re-appearing prior view such as the welcome screen — duplicating itself on terminals without a scroll-position oracle (Ghostty/kitty/iTerm/WezTerm) when a foreground tool completes by rewriting a partly-committed block, or when the transcript is reset. A non-destructive viewport repaint no longer re-paints rows that are byte-identical to what is already committed to native scrollback into the active grid; the repaint anchor is clamped to the committed-and-unchanged prefix (
min(firstChanged, scrollbackHighWater)).
What's Changed
- fix(ai): route llama.cpp parallel tool calls by
item.call_idby @roboomp in #2016 - fix(debug): accept directory programs for dlv and auto-select dlv mode by @roboomp in #2021
- fix(ai): coerce singleton array arguments by @roboomp in #2027
Full Changelog: v15.10.0...v15.10.1