From 1bb29f597ab8d14f7dd921652b99c032631bb114 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 17 Jul 2026 15:14:32 +0000 Subject: [PATCH 1/4] Update @github/copilot to 1.0.72-0 - Updated nodejs and test harness dependencies - Re-ran code generators - Formatted generated code --- go/rpc/zrpc.go | 3 + java/pom.xml | 2 +- java/scripts/codegen/package-lock.json | 72 +++++++++---------- java/scripts/codegen/package.json | 2 +- .../copilot/generated/rpc/HookType.java | 2 + nodejs/package-lock.json | 72 +++++++++---------- nodejs/package.json | 2 +- nodejs/samples/package-lock.json | 2 +- nodejs/src/generated/rpc.ts | 2 + python/copilot/generated/rpc.py | 1 + rust/src/generated/api_types.rs | 3 + test/harness/package-lock.json | 72 +++++++++---------- test/harness/package.json | 2 +- 13 files changed, 124 insertions(+), 113 deletions(-) diff --git a/go/rpc/zrpc.go b/go/rpc/zrpc.go index 30ae6ff27..c6d5af4a2 100644 --- a/go/rpc/zrpc.go +++ b/go/rpc/zrpc.go @@ -11440,6 +11440,9 @@ const ( HookTypeSubagentStop HookType = "subagentStop" // Runs after the user submits a prompt. HookTypeUserPromptSubmitted HookType = "userPromptSubmitted" + // Runs after the runtime transforms the submitted prompt for the model, before it is added + // to session history. + HookTypeUserPromptTransformed HookType = "userPromptTransformed" ) // Constant value. Always "github". diff --git a/java/pom.xml b/java/pom.xml index ac95090b8..b2a2d3805 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -86,7 +86,7 @@ DO NOT EDIT MANUALLY. Updated by the update-copilot-dependency workflow. --> - ^1.0.71 + ^1.0.72-0 diff --git a/java/scripts/codegen/package-lock.json b/java/scripts/codegen/package-lock.json index 372e3daab..003246e16 100644 --- a/java/scripts/codegen/package-lock.json +++ b/java/scripts/codegen/package-lock.json @@ -6,7 +6,7 @@ "": { "name": "copilot-sdk-java-codegen", "dependencies": { - "@github/copilot": "^1.0.71", + "@github/copilot": "^1.0.72-0", "json-schema": "^0.4.0", "tsx": "^4.23.1" } @@ -428,9 +428,9 @@ } }, "node_modules/@github/copilot": { - "version": "1.0.71", - "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.71.tgz", - "integrity": "sha512-F3axBi+sXSLYDJbxCBW36bM6MYKNC2rlyAf3Ivo/MjiHKKJW7j5AmaR1IRYS9Gt8r9mxOwWFM1cJFA+CuLaR8g==", + "version": "1.0.72-0", + "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.72-0.tgz", + "integrity": "sha512-JS8LxGYvtPokXUHuqMbQhOqhMBPuOVMUIUy8BftlYxMJKN00oVp/Xe1aAqng6ULRdi35HnuyJ8nFfdBvE6UYsQ==", "license": "SEE LICENSE IN LICENSE.md", "dependencies": { "detect-libc": "^2.1.2" @@ -439,20 +439,20 @@ "copilot": "npm-loader.js" }, "optionalDependencies": { - "@github/copilot-darwin-arm64": "1.0.71", - "@github/copilot-darwin-x64": "1.0.71", - "@github/copilot-linux-arm64": "1.0.71", - "@github/copilot-linux-x64": "1.0.71", - "@github/copilot-linuxmusl-arm64": "1.0.71", - "@github/copilot-linuxmusl-x64": "1.0.71", - "@github/copilot-win32-arm64": "1.0.71", - "@github/copilot-win32-x64": "1.0.71" + "@github/copilot-darwin-arm64": "1.0.72-0", + "@github/copilot-darwin-x64": "1.0.72-0", + "@github/copilot-linux-arm64": "1.0.72-0", + "@github/copilot-linux-x64": "1.0.72-0", + "@github/copilot-linuxmusl-arm64": "1.0.72-0", + "@github/copilot-linuxmusl-x64": "1.0.72-0", + "@github/copilot-win32-arm64": "1.0.72-0", + "@github/copilot-win32-x64": "1.0.72-0" } }, "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.71", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.71.tgz", - "integrity": "sha512-mEWzyqbqRAWgyU7i2uuSRoVPx/TwaFQX0nZmw0bc30aJ0BnO7cy2kYQyCHw8ykmf/tfxT0xauZ6k0BOFmWizzQ==", + "version": "1.0.72-0", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.72-0.tgz", + "integrity": "sha512-/tWTIYNBXOQbJeniN9Sktsk0/Nnjnmr7jhaiY0ysumITK7wNQ3r8A2sZbv+WeODlfH/TfRvVS4e056U8NjKqnQ==", "cpu": [ "arm64" ], @@ -466,9 +466,9 @@ } }, "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.71", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.71.tgz", - "integrity": "sha512-Md9yEg406OBVBx3w4PeEj62TubulVLBcHleqmCoOoUmPgUxPZotUbrqz3rtbzADbXfrrD7JWvVsbd2UiNL194w==", + "version": "1.0.72-0", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.72-0.tgz", + "integrity": "sha512-Q6jFeRx1ch5S42EeJR616fGAg8eJ+Y9QlRx3s1xWUHPVQEUBbap8aD5REl1GSDr4+TkkPO+WDW5Hf9xhO5SK5A==", "cpu": [ "x64" ], @@ -482,9 +482,9 @@ } }, "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.71", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.71.tgz", - "integrity": "sha512-ykLJYOqBj3jRB5IJCDugLClAqbr7DmtTbUjlNY7+Jdq/n6i+d7xUQGclf1IWL5gnxbGQVAf+zkToD+sRM389Kg==", + "version": "1.0.72-0", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.72-0.tgz", + "integrity": "sha512-5A/DZUKSnXkP+nVIa1bPHslJlv6f+vagsoYbbDfsky8DrsjdR4KKyFXPsJkSD+o253f5zstl7CnpUejrZ1R/+Q==", "cpu": [ "arm64" ], @@ -498,9 +498,9 @@ } }, "node_modules/@github/copilot-linux-x64": { - "version": "1.0.71", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.71.tgz", - "integrity": "sha512-pC0FNHG+BBwZd6yZlM85kkAGN+uJhM6o+THi76N2GnnSxmw7+remb1mvYxdgRVbdCm+LBUIbCKRWJLuMwrfb6A==", + "version": "1.0.72-0", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.72-0.tgz", + "integrity": "sha512-SyY9sHaH/YwiKipKGSxXaAU755VlVInpSWCvNPycF7Z5oAmWNHeF1AOa5dwCYIRYxm8EgFXSLj5wTgNxXCz9Iw==", "cpu": [ "x64" ], @@ -514,9 +514,9 @@ } }, "node_modules/@github/copilot-linuxmusl-arm64": { - "version": "1.0.71", - "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-arm64/-/copilot-linuxmusl-arm64-1.0.71.tgz", - "integrity": "sha512-hBmDljFTjacxqZTasCEy43H8EIzuXB/hHEBBCMFjhB9J00nIxsO6Dh0woTifKpx7knTYZdpTjjca3D0pAoZlUA==", + "version": "1.0.72-0", + "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-arm64/-/copilot-linuxmusl-arm64-1.0.72-0.tgz", + "integrity": "sha512-QI5+rWpixbgcxwcpYwmHcGi2L996/Wmmoto4eTR+axoAHBCny84dkWP7DPoFO40GLTNTBWxZATxOP/awa1o5ww==", "cpu": [ "arm64" ], @@ -530,9 +530,9 @@ } }, "node_modules/@github/copilot-linuxmusl-x64": { - "version": "1.0.71", - "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-x64/-/copilot-linuxmusl-x64-1.0.71.tgz", - "integrity": "sha512-CfTXU8pa5dxRz22xQzoi3TiG1PJo9+WR8PRDiPSdkIBSyPJ1NvX87DJmfXjTgeAfR+wkjt/p0keDCaBBVhNmUA==", + "version": "1.0.72-0", + "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-x64/-/copilot-linuxmusl-x64-1.0.72-0.tgz", + "integrity": "sha512-msZCZZ4DovCKGJrUsvolozjOh9jq2u4ojAY70maB25MnnKm4uuvqUrkATVq9bDNU+nAcZuoiKKVRwDuByOlMww==", "cpu": [ "x64" ], @@ -546,9 +546,9 @@ } }, "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.71", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.71.tgz", - "integrity": "sha512-+HI1DokixXhHUahj06Fw67ZAigBuXKC58BFma4UJOGrQsDgwOSbqeTQHCw6vuymzjKlg3sactfsCUTaefkjscQ==", + "version": "1.0.72-0", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.72-0.tgz", + "integrity": "sha512-OOOh39JxHrjIvzS9UT5c6H+V7IiRuLT96iDGpn5V1g8CnwSyNq/LbiRZxoYpqzqzmSAvqQOCQpZUwoe/JFIYNw==", "cpu": [ "arm64" ], @@ -562,9 +562,9 @@ } }, "node_modules/@github/copilot-win32-x64": { - "version": "1.0.71", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.71.tgz", - "integrity": "sha512-02kXOBd9CwBbCaztuf71WYWn+uGapCuiaasomN4tcMH3HBVZ4gi3J0ZUoRcgcS80xh81uQyeBHbnUKzb/RE/9A==", + "version": "1.0.72-0", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.72-0.tgz", + "integrity": "sha512-Luix48UZ+dCs5xSCnc9i5/vJsIrvHhV62k6VyGUsbPVF5thjdXJU5T2Xi4Nsnt8ReLgQ5z/9YOBx9orUfDgGHg==", "cpu": [ "x64" ], diff --git a/java/scripts/codegen/package.json b/java/scripts/codegen/package.json index 32c609be4..8a3e173a5 100644 --- a/java/scripts/codegen/package.json +++ b/java/scripts/codegen/package.json @@ -7,7 +7,7 @@ "generate:java": "tsx java.ts" }, "dependencies": { - "@github/copilot": "^1.0.71", + "@github/copilot": "^1.0.72-0", "json-schema": "^0.4.0", "tsx": "^4.23.1" } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/HookType.java b/java/src/generated/java/com/github/copilot/generated/rpc/HookType.java index 006f8a7c1..8d7cd913c 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/HookType.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/HookType.java @@ -26,6 +26,8 @@ public enum HookType { POSTTOOLUSEFAILURE("postToolUseFailure"), /** The {@code userPromptSubmitted} variant. */ USERPROMPTSUBMITTED("userPromptSubmitted"), + /** The {@code userPromptTransformed} variant. */ + USERPROMPTTRANSFORMED("userPromptTransformed"), /** The {@code sessionStart} variant. */ SESSIONSTART("sessionStart"), /** The {@code sessionEnd} variant. */ diff --git a/nodejs/package-lock.json b/nodejs/package-lock.json index 799053c0f..270f73e73 100644 --- a/nodejs/package-lock.json +++ b/nodejs/package-lock.json @@ -9,7 +9,7 @@ "version": "0.0.0-dev", "license": "MIT", "dependencies": { - "@github/copilot": "^1.0.71", + "@github/copilot": "^1.0.72-0", "koffi": "^3.1.0", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" @@ -700,9 +700,9 @@ } }, "node_modules/@github/copilot": { - "version": "1.0.71", - "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.71.tgz", - "integrity": "sha512-F3axBi+sXSLYDJbxCBW36bM6MYKNC2rlyAf3Ivo/MjiHKKJW7j5AmaR1IRYS9Gt8r9mxOwWFM1cJFA+CuLaR8g==", + "version": "1.0.72-0", + "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.72-0.tgz", + "integrity": "sha512-JS8LxGYvtPokXUHuqMbQhOqhMBPuOVMUIUy8BftlYxMJKN00oVp/Xe1aAqng6ULRdi35HnuyJ8nFfdBvE6UYsQ==", "license": "SEE LICENSE IN LICENSE.md", "dependencies": { "detect-libc": "^2.1.2" @@ -711,20 +711,20 @@ "copilot": "npm-loader.js" }, "optionalDependencies": { - "@github/copilot-darwin-arm64": "1.0.71", - "@github/copilot-darwin-x64": "1.0.71", - "@github/copilot-linux-arm64": "1.0.71", - "@github/copilot-linux-x64": "1.0.71", - "@github/copilot-linuxmusl-arm64": "1.0.71", - "@github/copilot-linuxmusl-x64": "1.0.71", - "@github/copilot-win32-arm64": "1.0.71", - "@github/copilot-win32-x64": "1.0.71" + "@github/copilot-darwin-arm64": "1.0.72-0", + "@github/copilot-darwin-x64": "1.0.72-0", + "@github/copilot-linux-arm64": "1.0.72-0", + "@github/copilot-linux-x64": "1.0.72-0", + "@github/copilot-linuxmusl-arm64": "1.0.72-0", + "@github/copilot-linuxmusl-x64": "1.0.72-0", + "@github/copilot-win32-arm64": "1.0.72-0", + "@github/copilot-win32-x64": "1.0.72-0" } }, "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.71", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.71.tgz", - "integrity": "sha512-mEWzyqbqRAWgyU7i2uuSRoVPx/TwaFQX0nZmw0bc30aJ0BnO7cy2kYQyCHw8ykmf/tfxT0xauZ6k0BOFmWizzQ==", + "version": "1.0.72-0", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.72-0.tgz", + "integrity": "sha512-/tWTIYNBXOQbJeniN9Sktsk0/Nnjnmr7jhaiY0ysumITK7wNQ3r8A2sZbv+WeODlfH/TfRvVS4e056U8NjKqnQ==", "cpu": [ "arm64" ], @@ -738,9 +738,9 @@ } }, "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.71", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.71.tgz", - "integrity": "sha512-Md9yEg406OBVBx3w4PeEj62TubulVLBcHleqmCoOoUmPgUxPZotUbrqz3rtbzADbXfrrD7JWvVsbd2UiNL194w==", + "version": "1.0.72-0", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.72-0.tgz", + "integrity": "sha512-Q6jFeRx1ch5S42EeJR616fGAg8eJ+Y9QlRx3s1xWUHPVQEUBbap8aD5REl1GSDr4+TkkPO+WDW5Hf9xhO5SK5A==", "cpu": [ "x64" ], @@ -754,9 +754,9 @@ } }, "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.71", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.71.tgz", - "integrity": "sha512-ykLJYOqBj3jRB5IJCDugLClAqbr7DmtTbUjlNY7+Jdq/n6i+d7xUQGclf1IWL5gnxbGQVAf+zkToD+sRM389Kg==", + "version": "1.0.72-0", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.72-0.tgz", + "integrity": "sha512-5A/DZUKSnXkP+nVIa1bPHslJlv6f+vagsoYbbDfsky8DrsjdR4KKyFXPsJkSD+o253f5zstl7CnpUejrZ1R/+Q==", "cpu": [ "arm64" ], @@ -770,9 +770,9 @@ } }, "node_modules/@github/copilot-linux-x64": { - "version": "1.0.71", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.71.tgz", - "integrity": "sha512-pC0FNHG+BBwZd6yZlM85kkAGN+uJhM6o+THi76N2GnnSxmw7+remb1mvYxdgRVbdCm+LBUIbCKRWJLuMwrfb6A==", + "version": "1.0.72-0", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.72-0.tgz", + "integrity": "sha512-SyY9sHaH/YwiKipKGSxXaAU755VlVInpSWCvNPycF7Z5oAmWNHeF1AOa5dwCYIRYxm8EgFXSLj5wTgNxXCz9Iw==", "cpu": [ "x64" ], @@ -786,9 +786,9 @@ } }, "node_modules/@github/copilot-linuxmusl-arm64": { - "version": "1.0.71", - "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-arm64/-/copilot-linuxmusl-arm64-1.0.71.tgz", - "integrity": "sha512-hBmDljFTjacxqZTasCEy43H8EIzuXB/hHEBBCMFjhB9J00nIxsO6Dh0woTifKpx7knTYZdpTjjca3D0pAoZlUA==", + "version": "1.0.72-0", + "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-arm64/-/copilot-linuxmusl-arm64-1.0.72-0.tgz", + "integrity": "sha512-QI5+rWpixbgcxwcpYwmHcGi2L996/Wmmoto4eTR+axoAHBCny84dkWP7DPoFO40GLTNTBWxZATxOP/awa1o5ww==", "cpu": [ "arm64" ], @@ -802,9 +802,9 @@ } }, "node_modules/@github/copilot-linuxmusl-x64": { - "version": "1.0.71", - "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-x64/-/copilot-linuxmusl-x64-1.0.71.tgz", - "integrity": "sha512-CfTXU8pa5dxRz22xQzoi3TiG1PJo9+WR8PRDiPSdkIBSyPJ1NvX87DJmfXjTgeAfR+wkjt/p0keDCaBBVhNmUA==", + "version": "1.0.72-0", + "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-x64/-/copilot-linuxmusl-x64-1.0.72-0.tgz", + "integrity": "sha512-msZCZZ4DovCKGJrUsvolozjOh9jq2u4ojAY70maB25MnnKm4uuvqUrkATVq9bDNU+nAcZuoiKKVRwDuByOlMww==", "cpu": [ "x64" ], @@ -818,9 +818,9 @@ } }, "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.71", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.71.tgz", - "integrity": "sha512-+HI1DokixXhHUahj06Fw67ZAigBuXKC58BFma4UJOGrQsDgwOSbqeTQHCw6vuymzjKlg3sactfsCUTaefkjscQ==", + "version": "1.0.72-0", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.72-0.tgz", + "integrity": "sha512-OOOh39JxHrjIvzS9UT5c6H+V7IiRuLT96iDGpn5V1g8CnwSyNq/LbiRZxoYpqzqzmSAvqQOCQpZUwoe/JFIYNw==", "cpu": [ "arm64" ], @@ -834,9 +834,9 @@ } }, "node_modules/@github/copilot-win32-x64": { - "version": "1.0.71", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.71.tgz", - "integrity": "sha512-02kXOBd9CwBbCaztuf71WYWn+uGapCuiaasomN4tcMH3HBVZ4gi3J0ZUoRcgcS80xh81uQyeBHbnUKzb/RE/9A==", + "version": "1.0.72-0", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.72-0.tgz", + "integrity": "sha512-Luix48UZ+dCs5xSCnc9i5/vJsIrvHhV62k6VyGUsbPVF5thjdXJU5T2Xi4Nsnt8ReLgQ5z/9YOBx9orUfDgGHg==", "cpu": [ "x64" ], diff --git a/nodejs/package.json b/nodejs/package.json index 4f588640a..a840c642e 100644 --- a/nodejs/package.json +++ b/nodejs/package.json @@ -56,7 +56,7 @@ "author": "GitHub", "license": "MIT", "dependencies": { - "@github/copilot": "^1.0.71", + "@github/copilot": "^1.0.72-0", "koffi": "^3.1.0", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" diff --git a/nodejs/samples/package-lock.json b/nodejs/samples/package-lock.json index 89c74c153..2c7dc9c9d 100644 --- a/nodejs/samples/package-lock.json +++ b/nodejs/samples/package-lock.json @@ -18,7 +18,7 @@ "version": "0.0.0-dev", "license": "MIT", "dependencies": { - "@github/copilot": "^1.0.71", + "@github/copilot": "^1.0.72-0", "koffi": "^3.1.0", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" diff --git a/nodejs/src/generated/rpc.ts b/nodejs/src/generated/rpc.ts index 255a769d5..8e710cf24 100644 --- a/nodejs/src/generated/rpc.ts +++ b/nodejs/src/generated/rpc.ts @@ -540,6 +540,8 @@ export type HookType = | "postToolUseFailure" /** Runs after the user submits a prompt. */ | "userPromptSubmitted" + /** Runs after the runtime transforms the submitted prompt for the model, before it is added to session history. */ + | "userPromptTransformed" /** Runs when a session starts. */ | "sessionStart" /** Runs when a session ends. */ diff --git a/python/copilot/generated/rpc.py b/python/copilot/generated/rpc.py index 828eaa3ce..106292969 100644 --- a/python/copilot/generated/rpc.py +++ b/python/copilot/generated/rpc.py @@ -2466,6 +2466,7 @@ class _HookType(Enum): SUBAGENT_START = "subagentStart" SUBAGENT_STOP = "subagentStop" USER_PROMPT_SUBMITTED = "userPromptSubmitted" + USER_PROMPT_TRANSFORMED = "userPromptTransformed" # Internal: this type is an internal SDK API and is not part of the public surface. @dataclass diff --git a/rust/src/generated/api_types.rs b/rust/src/generated/api_types.rs index e243ec1dc..ea494d9ba 100644 --- a/rust/src/generated/api_types.rs +++ b/rust/src/generated/api_types.rs @@ -20866,6 +20866,9 @@ pub enum HookType { /// Runs after the user submits a prompt. #[serde(rename = "userPromptSubmitted")] UserPromptSubmitted, + /// Runs after the runtime transforms the submitted prompt for the model, before it is added to session history. + #[serde(rename = "userPromptTransformed")] + UserPromptTransformed, /// Runs when a session starts. #[serde(rename = "sessionStart")] SessionStart, diff --git a/test/harness/package-lock.json b/test/harness/package-lock.json index 1a8462d66..817b92379 100644 --- a/test/harness/package-lock.json +++ b/test/harness/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "ISC", "devDependencies": { - "@github/copilot": "^1.0.71", + "@github/copilot": "^1.0.72-0", "@modelcontextprotocol/sdk": "^1.26.0", "@types/node": "^25.3.3", "@types/node-forge": "^1.3.14", @@ -501,9 +501,9 @@ } }, "node_modules/@github/copilot": { - "version": "1.0.71", - "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.71.tgz", - "integrity": "sha512-F3axBi+sXSLYDJbxCBW36bM6MYKNC2rlyAf3Ivo/MjiHKKJW7j5AmaR1IRYS9Gt8r9mxOwWFM1cJFA+CuLaR8g==", + "version": "1.0.72-0", + "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.72-0.tgz", + "integrity": "sha512-JS8LxGYvtPokXUHuqMbQhOqhMBPuOVMUIUy8BftlYxMJKN00oVp/Xe1aAqng6ULRdi35HnuyJ8nFfdBvE6UYsQ==", "dev": true, "license": "SEE LICENSE IN LICENSE.md", "dependencies": { @@ -513,20 +513,20 @@ "copilot": "npm-loader.js" }, "optionalDependencies": { - "@github/copilot-darwin-arm64": "1.0.71", - "@github/copilot-darwin-x64": "1.0.71", - "@github/copilot-linux-arm64": "1.0.71", - "@github/copilot-linux-x64": "1.0.71", - "@github/copilot-linuxmusl-arm64": "1.0.71", - "@github/copilot-linuxmusl-x64": "1.0.71", - "@github/copilot-win32-arm64": "1.0.71", - "@github/copilot-win32-x64": "1.0.71" + "@github/copilot-darwin-arm64": "1.0.72-0", + "@github/copilot-darwin-x64": "1.0.72-0", + "@github/copilot-linux-arm64": "1.0.72-0", + "@github/copilot-linux-x64": "1.0.72-0", + "@github/copilot-linuxmusl-arm64": "1.0.72-0", + "@github/copilot-linuxmusl-x64": "1.0.72-0", + "@github/copilot-win32-arm64": "1.0.72-0", + "@github/copilot-win32-x64": "1.0.72-0" } }, "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.71", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.71.tgz", - "integrity": "sha512-mEWzyqbqRAWgyU7i2uuSRoVPx/TwaFQX0nZmw0bc30aJ0BnO7cy2kYQyCHw8ykmf/tfxT0xauZ6k0BOFmWizzQ==", + "version": "1.0.72-0", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.72-0.tgz", + "integrity": "sha512-/tWTIYNBXOQbJeniN9Sktsk0/Nnjnmr7jhaiY0ysumITK7wNQ3r8A2sZbv+WeODlfH/TfRvVS4e056U8NjKqnQ==", "cpu": [ "arm64" ], @@ -541,9 +541,9 @@ } }, "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.71", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.71.tgz", - "integrity": "sha512-Md9yEg406OBVBx3w4PeEj62TubulVLBcHleqmCoOoUmPgUxPZotUbrqz3rtbzADbXfrrD7JWvVsbd2UiNL194w==", + "version": "1.0.72-0", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.72-0.tgz", + "integrity": "sha512-Q6jFeRx1ch5S42EeJR616fGAg8eJ+Y9QlRx3s1xWUHPVQEUBbap8aD5REl1GSDr4+TkkPO+WDW5Hf9xhO5SK5A==", "cpu": [ "x64" ], @@ -558,9 +558,9 @@ } }, "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.71", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.71.tgz", - "integrity": "sha512-ykLJYOqBj3jRB5IJCDugLClAqbr7DmtTbUjlNY7+Jdq/n6i+d7xUQGclf1IWL5gnxbGQVAf+zkToD+sRM389Kg==", + "version": "1.0.72-0", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.72-0.tgz", + "integrity": "sha512-5A/DZUKSnXkP+nVIa1bPHslJlv6f+vagsoYbbDfsky8DrsjdR4KKyFXPsJkSD+o253f5zstl7CnpUejrZ1R/+Q==", "cpu": [ "arm64" ], @@ -575,9 +575,9 @@ } }, "node_modules/@github/copilot-linux-x64": { - "version": "1.0.71", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.71.tgz", - "integrity": "sha512-pC0FNHG+BBwZd6yZlM85kkAGN+uJhM6o+THi76N2GnnSxmw7+remb1mvYxdgRVbdCm+LBUIbCKRWJLuMwrfb6A==", + "version": "1.0.72-0", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.72-0.tgz", + "integrity": "sha512-SyY9sHaH/YwiKipKGSxXaAU755VlVInpSWCvNPycF7Z5oAmWNHeF1AOa5dwCYIRYxm8EgFXSLj5wTgNxXCz9Iw==", "cpu": [ "x64" ], @@ -592,9 +592,9 @@ } }, "node_modules/@github/copilot-linuxmusl-arm64": { - "version": "1.0.71", - "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-arm64/-/copilot-linuxmusl-arm64-1.0.71.tgz", - "integrity": "sha512-hBmDljFTjacxqZTasCEy43H8EIzuXB/hHEBBCMFjhB9J00nIxsO6Dh0woTifKpx7knTYZdpTjjca3D0pAoZlUA==", + "version": "1.0.72-0", + "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-arm64/-/copilot-linuxmusl-arm64-1.0.72-0.tgz", + "integrity": "sha512-QI5+rWpixbgcxwcpYwmHcGi2L996/Wmmoto4eTR+axoAHBCny84dkWP7DPoFO40GLTNTBWxZATxOP/awa1o5ww==", "cpu": [ "arm64" ], @@ -609,9 +609,9 @@ } }, "node_modules/@github/copilot-linuxmusl-x64": { - "version": "1.0.71", - "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-x64/-/copilot-linuxmusl-x64-1.0.71.tgz", - "integrity": "sha512-CfTXU8pa5dxRz22xQzoi3TiG1PJo9+WR8PRDiPSdkIBSyPJ1NvX87DJmfXjTgeAfR+wkjt/p0keDCaBBVhNmUA==", + "version": "1.0.72-0", + "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-x64/-/copilot-linuxmusl-x64-1.0.72-0.tgz", + "integrity": "sha512-msZCZZ4DovCKGJrUsvolozjOh9jq2u4ojAY70maB25MnnKm4uuvqUrkATVq9bDNU+nAcZuoiKKVRwDuByOlMww==", "cpu": [ "x64" ], @@ -626,9 +626,9 @@ } }, "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.71", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.71.tgz", - "integrity": "sha512-+HI1DokixXhHUahj06Fw67ZAigBuXKC58BFma4UJOGrQsDgwOSbqeTQHCw6vuymzjKlg3sactfsCUTaefkjscQ==", + "version": "1.0.72-0", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.72-0.tgz", + "integrity": "sha512-OOOh39JxHrjIvzS9UT5c6H+V7IiRuLT96iDGpn5V1g8CnwSyNq/LbiRZxoYpqzqzmSAvqQOCQpZUwoe/JFIYNw==", "cpu": [ "arm64" ], @@ -643,9 +643,9 @@ } }, "node_modules/@github/copilot-win32-x64": { - "version": "1.0.71", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.71.tgz", - "integrity": "sha512-02kXOBd9CwBbCaztuf71WYWn+uGapCuiaasomN4tcMH3HBVZ4gi3J0ZUoRcgcS80xh81uQyeBHbnUKzb/RE/9A==", + "version": "1.0.72-0", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.72-0.tgz", + "integrity": "sha512-Luix48UZ+dCs5xSCnc9i5/vJsIrvHhV62k6VyGUsbPVF5thjdXJU5T2Xi4Nsnt8ReLgQ5z/9YOBx9orUfDgGHg==", "cpu": [ "x64" ], diff --git a/test/harness/package.json b/test/harness/package.json index 18b19e21a..79758d524 100644 --- a/test/harness/package.json +++ b/test/harness/package.json @@ -14,7 +14,7 @@ "node": "^20.19.0 || >=22.12.0" }, "devDependencies": { - "@github/copilot": "^1.0.71", + "@github/copilot": "^1.0.72-0", "@modelcontextprotocol/sdk": "^1.26.0", "@types/node": "^25.3.3", "@types/node-forge": "^1.3.14", From f83d7036d0889bda5209d3aa906e012320128313 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Fri, 17 Jul 2026 23:30:47 -0400 Subject: [PATCH 2/4] Normalize report_intent tool-list drift in replay proxy The 1.0.72 CLI now appends the full built-in tool enumeration to the "Tool 'report_intent' does not exist." error, while existing snapshots recorded it truncated. Strip the whole "Available tools that can be called are ." sentence on both the request and stored sides so list-present, list-absent, and differing list contents are all equivalent, fixing subagent_hooks (all SDKs) and should_abort_a_session (Go/Python/Rust/.NET) without editing any snapshots. Also collapse the new trailing multi-turn advice on the task tool's background-agent start result to a stable placeholder. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: e94bab38-c22e-4a38-8d8b-c82c1991a33c --- test/harness/replayingCapiProxy.test.ts | 207 +++++++++++++++++++++++- test/harness/replayingCapiProxy.ts | 51 +++--- 2 files changed, 236 insertions(+), 22 deletions(-) diff --git a/test/harness/replayingCapiProxy.test.ts b/test/harness/replayingCapiProxy.test.ts index 009a1e40a..45db90775 100644 --- a/test/harness/replayingCapiProxy.test.ts +++ b/test/harness/replayingCapiProxy.test.ts @@ -509,7 +509,7 @@ Always include PINEAPPLE_COCONUT_42. expect(toolMessages[1].content).toBe("[beta result]"); }); - test("collapses the available-tools list to a stable placeholder", async () => { + test("strips the available-tools list so its contents don't matter", async () => { const requestBody = JSON.stringify({ messages: [ { role: "user", content: "Help me" }, @@ -543,10 +543,52 @@ Always include PINEAPPLE_COCONUT_42. const toolMessage = result.conversations[0].messages.find( (m) => m.role === "tool", ); - // The whole enumeration collapses so snapshots stay stable as the built-in - // tool set evolves (e.g. write_agent being added). + // The whole "Available tools..." sentence is stripped so snapshots stay + // stable whether or not the runtime emits the list and as the built-in tool + // set evolves (e.g. write_agent being added). expect(toolMessage?.content).toBe( - "Tool 'report_intent' does not exist. Available tools that can be called are ${available_tools}.", + "Tool 'report_intent' does not exist.", + ); + }); + + test("collapses background-agent start advice to a stable placeholder", async () => { + const requestBody = JSON.stringify({ + messages: [ + { role: "user", content: "Help me" }, + { + role: "assistant", + tool_calls: [ + { + id: "tc1", + type: "function", + function: { name: "task", arguments: "{}" }, + }, + ], + }, + { + role: "tool", + tool_call_id: "tc1", + content: + "Agent started in background with agent_id: read-file. You'll be notified when it completes. Tell the user you're waiting and end your response, or continue unrelated work until notified. The agent supports multi-turn conversations — use write_agent to send follow-up messages.", + }, + ], + }); + const responseBody = JSON.stringify({ + choices: [{ message: { role: "assistant", content: "Done" } }], + }); + + const outputPath = await createProxy([ + { url: "/chat/completions", requestBody, responseBody }, + ]); + + const result = await readYamlOutput(outputPath); + const toolMessage = result.conversations[0].messages.find( + (m) => m.role === "tool", + ); + // The volatile advice tail collapses so snapshots stay stable as the wording + // evolves across runtime versions, while the agent_id is preserved. + expect(toolMessage?.content).toBe( + "Agent started in background with agent_id: read-file. ${background_agent_advice}", ); }); @@ -947,6 +989,163 @@ Always include PINEAPPLE_COCONUT_42. } }); + test("matches truncated report_intent results against a runtime that emits the tool list", async () => { + const cachePath = path.join(tempDir, "cache.yaml"); + // Older snapshots truncated the report_intent error to just the first + // sentence, with no "Available tools..." enumeration recorded on disk. + const cacheContent = yaml.stringify({ + models: ["test-model"], + conversations: [ + { + messages: [ + { role: "system", content: "${system}" }, + { role: "user", content: "Report intent" }, + { + role: "assistant", + tool_calls: [ + { + id: "toolcall_0", + type: "function", + function: { name: "report_intent", arguments: "{}" }, + }, + ], + }, + { + role: "tool", + tool_call_id: "toolcall_0", + content: "Tool 'report_intent' does not exist.", + }, + { role: "assistant", content: "Done" }, + ], + }, + ], + } satisfies NormalizedData); + await writeFile(cachePath, cacheContent); + + const proxy = new ReplayingCapiProxy( + "http://localhost:9999", + cachePath, + workDir, + ); + const proxyUrl = await proxy.start(); + + try { + const response = await makeRequest(proxyUrl, "/chat/completions", { + body: { + model: "test-model", + messages: [ + { role: "system", content: "System prompt" }, + { role: "user", content: "Report intent" }, + { + role: "assistant", + tool_calls: [ + { + id: "runtime-call-id", + type: "function", + function: { name: "report_intent", arguments: "{}" }, + }, + ], + }, + { + role: "tool", + tool_call_id: "runtime-call-id", + // Newer runtime appends the full built-in tool enumeration. + content: + "Tool 'report_intent' does not exist. Available tools that can be called are bash, read_bash, view, read_agent, list_agents, write_agent, grep, glob, task.", + }, + ], + }, + }); + + expect(response.status).toBe(200); + expect( + (JSON.parse(response.body) as ChatCompletion).choices[0].message + .content, + ).toBe("Done"); + } finally { + await proxy.stop(); + } + }); + + test("matches background-agent start results after the advice wording changes", async () => { + const cachePath = path.join(tempDir, "cache.yaml"); + // Legacy snapshot recorded before the runtime appended multi-turn advice + // to the background-agent start message. + const cacheContent = yaml.stringify({ + models: ["test-model"], + conversations: [ + { + messages: [ + { role: "system", content: "${system}" }, + { role: "user", content: "Spawn an agent" }, + { + role: "assistant", + tool_calls: [ + { + id: "toolcall_0", + type: "function", + function: { name: "task", arguments: "{}" }, + }, + ], + }, + { + role: "tool", + tool_call_id: "toolcall_0", + content: + "Agent started in background with agent_id: read-file. You'll be notified when it completes. Tell the user you're waiting and end your response, or continue unrelated work until notified.", + }, + { role: "assistant", content: "Done" }, + ], + }, + ], + } satisfies NormalizedData); + await writeFile(cachePath, cacheContent); + + const proxy = new ReplayingCapiProxy( + "http://localhost:9999", + cachePath, + workDir, + ); + const proxyUrl = await proxy.start(); + + try { + const response = await makeRequest(proxyUrl, "/chat/completions", { + body: { + model: "test-model", + messages: [ + { role: "system", content: "System prompt" }, + { role: "user", content: "Spawn an agent" }, + { + role: "assistant", + tool_calls: [ + { + id: "runtime-call-id", + type: "function", + function: { name: "task", arguments: "{}" }, + }, + ], + }, + { + role: "tool", + tool_call_id: "runtime-call-id", + // Newer runtime appends multi-turn advice referencing write_agent. + content: + "Agent started in background with agent_id: read-file. You'll be notified when it completes. Tell the user you're waiting and end your response, or continue unrelated work until notified. The agent supports multi-turn conversations — use write_agent to send follow-up messages.", + }, + ], + }, + }); + + expect(response.status).toBe(200); + expect( + (JSON.parse(response.body) as ChatCompletion).choices[0].message + .content, + ).toBe("Done"); + } finally { + await proxy.stop(); + } + }); + test("expands workdir placeholder in cached response", async () => { const cachePath = path.join(tempDir, "cache.yaml"); const cacheContent = yaml.stringify({ diff --git a/test/harness/replayingCapiProxy.ts b/test/harness/replayingCapiProxy.ts index 1c29fb755..71d6b7c28 100644 --- a/test/harness/replayingCapiProxy.ts +++ b/test/harness/replayingCapiProxy.ts @@ -127,6 +127,7 @@ export class ReplayingCapiProxy extends CapturingHttpProxy { { toolName: "${shell}", normalizer: normalizeShellExitMarkers }, { toolName: "*", normalizer: normalizeGhAuthMessages }, { toolName: "*", normalizer: normalizeAvailableToolNames }, + { toolName: "task", normalizer: normalizeBackgroundAgentAdvice }, { toolName: "read_agent", normalizer: normalizeReadAgentTimings }, ]; @@ -1299,17 +1300,17 @@ function coalesceMessages( return result; } -// Re-normalizes the built-in tool enumeration in stored tool results at load -// time. Snapshots recorded before normalizeAvailableToolNames collapsed the -// whole list (or recorded against an older tool set) still contain the literal -// enumeration on disk; the result normalizers only run against live requests, -// so without this the stored side would keep the stale list and never match a -// request whose tool set has since changed. +// Re-normalizes stored tool results at load time. Snapshots recorded against an +// older runtime still contain the literal built-in tool enumeration (or the +// background-agent advice wording) on disk; the result normalizers only run +// against live requests, so without this the stored side would keep the stale +// text and never match a request whose runtime output has since changed. function normalizeStoredToolMessages(conversations: NormalizedConversation[]) { for (const conversation of conversations) { for (const message of conversation.messages) { if (message.role === "tool" && typeof message.content === "string") { message.content = normalizeAvailableToolNames(message.content); + message.content = normalizeBackgroundAgentAdvice(message.content); } } } @@ -1405,21 +1406,35 @@ function normalizeReadAgentTimings(result: string): string { .replace(/\bduration: \d+(?:\.\d+)?s\b/g, "duration: 0s"); } -// Stable placeholder for the built-in tool enumeration the runtime emits when a -// nonexistent tool is called (see normalizeAvailableToolNames). -export const availableToolsPlaceholder = "${available_tools}"; - // When a model calls a tool that doesn't exist (e.g., the removed report_intent -// tool), the runtime replies with "Available tools that can be called are ." -// That enumeration is both platform-specific (shell tool family names differ -// across OSes) and runtime-version-specific (built-in tools such as write_agent -// are added or removed over time), so any test that trips this path would break -// whenever the tool set changes. Collapse the whole list to a stable placeholder -// so snapshots keep matching as the built-in tool set evolves. +// tool), the runtime replies with "Tool '' does not exist. Available tools +// that can be called are ." Some runtime versions omit that second sentence +// entirely, and when present the enumeration is both platform-specific (shell tool +// family names differ across OSes) and runtime-version-specific (built-in tools +// such as write_agent are added or removed over time). Strip the whole "Available +// tools..." sentence so its presence, absence, and exact contents are all +// equivalent and snapshots keep matching as the built-in tool set evolves. function normalizeAvailableToolNames(result: string): string { return result.replace( - /(Available tools that can be called are )[^.]*/g, - (_full, prefix: string) => prefix + availableToolsPlaceholder, + /\s*Available tools that can be called are [^.]*\./g, + "", + ); +} + +// Stable placeholder for the advice text the runtime appends after starting a +// background agent (see normalizeBackgroundAgentAdvice). +export const backgroundAgentAdvicePlaceholder = "${background_agent_advice}"; + +// When the task tool starts an agent in the background, the runtime replies with +// "Agent started in background with agent_id: ." followed by advice text +// ("You'll be notified when it completes... The agent supports multi-turn +// conversations — use write_agent...") that changes across runtime versions. +// Collapse that trailing advice to a stable placeholder so snapshots keep +// matching as the wording evolves, while preserving the meaningful agent_id. +function normalizeBackgroundAgentAdvice(result: string): string { + return result.replace( + /(Agent started in background with agent_id: \S+\.)[\s\S]*/, + (_full, prefix: string) => `${prefix} ${backgroundAgentAdvicePlaceholder}`, ); } From 307fe21584773a3aff30c810a86f25abc4c30fa6 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Fri, 17 Jul 2026 23:56:12 -0400 Subject: [PATCH 3/4] Normalize 1.0.72 background-agent completion notification wording The 1.0.72 CLI reworded the background-agent completion system_notification that the runtime injects as a user message: the status sentence changed from "has completed successfully." to "has finished processing and is now idle." and the advice changed from "to retrieve unread results." to "to read the results, or write_agent to send follow-up messages." Existing snapshots store the old canonical wording, so the replay proxy could not match live 1.0.72 requests, failing subagent_hooks, should_abort_a_session, and the background-agent task-details test across every SDK. Extend the existing task-completion-notification normalization (which already mapped "retrieve unread results" to "retrieve the full results") into normalizeAgentCompletionNotification, collapsing every known phrasing of both sentences to one canonical form on both the request and stored sides. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: e94bab38-c22e-4a38-8d8b-c82c1991a33c --- test/harness/replayingCapiProxy.test.ts | 93 +++++++++++++++++++++++++ test/harness/replayingCapiProxy.ts | 31 ++++++--- 2 files changed, 114 insertions(+), 10 deletions(-) diff --git a/test/harness/replayingCapiProxy.test.ts b/test/harness/replayingCapiProxy.test.ts index 45db90775..b3dfcd670 100644 --- a/test/harness/replayingCapiProxy.test.ts +++ b/test/harness/replayingCapiProxy.test.ts @@ -379,6 +379,38 @@ describe("ReplayingCapiProxy", () => { expect(result.conversations[0].messages[0].content).toBe(fullNotification); }); + test("normalizes the idle-phrasing task completion notification", async () => { + const idleNotification = [ + "", + 'Agent "sdk-background-agent" (general-purpose) has finished processing and is now idle. Use read_agent with agent_id "sdk-background-agent" to read the results, or write_agent to send follow-up messages.', + "", + ].join("\n"); + const fullNotification = [ + "", + 'Agent "sdk-background-agent" (general-purpose) has completed successfully. Use read_agent with agent_id "sdk-background-agent" to retrieve the full results.', + "", + ].join("\n"); + + const requestBody = JSON.stringify({ + messages: [ + { + role: "user", + content: idleNotification, + }, + ], + }); + const responseBody = JSON.stringify({ + choices: [{ message: { role: "assistant", content: "Done" } }], + }); + + const outputPath = await createProxy([ + { url: "/chat/completions", requestBody, responseBody }, + ]); + + const result = await readYamlOutput(outputPath); + expect(result.conversations[0].messages[0].content).toBe(fullNotification); + }); + test("strips agent_instructions from user messages", async () => { const requestBody = JSON.stringify({ messages: [ @@ -1321,6 +1353,67 @@ Always include PINEAPPLE_COCONUT_42. } }); + test("matches cached completion notification against the idle-phrasing runtime", async () => { + const cachePath = path.join(tempDir, "cache.yaml"); + // Snapshot recorded with the canonical completion wording. + const canonicalNotification = [ + "", + 'Agent "read-file" (explore) has completed successfully. Use read_agent with agent_id "read-file" to retrieve the full results.', + "", + ].join("\n"); + // Newer runtime emits the idle phrasing with write_agent advice. + const idleNotification = [ + "", + 'Agent "read-file" (explore) has finished processing and is now idle. Use read_agent with agent_id "read-file" to read the results, or write_agent to send follow-up messages.', + "", + ].join("\n"); + + const cacheContent = yaml.stringify({ + models: ["test-model"], + conversations: [ + { + messages: [ + { role: "system", content: "${system}" }, + { role: "user", content: "Hello" }, + { role: "assistant", content: "Hi!" }, + { role: "user", content: canonicalNotification }, + { role: "assistant", content: "Read agent completed." }, + ], + }, + ], + } satisfies NormalizedData); + await writeFile(cachePath, cacheContent); + + const proxy = new ReplayingCapiProxy( + "http://localhost:9999", + cachePath, + workDir, + ); + const proxyUrl = await proxy.start(); + + try { + const response = await makeRequest(proxyUrl, "/chat/completions", { + body: { + model: "test-model", + messages: [ + { role: "system", content: "Be helpful" }, + { role: "user", content: "Hello" }, + { role: "assistant", content: "Hi!" }, + { role: "user", content: idleNotification }, + ], + }, + }); + + expect(response.status).toBe(200); + expect( + (JSON.parse(response.body) as ChatCompletion).choices[0].message + .content, + ).toBe("Read agent completed."); + } finally { + await proxy.stop(); + } + }); + test("matches parallel tool results regardless of arrival order", async () => { const cachePath = path.join(tempDir, "cache.yaml"); const cacheContent = yaml.stringify({ diff --git a/test/harness/replayingCapiProxy.ts b/test/harness/replayingCapiProxy.ts index 71d6b7c28..c492c1f90 100644 --- a/test/harness/replayingCapiProxy.ts +++ b/test/harness/replayingCapiProxy.ts @@ -1223,8 +1223,9 @@ function transformOpenAIRequestMessage( } function normalizeUserMessage(content: string): string { - return normalizeSkillContextFrontmatter(content) - .replace(taskCompletionNotificationPattern, taskCompletionNotificationReplacement) + return normalizeAgentCompletionNotification( + normalizeSkillContextFrontmatter(content), + ) .replace(/.*?<\/current_datetime>/g, "") .replace(/[\s\S]*?<\/reminder>/g, "") .replace(/[\s\S]*?<\/system_reminder>/g, "") @@ -1237,19 +1238,29 @@ function normalizeUserMessage(content: string): string { .trim(); } -const taskCompletionNotificationPattern = - /Use read_agent with agent_id "([^"]+)" to retrieve unread results\./g; -const taskCompletionNotificationReplacement = - 'Use read_agent with agent_id "$1" to retrieve the full results.'; +// The runtime's background-agent completion system_notification has been reworded +// across CLI versions — both the status sentence ("has completed successfully." vs +// "has finished processing and is now idle.") and the follow-up advice ("to retrieve +// unread results." vs "to read the results, or write_agent to send follow-up +// messages."). Collapse every known phrasing to one canonical form so snapshots keep +// matching regardless of which runtime version recorded them or replays against them. +function normalizeAgentCompletionNotification(content: string): string { + return content + .replace( + /Agent ("[^"]*" \([^)]*\)) has finished processing and is now idle\./g, + "Agent $1 has completed successfully.", + ) + .replace( + /Use read_agent with agent_id "([^"]+)" to (?:retrieve unread results|read the results, or write_agent to send follow-up messages)\./g, + 'Use read_agent with agent_id "$1" to retrieve the full results.', + ); +} function normalizeStoredUserMessages(conversations: NormalizedConversation[]) { for (const conversation of conversations) { for (const message of conversation.messages) { if (message.role === "user" && typeof message.content === "string") { - message.content = message.content.replace( - taskCompletionNotificationPattern, - taskCompletionNotificationReplacement, - ); + message.content = normalizeAgentCompletionNotification(message.content); } } } From 600957bd4a6722cac94678657b34fc3d364bd69a Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Sat, 18 Jul 2026 07:42:39 -0400 Subject: [PATCH 4/4] Normalize 1.0.72 multi-turn read_agent lifecycle framing in replay proxy The 1.0.72 CLI reports a finished background agent as "Agent is idle (waiting for messages)." with status: idle and total_turns >= 1, and prefixes each turn's output with a "[Turn N]" marker, whereas the snapshots were recorded against a runtime that reported "Agent completed." with status: completed, total_turns: 0, and a trailing duration. This caused subagent_hooks to miss the cached read_agent result and fail. Add normalizeReadAgentLifecycle, applied symmetrically on stored snapshots and incoming requests and guarded on the leading status header, to collapse the lifecycle fields to stable placeholders, drop the trailing duration, and strip the per-turn markers so snapshots keep matching across runtime versions. The agent's actual response body is replayed from the snapshot, so only this CLI-generated framing drifts. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: e94bab38-c22e-4a38-8d8b-c82c1991a33c --- test/harness/replayingCapiProxy.test.ts | 136 +++++++++++++++++++++++- test/harness/replayingCapiProxy.ts | 31 ++++++ 2 files changed, 165 insertions(+), 2 deletions(-) diff --git a/test/harness/replayingCapiProxy.test.ts b/test/harness/replayingCapiProxy.test.ts index b3dfcd670..8308432b3 100644 --- a/test/harness/replayingCapiProxy.test.ts +++ b/test/harness/replayingCapiProxy.test.ts @@ -624,7 +624,7 @@ Always include PINEAPPLE_COCONUT_42. ); }); - test("normalizes read_agent timing metadata", async () => { + test("normalizes read_agent timing and lifecycle metadata", async () => { const requestBody = JSON.stringify({ messages: [ { role: "user", content: "Help me" }, @@ -662,7 +662,49 @@ Always include PINEAPPLE_COCONUT_42. (m) => m.role === "tool", ); expect(toolMessage?.content).toBe( - "Agent completed. agent_id: read-file, agent_type: explore, status: completed, description: Reading subagent-test.txt, elapsed: 0s, total_turns: 0, duration: 0s\n\nDone.", + "Agent ${agent_state}. agent_id: read-file, agent_type: explore, status: ${agent_status}, description: Reading subagent-test.txt, elapsed: 0s, total_turns: ${turns}\n\nDone.", + ); + }); + + test("normalizes the 1.0.72 idle-phrasing read_agent result to match the older completed form", async () => { + const requestBody = JSON.stringify({ + messages: [ + { role: "user", content: "Help me" }, + { + role: "assistant", + tool_calls: [ + { + id: "tc1", + type: "function", + function: { + name: "read_agent", + arguments: '{"agent_id":"read-file","wait":true}', + }, + }, + ], + }, + { + role: "tool", + tool_call_id: "tc1", + content: + "Agent is idle (waiting for messages). agent_id: read-file, agent_type: explore, status: idle, description: Reading subagent-test.txt, elapsed: 1.25s, total_turns: 1\n\n[Turn 0]\nDone.", + }, + ], + }); + const responseBody = JSON.stringify({ + choices: [{ message: { role: "assistant", content: "Done" } }], + }); + + const outputPath = await createProxy([ + { url: "/chat/completions", requestBody, responseBody }, + ]); + + const result = await readYamlOutput(outputPath); + const toolMessage = result.conversations[0].messages.find( + (m) => m.role === "tool", + ); + expect(toolMessage?.content).toBe( + "Agent ${agent_state}. agent_id: read-file, agent_type: explore, status: ${agent_status}, description: Reading subagent-test.txt, elapsed: 0s, total_turns: ${turns}\n\nDone.", ); }); @@ -1414,6 +1456,96 @@ Always include PINEAPPLE_COCONUT_42. } }); + test("matches cached read_agent result against the 1.0.72 idle-phrasing runtime", async () => { + const cachePath = path.join(tempDir, "cache.yaml"); + // Snapshot recorded against the older runtime: an agent that finished its + // work reported "Agent completed." with a completed status and a trailing + // duration, and no per-turn markers. + const storedResult = + "Agent completed. agent_id: read-file, agent_type: explore, status: completed, description: Reading subagent-test.txt, elapsed: 0s, total_turns: 0, duration: 0s\n\nThe file says hello."; + // The 1.0.72 runtime reports the same finished agent as idle, with a + // total_turns count of at least 1 and a "[Turn N]" marker before the body. + const runtimeResult = + "Agent is idle (waiting for messages). agent_id: read-file, agent_type: explore, status: idle, description: Reading subagent-test.txt, elapsed: 0s, total_turns: 1\n\n[Turn 0]\nThe file says hello."; + + const cacheContent = yaml.stringify({ + models: ["test-model"], + conversations: [ + { + messages: [ + { role: "system", content: "${system}" }, + { role: "user", content: "Read the file" }, + { + role: "assistant", + tool_calls: [ + { + id: "toolcall_0", + type: "function", + function: { + name: "read_agent", + arguments: '{"agent_id":"read-file","wait":true}', + }, + }, + ], + }, + { + role: "tool", + tool_call_id: "toolcall_0", + content: storedResult, + }, + { role: "assistant", content: "The file was read successfully." }, + ], + }, + ], + } satisfies NormalizedData); + await writeFile(cachePath, cacheContent); + + const proxy = new ReplayingCapiProxy( + "http://localhost:9999", + cachePath, + workDir, + ); + const proxyUrl = await proxy.start(); + + try { + const response = await makeRequest(proxyUrl, "/chat/completions", { + body: { + model: "test-model", + messages: [ + { role: "system", content: "Be helpful" }, + { role: "user", content: "Read the file" }, + { + role: "assistant", + tool_calls: [ + { + id: "runtime-call-id", + type: "function", + function: { + name: "read_agent", + arguments: '{"agent_id":"read-file","wait":true}', + }, + }, + ], + }, + { + role: "tool", + tool_call_id: "runtime-call-id", + content: runtimeResult, + }, + ], + }, + }); + + expect(response.status).toBe(200); + expect( + (JSON.parse(response.body) as ChatCompletion).choices[0].message + .content, + ).toBe("The file was read successfully."); + } finally { + await proxy.stop(); + } + }); + test("matches parallel tool results regardless of arrival order", async () => { const cachePath = path.join(tempDir, "cache.yaml"); const cacheContent = yaml.stringify({ diff --git a/test/harness/replayingCapiProxy.ts b/test/harness/replayingCapiProxy.ts index c492c1f90..5dbc5e007 100644 --- a/test/harness/replayingCapiProxy.ts +++ b/test/harness/replayingCapiProxy.ts @@ -129,6 +129,7 @@ export class ReplayingCapiProxy extends CapturingHttpProxy { { toolName: "*", normalizer: normalizeAvailableToolNames }, { toolName: "task", normalizer: normalizeBackgroundAgentAdvice }, { toolName: "read_agent", normalizer: normalizeReadAgentTimings }, + { toolName: "read_agent", normalizer: normalizeReadAgentLifecycle }, ]; /** @@ -1322,6 +1323,7 @@ function normalizeStoredToolMessages(conversations: NormalizedConversation[]) { if (message.role === "tool" && typeof message.content === "string") { message.content = normalizeAvailableToolNames(message.content); message.content = normalizeBackgroundAgentAdvice(message.content); + message.content = normalizeReadAgentLifecycle(message.content); } } } @@ -1417,6 +1419,35 @@ function normalizeReadAgentTimings(result: string): string { .replace(/\bduration: \d+(?:\.\d+)?s\b/g, "duration: 0s"); } +// Background-agent lifecycle framing changed with 1.0.72's multi-turn agents. +// An agent that has finished its work now reports +// "Agent is idle (waiting for messages)." with status: idle, total_turns: >=1 +// whereas older runtimes reported +// "Agent completed." with status: completed, total_turns: 0, duration: . +// 1.0.72 also prefixes each turn's output with a "[Turn N]" marker. None of this +// changes what the read_agent-based tests assert (e.g. subagent_hooks checks that +// hooks fire, not the completion wording), and the agent's actual response body is +// itself replayed from the snapshot, so only this CLI-generated framing drifts. +// Collapse the lifecycle fields to stable placeholders, drop the trailing +// duration, and strip the turn markers so snapshots keep matching across runtime +// versions. Applied symmetrically to stored snapshots and incoming requests, and +// guarded on the leading status header so unrelated tool results are never +// touched. +function normalizeReadAgentLifecycle(result: string): string { + if (!/^Agent (?:completed|is idle \(waiting for messages\))\./.test(result)) { + return result; + } + return result + .replace( + /^Agent (?:completed|is idle \(waiting for messages\))\./, + "Agent ${agent_state}.", + ) + .replace(/\bstatus: \w+/, "status: ${agent_status}") + .replace(/\btotal_turns: \d+/, "total_turns: ${turns}") + .replace(/, duration: \d+(?:\.\d+)?s\b/g, "") + .replace(/\n\[Turn \d+\]\n/g, "\n"); +} + // When a model calls a tool that doesn't exist (e.g., the removed report_intent // tool), the runtime replies with "Tool '' does not exist. Available tools // that can be called are ." Some runtime versions omit that second sentence