feat(mobile): pick custom agent modes in new and open sessions - #5323
Conversation
Mobile consumes the existing agentProfiles router for custom agent modes. No procedures added; the server router composition is unchanged.
Move normalizeAgentMode to a Lucide-free mode-normalize module so unknown custom slugs pass through instead of being rewritten to code. Add picker helpers (visible/dedupe/ensure/pinned-model) and render custom modes in the mode picker under a Custom modes heading. Re-export the widened AgentMode and point send-path callers at the pure module.
Read the effective default profile's visible agents and resolve the pinned model for the selected mode. When a Cloud Agent target's agent pins a model, disable the model picker and show the pinned id and variant; remote targets stay unlocked and keep today's spawn selection.
Show session runtimeAgents in the mode picker and lock the model when the selected agent pins one. Send the pinned model/variant and keep the custom slug on the wire; clear the pin only on mode change so a user model pick is not dropped.
On send, set the override to the model actually sent so the SDK (which prefers the override) uses the pin when pinned and the user pick otherwise. Never clear to null on an unpinned send, which would drop a user pick.
Code Review SummaryStatus: No Issues Found | Recommendation: Merge Incremental review of Files Reviewed (3 files)
Previous Review Summaries (3 snapshots, latest commit a13c3f6)Current summary above is authoritative. Previous snapshots are kept for context only. Previous review (commit a13c3f6)Status: No Issues Found | Recommendation: Merge Incremental review of Files Reviewed (9 files)
Previous review (commit d37e714)Status: No Issues Found | Recommendation: Merge Reviewed the full mobile custom-modes feature with high confidence: mode normalization/aliasing in the new pure Files Reviewed (28 files)
Previous review (commit 0e7f49d)Status: No Issues Found | Recommendation: Merge Reviewed the full mobile custom-modes feature with high confidence: mode normalization/aliasing in the new pure Files Reviewed (28 files)
Reviewed by grok-4.6 · Input: 83.4K · Output: 10.4K · Cached: 332.3K Review guidance: REVIEW.md from base branch |
Use the SessionModelOption type for lockedModelOption and drop the one-consumer ModeOption re-export. Type-only; no runtime change.
A remote spawn sends the chosen agent slug, so the CLI creates the session with it. The mobile then opened the session and auto-sent the first message with `mode` from the session config, which a fresh session does not have yet, so the send fell back to `code` and switched the CLI session off the mode. Thread the spawn-chosen mode through the route params and seed the initial composer mode from it. A stored mode, a live session config, and a later user pick all still win.
# Conflicts: # apps/mobile/src/components/agents/session-detail-content.tsx
The base merge pushed new-session-prompt.tsx over the max-lines limit. The props type is the only self-contained block, and it owned every type-only import the component body never used.
Summary
For the user: you can now pick a custom mode (a profile agent) for Cloud Agent and remote CLI sessions, in both the new-session screen and an open session. The mode you pick stays on the composer and on send. When a custom agent pins a model, the model chip shows that model and locks, so you cannot change it.
For the product manager: the mode picker lists built-in modes first, then a "Custom modes" group. A custom mode that collides with a built-in slug is hidden; the built-in wins. Hidden or empty custom lists look like today (built-ins only). A custom agent that pins a model locks the model picker, matching web.
For the maintainer: this copies the web contract to mobile.
normalizeAgentModenow aliasesbuild/architectand passes any other non-empty slug through instead of rewriting tocode. A Lucide-freemode-normalize.tsholds the picker helpers (visibleProfileAgents,dedupeCustomModeOptions,ensureSelectedCustomOption,resolvePinnedAgentModel,lockedModelOption). The existingagentProfilesRouteris mounted on the mobile tRPC surface. New-session reads the effective default profile; open sessions readsessionConfig.runtimeAgents. Cloud Agent send maps the slug throughprepareSession/manager.sendand sets the cloud-agent model override to the sent model/variant; remote spawn maps the slug toagent.Verification
runtimeAgents), S4 (empty account built-ins only). Re-verify round 2 — S6 (remote inherit): the remote session starts from the tap, with the spawn payload carryingagent:'reviewer'(unit-tested inuse-remote-instance-spawn.test.ts).reviewerruns asreviewer(CLI log:created ... agent=reviewer,stream ... agent=reviewer) and the composer keeps the mode. Built-inplanregression check passes the same way.mode-normalize.test.ts,mode-selector-options.test.ts,chat-toolbar.test.ts,mobile-session-transport-payload.test.ts,use-new-session-creator.test.ts,use-continue-session.test.ts,use-remote-instance-spawn.test.ts.S6 correction: the earlier S6 note blamed the CLI. That was wrong. E2E with a custom agent present in the CLI's own catalog (
kilo agent listprintsreviewer (primary)) still showedCode. The CLI honors a known slug: a control runkilo run "..." --agent reviewerstreamsagent=reviewer. The defect was in mobile: the remote spawn sentagent:'reviewer'and the CLI created the session with it, then the mobile's own auto-sent first message carriedmodefrom the not-yet-populated session config, fell back tocode, and switched the session off the mode. The same path also lost a built-in pick such asplan. Fixed by threading the spawn-chosen mode through the route params and seeding the composer mode from it.Visual Changes
Reviewer, composer fell back toCodeReviewer Notes
ModeComboboxbehavior is unchanged; this only adds the mobile picker.createAgentrejects built-in slugs, so the collision-hide path is covered by a unit test, not E2E.runtimeAgents, so the composer chip falls back to the raw slug (reviewer, notReviewer). The mode itself is correct and the CLI runs it. Web shows its placeholder in the same case, so this is not a regression; say the word if the display name is worth threading too.agentand keeps today's model selection.Human steps
No human step is needed. No migration, secret, environment value, or flag is required.