diff --git a/apps/web/src/components/cloud-agent-next/ErrorBanner.tsx b/apps/web/src/components/cloud-agent-next/ErrorBanner.tsx deleted file mode 100644 index 1cc6b4bbe09..00000000000 --- a/apps/web/src/components/cloud-agent-next/ErrorBanner.tsx +++ /dev/null @@ -1,45 +0,0 @@ -'use client'; - -import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; -import { Button } from '@/components/ui/button'; -import { AlertCircle, X } from 'lucide-react'; - -type ErrorBannerProps = { - title?: string; - message: string; - onRetry?: () => void; - onDismiss?: () => void; -}; - -export function ErrorBanner({ title = 'Error', message, onRetry, onDismiss }: ErrorBannerProps) { - return ( - - - {title} - -

{message}

-
- {onRetry && ( - - )} - {onDismiss && ( - - )} -
-
- {onDismiss && ( - - )} -
- ); -} diff --git a/apps/web/src/components/cloud-agent-next/OrgContextModal.tsx b/apps/web/src/components/cloud-agent-next/OrgContextModal.tsx deleted file mode 100644 index e5da1788af5..00000000000 --- a/apps/web/src/components/cloud-agent-next/OrgContextModal.tsx +++ /dev/null @@ -1,103 +0,0 @@ -'use client'; - -import { useState } from 'react'; -import { useQuery } from '@tanstack/react-query'; -import { useTRPC } from '@/lib/trpc/utils'; -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, -} from '@/components/ui/dialog'; -import { Button } from '@/components/ui/button'; -import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'; -import { Label } from '@/components/ui/label'; -import { Loader2 } from 'lucide-react'; - -type OrgContextModalProps = { - isOpen: boolean; - onClose: () => void; - onConfirm: (orgContext: { organizationId: string } | null) => void; - sessionTitle: string | null; -}; - -export function OrgContextModal({ - isOpen, - onClose, - onConfirm, - sessionTitle, -}: OrgContextModalProps) { - const [selected, setSelected] = useState('personal'); - const trpc = useTRPC(); - - const { data: organizations, isLoading } = useQuery( - trpc.organizations.list.queryOptions(undefined, { - enabled: isOpen, - }) - ); - - const handleConfirm = () => { - if (selected === 'personal') { - onConfirm(null); - } else { - onConfirm({ organizationId: selected }); - } - }; - - const displayTitle = sessionTitle || 'this session'; - - return ( - !open && onClose()}> - - - Choose Context - - Select whether to resume "{displayTitle}" in your personal context or within - an organization. - - - - {isLoading ? ( -
- -
- ) : ( - -
- - -
- - {organizations?.map(org => ( -
- - -
- ))} -
- )} - - - - - -
-
- ); -} diff --git a/apps/web/src/components/cloud-agent-next/ResumeConfigModal.tsx b/apps/web/src/components/cloud-agent-next/ResumeConfigModal.tsx deleted file mode 100644 index f6bb1db81e0..00000000000 --- a/apps/web/src/components/cloud-agent-next/ResumeConfigModal.tsx +++ /dev/null @@ -1,232 +0,0 @@ -'use client'; - -import { useState, useMemo, useEffect } from 'react'; -import { Button } from '@/components/ui/button'; -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, -} from '@/components/ui/dialog'; -import { Label } from '@/components/ui/label'; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from '@/components/ui/select'; -import { ModelCombobox, type ModelOption } from '@/components/shared/ModelCombobox'; -import { EnvVarsDialog } from './EnvVarsDialog'; -import { SetupCommandsDialog } from './SetupCommandsDialog'; -import type { ResumeConfig } from './types'; -import type { DbSessionDetails } from './store/db-session-atoms'; -import { extractRepoFromGitUrl } from './utils/git-utils'; -import { Cloud } from 'lucide-react'; - -// Re-export ResumeConfig for backwards compatibility -export type { ResumeConfig }; - -type ResumeConfigModalProps = { - isOpen: boolean; - onClose: () => void; - onConfirm: (config: ResumeConfig) => void; - /** The session being configured — source for title, git_url, git_branch, last_mode, last_model */ - session: DbSessionDetails; - /** Available models to select from */ - modelOptions: ModelOption[]; - /** Whether models are still loading */ - isLoadingModels?: boolean; - /** Organization default model (fallback when session has no last_model) */ - orgDefaultModel?: string; -}; - -export const MODES = [ - { value: 'code', label: 'Code' }, - { value: 'plan', label: 'Plan' }, - { value: 'debug', label: 'Debug' }, - { value: 'orchestrator', label: 'Orchestrator' }, - { value: 'ask', label: 'Ask' }, -] as const; - -/** Valid mode values for validation */ -export const VALID_MODE_VALUES = MODES.map(m => m.value); - -function isValidMode(value: string): value is ResumeConfig['mode'] { - return (VALID_MODE_VALUES as readonly string[]).includes(value); -} - -/** - * Modal for collecting configuration when resuming a CLI session - * that has never run in cloud-agent (no cloud_agent_session_id). - * - * Collects: - * - Mode (required) - * - Model (required) - * - Environment Variables (optional) - * - Setup Commands (optional) - */ -export function ResumeConfigModal({ - isOpen, - onClose, - onConfirm, - session, - modelOptions, - isLoadingModels = false, - orgDefaultModel, -}: ResumeConfigModalProps) { - // Derive defaults from session - const sessionGitUrl = session.git_url; - const sessionGitBranch = session.git_branch; - const sessionTitle = session.title; - const defaultRepo = sessionGitUrl ? extractRepoFromGitUrl(sessionGitUrl) : undefined; - const defaultBranch = sessionGitBranch ?? undefined; - const defaultMode = - session.last_mode && isValidMode(session.last_mode) ? session.last_mode : undefined; - const defaultModel = - session.last_model && modelOptions.some(m => m.id === session.last_model) - ? session.last_model - : orgDefaultModel; - - // Form state - const [mode, setMode] = useState(defaultMode || 'code'); - const [model, setModel] = useState(defaultModel || ''); - const [envVars, setEnvVars] = useState>({}); - const [setupCommands, setSetupCommands] = useState([]); - - // Sync mode when session changes (e.g., modal opens for different session) - useEffect(() => { - setMode(defaultMode || 'code'); - }, [defaultMode]); - - // Sync model when session changes - useEffect(() => { - setModel(defaultModel || ''); - }, [defaultModel]); - - // Auto-select first model if none selected and models are available - const effectiveModel = useMemo(() => { - if (model) return model; - if (defaultModel && modelOptions.some(m => m.id === defaultModel)) return defaultModel; - return modelOptions[0]?.id || ''; - }, [model, defaultModel, modelOptions]); - - const handleConfirm = () => { - const config: ResumeConfig = { - mode, - model: effectiveModel, - }; - - // Only include optional fields if they have values - if (Object.keys(envVars).length > 0) { - config.envVars = envVars; - } - if (setupCommands.length > 0) { - config.setupCommands = setupCommands; - } - - onConfirm(config); - }; - - const isFormValid = effectiveModel.length > 0; - - return ( - !open && onClose()}> - - - - - Resume Session in Cloud - - - Configure the cloud agent to continue this session. This session was started in Kilo - Code and needs configuration to run in the cloud. - - - -
- {/* Session title if available */} - {sessionTitle && ( -
-
- Session:{' '} - {sessionTitle} -
-
- )} - - {/* Repository (read-only from session) */} - {defaultRepo && ( -
- -

{defaultRepo}

-
- )} - - {/* Branch (read-only from session) */} - {defaultBranch && ( -
- -

{defaultBranch}

-
- )} - - {/* Required: Mode selector */} -
- - -

