Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 0 additions & 25 deletions .github/aw/actions-lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -165,31 +165,6 @@
"version": "v3",
"sha": "3477b6488008d9411aaf22a0924ec7c1f6a69980"
},
"github/stale-repos@v3.0.2": {
"repo": "github/stale-repos",
"version": "v3.0.2",
"sha": "a21e55567b83cf3c3f3f9085d3038dc6cee02598"
},
"haskell-actions/setup@v2.10.3": {
"repo": "haskell-actions/setup",
"version": "v2.10.3",
"sha": "9cd1b7bf3f36d5a3c3b17abc3545bfb5481912ea"
},
"oven-sh/setup-bun@v2.1.2": {
"repo": "oven-sh/setup-bun",
"version": "v2.1.2",
"sha": "3d267786b128fe76c2f16a390aa2448b815359f3"
},
"ruby/setup-ruby@v1.288.0": {
"repo": "ruby/setup-ruby",
"version": "v1.288.0",
"sha": "09a7688d3b55cf0e976497ff046b70949eeaccfd"
},
"super-linter/super-linter@v8.2.1": {
"repo": "super-linter/super-linter",
"version": "v8.2.1",
"sha": "2bdd90ed3262e023ac84bf8fe35dc480721fc1f2"
},
"super-linter/super-linter@v8.5.0": {
"repo": "super-linter/super-linter",
"version": "v8.5.0",
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/docs-noob-tester.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .github/workflows/slide-deck-maintainer.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .github/workflows/stale-repo-identifier.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions docs/src/content/docs/reference/glossary.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,10 @@ A GitHub Personal Access Token with granular permission control, specifying exac

The `gh-aw` extension for GitHub CLI providing commands for managing agentic workflows: compile, run, status, logs, add, and project management.

### Playground

An interactive web-based editor for authoring, compiling, and previewing agentic workflows without local installation. The Playground runs the gh-aw compiler in the browser using [WebAssembly](#webassembly-wasm) and auto-saves editor content to `localStorage` so work is preserved across sessions. Available at `/gh-aw/editor/`.

### Validation

Checking workflow files for errors, security issues, and best practices. Occurs during compilation and can be enhanced with strict mode and security scanners.
Expand Down
8 changes: 4 additions & 4 deletions pkg/cli/audit_report.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/github/gh-aw/pkg/constants"
"github.com/github/gh-aw/pkg/logger"
"github.com/github/gh-aw/pkg/sliceutil"
"github.com/github/gh-aw/pkg/timeutil"
"github.com/github/gh-aw/pkg/workflow"
)
Expand Down Expand Up @@ -228,8 +229,7 @@ func buildAuditData(processedRun ProcessedRun, metrics LogMetrics, mcpToolUsage
}

// Build job data
var jobs []JobData
for _, jobDetail := range processedRun.JobDetails {
jobs := sliceutil.Map(processedRun.JobDetails, func(jobDetail JobInfoWithDuration) JobData {
job := JobData{
Name: jobDetail.Name,
Status: jobDetail.Status,
Expand All @@ -238,8 +238,8 @@ func buildAuditData(processedRun ProcessedRun, metrics LogMetrics, mcpToolUsage
if jobDetail.Duration > 0 {
job.Duration = timeutil.FormatDuration(jobDetail.Duration)
}
jobs = append(jobs, job)
}
return job
})

// Build downloaded files list
downloadedFiles := extractDownloadedFiles(run.LogsPath)
Expand Down
13 changes: 7 additions & 6 deletions pkg/cli/audit_report_analysis.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,12 +254,13 @@ func generateFailureAnalysis(processedRun ProcessedRun, errors []ErrorInfo) *Fai
}

// Collect failed job names
var failedJobs []string
for _, job := range processedRun.JobDetails {
if job.Conclusion == "failure" || job.Conclusion == "timed_out" || job.Conclusion == "cancelled" {
failedJobs = append(failedJobs, job.Name)
}
}
failedJobs := sliceutil.FilterMap(
processedRun.JobDetails,
func(job JobInfoWithDuration) bool {
return job.Conclusion == "failure" || job.Conclusion == "timed_out" || job.Conclusion == "cancelled"
},
func(job JobInfoWithDuration) string { return job.Name },
)

// Generate error summary
errorSummary := "No specific errors identified"
Expand Down
38 changes: 14 additions & 24 deletions pkg/cli/compile_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cli

import (
"github.com/github/gh-aw/pkg/logger"
"github.com/github/gh-aw/pkg/sliceutil"
"github.com/github/gh-aw/pkg/stringutil"
)

Expand Down Expand Up @@ -78,34 +79,23 @@ func sanitizeValidationResults(results []ValidationResult) []ValidationResult {

compileConfigLog.Printf("Sanitizing validation results: workflow_count=%d", len(results))

sanitized := make([]ValidationResult, len(results))
for i, result := range results {
sanitized[i] = ValidationResult{
return sliceutil.Map(results, func(result ValidationResult) ValidationResult {
return ValidationResult{
Workflow: result.Workflow,
Valid: result.Valid,
CompiledFile: result.CompiledFile,
Errors: make([]CompileValidationError, len(result.Errors)),
Warnings: make([]CompileValidationError, len(result.Warnings)),
}

// Sanitize all error messages
for j, err := range result.Errors {
sanitized[i].Errors[j] = CompileValidationError{
Type: err.Type,
Message: stringutil.SanitizeErrorMessage(err.Message),
Line: err.Line,
}
Errors: sliceutil.Map(result.Errors, sanitizeCompileValidationError),
Warnings: sliceutil.Map(result.Warnings, sanitizeCompileValidationError),
}
})
}

// Sanitize all warning messages
for j, warn := range result.Warnings {
sanitized[i].Warnings[j] = CompileValidationError{
Type: warn.Type,
Message: stringutil.SanitizeErrorMessage(warn.Message),
Line: warn.Line,
}
}
// sanitizeCompileValidationError returns a copy of the error with its message sanitized
// to remove potential secret key names.
func sanitizeCompileValidationError(err CompileValidationError) CompileValidationError {
return CompileValidationError{
Type: err.Type,
Message: stringutil.SanitizeErrorMessage(err.Message),
Line: err.Line,
}

return sanitized
}
25 changes: 0 additions & 25 deletions pkg/workflow/data/action_pins.json
Original file line number Diff line number Diff line change
Expand Up @@ -165,31 +165,6 @@
"version": "v3",
"sha": "3477b6488008d9411aaf22a0924ec7c1f6a69980"
},
"github/stale-repos@v3.0.2": {
"repo": "github/stale-repos",
"version": "v3.0.2",
"sha": "a21e55567b83cf3c3f3f9085d3038dc6cee02598"
},
"haskell-actions/setup@v2.10.3": {
"repo": "haskell-actions/setup",
"version": "v2.10.3",
"sha": "9cd1b7bf3f36d5a3c3b17abc3545bfb5481912ea"
},
"oven-sh/setup-bun@v2.1.2": {
"repo": "oven-sh/setup-bun",
"version": "v2.1.2",
"sha": "3d267786b128fe76c2f16a390aa2448b815359f3"
},
"ruby/setup-ruby@v1.288.0": {
"repo": "ruby/setup-ruby",
"version": "v1.288.0",
"sha": "09a7688d3b55cf0e976497ff046b70949eeaccfd"
},
"super-linter/super-linter@v8.2.1": {
"repo": "super-linter/super-linter",
"version": "v8.2.1",
"sha": "2bdd90ed3262e023ac84bf8fe35dc480721fc1f2"
},
"super-linter/super-linter@v8.5.0": {
"repo": "super-linter/super-linter",
"version": "v8.5.0",
Expand Down
33 changes: 33 additions & 0 deletions pkg/workflow/domains.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,13 @@ var ClaudeDefaultDomains = []string{
"ts-ocsp.ws.symantec.com",
}

// PlaywrightDomains are the domains required for Playwright browser downloads
// These domains are needed when Playwright MCP server initializes in the Docker container
var PlaywrightDomains = []string{
"cdn.playwright.dev",
"playwright.download.prss.microsoft.com",
}
Comment on lines +102 to +107
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Major discrepancy between PR description and actual changes

The PR title and description claim this PR improves "pkg/cli functional/immutability patterns", but the majority of changes are unrelated to this stated purpose:

  1. pkg/workflow changes (NOT mentioned in description):

    • New Playwright domain support in domains.go
    • New test cases in http_mcp_domains_test.go
  2. Lock file changes (NOT mentioned in description):

    • Removal of 6 action pin entries in action_pins.json and actions-lock.json
    • SHA updates in workflow lock files
    • Playwright domain additions to workflow allow-lists
  3. Documentation changes (NOT mentioned in description):

    • New Playground glossary entry

The PR description should accurately reflect all changes being made. Either:

  • Split this into separate PRs (one for functional patterns, one for Playwright support)
  • Update the PR description to accurately describe all changes included

Copilot uses AI. Check for mistakes.

// init loads the ecosystem domains from the embedded JSON
func init() {
domainsLog.Print("Loading ecosystem domains from embedded JSON")
Expand Down Expand Up @@ -349,6 +356,23 @@ func extractHTTPMCPDomains(tools map[string]any) []string {
return domains
}

// extractPlaywrightDomains returns Playwright domains when Playwright tool is configured
// Returns a slice of domain names required for Playwright browser downloads
// These domains are needed when Playwright MCP server initializes in the Docker container
func extractPlaywrightDomains(tools map[string]any) []string {
if tools == nil {
return []string{}
}

// Check if Playwright tool is configured
if _, hasPlaywright := tools["playwright"]; hasPlaywright {
domainsLog.Printf("Detected Playwright tool, adding %d domains for browser downloads", len(PlaywrightDomains))
return PlaywrightDomains
}

return []string{}
}

// mergeDomainsWithNetwork combines default domains with NetworkPermissions allowed domains
// Returns a deduplicated, sorted, comma-separated string suitable for AWF's --allow-domains flag
func mergeDomainsWithNetwork(defaultDomains []string, network *NetworkPermissions) string {
Expand Down Expand Up @@ -388,6 +412,15 @@ func mergeDomainsWithNetworkToolsAndRuntimes(defaultDomains []string, network *N
}
}

// Add Playwright ecosystem domains (if Playwright tool is specified)
// This ensures browser binaries can be downloaded when Playwright initializes
if tools != nil {
playwrightDomains := extractPlaywrightDomains(tools)
for _, domain := range playwrightDomains {
domainMap[domain] = true
}
}

// Add runtime ecosystem domains (if runtimes are specified)
if runtimes != nil {
runtimeDomains := getDomainsFromRuntimes(runtimes)
Expand Down
104 changes: 104 additions & 0 deletions pkg/workflow/http_mcp_domains_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -232,3 +232,107 @@ func TestGetClaudeAllowedDomainsWithTools(t *testing.T) {
require.Contains(t, result, "anthropic.com", "Should include Claude defaults")
require.Contains(t, result, "registry.npmjs.org", "Should include Node ecosystem")
}

// TestExtractPlaywrightDomains tests extraction of Playwright ecosystem domains when Playwright tool is configured
func TestExtractPlaywrightDomains(t *testing.T) {
tests := []struct {
name string
tools map[string]any
expected []string
}{
{
name: "playwright tool configured",
tools: map[string]any{
"playwright": map[string]any{
"allowed_domains": []string{"github.com"},
},
},
expected: []string{"playwright.download.prss.microsoft.com", "cdn.playwright.dev"},
},
{
name: "playwright tool with empty config",
tools: map[string]any{
"playwright": map[string]any{},
},
expected: []string{"playwright.download.prss.microsoft.com", "cdn.playwright.dev"},
},
{
name: "playwright tool with null config",
tools: map[string]any{
"playwright": nil,
},
expected: []string{"playwright.download.prss.microsoft.com", "cdn.playwright.dev"},
},
{
name: "no playwright tool",
tools: map[string]any{
"github": map[string]any{
"mode": "local",
},
},
expected: []string{},
},
{
name: "empty tools",
tools: map[string]any{},
expected: []string{},
},
{
name: "nil tools",
tools: nil,
expected: []string{},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := extractPlaywrightDomains(tt.tools)

// Sort both slices for comparison
SortStrings(result)
SortStrings(tt.expected)

assert.Equal(t, tt.expected, result, "Extracted Playwright domains should match expected")
})
}
}

// TestGetCopilotAllowedDomainsWithPlaywright tests that Playwright domains are automatically included for Copilot engine
func TestGetCopilotAllowedDomainsWithPlaywright(t *testing.T) {
network := &NetworkPermissions{
Allowed: []string{"defaults"},
}

tools := map[string]any{
"playwright": map[string]any{
"allowed_domains": []string{"github.com"},
},
}

result := GetCopilotAllowedDomainsWithTools(network, tools)

// Should include Copilot defaults and Playwright ecosystem domains
require.Contains(t, result, "playwright.download.prss.microsoft.com", "Should include Playwright download domain")
require.Contains(t, result, "cdn.playwright.dev", "Should include Playwright CDN domain")
require.Contains(t, result, "api.githubcopilot.com", "Should include Copilot defaults")
}

// TestGetCodexAllowedDomainsWithPlaywright tests that Playwright domains are automatically included for Codex engine
func TestGetCodexAllowedDomainsWithPlaywright(t *testing.T) {
network := &NetworkPermissions{
Allowed: []string{"defaults"},
}

tools := map[string]any{
"playwright": map[string]any{
"allowed_domains": []string{"example.com"},
},
}

result := GetCodexAllowedDomainsWithTools(network, tools)

// Should include Codex defaults and Playwright ecosystem domains
require.Contains(t, result, "playwright.download.prss.microsoft.com", "Should include Playwright download domain")
require.Contains(t, result, "cdn.playwright.dev", "Should include Playwright CDN domain")
require.Contains(t, result, "api.openai.com", "Should include Codex defaults")
}