github can1357/oh-my-pi v15.11.4

latest releases: v15.11.7, v15.11.6, v15.11.5...
6 hours ago

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

Added

  • Added hasSteeringMessages to AgentLoopConfig (wired by Agent to its steering queue): a peek used by the immediate-interrupt poll during tool execution, so the loop can detect queued steering without dequeuing and the queue keeps owning its messages until the injection boundary
  • The agent loop now re-samples after a non-terminal stop (stopReason: "stop" with stopDetails: { type: "pause_turn" }, emitted by the Codex providers for end_turn: false commentary-only responses): the assistant message is committed to history and the model is called again without ending the turn. Consecutive pause continuations without an intervening tool call are capped at 8 to bound a backend that never stops pausing.

Changed

  • Changed steering handling so queued steering messages are now dequeued only at injection boundaries, with immediate mid-batch interrupt polling using hasSteeringMessages. Consumers constructing AgentLoopConfig directly with only getSteeringMessages no longer get mid-batch interrupts — steering degrades to boundary-only delivery until they also supply hasSteeringMessages
  • Compaction, handoff, short-summary, and branch-summarization helpers now accept an ApiKey (static string or resolver) instead of a pre-resolved string, so a 401 mid-compaction force-refreshes and rotates the credential through the central auth-retry policy before any model-level fallback. The remote OpenAI compaction request is wrapped in withAuth and its HTTP failures now carry .status, so the retry classifier actually fires on remote-compaction 401s.
  • transformProviderContext now receives the dispatch model as a second argument ((context, model) => Context), so per-request transforms can gate on model capabilities (vision input, provider, API family). Existing single-argument implementations keep working unchanged.
  • Remote-compaction and summarization failures now throw pi-ai's typed ProviderHttpError instead of mutating plain Errors with a .status property; the generic requestRemoteCompaction error now carries .status (and response headers) too.

Fixed

  • Fixed a regression where steering messages could be injected into history during an aborted in-flight tool batch, leaving them hidden from queue consumers for post-abort continue

@oh-my-pi/pi-ai

Added

  • Codex/Responses providers now map end_turn: false on the terminal stream event (Codex backend signal for "response ended, turn didn't" — commentary-only progress updates) to stopDetails: { type: "pause_turn" } with stopReason "stop", so the agent loop can re-sample instead of ending the turn. Wired in openai-codex-responses and processResponsesStream (openai-responses/azure-openai-responses); inert for backends that never send the field.
  • Added Codex upstream protocol features to openai-codex-responses (tracking codex-rs as of June 2026): onModerationMetadata callback surfacing response.metadataopenai_chatgpt_moderation_metadata on both transports; reasoningContext option emitting reasoning.context (auto/current_turn/all_turns); clientMetadata option emitting client_metadata in the request body (canonical x-codex-turn-metadata envelope) without breaking the websocket append fast-path; and an opt-in responsesLite mode mirroring codex-rs — lite header on HTTP requests and the websocket upgrade, ws_request_header_* marker in response.create client metadata, lite-keyed socket pooling, image-detail stripping, forced serial tool calls, and reasoning.context: all_turns default. Dormant until OpenAI flips use_responses_lite in the model catalog.
  • Added withOAuthAccess — the withAuth counterpart for OAuth-access consumers: runs an operation through the central a/b/c auth-retry policy (resolve → force-refresh same account → rotate to a sibling) while handing the attempt the full OAuthAccess (bearer plus accountId/projectId/enterpriseUrl identity metadata). Use it instead of hand-rolled getOAuthAccess + fetch flows so 401s and usage-limits rotate credentials instead of failing the call.
  • Added ProviderHttpError — a typed HTTP error carrying status, headers, and code — replacing the ad-hoc as Error & { status?... } / Object.assign hacks at provider throw sites, with per-provider subclasses CodexApiError, AuthGatewayError, GoogleApiError, GeminiCliApiError, OllamaApiError, and BedrockApiError; AnthropicApiError now extends it. Google, Gemini CLI, Ollama, and Bedrock HTTP errors now also carry response headers, so server-suggested retry-after delays are visible to retry classification on those paths. The internal withHttpStatus helper was removed.
  • Added stateful SSE turn chaining for OpenAI Codex (on by default; disable with PI_CODEX_STATEFUL=0 or statefulResponses: false): SSE requests now reuse previous_response_id with delta-only input instead of replaying the full transcript, mirroring the websocket fast-path via a shared transport-aware builder. Any history mutation or option change falls back to a full replay; a server-side previous_response_not_found (HTTP or in-stream) resets the chain and retries the turn with full context, and three consecutive stale failures disable chaining for the session.
  • Added stateful previous_response_id chaining to the platform OpenAI Responses provider (openai-responses): on by default against the official api.openai.com endpoint (forces store: true, which chaining requires), off for other Responses endpoints; override with statefulResponses or PI_OPENAI_STATEFUL. Chain detection compares the wire form of the conversation arguments alone — per-turn trailing scaffolding such as the GPT-5 "Juice: 0" developer item is excluded from the append-baseline prefix check and re-appended to the delta — and a rejected/stale previous response falls back to a one-shot full replay with the same circuit breaker.
  • Added AuthStorage.getOAuthAccountIdentity() and the OAuthAccountIdentity type — a read-only lookup returning the accountId/email/projectId of the OAuth credential a session is currently routed to, for display and metadata paths.

