@oh-my-pi/pi-agent-core
Fixed
- Fixed proxy stream silently returning a zero-token success response when the server disconnects without sending a
doneorerrorterminal SSE event. The stream now throws an error, surfacing the disconnect as anerrorevent withstopReason: "error"and resolvingfinalResultPromise, instead of defaulting tostopReason: "stop"with empty content and leavingstream.result()callers hanging indefinitely.
@oh-my-pi/pi-ai
Added
- Added support for
impersonated_service_accountApplication Default Credentials (ADC) in Vertex AI to enable chained impersonation without failing via 401invalid_client. - Added
AuthStorage.getCredentialOrigin(provider)(returning a structuredCredentialOrigin/CredentialOriginKind) andgetEnvApiKeyName(provider), so callers can render where a provider's auth comes from — runtime override, config, stored OAuth/api-key, env var (with the backing variable name), or fallback resolver — without parsing the prose ofdescribeCredentialSource.
Changed
- Changed
onSseEventrecording for OpenAI Responses, Azure OpenAI Responses, OpenAI Completions, and Anthropic stream providers to emit reconstructed SSE events from decoded SDK stream items instead of wrapping raw fetch responses - Changed OpenAI Completions SSE diagnostics to include
event: "chat.completion.chunk"inonSseEventrecords for chunked responses - Changed the default Anthropic model in
DEFAULT_MODEL_PER_PROVIDERfromclaude-sonnet-4-6toclaude-opus-4-6, so sessions that fall back to the provider default (no configureddefaultrole, no--model, no restored session) now start on Claude Opus 4.6.
Fixed
- Fixed duplicate upstream
tool_call_idvalues collapsing distinct tool calls during message transformation, preserving one call/result pairing per emitted tool call before provider replay and keeping generated duplicate IDs distinct after OpenAI/Mistral wire-length caps. (#2055) - Fixed the Anthropic provider retrying persistent account usage/quota limits (e.g.
429 "This request would exceed your account's rate limit",usage_limit_reached) as if they were transient. Because the error text contains "rate limit",isProviderRetryableErrormatched it and the stream retry loop looped through its 2s/4s/8s backoff (then thestreamSimplea/b/c policy re-minted the credential and ran the whole thing again) before surfacing the failure — even though the server'sretry-afterparked the account for minutes-to-hours. These errors are now recognized viaisUsageLimitErrorand surfaced immediately to the credential-rotation layer, so e.g.omp dry-balance --benchreports a rate-limited account as failed at once instead of appearing to hang. - Fixed MiniMax-compatible OpenAI-completions hosts losing tool-call argument content when
function.argumentsis streamed as an object across more than one delta. The accumulator added in #1776 wroteblock.partialArgs = rawArgsper chunk, so every chunk but the last was overwritten — for aneditcall this surfaced as a tail-slice of the patch text being applied (e.g. a single-linereplace 91..91:body extending the deletion across the surrounding rows). Chunks are now shallow-merged; for shared string keys,startsWithdistinguishes cumulative restatements (take the latest) from per-chunk-delta fragments (concatenate). Per-chunktoolcall_deltaemission for the object branch is suppressed (the previous code emittedJSON.stringify(rawArgs)per chunk, which fed downstream concat consumers —packages/agent/src/proxy.ts,openai-chat-server,openai-responses-server,anthropic-messages-server— an invalid sequence like{"input":"a"}{"input":"b"}); the merged object is flushed instead as a single concat-safe delta infinishToolCallBlockbeforetoolcall_end, so accumulators reconstruct the args correctly. The single-chunk shape covered by the existing #1776 regression test stays correct end-to-end. (#2080) - Fixed the OpenAI Responses compatibility server misrouting late
toolcall_deltaevents for earlier parallel tool calls after a latertoolcall_start. The encoder now keeps OpenFunctionCall state by content index, allocates output indexes at item start, and closes each tool item by its owntoolcall_end, preserving deferred MiniMax object-argument flushes for the matching call. (#2080)
@oh-my-pi/pi-coding-agent
Added
- Added
raw-sse.txtto debug report bundles, exporting recent raw provider SSE diagnostics when captured - Added
/modelvisibility for auto-selected role defaults: inferredpi/smol/pi/slow/designer choices now show as compact[ROLE auto]badges, while explicitly configured roles keep the existing solid badges and thinking labels. - Added credential provenance to the
/loginand/logoutprovider picker: each authenticated provider now shows where its credential comes from —(login),(api key),(env: VAR_NAME),(config),(--api-key), or(custom provider)— so a real OAuth login is distinguishable from an env var that merely aliases the provider (e.g.COPILOT_GITHUB_TOKEN). The origin is also matched by the picker's type-to-search filter.
Changed
- Changed raw SSE debug export output to prepend dropped-record metadata so truncated sessions in debug bundles now report dropped record and character counts
- Changed settings reads to cache pre-split schema paths and resolved values, with coarse invalidation on source/cwd changes.
- Changed status-line rendering to cache merged effective settings until
updateSettings()changes the configuration. - Changed
CustomEditorapp shortcut dispatch to parse each input packet once and match against precomputed canonical key sets, preserving the existing shortcut precedence while avoiding repeated key reparses. - Changed
lsp referencesto retry only when no references or only the queried declaration are returned, using two fixed 250ms retries for project-aware servers - Changed
readhandling ofhttps://github.com/<owner>/<repo>:rawto use raw page rendering only, removing the GitHub API README fallback - Changed model resolution to apply provider-priority ordering when selecting models for roles and ambiguous patterns, using
modelProviderOrdersettings and built-in provider priority so first-party providers are preferred over relays in tie cases - Changed model canonical variant selection to use the same provider-priority ordering instead of candidate order when deduplicating equivalent upstream models
- Changed the working-message shimmer to sweep at a fixed velocity (cells/second) instead of a fixed sweep duration divided by the message length. The band now advances ≤1 cell per 30fps redraw frame and stays equally smooth on short and long messages — previously a longer message swept proportionally faster and stepped visibly because it outran the redraw cadence. Sweep/round-trip duration now scales with length. Additionally, when
display.shimmer = disabledthe working line is static, so the loader no longer schedules 30fps redraws for it and falls back to the spinner-only ~12.5fps cadence. - Changed the eval fan-out trigger keyword from
workflow/workflowstoworkflowz.
Fixed
- Fixed working-message loader session accents so spinner/message color math is cached per session name, session-accent setting, and theme luminance while still updating immediately on renames, setting toggles, and theme changes.
- Fixed startup model fallback selection so sessions now prefer each provider’s configured default model before choosing the first available authenticated model
- Fixed implicit model selection path for tools and sessions by honoring persisted model-provider order when no explicit pattern is provided
- Fixed the working spinner appearing to ignore Esc for 2-3 seconds when an interrupt lands mid-tool. Esc fires the abort synchronously, but the agent loop only stops the loader at
agent_end, which it cannot reach until every in-flight tool settles inexecuteToolCalls'await Promise.allSettled(...)— and process/subagent/kernel-owning tools tear down gracefully (SIGTERM, 2-3s grace, SIGKILL), so the loader kept showing the unchanged "Working…/" line and read as a dropped keypress. The loader now switches to "Interrupting…" the instant Esc requests the abort and freezes intent-driven label updates until the turn unwinds (EventController.notifyInterrupting), so the interrupt is acknowledged immediately even while teardown completes. - Fixed a flaky JS eval worker startup that intermittently failed unrelated CI runs. The worker-ready wait reused Bun's 5s default per-test timeout as its floor, so a slow cold-start under
--isolate+ high concurrency was aborted mid-init; terminating a still-initializing Bun worker is the documented SIGILL/SIGTRAP crash trigger, which took down the whole test file. Worker init now floors at a fixed 15s infrastructure budget (independent of, and still dominated by, a larger per-celltimeout), and the JS eval test suites set a 20s file-local timeout so cold starts complete instead of being torn down. - Fixed reviewer-style subagent yields crashing the calling eval cell when a caller-supplied output schema declares
additionalProperties: falsewithout afindingsproperty.normalizeCompleteDatanow consults the active validator before splicing collectedreport_findingentries onto the yielded payload, so injection is suppressed when the schema would reject it — keeping the executor's post-mortem validation in lockstep with the in-toolyieldvalidation that already accepted the same raw payload (#2070) - Fixed Anthropic empty
toolUsestops without tool calls corrupting session history by retrying them and removing orphaned turns even at the retry cap. - Fixed MCP tools hanging in non-yolo modes by declaring
approval = "write"onMCPToolandDeferredMCPTool, and propagating theapprovalproperty throughcustomToolToDefinition()insdk.ts - Fixed session resumption after a working directory is moved/renamed (e.g.
git worktree move):--continuenow re-roots the terminal's last session into the new directory when its original directory no longer exists, explicit--resume <id> --session-dir <dir>local matches re-root instead of reopening with the stale cwd, and cross-project--resume <id>offers to move (re-root) the session rather than only forking a duplicate copy when the source directory is gone - Fixed Kitty OSC 5522 paste rejecting plain text as "no supported text or image data": the listing parser now decodes the
mime="."DATA payload (whitespace-separated MIME list) Kitty actually sends, in addition to the per-type DATA packets described by the ancillary 5522-mode spec, and per-type spec listings now request the selected payload withtype=read:mime=...instead of Kitty's dot-payload request shape (#2051) - Fixed follow-up shortcut submission of builtin slash commands so
/goal set ...applies goal mode instead of queueing as plain text. - Fixed Ctrl+Z crashing the agent on Windows with
TypeError: Unknown signal: SIGTSTP.InputController.handleCtrlZcalledprocess.kill(0, "SIGTSTP")unconditionally, butSIGTSTPis POSIX job-control and Bun/Node on Windows rejects the signal name from the JS side; the throw propagated out of the TUI input dispatcher as an uncaught exception. The handler now no-ops with a "Suspend (Ctrl+Z) is not supported on this platform" status on Windows, and on POSIX wrapsprocess.killin a try/catch that detaches the registered SIGCONT resume hook and re-start()s the TUI on failure so a rejected signal can never leave the UI stranded with a leaked listener (#2036). - Fixed a relative
--cwdtarget (e.g.omp --cwd repolaunched from/tmp) leaking the raw relative string into the session config.applyStartupCwdchdired into the resolved directory viasetProjectDirbut leftparsed.cwdas"repo", sobuildSessionOptions(which prefersparsed.cwdovergetProjectDir()) handed downstream settings/discovery/session creation a value that re-resolved against the new process cwd (/tmp/repo/repo) or persisted a relative session cwd.parsed.cwdis now re-synced to the resolved absolute project dir after the chdir. - Fixed the
--cwdlaunch flag so it is parsed and can override the startup directory instead of always falling back to the current process directory or home auto-switch target. - Fixed session auto-retry for generic
upstream_error: Upstream request failedgateway failures. - Stripped read-output line-number prefixes (
N:) from auto-piped bare body rows in the hashline edit parser, so pasting3:textwithout a+prefix no longer injects3:as literal content. Uses single-pass stripping to avoid corrupting content whose own text starts withdigits:(#1492). - Fixed
evalllm()returning HTTP 400 "Instructions are required" when called without asystemprompt against providers (notablyopenai-codex) whose Responses transformer drops theinstructionsfield on an empty system prompt.runEvalLlmnow sends a minimal default system prompt ("You are a helpful assistant.") when nosystemis supplied, sollm("question")works against every provider; an explicitsystem=still wins. - Fixed the Python
read(path, offset, limit)prelude helper rejecting documented positional arguments withTypeError: read() takes 1 positional argument but 3 were given. The signature was keyword-only (def read(path, *, offset=1, limit=None)) while the eval helper table advertises positional optional args; agents that calledread("file.py", 10, 20)literally crashed. The*is removed so bothread("f", 10, 20)andread("f", offset=10, limit=20)work. - Fixed
evalreset cells failing with"Python kernel reset already in progress"/"JS context reset already in progress"when two cells happened to overlap on the same session (e.g. a rapid resubmit, or a parallel-cell race). The executor now coalesces concurrent resets — additional callers wait for the in-flight reset to finish and then run on the freshly restarted kernel — instead of throwing a user-visible error for what is purely an internal coordination state. - Fixed the
evaltool description advertising theagent()helper unconditionally even in subagent sessions whose parent forbids spawning. WhengetSessionSpawns()returns"", the prelude doc now omitsagent()so the model is not promised a helper that can only ever throw "Cannot spawn 'task'. Allowed: none (spawns disabled for this agent)". - Fixed plan mode rejecting
local://plan-artifact edits when addressed via the absolute path thereadtool echoes back in the[path#tag]header.enforcePlanModeWritepreviously only matched the literallocal://scheme; it now also accepts any absolute path whose realpath resolves inside the session's local sandbox root, so the absolute spelling and thelocal://spelling are interchangeable in plan mode. - Fixed snapshot tags freshly minted by
readbeing rejected as stale by a subsequenteditagainst the same file when the two sides reached the file via symlink-equivalent spellings (e.g. macOS/tmp/…vs/private/tmp/…, orread local://foo.mdrecording under the file'sfs.realpathwhileedit local://foo.mdlooked up under the rawpath.resolve(localRoot, …)form). The file snapshot store now keys every record/lookup through arealpath-canonicalized key (canonicalSnapshotKey), fusing all spellings of the same on-disk file onto one snapshot entry. - Fixed
readof agithub.com/<owner>/<repo>URL with:rawreturning the full JS-rendered HTML shell. Repo roots now resolve to the decoded README via the GitHub API (/repos/<owner>/<repo>/readme), falling back to the raw HTML only when the API returns no usable payload. - Fixed
issue://andpr://reads returning stale OPEN/CLOSED state after a successfulgh issue close/gh pr merge(or any other state-changingghinvocation) in the same session. Thebashtool now invalidates the matchinggithub-cacherows before executing anygh (issue|pr) <close|reopen|merge|delete|edit|comment|lock|unlock|pin|unpin|transfer|develop|ready|review>command. - Fixed line-range selectors on PDF/DOCX/PPTX/XLSX/RTF/EPUB reads being ignored. The markit-converted markdown body now flows through the same in-memory range slicer used for plain text, so
file.pdf:50-100andfile.pdf:5-16,40-80slice the converted body instead of returning the whole document. - Fixed the
readselector cheatsheet incorrectly promising "exactly one line" for:N+1while the implementation pads single-line reads with ≤1 leading and ≤3 trailing context; documented that multi-range selectors do not pad, giving callers a way to request exact bounds. - Documented the
bash.autoBackground.enabledbehavior in thebashtool prompt so theBackground job <id> started: …notice for foreground commands that exceedautoBackgroundThresholdSecondsno longer reads as a tool malfunction. - Fixed
tasksubagents whose in-toolyieldvalidator had already accepted a payload after exhaustingMAX_SCHEMA_RETRIESbeing rejected a second time by the post-mortem executor validator. The override now propagates through the yield tool'sdetails.schemaOverriddenflag, and the executor surfaces aSUBAGENT_WARNING_SCHEMA_OVERRIDDENstderr line instead of re-emittingschema_violationfor data the subagent already had to ship. Finalize also degrades to no validation (matching the yield tool'slooseRecordSchemafallback) when the caller-supplied output schema fails to normalize. - Fixed the
web_searchcodexprovider returning(see attached image)/[Attached image]/See image aboveand similar non-informational image-placeholder strings as the answer. Detection broadened to a small regex set, and when annotations did produce sources we now drop the placeholder prose fromanswer(returning sources only); when neither annotations nor a real answer materialize, we throw 502 to advance the provider chain. - Fixed
search,find,ast_grep, andast_editrejecting bracket-containing file paths (Next.js routes likeapps/[id]/page.tsx) as glob patterns when the literal path exists on disk.parseSearchPathPreferringLiteralnow prefers the literal interpretation for paths that resolve on disk and only falls back to glob expansion when the literal does not exist. - Fixed
searchwith an externalhttp(s)/ftp/ws/file://URL inpathssurfacing a misleading "Path not found" error. The tool now rejects external URLs with a clear "usereadfor URLs" message. - Fixed
searchrejectingskip: nullat the schema layer;nullnow normalizes to0alongside the omitted case, matching how callers serialize default pagination state. - Fixed
searchreturning zero matches with no explanation when explicit file targets exceed the native grep cap (4 MB). The tool now surfaces aSkipped oversized files (>4MB grep limit; …)notice listing the truncated paths. - Fixed the archive-extraction error message in
searchrecommendinggrep— which the system prompt forbids — instead of pointing toread <archive>:<member>. - Fixed
browsertab.open(name, { viewport })on an existing tab not applying the new viewport:acquireTab's reuse path now resizes the page in addition to navigating. - Fixed
browsertab metadata leakinguser:pass@basic-auth credentials in the URL surfaced to transcripts and observe snapshots; URLs are now redacted viaredactUrlCredentials(). - Fixed
browsertab.extract(format)returning aReadableResult | nullshape that the tool prompt advertised as plain content. The helper now returns the markdown/text string directly (or throws a clearToolErrorwhen extraction is empty), and the prompt matches. - Fixed
lsprequests timing out at a hard-coded 30 s ceiling when the caller supplied an explicit abort signal (e.g. the tool wall-clock). The signal is now the deadline; the 30 s default still applies when neither a signal nor an explicittimeoutMsis provided. - Fixed
lsp statusreporting servers asActive language servers: …when the binary resolves on PATH but never spawns (rustup wrapper, missing toolchain component, etc.). Status now labels each entry as(ready)or(configured, not started). - Fixed
lsp rename_filefanningwillRenameFilesrequests across every configured server (including ones with no jurisdiction over the file type) and burning the wall-clock timeout. The action now pre-filters configured LSPs to those whosefileTypescover the source or destination path, falling back to a plain filesystem rename when no server claims the type. - Fixed
lsp referencesreturning only the queried declaration (or only in-file results) on project-aware servers that had not finished indexing. The retry budget is raised from 2 → 3 with 250 / 500 / 1000 ms backoff, and the retry trigger now also fires when all results live in the queried file. - Fixed
lsp configacceptingfileTypesentries with or without a leading dot inconsistently across actions; both.tsandtsare now normalized so a missing-dot entry no longer silently excludes a server from extension-based routing. - Fixed
lsp requesterror path swallowing the params that were sent, making shape/coercion bugs on raw LSP calls impossible to diagnose in one round-trip; the error now echoes a truncated copy of the request params. - Fixed
findwith a single-star segment likedir/*recursing into subdirectories and returning nested matches.parseFindPatternalready prepends**/for top-level globs (*.ts→**/*.ts), so anything reaching native without**/was deliberately scoped by the user;recursive: falseis now passed tonatives.globto honor that scope.
@oh-my-pi/hashline
Fixed
- Stripped read-output line-number prefixes (
N:) from auto-piped bare body rows so that pasting3:textwithout a+prefix no longer injects3:as literal content. Stripping is applied only when every bare row in the hunk carries the prefix (the signature of a pasted snapshot) and removes at most one prefix per row, so a genuine body that merely starts withdigits:(YAML port maps, timestamps) is left intact (#1492).
@oh-my-pi/pi-natives
Added
- Added the
supermodifier tomatchesKey/parseKey/parseKittySequence. Key identifiers may now includesuper+(anywhere in the modifier prefix), and Kitty CSI-u sequences whose modifier mask contains the super bit (8) — e.g. Ghostty's macOS Option+BackspaceESC [127;11u— are now recognised instead of dropped (#2064).
Fixed
- Fixed the native
copyToClipboardleaving the X11 clipboard empty on Linux even while the process kept running. arboard answers clipboardSelectionRequests from a background thread that lives only as long as aClipboardinstance exists, and the binding dropped its transientClipboardimmediately afterset_text— tearing that thread down so the selection lost its owner and the clipboard read back empty (matching thereturned ok but clipboard=''symptom). The Linux path now holds a singleClipboardfor the lifetime of the process so the owner thread keeps serving, with noxclip/wl-copysubprocess; macOS/Windows keep the transient write on the calling thread (#2075).
@oh-my-pi/pi-tui
Added
- Added exported
canonicalKeyIdandaddKeyAliaseskeybinding helpers so consumers can share the same canonical shortcut matching semantics asKeybindingsManager. - Added
supermodifier support to native key parsing/matching and boundsuper+alt+backspace/super+alt+delete(andsuper+alt+d) into the word-delete defaults so Ghostty's default macOS Option+Backspace wire (ESC [127;11u— kitty modifier 11 = super|alt) deletes a word instead of falling through to single-char delete (#2064).
Fixed
- Fixed focus-changing in-place menus leaving stale Working/menu rows and parking the hardware cursor in the old menu viewport on terminals without a scroll-position oracle.
- Fixed redundant terminal cursor updates so repeated renders that do not change the cursor row, column, or visibility no longer emit ANSI move/hide sequences
- Fixed repeated cursor updates during no-op re-renders by reusing the last known cursor state, preventing unnecessary cursor position changes and hide/show sequences
- Fixed the kitty keyboard progressive-enhancement probe to honor the
CSI ? <flags> ureply even when the terminal answers the DA1 sentinel first. Previously the kitty reply was discarded once the DA1-drivenmodifyOtherKeysfallback engaged, so terminals like Superset/xterm-on-Electron stayed on the fallback and delivered Shift+Enter as a bare\r(#2042). - Bounded TUI line fitting for oversized raw rows so ANSI-heavy subagent output and zero-width-heavy text cannot grow render buffers independently of the viewport or hide visible suffix text (#2045).
- Fixed tmux offscreen-shrink frames to skip repainting when the visible tail is unchanged, avoiding intermittent blank/refresh flashes in pane terminals (#2046).
- Fixed Windows ConPTY hosts (Windows Terminal, Tabby, Hyper, VS Code) parking the viewport at the top of a full paint after a
/resumeor any long-session repaint.ProcessTerminal#safeWritenow splits oversized writes into ≤ 8 KiB pieces at line boundaries onwin32and inside WSL (where stdout still crosses ConPTY at thewslhostboundary) so each underlyingWriteFilestays below the ~32 KiB threshold where ConPTY stops tracking the cursor; the data was always delivered, but the host UI's scroll position would not follow until any focus event forced a re-query. (#2034)
What's Changed
- fix(coding-agent): suppressed reviewer findings injection when schema rejects it by @roboomp in #2071
- fix(eval): floor JS worker init timeout to stop terminate-mid-init CI flake by @AsafMah in #1982
- fix(coding-agent): retry orphaned toolUse stops to prevent history corruption by @DarkPhilosophy in #1947
- docs: document Pi extension export drift in porting guide by @tc97222 in #1978
- fix(ai): support impersonated service account ADC for vertex by @schmidtmarek in #2059
- fix(mcp): declare approval tier for MCP tools to prevent hangs in non-yolo mode by @Null3rror in #2060
- fix(tui): chunk oversized writes on Windows ConPTY so viewport tracks the cursor by @roboomp in #2035
- fix(coding-agent): guard ctrl+z handler against missing SIGTSTP by @roboomp in #2037
- fix(tui): apply follow-up slash commands by @roboomp in #2039
- fix(tui): honor kitty reply when DA1 sentinel arrives first by @roboomp in #2043
- fix(tui): bound oversized render rows by @roboomp in #2047
- fix(tui): preserve tmux viewport on unchanged offscreen shrink by @roboomp in #2048
- fix(ai): parse MiniMax think tags across providers by @roboomp in #2050
- fix(tui): handle kitty osc 5522 dot-listing paste responses by @roboomp in #2053
- fix(ai): deduplicate replayed tool call ids by @roboomp in #2057
- fix(agent): retry generic upstream gateway failures by @roboomp in #2058
- fix(tui): recognized Ghostty's super+alt+backspace as word delete by @roboomp in #2065
- Fix session resume after moving a worktree by @tc97222 in #2067
- fix(coding-agent): parse --cwd launch flag by @DarkPhilosophy in #2066
- fix(hashline): strip N: line-number prefix from auto-piped bare body rows by @WodenJay in #1997
- fix(proxy): throw error when server disconnects without terminal event by @WodenJay in #2033
- fix(ai): merge MiniMax multi-chunk object tool arguments by @roboomp in #2082
New Contributors
- @AsafMah made their first contribution in #1982
- @schmidtmarek made their first contribution in #2059
- @Null3rror made their first contribution in #2060
Full Changelog: v15.10.1...v15.10.2