From 98e1054487e6d49cabef1475b718833174fa2eaa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 1 Jul 2026 15:16:28 +0000 Subject: [PATCH 1/4] Initial plan From 7dc20fee1cc0888e72f4b7cef89fbec1d1317bd3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 1 Jul 2026 15:26:55 +0000 Subject: [PATCH 2/4] Add issue_intents description suffix for issue tools Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .../setup/js/generate_safe_outputs_tools.cjs | 4 ++++ .../js/generate_safe_outputs_tools.test.cjs | 23 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/actions/setup/js/generate_safe_outputs_tools.cjs b/actions/setup/js/generate_safe_outputs_tools.cjs index fae7e0c0225..f67c3399da0 100644 --- a/actions/setup/js/generate_safe_outputs_tools.cjs +++ b/actions/setup/js/generate_safe_outputs_tools.cjs @@ -130,6 +130,10 @@ async function main() { if (descSuffix) { enhancedTool.description = (enhancedTool.description || "") + descSuffix; } + const runtimeFeatures = (process.env.GH_AW_RUNTIME_FEATURES || "").split(","); + if (runtimeFeatures.includes("issue_intents") && ["set_issue_type", "set_issue_field", "add_labels"].includes(tool.name)) { + enhancedTool.description += " INTENT: Include rationale (max 280 chars) and confidence (LOW/MEDIUM/HIGH) with each call."; + } if (tool.name === "add_comment") { enhancedTool.description = updateAddCommentDescription(enhancedTool.description, config.add_comment); diff --git a/actions/setup/js/generate_safe_outputs_tools.test.cjs b/actions/setup/js/generate_safe_outputs_tools.test.cjs index 00bb22cf856..caf855ccae9 100644 --- a/actions/setup/js/generate_safe_outputs_tools.test.cjs +++ b/actions/setup/js/generate_safe_outputs_tools.test.cjs @@ -306,4 +306,27 @@ describe("generate_safe_outputs_tools", () => { expect(addCommentTool.description).toContain("Discussion comments are disabled for this workflow"); expect(addCommentTool.description).not.toContain("Supports reply_to_id for discussion threading."); }); + + it("adds issue intent suffix for issue tools when issue_intents runtime feature is enabled", () => { + fs.writeFileSync( + toolsSourcePath, + JSON.stringify([ + { name: "set_issue_type", description: "Sets issue type.", inputSchema: { type: "object", properties: {} } }, + { name: "set_issue_field", description: "Sets issue field.", inputSchema: { type: "object", properties: {} } }, + { name: "add_labels", description: "Adds labels.", inputSchema: { type: "object", properties: {} } }, + { name: "create_issue", description: "Creates a GitHub issue.", inputSchema: { type: "object", properties: {} } }, + ]) + ); + fs.writeFileSync(configPath, JSON.stringify({ set_issue_type: {}, set_issue_field: {}, add_labels: {}, create_issue: {} })); + fs.writeFileSync(toolsMetaPath, JSON.stringify({ description_suffixes: {}, repo_params: {}, dynamic_tools: [] })); + + runScript({ GH_AW_RUNTIME_FEATURES: "other,issue_intents,another" }); + + const result = JSON.parse(fs.readFileSync(outputPath, "utf8")); + const intentSuffix = "INTENT: Include rationale (max 280 chars) and confidence (LOW/MEDIUM/HIGH) with each call."; + expect(result.find((/** @type {{name: string, description: string}} */ t) => t.name === "set_issue_type").description).toContain(intentSuffix); + expect(result.find((/** @type {{name: string, description: string}} */ t) => t.name === "set_issue_field").description).toContain(intentSuffix); + expect(result.find((/** @type {{name: string, description: string}} */ t) => t.name === "add_labels").description).toContain(intentSuffix); + expect(result.find((/** @type {{name: string, description: string}} */ t) => t.name === "create_issue").description).not.toContain(intentSuffix); + }); }); From 5fd6983d949ec3a7ef92f044193111421b547465 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 1 Jul 2026 15:29:35 +0000 Subject: [PATCH 3/4] Refactor runtime feature parsing for tool descriptions Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/generate_safe_outputs_tools.cjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actions/setup/js/generate_safe_outputs_tools.cjs b/actions/setup/js/generate_safe_outputs_tools.cjs index f67c3399da0..cdc862c8150 100644 --- a/actions/setup/js/generate_safe_outputs_tools.cjs +++ b/actions/setup/js/generate_safe_outputs_tools.cjs @@ -117,6 +117,7 @@ async function main() { // This filters out non-tool config entries like dispatch_workflow, call_workflow, // mentions, max_bot_mentions, etc. const enabledToolNames = new Set(Object.keys(config).filter(k => sourceToolNames.has(k))); + const runtimeFeatures = (process.env.GH_AW_RUNTIME_FEATURES || "").split(","); // Filter predefined tools to those enabled in config and apply enhancements const filteredTools = allTools @@ -130,7 +131,6 @@ async function main() { if (descSuffix) { enhancedTool.description = (enhancedTool.description || "") + descSuffix; } - const runtimeFeatures = (process.env.GH_AW_RUNTIME_FEATURES || "").split(","); if (runtimeFeatures.includes("issue_intents") && ["set_issue_type", "set_issue_field", "add_labels"].includes(tool.name)) { enhancedTool.description += " INTENT: Include rationale (max 280 chars) and confidence (LOW/MEDIUM/HIGH) with each call."; } From 12050f698aea2fccbcb970aa9887b757fb757109 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 1 Jul 2026 16:48:17 +0000 Subject: [PATCH 4/4] Use shared runtime feature parsing in safe-output generator Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- .../setup/js/generate_safe_outputs_tools.cjs | 9 +++++--- .../js/generate_safe_outputs_tools.test.cjs | 23 ++++++++++++++++++- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/actions/setup/js/generate_safe_outputs_tools.cjs b/actions/setup/js/generate_safe_outputs_tools.cjs index cdc862c8150..9c90f7f2c4d 100644 --- a/actions/setup/js/generate_safe_outputs_tools.cjs +++ b/actions/setup/js/generate_safe_outputs_tools.cjs @@ -25,11 +25,14 @@ * Default: ${RUNNER_TEMP}/gh-aw/safeoutputs/tools_meta.json * GH_AW_SAFE_OUTPUTS_TOOLS_PATH - Output path for the generated tools.json * Default: ${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json + * GH_AW_RUNTIME_FEATURES - Newline-delimited runtime features in key or key=value format + * Parsed using runtime_features.cjs helpers */ const fs = require("fs"); const path = require("path"); const { ERR_CONFIG } = require("./error_codes.cjs"); +const { parseRuntimeFeatures, hasRuntimeFeature } = require("./runtime_features.cjs"); const ADD_COMMENT_DEFAULT_DISCUSSIONS_NOTE = "NOTE: By default, this tool does not require discussions:write permission. Set 'discussions: true' in the workflow's safe-outputs.add-comment configuration to enable discussion comments and request this permission."; @@ -117,7 +120,7 @@ async function main() { // This filters out non-tool config entries like dispatch_workflow, call_workflow, // mentions, max_bot_mentions, etc. const enabledToolNames = new Set(Object.keys(config).filter(k => sourceToolNames.has(k))); - const runtimeFeatures = (process.env.GH_AW_RUNTIME_FEATURES || "").split(","); + const runtimeFeatures = parseRuntimeFeatures(process.env.GH_AW_RUNTIME_FEATURES); // Filter predefined tools to those enabled in config and apply enhancements const filteredTools = allTools @@ -131,8 +134,8 @@ async function main() { if (descSuffix) { enhancedTool.description = (enhancedTool.description || "") + descSuffix; } - if (runtimeFeatures.includes("issue_intents") && ["set_issue_type", "set_issue_field", "add_labels"].includes(tool.name)) { - enhancedTool.description += " INTENT: Include rationale (max 280 chars) and confidence (LOW/MEDIUM/HIGH) with each call."; + if (hasRuntimeFeature(runtimeFeatures, "issue_intents") && ["set_issue_type", "set_issue_field", "add_labels"].includes(tool.name)) { + enhancedTool.description = `${enhancedTool.description || ""} INTENT: Include rationale (max 280 chars) and confidence (LOW/MEDIUM/HIGH) with each call.`.trim(); } if (tool.name === "add_comment") { diff --git a/actions/setup/js/generate_safe_outputs_tools.test.cjs b/actions/setup/js/generate_safe_outputs_tools.test.cjs index caf855ccae9..dcc59e97d1c 100644 --- a/actions/setup/js/generate_safe_outputs_tools.test.cjs +++ b/actions/setup/js/generate_safe_outputs_tools.test.cjs @@ -320,7 +320,7 @@ describe("generate_safe_outputs_tools", () => { fs.writeFileSync(configPath, JSON.stringify({ set_issue_type: {}, set_issue_field: {}, add_labels: {}, create_issue: {} })); fs.writeFileSync(toolsMetaPath, JSON.stringify({ description_suffixes: {}, repo_params: {}, dynamic_tools: [] })); - runScript({ GH_AW_RUNTIME_FEATURES: "other,issue_intents,another" }); + runScript({ GH_AW_RUNTIME_FEATURES: "other\nissue_intents\nanother=true" }); const result = JSON.parse(fs.readFileSync(outputPath, "utf8")); const intentSuffix = "INTENT: Include rationale (max 280 chars) and confidence (LOW/MEDIUM/HIGH) with each call."; @@ -329,4 +329,25 @@ describe("generate_safe_outputs_tools", () => { expect(result.find((/** @type {{name: string, description: string}} */ t) => t.name === "add_labels").description).toContain(intentSuffix); expect(result.find((/** @type {{name: string, description: string}} */ t) => t.name === "create_issue").description).not.toContain(intentSuffix); }); + + it("does not add issue intent suffix when issue_intents runtime feature is not enabled", () => { + fs.writeFileSync( + toolsSourcePath, + JSON.stringify([ + { name: "set_issue_type", description: "Sets issue type.", inputSchema: { type: "object", properties: {} } }, + { name: "set_issue_field", description: "Sets issue field.", inputSchema: { type: "object", properties: {} } }, + { name: "add_labels", description: "Adds labels.", inputSchema: { type: "object", properties: {} } }, + ]) + ); + fs.writeFileSync(configPath, JSON.stringify({ set_issue_type: {}, set_issue_field: {}, add_labels: {} })); + fs.writeFileSync(toolsMetaPath, JSON.stringify({ description_suffixes: {}, repo_params: {}, dynamic_tools: [] })); + + runScript({ GH_AW_RUNTIME_FEATURES: "other\nanother=true" }); + + const result = JSON.parse(fs.readFileSync(outputPath, "utf8")); + const intentSuffix = "INTENT: Include rationale (max 280 chars) and confidence (LOW/MEDIUM/HIGH) with each call."; + expect(result.find((/** @type {{name: string, description: string}} */ t) => t.name === "set_issue_type").description).not.toContain(intentSuffix); + expect(result.find((/** @type {{name: string, description: string}} */ t) => t.name === "set_issue_field").description).not.toContain(intentSuffix); + expect(result.find((/** @type {{name: string, description: string}} */ t) => t.name === "add_labels").description).not.toContain(intentSuffix); + }); });