Unanchored regex mapped BCP-47 script subtags to bogus regions
(lang:zh-hans -> location=HA) and prefix-matched 3+ letter codes
(lang:eng -> language=en). Require a subtag boundary, matching the
Perplexity provider's parsing.
ExtensionRunner.emitAfterProviderResponse accepted the response model but
discarded it, calling createContext() with no model. Response-scoped hooks
therefore saw the primary session model in ctx.model and ctx.models.current()
even when the response came from a cross-provider side request, so an extension
that revokes a credential on an HTTP 402 could target the wrong provider.
Call createContext(model) to match emitBeforeProviderRequest, plus a regression
test asserting both fields expose the response model.
Fixes#8955
The shared query pipeline parses a lang:/language: directive into StructuredQuery.lang, which sibling providers (DuckDuckGo, Perplexity, SearXNG) map onto their native locale params. The TinyFish provider dropped parsed.lang entirely, so every request fell back to the API's US/English default and non-US locales were silently lost.
Map parsed.lang onto TinyFish location (ISO 3166-1 alpha-2) and language (ISO 639-1): lang:it-it yields location=IT&language=it, lang:it yields language=it only. Behaviour is unchanged when no locale directive is present.
Fixes#8913
Remote OAuth MCP servers dropped out of /mcp under `omp auth-broker
serve` once their access token expired: neither the client nor the
broker could complete the refresh.
- Client: the MCP manager threw on the broker-redacted refresh sentinel
(REMOTE_REFRESH_SENTINEL) instead of asking the broker to refresh. It
now routes redacted MCP refreshes through
AuthStorage.forceRefreshCredentialById, which calls back to the broker
(the real refresh token never leaves the broker host).
- Broker: the serve process had no mcp_oauth:* refresh path, so
POST /v1/credential/:id/refresh answered "Unknown OAuth provider". Its
AuthStorage is now built with a refreshOAuthCredential override that
refreshes MCP credentials with a generic refresh_token grant from the
credential's embedded token endpoint and client id. The background
refresher keeps MCP tokens live through the same path.
Extract shared refreshManagedMcpOAuthCredential and
mcpOAuthServerUrlFromCredentialId helpers so both paths use identical
refresh material selection and RFC 8707 fallback-resource logic.
Fixes#8933
InputController.#invokeSkillCommand cleared the draft and awaited the full
promptCustomMessage dispatch with no transcript render. AgentSession.#promptWithMessage
runs awaited preflight (memory recall, before_agent_start hooks, auto-thinking
classification, pre-prompt compaction) before the message reaches the agent, so a slow
step such as a Hindsight auto-recall timeout left the composer cleared with no pending
row, unlike a normal prompt's optimistic row from startPendingSubmission.
Idle skill submissions now paint an optimistic skill row before the awaited dispatch;
the canonical message_start reconciles it in place via EventController instead of
appending a duplicate. Streaming submissions still queue and show their chip.
Fixes#8895
getContextBreakdown used message position (anchorIndex >= pending.cutoffCount) as a proxy for usage freshness. After a mid-run compaction rebased the in-flight snapshot, an in-flight provider response whose request predated the compaction landed past the rebase cutoff carrying pre-compaction usage, so it out-ranked the rebased estimate and reported the pre-compaction token count (~2.6x the real one). That phantom overflow tripped the "freed too little context to make progress" guard and drove the frame-rescue path on a byte-identical tokensBefore.
Assistant context snapshots now carry a monotonic compaction epoch, bumped in rebaseAfterCompaction and stamped at message-record time. A post-cutoff anchor whose epoch predates the pending snapshot's epoch is no longer trusted over the rebased estimate.
Fixes#8887
Split-commit captured the staged diff with `git diff --cached --binary`,
whose stdout is hard-capped at GIT_COMMAND_OUTPUT_LIMIT_BYTES (8 MiB) by
readCappedText. A single large binary (base85-encoded inline) crossed the
cap; the capture was truncated silently, so files sorting after the binary
were absent from the parsed diff and stage.hunks threw a misleading
`No diff found for <path>` naming an innocent file.
Surface truncation as GitCommandResult.truncated, add a requireComplete
diff option that throws the new GitOutputTruncatedError instead of
returning a silently truncated diff, and have runSplitCommit request a
complete diff and abort with a clear message pointing at the real cause.
Fixes#8897
TUI.render merged live-region seams with a topmost-seam-wins rule that
adopted only that seam's pin policy for the whole frame. While the primary
turn streams, the transcript reports the topmost, unpinned seam, so the
frame-wide pin flag becomes false and a pinned AnchoredLiveContainer below
it (the /btw panel, the working HUD) loses its pinning: once its content
grows past the viewport, the emit path commits the scrolled-off rows as
frozen snapshots on every growth frame, piling duplicates into native
scrollback.
Track a pinnedBoundary (start row of the topmost pinned region)
independently of which seam wins the topmost merge, and cap every commit
ceiling at it. Equivalent to the prior behavior for a fully-pinned frame
and for a frame with no pinned region; only the mixed case changes.
Fixes#8793
bash.patterns only feeds the bash tool's approval decision. The eval
tool declares the exec tier and can spawn a shell via subprocess, so a
deny rule there does nothing for the same command run through eval;
under yolo the exec call resolves to allow. Note the scope and point at
tools.approval.eval as the lever that closes the path in
bash-tool-runtime.md, approval-mode.md, and settings.md.
Fixes#8838
Both subagent revivers rebuilt the session but never wired the extension
runtime, leaving it pre-init where every action method throws
ExtensionRuntimeNotInitializedError. An extension with a tool_call handler
touching a runtime action then tripped the fail-closed gate in emitToolCall
and blocked every tool, including the hidden yield, so the revived agent
could neither finish nor exit and looped until killed.
Both the warm lifecycle reviver (executor.ts) and the cold persisted
reviver (persisted-revive.ts) now call the shared initializeExtensions
helper on the rebuilt session, restoring runtime actions, onError, and the
session_start event.
Fixes#8824
- Exported stopSharedSpinnerTicker() and wired it into InteractiveMode.stop(): a live block missed by per-component stopAnimation kept the shared 80ms interval alive as a lingering event-loop handle; the spinner suite uses it to observe a freshly armed ticker instead of one leaked by earlier files
- Title-disposal tests now save/clear/restore PI_NO_TITLE (main() in ACP/RPC mode sets it process-wide), matching the prewarm and orphan-submit precedent
The title latch from PR #8911 dedupes a second title start while one is in flight; the orphan-submit loop implicitly relied on the first mocked request having settled. Drain its promise chain at a macrotask boundary before the next submit.
- Kept the shared cursor/interaction-query module as the single handler and deleted the duplicate local implementation in cursor.ts
- Added the named webFetchRequestQuery approval case (field 9 is named under the regenerated proto)
- Preserved the deliberate no-fake-VM-success semantics for setupVmEnvironmentArgs (review of #8047)
- Updated the field-9 regression test to assert the named decode of the raw same-field reply, which also pins the LEN-prefix wire framing
Mirrors the composer's raw-sequence fallback (editor.ts:1466) so
\x1b[13;2~ triggers summarize-and-switch instead of being dropped as
shift+f3, completing the parity requested in issue #8821.
resolveWorkerSpawnCmd no longer pins the host-entry worker cwd to the
install dir; the subprocess inherits the agent cwd (or the package root
under the bun-test fallback). Update the chdir rationale accordingly.
The matcher compares selector ids case-insensitively, but the lock's
bundled-catalog lookup was exact-case: Anthropic/Claude-Opus-5 exact-
matched OpenRouter's flat id while getBundledModel("Anthropic", ...)
missed, silently re-enabling the aggregator shadow the lock exists to
prevent. Scan the named provider's bundled ids case-insensitively.