import { c as _c } from "react-compiler-runtime"; import chalk from 'chalk'; import type { UUID } from 'crypto'; import figures from 'figures'; import * as React from 'react'; import { getOriginalCwd, getSessionId } from '../../bootstrap/state.js'; import type { CommandResultDisplay, ResumeEntrypoint } from '../../commands.js'; import { LogSelector } from '../../components/LogSelector.js'; import { MessageResponse } from '../../components/MessageResponse.js'; import { Spinner } from '../../components/Spinner.js'; import { useIsInsideModal } from '../../context/modalContext.js'; import { useTerminalSize } from '../../hooks/useTerminalSize.js'; import { setClipboard } from '../../ink/termio/osc.js'; import { Box, Text } from '../../ink.js'; import type { LocalJSXCommandCall } from '../../types/command.js'; import type { LogOption } from '../../types/logs.js'; import { agenticSessionSearch } from '../../utils/agenticSessionSearch.js'; import { checkCrossProjectResume } from '../../utils/crossProjectResume.js'; import { getWorktreePaths } from '../../utils/getWorktreePaths.js'; import { logError } from '../../utils/log.js'; import { getLastSessionLog, getSessionIdFromLog, isCustomTitleEnabled, isLiteLog, loadAllProjectsMessageLogs, loadFullLog, loadSameRepoMessageLogs, searchSessionsByCustomTitle } from '../../utils/sessionStorage.js'; import { validateUuid } from '../../utils/uuid.js'; type ResumeResult = { resultType: 'sessionNotFound'; arg: string; } | { resultType: 'multipleMatches'; arg: string; count: number; }; function resumeHelpMessage(result: ResumeResult): string { switch (result.resultType) { case 'sessionNotFound': return `Session ${chalk.bold(result.arg)} was not found.`; case 'multipleMatches': return `Found ${result.count} sessions matching ${chalk.bold(result.arg)}. Please use /resume to pick a specific session.`; } } function ResumeError(t0) { const $ = _c(10); const { message, args, onDone } = t0; let t1; let t2; if ($[0] !== onDone) { t1 = () => { const timer = setTimeout(onDone, 0); return () => clearTimeout(timer); }; t2 = [onDone]; $[0] = onDone; $[1] = t1; $[2] = t2; } else { t1 = $[1]; t2 = $[2]; } React.useEffect(t1, t2); let t3; if ($[3] !== args) { t3 = {figures.pointer} /resume {args}; $[3] = args; $[4] = t3; } else { t3 = $[4]; } let t4; if ($[5] !== message) { t4 = {message}; $[5] = message; $[6] = t4; } else { t4 = $[6]; } let t5; if ($[7] !== t3 || $[8] !== t4) { t5 = {t3}{t4}; $[7] = t3; $[8] = t4; $[9] = t5; } else { t5 = $[9]; } return t5; } function ResumeCommand({ onDone, onResume }: { onDone: (result?: string, options?: { display?: CommandResultDisplay; }) => void; onResume: (sessionId: UUID, log: LogOption, entrypoint: ResumeEntrypoint) => Promise; }): React.ReactNode { const [logs, setLogs] = React.useState([]); const [worktreePaths, setWorktreePaths] = React.useState([]); const [loading, setLoading] = React.useState(true); const [resuming, setResuming] = React.useState(false); const [showAllProjects, setShowAllProjects] = React.useState(false); const { rows } = useTerminalSize(); const insideModal = useIsInsideModal(); const loadLogs = React.useCallback(async (allProjects: boolean, paths: string[]) => { setLoading(true); try { const allLogs = allProjects ? await loadAllProjectsMessageLogs() : await loadSameRepoMessageLogs(paths); const resumable = filterResumableSessions(allLogs, getSessionId()); if (resumable.length === 0) { onDone('No conversations found to resume'); return; } setLogs(resumable); } catch (_err) { onDone('Failed to load conversations'); } finally { setLoading(false); } }, [onDone]); React.useEffect(() => { async function init() { const paths_0 = await getWorktreePaths(getOriginalCwd()); setWorktreePaths(paths_0); void loadLogs(false, paths_0); } void init(); }, [loadLogs]); const handleToggleAllProjects = React.useCallback(() => { const newValue = !showAllProjects; setShowAllProjects(newValue); void loadLogs(newValue, worktreePaths); }, [showAllProjects, loadLogs, worktreePaths]); async function handleSelect(log: LogOption) { const sessionId = validateUuid(getSessionIdFromLog(log)); if (!sessionId) { onDone('Failed to resume conversation'); return; } // Load full messages for lite logs const fullLog = isLiteLog(log) ? await loadFullLog(log) : log; // Check if this conversation is from a different directory const crossProjectCheck = checkCrossProjectResume(fullLog, showAllProjects, worktreePaths); if (crossProjectCheck.isCrossProject) { if (crossProjectCheck.isSameRepoWorktree) { // Same repo worktree - can resume directly setResuming(true); void onResume(sessionId, fullLog, 'slash_command_picker'); return; } // Different project - show command instead of resuming const raw = await setClipboard(crossProjectCheck.command); if (raw) process.stdout.write(raw); // Format the output message const message = ['', 'This conversation is from a different directory.', '', 'To resume, run:', ` ${crossProjectCheck.command}`, '', '(Command copied to clipboard)', ''].join('\n'); onDone(message, { display: 'user' }); return; } // Same directory - proceed with resume setResuming(true); void onResume(sessionId, fullLog, 'slash_command_picker'); } function handleCancel() { onDone('Resume cancelled', { display: 'system' }); } if (loading) { return Loading conversations… ; } if (resuming) { return Resuming conversation… ; } return loadLogs(showAllProjects, worktreePaths)} showAllProjects={showAllProjects} onToggleAllProjects={handleToggleAllProjects} onAgenticSearch={agenticSessionSearch} />; } export function filterResumableSessions(logs: LogOption[], currentSessionId: string): LogOption[] { return logs.filter(l => !l.isSidechain && getSessionIdFromLog(l) !== currentSessionId); } export const call: LocalJSXCommandCall = async (onDone, context, args) => { const onResume = async (sessionId: UUID, log: LogOption, entrypoint: ResumeEntrypoint) => { try { await context.resume?.(sessionId, log, entrypoint); onDone(undefined, { display: 'skip' }); } catch (error) { logError(error as Error); onDone(`Failed to resume: ${(error as Error).message}`); } }; const arg = args?.trim(); // No argument provided - show picker if (!arg) { return ; } // Load logs to search (includes same-repo worktrees) const worktreePaths = await getWorktreePaths(getOriginalCwd()); const logs = await loadSameRepoMessageLogs(worktreePaths); if (logs.length === 0) { const message = 'No conversations found to resume.'; return onDone(message)} />; } // First, check if arg is a valid UUID const maybeSessionId = validateUuid(arg); if (maybeSessionId) { const matchingLogs = logs.filter(l => getSessionIdFromLog(l) === maybeSessionId).sort((a, b) => b.modified.getTime() - a.modified.getTime()); if (matchingLogs.length > 0) { const log = matchingLogs[0]!; const fullLog = isLiteLog(log) ? await loadFullLog(log) : log; void onResume(maybeSessionId, fullLog, 'slash_command_session_id'); return null; } // Enriched logs didn't find it — try direct file lookup. This handles // sessions filtered out by enrichLogs (e.g., first message >16KB makes // firstPrompt extraction fail, causing the session to be dropped). const directLog = await getLastSessionLog(maybeSessionId); if (directLog) { void onResume(maybeSessionId, directLog, 'slash_command_session_id'); return null; } } // Next, try exact custom title match (only if feature is enabled) if (isCustomTitleEnabled()) { const titleMatches = await searchSessionsByCustomTitle(arg, { exact: true }); if (titleMatches.length === 1) { const log = titleMatches[0]!; const sessionId = getSessionIdFromLog(log); if (sessionId) { const fullLog = isLiteLog(log) ? await loadFullLog(log) : log; void onResume(sessionId, fullLog, 'slash_command_title'); return null; } } // Multiple matches - show error if (titleMatches.length > 1) { const message = resumeHelpMessage({ resultType: 'multipleMatches', arg, count: titleMatches.length }); return onDone(message)} />; } } // No match found - show error const message = resumeHelpMessage({ resultType: 'sessionNotFound', arg }); return onDone(message)} />; };