diff --git a/.github/skills/agentic-workflow-designer/SKILL.md b/.github/skills/agentic-workflow-designer/SKILL.md index 0550b85156d..b7ee93f0ea5 100644 --- a/.github/skills/agentic-workflow-designer/SKILL.md +++ b/.github/skills/agentic-workflow-designer/SKILL.md @@ -152,13 +152,8 @@ Present a structured summary and ask for approval before generation. | "link a sub-issue" | `link-sub-issue` | | "add labels", "remove labels" | `add-labels`, `remove-labels` | | "replace a label with another" | `replace-label` | -| "report missing data needed for the task" | `missing-data` (system type, auto-enabled) | -| "report unavailable or missing tool/permission" | `missing-tool` (system type, auto-enabled) | -| "signal task could not be completed due to infrastructure failure" | `report-incomplete` (system type, auto-enabled) | | "nothing visible", "just analyze" | no safe outputs required | -> **System types** (`missing-data`, `missing-tool`, `report-incomplete`) are error-signaling outputs that are automatically available in every workflow without being declared in `safe-outputs:`. They emit structured infrastructure signals (not task results) and can be disabled explicitly (e.g. `missing-tool: false`) but should rarely be suppressed. - ### Network Mapping | User says... | Maps to | @@ -317,14 +312,8 @@ After confirmation, generate one workflow file using the same skeleton style as --- emoji: description: -strict: true on: -max-turns: -max-ai-credits: -max-daily-ai-credits: -user-rate-limit: -models: permissions: contents: read issues: read diff --git a/pkg/workflow/runtime_override_test.go b/pkg/workflow/runtime_override_test.go index 881f6c1c544..12d076bdea0 100644 --- a/pkg/workflow/runtime_override_test.go +++ b/pkg/workflow/runtime_override_test.go @@ -3,6 +3,8 @@ package workflow import ( + "maps" + "reflect" "testing" ) @@ -230,6 +232,142 @@ func TestApplyRuntimeOverrides(t *testing.T) { } } +func TestApplyRuntimeOverrides_KnownRuntimeActionOverrides(t *testing.T) { + var knownNode *Runtime + for _, runtime := range knownRuntimes { + if runtime.ID == "node" { + knownNode = runtime + break + } + } + if knownNode == nil { + t.Fatal("expected known node runtime to exist") + } + originalKnownNode := *knownNode + originalKnownNode.Commands = append([]string(nil), knownNode.Commands...) + originalKnownNode.ManifestFiles = append([]string(nil), knownNode.ManifestFiles...) + originalKnownNode.ExtraWithFields = maps.Clone(knownNode.ExtraWithFields) + + requirements := map[string]*RuntimeRequirement{} + + applyRuntimeOverrides(map[string]any{ + "node": map[string]any{ + "version": "22", + "action-repo": "custom/setup-node", + "action-version": "v5", + }, + }, requirements) + + nodeReq, ok := requirements["node"] + if !ok { + t.Fatal("expected node requirement to be added") + } + if nodeReq.Version != "22" { + t.Fatalf("expected version 22, got %s", nodeReq.Version) + } + if nodeReq.Runtime == knownNode { + t.Fatal("expected action overrides to clone the known runtime") + } + if nodeReq.Runtime.ActionRepo != "custom/setup-node" { + t.Fatalf("expected ActionRepo custom/setup-node, got %s", nodeReq.Runtime.ActionRepo) + } + if nodeReq.Runtime.ActionVersion != "v5" { + t.Fatalf("expected ActionVersion v5, got %s", nodeReq.Runtime.ActionVersion) + } + if knownNode.ActionRepo != "actions/setup-node" { + t.Fatalf("expected known runtime ActionRepo to remain actions/setup-node, got %s", knownNode.ActionRepo) + } + if knownNode.ActionVersion != "v6" { + t.Fatalf("expected known runtime ActionVersion to remain v6, got %s", knownNode.ActionVersion) + } + if !reflect.DeepEqual(*knownNode, originalKnownNode) { + t.Fatal("expected all known runtime fields to remain unchanged") + } +} + +func TestApplyRuntimeOverrides_KnownRuntimeActionRepoOnly(t *testing.T) { + var knownNode *Runtime + for _, runtime := range knownRuntimes { + if runtime.ID == "node" { + knownNode = runtime + break + } + } + if knownNode == nil { + t.Fatal("expected known node runtime to exist") + } + + originalKnownNode := *knownNode + originalKnownNode.Commands = append([]string(nil), knownNode.Commands...) + originalKnownNode.ManifestFiles = append([]string(nil), knownNode.ManifestFiles...) + originalKnownNode.ExtraWithFields = maps.Clone(knownNode.ExtraWithFields) + + requirements := map[string]*RuntimeRequirement{} + applyRuntimeOverrides(map[string]any{ + "node": map[string]any{ + "action-repo": "custom/setup-node", + }, + }, requirements) + + nodeReq, ok := requirements["node"] + if !ok { + t.Fatal("expected node requirement to be added") + } + if nodeReq.Runtime == knownNode { + t.Fatal("expected action overrides to clone the known runtime") + } + if nodeReq.Runtime.ActionRepo != "custom/setup-node" { + t.Fatalf("expected ActionRepo custom/setup-node, got %s", nodeReq.Runtime.ActionRepo) + } + if nodeReq.Runtime.ActionVersion != knownNode.ActionVersion { + t.Fatalf("expected ActionVersion %s, got %s", knownNode.ActionVersion, nodeReq.Runtime.ActionVersion) + } + if !reflect.DeepEqual(*knownNode, originalKnownNode) { + t.Fatal("expected all known runtime fields to remain unchanged") + } +} + +func TestApplyRuntimeOverrides_ExistingRequirementActionRepoOnlyClonesRuntime(t *testing.T) { + existingRuntime := &Runtime{ + ID: "node", + ActionRepo: "actions/setup-node", + ActionVersion: "v4", + Commands: []string{"node --version"}, + ManifestFiles: []string{"package.json"}, + ExtraWithFields: map[string]string{"cache": "npm"}, + } + originalExistingRuntime := *existingRuntime + originalExistingRuntime.Commands = append([]string(nil), existingRuntime.Commands...) + originalExistingRuntime.ManifestFiles = append([]string(nil), existingRuntime.ManifestFiles...) + originalExistingRuntime.ExtraWithFields = maps.Clone(existingRuntime.ExtraWithFields) + + requirements := map[string]*RuntimeRequirement{ + "node": { + Runtime: existingRuntime, + Version: "20", + }, + } + applyRuntimeOverrides(map[string]any{ + "node": map[string]any{ + "action-repo": "custom/setup-node", + }, + }, requirements) + + nodeReq := requirements["node"] + if nodeReq.Runtime == existingRuntime { + t.Fatal("expected existing runtime to be cloned when applying action overrides") + } + if nodeReq.Runtime.ActionRepo != "custom/setup-node" { + t.Fatalf("expected ActionRepo custom/setup-node, got %s", nodeReq.Runtime.ActionRepo) + } + if nodeReq.Runtime.ActionVersion != "v4" { + t.Fatalf("expected ActionVersion v4, got %s", nodeReq.Runtime.ActionVersion) + } + if !reflect.DeepEqual(*existingRuntime, originalExistingRuntime) { + t.Fatal("expected existing runtime fields to remain unchanged") + } +} + func TestDetectRuntimeRequirementsWithOverrides(t *testing.T) { tests := []struct { name string diff --git a/pkg/workflow/runtime_overrides.go b/pkg/workflow/runtime_overrides.go index e01b6a7e92e..6a9b9436c97 100644 --- a/pkg/workflow/runtime_overrides.go +++ b/pkg/workflow/runtime_overrides.go @@ -2,9 +2,31 @@ package workflow import ( "fmt" + "maps" + "slices" "strconv" ) +func cloneRuntimeWithActionOverrides(base *Runtime, actionRepo, actionVersion string) *Runtime { + if base == nil { + return nil + } + + customRuntime := *base + customRuntime.Commands = slices.Clone(base.Commands) + customRuntime.ManifestFiles = slices.Clone(base.ManifestFiles) + customRuntime.ExtraWithFields = maps.Clone(base.ExtraWithFields) + + if actionRepo != "" { + customRuntime.ActionRepo = actionRepo + } + if actionVersion != "" { + customRuntime.ActionVersion = actionVersion + } + + return &customRuntime +} + // applyRuntimeOverrides applies runtime version overrides from frontmatter func applyRuntimeOverrides(runtimes map[string]any, requirements map[string]*RuntimeRequirement) { runtimeSetupLog.Printf("Applying runtime overrides for %d configured runtimes", len(runtimes)) @@ -70,27 +92,7 @@ func applyRuntimeOverrides(runtimes map[string]any, requirements map[string]*Run // If action-repo or action-version is specified, create a custom Runtime if actionRepo != "" || actionVersion != "" { runtimeSetupLog.Printf("Applying custom action config for runtime %s: repo=%s, version=%s", runtimeID, actionRepo, actionVersion) - // Clone the existing runtime to avoid modifying the global knownRuntimes - customRuntime := &Runtime{ - ID: existing.Runtime.ID, - Name: existing.Runtime.Name, - ActionRepo: existing.Runtime.ActionRepo, - ActionVersion: existing.Runtime.ActionVersion, - VersionField: existing.Runtime.VersionField, - DefaultVersion: existing.Runtime.DefaultVersion, - Commands: existing.Runtime.Commands, - ExtraWithFields: existing.Runtime.ExtraWithFields, - } - - // Apply overrides - if actionRepo != "" { - customRuntime.ActionRepo = actionRepo - } - if actionVersion != "" { - customRuntime.ActionVersion = actionVersion - } - - existing.Runtime = customRuntime + existing.Runtime = cloneRuntimeWithActionOverrides(existing.Runtime, actionRepo, actionVersion) } } else { // Check if this is a known runtime @@ -101,24 +103,7 @@ func applyRuntimeOverrides(runtimes map[string]any, requirements map[string]*Run // Clone the known runtime if we need to customize it if actionRepo != "" || actionVersion != "" { runtimeSetupLog.Printf("Cloning known runtime %s with custom action config: repo=%s, version=%s", runtimeID, actionRepo, actionVersion) - runtime = &Runtime{ - ID: knownRuntime.ID, - Name: knownRuntime.Name, - ActionRepo: knownRuntime.ActionRepo, - ActionVersion: knownRuntime.ActionVersion, - VersionField: knownRuntime.VersionField, - DefaultVersion: knownRuntime.DefaultVersion, - Commands: knownRuntime.Commands, - ExtraWithFields: knownRuntime.ExtraWithFields, - } - - // Apply overrides - if actionRepo != "" { - runtime.ActionRepo = actionRepo - } - if actionVersion != "" { - runtime.ActionVersion = actionVersion - } + runtime = cloneRuntimeWithActionOverrides(knownRuntime, actionRepo, actionVersion) } else { runtimeSetupLog.Printf("Using known runtime %s as-is", runtimeID) runtime = knownRuntime