From 1135e9342e4516499723eda8e6a8d38e691be3dc Mon Sep 17 00:00:00 2001 From: Johannes du Plessis Date: Wed, 10 Jun 2026 13:27:04 -0700 Subject: [PATCH] feat: add cancel run button to agent UI (#1485) * feat: add cancel run button to agent UI Wire the existing cancel-thread mutation into the agent thread view by adding a stop button to the prompt bar that appears while a run is active. Co-authored-by: open-swe[bot] * fix: preserve thread messages when cancel response omits them --------- Co-authored-by: open-swe[bot] --- ui/src/components/agents/AgentThreadView.tsx | 17 ++++++++- .../agents/ported/CloudPromptBar.tsx | 37 ++++++++++++++++++- ui/src/lib/agents/queries.ts | 12 +++++- 3 files changed, 63 insertions(+), 3 deletions(-) diff --git a/ui/src/components/agents/AgentThreadView.tsx b/ui/src/components/agents/AgentThreadView.tsx index 296bb7b1..f867d137 100644 --- a/ui/src/components/agents/AgentThreadView.tsx +++ b/ui/src/components/agents/AgentThreadView.tsx @@ -6,7 +6,11 @@ import type { AgentThread, ImageChunk, Message } from "@/lib/agents/types" import type { ModelSelection } from "@/lib/agents/useModelOptions" import { AgentPromptBar } from "@/components/agents/AgentPromptBar" import { MessageView } from "@/components/agents/ported" -import { agentThreadKeys, useSendAgentMessage } from "@/lib/agents/queries" +import { + agentThreadKeys, + useCancelAgentThread, + useSendAgentMessage, +} from "@/lib/agents/queries" import { addPendingPrompt, dropPendingPrompts, @@ -55,6 +59,7 @@ function isPendingPromptConfirmed( export function AgentThreadView({ thread }: AgentThreadViewProps) { const queryClient = useQueryClient() const sendMessage = useSendAgentMessage(thread.id) + const cancelThread = useCancelAgentThread(thread.id) useAgentThreadStream(thread.id, thread.status === "running") const [pendingPrompts, setPendingPrompts] = useState>( () => getPendingPrompts(thread.id) @@ -134,6 +139,10 @@ export function AgentThreadView({ thread }: AgentThreadViewProps) { ] ) + const handleCancel = useCallback(() => { + cancelThread.mutate() + }, [cancelThread]) + const displayMessages = useMemo>(() => { if (pendingPrompts.length === 0) return thread.messages const baseTimestamp = new Date().toISOString() @@ -177,6 +186,9 @@ export function AgentThreadView({ thread }: AgentThreadViewProps) { compact busy={isStreaming} disabled={sendMessage.isPending} + canCancel={hasActiveRun} + cancelling={cancelThread.isPending} + onCancel={handleCancel} onSubmit={handleSubmit} models={models} selection={activeSelection} @@ -196,6 +208,9 @@ export function AgentThreadView({ thread }: AgentThreadViewProps) { compact busy={isStreaming} disabled={sendMessage.isPending} + canCancel={hasActiveRun} + cancelling={cancelThread.isPending} + onCancel={handleCancel} onSubmit={handleSubmit} models={models} selection={activeSelection} diff --git a/ui/src/components/agents/ported/CloudPromptBar.tsx b/ui/src/components/agents/ported/CloudPromptBar.tsx index 26e4585b..f60e2a40 100644 --- a/ui/src/components/agents/ported/CloudPromptBar.tsx +++ b/ui/src/components/agents/ported/CloudPromptBar.tsx @@ -1,4 +1,11 @@ -import { ArrowUp, ChevronDown, ImagePlus, LoaderCircle, X } from "lucide-react" +import { + ArrowUp, + ChevronDown, + ImagePlus, + LoaderCircle, + Square, + X, +} from "lucide-react" import { memo, useCallback, @@ -31,6 +38,10 @@ export interface CloudPromptBarProps { compact?: boolean disabled?: boolean busy?: boolean + /** When true, a stop button is shown next to send to cancel the active run. */ + canCancel?: boolean + cancelling?: boolean + onCancel?: () => void onSubmit?: (value: string, images: Array) => void models?: Array selection?: ModelSelection | null @@ -73,6 +84,9 @@ export const CloudPromptBar = memo(function CloudPromptBarComponent({ compact = false, disabled = false, busy = false, + canCancel = false, + cancelling = false, + onCancel, onSubmit, models = [], selection = null, @@ -346,6 +360,27 @@ export const CloudPromptBar = memo(function CloudPromptBarComponent({ + {canCancel && ( + + )} +