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
13 changes: 3 additions & 10 deletions actions/setup/js/messages.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,10 @@
* - ./messages_run_status.cjs - Run status messages (getRunStartedMessage, getRunSuccessMessage, getRunFailureMessage)
* - ./messages_close_discussion.cjs - Close discussion messages (getCloseOlderDiscussionMessage)
*
* Supported placeholders:
* - {workflow_name} - Name of the workflow
* - {run_url} - URL to the workflow run
* - {workflow_source} - Source specification (owner/repo/path@ref)
* - {workflow_source_url} - GitHub URL for the workflow source
* - {triggering_number} - Issue/PR/Discussion number that triggered this workflow
* - {operation} - Operation name (for staged mode titles/descriptions)
* - {event_type} - Event type description (for run-started messages)
* - {status} - Workflow status text (for run-failure messages)
*
* This module supports placeholder-based templates for messages.
* Both camelCase and snake_case placeholder formats are supported.
* For the authoritative and up-to-date list of supported placeholders,
* see the documentation in ./messages_core.cjs.
*/

// Re-export core utilities
Expand Down
36 changes: 32 additions & 4 deletions actions/setup/js/messages.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ describe("messages.cjs", () => {
runUrl: "https://github.com/test/repo/actions/runs/123",
});

expect(result).toBe("> Generated by [Test Workflow](https://github.com/test/repo/actions/runs/123)");
expect(result).toBe("> Generated by [Test Workflow](https://github.com/test/repo/actions/runs/123/agentic_workflow)");
});

it("should append triggering number when provided", async () => {
Expand All @@ -177,7 +177,7 @@ describe("messages.cjs", () => {
triggeringNumber: 42,
});

expect(result).toBe("> Generated by [Test Workflow](https://github.com/test/repo/actions/runs/123) for issue #42");
expect(result).toBe("> Generated by [Test Workflow](https://github.com/test/repo/actions/runs/123/agentic_workflow) for issue #42");
});

it("should use custom footer template", async () => {
Expand Down Expand Up @@ -252,7 +252,7 @@ describe("messages.cjs", () => {
historyUrl: "https://github.com/search?q=repo:test/repo+is:issue&type=issues",
});

expect(result).toBe("> Generated by [Test Workflow](https://github.com/test/repo/actions/runs/123) · [◷](https://github.com/search?q=repo:test/repo+is:issue&type=issues)");
expect(result).toBe("> Generated by [Test Workflow](https://github.com/test/repo/actions/runs/123/agentic_workflow) · [◷](https://github.com/search?q=repo:test/repo+is:issue&type=issues)");
});

it("should include both triggering number and history link when both are provided", async () => {
Expand All @@ -265,7 +265,7 @@ describe("messages.cjs", () => {
historyUrl: "https://github.com/search?q=repo:test/repo+is:issue&type=issues",
});

expect(result).toBe("> Generated by [Test Workflow](https://github.com/test/repo/actions/runs/123) for issue #42 · [◷](https://github.com/search?q=repo:test/repo+is:issue&type=issues)");
expect(result).toBe("> Generated by [Test Workflow](https://github.com/test/repo/actions/runs/123/agentic_workflow) for issue #42 · [◷](https://github.com/search?q=repo:test/repo+is:issue&type=issues)");
});

it("should not append history link when historyUrl is not provided", async () => {
Expand Down Expand Up @@ -312,6 +312,34 @@ describe("messages.cjs", () => {
expect(result).toBe("> 🤖 *Generated by [Test Workflow](https://github.com/test/repo/actions/runs/123)*");
expect(result).not.toContain("{history_link}");
});

it("should expose {agentic_workflow_url} placeholder in custom footer templates", async () => {
process.env.GH_AW_SAFE_OUTPUT_MESSAGES = JSON.stringify({
footer: "> Generated by [{workflow_name}]({agentic_workflow_url})",
});

const { getFooterMessage } = await import("./messages.cjs");

const result = getFooterMessage({
workflowName: "Test Workflow",
runUrl: "https://github.com/test/repo/actions/runs/123",
});

expect(result).toBe("> Generated by [Test Workflow](https://github.com/test/repo/actions/runs/123/agentic_workflow)");
});

it("should use explicit agenticWorkflowUrl from context instead of computing from runUrl", async () => {
const { getFooterMessage } = await import("./messages.cjs");

const result = getFooterMessage({
workflowName: "Test Workflow",
runUrl: "https://github.com/test/repo/actions/runs/123",
agenticWorkflowUrl: "https://github.com/test/repo/actions/runs/123/custom_path",
});

expect(result).toBe("> Generated by [Test Workflow](https://github.com/test/repo/actions/runs/123/custom_path)");
expect(result).not.toContain("/agentic_workflow");
});
});

