diff --git a/mobile/src/session/mobile-existing-agent-launch.ts b/mobile/src/session/mobile-existing-agent-launch.ts index b4a16281baf..7aab999b2f4 100644 --- a/mobile/src/session/mobile-existing-agent-launch.ts +++ b/mobile/src/session/mobile-existing-agent-launch.ts @@ -50,6 +50,7 @@ export async function launchAgentInExistingWorkspace(args: { hostCapabilities: readonly string[] | null | undefined worktreeId: string agent: TuiAgent + agentArgs?: string prompt?: AgentLaunchPrompt launchSource?: string // Injected in tests; each call is one new operation, so a later tap never replays this one. @@ -62,6 +63,7 @@ export async function launchAgentInExistingWorkspace(args: { agent: args.agent, worktreeId: args.worktreeId, operationId: (args.mintOperationId ?? structuredSessionOperationId)(), + ...(args.agentArgs !== undefined ? { agentArgs: args.agentArgs } : {}), ...(args.prompt ? { prompt: args.prompt } : {}), ...(args.launchSource ? { launchSource: args.launchSource } : {}) }) diff --git a/mobile/src/session/mobile-source-control-launch-agent.ts b/mobile/src/session/mobile-source-control-launch-agent.ts index 8c53c9fb7b2..cdfcc839d18 100644 --- a/mobile/src/session/mobile-source-control-launch-agent.ts +++ b/mobile/src/session/mobile-source-control-launch-agent.ts @@ -5,7 +5,10 @@ import type { GlobalSettings } from '../../../src/shared/global-settings-types' import type { Repo } from '../../../src/shared/repo-types' -import type { SourceControlLaunchActionId } from '../../../src/shared/source-control-ai-actions' +import type { + SourceControlActionRecipe, + SourceControlLaunchActionId +} from '../../../src/shared/source-control-ai-actions' import { resolveSourceControlActionRecipe } from '../../../src/shared/source-control-ai' import { pickSourceControlLaunchAgent, @@ -17,8 +20,8 @@ import { isMobileTuiAgent } from '../tasks/mobile-tui-agents' import type { MobileAgentLaunchContext } from './mobile-new-tab-agent-loader' export type MobileSourceControlLaunchAgent = - | { kind: 'agent'; agent: TuiAgent } - | { kind: 'unavailable'; message: string } + /** `agentArgs`: the recipe's saved launch arguments, sent as the desktop sends them. */ + { kind: 'agent'; agent: TuiAgent; agentArgs?: string } | { kind: 'unavailable'; message: string } type LaunchAgentSettings = Pick< GlobalSettings, @@ -32,7 +35,8 @@ export function resolveMobileSourceControlLaunchAgent( ): MobileSourceControlLaunchAgent { const settings = readLaunchAgentSettings(context.settings) const detectedAgents = context.detectedAgents.filter(isMobileTuiAgent) - const savedAgent = actionId ? readSavedAgent(settings, context.repo, actionId) : null + const recipe = actionId ? readSavedRecipe(settings, context.repo, actionId) : null + const savedAgent = readSourceControlLaunchRecipeAgentId(recipe) // Why: the phone has no pre-launch dialog showing the pick, so a saved agent that can't run here // is an error, as on the desktop's direct launch, rather than a silent swap to another agent. if ( @@ -51,7 +55,11 @@ export function resolveMobileSourceControlLaunchAgent( disabledAgents: settings?.disabledTuiAgents }) return agent - ? { kind: 'agent', agent } + ? { + kind: 'agent', + agent, + ...(recipe?.agentArgs !== undefined ? { agentArgs: recipe.agentArgs } : {}) + } : { kind: 'unavailable', message: 'No enabled AI agent was detected on this workspace host.' } } @@ -63,15 +71,13 @@ function readLaunchAgentSettings(settings: unknown): LaunchAgentSettings | null return settings as LaunchAgentSettings } -function readSavedAgent( +function readSavedRecipe( settings: LaunchAgentSettings | null, repo: unknown, actionId: SourceControlLaunchActionId -): TuiAgent | null { +): SourceControlActionRecipe | null { try { - return readSourceControlLaunchRecipeAgentId( - resolveSourceControlActionRecipe({ settings, repo: readRepoOverrides(repo), actionId }) - ) + return resolveSourceControlActionRecipe({ settings, repo: readRepoOverrides(repo), actionId }) } catch { // A recipe the phone cannot read must not block the button; the default agent still applies. return null diff --git a/mobile/src/session/pr-ai-triage-launch.test.ts b/mobile/src/session/pr-ai-triage-launch.test.ts index 60919caf5b1..b6fc02c70b4 100644 --- a/mobile/src/session/pr-ai-triage-launch.test.ts +++ b/mobile/src/session/pr-ai-triage-launch.test.ts @@ -106,6 +106,8 @@ describe('launchAgentWithPrompt', () => { prompt: { text: 'Fix the failing checks', delivery: 'submit' }, launchSource: 'task_page' }) + // No saved arguments: the host applies the user's configured defaults. + expect(launchParams(sendRequest)).not.toHaveProperty('agentArgs') expect(sendRequest.mock.calls.some(([method]) => method === 'terminal.send')).toBe(false) expect( sendRequest.mock.calls.some(([method]) => method === 'session.tabs.createTerminal') @@ -124,6 +126,18 @@ describe('launchAgentWithPrompt', () => { expect(launchParams(sendRequest)).toMatchObject({ agent: 'claude' }) }) + it("sends the action's saved agent arguments, as the desktop does", async () => { + const { client, sendRequest } = hostClient({ + settings: { + defaultTuiAgent: 'claude', + sourceControlAi: { actions: { fixChecks: { agentArgs: '--model opus' } } } + }, + launch: launchedWith({ delivery: 'submit', outcome: 'handed-to-terminal' }) + }) + await run(client) + expect(launchParams(sendRequest)).toMatchObject({ agent: 'claude', agentArgs: '--model opus' }) + }) + it('refuses rather than swaps when the saved agent is not on this host', async () => { const { client, sendRequest } = hostClient({ settings: { sourceControlAi: { actions: { fixChecks: { agentId: 'gemini' } } } }, diff --git a/mobile/src/session/pr-ai-triage-launch.ts b/mobile/src/session/pr-ai-triage-launch.ts index ac2d28aa209..613572524e2 100644 --- a/mobile/src/session/pr-ai-triage-launch.ts +++ b/mobile/src/session/pr-ai-triage-launch.ts @@ -52,6 +52,7 @@ export async function launchAgentWithPrompt(args: { hostCapabilities: args.hostCapabilities, worktreeId: args.worktreeId, agent: resolved.agent, + ...(resolved.agentArgs !== undefined ? { agentArgs: resolved.agentArgs } : {}), prompt: { text: args.prompt, delivery: 'submit' }, launchSource: args.launchSource }) diff --git a/mobile/src/tasks/agent-launch-request.ts b/mobile/src/tasks/agent-launch-request.ts index ddd0d85383e..6e029de5448 100644 --- a/mobile/src/tasks/agent-launch-request.ts +++ b/mobile/src/tasks/agent-launch-request.ts @@ -80,6 +80,7 @@ export function agentLaunchExistingParams(args: { agent: TuiAgent worktreeId: string operationId: string + agentArgs?: string prompt?: AgentLaunchPrompt launchSource?: string }): RpcSendParams<'agent.launchReplay'> { @@ -87,6 +88,7 @@ export function agentLaunchExistingParams(args: { agent: args.agent, operationId: args.operationId, target: { kind: 'existing', worktree: `id:${args.worktreeId}` }, + ...(args.agentArgs !== undefined ? { agentArgs: args.agentArgs } : {}), ...(args.prompt ? { prompt: args.prompt } : {}), ...(args.launchSource ? { launchSource: args.launchSource } : {}) }