diff --git a/.github/workflows/grumpy-reviewer.lock.yml b/.github/workflows/grumpy-reviewer.lock.yml index fdd059e89b5..9ca465ff961 100644 --- a/.github/workflows/grumpy-reviewer.lock.yml +++ b/.github/workflows/grumpy-reviewer.lock.yml @@ -700,6 +700,13 @@ jobs: "REQUEST_CHANGES", "COMMENT" ] + }, + "pull_request_number": { + "optionalPositiveInteger": true + }, + "repo": { + "type": "string", + "maxLength": 256 } } } @@ -1465,18 +1472,18 @@ jobs: DOCKER_SOCK_GID=$(stat -c '%g' "$DOCKER_SOCK_PATH" 2>/dev/null || echo '0') export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e CODEX_HOME -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.22' - cat > "${RUNNER_TEMP}/gh-aw/mcp-config/config.toml" << GH_AW_MCP_CONFIG_f05a22f783047f96_EOF + cat > "${RUNNER_TEMP}/gh-aw/mcp-config/config.toml" << GH_AW_MCP_CONFIG_be3befb6a8781e4e_EOF [history] persistence = "none" [shell_environment_policy] inherit = "core" include_only = ["^CODEX_API_KEY$", "^HOME$", "^OPENAI_API_KEY$", "^PATH$"] - GH_AW_MCP_CONFIG_f05a22f783047f96_EOF + GH_AW_MCP_CONFIG_be3befb6a8781e4e_EOF # Generate JSON config for MCP gateway GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_37818d2d8303bcd2_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_e692dacf94a64d87_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { }, @@ -1487,11 +1494,11 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_37818d2d8303bcd2_EOF + GH_AW_MCP_CONFIG_e692dacf94a64d87_EOF # Sync converter output to writable CODEX_HOME for Codex mkdir -p /tmp/gh-aw/mcp-config - cat > "/tmp/gh-aw/mcp-config/config.toml" << GH_AW_CODEX_SHELL_POLICY_b89ca6ac1269abd5_EOF + cat > "/tmp/gh-aw/mcp-config/config.toml" << GH_AW_CODEX_SHELL_POLICY_110393beabafb1b9_EOF model_provider = "openai-proxy" [model_providers.openai-proxy] name = "OpenAI AWF proxy" @@ -1501,7 +1508,7 @@ jobs: [shell_environment_policy] inherit = "core" include_only = ["^CODEX_API_KEY$", "^HOME$", "^OPENAI_API_KEY$", "^PATH$"] - GH_AW_CODEX_SHELL_POLICY_b89ca6ac1269abd5_EOF + GH_AW_CODEX_SHELL_POLICY_110393beabafb1b9_EOF awk ' BEGIN { skip_openai_proxy = 0 } /^[[:space:]]*model_provider[[:space:]]*=/ { next } diff --git a/.github/workflows/mattpocock-skills-reviewer.lock.yml b/.github/workflows/mattpocock-skills-reviewer.lock.yml index 210b13ed718..85be0c070ce 100644 --- a/.github/workflows/mattpocock-skills-reviewer.lock.yml +++ b/.github/workflows/mattpocock-skills-reviewer.lock.yml @@ -740,6 +740,13 @@ jobs: "REQUEST_CHANGES", "COMMENT" ] + }, + "pull_request_number": { + "optionalPositiveInteger": true + }, + "repo": { + "type": "string", + "maxLength": 256 } } } diff --git a/.github/workflows/pr-code-quality-reviewer.lock.yml b/.github/workflows/pr-code-quality-reviewer.lock.yml index d459b7dd0d6..e3f7ff68216 100644 --- a/.github/workflows/pr-code-quality-reviewer.lock.yml +++ b/.github/workflows/pr-code-quality-reviewer.lock.yml @@ -702,6 +702,13 @@ jobs: "REQUEST_CHANGES", "COMMENT" ] + }, + "pull_request_number": { + "optionalPositiveInteger": true + }, + "repo": { + "type": "string", + "maxLength": 256 } } } diff --git a/.github/workflows/pr-nitpick-reviewer.lock.yml b/.github/workflows/pr-nitpick-reviewer.lock.yml index 4f299ddaf06..e42434f5dba 100644 --- a/.github/workflows/pr-nitpick-reviewer.lock.yml +++ b/.github/workflows/pr-nitpick-reviewer.lock.yml @@ -733,6 +733,13 @@ jobs: "REQUEST_CHANGES", "COMMENT" ] + }, + "pull_request_number": { + "optionalPositiveInteger": true + }, + "repo": { + "type": "string", + "maxLength": 256 } } } diff --git a/.github/workflows/pr-triage-agent.lock.yml b/.github/workflows/pr-triage-agent.lock.yml index a5f751d251c..1e5a04af0cf 100644 --- a/.github/workflows/pr-triage-agent.lock.yml +++ b/.github/workflows/pr-triage-agent.lock.yml @@ -751,6 +751,13 @@ jobs: "REQUEST_CHANGES", "COMMENT" ] + }, + "pull_request_number": { + "optionalPositiveInteger": true + }, + "repo": { + "type": "string", + "maxLength": 256 } } } diff --git a/.github/workflows/refiner.lock.yml b/.github/workflows/refiner.lock.yml index 77cbbbb25b0..32df7f8c058 100644 --- a/.github/workflows/refiner.lock.yml +++ b/.github/workflows/refiner.lock.yml @@ -746,6 +746,13 @@ jobs: "REQUEST_CHANGES", "COMMENT" ] + }, + "pull_request_number": { + "optionalPositiveInteger": true + }, + "repo": { + "type": "string", + "maxLength": 256 } } } diff --git a/.github/workflows/security-review.lock.yml b/.github/workflows/security-review.lock.yml index ce710b1f7ba..245f8018fb6 100644 --- a/.github/workflows/security-review.lock.yml +++ b/.github/workflows/security-review.lock.yml @@ -765,6 +765,13 @@ jobs: "REQUEST_CHANGES", "COMMENT" ] + }, + "pull_request_number": { + "optionalPositiveInteger": true + }, + "repo": { + "type": "string", + "maxLength": 256 } } } diff --git a/.github/workflows/smoke-claude.lock.yml b/.github/workflows/smoke-claude.lock.yml index 12e1a46b31b..8a71688404b 100644 --- a/.github/workflows/smoke-claude.lock.yml +++ b/.github/workflows/smoke-claude.lock.yml @@ -1453,6 +1453,13 @@ jobs: "REQUEST_CHANGES", "COMMENT" ] + }, + "pull_request_number": { + "optionalPositiveInteger": true + }, + "repo": { + "type": "string", + "maxLength": 256 } } }, diff --git a/.github/workflows/smoke-copilot-arm.lock.yml b/.github/workflows/smoke-copilot-arm.lock.yml index 9df92e97ab3..9d535706c26 100644 --- a/.github/workflows/smoke-copilot-arm.lock.yml +++ b/.github/workflows/smoke-copilot-arm.lock.yml @@ -1026,6 +1026,13 @@ jobs: "REQUEST_CHANGES", "COMMENT" ] + }, + "pull_request_number": { + "optionalPositiveInteger": true + }, + "repo": { + "type": "string", + "maxLength": 256 } } } diff --git a/.github/workflows/smoke-copilot.lock.yml b/.github/workflows/smoke-copilot.lock.yml index 41805f9432a..3c3031ee3c7 100644 --- a/.github/workflows/smoke-copilot.lock.yml +++ b/.github/workflows/smoke-copilot.lock.yml @@ -1166,6 +1166,13 @@ jobs: "REQUEST_CHANGES", "COMMENT" ] + }, + "pull_request_number": { + "optionalPositiveInteger": true + }, + "repo": { + "type": "string", + "maxLength": 256 } } } diff --git a/.github/workflows/test-quality-sentinel.lock.yml b/.github/workflows/test-quality-sentinel.lock.yml index 1013d6d0d22..070c6307979 100644 --- a/.github/workflows/test-quality-sentinel.lock.yml +++ b/.github/workflows/test-quality-sentinel.lock.yml @@ -693,6 +693,13 @@ jobs: "REQUEST_CHANGES", "COMMENT" ] + }, + "pull_request_number": { + "optionalPositiveInteger": true + }, + "repo": { + "type": "string", + "maxLength": 256 } } } diff --git a/actions/setup/js/safe_output_type_validator.test.cjs b/actions/setup/js/safe_output_type_validator.test.cjs index a70e4aff5ea..aa9e21ffc36 100644 --- a/actions/setup/js/safe_output_type_validator.test.cjs +++ b/actions/setup/js/safe_output_type_validator.test.cjs @@ -81,6 +81,8 @@ const SAMPLE_VALIDATION_CONFIG = { fields: { body: { type: "string", sanitize: true, maxLength: 65000 }, event: { type: "string", enum: ["APPROVE", "REQUEST_CHANGES", "COMMENT"] }, + pull_request_number: { optionalPositiveInteger: true }, + repo: { type: "string", maxLength: 256 }, }, }, link_sub_issue: { @@ -212,6 +214,55 @@ describe("safe_output_type_validator", () => { // The sanitizeContent function converts @mentions to backticked format expect(result.normalizedItem.title).toContain("`@mention`"); }); + + it("should validate submit_pull_request_review with pull_request_number and repo", async () => { + const { validateItem } = await import("./safe_output_type_validator.cjs"); + + const result = validateItem({ type: "submit_pull_request_review", event: "APPROVE", pull_request_number: "42", repo: "owner/repo" }, "submit_pull_request_review", 1); + + expect(result.isValid).toBe(true); + expect(result.normalizedItem.pull_request_number).toBe(42); + expect(result.normalizedItem.repo).toBe("owner/repo"); + }); + + it("should validate submit_pull_request_review with only pull_request_number", async () => { + const { validateItem } = await import("./safe_output_type_validator.cjs"); + + const result = validateItem({ type: "submit_pull_request_review", event: "COMMENT", pull_request_number: 42 }, "submit_pull_request_review", 1); + + expect(result.isValid).toBe(true); + expect(result.normalizedItem.pull_request_number).toBe(42); + expect(result.normalizedItem.repo).toBeUndefined(); + }); + + it("should validate submit_pull_request_review with only repo", async () => { + const { validateItem } = await import("./safe_output_type_validator.cjs"); + + const result = validateItem({ type: "submit_pull_request_review", event: "COMMENT", repo: "owner/repo" }, "submit_pull_request_review", 1); + + expect(result.isValid).toBe(true); + expect(result.normalizedItem.pull_request_number).toBeUndefined(); + expect(result.normalizedItem.repo).toBe("owner/repo"); + }); + + it("should validate submit_pull_request_review with neither pull_request_number nor repo", async () => { + const { validateItem } = await import("./safe_output_type_validator.cjs"); + + const result = validateItem({ type: "submit_pull_request_review", event: "COMMENT", body: "Looks good." }, "submit_pull_request_review", 1); + + expect(result.isValid).toBe(true); + }); + + it("should reject invalid submit_pull_request_review pull_request_number", async () => { + const { validateItem } = await import("./safe_output_type_validator.cjs"); + + const invalidValues = [0, -1, "abc", 3.14]; + for (const value of invalidValues) { + const result = validateItem({ type: "submit_pull_request_review", event: "APPROVE", pull_request_number: value }, "submit_pull_request_review", 1); + expect(result.isValid).toBe(false); + expect(result.error).toContain("pull_request_number"); + } + }); }); describe("validatePositiveInteger", () => { diff --git a/actions/setup/js/safe_outputs_tools.json b/actions/setup/js/safe_outputs_tools.json index 7bff0945fc2..99f51f7eb91 100644 --- a/actions/setup/js/safe_outputs_tools.json +++ b/actions/setup/js/safe_outputs_tools.json @@ -392,7 +392,7 @@ }, { "name": "submit_pull_request_review", - "description": "Submit a pull request review with a status decision. This tool auto-targets the pull request that triggered the workflow \u2014 do NOT pass pull_request_number (unlike create_pull_request_review_comment and reply_to_pull_request_review_comment, which accept it; this tool will silently strip it). REQUIRED: every call must include either a non-empty body or be preceded by at least one create_pull_request_review_comment call; calling with no body and no prior comments is rejected with ERR_VALIDATION. All preceding create_pull_request_review_comment outputs are automatically attached as inline comments. If this tool is not called, buffered review comments are submitted as a COMMENT review at workflow end. Use COMMENT for non-blocking feedback; use REQUEST_CHANGES only for merge-blocking. Example (inline-only review): call create_pull_request_review_comment one or more times, then call this tool with event: COMMENT and no body.", + "description": "Submit a pull request review with a status decision. By default this tool targets the pull request that triggered the workflow. When the workflow is configured with `target: \"*\"`, you must specify `pull_request_number` to indicate which PR to target. REQUIRED: every call must include either a non-empty body or be preceded by at least one create_pull_request_review_comment call; calling with no body and no prior comments is rejected with ERR_VALIDATION. All preceding create_pull_request_review_comment outputs are automatically attached as inline comments. If this tool is not called, buffered review comments are submitted as a COMMENT review at workflow end. Use COMMENT for non-blocking feedback; use REQUEST_CHANGES only for merge-blocking. Example (inline-only review): call create_pull_request_review_comment one or more times, then call this tool with event: COMMENT and no body.", "inputSchema": { "type": "object", "properties": { @@ -405,6 +405,14 @@ "enum": ["APPROVE", "REQUEST_CHANGES", "COMMENT"], "description": "Review decision: APPROVE to approve the pull request, REQUEST_CHANGES to formally request changes before merging, or COMMENT for general feedback without a formal decision. Defaults to COMMENT when omitted." }, + "pull_request_number": { + "type": ["number", "string"], + "description": "Pull request number to submit the review on. This is the numeric ID from the GitHub URL (e.g., 876 in github.com/owner/repo/pull/876). If omitted, submits the review on the PR that triggered this workflow. Required when the workflow target is '*' (any PR) \u2014 omitting it will cause the review to fail." + }, + "repo": { + "type": "string", + "description": "Target repository in 'owner/repo' format. If omitted, uses the configured target repository. Must be in the allowed-repos list if specified." + }, "secrecy": { "type": "string", "description": "Confidentiality level of the message content (e.g., \"public\", \"internal\", \"private\")." diff --git a/pkg/workflow/js/safe_outputs_tools.json b/pkg/workflow/js/safe_outputs_tools.json index 0ffaeba54e7..2c0ec5098dc 100644 --- a/pkg/workflow/js/safe_outputs_tools.json +++ b/pkg/workflow/js/safe_outputs_tools.json @@ -467,7 +467,7 @@ }, { "name": "submit_pull_request_review", - "description": "Submit a pull request review with a status decision. This tool auto-targets the pull request that triggered the workflow \u2014 do NOT pass pull_request_number (unlike create_pull_request_review_comment and reply_to_pull_request_review_comment, which accept it; this tool will silently strip it). REQUIRED: every call must include either a non-empty body or be preceded by at least one create_pull_request_review_comment call; calling with no body and no prior comments is rejected with ERR_VALIDATION. All preceding create_pull_request_review_comment outputs are automatically attached as inline comments. If this tool is not called, buffered review comments are submitted as a COMMENT review at workflow end. Use COMMENT for non-blocking feedback; use REQUEST_CHANGES only for merge-blocking. Example (inline-only review): call create_pull_request_review_comment one or more times, then call this tool with event: COMMENT and no body.", + "description": "Submit a pull request review with a status decision. By default this tool targets the pull request that triggered the workflow. When the workflow is configured with `target: \"*\"`, you must specify `pull_request_number` to indicate which PR to target. REQUIRED: every call must include either a non-empty body or be preceded by at least one create_pull_request_review_comment call; calling with no body and no prior comments is rejected with ERR_VALIDATION. All preceding create_pull_request_review_comment outputs are automatically attached as inline comments. If this tool is not called, buffered review comments are submitted as a COMMENT review at workflow end. Use COMMENT for non-blocking feedback; use REQUEST_CHANGES only for merge-blocking. Example (inline-only review): call create_pull_request_review_comment one or more times, then call this tool with event: COMMENT and no body.", "inputSchema": { "type": "object", "properties": { @@ -484,6 +484,17 @@ ], "description": "Review decision: APPROVE to approve the pull request, REQUEST_CHANGES to formally request changes before merging, or COMMENT for general feedback without a formal decision. Defaults to COMMENT when omitted." }, + "pull_request_number": { + "type": [ + "number", + "string" + ], + "description": "Pull request number to submit the review on. This is the numeric ID from the GitHub URL (e.g., 876 in github.com/owner/repo/pull/876). If omitted, submits the review on the PR that triggered this workflow. Required when the workflow target is '*' (any PR) \u2014 omitting it will cause the review to fail." + }, + "repo": { + "type": "string", + "description": "Target repository in 'owner/repo' format. If omitted, uses the configured target repository. Must be in the allowed-repos list if specified." + }, "secrecy": { "type": "string", "description": "Confidentiality level of the message content (e.g., \"public\", \"internal\", \"private\")." diff --git a/pkg/workflow/safe_outputs_target_validation_test.go b/pkg/workflow/safe_outputs_target_validation_test.go index 748d9e5dce7..b0cf3433ac5 100644 --- a/pkg/workflow/safe_outputs_target_validation_test.go +++ b/pkg/workflow/safe_outputs_target_validation_test.go @@ -246,6 +246,43 @@ func TestValidateSafeOutputsTarget(t *testing.T) { }, wantErr: false, }, + { + name: "valid wildcard target for submit-pull-request-review", + config: &SafeOutputsConfig{ + SubmitPullRequestReview: &SubmitPullRequestReviewConfig{ + BaseSafeOutputConfig: BaseSafeOutputConfig{}, + SafeOutputTargetConfig: SafeOutputTargetConfig{ + Target: "*", + }, + }, + }, + wantErr: false, + }, + { + name: "valid numeric target for submit-pull-request-review", + config: &SafeOutputsConfig{ + SubmitPullRequestReview: &SubmitPullRequestReviewConfig{ + BaseSafeOutputConfig: BaseSafeOutputConfig{}, + SafeOutputTargetConfig: SafeOutputTargetConfig{ + Target: "99", + }, + }, + }, + wantErr: false, + }, + { + name: "invalid target for submit-pull-request-review", + config: &SafeOutputsConfig{ + SubmitPullRequestReview: &SubmitPullRequestReviewConfig{ + BaseSafeOutputConfig: BaseSafeOutputConfig{}, + SafeOutputTargetConfig: SafeOutputTargetConfig{ + Target: "invalid-value", + }, + }, + }, + wantErr: true, + errText: "invalid target value for submit-pull-request-review: \"invalid-value\"", + }, } for _, tt := range tests { diff --git a/pkg/workflow/safe_outputs_validation_config.go b/pkg/workflow/safe_outputs_validation_config.go index fe3ab2cd5cd..b799600a862 100644 --- a/pkg/workflow/safe_outputs_validation_config.go +++ b/pkg/workflow/safe_outputs_validation_config.go @@ -226,8 +226,10 @@ var ValidationConfig = map[string]TypeValidationConfig{ "submit_pull_request_review": { DefaultMax: 1, Fields: map[string]FieldValidation{ - "body": {Type: "string", Sanitize: true, MaxLength: MaxBodyLength}, - "event": {Type: "string", Enum: []string{"APPROVE", "REQUEST_CHANGES", "COMMENT"}}, + "body": {Type: "string", Sanitize: true, MaxLength: MaxBodyLength}, + "event": {Type: "string", Enum: []string{"APPROVE", "REQUEST_CHANGES", "COMMENT"}}, + "pull_request_number": {OptionalPositiveInteger: true}, + "repo": {Type: "string", MaxLength: 256}, // Optional: target repository in format "owner/repo" }, }, "reply_to_pull_request_review_comment": { diff --git a/pkg/workflow/tool_description_enhancer.go b/pkg/workflow/tool_description_enhancer.go index e7a85cc7257..ec1d12eb111 100644 --- a/pkg/workflow/tool_description_enhancer.go +++ b/pkg/workflow/tool_description_enhancer.go @@ -258,6 +258,12 @@ func enhanceToolDescription(toolName, baseDescription string, safeOutputs *SafeO if templatableIntValue(config.Max) > 0 { constraints = append(constraints, fmt.Sprintf("Maximum %d review(s) can be submitted.", templatableIntValue(config.Max))) } + if config.Target != "" { + constraints = append(constraints, fmt.Sprintf("Target: %s.", config.Target)) + } + if config.TargetRepoSlug != "" { + constraints = append(constraints, fmt.Sprintf("Reviews will be submitted in repository %q.", config.TargetRepoSlug)) + } } case "reply_to_pull_request_review_comment": diff --git a/pkg/workflow/tool_description_enhancer_test.go b/pkg/workflow/tool_description_enhancer_test.go index 054a040f291..4793cee867e 100644 --- a/pkg/workflow/tool_description_enhancer_test.go +++ b/pkg/workflow/tool_description_enhancer_test.go @@ -176,3 +176,29 @@ func TestEnhanceToolDescriptionMarkPRReadyForReviewMaxCount(t *testing.T) { t.Fatalf("expected max count constraint in description, got: %s", description) } } + +func TestEnhanceToolDescriptionSubmitPullRequestReviewTarget(t *testing.T) { + description := enhanceToolDescription("submit_pull_request_review", "Submit a PR review.", &SafeOutputsConfig{ + SubmitPullRequestReview: &SubmitPullRequestReviewConfig{ + BaseSafeOutputConfig: BaseSafeOutputConfig{Max: defaultIntStr(1)}, + SafeOutputTargetConfig: SafeOutputTargetConfig{Target: "*"}, + }, + }) + + if !strings.Contains(description, "Target: *.") { + t.Fatalf("expected target constraint in description, got: %s", description) + } +} + +func TestEnhanceToolDescriptionSubmitPullRequestReviewTargetRepo(t *testing.T) { + description := enhanceToolDescription("submit_pull_request_review", "Submit a PR review.", &SafeOutputsConfig{ + SubmitPullRequestReview: &SubmitPullRequestReviewConfig{ + BaseSafeOutputConfig: BaseSafeOutputConfig{Max: defaultIntStr(1)}, + SafeOutputTargetConfig: SafeOutputTargetConfig{TargetRepoSlug: "myorg/myrepo"}, + }, + }) + + if !strings.Contains(description, `"myorg/myrepo"`) { + t.Fatalf("expected target repo constraint in description, got: %s", description) + } +} diff --git a/schemas/agent-output.json b/schemas/agent-output.json index 72a4ead7d21..aca093c99bd 100644 --- a/schemas/agent-output.json +++ b/schemas/agent-output.json @@ -345,6 +345,14 @@ "description": "Review decision: APPROVE to approve the PR, REQUEST_CHANGES to request changes, or COMMENT for general feedback. Defaults to COMMENT when omitted.", "enum": ["APPROVE", "REQUEST_CHANGES", "COMMENT"], "default": "COMMENT" + }, + "pull_request_number": { + "type": ["number", "string"], + "description": "Pull request number to submit the review on (optional - uses triggering PR if not provided). Required when the workflow target is '*' (any PR)." + }, + "repo": { + "type": "string", + "description": "Target repository in 'owner/repo' format (optional - uses configured target repository if not provided)." } }, "required": ["type"],