diff --git a/actions/setup/js/generate_safe_outputs_tools.cjs b/actions/setup/js/generate_safe_outputs_tools.cjs index fae7e0c0225..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,6 +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 = parseRuntimeFeatures(process.env.GH_AW_RUNTIME_FEATURES); // Filter predefined tools to those enabled in config and apply enhancements const filteredTools = allTools @@ -130,6 +134,9 @@ async function main() { if (descSuffix) { enhancedTool.description = (enhancedTool.description || "") + descSuffix; } + 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") { 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..dcc59e97d1c 100644 --- a/actions/setup/js/generate_safe_outputs_tools.test.cjs +++ b/actions/setup/js/generate_safe_outputs_tools.test.cjs @@ -306,4 +306,48 @@ 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\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."; + 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); + }); + + 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); + }); });