diff --git a/.github/workflows/campaign-generator.lock.yml b/.github/workflows/campaign-generator.lock.yml index 18c715669bf..2904dbe0c88 100644 --- a/.github/workflows/campaign-generator.lock.yml +++ b/.github/workflows/campaign-generator.lock.yml @@ -230,25 +230,41 @@ jobs: "name": "add_comment" }, { - "description": "Assign the GitHub Copilot coding agent to work on an issue. The agent will analyze the issue and attempt to implement a solution, creating a pull request when complete. Use this to delegate coding tasks to Copilot.", + "description": "Assign the GitHub Copilot coding agent to work on an issue or pull request. The agent will analyze the issue/PR and attempt to implement a solution, creating a pull request when complete. Use this to delegate coding tasks to Copilot. Example usage: assign_to_agent(issue_number=123, agent=\"copilot\") or assign_to_agent(pull_number=456, agent=\"copilot\")", "inputSchema": { "additionalProperties": false, + "oneOf": [ + { + "required": [ + "issue_number" + ] + }, + { + "required": [ + "pull_number" + ] + } + ], "properties": { "agent": { "description": "Agent identifier to assign. Defaults to 'copilot' (the Copilot coding agent) if not specified.", "type": "string" }, "issue_number": { - "description": "Issue number to assign the Copilot agent to. This is the numeric ID from the GitHub URL (e.g., 234 in github.com/owner/repo/issues/234). The issue should contain clear, actionable requirements.", + "description": "Issue number to assign the Copilot agent to. This is the numeric ID from the GitHub URL (e.g., 234 in github.com/owner/repo/issues/234). The issue should contain clear, actionable requirements. Either issue_number or pull_number must be provided, but not both.", + "type": [ + "number", + "string" + ] + }, + "pull_number": { + "description": "Pull request number to assign the Copilot agent to. This is the numeric ID from the GitHub URL (e.g., 456 in github.com/owner/repo/pull/456). Either issue_number or pull_number must be provided, but not both.", "type": [ "number", "string" ] } }, - "required": [ - "issue_number" - ], "type": "object" }, "name": "assign_to_agent" diff --git a/.github/workflows/discussion-task-mining.campaign.lock.yml b/.github/workflows/discussion-task-mining.campaign.lock.yml index 1fd4548b6a9..0067b4519d7 100644 --- a/.github/workflows/discussion-task-mining.campaign.lock.yml +++ b/.github/workflows/discussion-task-mining.campaign.lock.yml @@ -1103,7 +1103,7 @@ jobs: | Field | Type | Allowed / Notes | |---|---|---| - | `status` | single-select | `Todo` / `In Progress` / `Done` | + | `status` | single-select | `Todo` / `In Progress` / `Review required` / `Blocked` / `Done` | | `campaign_id` | text | Must equal `discussion-task-mining` | | `worker_workflow` | text | workflow ID or `"unknown"` | | `repository` | text | `owner/repo` | diff --git a/.github/workflows/docs-quality-maintenance-project67.campaign.lock.yml b/.github/workflows/docs-quality-maintenance-project67.campaign.lock.yml index 98e98f2fad9..c6bdeeb8f7f 100644 --- a/.github/workflows/docs-quality-maintenance-project67.campaign.lock.yml +++ b/.github/workflows/docs-quality-maintenance-project67.campaign.lock.yml @@ -1113,7 +1113,7 @@ jobs: | Field | Type | Allowed / Notes | |---|---|---| - | `status` | single-select | `Todo` / `In Progress` / `Done` | + | `status` | single-select | `Todo` / `In Progress` / `Review required` / `Blocked` / `Done` | | `campaign_id` | text | Must equal `docs-quality-maintenance-project73` | | `worker_workflow` | text | workflow ID or `"unknown"` | | `repository` | text | `owner/repo` | diff --git a/.github/workflows/file-size-reduction-project71.campaign.lock.yml b/.github/workflows/file-size-reduction-project71.campaign.lock.yml index 8075e6e0d7b..34ee1b1aa56 100644 --- a/.github/workflows/file-size-reduction-project71.campaign.lock.yml +++ b/.github/workflows/file-size-reduction-project71.campaign.lock.yml @@ -1110,7 +1110,7 @@ jobs: | Field | Type | Allowed / Notes | |---|---|---| - | `status` | single-select | `Todo` / `In Progress` / `Done` | + | `status` | single-select | `Todo` / `In Progress` / `Review required` / `Blocked` / `Done` | | `campaign_id` | text | Must equal `file-size-reduction-project71` | | `worker_workflow` | text | workflow ID or `"unknown"` | | `repository` | text | `owner/repo` | diff --git a/.github/workflows/issue-monster.lock.yml b/.github/workflows/issue-monster.lock.yml index 6dc57f2e540..76585071d78 100644 --- a/.github/workflows/issue-monster.lock.yml +++ b/.github/workflows/issue-monster.lock.yml @@ -209,25 +209,41 @@ jobs: "name": "add_comment" }, { - "description": "Assign the GitHub Copilot coding agent to work on an issue. The agent will analyze the issue and attempt to implement a solution, creating a pull request when complete. Use this to delegate coding tasks to Copilot. CONSTRAINTS: Maximum 3 issue(s) can be assigned to agent.", + "description": "Assign the GitHub Copilot coding agent to work on an issue or pull request. The agent will analyze the issue/PR and attempt to implement a solution, creating a pull request when complete. Use this to delegate coding tasks to Copilot. Example usage: assign_to_agent(issue_number=123, agent=\"copilot\") or assign_to_agent(pull_number=456, agent=\"copilot\") CONSTRAINTS: Maximum 3 issue(s) can be assigned to agent.", "inputSchema": { "additionalProperties": false, + "oneOf": [ + { + "required": [ + "issue_number" + ] + }, + { + "required": [ + "pull_number" + ] + } + ], "properties": { "agent": { "description": "Agent identifier to assign. Defaults to 'copilot' (the Copilot coding agent) if not specified.", "type": "string" }, "issue_number": { - "description": "Issue number to assign the Copilot agent to. This is the numeric ID from the GitHub URL (e.g., 234 in github.com/owner/repo/issues/234). The issue should contain clear, actionable requirements.", + "description": "Issue number to assign the Copilot agent to. This is the numeric ID from the GitHub URL (e.g., 234 in github.com/owner/repo/issues/234). The issue should contain clear, actionable requirements. Either issue_number or pull_number must be provided, but not both.", + "type": [ + "number", + "string" + ] + }, + "pull_number": { + "description": "Pull request number to assign the Copilot agent to. This is the numeric ID from the GitHub URL (e.g., 456 in github.com/owner/repo/pull/456). Either issue_number or pull_number must be provided, but not both.", "type": [ "number", "string" ] } }, - "required": [ - "issue_number" - ], "type": "object" }, "name": "assign_to_agent" diff --git a/.github/workflows/playground-assign-to-agent.lock.yml b/.github/workflows/playground-assign-to-agent.lock.yml index 46bd4268106..6883c9f9c02 100644 --- a/.github/workflows/playground-assign-to-agent.lock.yml +++ b/.github/workflows/playground-assign-to-agent.lock.yml @@ -25,8 +25,8 @@ name: "Playground: assign-to-agent" "on": workflow_dispatch: inputs: - issue_url: - description: Issue URL to assign agent to (e.g., https://github.com/owner/repo/issues/123) + item_url: + description: Issue or PR URL to assign agent to (e.g., https://github.com/owner/repo/issues/123 or https://github.com/owner/repo/pull/456) required: true type: string @@ -178,25 +178,41 @@ jobs: cat > /opt/gh-aw/safeoutputs/tools.json << 'EOF' [ { - "description": "Assign the GitHub Copilot coding agent to work on an issue. The agent will analyze the issue and attempt to implement a solution, creating a pull request when complete. Use this to delegate coding tasks to Copilot. CONSTRAINTS: Maximum 1 issue(s) can be assigned to agent.", + "description": "Assign the GitHub Copilot coding agent to work on an issue or pull request. The agent will analyze the issue/PR and attempt to implement a solution, creating a pull request when complete. Use this to delegate coding tasks to Copilot. Example usage: assign_to_agent(issue_number=123, agent=\"copilot\") or assign_to_agent(pull_number=456, agent=\"copilot\") CONSTRAINTS: Maximum 1 issue(s) can be assigned to agent.", "inputSchema": { "additionalProperties": false, + "oneOf": [ + { + "required": [ + "issue_number" + ] + }, + { + "required": [ + "pull_number" + ] + } + ], "properties": { "agent": { "description": "Agent identifier to assign. Defaults to 'copilot' (the Copilot coding agent) if not specified.", "type": "string" }, "issue_number": { - "description": "Issue number to assign the Copilot agent to. This is the numeric ID from the GitHub URL (e.g., 234 in github.com/owner/repo/issues/234). The issue should contain clear, actionable requirements.", + "description": "Issue number to assign the Copilot agent to. This is the numeric ID from the GitHub URL (e.g., 234 in github.com/owner/repo/issues/234). The issue should contain clear, actionable requirements. Either issue_number or pull_number must be provided, but not both.", + "type": [ + "number", + "string" + ] + }, + "pull_number": { + "description": "Pull request number to assign the Copilot agent to. This is the numeric ID from the GitHub URL (e.g., 456 in github.com/owner/repo/pull/456). Either issue_number or pull_number must be provided, but not both.", "type": [ "number", "string" ] } }, - "required": [ - "issue_number" - ], "type": "object" }, "name": "assign_to_agent" @@ -446,31 +462,33 @@ jobs: env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} - GH_AW_GITHUB_EVENT_INPUTS_ISSUE_URL: ${{ github.event.inputs.issue_url }} + GH_AW_GITHUB_EVENT_INPUTS_ITEM_URL: ${{ github.event.inputs.item_url }} run: | bash /opt/gh-aw/actions/create_prompt_first.sh cat << 'PROMPT_EOF' > "$GH_AW_PROMPT" # Assign Agent Test Workflow - Test the `assign-to-agent` safe output feature by assigning the Copilot agent to an issue. + Test the `assign-to-agent` safe output feature by assigning the Copilot agent to an issue or pull request. ## Task - You have been provided with an issue URL: __GH_AW_GITHUB_EVENT_INPUTS_ISSUE_URL__ + You have been provided with a GitHub URL: __GH_AW_GITHUB_EVENT_INPUTS_ITEM_URL__ - 1. Parse the issue URL to extract the owner, repo, and issue number - 2. Validate that the URL is an issue URL (not a pull request URL) - 3. Use the `assign_to_agent` tool from the `safeoutputs` MCP server to assign the Copilot agent to the issue - 4. Pass the numeric issue_number (extracted from the URL) to the `assign_to_agent` tool + 1. Parse the URL to extract the owner, repo, and number + 2. Determine if the URL is an issue URL (contains `/issues/`) or a pull request URL (contains `/pull/`) + 3. Use the `assign_to_agent` tool from the `safeoutputs` MCP server to assign the Copilot agent + 4. Pass the appropriate parameter to the tool: + - For issues: pass `issue_number` (the numeric ID extracted from the URL) + - For pull requests: pass `pull_number` (the numeric ID extracted from the URL) - **Important**: Do not use GitHub tools directly for assignment. Only use the `assign_to_agent` safe output tool. + **Important**: Do not use GitHub tools directly for assignment. Only use the `assign_to_agent` safe output tool with the correct parameter based on the URL type. PROMPT_EOF - name: Substitute placeholders uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - GH_AW_GITHUB_EVENT_INPUTS_ISSUE_URL: ${{ github.event.inputs.issue_url }} + GH_AW_GITHUB_EVENT_INPUTS_ITEM_URL: ${{ github.event.inputs.item_url }} with: script: | const substitutePlaceholders = require('/opt/gh-aw/actions/substitute_placeholders.cjs'); @@ -479,7 +497,7 @@ jobs: return await substitutePlaceholders({ file: process.env.GH_AW_PROMPT, substitutions: { - GH_AW_GITHUB_EVENT_INPUTS_ISSUE_URL: process.env.GH_AW_GITHUB_EVENT_INPUTS_ISSUE_URL + GH_AW_GITHUB_EVENT_INPUTS_ITEM_URL: process.env.GH_AW_GITHUB_EVENT_INPUTS_ITEM_URL } }); - name: Append XPIA security instructions to prompt @@ -587,7 +605,7 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - GH_AW_GITHUB_EVENT_INPUTS_ISSUE_URL: ${{ github.event.inputs.issue_url }} + GH_AW_GITHUB_EVENT_INPUTS_ITEM_URL: ${{ github.event.inputs.item_url }} with: script: | const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); diff --git a/.github/workflows/playground-assign-to-agent.md b/.github/workflows/playground-assign-to-agent.md index d6416a4795c..57b55d7ff4b 100644 --- a/.github/workflows/playground-assign-to-agent.md +++ b/.github/workflows/playground-assign-to-agent.md @@ -3,8 +3,8 @@ name: "Playground: assign-to-agent" on: workflow_dispatch: inputs: - issue_url: - description: 'Issue URL to assign agent to (e.g., https://github.com/owner/repo/issues/123)' + item_url: + description: 'Issue or PR URL to assign agent to (e.g., https://github.com/owner/repo/issues/123 or https://github.com/owner/repo/pull/456)' required: true type: string description: Test assign-to-agent safe output feature @@ -32,15 +32,17 @@ timeout-minutes: 5 # Assign Agent Test Workflow -Test the `assign-to-agent` safe output feature by assigning the Copilot agent to an issue. +Test the `assign-to-agent` safe output feature by assigning the Copilot agent to an issue or pull request. ## Task -You have been provided with an issue URL: ${{ github.event.inputs.issue_url }} +You have been provided with a GitHub URL: ${{ github.event.inputs.item_url }} -1. Parse the issue URL to extract the owner, repo, and issue number -2. Validate that the URL is an issue URL (not a pull request URL) -3. Use the `assign_to_agent` tool from the `safeoutputs` MCP server to assign the Copilot agent to the issue -4. Pass the numeric issue_number (extracted from the URL) to the `assign_to_agent` tool +1. Parse the URL to extract the owner, repo, and number +2. Determine if the URL is an issue URL (contains `/issues/`) or a pull request URL (contains `/pull/`) +3. Use the `assign_to_agent` tool from the `safeoutputs` MCP server to assign the Copilot agent +4. Pass the appropriate parameter to the tool: + - For issues: pass `issue_number` (the numeric ID extracted from the URL) + - For pull requests: pass `pull_number` (the numeric ID extracted from the URL) -**Important**: Do not use GitHub tools directly for assignment. Only use the `assign_to_agent` safe output tool. +**Important**: Do not use GitHub tools directly for assignment. Only use the `assign_to_agent` safe output tool with the correct parameter based on the URL type. diff --git a/.github/workflows/workflow-generator.lock.yml b/.github/workflows/workflow-generator.lock.yml index e99ffce9521..b352d209c87 100644 --- a/.github/workflows/workflow-generator.lock.yml +++ b/.github/workflows/workflow-generator.lock.yml @@ -208,25 +208,41 @@ jobs: cat > /opt/gh-aw/safeoutputs/tools.json << 'EOF' [ { - "description": "Assign the GitHub Copilot coding agent to work on an issue. The agent will analyze the issue and attempt to implement a solution, creating a pull request when complete. Use this to delegate coding tasks to Copilot.", + "description": "Assign the GitHub Copilot coding agent to work on an issue or pull request. The agent will analyze the issue/PR and attempt to implement a solution, creating a pull request when complete. Use this to delegate coding tasks to Copilot. Example usage: assign_to_agent(issue_number=123, agent=\"copilot\") or assign_to_agent(pull_number=456, agent=\"copilot\")", "inputSchema": { "additionalProperties": false, + "oneOf": [ + { + "required": [ + "issue_number" + ] + }, + { + "required": [ + "pull_number" + ] + } + ], "properties": { "agent": { "description": "Agent identifier to assign. Defaults to 'copilot' (the Copilot coding agent) if not specified.", "type": "string" }, "issue_number": { - "description": "Issue number to assign the Copilot agent to. This is the numeric ID from the GitHub URL (e.g., 234 in github.com/owner/repo/issues/234). The issue should contain clear, actionable requirements.", + "description": "Issue number to assign the Copilot agent to. This is the numeric ID from the GitHub URL (e.g., 234 in github.com/owner/repo/issues/234). The issue should contain clear, actionable requirements. Either issue_number or pull_number must be provided, but not both.", + "type": [ + "number", + "string" + ] + }, + "pull_number": { + "description": "Pull request number to assign the Copilot agent to. This is the numeric ID from the GitHub URL (e.g., 456 in github.com/owner/repo/pull/456). Either issue_number or pull_number must be provided, but not both.", "type": [ "number", "string" ] } }, - "required": [ - "issue_number" - ], "type": "object" }, "name": "assign_to_agent" diff --git a/actions/setup/js/assign_agent_helpers.cjs b/actions/setup/js/assign_agent_helpers.cjs index 23dd6d37181..bb4daeb4785 100644 --- a/actions/setup/js/assign_agent_helpers.cjs +++ b/actions/setup/js/assign_agent_helpers.cjs @@ -173,14 +173,60 @@ async function getIssueDetails(owner, repo, issueNumber) { } /** - * Assign agent to issue using GraphQL replaceActorsForAssignable mutation - * @param {string} issueId - GitHub issue ID + * Get pull request details (ID and current assignees) using GraphQL + * @param {string} owner - Repository owner + * @param {string} repo - Repository name + * @param {number} pullNumber - Pull request number + * @returns {Promise<{pullRequestId: string, currentAssignees: string[]}|null>} + */ +async function getPullRequestDetails(owner, repo, pullNumber) { + const query = ` + query($owner: String!, $repo: String!, $pullNumber: Int!) { + repository(owner: $owner, name: $repo) { + pullRequest(number: $pullNumber) { + id + assignees(first: 100) { + nodes { + id + } + } + } + } + } + `; + + try { + const response = await github.graphql(query, { owner, repo, pullNumber }); + const pullRequest = response.repository.pullRequest; + + if (!pullRequest || !pullRequest.id) { + core.error("Could not get pull request data"); + return null; + } + + const currentAssignees = pullRequest.assignees.nodes.map(assignee => assignee.id); + + return { + pullRequestId: pullRequest.id, + currentAssignees, + }; + } catch (error) { + const errorMessage = getErrorMessage(error); + core.error(`Failed to get pull request details: ${errorMessage}`); + // Re-throw the error to preserve the original error message for permission error detection + throw error; + } +} + +/** + * Assign agent to issue or pull request using GraphQL replaceActorsForAssignable mutation + * @param {string} assignableId - GitHub issue or pull request ID * @param {string} agentId - Agent ID * @param {string[]} currentAssignees - List of current assignee IDs * @param {string} agentName - Agent name for error messages * @returns {Promise} True if successful */ -async function assignAgentToIssue(issueId, agentId, currentAssignees, agentName) { +async function assignAgentToIssue(assignableId, agentId, currentAssignees, agentName) { // Build actor IDs array - include agent and preserve other assignees const actorIds = [agentId, ...currentAssignees.filter(id => id !== agentId)]; @@ -198,9 +244,9 @@ async function assignAgentToIssue(issueId, agentId, currentAssignees, agentName) try { core.info("Using built-in github object for mutation"); - core.debug(`GraphQL mutation with variables: assignableId=${issueId}, actorIds=${JSON.stringify(actorIds)}`); + core.debug(`GraphQL mutation with variables: assignableId=${assignableId}, actorIds=${JSON.stringify(actorIds)}`); const response = await github.graphql(mutation, { - assignableId: issueId, + assignableId: assignableId, actorIds, }); @@ -295,9 +341,9 @@ async function assignAgentToIssue(issueId, agentId, currentAssignees, agentName) } `; core.info("Using built-in github object for fallback mutation"); - core.debug(`Fallback GraphQL mutation with variables: assignableId=${issueId}, assigneeIds=[${agentId}]`); + core.debug(`Fallback GraphQL mutation with variables: assignableId=${assignableId}, assigneeIds=[${agentId}]`); const fallbackResp = await github.graphql(fallbackMutation, { - assignableId: issueId, + assignableId: assignableId, assigneeIds: [agentId], }); if (fallbackResp?.addAssigneesToAssignable) { @@ -447,6 +493,7 @@ module.exports = { getAvailableAgentLogins, findAgent, getIssueDetails, + getPullRequestDetails, assignAgentToIssue, logPermissionError, generatePermissionErrorSummary, diff --git a/actions/setup/js/assign_to_agent.cjs b/actions/setup/js/assign_to_agent.cjs index b29a7f5f09b..6e90f184568 100644 --- a/actions/setup/js/assign_to_agent.cjs +++ b/actions/setup/js/assign_to_agent.cjs @@ -3,7 +3,7 @@ const { loadAgentOutput } = require("./load_agent_output.cjs"); const { generateStagedPreview } = require("./staged_preview.cjs"); -const { AGENT_LOGIN_NAMES, getAvailableAgentLogins, findAgent, getIssueDetails, assignAgentToIssue, generatePermissionErrorSummary } = require("./assign_agent_helpers.cjs"); +const { AGENT_LOGIN_NAMES, getAvailableAgentLogins, findAgent, getIssueDetails, getPullRequestDetails, assignAgentToIssue, generatePermissionErrorSummary } = require("./assign_agent_helpers.cjs"); const { getErrorMessage } = require("./error_helpers.cjs"); async function main() { @@ -27,7 +27,12 @@ async function main() { description: "The following agent assignments would be made if staged mode was disabled:", items: assignItems, renderItem: item => { - let content = `**Issue:** #${item.issue_number}\n`; + let content = ""; + if (item.issue_number) { + content += `**Issue:** #${item.issue_number}\n`; + } else if (item.pull_number) { + content += `**Pull Request:** #${item.pull_number}\n`; + } content += `**Agent:** ${item.agent || "copilot"}\n`; content += "\n"; return content; @@ -80,11 +85,27 @@ async function main() { // Process each agent assignment const results = []; for (const item of itemsToProcess) { - const issueNumber = typeof item.issue_number === "number" ? item.issue_number : parseInt(String(item.issue_number), 10); + // Determine if this is an issue or PR assignment + const issueNumber = item.issue_number ? (typeof item.issue_number === "number" ? item.issue_number : parseInt(String(item.issue_number), 10)) : null; + const pullNumber = item.pull_number ? (typeof item.pull_number === "number" ? item.pull_number : parseInt(String(item.pull_number), 10)) : null; const agentName = item.agent ?? defaultAgent; - if (isNaN(issueNumber) || issueNumber <= 0) { - core.error(`Invalid issue_number: ${item.issue_number}`); + // Validate that we have either issue_number or pull_number + if (!issueNumber && !pullNumber) { + core.error("Missing both issue_number and pull_number in assign_to_agent item"); + continue; + } + + if (issueNumber && pullNumber) { + core.error("Cannot specify both issue_number and pull_number in the same assign_to_agent item"); + continue; + } + + const number = issueNumber || pullNumber; + const type = issueNumber ? "issue" : "pull request"; + + if (isNaN(number) || number <= 0) { + core.error(`Invalid ${type} number: ${number}`); continue; } @@ -93,6 +114,7 @@ async function main() { core.warning(`Agent "${agentName}" is not supported. Supported agents: ${Object.keys(AGENT_LOGIN_NAMES).join(", ")}`); results.push({ issue_number: issueNumber, + pull_number: pullNumber, agent: agentName, success: false, error: `Unsupported agent: ${agentName}`, @@ -100,7 +122,7 @@ async function main() { continue; } - // Assign the agent to the issue using GraphQL + // Assign the agent to the issue or PR using GraphQL try { // Find agent (use cache if available) - uses built-in github object authenticated via github-token let agentId = agentCache[agentName]; @@ -114,20 +136,35 @@ async function main() { core.info(`Found ${agentName} coding agent (ID: ${agentId})`); } - // Get issue details (ID and current assignees) via GraphQL - core.info("Getting issue details..."); - const issueDetails = await getIssueDetails(targetOwner, targetRepo, issueNumber); - if (!issueDetails) { - throw new Error("Failed to get issue details"); + // Get issue or PR details (ID and current assignees) via GraphQL + core.info(`Getting ${type} details...`); + let assignableId; + let currentAssignees; + + if (issueNumber) { + const issueDetails = await getIssueDetails(targetOwner, targetRepo, issueNumber); + if (!issueDetails) { + throw new Error(`Failed to get issue details`); + } + assignableId = issueDetails.issueId; + currentAssignees = issueDetails.currentAssignees; + } else { + const prDetails = await getPullRequestDetails(targetOwner, targetRepo, pullNumber); + if (!prDetails) { + throw new Error(`Failed to get pull request details`); + } + assignableId = prDetails.pullRequestId; + currentAssignees = prDetails.currentAssignees; } - core.info(`Issue ID: ${issueDetails.issueId}`); + core.info(`${type} ID: ${assignableId}`); // Check if agent is already assigned - if (issueDetails.currentAssignees.includes(agentId)) { - core.info(`${agentName} is already assigned to issue #${issueNumber}`); + if (currentAssignees.includes(agentId)) { + core.info(`${agentName} is already assigned to ${type} #${number}`); results.push({ issue_number: issueNumber, + pull_number: pullNumber, agent: agentName, success: true, }); @@ -135,16 +172,17 @@ async function main() { } // Assign agent using GraphQL mutation - uses built-in github object authenticated via github-token - core.info(`Assigning ${agentName} coding agent to issue #${issueNumber}...`); - const success = await assignAgentToIssue(issueDetails.issueId, agentId, issueDetails.currentAssignees, agentName); + core.info(`Assigning ${agentName} coding agent to ${type} #${number}...`); + const success = await assignAgentToIssue(assignableId, agentId, currentAssignees, agentName); if (!success) { throw new Error(`Failed to assign ${agentName} via GraphQL`); } - core.info(`Successfully assigned ${agentName} coding agent to issue #${issueNumber}`); + core.info(`Successfully assigned ${agentName} coding agent to ${type} #${number}`); results.push({ issue_number: issueNumber, + pull_number: pullNumber, agent: agentName, success: true, }); @@ -161,9 +199,10 @@ async function main() { core.debug("Failed to enrich unavailable agent message with available list"); } } - core.error(`Failed to assign agent "${agentName}" to issue #${issueNumber}: ${errorMessage}`); + core.error(`Failed to assign agent "${agentName}" to ${type} #${number}: ${errorMessage}`); results.push({ issue_number: issueNumber, + pull_number: pullNumber, agent: agentName, success: false, error: errorMessage, @@ -181,7 +220,10 @@ async function main() { summaryContent += `✅ Successfully assigned ${successCount} agent(s):\n\n`; summaryContent += results .filter(r => r.success) - .map(r => `- Issue #${r.issue_number} → Agent: ${r.agent}`) + .map(r => { + const itemType = r.issue_number ? `Issue #${r.issue_number}` : `Pull Request #${r.pull_number}`; + return `- ${itemType} → Agent: ${r.agent}`; + }) .join("\n"); summaryContent += "\n\n"; } @@ -190,7 +232,10 @@ async function main() { summaryContent += `❌ Failed to assign ${failureCount} agent(s):\n\n`; summaryContent += results .filter(r => !r.success) - .map(r => `- Issue #${r.issue_number} → Agent: ${r.agent}: ${r.error}`) + .map(r => { + const itemType = r.issue_number ? `Issue #${r.issue_number}` : `Pull Request #${r.pull_number}`; + return `- ${itemType} → Agent: ${r.agent}: ${r.error}`; + }) .join("\n"); // Check if any failures were permission-related @@ -206,7 +251,11 @@ async function main() { // Set outputs const assignedAgents = results .filter(r => r.success) - .map(r => `${r.issue_number}:${r.agent}`) + .map(r => { + const number = r.issue_number || r.pull_number; + const prefix = r.issue_number ? "issue" : "pr"; + return `${prefix}:${number}:${r.agent}`; + }) .join("\n"); core.setOutput("assigned_agents", assignedAgents); diff --git a/actions/setup/js/assign_to_agent.test.cjs b/actions/setup/js/assign_to_agent.test.cjs index 39d986cd625..d37c628711b 100644 --- a/actions/setup/js/assign_to_agent.test.cjs +++ b/actions/setup/js/assign_to_agent.test.cjs @@ -227,7 +227,7 @@ describe("assign_to_agent", () => { await eval(`(async () => { ${assignToAgentScript}; await main(); })()`); - expect(mockCore.error).toHaveBeenCalledWith(expect.stringContaining("Invalid issue_number")); + expect(mockCore.error).toHaveBeenCalledWith(expect.stringContaining("Invalid issue number")); }); it("should handle agent already assigned", async () => { diff --git a/actions/setup/js/safe_outputs_tools.json b/actions/setup/js/safe_outputs_tools.json index df900b4e211..be96b9081ad 100644 --- a/actions/setup/js/safe_outputs_tools.json +++ b/actions/setup/js/safe_outputs_tools.json @@ -337,21 +337,32 @@ }, { "name": "assign_to_agent", - "description": "Assign the GitHub Copilot coding agent to work on an issue. The agent will analyze the issue and attempt to implement a solution, creating a pull request when complete. Use this to delegate coding tasks to Copilot.", + "description": "Assign the GitHub Copilot coding agent to work on an issue or pull request. The agent will analyze the issue/PR and attempt to implement a solution, creating a pull request when complete. Use this to delegate coding tasks to Copilot. Example usage: assign_to_agent(issue_number=123, agent=\"copilot\") or assign_to_agent(pull_number=456, agent=\"copilot\")", "inputSchema": { "type": "object", - "required": ["issue_number"], "properties": { "issue_number": { "type": ["number", "string"], - "description": "Issue number to assign the Copilot agent to. This is the numeric ID from the GitHub URL (e.g., 234 in github.com/owner/repo/issues/234). The issue should contain clear, actionable requirements." + "description": "Issue number to assign the Copilot agent to. This is the numeric ID from the GitHub URL (e.g., 234 in github.com/owner/repo/issues/234). The issue should contain clear, actionable requirements. Either issue_number or pull_number must be provided, but not both." + }, + "pull_number": { + "type": ["number", "string"], + "description": "Pull request number to assign the Copilot agent to. This is the numeric ID from the GitHub URL (e.g., 456 in github.com/owner/repo/pull/456). Either issue_number or pull_number must be provided, but not both." }, "agent": { "type": "string", "description": "Agent identifier to assign. Defaults to 'copilot' (the Copilot coding agent) if not specified." } }, - "additionalProperties": false + "additionalProperties": false, + "oneOf": [ + { + "required": ["issue_number"] + }, + { + "required": ["pull_number"] + } + ] } }, { diff --git a/docs/src/content/docs/reference/safe-outputs.md b/docs/src/content/docs/reference/safe-outputs.md index efc089c9e90..c4e7186b15d 100644 --- a/docs/src/content/docs/reference/safe-outputs.md +++ b/docs/src/content/docs/reference/safe-outputs.md @@ -49,7 +49,7 @@ The agent requests issue creation; a separate job with `issues: write` creates i - [**Add Labels**](#add-labels-add-labels) (`add-labels`) — Add labels to issues or PRs (max: 3) - [**Add Reviewer**](#add-reviewer-add-reviewer) (`add-reviewer`) — Add reviewers to pull requests (max: 3) - [**Assign Milestone**](#assign-milestone-assign-milestone) (`assign-milestone`) — Assign issues to milestones (max: 1) -- [**Assign to Agent**](#assign-to-agent-assign-to-agent) (`assign-to-agent`) — Assign Copilot agents to issues (max: 1) +- [**Assign to Agent**](#assign-to-agent-assign-to-agent) (`assign-to-agent`) — Assign Copilot agents to issues or PRs (max: 1) - [**Assign to User**](#assign-to-user-assign-to-user) (`assign-to-user`) — Assign users to issues (max: 1) ### Projects, Releases & Assets @@ -746,13 +746,15 @@ Creates Copilot agent sessions. Requires `COPILOT_GITHUB_TOKEN` or `GH_AW_GITHUB ### Assign to Agent (`assign-to-agent:`) -Assigns Copilot coding agent to issues. Requires fine-grained PAT with actions, contents, issues, pull requests write access stored as `GH_AW_AGENT_TOKEN`, or GitHub App token. Supported agents: `copilot` (`copilot-swe-agent`). +Assigns Copilot coding agent to issues or pull requests. Requires fine-grained PAT with actions, contents, issues, pull requests write access stored as `GH_AW_AGENT_TOKEN`, or GitHub App token. Supported agents: `copilot` (`copilot-swe-agent`). + +The agent must provide either `issue_number` or `pull_number` in the output to specify which item to assign. ```yaml wrap safe-outputs: assign-to-agent: - name: "copilot" - target-repo: "owner/repo" # cross-repository + name: "copilot" # default agent (optional) + target-repo: "owner/repo" # cross-repository ``` ### Assign to User (`assign-to-user:`) diff --git a/pkg/workflow/js/safe_outputs_tools.json b/pkg/workflow/js/safe_outputs_tools.json index bd0d42ee1ac..a5f69ccb01f 100644 --- a/pkg/workflow/js/safe_outputs_tools.json +++ b/pkg/workflow/js/safe_outputs_tools.json @@ -489,26 +489,42 @@ }, { "name": "assign_to_agent", - "description": "Assign the GitHub Copilot coding agent to work on an issue. The agent will analyze the issue and attempt to implement a solution, creating a pull request when complete. Use this to delegate coding tasks to Copilot.", + "description": "Assign the GitHub Copilot coding agent to work on an issue or pull request. The agent will analyze the issue/PR and attempt to implement a solution, creating a pull request when complete. Use this to delegate coding tasks to Copilot. Example usage: assign_to_agent(issue_number=123, agent=\"copilot\") or assign_to_agent(pull_number=456, agent=\"copilot\")", "inputSchema": { "type": "object", - "required": [ - "issue_number" - ], "properties": { "issue_number": { "type": [ "number", "string" ], - "description": "Issue number to assign the Copilot agent to. This is the numeric ID from the GitHub URL (e.g., 234 in github.com/owner/repo/issues/234). The issue should contain clear, actionable requirements." + "description": "Issue number to assign the Copilot agent to. This is the numeric ID from the GitHub URL (e.g., 234 in github.com/owner/repo/issues/234). The issue should contain clear, actionable requirements. Either issue_number or pull_number must be provided, but not both." + }, + "pull_number": { + "type": [ + "number", + "string" + ], + "description": "Pull request number to assign the Copilot agent to. This is the numeric ID from the GitHub URL (e.g., 456 in github.com/owner/repo/pull/456). Either issue_number or pull_number must be provided, but not both." }, "agent": { "type": "string", "description": "Agent identifier to assign. Defaults to 'copilot' (the Copilot coding agent) if not specified." } }, - "additionalProperties": false + "additionalProperties": false, + "oneOf": [ + { + "required": [ + "issue_number" + ] + }, + { + "required": [ + "pull_number" + ] + } + ] } }, {