Skip to content

Show model and effort choices for every agent before its session starts - #26407

Merged
brennanb2025 merged 39 commits into
mainfrom
brennanb2025/agent-model-catalog-all-agents
Oct 9, 2026
Merged

brennanb2025 merged 39 commits into
mainfrom
brennanb2025/agent-model-catalog-all-agents

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 54 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​4740 $\color{#cf222e}{\Huge{\mathbf{−}}}$​632 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​4108
Prod 75 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​3122 $\color{#cf222e}{\Huge{\mathbf{−}}}$​655 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​2467

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 contain opus[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

  • The model and effort pills don't jump. A new chat's pills come from the saved model list on the machine running the agent, which the app has already loaded, so the first frame is the final one. If that list hasn't reached the app yet, the pill shows the plain "Model" placeholder until the host answers (a quick read of its saved list, not a new listing), then shows the model and effort once. Orca's built-in list never puts a label on the pill, and a saved pick that no list contains shows the placeholder rather than its raw id. On main (and earlier rounds of this PR) the first frame came from the built-in list and could change when the saved list arrived.
  • The model picker never waits on a listing. Before, an account with no saved list showed a greyed-out pill until Orca's listing answered. Now, once the host says it has nothing saved for the account, the "Model" pill opens to Claude's and Codex's built-in lists (Grok, OpenCode and Pi keep the placeholder) without naming a model. When the listing lands, the pill names the model in place. A model you picked meanwhile stays picked.
  • Every agent gets a saved list. Grok, OpenCode and OMP list their models and efforts straight away. Once one chat has started with no model pick, the next new chat names the model and effort that chat ran on its first frame: Grok in any workspace (Grok's model never comes from a project's config); OpenCode and OMP in workspaces whose own config picks no model (checked as for Claude, below). Before any such chat, or in a workspace whose config picks a model, their pill reads "Model" until the chat reports. Pi still names no model until its chat reports one.
  • Every agent's saved list is loaded before a chat opens, whether or not you ever picked a model for it: at start for this machine, on connecting to a paired server, after reconnecting, and after an account or agent-setting change. Loading reads only what the machine already saved and never starts an agent. An agent with nothing saved shows the quiet placeholder until its first chat's answer.
  • Claude names its model and effort on a new chat's first frame once Orca has seen what Claude runs by default. On main a 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.json just for permissions or hooks, or has the settings.local.json Claude writes when you choose "always allow", still gets the named default.
  • Orca lists ahead of time, for the agents you use. At start (on a remote Orca server, on that machine), and after you switch, add or remove a Claude or Codex account, Orca lists, for every agent you have used here (one with a saved list, or one with any chat), whatever is missing or out of date. After you change an agent's command or environment setting, Orca re-lists that agent even if its saved list is fresh, and re-checks its "wasn't found" or "not signed in" notice. An agent you have never used isn't started in the background; its first chat shows the quiet placeholder until its first listing answers. Listings stop when Orca quits.
  • The "not signed in" / "not installed" notice can be known before a chat opens. These background listings also set and clear that notice's reason, as a picker's own check does, so a new chat can show it (or not) from the start. A reason older than 30 seconds is checked again by the next background listing, even when the saved model list beside it is still fresh. A running Codex chat's own picker never re-lists models to re-check the reason: only the background check can answer it.
  • A changed default model shows up in new chats; before, a chat in use kept the saved list "fresh" forever.
  • Phone: every agent's chat gets the same picker as the desktop. OpenCode and Pi picks are now remembered for the next chat. The phone follows the same "no guessed first frame" rule (placeholder until the host answers) and keeps the host's list when the chat reattaches instead of falling back to the placeholder.

After merging the "not signed in" / "not installed" notice (#25666). This PR merged main after #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:

  • The background listings above are that same session-free check, so they set and clear the reason too.
  • The Claude check keeps this PR's list_models listing (main used Claude's text-generation model discovery) and reports a Claude program that cannot be started as "not installed".
  • The Codex check syncs Orca's shared Codex home before listing, as on main, but now that also happens when the background listing runs (at start and on an account switch), not only when a picker reads.
  • A Codex check stopped because Orca is quitting leaves the reason as it was.

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 ~/.codex and two Orca-managed ones), not one account whose key drifted; each switch started cold.

Mechanism

  • One catalog contract on every agent registration: how its models are listed without a chat, and whether that listing names the configured model (Codex yes; Claude, Grok, OpenCode, OMP, Pi no).
  • The host says whether the listed default is what a new chat runs. The catalog answer gains an optional listingNamesConfiguredModel field, 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.
  • Project config is parsed, not just found. A Claude project settings file counts only if it sets model, agent (which applies that agent's model), effortLevel, maxEffortLevel (an effort cap), modelSettings, availableModels, enforceAvailableModels, modelOverrides, or an env model, effort or provider variable (ANTHROPIC_MODEL, ANTHROPIC_DEFAULT_*_MODEL, CLAUDE_CODE_EFFORT_LEVEL, and the provider switches CLAUDE_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 project config.toml counts if it sets model, model_reasoning_effort, model_provider, profile, or a profile with one of those. Unreadable or invalid files still count.
  • Grok, OpenCode and OMP learn their default from a chat with no pick. A new ACP chat (not one reopened from a saved conversation) that restored no model pick and was sent none reports, with its listing, the model it runs and, if it was sent no effort, its effort. The host saves them as the account's configured default through the same step as Claude's, after the workspace check passes for that chat's folder; a model the account doesn't list forgets the saved one. A catalog read waits for a save still being checked, so the reporting chat's next read already names it, and the app reads the host's list again once a no-pick chat reports, so the next chat's first frame has it. Grok's session-free currentModelId is no longer marked as the default.
  • OpenCode and OMP project config is parsed. OpenCode (from its 2.x config discovery, which walks every folder above the workspace): opencode.json, opencode.jsonc, .opencode/opencode.json(c) count if they set model, default_agent, provider, providers, enabled_providers, disabled_providers, or an agent/agents/mode entry with model, variant or disable; agent files under .opencode/agent(s)/ and .opencode/mode(s)/ count if their front matter sets model or variant. 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 set modelRoles, enabledModels, disabledProviders, modelProviderOrder, modelTags, providers or defaultThinkingLevel (or Pi's defaultModel/defaultProvider). Key names come from each CLI's own bundled config schema, read without running it. Unreadable or invalid files count.
  • Two listings per saved entry. The account-level listing alone names the default and decides when to refresh; the chat listing says which models exist. Claude, Grok, OpenCode and Pi hand chat listings to the host's one save step.
  • Claude's configured default: model and effort, saved together. A Claude chat launched with no model pick reports, with its listing, the listed row matching the model Claude says it applies (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.
  • Prewarm, for agents in use, starts when the host is installed: for each, the account a new chat would pin, skipped if fresh, already listing, or failed in the last 30 seconds, unless a held notice reason is older than 30 seconds; at most two at once; joined with reads. A change to an agent's command or environment setting marks that agent's saved lists as due (until the next account-level listing lands) and ages its held reasons, so the prewarm that change triggers re-lists them. Host teardown (quit included) stops it: no queued agent starts, and running listings get an abort signal that the listing runners and Codex's app-server runner honor by stopping their child.
  • The app keeps each host's saved lists (per machine and agent) so a chat's first frame reads them directly. Every new chat's own read updates them. The host stays the only source: whether a new chat runs the listed default depends on its workspace's config, so another workspace's answer is used only when the chat names its model, the list names no default, or the host says no workspace can replace the default.
  • A saved-only catalog read. agentSession.modelCatalog takes an optional savedOnly: 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.
  • Capability-gated. The catalog read's parameters are strict, so an older host refuses savedOnly; it would also start a listing on a plain read. The host advertises agent-session.model-catalog.saved-only.v1, and the app sends savedOnly only 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.
  • "Holds in every workspace." A catalog answer gains an optional defaultHoldsInEveryWorkspace when 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.
  • Probes use each agent's own chat binary, account, environment and command overrides (Grok initialize only, OpenCode models --verbose, Pi --list-models, Claude list_models, Codex model/list). Each listing writes an agentModelCatalog.discovery trace 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:

  • Show each agent's terminal-mode model list. Rejected: it names flags and models structured chat doesn't offer.
  • One combined listing that chat listings just don't age. Rejected: a background listing finishing after a chat listing would have to overwrite or be dropped, and a cold account's first chat listing has no default to take.
  • Read Claude's settings files ourselves to find its default. Rejected: Claude resolves env over several settings layers over its own default; its applied answer is the only exposed result, so Orca uses it rather than re-implementing it.
  • Prewarm every registered agent. Rejected: it would start CLIs the user never uses on every launch.

Differences from the common pattern

  • Intended: the saved list lives on the machine that runs the agent, not a central server, which keeps execution-host ownership for SSH hosts.
  • Intended: with no list, a quiet "Model" placeholder; OpenCode, Pi and (until it has learned one) Claude name no current model, because their listings don't say which is configured.
  • Intended: background listings run at host start and on account or agent-setting changes, not on client connect, and only for agents in use. Safe because the picker never waits.
  • Intended: "installed" is decided by the listing itself (a missing binary fails at spawn, then the 30-second back-off), so there is no second way to resolve the binary that could disagree with what chats launch.
  • Intended: Codex keeps its own app-server runner, under the same supervision, 15-second budget and now abort signal.
  • Intended: Claude's default is learned from a Claude chat started with nothing picked, not from the session-free listing.
  • Temporary: Claude's saved default is refreshed only by a chat started with no model pick. If every later chat starts from a remembered pick, a default changed in Claude's settings stays stale until the next no-pick chat corrects or forgets it. Follow-up: have the session-free Claude listing ask get_settings once verified against a real CLI in that mode.
  • Temporary: Codex still saves its listing from inside its adapter rather than through the host's one save step. Follow-up: run Codex's model/list as a host-run account lister and give the adapter a read-only view.
  • Intended: Grok, OpenCode and OMP learn their default from a chat started with no pick, as Claude does, because their session-free lists don't say (OpenCode, OMP) or can disagree with what a chat runs (Grok). Before the first such chat, that chat's pill goes from "Model" to the model once.
  • Temporary: the same staleness as Claude's: a learned Grok, OpenCode or OMP default is refreshed only by a later chat started with no pick. Follow-up: shared with Claude's item above.
  • Temporary: a new OpenCode, OMP or Claude chat with no remembered pick, in a worktree the app has not read this run, shows "Model" until the host answers, because only the host can check that worktree's config. Follow-up: load the workspace check for the open worktrees with the saved-only read.
  • Temporary: OpenCode 2.x also reads .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.
  • Temporary: Pi names no model until a chat reports the one it runs. Follow-up: Pi's project-config check, then the same learning.
  • Temporary: the saved list is keyed by agent, account and WSL distro, not by config scope, so models only an OpenCode project adds appear once that chat reports them. Follow-up: key OpenCode's list by its resolved project config.
  • Temporary: WSL-pinned chats have no host-side background listing. Removed by the planned WSL execution-host work.
  • Intended: the pills render from the host's saved list held in the app, or the placeholder until the host answers, never from a guessed list later corrected, matching the common pattern. The app keeps the host's answers rather than having the host push changes; it reads again on reconnect and drops this machine's lists on an account or agent-setting change. Safe because each new chat still reads the host and the list only changes on those events.
  • Temporary: the app reads a host's saved lists at start, connect, reconnect and on this machine's setting changes, rather than the host pushing changes; an account switch made on a paired server itself is seen at the next chat's read. Follow-up: have the host push catalog changes to connected clients (needs a negotiated capability).
  • Temporary: a paired server older than this PR answers a plain read by listing, so its lists are loaded only for agents it already answered this app (from its last new chat), and the first chat for another agent there shows the placeholder briefly. Removed once those servers update.
  • Temporary: OMP registers no session-free listing. OMP 17.0.5 (the oldest release Orca runs) has omp models --json, read from its bundled source (no real omp was run), and its ids (provider/model) match what an OMP chat offers, but it lists every model without the enabledModels filter 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 advertising agent-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.

Cold Codex chat: usable Model pill
Cold Codex chat: picker open with the built-in list

The same chat once the listing answered: it updates in place to the account's model and effort.

Cold Codex chat after the listing: GPT-6.1 Sol, Medium

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 .claude settings).

First Claude chat after it started: Haiku, Low
New Claude chat first frame: Haiku, Low
New Claude chat model menu: Haiku checked

Grok after one listing (earlier round; since round 9 Grok names the model a no-pick chat ran, not the one the listing marks):

Warm Grok chat first frame: Grok Build, High

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):

Cold Grok chat before the listing
Cold Grok chat after the listing: Grok Build, High

OpenCode and Pi: a model picker with the listed models before the session starts (neither names a default).

OpenCode model menu before the session
Pi model menu before the session

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

  • I manually tested these changes locally
  • Automated tests added/updated, or explained why not below

Key tests:

  • No jump (round 7): a new Claude chat with a saved list and the saved pick 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.
  • Saved-only load (round 8): Grok, OpenCode and OMP with a saved list and no remembered pick render their final pills on a new chat's first frame (Grok named, OpenCode and OMP a usable unnamed pill), and every later render matches; the load reads every registered agent with savedOnly and 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 names defaultHoldsInEveryWorkspace for Grok only. All six new app-side cases fail on the sources before this round.
  • Learned ACP default (round 9): through the real Grok probe, adapter and host, a listing whose initialize says 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.
  • A cold Codex first frame has a usable pill and the landed listing updates it in place (desktop and phone).
  • Prewarm lists only agents with a saved list or a chat; joins, freshness, account switch, back-off and the two-at-once limit hold. After stop no queued agent starts, running listings are aborted, and nothing new lists; host install starts prewarm and teardown stops it; the runner kills a running child on abort.
  • Probes built from the registration list spawn the command overrides for Claude, Codex and Grok.
  • Codex account key, through the real read-only resolver and real launch preparation wired as in the runtime service: one key for the system account (real ~/.codex and Orca's shared home) and a managed account, before and after a launch. Breaking the read resolver fails it.
  • Project config: Claude permission, hooks and env-only settings don't count; 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 or profile does.
  • Notice reason with background listing: prewarm sets and clears it like a picker's check and re-checks an aged one beside a fresh list; a running Codex chat's picker does not re-list for an aged reason; a command change re-lists a fresh list and clears a stale "not installed"; a check stopped at quit leaves the reason. Native chat: say when Codex is signed out or the agent isn't installed, without disabling Send #25666's own tests (notice reason, Claude and Codex checks, Codex home sync, chat-start re-check, phone and desktop notice) pass unchanged except two picker assertions, which now check the pill is settable and offers choices.
  • Claude: a no-pick chat saves Sonnet with effort "high" and a new chat's first frame names both; a chat in a project whose settings pick a model saves neither model nor effort; a later config naming an unlisted model, or none, forgets the default and a background listing doesn't restore it; a chat's own effort pick doesn't erase the saved effort; a resting chat on the same model shows the saved effort.

Verified locally on macOS (round 9, after merging main once): pnpm tc:node, pnpm tc:web, mobile tsc and test-typecheck ratchet pass on the merged tree; 26 related test files run by name (274 tests) pass; orca-ci-checks 22/23, the one failure the same mobile/src/transport and mobile/src/terminal/document import 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, mobile pnpm typecheck and 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-checks 22/23, the one failure the same mobile/src/transport and mobile/src/terminal/document import cycles as earlier rounds. CI did not run on this head: the branch now conflicts with main (three files changed on main since 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, mobile pnpm typecheck and 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-checks as 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, mobile pnpm typecheck and 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 except audit:code-quality:native, the same import-cycle warnings inside mobile/src/transport and mobile/src/terminal/document as 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 on c77d0bd3cbe: 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

  • Not applicable, or this change copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

Ensure no issues in: Security, Cross-platoform support (Linux, Windows, Mac), Remote SSH, Mobile, general backwards compatibility, performance

  • Probes and saves run on the execution host through the same command and account resolvers as chats.
  • On Windows the runner has no supervisor; teardown uses the existing process-tree termination (not verified on Windows).

Checklist

  • This PR is small and focused
  • I explained what changed and why (ELI5, the user-facing before/after, the mechanism, and why over the alternatives)
  • Before/after screenshots or videos attached for UI changes, or N/A with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (or CI will cover; local preferred)

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
…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
…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
@brennanb2025

Copy link
Copy Markdown
Contributor Author

Review summary (coordinator)

State: head 068daa35a89 (includes current main with #26416, #25666, #26494, #26643 and #26648). CI on this head: see the final line below. Still a draft until the owner decides to merge.

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)

Round Found Fixed
1 Warm list didn't name Grok/OpenCode/Pi's model + effort; live sessions didn't update the saved list for ACP/Pi; phone not seeded; OpenCode/Pi picks rejected Named defaults from the listing, live write-through for every agent, phone seeded, picks accepted
2 Live saves starved the background refresh (default went stale); phone-created chats missed the new-launch path; three different write paths One write path; discovery and live-session facts kept apart
3 Ready for visual QA —
QA 1 (macOS) Grok cold/warm, OpenCode, Pi, Codex warm proven; Claude first frame unnamed; cold Codex picker disabled ~10 s → round 4
4 (the "Codex picker is slow" report) Picker locked while the first listing ran; nothing listed ahead of time; three Codex keys in the saved list (verified: three different accounts, not drift) Picker never waits; background refresh at host start, connect and account switch; per-listing timing span; Claude learns its configured default from a chat launched with no pick
5 Claude's project check treated any settings file as an override; a project's effort leaked into the account default; a stale default never cleared; the Codex key test was stubbed; background refresh ran for unused agents and outlived quit Settings parsed (only model/effort keys count); model + effort saved together behind the project check; stale default cleared; real Codex key test; refresh only for agents in use, stopped on teardown and quit
Main merge (#25666) Sign-in / CLI-missing reason moved onto the saved list's failure record Kept both: only a check sets or clears that reason; background refresh does it exactly like a read
6 The merge re-added a line that made a running Codex chat re-list models on every picker read while a sign-in reason was held; two Claude settings keys missed; a command/env change didn't refresh; two weakened tests Removed (with a test that fails if it returns); keys added from the SDK's own lists; command/env change marks that agent's list due; tests restored
QA 2 (Windows) Cold Codex picker usable at once with the built-in list, then updates in place; a new Claude chat names its saved Haiku/Low before its session answers; Grok still names Grok Build/High —
Final main syncs #26494 made pluginDir required in ACP launch args (the probe passes none: no visuals skill for a listing); #26416's startup flow kept with this PR's catalog calls at the same moments (6 conflicts) —

Verified

Not verified

  • Real agent CLIs (stand-ins emit the shapes this PR's parsers and tests read), the project-settings exclusion for Claude's saved default in a live run, phone, SSH, WSL, Linux.

Deviations (also in the body)

  • OMP lists no models without a session yet — temporary (its launch spec has no session-free listing).

CI on 068daa35a89: 20 passed, 15 skipped by path, 0 failed. That includes all 5 test shards, cross-version wire compatibility, packaging (incl. Windows) and static analysis.

@brennanb2025
brennanb2025 marked this pull request as ready for review October 8, 2026 20:21
@coderabbitai

coderabbitai Bot commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 69a8574e-f292-407c-ad5a-ec2803282ad8

📥 Commits

Reviewing files that changed from the base of the PR and between f78a9f5 and e0f8d6a.


📒 Files selected for processing (17)
  • src/main/acp/acp-dialects/acp-dialect.ts
  • src/main/acp/acp-dialects/grok-dialect.ts
  • src/main/acp/acp-dialects/grok-model-catalog.ts
  • src/main/acp/acp-launch-specs.ts
  • src/main/acp/acp-model-catalog-probe.test.ts
  • src/main/acp/acp-model-catalog-probe.ts
  • src/main/acp/acp-session-runtime.ts
  • src/main/acp/acp-structured-acquire.ts
  • src/main/acp/acp-structured-host-configured-default.test.ts
  • src/main/acp/acp-structured-host-live-listing.test.ts
  • src/main/acp/acp-structured-host.test-support.ts
  • src/main/acp/acp-structured-options.test.ts
  • src/main/acp/acp-structured-session-adapter.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-options-read.ts
  • src/main/runtime/structured-agent-runtime-registrations.ts
  • src/renderer/src/components/native-chat/NativeChatSessionOptionPickers.test.tsx
  • src/shared/structured-agent-session-options.test.ts

💤 Files with no reviewable changes (1)
  • src/renderer/src/components/native-chat/NativeChatSessionOptionPickers.test.tsx

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 5 remain after this review.



📝 Walkthrough

Walkthrough

The 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 e0f8d

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)

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 48.70% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 193 functions across 89 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check Passed The title clearly summarizes the main change: exposing model and effort choices for all agents before their sessions start.
Description check Passed The description is comprehensive and covers the required change summary, rationale, testing, visual proof, compatibility considerations, limitations, and checklist. It documents unverified areas and r…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.


  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR


  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 218b635f-c34c-4227-b4ce-d404184bfd91
📥 Commits

Reviewing files that changed from the base of the PR and between 5de5391 and 068daa3.

📒 Files selected for processing (110)
  • config/scripts/vitest-sqlite-runtime-files.mjs
  • mobile/src/session/mobile-created-structured-sessions.ts
  • mobile/src/session/mobile-existing-agent-launch.test.ts
  • mobile/src/session/mobile-existing-agent-launch.ts
  • mobile/src/session/mobile-structured-agent-session-launch.test.ts
  • mobile/src/session/mobile-structured-agent-session-launch.ts
  • mobile/src/session/use-mobile-host-model-catalog-upgrade.ts
  • mobile/src/session/use-mobile-structured-agent-options.test.ts
  • mobile/src/session/use-mobile-structured-agent-options.ts
  • mobile/src/session/use-mobile-structured-agent-session.test.tsx
  • src/main/acp/acp-dialects/acp-dialect.ts
  • src/main/acp/acp-dialects/grok-dialect.ts
  • src/main/acp/acp-dialects/grok-model-catalog.ts
  • src/main/acp/acp-launch-specs.ts
  • src/main/acp/acp-model-catalog-probe.test.ts
  • src/main/acp/acp-model-catalog-probe.ts
  • src/main/acp/acp-session-runtime.ts
  • src/main/acp/acp-structured-acquire.ts
  • src/main/acp/acp-structured-host-live-listing.test.ts
  • src/main/acp/acp-structured-host.test-support.ts
  • src/main/acp/acp-structured-launch-resolution.ts
  • src/main/acp/acp-structured-options.test.ts
  • src/main/acp/acp-structured-options.ts
  • src/main/acp/acp-structured-session-adapter.ts
  • src/main/claude/claude-model-catalog-probe.test.ts
  • src/main/claude/claude-model-catalog-probe.ts
  • src/main/claude/claude-structured-acquire-catalog.ts
  • src/main/claude/claude-structured-catalog-listing.ts
  • src/main/claude/claude-structured-configured-default.test.ts
  • src/main/claude/claude-structured-effort-default-at-rest.test.ts
  • src/main/claude/claude-structured-retired-model.test.ts
  • src/main/claude/claude-structured-retired-model.ts
  • src/main/claude/claude-structured-session-acquisition.ts
  • src/main/claude/claude-structured-session-adapter-turns.test.ts
  • src/main/claude/claude-structured-session-options.ts
  • src/main/claude/claude-structured-session-startup.test.ts
  • src/main/claude/claude-structured-session-startup.ts
  • src/main/claude/claude-structured-session-state.ts
  • src/main/codex-accounts/runtime-home-catalog-account-key.test.ts
  • src/main/codex/codex-app-server-session.ts
  • src/main/codex/codex-model-catalog-probe.test.ts
  • src/main/codex/codex-model-catalog-probe.ts
  • src/main/codex/codex-short-lived-app-server-spawn.test.ts
  • src/main/codex/codex-structured-background-catalog.ts
  • src/main/codex/codex-structured-fast-mode.test.ts
  • src/main/codex/codex-structured-session-options-catalog.test.ts
  • src/main/native-chat/agent-model-catalog/agent-model-catalog-account-expiry.ts
  • src/main/native-chat/agent-model-catalog/agent-model-catalog-discovery-freshness.test.ts
  • src/main/native-chat/agent-model-catalog/agent-model-catalog-discovery.ts
  • src/main/native-chat/agent-model-catalog/agent-model-catalog-entry.ts
  • src/main/native-chat/agent-model-catalog/agent-model-catalog-failures.ts
  • src/main/native-chat/agent-model-catalog/agent-model-catalog-listing-waiters.ts
  • src/main/native-chat/agent-model-catalog/agent-model-catalog-persistence.ts
  • src/main/native-chat/agent-model-catalog/agent-model-catalog-prewarm.test.ts
  • src/main/native-chat/agent-model-catalog/agent-model-catalog-probe-runner.test.ts
  • src/main/native-chat/agent-model-catalog/agent-model-catalog-probe-runner.ts
  • src/main/native-chat/agent-model-catalog/agent-model-catalog-service.test.ts
  • src/main/native-chat/agent-model-catalog/agent-model-catalog-service.ts
  • src/main/native-chat/agent-model-catalog/agent-model-catalog-store.test.ts
  • src/main/native-chat/agent-model-catalog/agent-model-catalog-store.ts
  • src/main/native-chat/agent-model-catalog/agent-model-catalog-unavailable.test.ts
  • src/main/native-chat/agent-model-catalog/agent-project-model-config-keys.ts
  • src/main/native-chat/agent-model-catalog/agent-project-model-override.test.ts
  • src/main/native-chat/agent-model-catalog/agent-project-model-override.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-adapter.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-options-read.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-provider-started-catalog.test.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-provider-started.test.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-provider-started.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-startup-attempt-host.test.ts
  • src/main/opencode/opencode-model-catalog-listing.test.ts
  • src/main/opencode/opencode-model-catalog-listing.ts
  • src/main/pi/rpc-model-catalog-probe.test.ts
  • src/main/pi/rpc-model-catalog-probe.ts
  • src/main/pi/rpc-options.ts
  • src/main/pi/rpc-runtime-registration.ts
  • src/main/pi/rpc-session-adapter.ts
  • src/main/provider-process/managed-provider-process.ts
  • src/main/runtime/rpc/methods/client-native-chat-settings.test.ts
  • src/main/runtime/structured-agent-model-catalog-discovery.test.ts
  • src/main/runtime/structured-agent-model-catalog-discovery.ts
  • src/main/runtime/structured-agent-model-catalog-first-frame.test.ts
  • src/main/runtime/structured-agent-model-catalog-wiring.ts
  • src/main/runtime/structured-agent-runtime-registrations.ts
  • src/main/runtime/structured-agent-session-runtime-model-catalog-lifetime.test.ts
  • src/main/runtime/structured-agent-session-runtime.ts
  • src/main/runtime/structured-claude-runtime-adapter.ts
  • src/main/startup/codex-launch-preparation.ts
  • src/main/startup/main-process-account-services.ts
  • src/main/startup/main-process-runtime-service.ts
  • src/renderer/src/components/native-chat/NativeChatSessionOptionPickers.menu-request.test.tsx
  • src/renderer/src/components/native-chat/NativeChatSessionOptionPickers.test.tsx
  • src/renderer/src/components/native-chat/NativeChatSessionOptionPickers.tsx
  • src/renderer/src/components/native-chat/host-model-listing-waits.ts
  • src/renderer/src/components/native-chat/structured-agent-registered-agent-surface.test.ts
  • src/renderer/src/components/native-chat/structured-agent-session-seed-catalog.ts
  • src/renderer/src/components/native-chat/use-host-model-catalog-upgrade.test.tsx
  • src/renderer/src/components/native-chat/use-host-model-catalog-upgrade.ts
  • src/renderer/src/components/native-chat/use-host-model-catalog-upgrade.unavailable.test.tsx
  • src/renderer/src/components/native-chat/use-structured-agent-session-option-state.ts
  • src/renderer/src/components/native-chat/use-structured-agent-session-options.test.tsx
  • src/renderer/src/components/native-chat/use-structured-agent-session-options.ts
  • src/shared/agent-session-option-catalog-types.ts
  • src/shared/agent-session-wire.ts
  • src/shared/native-chat-session-options.ts
  • src/shared/rpc-contract/client-settings-params.ts
  • src/shared/structured-agent-session-first-frame-picker.test.ts
  • src/shared/structured-agent-session-model-catalog-application.test.ts
  • src/shared/structured-agent-session-options.ts
  • src/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.

Comment on lines +105 to +108
// 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()
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 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' src

Repository: 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.

@brennanb2025
brennanb2025 marked this pull request as draft October 8, 2026 20:42
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
@brennanb2025

Copy link
Copy Markdown
Contributor Author

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 (opus_1m) with no effort, then the real "Opus (1M context) · High"; later, Grok, OpenCode and OMP going from "Model" to their real model. The common pattern draws the composer's model and effort once, from data the client already holds, and never shows placeholder data as real.

Round Found Fixed
7 The first frame came from the built-in starter list; a saved pick it lacked showed as a raw id until the host's list replaced it The app keeps each host's saved lists and draws the first frame from them; before the host answers it shows a quiet placeholder, never a guess or a raw id
8 Only agents with a saved pick were preloaded, because the existing read starts a listing when nothing is saved A saved-only read that never starts an agent, advertised as a capability (older hosts keep the round-7 rule); every registered agent preloaded
9 Grok's pre-session list named 4.7 as default while the chat ran 4.6 (Grok resolves its own default, including server-pushed defaults; Orca sends no model); OpenCode and OMP lists name no default Grok's pre-session list no longer claims a default; Grok, OpenCode and OMP learn the configured default from a chat started with no pick (as Claude does), behind a parse of their project config; final main sync

Manual check by the owner on a027f97b77c: Claude, Codex, Grok, OpenCode and OMP render correctly; Pi not tried.

CI on a027f97b77c: 20 passed, 15 skipped by path, 0 failed.

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.

@brennanb2025
brennanb2025 marked this pull request as ready for review October 9, 2026 06:58

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/main/runtime/structured-agent-model-catalog-discovery.test.ts (1)

93-132: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the temp directory in a finally block.

If an assertion fails, line 131 does not run. Each failed run then leaves an orca-catalog-overrides-* directory in tmp. Wrap the body in try/finally.


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 24fe5263-225b-44b3-b321-9e35fe6158d2
📥 Commits

Reviewing files that changed from the base of the PR and between 068daa3 and a027f97.

📒 Files selected for processing (51)
  • config/scripts/vitest-sqlite-runtime-files.mjs
  • mobile/src/session/mobile-structured-agent-session-launch.test.ts
  • mobile/src/session/use-mobile-host-model-catalog-upgrade.ts
  • mobile/src/session/use-mobile-structured-agent-options.first-frame.test.ts
  • mobile/src/session/use-mobile-structured-agent-options.ts
  • src/main/acp/acp-dialects/grok-dialect.ts
  • src/main/acp/acp-dialects/grok-model-catalog.ts
  • src/main/acp/acp-launch-specs.ts
  • src/main/acp/acp-model-catalog-probe.test.ts
  • src/main/acp/acp-structured-acquire.ts
  • src/main/acp/acp-structured-host-configured-default.test.ts
  • src/main/acp/acp-structured-options.test.ts
  • src/main/acp/acp-structured-options.ts
  • src/main/acp/acp-structured-session-adapter.ts
  • src/main/claude/claude-structured-session-acquisition.ts
  • src/main/native-chat/agent-model-catalog/agent-model-catalog-entry.ts
  • src/main/native-chat/agent-model-catalog/agent-model-catalog-learned-default.test.ts
  • src/main/native-chat/agent-model-catalog/agent-model-catalog-saved-only-read.test.ts
  • src/main/native-chat/agent-model-catalog/agent-model-catalog-service.ts
  • src/main/native-chat/agent-model-catalog/agent-project-model-config-keys.ts
  • src/main/native-chat/agent-model-catalog/agent-project-model-override.test.ts
  • src/main/native-chat/agent-model-catalog/agent-project-model-override.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-queued-mutations.ts
  • src/main/runtime/rpc/methods/structured-agent-session-options-read.test.ts
  • src/main/runtime/structured-agent-model-catalog-discovery.test.ts
  • src/main/runtime/structured-agent-model-catalog-first-frame.test.ts
  • src/main/runtime/structured-agent-model-catalog-wiring.ts
  • src/main/runtime/structured-agent-runtime-registrations.ts
  • src/renderer/src/app-shell/use-app-shell-services.ts
  • src/renderer/src/components/native-chat/NativeChatSessionOptionPickers.tsx
  • src/renderer/src/components/native-chat/use-host-model-catalog-upgrade.test.tsx
  • src/renderer/src/components/native-chat/use-host-model-catalog-upgrade.ts
  • src/renderer/src/components/native-chat/use-host-model-catalog-upgrade.unavailable.test.tsx
  • src/renderer/src/components/native-chat/use-native-chat-provisional-launch.test.tsx
  • src/renderer/src/components/native-chat/use-structured-agent-session-option-state.ts
  • src/renderer/src/components/native-chat/use-structured-agent-session-options.first-frame.test.tsx
  • src/renderer/src/components/native-chat/use-structured-agent-session-options.learned-default.test.tsx
  • src/renderer/src/components/native-chat/use-structured-agent-session-options.preloaded-agents.test.tsx
  • src/renderer/src/components/native-chat/use-structured-agent-session-options.test.tsx
  • src/renderer/src/components/native-chat/use-structured-agent-session-options.ts
  • src/renderer/src/components/native-chat/use-structured-agent-session-provisional.test.tsx
  • src/renderer/src/runtime/host-model-catalog-snapshots-sync.ts
  • src/renderer/src/runtime/host-model-catalog-snapshots.test.ts
  • src/renderer/src/runtime/host-model-catalog-snapshots.ts
  • src/shared/agent-session-wire.ts
  • src/shared/rpc-contract/structured-agent-session-params.ts
  • src/shared/structured-agent-session-model-catalog-application.test.ts
  • src/shared/structured-agent-session-option-view.ts
  • src/shared/structured-agent-session-options.test.ts
  • src/shared/structured-agent-session-options.ts
  • src/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.
@brennanb2025
brennanb2025 merged commit 6ba8641 into main Oct 9, 2026
35 checks passed
brennanb2025 added a commit that referenced this pull request Oct 9, 2026
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.
brennanb2025 added a commit that referenced this pull request Oct 9, 2026
…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.
@brennanb2025
brennanb2025 deleted the brennanb2025/agent-model-catalog-all-agents branch October 12, 2026 07:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant