File size: 1,967 Bytes
d522ffa | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | import { appendFile, mkdir, readFile } from 'node:fs/promises';
import { dirname } from 'node:path';
import { targetSessionIndex } from './paths.js';
export interface SessionIndexEntry {
readonly sessionId: string;
readonly sessionDir: string;
readonly workDir: string;
}
export async function appendSessionIndexEntry(
targetHome: string,
entry: SessionIndexEntry,
): Promise<void> {
const path = targetSessionIndex(targetHome);
await mkdir(dirname(path), { recursive: true, mode: 0o700 });
await appendFile(path, JSON.stringify(entry) + '\n', 'utf-8');
}
/**
* Idempotently ensure `entry` is present in `session_index.jsonl`.
*
* Appends the entry only when no existing line carries the same `sessionId`.
* Used for the `already-migrated` re-run path: if a prior run wrote the session
* directory but crashed before appending its index entry, this self-heals the
* index on a subsequent run instead of leaving the session permanently
* unreachable by id.
*/
export async function ensureSessionIndexEntry(
targetHome: string,
entry: SessionIndexEntry,
): Promise<void> {
const path = targetSessionIndex(targetHome);
if (await hasSessionIndexEntry(path, entry.sessionId)) return;
await mkdir(dirname(path), { recursive: true, mode: 0o700 });
await appendFile(path, JSON.stringify(entry) + '\n', 'utf-8');
}
async function hasSessionIndexEntry(path: string, sessionId: string): Promise<boolean> {
let text: string;
try {
text = await readFile(path, 'utf-8');
} catch {
return false;
}
for (const line of text.split(/\r?\n/)) {
if (line.length === 0) continue;
try {
const parsed: unknown = JSON.parse(line);
if (
parsed !== null &&
typeof parsed === 'object' &&
(parsed as { sessionId?: unknown }).sessionId === sessionId
) {
return true;
}
} catch {
// Skip malformed lines — treat them as absent entries.
}
}
return false;
}
|