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;
}