From 2955b09c9552b60d01e09ea2422e4cfc810c166e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 23 Jan 2026 05:54:50 +0000 Subject: [PATCH 1/3] Initial plan From 4acb56cfb347fd88b335ff88d35bb69ed5b7f054 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 23 Jan 2026 06:08:08 +0000 Subject: [PATCH 2/3] Add step summary generation for safe-outputs - Created safe_output_summary.cjs helper with generateSafeOutputSummary and writeSafeOutputSummaries functions - Updated safe_output_handler_manager.cjs to generate step summaries after processing messages - Updated safe_output_project_handler_manager.cjs to generate step summaries after processing messages - Each safe-output result is wrapped in a
section for collapsibility - Added comprehensive tests for the new functionality - All JavaScript tests pass (3021 passed) Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .../setup/js/safe_output_handler_manager.cjs | 4 + .../safe_output_project_handler_manager.cjs | 4 + actions/setup/js/safe_output_summary.cjs | 131 +++++++++++ actions/setup/js/safe_output_summary.test.cjs | 211 ++++++++++++++++++ 4 files changed, 350 insertions(+) create mode 100644 actions/setup/js/safe_output_summary.cjs create mode 100644 actions/setup/js/safe_output_summary.test.cjs diff --git a/actions/setup/js/safe_output_handler_manager.cjs b/actions/setup/js/safe_output_handler_manager.cjs index a58b1c87a1a..5838c4e241a 100644 --- a/actions/setup/js/safe_output_handler_manager.cjs +++ b/actions/setup/js/safe_output_handler_manager.cjs @@ -14,6 +14,7 @@ const { getErrorMessage } = require("./error_helpers.cjs"); const { hasUnresolvedTemporaryIds, replaceTemporaryIdReferences, normalizeTemporaryId } = require("./temporary_id.cjs"); const { generateMissingInfoSections } = require("./missing_info_formatter.cjs"); const { setCollectedMissings } = require("./missing_messages_helper.cjs"); +const { writeSafeOutputSummaries } = require("./safe_output_summary.cjs"); const DEFAULT_AGENTIC_CAMPAIGN_LABEL = "agentic-campaign"; @@ -835,6 +836,9 @@ async function main() { syntheticUpdateCount = await processSyntheticUpdates(github, context, processingResult.outputsWithUnresolvedIds, temporaryIdMap); } + // Write step summaries for all processed safe-outputs + await writeSafeOutputSummaries(processingResult.results, agentOutput.items); + // Log summary const successCount = processingResult.results.filter(r => r.success).length; const failureCount = processingResult.results.filter(r => !r.success && !r.deferred && !r.skipped).length; diff --git a/actions/setup/js/safe_output_project_handler_manager.cjs b/actions/setup/js/safe_output_project_handler_manager.cjs index d4b69d7b5ba..3f9bbe477ab 100644 --- a/actions/setup/js/safe_output_project_handler_manager.cjs +++ b/actions/setup/js/safe_output_project_handler_manager.cjs @@ -15,6 +15,7 @@ const { loadAgentOutput } = require("./load_agent_output.cjs"); const { getErrorMessage } = require("./error_helpers.cjs"); +const { writeSafeOutputSummaries } = require("./safe_output_summary.cjs"); /** * Handler map configuration for project-related safe outputs @@ -226,6 +227,9 @@ async function main() { // Process messages const { results, processedCount, temporaryProjectMap } = await processMessages(messageHandlers, messages); + // Write step summaries for all processed safe-outputs + await writeSafeOutputSummaries(results, messages); + // Set outputs core.setOutput("processed_count", processedCount); diff --git a/actions/setup/js/safe_output_summary.cjs b/actions/setup/js/safe_output_summary.cjs new file mode 100644 index 00000000000..a110f8c45aa --- /dev/null +++ b/actions/setup/js/safe_output_summary.cjs @@ -0,0 +1,131 @@ +// @ts-check +/// + +/** + * Safe Output Summary Generator + * + * This module provides functionality to generate step summaries for safe-output messages. + * Each processed safe-output generates a summary enclosed in a
section. + */ + +/** + * Generate a step summary for a single safe-output message + * @param {Object} options - Summary generation options + * @param {string} options.type - The safe-output type (e.g., "create_issue", "create_project") + * @param {number} options.messageIndex - The message index (1-based) + * @param {boolean} options.success - Whether the message was processed successfully + * @param {any} options.result - The result from the handler + * @param {any} options.message - The original message + * @param {string} [options.error] - Error message if processing failed + * @returns {string} - Markdown content for the step summary + */ +function generateSafeOutputSummary(options) { + const { type, messageIndex, success, result, message, error } = options; + + // Format the type for display (e.g., "create_issue" -> "Create Issue") + const displayType = type + .split("_") + .map(word => word.charAt(0).toUpperCase() + word.slice(1)) + .join(" "); + + // Choose emoji and status based on success + const emoji = success ? "✅" : "❌"; + const status = success ? "Success" : "Failed"; + + // Start building the summary + let summary = `
\n${emoji} ${displayType} - ${status} (Message ${messageIndex})\n\n`; + + // Add message details + summary += `### ${displayType}\n\n`; + + if (success && result) { + // Add result-specific information based on type + if (result.url) { + summary += `**URL:** ${result.url}\n\n`; + } + if (result.repo && result.number) { + summary += `**Location:** ${result.repo}#${result.number}\n\n`; + } + if (result.projectUrl) { + summary += `**Project URL:** ${result.projectUrl}\n\n`; + } + if (result.temporaryId) { + summary += `**Temporary ID:** \`${result.temporaryId}\`\n\n`; + } + + // Add original message details if available + if (message) { + if (message.title) { + summary += `**Title:** ${message.title}\n\n`; + } + if (message.body && typeof message.body === "string") { + // Truncate body if too long + const maxBodyLength = 500; + const bodyPreview = message.body.length > maxBodyLength ? message.body.substring(0, maxBodyLength) + "..." : message.body; + summary += `**Body Preview:**\n\`\`\`\n${bodyPreview}\n\`\`\`\n\n`; + } + if (message.labels && Array.isArray(message.labels)) { + summary += `**Labels:** ${message.labels.join(", ")}\n\n`; + } + } + } else if (error) { + // Show error information + summary += `**Error:** ${error}\n\n`; + + // Add original message details for debugging + if (message) { + summary += `**Message Details:**\n\`\`\`json\n${JSON.stringify(message, null, 2).substring(0, 1000)}\n\`\`\`\n\n`; + } + } + + summary += `
\n\n`; + + return summary; +} + +/** + * Write safe-output summaries to the GitHub Actions step summary + * @param {Array} results - Array of processing results + * @param {Array} messages - Array of original messages + * @returns {Promise} + */ +async function writeSafeOutputSummaries(results, messages) { + if (!results || results.length === 0) { + return; + } + + let summaryContent = `## Safe Output Processing Summary\n\n`; + summaryContent += `Processed ${results.length} safe-output message(s).\n\n`; + + // Generate summary for each result + for (const result of results) { + // Skip if this was handled by a standalone step + if (result.skipped) { + continue; + } + + // Get the original message + const message = messages[result.messageIndex]; + + summaryContent += generateSafeOutputSummary({ + type: result.type, + messageIndex: result.messageIndex + 1, // Convert to 1-based + success: result.success, + result: result.result, + message: message, + error: result.error, + }); + } + + try { + await core.summary.addRaw(summaryContent).write(); + core.info(`📝 Safe output summaries written to step summary`); + } catch (error) { + core.warning(`Failed to write safe output summaries: ${error instanceof Error ? error.message : String(error)}`); + } +} + +module.exports = { + generateSafeOutputSummary, + writeSafeOutputSummaries, +}; diff --git a/actions/setup/js/safe_output_summary.test.cjs b/actions/setup/js/safe_output_summary.test.cjs new file mode 100644 index 00000000000..fc37175b9d2 --- /dev/null +++ b/actions/setup/js/safe_output_summary.test.cjs @@ -0,0 +1,211 @@ +import { describe, it, expect, beforeEach, vi } from "vitest"; + +// Mock the global objects that GitHub Actions provides +const mockCore = { + info: vi.fn(), + warning: vi.fn(), + summary: { + addRaw: vi.fn().mockReturnThis(), + write: vi.fn().mockResolvedValue(undefined), + }, +}; + +// Set up global mocks before importing the module +globalThis.core = mockCore; + +const { generateSafeOutputSummary, writeSafeOutputSummaries } = await import("./safe_output_summary.cjs"); + +describe("safe_output_summary", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + describe("generateSafeOutputSummary", () => { + it("should generate summary for successful create_issue", () => { + const options = { + type: "create_issue", + messageIndex: 1, + success: true, + result: { + repo: "owner/repo", + number: 123, + url: "https://github.com/owner/repo/issues/123", + temporaryId: "issue-1", + }, + message: { + title: "Test Issue", + body: "This is a test issue body", + labels: ["bug", "enhancement"], + }, + }; + + const summary = generateSafeOutputSummary(options); + + expect(summary).toContain("
"); + expect(summary).toContain("
"); + expect(summary).toContain("✅"); + expect(summary).toContain("Create Issue"); + expect(summary).toContain("Message 1"); + expect(summary).toContain("owner/repo#123"); + expect(summary).toContain("https://github.com/owner/repo/issues/123"); + expect(summary).toContain("issue-1"); + expect(summary).toContain("Test Issue"); + expect(summary).toContain("bug, enhancement"); + }); + + it("should generate summary for failed message with error", () => { + const options = { + type: "create_project", + messageIndex: 2, + success: false, + result: null, + message: { + title: "Test Project", + }, + error: "Failed to create project: permission denied", + }; + + const summary = generateSafeOutputSummary(options); + + expect(summary).toContain("❌"); + expect(summary).toContain("Failed"); + expect(summary).toContain("Create Project"); + expect(summary).toContain("Message 2"); + expect(summary).toContain("permission denied"); + }); + + it("should truncate long body content", () => { + const longBody = "a".repeat(1000); + + const options = { + type: "create_discussion", + messageIndex: 3, + success: true, + result: { + repo: "owner/repo", + number: 456, + }, + message: { + title: "Test Discussion", + body: longBody, + }, + }; + + const summary = generateSafeOutputSummary(options); + + expect(summary).toContain("Body Preview"); + expect(summary).toContain("..."); + expect(summary.length).toBeLessThan(longBody.length + 1000); + }); + + it("should handle project-specific results", () => { + const options = { + type: "create_project", + messageIndex: 4, + success: true, + result: { + projectUrl: "https://github.com/orgs/owner/projects/123", + }, + message: { + title: "Test Project", + }, + }; + + const summary = generateSafeOutputSummary(options); + + expect(summary).toContain("Project URL"); + expect(summary).toContain("https://github.com/orgs/owner/projects/123"); + }); + }); + + describe("writeSafeOutputSummaries", () => { + it("should write summaries for multiple results", async () => { + const results = [ + { + type: "create_issue", + messageIndex: 0, + success: true, + result: { + repo: "owner/repo", + number: 123, + url: "https://github.com/owner/repo/issues/123", + }, + }, + { + type: "create_project", + messageIndex: 1, + success: true, + result: { + projectUrl: "https://github.com/orgs/owner/projects/456", + }, + }, + ]; + + const messages = [{ title: "Issue 1", body: "Body 1" }, { title: "Project 1" }]; + + await writeSafeOutputSummaries(results, messages); + + expect(mockCore.summary.addRaw).toHaveBeenCalledTimes(1); + expect(mockCore.summary.write).toHaveBeenCalledTimes(1); + expect(mockCore.info).toHaveBeenCalledWith("📝 Safe output summaries written to step summary"); + + const summaryContent = mockCore.summary.addRaw.mock.calls[0][0]; + expect(summaryContent).toContain("Safe Output Processing Summary"); + expect(summaryContent).toContain("Processed 2 safe-output message(s)"); + expect(summaryContent).toContain("Create Issue"); + expect(summaryContent).toContain("Create Project"); + }); + + it("should skip results handled by standalone steps", async () => { + const results = [ + { + type: "create_issue", + messageIndex: 0, + success: true, + result: { repo: "owner/repo", number: 123 }, + }, + { + type: "noop", + messageIndex: 1, + success: false, + skipped: true, + reason: "Handled by standalone step", + }, + ]; + + const messages = [{ title: "Issue 1" }, { message: "Noop message" }]; + + await writeSafeOutputSummaries(results, messages); + + const summaryContent = mockCore.summary.addRaw.mock.calls[0][0]; + expect(summaryContent).toContain("Create Issue"); + expect(summaryContent).not.toContain("Noop"); + }); + + it("should handle empty results", async () => { + await writeSafeOutputSummaries([], []); + + expect(mockCore.summary.addRaw).not.toHaveBeenCalled(); + expect(mockCore.summary.write).not.toHaveBeenCalled(); + }); + + it("should handle write failures gracefully", async () => { + mockCore.summary.write.mockRejectedValueOnce(new Error("Write failed")); + + const results = [ + { + type: "create_issue", + messageIndex: 0, + success: true, + result: { repo: "owner/repo", number: 123 }, + }, + ]; + + const messages = [{ title: "Issue 1" }]; + + await writeSafeOutputSummaries(results, messages); + + expect(mockCore.warning).toHaveBeenCalledWith("Failed to write safe output summaries: Write failed"); + }); + }); +}); From 76725454d471ecd36e4e88ba084736f150586cc0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 23 Jan 2026 06:28:01 +0000 Subject: [PATCH 3/3] Add changeset: safe-output step summaries [skip-ci] --- .changeset/patch-add-safe-output-summaries.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .changeset/patch-add-safe-output-summaries.md diff --git a/.changeset/patch-add-safe-output-summaries.md b/.changeset/patch-add-safe-output-summaries.md new file mode 100644 index 00000000000..52f2922e47d --- /dev/null +++ b/.changeset/patch-add-safe-output-summaries.md @@ -0,0 +1,12 @@ +--- +"gh-aw": patch +--- + +Add step summaries for safe-output processing results. + +Safe-output handlers now generate collapsible step summaries for each processed +message, providing visibility into what was created or updated during workflow +execution. Body previews are truncated at 500 characters to avoid bloat. The +feature is implemented for both regular safe-outputs and project-based +safe-outputs via a shared helper module. +