import { readFile } from 'node:fs/promises'; import path from 'node:path'; import type { CodingConversationV2 } from './conversation-store'; import { validateSessionKey } from './conversation-store'; import type { ConversationSnapshot } from '../../shared/coding-conversation-contracts'; import { getPiManagedPaths } from '../coding-runtime/pi/resource-loader'; import { projectPiSessionSnapshot } from '../coding-runtime/pi/session-projector'; /** Read durable active-branch history without starting or reconfiguring Pi. */ export async function readCodingConversationHistory( userDataDir: string, projectId: string, conversation: CodingConversationV2 ): Promise { const base: ConversationSnapshot = { schemaVersion: 1, conversation: { id: conversation.id, projectId, agentId: conversation.agentId, title: conversation.title, model: { model: conversation.model, modelResolution: conversation.modelResolution }, }, nodes: [], run: { status: 'idle' }, queue: { items: [] }, context: { usedTokens: 0, contextWindow: 0, compaction: 'idle' }, pendingInteractions: [], worker: { status: 'stopped', generation: 0 }, cursor: { workerGeneration: 0, seq: 0 }, }; if (!conversation.sessionKey) return base; const filename = path.join( getPiManagedPaths(userDataDir).sessionsDir, projectId, validateSessionKey(conversation.sessionKey) + '.jsonl' ); const text = await readFile(filename, 'utf8'); // Pi 0.84.2 rebuilds its current leaf from the last non-header entry. const entries = text .split('\n') .filter((line) => line.trim()) .map((line) => JSON.parse(line) as { id: string; type: string }) .filter((entry) => entry.type !== 'session'); const snapshot = await projectPiSessionSnapshot({ snapshot: base, workerGeneration: 1, state: { isStreaming: false }, entries: { entries, leafId: entries.at(-1)?.id ?? null }, }); return { ...snapshot, worker: { status: 'stopped', generation: 1 } }; }