Skip to content

Commit f889c9c

Browse files
chore: sync actions from gh-aw@v0.75.0 (#112)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 318d7f4 commit f889c9c

15 files changed

Lines changed: 652 additions & 318 deletions

setup/js/awf_reflect.cjs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,8 +142,9 @@ async function enrichReflectModels(reflectData, timeoutMs, logger) {
142142
/**
143143
* Fetch the AWF API proxy /reflect endpoint and persist the response to disk.
144144
*
145-
* The /reflect endpoint is exposed by the api-proxy sidecar on its management port (10000)
146-
* and returns the list of configured LLM providers together with their available model lists.
145+
* The /reflect endpoint is exposed by the api-proxy sidecar on each started provider port.
146+
* The active provider's gateway port should be used rather than a hardcoded port, since
147+
* port 10000 (the OpenAI sidecar) is only started when OpenAI credentials are configured.
147148
* This information is saved to AWF_REFLECT_OUTPUT_PATH so the post-run GitHub Actions step
148149
* (awf_reflect_summary.cjs) can include it in the step summary without requiring the
149150
* containers to still be running.

setup/js/close_discussion.cjs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -284,9 +284,7 @@ async function main(config = {}) {
284284
if (!allowBody) {
285285
// allow-body: false — drop any body the agent provided and close without a comment
286286
if (item.body) {
287-
core.warning(
288-
`close_discussion: allow-body is false — dropping non-empty body (length=${item.body.length}) and closing without a comment`
289-
);
287+
core.warning(`close_discussion: allow-body is false — dropping non-empty body (length=${item.body.length}) and closing without a comment`);
290288
} else {
291289
core.info("close_discussion: allow-body is false — closing without a comment");
292290
}

setup/js/close_entity_helpers.cjs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -302,9 +302,7 @@ function createCloseEntityHandler(config, entityConfig, callbacks, githubClient)
302302
if (!allowBody) {
303303
// allow-body: false — drop any body the agent provided and skip the comment
304304
if (typeof item.body === "string" && item.body.trim() !== "") {
305-
core.warning(
306-
`${entityConfig.itemType}: allow-body is false — dropping non-empty body (length=${item.body.length}) and closing without a comment`
307-
);
305+
core.warning(`${entityConfig.itemType}: allow-body is false — dropping non-empty body (length=${item.body.length}) and closing without a comment`);
308306
} else {
309307
core.info(`${entityConfig.itemType}: allow-body is false — closing without a comment`);
310308
}

setup/js/create_pull_request.cjs

Lines changed: 33 additions & 246 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,32 @@ const { getBaseBranch } = require("./get_base_branch.cjs");
2727
const { createAuthenticatedGitHubClient } = require("./handler_auth.cjs");
2828
const { buildWorkflowRunUrl } = require("./workflow_metadata_helpers.cjs");
2929
const { checkFileProtection } = require("./manifest_file_helpers.cjs");
30-
const { renderTemplateFromFile, buildProtectedFileList, encodePathSegments, getPromptPath } = require("./messages_core.cjs");
31-
const { COPILOT_REVIEWER_BOT, FAQ_CREATE_PR_PERMISSIONS_URL, MAX_ASSIGNEES } = require("./constants.cjs");
30+
const { renderTemplateFromFile, buildProtectedFileList, getPromptPath } = require("./messages_core.cjs");
31+
const { COPILOT_REVIEWER_BOT, FAQ_CREATE_PR_PERMISSIONS_URL } = require("./constants.cjs");
3232
const { isStagedMode } = require("./safe_output_helpers.cjs");
33-
const { withRetry, isTransientError, RATE_LIMIT_RETRY_CONFIG } = require("./error_recovery.cjs");
34-
const { tryEnforceArrayLimit } = require("./limit_enforcement_helpers.cjs");
33+
const { withRetry, RATE_LIMIT_RETRY_CONFIG } = require("./error_recovery.cjs");
3534
const { findAgent, getIssueDetails, assignAgentToIssue } = require("./assign_agent_helpers.cjs");
36-
const { globPatternToRegex } = require("./glob_pattern_helpers.cjs");
3735
const { ensureFullHistoryForBundle, extractBundlePrerequisiteCommits } = require("./git_helpers.cjs");
3836
const { parseDiffGitHeader: parseDiffGitHeaderPaths, extractDiffGitHeaderEntries } = require("./patch_path_helpers.cjs");
3937
const { resolveAllowedMentionsFromPayload } = require("./resolve_mentions_from_payload.cjs");
38+
const {
39+
MANAGED_FALLBACK_ISSUE_LABEL,
40+
LABEL_MAX_RETRIES,
41+
LABEL_INITIAL_DELAY_MS,
42+
LABEL_MAX_DELAY_MS,
43+
summarizeListForLog,
44+
createBundleTempRef,
45+
isLabelTransientError,
46+
parseAllowedBaseBranches,
47+
isBaseBranchAllowed,
48+
parseStringListConfig,
49+
mergeFallbackIssueLabels,
50+
sanitizeFallbackAssignees,
51+
neutralizeClosingKeywordsForIssueBody,
52+
generatePatchPreview,
53+
buildManifestProtectionCreatePrUrl,
54+
renderManifestProtectionFallbackBody,
55+
} = require("./create_pull_request_helpers.cjs");
4056

4157
/**
4258
* @typedef {import('./types/handler-factory').HandlerFactoryFunction} HandlerFactoryFunction
@@ -68,35 +84,8 @@ async function createCopilotAssignmentClient(config) {
6884
/** @type {string} Safe output type handled by this module */
6985
const HANDLER_TYPE = "create_pull_request";
7086

71-
/** @type {string} Label always added to fallback issues so the triage system can find them */
72-
const MANAGED_FALLBACK_ISSUE_LABEL = "agentic-workflows";
73-
74-
/**
75-
* Creates a temporary refs/bundles ref for applying create_pull_request bundles.
76-
* Branch names are sanitized for ref compatibility, and a short crypto-random
77-
* suffix avoids collisions between branches that sanitize to the same value.
78-
*
79-
* @param {string} branchName - Target branch name
80-
* @returns {string} Temporary bundle ref name
81-
*/
82-
function createBundleTempRef(branchName) {
83-
const suffix = crypto.randomBytes(4).toString("hex");
84-
return `refs/bundles/create-pr-${branchName.replace(/[^a-zA-Z0-9-]/g, "-")}-${suffix}`;
85-
}
86-
87-
/**
88-
* Summarize a list for log output to avoid excessively long lines.
89-
* @param {string[]} values
90-
* @param {number} limit
91-
* @returns {string}
92-
*/
93-
function summarizeListForLog(values, limit = 10) {
94-
if (!Array.isArray(values) || values.length === 0) {
95-
return "(none)";
96-
}
97-
const preview = values.slice(0, limit).join(", ");
98-
return values.length > limit ? `${preview} ... and ${values.length - limit} more` : preview;
99-
}
87+
// NOTE: MANAGED_FALLBACK_ISSUE_LABEL, createBundleTempRef, and summarizeListForLog
88+
// are imported from create_pull_request_helpers.cjs above.
10089

10190
/**
10291
* Attempt automatic recovery for git am add/add conflicts by preferring the patch version.
@@ -319,131 +308,11 @@ async function rewriteBundleBranchAsSingleCommit(baseBranch, execApi) {
319308
}
320309
}
321310

322-
/**
323-
* Determines if a label API error is transient and worth retrying.
324-
* Returns true for:
325-
* - The GitHub race condition where a newly-created PR's node ID is not immediately
326-
* resolvable via the REST/GraphQL bridge (unprocessable validation error).
327-
* - Any standard transient error matched by {@link isTransientError} (network issues,
328-
* rate limits, 5xx gateway errors, etc.).
329-
* @param {any} error - The error to check
330-
* @returns {boolean} True if the error is transient and should be retried
331-
*/
332-
function isLabelTransientError(error) {
333-
const msg = getErrorMessage(error);
334-
if (msg.includes("Could not resolve to a node with the global id")) {
335-
return true;
336-
}
337-
return isTransientError(error);
338-
}
339-
340-
/** @type {number} Number of retry attempts for label operations */
341-
const LABEL_MAX_RETRIES = 5;
342-
/** @type {number} Base delay in ms used to calculate label retry backoff (3 seconds) */
343-
const LABEL_INITIAL_DELAY_MS = 3000;
344-
/** @type {number} Maximum delay in ms between label retries (30 seconds) */
345-
const LABEL_MAX_DELAY_MS = 30000;
346-
347-
/**
348-
* Parse allowed base branch patterns from config value (array or comma-separated string)
349-
* @param {string[]|string|undefined} allowedBaseBranchesValue
350-
* @returns {Set<string>}
351-
*/
352-
function parseAllowedBaseBranches(allowedBaseBranchesValue) {
353-
const set = new Set();
354-
if (Array.isArray(allowedBaseBranchesValue)) {
355-
allowedBaseBranchesValue
356-
.map(branch => String(branch).trim())
357-
.filter(Boolean)
358-
.forEach(branch => set.add(branch));
359-
} else if (typeof allowedBaseBranchesValue === "string") {
360-
allowedBaseBranchesValue
361-
.split(",")
362-
.map(branch => branch.trim())
363-
.filter(Boolean)
364-
.forEach(branch => set.add(branch));
365-
}
366-
return set;
367-
}
368-
369-
/**
370-
* Check if a base branch matches an allowed pattern.
371-
* Supports exact matches and "*" glob patterns (e.g. "release/*").
372-
* @param {string} baseBranch
373-
* @param {Set<string>} allowedBaseBranches
374-
* @returns {boolean}
375-
*/
376-
function isBaseBranchAllowed(baseBranch, allowedBaseBranches) {
377-
if (allowedBaseBranches.has(baseBranch)) {
378-
return true;
379-
}
380-
for (const pattern of allowedBaseBranches) {
381-
if (pattern === "*") {
382-
return true;
383-
}
384-
if (pattern.includes("*") && globPatternToRegex(pattern, { pathMode: true, caseSensitive: true }).test(baseBranch)) {
385-
return true;
386-
}
387-
}
388-
return false;
389-
}
390-
391-
/**
392-
* Parse config values that may be arrays or comma-separated strings.
393-
* @param {string[]|string|undefined} value
394-
* @returns {string[]}
395-
*/
396-
function parseStringListConfig(value) {
397-
if (!value) {
398-
return [];
399-
}
400-
const raw = Array.isArray(value) ? value : String(value).split(",");
401-
return raw.map(item => String(item).trim()).filter(Boolean);
402-
}
403-
404-
/**
405-
* Merges the required fallback label with any workflow-configured labels,
406-
* deduplicating and filtering empty values.
407-
* @param {string[]} [labels]
408-
* @returns {string[]}
409-
*/
410-
function mergeFallbackIssueLabels(labels = []) {
411-
const normalizedLabels = labels
412-
.filter(label => !!label)
413-
.map(label => String(label).trim())
414-
.filter(label => label);
415-
return [...new Set([MANAGED_FALLBACK_ISSUE_LABEL, ...normalizedLabels])];
416-
}
417-
418-
/**
419-
* Sanitizes configured assignees for fallback issue creation.
420-
* Filters invalid values, removes the special "copilot" username (not a valid GitHub user
421-
* for issue assignment), and enforces the MAX_ASSIGNEES limit.
422-
* Returns null (no assignees field) if the sanitized list is empty.
423-
* @param {string[]} assignees - Raw assignees from config
424-
* @returns {string[] | null} Sanitized assignees or null if none remain
425-
*/
426-
function sanitizeFallbackAssignees(assignees) {
427-
if (!assignees || assignees.length === 0) {
428-
return null;
429-
}
430-
const sanitized = assignees
431-
.filter(a => typeof a === "string")
432-
.map(a => a.trim())
433-
.filter(a => a.length > 0 && a.toLowerCase() !== "copilot");
434-
435-
if (sanitized.length === 0) {
436-
return null;
437-
}
438-
439-
const limitResult = tryEnforceArrayLimit(sanitized, MAX_ASSIGNEES, "assignees");
440-
if (!limitResult.success) {
441-
core.warning(`Assignees limit exceeded for fallback issue: ${limitResult.error}. Using first ${MAX_ASSIGNEES}.`);
442-
return sanitized.slice(0, MAX_ASSIGNEES);
443-
}
444-
445-
return sanitized;
446-
}
311+
// NOTE: isLabelTransientError, LABEL_MAX_RETRIES, LABEL_INITIAL_DELAY_MS, LABEL_MAX_DELAY_MS,
312+
// parseAllowedBaseBranches, isBaseBranchAllowed, parseStringListConfig, mergeFallbackIssueLabels,
313+
// sanitizeFallbackAssignees, neutralizeClosingKeywordsForIssueBody, generatePatchPreview,
314+
// buildManifestProtectionCreatePrUrl, and renderManifestProtectionFallbackBody
315+
// are imported from create_pull_request_helpers.cjs above.
447316

448317
/**
449318
* Creates a fallback GitHub issue, retrying on rate-limit and other transient errors
@@ -494,61 +363,6 @@ async function createFallbackIssue(githubClient, repoParts, title, body, labels,
494363
);
495364
}
496365

497-
/**
498-
* Builds a compare URL used in protected-files fallback issue bodies.
499-
* Optionally appends a prefilled PR body that closes the fallback issue.
500-
* @param {string} githubServer
501-
* @param {{owner: string, repo: string}} repoParts
502-
* @param {string} baseBranch
503-
* @param {string} branchName
504-
* @param {string} title
505-
* @param {number} [fallbackIssueNumber]
506-
* @returns {string}
507-
*/
508-
function buildManifestProtectionCreatePrUrl(githubServer, repoParts, baseBranch, branchName, title, fallbackIssueNumber) {
509-
const encodedBase = encodePathSegments(baseBranch);
510-
const encodedHead = encodePathSegments(branchName);
511-
let createPrUrl = `${githubServer}/${repoParts.owner}/${repoParts.repo}/compare/${encodedBase}...${encodedHead}?expand=1&title=${encodeURIComponent(title)}`;
512-
if (typeof fallbackIssueNumber === "number") {
513-
createPrUrl += `&body=${encodeURIComponent(`Closes #${fallbackIssueNumber}`)}`;
514-
}
515-
return createPrUrl;
516-
}
517-
518-
/**
519-
* Renders protected-files fallback issue body with a prefilled compare URL.
520-
* @param {string} mainBodyContent
521-
* @param {string} footerContent
522-
* @param {string} fileList
523-
* @param {string} createPrUrl
524-
* @returns {string}
525-
*/
526-
function renderManifestProtectionFallbackBody(mainBodyContent, footerContent, fileList, createPrUrl) {
527-
const templatePath = getPromptPath("manifest_protection_create_pr_fallback.md");
528-
return renderTemplateFromFile(templatePath, {
529-
main_body: mainBodyContent,
530-
footer: footerContent,
531-
files: fileList,
532-
create_pr_url: createPrUrl,
533-
});
534-
}
535-
536-
/**
537-
* Neutralizes issue-closing keywords in body text to avoid unintended cross-issue closure
538-
* when PR content is reused in fallback issue bodies.
539-
*
540-
* Example: "Closes #123" -> "Closes \\#123"
541-
*
542-
* @param {string} content
543-
* @returns {string}
544-
*/
545-
function neutralizeClosingKeywordsForIssueBody(content) {
546-
if (!content) {
547-
return content;
548-
}
549-
return String(content).replace(/\b(fix|fixes|fixed|close|closes|closed|resolve|resolves|resolved)\s+((?:[a-z0-9_.-]+\/[a-z0-9_.-]+)?#\d+)\b/gi, (_match, keyword, issueRef) => `${keyword} ${String(issueRef).replace("#", "\\#")}`);
550-
}
551-
552366
/**
553367
* Maximum limits for pull request parameters to prevent resource exhaustion.
554368
* These limits align with GitHub's API constraints and security best practices.
@@ -632,35 +446,7 @@ function enforcePullRequestLimits(patchContent, maxFiles = MAX_FILES) {
632446
}
633447
}
634448

635-
/**
636-
* Generate a patch preview with max 500 lines and 2000 chars for issue body
637-
* @param {string} patchContent - The full patch content
638-
* @returns {string} Formatted patch preview
639-
*/
640-
function generatePatchPreview(patchContent) {
641-
if (!patchContent || !patchContent.trim()) {
642-
return "";
643-
}
644-
645-
const lines = patchContent.split("\n");
646-
const maxLines = 500;
647-
const maxChars = 2000;
648-
649-
// Apply line limit first
650-
let preview = lines.length <= maxLines ? patchContent : lines.slice(0, maxLines).join("\n");
651-
const lineTruncated = lines.length > maxLines;
652-
653-
// Apply character limit
654-
const charTruncated = preview.length > maxChars;
655-
if (charTruncated) {
656-
preview = preview.slice(0, maxChars);
657-
}
658-
659-
const truncated = lineTruncated || charTruncated;
660-
const summary = truncated ? `Show patch preview (${Math.min(maxLines, lines.length)} of ${lines.length} lines)` : `Show patch (${lines.length} lines)`;
661-
662-
return `\n\n<details><summary>${summary}</summary>\n\n\`\`\`diff\n${preview}${truncated ? "\n... (truncated)" : ""}\n\`\`\`\n\n</details>`;
663-
}
449+
// NOTE: generatePatchPreview is imported from create_pull_request_helpers.cjs above.
664450

665451
/**
666452
* Check whether the remote branch already exists and, if so, either reuse it
@@ -2303,10 +2089,11 @@ ${patchPreview}`;
23032089
// Return success with PR details
23042090
return {
23052091
success: true,
2306-
pull_request_number: pullRequest.number,
2307-
pull_request_url: pullRequest.html_url,
2092+
number: pullRequest.number,
2093+
url: pullRequest.html_url,
2094+
managedBody: body,
23082095
branch_name: branchName,
2309-
temporary_id: temporaryId,
2096+
temporaryId: temporaryId,
23102097
repo: itemRepo,
23112098
};
23122099
} catch (prError) {

0 commit comments

Comments
 (0)