Skip to content

feat(mobile): pick custom agent modes in new and open sessions - #5323

Merged
iscekic merged 11 commits into
mainfrom
custom-modes-a169
Aug 18, 2026
Merged

feat(mobile): pick custom agent modes in new and open sessions#5323
iscekic merged 11 commits into
mainfrom
custom-modes-a169

Conversation

@iscekic

@iscekic iscekic commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

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. normalizeAgentMode now aliases build/architect and passes any other non-empty slug through instead of rewriting to code. A Lucide-free mode-normalize.ts holds the picker helpers (visibleProfileAgents, dedupeCustomModeOptions, ensureSelectedCustomOption, resolvePinnedAgentModel, lockedModelOption). The existing agentProfilesRouter is mounted on the mobile tRPC surface. New-session reads the effective default profile; open sessions read sessionConfig.runtimeAgents. Cloud Agent send maps the slug through prepareSession/manager.send and sets the cloud-agent model override to the sent model/variant; remote spawn maps the slug to agent.

Verification

  • E2E (bot-e2e, iOS): all scenarios pass. Round 1 — S1 (picker lists custom modes), S2 (select Reviewer + start cloud), S3 (pinned model lock), S5 (open-session 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 carrying agent:'reviewer' (unit-tested in use-remote-instance-spawn.test.ts).
  • E2E re-verify (bot-e2e, iOS) after the mode-seed fix: a remote CLI session started with the custom mode reviewer runs as reviewer (CLI log: created ... agent=reviewer, stream ... agent=reviewer) and the composer keeps the mode. Built-in plan regression check passes the same way.
  • Unit tests: 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.
  • CI green: typecheck, lint, format-check, test, drizzle-check, check-unused, trufflehog.

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 list prints reviewer (primary)) still showed Code. The CLI honors a known slug: a control run kilo run "..." --agent reviewer streams agent=reviewer. The defect was in mobile: the remote spawn sent agent:'reviewer' and the CLI created the session with it, then the mobile's own auto-sent first message carried mode from the not-yet-populated session config, fell back to code, and switched the session off the mode. The same path also lost a built-in pick such as plan. Fixed by threading the spawn-chosen mode through the route params and seeding the composer mode from it.

Visual Changes

