From 904e68143ef330bb15794c1521b8234b09a65f59 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Sun, 7 Jun 2026 18:01:02 -0700 Subject: [PATCH] FN-6006: name built-in workflow seam nodes Ensure built-in workflow seams render readable labels instead of "Not configured". - add explicit names to built-in execute, review, merge, planning, and step-execute seam nodes - surface seam-specific summaries in dashboard node summaries, with a fallback for unknown seam values - extend core and dashboard tests to cover seam naming invariants and rendered summaries Files changed: .../__tests__/builtin-coding-workflow-ir.test.ts | 7 +++++ .../core/src/__tests__/builtin-workflows.test.ts | 24 +++++++++++++++ packages/core/src/builtin-coding-workflow-ir.ts | 6 ++-- .../src/builtin-stepwise-coding-workflow-ir.ts | 8 ++--- packages/core/src/builtin-workflows.ts | 22 +++++++------- .../nodes/__tests__/node-summary.test.ts | 35 ++++++++++++++++++++++ .../dashboard/app/components/nodes/node-summary.ts | 17 +++++++++++ 7 files changed, 101 insertions(+), 18 deletions(-) Fusion-Task-Id: FN-6006 Fusion-Task-Lineage: 198fc500-ac8f-45c9-b6b5-7aa15285d1df --- .../builtin-coding-workflow-ir.test.ts | 7 ++++ .../src/__tests__/builtin-workflows.test.ts | 24 +++++++++++++ .../core/src/builtin-coding-workflow-ir.ts | 6 ++-- .../builtin-stepwise-coding-workflow-ir.ts | 8 ++--- packages/core/src/builtin-workflows.ts | 22 ++++++------ .../nodes/__tests__/node-summary.test.ts | 35 +++++++++++++++++++ .../app/components/nodes/node-summary.ts | 17 +++++++++ 7 files changed, 101 insertions(+), 18 deletions(-) diff --git a/packages/core/src/__tests__/builtin-coding-workflow-ir.test.ts b/packages/core/src/__tests__/builtin-coding-workflow-ir.test.ts index 3edf7b5f8d..977739e070 100644 --- a/packages/core/src/__tests__/builtin-coding-workflow-ir.test.ts +++ b/packages/core/src/__tests__/builtin-coding-workflow-ir.test.ts @@ -58,4 +58,11 @@ describe("builtin coding workflow ir", () => { expect(byId.get("review")?.column).toBe("in-review"); expect(byId.get("merge")?.column).toBe("in-review"); }); + + it("assigns descriptive names to execute/review/merge seam nodes", () => { + const byId = new Map(BUILTIN_CODING_WORKFLOW_IR.nodes.map((n) => [n.id, n])); + expect(byId.get("execute")?.config?.name).toBe("Execute"); + expect(byId.get("review")?.config?.name).toBe("Review"); + expect(byId.get("merge")?.config?.name).toBe("Merge boundary"); + }); }); diff --git a/packages/core/src/__tests__/builtin-workflows.test.ts b/packages/core/src/__tests__/builtin-workflows.test.ts index d81fc2c89e..b3002b18fc 100644 --- a/packages/core/src/__tests__/builtin-workflows.test.ts +++ b/packages/core/src/__tests__/builtin-workflows.test.ts @@ -91,6 +91,30 @@ describe("built-in workflows", () => { expect(getBuiltinWorkflow("builtin:compound-engineering")).toBeDefined(); }); + it("all seam nodes carry a descriptive name", () => { + for (const workflow of BUILTIN_WORKFLOWS) { + const visitNodes = (nodes: Array<{ config?: unknown; id: string }>) => { + for (const node of nodes) { + const config = node.config as { seam?: unknown; name?: unknown } | undefined; + if (typeof config?.seam === "string") { + expect(typeof config.name).toBe("string"); + expect(String(config.name).trim().length).toBeGreaterThan(0); + } + } + }; + + visitNodes(workflow.ir.nodes); + if (workflow.ir.version === "v2") { + for (const node of workflow.ir.nodes) { + if (node.kind !== "foreach") continue; + const template = (node.config as { template?: { nodes?: Array<{ config?: unknown; id: string }> } } | undefined) + ?.template; + if (template?.nodes) visitNodes(template.nodes); + } + } + } + }); + it("compound-engineering compiles its skill nodes to steps", () => { const ce = getBuiltinWorkflow("builtin:compound-engineering")!; const steps = compileWorkflowToSteps(ce.ir); diff --git a/packages/core/src/builtin-coding-workflow-ir.ts b/packages/core/src/builtin-coding-workflow-ir.ts index 3ae675ed8d..07a04a6435 100644 --- a/packages/core/src/builtin-coding-workflow-ir.ts +++ b/packages/core/src/builtin-coding-workflow-ir.ts @@ -46,9 +46,9 @@ const RAW_BUILTIN_CODING_WORKFLOW_IR: WorkflowIr = { ], nodes: [ { id: "start", kind: "start", column: "triage" }, - { id: "execute", kind: "prompt", column: "in-progress", config: { seam: "execute" } }, - { id: "review", kind: "prompt", column: "in-review", config: { seam: "review" } }, - { id: "merge", kind: "prompt", column: "in-review", config: { seam: "merge" } }, + { id: "execute", kind: "prompt", column: "in-progress", config: { seam: "execute", name: "Execute" } }, + { id: "review", kind: "prompt", column: "in-review", config: { seam: "review", name: "Review" } }, + { id: "merge", kind: "prompt", column: "in-review", config: { seam: "merge", name: "Merge boundary" } }, { id: "end", kind: "end", column: "done" }, ], edges: [ diff --git a/packages/core/src/builtin-stepwise-coding-workflow-ir.ts b/packages/core/src/builtin-stepwise-coding-workflow-ir.ts index 179ae134f5..aaeb5214d2 100644 --- a/packages/core/src/builtin-stepwise-coding-workflow-ir.ts +++ b/packages/core/src/builtin-stepwise-coding-workflow-ir.ts @@ -63,7 +63,7 @@ const RAW_BUILTIN_STEPWISE_CODING_WORKFLOW_IR: WorkflowIr = { nodes: [ { id: "start", kind: "start", column: "triage" }, // Planning seam: produces PROMPT.md (the declared step-source artifact). - { id: "plan", kind: "prompt", column: "in-progress", config: { seam: "planning" } }, + { id: "plan", kind: "prompt", column: "in-progress", config: { seam: "planning", name: "Plan" } }, // KTD-12: parse the planned PROMPT.md into the task step list. This node must // dominate the foreach (validator-enforced). { @@ -86,7 +86,7 @@ const RAW_BUILTIN_STEPWISE_CODING_WORKFLOW_IR: WorkflowIr = { template: { nodes: [ // KTD-2: run exactly this step inside the task's session/worktree. - { id: "step-execute", kind: "prompt", config: { seam: "step-execute" } }, + { id: "step-execute", kind: "prompt", config: { seam: "step-execute", name: "Step execute" } }, // KTD-4: per-step code review; verdicts become outcome edges. { id: "step-review", kind: "step-review", config: { type: "code" } }, // Template exit (the single sink the validator requires): a config-less @@ -121,8 +121,8 @@ const RAW_BUILTIN_STEPWISE_CODING_WORKFLOW_IR: WorkflowIr = { }, // KTD-5: rework exhaustion escalates to a manual hold (a human releases it). { id: "rework-hold", kind: "hold", column: "in-progress", config: { release: "manual" } }, - { id: "review", kind: "prompt", column: "in-review", config: { seam: "review" } }, - { id: "merge", kind: "prompt", column: "in-review", config: { seam: "merge" } }, + { id: "review", kind: "prompt", column: "in-review", config: { seam: "review", name: "Review" } }, + { id: "merge", kind: "prompt", column: "in-review", config: { seam: "merge", name: "Merge boundary" } }, { id: "end", kind: "end", column: "done" }, ], edges: [ diff --git a/packages/core/src/builtin-workflows.ts b/packages/core/src/builtin-workflows.ts index b1ede14669..1bc65e2204 100644 --- a/packages/core/src/builtin-workflows.ts +++ b/packages/core/src/builtin-workflows.ts @@ -78,9 +78,9 @@ export const BUILTIN_WORKFLOWS: WorkflowDefinition[] = [ name: "Coding (built-in)", description: "The standard coding pipeline: implement, review, then merge. Equivalent to the default behavior.", nodes: [ - { id: "execute", kind: "prompt", config: { seam: "execute" } }, - { id: "review", kind: "prompt", config: { seam: "review" } }, - { id: "merge", kind: "prompt", config: { seam: "merge" } }, + { id: "execute", kind: "prompt", config: { seam: "execute", name: "Execute" } }, + { id: "review", kind: "prompt", config: { seam: "review", name: "Review" } }, + { id: "merge", kind: "prompt", config: { seam: "merge", name: "Merge boundary" } }, ], }), linear({ @@ -88,8 +88,8 @@ export const BUILTIN_WORKFLOWS: WorkflowDefinition[] = [ name: "Quick fix (built-in)", description: "Implement and merge with no review step — for trivial, low-risk changes.", nodes: [ - { id: "execute", kind: "prompt", config: { seam: "execute" } }, - { id: "merge", kind: "prompt", config: { seam: "merge" } }, + { id: "execute", kind: "prompt", config: { seam: "execute", name: "Execute" } }, + { id: "merge", kind: "prompt", config: { seam: "merge", name: "Merge boundary" } }, ], }), linear({ @@ -97,8 +97,8 @@ export const BUILTIN_WORKFLOWS: WorkflowDefinition[] = [ name: "Review-heavy (built-in)", description: "Adds an extra security pass before merge, on top of the standard review.", nodes: [ - { id: "execute", kind: "prompt", config: { seam: "execute" } }, - { id: "review", kind: "prompt", config: { seam: "review" } }, + { id: "execute", kind: "prompt", config: { seam: "execute", name: "Execute" } }, + { id: "review", kind: "prompt", config: { seam: "review", name: "Review" } }, { id: "security", kind: "gate", @@ -108,7 +108,7 @@ export const BUILTIN_WORKFLOWS: WorkflowDefinition[] = [ prompt: "Review the diff for security issues: injection, auth/authorization gaps, secret handling, unsafe deserialization. Block on any exploitable finding.", }, }, - { id: "merge", kind: "prompt", config: { seam: "merge" } }, + { id: "merge", kind: "prompt", config: { seam: "merge", name: "Merge boundary" } }, ], }), linear({ @@ -126,8 +126,8 @@ export const BUILTIN_WORKFLOWS: WorkflowDefinition[] = [ prompt: "Produce a short implementation plan for this task before any code is written.", }, }, - { id: "execute", kind: "prompt", config: { seam: "execute" } }, - { id: "review", kind: "prompt", config: { seam: "review" } }, + { id: "execute", kind: "prompt", config: { seam: "execute", name: "Execute" } }, + { id: "review", kind: "prompt", config: { seam: "review", name: "Review" } }, { id: "code-review", kind: "gate", @@ -139,7 +139,7 @@ export const BUILTIN_WORKFLOWS: WorkflowDefinition[] = [ prompt: "Run a structured code review of the changes. Block merge on P0/P1 findings.", }, }, - { id: "merge", kind: "prompt", config: { seam: "merge" } }, + { id: "merge", kind: "prompt", config: { seam: "merge", name: "Merge boundary" } }, { id: "document", kind: "prompt", diff --git a/packages/dashboard/app/components/nodes/__tests__/node-summary.test.ts b/packages/dashboard/app/components/nodes/__tests__/node-summary.test.ts index 3025dfbf94..7a577ec617 100644 --- a/packages/dashboard/app/components/nodes/__tests__/node-summary.test.ts +++ b/packages/dashboard/app/components/nodes/__tests__/node-summary.test.ts @@ -68,6 +68,41 @@ describe("nodeConfigSummary", () => { expect(summary).toBe("deploy"); }); + it("prompt with seam=execute → Execute (engine)", () => { + expect(nodeConfigSummary(node("prompt", { seam: "execute" }))).toBe("Execute (engine)"); + }); + + it("prompt with seam=review → Review (engine)", () => { + expect(nodeConfigSummary(node("prompt", { seam: "review" }))).toBe("Review (engine)"); + }); + + it("prompt with seam=merge → Merge boundary", () => { + expect(nodeConfigSummary(node("prompt", { seam: "merge" }))).toBe("Merge boundary"); + }); + + it("prompt with seam=planning → Plan (engine)", () => { + expect(nodeConfigSummary(node("prompt", { seam: "planning" }))).toBe("Plan (engine)"); + }); + + it("prompt with seam=step-execute → Step execute (engine)", () => { + expect(nodeConfigSummary(node("prompt", { seam: "step-execute" }))).toBe("Step execute (engine)"); + }); + + it("prompt with unknown seam → Seam: ", () => { + expect(nodeConfigSummary(node("prompt", { seam: "custom-seam" }))).toBe("Seam: custom-seam"); + }); + + it("seam node ignores executor/model config — seam takes priority", () => { + const summary = nodeConfigSummary( + node("prompt", { + seam: "execute", + modelProvider: "openai", + modelId: "gpt-4", + }), + ); + expect(summary).toBe("Execute (engine)"); + }); + it("prompt with awaitInput → waits for user input", () => { const summary = nodeConfigSummary(node("prompt", { awaitInput: true })); expect(summary).toBe("Waits for user input"); diff --git a/packages/dashboard/app/components/nodes/node-summary.ts b/packages/dashboard/app/components/nodes/node-summary.ts index 6501699b3d..00e4cdd3dd 100644 --- a/packages/dashboard/app/components/nodes/node-summary.ts +++ b/packages/dashboard/app/components/nodes/node-summary.ts @@ -89,6 +89,23 @@ export function nodeConfigSummary( switch (kind) { case "prompt": { + const seam = str(config.seam); + if (seam) { + switch (seam) { + case "execute": + return t("workflowNodes.summarySeamExecute", "Execute (engine)"); + case "review": + return t("workflowNodes.summarySeamReview", "Review (engine)"); + case "merge": + return t("workflowNodes.summarySeamMerge", "Merge boundary"); + case "planning": + return t("workflowNodes.summarySeamPlanning", "Plan (engine)"); + case "step-execute": + return t("workflowNodes.summarySeamStepExecute", "Step execute (engine)"); + default: + return t("workflowNodes.summarySeamUnknown", "Seam: {{seam}}", { seam }); + } + } const executor = str(config.executor) || "model"; if (executor === "agent") { const agentId = str(config.agentId);