Skip to content

feat(agent-routing): assign a per-agent model from the /agents menu#1632

Open
beardthelion wants to merge 21 commits into
Gitlawb:mainfrom
beardthelion:feat/agents-model-routing-ui
Open

feat(agent-routing): assign a per-agent model from the /agents menu#1632
beardthelion wants to merge 21 commits into
Gitlawb:mainfrom
beardthelion:feat/agents-model-routing-ui

Conversation

@beardthelion

@beardthelion beardthelion commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

What and why

Per-agent model routing already works through settings (agentModels + agentRouting), but the only way to set it was hand-editing ~/.openclaude.json, and the built-in agents' route keys (verification, Explore, Plan) were undiscoverable. This adds a way to do it from the /agents menu.

Open any agent in /agents, press m, and pick a model. You can choose a model on your current provider (typing a custom id for OpenAI-compatible providers), point at an already-defined cross-provider entry, or clear the route to remove the agent's own assignment (it then falls back to the default route if one is configured, otherwise inherits the main-loop model). It writes to user-global settings and takes effect on the next spawn of that agent type. Built-in agents are routable the same way, since routing is keyed by agent type.

Impact

  • New: a model-route picker in the /agents detail view (press m).
  • Writes agentModels / agentRouting under user settings.
  • Resolution path changed (review note): model-only routes naming a built-in alias (sonnet/opus/haiku/inherit) now resolve through getAgentModel with the active permission mode, so on non-Claude-native providers they inherit the parent model instead of sending a literal alias that 404s. Real model ids still pass through unchanged. The same model-only resolution now also applies to pane/window teammate spawns, which previously honored only cross-provider routes and otherwise inherited the parent.
  • Writes are guarded when user settings are not an enabled setting source (e.g. launched with --setting-sources excluding them): the picker reports an error instead of saving a route to a file the runtime would never load.

Design notes

The settings-shaping logic lives in a pure module (agentRouteSettings.ts) with thin I/O wrappers, unit-tested with plain objects. The selector is a small standalone component. The route resolver (agentRouting.ts) was extended so model-only alias routes go through getAgentModel and receive permissionMode, threaded from AgentTool/runAgent; the cross-provider override path is unchanged. The touch to the (React-Compiler-output) AgentDetail is minimal and does not alter existing memo slots. Creating new cross-provider credentials from the TUI is intentionally out of scope; those stay JSON-managed.

Checks

  • bun test src/services/api/agentRouting.test.ts — 54 pass
  • bun test src/services/api/agentRouteSettings.test.ts src/services/api/agentRouting.test.ts src/utils/settings/ — pass
  • bun test src/tools/AgentTool/ — 46 pass
  • bun run typecheck — clean
  • bun run build && bun run smoke — pass (0.18.0)
  • Full bun test — only pre-existing sandbox-only failures (3 /export, 1 /lsp recommend, 1 usage child-process error) that pass on CI; unrelated to this change.

Provider path

The model-only resolution that this now routes through getAgentModel is provider-aware: built-in aliases inherit the parent on non-Claude-native providers, and OpenAI-compatible custom model ids pass through as-is. Cross-provider routes (full agentModels entries with base_url+api_key) are unchanged and still apply across providers.

UI

This adds terminal UI. From /agents, opening an agent shows a Route: line and (press "m" to change); pressing m opens a select with the model options plus a custom-id input and a clear option. Happy to add an asciinema/screenshot if useful.

Summary by CodeRabbit

  • New Features

    • Added a keyboard-driven per-agent routing view in agent details (toggle with m) showing current route context and enabling save/clear/custom selection with shadowing-aware read-only behavior.
    • Improved model-only routing to resolve aliases with provider-aware logic, and ensured routing resolution honors permissionMode during agent runs and teammate spawning.
  • Bug Fixes

    • Prevented Esc from canceling/backing out while the route picker is open, both in the embedded view and dialog flow.
  • Tests

    • Added/expanded Bun/React and API tests covering routing selection, shadowing rules, save/clear error handling, Esc behavior regressions, and model-only teammate routing.

@coderabbitai

coderabbitai Bot commented Jun 14, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: d970a6c9-6128-4a3a-a6cf-11337ec76842

📥 Commits

Reviewing files that changed from the base of the PR and between ceb3e78 and e2ea45b.

📒 Files selected for processing (8)
  • src/components/agents/AgentDetail.tsx
  • src/components/agents/AgentDetailDialog.test.tsx
  • src/components/agents/AgentDetailDialog.tsx
  • src/components/agents/AgentsMenu.tsx
  • src/services/api/agentRouting.test.ts
  • src/tools/AgentTool/runAgent.ts
  • src/tools/shared/spawnMultiAgent.ts
  • src/utils/swarm/inProcessRunner.ts
📜 Recent review details
🧰 Additional context used
📓 Path-based instructions (15)
src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use TypeScript with strict mode and ESM imports

Files:

  • src/components/agents/AgentsMenu.tsx
  • src/components/agents/AgentDetailDialog.tsx
  • src/tools/shared/spawnMultiAgent.ts
  • src/utils/swarm/inProcessRunner.ts
  • src/components/agents/AgentDetailDialog.test.tsx
  • src/tools/AgentTool/runAgent.ts
  • src/services/api/agentRouting.test.ts
  • src/components/agents/AgentDetail.tsx
src/components/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use React + Ink for terminal UI implementations

Files:

  • src/components/agents/AgentsMenu.tsx
  • src/components/agents/AgentDetailDialog.tsx
  • src/components/agents/AgentDetailDialog.test.tsx
  • src/components/agents/AgentDetail.tsx
**/*.{ts,tsx,js,jsx,py,json,md}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Follow the existing code style in the touched files

Files:

  • src/components/agents/AgentsMenu.tsx
  • src/components/agents/AgentDetailDialog.tsx
  • src/tools/shared/spawnMultiAgent.ts
  • src/utils/swarm/inProcessRunner.ts
  • src/components/agents/AgentDetailDialog.test.tsx
  • src/tools/AgentTool/runAgent.ts
  • src/services/api/agentRouting.test.ts
  • src/components/agents/AgentDetail.tsx
**/*.{ts,tsx,js,jsx,py}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Keep comments useful and concise

Files:

  • src/components/agents/AgentsMenu.tsx
  • src/components/agents/AgentDetailDialog.tsx
  • src/tools/shared/spawnMultiAgent.ts
  • src/utils/swarm/inProcessRunner.ts
  • src/components/agents/AgentDetailDialog.test.tsx
  • src/tools/AgentTool/runAgent.ts
  • src/services/api/agentRouting.test.ts
  • src/components/agents/AgentDetail.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Follow TypeScript strict mode and type safety practices by running typecheck before submitting

Files:

  • src/components/agents/AgentsMenu.tsx
  • src/components/agents/AgentDetailDialog.tsx
  • src/tools/shared/spawnMultiAgent.ts
  • src/utils/swarm/inProcessRunner.ts
  • src/components/agents/AgentDetailDialog.test.tsx
  • src/tools/AgentTool/runAgent.ts
  • src/services/api/agentRouting.test.ts
  • src/components/agents/AgentDetail.tsx
**/*

⚙️ CodeRabbit configuration file

**/*: Apply the OpenClaude maintainer review rubric from AGENTS.md. Review the current diff, not stale discussion context. Separate real blockers from suggestions. Do not request changes for vague style churn. Treat approval as merge-ready from CodeRabbit's side, pending required human review and GitHub Checks. If checks are failing or unavailable, say so clearly instead of implying the PR is fully ready.

Files:

  • src/components/agents/AgentsMenu.tsx
  • src/components/agents/AgentDetailDialog.tsx
  • src/tools/shared/spawnMultiAgent.ts
  • src/utils/swarm/inProcessRunner.ts
  • src/components/agents/AgentDetailDialog.test.tsx
  • src/tools/AgentTool/runAgent.ts
  • src/services/api/agentRouting.test.ts
  • src/components/agents/AgentDetail.tsx
**

