Skip to content

Commit 105d1d5

Browse files
stephentoubCopilot
andcommitted
Normalize interrupted shell replay results
Treat runtime shell-context reconfiguration as the same semantic interruption already represented by abort cassettes, while retaining strict matching for unrelated tool failures. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent 21d738e commit 105d1d5

2 files changed

Lines changed: 132 additions & 5 deletions

File tree

test/harness/replayingCapiProxy.test.ts

Lines changed: 131 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -546,7 +546,7 @@ Always include PINEAPPLE_COCONUT_42.
546546
expect(toolMessage?.content).toBe("Tool 'report_intent' does not exist.");
547547
});
548548

549-
test("normalizes aborted tool execution results", async () => {
549+
test("normalizes interrupted tool execution results", async () => {
550550
const requestBody = JSON.stringify({
551551
messages: [
552552
{ role: "user", content: "Run a slow analysis" },
@@ -561,6 +561,14 @@ Always include PINEAPPLE_COCONUT_42.
561561
arguments: '{"value":"test_abort"}',
562562
},
563563
},
564+
{
565+
id: "tc2",
566+
type: "function",
567+
function: {
568+
name: "powershell",
569+
arguments: '{"command":"sleep 100"}',
570+
},
571+
},
564572
],
565573
},
566574
{
@@ -569,6 +577,11 @@ Always include PINEAPPLE_COCONUT_42.
569577
content:
570578
'Failed to execute `slow_analysis` tool with arguments: {"value":"test_abort"} due to error: Error: Session aborted',
571579
},
580+
{
581+
role: "tool",
582+
tool_call_id: "tc2",
583+
content: "<shell context is being reconfigured; retry the command>",
584+
},
572585
],
573586
});
574587
const responseBody = JSON.stringify({
@@ -580,12 +593,13 @@ Always include PINEAPPLE_COCONUT_42.
580593
]);
581594

582595
const result = await readYamlOutput(outputPath);
583-
const toolMessage = result.conversations[0].messages.find(
596+
const toolMessages = result.conversations[0].messages.filter(
584597
(m) => m.role === "tool",
585598
);
586-
expect(toolMessage?.content).toBe(
599+
expect(toolMessages.map((message) => message.content)).toEqual([
587600
"The execution of this tool, or a previous tool was interrupted.",
588-
);
601+
"The execution of this tool, or a previous tool was interrupted.",
602+
]);
589603
});
590604

591605
test("normalizes background agent IDs and removes runtime advisories", async () => {
@@ -945,6 +959,119 @@ Always include PINEAPPLE_COCONUT_42.
945959
}
946960
});
947961

962+
test("matches semantically equivalent interrupted shell results", async () => {
963+
const originalShellConfig =
964+
process.platform === "win32"
965+
? ShellConfig.powerShell
966+
: ShellConfig.bash;
967+
const cachePath = path.join(tempDir, "cache.yaml");
968+
const cacheContent = yaml.stringify({
969+
models: ["test-model"],
970+
conversations: [
971+
{
972+
messages: [
973+
{ role: "system", content: "${system}" },
974+
{ role: "user", content: "Run command" },
975+
{
976+
role: "assistant",
977+
tool_calls: [
978+
{
979+
id: "toolcall_0",
980+
type: "function",
981+
function: {
982+
name: "${shell}",
983+
arguments: '{"command":"sleep 100"}',
984+
},
985+
},
986+
],
987+
},
988+
{
989+
role: "tool",
990+
tool_call_id: "toolcall_0",
991+
content:
992+
"The execution of this tool, or a previous tool was interrupted.",
993+
},
994+
{ role: "assistant", content: "Ready for another request." },
995+
],
996+
},
997+
],
998+
} satisfies NormalizedData);
999+
await writeFile(cachePath, cacheContent);
1000+
1001+
const proxy = new ReplayingCapiProxy(
1002+
"http://localhost:9999",
1003+
cachePath,
1004+
workDir,
1005+
);
1006+
const proxyUrl = await proxy.start();
1007+
1008+
try {
1009+
const messages = [
1010+
{ role: "system", content: "System prompt" },
1011+
{ role: "user", content: "Run command" },
1012+
{
1013+
role: "assistant",
1014+
tool_calls: [
1015+
{
1016+
id: "runtime-call-id",
1017+
type: "function",
1018+
function: {
1019+
name: originalShellConfig.shellToolName,
1020+
arguments: '{"command":"sleep 100"}',
1021+
},
1022+
},
1023+
],
1024+
},
1025+
];
1026+
const interruptedResponse = await makeRequest(
1027+
proxyUrl,
1028+
"/chat/completions",
1029+
{
1030+
body: {
1031+
model: "test-model",
1032+
messages: [
1033+
...messages,
1034+
{
1035+
role: "tool",
1036+
tool_call_id: "runtime-call-id",
1037+
content:
1038+
"<shell context is being reconfigured; retry the command>",
1039+
},
1040+
],
1041+
},
1042+
},
1043+
);
1044+
1045+
expect(interruptedResponse.status).toBe(200);
1046+
expect(
1047+
(JSON.parse(interruptedResponse.body) as ChatCompletion).choices[0]
1048+
.message.content,
1049+
).toBe("Ready for another request.");
1050+
1051+
const meaningfulErrorResponse = await makeRequest(
1052+
proxyUrl,
1053+
"/chat/completions",
1054+
{
1055+
body: {
1056+
model: "test-model",
1057+
messages: [
1058+
...messages,
1059+
{
1060+
role: "tool",
1061+
tool_call_id: "runtime-call-id",
1062+
content:
1063+
"The command failed because the executable was missing.",
1064+
},
1065+
],
1066+
},
1067+
},
1068+
);
1069+
expect(meaningfulErrorResponse.status).toBe(500);
1070+
} finally {
1071+
await proxy.stop();
1072+
}
1073+
});
1074+
9481075
test("matches available-tools results after the built-in tool set changes", async () => {
9491076
const cachePath = path.join(tempDir, "cache.yaml");
9501077
// Legacy snapshot recorded before write_agent was a built-in tool: the

test/harness/replayingCapiProxy.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1556,7 +1556,7 @@ function normalizeAvailableToolNames(result: string): string {
15561556

15571557
function normalizeInterruptedToolResult(result: string): string {
15581558
return result.replace(
1559-
/^Failed to execute `[^`]+` tool(?: with arguments: [\s\S]*?)? due to error: (?:Error: )?Session aborted$/,
1559+
/^(?:Failed to execute `[^`]+` tool(?: with arguments: [\s\S]*?)? due to error: (?:Error: )?Session aborted|<shell context is being reconfigured; retry the command>)$/,
15601560
"The execution of this tool, or a previous tool was interrupted.",
15611561
);
15621562
}

0 commit comments

Comments
 (0)