- Added `renderSnapcompactFrames()` and `snapcompactFrameCount()` to @oh-my-pi/snapcompact for paging arbitrary text into PNG image blocks without dim-marker bookkeeping.
- Widened the agent loop's `transformProviderContext` hook to `(context, model) => Context` so per-request transforms can gate on the dispatch model's capabilities.
- Added `SnapcompactInlineTransformer` rendering the system prompt and large historical tool results as snapcompact frames on vision models: vision gate, per-provider image budgets, 3k-token floor, savings-margin gate, skip-last rule, and hash-keyed render caches swept to live tool calls.
- Added default-off `snapcompact.systemPrompt` and `snapcompact.toolResults` settings under a new Context → Experimental group, composed after secret obfuscation in `sdk.ts` so frames are built per-request and never persisted to session.jsonl.
- Added prompt stubs (`snapcompact-system-stub.md`, `snapcompact-system-frames-note.md`, `snapcompact-toolresult-note.md`) and unit tests covering frame paging, no-mutate guarantees, budget caps, gates, and render caching.
Dropped `mapReasoningEffort` and `resolveActiveEffortMap` in both
`openai-completions.ts` and `openai-responses.ts`. Each callsite now chains
two ?? lookups: `compat.reasoningEffortMap?.[k] ?? model.thinking?.effortMap?.[k] ?? k`.
No `Object.keys`, no spread-merged object per request — same precedence
(post-`whenThinking` compat overrides win over catalog-baked thinking map).
Fixes#2315
blockRangeAt inferred no language for files like `zshrc`/`.zshrc`/`.bashrc`
because `Path::extension` returns `None` for bare and dotfile names alike,
so the edit tool's `replace block` / `insert after block` ops were
permanently unresolvable on them — an agent retrying the block op would
loop on the same "could not resolve a syntactic block" error.
from_extension now maps known shell rc/profile basenames (zshrc, zshenv,
zprofile, zlogin, zlogout, zsh_aliases, bashrc, bash_profile, bash_login,
bash_logout, bash_aliases, profile, kshrc, mkshrc, shrc; with or without a
leading dot) to the bash grammar, alongside the existing Makefile/Dockerfile
basename handling. Adds a block-resolution regression test over a zsh
`if [[ … ]]; then … fi` block in `zshrc`/`.zshrc`/`.bashrc`.
- Updated `settings-selector-memory-refresh.test.ts` to assert the cross-tab search banner's cursor rendering (`b▌`) instead of the old `Search: b` label when verifying Escape clears the search before closing the selector.
- Replaced the fixed `CALL_PREVIEW_MAX_LINES` cap in `render-utils.ts` with a dynamic `previewWindowRows()` (terminal rows minus a chrome reserve); `capPreviewLines` now returns lines untouched when expanded and windows them otherwise.
- Applied the cap in every render state for bash and ssh command previews — streaming and final render identically instead of snapping fully open on completion — and included the preview window in bash's render cache key so terminal resizes invalidate it.
- Capped eval cell code previews via the same window; previously cell code was never capped at all, with `ctrl+o` remaining the only way to uncap.
- Added stream-vs-final parity coverage in `bash-sixel-render.test.ts`, `ssh-render.test.ts`, and the new `eval-code-preview.test.ts`, and a changelog entry.
- Fixed `/dump` output (`session-dump-format.ts`) and the RPC `get_state` `dumpTools` payload (`rpc-mode.ts`) serializing Zod schema instances — leaking `def`/`shape` internals and stringified methods — by converting parameters through `zodToWireSchema` like providers receive.
- Fixed context-usage token estimation stringifying the Zod `def` tree, which overcounted tool schema tokens in the status line.
- Fixed tool-discovery indexing (`tool-index.ts`) returning empty `schemaKeys` for Zod tools, weakening BM25 ranking; keys are now recovered via wire conversion.
- Fixed the extension inspector panel rendering "(no arguments)" for Zod tools by reading properties off the converted wire schema.
- Added coverage in `tool-index.test.ts` plus new `context-usage.test.ts` and `session-dump-format.test.ts`, and a changelog entry.
- Added a `SettingsSearchHeader` banner to `SettingsSelectorComponent`, permanently mounted under the top border and rendering only while a cross-tab search is active.
- Typing now filters settings across every tab via `getSettingItemFilterText` + `fuzzyFilter`, with per-tab first-match ids so Tab jumps between tabs that have matches; non-matching tabs render muted and the pre-search tab is restored on exit.
- Moved the tab bar below the tab content, disabled `SettingsList`'s own type-to-search in favor of the selector-owned global search, and only advertised PgUp/PgDn in the hint when multiple sections exist.
- Wired the new `mutedTab` style into `getTabBarTheme` in `modes/shared.ts`.
- Added `SettingsListOptions` to `SettingsList` with a `layout` override ("auto" sidebar vs "flat" inline headings) and a `typeToSearch: false` switch for hosts that own filtering globally.
- Added `getSelectedItem()` and an `onSelectionChange` callback to `SettingsList`, replaced index-based submenu selection restore with id-based item resolution, and exported `getSettingItemFilterText` for external filtering.
- Padded short section panes to the full viewport in `SettingsList` so the panel height stays constant across sections.
- Added `muted` tabs to `TabBar` (skipped during keyboard navigation, styled via the new `TabBarTheme.mutedTab`), a `setTabs()` replace API that preserves the active tab by id without firing `onTabChange`, a `showHint` toggle for the trailing "(tab to cycle)" hint, and omission of the label prefix when the label is empty.
Also normalizes setting labels/descriptions and carries the package changelog entries for this batch (adjacent unreleased bullets are not splittable per commit), restoring section placement disturbed by the two prior changelog hunks.
- Caught and logged npm `PluginManager.list()` rejections in `PluginSettingsComponent`, returning an empty plugin list so the settings tab no longer stays blank when plugin listing fails.
- Added tests covering Escape handling while lists are loading and marketplace rendering when npm listing rejects.
- Added `responsesLite`, `reasoningContext`, `clientMetadata`, and `onModerationMetadata` options.
- Changed Lite request transformation to default `reasoning.context` to `all_turns`.
- Changed request shaping to strip image `detail` fields and force `parallel_tool_calls` off in Lite mode.
- Fixed streaming behavior by excluding `client_metadata` from websocket append diffs and swallowing moderation metadata errors.
- AuthStorage now synchronizes the selected OAuth row from SQLite before returning or force-refreshing credentials.
- Credential preparation was updated to resync the selected credential after invoking `prepareForRequest`, so peer-updated tokens are observed.
- Added an OAuth refresh race test that confirmed peer-updated SQLite rows are returned without extra refresh calls.
- Mapped Codex `end_turn:false` terminal events to `pause_turn` stop details in response stream parsing.
- Updated `agent-loop` to re-sample `pause_turn` turns, reset on tool calls, and cap continuations at 8.
- Added coverage for pause-turn mapping and continuation-capping in agent and AI stream tests.
After PR #2317 moved detected effort maps into model.thinking.effortMap,
the request builders stopped reading the active compat's reasoningEffortMap
at all. That dropped two surfaces:
1) compat.whenThinking variants whose reasoningEffortMap was applied at
buildModel time were silently ignored on thinking turns.
2) Custom raw Model configs that author reasoningEffortMap on compat (no
buildModel pass) had no path onto the wire.
Introduce a single resolveActiveEffortMap helper in both openai-completions
and openai-responses that overlays the active compat's reasoningEffortMap on
top of model.thinking.effortMap. Default catalog path is unchanged because
the resolved compat.reasoningEffortMap is empty.
Fixes#2315
Stale cached or dynamically discovered MiniMax M2 / GPT-OSS rows can carry
explicit pre-fix thinking.efforts with minimal/xhigh. buildModel used to
trust that explicit surface and only backfill wire maps, so those rows could
still make disableReasoning choose minimal and hit Fireworks' none mapping.
Normalize model-defined effort restrictions before returning explicit
thinking metadata, and drop stale effortMap entries that no longer apply to
the normalized effort surface.
Fixes#2315
Moved buildOpenAICompat's detected reasoning effort maps into catalog
thinking metadata so request handlers read model.thinking.effortMap instead
of model.compat.reasoningEffortMap. The moved defaults cover Groq Qwen,
DeepSeek-family models, OpenRouter Anthropic adaptive models, and Fireworks'
minimal-to-none mapping.
Explicit provider/user compat reasoningEffortMap values remain supported as
build-time override inputs and are merged into thinking.effortMap, filtered
to the model's declared effort surface. Regenerated models.json carries the
baked maps.
Fixes#2315
Moved the MiniMax M2 / GPT-OSS reasoning effort fix out of the
OpenAI-compatible compat map and into catalog thinking metadata. These
families now derive low|medium|high as their supported efforts for
openai-completions models, and regenerated models.json carries the same
limits for bundled entries.
Fireworks' generic minimal -> none map remains unchanged for GLM and other
models that accept the none literal; the affected MiniMax/GPT-OSS models now
pick low from catalog metadata for disableReasoning.
Fixes#2315
Fireworks-hosted reasoning models share a blanket reasoningEffortMap of
{ minimal: "none" }, but MiniMax M2 (M2/M2.1/M2.5/M2.7) and OpenAI
gpt-oss (Harmony) only accept low|medium|high — so the auto-thinking
classifier's disableReasoning: true path made every turn 400 when the
smol model was fireworks/minimax-m2.7 (or fireworks/gpt-oss-120b).
Added isMinimaxM2FamilyModelId and isOpenAIGptOssModelId identity
predicates in packages/catalog/src/identity/family.ts and routed those
families to a model-family-specific effort map { minimal: "low",
xhigh: "high" } in buildOpenAICompat, ordered before the Fireworks
blanket rule so GLM-5.x and other Fireworks models keep mapping
minimal → none unchanged.
Fixes#2315