Before After
Mode picker: built-ins only (today's look) frame-s1-picker.png
Model chip: unlocked, user-selectable frame-s3-locked.png
Remote session started as Reviewer, composer fell back to Code frame-s6-code.png Composer keeps the mode after the fix frame-s6-reviewer.png

Reviewer Notes

  • The web ModeCombobox behavior is unchanged; this only adds the mobile picker.
  • createAgent rejects built-in slugs, so the collision-hide path is covered by a unit test, not E2E.
  • An open remote session has no runtimeAgents, so the composer chip falls back to the raw slug (reviewer, not Reviewer). 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.
  • Model lock and the gateway pin apply only to Cloud Agent; remote spawn sends the slug as agent and keeps today's model selection.

Human steps

No human step is needed. No migration, secret, environment value, or flag is required.

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.
@iscekic
iscekic requested a review from jeanduplessis August 18, 2026 04:46
@iscekic iscekic self-assigned this Aug 18, 2026
@iscekic
iscekic requested a review from pandemicsyn August 18, 2026 04:46
@kilo-code-bot

kilo-code-bot Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Incremental review of 9d58aaaf (extract NewSessionPromptProps after the main merge). The props type moved to new-session-prompt-types.ts with AgentMode imported from mode-normalize (same type, previously re-exported via mode-selector). Merge kept the pinned-model hasModel seed and adopted main's resolveSessionComposerDisabled helper. No behavior change, no memory-leak concerns.

Files Reviewed (3 files)
  • apps/mobile/src/components/agents/new-session-prompt-types.ts
  • apps/mobile/src/components/agents/new-session-prompt.tsx
  • apps/mobile/src/components/agents/session-detail-content.tsx
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 a13c3f64 (keep the picked mode on a spawned remote session). Mode is threaded through appendShareParams on both the remote-spawn ready path and continue-session remote spawn, read from the session route, and used only as the useSessionConfigSync initial seed — stored/live session modes still win. Tests cover param encoding, omit-when-empty, and seed-vs-session precedence. No memory-leak concerns in the new mounted hook test.

Files Reviewed (9 files)
  • apps/mobile/src/app/(app)/agent-chat/[session-id].tsx
  • apps/mobile/src/components/agents/session-detail-content.tsx
  • apps/mobile/src/components/agents/use-continue-session.ts
  • apps/mobile/src/components/agents/use-remote-spawn-dispatch.test.ts
  • apps/mobile/src/components/agents/use-remote-spawn-dispatch.ts
  • apps/mobile/src/components/agents/use-session-config-sync.mounted.test.tsx
  • apps/mobile/src/components/agents/use-session-config-sync.ts
  • apps/mobile/src/lib/share-navigation.test.ts
  • apps/mobile/src/lib/share-navigation.ts

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 mode-normalize.ts, picker option composition (dedupe / ensure-selected / pin resolution), model locking on the Cloud Agent target, remote-spawn mode passthrough, the open-session send path (including the cloudAgentModelOverride sync on mode change and on send), and the agentProfiles mount on the mobile tRPC surface against the existing server schemas (preparedSessionModeSchema / agentModeSendMessageSchema and AgentConfigSchema shapes all line up). No new listeners, timers, or subscriptions were introduced — no memory-leak concerns. Server-side slug validation and runtimeAgents cross-checks cover the custom-slug passthrough, and the changed logic is well covered by the new unit tests. The markdown/image HTML-tag rule does not apply (no .md files changed).

Files Reviewed (28 files)
  • packages/trpc/src/mobile.ts
  • apps/mobile/src/app/(app)/agent-chat/mode-picker.tsx
  • apps/mobile/src/app/(app)/agent-chat/new.tsx
  • apps/mobile/src/components/agents/chat-composer.tsx
  • apps/mobile/src/components/agents/chat-toolbar.test.ts
  • apps/mobile/src/components/agents/chat-toolbar.tsx
  • apps/mobile/src/components/agents/continuation-seed.ts
  • apps/mobile/src/components/agents/mobile-session-manager.ts
  • apps/mobile/src/components/agents/mobile-session-transport-payload.test.ts
  • apps/mobile/src/components/agents/mobile-session-transport-payload.ts
  • apps/mobile/src/components/agents/mode-normalize.test.ts
  • apps/mobile/src/components/agents/mode-normalize.ts
  • apps/mobile/src/components/agents/mode-options.ts
  • apps/mobile/src/components/agents/mode-selector-options.test.ts
  • apps/mobile/src/components/agents/mode-selector.tsx
  • apps/mobile/src/components/agents/model-selector.tsx
  • apps/mobile/src/components/agents/new-session-configure-form.tsx
  • apps/mobile/src/components/agents/new-session-prefill.test.ts
  • apps/mobile/src/components/agents/new-session-prefill.ts
  • apps/mobile/src/components/agents/new-session-prompt.tsx
  • apps/mobile/src/components/agents/session-detail-content.tsx
  • apps/mobile/src/components/agents/use-continue-cloud-create.ts
  • apps/mobile/src/components/agents/use-continue-session.test.ts
  • apps/mobile/src/components/agents/use-effective-profile-custom-modes.ts
  • apps/mobile/src/components/agents/use-new-session-creator.test.ts
  • apps/mobile/src/components/agents/use-session-config-sync.ts
  • apps/mobile/src/lib/hooks/use-remote-instance-spawn.test.ts
  • apps/mobile/src/lib/picker-bridge.ts

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 mode-normalize.ts, picker option composition (dedupe / ensure-selected / pin resolution), model locking on the Cloud Agent target, remote-spawn mode passthrough, the open-session send path (including the cloudAgentModelOverride sync on mode change and on send), and the agentProfiles mount on the mobile tRPC surface against the existing server schemas (preparedSessionModeSchema / agentModeSendMessageSchema and AgentConfigSchema shapes all line up). No new listeners, timers, or subscriptions were introduced — no memory-leak concerns. Server-side slug validation and runtimeAgents cross-checks cover the custom-slug passthrough, and the changed logic is well covered by the new unit tests. The markdown/image HTML-tag rule does not apply (no .md files changed).

Files Reviewed (28 files)
  • packages/trpc/src/mobile.ts
  • apps/mobile/src/app/(app)/agent-chat/mode-picker.tsx
  • apps/mobile/src/app/(app)/agent-chat/new.tsx
  • apps/mobile/src/components/agents/chat-composer.tsx
  • apps/mobile/src/components/agents/chat-toolbar.test.ts
  • apps/mobile/src/components/agents/chat-toolbar.tsx
  • apps/mobile/src/components/agents/continuation-seed.ts
  • apps/mobile/src/components/agents/mobile-session-manager.ts
  • apps/mobile/src/components/agents/mobile-session-transport-payload.test.ts
  • apps/mobile/src/components/agents/mobile-session-transport-payload.ts
  • apps/mobile/src/components/agents/mode-normalize.test.ts
  • apps/mobile/src/components/agents/mode-normalize.ts
  • apps/mobile/src/components/agents/mode-options.ts
  • apps/mobile/src/components/agents/mode-selector-options.test.ts
  • apps/mobile/src/components/agents/mode-selector.tsx
  • apps/mobile/src/components/agents/model-selector.tsx
  • apps/mobile/src/components/agents/new-session-configure-form.tsx
  • apps/mobile/src/components/agents/new-session-prefill.test.ts
  • apps/mobile/src/components/agents/new-session-prefill.ts
  • apps/mobile/src/components/agents/new-session-prompt.tsx
  • apps/mobile/src/components/agents/session-detail-content.tsx
  • apps/mobile/src/components/agents/use-continue-cloud-create.ts
  • apps/mobile/src/components/agents/use-continue-session.test.ts
  • apps/mobile/src/components/agents/use-effective-profile-custom-modes.ts
  • apps/mobile/src/components/agents/use-new-session-creator.test.ts
  • apps/mobile/src/components/agents/use-session-config-sync.ts
  • apps/mobile/src/lib/hooks/use-remote-instance-spawn.test.ts
  • apps/mobile/src/lib/picker-bridge.ts

Reviewed by grok-4.6 · Input: 83.4K · Output: 10.4K · Cached: 332.3K

Review guidance: REVIEW.md from base branch main

Use the SessionModelOption type for lockedModelOption and drop the
one-consumer ModeOption re-export. Type-only; no runtime change.
@iscekic iscekic added the human-ready The PR is ready for human review. label Aug 18, 2026
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.
@iscekic
iscekic merged commit 20dc7ec into main Aug 18, 2026
19 checks passed
@iscekic
iscekic deleted the custom-modes-a169 branch August 18, 2026 14:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

human-ready The PR is ready for human review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants