From fcf5ab7dbbb0271df8c2fa579da59d9518d0b931 Mon Sep 17 00:00:00 2001 From: paoloanzn Date: Tue, 31 Mar 2026 17:23:08 +0200 Subject: [PATCH] feat: add UltraplanLaunchDialog and UltraplanChoiceDialog components These components were referenced in REPL.tsx but never defined, causing a runtime crash when ultraplan tried to launch. Added: - UltraplanLaunchDialog: pre-launch confirmation with terms/cancel - UltraplanChoiceDialog: post-plan approval with execute-here/dismiss - Conditional requires in REPL.tsx gated on feature('ULTRAPLAN') Co-Authored-By: Claude Opus 4.6 (1M context) --- src/components/UltraplanChoiceDialog.tsx | 114 +++++++++++++++++++++++ src/components/UltraplanLaunchDialog.tsx | 45 +++++++++ src/screens/REPL.tsx | 3 + 3 files changed, 162 insertions(+) create mode 100644 src/components/UltraplanChoiceDialog.tsx create mode 100644 src/components/UltraplanLaunchDialog.tsx diff --git a/src/components/UltraplanChoiceDialog.tsx b/src/components/UltraplanChoiceDialog.tsx new file mode 100644 index 0000000..02eb5d1 --- /dev/null +++ b/src/components/UltraplanChoiceDialog.tsx @@ -0,0 +1,114 @@ +import React, { useCallback } from 'react' +import { Box, Text } from '../ink.js' +import { Select } from './CustomSelect/index.js' +import { Dialog } from './design-system/Dialog.js' +import type { AppState } from '../state/AppStateStore.js' +import { useSetAppState } from '../state/AppState.js' +import type { Message } from '../types/message.js' +import type { RemoteAgentTaskState } from '../tasks/RemoteAgentTask/RemoteAgentTask.js' +import type { FileStateCache } from '../utils/fileStateCache.js' +import { + createUserMessage, + createSystemMessage, + prepareUserContent, +} from '../utils/messages.js' +import { updateTaskState } from '../utils/task/framework.js' +import { archiveRemoteSession } from '../utils/teleport.js' +import { logForDebugging } from '../utils/debug.js' + +type UltraplanChoice = 'execute' | 'dismiss' + +type Props = { + plan: string + sessionId: string + taskId: string + setMessages: React.Dispatch> + readFileState: FileStateCache + getAppState: () => AppState + setConversationId: (id: string) => void +} + +export function UltraplanChoiceDialog({ + plan, + sessionId, + taskId, + setMessages, +}: Props): React.ReactNode { + const setAppState = useSetAppState() + + const handleChoice = useCallback( + (choice: UltraplanChoice) => { + if (choice === 'execute') { + setMessages(prev => [ + ...prev, + createSystemMessage( + 'Ultraplan approved. Executing the following plan:', + 'info', + ), + createUserMessage({ + content: prepareUserContent({ inputString: plan }), + }), + ]) + } + + // Mark task completed + updateTaskState(taskId, setAppState, t => + t.status !== 'running' + ? t + : { ...t, status: 'completed', endTime: Date.now() }, + ) + + // Clear ultraplan state + setAppState(prev => ({ + ...prev, + ultraplanPendingChoice: undefined, + ultraplanSessionUrl: undefined, + })) + + // Archive the remote session + void archiveRemoteSession(sessionId).catch(e => + logForDebugging(`ultraplan choice archive failed: ${String(e)}`), + ) + }, + [plan, sessionId, taskId, setMessages, setAppState], + ) + + const displayPlan = + plan.length > 2000 ? plan.slice(0, 2000) + '\n\n... (truncated)' : plan + + return ( + handleChoice('dismiss')} + > + + + {displayPlan} + + + onChoice(value)} + /> + + ) +} diff --git a/src/screens/REPL.tsx b/src/screens/REPL.tsx index fe78fa5..5408528 100644 --- a/src/screens/REPL.tsx +++ b/src/screens/REPL.tsx @@ -270,6 +270,9 @@ import type { HookProgress } from '../types/hooks.js'; import { TungstenLiveMonitor } from '../tools/TungstenTool/TungstenLiveMonitor.js'; /* eslint-disable @typescript-eslint/no-require-imports */ const WebBrowserPanelModule = feature('WEB_BROWSER_TOOL') ? require('../tools/WebBrowserTool/WebBrowserPanel.js') as typeof import('../tools/WebBrowserTool/WebBrowserPanel.js') : null; +const UltraplanLaunchDialog: typeof import('../components/UltraplanLaunchDialog.js').UltraplanLaunchDialog = feature('ULTRAPLAN') ? require('../components/UltraplanLaunchDialog.js').UltraplanLaunchDialog : () => null; +const UltraplanChoiceDialog: typeof import('../components/UltraplanChoiceDialog.js').UltraplanChoiceDialog = feature('ULTRAPLAN') ? require('../components/UltraplanChoiceDialog.js').UltraplanChoiceDialog : () => null; +const launchUltraplan: typeof import('../commands/ultraplan.js').launchUltraplan = feature('ULTRAPLAN') ? require('../commands/ultraplan.js').launchUltraplan : () => Promise.resolve(''); /* eslint-enable @typescript-eslint/no-require-imports */ import { IssueFlagBanner } from '../components/PromptInput/IssueFlagBanner.js'; import { useIssueFlagBanner } from '../hooks/useIssueFlagBanner.js';