Skip to content

Commit a95cb38

Browse files
chore: sync actions from gh-aw@v0.81.3 (#171)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent b5cde6c commit a95cb38

23 files changed

Lines changed: 812 additions & 227 deletions

setup/js/assign_agent_helpers.cjs

Lines changed: 64 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -72,34 +72,86 @@ function getAgentName(assignee) {
7272

7373
/**
7474
* Return list of coding agent bot login names that are currently available as assignable actors
75-
* in this repository, as determined by checkUserCanBeAssigned.
75+
* in this repository, preferring issue-scoped checks when issue/PR context is available
76+
* and falling back to repository-scoped checks.
7677
* @param {string} owner
7778
* @param {string} repo
79+
* @param {number|string|null} [issueNumber]
7880
* @param {Object} [githubClient] - Authenticated GitHub client (defaults to global github)
7981
* @returns {Promise<string[]>}
8082
*/
81-
async function getAvailableAgentLogins(owner, repo, githubClient = github) {
83+
async function getAvailableAgentLogins(owner, repo, issueNumber = null, githubClient = github) {
8284
// Deduplicate defensively so future alias additions across agents do not duplicate REST lookups.
8385
const knownValues = [...new Set(Object.values(AGENT_LOGIN_NAMES).flat())];
8486
const available = [];
8587
for (const login of knownValues) {
8688
try {
87-
await githubClient.rest.issues.checkUserCanBeAssigned({
88-
owner,
89-
repo,
90-
assignee: login,
91-
});
89+
await validateAssigneeAlias(owner, repo, login, issueNumber, githubClient);
9290
available.push(login);
9391
} catch (e) {
9492
const status = e && typeof e === "object" && "status" in e ? e.status : undefined;
9593
if (status !== 404) {
96-
core.debug(`Failed to check assignability for ${login}: ${getErrorMessage(e)}`);
94+
core.info(`Failed to check assignability for ${login}: ${getErrorMessage(e)}`);
9795
}
9896
}
9997
}
10098
return available.sort();
10199
}
102100

101+
/**
102+
* Validate whether an assignee alias can be assigned in the repository context.
103+
* Prefer issue-level assignability checks when issue/PR number is available because
104+
* some agent bots are not surfaced by repository-scoped checks.
105+
* @param {string} owner
106+
* @param {string} repo
107+
* @param {string} assignee
108+
* @param {number|string|null} issueNumber
109+
* @param {Object} githubClient
110+
*/
111+
async function validateAssigneeAlias(owner, repo, assignee, issueNumber, githubClient) {
112+
const parsedIssueNumber = Number(issueNumber);
113+
const hasValidIssueNumber = Number.isInteger(parsedIssueNumber) && parsedIssueNumber > 0;
114+
const hasIssueScopedRequest = typeof githubClient?.request === "function";
115+
116+
if (issueNumber && hasValidIssueNumber && hasIssueScopedRequest) {
117+
core.info(`Checking assignee alias ${assignee} via issue-scoped endpoint for ${owner}/${repo}#${parsedIssueNumber}`);
118+
try {
119+
const issueScopedResponse = await githubClient.request("GET /repos/{owner}/{repo}/issues/{issue_number}/assignees/{assignee}", {
120+
owner,
121+
repo,
122+
issue_number: parsedIssueNumber,
123+
assignee,
124+
});
125+
const issueScopedStatus = issueScopedResponse && typeof issueScopedResponse === "object" && "status" in issueScopedResponse ? Number(issueScopedResponse.status) : undefined;
126+
if (issueScopedStatus !== undefined && Number.isInteger(issueScopedStatus) && issueScopedStatus >= 200 && issueScopedStatus < 300) {
127+
core.info(`Assignee alias ${assignee} is assignable via issue-scoped check`);
128+
return;
129+
}
130+
core.info(`Issue-scoped assignee check returned unexpected response for ${assignee} (status ${issueScopedStatus ?? "unknown"}); falling back to repository-scoped check`);
131+
} catch (e) {
132+
const status = e && typeof e === "object" && "status" in e ? e.status : undefined;
133+
// Some coding-agent bot aliases can return 404 on issue-scoped checks even when
134+
// assignment may still succeed; use repository-scoped endpoint as fallback.
135+
if (status !== 404 && status !== 422) {
136+
core.info(`Issue-scoped assignee check failed for ${assignee} with status ${status ?? "unknown"}: ${getErrorMessage(e)}`);
137+
throw e;
138+
}
139+
core.info(`Issue-scoped assignee check returned ${status} for ${assignee}; falling back to repository-scoped check`);
140+
}
141+
} else if (issueNumber && !hasValidIssueNumber) {
142+
core.info(`Skipping issue-scoped assignee check for ${assignee}: invalid issue number ${String(issueNumber)}`);
143+
} else if (issueNumber && !hasIssueScopedRequest) {
144+
core.info(`Skipping issue-scoped assignee check for ${assignee}: github client does not support request()`);
145+
}
146+
core.info(`Checking assignee alias ${assignee} via repository-scoped endpoint for ${owner}/${repo}`);
147+
await githubClient.rest.issues.checkUserCanBeAssigned({
148+
owner,
149+
repo,
150+
assignee,
151+
});
152+
core.info(`Assignee alias ${assignee} is assignable via repository-scoped check`);
153+
}
154+
103155
/**
104156
* Return assignable bot logins from the repository assignee list.
105157
* @param {string} owner
@@ -145,10 +197,11 @@ async function getAssignableBots(owner, repo, githubClient = github) {
145197
* @param {string} owner - Repository owner
146198
* @param {string} repo - Repository name
147199
* @param {string} agentName - Agent name (copilot)
200+
* @param {number|string|null} [issueNumber] - Optional issue/PR number for issue-scoped assignability check
148201
* @param {Object} [githubClient] - Authenticated GitHub client (defaults to global github)
149202
* @returns {Promise<string|null>} Agent ID or null if not found
150203
*/
151-
async function findAgent(owner, repo, agentName, githubClient = github) {
204+
async function findAgent(owner, repo, agentName, issueNumber = null, githubClient = github) {
152205
const loginNames = getAgentLogins(agentName);
153206
if (loginNames.length === 0) {
154207
core.error(`Unknown agent: ${agentName}. Supported agents: ${Object.keys(AGENT_LOGIN_NAMES).join(", ")}`);
@@ -161,11 +214,7 @@ async function findAgent(owner, repo, agentName, githubClient = github) {
161214
for (const loginName of loginNames) {
162215
try {
163216
core.info(`Checking assignee alias: ${loginName}`);
164-
await githubClient.rest.issues.checkUserCanBeAssigned({
165-
owner,
166-
repo,
167-
assignee: loginName,
168-
});
217+
await validateAssigneeAlias(owner, repo, loginName, issueNumber, githubClient);
169218
} catch (checkError) {
170219
const errorMessage = getErrorMessage(checkError);
171220
const status = checkError?.status;
@@ -536,7 +585,7 @@ async function assignAgentToIssueByName(owner, repo, issueNumber, agentName) {
536585
try {
537586
// Find agent using the github object authenticated via step-level github-token
538587
core.info(`Looking for ${agentName} coding agent...`);
539-
const agentId = await findAgent(owner, repo, agentName);
588+
const agentId = await findAgent(owner, repo, agentName, issueNumber);
540589
if (!agentId) {
541590
return { success: false, error: `${agentName} coding agent is not available for this repository` };
542591
}

setup/js/assign_copilot_to_created_issues.cjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ async function main() {
7373
// Find agent (reuse cached ID for same repo)
7474
if (!agentId) {
7575
core.info(`Looking for ${agentName} coding agent...`);
76-
agentId = await findAgent(owner, repo, agentName);
76+
agentId = await findAgent(owner, repo, agentName, issueNumber);
7777
if (!agentId) {
7878
throw new Error(`${ERR_PERMISSION}: ${agentName} coding agent is not available for this repository`);
7979
}

setup/js/assign_to_agent.cjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ async function main(config = {}) {
330330
let agentId = agentCache[agentName];
331331
if (!agentId) {
332332
core.info(`Looking for ${agentName} coding agent...`);
333-
agentId = await findAgent(effectiveOwner, effectiveRepo, agentName, githubClient);
333+
agentId = await findAgent(effectiveOwner, effectiveRepo, agentName, issueNumber || pullNumber, githubClient);
334334
if (!agentId) {
335335
throw new Error(`${agentName} coding agent is not available for this repository`);
336336
}
@@ -405,7 +405,7 @@ async function main(config = {}) {
405405

406406
if (isAvailabilityError) {
407407
try {
408-
const available = await getAvailableAgentLogins(effectiveOwner, effectiveRepo, githubClient);
408+
const available = await getAvailableAgentLogins(effectiveOwner, effectiveRepo, issueNumber || pullNumber, githubClient);
409409
if (available.length > 0) errorMessage += ` (available agents: ${available.join(", ")})`;
410410
} catch (e) {
411411
core.debug("Failed to enrich unavailable agent message with available list");

setup/js/create_issue.cjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1073,7 +1073,7 @@ async function main(config = {}) {
10731073
}
10741074
core.info(`Assigning copilot coding agent to issue #${issue.number} in ${qualifiedItemRepo}...`);
10751075
try {
1076-
const agentId = await findAgent(repoParts.owner, repoParts.repo, "copilot", copilotClient);
1076+
const agentId = await findAgent(repoParts.owner, repoParts.repo, "copilot", issue.number, copilotClient);
10771077
if (!agentId) {
10781078
core.warning(`copilot coding agent is not available for ${qualifiedItemRepo}`);
10791079
} else {

setup/js/create_pr_review_comment.cjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const { resolveTargetRepoConfig, resolveAndValidateRepo } = require("./repo_help
1010
const { sanitizeContent } = require("./sanitize_content.cjs");
1111
const { createAuthenticatedGitHubClient } = require("./handler_auth.cjs");
1212
const { buildWorkflowRunUrl } = require("./workflow_metadata_helpers.cjs");
13-
const { isStagedMode, logStagedPreviewInfo, checkRequiredFilter } = require("./safe_output_helpers.cjs");
13+
const { isTemplatableTrue, isStagedMode, logStagedPreviewInfo, checkRequiredFilter } = require("./safe_output_helpers.cjs");
1414
const { resolveAllowedMentionsFromPayload } = require("./resolve_mentions_from_payload.cjs");
1515

1616
/** @type {string} Safe output type handled by this module */
@@ -59,7 +59,7 @@ async function main(config = {}) {
5959
}
6060

6161
// Propagate per-handler staged flag to the shared PR review buffer
62-
if (config.staged === true) {
62+
if (isTemplatableTrue(config.staged)) {
6363
buffer.setStaged(true);
6464
}
6565
if (isStagedMode(config)) {

setup/js/create_pull_request.cjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -676,7 +676,7 @@ async function main(config = {}) {
676676
}
677677
core.info(`Assigning copilot coding agent to fallback issue #${issueNumber} in ${owner}/${repo}...`);
678678
try {
679-
const agentId = await findAgent(owner, repo, "copilot", copilotClient);
679+
const agentId = await findAgent(owner, repo, "copilot", issueNumber, copilotClient);
680680
if (!agentId) {
681681
core.warning(`copilot coding agent is not available for ${owner}/${repo}`);
682682
return;

setup/js/dispatch_repository.cjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ async function main(config = {}) {
137137
core.info(`dispatch_repository: dispatching event_type="${eventType}" to ${targetRepoSlug} (workflow: ${toolConfig.workflow || "unspecified"})`);
138138

139139
// If in staged mode, preview without executing
140-
if (isStaged || toolConfig.staged) {
140+
if (isStaged || isStagedMode(toolConfig)) {
141141
logStagedPreviewInfo(`Would dispatch repository_dispatch event: event_type="${eventType}" to ${targetRepoSlug}, client_payload=${JSON.stringify(clientPayload)}`);
142142
dispatchCounts[toolName] = currentCount + 1;
143143
return {

setup/js/issue_intents.cjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// @ts-check
22
/// <reference types="@actions/github-script" />
3+
// @safe-outputs-exempt SEC-005: this file only normalizes issue-intent labels/metadata; "target repository" appears only in a label-not-found error message, with no target-repo parameter or cross-repo write path.
34

45
const { sanitizeContent } = require("./sanitize_content.cjs");
56
const { sanitizeLabelContent } = require("./sanitize_label_content.cjs");

setup/js/messages_footer.cjs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ function getAICFromEnv() {
136136
* @property {string} [workflowSource] - Source of the workflow (owner/repo/path@ref)
137137
* @property {string} [workflowSourceUrl] - GitHub URL for the workflow source
138138
* @property {number|string} [triggeringNumber] - Issue, PR, or discussion number that triggered this workflow
139+
* @property {"issue"|"PR"|"discussion"} [triggeringType] - Triggering item type used in the default footer
139140
* @property {string} [historyUrl] - GitHub search URL for items created by this workflow (for the history link)
140141
* @property {string} [historyLink] - Pre-formatted markdown history link (e.g. " · [◷](url)"), or "" if unavailable
141142
* @property {number|string} [aiCredits] - Total AI Credits cost for the run (1 AIC == 0.01 USD)
@@ -211,7 +212,8 @@ function getFooterMessage(ctx) {
211212
const workflowLabel = ctx.emoji ? `${ctx.emoji} {workflow_name}` : "{workflow_name}";
212213
let defaultFooter = `> Generated by [${workflowLabel}]({run_url})`;
213214
if (ctx.triggeringNumber) {
214-
defaultFooter += " for issue #{triggering_number}";
215+
const prefix = ctx.triggeringType === "discussion" ? "discussion " : "";
216+
defaultFooter += ` for ${prefix}#{triggering_number}`;
215217
}
216218
const metricSuffixes = [];
217219
if (aiCredits) {
@@ -560,12 +562,17 @@ function generateXMLMarker(workflowName, runUrl) {
560562
function generateFooterWithMessages(workflowName, runUrl, workflowSource, workflowSourceURL, triggeringIssueNumber, triggeringPRNumber, triggeringDiscussionNumber, historyUrl, options) {
561563
// Determine triggering number (issue takes precedence, then PR, then discussion)
562564
let triggeringNumber;
565+
/** @type {"issue"|"PR"|"discussion"|undefined} */
566+
let triggeringType;
563567
if (triggeringIssueNumber) {
564568
triggeringNumber = triggeringIssueNumber;
569+
triggeringType = "issue";
565570
} else if (triggeringPRNumber) {
566571
triggeringNumber = triggeringPRNumber;
572+
triggeringType = "PR";
567573
} else if (triggeringDiscussionNumber) {
568-
triggeringNumber = `discussion #${triggeringDiscussionNumber}`;
574+
triggeringNumber = triggeringDiscussionNumber;
575+
triggeringType = "discussion";
569576
}
570577

571578
// Read workflow emoji from environment variable if available.
@@ -598,6 +605,7 @@ function generateFooterWithMessages(workflowName, runUrl, workflowSource, workfl
598605
workflowSource,
599606
workflowSourceUrl: workflowSourceURL,
600607
triggeringNumber,
608+
triggeringType,
601609
historyUrl: historyUrl || undefined,
602610
emoji,
603611
slashCommand,

setup/js/mount_mcp_as_cli.cjs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ const MANIFEST_FILE = path.join(process.env.RUNNER_TEMP || "/home/runner/work/_t
3232
const RUNNER_TEMP = process.env.RUNNER_TEMP || "/home/runner/work/_temp";
3333
const CLI_BIN_DIR = `${RUNNER_TEMP}/gh-aw/mcp-cli/bin`;
3434
const TOOLS_DIR = `${RUNNER_TEMP}/gh-aw/mcp-cli/tools`;
35+
const AWF_GATEWAY_IP = "172.30.0.1";
3536

3637
/** MCP servers that are handled differently and should not be user-facing CLIs.
3738
* Note: safeoutputs and mcpscripts are NOT excluded — they are always CLI-mounted
@@ -76,8 +77,13 @@ function shellEscapeDoubleQuoted(str) {
7677
* @returns {string} URL suitable for use inside AWF containers
7778
*/
7879
function toContainerUrl(rawUrl) {
79-
const domain = process.env.MCP_GATEWAY_DOMAIN;
80+
let domain = process.env.MCP_GATEWAY_DOMAIN;
8081
const port = process.env.MCP_GATEWAY_PORT;
82+
if (domain === "host.docker.internal") {
83+
// The CLI wrappers may run inside a chrooted host environment where
84+
// host.docker.internal is not resolvable. Use the AWF gateway IP instead.
85+
domain = AWF_GATEWAY_IP;
86+
}
8187
if (domain && port) {
8288
return rawUrl.replace(/^https?:\/\/[^/]+\/mcp\//, `http://${domain}:${port}/mcp/`);
8389
}
@@ -447,4 +453,4 @@ async function main() {
447453
core.setOutput("mounted-servers", mountedServers.join(","));
448454
}
449455

450-
module.exports = { main, fetchMCPTools, generateCLIWrapperScript, isValidServerName, shellEscapeDoubleQuoted, parseMCPResponseBody };
456+
module.exports = { AWF_GATEWAY_IP, main, fetchMCPTools, generateCLIWrapperScript, isValidServerName, shellEscapeDoubleQuoted, parseMCPResponseBody, toContainerUrl };

0 commit comments

Comments
 (0)