API-key /login appends sibling keys for every provider (#3265); the stale test still asserted MiniMax replace-on-relogin (#156). Assert append, same-key idempotency, and individual removal via removeCredential (the logout path).
- Added `Shell.liveBackgroundJobCount` to query active background processes.
- Retained per-call `:async:` shells if background jobs are still running upon turn completion.
- Reaped shells automatically once their last background process exits to prevent lingering processes.
- Added a `share.store` configuration option (`blob` | `gist`) that allows users to choose between the default share server or a GitHub gist for storing exported session data.
- Changed the default upload target from secret GitHub gists to the share server to avoid GitHub API rate limits for shared sessions.
- Enabled fallback to the share server when a gist upload fails or the GitHub CLI is unavailable.
The key-creation fix still built the obfuscator from allEntries when those
entries were all ineffective (e.g. only ignored short plain secrets), so a
pre-existing secret-placeholder.key was left unredacted (hasSecrets() false
disables obfuscation) and a tool read shipped the reusable HMAC key. Gate
the redaction-only obfuscator on hasSecrets() instead of allEntries.length:
whenever a persisted key exists but no configured entry produced an active
secret, build the key-redaction-only obfuscator so the key file is redacted.
When the only obfuscate entries were short (<8 char) plain secrets, the
key-creation gate still saw them as obfuscate-mode and wrote/persisted
`secret-placeholder.key`, yet SecretObfuscator tones those entries down so
hasSecrets() is false and provider-output redaction is disabled. The
persisted key was then readable via a tool (leaking it to the model) and
reused for later placeholders. Gate key creation on whether an entry can
actually produce a reversible placeholder via a shared predicate
(secretEntryNeedsPlaceholderKey), single-sourcing the tone-down threshold
as MIN_OBFUSCATE_SECRET_LEN.
The default replace-mode regex path emitted the raw match verbatim when
its deterministic replacement equalled the `Z`/`ZZ` sentinel (e.g. a
literal regex matching "ZZ"). Route it through a regex-aware distinctness
guard: perturb to a length-preserving distinct value, but only when the
regex does not re-match the perturbed output. For a self-matching short
regex (e.g. `Z+`, `[A-Z]{2}`) any non-sentinel 2-char value is re-matched
and would oscillate, re-leaking on alternate passes, so the sentinel is
kept to preserve the obfuscate() fixed-point invariant. Such configs are
pathological. Plain replace secrets remain unconditionally perturbed.
A plain replace-mode secret (or the redacted key) whose entire value is
exactly the deterministic sentinel (`Z`/`ZZ`) was emitted unchanged,
shipping the raw value to the provider. Scope a distinctness guard to
whole configured-secret replacements so the output differs from the input
while staying length-preserving and deterministic; a plain secret only
matches its own literal, so the perturbed output remains a fixed point
under re-obfuscation. Per-chunk remainder redaction keeps the sentinel
fixed-point for cross-restart idempotence.
The two Julia prelude helper tests run only on GitHub-hosted PR runners
(Julia is preinstalled there; the self-hosted runner skips them via
skipIf(!HAS_JULIA)). With the default 5000ms per-test timeout they time
out on cold-start JIT compilation (~26s observed locally). Give them a
120s timeout, matching the repo's other kernel-spawn tests.
The completions provider stores session state under the request-time resolved base URL, which can differ from the catalog baseUrl for Moonshot, Alibaba Coding Plan, Azure deployments, and similar provider overrides. The model-switch cleanup now evicts the previous provider prefix whenever the switch leaves that completions backend, so those resolved-url keys cannot survive the switch.
`AgentSession.#closeProviderSessionsForModelSwitch` only handled
`openai-codex-responses` and `openai-responses:<provider>` keys. The
`openai-completions:<provider>:<baseUrl>:<modelId>` entries — which cache
strict-tools disable scopes and reasoning-effort fallbacks tied to the
upstream backend — survived /model switches between different providers or
base URLs, so the next request to that backend (e.g. on /model toggle
back) replayed stale decisions made against an entirely different
transport.
Switching to a model whose `(provider, baseUrl)` differs from the current
openai-completions model now evicts every cached entry sharing the old
prefix. Same-backend model toggles keep their cached state, matching the
existing codex/responses semantics.
Fixes#3260
Bun.spawn -> CreateProcess only appends `.exe` to extensionless names; `.cmd`/`.bat` are never tried. Bare MCP commands like `npx` (which exists only as `npx.cmd` on Windows) crashed the subprocess ~140ms after spawn with ENOENT/EINVAL whenever our own PATH walk couldn't pin the file down (empty `Bun.env.PATH` under a restricted parent process, UNC mounts that reject `fs.access`, locked-down shells).
`resolveStdioSpawnCommand` now routes any unresolved bare command through `cmd.exe /d /s /c` so Windows's PATHEXT search runs Windows-native. Direct-spawn fast path is preserved for resolved `.exe`/`.com` files; the existing `.cmd`/`.bat` wrap is unchanged. The reporter's stated cause ("process.env not merged") was incorrect — the merge has been in place at `transports/stdio.ts:316-319` since well before 16.1.14 — but the symptom they hit is real.
Fixes#3250
Require bracketed image-path paste detection to see an explicit local path separator or file URI before routing .png-like text to image attachment handling.\n\nFixes #3253
PerplexityProvider.isAvailable() accepted authStorage.hasAuth("openrouter")
as a valid credential, so any user with an OpenRouter key configured (for
LLM access) had every webSearch: auto request silently routed through
OpenRouter's perplexity/sonar-pro endpoint. Since Perplexity sits first in
SEARCH_PROVIDER_ORDER, downstream providers like Gemini were never reached
and users saw unexpected charges on their OpenRouter billing.
Auto-chain admission now requires a direct Perplexity credential
(PERPLEXITY_COOKIES, Perplexity OAuth, or PERPLEXITY_API_KEY).
isExplicitlyAvailable still returns true, so users who want the
OpenRouter-backed perplexity/sonar-pro path can opt in by setting
webSearch: perplexity explicitly — the existing OpenRouter fallback in
getApiConfigs handles that case unchanged.
Fixes#3251
Configured provider discovery now treats models.yml/models.json edits as a cache staleness boundary, forcing online-if-uncached refreshes instead of reusing fresh rows written before the config change.
Added regression coverage for Ollama metadata overrides with a pre-existing models.db row.
Fixes#3242
Walked custom/hook `details` recursively through the obfuscator so nested renderer fields (e.g. async-result `jobs[].label`) cannot leak configured secrets into the advisor prompt.
Fixes#3237
Rewrote file-mention path and content through the configured obfuscator before the advisor delta is formatted, matching the primary provider's hide-secrets behavior.
Fixes#3237
brush-core's alias expander resolves aliases via
`value.split_ascii_whitespace()` (`crates/brush-core-vendored/src/interp.rs:1500`,
upstream brush issue reubeno/brush#57): each whitespace piece is dropped
into argv as-is, completely bypassing the shell parser. Any alias body
containing `(`, `)`, `|`, `&`, `;`, `<`, `>`, or `\`` therefore
turns the first piece into the command name, so Fedora's default
`alias which='(alias; declare -f) | /usr/bin/which …'` produces
`error: command not found: (alias;` for every `which` invocation.
The user's shell snapshot is generated by sourcing their real rc-file
under `/bin/bash` or `/bin/zsh` (so we can capture functions, options,
PATH) and then sourced by brush per-session. `sanitizeSnapshotForBrush`
now scans the emitted `alias -- NAME='VALUE'` lines after generation,
drops any whose decoded body contains those metacharacters, and rewrites
the file in place before caching. Compatible aliases (`ll='ls -l'`,
`gc='git --color=auto commit'`, embedded-quote `say='echo '\\''hi'\\'''`)
are preserved untouched; dropped names are logged at debug. brush then
falls through to whatever lives on `PATH`, which is what the user
expected when they ran `which` in the first place.
Covered by unit tests for the sanitizer (Fedora-which case, every
incompatible-metachar shape, every preserve case) and an integration
test that loads a poisoned snapshot and verifies `which sh` now exits
`0` with a real path.
Fixes#3234
- Updated system and tool prompts to explicitly forbid using shell utilities like grep, rg, awk, and find for tasks better suited to specialized tools.
- Clarified that bash should be reserved for terminal operations and computational pipelines that produce facts not available through existing specialized tool outputs.
- Updated status line to display token usage with an unknown context marker (" 5K/? ") when the model context window is unavailable.
- Updated `fugu` model specifications in `models.json` and catalog constants with corrected pricing, increased context windows, and disabled stream idle timeouts.
- Corrected OpenAI usage accounting by excluding redundant orchestration input tokens in `openai-shared` logic.
- Extracted common kernel and executor logic into `BaseKernel` and `executor-base` to eliminate duplicated implementations for Julia, Python, and Ruby.
- Migrated shared operational workflows--including session namespacing, environment filtering, and result mapping--to centralized backend helpers.
- Consolidated runtime discovery and resolution logic into a unified `runtime-env` utility module.
- Simplified language-specific modules by delegating subprocess lifecycle, IPC, and configuration management to the newly established base classes.
- Centralized Parallel API utilities and parsing logic into a single module.
- Exported constants and helper functions from `parallel.ts` to replace duplicated definitions in the search provider.
- Updated the search provider to leverage the unified `parseParallelSearchPayload` function with metadata parsing toggled off.
- Assert the eval tool hides disabled backends from the model-facing wire
schema (language enum + field descriptions), summary, and description by
default (rb/jl off), and advertises them once enabled — including the
enabled-subset case.
- Update the env-flag fallback test for the new rb/jl opt-in defaults and
extend the env guard to PI_RB/PI_JL so the suite is shell-independent.
- Note the opt-in default and dynamic advertising in the changelog.
- Changed default evaluation backend configuration to only enable Python and JavaScript by default.
- Implemented dynamic tool parameter generation to hide Ruby and Julia from the model's schema when they are disabled in settings.
- Updated tool summary and field descriptions to reflect the currently enabled runtime backends.
- Implemented persistent execution backends for Ruby and Julia using dedicated kernel processes and NDJSON-based IPC.
- Integrated language-specific prelude environments, runtime path resolution, and security-focused environment variable filtering.
- Exposed configuration options, tool schema updates, and lifecycle management for seamless agent interaction with both languages.
- Added comprehensive integration tests and updated prompt documentation to support the new evaluation capabilities.
- Centralized draft state and image management by migrating fields from context to the CustomEditor component.
- Standardized transcript row construction by introducing shared helpers for background jobs, IRC traffic, and file mentions.
- Refactored redundant UI logic and helper functions into reusable utility modules to streamline message submission and component rendering.
- Standardized event handler types by consolidating lifecycle definitions into a shared module while maintaining public API stability.
- Removed the `readHashLines` setting to consolidate hashline display logic.
- Simplified `resolveFileDisplayMode` to derive hashline visibility solely from the active edit mode.
- Added automatic cleanup of the `readHashLines` key from existing configuration files.
- Implement `handleWheelAt` to restrict wheel scrolling to the settings pane.
- Update selection movement to support clamping when scrolling at boundaries.
- Add tests to verify boundary behavior and spatial constraints.
- Established shared `subprocess` infrastructure to standardize worker IPC, environment resolution, and error handling.
- Migrated mnemopi, speech-to-text, tiny-model, and TTS clients to utilize consolidated worker-client utilities.
- Implemented common runtime helpers for ONNX model loading, logging, and process readiness probing.
- Eliminated redundant local spawn logic and environment mapping across all inference worker clients.
- Updated system and tool prompts to present dedicated tools as preferred defaults rather than absolute prohibitions.
- Relaxed the hard-forbidding of shell equivalents for file operations, searching, and editing.
- Retained guidance on prioritizing tools for their gitignore semantics, structure, and line-anchoring capabilities.
- Introduced `selector-helpers.ts` to centralize reusable list, dashboard, and selection utilities.
- Refactored `AgentDashboard`, `ExtensionList`, `HistoryResultsList`, and `TreeList` to use standardized rendering and navigation helpers.
- Encapsulated viewport padding, scrolling logic, and key matching to reduce code duplication across TUI components.
- Maintained consistent scrollbar themes and keyboard behaviors while simplifying component-specific implementations.
- Extracted incremental byte-stream framing logic into a shared `MessageFramer` class.
- Replaced redundant, per-client implementations in `dap/client.ts` and `lsp/client.ts`.
- Centralized buffer management to avoid O(n^2) allocation patterns during large message bursts.