Select the agent mode for this session

-
- - {/* Required: Model selector */} - - - {/* Optional: Advanced Configuration */} -
- -
- - -
-

- Configure environment variables and setup commands for the agent -

-
-
- - - - - -
-
- ); -} diff --git a/apps/web/src/components/cloud-agent-next/TodoReadToolCard.tsx b/apps/web/src/components/cloud-agent-next/TodoReadToolCard.tsx deleted file mode 100644 index 3676eb165dc..00000000000 --- a/apps/web/src/components/cloud-agent-next/TodoReadToolCard.tsx +++ /dev/null @@ -1,131 +0,0 @@ -import { ListTodo } from 'lucide-react'; -import { cn } from '@/lib/utils'; -import type { ToolPart } from './types'; -import { ToolCardShell } from './ToolCardShell'; - -type TodoReadToolCardProps = { - toolPart: ToolPart; -}; - -type TodoItem = { - id: string; - content: string; - status: 'pending' | 'in_progress' | 'completed' | 'cancelled'; - priority: 'high' | 'medium' | 'low'; -}; - -function parseTodoOutput(output: string | undefined): TodoItem[] { - if (!output) return []; - try { - const parsed: unknown = JSON.parse(output); - if (Array.isArray(parsed)) { - return parsed as TodoItem[]; - } - if (parsed && typeof parsed === 'object' && 'todos' in parsed && Array.isArray(parsed.todos)) { - return parsed.todos as TodoItem[]; - } - } catch { - // Output might not be JSON - } - return []; -} - -function getStatusIcon(status: TodoItem['status']): string { - switch (status) { - case 'completed': - return '✓'; - case 'in_progress': - return '→'; - case 'cancelled': - return '✗'; - case 'pending': - default: - return '○'; - } -} - -function getStatusColor(status: TodoItem['status']): string { - switch (status) { - case 'completed': - return 'text-green-500'; - case 'in_progress': - return 'text-blue-500'; - case 'cancelled': - return 'text-muted-foreground line-through'; - case 'pending': - default: - return 'text-muted-foreground'; - } -} - -export function TodoReadToolCard({ toolPart }: TodoReadToolCardProps) { - const state = toolPart.state; - const output = state.status === 'completed' ? state.output : undefined; - const error = state.status === 'error' ? state.error : undefined; - - const todos = parseTodoOutput(output); - const completedCount = todos.filter(t => t.status === 'completed').length; - const inProgressCount = todos.filter(t => t.status === 'in_progress').length; - - const summaryParts: string[] = []; - if (todos.length > 0) { - summaryParts.push(`${todos.length} todos`); - if (inProgressCount > 0) summaryParts.push(`${inProgressCount} active`); - if (completedCount > 0) summaryParts.push(`${completedCount} done`); - } - - return ( - - {summaryParts.join(', ') || 'empty'} - - ) : undefined - } - > - {/* Todo list */} - {todos.length > 0 && ( -
- {todos.map(todo => ( -
- {getStatusIcon(todo.status)} - - {todo.content} - - {todo.priority === 'high' && (high)} -
- ))} -
- )} - - {/* Empty state */} - {state.status === 'completed' && todos.length === 0 && ( -
No todos
- )} - - {/* Error */} - {error && ( -
-
Error:
-
-            {error}
-          
-
- )} - - {/* Running state */} - {state.status === 'running' && ( -
Reading todos...
- )} - - {/* Pending state */} - {state.status === 'pending' && ( -
Waiting to read...
- )} -
- ); -} diff --git a/apps/web/src/components/cloud-agent-next/hooks/useOrgContextCommand.ts b/apps/web/src/components/cloud-agent-next/hooks/useOrgContextCommand.ts deleted file mode 100644 index 57e2f2ac67a..00000000000 --- a/apps/web/src/components/cloud-agent-next/hooks/useOrgContextCommand.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { useCallback } from 'react'; -import { useSetAtom } from 'jotai'; -import { useRouter } from 'next/navigation'; -import { - updateOrgContextAtom, - type OrgContext, - type IndexedDbSessionData, - type DbSessionDetails, -} from '../store/db-session-atoms'; - -/** - * Command hook for applying organization context. - * Handles persistence and navigation in one place. - * - * Separates concerns: - * - Modal UI state: handled by parent component - * - Persistence + navigation: handled here - * - Orchestration: handled by CloudChatPage via callbacks - */ -export function useOrgContextCommand(organizationId?: string) { - const updateOrgContext = useSetAtom(updateOrgContextAtom); - const router = useRouter(); - - /** - * Apply organization context to a session. - * This command: - * 1. Persists org context to IndexedDB - * 2. Navigates to correct org URL if needed - * 3. Returns whether navigation occurred and session details for resume modal - * - * @returns Object indicating if navigation occurred and target session for modal - */ - const applyOrgContext = useCallback( - async (params: { - orgContext: OrgContext | null; - pendingSession: IndexedDbSessionData; - }): Promise<{ navigated: boolean; targetSessionForModal: DbSessionDetails | null }> => { - const { orgContext, pendingSession } = params; - - try { - // 1. Update IndexedDB with confirmed org context - await updateOrgContext({ - sessionId: pendingSession.sessionId, - orgContext, - orgContextConfirmed: true, - }); - - // 2. Check if we need to navigate to a DIFFERENT org - const needsNavigation = orgContext && orgContext.organizationId !== organizationId; - - if (needsNavigation && orgContext) { - // Navigate to the new org URL - // The new page will handle showing ResumeConfigModal if needed - // (orgContextConfirmed is preserved in IndexedDB, so only resume modal will show) - const basePath = `/organizations/${orgContext.organizationId}/cloud`; - router.push(`${basePath}/chat?sessionId=${pendingSession.sessionId}`); - return { navigated: true, targetSessionForModal: null }; - } - - // 3. Either personal context OR same org selected - // For CLI sessions, return session details so caller can show resume modal - if (!pendingSession.cloudAgentSessionId) { - const sessionForModal: DbSessionDetails = { - session_id: pendingSession.sessionId, - title: pendingSession.title, - git_url: pendingSession.gitUrl, - cloud_agent_session_id: pendingSession.cloudAgentSessionId, - created_at: new Date(pendingSession.createdAt), - updated_at: new Date(pendingSession.updatedAt), - kilo_user_id: '', - created_on_platform: 'unknown', - forked_from: null, - api_conversation_history_blob_url: null, - task_metadata_blob_url: null, - ui_messages_blob_url: null, - git_state_blob_url: null, - last_mode: pendingSession.lastMode, - last_model: pendingSession.lastModel, - // Use the newly confirmed orgContext, not the old pendingSession.orgContext - organization_id: orgContext?.organizationId ?? null, - }; - return { navigated: false, targetSessionForModal: sessionForModal }; - } - - return { navigated: false, targetSessionForModal: null }; - } catch (error) { - console.error('Failed to apply org context:', error); - throw error; - } - }, - [updateOrgContext, router, organizationId] - ); - - return { applyOrgContext }; -}