import type { SessionEntry } from "@earendil-works/pi-coding-agent"; import { CHECKPOINT_TOOL_NAME, PREVIEW_MAX_LENGTH, SUMMARY_PREVIEW_MAX_LENGTH } from "./constants"; import type { CheckpointDetails, CheckpointInfo, RollbackResultDetails } from "./types"; export function parseCheckpointId(input: string): number | null { const trimmed = input.trim(); const match = trimmed.match(/^#?(\d+)$/); if (!match) { return null; } const value = Number.parseInt(match[1], 10); return Number.isFinite(value) && value > 0 ? value : null; } export function formatCheckpointId(id: number): string { return `#${id}`; } export function formatCheckpointCreatedMessage(id: number, options?: { firstUse?: boolean }): string { const lines = [ `Checkpoint ${formatCheckpointId(id)} created.`, "", "Semantics:", "- Conversation-only bookmark (filesystem unchanged).", "- Work after this point can be COMPACTED via restore_conversation(summary).", "- Restore conversation does NOT mean undo — it replaces intermediate messages with your summary.", "", `Next: restore_conversation(${formatCheckpointId(id)}, summary) when done investigating.`, ]; if (options?.firstUse) { lines.splice( 2, 0, "First time: restore_conversation(summary) compacts messages; the summary is written by you and becomes your memory.", "", ); } return lines.join("\n"); } export function truncateSummaryPreview(summary: string, max = SUMMARY_PREVIEW_MAX_LENGTH): string { const trimmed = summary.trim(); if (trimmed.length <= max) { return trimmed; } return `${trimmed.slice(0, max - 1)}…`; } export function formatRestoreConversationResultMessage(details: RollbackResultDetails): string { const checkpointLabel = details.label ? `${formatCheckpointId(details.checkpointId)} ("${details.label}")` : formatCheckpointId(details.checkpointId); const timeRange = details.compactedFrom && details.compactedTo ? `${formatPickerTimestamp(details.compactedFrom)} → ${formatPickerTimestamp(details.compactedTo)}` : "unknown"; const lines = [ "Conversation restored successfully.", "", `Checkpoint: ${checkpointLabel}`, `Compacted: ${details.compactedMessageCount} messages (${timeRange})`, `Invoked by: ${details.invokedBy}`, "", "Intermediate tool outputs are no longer in context.", "Filesystem unchanged.", "", "Provenance:", "- branch: conversation-only (filesystem unchanged)", ]; return lines.join("\n"); } export function validateSummary(summary: string): string | null { if (!summary.trim()) { return "Summary is required."; } return null; } export function truncatePreview(text: string, max = PREVIEW_MAX_LENGTH): string { const trimmed = text.replace(/\s+/g, " ").trim(); if (trimmed.length <= max) { return trimmed; } return `${trimmed.slice(0, max - 1)}…`; } export function extractUserText(content: unknown): string { if (typeof content === "string") { return content; } if (!Array.isArray(content)) { return ""; } return content .filter((block): block is { type: "text"; text: string } => block?.type === "text") .map((block) => block.text) .join(" "); } export function getDefaultPreview(branch: SessionEntry[], checkpointEntryId: string): string { const index = branch.findIndex((entry) => entry.id === checkpointEntryId); if (index === -1) { return ""; } for (let i = index - 1; i >= 0; i--) { const entry = branch[i]; if (entry.type !== "message") { continue; } const message = entry.message; if (message.role !== "user") { continue; } return truncatePreview(extractUserText(message.content)); } return ""; } export function getNextCheckpointId(entries: SessionEntry[]): number { let max = 0; for (const entry of entries) { if (entry.type !== "message") { continue; } const message = entry.message; if (message.role !== "toolResult" || message.toolName !== CHECKPOINT_TOOL_NAME) { continue; } const details = message.details as CheckpointDetails | undefined; if (details?.checkpointId && details.checkpointId > max) { max = details.checkpointId; } } return max + 1; } export function listCheckpointsOnBranch(branch: SessionEntry[]): CheckpointInfo[] { const checkpoints: CheckpointInfo[] = []; for (const entry of branch) { if (entry.type !== "message") { continue; } const message = entry.message; if (message.role !== "toolResult" || message.toolName !== CHECKPOINT_TOOL_NAME) { continue; } const details = message.details as CheckpointDetails | undefined; if (!details?.checkpointId) { continue; } checkpoints.push({ id: details.checkpointId, displayId: formatCheckpointId(details.checkpointId), entryId: entry.id, label: details.label, preview: details.label ?? getDefaultPreview(branch, entry.id), timestamp: entry.timestamp, }); } return checkpoints.sort((left, right) => right.id - left.id); } export function formatPickerLine(checkpoint: CheckpointInfo): string { const timestamp = formatPickerTimestamp(checkpoint.timestamp); const detail = checkpoint.label ?? checkpoint.preview; return `${checkpoint.displayId} · ${timestamp} · ${detail}`; } export function formatPickerTimestamp(timestamp: string): string { const date = new Date(timestamp); if (Number.isNaN(date.getTime())) { return timestamp; } return date.toISOString().replace("T", " ").slice(0, 16); } export function resolveCheckpointOnBranch( branch: SessionEntry[], checkpointIdInput: string, ): { info: CheckpointInfo; entryId: string } | { error: string } { const parsed = parseCheckpointId(checkpointIdInput); const onBranch = listCheckpointsOnBranch(branch); const validIds = onBranch.map((checkpoint) => checkpoint.displayId).join(", "); if (parsed === null) { return { error: validIds ? `Checkpoint not found. Valid: ${validIds}` : "Checkpoint not found. No checkpoints on current branch.", }; } const found = onBranch.find((checkpoint) => checkpoint.id === parsed); if (!found) { return { error: validIds ? `Checkpoint ${formatCheckpointId(parsed)} not found on current branch. Valid: ${validIds}` : `Checkpoint ${formatCheckpointId(parsed)} not found on current branch.`, }; } return { info: found, entryId: found.entryId }; }