- Introduced `serviceTierSubagent` and `serviceTierAdvisor` settings to allow independent service tier control for subagents and the advisor model.
- Enabled `"inherit"` mode for these settings, allowing subagents and the advisor to track the main session's live effective service tier, including dynamic toggles like `/fast`.
- Added a resolution layer to ensure service tier propagation from parent sessions to spawned task agents and evaluators.
Direct Anthropic Claude Sonnet 4.5 and Haiku 4.5 (plus their Cloudflare,
Vertex, GitLab-Duo, Copilot, OpenCode-Zen, and Bedrock cross-region passes)
were classified as anthropic-budget-effort, which made the Anthropic provider
serialize output_config.effort alongside the thinking.budget_tokens block.
Anthropic only honors output_config.effort on Opus 4.5 and adaptive (4.6+)
Messages-API models — Sonnet 4.5 and Haiku 4.5 reject every request with
HTTP 400 'This model does not support the effort parameter.', so the advisor
(and any agent on a Sonnet/Haiku 4.5 SKU) failed every turn.
inferThinkingControlMode now gates anthropic-budget-effort to
parsedModel.kind === 'opus' && semverGte(version, '4.5') on both
anthropic-messages and bedrock-converse-stream. Sonnet/Haiku 4.5 fall through
to mode: 'budget' (effort still scales the per-tier thinking budget via
ANTHROPIC_THINKING[reasoning]); Opus 4.5 keeps anthropic-budget-effort and
continues to emit output_config.effort. anthropic-budget-effort also remains
in use for Anthropic-compatible third-party backends that natively support
the field (Umans GLM 5.2).
Regenerated models.json so the 19 Sonnet/Haiku 4.5 first-party entries flip
to mode: 'budget' and the 12 Opus 4.5 entries stay on anthropic-budget-effort.
Regression tests cover both sides: anthropic-alignment.test.ts asserts the
Sonnet 4.5 wire body omits output_config and the Opus 4.5 wire body emits
output_config.effort: 'medium'.
Fixes#3497
- Updated the `ai` package E2E tests to retrieve environment variables via `e2eApiKey` to ensure they only execute when E2E testing is enabled.
- Refactored the `scripts/ci-test-ts` runner to conditionally apply the `--only-failures` flag based on provided arguments.
- Adjusted the conditional check to correctly identify string-based ellipsis inputs.
- Resolved logic for applying default ellipsis behavior when invalid or empty types are provided.
Fixes#3492
- Update follow-up dispatch tests to assert error surfacing and draft recovery.
- Remove erroneous expectations that rethrow exceptions from fire-and-forget calls.
- Track error notifications via a new `showError` spy.
In #registerSpawnJob the markRunning()/reportProgress() calls sat between
semaphore.acquire() and the try whose finally releases the slot. If progress
reporting threw there, the acquired task.maxConcurrency slot leaked and
permanently shrank subagent concurrency. Move those statements inside the try
so finally always releases. The abort-before-execution branch is unchanged
(releases once and throws before the try is entered — not a double release).
Refs #3464
The per-provider subagent limiter (providers.ollama-cloud.maxConcurrency)
created a fresh Semaphore whenever the configured limit changed, orphaning
in-flight slots on the old instance so a runtime or mixed limit value could
exceed the cap. getProviderSemaphore now always hands out one shared limiter
(Infinity when unlimited, so every run is still counted) and resizes it in
place. Semaphore.release() decrements before admitting, and the new
Semaphore.resize() raises the ceiling by admitting queued waiters while
lowering it drains in-flight holders without admitting past the new cap.
Refs #3464
The preserveCompaction abort path skipped abortCompaction() entirely,
so a manual /compact starting while auto-compaction was in flight no
longer cancelled it. Both passes could then appendCompaction/
replaceMessages, double-rewriting history (reachable via the RPC/
extension compact paths, whose only guard checks #compactionAbortController).
Preserve the just-installed manual controller but still abort the
auto-compaction controller. Adds regression coverage.
Adding `paths` to the global PRIMARY_ARG_KEYS hid the pattern for the
structural tools: ast_grep ({pat,paths}) and ast_edit ({ops,paths})
rendered scope-only (e.g. `ast_grep(src/**/*.ts)`), dropping `pat`/`ops`
— the most decision-relevant argument. Drop the global `paths` key and
special-case `find` (mirroring `search`) so find/search still surface
scope while ast_grep/ast_edit keep showing their pattern via the
existing fallback. Adds regression tests for both structural tools.
The two getOrCreateSnapshot e2e tests hard-coded /usr/bin/bash and
/usr/bin/echo, both absent on macOS (bash is /bin/bash, echo is
/bin/echo). The symlink then pointed at a nonexistent target so
getOrCreateSnapshot returned null, and the replay invoked a missing
echo. Since #3470 is a macOS bug, the regression tests could not run on
the affected platform. Resolve bash via Bun.env.SHELL (mirroring
bash-executor.test.ts) with a /bin/bash fallback and an existsSync skip
guard, and resolve echo via Bun.which with a /bin/echo fallback.
Install the manual compaction abort controller before abort teardown so input routing observes session.isCompacting during the starting window.
Fixes#3485
Added scoped path summaries for find/search tool calls in concise session history rendering, with regression coverage for JSON fallback and hidden search scope.
Fixes#3482
PR #3474 second review: previous revision ran `umask 077` only BEFORE
sourcing the rc, so a typical `.bashrc`/`.zshrc` that calls
`umask 022` reopened the world-read window between the spawned shell's
first `>|` and the JS post-spawn chmod. Snapshot file (with inlined
env-var values) lived at 0644 for the full body of the script.
Two-layer fix:
- JS caller now pre-creates the snapshot file at 0600 with
`fs.writeFileSync(path, "", { mode: 0o600 })` before spawning. The
shell's `>|` (truncate) and `>>` (append) preserve the existing
inode mode, so the file is 0600 from byte zero regardless of the
spawned shell's umask state.
- Script also re-applies `umask 077` after the rc source so any
other file the script might create (none today, defensive) stays
private even when the rc resets umask.
New e2e regression test seeds a `.bashrc` containing `umask 022` and
asserts the resulting snapshot mode `& 0o077 === 0`.
Accepted displayed gallery lifecycle labels as --state aliases, rejected unknown values before rendering, and updated failed fixtures to render visibly failed states.
Fixes#3473
Bun reports process.platform as 'linux' inside WSL even when the rendering Warp is the Windows build, which lacks Kitty support and prints APC sequences as visible garbage. Detected WSL via WSL_DISTRO_NAME/WSL_INTEROP and disabled the Kitty image protocol in that case.
Refs #3471
PR #3474 review: the new export pass writes referenced env-var values
into a snapshot file under `os.tmpdir()/omp-shell-snapshots`. On Linux
where `os.tmpdir()` is `/tmp` and the umask is the default 022, the
file ended up world-readable (0644) until postmortem cleanup. A user
rcfile defining `deploy(){ curl -H "Authorization: $GITHUB_TOKEN" ...; }`
would have its token written verbatim to that file.
Three-layer mitigation:
- `umask 077` at the top of the snapshot script so the file is 0600
from the first byte (the shell creates it via redirection, not JS).
- JS caller now passes `mode: 0o700` to `mkdirSync` and chmods the
dir + file defensively after the script exits, covering pre-existing
dirs and exotic shells where the umask call might not take.
- Helper denylist gained the common secret-shaped name patterns
(`*TOKEN*`, `*SECRET*`, `*API_KEY*`, `*PASSWORD*`, `*PASSWD*`,
`*PRIVATE_KEY*`, `*ACCESS_KEY*`, `*CREDENTIAL*`, `*SESSION_KEY*`)
so even when the file is locked down, we don't materialise tokens
onto disk in the first place.
Tests cover both: a new helper-level test asserts none of the secret
names (or their values) appear in the export stream, and the e2e test
now stats the snapshot file + dir and asserts `mode & 0o077 === 0`.
generateSnapshotScript captured the user's shell functions via declare -f /
typeset -f and dropped everything except PATH on the export floor. mise
activate installs a mise() function whose body expands $__MISE_EXE; the
replay shell then ran `command "" "$@"` and died with
`command: command not found:` (exit 127). The same shape breaks asdf
shims, direnv-style helpers, and any other activation idiom that pairs a
shell function with a sidecar env var.
The snapshot script now scans captured function bodies for $VAR /
${VAR…} references and re-emits `export NAME='value'` for each name
that is currently set and not on a shell-internal denylist (PATH, HOME,
BASH_*, LC_*, …). getShellConfigFile also honours env.HOME so callers
(and tests) can target a sandboxed home — os.homedir() is cached by Bun
and ignores later process.env.HOME mutations.
Fixes#3470
Wrap the streaming Enter steer dispatch in try/catch so prompt failures restore text plus pendingImages / pendingImageLinks / imageLinks and surface showError instead of losing an image-only draft.\n\nAlso remove a forbidden ReturnType<> from the follow-up image regression helper.\n\nFixes #3467
Snapshot pendingImageLinks alongside pendingImages at the top of handleFollowUp and wrap both the streaming and idle session.prompt dispatches in try/catch that restores text + pendingImages + pendingImageLinks + imageLinks and surfaces showError, mirroring the main submit and focused submit error paths so an image-only or text+image Ctrl+Enter draft survives dispatch rejection.\n\nFixes #3467
Snapshot pendingImageLinks and re-seed editor.pendingImages / pendingImageLinks / imageLinks in the focused-session submit catch block so an image-only or text+image draft survives a viewSession.prompt rejection, mirroring the main controller error path.\n\nFixes #3467
Treat pending pasted images as submit content in the main and focused input controller paths so image-only Enter/Ctrl+Enter submissions queue instead of dropping or aborting.\n\nFixes #3467
Semaphore.acquire now accepts an AbortSignal so a queued waiter that is cancelled (parent task abort, wall-clock budget elapsing) removes itself from the wait queue instead of being resolved by the next release. The provider semaphore in runSubprocess passes the run's abortSignal through, preventing aborted ollama-cloud subagents from permanently draining the provider concurrency budget.
Fixes#3464
brush-core's Process::wait calls tokio::signal::unix::signal(SIGTSTP) to
detect when its children get stopped. Per tokio's documented contract,
the first call for a SignalKind permanently replaces the kernel-default
handler for the lifetime of the process. So once omp has executed any
bash tool call — even /usr/bin/true — SIGTSTP's default "stop" action
is gone, and InputController.handleCtrlZ's process.kill(0, "SIGTSTP")
gets swallowed by tokio. The TUI tore down via ui.stop() but the process
kept running in Sl+ state, leaving the user with a dead terminal that
only kill -9 could recover.
Send SIGSTOP to our own PID instead. SIGSTOP can't be caught, blocked,
or ignored — it stops the process at the kernel regardless of installed
handlers. Targeting self (not pgid=0) also leaves long-lived children
(MCP stdio servers, the persistent brush native shell) running across
the suspend, so they no longer freeze mid-IPC during a quick fg/bg
detour.
Fixes#3461
- Automatically rebind edits to the correct file when an authored path does not exist but the filename and snapshot tag uniquely match a file read earlier in the session.
- Prevent path recovery for paths that would escalate write privileges, ensuring compatibility with read-only internal URL targets.
- Surface warning messages to the model and user upon successful path recovery to encourage correct future path usage.
- Removed `onPasteFilePath` handler to prevent automatic background file attachment when pasting paths.
- Updated `CustomEditor` to treat non-image paths as literal text input.
- Cleaned up unused file system utilities and paste path resolution logic.
- Set logger to silent mode when no transports are active to avoid "no transports" errors during log emission.
- Added a regression test to verify that disabling all transports suppresses warnings and that log output resumes after re-enabling transports.
- Added a check for empty transport arrays during logger initialization.
- Set the logger to silent mode if no transports exist to prevent unnecessary warning messages on every log emit.