Skip to content
Closed
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
15 changes: 12 additions & 3 deletions .github/workflows/smoke-codex.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 8 additions & 2 deletions .github/workflows/smoke-copilot-aoai-apikey.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 8 additions & 2 deletions .github/workflows/smoke-copilot-aoai-entra.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 8 additions & 2 deletions .github/workflows/smoke-copilot.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

58 changes: 58 additions & 0 deletions actions/setup/js/collect_ndjson_output.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,29 @@ describe("collect_ndjson_output.cjs", () => {
customValidation: "requiresOneOf:status,title,body",
fields: { status: { type: "string", enum: ["open", "closed"] }, title: { type: "string", sanitize: !0, maxLength: 128 }, body: { type: "string", sanitize: !0, maxLength: 65e3 }, issue_number: { issueOrPRNumber: !0 } },
},
set_issue_type: {
defaultMax: 5,
fields: {
issue_number: { issueOrPRNumber: !0, "x-synonyms": ["issueNumber"] },
issue_type: { required: !0, type: "string", sanitize: !0, maxLength: 128, "x-synonyms": ["issueType"] },
rationale: { type: "string", sanitize: !0, maxLength: 280 },
confidence: { type: "string", enum: ["LOW", "MEDIUM", "HIGH"] },
suggest: { type: "boolean" },
},
},
set_issue_field: {
defaultMax: 5,
customValidation: "requiresOneOf:field_name,field_node_id",
fields: {
issue_number: { issueOrPRNumber: !0, "x-synonyms": ["issueNumber"] },
field_name: { type: "string", sanitize: !0, maxLength: 128, "x-synonyms": ["fieldName"] },
field_node_id: { type: "string", maxLength: 256, "x-synonyms": ["fieldNodeId"] },
value: { required: !0, type: "string", sanitize: !0, maxLength: 256 },
rationale: { type: "string", sanitize: !0, maxLength: 280 },
confidence: { type: "string", enum: ["LOW", "MEDIUM", "HIGH"] },
suggest: { type: "boolean" },
},
},
create_pull_request_review_comment: {
defaultMax: 1,
customValidation: "startLineLessOrEqualLine",
Expand Down Expand Up @@ -195,6 +218,41 @@ describe("collect_ndjson_output.cjs", () => {
const parsedOutput = JSON.parse(outputCall[1]);
(expect(parsedOutput.items).toHaveLength(2), expect(parsedOutput.items[0].type).toBe("create_issue"), expect(parsedOutput.items[1].type).toBe("add_comment"), expect(parsedOutput.errors).toHaveLength(0));
}),
it("should strip invalid optional issue intent fields instead of dropping valid items", async () => {
const testFile = "/tmp/gh-aw/test-ndjson-output.txt",
ndjsonContent =
'{"type": "set_issue_type", "issue_type": "Bug", "confidence": 0.9, "rationale": 17, "suggest": true}\n{"type": "set_issue_field", "field_name": "Priority", "value": "P1", "confidence": "0.95", "rationale": {"why": "bad"}}';
(fs.writeFileSync(testFile, ndjsonContent), (process.env.GH_AW_SAFE_OUTPUTS = testFile));
const __config = '{"set_issue_type": true, "set_issue_field": true}',
configPath = "/tmp/gh-aw/safeoutputs/config.json";
(fs.writeFileSync(configPath, __config), await eval(`(async () => { ${collectScript}; await main(); })()`));
const setOutputCalls = mockCore.setOutput.mock.calls,
outputCall = setOutputCalls.find(call => "output" === call[0]);
expect(outputCall).toBeDefined();
const parsedOutput = JSON.parse(outputCall[1]);
expect(parsedOutput.errors).toHaveLength(0);
expect(parsedOutput.items).toEqual([
{ type: "set_issue_type", issue_type: "Bug", suggest: !0 },
{ type: "set_issue_field", field_name: "Priority", value: "P1" },
]);
}),
it("should map issue-intent synonym fields and parse confidence case-insensitively", async () => {
const testFile = "/tmp/gh-aw/test-ndjson-output.txt",
ndjsonContent = '{"type":"set_issue_type","issueType":"Bug","confidence":" high "}\n{"type":"set_issue_field","FieldName":"Priority","value":"P1","confidence":" medium "}';
(fs.writeFileSync(testFile, ndjsonContent), (process.env.GH_AW_SAFE_OUTPUTS = testFile));
const __config = '{"set_issue_type": true, "set_issue_field": true}',
configPath = "/tmp/gh-aw/safeoutputs/config.json";
(fs.writeFileSync(configPath, __config), await eval(`(async () => { ${collectScript}; await main(); })()`));
const setOutputCalls = mockCore.setOutput.mock.calls,
outputCall = setOutputCalls.find(call => "output" === call[0]);
expect(outputCall).toBeDefined();
const parsedOutput = JSON.parse(outputCall[1]);
expect(parsedOutput.errors).toHaveLength(0);
expect(parsedOutput.items).toEqual([
{ type: "set_issue_type", issue_type: "Bug", confidence: "HIGH" },
{ type: "set_issue_field", field_name: "Priority", value: "P1", confidence: "MEDIUM" },
]);
}),
it("should reject items with unexpected output types", async () => {
const testFile = "/tmp/gh-aw/test-ndjson-output.txt",
ndjsonContent = '{"type": "create_issue", "title": "Test Issue", "body": "Test body"}\n{"type": "unexpected-type", "data": "some data"}';
Expand Down
2 changes: 1 addition & 1 deletion actions/setup/js/generate_safe_outputs_tools.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ async function main() {
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();
enhancedTool.description = `${enhancedTool.description || ""} INTENT: Include rationale (string, max 280 chars) and confidence (string, exactly one of: LOW, MEDIUM, HIGH) with each call.`.trim();
}

if (tool.name === "add_comment") {
Expand Down
4 changes: 2 additions & 2 deletions actions/setup/js/generate_safe_outputs_tools.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ describe("generate_safe_outputs_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.";
const intentSuffix = "INTENT: Include rationale (string, max 280 chars) and confidence (string, exactly one of: 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);
Expand All @@ -345,7 +345,7 @@ describe("generate_safe_outputs_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.";
const intentSuffix = "INTENT: Include rationale (string, max 280 chars) and confidence (string, exactly one of: 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);
Expand Down
Loading