Something went wrong. Try again.
source dump of claude code forked from oppi.li/claude-code
Something went wrong. Try again.
4.5 kB · 140 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141// Lock file whose mtime IS lastConsolidatedAt. Body is the holder's PID.//// Lives inside the memory dir (getAutoMemPath) so it keys on git-root// like memory does, and so it's writable even when the memory path comes// from an env/settings override whose parent may not be.
import { mkdir, readFile, stat, unlink, utimes, writeFile } from 'fs/promises'import { join } from 'path'import { getOriginalCwd } from '../../bootstrap/state.js'import { getAutoMemPath } from '../../memdir/paths.js'import { logForDebugging } from '../../utils/debug.js'import { isProcessRunning } from '../../utils/genericProcessUtils.js'import { listCandidates } from '../../utils/listSessionsImpl.js'import { getProjectDir } from '../../utils/sessionStorage.js'
const LOCK_FILE = '.consolidate-lock'
// Stale past this even if the PID is live (PID reuse guard).const HOLDER_STALE_MS = 60 * 60 * 1000
function lockPath(): string { return join(getAutoMemPath(), LOCK_FILE)}
/** * mtime of the lock file = lastConsolidatedAt. 0 if absent. * Per-turn cost: one stat. */export async function readLastConsolidatedAt(): Promise<number> { try { const s = await stat(lockPath()) return s.mtimeMs } catch { return 0 }}
/** * Acquire: write PID → mtime = now. Returns the pre-acquire mtime * (for rollback), or null if blocked / lost a race. * * Success → do nothing. mtime stays at now. * Failure → rollbackConsolidationLock(priorMtime) rewinds mtime. * Crash → mtime stuck, dead PID → next process reclaims. */export async function tryAcquireConsolidationLock(): Promise<number | null> { const path = lockPath()
let mtimeMs: number | undefined let holderPid: number | undefined try { const [s, raw] = await Promise.all([stat(path), readFile(path, 'utf8')]) mtimeMs = s.mtimeMs const parsed = parseInt(raw.trim(), 10) holderPid = Number.isFinite(parsed) ? parsed : undefined } catch { // ENOENT — no prior lock. }
if (mtimeMs !== undefined && Date.now() - mtimeMs < HOLDER_STALE_MS) { if (holderPid !== undefined && isProcessRunning(holderPid)) { logForDebugging( `[autoDream] lock held by live PID ${holderPid} (mtime ${Math.round((Date.now() - mtimeMs) / 1000)}s ago)`, ) return null } // Dead PID or unparseable body — reclaim. }
// Memory dir may not exist yet. await mkdir(getAutoMemPath(), { recursive: true }) await writeFile(path, String(process.pid))
// Two reclaimers both write → last wins the PID. Loser bails on re-read. let verify: string try { verify = await readFile(path, 'utf8') } catch { return null } if (parseInt(verify.trim(), 10) !== process.pid) return null
return mtimeMs ?? 0}
/** * Rewind mtime to pre-acquire after a failed fork. Clears the PID body — * otherwise our still-running process would look like it's holding. * priorMtime 0 → unlink (restore no-file). */export async function rollbackConsolidationLock( priorMtime: number,): Promise<void> { const path = lockPath() try { if (priorMtime === 0) { await unlink(path) return } await writeFile(path, '') const t = priorMtime / 1000 // utimes wants seconds await utimes(path, t, t) } catch (e: unknown) { logForDebugging( `[autoDream] rollback failed: ${(e as Error).message} — next trigger delayed to minHours`, ) }}
/** * Session IDs with mtime after sinceMs. listCandidates handles UUID * validation (excludes agent-*.jsonl) and parallel stat. * * Uses mtime (sessions TOUCHED since), not birthtime (0 on ext4). * Caller excludes the current session. Scans per-cwd transcripts — it's * a skip-gate, so undercounting worktree sessions is safe. */export async function listSessionsTouchedSince( sinceMs: number,): Promise<string[]> { const dir = getProjectDir(getOriginalCwd()) const candidates = await listCandidates(dir, true) return candidates.filter(c => c.mtime > sinceMs).map(c => c.sessionId)}
/** * Stamp from manual /dream. Optimistic — fires at prompt-build time, * no post-skill completion hook. Best-effort. */export async function recordConsolidation(): Promise<void> { try { // Memory dir may not exist yet (manual /dream before any auto-trigger). await mkdir(getAutoMemPath(), { recursive: true }) await writeFile(lockPath(), String(process.pid)) } catch (e: unknown) { logForDebugging( `[autoDream] recordConsolidation write failed: ${(e as Error).message}`, ) }}