llama.cpp and Ollama model discovery probed /models and /props with a
250ms timeout tuned for a loopback server. That cap also applied to a
host reached over the network, so a remote or LAN LLAMA_CPP_BASE_URL
(or OLLAMA_BASE_URL/OLLAMA_HOST) with normal round-trip latency timed
out, discovery returned no models, and the picker fell back to stale
127.0.0.1:8080 entries.
Select the probe timeout by host: strictly-loopback base URLs keep the
fast fail so a busy or foreign service on the default port never stalls
startup; every non-loopback host gets a generous discovery budget.
Fixes#7087
The soft request budget resolved to `SOFT_REQUEST_BUDGET[agent.name] ??
configured`, so the bundled entries for scout and sonic replaced the
configured value outright. Lowering `task.softRequestBudget` to tighten
the guard therefore did nothing for exactly the two agents that spawn
most often: a scout kept its 100-request budget no matter how small the
user set the knob. Only 0 (disable) and raising the value for
non-bundled agents had any effect.
Treat both numbers as upper bounds and take the smaller one. The bundled
entries stay ceilings, so a runaway scout is still stopped at 100 by
default and existing behavior is unchanged for anyone who has not
lowered the setting; a configured 0 still disables the guard entirely.
Resolution moves into `resolveSoftRequestBudget`, which also normalizes
negative and fractional inputs, so the rule is testable without standing
up a subprocess run.
This composes with `task.maxEffort` on a separate axis: effort caps how
hard each request thinks, this caps how many requests a run may spend.
(cherry picked from commit f0db29f8f725f11390b64ca9342300c482ff5c5d)
The previous commits patched each rebuild path individually to avoid feeding a
modifyModels hook its own output. That left the invariant implicit and the
provider-scoped path applying only a subset of hooks, which is wrong for a hook
that inspects or suppresses another provider's models.
Keep #unprojectedModels as the canonical pre-projection catalog and derive
#models from it at every mutation point, so projections are always a pure
function of the unprojected base:
- #composeUnprojectedStaticModels builds the catalog; #composeStaticModels
projects it. A scoped lookup with modifiers registered composes and projects
the whole catalog before narrowing, matching getAll() followed by a filter.
Providers without modifiers keep the cheap filtered path.
- Discovery completion, registerProvider, and runtime transport overrides
update the unprojected snapshot and reproject, instead of mutating an
already-projected array.
- Runtime metadata patches apply to the unprojected model, then reproject, so
a later registration cannot discard them.
- Provider lookup snapshots are invalidated wherever the projection changes.
Hooks no longer take a providerFilter: a modifier is a whole-catalog transform
and every rebuild now runs the full ordered set exactly once.
(cherry picked from commit e5d2e9eac7c371cc196e9b362f77d3a5d7bdf507)
registerProvider composed nextModels from the already-projected #models,
stripping only the incoming provider, then reran every stored modifier over
it. Loaders drain registrations one at a time, so the previously registered
provider's projection was fed back into its own hook — an append-style hook
compounded on each subsequent registration.
Apply only the incoming provider's hook. Every other provider's projection is
already present exactly once, and full rebuilds still go through
#composeStaticModels.
(cherry picked from commit b16db642b08cc223b062c0c556f00d46fda2520a)
Review follow-up on two defects in the original change:
- The throwing-hook fallback wrote to #lastDiscoveryWarnings, which is only
ever read to dedup a logger.warn inside #warnProviderDiscoveryFailure. No
log line was emitted, so a broken extension degraded invisibly, and the
shared key could mask a later discovery failure for the same provider. Log
via logger.warn with its own dedup map.
- #refreshRuntimeDiscoveries starts from the already-projected #models, and
the overlay merge only replaces matching provider+id pairs, so a hook's
projection-only entries survived and were fed back into it. An append-style
hook duplicated its output on every refresh. Drop each modifier provider
before the merge so it re-seeds from the unprojected overlays.
(cherry picked from commit b6f841e080d4882a08b8d713de009461b6acc6fe)
`registerProvider` applies `oauth.modifyModels` once and assigns the result
straight to `#models`, but only the pre-projection definitions are persisted
in `#runtimeModelOverlays`. Any subsequent static reload rebuilds `#models`
from those overlays and silently drops the projection.
The model selector reloads on every open (`refresh("offline")`), so an
extension provider that projects a credential-aware catalog shows its
correct models everywhere except the picker — the one place users look.
`refreshProvider()` and online discovery completion had the same hole.
Persist the hook per provider and re-apply it wherever `#models` is
recomposed, honouring the `providerFilter` used by scoped lookups. A hook
that throws now degrades to that provider's unprojected catalog instead of
failing the whole composition, so one broken extension cannot empty the
registry.
(cherry picked from commit 33b7c72f225b4253b68bb71ecb3a9186151b18b3)
Attaching to a long-running browser (a signed-in profile, an Electron app kept
open for a session) meant repeating app.cdp_url on every browser call, and any
call that omitted it silently launched a fresh headless Chromium instead.
browser.cdpUrl supplies that endpoint once. It is a default rather than an
override: app.cdp_url and app.path still win, and an unset or blank value leaves
cmux and headless resolution exactly as before.
- read now derives its image behavior from actual tool availability
(session.isToolActive) with the mode computation as fallback, so
restricted sessions whose explicit slate omits inspect_image (e.g.
subagents) never get metadata-only reads pointing at an absent tool
- reconcile passes the post-change availability into the read
description sync, keeping the advertised prompt correct across flips
in both directions and when tool construction fails
- flat quoted-dotted inspect_image.mode is normalized into the nested
target during migration instead of being silently dropped when a
legacy flat enabled key is present
- regression tests for all three: availability-driven read behavior,
flat+flat migration, description advertising
- Reconcile inspect_image centrally from setModelWithProviderSessionReset
so retry-fallback model changes (turn-recovery.ts) that bypass
syncAfterModelChange cannot leave a stale tool set
- Apply persisted inspect_image.mode changes immediately from the
settings selector via a new handleSettingChange branch
- Refresh the read tool's advertised description during reconciliation,
before applyActiveToolsByName rebuilds the prompt, instead of only
lazily on the next image read
- Fix the flat (quoted-dotted) enabled->mode migration to write the
nested target form the resolver actually reads
- Add committed regression tests: tri-state x capability matrix,
override precedence, and enabled->mode migration (nested, flat, and
explicit-mode-wins)
Replace the inspect_image.enabled boolean with inspect_image.mode
(auto|on|off, default auto). In auto the tool is registered only when
the active model lacks native image input, so vision-capable models
(e.g. kimi-code/k3) read images inline with their own capabilities
instead of delegating to a separate vision model. on/off force
registration regardless of model capability.
- New utils/inspect-image-mode.ts resolves the effective state from the
/vision session override, the persisted setting, and model capability
- read tool re-evaluates the effective state per image read and
re-renders its description, so it returns decoded image blocks again
whenever inspect_image is hidden
- /vision [on|off|auto|status] slash command (modeled on /computer)
overrides the mode for the current session only
- Tool set is reconciled on model switch with a status notice when
inspect_image appears/disappears
- Legacy inspect_image.enabled true/false migrates to mode on/off
- Keep terminal working titles static with a colon separator on Windows instead of scheduling animated spinner updates.
- Update terminal title builder and state machine to check the platform and bypass timer intervals on win32.
- Introduce task.enableEffort setting defaulting to false to hide per-spawn effort parameters.
- Conditionally include effort in single and batch task schemas and descriptions based on the new setting.
- Treated missing files separately from malformed or unreadable configs.
- Backed up malformed YAML and wrote settings atomically without dropping pending changes.
- Reported success only after saving, with regression tests for global and project configs.
Added the supported realtime voice catalog to settings and passed the selected value into each new live session.
Covered the voice list, default, UI options, and persisted override.
Fixes#6566
The credential pass marked `hindsight.apiUrl` secret instead of
`hindsight.apiToken`: both share `condition: "hindsightActive"` and the flag
landed on the wrong one, so the settings panel masked an ordinary endpoint. The
token keeps its masking through the `credential` marker.
`config list` also redacted on classification alone, so a fresh configuration
reported every unset credential as though one were stored. Redaction now
depends on a value being present.
The suite asserted classification and panel metadata only, so both output
branches could be deleted while every test passed, which is how the wrong flag
shipped. It now drives `runConfigCommand` and reads the real human and JSON
output.
omp config list printed every configured value, including auth.broker.token,
searxng.token, searxng.basicPassword and dev.autoqaPush.token, in both the
human and --json output. Nobody asked for those specific credentials; the
command dumps everything.
Credentials are marked with a top-level credential flag rather than ui.secret,
because four of them have no settings-panel entry and so have nowhere to put a
UI-level flag. isCredential is the single accessor both the CLI and the panel
consult, so the two spellings cannot produce different behaviour on different
surfaces.
Human output shows dots. JSON omits value and marks the entry redacted instead
of substituting a placeholder, which a consumer could not distinguish from a
real value and might write back.
config get <path> is deliberately unchanged: that is an explicit request for a
single value, and masking it would break a retrieval API with no way to read
your own token back.
`max` became a first-class effort tier in d435385a, but the `auto`
classifier prompt still offers only `low|medium|high|xhigh`. On a model
that exposes the tier, `auto` can therefore never reach it — only the
`ultrathink` keyword can, because it bypasses the classifier entirely.
`providers.autoThinkingMaxEffort` (`xhigh` | `max`, default `xhigh`) lifts
that ceiling. Opting in adds `max` to the classifier vocabulary, gated on
the target model actually supporting the tier, and scopes the tie-break
exception to that prompt variant so the default renders byte-for-byte as
before. A classification above the configured ceiling is clamped before
the model clamp, so a hallucinated `max` cannot cross a ceiling the user
did not opt into. The on-device 3-bucket classifier stays capped at
`xhigh`, and the provisional/fallback level still never provisions `max`.
Also corrects the two `Auto-detect per prompt (low-xhigh)` labels and the
stale `xhigh auto ceiling` comment, which the new setting makes wrong.
Returned the historical missing-key error when request authentication resolves without a credential.
Covered the non-throwing empty-auth result through the real registry path.
Restored the historical clampThinkingLevel export through the legacy pi-ai shim and exposed ModelRegistry.getApiKeyAndHeaders for extension request authentication.
Added compatibility and auth result regression coverage.
Fixes#6648
The pi-native-only filter still let any non-pi-native models.yml provider override (baseUrl/apiKey/headers) reach the broker-backed gateway: config keys shadowed broker credentials (bypassing account pooling, refresh, and accounting) and a custom baseUrl redirected the broker bearer to a configured endpoint. Generalized the flag to ignoreLocalModelConfig, which short-circuits loadCustomModels so the gateway serves bundled plus broker-discovered catalog metadata only, applying no local overrides, config API keys, custom models, or custom discovery.
Fixes#6615
Gateway catalog construction now ignores models.yml provider entries whose transport is pi-native. This keeps the registry's bundled, cached, and upstream-discovered models while preventing client-side gateway base URLs and bearer keys from being applied to server-side dispatch.
Added a regression test proving a normal client registry retains pi-native routing while the gateway registry restores the bundled provider route before indexing the model.
Fixes#6615
Tracked the effective global, project, overlay, or runtime source of shellPath so resolution errors identify the configuration users must edit.
Covered custom agent directories and higher-precedence settings layers with focused regressions.
Fixes#6579