// @vitest-environment node import { afterEach, describe, expect, it, vi } from "vitest"; import { WORKFLOW_EXTENSION_SCHEMA_VERSION, __resetWorkflowExtensionRegistryForTests, getWorkflowExtensionRegistry, workflowExtensionRegistryId, type TaskDetail, type WorkflowIr, } from "@fusion/core"; import { WorkflowGraphExecutor } from "../workflow-graph-executor.js"; const settingsOn = { experimentalFeatures: { workflowGraphExecutor: true } }; describe("workflow node-handler extensions", () => { afterEach(() => { __resetWorkflowExtensionRegistryForTests(); }); it("executes an extension-marked node and routes custom outcomes", async () => { const extensionKey = workflowExtensionRegistryId("node-plugin", "decision"); const handle = vi.fn().mockResolvedValue({ outcome: "outcome:needs-human", contextPatch: { decidedBy: "plugin" }, }); getWorkflowExtensionRegistry().register("node-plugin", { extensionId: "decision", name: "Decision", kind: "node-handler", nodeKind: "prompt", schemaVersion: WORKFLOW_EXTENSION_SCHEMA_VERSION, fallback: "failClosed", handle, }); const workflow: WorkflowIr = { version: "v2", name: "node-extension", columns: [{ id: "work", name: "Work", traits: [] }], nodes: [ { id: "start", kind: "start" }, { id: "decide", kind: "prompt", column: "work", extensions: { [extensionKey]: {} } }, { id: "human", kind: "prompt", column: "work", config: { prompt: "human" } }, { id: "default", kind: "end" }, { id: "end", kind: "end" }, ], edges: [ { from: "start", to: "decide" }, { from: "decide", to: "human", condition: "outcome:needs-human" }, { from: "decide", to: "default", condition: "success" }, { from: "human", to: "end" }, ], }; const prompt = vi.fn(async () => ({ outcome: "success" as const })); const executor = new WorkflowGraphExecutor({ handlers: { prompt } }); const result = await executor.run({ id: "FN-NODE" } as TaskDetail, settingsOn, workflow); expect(result.outcome).toBe("success"); expect(result.visitedNodeIds).toEqual(["start", "decide", "human"]); expect(result.context).toMatchObject({ decidedBy: "plugin" }); expect(handle).toHaveBeenCalledWith(expect.objectContaining({ node: expect.objectContaining({ id: "decide" }), workflow, })); expect(prompt).toHaveBeenCalledWith(expect.objectContaining({ id: "human" }), expect.any(Object)); }); });