diff --git a/actions/setup/js/safe_output_type_validator.cjs b/actions/setup/js/safe_output_type_validator.cjs index 0196d83d4b5..fff02bf7694 100644 --- a/actions/setup/js/safe_output_type_validator.cjs +++ b/actions/setup/js/safe_output_type_validator.cjs @@ -78,6 +78,8 @@ function normalizeIssueClosingKeywordBackticks(content) { * @property {string} [itemType] - For arrays, the type of items * @property {boolean} [itemSanitize] - For arrays, whether to sanitize items * @property {number} [itemMaxLength] - For arrays, max length per item + * @property {number} [minimum] - For numbers, minimum allowed value (inclusive) + * @property {number} [maximum] - For numbers, maximum allowed value (inclusive) * @property {string} [pattern] - Regex pattern the value must match * @property {string} [patternError] - Error message for pattern mismatch */ @@ -468,6 +470,18 @@ function validateField(value, fieldName, validation, itemType, lineNum, options) error: `Line ${lineNum}: ${itemType} '${fieldName}' must be a number`, }; } + if (validation.minimum !== undefined && value < validation.minimum) { + return { + isValid: false, + error: `Line ${lineNum}: ${itemType} '${fieldName}' must be >= ${validation.minimum}`, + }; + } + if (validation.maximum !== undefined && value > validation.maximum) { + return { + isValid: false, + error: `Line ${lineNum}: ${itemType} '${fieldName}' must be <= ${validation.maximum}`, + }; + } return { isValid: true, normalizedValue: value }; } diff --git a/actions/setup/js/safe_output_type_validator.test.cjs b/actions/setup/js/safe_output_type_validator.test.cjs index 1b9dfb9abf1..4f9f899baa3 100644 --- a/actions/setup/js/safe_output_type_validator.test.cjs +++ b/actions/setup/js/safe_output_type_validator.test.cjs @@ -43,6 +43,7 @@ const SAMPLE_VALIDATION_CONFIG = { status: { type: "string", enum: ["open", "closed"] }, title: { type: "string", sanitize: true, maxLength: 128 }, body: { type: "string", sanitize: true, maxLength: 65000 }, + confidence: { type: "number", minimum: 0, maximum: 100 }, issue_number: { issueOrPRNumber: true }, }, }, @@ -705,6 +706,29 @@ describe("safe_output_type_validator", () => { expect(result.normalizedItem.status).toBe("open"); }); + describe("number range validation", () => { + it("should accept confidence within range", async () => { + const { validateItem } = await import("./safe_output_type_validator.cjs"); + const result = validateItem({ type: "update_issue", status: "open", confidence: 75 }, "update_issue", 1); + expect(result.isValid).toBe(true); + expect(result.normalizedItem.confidence).toBe(75); + }); + + it("should reject confidence below minimum", async () => { + const { validateItem } = await import("./safe_output_type_validator.cjs"); + const result = validateItem({ type: "update_issue", status: "open", confidence: -1 }, "update_issue", 1); + expect(result.isValid).toBe(false); + expect(result.error).toContain("must be >= 0"); + }); + + it("should reject confidence above maximum", async () => { + const { validateItem } = await import("./safe_output_type_validator.cjs"); + const result = validateItem({ type: "update_issue", status: "open", confidence: 101 }, "update_issue", 1); + expect(result.isValid).toBe(false); + expect(result.error).toContain("must be <= 100"); + }); + }); + it("should reject invalid enum value", async () => { const { validateItem } = await import("./safe_output_type_validator.cjs"); diff --git a/actions/setup/js/safe_outputs_tools.json b/actions/setup/js/safe_outputs_tools.json index 99f51f7eb91..dab602f2eae 100644 --- a/actions/setup/js/safe_outputs_tools.json +++ b/actions/setup/js/safe_outputs_tools.json @@ -759,7 +759,7 @@ }, { "name": "update_issue", - "description": "Update an existing GitHub issue's title, body, labels, assignees, or milestone WITHOUT closing it. This tool is primarily for editing issue metadata and content. While it supports changing status between 'open' and 'closed', use close_issue instead when you want to close an issue with a closing comment. Body updates support replacing, appending to, prepending content, or updating a per-run \"island\" section. IMPORTANT: The behavior of this tool depends on the workflow's `update-issue: target:` configuration. When `target: triggering` (the default), the tool always updates the issue that triggered the workflow and `issue_number` is ignored. When `target: '*'`, the `issue_number` field controls which issue is updated. The tool will fail (not skip silently) when `target: triggering` and there is no triggering issue (e.g., in scheduled or workflow_dispatch workflows).", + "description": "Update an existing GitHub issue's title, body, labels, assignees, or milestone WITHOUT closing it. This tool is primarily for editing issue metadata and content. While it supports changing status between 'open' and 'closed', use close_issue instead when you want to close an issue with a closing comment. Body updates support replacing, appending to, prepending content, or updating a per-run \"island\" section. Include `confidence` (0-100) when the update is inferred from analysis or heuristics rather than explicit user instruction. IMPORTANT: The behavior of this tool depends on the workflow's `update-issue: target:` configuration. When `target: triggering` (the default), the tool always updates the issue that triggered the workflow and `issue_number` is ignored. When `target: '*'`, the `issue_number` field controls which issue is updated. The tool will fail (not skip silently) when `target: triggering` and there is no triggering issue (e.g., in scheduled or workflow_dispatch workflows).", "inputSchema": { "type": "object", "properties": { @@ -803,6 +803,12 @@ "type": ["number", "string"], "description": "Issue number to update. This is the numeric ID from the GitHub URL (e.g., 789 in github.com/owner/repo/issues/789). ONLY effective when the workflow is configured with `update-issue: target: '*'` in the frontmatter. When the workflow uses `target: triggering` (the default), this field is ignored and the tool updates the issue that triggered the workflow instead. If you need to update a specific issue in a scheduled or workflow_dispatch workflow, the workflow frontmatter must include `update-issue: target: '*'`." }, + "confidence": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "description": "Optional confidence score from 0 to 100 that expresses how certain you are this issue update is correct. Use lower values (0-40) for weak evidence, mid-range values (41-79) for partial confidence, and high values (80-100) for strong confidence." + }, "secrecy": { "type": "string", "description": "Confidentiality level of the message content (e.g., \"public\", \"internal\", \"private\")." @@ -1150,7 +1156,7 @@ }, { "name": "set_issue_type", - "description": "Set the type of a GitHub issue. Pass an empty string \"\" to clear the issue type. Issue types must be configured in the repository or organization settings before they can be assigned.", + "description": "Set the type of a GitHub issue. Pass an empty string \"\" to clear the issue type. Issue types must be configured in the repository or organization settings before they can be assigned. Include `confidence` (0-100) when the selected type is inferred from issue signals rather than explicit user instruction.", "inputSchema": { "type": "object", "required": ["issue_type"], @@ -1163,6 +1169,12 @@ "type": "string", "description": "Issue type name to set (e.g., \"Bug\", \"Feature\", \"Task\"). Use an empty string \"\" to clear the current issue type." }, + "confidence": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "description": "Optional confidence score from 0 to 100 that expresses how certain you are this issue type assignment is correct. Use lower values (0-40) for weak evidence, mid-range values (41-79) for partial confidence, and high values (80-100) for strong confidence." + }, "secrecy": { "type": "string", "description": "Confidentiality level of the message content (e.g., \"public\", \"internal\", \"private\")." @@ -1177,7 +1189,7 @@ }, { "name": "set_issue_field", - "description": "Set a single GitHub issue field by name and value. Use field_name for discovery by field label (for example, \"Priority\"), or provide field_node_id to skip discovery. Supports text, number, date (YYYY-MM-DD), and single-select fields (value must match an option name).", + "description": "Set a single GitHub issue field by name and value. Use field_name for discovery by field label (for example, \"Priority\"), or provide field_node_id to skip discovery. Supports text, number, date (YYYY-MM-DD), and single-select fields (value must match an option name). Include `confidence` (0-100) when the field value is inferred rather than explicitly requested.", "inputSchema": { "type": "object", "required": ["value"], @@ -1198,6 +1210,12 @@ "type": "string", "description": "Field value to set. For single-select fields, this must match an existing option name (e.g., \"P1\" or \"High\"). For date fields, use format: YYYY-MM-DD (for example, 2026-05-08)." }, + "confidence": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "description": "Optional confidence score from 0 to 100 that expresses how certain you are this field update is correct. Use lower values (0-40) for weak evidence, mid-range values (41-79) for partial confidence, and high values (80-100) for strong confidence." + }, "secrecy": { "type": "string", "description": "Confidentiality level of the message content (e.g., \"public\", \"internal\", \"private\")." diff --git a/actions/setup/js/set_issue_field.cjs b/actions/setup/js/set_issue_field.cjs index 73928fbf8a0..e69eeebfc52 100644 --- a/actions/setup/js/set_issue_field.cjs +++ b/actions/setup/js/set_issue_field.cjs @@ -181,23 +181,51 @@ function buildFieldUpdatePayload(field, rawValue) { * Sets one issue field via GraphQL mutation. * @param {Object} githubClient - Authenticated GitHub client * @param {string} issueNodeId - GraphQL node ID of the issue - * @param {{fieldId: string, singleSelectOptionId?: string, numberValue?: number, dateValue?: string, textValue?: string}} fieldUpdate + * @param {{fieldId: string, singleSelectOptionId?: string, numberValue?: number, dateValue?: string, textValue?: string, confidence?: number}} fieldUpdate * @returns {Promise} */ async function setIssueFieldValue(githubClient, issueNodeId, fieldUpdate) { - await githubClient.graphql( - `mutation($issueId: ID!, $issueFields: [IssueFieldCreateOrUpdateInput!]!) { - setIssueFieldValue(input: { issueId: $issueId, issueFields: $issueFields }) { - issue { - id + const variables = { + issueId: issueNodeId, + issueFields: [fieldUpdate], + }; + + try { + await githubClient.graphql( + `mutation($issueId: ID!, $issueFields: [IssueFieldCreateOrUpdateInput!]!) { + setIssueFieldValue(input: { issueId: $issueId, issueFields: $issueFields }) { + issue { + id + } } - } - }`, - { - issueId: issueNodeId, - issueFields: [fieldUpdate], + }`, + variables + ); + } catch (error) { + const message = getErrorMessage(error).toLowerCase(); + const hasConfidence = typeof fieldUpdate.confidence === "number"; + const mentionsConfidence = message.includes("confidence"); + if (!hasConfidence || !mentionsConfidence) { + throw error; } - ); + + const retryFieldUpdate = { ...fieldUpdate }; + delete retryFieldUpdate.confidence; + core.warning("Issue field confidence is not supported by this API; retrying without confidence."); + await githubClient.graphql( + `mutation($issueId: ID!, $issueFields: [IssueFieldCreateOrUpdateInput!]!) { + setIssueFieldValue(input: { issueId: $issueId, issueFields: $issueFields }) { + issue { + id + } + } + }`, + { + issueId: issueNodeId, + issueFields: [retryFieldUpdate], + } + ); + } } /** @@ -300,6 +328,7 @@ async function main(config = {}) { field_name: fieldName, field_node_id: fieldNodeId, value, + confidence: typeof item.confidence === "number" ? item.confidence : undefined, repo: itemRepo, }, }; @@ -374,6 +403,10 @@ async function main(config = {}) { ...fieldUpdateResult.update, }; + const confidence = typeof item.confidence === "number" ? item.confidence : undefined; + if (confidence !== undefined) { + fieldUpdate.confidence = confidence; + } await setIssueFieldValue(githubClient, issueNodeId, fieldUpdate); core.info(`Successfully set issue field ${JSON.stringify(fieldName || fieldNodeId)} to ${JSON.stringify(value)} on issue #${issueNumber}`); @@ -384,6 +417,7 @@ async function main(config = {}) { field_name: fieldName, field_node_id: fieldNodeId, value, + confidence, repo: itemRepo, }; } catch (error) { diff --git a/actions/setup/js/set_issue_field.test.cjs b/actions/setup/js/set_issue_field.test.cjs index e3b7c8b5bb5..814586606f8 100644 --- a/actions/setup/js/set_issue_field.test.cjs +++ b/actions/setup/js/set_issue_field.test.cjs @@ -105,6 +105,7 @@ describe("set_issue_field (Handler Factory Architecture)", () => { issue_number: 42, field_name: "Customer Impact", value: "High", + confidence: 72, }; const result = await handler(message, {}); @@ -113,11 +114,12 @@ describe("set_issue_field (Handler Factory Architecture)", () => { expect(result.issue_number).toBe(42); expect(result.field_name).toBe("Customer Impact"); expect(result.field_node_id).toBe(textFieldId); + expect(result.confidence).toBe(72); expect(mockGraphql).toHaveBeenCalledWith( expect.stringContaining("setIssueFieldValue"), expect.objectContaining({ issueId: issueNodeId, - issueFields: [expect.objectContaining({ fieldId: textFieldId, textValue: "High" })], + issueFields: [expect.objectContaining({ fieldId: textFieldId, textValue: "High", confidence: 72 })], }) ); }); diff --git a/actions/setup/js/set_issue_type.cjs b/actions/setup/js/set_issue_type.cjs index f3ec956c878..39aa0c48482 100644 --- a/actions/setup/js/set_issue_type.cjs +++ b/actions/setup/js/set_issue_type.cjs @@ -72,19 +72,41 @@ async function fetchIssueTypes(githubClient, owner, repo) { * @param {Object} githubClient - Authenticated GitHub client * @param {string} issueNodeId - GraphQL node ID of the issue * @param {string|null} typeId - GraphQL node ID of the issue type, or null to clear + * @param {number|undefined} confidence - Optional confidence score (0-100) * @returns {Promise} */ -async function setIssueTypeById(githubClient, issueNodeId, typeId) { - await githubClient.graphql( - `mutation($issueId: ID!, $typeId: ID) { - updateIssue(input: { id: $issueId, issueTypeId: $typeId }) { - issue { - id +async function setIssueTypeById(githubClient, issueNodeId, typeId, confidence) { + try { + await githubClient.graphql( + `mutation($issueId: ID!, $typeId: ID, $confidence: Int) { + updateIssue(input: { id: $issueId, issueTypeId: $typeId, confidence: $confidence }) { + issue { + id + } } - } - }`, - { issueId: issueNodeId, typeId } - ); + }`, + { issueId: issueNodeId, typeId, confidence } + ); + } catch (error) { + const message = getErrorMessage(error).toLowerCase(); + const hasConfidence = typeof confidence === "number"; + const mentionsConfidence = message.includes("confidence"); + if (!hasConfidence || !mentionsConfidence) { + throw error; + } + + core.warning("Issue type confidence is not supported by this API; retrying without confidence."); + await githubClient.graphql( + `mutation($issueId: ID!, $typeId: ID) { + updateIssue(input: { id: $issueId, issueTypeId: $typeId }) { + issue { + id + } + } + }`, + { issueId: issueNodeId, typeId } + ); + } } /** @@ -197,6 +219,7 @@ async function main(config = {}) { previewInfo: { issue_number: issueNumber, issue_type: issueTypeName, + confidence: typeof item.confidence === "number" ? item.confidence : undefined, repo: itemRepo, }, }; @@ -231,7 +254,8 @@ async function main(config = {}) { core.info(`Resolved issue type ${JSON.stringify(issueTypeName)} to node ID: ${typeId}`); } - await setIssueTypeById(githubClient, issueNodeId, typeId); + const confidence = typeof item.confidence === "number" ? item.confidence : undefined; + await setIssueTypeById(githubClient, issueNodeId, typeId, confidence); const successMsg = isClear ? `Successfully cleared issue type on issue #${issueNumber}` : `Successfully set issue type to ${JSON.stringify(issueTypeName)} on issue #${issueNumber}`; core.info(successMsg); @@ -240,6 +264,7 @@ async function main(config = {}) { success: true, issue_number: issueNumber, issue_type: issueTypeName, + confidence, repo: itemRepo, }; } catch (error) { diff --git a/actions/setup/js/set_issue_type.test.cjs b/actions/setup/js/set_issue_type.test.cjs index ae07f9cfa20..10e20b546ad 100644 --- a/actions/setup/js/set_issue_type.test.cjs +++ b/actions/setup/js/set_issue_type.test.cjs @@ -88,6 +88,7 @@ describe("set_issue_type (Handler Factory Architecture)", () => { type: "set_issue_type", issue_number: 42, issue_type: "Bug", + confidence: 88, }; const result = await handler(message, {}); @@ -95,12 +96,13 @@ describe("set_issue_type (Handler Factory Architecture)", () => { expect(result.success).toBe(true); expect(result.issue_number).toBe(42); expect(result.issue_type).toBe("Bug"); + expect(result.confidence).toBe(88); expect(mockGithub.rest.issues.get).toHaveBeenCalledWith({ owner: "test-owner", repo: "test-repo", issue_number: 42, }); - expect(mockGraphql).toHaveBeenCalledWith(expect.stringContaining("updateIssue"), expect.objectContaining({ issueId: issueNodeId, typeId: bugTypeId })); + expect(mockGraphql).toHaveBeenCalledWith(expect.stringContaining("updateIssue"), expect.objectContaining({ issueId: issueNodeId, typeId: bugTypeId, confidence: 88 })); }); it("should clear issue type when issue_type is empty string", async () => { diff --git a/actions/setup/js/types/safe-outputs.d.ts b/actions/setup/js/types/safe-outputs.d.ts index 4666139befd..2ecbb4370f5 100644 --- a/actions/setup/js/types/safe-outputs.d.ts +++ b/actions/setup/js/types/safe-outputs.d.ts @@ -246,6 +246,8 @@ interface UpdateIssueItem extends BaseSafeOutputItem { body?: string; /** Optional issue number for target "*" */ issue_number?: number | string; + /** Optional confidence score (0-100) indicating certainty for this issue update. */ + confidence?: number; } /** @@ -322,6 +324,8 @@ interface SetIssueTypeItem extends BaseSafeOutputItem { issue_type: string; /** Issue number (optional - uses triggering issue if not provided) */ issue_number?: number | string; + /** Optional confidence score (0-100) indicating certainty in the issue type selection. */ + confidence?: number; } /** @@ -337,6 +341,8 @@ interface SetIssueFieldItem extends BaseSafeOutputItem { value: string; /** Issue number (optional - uses triggering issue if not provided) */ issue_number?: number | string; + /** Optional confidence score (0-100) indicating certainty in the field update value. */ + confidence?: number; } /** diff --git a/actions/setup/js/update_issue.cjs b/actions/setup/js/update_issue.cjs index 5abe7f11317..0123ed4cec2 100644 --- a/actions/setup/js/update_issue.cjs +++ b/actions/setup/js/update_issue.cjs @@ -103,12 +103,29 @@ async function executeIssueUpdate(github, context, issueNumber, updateData) { } } - const { data: issue } = await github.rest.issues.update({ + const issueUpdateParams = { owner: context.repo.owner, repo: context.repo.repo, issue_number: issueNumber, ...apiData, - }); + }; + + let issue; + try { + ({ data: issue } = await github.rest.issues.update(issueUpdateParams)); + } catch (error) { + const message = (error instanceof Error ? error.message : String(error)).toLowerCase(); + const hasConfidence = typeof apiData.confidence === "number"; + const mentionsConfidence = message.includes("confidence"); + if (!hasConfidence || !mentionsConfidence) { + throw error; + } + + core.warning("Issue confidence is not supported by this API; retrying without confidence."); + const retryParams = { ...issueUpdateParams }; + delete retryParams.confidence; + ({ data: issue } = await github.rest.issues.update(retryParams)); + } return issue; } @@ -164,6 +181,9 @@ function buildIssueUpdateData(item, config) { if (item.milestone !== undefined) { updateData.milestone = item.milestone; } + if (item.confidence !== undefined) { + updateData.confidence = item.confidence; + } // Enforce max limits on labels and assignees before API calls const labelsLimitResult = tryEnforceArrayLimit(updateData.labels, MAX_LABELS, "labels"); diff --git a/actions/setup/js/update_issue_generator.test.cjs b/actions/setup/js/update_issue_generator.test.cjs index 1e1f670b8a2..5b97176fb6e 100644 --- a/actions/setup/js/update_issue_generator.test.cjs +++ b/actions/setup/js/update_issue_generator.test.cjs @@ -59,6 +59,21 @@ describe("update_issue.cjs - generator payload", () => { expect(data.state).toBe("closed"); }); + it("passes confidence through to issue update payload", async () => { + const updateIssueModule = await import("./update_issue.cjs"); + + const { success, data } = updateIssueModule.buildIssueUpdateData( + { + status: "open", + confidence: 91, + }, + {} + ); + + expect(success).toBe(true); + expect(data.confidence).toBe(91); + }); + it("respects explicit operation when provided", async () => { const updateIssueModule = await import("./update_issue.cjs"); diff --git a/pkg/workflow/js/safe_outputs_tools.json b/pkg/workflow/js/safe_outputs_tools.json index 2c0ec5098dc..63df821eed5 100644 --- a/pkg/workflow/js/safe_outputs_tools.json +++ b/pkg/workflow/js/safe_outputs_tools.json @@ -903,7 +903,7 @@ }, { "name": "update_issue", - "description": "Update an existing GitHub issue's title, body, labels, assignees, or milestone WITHOUT closing it. This tool is primarily for editing issue metadata and content. While it supports changing status between 'open' and 'closed', use close_issue instead when you want to close an issue with a closing comment. Body updates support replacing, appending to, prepending content, or updating a per-run \"island\" section. IMPORTANT: The behavior of this tool depends on the workflow's `update-issue: target:` configuration. When `target: triggering` (the default), the tool always updates the issue that triggered the workflow and `issue_number` is ignored. When `target: '*'`, the `issue_number` field controls which issue is updated. The tool will fail (not skip silently) when `target: triggering` and there is no triggering issue (e.g., in scheduled or workflow_dispatch workflows).", + "description": "Update an existing GitHub issue's title, body, labels, assignees, or milestone WITHOUT closing it. This tool is primarily for editing issue metadata and content. While it supports changing status between 'open' and 'closed', use close_issue instead when you want to close an issue with a closing comment. Body updates support replacing, appending to, prepending content, or updating a per-run \"island\" section. Include `confidence` (0-100) when the update is inferred from analysis or heuristics rather than explicit user instruction. IMPORTANT: The behavior of this tool depends on the workflow's `update-issue: target:` configuration. When `target: triggering` (the default), the tool always updates the issue that triggered the workflow and `issue_number` is ignored. When `target: '*'`, the `issue_number` field controls which issue is updated. The tool will fail (not skip silently) when `target: triggering` and there is no triggering issue (e.g., in scheduled or workflow_dispatch workflows).", "inputSchema": { "type": "object", "properties": { @@ -961,6 +961,12 @@ ], "description": "Issue number to update. This is the numeric ID from the GitHub URL (e.g., 789 in github.com/owner/repo/issues/789). ONLY effective when the workflow is configured with `update-issue: target: '*'` in the frontmatter. When the workflow uses `target: triggering` (the default), this field is ignored and the tool updates the issue that triggered the workflow instead. If you need to update a specific issue in a scheduled or workflow_dispatch workflow, the workflow frontmatter must include `update-issue: target: '*'`." }, + "confidence": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "description": "Optional confidence score from 0 to 100 that expresses how certain you are this issue update is correct. Use lower values (0-40) for weak evidence, mid-range values (41-79) for partial confidence, and high values (80-100) for strong confidence." + }, "secrecy": { "type": "string", "description": "Confidentiality level of the message content (e.g., \"public\", \"internal\", \"private\")." @@ -1365,7 +1371,7 @@ }, { "name": "set_issue_type", - "description": "Set the type of a GitHub issue. Pass an empty string \"\" to clear the issue type. Issue types must be configured in the repository or organization settings before they can be assigned.", + "description": "Set the type of a GitHub issue. Pass an empty string \"\" to clear the issue type. Issue types must be configured in the repository or organization settings before they can be assigned. Include `confidence` (0-100) when the selected type is inferred from issue signals rather than explicit user instruction.", "inputSchema": { "type": "object", "required": [ @@ -1383,6 +1389,12 @@ "type": "string", "description": "Issue type name to set (e.g., \"Bug\", \"Feature\", \"Task\"). Use an empty string \"\" to clear the current issue type." }, + "confidence": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "description": "Optional confidence score from 0 to 100 that expresses how certain you are this issue type assignment is correct. Use lower values (0-40) for weak evidence, mid-range values (41-79) for partial confidence, and high values (80-100) for strong confidence." + }, "secrecy": { "type": "string", "description": "Confidentiality level of the message content (e.g., \"public\", \"internal\", \"private\")." @@ -1397,7 +1409,7 @@ }, { "name": "set_issue_field", - "description": "Set a single GitHub issue field by name and value. Use field_name for discovery by field label (for example, \"Priority\"), or provide field_node_id to skip discovery. Supports text, number, date (YYYY-MM-DD), and single-select fields (value must match an option name).", + "description": "Set a single GitHub issue field by name and value. Use field_name for discovery by field label (for example, \"Priority\"), or provide field_node_id to skip discovery. Supports text, number, date (YYYY-MM-DD), and single-select fields (value must match an option name). Include `confidence` (0-100) when the field value is inferred rather than explicitly requested.", "inputSchema": { "type": "object", "required": [ @@ -1423,6 +1435,12 @@ "type": "string", "description": "Field value to set. For single-select fields, this must match an existing option name (e.g., \"P1\" or \"High\"). For date fields, use format: YYYY-MM-DD (for example, 2026-05-08)." }, + "confidence": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "description": "Optional confidence score from 0 to 100 that expresses how certain you are this field update is correct. Use lower values (0-40) for weak evidence, mid-range values (41-79) for partial confidence, and high values (80-100) for strong confidence." + }, "secrecy": { "type": "string", "description": "Confidentiality level of the message content (e.g., \"public\", \"internal\", \"private\")." diff --git a/pkg/workflow/safe_outputs_validation_config.go b/pkg/workflow/safe_outputs_validation_config.go index b799600a862..a3edaca9b3f 100644 --- a/pkg/workflow/safe_outputs_validation_config.go +++ b/pkg/workflow/safe_outputs_validation_config.go @@ -27,6 +27,8 @@ type FieldValidation struct { ItemType string `json:"itemType,omitempty"` ItemSanitize bool `json:"itemSanitize,omitempty"` ItemMaxLength int `json:"itemMaxLength,omitempty"` + Minimum int `json:"minimum,omitempty"` + Maximum int `json:"maximum,omitempty"` Pattern string `json:"pattern,omitempty"` PatternError string `json:"patternError,omitempty"` TemporaryID bool `json:"temporaryId,omitempty"` @@ -130,7 +132,8 @@ var ValidationConfig = map[string]TypeValidationConfig{ Fields: map[string]FieldValidation{ "issue_number": {IssueOrPRNumber: true}, "issue_type": {Required: true, Type: "string", Sanitize: true, MaxLength: 128}, // Empty string clears the type - "repo": {Type: "string", MaxLength: 256}, // Optional: target repository in format "owner/repo" + "confidence": {Type: "number", Minimum: 0, Maximum: 100}, + "repo": {Type: "string", MaxLength: 256}, // Optional: target repository in format "owner/repo" }, }, "set_issue_field": { @@ -141,6 +144,7 @@ var ValidationConfig = map[string]TypeValidationConfig{ "field_name": {Type: "string", Sanitize: true, MaxLength: 128}, "field_node_id": {Type: "string", MaxLength: 256}, "value": {Required: true, Type: "string", Sanitize: true, MaxLength: 256}, + "confidence": {Type: "number", Minimum: 0, Maximum: 100}, "repo": {Type: "string", MaxLength: 256}, // Optional: target repository in format "owner/repo" }, }, @@ -175,6 +179,7 @@ var ValidationConfig = map[string]TypeValidationConfig{ "labels": {Type: "array", ItemType: "string", ItemSanitize: true, ItemMaxLength: 128}, "assignees": {Type: "array", ItemType: "string", ItemSanitize: true, ItemMaxLength: MaxGitHubUsernameLength}, "milestone": {OptionalPositiveInteger: true}, + "confidence": {Type: "number", Minimum: 0, Maximum: 100}, "issue_number": {IssueOrPRNumber: true}, "repo": {Type: "string", MaxLength: 256}, // Optional: target repository in format "owner/repo" },