Skip to content

Commit c34785f

Browse files
github-actions[bot]stephentoubCopilot
authored
Update @github/copilot to 1.0.56 (#1504)
* Update @github/copilot to 1.0.56 - Updated nodejs and test harness dependencies - Re-ran code generators - Formatted generated code * test: cover pending external tool resume in both warm and cold modes Runtime 1.0.56 changed disconnect semantics so that ForceStop of the last RPC owner now triggers session cleanup. The previous warm-only test for `Should_Keep_Pending_External_Tool_Handleable_On_*_Resume_When_ContinuePendingWork_Is_False` asserted SessionWasActive == true and that `handlePendingToolCall` fed a result into the assistant reply -- assumptions that only hold for warm resume. On cold resume the runtime intentionally auto-completes orphan tool calls with a synthetic interrupt result, so the SDK call correctly returns success=false. Split that test into two scenarios across Node, Go, .NET, and Python: - Warm (original client stays connected): SessionWasActive=true, handlePendingToolCall returns success=true, and the assistant echoes the supplied result. - Cold (original client ForceStopped before resume): SessionWasActive=false, handlePendingToolCall returns success=false, and a follow-up turn confirms the resumed session is still healthy. In warm mode the resumed client must not re-register the external tool (it would clash with the original owner); in cold mode it re-registers with a throwing handler to assert the runtime does not re-invoke the handler on resume. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Skip pending-resume tests broken by runtime cold-cleanup contract Runtime PR #9040 (commit b8e1220b45) made SDKServer.handleConnectionClosed clean up session state when the last RPC owner disconnects. As a result, the existing Should_Continue_Pending_{Permission,External_Tool}_Request_ After_Resume tests, which ForceStop the suspended client and then expect the resumed client to successfully complete the pending request, no longer pass: HandlePendingPermissionRequest/HandlePendingToolCall return success=false after the cold cleanup runs. A runtime-side fix is being tracked separately. Skip these two tests in all four SDKs (.NET, Go, Node, Python) with a TODO referencing the runtime contract change so they can be re-enabled once that work lands. Also drop the GetFinalAssistantMessageAsync continuation assertions from the .NET and Go warm theory and from the older OLD tests, matching the Node and Python tests which never made that assertion. The ContinuePendingWork=false warm path provides no guarantee that the suspended client's agentic loop will propagate a final assistant.message to the resumed client, and waiting for one was flaking on Linux .NET CI. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Reword pending-resume skip messages to match actual runtime contract The previous wording ('Pending runtime fix') implied a runtime change was being tracked separately. That isn't accurate: runtime 1.0.56 (copilot-agent-runtime PR #9040) intentionally cleans up the session when the last RPC client disconnects, and there is no in-flight fix planned. The OLD tests model same-process ForceStop+resume and rely on the old behavior where the session stayed alive in the runtime process. They need to be redesigned to either keep an owner connected (warm resume) or to model true process-restart resume from persisted session state. No code change beyond the skip-reason text; tests remain skipped while the redesign is figured out. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Stephen Toub <stoub@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent d882812 commit c34785f

15 files changed

Lines changed: 583 additions & 404 deletions

File tree

dotnet/src/Generated/Rpc.cs

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dotnet/test/E2E/PendingWorkResumeE2ETests.cs

Lines changed: 70 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,18 @@ public class PendingWorkResumeE2ETests(E2ETestFixture fixture, ITestOutputHelper
1919
private static readonly TimeSpan PendingWorkTimeout = TimeSpan.FromSeconds(60);
2020
private const string SharedToken = "pending-work-resume-shared-token";
2121

22-
[Fact]
22+
// Skipped after the runtime 1.0.56 bump. Runtime PR #9040 (commit b8e1220b45) changed
23+
// SDKServer.handleConnectionClosed to tear down the session when the last RPC client
24+
// disconnects, so the in-memory pending permission request is gone before the resumed
25+
// client can satisfy it and HandlePendingPermissionRequest returns success=false. This
26+
// test models same-process ForceStop+resume; it needs to be redesigned to either keep
27+
// an owner connected (warm resume) or to model a true process restart against the
28+
// persisted session state.
29+
[Fact(Skip = "Runtime 1.0.56 cleans up the session on last-client disconnect (copilot-agent-runtime PR #9040), so the in-memory pending request is gone before resume can satisfy it. Test needs redesign.")]
2330
public async Task Should_Continue_Pending_Permission_Request_After_Resume()
2431
{
2532
var originalPermissionRequest = new TaskCompletionSource<PermissionRequest>(TaskCreationOptions.RunContinuationsAsynchronously);
2633
var releaseOriginalPermission = new TaskCompletionSource<PermissionDecision>(TaskCreationOptions.RunContinuationsAsynchronously);
27-
var resumedToolInvoked = false;
2834

2935
await using var server = Ctx.CreateClient(options: new CopilotClientOptions { Connection = RuntimeConnection.ForTcp(connectionToken: SharedToken) });
3036
await server.StartAsync();
@@ -66,10 +72,7 @@ await session1.SendAsync(new MessageOptions
6672
[
6773
AIFunctionFactory.Create(
6874
([Description("Value to transform")] string value) =>
69-
{
70-
resumedToolInvoked = true;
71-
return $"PERMISSION_RESUMED_{value.ToUpperInvariant()}";
72-
},
75+
$"PERMISSION_RESUMED_{value.ToUpperInvariant()}",
7376
"resume_permission_tool")
7477
],
7578
});
@@ -79,11 +82,6 @@ await session1.SendAsync(new MessageOptions
7982
new RpcPermissionDecisionApproveOnce());
8083
Assert.True(permissionResult.Success);
8184

82-
var answer = await TestHelper.GetFinalAssistantMessageAsync(session2, PendingWorkTimeout);
83-
84-
Assert.True(resumedToolInvoked);
85-
Assert.Contains("PERMISSION_RESUMED_ALPHA", answer?.Data.Content ?? string.Empty);
86-
8785
await session2.DisposeAsync();
8886
await resumedTcpClient.ForceStopAsync();
8987
}
@@ -97,7 +95,12 @@ static string ResumePermissionTool([Description("Value to transform")] string va
9795
$"ORIGINAL_SHOULD_NOT_RUN_{value}";
9896
}
9997

100-
[Fact]
98+
// Skipped for the same reason as Should_Continue_Pending_Permission_Request_After_Resume:
99+
// runtime 1.0.56 (copilot-agent-runtime PR #9040) tears down the session when the last
100+
// RPC client disconnects, so the in-memory pending external tool call is gone before
101+
// the resumed client can satisfy it. Needs redesign to keep an owner connected (warm)
102+
// or to model true process-restart resume from persisted state.
103+
[Fact(Skip = "Runtime 1.0.56 cleans up the session on last-client disconnect (copilot-agent-runtime PR #9040), so the in-memory pending tool call is gone before resume can satisfy it. Test needs redesign.")]
101104
public async Task Should_Continue_Pending_External_Tool_Request_After_Resume()
102105
{
103106
var originalToolStarted = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);
@@ -140,10 +143,6 @@ await session1.SendAsync(new MessageOptions
140143
result: JsonDocument.Parse("\"EXTERNAL_RESUMED_BETA\"").RootElement.Clone());
141144
Assert.True(toolResult.Success);
142145

143-
var answer = await TestHelper.GetFinalAssistantMessageAsync(session2, PendingWorkTimeout);
144-
145-
Assert.Contains("EXTERNAL_RESUMED_BETA", answer?.Data.Content ?? string.Empty);
146-
147146
await session2.DisposeAsync();
148147
await resumedClient.ForceStopAsync();
149148
}
@@ -161,7 +160,23 @@ async Task<string> BlockingExternalTool([Description("Value to look up")] string
161160
}
162161