Changed

  • The GPT-5 "Juice: 0" no-reasoning developer item in applyResponsesReasoningParams is now gated on the resolved compat.requiresJuiceZeroHack flag (auto-detected from GPT-5-family model names by @oh-my-pi/pi-catalog, overridable per model) instead of an inline model-name check.

Fixed

  • Fixed websocket append fast-path to remain usable when only client_metadata changes between turns
  • Fixed onModerationMetadata handling so exceptions thrown by callback observers no longer terminate the response stream
  • Fixed local SQLite OAuth credential caches returning a stale Anthropic access token after another omp process refreshed and persisted the same row. AuthStorage now syncs the selected row from storage before returning or force-refreshing OAuth credentials, so concurrent sessions pick up peer-rotated tokens instead of surfacing a one-turn 401 Invalid authentication credentials.
  • Fixed forced OAuth preflight refresh failures being swallowed silently in credential selection; they now emit a debug log (OAuth preflight refresh failed) so stale-refresh-token replays from concurrent sessions are diagnosable.

@oh-my-pi/pi-catalog

Fixed

  • Fixed MiniMax M2-family and OpenAI gpt-oss model metadata so OpenAI-compatible catalog entries declare only low|medium|high thinking efforts. Their upstreams reject minimal, xhigh, and Fireworks' minimal → none wire mapping, so fireworks/minimax-m2.7 as the smol auto-thinking classifier model 400ed on every turn. OpenAI-compatible provider effort maps (Groq qwen/qwen3-32b, DeepSeek-family, OpenRouter Anthropic adaptive, Fireworks minimal → none) now bake into thinking.effortMap in catalog metadata instead of buildOpenAICompat, and request builders read that field directly. Regenerated models.json now makes disableReasoning choose low for those families while leaving GLM-5.x and other Fireworks models on the existing minimal → none path (#2315).

Added

  • Added requiresJuiceZeroHack Responses-API compat flag, resolved by buildOpenAIResponsesCompat from GPT-5-family model names and overridable via sparse model compat config. Replaces the request-time model.name.startsWith("gpt-5") sniff that gated the trailing # Juice: 0 !important no-reasoning developer item.

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