⚙️ CodeRabbit configuration file

**: # Contributing to OpenClaude

Thanks for contributing.

OpenClaude is a fast-moving open-source coding-agent CLI with support for multiple providers, local backends, MCP, and a terminal-first workflow. The best contributions here are focused, well-tested, and easy to review.

Before You Start

  • Search existing issues and discussions before opening a new thread.
  • Check open pull requests for work that overlaps with your contribution. If a PR already exists that addresses the same change, open an issue or discussion first to align on direction — duplicate PRs may be closed without review.
  • Use issues for confirmed bugs and actionable feature work.
  • Use discussions for setup help, ideas, and general community conversation.
  • For larger changes, open an issue first so the scope is clear before implementation.
  • For security reports, follow SECURITY.md.

Pull Requests

Every PR needs a reason. Your PR description must include:

  • what changed and why
  • the user or developer impact
  • the exact checks you ran
  • a linked issue when one exists, using Fixes fix: skip assertMinVersion for third-party providers #123, `Closes `#123, or another clear link
  • screenshots when the PR touches UI, terminal presentation, or the VS Code extension
  • which provider path was tested when the PR changes provider behavior

The PR author is responsible for ensuring their PR is merge-ready. PRs with merge conflicts will not be reviewed or approved until the conflicts are resolved.

Issues are the recommended starting point for anything non-trivial — opening one first helps avoid wasted effort if the change is out of scope or already being worked on. Small fixes, doc corrections, and obvious improvements can stand on their own without a linked issue, as long as the PR description explains the intent.

What Gets Closed Without Review

PRs may be closed without review...

Files:

  • src/components/agents/AgentsMenu.tsx
  • src/components/agents/AgentDetailDialog.tsx
  • src/tools/shared/spawnMultiAgent.ts
  • src/utils/swarm/inProcessRunner.ts
  • src/components/agents/AgentDetailDialog.test.tsx
  • src/tools/AgentTool/runAgent.ts
  • src/services/api/agentRouting.test.ts
  • src/components/agents/AgentDetail.tsx
src/{components/permissions,utils/permissions,hooks/toolPermission,tools,entrypoints/sdk}/**

⚙️ CodeRabbit configuration file

src/{components/permissions,utils/permissions,hooks/toolPermission,tools,entrypoints/sdk}/**: Review permission prompts, auto-allow logic, sandbox behavior, SDK permission schemas, shell/PowerShell execution, and background execution paths as security-sensitive. Block on bypasses, unclear trust boundaries, unsafe path handling, missing user visibility, or changes that broaden allowed behavior without an explicit maintainer decision.

Files:

  • src/tools/shared/spawnMultiAgent.ts
  • src/tools/AgentTool/runAgent.ts
{src/services/**/*.ts,src/utils/**/*.ts}

📄 CodeRabbit inference engine (AGENTS.md)

Use execa for child processes

Files:

  • src/utils/swarm/inProcessRunner.ts
  • src/services/api/agentRouting.test.ts
**/*.test.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Add or update tests when the change affects behavior

Files:

  • src/components/agents/AgentDetailDialog.test.tsx
  • src/services/api/agentRouting.test.ts
**/*.test.{ts,tsx,js}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Test the exact provider/model path you changed when possible

Files:

  • src/components/agents/AgentDetailDialog.test.tsx
  • src/services/api/agentRouting.test.ts
{src/**/*.test.ts,src/**/*.test.tsx,tests/**,scripts/**/*.test.ts,vscode-extension/**/*.test.js}

⚙️ CodeRabbit configuration file

{src/**/*.test.ts,src/**/*.test.tsx,tests/**,scripts/**/*.test.ts,vscode-extension/**/*.test.js}: Review tests for meaningful coverage of the changed behavior, isolation of global/env/config state, async cleanup, fake timers, provider profile leaks, and Windows-compatible assumptions. Block when risky runtime changes lack focused regression coverage or tests assert implementation details while missing the user-visible behavior.

Files:

  • src/components/agents/AgentDetailDialog.test.tsx
  • src/services/api/agentRouting.test.ts
{src/commands/**/*.ts,src/services/**/*.ts,src/entrypoints/**/*.ts}

📄 CodeRabbit inference engine (AGENTS.md)

Use chalk for terminal color in CLI code

Files:

  • src/services/api/agentRouting.test.ts
{src/integrations/**/*.ts,src/services/**/*.ts}

📄 CodeRabbit inference engine (AGENTS.md)

Test the exact provider/model path you changed when possible for provider modifications