describe("getFooterInstallMessage", () => {
Expand Down
1 change: 1 addition & 0 deletions actions/setup/js/messages_core.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
* Supported placeholders:
* - {workflow_name} - Name of the workflow
* - {run_url} - URL to the workflow run
* - {agentic_workflow_url} - Direct URL to the agentic workflow page ({run_url}/agentic_workflow)
* - {workflow_source} - Source specification (owner/repo/path@ref)
* - {workflow_source_url} - GitHub URL for the workflow source
* - {triggering_number} - Issue/PR/Discussion number that triggered this workflow
Expand Down
51 changes: 36 additions & 15 deletions actions/setup/js/messages_footer.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const { getDifcFilteredEvents, generateDifcFilteredSection } = require("./gatewa
* @typedef {Object} FooterContext
* @property {string} workflowName - Name of the workflow
* @property {string} runUrl - URL of the workflow run
* @property {string} [agenticWorkflowUrl] - Direct URL to the agentic workflow page ({run_url}/agentic_workflow)
* @property {string} [workflowSource] - Source of the workflow (owner/repo/path@ref)
* @property {string} [workflowSourceUrl] - GitHub URL for the workflow source
* @property {number|string} [triggeringNumber] - Issue, PR, or discussion number that triggered this workflow
Expand All @@ -35,16 +36,19 @@ function getFooterMessage(ctx) {
// Pre-compute history_link as a ready-to-use markdown suffix (empty string when unavailable)
const historyLink = ctx.historyUrl ? ` · [◷](${ctx.historyUrl})` : "";

// Create context with both camelCase and snake_case keys, including computed history_link
const templateContext = toSnakeCase({ ...ctx, historyLink });
// Pre-compute agentic_workflow_url as the direct link to the agentic workflow page
const agenticWorkflowUrl = ctx.agenticWorkflowUrl || (ctx.runUrl ? `${ctx.runUrl}/agentic_workflow` : "");

// Create context with both camelCase and snake_case keys, including computed history_link and agentic_workflow_url
const templateContext = toSnakeCase({ ...ctx, historyLink, agenticWorkflowUrl });

// Use custom footer template if configured (no automatic suffix appended)
if (messages?.footer) {
return renderTemplate(messages.footer, templateContext);
}

// Default footer template - includes triggering reference if available
let defaultFooter = "> Generated by [{workflow_name}]({run_url})";
let defaultFooter = "> Generated by [{workflow_name}]({agentic_workflow_url})";
if (ctx.triggeringNumber) {
defaultFooter += " for issue #{triggering_number}";
}
Expand All @@ -67,8 +71,11 @@ function getFooterInstallMessage(ctx) {

const messages = getMessages();

// Create context with both camelCase and snake_case keys
const templateContext = toSnakeCase(ctx);
// Pre-compute agentic_workflow_url as the direct link to the agentic workflow page
const agenticWorkflowUrl = ctx.agenticWorkflowUrl || (ctx.runUrl ? `${ctx.runUrl}/agentic_workflow` : "");

// Create context with both camelCase and snake_case keys, including computed agentic_workflow_url
const templateContext = toSnakeCase({ ...ctx, agenticWorkflowUrl });

// Default installation template
const defaultInstall = "> To install this [agentic workflow]({workflow_source_url}), run\n> ```\n> gh aw add {workflow_source}\n> ```";
Expand All @@ -81,6 +88,7 @@ function getFooterInstallMessage(ctx) {
* @typedef {Object} WorkflowRecompileContext
* @property {string} workflowName - Name of the workflow
* @property {string} runUrl - URL of the workflow run
* @property {string} [agenticWorkflowUrl] - Direct URL to the agentic workflow page ({run_url}/agentic_workflow)
* @property {string} repository - Repository name (owner/repo)
*/

Expand All @@ -92,11 +100,14 @@ function getFooterInstallMessage(ctx) {
function getFooterWorkflowRecompileMessage(ctx) {
const messages = getMessages();

// Pre-compute agentic_workflow_url as the direct link to the agentic workflow page
const agenticWorkflowUrl = ctx.agenticWorkflowUrl || (ctx.runUrl ? `${ctx.runUrl}/agentic_workflow` : "");

// Create context with both camelCase and snake_case keys
const templateContext = toSnakeCase(ctx);
const templateContext = toSnakeCase({ ...ctx, agenticWorkflowUrl });

// Default footer template
const defaultFooter = "> Generated by [{workflow_name}]({run_url})";
const defaultFooter = "> Generated by [{workflow_name}]({agentic_workflow_url})";

// Use custom workflow recompile footer if configured, otherwise use default footer
return messages?.footerWorkflowRecompile ? renderTemplate(messages.footerWorkflowRecompile, templateContext) : renderTemplate(defaultFooter, templateContext);
Expand All @@ -110,11 +121,14 @@ function getFooterWorkflowRecompileMessage(ctx) {
function getFooterWorkflowRecompileCommentMessage(ctx) {
const messages = getMessages();

// Pre-compute agentic_workflow_url as the direct link to the agentic workflow page
const agenticWorkflowUrl = ctx.agenticWorkflowUrl || (ctx.runUrl ? `${ctx.runUrl}/agentic_workflow` : "");

// Create context with both camelCase and snake_case keys
const templateContext = toSnakeCase(ctx);
const templateContext = toSnakeCase({ ...ctx, agenticWorkflowUrl });

// Default footer template
const defaultFooter = "> Updated by [{workflow_name}]({run_url})";
const defaultFooter = "> Updated by [{workflow_name}]({agentic_workflow_url})";

// Use custom workflow recompile comment footer if configured, otherwise use default footer
return messages?.footerWorkflowRecompileComment ? renderTemplate(messages.footerWorkflowRecompileComment, templateContext) : renderTemplate(defaultFooter, templateContext);
Expand All @@ -124,6 +138,7 @@ function getFooterWorkflowRecompileCommentMessage(ctx) {
* @typedef {Object} AgentFailureContext
* @property {string} workflowName - Name of the workflow
* @property {string} runUrl - URL of the workflow run
* @property {string} [agenticWorkflowUrl] - Direct URL to the agentic workflow page ({run_url}/agentic_workflow)
* @property {string} [workflowSource] - Source of the workflow (owner/repo/path@ref)
* @property {string} [workflowSourceUrl] - GitHub URL for the workflow source
* @property {string} [historyUrl] - GitHub search URL for issues created by this workflow (for the history link)
Expand All @@ -140,16 +155,19 @@ function getFooterAgentFailureIssueMessage(ctx) {
// Pre-compute history_link as a ready-to-use markdown suffix (empty string when unavailable)
const historyLink = ctx.historyUrl ? ` · [◷](${ctx.historyUrl})` : "";

// Create context with both camelCase and snake_case keys, including computed history_link
const templateContext = toSnakeCase({ ...ctx, historyLink });
// Pre-compute agentic_workflow_url as the direct link to the agentic workflow page
const agenticWorkflowUrl = ctx.agenticWorkflowUrl || (ctx.runUrl ? `${ctx.runUrl}/agentic_workflow` : "");

// Create context with both camelCase and snake_case keys, including computed history_link and agentic_workflow_url
const templateContext = toSnakeCase({ ...ctx, historyLink, agenticWorkflowUrl });

// Use custom agent failure issue footer if configured, otherwise use default footer
if (messages?.agentFailureIssue) {
return renderTemplate(messages.agentFailureIssue, templateContext);
}

// Default footer template with link to workflow run
let defaultFooter = "> Generated from [{workflow_name}]({run_url})";
let defaultFooter = "> Generated from [{workflow_name}]({agentic_workflow_url})";
// Append history link when available
if (ctx.historyUrl) {
defaultFooter += " · [◷]({history_url})";
Expand All @@ -168,16 +186,19 @@ function getFooterAgentFailureCommentMessage(ctx) {
// Pre-compute history_link as a ready-to-use markdown suffix (empty string when unavailable)
const historyLink = ctx.historyUrl ? ` · [◷](${ctx.historyUrl})` : "";

// Create context with both camelCase and snake_case keys, including computed history_link
const templateContext = toSnakeCase({ ...ctx, historyLink });
// Pre-compute agentic_workflow_url as the direct link to the agentic workflow page
const agenticWorkflowUrl = ctx.agenticWorkflowUrl || (ctx.runUrl ? `${ctx.runUrl}/agentic_workflow` : "");

// Create context with both camelCase and snake_case keys, including computed history_link and agentic_workflow_url
const templateContext = toSnakeCase({ ...ctx, historyLink, agenticWorkflowUrl });

// Use custom agent failure comment footer if configured, otherwise use default footer
if (messages?.agentFailureComment) {
return renderTemplate(messages.agentFailureComment, templateContext);
}

// Default footer template with link to workflow run
let defaultFooter = "> Generated from [{workflow_name}]({run_url})";
let defaultFooter = "> Generated from [{workflow_name}]({agentic_workflow_url})";
// Append history link when available
if (ctx.historyUrl) {
defaultFooter += " · [◷]({history_url})";
Expand Down
2 changes: 1 addition & 1 deletion pkg/workflow/cache_memory_threat_detection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ Test workflow with cache-memory and threat detection enabled.`,
// Should have update_cache_memory job (depends on detection job)
"update_cache_memory:",
"- detection",
"if: always() && needs.detection.result == 'success'",
"if: always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped')",
"- name: Download cache-memory artifact (default)",
"- name: Save cache-memory to cache (default)",
"uses: actions/cache/save@",
Expand Down
Loading