Added

  • Added mouse-driven interaction to /settings, including tab and setting row hover highlighting, wheel scrolling, and left-click activation for entries and submenus
  • Added fullscreen /settings mouse-event handling so scrolling and clicks work in an alternate-screen overlay
  • ModelRegistry.resolver now accepts a model directly — resolver(model, sessionId) — deriving provider, baseUrl, and modelId from it; all model-scoped call sites migrated from the verbose resolver(model.provider, { sessionId, baseUrl, modelId }) form.
  • Added experimental snapcompact.systemPrompt and snapcompact.toolResults settings (off by default, /settings → Context → Experimental) that render the system prompt and large historical tool results as dense snapcompact PNG frames on vision-capable models to cut token cost. Frames are built per-request in the provider-context transform, cached across turns, capped by a per-provider image budget, and gated on a token-savings estimate — they never reach session.jsonl.
  • Added a Personality selector to /settings (Model → Prompt): default (the previous built-in reply style), friendly, pragmatic, or none. The selected spec renders into a dedicated <personality> system-prompt block (extracted from the former <reply-guidelines> section) and applies to the live session immediately; subagents always omit the block.
  • Added mnemopi.polyphonicRecall and mnemopi.enhancedRecall config.yml settings (off by default, /settings → Memory → Mnemopi) that enable the mnemopi 4-voice polyphonic recall engine and the tiered query result cache without environment variables; MNEMOPI_POLYPHONIC_RECALL / MNEMOPI_ENHANCED_RECALL still override the configured values when set (#2323).

Changed

  • Changed /settings keyboard navigation so Tab and Shift+Tab toggle focus between section headings and setting rows in sectioned lists, with ↑/↓ jumping sections, ←/→ switching tabs, and status text reflecting the active controls

  • The task tool description now mandates batching parallel spawns into one call's tasks[] (sharing context once) instead of presenting multiple task calls per message as an equal alternative; separate calls are reserved for different agent types or unrelated context

  • Updated /settings on-screen navigation hints to match the new section-focus behavior (↑/↓ and Tab/Enter) and tab-switching arrows

  • Changed /settings to open as a full-screen overlay on the alternate screen so it no longer shares space with the underlying transcript

  • Codex, Gemini, and Perplexity web search now route their OAuth bearers through the new withOAuthAccess driver: a 401 or usage-limit force-refreshes the same account and then rotates to a sibling instead of failing the search, while identity metadata (chatgpt-account-id, Google projectId) is re-derived from the refreshed credential on every retry.

  • Kagi web search, the xAI TTS tool, and model-discovery list fetches now resolve their bearers through withAuth with an auth-storage resolver instead of a one-shot key snapshot, gaining the same force-refresh + rotate retry on 401.

  • Compaction, handoff, and branch-summarization call sites now hand the compactor a per-candidate API-key resolver (availability still gated on a key snapshot), so credential refresh happens before the #986 fallback-model loop advances.

  • The mnemopi backend now passes an OpenRouter resolver for default embedding/extraction setups (AuthStorage-stored keys included), keeping pinned literal keys and custom endpoints unchanged.

  • Reorganized the /settings panel for findability: every tab now has titled sections backed by a per-tab layout contract (TAB_GROUPS); on wide terminals the panel renders a section sidebar with the active section's settings beside it (narrow terminals keep a flat list with inline headings), and PgUp/PgDn jump section-to-section. The Editing tab became Files (edit/read/LSP) and a new Shell tab hosts bash, eval, and Python settings. Misplaced settings were rehomed: bash toggles united under Shell, tool approval mode and policies together under Interaction → Approvals, marketplace auto-update next to startup update checks, and the todo auto-clear delay beside the other todo settings.

  • Restyled the /settings chrome: the tab strip moved below the content as a label-less footer, the panel keeps one constant height across navigation, value changes, and submenus (no more viewport jumps after each change), and typing now runs a global cross-tab search — results group under per-tab headings, the footer shows live match counts with non-matching tabs muted, Tab hops between matching tabs, and Esc exits search landing on the selected result's tab.

  • Normalized /settings labels and descriptions: consistent Title Case labels (e.g. "Todo Auto-Clear Delay", "GitHub View Cache"), uniform unit placement, articles and verb-first phrasing in descriptions ("If false…"/"Whether to…" rewritten), and a stale browser-tool description (Ulixee Hero) corrected to the actual puppeteer/Chromium implementation.

  • /settings section headings are now underlined — the active section's heading stays bold, and headings outside the active section render dim with the same underline — so section boundaries read at a glance.

  • /settings: Tab now toggles keyboard focus between section headings and the setting rows — while headings are focused, ↑/↓ jump whole sections and Enter/Esc drop back into the rows — instead of cycling tabs. ←/→ still switch tabs everywhere, tabs without sections (e.g. Plugins) keep Tab as tab-switching, and the footer hint follows the focus state.

  • Image-generation (Antigravity, xAI, OpenRouter, Gemini) and xAI TTS request failures now throw pi-ai's typed ProviderHttpError carrying status and response headers instead of Object.assign-patched Errors.

  • Collapsed bash, ssh, and eval previews now cap the command/code section to a viewport-sized tail window (terminal rows minus a chrome reserve) that renders identically while streaming and after completion, with ctrl+o as the only way to uncap. Previously bash/ssh capped the command only while streaming and snapped it fully open the moment the tool finished, and eval never capped cell code at all.

  • The job renderer now shimmers running-job labels while the poll block is live, freezing to static once the block seals, and drops the id column when the label repeats it — task jobs label themselves with their agent id, so rows read ⟨task⟩ SessionTree instead of SessionTree ⟨task⟩ SessionTree.

  • Task progress rows now render static text with the task icon instead of shimmering the description or showing the pending/hourglass status glyph.

  • app.clipboard.pasteImage (Ctrl+V) now falls back to pasting clipboard text when no image is present, so hosts that deliver only that chord (VS Code's integrated terminal forwarding Ctrl+V, Windows clipboard history via Win+V) cover both payload kinds; WSL text reads now reach the Windows clipboard through host PowerShell like image reads already did (#1628).

  • Changed /usage to show the OAuth account currently selected for the active model provider when usage reports include multiple accounts, making multi-account sessions easier to verify without marking unrelated providers.

Fixed

  • Fixed session tree connector rendering in /tree and the HTML export: flattened chain rows under a last-sibling (└─) branch drew a dangling in the corner column itself and shifted columns once the chain branched deeper; the chain anchor now sits one level right, below the branch head's content, so connectors terminate at └─ and indentation stays stable (#2325).
  • Fixed prompt and image input typed while the agent is idle between turns from being dropped so steering now queues and auto-resumes processing once the session is ready
  • Fixed image-bearing prompts queued during compaction registering their local-submission signature without the image count, so the delivery event treated them as foreign messages — clearing any draft typed since queuing and leaking the stale signature.
  • Fixed Esc/Alt+Up queue restoration dropping attached images: clearQueue() and popLastQueuedMessage() now hand queued images back alongside the text, and the restore paths return them to the pending-image buffer instead of silently discarding them.
  • Fixed a failed steer submission in the no-input-waiter path discarding the message: the text and images are restored to the editor for retry instead of being lost to history-only recovery.
  • Fixed local memory consolidation on Responses-style models that reject user-only requests by sending a dedicated stage-two system prompt.
  • Fixed the settings sidebar divider alignment in /settings by locking sidebar width to the widest group name so switching tabs no longer shifts the layout
  • Fixed plan-review overlay mouse hit-testing so wheel, hover, TOC, and body clicks map to the intended regions
  • Fixed lsp.formatOnWrite sending a hardcoded tabSize: 3, insertSpaces: true on every textDocument/formatting request, which silently re-indented 2-space YAML (and any LSP-formatted file) to 3-space on every write/edit through formatter-aware servers like yaml-language-server. Format options are now resolved per-file from .editorconfig (indent_size, indent_style, tab_width), falling back to the indent sniffed from the in-memory content the agent is about to write, then to a 2-space default. The duplicate DEFAULT_FORMAT_OPTIONS constant in lsp/index.ts and lsp/clients/lsp-linter-client.ts is replaced with a single shared resolveFormatOptions helper (#2329).
  • Fixed stale OpenAI Responses replay failures such as Item with id 'rs_...' not found. by resetting the provider session and retrying without advancing fallback chains.
  • Fixed /settings Escape handling so an open submenu receives Esc and returns to the settings list before a second Esc closes the panel (#2331).
  • Fixed Escape not closing /settings on the Plugins tab while the async plugin list is still loading: PluginSettingsComponent now closes on Esc while no child view is mounted, and an npm plugin registry listing failure is caught (like marketplace failures) so a bad registry no longer leaves the tab permanently blank (#2331).
  • Fixed unconfigured pi/smol, pi/slow, and pi/designer agent model roles using cloud-priority defaults before the user's configured modelRoles.default, which could route local-default setups to authenticated paid providers (#2336).
  • Fixed issue:// reads failing on older GitHub CLI releases that reject the optional stateReason issue JSON field; single issue reads now retry without it and issue listings no longer request it (#2333).
  • Fixed image generation ignoring /login-stored OpenRouter and Google API keys: provider selection and requests now resolve through the model registry (with env-var fallback) instead of environment variables only.
  • Fixed detached (task async spawn) progress snapshots repainting commit-eligible rows after the block leaves the live transcript region; later partial snapshots are dropped while the final completion snapshot still applies.
  • Fixed five consumers treating Zod tool-parameter schemas as plain JSON Schema objects, leaking schema-instance internals (def, shape, methods stringified as undefined) or silently reading nothing: /dump and the RPC get_state dumpTools payload now convert parameters through the same wire-schema conversion providers receive; context-usage token estimation no longer stringifies the Zod def tree (overcounting tool schema tokens in the status line); tool-discovery search indexing recovers schemaKeys from Zod tools (previously empty, weakening BM25 ranking); and the extension inspector panel renders Zod tool arguments instead of "(no arguments)".

@oh-my-pi/hashline

Added

  • Added inward landing correction for insert after block N:: a body indented deeper than the block's closing line now slides back across the block's trailing closer lines and lands inside the block at its claimed depth, with a warning naming the landing line. Same conservative guards as the outward shift — comparable indentation only, closers only, abandoned when another hunk targets a crossed line; plain insert after M: stays literal
  • Added closer-anchor lowering for insert after block N:: anchoring on a pure closing-delimiter line (where no block begins, so resolution previously failed the whole patch) now applies as plain insert after N: with a warning teaching the opener-only rule. resolveBlockEdits gained an onWarning callback; apply, preview, and patcher paths surface it on warnings

Changed

  • Condensed the edit-tool prompt: one-line op definitions, 5–20-word rules, and a tighter <critical> recap; landing-correction mechanics are no longer described to the agent

@oh-my-pi/pi-mnemopi

Added

  • Added configureRecallFeatures() (exported from the package root, core, and config) so hosts can enable the polyphonic recall engine and the enhanced recall query cache programmatically. polyphonicRecallEnabled(), enhancedRecallEnabled(), and isEnhancedRecallEnabled() now fall back to these configured defaults, with the MNEMOPI_POLYPHONIC_RECALL / MNEMOPI_ENHANCED_RECALL environment variables still taking precedence whenever they are set. (#2323)

Fixed

  • Fixed the embedding pipeline's silent catch {} blocks (runEmbedding(), getLocalModel(), and the local-model path of embed()) swallowing failures with zero diagnostics. These best-effort paths still degrade gracefully (return null / skip the write), but now emit structured logger.debug entries with the error and per-site context (item count, model name). The mnemopi.debug config flag now propagates into the core library via runtime options (MnemopiOptions.debugResolvedMnemopiRuntimeOptions.debug) and escalates these logs to warn so they surface at the default log level. (#2322)

Changed

  • Extraction, embedding, and remote-LLM clients now accept an ApiKey (static string or resolver) and resolve it per request through withAuth, so 401s force-refresh and rotate credentials via the central auth-retry policy instead of failing with a stale key. Empty-key setups (local/proxy endpoints without Authorization) and pinned literal keys behave exactly as before.
  • Embedding and remote-LLM 401 errors now throw pi-ai's typed ProviderHttpError instead of Object.assign-patched Errors, keeping the same structural .status contract for the auth-retry classifier.
  • SHMR consolidation clustering (core/shmr) now uses the real embedding provider when one is configured instead of always hashing: embed(), the new embedBatch(), clusterBySimilarity(), computeHarmonyScore(), harmonize(), and recallBeliefs() are now async, batch-embed candidate texts in a single provider call, and reuse precomputed vectors from memory_embeddings for episodic candidates. The SHA1 bag-of-words hash remains as the deterministic fallback when no provider is available or embedding fails. (#2324)

@oh-my-pi/pi-natives

Fixed

  • Fixed blockRangeAt (and thus the edit tool's replace block / insert after block ops) failing on extensionless shell rc/profile files. Path::extension returns None for both bare (zshrc) and dotfile (.zshrc, .bashrc) forms, so language inference fell through to "unrecognized" and block resolution was permanently unresolvable on those files — an agent retrying the block op would loop on the same error. Known shell rc/profile basenames (zshrc/zshenv/zprofile/zlogin/zlogout/bashrc/bash_profile/bash_login/bash_logout/bash_aliases/profile/kshrc/mkshrc/shrc, with or without a leading dot) now resolve to the bash grammar.

@oh-my-pi/snapcompact

Breaking Changes

  • Renamed every export to drop the snapcompact/Snapcompact/SNAPCOMPACT_ qualifier — the package is meant to be consumed via import * as snapcompact from "@oh-my-pi/snapcompact". Functions: snapcompactCompactcompact, renderSnapcompactFramerender, snapcompactGeometrygeometry, normalizeForSnapcompactnormalize, serializeSnapcompactConversationserializeConversation, snapcompactImagesimages, getPreservedSnapcompactArchivegetPreservedArchive, isSnapcompactShapeisShape, resolveSnapcompactShaperesolveShape, createSnapcompactFileOpscreateFileOps, computeSnapcompactFileListscomputeFileLists, upsertSnapcompactFileOperationsupsertFileOperations. Types: SnapcompactShapeShape, SnapcompactFrameFrame, SnapcompactArchiveArchive, SnapcompactGeometryGeometry, SnapcompactOptionsOptions, SnapcompactSerializeOptionsSerializeOptions, SnapcompactFileOperationsFileOperations, SnapcompactCompactionDetails/Preparation/ResultCompactionDetails/CompactionPreparation/CompactionResult, SnapcompactConvertToLlmConvertToLlm. Constants: SNAPCOMPACT_XX (SHAPES, FRAME_SIZE, MAX_FRAMES, FRAME_TOKEN_ESTIMATE, PRESERVE_KEY, TOOL_RESULT_MAX_CHARS, TOOL_ARG_MAX_CHARS, TOOL_CALL_MAX_CHARS, TRUNCATE_HEAD_RATIO, DIM_ON, DIM_OFF).

Added

  • Added renderMany() for paging arbitrary text into snapcompact PNG frames as LLM image blocks, and frames() for predicting the frame count without rendering

@oh-my-pi/pi-tui

Added

  • Added partialHoldTimeout to StdinBufferOptions to control the maximum extra delay held for unambiguous incomplete escape sequences before they are flushed
  • Added SettingsList.sidebarWidth option for a fixed split-layout sidebar width
  • Added mouse pointer support APIs to SettingsList with setHoverItem, hitTest, hoverTest, and routeSubmenuMouse for row targeting and submenu routing
  • Added SettingsList.setMaxVisible(rows) and SettingsList.handleWheel(delta) for dynamic viewport sizing and mouse-wheel step selection
  • Added compact tab features with new Tab.short labels and TabBar.selectTab(id) for id-based activation of non-muted tabs
  • Added pointer-hover and hit-testing APIs to TabBar with setHoverTab, tabAt, and hoverTab theme
  • Added exported SGR mouse utilities parseSgrMouse, SgrMouseEvent, and MouseRoutable
  • Added section support to SettingsList: SettingItem.heading rows split the list into sections, PgUp/PgDn (tui.select.pageUp/pageDown) jump between sections (or page when none exist), and wide renders use a split layout — section sidebar on the left, the active section's items on the right — falling back to inline heading rows when the width cannot fit both panes. Headings are skipped by navigation, excluded from search, and styled through the optional SettingsListTheme.heading (which receives a dimmed flag for headings outside the active section) and section.
  • Added a host-integration surface to SettingsList: a SettingsListOptions constructor arg (layout to force the flat layout, typeToSearch: false to hand the query to a parent, emptyText, hint), selectItem(id), getSelectedItem(), onSelectionChange, hasOpenSubmenu(), and the exported getSettingItemFilterText helper.
  • Added keyboard section focus to SettingsList: toggleSectionFocus() / sectionFocused / hasSectionFocusTargets() flip Up/Down between row navigation and whole-section jumps — the cursor glyph parks on the active sidebar entry (or the active heading row in the flat layout) while the row cursor hides, Enter/Esc drop focus back to the rows, and any explicit row selection (selectItem, wheel, filtering) exits it.
  • Added muted tabs to TabBar (Tab.muted + TabBarTheme.mutedTab, skipped by keyboard navigation), setTabs(tabs, activeId?)/setActiveById(id) for re-rendering the strip without firing onTabChange, an optional empty label (drops the Label: prefix), and a showHint switch for the trailing "(tab to cycle)" hint.

Changed

  • Changed SettingsList section-focused keyboard handling so Up/Down now jump between sections and Enter/Escape exit section focus before confirming or cancelling a setting
  • Changed SettingsList split layout at wide widths to render the full list in the right pane and dim items outside the active section instead of showing only the active-section rows
  • Changed SettingsList to omit the default hint row (and preceding blank line) when options.hint is set to an empty string
  • Changed tab-bar overflow handling to collapse tabs to their short forms before wrapping to multiple lines

Fixed

  • Fixed StdinBuffer handling of split SGR mouse reports so fragmented sequences are reassembled instead of leaking their tail bytes as literal input
  • Fixed Esc being unreliable (or seconds-slow) inside fullscreen overlays such as /settings on kitty-protocol terminals (Ghostty/kitty): the kitty keyboard mode stack is per-screen, so entering the alternate screen silently reverted keys to legacy encoding while the app still parsed them as kitty input. The TUI now re-pushes the active kitty flags right after \x1b[?1049h and pops them before \x1b[?1049l.
  • Fixed StdinBuffer tearing a buffered bare ESC followed by another escape sequence: the \x1b\x1b candidate was consumed as alt+esc before the CSI/SS3 continuation byte was ever inspected, swallowing the Esc keypress and leaking the follower's tail ([B, [<35;22;17M) as typed text into focused components. Meta-CSI chords (\x1b\x1b[A) now stay whole, and ESC + SGR mouse report is split into a real Esc keypress plus a parseable report.
  • Lowered PARTIAL_HOLD_MAX_MS from 500ms to 150ms so a dangling escape partial that never completes (e.g. a bare ESC arriving while the kitty-active flag is stale) is delivered after at most ~200ms instead of half a second.
  • Fixed deferred partial-flush behavior so pending incomplete escapes are not split across timer boundaries and can still complete when the next chunk arrives
  • Fixed kitty keyboard-mode handling of a dangling ESC so it can be joined with subsequent CSI mouse/kitty input instead of being emitted as a standalone sequence
  • Fixed SettingsList to clear section-focus state when filtering items, changing data, scrolling with the mouse wheel, or selecting by ID so stale heading focus does not persist across interactions
  • SettingsList now renders every state — list, open submenu, filtered results, empty — at one stable height, so interacting with a bottom-anchored settings panel no longer resizes the live terminal region on each keystroke (which forced re-anchoring and could strand stale scrollback rows).

@oh-my-pi/pi-utils

Added

  • Added getEditorConfigFormatting(file): returns the .editorconfig-pinned tabSize/insertSpaces (both optional, no fallback) so LSP-format callers can layer per-file defaults under it without paving over silence with the renderer's display tab width (#2329).

What's Changed

  • fix(tool): tolerate missing issue stateReason field by @roboomp in #2335
  • fix(agent): fall back unset smol and slow roles to default by @roboomp in #2338
  • fix(settings): let Esc close submenus before the settings panel by @roboomp in #2332
  • fix(lsp): resolve per-file format options from editorconfig and content by @roboomp in #2330
  • session accent: derive from theme mode with collision avoidance by @H4vC in #2256
  • fix(catalog): clamp MiniMax M2 / GPT-OSS reasoning_effort on Fireworks by @roboomp in #2317
  • fix(natives): resolve bash for extensionless shell rc files (zshrc/.bashrc) by @blingdivinity in #2339
  • fix(coding-agent): add local memory consolidation instructions by @bjin in #2311
  • feat(coding-agent): fall back to text paste when clipboard holds no image by @chan1103 in #2248
  • Show active OAuth account in usage by @insodimension in #2260

New Contributors

Full Changelog: v15.11.3...v15.11.4

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

NewReleases is sending notifications on new releases.