Files:

  • src/services/api/agentRouting.test.ts
{src/services/api/**,src/integrations/**,src/utils/model/**,src/utils/provider*.ts,src/commands/provider/**}

⚙️ CodeRabbit configuration file

{src/services/api/**,src/integrations/**,src/utils/model/**,src/utils/provider*.ts,src/commands/provider/**}: Review provider routing, model selection, env precedence, auth/token handling, OpenAI-compatible shims, retries, proxy behavior, and outbound HTTP behavior with high scrutiny. Block on silent default changes, hidden fallback expansion, credential reuse mistakes, hardcoded provider assumptions, or new network reach that is not intentional and documented.

Files:

  • src/services/api/agentRouting.test.ts
🔇 Additional comments (8)
src/components/agents/AgentDetail.tsx (1)

23-25: LGTM!

Also applies to: 32-33, 41-41, 233-236

src/components/agents/AgentDetailDialog.tsx (1)

1-49: LGTM!

src/components/agents/AgentDetailDialog.test.tsx (1)

1-141: LGTM!

src/components/agents/AgentsMenu.tsx (1)

19-19: LGTM!

Also applies to: 511-524

src/services/api/agentRouting.test.ts (1)

746-791: LGTM!

src/tools/AgentTool/runAgent.ts (1)

268-268: LGTM!

Also applies to: 330-334, 362-362

src/tools/shared/spawnMultiAgent.ts (1)

1000-1000: LGTM!

src/utils/swarm/inProcessRunner.ts (1)

494-497: LGTM!

Also applies to: 907-907, 1226-1226


📝 Walkthrough

Walkthrough

Adds per-agent model route configuration and model-only route resolution. A new agentRouteSettings.ts module provides pure helpers (readAgentRoute, computeSetRouteUpdate, computeClearRouteUpdate, buildRouteOptions, describeRouteLine) and storage wrappers (getAgentRoute, setAgentRoute, clearAgentRoute) with shadow-route detection. A new AgentRouteSelector component exposes a dropdown for selecting or clearing a route, with fallback read-only UI when higher-priority settings override the route. AgentDetail integrates this behind the m key, showing a route summary footer when not in routing mode and disabling Esc handling while the picker is open. New AgentDetailDialog wrapper handles dialog cancel behavior coordination between AgentDetail and the route picker. Additionally, agentRouting.ts now resolves model-only routes (including aliases like inherit) through getAgentModel with permission mode context, and callers (AgentTool, runAgent) pass permissionMode and routing identity through to routing resolution.

Changes

Per-agent model route configuration and model-only resolution

Layer / File(s) Summary
Route settings types, helpers, and storage wrappers
src/services/api/agentRouteSettings.ts
Defines CurrentAgentRoute discriminated union (none, model-only, cross-provider, dangling) and sentinel constants, implements pure helpers for reading/computing route updates and building picker options, adds shadow-route detection for non-user settings overrides, and wraps user settings storage via getAgentRoute/setAgentRoute/clearAgentRoute with write guards when userSettings is disabled or when edits would be shadowed by higher-priority settings.
agentRouteSettings unit tests
src/services/api/agentRouteSettings.test.ts
Tests readAgentRoute outcomes (none/model-only/cross-provider/dangling with default fallback and normalization), computeSetRouteUpdate create/preserve/unrelated semantics, computeClearRouteUpdate undefined-deletion, buildRouteOptions clear-option presence and labeling, currentRouteValue, describeRouteLine with "via default" marker, findShadowingSource and findModelKeyShadowingSource source precedence with consistency checks, shadowRemediation messaging for file-backed and flag settings, and write-guard validation when userSettings is disabled.
AgentRouteSelector UI component
src/components/agents/AgentRouteSelector.tsx
New component builds dropdown options from buildRouteOptions plus custom-model-id input entry, detects shadow-route overrides via getRouteShadowSource and shows read-only fallback UI, dispatches route changes via setAgentRoute/clearAgentRoute wrapped in an apply helper that captures errors into local state and calls onClose only on success.
AgentRouteSelector component tests
src/components/agents/AgentRouteSelector.test.tsx
Tests AgentRouteSelector with mocked stream-based TTY rendering, verifying shadow-mode read-only fallback with overriding-source messaging (projectSettings and flagSettings variants), normal-mode dropdown selection and persistence, error handling with inline error display and dialog remaining open on save failure, and custom model id buffering and submission.
AgentDetail and AgentDetailDialog routing integration
src/components/agents/AgentDetail.tsx, src/components/agents/AgentDetailDialog.tsx
AgentDetail adds routing state and useInput handler for m/M; switches render to AgentRouteSelector when active, otherwise appends dim describeRouteLine footer with key hint, and disables confirm:no (Esc) keybinding while routing is active. New AgentDetailDialog wrapper renders AgentDetail inside Dialog and tracks routing state to control dialog cancel behavior (isCancelActive={!routing}), preventing Esc from canceling the dialog while the picker is open.
AgentDetail and AgentDetailDialog tests
src/components/agents/AgentDetail.test.tsx, src/components/agents/AgentDetailDialog.test.tsx
AgentDetail test verifies pressing m opens the route picker, pressing Esc closes the picker without invoking parent onBack, and detail footer returns. AgentDetailDialog test verifies Esc closes only the picker (output no longer contains "Set model route") while leaving the dialog open, without triggering onCancel or onBack.
AgentsMenu integration
src/components/agents/AgentsMenu.tsx
AgentsMenu now imports and uses AgentDetailDialog when rendering the "view-agent" mode, replacing the previous inline composition of AgentDetail inside a generic Dialog.
Model-only route resolution with permission mode
src/services/api/agentRouting.ts
Extends resolveAgentRunModelRouting to accept parentModel and optional permissionMode, implements resolveModelOnlyModel helper to route alias/inherit values through getAgentModel for provider-aware resolution, applies this for both tool-specified and default agent routing paths, and exports new resolveOutOfProcessTeammateModelOnly helper.
Agent routing tests for model-only resolution
src/services/api/agentRouting.test.ts
Updates existing resolveAgentRunModelRouting tests to include parentModel parameter across routing paths, adds import of getAgentModel and spying utilities, introduces new tests verifying model-only alias routes resolve provider-aware through getAgentModel (not literal alias) and that permissionMode is forwarded, and covers comprehensive resolveOutOfProcessTeammateModelOnly test cases for routed/unrouted/cross-provider teammates and CLI model precedence.
AgentTool, runAgent, and spawn chain integration
src/tools/AgentTool/AgentTool.tsx, src/tools/AgentTool/runAgent.ts, src/tools/shared/spawnMultiAgent.ts, src/utils/swarm/inProcessRunner.ts, src/tools/AgentTool/AgentTool.teammateModel.test.ts
AgentTool imports and uses resolveOutOfProcessTeammateModelOnly during multi-agent teammate spawning to separately resolve model-only routing when no cross-provider override is present, applying model-allowlist validation; both AgentTool and runAgent pass permissionMode from context into resolveAgentRunModelRouting; runAgent accepts optional routingSubagentType parameter to override subagent routing input; in-process teammate spawn chain (spawnMultiAgent, inProcessRunner) passes subagentType through so runAgent can use original agent type for routing identity; new test verifies model-only route resolution for teammates.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • Gitlawb/openclaude#1617: Modifies core routing logic for "model-only" routes in src/services/api/agentRouting.ts and downstream callsites; this PR builds directly on those same codepaths to add user-facing route selection and persistence.

Suggested reviewers

  • jatmn
  • kevincodex1
🚥 Pre-merge checks | ✅ 6 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 62.79% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (6 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately captures the main change: adding per-agent model routing UI to the /agents menu. It is concise, specific, and directly reflects the core functionality introduced.
Description check ✅ Passed The description provides comprehensive coverage of all required sections: summary of what/why changed, detailed impact (user-facing and developer), extensive testing checks performed, and design notes. All critical sections are well-filled and substantive.
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.
Risk Surface Disclosed ✅ Passed PR touches provider routing, permissions, and config-home behavior (writes to ~/.openclaude.json). Review explicitly calls out these risk surfaces in "Provider path" section, "Impact" section, and...
No Hidden Policy Change ✅ Passed All policy changes (permission-mode threading, teammate routing defaults, settings-source guards, shadowing checks) are explicit and documented in PR description; no hidden policy changes in unrela...

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/agents/AgentRouteSelector.tsx`:
- Around line 35-37: The route option discovery at line 35 uses
getInitialSettings() which may include settings from non-user scopes, while the
persistence calls to setAgentRoute at lines 46-47 and 57 write to user-settings
scope. This scope mismatch can create shadow entries in user-settings for keys
that only exist in non-user scopes, altering routing behavior. Ensure consistent
scope usage by either using a user-settings-specific getter instead of
getInitialSettings() when building options in buildRouteOptions at line 35, or
by adjusting how setAgentRoute at lines 46-47 and 57 reads and persists settings
to match the same scope used for option discovery.

In `@src/services/api/agentRouteSettings.test.ts`:
- Around line 112-115: The test named 'returns the route key, or undefined for
none/dangling' in the currentRouteValue test block does not actually test the
dangling case, creating a mismatch between the test name and its assertions.
Either add an additional expect statement that tests the dangling case (with
appropriate kind value) to match the test name description, or rename the test
to accurately reflect that it only tests the 'model-only' and 'none' cases and
remove the reference to dangling.

In `@src/services/api/agentRouteSettings.ts`:
- Around line 31-33: The cross-provider detection logic is inconsistent between
two locations in the file. The condition at line 31 treats a key as
cross-provider when either base_url or api_key exists (using OR logic:
entry.base_url || entry.api_key), but the condition at lines 94-98 apparently
applies different logic (requiring both to exist). Align these two detection
points by applying the same condition in both places—ensure that the same
criteria determine whether a route is cross-provider in both the initial read
and the option-label paths so the UI presentation remains consistent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 325c2f3c-1a47-47fb-993b-0cd60eaff94b

📥 Commits

Reviewing files that changed from the base of the PR and between de726c4 and 30ed919.

📒 Files selected for processing (4)
  • src/components/agents/AgentDetail.tsx
  • src/components/agents/AgentRouteSelector.tsx
  • src/services/api/agentRouteSettings.test.ts
  • src/services/api/agentRouteSettings.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx,js,jsx,py}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

**/*.{ts,tsx,js,jsx,py}: Follow the existing code style in the touched files
Keep comments useful and concise

Files:

  • src/components/agents/AgentRouteSelector.tsx
  • src/components/agents/AgentDetail.tsx
  • src/services/api/agentRouteSettings.test.ts
  • src/services/api/agentRouteSettings.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Typecheck TypeScript code before submitting (use bun run typecheck)

Files:

  • src/components/agents/AgentRouteSelector.tsx
  • src/components/agents/AgentDetail.tsx
  • src/services/api/agentRouteSettings.test.ts
  • src/services/api/agentRouteSettings.ts
**/*

⚙️ CodeRabbit configuration file

**/*: Apply the OpenClaude maintainer review rubric from AGENTS.md. Review the current diff, not stale discussion context. Separate real blockers from suggestions. Do not request changes for vague style churn. Treat approval as merge-ready from CodeRabbit's side, pending required human review and GitHub Checks. If checks are failing or unavailable, say so clearly instead of implying the PR is fully ready.

Files:

  • src/components/agents/AgentRouteSelector.tsx
  • src/components/agents/AgentDetail.tsx
  • src/services/api/agentRouteSettings.test.ts
  • src/services/api/agentRouteSettings.ts
**

⚙️ CodeRabbit configuration file

**: # Contributing to OpenClaude

Thanks for contributing.

OpenClaude is a fast-moving open-source coding-agent CLI with support for multiple providers, local backends, MCP, and a terminal-first workflow. The best contributions here are focused, well-tested, and easy to review.

Before You Start

  • Search existing issues and discussions before opening a new thread.
  • Check open pull requests for work that overlaps with your contribution. If a PR already exists that addresses the same change, open an issue or discussion first to align on direction — duplicate PRs may be closed without review.
  • Use issues for confirmed bugs and actionable feature work.
  • Use discussions for setup help, ideas, and general community conversation.
  • For larger changes, open an issue first so the scope is clear before implementation.
  • For security reports, follow SECURITY.md.

Pull Requests

Every PR needs a reason. Your PR description must include:

  • what changed and why
  • the user or developer impact
  • the exact checks you ran
  • a linked issue when one exists, using Fixes #123, `Closes `#123, or another clear link
  • screenshots when the PR touches UI, terminal presentation, or the VS Code extension
  • which provider path was tested when the PR changes provider behavior

The PR author is responsible for ensuring their PR is merge-ready. PRs with merge conflicts will not be reviewed or approved until the conflicts are resolved.

Issues are the recommended starting point for anything non-trivial — opening one first helps avoid wasted effort if the change is out of scope or already being worked on. Small fixes, doc corrections, and obvious improvements can stand on their own without a linked issue, as long as the PR description explains the intent.

What Gets Closed Without Review

PRs may be closed without review...

Files:

  • src/components/agents/AgentRouteSelector.tsx
  • src/components/agents/AgentDetail.tsx
  • src/services/api/agentRouteSettings.test.ts
  • src/services/api/agentRouteSettings.ts
{src/services/api/**,src/integrations/**,src/utils/model/**,src/utils/provider*.ts,src/commands/provider/**}

⚙️ CodeRabbit configuration file

{src/services/api/**,src/integrations/**,src/utils/model/**,src/utils/provider*.ts,src/commands/provider/**}: Review provider routing, model selection, env precedence, auth/token handling, OpenAI-compatible shims, retries, proxy behavior, and outbound HTTP behavior with high scrutiny. Block on silent default changes, hidden fallback expansion, credential reuse mistakes, hardcoded provider assumptions, or new network reach that is not intentional and documented.

Files:

  • src/services/api/agentRouteSettings.test.ts
  • src/services/api/agentRouteSettings.ts
{src/**/*.test.ts,src/**/*.test.tsx,tests/**,scripts/**/*.test.ts,vscode-extension/**/*.test.js}

⚙️ CodeRabbit configuration file

{src/**/*.test.ts,src/**/*.test.tsx,tests/**,scripts/**/*.test.ts,vscode-extension/**/*.test.js}: Review tests for meaningful coverage of the changed behavior, isolation of global/env/config state, async cleanup, fake timers, provider profile leaks, and Windows-compatible assumptions. Block when risky runtime changes lack focused regression coverage or tests assert implementation details while missing the user-visible behavior.

Files:

  • src/services/api/agentRouteSettings.test.ts
🔇 Additional comments (1)
src/components/agents/AgentDetail.tsx (1)

32-38: LGTM!

Also applies to: 228-231

Comment thread src/components/agents/AgentRouteSelector.tsx Outdated
Comment thread src/services/api/agentRouteSettings.test.ts Outdated
Comment thread src/services/api/agentRouteSettings.ts Outdated
…-provider labels

Build route options from getSettingsForSource('userSettings') instead of
getInitialSettings(), matching the scope we persist to. Reading from the
merged view could surface an agentModels key that only exists in a
non-user scope; selecting it would write a shadow user-settings entry that
diverges from the original (losing its base_url/api_key).

Label a key as cross-provider when either base_url or api_key is present,
matching readAgentRoute, so the same route is described consistently.

Also assert the dangling case in the currentRouteValue test, which the
old test name referenced but did not cover.
@beardthelion

Copy link
Copy Markdown
Contributor Author

All three addressed in 6ddd129.

  • Scope mismatch: AgentRouteSelector now builds options from getSettingsForSource('userSettings'), the same scope setAgentRoute writes to, so a shown key can't create a shadow user-settings entry that diverges from a non-user-scope original.
  • Cross-provider labeling: buildRouteOptions now treats a key as cross-provider when either base_url or api_key is present, matching readAgentRoute.
  • Test: added the dangling assertion to the currentRouteValue test and renamed it to match.

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 14, 2026

@jatmn jatmn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I found a couple of issues that need to be addressed before this is ready.

Findings

  • [P2] Commit custom model ids only after input submit
    src/components/agents/AgentRouteSelector.tsx:45
    The custom-model option saves from the input option's onChange, but CustomSelect calls that handler on every text edit and paste. As soon as a user focuses "Enter a custom model id" and types the first character of gpt-5-mini, this calls setAgentRoute(agentType, "g") and closes the selector, so the advertised custom-id path cannot actually enter a full model id. The later submit path only calls the parent onChange with CUSTOM_MODEL_VALUE, which this component ignores. Please commit the typed value from the input submit path instead of persisting from the per-keystroke input handler.

  • [P2] Match route display/selection to runtime credential validation
    src/services/api/agentRouteSettings.ts:31
    readAgentRoute and buildRouteOptions treat an agentModels entry as cross-provider when either base_url or api_key exists, but the runtime resolver requires both and skips partial entries. For a user-settings entry like { "half-entry": { "base_url": "https://api.example.com/v1" } }, /agents now shows/selects it as a cross-provider route and writes agentRouting to it, while the spawned agent falls back because toAgentRoute returns null. Please use the same validity rule as runtime here, or surface the entry as unconfigured so the UI does not claim a route that will not execute.

…red rule

The custom-model input persisted from the per-keystroke onChange, so typing
the first character of a model id saved that single character and closed the
selector. Track the typed value and persist only on submit (the sentinel
fires the Select onChange with the full value).

Cross-provider detection now mirrors the runtime resolver (toAgentRoute):
a route is cross-provider only when both base_url and api_key are present. A
partial entry is skipped at runtime and inherits, so readAgentRoute reports it
as unconfigured (dangling) and buildRouteOptions labels it 'unconfigured,
inherits' rather than claiming a cross-provider route that will not execute.
@beardthelion

Copy link
Copy Markdown
Contributor Author

Both addressed in 20f718f.

  • Custom model id on submit: the input option's onChange fires per keystroke, so it now only records the typed value; persistence happens on submit (the sentinel triggers the Select onChange with the full id). Typing gpt-5-mini now saves the whole id instead of g.
  • Match runtime credential rule: readAgentRoute and buildRouteOptions now use the same rule as toAgentRoute (both base_url and api_key required for cross-provider). A partial entry is reported as unconfigured (it's skipped at runtime and inherits) and labeled unconfigured, inherits instead of being shown/selected as a cross-provider route. Added tests for both partial cases.

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 14, 2026

@jatmn jatmn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for the update. I rechecked the changed paths and found one issue that still needs to be addressed.

Findings

  • [P2] Match the picker to normalized/default runtime routes
    src/services/api/agentRouteSettings.ts:26
    The new UI reads and writes agentRouting with an exact agentType key, but the runtime resolver normalizes route keys case-insensitively, treats hyphen and underscore as equivalent, and falls back to default. For example, an existing user setting like { "agentRouting": { "general_purpose": "mini" } } routes general-purpose agents at runtime, but /agents now reports that agent as inheriting. If the user then selects another model, the UI writes a new general-purpose entry beside the existing general_purpose entry; runtime keeps the first normalized entry and still runs the old model while the UI shows the new one. The same display gap applies to agents currently routed by default. Please have the picker read, update, and clear the same effective route key that resolveAgentProvider would use, or otherwise surface these normalized/default routes so the menu cannot claim a route change that runtime ignores.

…e route key

The runtime resolver normalizes routing keys (case-insensitive,
hyphen/underscore-equivalent) and falls back to default, but the picker
read and wrote agentRouting with the exact agentType key. So an existing
general_purpose route showed general-purpose agents as inheriting, and
selecting a model wrote a general-purpose sibling that first-wins lookup
ignored while the menu claimed the change took effect.

readAgentRoute now matches the normalized per-agent key the resolver
would use, and surfaces a default-fallback route with viaDefault.
computeSetRouteUpdate/computeClearRouteUpdate overwrite or clear that
existing key spelling instead of writing a sibling. The clear option is
hidden for default-inherited routes since there is no own key to remove.
@beardthelion

Copy link
Copy Markdown
Contributor Author

Fixed in 7aba642. The picker now resolves against the same effective route key as the runtime resolver.

  • readAgentRoute matches a routing key by the resolver's normalization (case-insensitive, hyphen/underscore-equivalent), so an existing general_purpose entry is correctly shown for general-purpose agents instead of reporting inherit.
  • computeSetRouteUpdate/computeClearRouteUpdate reuse that existing key's spelling, so selecting or clearing a model overwrites the entry runtime honors rather than writing a normalized sibling that first-wins lookup ignores.
  • Routes that only apply through default are surfaced with viaDefault ("(via default)" in the route line), and the clear option is hidden for them since there's no agent-specific key to remove.

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 15, 2026

@jatmn jatmn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for the update. I rechecked the changed paths and found one issue that still needs to be addressed.

Findings

  • [P2] Do not save routes that higher-priority settings will ignore
    src/services/api/agentRouteSettings.ts:185
    The new picker reads and writes only userSettings, but the runtime route resolver runs from the merged settings chain, where project, local, and policy settings are merged after user settings and therefore win. If a repo already has .openclaude/settings.json with { "agentRouting": { "verification": "project-mini" } }, /agents now reports verification as inheriting because getAgentRoute() only reads the user file. When the user presses m and selects gpt-5-mini, setAgentRoute() writes { "verification": "gpt-5-mini" } to user settings, closes successfully, and the UI can now show the user route, but the next verifier spawn still resolves the project route from getInitialSettings(). Please make the picker surface/handle the effective higher-priority route, or refuse/explain edits that cannot take effect instead of saving a lower-priority route that runtime will ignore.

…ides

The runtime resolver reads merged settings (userSettings ->
projectSettings -> localSettings -> flagSettings -> policySettings), but
the picker read and wrote only userSettings. A project/local/policy
agentRouting entry made /agents report the agent as inheriting, and
selecting a model wrote a userSettings route the merged chain ignored
while the menu reported success.

getAgentRoute now reads the effective merged settings, so the route line
reflects what runtime resolves. setAgentRoute/clearAgentRoute refuse with
an explanation when a higher-priority source owns the normalized route
key, and the picker surfaces that as a read-only notice instead of
offering an edit that cannot take effect.
@beardthelion

Copy link
Copy Markdown
Contributor Author

Fixed in fedf141. The picker no longer saves routes the merged settings chain would ignore.

  • getAgentRoute now reads the effective merged settings (getInitialSettings()), so an agent routed by a project/local/policy entry is shown as routed instead of inheriting.
  • setAgentRoute/clearAgentRoute detect when a higher-priority source defines the normalized route key and refuse with an explanation rather than writing a shadowed userSettings entry.
  • When a route is owned by a higher-priority source, the selector renders a read-only notice naming that source instead of offering an edit that can't take effect.

Added findShadowingSource with tests covering normalization, default-source absence, and highest-priority-wins.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/components/agents/AgentRouteSelector.tsx (1)

24-109: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Add focused regression tests for selector interaction paths before merge.

This component now owns critical user-visible routing behavior (shadow read-only mode, custom-ID submit semantics, clear/set write paths, error display), but the PR only adds service-helper tests. Please add focused tests for these selector flows so regressions in UI-driven persistence are caught.
As per coding guidelines, “Add or update tests when the change affects behavior in TypeScript and JavaScript files” and “Block when risky runtime changes lack focused regression coverage…”.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/agents/AgentRouteSelector.tsx` around lines 24 - 109, Add
focused regression tests for the AgentRouteSelector component to cover its
critical user-visible routing behaviors. Create test cases that verify: the
shadow read-only mode display and behavior when getRouteShadowSource returns a
value, custom model ID input and submission flow via the CUSTOM_MODEL_VALUE
path, clear and set operations through the onChange handler with different value
parameters (CLEAR_ROUTE_VALUE and regular values), and error display when
persistence operations fail. These tests should exercise the apply function's
error handling, the customIdRef tracking mechanism for custom IDs, and the
onClose callback invocation in various scenarios to ensure UI-driven persistence
works correctly across all interaction paths.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/services/api/agentRouteSettings.ts`:
- Around line 252-256: The shadowError function generates a generic error
message that assumes all setting sources are file-backed, but flagSettings has
no file to edit. Modify the shadowError function to branch the error message
based on the source parameter: when source is flagSettings, provide guidance
pointing to flags or profile configuration instead of telling users to edit a
settings file; for other sources, keep the current file-editing guidance. This
ensures the remediation advice is appropriate for each source type.

---

Outside diff comments:
In `@src/components/agents/AgentRouteSelector.tsx`:
- Around line 24-109: Add focused regression tests for the AgentRouteSelector
component to cover its critical user-visible routing behaviors. Create test
cases that verify: the shadow read-only mode display and behavior when
getRouteShadowSource returns a value, custom model ID input and submission flow
via the CUSTOM_MODEL_VALUE path, clear and set operations through the onChange
handler with different value parameters (CLEAR_ROUTE_VALUE and regular values),
and error display when persistence operations fail. These tests should exercise
the apply function's error handling, the customIdRef tracking mechanism for
custom IDs, and the onClose callback invocation in various scenarios to ensure
UI-driven persistence works correctly across all interaction paths.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 0f1b99af-e1dc-45a9-b31d-757492139bef

📥 Commits

Reviewing files that changed from the base of the PR and between 7aba642 and fedf141.

📒 Files selected for processing (3)
  • src/components/agents/AgentRouteSelector.tsx
  • src/services/api/agentRouteSettings.test.ts
  • src/services/api/agentRouteSettings.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx,js,jsx,py,md}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Follow the existing code style in the touched files

Files:

  • src/services/api/agentRouteSettings.test.ts
  • src/components/agents/AgentRouteSelector.tsx
  • src/services/api/agentRouteSettings.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Add or update tests when the change affects behavior in TypeScript and JavaScript files

Files:

  • src/services/api/agentRouteSettings.test.ts
  • src/components/agents/AgentRouteSelector.tsx
  • src/services/api/agentRouteSettings.ts
**/*

⚙️ CodeRabbit configuration file

**/*: Apply the OpenClaude maintainer review rubric from AGENTS.md. Review the current diff, not stale discussion context. Separate real blockers from suggestions. Do not request changes for vague style churn. Treat approval as merge-ready from CodeRabbit's side, pending required human review and GitHub Checks. If checks are failing or unavailable, say so clearly instead of implying the PR is fully ready.

Files:

  • src/services/api/agentRouteSettings.test.ts
  • src/components/agents/AgentRouteSelector.tsx
  • src/services/api/agentRouteSettings.ts
{src/services/api/**,src/integrations/**,src/utils/model/**,src/utils/provider*.ts,src/commands/provider/**}

⚙️ CodeRabbit configuration file

{src/services/api/**,src/integrations/**,src/utils/model/**,src/utils/provider*.ts,src/commands/provider/**}: Review provider routing, model selection, env precedence, auth/token handling, OpenAI-compatible shims, retries, proxy behavior, and outbound HTTP behavior with high scrutiny. Block on silent default changes, hidden fallback expansion, credential reuse mistakes, hardcoded provider assumptions, or new network reach that is not intentional and documented.

Files:

  • src/services/api/agentRouteSettings.test.ts
  • src/services/api/agentRouteSettings.ts
{src/**/*.test.ts,src/**/*.test.tsx,tests/**,scripts/**/*.test.ts,vscode-extension/**/*.test.js}

⚙️ CodeRabbit configuration file

{src/**/*.test.ts,src/**/*.test.tsx,tests/**,scripts/**/*.test.ts,vscode-extension/**/*.test.js}: Review tests for meaningful coverage of the changed behavior, isolation of global/env/config state, async cleanup, fake timers, provider profile leaks, and Windows-compatible assumptions. Block when risky runtime changes lack focused regression coverage or tests assert implementation details while missing the user-visible behavior.

Files:

  • src/services/api/agentRouteSettings.test.ts
**

⚙️ CodeRabbit configuration file

**: # Contributing to OpenClaude

Thanks for contributing.

OpenClaude is a fast-moving open-source coding-agent CLI with support for multiple providers, local backends, MCP, and a terminal-first workflow. The best contributions here are focused, well-tested, and easy to review.

Before You Start

  • Search existing issues and discussions before opening a new thread.
  • Check open pull requests for work that overlaps with your contribution. If a PR already exists that addresses the same change, open an issue or discussion first to align on direction — duplicate PRs may be closed without review.
  • Use issues for confirmed bugs and actionable feature work.
  • Use discussions for setup help, ideas, and general community conversation.
  • For larger changes, open an issue first so the scope is clear before implementation.
  • For security reports, follow SECURITY.md.

Pull Requests

Every PR needs a reason. Your PR description must include:

  • what changed and why
  • the user or developer impact
  • the exact checks you ran
  • a linked issue when one exists, using Fixes #123, `Closes `#123, or another clear link
  • screenshots when the PR touches UI, terminal presentation, or the VS Code extension
  • which provider path was tested when the PR changes provider behavior

The PR author is responsible for ensuring their PR is merge-ready. PRs with merge conflicts will not be reviewed or approved until the conflicts are resolved.

Issues are the recommended starting point for anything non-trivial — opening one first helps avoid wasted effort if the change is out of scope or already being worked on. Small fixes, doc corrections, and obvious improvements can stand on their own without a linked issue, as long as the PR description explains the intent.

What Gets Closed Without Review

PRs may be closed without review...

Files:

  • src/services/api/agentRouteSettings.test.ts
  • src/components/agents/AgentRouteSelector.tsx
  • src/services/api/agentRouteSettings.ts
🔇 Additional comments (1)
src/services/api/agentRouteSettings.test.ts (1)

1-262: LGTM!

Comment thread src/services/api/agentRouteSettings.ts
flagSettings has no file to edit (it comes from the --settings flag or
SDK inline settings), so the shadow message no longer tells users to edit
a nonexistent file. Extracted shadowRemediation so the error and the
read-only selector notice share one source-aware string.

Added focused AgentRouteSelector tests covering the shadow read-only mode
(file-backed and flag sources), persisting a selected model, and surfacing
a failed save without closing the dialog.
@beardthelion

Copy link
Copy Markdown
Contributor Author

Both addressed in 975fde2.

  • flagSettings guidance: extracted shadowRemediation(source) so the error and the read-only selector notice share one source-aware string. flagSettings now points at the --settings flag / SDK inline settings instead of telling the user to edit a file that doesn't exist; file-backed sources keep the edit-the-file guidance.
  • Selector tests: added AgentRouteSelector.test.tsx covering the shadow read-only mode (file-backed and flag sources, asserting the picker is suppressed), persisting a selected model through the onChange path, and surfacing a failed save while keeping the dialog open.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/agents/AgentRouteSelector.test.tsx`:
- Around line 79-91: The test "persists a selected model" is not deterministic
because it selects a route by ordinal index ('1') without controlling what
options are available, and it only asserts the agentType in the setCalls rather
than the complete call tuple. Fix this by ensuring the getRouteShadowSource mock
returns a controlled set of route options with known model keys at known
positions, and then update the assertion on setCalls to verify the complete
tuple [agentType, modelKey] rather than just the agentType alone. This ensures
the test actually proves the correct model key was selected and persisted.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b6d152d1-8736-45b7-85d0-b695c1ca2671

📥 Commits

Reviewing files that changed from the base of the PR and between fedf141 and 975fde2.

📒 Files selected for processing (4)
  • src/components/agents/AgentRouteSelector.test.tsx
  • src/components/agents/AgentRouteSelector.tsx
  • src/services/api/agentRouteSettings.test.ts
  • src/services/api/agentRouteSettings.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx,js,jsx,py,md}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Follow the existing code style in the touched files

Files:

  • src/components/agents/AgentRouteSelector.test.tsx
  • src/components/agents/AgentRouteSelector.tsx
  • src/services/api/agentRouteSettings.test.ts
  • src/services/api/agentRouteSettings.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Add or update tests when the change affects behavior in TypeScript and JavaScript files

Files:

  • src/components/agents/AgentRouteSelector.test.tsx
  • src/components/agents/AgentRouteSelector.tsx
  • src/services/api/agentRouteSettings.test.ts
  • src/services/api/agentRouteSettings.ts
**/*

⚙️ CodeRabbit configuration file

**/*: Apply the OpenClaude maintainer review rubric from AGENTS.md. Review the current diff, not stale discussion context. Separate real blockers from suggestions. Do not request changes for vague style churn. Treat approval as merge-ready from CodeRabbit's side, pending required human review and GitHub Checks. If checks are failing or unavailable, say so clearly instead of implying the PR is fully ready.

Files:

  • src/components/agents/AgentRouteSelector.test.tsx
  • src/components/agents/AgentRouteSelector.tsx
  • src/services/api/agentRouteSettings.test.ts
  • src/services/api/agentRouteSettings.ts
{src/**/*.test.ts,src/**/*.test.tsx,tests/**,scripts/**/*.test.ts,vscode-extension/**/*.test.js}

⚙️ CodeRabbit configuration file

{src/**/*.test.ts,src/**/*.test.tsx,tests/**,scripts/**/*.test.ts,vscode-extension/**/*.test.js}: Review tests for meaningful coverage of the changed behavior, isolation of global/env/config state, async cleanup, fake timers, provider profile leaks, and Windows-compatible assumptions. Block when risky runtime changes lack focused regression coverage or tests assert implementation details while missing the user-visible behavior.

Files:

  • src/components/agents/AgentRouteSelector.test.tsx
  • src/services/api/agentRouteSettings.test.ts
**

⚙️ CodeRabbit configuration file

**: # Contributing to OpenClaude

Thanks for contributing.

OpenClaude is a fast-moving open-source coding-agent CLI with support for multiple providers, local backends, MCP, and a terminal-first workflow. The best contributions here are focused, well-tested, and easy to review.

Before You Start

  • Search existing issues and discussions before opening a new thread.
  • Check open pull requests for work that overlaps with your contribution. If a PR already exists that addresses the same change, open an issue or discussion first to align on direction — duplicate PRs may be closed without review.
  • Use issues for confirmed bugs and actionable feature work.
  • Use discussions for setup help, ideas, and general community conversation.
  • For larger changes, open an issue first so the scope is clear before implementation.
  • For security reports, follow SECURITY.md.

Pull Requests

Every PR needs a reason. Your PR description must include:

  • what changed and why
  • the user or developer impact
  • the exact checks you ran
  • a linked issue when one exists, using Fixes #123, `Closes `#123, or another clear link
  • screenshots when the PR touches UI, terminal presentation, or the VS Code extension
  • which provider path was tested when the PR changes provider behavior

The PR author is responsible for ensuring their PR is merge-ready. PRs with merge conflicts will not be reviewed or approved until the conflicts are resolved.

Issues are the recommended starting point for anything non-trivial — opening one first helps avoid wasted effort if the change is out of scope or already being worked on. Small fixes, doc corrections, and obvious improvements can stand on their own without a linked issue, as long as the PR description explains the intent.

What Gets Closed Without Review

PRs may be closed without review...

Files:

  • src/components/agents/AgentRouteSelector.test.tsx
  • src/components/agents/AgentRouteSelector.tsx
  • src/services/api/agentRouteSettings.test.ts
  • src/services/api/agentRouteSettings.ts
{src/services/api/**,src/integrations/**,src/utils/model/**,src/utils/provider*.ts,src/commands/provider/**}

⚙️ CodeRabbit configuration file

{src/services/api/**,src/integrations/**,src/utils/model/**,src/utils/provider*.ts,src/commands/provider/**}: Review provider routing, model selection, env precedence, auth/token handling, OpenAI-compatible shims, retries, proxy behavior, and outbound HTTP behavior with high scrutiny. Block on silent default changes, hidden fallback expansion, credential reuse mistakes, hardcoded provider assumptions, or new network reach that is not intentional and documented.

Files:

  • src/services/api/agentRouteSettings.test.ts
  • src/services/api/agentRouteSettings.ts
🔇 Additional comments (15)
src/services/api/agentRouteSettings.ts (8)

252-261: LGTM!


1-51: LGTM!


53-76: LGTM!


78-114: LGTM!


116-154: LGTM!


156-209: LGTM!


211-250: LGTM!


263-268: LGTM!

src/services/api/agentRouteSettings.test.ts (6)

1-29: LGTM!


30-108: LGTM!


110-159: LGTM!


161-222: LGTM!


224-262: LGTM!


264-275: LGTM!

src/components/agents/AgentRouteSelector.tsx (1)

12-12: LGTM!

Also applies to: 52-52

Comment thread src/components/agents/AgentRouteSelector.test.tsx Outdated
The persist test selected an option by ordinal without controlling the
option list and only asserted the agentType. Mock buildRouteOptions to a
known single option and assert the full [agentType, modelKey] tuple, so
the test proves the selected model key is what gets persisted.
@beardthelion

Copy link
Copy Markdown
Contributor Author

Fixed in 8cc54ec. The set-route test now mocks buildRouteOptions to a single known option (gpt-5-mini) so selecting index 1 is deterministic, and asserts the full ['verification', 'gpt-5-mini'] tuple rather than just the agentType.

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 15, 2026
bun's mock.module persists across files in the same process and
mock.restore() does not undo it, so mocking buildRouteOptions in the
selector test leaked into agentRouteSettings.test.ts and failed its
buildRouteOptions assertions in the full suite (passed in isolation).
Re-point the module at the real implementation in afterEach, matching the
AgentsMenu.test.tsx pattern.

@jatmn jatmn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for the update. I rechecked the changed paths and found one issue that still needs to be addressed.

Findings

  • [P2] Keep rejecting model keys shadowed by higher settings
    src/services/api/agentRouteSettings.ts:326
    The new shadow guard still skips the check whenever userSettings.agentModels[modelKey] already exists, but that does not make the saved route take effect. Settings are merged from user to project/local/flag/policy, and resolveAgentProvider() later reads agentModels[modelKey] from the merged settings, so a project or policy agentModels.sonnet still wins over the user's agentModels.sonnet. In that case the picker even labels the option as shadowed, but selecting it writes userSettings.agentRouting.verification = "sonnet" successfully and the next spawn resolves to the higher-priority provider/model instead of the user's current-provider route. Please keep the model-key shadow rejection whenever a higher-priority source defines the same key, or write user-created routes under a collision-proof key before allowing the save.

… owns them

The previous shadow guard skipped the check when userSettings already
defined agentModels[modelKey], but that does not make the route take
effect: agentModels merges by source priority, so a higher-priority
project/policy entry for the same key still wins and the agent resolves
to that provider/model. The picker even flagged the key as shadowed
while the save path let it through, so offer and save disagreed.

Drop the ownsKey carve-out so setAgentRoute rejects whenever a higher
source defines the key. Extract collectShadowedModelKeys as the single
definition of 'defined above userSettings' that both the offer flag
(getShadowedModelKeys) and the save guard (findModelKeyShadowingSource)
derive from, and add a test locking the two paths in agreement.
@beardthelion

Copy link
Copy Markdown
Contributor Author

Fixed in bd10cdf.

You're right, the ownsKey carve-out was wrong and it made the offer and save paths disagree: buildRouteOptions flagged the key as shadowed (it keys purely off higher sources) while setAgentRoute still let the save through. Since agentModels merges by priority, a higher project/policy entry wins even when userSettings defines the same key, so owning it doesn't help.

Dropped the carve-out, so setAgentRoute rejects whenever a higher source defines the key. To keep the two paths from drifting again I extracted collectShadowedModelKeys as the single definition of "defined above userSettings" that both the offer flag (getShadowedModelKeys) and the save guard (findModelKeyShadowingSource) derive from, with a test asserting they agree.

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 16, 2026

@jatmn jatmn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for the update. I rechecked the changed paths and found a couple of test coverage gaps around the fixes from the last review cycle.

Findings

  • [P3] Complete CodeRabbit's shadow-write guard test request
    src/services/api/agentRouteSettings.ts:337
    CodeRabbit's review item asked for focused coverage of the actual setAgentRoute() model-key shadow guard, including the higher-priority-shadow rejection path. The current patch covers the pure helpers (findModelKeyShadowingSource() / collectShadowedModelKeys()) and the option labeling, but it still never calls setAgentRoute() with a higher-priority agentModels[modelKey] source. That means the user-visible save guard could be dropped or bypassed later while these tests continue to pass, reintroducing the route-save bug where the picker accepts a key that resolves to project/policy settings instead of the user's current-provider model. Please complete that request with direct setAgentRoute() coverage for both the rejected shadowed-key case and the allowed user-only-key case.

  • [P3] Add a spawn-level regression for model-only teammate routes
    src/tools/AgentTool/AgentTool.tsx:342
    The model-only teammate fix is currently covered at the helper level by resolveOutOfProcessTeammateModelOnly(), but the actual AgentTool teammate-spawn tests still only assert cross-provider agentModels routing at the spawnTeammate() boundary. A future change could stop passing routedTeammateModelOnly into spawnTeammate() and the helper tests would still pass, even though pane/window teammates would go back to inheriting the parent model instead of using the route saved from /agents. Please add an AgentTool.teammateModel regression with a model-only route such as agentModels.mini = { model: "gpt-5-mini" } and agentRouting.verification = "mini", then assert the spawned teammate receives model: "gpt-5-mini" with no cross-provider override.

…teammate spawn

Adds direct setAgentRoute() coverage for the higher-priority-shadow rejection
and the allowed user-only-key save, and an AgentTool teammate-spawn regression
that a model-only route reaches spawnTeammate as the resolved model with no
cross-provider override.
@beardthelion

Copy link
Copy Markdown
Contributor Author

Both added in 76c9a44. setAgentRoute() now has direct coverage for the shadowed-key rejection (asserting no write lands) and the user-only-key save; AgentTool has a model-only teammate-spawn regression (agentModels.mini = { model: "gpt-5-mini" }, agentRouting.verification = "mini") asserting the spawn receives model gpt-5-mini with no cross-provider override.

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 16, 2026

@jatmn jatmn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for grinding through the previous review items. The latest commit covers the two specific asks I had on the shadow-write guard and model-only teammate spawn boundary, and the focused tests/build/typecheck/smoke checks are green locally.

One remaining coverage gap still worries me because it is exactly the interaction that regressed earlier in this PR:

  • [P3] Add a regression for custom model input submission

    src/components/agents/AgentRouteSelector.test.tsx exercises selecting a built-in model and failed-save behavior, but none of the component tests focus the custom-model input option, type or paste a custom id, submit it, and assert that setAgentRoute(agentType, 'gpt-5-mini') receives the full id. That custom input path is the one that previously saved only the first character because the input onChange path was treated like a committed selection. The current implementation now appears to route typed input through customIdRef and commit on submit, but without an interaction regression this can break again while the existing tests still pass. Please add a focused AgentRouteSelector test that enters a custom id and verifies the full id is persisted on submit.

@beardthelion

Copy link
Copy Markdown
Contributor Author

Added a focused AgentRouteSelector regression in c7d2bb9: it focuses the custom-model input, types gpt-5-mini one character at a time, submits, and asserts setAgentRoute('verification', 'gpt-5-mini') fires exactly once with the full id. Confirmed it fails against the old commit-on-first-keystroke behavior. typecheck/build/smoke green.

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 16, 2026

@jatmn jatmn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for grinding through the previous review items. I took another pass through the updated routing, settings, and teammate-spawn paths and found one issue that still needs to be addressed.

Findings

  • [P2] Make the custom model input regression pass on Windows
    src/components/agents/AgentRouteSelector.test.tsx:271
    The latest regression test for custom model submission is still failing in a fresh Windows checkout. After selecting the custom input option, the rendered frame shows the focused row as > 4. e.g. gpt-5-mini, but the test waits for a line containing the Unicode glyph, so it times out before typing or submitting the custom id. This means the new coverage for the exact custom-input bug is not actually green locally, even though the component path may be fixed. Please make the wait condition assert focus/input state without depending on that glyph, or accept both rendered cursor forms, so bun test src/components/agents/AgentRouteSelector.test.tsx passes and continues to protect the full-id submission path.

The custom model regression test waited for the ❯ focus pointer, but ink
renders figures.pointer as ">" on Windows, so the wait timed out in a
Windows checkout before typing the id. Match figures.pointer directly so
the wait tracks whatever glyph the renderer emits on each platform.
@beardthelion

Copy link
Copy Markdown
Contributor Author

Fixed in 11e3a7a. The wait now matches figures.pointer directly instead of the literal ❯, so it tracks the ">" the renderer emits on Windows. bun test src/components/agents/AgentRouteSelector.test.tsx passes (5/5).

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 16, 2026

@jatmn jatmn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for grinding through the previous review items. I found one remaining issue in the new /agents route picker interaction.

Findings

  • [P2] Keep Esc scoped to the route picker while it is open
    src/components/agents/AgentDetail.tsx:66

    AgentDetail keeps its parent confirm:no handler active after switching into routing mode, even though the nested AgentRouteSelector also passes onCancel={onClose} to its Select. Because the parent handler was registered first and resolves only the Confirmation context, a bare Esc is consumed by onBack before the child select can resolve escape to select:cancel. I reproduced this with an AgentDetail + KeybindingSetup render: press m to open the route picker, then press Esc; onBack is invoked instead of just returning to the agent detail view. In the full /agents dialog this sends the user out of the selected-agent detail rather than merely closing the model picker, despite the picker/read-only copy telling users that Esc goes back. Please make the parent detail/dialog cancel handlers inactive while routing is true, or otherwise ensure the route selector owns Esc, and add an AgentDetail-level regression for m then Esc.

AgentDetail kept its parent confirm:no (Esc -> onBack) handler active after
switching into routing mode. Since the Confirmation context was registered
first, a bare Esc resolved to confirm:no and exited the detail view instead
of resolving to the nested select's select:cancel, despite the picker copy
telling users Esc goes back. Gate confirm:no with isActive=!routing so the
route selector owns Esc and a single Esc only closes the picker. Add an
AgentDetail regression covering m then Esc.
@beardthelion

Copy link
Copy Markdown
Contributor Author

Fixed in ceb3e78. The detail-level confirm:no handler is now gated with isActive: !routing, so while the route picker is open its select owns Esc (select:cancel via onClose) and a bare Esc only closes the picker instead of firing onBack. Added an AgentDetail regression (m then Esc) that asserts the picker closes and onBack is not called; verified it fails against the un-gated handler.

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 16, 2026

@jatmn jatmn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for the update. I rechecked the route-picker interaction and teammate routing paths and found a couple of issues that still need to be addressed.

Findings

  • [P2] Complete the Esc fix for the enclosing AgentsMenu dialog
    src/components/agents/AgentsMenu.tsx:522
    The latest patch disables AgentDetail's own confirm:no handler while the route picker is open, but the real /agents view still wraps that detail component in a Dialog whose onCancel remains active. Dialog registers its own confirm:no handler by default, and AgentDetail has no way to tell the wrapper that routing is true. The new regression mounts AgentDetail directly, so it misses this outer handler. In the full menu, pressing m opens AgentRouteSelector, whose Select expects Esc to call onClose, but the enclosing dialog can still consume Esc and jump back to the agent menu instead of simply closing the picker. Please complete the previous Esc-scoping request by making the enclosing Dialog cancel inactive while the route picker is open, or by otherwise letting the selector own Esc in the full AgentsMenu path, and cover that full wrapper interaction in the regression.

  • [P2] Preserve cross-provider routes in in-process teammate mode
    src/tools/AgentTool/AgentTool.tsx:367
    The teammate path now resolves /agents cross-provider routes before calling spawnTeammate(), but it only passes routedTeammateProvider.model plus modelWasToolSpecified: false. That is enough for pane/window teammates because the child CLI can re-resolve --model against settings and apply the provider override, but in-process teammates do not cross the CLI startup path. handleSpawnInProcess() forwards only the model string, and runInProcessTeammate() builds a synthetic agent definition from that string; no baseURL/apiKey provider override or original route key is preserved. For common configs where the route key differs from the API model, for example agentRouting.verification = "ds" and agentModels.ds = { model: "deepseek-chat", base_url, api_key }, the in-process teammate runs deepseek-chat on the parent provider instead of the configured DeepSeek/OpenAI-compatible provider. OpenClaude uses in-process teammates when that mode is selected or when pane backend detection falls back, so saved /agents cross-provider routes still fail in a supported teammate mode. Please carry the provider override (or enough route identity to re-resolve it) through the in-process handoff and add coverage past the AgentTool -> spawnTeammate() mock boundary.

… teammate routes

Two follow-ups from review of the per-agent model routing UI.

The /agents detail view wraps AgentDetail in a Dialog whose own confirm:no
(onCancel) stayed active while the route picker was open. As the
first-registered Confirmation context it swallowed Esc and dropped back to the
agent list instead of just closing the picker. AgentDetail now reports its
picker state up through onRoutingChange, and a small AgentDetailDialog wrapper
feeds that into the Dialog's isCancelActive so the picker's Select owns Esc
while it is open.

In-process teammates run the same runAgent() as normal subagents, but the
synthetic agent definition overwrites agentType with the teammate's display
name, so the original subagent_type that agentRouting is keyed on was lost and
the configured cross-provider route never resolved (the teammate ran the routed
model on the parent provider). The original subagent_type is now carried through
the in-process handoff and used as the routing key, matching what pane and
window teammates already get by re-resolving from their CLI identity.

Adds a regression test for the picker Esc interaction through the Dialog wrapper
and resolver tests proving the route resolves from the original subagent_type
rather than the teammate name.
@beardthelion

Copy link
Copy Markdown
Contributor Author

Both addressed in e2ea45b.

Esc scoping for the enclosing Dialog. The detail view now reports its route-picker state up via an onRoutingChange callback, and a small AgentDetailDialog wrapper feeds that into the Dialog's isCancelActive. So while the picker is open the Dialog's confirm:no is inactive and the selector's Select owns Esc. The new regression mounts that wrapper (not AgentDetail directly), so it exercises the full AgentsMenu interaction: press m, one Esc closes the picker without firing the Dialog's onCancel or the back handler.

In-process teammate routes. Rather than carry a pre-resolved override, I carry the route identity: the original subagent_type is threaded through the in-process handoff and used as the routing key in runAgent, so resolveAgentRunModelRouting resolves the same configured route the parent would (the synthetic agent definition had been overwriting agentType with the teammate name, which dropped the key). agentRouting.test.ts now covers it past the resolver boundary: the teammate display name misses the route, the original subagent_type resolves the cross-provider override. Both the in-process-selected and pane-detection-fallback cases run through handleSpawnInProcess, so they share this fix. (InProcessBackend.spawn() is a separate executor implementation with no production callers, so it isn't a second live path here.)

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.

2 participants