Skip to content

Commit c129794

Browse files
dsuresh-apclaude
andauthored
feat(conversations): expose disconnect method for clean process exit (#340)
The SessionManager already has a disconnect() method but it was not exposed through the public ConversationService API. CLI consumers that use startSession/endSession had no way to close the underlying WebSocket, causing the process to hang indefinitely due to socket.io reconnection timers keeping the event loop alive. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 97d17a7 commit c129794

3 files changed

Lines changed: 40 additions & 0 deletions

File tree

src/models/conversational-agent/conversations/conversations.models.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,20 @@ export interface ConversationServiceModel {
364364
* @internal
365365
*/
366366
onConnectionStatusChanged(handler: ConnectionStatusChangedHandler): () => void;
367+
368+
/**
369+
* Closes the WebSocket connection and releases all session resources.
370+
*
371+
* In Node.js the WebSocket keeps the event loop alive until disconnected,
372+
* so call this to allow the process to exit cleanly. In the browser the
373+
* runtime handles socket cleanup on page unload, so this is effectively a no-op.
374+
*
375+
* @example
376+
* ```typescript
377+
* conversationalAgent.conversations.disconnect();
378+
* ```
379+
*/
380+
disconnect(): void;
367381
}
368382

369383
/**

src/services/conversational-agent/conversations/conversations.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,23 @@ export class ConversationService extends BaseService implements ConversationServ
471471
return this._sessionManager.onConnectionStatusChanged(handler);
472472
}
473473

474+
/**
475+
* Closes the WebSocket connection and releases all session resources.
476+
*
477+
* In Node.js the WebSocket keeps the event loop alive until disconnected,
478+
* so call this to allow the process to exit cleanly. In the browser the
479+
* runtime handles socket cleanup on page unload, so this is effectively a no-op.
480+
*
481+
* @example
482+
* ```typescript
483+
* conversationalAgent.conversations.disconnect();
484+
* ```
485+
*/
486+
@track('ConversationalAgent.Conversations.Disconnect')
487+
disconnect(): void {
488+
this._sessionManager.disconnect();
489+
}
490+
474491
// ==================== Private Methods ====================
475492

476493
private _getEvents(): ConversationEventHelperManager {

tests/unit/services/conversational-agent/conversations.test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ vi.mock('@/services/conversational-agent/conversations/session/session-manager',
3636
setLogLevel: vi.fn(),
3737
setEventDispatcher: vi.fn(),
3838
emitEvent: vi.fn(),
39+
disconnect: vi.fn(),
3940
}))
4041
}));
4142

@@ -651,5 +652,13 @@ describe('ConversationalAgent.conversations Unit Tests', () => {
651652
expect(cleanup).toBeDefined();
652653
expect(typeof cleanup).toBe('function');
653654
});
655+
656+
it('should call disconnect on the session manager', () => {
657+
const sessionManager = (conversationalAgent.conversations as any)._sessionManager;
658+
659+
conversationalAgent.conversations.disconnect();
660+
661+
expect(sessionManager.disconnect).toHaveBeenCalledTimes(1);
662+
});
654663
});
655664
});

0 commit comments

Comments
 (0)