From b0cfa2cfad94600b0f7febb04dede841ce9aa7ad Mon Sep 17 00:00:00 2001 From: Johannes du Plessis Date: Mon, 8 Jun 2026 14:35:21 -0700 Subject: [PATCH] feat: show sandbox setup status (#1455) * feat: show sandbox setup status Co-authored-by: open-swe[bot] * fix: avoid sandbox setup status for queued follow-ups --------- Co-authored-by: open-swe[bot] --- ui/src/components/agents/AgentThreadView.tsx | 3 ++ .../components/agents/ported/MessageView.tsx | 32 +++++++++++++++---- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/ui/src/components/agents/AgentThreadView.tsx b/ui/src/components/agents/AgentThreadView.tsx index 805f20c3..ae9a67d3 100644 --- a/ui/src/components/agents/AgentThreadView.tsx +++ b/ui/src/components/agents/AgentThreadView.tsx @@ -120,6 +120,8 @@ export function AgentThreadView({ thread }: AgentThreadViewProps) { const hasMessages = displayMessages.length > 0 const hasActiveRun = thread.status === "running" const isStreaming = hasActiveRun || pendingPrompts.length > 0 + const settingUpSandbox = + hasActiveRun && thread.messages.length === 0 && pendingPrompts.length > 0 return (
@@ -129,6 +131,7 @@ export function AgentThreadView({ thread }: AgentThreadViewProps) {
diff --git a/ui/src/components/agents/ported/MessageView.tsx b/ui/src/components/agents/ported/MessageView.tsx index 64541f25..ca9e211e 100644 --- a/ui/src/components/agents/ported/MessageView.tsx +++ b/ui/src/components/agents/ported/MessageView.tsx @@ -297,6 +297,7 @@ export type MessageViewScrollControl = { interface MessageViewProps extends ApprovalCallbacks { messages: Message[]; isStreaming: boolean; + settingUpSandbox?: boolean; project?: Project | null; contentWidthClass?: string; /** Horizontal padding on centered content (scroll track stays edge-to-edge). */ @@ -654,12 +655,19 @@ const MessageBubble = memo(function MessageBubble({ ); }); -function ThinkingSpinner({ isStreaming }: { isStreaming: boolean }) { +function ThinkingSpinner({ + isStreaming, + settingUpSandbox, +}: { + isStreaming: boolean; + settingUpSandbox: boolean; +}) { const [textIdx, setTextIdx] = useState(0); const [done, setDone] = useState<{ past: string; elapsed: string } | null>(null); const startTimeRef = useRef(0); const textIdxRef = useRef(textIdx); const wasStreamingRef = useRef(false); + const settingUpSandboxRef = useRef(settingUpSandbox); textIdxRef.current = textIdx; useEffect(() => { @@ -670,18 +678,24 @@ function ThinkingSpinner({ isStreaming }: { isStreaming: boolean }) { setDone(null); } else if (wasStreamingRef.current) { setDone({ - past: BUSY_TEXTS[textIdxRef.current].past, + past: settingUpSandboxRef.current + ? "Set up sandbox" + : BUSY_TEXTS[textIdxRef.current].past, elapsed: formatElapsed(Date.now() - startTimeRef.current), }); } }, [isStreaming]); useEffect(() => { - if (!isStreaming) return; + settingUpSandboxRef.current = settingUpSandbox; + }, [settingUpSandbox]); + + useEffect(() => { + if (!isStreaming || settingUpSandbox) return; const BUSY_TEXT_ROTATE_INTERVAL_MS = 12000; const id = setInterval(() => setTextIdx((i) => (i + 1) % BUSY_TEXTS.length), BUSY_TEXT_ROTATE_INTERVAL_MS); return () => clearInterval(id); - }, [isStreaming]); + }, [isStreaming, settingUpSandbox]); if (!isStreaming && !done) return null; @@ -696,7 +710,9 @@ function ThinkingSpinner({ isStreaming }: { isStreaming: boolean }) { return (
- {BUSY_TEXTS[textIdx].present} + + {settingUpSandbox ? "Setting up sandbox..." : BUSY_TEXTS[textIdx].present} +
); } @@ -706,6 +722,7 @@ const BOTTOM_LOCK_THRESHOLD_PX = 24; export const MessageView = memo(function MessageView({ messages, isStreaming, + settingUpSandbox = false, project, contentWidthClass = "max-w-[42rem]", contentPaddingClass = "px-6", @@ -885,7 +902,10 @@ export const MessageView = memo(function MessageView({ onOpenDiff={onOpenDiff} /> ))} - +