import assert from "node:assert/strict"; import { mkdtemp, rm } from "node:fs/promises"; import { tmpdir } from "node:os"; import * as path from "node:path"; import test from "node:test"; import type { ExtensionAPI, ExtensionContext, } from "@earendil-works/pi-coding-agent"; import { DEFAULT_WORKFLOW_CONFIG, type WorkflowRun, } from "./domain.ts"; import { reconcileWorkflowDeliveries, workflowNotificationType, } from "./notifications.ts"; import { WorkflowStore } from "./store.ts"; async function withTempDir(run: (directory: string) => Promise) { const directory = await mkdtemp(path.join(tmpdir(), "workflow-delivery-")); try { await run(directory); } finally { await rm(directory, { recursive: true, force: true }); } } function workflow(background = false): WorkflowRun { const gateId = "gate-1"; return { runId: "run-1", revision: 0, mode: "feature", status: "waiting", background, request: "Build search", featureName: "search", parentSessionIds: ["session-1"], lease: { processId: 100, activeParentSessionId: "session-1", heartbeatAt: 1, }, workspace: { state: "ready", sourceRoot: "/repo", root: "/repo/.worktrees/run-1", branch: "feature/search", baseHead: "abc123", }, effectiveConfig: structuredClone(DEFAULT_WORKFLOW_CONFIG), pinnedProfiles: [], preflightEligibility: [], facts: { stage: "design", taskKind: "feature", requestTerms: ["search"], candidatePaths: [], languages: [], tags: [], riskTags: [], }, stages: [], gates: [ { id: gateId, kind: "plan-approval", status: "open", revision: 0, openedAt: 1, }, ], decisions: [], assignments: [], prototypeCandidates: [], prototypeCleanupCompleted: false, deliveries: [ { key: `gate:run-1:${gateId}:0`, state: "pending", createdAt: 1, }, ], commandReceipts: [], uiImpactEvidence: [], affectedUiSurfaces: [], mutatorProfileNames: [], reviewCycles: 0, verificationCycles: 0, }; } function deliveryHarness(entries: unknown[], persistImmediately = true) { const sent: Array<{ message: unknown; options: unknown }> = []; const pi = { sendMessage(message: unknown, options: unknown) { sent.push({ message, options }); if (persistImmediately) { const record = message as Record; entries.push({ type: "message", message: { role: "custom", customType: record.customType, details: record.details, }, }); } }, } as unknown as Pick; const ctx = { sessionManager: { getSessionId: () => "session-1", getEntries: () => entries, }, } as unknown as ExtensionContext; return { pi, ctx, sent }; } test("delivery receipt closes the send/save crash window exactly once", async () => { await withTempDir(async (directory) => { const store = new WorkflowStore(path.join(directory, "state")); await store.createRun(workflow()); const entries: unknown[] = []; const harness = deliveryHarness(entries); await reconcileWorkflowDeliveries( harness.pi, { store, engineForContext: () => Promise.reject(new Error("unused")) }, harness.ctx, ); assert.equal(harness.sent.length, 1); const message = harness.sent[0]?.message as Record; assert.equal(message.customType, workflowNotificationType); assert.match(String(message.content), /workflow search is waiting/); assert.equal( (message.details as Record).runId, "run-1", ); const delivered = await store.loadRun("run-1"); assert.equal(delivered.deliveries[0]?.state, "sent"); assert.equal(delivered.revision, 0); await reconcileWorkflowDeliveries( harness.pi, { store, engineForContext: () => Promise.reject(new Error("unused")) }, harness.ctx, ); assert.equal(harness.sent.length, 1); }); }); test("queued foreground delivery suppresses immediate duplicates and retries a missing receipt", async () => { await withTempDir(async (directory) => { const originalNow = Date.now; let now = 1_000; Date.now = () => now; try { const store = new WorkflowStore(path.join(directory, "state")); const run = workflow(); await store.createRun(run); const entries: unknown[] = []; const harness = deliveryHarness(entries, false); const runtime = { store, engineForContext: () => Promise.reject(new Error("unused")), }; await reconcileWorkflowDeliveries(harness.pi, runtime, harness.ctx); now++; await reconcileWorkflowDeliveries(harness.pi, runtime, harness.ctx); assert.equal(harness.sent.length, 1); assert.deepEqual(harness.sent[0]?.options, { deliverAs: "steer" }); now += 30_000; await reconcileWorkflowDeliveries(harness.pi, runtime, harness.ctx); assert.equal(harness.sent.length, 2); const sentMessage = harness.sent[1]?.message as Record; entries.push({ type: "message", message: { role: "custom", customType: sentMessage.customType, details: sentMessage.details, }, }); await reconcileWorkflowDeliveries(harness.pi, runtime, harness.ctx); assert.equal(harness.sent.length, 2); assert.equal((await store.loadRun("run-1")).deliveries[0]?.state, "sent"); } finally { Date.now = originalNow; } }); }); test("obsolete Gate deliveries settle without sending contradictory notices", async () => { await withTempDir(async (directory) => { const store = new WorkflowStore(path.join(directory, "state")); const run = workflow(); run.status = "running"; run.gates[0]!.status = "satisfied"; await store.createRun(run); const harness = deliveryHarness([]); await reconcileWorkflowDeliveries( harness.pi, { store, engineForContext: () => Promise.reject(new Error("unused")) }, harness.ctx, ); assert.equal(harness.sent.length, 0); assert.equal((await store.loadRun("run-1")).deliveries[0]?.state, "sent"); }); }); test("an existing receiver receipt marks pending delivery without resending", async () => { await withTempDir(async (directory) => { const store = new WorkflowStore(path.join(directory, "state")); const run = workflow(true); await store.createRun(run); const entries = [ { type: "message", message: { role: "custom", customType: workflowNotificationType, details: { deliveryKey: run.deliveries[0]?.key }, }, }, ]; const harness = deliveryHarness(entries); await reconcileWorkflowDeliveries( harness.pi, { store, engineForContext: () => Promise.reject(new Error("unused")) }, harness.ctx, ); assert.equal(harness.sent.length, 0); assert.equal( (await store.loadRun("run-1")).deliveries[0]?.state, "sent", ); }); }); test("terminal deliveries include the persisted report and settle once", async () => { await withTempDir(async (directory) => { const store = new WorkflowStore(path.join(directory, "state")); const run = workflow(true); run.status = "completed"; run.gates = []; run.reportArtifact = { path: "/state/report.md", digest: "digest", revision: 4, }; run.deliveries = [ { key: "terminal:run-1:completed:4", state: "pending", createdAt: 4, }, ]; await store.createRun(run); const harness = deliveryHarness([]); await reconcileWorkflowDeliveries( harness.pi, { store, engineForContext: () => Promise.reject(new Error("unused")) }, harness.ctx, ); assert.equal(harness.sent.length, 1); assert.match( String((harness.sent[0]?.message as Record).content), /workflow search finished with status completed.*Report: \/state\/report.md/, ); assert.equal((await store.loadRun("run-1")).deliveries[0]?.state, "sent"); }); }); test("background deliveries request a follow-up turn", async () => { await withTempDir(async (directory) => { const store = new WorkflowStore(path.join(directory, "state")); await store.createRun(workflow(true)); const harness = deliveryHarness([]); await reconcileWorkflowDeliveries( harness.pi, { store, engineForContext: () => Promise.reject(new Error("unused")) }, harness.ctx, ); assert.deepEqual(harness.sent[0]?.options, { deliverAs: "followUp", triggerTurn: true, }); }); });