Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions actions/setup/js/safe_output_helpers.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -228,8 +228,38 @@ function resolveTarget(params) {
};
}

/**
* Load custom safe output job types from environment variable
* These are job names defined in safe-outputs.jobs that are processed by custom jobs
* @returns {Set<string>} Set of custom safe output job type names
*/
function loadCustomSafeOutputJobTypes() {
const safeOutputJobsEnv = process.env.GH_AW_SAFE_OUTPUT_JOBS;
if (!safeOutputJobsEnv) {
return new Set();
}

try {
const safeOutputJobs = JSON.parse(safeOutputJobsEnv);
// The environment variable is a map of job names to output keys
// We need the job names (keys) as the message types to ignore
const jobTypes = Object.keys(safeOutputJobs);
if (typeof core !== "undefined") {
core.debug(`Loaded ${jobTypes.length} custom safe output job type(s): ${jobTypes.join(", ")}`);
}
return new Set(jobTypes);
} catch (error) {
if (typeof core !== "undefined") {
const { getErrorMessage } = require("./error_helpers.cjs");
core.warning(`Failed to parse GH_AW_SAFE_OUTPUT_JOBS: ${getErrorMessage(error)}`);
}
return new Set();
}
}

module.exports = {
parseAllowedItems,
parseMaxCount,
resolveTarget,
loadCustomSafeOutputJobTypes,
};
49 changes: 49 additions & 0 deletions actions/setup/js/safe_output_helpers.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -453,4 +453,53 @@ describe("safe_output_helpers", () => {
});
});
});

describe("loadCustomSafeOutputJobTypes", () => {
beforeEach(() => {
// Clean up environment variables
delete process.env.GH_AW_SAFE_OUTPUT_JOBS;
});

it("should return empty set when GH_AW_SAFE_OUTPUT_JOBS is not set", () => {
const result = helpers.loadCustomSafeOutputJobTypes();

expect(result).toBeInstanceOf(Set);
expect(result.size).toBe(0);
});

it("should parse and return custom job types from GH_AW_SAFE_OUTPUT_JOBS", () => {
process.env.GH_AW_SAFE_OUTPUT_JOBS = JSON.stringify({
notion_add_comment: "comment_url",
slack_post_message: "message_url",
custom_job: "output_url",
});

const result = helpers.loadCustomSafeOutputJobTypes();

expect(result).toBeInstanceOf(Set);
expect(result.size).toBe(3);
expect(result.has("notion_add_comment")).toBe(true);
expect(result.has("slack_post_message")).toBe(true);
expect(result.has("custom_job")).toBe(true);
});

it("should return empty set when GH_AW_SAFE_OUTPUT_JOBS is invalid JSON", () => {
process.env.GH_AW_SAFE_OUTPUT_JOBS = "invalid json";

const result = helpers.loadCustomSafeOutputJobTypes();

expect(result).toBeInstanceOf(Set);
expect(result.size).toBe(0);
// Note: Warning is logged but we don't test for it since core is not mocked in this test file
});

it("should handle empty object in GH_AW_SAFE_OUTPUT_JOBS", () => {
process.env.GH_AW_SAFE_OUTPUT_JOBS = JSON.stringify({});

const result = helpers.loadCustomSafeOutputJobTypes();

expect(result).toBeInstanceOf(Set);
expect(result.size).toBe(0);
});
});
});
11 changes: 11 additions & 0 deletions actions/setup/js/safe_output_project_handler_manager.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const { loadAgentOutput } = require("./load_agent_output.cjs");
const { getErrorMessage } = require("./error_helpers.cjs");
const { writeSafeOutputSummaries } = require("./safe_output_summary.cjs");
const { loadTemporaryIdMap } = require("./temporary_id.cjs");
const { loadCustomSafeOutputJobTypes } = require("./safe_output_helpers.cjs");

