Files
fusion/packages/dashboard/app/components/__tests__/node-summary.test.ts
gsxdsm 42bbe58c03 FN-7579: add ask-user and exit-gate workflow nodes
Add workflow nodes for mid-flow user reach-out and early exit from a workflow run.

- Add `ask-user` IR node kind that reuses the await-input park/resume mechanism and surfaces the question in the task chat for brainstorming/clarification.
- Add `exit-gate` IR node kind that terminates the workflow early, with an optional condition.
- Wire both node kinds through the engine executor and workflow-node-handlers, including a new exit-gate-runner.
- Update the WorkflowNodeEditor palette, node summaries, and node help text for the two new node types.
- Extend workflow-flow-mapping to support the new node kinds.
- Keep `prompt`+`awaitInput` as a back-compat alias.
- Add core/engine/dashboard tests covering the new node kinds.
- Document the new nodes in docs/workflow-steps.md.
- Add changeset for the new minor feature.

Files changed:
 .changeset/fn-7579-ask-user-exit-gate-nodes.md     |   7 +
 docs/workflow-steps.md                             |  28 ++++
 packages/core/src/__tests__/workflow-ir.test.ts    | 120 ++++++++++++++
 packages/core/src/workflow-ir-types.ts             |  12 +-
 packages/core/src/workflow-ir.ts                   |  47 ++++++
 .../app/components/WorkflowNodeEditor.tsx          | 181 ++++++++++++++++++++-
 .../app/components/__tests__/node-summary.test.ts  |  43 +++++
 .../__tests__/workflow-flow-mapping.test.ts        |  49 ++++++
 .../app/components/nodes/WorkflowNodeTypes.tsx     |  14 +-
 .../dashboard/app/components/nodes/node-help.ts    |  24 +++
 .../dashboard/app/components/nodes/node-summary.ts |  28 ++++
 .../app/components/workflow-flow-mapping.ts        |   4 +
 .../workflow-graph-executor-handlers.test.ts       | 115 +++++++++++++
 .../src/__tests__/workflow-node-handlers.test.ts   |  66 ++++++++
 packages/engine/src/executor.ts                    |  23 ++-
 packages/engine/src/workflow-node-handlers.ts      |  18 +-
 .../src/workflow-node-runners/exit-gate-runner.ts  |  81 +++++++++
 17 files changed, 849 insertions(+), 11 deletions(-)

Fusion-Task-Id: FN-7579
Fusion-Task-Lineage: 9a89ff49-200d-4a6c-b97c-15d219349ee5
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-05 11:31:48 -07:00

73 lines
2.4 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { nodeConfigSummary } from "../nodes/node-summary";
import type { WorkflowFlowNodeData } from "../nodes/WorkflowNodeTypes";
describe("nodeConfigSummary", () => {
it("summarizes notify nodes by event", () => {
const data: WorkflowFlowNodeData = {
kind: "notify",
label: "Notify",
config: { event: "custom-event" },
};
expect(nodeConfigSummary(data)).toBe("custom-event");
});
it("includes a truncated notify message preview", () => {
const data: WorkflowFlowNodeData = {
kind: "notify",
label: "Notify",
config: {
event: "workflow-notify",
message: "This message is intentionally long enough that the summary should truncate it cleanly.",
},
};
expect(nodeConfigSummary(data)).toBe("workflow-notify · This message is intentionally long enou…");
});
// FN-7579
it("summarizes an ask-user node by its question", () => {
const data: WorkflowFlowNodeData = {
kind: "ask-user",
label: "Ask user question",
config: { question: "Anything to refine before we finish?" },
};
expect(nodeConfigSummary(data)).toBe("Anything to refine before we finish?");
});
it("falls back to the default prompt summary when an ask-user node has no question", () => {
const data: WorkflowFlowNodeData = { kind: "ask-user", label: "Ask user question", config: {} };
expect(nodeConfigSummary(data)).toBe("Waits for user input");
});
it("summarizes an unconditional exit-gate", () => {
const data: WorkflowFlowNodeData = { kind: "exit-gate", label: "Exit gate", config: {} };
expect(nodeConfigSummary(data)).toBe("Always exits");
});
it("summarizes a conditional exit-gate (output-contains)", () => {
const data: WorkflowFlowNodeData = {
kind: "exit-gate",
label: "Exit gate",
config: { condition: { type: "output-contains", nodeId: "ask", value: "looks good" } },
};
expect(nodeConfigSummary(data)).toBe('Exits when contains "looks good"');
});
it("summarizes a conditional exit-gate (output-matches)", () => {
const data: WorkflowFlowNodeData = {
kind: "exit-gate",
label: "Exit gate",
config: { condition: { type: "output-matches", nodeId: "ask", pattern: "approve(d)?" } },
};
expect(nodeConfigSummary(data)).toBe("Exits when matches /approve(d)?/");
});
});