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) <noreply@anthropic.com>
This commit is contained in:
paoloanznandClaude Opus 4.6 committed 2026-03-31 17:23:08 +02:00
1 parent 61a23c19bf
commit fcf5ab7dbb
3 files changed
+162

No files matched your search

+114
View File
@@ -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<React.SetStateAction<Message[]>>
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<RemoteAgentTaskState>(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 (
<Dialog
title="Ultraplan ready"
onCancel={() => handleChoice('dismiss')}
>
<Box flexDirection="column" gap={1}>
<Box
flexDirection="column"
borderStyle="single"
borderColor="gray"
paddingX={1}
height={Math.min(displayPlan.split('\n').length + 2, 20)}
overflow="hidden"
>
<Text>{displayPlan}</Text>
</Box>
</Box>
<Select
options={[
{
value: 'execute' as const,
label: 'Execute plan here',
description:
'Send the plan to Claude for execution in this session',
},
{
value: 'dismiss' as const,
label: 'Dismiss',
description: 'Discard the plan',
},
]}
onChange={(value: UltraplanChoice) => handleChoice(value)}
/>
</Dialog>
)
}
+45
View File
@@ -0,0 +1,45 @@
import React from 'react'
import { Box, Text } from '../ink.js'
import { Select } from './CustomSelect/index.js'
import { Dialog } from './design-system/Dialog.js'
import { CCR_TERMS_URL } from '../commands/ultraplan.js'
type UltraplanLaunchChoice = 'launch' | 'cancel'
type Props = {
onChoice: (
choice: UltraplanLaunchChoice,
opts?: { disconnectedBridge?: boolean },
) => void
}
export function UltraplanLaunchDialog({ onChoice }: Props): React.ReactNode {
return (
<Dialog
title="Launch ultraplan?"
onCancel={() => onChoice('cancel')}
>
<Box flexDirection="column" gap={1}>
<Text>
This will start a remote Claude Code session on the web to draft an
advanced plan using Opus. The plan typically takes 10–30 minutes.
Your terminal stays free while it works.
</Text>
<Text dimColor>Terms: {CCR_TERMS_URL}</Text>
</Box>
<Select
options={[
{
value: 'launch' as const,
label: 'Launch ultraplan',
},
{
value: 'cancel' as const,
label: 'Cancel',
},
]}
onChange={(value: UltraplanLaunchChoice) => onChoice(value)}
/>
</Dialog>
)
}
+3
View File
@@ -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';