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",
diff --git a/test/harness/replayingCapiProxy.test.ts b/test/harness/replayingCapiProxy.test.ts
index 009a1e40a..8308432b3 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: [
@@ -509,7 +541,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,14 +575,56 @@ 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.",
+ );
+ });
+
+ 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(
- "Tool 'report_intent' does not exist. Available tools that can be called are ${available_tools}.",
+ "Agent started in background with agent_id: read-file. ${background_agent_advice}",
);
});
- 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" },
@@ -588,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.",
);
});
@@ -947,6 +1063,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({
@@ -1122,6 +1395,157 @@ 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 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 1c29fb755..5dbc5e007 100644
--- a/test/harness/replayingCapiProxy.ts
+++ b/test/harness/replayingCapiProxy.ts
@@ -127,7 +127,9 @@ export class ReplayingCapiProxy extends CapturingHttpProxy {
{ toolName: "${shell}", normalizer: normalizeShellExitMarkers },
{ toolName: "*", normalizer: normalizeGhAuthMessages },
{ toolName: "*", normalizer: normalizeAvailableToolNames },
+ { toolName: "task", normalizer: normalizeBackgroundAgentAdvice },
{ toolName: "read_agent", normalizer: normalizeReadAgentTimings },
+ { toolName: "read_agent", normalizer: normalizeReadAgentLifecycle },
];
/**
@@ -1222,8 +1224,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, "")
@@ -1236,19 +1239,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);
}
}
}
@@ -1299,17 +1312,18 @@ 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);
+ message.content = normalizeReadAgentLifecycle(message.content);
}
}
}
@@ -1405,21 +1419,64 @@ 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}";
+// 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 "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}`,
);
}