github can1357/oh-my-pi v15.10.2

latest release: v15.10.3
5 hours ago

@oh-my-pi/pi-agent-core

Fixed

  • Fixed proxy stream silently returning a zero-token success response when the server disconnects without sending a done or error terminal SSE event. The stream now throws an error, surfacing the disconnect as an error event with stopReason: "error" and resolving finalResultPromise, instead of defaulting to stopReason: "stop" with empty content and leaving stream.result() callers hanging indefinitely.

@oh-my-pi/pi-ai

Added

  • Added support for impersonated_service_account Application Default Credentials (ADC) in Vertex AI to enable chained impersonation without failing via 401 invalid_client.
  • Added AuthStorage.getCredentialOrigin(provider) (returning a structured CredentialOrigin / CredentialOriginKind) and getEnvApiKeyName(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 of describeCredentialSource.

Changed

  • Changed onSseEvent recording 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" in onSseEvent records for chunked responses
  • Changed the default Anthropic model in DEFAULT_MODEL_PER_PROVIDER from claude-sonnet-4-6 to claude-opus-4-6, so sessions that fall back to the provider default (no configured default role, no --model, no restored session) now start on Claude Opus 4.6.

Fixed

  • Fixed duplicate upstream tool_call_id values 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", isProviderRetryableError matched it and the stream retry loop looped through its 2s/4s/8s backoff (then the streamSimple a/b/c policy re-minted the credential and ran the whole thing again) before surfacing the failure — even though the server's retry-after parked the account for minutes-to-hours. These errors are now recognized via isUsageLimitError and surfaced immediately to the credential-rotation layer, so e.g. omp dry-balance --bench reports 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.arguments is streamed as an object across more than one delta. The accumulator added in #1776 wrote block.partialArgs = rawArgs per chunk, so every chunk but the last was overwritten — for an edit call this surfaced as a tail-slice of the patch text being applied (e.g. a single-line replace 91..91: body extending the deletion across the surrounding rows). Chunks are now shallow-merged; for shared string keys, startsWith distinguishes cumulative restatements (take the latest) from per-chunk-delta fragments (concatenate). Per-chunk toolcall_delta emission for the object branch is suppressed (the previous code emitted JSON.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 in finishToolCallBlock before toolcall_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_delta events for earlier parallel tool calls after a later toolcall_start. The encoder now keeps OpenFunctionCall state by content index, allocates output indexes at item start, and closes each tool item by its own toolcall_end, preserving deferred MiniMax object-argument flushes for the matching call. (#2080)

@oh-my-pi/pi-coding-agent

Added

  • Added raw-sse.txt to debug report bundles, exporting recent raw provider SSE diagnostics when captured
  • Added /model visibility for auto-selected role defaults: inferred pi/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 /login and /logout provider 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 CustomEditor app 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 references to retry only when no references or only the queried declaration are returned, using two fixed 250ms retries for project-aware servers
  • Changed read handling of https://github.com/<owner>/<repo>:raw to 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 modelProviderOrder settings 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 = disabled the 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/workflows to workflowz.

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 in executeToolCalls' 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-cell timeout), 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: false without a findings property. normalizeCompleteData now consults the active validator before splicing collected report_finding entries 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-tool yield validation that already accepted the same raw payload (#2070)
  • Fixed Anthropic empty toolUse stops 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" on MCPTool and DeferredMCPTool, and propagating the approval property through customToolToDefinition() in sdk.ts
  • Fixed session resumption after a working directory is moved/renamed (e.g. git worktree move): --continue now 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 with type=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.handleCtrlZ called process.kill(0, "SIGTSTP") unconditionally, but SIGTSTP is 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 wraps process.kill in 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 --cwd target (e.g. omp --cwd repo launched from /tmp) leaking the raw relative string into the session config. applyStartupCwd chdired into the resolved directory via setProjectDir but left parsed.cwd as "repo", so buildSessionOptions (which prefers parsed.cwd over getProjectDir()) 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.cwd is now re-synced to the resolved absolute project dir after the chdir.
  • Fixed the --cwd launch 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 failed gateway failures.
  • Stripped read-output line-number prefixes (N:) from auto-piped bare body rows in the hashline edit parser, so pasting 3:text without a + prefix no longer injects 3: as literal content. Uses single-pass stripping to avoid corrupting content whose own text starts with digits: (#1492).
  • Fixed eval llm() returning HTTP 400 "Instructions are required" when called without a system prompt against providers (notably openai-codex) whose Responses transformer drops the instructions field on an empty system prompt. runEvalLlm now sends a minimal default system prompt ("You are a helpful assistant.") when no system is supplied, so llm("question") works against every provider; an explicit system= still wins.
  • Fixed the Python read(path, offset, limit) prelude helper rejecting documented positional arguments with TypeError: 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 called read("file.py", 10, 20) literally crashed. The * is removed so both read("f", 10, 20) and read("f", offset=10, limit=20) work.
  • Fixed eval reset 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 eval tool description advertising the agent() helper unconditionally even in subagent sessions whose parent forbids spawning. When getSessionSpawns() returns "", the prelude doc now omits agent() 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 the read tool echoes back in the [path#tag] header. enforcePlanModeWrite previously only matched the literal local:// scheme; it now also accepts any absolute path whose realpath resolves inside the session's local sandbox root, so the absolute spelling and the local:// spelling are interchangeable in plan mode.
  • Fixed snapshot tags freshly minted by read being rejected as stale by a subsequent edit against the same file when the two sides reached the file via symlink-equivalent spellings (e.g. macOS /tmp/… vs /private/tmp/…, or read local://foo.md recording under the file's fs.realpath while edit local://foo.md looked up under the raw path.resolve(localRoot, …) form). The file snapshot store now keys every record/lookup through a realpath-canonicalized key (canonicalSnapshotKey), fusing all spellings of the same on-disk file onto one snapshot entry.
  • Fixed read of a github.com/<owner>/<repo> URL with :raw returning 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:// and pr:// reads returning stale OPEN/CLOSED state after a successful gh issue close / gh pr merge (or any other state-changing gh invocation) in the same session. The bash tool now invalidates the matching github-cache rows before executing any gh (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-100 and file.pdf:5-16,40-80 slice the converted body instead of returning the whole document.
  • Fixed the read selector cheatsheet incorrectly promising "exactly one line" for :N+1 while 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.enabled behavior in the bash tool prompt so the Background job <id> started: … notice for foreground commands that exceed autoBackgroundThresholdSeconds no longer reads as a tool malfunction.
  • Fixed task subagents whose in-tool yield validator had already accepted a payload after exhausting MAX_SCHEMA_RETRIES being rejected a second time by the post-mortem executor validator. The override now propagates through the yield tool's details.schemaOverridden flag, and the executor surfaces a SUBAGENT_WARNING_SCHEMA_OVERRIDDEN stderr line instead of re-emitting schema_violation for data the subagent already had to ship. Finalize also degrades to no validation (matching the yield tool's looseRecordSchema fallback) when the caller-supplied output schema fails to normalize.
  • Fixed the web_search codex provider returning (see attached image) / [Attached image] / See image above and 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 from answer (returning sources only); when neither annotations nor a real answer materialize, we throw 502 to advance the provider chain.
  • Fixed search, find, ast_grep, and ast_edit rejecting bracket-containing file paths (Next.js routes like apps/[id]/page.tsx) as glob patterns when the literal path exists on disk. parseSearchPathPreferringLiteral now prefers the literal interpretation for paths that resolve on disk and only falls back to glob expansion when the literal does not exist.
  • Fixed search with an external http(s)/ftp/ws/file:// URL in paths surfacing a misleading "Path not found" error. The tool now rejects external URLs with a clear "use read for URLs" message.
  • Fixed search rejecting skip: null at the schema layer; null now normalizes to 0 alongside the omitted case, matching how callers serialize default pagination state.
  • Fixed search returning zero matches with no explanation when explicit file targets exceed the native grep cap (4 MB). The tool now surfaces a Skipped oversized files (>4MB grep limit; …) notice listing the truncated paths.
  • Fixed the archive-extraction error message in search recommending grep — which the system prompt forbids — instead of pointing to read <archive>:<member>.
  • Fixed browser tab.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 browser tab metadata leaking user:pass@ basic-auth credentials in the URL surfaced to transcripts and observe snapshots; URLs are now redacted via redactUrlCredentials().
  • Fixed browser tab.extract(format) returning a ReadableResult | null shape that the tool prompt advertised as plain content. The helper now returns the markdown/text string directly (or throws a clear ToolError when extraction is empty), and the prompt matches.
  • Fixed lsp requests 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 explicit timeoutMs is provided.
  • Fixed lsp status reporting servers as Active 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_file fanning willRenameFiles requests 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 whose fileTypes cover the source or destination path, falling back to a plain filesystem rename when no server claims the type.
  • Fixed lsp references returning 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 config accepting fileTypes entries with or without a leading dot inconsistently across actions; both .ts and ts are now normalized so a missing-dot entry no longer silently excludes a server from extension-based routing.
  • Fixed lsp request error 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 find with a single-star segment like dir/* recursing into subdirectories and returning nested matches. parseFindPattern already prepends **/ for top-level globs (*.ts**/*.ts), so anything reaching native without **/ was deliberately scoped by the user; recursive: false is now passed to natives.glob to honor that scope.

@oh-my-pi/hashline

Fixed

  • Stripped read-output line-number prefixes (N:) from auto-piped bare body rows so that pasting 3:text without a + prefix no longer injects 3: 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 with digits: (YAML port maps, timestamps) is left intact (#1492).

@oh-my-pi/pi-natives

Added

  • Added the super modifier to matchesKey / parseKey / parseKittySequence. Key identifiers may now include super+ (anywhere in the modifier prefix), and Kitty CSI-u sequences whose modifier mask contains the super bit (8) — e.g. Ghostty's macOS Option+Backspace ESC [127;11u — are now recognised instead of dropped (#2064).

Fixed

  • Fixed the native copyToClipboard leaving the X11 clipboard empty on Linux even while the process kept running. arboard answers clipboard SelectionRequests from a background thread that lives only as long as a Clipboard instance exists, and the binding dropped its transient Clipboard immediately after set_text — tearing that thread down so the selection lost its owner and the clipboard read back empty (matching the returned ok but clipboard='' symptom). The Linux path now holds a single Clipboard for the lifetime of the process so the owner thread keeps serving, with no xclip/wl-copy subprocess; macOS/Windows keep the transient write on the calling thread (#2075).

@oh-my-pi/pi-tui

Added

  • Added exported canonicalKeyId and addKeyAliases keybinding helpers so consumers can share the same canonical shortcut matching semantics as KeybindingsManager.
  • Added super modifier support to native key parsing/matching and bound super+alt+backspace / super+alt+delete (and super+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> u reply even when the terminal answers the DA1 sentinel first. Previously the kitty reply was discarded once the DA1-driven modifyOtherKeys fallback 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 /resume or any long-session repaint. ProcessTerminal#safeWrite now splits oversized writes into ≤ 8 KiB pieces at line boundaries on win32 and inside WSL (where stdout still crosses ConPTY at the wslhost boundary) so each underlying WriteFile stays 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

Full Changelog: v15.10.1...v15.10.2

Don't miss a new oh-my-pi release

NewReleases is sending notifications on new releases.