diff --git a/dotnet/src/Session.cs b/dotnet/src/Session.cs index 34f4d02d5..00c410cbc 100644 --- a/dotnet/src/Session.cs +++ b/dotnet/src/Session.cs @@ -580,6 +580,11 @@ await InvokeRpcAsync( // Connection is broken or closed } + // Wait briefly for any final notifications (e.g., session.shutdown) + // to arrive before clearing handlers. The CLI may send these after + // the RPC response. + await Task.Delay(100); + _eventHandlers.Clear(); _toolHandlers.Clear(); diff --git a/go/session.go b/go/session.go index ce1a3eff0..7012fb309 100644 --- a/go/session.go +++ b/go/session.go @@ -531,6 +531,11 @@ func (s *Session) Destroy() error { return fmt.Errorf("failed to destroy session: %w", err) } + // Wait briefly for any final notifications (e.g., session.shutdown) + // to arrive before clearing handlers. The CLI may send these after + // the RPC response. + time.Sleep(100 * time.Millisecond) + // Clear handlers s.handlerMutex.Lock() s.handlers = nil diff --git a/nodejs/examples/test-shutdown-event.ts b/nodejs/examples/test-shutdown-event.ts new file mode 100644 index 000000000..9ab3c0e85 --- /dev/null +++ b/nodejs/examples/test-shutdown-event.ts @@ -0,0 +1,42 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import { CopilotClient } from "../src/index.js"; + +console.log("šŸš€ Testing session.shutdown event\n"); + +// Create client +const client = new CopilotClient({ logLevel: "info" }); +const session = await client.createSession(); +console.log(`āœ… Session created: ${session.sessionId}\n`); + +// Track if we receive the shutdown event +let shutdownEventReceived = false; + +// Listen to all events +session.on((event) => { + console.log(`šŸ“¢ Event [${event.type}]`); + if (event.type === "session.shutdown") { + shutdownEventReceived = true; + console.log("āœ… SHUTDOWN EVENT RECEIVED!"); + console.log(" Data:", JSON.stringify(event.data, null, 2)); + } +}); + +// Send a simple message +console.log("šŸ’¬ Sending message..."); +const result = await session.sendAndWait({ prompt: "What is 2+2?" }); +console.log("šŸ“ Response:", result?.data.content); + +// Clean up +console.log("\nšŸ”„ Destroying session..."); +await session.destroy(); + +// Give a bit more time for any delayed events +await new Promise(resolve => setTimeout(resolve, 200)); + +console.log("\n" + (shutdownEventReceived ? "āœ… SUCCESS: session.shutdown event was received!" : "āŒ FAILURE: session.shutdown event was NOT received!")); + +await client.stop(); +console.log("\nāœ… Done!"); diff --git a/nodejs/src/session.ts b/nodejs/src/session.ts index 04525d2bb..ca2d7a253 100644 --- a/nodejs/src/session.ts +++ b/nodejs/src/session.ts @@ -518,6 +518,12 @@ export class CopilotSession { await this.connection.sendRequest("session.destroy", { sessionId: this.sessionId, }); + + // Wait briefly for any final notifications (e.g., session.shutdown) + // to arrive before clearing handlers. The CLI may send these after + // the RPC response. + await new Promise((resolve) => setTimeout(resolve, 100)); + this.eventHandlers.clear(); this.typedEventHandlers.clear(); this.toolHandlers.clear(); diff --git a/python/copilot/session.py b/python/copilot/session.py index d7bd1a3f4..4cc5210fd 100644 --- a/python/copilot/session.py +++ b/python/copilot/session.py @@ -487,6 +487,12 @@ async def destroy(self) -> None: >>> await session.destroy() """ await self._client.request("session.destroy", {"sessionId": self.session_id}) + + # Wait briefly for any final notifications (e.g., session.shutdown) + # to arrive before clearing handlers. The CLI may send these after + # the RPC response. + await asyncio.sleep(0.1) + with self._event_handlers_lock: self._event_handlers.clear() with self._tool_handlers_lock: