import React, { useCallback, useState } from 'react'; import { useTerminalSize } from 'src/hooks/useTerminalSize.js'; import { type CodeSession, fetchCodeSessionsFromSessionsAPI } from 'src/utils/teleport/api.js'; // eslint-disable-next-line custom-rules/prefer-use-keybindings -- raw j/k/arrow list navigation import { Box, Text, useInput } from '../ink.js'; import { useKeybinding } from '../keybindings/useKeybinding.js'; import { useShortcutDisplay } from '../keybindings/useShortcutDisplay.js'; import { logForDebugging } from '../utils/debug.js'; import { detectCurrentRepository } from '../utils/detectRepository.js'; import { formatRelativeTime } from '../utils/format.js'; import { ConfigurableShortcutHint } from './ConfigurableShortcutHint.js'; import { Select } from './CustomSelect/index.js'; import { Byline } from './design-system/Byline.js'; import { KeyboardShortcutHint } from './design-system/KeyboardShortcutHint.js'; import { Spinner } from './Spinner.js'; import { TeleportError } from './TeleportError.js'; type Props = { onSelect: (session: CodeSession) => void; onCancel: () => void; isEmbedded?: boolean; }; type LoadErrorType = 'network' | 'auth' | 'api' | 'other'; const UPDATED_STRING = 'Updated'; const SPACE_BETWEEN_TABLE_COLUMNS = ' '; export function ResumeTask({ onSelect, onCancel, isEmbedded = false }: Props): React.ReactNode { const { rows } = useTerminalSize(); const [sessions, setSessions] = useState([]); const [currentRepo, setCurrentRepo] = useState(null); const [loading, setLoading] = useState(true); const [loadErrorType, setLoadErrorType] = useState(null); const [retrying, setRetrying] = useState(false); const [hasCompletedTeleportErrorFlow, setHasCompletedTeleportErrorFlow] = useState(false); // Track focused index for scroll position display in title const [focusedIndex, setFocusedIndex] = useState(1); const escKey = useShortcutDisplay('confirm:no', 'Confirmation', 'Esc'); const loadSessions = useCallback(async () => { try { setLoading(true); setLoadErrorType(null); // Detect current repository const detectedRepo = await detectCurrentRepository(); setCurrentRepo(detectedRepo); logForDebugging(`Current repository: ${detectedRepo || 'not detected'}`); const codeSessions = await fetchCodeSessionsFromSessionsAPI(); // Filter sessions by current repository if detected let filteredSessions = codeSessions; if (detectedRepo) { filteredSessions = codeSessions.filter(session => { if (!session.repo) return false; const sessionRepo = `${session.repo.owner.login}/${session.repo.name}`; return sessionRepo === detectedRepo; }); logForDebugging(`Filtered ${filteredSessions.length} sessions for repo ${detectedRepo} from ${codeSessions.length} total`); } // Sort by updated_at (newest first) const sortedSessions = [...filteredSessions].sort((a, b) => { const dateA = new Date(a.updated_at); const dateB = new Date(b.updated_at); return dateB.getTime() - dateA.getTime(); }); setSessions(sortedSessions); } catch (err) { const errorMessage = err instanceof Error ? err.message : String(err); logForDebugging(`Error loading code sessions: ${errorMessage}`); setLoadErrorType(determineErrorType(errorMessage)); } finally { setLoading(false); setRetrying(false); } }, []); const handleRetry = () => { setRetrying(true); void loadSessions(); }; // Handle escape via keybinding useKeybinding('confirm:no', onCancel, { context: 'Confirmation' }); useInput((input, key) => { // We need to handle ctrl+c in case we don't render a { const session_1 = sessions.find(s => s.id === value); if (session_1) { onSelect(session_1); } }} onFocus={value_0 => { const index = options.findIndex(o => o.value === value_0); if (index >= 0) { setFocusedIndex(index + 1); } }} /> ; } /** * Determines the type of error based on the error message */ function determineErrorType(errorMessage: string): LoadErrorType { const message = errorMessage.toLowerCase(); if (message.includes('fetch') || message.includes('network') || message.includes('timeout')) { return 'network'; } if (message.includes('auth') || message.includes('token') || message.includes('permission') || message.includes('oauth') || message.includes('not authenticated') || message.includes('/login') || message.includes('console account') || message.includes('403')) { return 'auth'; } if (message.includes('api') || message.includes('rate limit') || message.includes('500') || message.includes('529')) { return 'api'; } return 'other'; } /** * Renders error-specific troubleshooting guidance */ function renderErrorSpecificGuidance(errorType: LoadErrorType): React.ReactNode { switch (errorType) { case 'network': return Check your internet connection ; case 'auth': return Teleport requires a Claude account Run /login and select "Claude account with subscription" ; case 'api': return Sorry, Claude encountered an error ; case 'other': return Sorry, Claude Code encountered an error ; } }