Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions dotnet/src/Session.cs
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,11 @@ await InvokeRpcAsync<object>(
// 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();

Expand Down
5 changes: 5 additions & 0 deletions go/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
42 changes: 42 additions & 0 deletions nodejs/examples/test-shutdown-event.ts
Original file line number Diff line number Diff line change
@@ -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!");
6 changes: 6 additions & 0 deletions nodejs/src/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
6 changes: 6 additions & 0 deletions python/copilot/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down