import { stat } from "node:fs/promises"; import { dirname, join } from "node:path"; import { spawnCli } from "@uncaged/workflow-util-agent"; import type { AdapterFn, RoleResult, ThreadContext, WorkflowRuntime } from "@uncaged/workflow-runtime"; import * as z from "zod/v4"; export type DifferAdapterConfig = { command: string | null; timeout: number | null; }; type SpawnCliFn = typeof spawnCli; export function createDifferAdapter( config: DifferAdapterConfig, spawnCliFn: SpawnCliFn = spawnCli, ): AdapterFn { const command = config.command ?? "docx-diff"; return (_systemPrompt: string, schema: z.ZodType) => async (ctx: ThreadContext, _runtime: WorkflowRuntime): Promise> => { const officeStep = ctx.steps.find((s) => s.role === "office"); if (officeStep === undefined) { throw new Error("differ: office step not found in ctx.steps (invariant violation)"); } const officeMeta = officeStep.meta as { outputDocx: string; sourceDocx: string }; const { outputDocx, sourceDocx } = officeMeta; const reportPath = join(dirname(outputDocx), "diff_report.html"); const result = await spawnCliFn( command, [sourceDocx, outputDocx, "-o", "html", "--out-file", reportPath], { cwd: null, timeoutMs: config.timeout }, ); if (!result.ok) { const e = result.error; if (e.kind === "non_zero_exit" && e.exitCode === 1) { // docx-diff exits 1 when documents have differences — this is a success path } else if (e.kind === "non_zero_exit") { throw new Error(`differ: docx-diff failed (exit ${e.exitCode}): ${e.stderr}`); } else if (e.kind === "timeout") { throw new Error("differ: timed out"); } else { throw new Error(`differ: spawn failed: ${(e as { message: string }).message}`); } } try { await stat(reportPath); } catch { throw new Error(`differ: report file not found: ${reportPath}`); } const meta = schema.parse({ outputDocx, sourceDocx, diffReport: reportPath }); return { meta, childThread: null }; }; }