baa-conductor

git clone 

baa-conductor / packages / checkpointing / src
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}