From a2f1b50e9261a2c77891a8e5dd71ee37310b4370 Mon Sep 17 00:00:00 2001 From: James Grugett Date: Tue, 5 May 2026 22:09:19 -0700 Subject: [PATCH 1/4] Add Freebuff session restart action --- cli/src/components/session-ended-banner.tsx | 77 +++++++++++++++------ cli/src/hooks/use-send-message.ts | 5 +- 2 files changed, 60 insertions(+), 22 deletions(-) diff --git a/cli/src/components/session-ended-banner.tsx b/cli/src/components/session-ended-banner.tsx index 19b247f11..7f3e30d85 100644 --- a/cli/src/components/session-ended-banner.tsx +++ b/cli/src/components/session-ended-banner.tsx @@ -3,7 +3,10 @@ import { useKeyboard } from '@opentui/react' import React, { useCallback, useState } from 'react' import { Button } from './button' -import { returnToFreebuffLanding } from '../hooks/use-freebuff-session' +import { + refreshFreebuffSession, + returnToFreebuffLanding, +} from '../hooks/use-freebuff-session' import { useTheme } from '../hooks/use-theme' import { BORDER_CHARS } from '../utils/ui-constants' @@ -25,36 +28,46 @@ export const SessionEndedBanner: React.FC = ({ isStreaming, }) => { const theme = useTheme() - const [rejoining, setRejoining] = useState(false) + const [pendingAction, setPendingAction] = useState< + 'waiting-room' | 'same-chat' | null + >(null) - // While a request is still streaming, rejoin is disabled: it would + // While a request is still streaming, restart is disabled: it would // unmount and abort the in-flight agent run. The promise is "we // let the agent finish" — honoring that means Enter does nothing until // the stream ends or the user hits Esc. - const canRejoin = !isStreaming && !rejoining - const rejoin = useCallback(() => { - if (!canRejoin) return - setRejoining(true) + const canRestart = !isStreaming && pendingAction === null + const pickNewModel = useCallback(() => { + if (!canRestart) return + setPendingAction('waiting-room') // Drop back to the landing picker (status: 'none') so the user picks a // model and hits Enter again to commit, instead of being silently // re-queued. app.tsx swaps us into on the - // transition, unmounting this banner — no need to clear `rejoining` on + // transition, unmounting this banner — no need to clear the pending state on // success. returnToFreebuffLanding({ resetChat: true }).catch(() => - setRejoining(false), + setPendingAction(null), ) - }, [canRejoin]) + }, [canRestart]) + + const startSameChatSession = useCallback(() => { + if (!canRestart) return + setPendingAction('same-chat') + // Re-POST with the currently selected model and keep the chat/run state + // intact so the next prompt continues the same conversation. + refreshFreebuffSession().catch(() => setPendingAction(null)) + }, [canRestart]) useKeyboard( useCallback( (key: KeyEvent) => { - if (!canRejoin) return + if (!canRestart) return if (key.name === 'return' || key.name === 'enter') { key.preventDefault?.() - rejoin() + startSameChatSession() } }, - [rejoin, canRejoin], + [startSameChatSession, canRestart], ), ) @@ -83,14 +96,36 @@ export const SessionEndedBanner: React.FC = ({ Agent is wrapping up. Rejoin the wait room after it's finished. ) : ( - + + + + )} ) diff --git a/cli/src/hooks/use-send-message.ts b/cli/src/hooks/use-send-message.ts index cdb67f255..cd66a8234 100644 --- a/cli/src/hooks/use-send-message.ts +++ b/cli/src/hooks/use-send-message.ts @@ -138,7 +138,9 @@ export const useSendMessage = ({ setRunState, setIsRetrying, } = useChatStore.getState() - const previousRunStateRef = useRef(null) + const previousRunStateRef = useRef( + useChatStore.getState().runState, + ) // Memoize stream controller to maintain referential stability across renders const streamRefsRef = useRef Date: Tue, 5 May 2026 22:29:32 -0700 Subject: [PATCH 2/4] Fix Freebuff fallback session restart --- cli/src/hooks/use-freebuff-session.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cli/src/hooks/use-freebuff-session.ts b/cli/src/hooks/use-freebuff-session.ts index 332ab6450..9d3c44e7b 100644 --- a/cli/src/hooks/use-freebuff-session.ts +++ b/cli/src/hooks/use-freebuff-session.ts @@ -460,7 +460,10 @@ export function useFreebuffSession(): UseFreebuffSessionResult { useFreebuffModelStore .getState() .setSelectedModel(FALLBACK_FREEBUFF_MODEL_ID) - nextMethod = 'GET' + // The unavailable response came from a POST attempt. Re-POST with + // the fallback model; a GET would only redisplay the old ended row + // and leave the restart banner stuck in its pending state. + nextMethod = 'POST' schedule(0) return } From c923f05ebe16539c225821a5058cc42fd28072db Mon Sep 17 00:00:00 2001 From: James Grugett Date: Wed, 6 May 2026 15:24:05 -0700 Subject: [PATCH 3/4] Refine Freebuff ended session actions --- cli/src/components/session-ended-banner.tsx | 44 ++++++++++++++++----- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/cli/src/components/session-ended-banner.tsx b/cli/src/components/session-ended-banner.tsx index 7f3e30d85..9257f1699 100644 --- a/cli/src/components/session-ended-banner.tsx +++ b/cli/src/components/session-ended-banner.tsx @@ -21,8 +21,8 @@ interface SessionEndedBannerProps { /** * Replaces the chat input when the freebuff session has ended. Captures - * Enter to re-queue the user; Esc keeps falling through to the global - * stream-interrupt handler so in-flight work can be cancelled. + * Enter to start a new same-chat session. Esc returns to model selection + * once no in-flight work needs the global stream-interrupt handler. */ export const SessionEndedBanner: React.FC = ({ isStreaming, @@ -65,9 +65,14 @@ export const SessionEndedBanner: React.FC = ({ if (key.name === 'return' || key.name === 'enter') { key.preventDefault?.() startSameChatSession() + return + } + if (key.name === 'escape') { + key.preventDefault?.() + pickNewModel() } }, - [startSameChatSession, canRestart], + [startSameChatSession, pickNewModel, canRestart], ), ) @@ -96,7 +101,14 @@ export const SessionEndedBanner: React.FC = ({ Agent is wrapping up. Rejoin the wait room after it's finished. ) : ( - + - From 5db9aa1c0afe51e59088d5f9f38e8e178ebf895d Mon Sep 17 00:00:00 2001 From: James Grugett Date: Wed, 6 May 2026 15:29:30 -0700 Subject: [PATCH 4/4] Update Freebuff ended session copy --- cli/src/components/session-ended-banner.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/src/components/session-ended-banner.tsx b/cli/src/components/session-ended-banner.tsx index 9257f1699..7482cbdf5 100644 --- a/cli/src/components/session-ended-banner.tsx +++ b/cli/src/components/session-ended-banner.tsx @@ -121,7 +121,7 @@ export const SessionEndedBanner: React.FC = ({ > {pendingAction === 'same-chat' ? 'Starting…' - : 'Press Enter to continue with a new session'} + : 'Press Enter to continue in a new session'} @@ -148,7 +148,7 @@ export const SessionEndedBanner: React.FC = ({ > {pendingAction === 'waiting-room' ? 'Opening model selection…' - : 'Back to model selection (ESC)'} + : 'Change model (ESC)'}