163162
[Fact]
164-
public async Task Should_Keep_Pending_External_Tool_Handleable_On_Warm_Resume_When_ContinuePendingWork_Is_False()
163+
public Task Should_Keep_Pending_External_Tool_Handleable_On_Warm_Resume_When_ContinuePendingWork_Is_False() =>
164+
AssertPendingExternalToolHandleableOnResumeAsync(
165+
disconnectOriginalClient: false,
166+
expectedSessionWasActive: true,
167+
expectedHandleResult: true);
168+
169+
[Fact]
170+
public Task Should_Keep_Pending_External_Tool_Handleable_On_Cold_Resume_When_ContinuePendingWork_Is_False() =>
171+
AssertPendingExternalToolHandleableOnResumeAsync(
172+
disconnectOriginalClient: true,
173+
expectedSessionWasActive: false,
174+
expectedHandleResult: false);
175+
176+
private async Task AssertPendingExternalToolHandleableOnResumeAsync(
177+
bool disconnectOriginalClient,
178+
bool expectedSessionWasActive,
179+
bool expectedHandleResult)
165180
{
166181
var originalToolStarted = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);
167182
var releaseOriginalTool = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);
@@ -191,28 +206,54 @@ await session1.SendAsync(new MessageOptions
191206
var toolEvent = await toolRequested;
192207
Assert.Equal("beta", await originalToolStarted.Task.WaitAsync(PendingWorkTimeout));
193208

194-
await suspendedClient.ForceStopAsync();
209+
if (disconnectOriginalClient)
210+
{
211+
await suspendedClient.ForceStopAsync();
212+
}
195213

196214
await using var resumedClient = Ctx.CreateClient(options: new CopilotClientOptions { Connection = RuntimeConnection.ForUri(cliUrl, connectionToken: SharedToken) });
197-
var session2 = await resumedClient.ResumeSessionAsync(sessionId, new ResumeSessionConfig
215+
216+
// In warm mode the original client still owns the tool registration;
217+
// re-registering it from the resumed client would cause a name-clash. In
218+
// cold mode the original is gone, so we register a fresh throwing handler
219+
// to assert the runtime doesn't re-invoke the tool on resume (orphan
220+
// auto-completion happens internally).
221+
var resumeConfig = new ResumeSessionConfig
198222
{
199223
ContinuePendingWork = false,
200224
OnPermissionRequest = PermissionHandler.ApproveAll,
201-
});
225+
};
226+
if (disconnectOriginalClient)
227+
{
228+
resumeConfig.Tools = [AIFunctionFactory.Create(ResumedExternalTool, "resume_external_tool")];
229+
}
230+
231+
var session2 = await resumedClient.ResumeSessionAsync(sessionId, resumeConfig);
202232

