im_wower
·
2026-03-22
persistence.ts
1import { appendFile, mkdir, writeFile } from "node:fs/promises";
2import type {
3 LocalRunLogSession,
4 LocalRunPaths,
5 RunMetadata,
6 RunStateSnapshot,
7 StreamChunkLogEntry,
8 WorkerLifecycleLogEntry
9} from "./contracts";
10
11function renderJsonFile(value: unknown): string {
12 return `${JSON.stringify(value, null, 2)}\n`;
13}
14
15function ensureTrailingNewline(value: string): string {
16 if (value === "" || value.endsWith("\n")) {
17 return value;
18 }
19
20 return `${value}\n`;
21}
22
23export async function ensureLocalRunLayout(paths: LocalRunPaths): Promise<void> {
24 await Promise.all([
25 mkdir(paths.taskRunsDir, { recursive: true }),
26 mkdir(paths.runDir, { recursive: true }),
27 mkdir(paths.checkpointsDir, { recursive: true }),
28 mkdir(paths.artifactsDir, { recursive: true })
29 ]);
30}
31
32export async function writeRunMetadata(
33 paths: LocalRunPaths,
34 metadata: RunMetadata
35): Promise<void> {
36 await writeFile(paths.metaPath, renderJsonFile(metadata), "utf8");
37}
38
39export async function writeRunState(
40 paths: LocalRunPaths,
41 state: RunStateSnapshot
42): Promise<void> {
43 await writeFile(paths.statePath, renderJsonFile(state), "utf8");
44}
45
46export async function initializeLocalRunFiles(
47 paths: LocalRunPaths,
48 metadata: RunMetadata,
49 state: RunStateSnapshot
50): Promise<void> {
51 await ensureLocalRunLayout(paths);
52 await Promise.all([
53 writeRunMetadata(paths, metadata),
54 writeRunState(paths, state),
55 writeFile(paths.workerLogPath, "", "utf8"),
56 writeFile(paths.stdoutLogPath, "", "utf8"),
57 writeFile(paths.stderrLogPath, "", "utf8")
58 ]);
59}
60
61export async function appendLifecycleEntry(
62 session: LocalRunLogSession,
63 entry: WorkerLifecycleLogEntry
64): Promise<void> {
65 await appendFile(session.worker.filePath, ensureTrailingNewline(entry.renderedLine), "utf8");
66}
67
68export async function appendStreamEntry(
69 session: LocalRunLogSession,
70 entry: StreamChunkLogEntry
71): Promise<void> {
72 const filePath =
73 entry.channel === "stdout" ? session.stdout.filePath : session.stderr.filePath;
74
75 await appendFile(filePath, ensureTrailingNewline(entry.text), "utf8");
76}