From ef5cde75592cb19bdb5eb75d7cff03ac4c4e98f7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 14 Sep 2025 17:26:00 +0000 Subject: [PATCH 1/6] Initial plan From 05fde766544b15ab8a91659883807b05aeec709b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 14 Sep 2025 17:44:32 +0000 Subject: [PATCH 2/6] Implement playwright tool allowlist to match copilot agent MCP tools Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/workflow/claude_engine.go | 48 ++++++++++++++++++++++- pkg/workflow/claude_engine_tools_test.go | 7 ++++ pkg/workflow/neutral_tools_test.go | 50 ++++++++++++++++++++++++ 3 files changed, 103 insertions(+), 2 deletions(-) diff --git a/pkg/workflow/claude_engine.go b/pkg/workflow/claude_engine.go index 5384008c4cf..949589e9c9f 100644 --- a/pkg/workflow/claude_engine.go +++ b/pkg/workflow/claude_engine.go @@ -238,6 +238,41 @@ func (e *ClaudeEngine) convertStepToYAML(stepMap map[string]any) (string, error) return ConvertStepToYAML(stepMap) } +// getCopilotAgentPlaywrightTools returns the list of playwright tools available in the copilot agent +// This matches the tools available in the copilot agent MCP server configuration +func (e *ClaudeEngine) getCopilotAgentPlaywrightTools() []any { + tools := []string{ + "browser_click", + "browser_close", + "browser_console_messages", + "browser_drag", + "browser_evaluate", + "browser_file_upload", + "browser_fill_form", + "browser_handle_dialog", + "browser_hover", + "browser_install", + "browser_navigate", + "browser_navigate_back", + "browser_network_requests", + "browser_press_key", + "browser_resize", + "browser_select_option", + "browser_snapshot", + "browser_tabs", + "browser_take_screenshot", + "browser_type", + "browser_wait_for", + } + + // Convert []string to []any for compatibility with the configuration system + result := make([]any, len(tools)) + for i, tool := range tools { + result[i] = tool + } + return result +} + // expandNeutralToolsToClaudeTools converts neutral tools to Claude-specific tools format func (e *ClaudeEngine) expandNeutralToolsToClaudeTools(tools map[string]any) map[string]any { result := make(map[string]any) @@ -310,6 +345,15 @@ func (e *ClaudeEngine) expandNeutralToolsToClaudeTools(tools map[string]any) map _ = editTool } + // Handle playwright tool by converting it to an MCP tool configuration + if _, hasPlaywright := tools["playwright"]; hasPlaywright { + // Create playwright as an MCP tool with the same tools available as copilot agent + playwrightMCP := map[string]any{ + "allowed": e.getCopilotAgentPlaywrightTools(), + } + result["playwright"] = playwrightMCP + } + // Update claude section claudeSection["allowed"] = claudeAllowed result["claude"] = claudeSection @@ -468,8 +512,8 @@ func (e *ClaudeEngine) computeAllowedClaudeToolsString(tools map[string]any, saf isCustomMCP = true } - // Handle standard MCP tools (github) or tools with MCP-compatible type - if toolName == "github" || isCustomMCP { + // Handle standard MCP tools (github, playwright) or tools with MCP-compatible type + if toolName == "github" || toolName == "playwright" || isCustomMCP { if allowed, hasAllowed := mcpConfig["allowed"]; hasAllowed { if allowedSlice, ok := allowed.([]any); ok { // Check for wildcard access first diff --git a/pkg/workflow/claude_engine_tools_test.go b/pkg/workflow/claude_engine_tools_test.go index c756ef290b3..de422665c77 100644 --- a/pkg/workflow/claude_engine_tools_test.go +++ b/pkg/workflow/claude_engine_tools_test.go @@ -227,6 +227,13 @@ func TestClaudeEngineComputeAllowedTools(t *testing.T) { }, expected: "Bash,BashOutput,ExitPlanMode,Glob,Grep,KillBash,LS,NotebookRead,Read,Task,TodoWrite", }, + { + name: "neutral playwright tool", + tools: map[string]any{ + "playwright": nil, + }, + expected: "ExitPlanMode,Glob,Grep,LS,NotebookRead,Read,Task,TodoWrite,mcp__playwright__browser_click,mcp__playwright__browser_close,mcp__playwright__browser_console_messages,mcp__playwright__browser_drag,mcp__playwright__browser_evaluate,mcp__playwright__browser_file_upload,mcp__playwright__browser_fill_form,mcp__playwright__browser_handle_dialog,mcp__playwright__browser_hover,mcp__playwright__browser_install,mcp__playwright__browser_navigate,mcp__playwright__browser_navigate_back,mcp__playwright__browser_network_requests,mcp__playwright__browser_press_key,mcp__playwright__browser_resize,mcp__playwright__browser_select_option,mcp__playwright__browser_snapshot,mcp__playwright__browser_tabs,mcp__playwright__browser_take_screenshot,mcp__playwright__browser_type,mcp__playwright__browser_wait_for", + }, } for _, tt := range tests { diff --git a/pkg/workflow/neutral_tools_test.go b/pkg/workflow/neutral_tools_test.go index ad8b4dd6cd3..d0f7d7b0e53 100644 --- a/pkg/workflow/neutral_tools_test.go +++ b/pkg/workflow/neutral_tools_test.go @@ -98,6 +98,31 @@ func TestExpandNeutralToolsToClaudeTools(t *testing.T) { "claude": map[string]any{ "allowed": map[string]any{}, }, + "playwright": map[string]any{ + "allowed": []any{ + "browser_click", + "browser_close", + "browser_console_messages", + "browser_drag", + "browser_evaluate", + "browser_file_upload", + "browser_fill_form", + "browser_handle_dialog", + "browser_hover", + "browser_install", + "browser_navigate", + "browser_navigate_back", + "browser_network_requests", + "browser_press_key", + "browser_resize", + "browser_select_option", + "browser_snapshot", + "browser_tabs", + "browser_take_screenshot", + "browser_type", + "browser_wait_for", + }, + }, }, }, { @@ -121,6 +146,31 @@ func TestExpandNeutralToolsToClaudeTools(t *testing.T) { "Write": nil, }, }, + "playwright": map[string]any{ + "allowed": []any{ + "browser_click", + "browser_close", + "browser_console_messages", + "browser_drag", + "browser_evaluate", + "browser_file_upload", + "browser_fill_form", + "browser_handle_dialog", + "browser_hover", + "browser_install", + "browser_navigate", + "browser_navigate_back", + "browser_network_requests", + "browser_press_key", + "browser_resize", + "browser_select_option", + "browser_snapshot", + "browser_tabs", + "browser_take_screenshot", + "browser_type", + "browser_wait_for", + }, + }, }, }, { From 9f3c4c74272bbf265da5655331724e6d6b44939c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 14 Sep 2025 17:45:33 +0000 Subject: [PATCH 3/6] Final validation complete - playwright tool allowlist implementation working correctly Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- ...playwright-accessibility-contrast.lock.yml | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/pkg/cli/workflows/test-playwright-accessibility-contrast.lock.yml b/pkg/cli/workflows/test-playwright-accessibility-contrast.lock.yml index 6b925e2f6c5..c85565eb48d 100644 --- a/pkg/cli/workflows/test-playwright-accessibility-contrast.lock.yml +++ b/pkg/cli/workflows/test-playwright-accessibility-contrast.lock.yml @@ -361,7 +361,28 @@ jobs: # - mcp__github__search_pull_requests # - mcp__github__search_repositories # - mcp__github__search_users - allowed_tools: "ExitPlanMode,Glob,Grep,LS,NotebookRead,Read,Task,TodoWrite,Write,mcp__github__download_workflow_run_artifact,mcp__github__get_code_scanning_alert,mcp__github__get_commit,mcp__github__get_dependabot_alert,mcp__github__get_discussion,mcp__github__get_discussion_comments,mcp__github__get_file_contents,mcp__github__get_issue,mcp__github__get_issue_comments,mcp__github__get_job_logs,mcp__github__get_me,mcp__github__get_notification_details,mcp__github__get_pull_request,mcp__github__get_pull_request_comments,mcp__github__get_pull_request_diff,mcp__github__get_pull_request_files,mcp__github__get_pull_request_reviews,mcp__github__get_pull_request_status,mcp__github__get_secret_scanning_alert,mcp__github__get_tag,mcp__github__get_workflow_run,mcp__github__get_workflow_run_logs,mcp__github__get_workflow_run_usage,mcp__github__list_branches,mcp__github__list_code_scanning_alerts,mcp__github__list_commits,mcp__github__list_dependabot_alerts,mcp__github__list_discussion_categories,mcp__github__list_discussions,mcp__github__list_issues,mcp__github__list_notifications,mcp__github__list_pull_requests,mcp__github__list_secret_scanning_alerts,mcp__github__list_tags,mcp__github__list_workflow_jobs,mcp__github__list_workflow_run_artifacts,mcp__github__list_workflow_runs,mcp__github__list_workflows,mcp__github__search_code,mcp__github__search_issues,mcp__github__search_orgs,mcp__github__search_pull_requests,mcp__github__search_repositories,mcp__github__search_users" + # - mcp__playwright__browser_click + # - mcp__playwright__browser_close + # - mcp__playwright__browser_console_messages + # - mcp__playwright__browser_drag + # - mcp__playwright__browser_evaluate + # - mcp__playwright__browser_file_upload + # - mcp__playwright__browser_fill_form + # - mcp__playwright__browser_handle_dialog + # - mcp__playwright__browser_hover + # - mcp__playwright__browser_install + # - mcp__playwright__browser_navigate + # - mcp__playwright__browser_navigate_back + # - mcp__playwright__browser_network_requests + # - mcp__playwright__browser_press_key + # - mcp__playwright__browser_resize + # - mcp__playwright__browser_select_option + # - mcp__playwright__browser_snapshot + # - mcp__playwright__browser_tabs + # - mcp__playwright__browser_take_screenshot + # - mcp__playwright__browser_type + # - mcp__playwright__browser_wait_for + allowed_tools: "ExitPlanMode,Glob,Grep,LS,NotebookRead,Read,Task,TodoWrite,Write,mcp__github__download_workflow_run_artifact,mcp__github__get_code_scanning_alert,mcp__github__get_commit,mcp__github__get_dependabot_alert,mcp__github__get_discussion,mcp__github__get_discussion_comments,mcp__github__get_file_contents,mcp__github__get_issue,mcp__github__get_issue_comments,mcp__github__get_job_logs,mcp__github__get_me,mcp__github__get_notification_details,mcp__github__get_pull_request,mcp__github__get_pull_request_comments,mcp__github__get_pull_request_diff,mcp__github__get_pull_request_files,mcp__github__get_pull_request_reviews,mcp__github__get_pull_request_status,mcp__github__get_secret_scanning_alert,mcp__github__get_tag,mcp__github__get_workflow_run,mcp__github__get_workflow_run_logs,mcp__github__get_workflow_run_usage,mcp__github__list_branches,mcp__github__list_code_scanning_alerts,mcp__github__list_commits,mcp__github__list_dependabot_alerts,mcp__github__list_discussion_categories,mcp__github__list_discussions,mcp__github__list_issues,mcp__github__list_notifications,mcp__github__list_pull_requests,mcp__github__list_secret_scanning_alerts,mcp__github__list_tags,mcp__github__list_workflow_jobs,mcp__github__list_workflow_run_artifacts,mcp__github__list_workflow_runs,mcp__github__list_workflows,mcp__github__search_code,mcp__github__search_issues,mcp__github__search_orgs,mcp__github__search_pull_requests,mcp__github__search_repositories,mcp__github__search_users,mcp__playwright__browser_click,mcp__playwright__browser_close,mcp__playwright__browser_console_messages,mcp__playwright__browser_drag,mcp__playwright__browser_evaluate,mcp__playwright__browser_file_upload,mcp__playwright__browser_fill_form,mcp__playwright__browser_handle_dialog,mcp__playwright__browser_hover,mcp__playwright__browser_install,mcp__playwright__browser_navigate,mcp__playwright__browser_navigate_back,mcp__playwright__browser_network_requests,mcp__playwright__browser_press_key,mcp__playwright__browser_resize,mcp__playwright__browser_select_option,mcp__playwright__browser_snapshot,mcp__playwright__browser_tabs,mcp__playwright__browser_take_screenshot,mcp__playwright__browser_type,mcp__playwright__browser_wait_for" anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} claude_env: | GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }} From 6f76f29c0d2f822d34b03d613d93a6157eb0a391 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 14 Sep 2025 17:55:11 +0000 Subject: [PATCH 4/6] Move getCopilotAgentPlaywrightTools to common agentic engine code for all engines Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/workflow/agentic_engine.go | 36 +++++++++++++++++++++++++++++++++ pkg/workflow/claude_engine.go | 37 +--------------------------------- 2 files changed, 37 insertions(+), 36 deletions(-) diff --git a/pkg/workflow/agentic_engine.go b/pkg/workflow/agentic_engine.go index 814f0572c4b..bea157ae86b 100644 --- a/pkg/workflow/agentic_engine.go +++ b/pkg/workflow/agentic_engine.go @@ -207,6 +207,42 @@ func (r *EngineRegistry) GetAllEngines() []CodingAgentEngine { return engines } +// GetCopilotAgentPlaywrightTools returns the list of playwright tools available in the copilot agent +// This matches the tools available in the copilot agent MCP server configuration +// This is a shared function used by all engines for consistent playwright tool configuration +func GetCopilotAgentPlaywrightTools() []any { + tools := []string{ + "browser_click", + "browser_close", + "browser_console_messages", + "browser_drag", + "browser_evaluate", + "browser_file_upload", + "browser_fill_form", + "browser_handle_dialog", + "browser_hover", + "browser_install", + "browser_navigate", + "browser_navigate_back", + "browser_network_requests", + "browser_press_key", + "browser_resize", + "browser_select_option", + "browser_snapshot", + "browser_tabs", + "browser_take_screenshot", + "browser_type", + "browser_wait_for", + } + + // Convert []string to []any for compatibility with the configuration system + result := make([]any, len(tools)) + for i, tool := range tools { + result[i] = tool + } + return result +} + // ConvertStepToYAML converts a step map to YAML string with proper indentation // This is a shared utility function used by all engines and the compiler func ConvertStepToYAML(stepMap map[string]any) (string, error) { diff --git a/pkg/workflow/claude_engine.go b/pkg/workflow/claude_engine.go index 949589e9c9f..ce1c326ec82 100644 --- a/pkg/workflow/claude_engine.go +++ b/pkg/workflow/claude_engine.go @@ -238,41 +238,6 @@ func (e *ClaudeEngine) convertStepToYAML(stepMap map[string]any) (string, error) return ConvertStepToYAML(stepMap) } -// getCopilotAgentPlaywrightTools returns the list of playwright tools available in the copilot agent -// This matches the tools available in the copilot agent MCP server configuration -func (e *ClaudeEngine) getCopilotAgentPlaywrightTools() []any { - tools := []string{ - "browser_click", - "browser_close", - "browser_console_messages", - "browser_drag", - "browser_evaluate", - "browser_file_upload", - "browser_fill_form", - "browser_handle_dialog", - "browser_hover", - "browser_install", - "browser_navigate", - "browser_navigate_back", - "browser_network_requests", - "browser_press_key", - "browser_resize", - "browser_select_option", - "browser_snapshot", - "browser_tabs", - "browser_take_screenshot", - "browser_type", - "browser_wait_for", - } - - // Convert []string to []any for compatibility with the configuration system - result := make([]any, len(tools)) - for i, tool := range tools { - result[i] = tool - } - return result -} - // expandNeutralToolsToClaudeTools converts neutral tools to Claude-specific tools format func (e *ClaudeEngine) expandNeutralToolsToClaudeTools(tools map[string]any) map[string]any { result := make(map[string]any) @@ -349,7 +314,7 @@ func (e *ClaudeEngine) expandNeutralToolsToClaudeTools(tools map[string]any) map if _, hasPlaywright := tools["playwright"]; hasPlaywright { // Create playwright as an MCP tool with the same tools available as copilot agent playwrightMCP := map[string]any{ - "allowed": e.getCopilotAgentPlaywrightTools(), + "allowed": GetCopilotAgentPlaywrightTools(), } result["playwright"] = playwrightMCP } From 47344f135a5df521cf1c05fe6c0558170fd2bcb0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 14 Sep 2025 18:13:59 +0000 Subject: [PATCH 5/6] Add playwright tool to dev agentic workflow Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/dev.lock.yml | 39 +++++++++++++++++++++++++++++++++- .github/workflows/dev.md | 1 + 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/.github/workflows/dev.lock.yml b/.github/workflows/dev.lock.yml index be5db7dd8b0..82b9a2413f8 100644 --- a/.github/workflows/dev.lock.yml +++ b/.github/workflows/dev.lock.yml @@ -289,6 +289,22 @@ jobs: "env": { "GITHUB_PERSONAL_ACCESS_TOKEN": "${{ secrets.GITHUB_TOKEN }}" } + }, + "playwright": { + "command": "docker", + "args": [ + "run", + "-i", + "--rm", + "--shm-size=2gb", + "--cap-add=SYS_ADMIN", + "-e", + "PLAYWRIGHT_ALLOWED_DOMAINS", + "mcr.microsoft.com/playwright:latest" + ], + "env": { + "PLAYWRIGHT_ALLOWED_DOMAINS": "localhost,127.0.0.1" + } } } } @@ -457,7 +473,28 @@ jobs: # - mcp__github__search_repositories # - mcp__github__search_users # - mcp__memory - allowed_tools: "ExitPlanMode,Glob,Grep,LS,NotebookRead,Read,Task,TodoWrite,Write,mcp__github__download_workflow_run_artifact,mcp__github__get_code_scanning_alert,mcp__github__get_commit,mcp__github__get_dependabot_alert,mcp__github__get_discussion,mcp__github__get_discussion_comments,mcp__github__get_file_contents,mcp__github__get_issue,mcp__github__get_issue_comments,mcp__github__get_job_logs,mcp__github__get_me,mcp__github__get_notification_details,mcp__github__get_pull_request,mcp__github__get_pull_request_comments,mcp__github__get_pull_request_diff,mcp__github__get_pull_request_files,mcp__github__get_pull_request_reviews,mcp__github__get_pull_request_status,mcp__github__get_secret_scanning_alert,mcp__github__get_tag,mcp__github__get_workflow_run,mcp__github__get_workflow_run_logs,mcp__github__get_workflow_run_usage,mcp__github__list_branches,mcp__github__list_code_scanning_alerts,mcp__github__list_commits,mcp__github__list_dependabot_alerts,mcp__github__list_discussion_categories,mcp__github__list_discussions,mcp__github__list_issues,mcp__github__list_notifications,mcp__github__list_pull_requests,mcp__github__list_secret_scanning_alerts,mcp__github__list_tags,mcp__github__list_workflow_jobs,mcp__github__list_workflow_run_artifacts,mcp__github__list_workflow_runs,mcp__github__list_workflows,mcp__github__search_code,mcp__github__search_issues,mcp__github__search_orgs,mcp__github__search_pull_requests,mcp__github__search_repositories,mcp__github__search_users,mcp__memory" + # - mcp__playwright__browser_click + # - mcp__playwright__browser_close + # - mcp__playwright__browser_console_messages + # - mcp__playwright__browser_drag + # - mcp__playwright__browser_evaluate + # - mcp__playwright__browser_file_upload + # - mcp__playwright__browser_fill_form + # - mcp__playwright__browser_handle_dialog + # - mcp__playwright__browser_hover + # - mcp__playwright__browser_install + # - mcp__playwright__browser_navigate + # - mcp__playwright__browser_navigate_back + # - mcp__playwright__browser_network_requests + # - mcp__playwright__browser_press_key + # - mcp__playwright__browser_resize + # - mcp__playwright__browser_select_option + # - mcp__playwright__browser_snapshot + # - mcp__playwright__browser_tabs + # - mcp__playwright__browser_take_screenshot + # - mcp__playwright__browser_type + # - mcp__playwright__browser_wait_for + allowed_tools: "ExitPlanMode,Glob,Grep,LS,NotebookRead,Read,Task,TodoWrite,Write,mcp__github__download_workflow_run_artifact,mcp__github__get_code_scanning_alert,mcp__github__get_commit,mcp__github__get_dependabot_alert,mcp__github__get_discussion,mcp__github__get_discussion_comments,mcp__github__get_file_contents,mcp__github__get_issue,mcp__github__get_issue_comments,mcp__github__get_job_logs,mcp__github__get_me,mcp__github__get_notification_details,mcp__github__get_pull_request,mcp__github__get_pull_request_comments,mcp__github__get_pull_request_diff,mcp__github__get_pull_request_files,mcp__github__get_pull_request_reviews,mcp__github__get_pull_request_status,mcp__github__get_secret_scanning_alert,mcp__github__get_tag,mcp__github__get_workflow_run,mcp__github__get_workflow_run_logs,mcp__github__get_workflow_run_usage,mcp__github__list_branches,mcp__github__list_code_scanning_alerts,mcp__github__list_commits,mcp__github__list_dependabot_alerts,mcp__github__list_discussion_categories,mcp__github__list_discussions,mcp__github__list_issues,mcp__github__list_notifications,mcp__github__list_pull_requests,mcp__github__list_secret_scanning_alerts,mcp__github__list_tags,mcp__github__list_workflow_jobs,mcp__github__list_workflow_run_artifacts,mcp__github__list_workflow_runs,mcp__github__list_workflows,mcp__github__search_code,mcp__github__search_issues,mcp__github__search_orgs,mcp__github__search_pull_requests,mcp__github__search_repositories,mcp__github__search_users,mcp__memory,mcp__playwright__browser_click,mcp__playwright__browser_close,mcp__playwright__browser_console_messages,mcp__playwright__browser_drag,mcp__playwright__browser_evaluate,mcp__playwright__browser_file_upload,mcp__playwright__browser_fill_form,mcp__playwright__browser_handle_dialog,mcp__playwright__browser_hover,mcp__playwright__browser_install,mcp__playwright__browser_navigate,mcp__playwright__browser_navigate_back,mcp__playwright__browser_network_requests,mcp__playwright__browser_press_key,mcp__playwright__browser_resize,mcp__playwright__browser_select_option,mcp__playwright__browser_snapshot,mcp__playwright__browser_tabs,mcp__playwright__browser_take_screenshot,mcp__playwright__browser_type,mcp__playwright__browser_wait_for" anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} claude_env: | GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }} diff --git a/.github/workflows/dev.md b/.github/workflows/dev.md index 880fc5cf859..d3ab4a055b9 100644 --- a/.github/workflows/dev.md +++ b/.github/workflows/dev.md @@ -13,6 +13,7 @@ engine: max-turns: 5 tools: cache-memory: true + playwright: null permissions: read-all concurrency: group: "gh-aw-${{ github.workflow }}-${{ github.ref }}" From efbc308cf2a2e30bb0b25dae04891e918259bd5d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 14 Sep 2025 19:54:26 +0000 Subject: [PATCH 6/6] Add playwright tool allowlist support for Codex engine to match copilot agent MCP tools Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/workflow/codex_engine.go | 40 +++++++++- pkg/workflow/codex_playwright_test.go | 102 ++++++++++++++++++++++++++ 2 files changed, 139 insertions(+), 3 deletions(-) create mode 100644 pkg/workflow/codex_playwright_test.go diff --git a/pkg/workflow/codex_engine.go b/pkg/workflow/codex_engine.go index af8c8c3e6fb..f7eda13fb95 100644 --- a/pkg/workflow/codex_engine.go +++ b/pkg/workflow/codex_engine.go @@ -169,6 +169,37 @@ func (e *CodexEngine) convertStepToYAML(stepMap map[string]any) (string, error) return ConvertStepToYAML(stepMap) } +// expandNeutralToolsToCodexTools converts neutral tools to Codex-specific tools format +// This ensures that playwright tools get the same allowlist as the copilot agent +func (e *CodexEngine) expandNeutralToolsToCodexTools(tools map[string]any) map[string]any { + result := make(map[string]any) + + // Copy all existing tools + for key, value := range tools { + result[key] = value + } + + // Handle playwright tool by converting it to an MCP tool configuration with copilot agent tools + if _, hasPlaywright := tools["playwright"]; hasPlaywright { + // Create playwright as an MCP tool with the same tools available as copilot agent + playwrightMCP := map[string]any{ + "allowed": GetCopilotAgentPlaywrightTools(), + } + // If the original playwright tool has additional configuration (like docker_image_version), + // preserve it while adding the allowed tools + if playwrightConfig, ok := tools["playwright"].(map[string]any); ok { + for key, value := range playwrightConfig { + playwrightMCP[key] = value + } + } + // Always set the allowed tools to match copilot agent + playwrightMCP["allowed"] = GetCopilotAgentPlaywrightTools() + result["playwright"] = playwrightMCP + } + + return result +} + func (e *CodexEngine) RenderMCPConfig(yaml *strings.Builder, tools map[string]any, mcpTools []string, workflowData *WorkflowData) { yaml.WriteString(" cat > /tmp/mcp-config/config.toml << EOF\n") @@ -176,18 +207,21 @@ func (e *CodexEngine) RenderMCPConfig(yaml *strings.Builder, tools map[string]an yaml.WriteString(" [history]\n") yaml.WriteString(" persistence = \"none\"\n") + // Expand neutral tools (like playwright: null) to include the copilot agent tools + expandedTools := e.expandNeutralToolsToCodexTools(tools) + // Generate [mcp_servers] section for _, toolName := range mcpTools { switch toolName { case "github": - githubTool := tools["github"] + githubTool := expandedTools["github"] e.renderGitHubCodexMCPConfig(yaml, githubTool, workflowData) case "playwright": - playwrightTool := tools["playwright"] + playwrightTool := expandedTools["playwright"] e.renderPlaywrightCodexMCPConfig(yaml, playwrightTool, workflowData.NetworkPermissions) default: // Handle custom MCP tools (those with MCP-compatible type) - if toolConfig, ok := tools[toolName].(map[string]any); ok { + if toolConfig, ok := expandedTools[toolName].(map[string]any); ok { if hasMcp, _ := hasMCPConfig(toolConfig); hasMcp { if err := e.renderCodexMCPConfig(yaml, toolName, toolConfig); err != nil { fmt.Printf("Error generating custom MCP configuration for %s: %v\n", toolName, err) diff --git a/pkg/workflow/codex_playwright_test.go b/pkg/workflow/codex_playwright_test.go new file mode 100644 index 00000000000..28db90d6e17 --- /dev/null +++ b/pkg/workflow/codex_playwright_test.go @@ -0,0 +1,102 @@ +package workflow + +import ( + "strings" + "testing" +) + +func TestCodexEnginePlaywrightToolsExpansion(t *testing.T) { + engine := NewCodexEngine() + + tests := []struct { + name string + input map[string]any + expected int // Expected number of playwright tools + }{ + { + name: "playwright null expands to copilot agent tools", + input: map[string]any{"playwright": nil}, + expected: 21, // Should expand to all 21 copilot agent playwright tools + }, + { + name: "playwright with config preserves config and adds tools", + input: map[string]any{ + "playwright": map[string]any{ + "docker_image_version": "v1.40.0", + }, + }, + expected: 21, // Should still expand to all 21 tools + }, + { + name: "no playwright tool", + input: map[string]any{"github": nil}, + expected: 0, // No playwright tools expected + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := engine.expandNeutralToolsToCodexTools(tt.input) + + if tt.expected == 0 { + // Should not have playwright in result + if _, hasPlaywright := result["playwright"]; hasPlaywright { + t.Error("Expected no playwright tool in result") + } + } else { + // Should have playwright with correct number of allowed tools + playwrightTool, hasPlaywright := result["playwright"] + if !hasPlaywright { + t.Error("Expected playwright tool in result") + return + } + + playwrightConfig, ok := playwrightTool.(map[string]any) + if !ok { + t.Error("Expected playwright tool to be a map") + return + } + + allowed, hasAllowed := playwrightConfig["allowed"] + if !hasAllowed { + t.Error("Expected playwright tool to have 'allowed' field") + return + } + + allowedSlice, ok := allowed.([]any) + if !ok { + t.Error("Expected 'allowed' field to be a slice") + return + } + + if len(allowedSlice) != tt.expected { + t.Errorf("Expected %d playwright tools, got %d", tt.expected, len(allowedSlice)) + } + + // Verify that all expected copilot agent tools are present + expectedTools := GetCopilotAgentPlaywrightTools() + if len(allowedSlice) != len(expectedTools) { + t.Errorf("Expected %d tools to match copilot agent tools, got %d", len(expectedTools), len(allowedSlice)) + } + + // Check that some key tools are present + toolsStr := strings.Join(func() []string { + var tools []string + for _, tool := range allowedSlice { + if str, ok := tool.(string); ok { + tools = append(tools, str) + } + } + return tools + }(), ",") + + expectedKeyTools := []string{"browser_click", "browser_navigate", "browser_type", "browser_snapshot"} + for _, keyTool := range expectedKeyTools { + if !strings.Contains(toolsStr, keyTool) { + t.Errorf("Expected key tool '%s' to be present in allowed tools: %s", keyTool, toolsStr) + } + } + } + }) + } +}