Repository navigation
Show model and effort choices for every agent before its session starts - #26407
Conversation
Every agent registration now declares how its models are listed without a session (a probe, or explicitly none). Probes share one supervised runner (15 s budget, bounded output, teardown on every path). Grok lists from initialize (or x.ai/models/list), OpenCode from models --verbose, Pi from --list-models; Claude moves onto the shared runner. ACP option reads no longer copy the running model's effort menu or selection onto every model. Desktop and phone pickers read the same catalog for every agent, with a non-pickable provider-default model pill before any list exists.
…ngs saved for every agent - The catalog answer carries an optional listingNamesConfiguredModel field, set per registration (Codex, Grok) and per workspace; pickers name the listed default from it instead of a Codex-only client flag (older hosts fall back). - Grok, OpenCode and Pi live option reads save their model list through one host step, keeping the account's known default and per-model efforts. - Phone reads the host catalog like the desktop and seeds every agent. - Saved picks accept any known agent id (OpenCode, Pi). - ACP agents with no session-free listing register as unavailable; Grok's live effort menu falls back to the session's when the advertised one is empty.
…'s live listing saved by the host; phone-created chats name their model
… answer; Claude's startup listing reaches the host catalog
…del-catalog-all-agents # Conflicts: # src/main/claude/claude-model-catalog-probe.ts
… wire types don't pull the store's persistence into the phone
…listing lands in place
…and traces each listing
…onfig resolves to
…only when it sets a model or effort A committed .claude/settings.json for permissions or hooks, or the settings.local.json Claude writes on always-allow, no longer hides the configured Claude model in that project. Unreadable config still counts; Codex config.toml follows the same rule.
…olve one account key, through the real resolvers Runs resolveHostCodexHomePathForLaunchReadOnly and prepareCodexRuntimeHomeForLaunch as the runtime service wires them, for the system account on the real home, on the shared runtime home, and a managed account. Also scopes the prewarm suite to agents in use.
…ther behind the project check; prewarm lists only agents in use and stops with its host A live Claude chat no longer stamps its effort onto the account catalog; the effort travels with the configured default, which a no-pick chat whose config resolves to no listed model now forgets. Prewarm lists agents with a saved catalog or a chat, and host teardown (quit included) starts no queued listing and stops running ones through an abort signal the listing runners honor.
…del-catalog-all-agents # Conflicts: # mobile/src/session/use-mobile-structured-agent-session.test.tsx # src/main/acp/acp-dialects/acp-dialect.ts
…-only namespace imports
…del-catalog-all-agents Brings in #25666 (a probe's "not signed in" / "CLI missing" verdict on the catalog's failure record). Both sides kept: - Store: the verdict rides only discovery listings; a chat's live listing touches neither a plain failure nor a verdict. A superseded probe answer still settles the verdict. The listing-to-entry builder moves to the entry module to keep the store under its line budget. - Service: every answer carries the verdict; a verdict past its TTL is re-probed through the same host-lifetime listing prewarm uses, and prewarm counts an aged verdict as due. - Claude probe keeps its own list_models runner; a spawn whose executable is missing now says so, and the probe turns that into `cliMissing`. - Codex probe registration passes the account kind and launch-home sync. - Renderer: the verdict is reported without bringing back the picker wait.
…t like a read's, and re-checks an aged one beside a fresh catalog
…aths, not a Codex chat's picker The main merge put the held-verdict check into store.shouldRefresh, which also gates the live Codex picker. A chat's own listing never clears the verdict, so a signed-out Codex whose chat works re-listed model/list on every picker read. shouldRefresh is back to main's rule; the catalog read's probe and prewarm ask probeDue instead.
…thread agent or an Anthropic cloud provider count as an override maxEffortLevel clamps every effort including the model default, and agent applies that agent's model (both from the bundled SDK settings type). CLAUDE_CODE_USE_ANTHROPIC_AWS and CLAUDE_CODE_USE_ANTHROPIC_GOOGLE_CLOUD sit in the SDK's provider-switch list beside Bedrock and Vertex. A project that sets only these no longer shows, or saves as the account default, the account's model or effort.
…ir own module No behavior change: pendingListing and the waiter notifications move as they were, which keeps the store under its line limit for the fixes that follow.
… its catalogs and re-checks its notice The settings change called prewarm, but prewarm skipped any catalog still fresh (the catalog key is agent + account, not the command), and a held "not installed" verdict kept its 30-second window. The change now marks that agent's catalogs due and ages its held verdicts, so the prewarm it triggers re-lists them and re-checks the notice. Only agents whose command or env actually changed are touched.
… really usable They checked only that a model entry existed, which a disabled or empty pill also passes. They now check it is settable, carries no disabled reason, and offers the catalog's or the seed's model.
…, and a stopped prewarm reads nothing A probe aborted because the host is going away says nothing about the account, so it no longer replaces a held sign-in or not-installed reason with a plain failure. A prewarm after stop returns before reading chat records.
…ex launch-home resolvers The resolvers move to codex-launch-preparation as one export the runtime service spreads, so the test runs exactly what the runtime is handed instead of a copy.
…del-catalog-all-agents # Conflicts: # src/main/acp/acp-launch-specs.ts
…del-catalog-all-agents # Conflicts: # src/main/claude/claude-model-catalog-probe.ts # src/main/claude/claude-structured-session-startup.test.ts # src/main/claude/claude-structured-session-startup.ts # src/main/native-chat/agent-session-wire/structured-agent-session-adapter.ts # src/main/native-chat/agent-session-wire/structured-agent-session-provider-started.ts # src/main/provider-process/managed-provider-process.ts
…del-catalog-all-agents
Review summary (coordinator)State: head What this PR is for: a new native chat's model and effort pickers used to wait on the agent's session. Grok, OpenCode and Pi showed an empty picker until their session started (5–8 s for Grok), and a cold Codex picker sat disabled for about 10 s while it listed models. Now every agent's picker is filled from a saved per-account model list that a session-free check refreshes in the background, and the picker never waits on that check. Review rounds (each by a fresh reviewer reading the diff against that goal)
Verified
Not verified
Deviations (also in the body)
CI on |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configuration
📒 Files selected for processing (17)
💤 Files with no reviewable changes (1)
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 5 remain after this review. 📝 WalkthroughWalkthroughThe pull request adds shared model-catalog discovery, probing, persistence, expiry, waiting, and runtime wiring for structured agents. It adds provider support for ACP, Claude, Codex, OpenCode, and Pi. Session adapters report live listings and configured defaults. Mobile and renderer option flows use host catalogs, launch state, fallbacks, and broader agent support. Tests cover discovery, catalog merging, defaults, lifecycle handling, and picker behavior. Priority: ➖ Normal Merge Risk: ⚪ Minimal · up to This change shows model and effort choices before a session starts, using saved catalogs and a placeholder. No concrete merge-blocking risk was found in the reviewed changes. Real agent CLIs and some environments such as SSH and WSL were not verified, so a manual check there is reasonable. 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
- Configuration used: Repository UI
- Review profile: CHILL
- Plan: Advanced
- Run ID:
218b635f-c34c-4227-b4ce-d404184bfd91
📒 Files selected for processing (110)
config/scripts/vitest-sqlite-runtime-files.mjsmobile/src/session/mobile-created-structured-sessions.tsmobile/src/session/mobile-existing-agent-launch.test.tsmobile/src/session/mobile-existing-agent-launch.tsmobile/src/session/mobile-structured-agent-session-launch.test.tsmobile/src/session/mobile-structured-agent-session-launch.tsmobile/src/session/use-mobile-host-model-catalog-upgrade.tsmobile/src/session/use-mobile-structured-agent-options.test.tsmobile/src/session/use-mobile-structured-agent-options.tsmobile/src/session/use-mobile-structured-agent-session.test.tsxsrc/main/acp/acp-dialects/acp-dialect.tssrc/main/acp/acp-dialects/grok-dialect.tssrc/main/acp/acp-dialects/grok-model-catalog.tssrc/main/acp/acp-launch-specs.tssrc/main/acp/acp-model-catalog-probe.test.tssrc/main/acp/acp-model-catalog-probe.tssrc/main/acp/acp-session-runtime.tssrc/main/acp/acp-structured-acquire.tssrc/main/acp/acp-structured-host-live-listing.test.tssrc/main/acp/acp-structured-host.test-support.tssrc/main/acp/acp-structured-launch-resolution.tssrc/main/acp/acp-structured-options.test.tssrc/main/acp/acp-structured-options.tssrc/main/acp/acp-structured-session-adapter.tssrc/main/claude/claude-model-catalog-probe.test.tssrc/main/claude/claude-model-catalog-probe.tssrc/main/claude/claude-structured-acquire-catalog.tssrc/main/claude/claude-structured-catalog-listing.tssrc/main/claude/claude-structured-configured-default.test.tssrc/main/claude/claude-structured-effort-default-at-rest.test.tssrc/main/claude/claude-structured-retired-model.test.tssrc/main/claude/claude-structured-retired-model.tssrc/main/claude/claude-structured-session-acquisition.tssrc/main/claude/claude-structured-session-adapter-turns.test.tssrc/main/claude/claude-structured-session-options.tssrc/main/claude/claude-structured-session-startup.test.tssrc/main/claude/claude-structured-session-startup.tssrc/main/claude/claude-structured-session-state.tssrc/main/codex-accounts/runtime-home-catalog-account-key.test.tssrc/main/codex/codex-app-server-session.tssrc/main/codex/codex-model-catalog-probe.test.tssrc/main/codex/codex-model-catalog-probe.tssrc/main/codex/codex-short-lived-app-server-spawn.test.tssrc/main/codex/codex-structured-background-catalog.tssrc/main/codex/codex-structured-fast-mode.test.tssrc/main/codex/codex-structured-session-options-catalog.test.tssrc/main/native-chat/agent-model-catalog/agent-model-catalog-account-expiry.tssrc/main/native-chat/agent-model-catalog/agent-model-catalog-discovery-freshness.test.tssrc/main/native-chat/agent-model-catalog/agent-model-catalog-discovery.tssrc/main/native-chat/agent-model-catalog/agent-model-catalog-entry.tssrc/main/native-chat/agent-model-catalog/agent-model-catalog-failures.tssrc/main/native-chat/agent-model-catalog/agent-model-catalog-listing-waiters.tssrc/main/native-chat/agent-model-catalog/agent-model-catalog-persistence.tssrc/main/native-chat/agent-model-catalog/agent-model-catalog-prewarm.test.tssrc/main/native-chat/agent-model-catalog/agent-model-catalog-probe-runner.test.tssrc/main/native-chat/agent-model-catalog/agent-model-catalog-probe-runner.tssrc/main/native-chat/agent-model-catalog/agent-model-catalog-service.test.tssrc/main/native-chat/agent-model-catalog/agent-model-catalog-service.tssrc/main/native-chat/agent-model-catalog/agent-model-catalog-store.test.tssrc/main/native-chat/agent-model-catalog/agent-model-catalog-store.tssrc/main/native-chat/agent-model-catalog/agent-model-catalog-unavailable.test.tssrc/main/native-chat/agent-model-catalog/agent-project-model-config-keys.tssrc/main/native-chat/agent-model-catalog/agent-project-model-override.test.tssrc/main/native-chat/agent-model-catalog/agent-project-model-override.tssrc/main/native-chat/agent-session-wire/structured-agent-session-adapter.tssrc/main/native-chat/agent-session-wire/structured-agent-session-options-read.tssrc/main/native-chat/agent-session-wire/structured-agent-session-provider-started-catalog.test.tssrc/main/native-chat/agent-session-wire/structured-agent-session-provider-started.test.tssrc/main/native-chat/agent-session-wire/structured-agent-session-provider-started.tssrc/main/native-chat/agent-session-wire/structured-agent-session-startup-attempt-host.test.tssrc/main/opencode/opencode-model-catalog-listing.test.tssrc/main/opencode/opencode-model-catalog-listing.tssrc/main/pi/rpc-model-catalog-probe.test.tssrc/main/pi/rpc-model-catalog-probe.tssrc/main/pi/rpc-options.tssrc/main/pi/rpc-runtime-registration.tssrc/main/pi/rpc-session-adapter.tssrc/main/provider-process/managed-provider-process.tssrc/main/runtime/rpc/methods/client-native-chat-settings.test.tssrc/main/runtime/structured-agent-model-catalog-discovery.test.tssrc/main/runtime/structured-agent-model-catalog-discovery.tssrc/main/runtime/structured-agent-model-catalog-first-frame.test.tssrc/main/runtime/structured-agent-model-catalog-wiring.tssrc/main/runtime/structured-agent-runtime-registrations.tssrc/main/runtime/structured-agent-session-runtime-model-catalog-lifetime.test.tssrc/main/runtime/structured-agent-session-runtime.tssrc/main/runtime/structured-claude-runtime-adapter.tssrc/main/startup/codex-launch-preparation.tssrc/main/startup/main-process-account-services.tssrc/main/startup/main-process-runtime-service.tssrc/renderer/src/components/native-chat/NativeChatSessionOptionPickers.menu-request.test.tsxsrc/renderer/src/components/native-chat/NativeChatSessionOptionPickers.test.tsxsrc/renderer/src/components/native-chat/NativeChatSessionOptionPickers.tsxsrc/renderer/src/components/native-chat/host-model-listing-waits.tssrc/renderer/src/components/native-chat/structured-agent-registered-agent-surface.test.tssrc/renderer/src/components/native-chat/structured-agent-session-seed-catalog.tssrc/renderer/src/components/native-chat/use-host-model-catalog-upgrade.test.tsxsrc/renderer/src/components/native-chat/use-host-model-catalog-upgrade.tssrc/renderer/src/components/native-chat/use-host-model-catalog-upgrade.unavailable.test.tsxsrc/renderer/src/components/native-chat/use-structured-agent-session-option-state.tssrc/renderer/src/components/native-chat/use-structured-agent-session-options.test.tsxsrc/renderer/src/components/native-chat/use-structured-agent-session-options.tssrc/shared/agent-session-option-catalog-types.tssrc/shared/agent-session-wire.tssrc/shared/native-chat-session-options.tssrc/shared/rpc-contract/client-settings-params.tssrc/shared/structured-agent-session-first-frame-picker.test.tssrc/shared/structured-agent-session-model-catalog-application.test.tssrc/shared/structured-agent-session-options.tssrc/shared/structured-agent-session-seed-catalog.ts
💤 Files with no reviewable changes (9)
- src/renderer/src/components/native-chat/NativeChatSessionOptionPickers.menu-request.test.tsx
- src/main/claude/claude-structured-acquire-catalog.ts
- src/shared/native-chat-session-options.ts
- src/renderer/src/components/native-chat/NativeChatSessionOptionPickers.test.tsx
- src/main/claude/claude-structured-retired-model.ts
- src/renderer/src/components/native-chat/structured-agent-session-seed-catalog.ts
- src/main/claude/claude-structured-session-acquisition.ts
- src/renderer/src/components/native-chat/host-model-listing-waits.ts
- src/main/claude/claude-structured-session-state.ts
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 2 remain after this review.
| // An account switch would otherwise leave the next chat's picker on a cold catalog. | ||
| if (MODEL_CATALOG_ACCOUNT_SETTINGS.some((key) => key in updates)) { | ||
| prewarmStructuredAgentModelCatalogs() | ||
| } |
There was a problem hiding this comment.
🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
#!/bin/bash
# Find the writers of agentDefaultEnv / agentCmdOverrides and see whether they are debounced.
rg -nP -C4 'agentDefaultEnv|agentCmdOverrides' --type=ts --type=tsx -g '!**/*.test.*' src/renderer | head -n 200
rg -nP -C3 'updateSettings\(\s*\{\s*agent(DefaultEnv|CmdOverrides)' --type=ts -g '!**/*.test.*'Repository: stablyai/orca
Length of output: 1420
🏁 Script executed:
set -o pipefail
printf '%s\n' '--- AgentsPane relevant declarations and callbacks ---'
nl -ba src/renderer/src/components/settings/AgentsPane.tsx | sed -n '1,280p'
printf '%s\n' '--- settings update definitions and save-control bindings ---'
rg -n -F --glob '*.ts' --glob '*.tsx' -- 'onSaveEnv' src/renderer/src src/main || true
rg -n -F --glob '*.ts' --glob '*.tsx' -- 'function updateSettings' src/renderer/src src/main || true
rg -n -F --glob '*.ts' --glob '*.tsx' -- 'updateSettings:' src/renderer/src/components/settings src/renderer/src | head -n 120
printf '%s\n' '--- changed account-services path ---'
nl -ba src/main/startup/main-process-account-services.ts | sed -n '1,180p'
printf '%s\n' '--- catalog expiry and prewarm definitions/callers ---'
rg -n -F --glob '*.ts' --glob '*.tsx' -- 'prewarmStructuredAgentModelCatalogs' src
rg -n -F --glob '*.ts' --glob '*.tsx' -- 'expireModelCatalogs' srcRepository: stablyai/orca
Length of output: 39871
🏁 Script executed:
printf '%s\n' '--- AgentLaunchDefaultsEditor complete declaration ---'
nl -ba src/renderer/src/components/settings/AgentLaunchDefaultsEditor.tsx | sed -n '1,240p'
printf '%s\n' '--- AgentCatalogRow composition around editor ---'
nl -ba src/renderer/src/components/settings/AgentCatalogRow.tsx | sed -n '1,260p'
printf '%s\n' '--- catalog expiry implementation ---'
rg -n -F --glob '*.ts' --glob '*.tsx' -- 'createAgentModelCatalogSettingsExpiry' src/main src/shared
nl -ba src/main/native-chat/agent-model-catalog/agent-model-catalog-account-expiry.ts | sed -n '1,260p'
printf '%s\n' '--- prewarm implementation and listing lifecycle ---'
nl -ba src/main/runtime/structured-agent-model-catalog-wiring.ts | sed -n '1,220p'Repository: stablyai/orca
Length of output: 29322
🏁 Script executed:
printf '%s\n' '--- catalog service prewarm and refresh lifecycle ---'
rg -n -F --glob '*.ts' --glob '*.tsx' -- 'prewarm()' src/main/native-chat/agent-model-catalog src/main | head -n 80
rg -n -F --glob '*.ts' --glob '*.tsx' -- 'prewarm' src/main/native-chat/agent-model-catalog
nl -ba src/main/native-chat/agent-model-catalog/agent-model-catalog-service.ts | sed -n '1,360p'
printf '%s\n' '--- catalog store due and in-flight lifecycle ---'
nl -ba src/main/native-chat/agent-model-catalog/agent-model-catalog-store.ts | sed -n '1,420p'Repository: stablyai/orca
Length of output: 39985
Coalesce catalog prewarming after launch-setting saves.
The editors do not save on every keystroke. They save on blur, Enter, and Reset. A changed field can still trigger a catalog refresh on each blur. If a previous listing has finished, the next saved change marks the used agent's catalog due and starts another CLI listing.
Debounce or otherwise coalesce prewarmStructuredAgentModelCatalogs() calls. Keep expireModelCatalogs() immediate so the latest settings remain invalidated.
A chat picker could only learn the host's saved model list by reading it after it mounted, so its first frame was a guess. The renderer now keeps the host's answers per host and agent: this machine's lists are read once its runtime answers, for the agents its chats were used with; a paired host's again when this client regains contact; and an account or launch setting change drops this machine's lists. Every entry is a host answer.
A new chat's composer painted its first frame from the built-in model list: a saved pick that list lacks (Claude's `opus[1m]`) showed as a raw id with no effort pill, then jumped to "Opus (1M context) · High" when the host's list landed. Every new chat painted a guess and then corrected it. Now the first frame reads the host's saved list the renderer already holds, so it is the final frame. Without one, the quiet "Model" placeholder stands until the host answers; a host with nothing saved makes the built-in list usable, naming nothing, until its listing lands. A saved pick is named only from a host or session list that names it. The phone follows the same rule and keeps the host's list across a new fence.
The app could preload a host's saved model lists only for agents with a saved pick, because the catalog read starts a listing (spawns the agent's CLI) when nothing is saved. Grok, OpenCode and OMP chats with no saved pick were never preloaded, so their pickers showed "Model" before the host's list. `agentSession.modelCatalog` now takes `savedOnly`: the host answers from what it saved (the entry, configured default and held reason) and starts, joins or re-checks no listing. Its params are strict, so the host advertises `agent-session.model-catalog.saved-only.v1` and clients send it only to hosts that do. An answer also says when its named default holds in every workspace (Grok reads no project config for its model), so a new chat in any workspace can use it on its first frame. OMP stays without a session-free listing: `omp models --json` lists every model without the `enabledModels` filter its chats apply.
Grok, OpenCode and OMP chats painted the "Model" placeholder, then the host's pills, because the app preloaded only agents with a saved pick. Now the app reads every agent a host registered through the saved-only read: this machine once its runtime and agents are known, a paired host at connect and on each reconnect, and this machine again after an account or agent-setting change. A host that does not advertise the saved-only read keeps the earlier rule (saved picks only; a paired host's answered agents). An agent the host saved nothing for stays unknown. An answer sent before the host's lists were dropped is not recorded.
…th no pick Grok's session-free listing named the model `initialize` computed (grok-4.7), but chats ran another (grok-4.6), so a new chat painted 4.7 and then jumped. The listing no longer names a default; a new ACP session with no model pick reports what it runs, and the host saves that as the account's configured default behind the workspace check. A catalog read waits for a recording still in flight, so the chat's own follow-up read names it.
…g their default OpenCode reads opencode.json(c) and .opencode/ in every ancestor of the workspace; OMP merges its .omp settings and other agents' project settings. A workspace whose config sets a model, effort or provider key (or can't be parsed) names no default; any other lets a no-pick chat's model become the account's default, so the next new chat names it on its first frame.
… its model The renderer kept the answer it read before the chat started, so the next new chat's first frame still named no model. A new chat that named no model reads the host's list again once it reports what it runs; a default no workspace can replace also serves chats in other worktrees.
…del-catalog-all-agents # Conflicts: # config/scripts/vitest-sqlite-runtime-files.mjs # src/main/acp/acp-dialects/grok-dialect.ts # src/main/acp/acp-launch-specs.ts
Review summary addendum (rounds 7–9, after manual testing)The owner tested the build by hand and found the model and effort pills jumping on a new chat's first frame: a raw id (
Manual check by the owner on CI on Still temporary (in the body): Pi names no model until its chat reports one (follow-up: same learned-default rule); a no-pick chat in a worktree the app hasn't read this run shows the placeholder until the host answers; a paired server's lists refresh on its next chat or reconnect until the host pushes changes; phone unchanged this round. |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/main/runtime/structured-agent-model-catalog-discovery.test.ts (1)
93-132: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueRemove the temp directory in a
finallyblock.If an assertion fails, line 131 does not run. Each failed run then leaves an
orca-catalog-overrides-*directory in tmp. Wrap the body intry/finally.
ℹ️ Review info
⚙️ Run configuration
- Configuration used: Repository UI
- Review profile: CHILL
- Plan: Advanced
- Run ID:
24fe5263-225b-44b3-b321-9e35fe6158d2
📒 Files selected for processing (51)
config/scripts/vitest-sqlite-runtime-files.mjsmobile/src/session/mobile-structured-agent-session-launch.test.tsmobile/src/session/use-mobile-host-model-catalog-upgrade.tsmobile/src/session/use-mobile-structured-agent-options.first-frame.test.tsmobile/src/session/use-mobile-structured-agent-options.tssrc/main/acp/acp-dialects/grok-dialect.tssrc/main/acp/acp-dialects/grok-model-catalog.tssrc/main/acp/acp-launch-specs.tssrc/main/acp/acp-model-catalog-probe.test.tssrc/main/acp/acp-structured-acquire.tssrc/main/acp/acp-structured-host-configured-default.test.tssrc/main/acp/acp-structured-options.test.tssrc/main/acp/acp-structured-options.tssrc/main/acp/acp-structured-session-adapter.tssrc/main/claude/claude-structured-session-acquisition.tssrc/main/native-chat/agent-model-catalog/agent-model-catalog-entry.tssrc/main/native-chat/agent-model-catalog/agent-model-catalog-learned-default.test.tssrc/main/native-chat/agent-model-catalog/agent-model-catalog-saved-only-read.test.tssrc/main/native-chat/agent-model-catalog/agent-model-catalog-service.tssrc/main/native-chat/agent-model-catalog/agent-project-model-config-keys.tssrc/main/native-chat/agent-model-catalog/agent-project-model-override.test.tssrc/main/native-chat/agent-model-catalog/agent-project-model-override.tssrc/main/native-chat/agent-session-wire/structured-agent-session-queued-mutations.tssrc/main/runtime/rpc/methods/structured-agent-session-options-read.test.tssrc/main/runtime/structured-agent-model-catalog-discovery.test.tssrc/main/runtime/structured-agent-model-catalog-first-frame.test.tssrc/main/runtime/structured-agent-model-catalog-wiring.tssrc/main/runtime/structured-agent-runtime-registrations.tssrc/renderer/src/app-shell/use-app-shell-services.tssrc/renderer/src/components/native-chat/NativeChatSessionOptionPickers.tsxsrc/renderer/src/components/native-chat/use-host-model-catalog-upgrade.test.tsxsrc/renderer/src/components/native-chat/use-host-model-catalog-upgrade.tssrc/renderer/src/components/native-chat/use-host-model-catalog-upgrade.unavailable.test.tsxsrc/renderer/src/components/native-chat/use-native-chat-provisional-launch.test.tsxsrc/renderer/src/components/native-chat/use-structured-agent-session-option-state.tssrc/renderer/src/components/native-chat/use-structured-agent-session-options.first-frame.test.tsxsrc/renderer/src/components/native-chat/use-structured-agent-session-options.learned-default.test.tsxsrc/renderer/src/components/native-chat/use-structured-agent-session-options.preloaded-agents.test.tsxsrc/renderer/src/components/native-chat/use-structured-agent-session-options.test.tsxsrc/renderer/src/components/native-chat/use-structured-agent-session-options.tssrc/renderer/src/components/native-chat/use-structured-agent-session-provisional.test.tsxsrc/renderer/src/runtime/host-model-catalog-snapshots-sync.tssrc/renderer/src/runtime/host-model-catalog-snapshots.test.tssrc/renderer/src/runtime/host-model-catalog-snapshots.tssrc/shared/agent-session-wire.tssrc/shared/rpc-contract/structured-agent-session-params.tssrc/shared/structured-agent-session-model-catalog-application.test.tssrc/shared/structured-agent-session-option-view.tssrc/shared/structured-agent-session-options.test.tssrc/shared/structured-agent-session-options.tssrc/shared/structured-agent-session-surface-capabilities.ts
💤 Files with no reviewable changes (2)
- src/main/native-chat/agent-session-wire/structured-agent-session-queued-mutations.ts
- src/main/claude/claude-structured-session-acquisition.ts
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 1 remain after this review.
…del-catalog-all-agents # Conflicts: # config/scripts/vitest-sqlite-runtime-files.mjs # src/main/acp/acp-dialects/grok-dialect.ts # src/renderer/src/components/native-chat/use-host-model-catalog-upgrade.ts # src/renderer/src/components/native-chat/use-structured-agent-session-options.ts
…del-catalog-all-agents Conflicts kept both sides: - grok-dialect.ts: this branch's modelEfforts plus main's subagent hooks and stop control. - acp-structured-acquire.ts: main's child-stop probe, then adoptSession with this branch's new/loaded origin. - acp-structured-session-adapter.ts: both imports (live catalog listing; child stop). - acp-structured-host.test-support.ts: both rig options (statusSink, modelCatalog); the attached rig takes modelCatalog as a third argument after main's statusSink. - NativeChatSessionOptionPickers.test.tsx: kept main's no-model test; the pending-choices test stays removed (this branch dropped choicesPending). Semantic conflict: both sides added AcpSessionRuntime.requestExtension with different contracts (main: session-bound, underscored, bounded; this branch: session-free listing). This branch's is renamed requestSessionFreeExtension so the model listing never needs a session. Adds a test that a new ACP session reporting no model records no configured default.
Conflict in acp-launch-specs.ts: keeps main's modelDiscovery on the Grok, OpenCode and OMP specs and re-adds only this PR's `compaction: true`. Main's model catalog work (#26407) left acp-structured-session-adapter.ts at its 300-line limit, so the adapter's `compact` now delegates in one expression and AcpStructuredTurns.compact returns the admission receipt itself; the adapter's two imports from acp-structured-connection are joined and releaseAcquisition drops a return annotation close() already gives. Behaviour is unchanged.
…ain) Rebuilds #26561 on main now that #26416 is squash-merged. Same change as before, re-applied onto what main has since: - Every adapter answers acquire at spawn and reports `started` on protocol readiness with its real handle (Claude initialize, Codex thread, ACP session new/load plus restore, Pi session file plus restore). No bridge, no per-adapter phase or Stop opt-outs. - Picks made while starting are intents applied model then effort before the start is accepted; rewind, goal, rewind recovery and compact wait for a proven start outside the session queue. - One Stop rule: Stop ends the session for every agent (Codex: interrupt, up to 2 s for the turn to end, then close). - Orchestration worker starts share one deadline; a send's answer copies its row; a re-attach during start keeps the start wait. Integrated with main: - ACP: the subagent-stop probe, child-work delivery and new/loaded origin move into the handshake; the sign-in rule (#26544) classifies a failed handshake; acpAgentName stays in acp-structured-agent-definitions. - Codex: the session journal translator and account kind (#26407) reach the start step. - Pi: the start choice (#26821) and the signed-out verdict (#26743) are read in Pi's start, so both exist when `started` is reported; commands follow. - openWithAgent keeps main's pre-agent check and replay rule. - A report with no model drops the saved one, as #26587 decided. - The renderer re-reads a start's sign-in verdict once the start is proven.
ELI5
A new Grok, OpenCode or Pi chat showed an empty model picker for the 5–8 seconds its agent took to start, while Claude and Codex showed their choices at once from a list Orca had saved. And the first chat for an account, Codex especially, showed a greyed-out "Model" pill for about 10 seconds (sometimes much longer) while Orca asked Codex for its models: the "model selector is slow" report.
This PR gives every agent the same saved model list, filled the same way, and the picker never waits on it. Orca refreshes the lists in the background for the agents you use.
Manual testing then found a jump: a new Claude chat whose saved pick was
opus[1m]first showed "opus_1m" with no effort pill, then switched to "Opus (1M context) · High". The first frame came from Orca's built-in model list, which doesn't containopus[1m], and was replaced when the saved list arrived. Now the model and effort pills are drawn once, from the saved list the app already loaded.A later manual test found the same for Grok, OpenCode and OMP: their pickers showed "Model", then "Grok 4.6 · High" (or the model). The app loaded saved lists ahead of time only for agents with a remembered model pick, because the host's catalog read starts the agent's listing (its CLI) when nothing is saved. These agents had no remembered pick, so their first frame waited for the host. Now the app loads every agent's saved list through a read that never starts anything.
The next manual test still saw Grok, OpenCode and OMP show "Model" (or Grok 4.7) before the model the chat actually ran (Grok 4.6 · High). Two causes. Grok's model list, read without a chat, marks a current model (4.7 here), but Grok's own chats ran 4.6: Grok picks its default model partly from settings it fetches from its servers, and the list and a chat can see different answers. Orca never sends a model for a chat with no pick, so the difference is Grok's own. OpenCode's and OMP's lists mark no default at all. Now none of these lists is trusted to name the default: a chat started with no model pick reports what it runs, and the host remembers that as the account's default (as it already did for Claude), so the next new chat shows that model and effort on its first frame.
What Changed
What you see
main(and earlier rounds of this PR) the first frame came from the built-in list and could change when the saved list arrived.maina new Claude chat lists Opus, Sonnet and Haiku with nothing named and no effort pill, so this improves unchanged behavior; it is not a regression fix. Orca learns the default from Claude itself (details below) and uses it only in projects whose own Claude settings don't pick a model or effort. A project that commits.claude/settings.jsonjust for permissions or hooks, or has thesettings.local.jsonClaude writes when you choose "always allow", still gets the named default.After merging the "not signed in" / "not installed" notice (#25666). This PR merged
mainafter #25666 landed and keeps its rules: only a session-free check decides the notice's reason, a chat's own listing never clears it, and starting a chat makes the next read re-check it. It also keeps #26743's rule: a running Pi chat whose agent started with no model shows "Pi isn't signed in" for that chat only; that answer is never saved and ends with that Pi. What this PR adds on top:list_modelslisting (mainused Claude's text-generation model discovery) and reports a Claude program that cannot be started as "not installed".main, but now that also happens when the background listing runs (at start and on an account switch), not only when a picker reads.Why Codex waited. While the host's first listing for an account ran, the client marked the pill "choices pending", and nothing listed ahead of time. The user's three saved Codex entries are three different accounts (the system
~/.codexand two Orca-managed ones), not one account whose key drifted; each switch started cold.Mechanism
listingNamesConfiguredModelfield, true only when the registration says so and the chat's workspace has no config of its own that picks a model or effort. Clients name the default only for a new chat and only when the host says so.model,agent(which applies that agent's model),effortLevel,maxEffortLevel(an effort cap),modelSettings,availableModels,enforceAvailableModels,modelOverrides, or anenvmodel, effort or provider variable (ANTHROPIC_MODEL,ANTHROPIC_DEFAULT_*_MODEL,CLAUDE_CODE_EFFORT_LEVEL, and the provider switchesCLAUDE_CODE_USE_BEDROCK,_VERTEX,_FOUNDRY,_ANTHROPIC_AWS,_ANTHROPIC_GOOGLE_CLOUD,_MANTLE,_GATEWAY). The settings keys come from the bundled Claude SDK's settings type; the provider switches from its provider list. A Codex projectconfig.tomlcounts if it setsmodel,model_reasoning_effort,model_provider,profile, or a profile with one of those. Unreadable or invalid files still count.currentModelIdis no longer marked as the default.opencode.json,opencode.jsonc,.opencode/opencode.json(c)count if they setmodel,default_agent,provider,providers,enabled_providers,disabled_providers, or anagent/agents/modeentry withmodel,variantordisable; agent files under.opencode/agent(s)/and.opencode/mode(s)/count if their front matter setsmodelorvariant. OMP (only the workspace folder itself, as OMP reads it):.omp/settings.json,.omp/config.yml/.yaml, and the other agents' project settings OMP merges in (.claude/settings.json,.cursor/settings.json,.gemini/settings.json,opencode.json,.codex/config.toml) count if they setmodelRoles,enabledModels,disabledProviders,modelProviderOrder,modelTags,providersordefaultThinkingLevel(or Pi'sdefaultModel/defaultProvider). Key names come from each CLI's own bundled config schema, read without running it. Unreadable or invalid files count.get_settings→applied) and, if the chat made no effort pick, the effort Claude applies. The host saves the two together, but only after the project check above passes for that chat's launch folder. A Claude chat's own effort is no longer stamped onto the account's list. If a no-pick chat's config now resolves to a model the account doesn't list, or to no model at all, the saved default is forgotten, and later background listings don't bring it back. Rows that run the same model (Claude's "Default" row and the model it stands for) share the saved effort.agentSession.modelCatalogtakes an optionalsavedOnly: the host answers from what it saved (the list, a learned default, a held "not signed in"/"not installed" reason) and starts, joins or re-checks no listing. The app uses it to load every agent the host registered (from the host's own agent list, not a list in the app): this machine once its runtime and agent list are known, a paired server when its agent list arrives and on each reconnect, and this machine again after a Claude or Codex account, agent command or environment change (an answer sent before that change is dropped). Background listing (prewarm) is still the only thing that lists ahead of use.savedOnly; it would also start a listing on a plain read. The host advertisesagent-session.model-catalog.saved-only.v1, and the app sendssavedOnlyonly to a host that does. An older host keeps the earlier rule: this machine's lists are read only for agents with a remembered pick, and a paired server's only for agents it already answered.defaultHoldsInEveryWorkspacewhen its named default can't be replaced by any project's config (Grok reads its model from user, managed and environment config only). An answer naming no workspace then serves a new chat in any workspace.initializeonly, OpenCodemodels --verbose, Pi--list-models, Claudelist_models, Codexmodel/list). Each listing writes anagentModelCatalog.discoverytrace span (agent, outcome, duration, no content). The saved file moves to version 2; native chat has no users yet, so nothing is migrated.Why
This removes per-agent differences instead of patching Grok: every agent goes through the same catalog service, store, runner and picker rules, and the host owns "is the listed default what a chat runs" instead of each client keeping a per-agent flag.
Alternatives considered:
appliedanswer is the only exposed result, so Orca uses it rather than re-implementing it.Differences from the common pattern
get_settingsonce verified against a real CLI in that mode.model/listas a host-run account lister and give the adapter a read-only view..claude/and.agents/folders above the workspace (skills and agent definitions); their agent files are not checked for a model. Follow-up: check them once OpenCode's rule for them is verified against its source.omp models --json, read from its bundled source (no realompwas run), and its ids (provider/model) match what an OMP chat offers, but it lists every model without theenabledModelsfilter a chat applies, so it could offer models a chat refuses. OMP's chats' own listings fill the saved list, and the saved-only load above shows it from the next chat on. Follow-up: an OMP listing that applies the same filter.Mixed versions: two optional fields on the catalog answer (
listingNamesConfiguredModel,defaultHoldsInEveryWorkspace) and one optional catalog parameter (savedOnly), sent only to hosts advertisingagent-session.model-catalog.saved-only.v1; no new methods or stream messages. An older host omits the fields and is never sent the parameter; an older client ignores the fields and never sends it. The saved file never crosses the wire.Linked Issue
Part of the agent startup lifecycle unification (model catalog PR).
Visual Proof
Hidden, isolated Orca windows with stand-in agents (no real agent CLI ran). Each shot is a NEW chat (new stand-in process), taken before that chat's agent session answered, so what the picker shows came from the saved model list or the built-in list, not from the session.
Round 2, head
c77d0bd3cbe(Windows):A brand-new Codex account, before its model listing answers: the picker is usable at once (it used to be a disabled "Model" placeholder for about 10 s) and opens with the built-in list. Since round 7 this usable pill appears once the host has answered that it has nothing saved for the account (a quick local read), and until then it is the same "Model" pill but not yet clickable, so the label never changes. This keeps the built-in list from ever labelling the pill.
The same chat once the listing answered: it updates in place to the account's model and effort.
Claude: a first chat with nothing saved shows an unnamed, usable "Model" pill. After that chat started with no pick and reported Haiku / Low as its configured default, a NEW Claude chat names Haiku and Low on its first frame (workspace has no
.claudesettings).Grok after one listing (earlier round; since round 9 Grok names the model a no-pick chat ran, not the one the listing marks):
Round 1, head
3997b59d3bc(macOS); later rounds did not change these paths' rendering:Grok on a fresh profile, before and after its first listing (the chat is still starting in both):
OpenCode and Pi: a model picker with the listed models before the session starts (neither names a default).
Not verified: real agent CLIs (the stand-ins emit the shapes this PR's parsers and tests read), the project-settings exclusion for Claude's saved default, phone, SSH and WSL.
Testing
Key tests:
opus[1m]shows "Opus (1M context)" and its effort on the first render, and every later render matches; Codex the same; Grok names its listed default on the first frame of the next chat in the same workspace; a cold account (Claude, Codex, Grok) shows only the placeholder until the host answers, then the host's label, never a built-in label or raw id; a saved pick the host list lacks never renders; a paired server's list is used for its next chat but not for this machine's. 9 of these 10 fail without the fix. Phone: placeholder until the host answers, host list kept through a new fence.savedOnlyand nothing else; an agent with nothing saved stays unknown; an older host (no capability) gets no saved-only read and only the remembered-pick read; a paired server is loaded saved-only only when it advertises it; an answer from before an account change is dropped. On the host, a saved-only read answers an aged entry, an empty account and a held reason past its TTL without starting a listing or probe, and namesdefaultHoldsInEveryWorkspacefor Grok only. All six new app-side cases fail on the sources before this round.initializesays 4.7 names no default, and after a chat with no pick runs 4.6 the next new chat (any workspace) names Grok 4.6 · High, never 4.7; OpenCode and OMP no-pick chats teach their model for workspaces without model config, a chat in a workspace whose config picks a model or a chat that picked its model teaches nothing, and a new chat in such a workspace names none; a reopened session, a restored model pick and an unlisted model are handled; OpenCode and OMP config keys, JSONC, agent front matter and folders above the repository; in the app, the second new Grok or OpenCode chat's first frame names the model the first one ran, and a Grok chat in another worktree does too. All of these fail on the sources before this round.~/.codexand Orca's shared home) and a managed account, before and after a launch. Breaking the read resolver fails it.model,agent, per-model effort,maxEffortLevel, model env variables, effort env variable and each provider switch (Bedrock, Anthropic on AWS, Anthropic on Google Cloud) do; broken JSON or TOML counts; Codex MCP-only config doesn't count, a profile effort orprofiledoes.Verified locally on macOS (round 9, after merging
mainonce):pnpm tc:node,pnpm tc:web, mobiletscand test-typecheck ratchet pass on the merged tree; 26 related test files run by name (274 tests) pass;orca-ci-checks22/23, the one failure the samemobile/src/transportandmobile/src/terminal/documentimport cycles as earlier rounds. Not verified in round 9: the running app, real Grok/OpenCode/OMP CLIs (why Grok's list and chats disagree on this machine was not reproduced; it is read from Grok's bundled documentation), paired/SSH servers beyond tests.Verified locally on macOS (round 8, head
3a38bf1d135):pnpm tc:node,pnpm tc:web, mobilepnpm typecheckand test-typecheck ratchet pass; 20 related test files run by name (221 tests: host catalog service, saved-only read, store, prewarm, discovery, unavailable notice, project override, main first-frame, catalog RPC, param schema, ACP probe, app snapshot sync, desktop first-frame and preloaded-agent picker, catalog upgrade, shared first-frame picker) pass;orca-ci-checks22/23, the one failure the samemobile/src/transportandmobile/src/terminal/documentimport cycles as earlier rounds. CI did not run on this head: the branch now conflicts withmain(three files changed onmainsince round 7, none by this round), and it is not synced while in review. Not verified in round 8: the running app (no screenshots), real Grok/OpenCode/OMP CLIs, paired/SSH servers beyond tests.Verified locally on macOS (round 7, head after this round's two commits):
pnpm tc:node,pnpm tc:web, mobilepnpm typecheckand test-typecheck ratchet pass; the touched and related test files run by name (desktop picker, catalog, composer and launch tests, shared option tests, main first-frame tests, phone option tests) pass;orca-ci-checksas below. Not verified in round 7: the running app (no new screenshots), SSH/paired servers beyond tests.Verified locally on macOS (earlier round):
pnpm tc:node,pnpm tc:web, mobilepnpm typecheckand the phone's test-typecheck ratchet pass; the touched and catalog test files, run by name with the repo's test runner (16 files, 176 tests), and #25666's catalog and notice test files (7 files, 70 tests) pass;orca-ci-checks: every check passes exceptaudit:code-quality:native, the same import-cycle warnings insidemobile/src/transportandmobile/src/terminal/documentas earlier rounds (no file of this PR is in those cycles). Earlier rounds also ran the real-Claude-CLI tests (12 of 12); no Claude structured-session file changed since. CI onc77d0bd3cbe: all 25 required checks pass (static analysis and typecheck, all 10 test shards, relay integration, runtime on macOS/Ubuntu/Windows, cross-version wire compatibility, packaging), 15 skipped. Not verified: the running app (no new screenshots), real Codex/Grok/OpenCode/Pi/OMP CLIs, SSH or WSL hosts, and Windows/Linux beyond CI.Agent skill upstream boundary
Notes
Ensure no issues in: Security, Cross-platoform support (Linux, Windows, Mac), Remote SSH, Mobile, general backwards compatibility, performance
Checklist
N/Awith reasonpnpm lint,pnpm typecheck,pnpm test, andpnpm buildpass (or CI will cover; local preferred)