im_wower
·
2026-03-22
index.ts
1import { mkdir, writeFile } from "node:fs/promises";
2
3export type CheckpointType = "summary" | "git_diff" | "log_tail" | "test_output";
4export type CheckpointFileFormat = "json" | "text" | "patch";
5export type CheckpointReplayStrategy = "none" | "git_apply_binary";
6export type CheckpointLogTailSource = "worker" | "stdout" | "stderr" | "combined";
7
8export type CheckpointJsonValue =
9 | string
10 | number
11 | boolean
12 | null
13 | CheckpointJsonValue[]
14 | { [key: string]: CheckpointJsonValue };
15
16export interface CheckpointFileDescriptor {
17 fileName: string;
18 relativePath: string;
19 storagePath: string;
20 format: CheckpointFileFormat;
21 mediaType: string;
22}
23
24export interface CheckpointReplayHint {
25 strategy: CheckpointReplayStrategy;
26 args: string[];
27 baseRef?: string;
28}
29
30export interface CheckpointRecord {
31 checkpointId: string;
32 taskId: string;
33 stepId: string;
34 runId: string;
35 seq: number;
36 type: CheckpointType;
37 summary: string;
38 createdAt: string;
39 file: CheckpointFileDescriptor;
40 contentText?: string;
41 contentJson?: { [key: string]: CheckpointJsonValue };
42 replay?: CheckpointReplayHint;
43}
44
45export interface RenderedCheckpointFile extends CheckpointFileDescriptor {
46 content: string;
47}
48
49export interface SummaryCheckpointInput {
50 summary: string;
51 detail?: string;
52 createdAt?: string;
53}
54
55export interface LogTailCheckpointInput {
56 summary: string;
57 source: CheckpointLogTailSource;
58 lines: string[];
59 truncated?: boolean;
60 createdAt?: string;
61}
62
63export interface GitDiffCheckpointInput {
64 summary: string;
65 statusShort: string;
66 diff: string;
67 diffStat?: string;
68 baseRef?: string;
69 createdAt?: string;
70}
71
72export interface GitCommandPlan {
73 purpose: string;
74 args: string[];
75}
76
77export interface GitDiffSnapshotPlan {
78 checkpointType: "git_diff";
79 commands: {
80 statusShort: GitCommandPlan;
81 binaryDiff: GitCommandPlan;
82 diffStat?: GitCommandPlan;
83 };
84 cadenceSec: {
85 min: number;
86 max: number;
87 };
88 replay: CheckpointReplayHint;
89}
90
91export interface CreateGitDiffSnapshotPlanInput {
92 baseRef?: string;
93 includeStat?: boolean;
94}
95
96export interface CreateCheckpointManagerInput {
97 taskId: string;
98 stepId: string;
99 runId: string;
100 directory: string;
101 resumeFromSeq?: number;
102 supportedTypes?: CheckpointType[];
103 gitDiffBaseRef?: string;
104 includeGitDiffStat?: boolean;
105 note?: string;
106}
107
108export interface CheckpointManagerState {
109 taskId: string;
110 stepId: string;
111 runId: string;
112 directory: string;
113 lastSeq: number;
114 nextSeq: number;
115 resumeFromSeq?: number;
116 supportedTypes: CheckpointType[];
117 records: CheckpointRecord[];
118 gitDiffPlan?: GitDiffSnapshotPlan;
119 note: string;
120}
121
122export interface CheckpointManager {
123 readonly state: CheckpointManagerState;
124 createSummaryCheckpoint(input: SummaryCheckpointInput): CheckpointRecord;
125 createLogTailCheckpoint(input: LogTailCheckpointInput): CheckpointRecord;
126 createGitDiffCheckpoint(input: GitDiffCheckpointInput): CheckpointRecord;
127}
128
129const DEFAULT_SUPPORTED_CHECKPOINT_TYPES: CheckpointType[] = ["summary", "git_diff", "log_tail"];
130
131const CHECKPOINT_FILE_LAYOUT: Record<
132 CheckpointType,
133 {
134 suffix: string;
135 format: CheckpointFileFormat;
136 mediaType: string;
137 }
138> = {
139 summary: {
140 suffix: "summary.json",
141 format: "json",
142 mediaType: "application/json"
143 },
144 git_diff: {
145 suffix: "git-diff.patch",
146 format: "patch",
147 mediaType: "text/x-diff"
148 },
149 log_tail: {
150 suffix: "log-tail.txt",
151 format: "text",
152 mediaType: "text/plain"
153 },
154 test_output: {
155 suffix: "test-output.txt",
156 format: "text",
157 mediaType: "text/plain"
158 }
159};
160
161function trimTrailingSlashes(value: string): string {
162 const trimmed = value.replace(/\/+$/u, "");
163
164 return trimmed === "" ? "/" : trimmed;
165}
166
167function joinPath(basePath: string, segment: string): string {
168 const normalizedBasePath = trimTrailingSlashes(basePath);
169
170 if (normalizedBasePath === "/") {
171 return `/${segment}`;
172 }
173
174 return `${normalizedBasePath}/${segment}`;
175}
176
177function getCheckpointFileLayout(type: CheckpointType) {
178 return CHECKPOINT_FILE_LAYOUT[type];
179}
180
181function createCheckpointRecordBase(
182 state: CheckpointManagerState,
183 type: CheckpointType,
184 summary: string,
185 createdAt?: string
186): CheckpointRecord {
187 const seq = state.nextSeq;
188
189 return {
190 checkpointId: `${state.runId}:checkpoint:${String(seq).padStart(4, "0")}`,
191 taskId: state.taskId,
192 stepId: state.stepId,
193 runId: state.runId,
194 seq,
195 type,
196 summary,
197 createdAt: createdAt ?? new Date().toISOString(),
198 file: createCheckpointFileDescriptor(state.directory, seq, type)
199 };
200}
201
202function pushRecord(state: CheckpointManagerState, record: CheckpointRecord): CheckpointRecord {
203 state.records.push(record);
204 state.lastSeq = record.seq;
205 state.nextSeq = record.seq + 1;
206
207 return record;
208}
209
210function assertSupportedType(state: CheckpointManagerState, type: CheckpointType): void {
211 if (!state.supportedTypes.includes(type)) {
212 throw new Error(`Checkpoint type ${type} is not enabled for ${state.runId}.`);
213 }
214}
215
216function createSummaryPayload(record: CheckpointRecord, detail?: string): {
217 [key: string]: CheckpointJsonValue;
218} {
219 const payload: { [key: string]: CheckpointJsonValue } = {
220 checkpoint_id: record.checkpointId,
221 task_id: record.taskId,
222 step_id: record.stepId,
223 run_id: record.runId,
224 seq: record.seq,
225 checkpoint_type: record.type,
226 summary: record.summary,
227 created_at: record.createdAt
228 };
229
230 if (detail !== undefined) {
231 payload.detail = detail;
232 }
233
234 return payload;
235}
236
237function createLogTailPayload(
238 source: CheckpointLogTailSource,
239 lines: string[],
240 truncated: boolean
241): { [key: string]: CheckpointJsonValue } {
242 return {
243 source,
244 line_count: lines.length,
245 truncated
246 };
247}
248
249function createGitDiffPayload(input: GitDiffCheckpointInput): { [key: string]: CheckpointJsonValue } {
250 const payload: { [key: string]: CheckpointJsonValue } = {
251 status_short: input.statusShort
252 };
253
254 if (input.diffStat !== undefined) {
255 payload.diff_stat = input.diffStat;
256 }
257
258 if (input.baseRef !== undefined) {
259 payload.base_ref = input.baseRef;
260 }
261
262 return payload;
263}
264
265function renderTextContent(contentText?: string): string {
266 if (contentText === undefined || contentText === "") {
267 return "";
268 }
269
270 return contentText.endsWith("\n") ? contentText : `${contentText}\n`;
271}
272
273function getParentDirectory(filePath: string): string {
274 const lastSlashIndex = filePath.lastIndexOf("/");
275
276 if (lastSlashIndex <= 0) {
277 return ".";
278 }
279
280 return filePath.slice(0, lastSlashIndex);
281}
282
283export function createCheckpointFilename(
284 seq: number,
285 checkpointTypeOrSuffix: CheckpointType | string
286): string {
287 const suffix =
288 checkpointTypeOrSuffix in CHECKPOINT_FILE_LAYOUT
289 ? getCheckpointFileLayout(checkpointTypeOrSuffix as CheckpointType).suffix
290 : checkpointTypeOrSuffix;
291
292 return `${String(seq).padStart(4, "0")}-${suffix}`;
293}
294
295export function createCheckpointFileDescriptor(
296 directory: string,
297 seq: number,
298 type: CheckpointType
299): CheckpointFileDescriptor {
300 const layout = getCheckpointFileLayout(type);
301 const relativePath = createCheckpointFilename(seq, layout.suffix);
302
303 return {
304 fileName: relativePath,
305 relativePath,
306 storagePath: joinPath(directory, relativePath),
307 format: layout.format,
308 mediaType: layout.mediaType
309 };
310}
311
312export function createGitDiffSnapshotPlan(
313 input: CreateGitDiffSnapshotPlanInput = {}
314): GitDiffSnapshotPlan {
315 const replayArgs = ["git", "apply", "--binary", "<checkpoint-file>"];
316
317 return {
318 checkpointType: "git_diff",
319 commands: {
320 statusShort: {
321 purpose: "Capture worktree status before diff snapshot.",
322 args: ["git", "status", "--short"]
323 },
324 binaryDiff: {
325 purpose: "Capture replayable patch content for checkpoint restore.",
326 args: ["git", "diff", "--binary"]
327 },
328 diffStat:
329 input.includeStat === false
330 ? undefined
331 : {
332 purpose: "Capture concise diff size summary for conductor UIs and D1 summary fields.",
333 args: ["git", "diff", "--stat"]
334 }
335 },
336 cadenceSec: {
337 min: 30,
338 max: 60
339 },
340 replay: {
341 strategy: "git_apply_binary",
342 args: replayArgs,
343 baseRef: input.baseRef
344 }
345 };
346}
347
348export function createCheckpointManagerState(
349 input: CreateCheckpointManagerInput
350): CheckpointManagerState {
351 const lastSeq = input.resumeFromSeq ?? 0;
352 const supportedTypes = input.supportedTypes ?? [...DEFAULT_SUPPORTED_CHECKPOINT_TYPES];
353 const gitDiffPlan = supportedTypes.includes("git_diff")
354 ? createGitDiffSnapshotPlan({
355 baseRef: input.gitDiffBaseRef,
356 includeStat: input.includeGitDiffStat
357 })
358 : undefined;
359
360 return {
361 taskId: input.taskId,
362 stepId: input.stepId,
363 runId: input.runId,
364 directory: trimTrailingSlashes(input.directory),
365 lastSeq,
366 nextSeq: lastSeq + 1,
367 resumeFromSeq: input.resumeFromSeq,
368 supportedTypes,
369 records: [],
370 gitDiffPlan,
371 note:
372 input.note ??
373 "Checkpoint manager tracks summary/log_tail payloads and exposes git diff snapshot commands."
374 };
375}
376
377export function renderCheckpointFile(record: CheckpointRecord): RenderedCheckpointFile {
378 let content: string;
379
380 switch (record.type) {
381 case "summary":
382 content = `${JSON.stringify(record.contentJson ?? {}, null, 2)}\n`;
383 break;
384 case "git_diff":
385 case "log_tail":
386 case "test_output":
387 content = renderTextContent(record.contentText);
388 break;
389 }
390
391 return {
392 ...record.file,
393 content
394 };
395}
396
397export async function persistCheckpointRecord(
398 record: CheckpointRecord
399): Promise<RenderedCheckpointFile> {
400 const rendered = renderCheckpointFile(record);
401
402 await mkdir(getParentDirectory(rendered.storagePath), { recursive: true });
403 await writeFile(rendered.storagePath, rendered.content, "utf8");
404
405 return rendered;
406}
407
408export function createCheckpointManager(input: CreateCheckpointManagerInput): CheckpointManager {
409 const state = createCheckpointManagerState(input);
410
411 return {
412 state,
413 createSummaryCheckpoint(summaryInput): CheckpointRecord {
414 assertSupportedType(state, "summary");
415
416 const record = createCheckpointRecordBase(
417 state,
418 "summary",
419 summaryInput.summary,
420 summaryInput.createdAt
421 );
422
423 record.contentJson = createSummaryPayload(record, summaryInput.detail);
424
425 return pushRecord(state, record);
426 },
427 createLogTailCheckpoint(logTailInput): CheckpointRecord {
428 assertSupportedType(state, "log_tail");
429
430 const record = createCheckpointRecordBase(
431 state,
432 "log_tail",
433 logTailInput.summary,
434 logTailInput.createdAt
435 );
436
437 record.contentText = logTailInput.lines.join("\n");
438 record.contentJson = createLogTailPayload(
439 logTailInput.source,
440 logTailInput.lines,
441 logTailInput.truncated ?? false
442 );
443
444 return pushRecord(state, record);
445 },
446 createGitDiffCheckpoint(gitDiffInput): CheckpointRecord {
447 assertSupportedType(state, "git_diff");
448
449 const record = createCheckpointRecordBase(
450 state,
451 "git_diff",
452 gitDiffInput.summary,
453 gitDiffInput.createdAt
454 );
455
456 record.contentText = gitDiffInput.diff;
457 record.contentJson = createGitDiffPayload(gitDiffInput);
458 record.replay = {
459 strategy: "git_apply_binary",
460 args: ["git", "apply", "--binary", record.file.storagePath],
461 baseRef: gitDiffInput.baseRef
462 };
463
464 return pushRecord(state, record);
465 }
466 };
467}