203233
var resumeEvent = await GetSingleResumeEventAsync(session2);
204234
Assert.Equal(false, resumeEvent.Data.ContinuePendingWork);
205-
Assert.Equal(true, resumeEvent.Data.SessionWasActive);
235+
Assert.Equal(expectedSessionWasActive, resumeEvent.Data.SessionWasActive);
206236

237+
// Warm: the runtime still has the pending request and HandlePendingToolCall
238+
// will succeed.
239+
// Cold: the runtime auto-completed the orphaned tool call with a synthetic
240+
// interrupt result during resume, so HandlePendingToolCall correctly reports
241+
// success=false. The session should still be healthy for new turns.
207242
var resumedResult = await session2.Rpc.Tools.HandlePendingToolCallAsync(
208243
toolEvent.Data.RequestId,
209244
result: JsonDocument.Parse("\"EXTERNAL_RESUMED_BETA\"").RootElement.Clone());
210-
Assert.True(resumedResult.Success);
211-
212-
// continuePendingWork=false may interrupt agent continuation before this response,
213-
// but the pending call should still accept an explicit completion.
245+
Assert.Equal(expectedHandleResult, resumedResult.Success);
214246
Assert.Equal(1, invocationCount);
215247

248+
if (!expectedHandleResult)
249+
{
250+
var followUp = await session2.SendAndWaitAsync(new MessageOptions
251+
{
252+
Prompt = "Reply with exactly: COLD_RESUMED_FOLLOWUP",
253+
});
254+
Assert.Contains("COLD_RESUMED_FOLLOWUP", followUp?.Data.Content ?? string.Empty);
255+
}
256+
216257
await session2.DisposeAsync();
217258
await resumedClient.ForceStopAsync();
218259
}
@@ -228,6 +269,10 @@ async Task<string> BlockingExternalTool([Description("Value to look up")] string
228269
originalToolStarted.TrySetResult(value);
229270
return await releaseOriginalTool.Task;
230271
}
272+
273+
[Description("Looks up a value after resumption")]
274+
string ResumedExternalTool([Description("Value to look up")] string value) =>
275+
throw new InvalidOperationException("Resumed-session handler should not be invoked");
231276
}
232277

233278
[Fact]

0 commit comments

Comments
 (0)