* fix(auth): preserve token health after OAuth migration
After a user migrates from OAuth to a token/setup-token credential, the
gateway model-auth rollup reported the provider as missing, producing a
false "model auth expired" warning.
Rename aggregateOAuthStatus → aggregateRefreshableAuthStatus and
extract aggregateProfileStatus helper. OAuth remains authoritative when
present; token credentials are the fallback when no effective OAuth
profile exists. Empty effectiveProfiles stays authoritative (missing).
Token fallback applies regardless of expectsOAuth flag.
Fixes#97996
Co-authored-by: SunnyShu0925 <SunnyShu0925@users.noreply.github.com>
* test(auth): use fake token fixture
* fix(doctor): repair stale auth profile orders
* fix(doctor): inspect retained auth profile stores
* fix(doctor): harden retained auth store proof
---------
Co-authored-by: SunnyShu0925 <SunnyShu0925@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(agents): apply stale-run liveness to aborted subagent orphan recovery
Skip stale unended subagent runs during orphan recovery and registry
restore, even when they carry abortedLastRun. Previously, restart-aborted
runs were exempt from the stale-unended age check, allowing hours-old
aborted child sessions to be resurrected after long downtime.
Fixes#90766
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(agents): finalize stale aborted runs instead of only skipping them
Previously the stale-run check in recoverOrphanedSubagentSessions only
incremented the skipped counter. Stale active runs were left unended
because scheduleOrphanRecovery only retries failedRuns, not skipped runs.
Now stale runs are finalized via finalizeInterruptedSubagentRun so they
don't remain orphaned in the registry.
Ref: #90766 review feedback
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(agents): await stale-run finalization in orphan recovery
Await finalizeInterruptedSubagentRun for stale aborted runs and report
failedRuns when finalization does not update the registry, so the
scheduler retry path can recover from finalization failures.
Co-authored-by: Cursor <cursoragent@cursor.com>
* test(agents): faithful restart-path proof for stale orphan recovery
Drive the real recoverOrphanedSubagentSessions against the real subagent
registry, the real isStaleUnendedSubagentRun policy, and a real on-disk
session store, mocking only the outbound gateway transport and transcript
reader. Proves finalizeInterruptedSubagentRun actually ends the stale
aborted run in the registry (endedAt set, outcome error) instead of
resuming it, while a fresh aborted run still resumes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore: amend author email
* fix(agents): scope orphan finalization to run generation
* fix(agents): preserve stale restart recovery ownership
* test(agents): isolate restart recovery scheduling
* fix(agents): make stale restart finalization durable
* fix(agents): keep stale retries generation-scoped
* test(agents): await restart recovery scheduling
* fix(agents): verify interrupted finalization
* fix(agents): defer interrupted finalization during restart
* fix(agents): preserve lifecycle type narrowing
* style(agents): use nonmutating recovery ordering
* chore: keep release note in PR body
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Pick-cat <266665499+Pick-cat@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* feat(ui): worktrees page owners and creation, structured snapshot failures, preserved-checkout cleanup
- Worktrees settings page gains an Owner column (manual/Workboard/session
with a link into the owning chat) and a New worktree form with repo
default, optional name, and a worktrees.branches-backed base picker
- non-forced worktrees.remove snapshot failures are a structured
{ removed: false, snapshotError } result (typed WorktreeSnapshotError in
the service) instead of error-string sniffing in the UI
- deleting a session whose dirty/unpushed checkout was preserved now
reports worktreePreserved through the session capability and offers an
explicit force removal instead of silently orphaning the checkout
Part of #103431
* fix(ui): report preserved checkouts from batch session deletes
deleteMany now aggregates worktreePreserved results, and the Sessions
page surfaces one notice pointing at Settings -> Worktrees instead of
silently orphaning dirty checkouts during bulk deletion.
Part of #103431
* fix(ui): reconcile generated locale artifacts after rebase
* feat(ui): sessions sidebar redesign with agent sections, smart groups, and draft sessions
- agents become collapsible top-level sidebar sections; expanding an agent
browses its sessions without navigating, replacing the hidden scope select
- built-in smart groups: one section per channel (rows keep their chat
titles), a Work section for worktree/exec-node sessions with a
repo/branch/node subtitle, custom groups, and Chats
- session names never show raw keys or peer ids; DM fallbacks shorten ids,
dashboard sessions read 'New session', unnamed work sessions read as
their checkout
- one + opens the new-session draft dialog: agent, exec host (paired
system.run nodes), folder, worktree toggle with base-branch picker
(worktrees.branches) and optional name; the first message creates the
session and starts the run in one sessions.create call
- custom group catalog/order moves to the gateway (sessions.groups.*) with
a one-time localStorage migration; rename/delete update members
server-side instead of client-side paging
Part of #103431
* fix(ui): resolve dragged sessions across browsed agent sections
Dropping a row dragged out of a non-active agent section now finds the
session in the per-agent row cache instead of only the active scope, so
the category patch is applied instead of silently doing nothing.
Part of #103431
* fix(ui): propagate group catalog changes to open clients
sessions.groups.put/rename/delete now always broadcast a groups-change
event, and the session capability reloads the gateway-owned catalog when
one arrives, so another browser's group create/reorder/rename/delete no
longer leaves this client on a stale snapshot for the rest of the
connection.
Part of #103431
* docs: restore new session dialog section after rebase
* fix(ui): translate new sidebar/session strings and refresh docs map
Real locale translations for the new-session dialog and sidebar keys
(the fallback gate requires zero recorded English fallbacks), plus the
regenerated docs map for the new Control UI section.
Part of #103431
* fix(ui): repair locale metadata after rebase conflict resolution
* fix(ui): merge mainline locale keys with the sidebar redesign strings
* fix(ui): refresh raw-copy baseline for mainline tool-card strings
* fix(ui): reconcile generated locale artifacts after rebase
* fix(chutes): redact OAuth error response body in token exchange and refresh
Replace readResponseTextLimited + raw body in error message with
extractProviderErrorDetail for bounded, redacted structured error detail.
Ref: #102953
* fix(chutes): update test assertions for extractProviderErrorDetail message format
* fix(chutes): remove unused readResponseTextLimited and CHUTES_OAUTH_ERROR_BODY_LIMIT_BYTES
* fix(chutes): normalize OAuth HTTP failures
* test(chutes): assert redaction without fixed mask
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(grep): stop before ripgrep when aborted during setup
* fix(grep): check cancellation before ripgrep spawn
* fix(grep): make cancellation own process lifecycle
Co-authored-by: qingminlong <qing.minlong@xydigit.com>
* test(grep): use bound lifecycle assertions
Co-authored-by: qingminlong <qing.minlong@xydigit.com>
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* feat(agents): derive a provider-declared default utility model when unset
When agents.defaults.utilityModel is not set, utility tasks (titles, progress
narration) now use the primary provider's declared small model
(modelCatalog.providers.<id>.defaultUtilityModel: OpenAI -> gpt-5.6-luna,
Anthropic -> claude-haiku-4-5). Auth is inherent because derivation follows
the agent's primary provider. Setting utilityModel to an empty string
disables utility routing entirely; narration turns on automatically when a
default resolves and stays off otherwise.
Formatting/lint/tests verified on Testbox (oxfmt, oxlint, plugins:inventory,
docs:map, import-cycles, check:test-types, 4 test files).
* fix(ai): drop the temperature parameter for models that reject it
The GPT-5.6 family 400s on temperature via the Responses API (live-verified:
gpt-5.6-luna/-terra reject it; gpt-5.5 and gpt-5.4-mini/nano accept it).
supportsOpenAITemperature gates all three OpenAI payload builders, with a
catalog compat override (compat.supportsTemperature) declared for the 5.6
family in the openai manifest. Without this, utility tasks (titles,
narration) would fail once gpt-5.6-luna becomes the derived default.
Gates on Testbox: oxfmt, oxlint, plugins:inventory, import-cycles,
check:test-types, 6 test files. Live proof on Testbox: gpt-5.6-luna and
claude-haiku-4-5 one-shot completions succeed with temperature requested.
* fix(agents): carry the primary model's auth profile onto the derived utility default
A primary like openai/gpt-5.5@work previously reached utility tasks via the
profiled fallback; the derived default shares the provider, so its trailing
auth profile carries over instead of silently switching to default
credentials (Codex review). Gates on Testbox: oxfmt, check:test-types, tests.
* docs: realign the manifest provider-fields table after adding defaultUtilityModel
Kind-aware tool rows (terminal-style commands with wrapper stripping and
display highlighting, file edits with inline numbered diffs and diffstat,
write previews, key-value args), aggregate group summaries with live run
status, and a new batched chat.toolTitles gateway RPC that titles complex
calls via the configured utilityModel or the OpenAI Luna default (gated to
OpenAI-primary agents, cached in the per-agent SQLite cache_entries).
Also fixes two transcript pairing bugs: result blocks now inherit call
id/name/details at merge time, and results pair with any open call in the
current tool run so parallel calls render as single rows.
Fixes#103554
* fix(agents): gate empty-error-retry on per-attempt side effects, not cumulative (#97877)
Derive currentAttemptReplayMetadata from per-attempt fields via
buildAttemptReplayMetadata so the retry gate can distinguish "this
model call had no tool side effects" from "cumulative session
replayMetadata carries prior-turn history".
Previously raw.replayMetadata was saved as currentAttemptReplayMetadata,
but attempt.ts already merges observedReplayMetadata with accumulated
session state before returning, so both fields carried the same
cumulative value — making the per-attempt check ineffective.
shouldRetrySilentErrorAssistantTurn prefers currentAttemptReplayMetadata
when available, falling back to replayMetadata for legacy harnesses.
Add regression coverage:
- 22→23 empty-error-retry tests (prior-side-effects + clean 5xx scenario)
- 111→114 incomplete-turn tests (currentAttemptReplayMetadata unit tests)
* fix(agents): harden silent retry replay gating
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
The compaction safeguard treated a successful keyless auth resolution
(ok: true with neither apiKey nor headers) as missing credentials and
cancelled compaction. Bedrock providers using auth: "aws-sdk" sign
requests with SigV4 at send time and legitimately return no static key
or header, so every message on a session large enough to need compaction
failed with a false "could not resolve request credentials" error.
Trust the registry's ok:true success signal; it already returns ok:false
when auth genuinely cannot resolve. Add regression coverage for the
keyless SDK-managed auth path.
Co-authored-by: Roy Osherove <575051+royosherove@users.noreply.github.com>
* fix: normalizeMediaSource should handle uppercase FILE:// URI schemes
URI schemes are case-insensitive per RFC 3986. normalizeMediaSource
only matched lowercase 'file://', so MEDIA:FILE:///tmp/generated.png
was left in assistant text instead of being extracted as a local
media attachment.
Replaced startsWith + replace with a case-insensitive regex to handle
FILE://, file://, and any mixed-case variant.
Fixes#103473
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(media): handle spaced uppercase file URLs
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>