@oh-my-pi/pi-agent-core
Removed
- Removed the
maxToolCallsPerTurnoption fromAgentOptionsandAgentLoopConfig, so assistant turns are no longer capped after a configured number of completed tool calls
Fixed
- Fixed stalled aborted assistant responses so the run now stops without waiting for provider iterator cleanup and returns the aborted message promptly
- Fixed
afterToolCallhandling so it now runs for completed tool executions even after a run is aborted so tool post-processing still applies - Fixed
agentLoopDetailed().detailed()so run telemetry and coverage are captured beforestream.result()resolves. - Fixed agent-loop stream invariants so
agentLoopContinueno longer mutates the caller's message array, emitted assistant events snapshot mutable provider content, terminal provider events win over late abort signals, transformed tool arguments are reflected consistently in hooks/events, and successful run-end telemetry fires from the same finalization path as failures. - Fixed tool result parsing to mark assistant tool outputs with unsupported content block shapes as errors and include a diagnostic text block
- Fixed GPT-5 Harmony leakage handling by recovering valid leaked tool calls when possible and discarding leaked partial assistant output before retrying
- Fixed tool-call cancellation handling so aborted tools are marked aborted with an explicit reason and do not report generic errors
- Fixed tool-call completion so assistant messages on abort keep only completed tool-call blocks and continue processing tool calls when a length stop still included results
- Fixed deliberate aborts (TTSR rule matches, user-interrupt labels) so a mid-stream tool-call block that never reached
toolcall_endis retained on the aborted assistant message and paired with a placeholder result labeled by the abort reason, instead of being dropped; anonymous aborts (bareabort()) still drop incomplete tool calls whose partial arguments are unsafe to replay - Fixed runs that stopped with reason
lengthafter returning tool results so execution continues to handle additional tool calls
@oh-my-pi/pi-ai
Breaking Changes
- Renamed the OAuth subpath export
@oh-my-pi/pi-ai/utils/oauth→@oh-my-pi/pi-ai/oauth(and@oh-my-pi/pi-ai/utils/oauth/*→@oh-my-pi/pi-ai/oauth/*, e.g.oauth/types,oauth/callback-server,oauth/openai-codex) after relocating the OAuth implementation out ofutils/oauth/intoregistry/oauth/. The high-level OAuth API (getOAuthProviders,refreshOAuthToken,getOAuthApiKey,registerOAuthProvider,unregisterOAuthProviders,getOAuthProvider) and theOAuth*types stay exported from the package root, unchanged.
Changed
- Changed Anthropic retry handling to avoid retrying 4xx responses other than 408 and 429
- Optimized the Anthropic
cchattestation patch to locate the billing-header placeholder with nativeBuffer.indexOf(memmem) instead of a hand-rolled byte loop. The marker sits ~99% through the body (messagesserializes beforesystem), so the old scan walked almost the entire payload; output bytes are unchanged but the patch is ~7.5x faster (563µs -> 75µs on a 1MB body). - Refactored provider configuration to a single-source registry (
registry/, renamed fromprovider-registry/with itsproviders/subdir flattened up). TheKnownProvider/OAuthProvidertype unions,PROVIDER_DESCRIPTORS,DEFAULT_MODEL_PER_PROVIDER, theserviceProviderMapenv-key fallbacks, the/loginprovider list (builtInOAuthProviders), and therefreshOAuthToken/AuthStorage.logindispatch are all derived from it. Provider defs live directly underregistry/; thin provider-specific login flows are inlined into the def file, while heavier provider-local OAuth flows and the shared OAuth flow infra (callback-server,pkce,google-oauth-shared,types, runtimeindex) now live together underregistry/oauth/(previously split acrossprovider-registry/providers/oauth/andutils/oauth/). The non-OAuth API-key paste/validation helpers (api-key-login,api-key-validation) sit beside the defs inregistry/. Adding a provider that reuses an existing wire API is now one new provider def plus one registry entry in the common case. ExposesPROVIDER_REGISTRY,getProviderDefinition,ProviderDefinition, andPASTE_CODE_LOGIN_PROVIDERS.
Fixed
- Disabled OpenAI Codex Responses stream obfuscation by sending
stream_options.include_obfuscation=false, reducing raw WebSocket/SSE debug noise and bandwidth. - Interrupted OpenAI Codex Responses streams that emit long runs of whitespace-only tool-call argument deltas, preventing degenerate WebSocket/SSE responses from filling the raw stream buffer indefinitely.
- Preserved streaming responses when Anthropic emits unrecognized content_block envelopes by ignoring unknown blocks and continuing to emit known content
- Applied cache control to the most recent tool result block when building Anthropic OAuth payloads without a preceding text block, enabling ephemeral caching for tool-result-only messages
- Kept Anthropic sampling parameters (temperature, top_p, top_k) when thinking is explicitly disabled
- Fixed raw Anthropic SSE handling by parsing event frames with strict JSON parsing and matching event-type validation, surfacing malformed frames as stream errors instead of repairing them
- Fixed Anthropic stream envelope handling to reject duplicate
content_block_startindexes and block deltas/stops for unopened blocks, preventing malformed envelope states from producing partial output - Fixed Anthropic image conversion to normalize
image/jpgtoimage/jpegand emit a placeholder for unsupported image MIME types - Fixed Anthropic thinking request preparation by clamping
max_tokensto provider/model limits and adjusting thinking budgets to a valid value - Fixed Anthropic request shaping around forced tool choice, unsigned thinking replay, prompt-cache marker placement, non-Anthropic bearer gateways, Foundry TLS loading, and strict tool-schema normalization so malformed or incompatible request payloads are rejected locally or shaped consistently before streaming
- Fixed the Anthropic stream parser shipping a truncated tool call as a completed turn. When a transport drop cut the SSE stream mid-
tool_useand a transparent reconnect spliced a fresh message envelope onto the same stream, the duplicatemessage_startwas deduped but the orphaned tool block — which never received itscontent_block_stop— survived in the assistant message with its seed{}(or partially-parsed) arguments. The terminal stop signal from the reconnect then let it flow through as a normal tool call, so e.g. areaddispatched with{}failed downstream validation (path: expected string, received undefined). The parser now treats any tool block left open at stream end as a truncated envelope and routes it through the existing retry/error path instead of emitting bogus arguments. - Fixed the Zhipu Coding Plan login prompt advertising a misleading
sk-...placeholder. Zhipu API keys are formatted<id>.<secret>(nosk-prefix), so the placeholder now matches the actual format instead of suggesting the wrong shape. (#2106) - Fixed Moonshot
kimi-k2.6(and any futurekimi-k2.x) discovered viaMOONSHOT_API_KEYstalling on first turn with no output. ThemoonshotModelManagerOptionsdiscovery mapper only marked ids containing"thinking"asreasoning: true, so dynamickimi-k2.6entries fell through withreasoning: false; the openai-completions z.ai branch was then skipped and the request reached Moonshot with nothinkingparameter at all. Moonshot K2.6 requires an explicitthinking: {type}field (the same native-API wire shape #1838 introducedthinking.keepfor), so the server held the stream silently. The mapper now stampsreasoning: true, vision input, and defaultthinkingmetadata on everykimi-k2.xid, restoring the explicitthinking: {type: "disabled"|"enabled"}wire body the Moonshot endpoint expects. (#2113)
@oh-my-pi/pi-coding-agent
Added
- Added Homebrew and mise package-manager update paths to the self-update command so installations launched from those tools are updated through their native workflows
- Added detection of Homebrew and mise install locations so self-update chooses the manager-specific updater when the active
ompbinary comes from a package-manager-managed path - Added
astConditionto TTSR rule frontmatter as a syntax-aware alternative to regexcondition, enabling AST-based matching for edit/write tool snapshots - Added a built-in
ts-redundant-clear-guardrule that flags redundant guards aroundclearTimeout,clearInterval, andclearImmediatecalls - Added a built-in
ts-no-test-timersrule that flags real timers (Bun.sleep,setTimeout,setInterval) in*.test.tsfiles, steering toward fake timers (vi.useFakeTimers()/vi.advanceTimersByTime()) - Added support for paste marker highlighting with accent styling (
[Paste #N, +X lines]/[Paste #N, Y chars]) in the prompt editor, matching the visual treatment of image references - Added pixel dimensions to pasted/loaded image placeholders in the prompt — the marker now reads
[Image #N, WxH](falling back to[Image #N]when the header can't be decoded). - The bundled shell now treats
nohupas a builtin:nohup … &runs the command without maskingSIGHUPor detaching it, so agent-started daemons stay tied to this agent's lifetime instead of leaking as orphans when the agent exits. Updated the bash tool prompt's daemon guidance to match (dropped thenohup … & / setsid … & / disowndetach recommendation in favor of a largetimeoutplus the persistent session). - Added per-tool
tool.*theme symbol keys (nerd/unicode/ascii presets) plus a quietstatus.doneglyph, so each tool's result header can carry a signature icon instead of a generic status mark
Changed
- Updated pi-ai OAuth imports to the renamed
@oh-my-pi/pi-ai/oauthsubpath (was@oh-my-pi/pi-ai/utils/oauth) across the login UI, MCP OAuth flow, model registry, setup wizard, and web-search Codex auth. The legacy-plugin specifier shim drops itspi-ai/oauth→pi-ai/utils/oauthsubpath rewrite, since the canonical@oh-my-pi/pi-ai/oauthexport now resolves directly. - Changed forced self-updates for Homebrew installs to run
brew reinstalland for mise installs to runmise install --forceaftermise upgradewhen--forceis requested - Changed TTSR rule bucketing and matching so rules with only
astConditionare treated as TTSR rules and evaluated in the interrupt flow using reconstructed edit/write source snapshots - Normalized image content before it enters model context so attached images are downscaled and preprocessed for prompts, steering messages, follow-ups, and custom agent messages
- Changed image marker format to include pixel dimensions when available (
[Image #N, WxH]), falling back to bare[Image #N]when header cannot be decoded - Changed the prompt editor to highlight large-paste placeholders (
[Paste #N, +X lines]/[Paste #N, Y chars]) with the same accent styling as image references (bold, no hyperlink), and to delete image/paste markers atomically: a single backspace or forward-delete removes the whole marker instead of leaving a broken[Paste #N, +X linesbehind. - Browser tool helpers (
tab.*) are now individually tracked and time-bounded: when aruncell hits its budget, the timeout error names the still-running helper(s) and how long each has been stalled (e.g.... (stalled on tab.screenshot({ selector: ".x" }) (29.9s))) instead of the opaqueBrowser code execution timed out after 30000ms. Page-coupled helpers that should resolve quickly (observe,screenshot,extract) also fail fast with a named per-op error atmin(cellBudget, 20s), leaving budget for the rest of the cell, rather than silently consuming the whole budget. - Derived the auth-broker OAuth callback ports (
CALLBACK_PORTS) and the paste-code login-provider set from the@oh-my-pi/pi-aiprovider registry, removing the duplicatedCALLBACK_SERVER_PROVIDERStables in the model selector and the setup-wizard sign-in scene. - Raised the
evaltool's per-celltimeoutceiling from 600s to 3600s (matchingbash), in both the Zod schema and theTOOL_TIMEOUTS.evalruntime clamp, so heavy local-compute cells can request budgets above 10 minutes. - Derived the auth-broker OAuth callback ports (
CALLBACK_PORTS) and the paste-code login-provider set from the@oh-my-pi/pi-aiprovider registry, removing the duplicatedCALLBACK_SERVER_PROVIDERStables in the model selector and the setup-wizard sign-in scene. - Reworked tool result-header glyphs to cut the overused success checkmark/dot: each tool now shows its own signature icon on success (terminal for bash, pencil for edit, magnifier/globe for search, plug for MCP, etc.; read keeps the read-group status dot), tools without a custom renderer fall back to a quiet
status.donedot, and error/warning/pending states keep the universal cross/warning/spinner - Changed steady-state health indicators (LSP server ready, OAuth logged-in, plugin-doctor checks) from a success checkmark to a colored
status.enableddot, so failures stand out instead of every line reading as a check - Changed one-shot MCP/SSH/debug confirmation messages from a generic checkmark to contextual action glyphs (add/remove, connect/enable/disable toggles, reload, job-completed), reflecting what happened rather than just "success"
- Derived the auth-broker OAuth callback ports (
CALLBACK_PORTS) and the paste-code login-provider set from the@oh-my-pi/pi-aiprovider registry, removing the duplicatedCALLBACK_SERVER_PROVIDERStables in the model selector and the setup-wizard sign-in scene.
Fixed
- Fixed package subpath exports for status-line, setup-wizard, tool-discovery, and gallery fixture modules so rewritten test imports resolve through
@oh-my-pi/pi-coding-agent. - Fixed runtime model provider discovery so extension-registered providers are now refreshed after extension load and extension-supplied models appear without restarting
- Fixed task-row shimmer timing so every running description starts its highlight on the first character together and reaches the last character together, regardless of text length.
- Fixed the
evaltool'sread/write/appendhelpers (both Python and JS backends) treatinglocal://(and other internal-URL) paths as plain filesystem paths.pathlib.Path/path.resolvecollapselocal://x.mdtolocal:/x.md, sowrite("local://x.md", …)created a junklocal:directory under the cwd instead of writing whereread local://x.mdresolves. The helpers now substitute injected on-disk roots for known schemes (currentlylocal://, pinned to the session's ownlocal://root), reject path traversal and unknownscheme://paths, and leave plain paths resolving against the cwd. - Fixed read and edit previews to surface the enclosing syntactic block's off-window boundary line (behind an ellipsis) when a shown line opens or closes a block whose other end falls outside the displayed range. Powered by a new tree-sitter
enclosingBlockBoundariesnative, so it covers brace languages and indentation languages (Python) using real syntactic spans, with a lexical bracket scan as fallback for unparseable sources. - Fixed
tab.screenshot({ selector })hanging for the entire cell budget on continuously-animating pages (WebGL /backdrop-filter"glass" effects). The element-screenshot path no longer routes through puppeteer'sscrollIntoViewIfNeeded(), whoseIntersectionObserverpromise can stall indefinitely under heavy rendering; it now does a single instantscrollIntoViewand captures withscrollIntoView: false(relying oncaptureBeyondViewport), so off-screen elements are still captured without the stall. - Fixed follow-up message submissions to forward pending clipboard-pasted images to
session.promptin both streaming and non-streaming flows - Fixed follow-up handling to clear consumed clipboard image state after submission so pasted images are not silently carried into later messages
- Fixed clipboard-pasted images being rejected when steering or following up during compaction. Instead of bailing with "Retry after it completes to send images", the message and its images are now queued via
queueCompactionMessageand forwarded to the session (steer/follow-up/prompt) when the compaction queue flushes. - Fixed edit tool result previews to show only current-file lines and collapse long inserted blocks instead of echoing removed content.
- Fixed
generateDiffStringto omit the mid-skip...placeholder between two nearby edits, conveying the elided gap via the jump in line numbers instead (consistent with how leading/trailing context skips already render). The placeholder row was indistinguishable from a genuine...context line and wasted a row in compact previews. - Fixed concurrent interactive dialogs clobbering each other on the shared editor surface.
ExtensionUiControllerpresents the selector / input / editor modals by swapping a component into the singleeditorContainerand stealing focus, with no serialization — so a secondselect/input/editorrequest (from a hook, extension, theasktool, or an internal flow) opened while one was already up would clear the container and re-focus, orphaning the first dialog. Its promise then hung until the caller's signal aborted (surfacing a strayAsk input was cancelledon top of the answered call). These modals are now serialized through#presentDialog: at most one shows at a time and the rest queue (FIFO); a queued request whose signal aborts before its turn resolvesundefinedand is never shown. The first dialog is still presented synchronously, so single-dialog timing is unchanged. - Fixed the
asktool potentially hanging when the model emitted twoaskcalls in one tool batch.asknow declaresconcurrency: "exclusive", so the agent loop serializes the batch and each question's selector runs to completion before the next starts, instead of racing for the shared selector surface. - Expanded
@path/to/fileimport references in CLAUDE.md / AGENTS.md / GEMINI.md (and the other discovered context-file flavors) when loading them into the system prompt, matching the convention used by Claude Code, Goose, and other agents. Imports resolve relative to the importing file's directory, support~/..., recurse up to 5 hops, and are skipped inside fenced code blocks and inline code spans so technical examples likenpm install @types/nodesurvive intact (#2111).
Removed
- Removed the special Anthropic
claude-opus-4-8tool-call batch cap; sessions no longer abort an in-flight provider stream after a fixed number of completed tool calls.
@oh-my-pi/hashline
Added
- Added
maxAddedRunContextoption to control how many added lines are shown at each side of collapsed inserted runs, withmaxUnchangedRunkept as a backward-compatible alias
Changed
- Changed
buildCompactDiffPreviewto omit removed lines from the preview while preserving removal counts for offset tracking - Changed
buildCompactDiffPreviewto collapse long contiguous added runs with a bare…marker, keeping only the first and lastmaxAddedRunContextlines visible (the surrounding line numbers convey how many were elided)
Fixed
- Fixed compact edit previews to omit deleted content, keep visible lines anchored to the current file, and collapse long inserted runs with a bare
…elision marker. - Fixed compact edit previews to render added/current lines without diff-prefix padding and normalize adjacent ASCII/Unicode elision markers to one
….
@oh-my-pi/pi-natives
Added
- Added the
enclosingBlockBoundariesnative API (withEnclosingBoundaryOptionsandLineRangetypes) that returns, for a set of visible line ranges, the off-window boundary lines of every multi-line tree-sitter node whose span crosses the window — the closer when an opener is shown and the opener when a closer is shown. Covers brace and indentation languages (Python) via real syntactic spans; returnsnullfor unrecognized languages or sources with syntax errors so callers can fall back to a lexical scan. - Added a
nohupshell builtin to the embeddedpi_shell, shadowing the external/usr/bin/nohup. It runs its operand command and propagates that command's exit status (and reportsmissing operand/ exit 125 with no operand), but deliberately does not maskSIGHUPor detach the child into a new session the way realnohupdoes. Agents reach fornohup … &assuming the shell is one-shot; in this persistent embedded shell that assumption is wrong and the only effect of realnohupwas to leak background processes that outlived the host. The builtin keeps such commands as ordinary descendants so they are reaped with the host instead of surviving as orphans.
@oh-my-pi/pi-tui
Added
- Added
atomicTokenPatterntoEditor: when set to a global regex matching placeholder tokens such as[Image #1, 800x600]or[Paste #2, +30 lines], a single backspace or forward-delete landing anywhere on a token removes the whole token instead of corrupting it into stray text.
Changed
- Changed the large-paste placeholder label from
[paste #N +X lines]/[paste #N Y chars]to[Paste #N, +X lines]/[Paste #N, Y chars].
Fixed
- Fixed pasting large text lagging the prompt for hundreds of milliseconds before the
[paste #N …]placeholder appeared.StdinBufferassembled bracketed pastes by re-concatenating and re-scanning the entire accumulated buffer on every incoming stdin chunk (#pasteBuffer += chunk; indexOf(END)), which is O(n²) in the paste size and dominates when the terminal/PTY delivers the paste in many small reads (SSH, tmux, slow hosts) — a 1 MB paste at 1 KB chunks cost ~33 ms and 5 MB ~740 ms. Chunks are now collected in an array and joined once when the end marker arrives, with a short overlap tail carried across chunk boundaries so a marker split between two reads is still detected without rescanning, making assembly O(n) (~1 ms for 5 MB). TheEditorpaste cleaner also dropped itssplit("").filter().join("")per-code-unit array allocation in favor of a single control-character regex pass (~20× faster on large pastes).
What's Changed
- fix(ai): corrected misleading Zhipu API key placeholder by @roboomp in #2107
- fix(coding-agent): expand @-imports in AGENTS.md / CLAUDE.md context files by @roboomp in #2112
- fix(ai): mark moonshot kimi-k2.x as reasoning + vision during discovery by @roboomp in #2114
Full Changelog: v15.10.4...v15.10.5