diff --git a/mobile/app/h/[hostId]/tasks.tsx b/mobile/app/h/[hostId]/tasks.tsx index d62cc31b914..dbfdf309187 100644 --- a/mobile/app/h/[hostId]/tasks.tsx +++ b/mobile/app/h/[hostId]/tasks.tsx @@ -59,6 +59,7 @@ import { filterWorkspaceAgents, isWorkspaceAgentEnabled, pickWorkspaceAgent, + resolveWorkspaceAgentSelection, workspaceAgentLabel, type WorkspaceAgentChoice } from '../../../src/tasks/workspace-agent-selection' @@ -4998,42 +4999,22 @@ export default function MobileTasksScreen() { workspaceCreateTargetRepo ]) - useEffect(() => { - if (!tasksSupported || !workspaceCreateDraft || workspaceAgentOverridden) return - setWorkspaceAgent(pickWorkspaceAgent(runtimeTaskSettings, workspaceDetectedAgentIds)) - }, [ - runtimeTaskSettings, - tasksSupported, - workspaceAgentOverridden, - workspaceCreateDraft, - workspaceDetectedAgentIds - ]) - - useEffect(() => { - if ( - !workspaceCreateDraft || - !tasksSupported || - workspaceDetectedAgentIds === null || - !workspaceAgent || - workspaceAgent === 'blank' || - (workspaceDetectedAgentIds.has(workspaceAgent) && - isWorkspaceAgentEnabled(workspaceAgent, runtimeTaskSettings.disabledTuiAgents)) - ) { - return - } - // Why: the drawer can open before SSH/local detection settles. If the user - // picked an agent that is not actually available on that host, fall back to - // the same detected-agent rule desktop uses instead of launching a bad CLI. - setWorkspaceAgent(pickWorkspaceAgent(runtimeTaskSettings, workspaceDetectedAgentIds)) - setWorkspaceAgentOverridden(false) - }, [ - runtimeTaskSettings, - tasksSupported, - workspaceAgent, - workspaceCreateDraft, - workspaceDetectedAgentIds, - runtimeTaskSettings.disabledTuiAgents - ]) + const workspaceAgentSelection = resolveWorkspaceAgentSelection({ + selectionActive: tasksSupported && workspaceCreateDraft !== null, + settings: runtimeTaskSettings, + detectedAgentIds: workspaceDetectedAgentIds, + agent: workspaceAgent, + overridden: workspaceAgentOverridden + }) + if ( + workspaceAgentSelection.agent !== workspaceAgent || + workspaceAgentSelection.overridden !== workspaceAgentOverridden + ) { + // Why: the drawer can open before SSH/local detection settles. Resolve the + // visible agent before commit so users do not see an unavailable override. + setWorkspaceAgent(workspaceAgentSelection.agent) + setWorkspaceAgentOverridden(workspaceAgentSelection.overridden) + } const resolvedWorkspaceAgent = useMemo( () => workspaceAgent ?? pickWorkspaceAgent(runtimeTaskSettings, workspaceDetectedAgentIds), diff --git a/mobile/src/tasks/workspace-agent-selection.test.ts b/mobile/src/tasks/workspace-agent-selection.test.ts index 816988faa4c..18202d5dc71 100644 --- a/mobile/src/tasks/workspace-agent-selection.test.ts +++ b/mobile/src/tasks/workspace-agent-selection.test.ts @@ -3,6 +3,7 @@ import { describe, expect, it } from 'vitest' import { normalizeWorkspaceAgent, pickWorkspaceAgent, + resolveWorkspaceAgentSelection, workspaceAgentLabel } from './workspace-agent-selection' @@ -54,4 +55,52 @@ describe('workspace agent selection', () => { expect(normalizeWorkspaceAgent('__blank__')).toBe('blank') expect(workspaceAgentLabel('codex')).toBe('Codex') }) + + it('keeps automatic selection current while create selection is active', () => { + expect( + resolveWorkspaceAgentSelection({ + selectionActive: true, + settings: { defaultTuiAgent: 'codex' }, + detectedAgentIds: new Set(['claude', 'codex']), + agent: null, + overridden: false + }) + ).toEqual({ agent: 'codex', overridden: false }) + }) + + it('preserves a valid user override', () => { + expect( + resolveWorkspaceAgentSelection({ + selectionActive: true, + settings: { defaultTuiAgent: 'claude' }, + detectedAgentIds: new Set(['claude', 'codex']), + agent: 'codex', + overridden: true + }) + ).toEqual({ agent: 'codex', overridden: true }) + }) + + it('falls back when a user override is unavailable after detection settles', () => { + expect( + resolveWorkspaceAgentSelection({ + selectionActive: true, + settings: { defaultTuiAgent: 'codex' }, + detectedAgentIds: new Set(['claude']), + agent: 'codex', + overridden: true + }) + ).toEqual({ agent: 'claude', overridden: false }) + }) + + it('does not repair inactive selection state', () => { + expect( + resolveWorkspaceAgentSelection({ + selectionActive: false, + settings: { defaultTuiAgent: 'codex' }, + detectedAgentIds: new Set(['codex']), + agent: null, + overridden: false + }) + ).toEqual({ agent: null, overridden: false }) + }) }) diff --git a/mobile/src/tasks/workspace-agent-selection.ts b/mobile/src/tasks/workspace-agent-selection.ts index 6d2bd384619..da406452882 100644 --- a/mobile/src/tasks/workspace-agent-selection.ts +++ b/mobile/src/tasks/workspace-agent-selection.ts @@ -15,6 +15,17 @@ type WorkspaceAgentSettings = { disabledTuiAgents?: unknown } +export type WorkspaceAgentSelectionState = { + agent: WorkspaceAgentChoice | null + overridden: boolean +} + +type ResolveWorkspaceAgentSelectionArgs = WorkspaceAgentSelectionState & { + selectionActive: boolean + settings: WorkspaceAgentSettings + detectedAgentIds: Set | null +} + export function workspaceAgentLabel(agent: WorkspaceAgentChoice): string { return agent === 'blank' ? 'Blank Terminal' : MOBILE_TUI_AGENT_LABELS[agent] } @@ -55,3 +66,32 @@ export function filterWorkspaceAgents(agents: readonly TuiAgent[], disabled?: un export function isWorkspaceAgentEnabled(agent: TuiAgent, disabled?: unknown): boolean { return isMobileTuiAgentEnabled(agent, disabled) } + +export function resolveWorkspaceAgentSelection({ + selectionActive, + settings, + detectedAgentIds, + agent, + overridden +}: ResolveWorkspaceAgentSelectionArgs): WorkspaceAgentSelectionState { + const current = { agent, overridden } + if (!selectionActive) { + return current + } + + const pickedAgent = pickWorkspaceAgent(settings, detectedAgentIds) + if (!overridden) { + return agent === pickedAgent ? current : { agent: pickedAgent, overridden: false } + } + + if ( + detectedAgentIds === null || + !agent || + agent === 'blank' || + (detectedAgentIds.has(agent) && isWorkspaceAgentEnabled(agent, settings.disabledTuiAgents)) + ) { + return current + } + + return { agent: pickedAgent, overridden: false } +}