From a3ad0c8ecf9bb400c9e785ac531f44fb4a803bb5 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Tue, 30 Jun 2026 21:55:02 -0700 Subject: [PATCH] fix(FN-7335): keep fast completion summaries --- .changeset/fast-completion-summary.md | 7 +++++++ .../executor-fast-mode-workflows.test.ts | 18 ++++++++++++++++++ packages/engine/src/executor.ts | 7 ++++++- 3 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 .changeset/fast-completion-summary.md diff --git a/.changeset/fast-completion-summary.md b/.changeset/fast-completion-summary.md new file mode 100644 index 0000000000..e1e2666c6b --- /dev/null +++ b/.changeset/fast-completion-summary.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Keep workflow completion summaries running for fast-mode tasks. +category: fix +dev: Excludes completion-summary/summaryTarget task nodes from the fast-mode custom review/gate skip path. diff --git a/packages/engine/src/__tests__/executor-fast-mode-workflows.test.ts b/packages/engine/src/__tests__/executor-fast-mode-workflows.test.ts index b3590853d8..88509adf2c 100644 --- a/packages/engine/src/__tests__/executor-fast-mode-workflows.test.ts +++ b/packages/engine/src/__tests__/executor-fast-mode-workflows.test.ts @@ -377,6 +377,24 @@ describe("fast mode workflow/runtime invariants", () => { expect(executeScript).not.toHaveBeenCalled(); }); + it.each([ + ["completion-summary id", { id: "completion-summary", kind: "prompt", config: { prompt: "summarize" } }], + ["summaryTarget task", { id: "custom-summary", kind: "prompt", config: { prompt: "summarize", summaryTarget: "task" } }], + ])("does not skip completion summary nodes in fast mode by %s", async (_label, node) => { + const { executor } = makeExecutorForTask(task({ executionMode: "fast", worktree: "/tmp/wt" })); + const executeStep = vi.spyOn(executor as any, "executeWorkflowStep").mockResolvedValue({ success: true, output: "Done." }); + + const result = await (executor as any).runGraphCustomNode( + node, + task({ executionMode: "fast" }), + {}, + undefined, + ); + + expect(result).toMatchObject({ outcome: "success", value: "passed" }); + expect(executeStep).toHaveBeenCalledTimes(1); + }); + it.each(["prompt", "script", "gate"])("executes optional-group template %s nodes in fast mode", async (kind) => { const { executor } = makeExecutorForTask(task({ executionMode: "fast", worktree: "/tmp/wt" })); const executeStep = vi.spyOn(executor as any, "executeWorkflowStep").mockResolvedValue({ success: true }); diff --git a/packages/engine/src/executor.ts b/packages/engine/src/executor.ts index 85e4181a2c..a500f7ae19 100644 --- a/packages/engine/src/executor.ts +++ b/packages/engine/src/executor.ts @@ -7058,7 +7058,12 @@ export class TaskExecutor { FNXC:FastOptionalSteps 2026-06-30-09:14: Fast skips top-level custom prompt/script/gate review bodies by default, but an enabled optional-group template is explicit operator intent. The graph marks those template nodes so Browser Verification and custom optional groups still run under fast mode. */ - if (live.executionMode === "fast" && !optionalGroupId && !cfg.seam && (node.kind === "prompt" || node.kind === "script" || node.kind === "gate")) { + const isCompletionSummaryNode = cfg.summaryTarget === "task" || node.id === "completion-summary"; + /* + FNXC:WorkflowCompletion 2026-07-01-18:42: + Fast mode skips review/validation work, not the agent-authored completion summary. FN-7335 reached review with "Fast mode — custom graph node 'completion-summary' skipped"; keep summary nodes executable so fast tasks still produce the same review/done card summary as standard tasks. + */ + if (live.executionMode === "fast" && !isCompletionSummaryNode && !optionalGroupId && !cfg.seam && (node.kind === "prompt" || node.kind === "script" || node.kind === "gate")) { executorLog.log(`${live.id}: fast mode — skipping custom graph node '${node.id}'`); await this.store.logEntry( live.id,