diff --git a/internal/agent/canvas/scheduler.go b/internal/agent/canvas/scheduler.go index 7936ce3a17..67e0971625 100644 --- a/internal/agent/canvas/scheduler.go +++ b/internal/agent/canvas/scheduler.go @@ -742,7 +742,15 @@ func BuildWorkflow(ctx context.Context, c *Canvas) (*compose.Workflow[map[string // (a downstream that's also a sub-graph member doesn't count — that // node is part of the loop's body, not the outer graph's edge). terminals := make([]string, 0, len(c.Components)) + roots := make(map[string]bool) + entries := make(map[string]bool) for cpnID, comp := range c.Components { + if !macroMembers[cpnID] && len(comp.Upstream) == 0 && !first[cpnID] { + roots[cpnID] = true + if strings.EqualFold(comp.Obj.ComponentName, "Begin") || strings.EqualFold(comp.Obj.ComponentName, "File") { + entries[cpnID] = true + } + } if node, isMacro := macroNodes[cpnID]; isMacro { // Macro parents with no upstream are START nodes. Parents // with upstream had their AddInput wired in Pass 2 already. @@ -773,6 +781,14 @@ func BuildWorkflow(ctx context.Context, c *Canvas) (*compose.Workflow[map[string } } + if len(entries) > 0 && len(entries) < len(roots) { + // Branch pruning skips disconnected roots and their terminal dependencies. + wf.AddBranch(compose.START, compose.NewGraphMultiBranch( + func(context.Context, map[string]any) (map[string]bool, error) { + return entries, nil + }, roots)) + } + if err := wireWorkflowTerminals(wf, terminals, "", true); err != nil { return nil, err } diff --git a/internal/service/agent_run_e2e_test.go b/internal/service/agent_run_e2e_test.go index 49720712c4..fa7e443061 100644 --- a/internal/service/agent_run_e2e_test.go +++ b/internal/service/agent_run_e2e_test.go @@ -548,20 +548,21 @@ func TestRunAgent_RealCanvas_WaitForUserResume(t *testing.T) { "component_name": "Begin", "params": map[string]any{}, }, - "downstream": []any{"message_0"}, + "downstream": []any{"user_fill_up_0"}, }, "message_0": map[string]any{ "obj": map[string]any{ "component_name": "Message", "params": map[string]any{"text": "got: {{user_fill_up_0@user_input}}"}, }, - "upstream": []any{"begin_0", "user_fill_up_0"}, + "upstream": []any{"user_fill_up_0"}, }, "user_fill_up_0": map[string]any{ "obj": map[string]any{ "component_name": "UserFillUp", "params": map[string]any{"enable_tips": true}, }, + "upstream": []any{"begin_0"}, "downstream": []any{"message_0"}, }, }, @@ -782,6 +783,7 @@ func TestRunAgent_RealCanvas_WaitForUserResume_EventSemantics(t *testing.T) { "component_name": "UserFillUp", "params": map[string]any{"enable_tips": true}, }, + "upstream": []any{"begin_0"}, "downstream": []any{"message_0"}, }, "message_0": map[string]any{ @@ -789,7 +791,7 @@ func TestRunAgent_RealCanvas_WaitForUserResume_EventSemantics(t *testing.T) { "component_name": "Message", "params": map[string]any{"text": "got: {{user_fill_up_0@user_input}}"}, }, - "upstream": []any{"begin_0", "user_fill_up_0"}, + "upstream": []any{"user_fill_up_0"}, }, }, "path": []any{"begin_0", "user_fill_up_0", "message_0"},