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>
73 lines
2.4 KiB
TypeScript
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)?/");
|
|
});
|
|
});
|