From 31e8423973c183502e629202a26a37f946e47859 Mon Sep 17 00:00:00 2001 From: "@tomny-dev" <20028678+tomny-dev@users.noreply.github.com> Date: Wed, 26 Aug 2026 17:38:59 +0000 Subject: [PATCH 1/4] feat: select models before waking tasks --- .../[taskId]/WakeTaskInput.client.test.tsx | 58 +++++++++++++++++++ .../(sandbox)/task/[taskId]/WakeTaskInput.tsx | 22 ++++++- .../prompt-input/TaskModelSwitcher.tsx | 28 ++++++++- .../tasks/TaskPromptInput.client.test.tsx | 15 +++++ .../src/components/tasks/TaskPromptInput.tsx | 4 ++ 5 files changed, 121 insertions(+), 6 deletions(-) diff --git a/apps/web/src/app/(sandbox)/task/[taskId]/WakeTaskInput.client.test.tsx b/apps/web/src/app/(sandbox)/task/[taskId]/WakeTaskInput.client.test.tsx index 1dce9673e..9d7342d00 100644 --- a/apps/web/src/app/(sandbox)/task/[taskId]/WakeTaskInput.client.test.tsx +++ b/apps/web/src/app/(sandbox)/task/[taskId]/WakeTaskInput.client.test.tsx @@ -26,6 +26,7 @@ let capturedSuggestion: unknown; let capturedSubmitWithMetaKey: boolean | undefined; let capturedSubmitIcon: unknown; let capturedSurface: string | undefined; +let capturedModelSwitcherDisabled: boolean | undefined; const submittedFilesRef: { current: Array<{ url?: string; @@ -99,6 +100,7 @@ vi.mock('@/components/tasks', () => ({ suggestion, submitWithMetaKey, submitIcon, + tools, surface, submitDisabledReason, }: { @@ -117,6 +119,7 @@ vi.mock('@/components/tasks', () => ({ suggestion?: unknown; submitWithMetaKey?: boolean; submitIcon?: unknown; + tools?: React.ReactNode; surface?: string; submitDisabledReason?: string; }) => { @@ -145,11 +148,30 @@ vi.mock('@/components/tasks', () => ({ > Send + {tools} ); }, })); +vi.mock('./prompt-input/TaskModelSwitcher', () => ({ + TaskModelSwitcher: ({ + disabled, + onPendingChange, + }: { + disabled?: boolean; + onPendingChange?: (pending: boolean) => void; + }) => { + capturedModelSwitcherDisabled = disabled; + + return ( + + ); + }, +})); + import { WakeTaskInput } from './WakeTaskInput'; function renderWithQueryClient(ui: React.ReactNode, queryClient: QueryClient) { @@ -166,6 +188,7 @@ describe('WakeTaskInput', () => { capturedSubmitWithMetaKey = undefined; capturedSubmitIcon = undefined; capturedSurface = undefined; + capturedModelSwitcherDisabled = undefined; submittedFilesRef.current = []; preparePromptAttachmentsMock.mockResolvedValue({ text: 'Wake up and keep going', @@ -219,6 +242,41 @@ describe('WakeTaskInput', () => { expect(capturedSurface).toBe('embedded'); }); + it('allows model selection before waking an OpenCode task', async () => { + const queryClient = new QueryClient({ + defaultOptions: { + queries: { retry: false }, + }, + }); + + renderWithQueryClient( + , + queryClient, + ); + + expect( + screen.getByRole('button', { name: 'Model selector' }), + ).toBeVisible(); + expect(capturedModelSwitcherDisabled).toBe(false); + + fireEvent.click(screen.getByRole('button', { name: 'Model selector' })); + + await waitFor(() => { + expect(screen.getByRole('button', { name: 'Send' })).toBeDisabled(); + expect(capturedModelSwitcherDisabled).toBe(true); + }); + + fireEvent.click(screen.getByRole('button', { name: 'Send' })); + expect(restoreMutateAsyncMock).not.toHaveBeenCalled(); + }); + it('prefills the sleeping draft, appends an optimistic transcript row, and resumes the task with a deferred prompt', async () => { const queryClient = new QueryClient({ defaultOptions: { diff --git a/apps/web/src/app/(sandbox)/task/[taskId]/WakeTaskInput.tsx b/apps/web/src/app/(sandbox)/task/[taskId]/WakeTaskInput.tsx index c15e8a651..08df1129f 100644 --- a/apps/web/src/app/(sandbox)/task/[taskId]/WakeTaskInput.tsx +++ b/apps/web/src/app/(sandbox)/task/[taskId]/WakeTaskInput.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useEffect, useState } from 'react'; +import { useCallback, useEffect, useState } from 'react'; import { toast } from 'sonner'; import { DEFAULT_MANAGED_DEPLOYMENT_ACCESS } from '@roomote/types'; @@ -16,9 +16,11 @@ import type { TaskRunDetail } from '@/lib/server'; import { cn } from '@/lib/utils'; import { useOptimisticPromptSubmission } from './prompt-input/useOptimisticPromptSubmission'; +import { TaskModelSwitcher } from './prompt-input/TaskModelSwitcher'; interface WakeTaskInputProps { - taskRun: Pick; + taskRun: Pick & + Partial>; initialPrompt?: string; embedded?: boolean; } @@ -34,6 +36,7 @@ export function WakeTaskInput({ } = useOptimisticPromptSubmission(); const [promptText, setPromptText] = useState(initialPrompt); const [sending, setSending] = useState(false); + const [modelSettingsPending, setModelSettingsPending] = useState(false); const { managedAccess = DEFAULT_MANAGED_DEPLOYMENT_ACCESS } = useAuthorizedUser(); const taskLaunchDisabledReason = getTaskLaunchDisabledReason(managedAccess); @@ -41,7 +44,11 @@ export function WakeTaskInput({ const restore = useRestoreTaskRunSnapshot({ onSuccess: () => setPromptText(''), }); - const isBusy = sending || restore.isPending; + const isBusy = sending || restore.isPending || modelSettingsPending; + const handleModelSettingsPendingChange = useCallback( + (pending: boolean) => setModelSettingsPending(pending), + [], + ); useEffect(() => { setPromptText(initialPrompt); @@ -142,6 +149,15 @@ export function WakeTaskInput({ animateContainer={false} submitWithMetaKey={false} submitIcon={promptText.trim().length === 0 ? : undefined} + tools={ + taskRun.harness === 'opencode-server' ? ( + + ) : undefined + } surface={embedded ? 'embedded' : 'default'} submitDisabledReason={taskLaunchDisabledReason} /> diff --git a/apps/web/src/app/(sandbox)/task/[taskId]/prompt-input/TaskModelSwitcher.tsx b/apps/web/src/app/(sandbox)/task/[taskId]/prompt-input/TaskModelSwitcher.tsx index 9502fc317..b40d14dc5 100644 --- a/apps/web/src/app/(sandbox)/task/[taskId]/prompt-input/TaskModelSwitcher.tsx +++ b/apps/web/src/app/(sandbox)/task/[taskId]/prompt-input/TaskModelSwitcher.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useEffect, useMemo, useState } from 'react'; +import { useEffect, useMemo, useRef, useState } from 'react'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { toast } from 'sonner'; @@ -60,9 +60,12 @@ type RoleSelection = { export function TaskModelSwitcher({ taskRun, disabled = false, + onPendingChange, }: { - taskRun: TaskRunDetail; + taskRun: Pick & + Partial>; disabled?: boolean; + onPendingChange?: (pending: boolean) => void; }) { const trpc = useTRPC(); const queryClient = useQueryClient(); @@ -73,6 +76,20 @@ export function TaskModelSwitcher({ const [localSelections, setLocalSelections] = useState< Partial> >({}); + const pendingOperationsRef = useRef(0); + + const startPendingOperation = () => { + pendingOperationsRef.current += 1; + onPendingChange?.(true); + }; + + const finishPendingOperation = () => { + pendingOperationsRef.current = Math.max( + 0, + pendingOperationsRef.current - 1, + ); + onPendingChange?.(pendingOperationsRef.current > 0); + }; const { data: roleDefaults } = useQuery( trpc.taskModels.roleDefaults.queryOptions(undefined, { enabled: open }), @@ -137,6 +154,7 @@ export function TaskModelSwitcher({ }); toast.error(error.message || 'Failed to update the model settings'); }, + onSettled: finishPendingOperation, }), ); @@ -148,6 +166,7 @@ export function TaskModelSwitcher({ const applyRoleSelection = (role: SwitcherRole, selection: RoleSelection) => { setLocalSelections((current) => ({ ...current, [role]: selection })); + startPendingOperation(); updateModelSelection.mutate({ taskId: taskRun.taskId, role, @@ -199,6 +218,7 @@ export function TaskModelSwitcher({ : 'Model'; const [resetting, setResetting] = useState(false); + const isPending = updateModelSelection.isPending || resetting; // Reset clears roles sequentially: firing the per-role mutations // concurrently would race their payload read-modify-writes (the server @@ -215,6 +235,7 @@ export function TaskModelSwitcher({ ]; const cleared: RoleSelection = { model: null, reasoningEffort: null }; + startPendingOperation(); setResetting(true); setLocalSelections( Object.fromEntries(rolesToClear.map((role) => [role, cleared])), @@ -238,6 +259,7 @@ export function TaskModelSwitcher({ ); } finally { setResetting(false); + finishPendingOperation(); void invalidateSession(); } }; @@ -401,7 +423,7 @@ export function TaskModelSwitcher({ size="sm" className="text-xs font-medium" onClick={() => void handleReset()} - disabled={disabled || resetting} + disabled={disabled || isPending} > Defaults diff --git a/apps/web/src/components/tasks/TaskPromptInput.client.test.tsx b/apps/web/src/components/tasks/TaskPromptInput.client.test.tsx index a361a166a..741717e9e 100644 --- a/apps/web/src/components/tasks/TaskPromptInput.client.test.tsx +++ b/apps/web/src/components/tasks/TaskPromptInput.client.test.tsx @@ -37,4 +37,19 @@ describe('TaskPromptInput', () => { expect(screen.getByRole('button', { name: 'Submit' })).toBeEnabled(); }); + + it('renders additional prompt tools', () => { + render( + {}} + onSubmit={() => {}} + placeholder="Describe a task" + tools={} + />, + ); + + expect(screen.getByRole('button', { name: 'Choose model' })).toBeVisible(); + }); }); diff --git a/apps/web/src/components/tasks/TaskPromptInput.tsx b/apps/web/src/components/tasks/TaskPromptInput.tsx index 069224adb..b53c9f6ba 100644 --- a/apps/web/src/components/tasks/TaskPromptInput.tsx +++ b/apps/web/src/components/tasks/TaskPromptInput.tsx @@ -112,6 +112,8 @@ type TaskPromptInputProps = { animateContainer?: boolean; /** Optional content rendered inside the prompt box, below the input. */ suggestion?: ReactNode; + /** Optional actions rendered alongside the attachment menu. */ + tools?: ReactNode; /** Optional reason that disables the submit button and explains why. */ submitDisabledReason?: string; /** When true, submit on Cmd/Ctrl+Enter instead of plain Enter. */ @@ -131,6 +133,7 @@ export function TaskPromptInput({ textareaMaxHeight, animateContainer = true, suggestion, + tools, submitDisabledReason, submitWithMetaKey = true, submitIcon, @@ -187,6 +190,7 @@ export function TaskPromptInput({ + {tools}
Date: Fri, 28 Aug 2026 17:41:22 +0000 Subject: [PATCH 2/4] feat: run task tools while waking tasks --- .../[taskId]/WakeTaskInput.client.test.tsx | 64 +++++++++++ .../(sandbox)/task/[taskId]/WakeTaskInput.tsx | 39 +++++-- .../sidebar-actions/TaskToolsButton.tsx | 108 ++++++++++-------- 3 files changed, 155 insertions(+), 56 deletions(-) diff --git a/apps/web/src/app/(sandbox)/task/[taskId]/WakeTaskInput.client.test.tsx b/apps/web/src/app/(sandbox)/task/[taskId]/WakeTaskInput.client.test.tsx index 9d7342d00..25d1f2c07 100644 --- a/apps/web/src/app/(sandbox)/task/[taskId]/WakeTaskInput.client.test.tsx +++ b/apps/web/src/app/(sandbox)/task/[taskId]/WakeTaskInput.client.test.tsx @@ -172,6 +172,27 @@ vi.mock('./prompt-input/TaskModelSwitcher', () => ({ }, })); +vi.mock('./sidebar-actions/TaskToolsButton', () => ({ + TaskToolsMenu: ({ + disabled, + onSelect, + }: { + disabled?: boolean; + onSelect: (actionId: 'simplify') => void; + }) => ( +
+ +
+ ), +})); + import { WakeTaskInput } from './WakeTaskInput'; function renderWithQueryClient(ui: React.ReactNode, queryClient: QueryClient) { @@ -277,6 +298,49 @@ describe('WakeTaskInput', () => { expect(restoreMutateAsyncMock).not.toHaveBeenCalled(); }); + it('runs a task tool as the wake-up prompt', async () => { + const queryClient = new QueryClient({ + defaultOptions: { + queries: { retry: false }, + }, + }); + preparePromptAttachmentsMock.mockImplementation( + ({ text }: { text: string }) => Promise.resolve({ text }), + ); + + renderWithQueryClient( + , + queryClient, + ); + + fireEvent.click( + screen.getByRole('button', { name: 'Simplify changed code' }), + ); + + await waitFor(() => { + expect(restoreMutateAsyncMock).toHaveBeenCalledWith({ + sourceSnapshotId: 'snap-42', + sourceRunId: 42, + clientMessageId: expect.any(String), + resumePrompt: '$simplify', + }); + }); + + expect(appendOptimisticAcpEventMock).toHaveBeenCalledWith( + expect.objectContaining({ + text: '$simplify', + }), + ); + }); + it('prefills the sleeping draft, appends an optimistic transcript row, and resumes the task with a deferred prompt', async () => { const queryClient = new QueryClient({ defaultOptions: { diff --git a/apps/web/src/app/(sandbox)/task/[taskId]/WakeTaskInput.tsx b/apps/web/src/app/(sandbox)/task/[taskId]/WakeTaskInput.tsx index 08df1129f..8df87865a 100644 --- a/apps/web/src/app/(sandbox)/task/[taskId]/WakeTaskInput.tsx +++ b/apps/web/src/app/(sandbox)/task/[taskId]/WakeTaskInput.tsx @@ -3,7 +3,11 @@ import { useCallback, useEffect, useState } from 'react'; import { toast } from 'sonner'; -import { DEFAULT_MANAGED_DEPLOYMENT_ACCESS } from '@roomote/types'; +import { + DEFAULT_MANAGED_DEPLOYMENT_ACCESS, + getTaskToolInvocation, + type TaskToolActionId, +} from '@roomote/types'; import type { PromptInputMessage } from '@/components/ai-elements'; import { TaskPromptInput } from '@/components/tasks'; @@ -17,10 +21,12 @@ import { cn } from '@/lib/utils'; import { useOptimisticPromptSubmission } from './prompt-input/useOptimisticPromptSubmission'; import { TaskModelSwitcher } from './prompt-input/TaskModelSwitcher'; +import { TaskToolsMenu } from './sidebar-actions/TaskToolsButton'; +import { shouldShowTaskToolsActions } from './sidebar-actions/utils'; interface WakeTaskInputProps { taskRun: Pick & - Partial>; + Partial>; initialPrompt?: string; embedded?: boolean; } @@ -133,6 +139,13 @@ export function WakeTaskInput({ } }; + const handleTaskToolSelect = (actionId: TaskToolActionId) => { + void handleSubmit({ + text: getTaskToolInvocation(actionId, taskRun.harness), + files: [], + }); + }; + const input = (
: undefined} tools={ - taskRun.harness === 'opencode-server' ? ( - - ) : undefined + <> + {shouldShowTaskToolsActions(taskRun.payloadKind) && ( + + )} + {taskRun.harness === 'opencode-server' && ( + + )} + } surface={embedded ? 'embedded' : 'default'} submitDisabledReason={taskLaunchDisabledReason} diff --git a/apps/web/src/app/(sandbox)/task/[taskId]/sidebar-actions/TaskToolsButton.tsx b/apps/web/src/app/(sandbox)/task/[taskId]/sidebar-actions/TaskToolsButton.tsx index ca0df70be..debd60615 100644 --- a/apps/web/src/app/(sandbox)/task/[taskId]/sidebar-actions/TaskToolsButton.tsx +++ b/apps/web/src/app/(sandbox)/task/[taskId]/sidebar-actions/TaskToolsButton.tsx @@ -3,6 +3,8 @@ import { Fragment, memo } from 'react'; import { toast } from 'sonner'; +import type { TaskToolActionId } from '@roomote/types'; + import { useUser } from '@/hooks/useUser'; import { generateClientUuid } from '@/lib/client-uuid'; import { useTRPCClient } from '@/trpc/client'; @@ -31,6 +33,44 @@ import { TASK_TOOL_CATALOG } from '../task-tools'; import { type SidebarActionBaseProps } from './types'; import { isTaskRunAsleep } from './utils'; +export function TaskToolsMenu({ + onSelect, + disabled = false, +}: { + onSelect: (actionId: TaskToolActionId) => void | Promise; + disabled?: boolean; +}) { + return ( + + + + + + + + Task Tools + {TASK_TOOL_CATALOG.map(({ actionId, label, separator, icon: Icon }) => ( + + {separator && } + void onSelect(actionId)} + className="flex items-center gap-2 cursor-pointer min-w-64" + > + + {label} + + + ))} + + + ); +} + function TaskToolsButtonBase({ taskRun, }: Pick) { @@ -52,54 +92,28 @@ function TaskToolsButtonBase({ return null; } - const content = ( - <> - Task Tools - {TASK_TOOL_CATALOG.map(({ actionId, label, separator, icon: Icon }) => ( - - {separator && } - { - const clientMessageId = generateClientUuid(); - - try { - await trpcClient.sandboxSession.sendPrompt.mutate({ - taskId: taskRun.taskId, - taskTool: { actionId }, - source: 'web', - clientMessageId, - userImageUrl, - }); - } catch (error) { - toast.error( - error instanceof Error - ? error.message - : 'Failed to send task tool.', - ); - } - }} - className="flex items-center gap-2 cursor-pointer min-w-64" - > - - {label} - - - ))} - - ); - return ( - - - - - - - {content} - + { + const clientMessageId = generateClientUuid(); + + try { + await trpcClient.sandboxSession.sendPrompt.mutate({ + taskId: taskRun.taskId, + taskTool: { actionId }, + source: 'web', + clientMessageId, + userImageUrl, + }); + } catch (error) { + toast.error( + error instanceof Error + ? error.message + : 'Failed to send task tool.', + ); + } + }} + /> ); } From c9dd7a8ef4ba24a25e1cd07b64282203901e3b2c Mon Sep 17 00:00:00 2001 From: "@tomny-dev" <20028678+tomny-dev@users.noreply.github.com> Date: Fri, 28 Aug 2026 18:20:49 +0000 Subject: [PATCH 3/4] fix: disable wake tools in read-only mode --- .../[taskId]/WakeTaskInput.client.test.tsx | 60 +++++++++++++++---- .../(sandbox)/task/[taskId]/WakeTaskInput.tsx | 2 +- 2 files changed, 51 insertions(+), 11 deletions(-) diff --git a/apps/web/src/app/(sandbox)/task/[taskId]/WakeTaskInput.client.test.tsx b/apps/web/src/app/(sandbox)/task/[taskId]/WakeTaskInput.client.test.tsx index 25d1f2c07..7dd35aa38 100644 --- a/apps/web/src/app/(sandbox)/task/[taskId]/WakeTaskInput.client.test.tsx +++ b/apps/web/src/app/(sandbox)/task/[taskId]/WakeTaskInput.client.test.tsx @@ -9,6 +9,7 @@ const { removeOptimisticQueuedMessageMock, restoreMutateAsyncMock, toastErrorMock, + useAuthorizedUserMock, useSandboxCurrentUserInfoMock, } = vi.hoisted(() => ({ appendOptimisticAcpEventMock: vi.fn(), @@ -18,6 +19,7 @@ const { removeOptimisticQueuedMessageMock: vi.fn(), restoreMutateAsyncMock: vi.fn(), toastErrorMock: vi.fn(), + useAuthorizedUserMock: vi.fn(), useSandboxCurrentUserInfoMock: vi.fn(), })); @@ -55,16 +57,7 @@ vi.mock('@/hooks/snapshots', () => ({ })); vi.mock('@/hooks/useUser', () => ({ - useAuthorizedUser: () => ({ - managedAccess: { - state: 'active', - reason: null, - revision: 1, - effectiveAt: '2026-01-01T00:00:00.000Z', - restrictionStartsAt: null, - remediationUrl: null, - }, - }), + useAuthorizedUser: useAuthorizedUserMock, })); vi.mock('@/trpc/client', () => ({ @@ -219,6 +212,16 @@ describe('WakeTaskInput', () => { runId: 84, taskId: 'task-42', }); + useAuthorizedUserMock.mockReturnValue({ + managedAccess: { + state: 'active', + reason: null, + revision: 1, + effectiveAt: '2026-01-01T00:00:00.000Z', + restrictionStartsAt: null, + remediationUrl: null, + }, + }); useSandboxCurrentUserInfoMock.mockReturnValue(null); }); @@ -341,6 +344,43 @@ describe('WakeTaskInput', () => { ); }); + it('disables task tools when managed access blocks waking the task', () => { + const queryClient = new QueryClient({ + defaultOptions: { + queries: { retry: false }, + }, + }); + useAuthorizedUserMock.mockReturnValue({ + managedAccess: { + state: 'read_only', + reason: 'billing_required', + revision: 2, + effectiveAt: '2026-01-02T00:00:00.000Z', + restrictionStartsAt: null, + remediationUrl: null, + }, + }); + + renderWithQueryClient( + , + queryClient, + ); + + expect(screen.getByRole('button', { name: 'Task Tools' })).toBeDisabled(); + fireEvent.click( + screen.getByRole('button', { name: 'Simplify changed code' }), + ); + expect(restoreMutateAsyncMock).not.toHaveBeenCalled(); + }); + it('prefills the sleeping draft, appends an optimistic transcript row, and resumes the task with a deferred prompt', async () => { const queryClient = new QueryClient({ defaultOptions: { diff --git a/apps/web/src/app/(sandbox)/task/[taskId]/WakeTaskInput.tsx b/apps/web/src/app/(sandbox)/task/[taskId]/WakeTaskInput.tsx index 8df87865a..9ad58ab52 100644 --- a/apps/web/src/app/(sandbox)/task/[taskId]/WakeTaskInput.tsx +++ b/apps/web/src/app/(sandbox)/task/[taskId]/WakeTaskInput.tsx @@ -167,7 +167,7 @@ export function WakeTaskInput({ {shouldShowTaskToolsActions(taskRun.payloadKind) && ( )} {taskRun.harness === 'opencode-server' && ( From d12bc99db6ce4c8890529a4df76d4339a0bc0c88 Mon Sep 17 00:00:00 2001 From: "@tomny-dev" <20028678+tomny-dev@users.noreply.github.com> Date: Fri, 28 Aug 2026 19:51:28 +0000 Subject: [PATCH 4/4] refactor: simplify wake model state --- .../web/src/app/(sandbox)/task/[taskId]/WakeTaskInput.tsx | 8 ++------ .../task/[taskId]/prompt-input/TaskModelSwitcher.tsx | 3 +-- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/apps/web/src/app/(sandbox)/task/[taskId]/WakeTaskInput.tsx b/apps/web/src/app/(sandbox)/task/[taskId]/WakeTaskInput.tsx index 9ad58ab52..b160792a3 100644 --- a/apps/web/src/app/(sandbox)/task/[taskId]/WakeTaskInput.tsx +++ b/apps/web/src/app/(sandbox)/task/[taskId]/WakeTaskInput.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useCallback, useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { toast } from 'sonner'; import { @@ -51,10 +51,6 @@ export function WakeTaskInput({ onSuccess: () => setPromptText(''), }); const isBusy = sending || restore.isPending || modelSettingsPending; - const handleModelSettingsPendingChange = useCallback( - (pending: boolean) => setModelSettingsPending(pending), - [], - ); useEffect(() => { setPromptText(initialPrompt); @@ -174,7 +170,7 @@ export function WakeTaskInput({ )} diff --git a/apps/web/src/app/(sandbox)/task/[taskId]/prompt-input/TaskModelSwitcher.tsx b/apps/web/src/app/(sandbox)/task/[taskId]/prompt-input/TaskModelSwitcher.tsx index b40d14dc5..26a615475 100644 --- a/apps/web/src/app/(sandbox)/task/[taskId]/prompt-input/TaskModelSwitcher.tsx +++ b/apps/web/src/app/(sandbox)/task/[taskId]/prompt-input/TaskModelSwitcher.tsx @@ -218,7 +218,6 @@ export function TaskModelSwitcher({ : 'Model'; const [resetting, setResetting] = useState(false); - const isPending = updateModelSelection.isPending || resetting; // Reset clears roles sequentially: firing the per-role mutations // concurrently would race their payload read-modify-writes (the server @@ -423,7 +422,7 @@ export function TaskModelSwitcher({ size="sm" className="text-xs font-medium" onClick={() => void handleReset()} - disabled={disabled || isPending} + disabled={disabled || updateModelSelection.isPending || resetting} > Defaults