Inline base64 source maps had been checked into tracked src files. This strips those comments from the repository without changing runtime behavior or adding ongoing guardrails, per the requested one-time cleanup scope. Constraint: Keep this change limited to tracked source cleanup only Rejected: Add CI/source verification guard | user requested one-time cleanup only Confidence: high Scope-risk: narrow Reversibility: clean Directive: If these directives reappear, fix the producing transform instead of reintroducing repo-side cleanup code Tested: rg -n "sourceMappingURL" ., bun run smoke, bun run verify:privacy, bun run test:provider, npm run test:provider-recommendation Not-tested: bun run typecheck (repository has many pre-existing unrelated failures) Co-authored-by: anandh8x <test@example.com>
275 lines
9.4 KiB
TypeScript
275 lines
9.4 KiB
TypeScript
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 = <Text dimColor={true}>{figures.pointer} /resume {args}</Text>;
|
|
$[3] = args;
|
|
$[4] = t3;
|
|
} else {
|
|
t3 = $[4];
|
|
}
|
|
let t4;
|
|
if ($[5] !== message) {
|
|
t4 = <MessageResponse><Text>{message}</Text></MessageResponse>;
|
|
$[5] = message;
|
|
$[6] = t4;
|
|
} else {
|
|
t4 = $[6];
|
|
}
|
|
let t5;
|
|
if ($[7] !== t3 || $[8] !== t4) {
|
|
t5 = <Box flexDirection="column">{t3}{t4}</Box>;
|
|
$[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<void>;
|
|
}): React.ReactNode {
|
|
const [logs, setLogs] = React.useState<LogOption[]>([]);
|
|
const [worktreePaths, setWorktreePaths] = React.useState<string[]>([]);
|
|
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 <Box>
|
|
<Spinner />
|
|
<Text> Loading conversations…</Text>
|
|
</Box>;
|
|
}
|
|
if (resuming) {
|
|
return <Box>
|
|
<Spinner />
|
|
<Text> Resuming conversation…</Text>
|
|
</Box>;
|
|
}
|
|
return <LogSelector logs={logs} maxHeight={insideModal ? Math.floor(rows / 2) : rows - 2} onCancel={handleCancel} onSelect={handleSelect} onLogsChanged={() => 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 <ResumeCommand key={Date.now()} onDone={onDone} onResume={onResume} />;
|
|
}
|
|
|
|
// 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 <ResumeError message={message} args={arg} onDone={() => 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 <ResumeError message={message} args={arg} onDone={() => onDone(message)} />;
|
|
}
|
|
}
|
|
|
|
// No match found - show error
|
|
const message = resumeHelpMessage({
|
|
resultType: 'sessionNotFound',
|
|
arg
|
|
});
|
|
return <ResumeError message={message} args={arg} onDone={() => onDone(message)} />;
|
|
};
|