/**
* Handler map configuration for project-related safe outputs
Expand Down Expand Up @@ -122,6 +123,9 @@ async function processMessages(messageHandlers, messages) {
core.info(`Loaded temporary ID map with ${temporaryIdMap.size} entry(ies)`);
}

// Load custom safe output job types that are processed by dedicated custom jobs
const customSafeOutputJobTypes = loadCustomSafeOutputJobTypes();

core.info(`Processing ${messages.length} project-related message(s)...`);

// Process messages in order of appearance
Expand All @@ -137,6 +141,13 @@ async function processMessages(messageHandlers, messages) {
const messageHandler = messageHandlers.get(messageType);

if (!messageHandler) {
// Check if this message type is a custom safe output job
if (customSafeOutputJobTypes.has(messageType)) {
// Silently skip - this is handled by a custom safe output job
core.debug(`Message ${i + 1} (${messageType}) will be handled by custom safe output job`);
continue;
}

// Skip messages that are not project-related
// These should be handled by other steps (main handler manager or standalone steps)
core.debug(`Message ${i + 1} (${messageType}) is not a project-related type - skipping`);
Expand Down
18 changes: 18 additions & 0 deletions actions/setup/js/safe_output_unified_handler_manager.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const { setCollectedMissings } = require("./missing_messages_helper.cjs");
const { writeSafeOutputSummaries } = require("./safe_output_summary.cjs");
const { getIssuesToAssignCopilot } = require("./create_issue.cjs");
const { getCampaignLabelsFromEnv } = require("./campaign_labels.cjs");
const { loadCustomSafeOutputJobTypes } = require("./safe_output_helpers.cjs");

/**
* Merge labels with trimming + case-insensitive de-duplication.
Expand Down Expand Up @@ -363,6 +364,9 @@ async function processMessages(messageHandlers, messages, projectOctokit = null)
// Collect missing_tool and missing_data messages first
const missings = collectMissingMessages(messages);

// Load custom safe output job types that are processed by dedicated custom jobs
const customSafeOutputJobTypes = loadCustomSafeOutputJobTypes();

// Initialize unified temporary ID map
// This will be populated by handlers as they create entities with temporary IDs
// Stores both issue/PR references ({repo, number}) and project URLs ({projectUrl})
Expand Down Expand Up @@ -418,6 +422,20 @@ async function processMessages(messageHandlers, messages, projectOctokit = null)
continue;
}

// Check if this message type is a custom safe output job
if (customSafeOutputJobTypes.has(messageType)) {
// Silently skip - this is handled by a custom safe output job
core.debug(`Message ${i + 1} (${messageType}) will be handled by custom safe output job`);
results.push({
type: messageType,
messageIndex: i,
success: false,
skipped: true,
reason: "Handled by custom safe output job",
});
continue;
}

// Unknown message type - warn the user
core.warning(
`⚠️ No handler loaded for message type '${messageType}' (message ${i + 1}/${messages.length}). The message will be skipped. This may happen if the safe output type is not configured in the workflow's safe-outputs section.`
Expand Down
8 changes: 0 additions & 8 deletions docs/src/content/docs/agent-factory-status.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -82,15 +82,7 @@ These are experimental agentic workflows used by the GitHub Next team to learn,
| [Example: Custom Error Patterns](https://github.com/githubnext/gh-aw/blob/main/.github/workflows/example-custom-error-patterns.md) | copilot | [![Example: Custom Error Patterns](https://github.com/githubnext/gh-aw/actions/workflows/example-custom-error-patterns.lock.yml/badge.svg)](https://github.com/githubnext/gh-aw/actions/workflows/example-custom-error-patterns.lock.yml) | - | - |
| [Example: Properly Provisioned Permissions](https://github.com/githubnext/gh-aw/blob/main/.github/workflows/example-permissions-warning.md) | copilot | [![Example: Properly Provisioned Permissions](https://github.com/githubnext/gh-aw/actions/workflows/example-permissions-warning.lock.yml/badge.svg)](https://github.com/githubnext/gh-aw/actions/workflows/example-permissions-warning.lock.yml) | - | - |
| [Firewall Test Agent](https://github.com/githubnext/gh-aw/blob/main/.github/workflows/firewall.md) | copilot | [![Firewall Test Agent](https://github.com/githubnext/gh-aw/actions/workflows/firewall.lock.yml/badge.svg)](https://github.com/githubnext/gh-aw/actions/workflows/firewall.lock.yml) | - | - |
<<<<<<< HEAD
| [Functional Pragmatist](https://github.com/githubnext/gh-aw/blob/main/.github/workflows/functional-pragmatist.md) | copilot | [![Functional Pragmatist](https://github.com/githubnext/gh-aw/actions/workflows/functional-pragmatist.lock.yml/badge.svg)](https://github.com/githubnext/gh-aw/actions/workflows/functional-pragmatist.lock.yml) | `0 9 * * 2,4` | - |
=======
<<<<<<< HEAD
| [Functional Enhancer](https://github.com/githubnext/gh-aw/blob/main/.github/workflows/functional-enhancer.md) | claude | [![Functional Enhancer](https://github.com/githubnext/gh-aw/actions/workflows/functional-enhancer.lock.yml/badge.svg)](https://github.com/githubnext/gh-aw/actions/workflows/functional-enhancer.lock.yml) | `0 9 * * 2,4` | - |
=======
| [Functional Pragmatist](https://github.com/githubnext/gh-aw/blob/main/.github/workflows/functional-programming-enhancer.md) | claude | [![Functional Pragmatist](https://github.com/githubnext/gh-aw/actions/workflows/functional-programming-enhancer.lock.yml/badge.svg)](https://github.com/githubnext/gh-aw/actions/workflows/functional-programming-enhancer.lock.yml) | `0 9 * * 2,4` | - |
>>>>>>> ba904f257b69bfcc9738b01ca4c61995b23506a4
>>>>>>> origin/main
| [GitHub MCP Remote Server Tools Report Generator](https://github.com/githubnext/gh-aw/blob/main/.github/workflows/github-mcp-tools-report.md) | claude | [![GitHub MCP Remote Server Tools Report Generator](https://github.com/githubnext/gh-aw/actions/workflows/github-mcp-tools-report.lock.yml/badge.svg)](https://github.com/githubnext/gh-aw/actions/workflows/github-mcp-tools-report.lock.yml) | - | - |
| [GitHub MCP Structural Analysis](https://github.com/githubnext/gh-aw/blob/main/.github/workflows/github-mcp-structural-analysis.md) | claude | [![GitHub MCP Structural Analysis](https://github.com/githubnext/gh-aw/actions/workflows/github-mcp-structural-analysis.lock.yml/badge.svg)](https://github.com/githubnext/gh-aw/actions/workflows/github-mcp-structural-analysis.lock.yml) | `0 11 * * 1-5` | - |
| [GitHub Remote MCP Authentication Test](https://github.com/githubnext/gh-aw/blob/main/.github/workflows/github-remote-mcp-auth-test.md) | copilot | [![GitHub Remote MCP Authentication Test](https://github.com/githubnext/gh-aw/actions/workflows/github-remote-mcp-auth-test.lock.yml/badge.svg)](https://github.com/githubnext/gh-aw/actions/workflows/github-remote-mcp-auth-test.lock.yml) | - | - |
Expand Down
14 changes: 14 additions & 0 deletions docs/src/content/docs/reference/frontmatter-full.md
Original file line number Diff line number Diff line change
Expand Up @@ -2157,6 +2157,13 @@ safe-outputs:
# (optional)
github-token: "${{ secrets.GITHUB_TOKEN }}"

# Default project URL for update-project operations. When specified, safe output
# messages can omit the project field and will use this URL by default. Must be a
# valid GitHub Projects v2 URL. Overridden by explicit project field in safe
# output messages.
# (optional)
project: "example-value"

# Optional array of project views to create. Each view must have a name and
# layout. Views are created during project setup.
# (optional)
Expand Down Expand Up @@ -2349,6 +2356,13 @@ safe-outputs:
# (optional)
github-token: "${{ secrets.GITHUB_TOKEN }}"

# Default project URL for status update operations. When specified, safe output
# messages can omit the project field and will use this URL by default. Must be a
# valid GitHub Projects v2 URL. Overridden by explicit project field in safe
# output messages.
# (optional)
project: "example-value"

# Option 2: Enable project status updates with default configuration (max=1)
create-project-status-update: null

Expand Down
Loading