chat: "Disconnect session" — end the whole session from the UI (first stab)
Adds a per-session teardown, distinct from the existing turn-only "stop": - New WS 'disconnect' message → handleDisconnect → sessionManager.deleteSession, which fires _claudeKill (kills any in-flight Claude/OpenCode turn) + _sidecarUnsub, clears the idle timer, and drops the in-memory session. WS stays open so a new prompt starts fresh. Server acks with 'disconnected'. - useChat: disconnectSession() + a 'disconnected' handler (commit partial stream, settle to idle). - UI: an Unplug button in the chat DetailBar (shown while connected). Scope note: targets the CURRENTLY-OPEN session (correct in-memory sessionKey). Disconnecting an arbitrary *listed* session isn't wired yet — session-list rows are keyed by the on-disk transcript uuid, which isn't the live sessionKey, so that needs a reverse lookup + a REST endpoint. NOT yet deployed (needs a server restart). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -67,6 +67,11 @@ export type ClientMessage =
|
||||
}
|
||||
| {
|
||||
type: 'stop';
|
||||
}
|
||||
| {
|
||||
// Tear down the whole session (kill any in-flight turn + drop the in-memory session), not just
|
||||
// the current turn. Frees the session so its transcript can be resumed elsewhere.
|
||||
type: 'disconnect';
|
||||
};
|
||||
|
||||
export type ServerMessage =
|
||||
@@ -117,6 +122,10 @@ export type ServerMessage =
|
||||
}
|
||||
| {
|
||||
type: 'stopped';
|
||||
}
|
||||
| {
|
||||
// Ack for a client 'disconnect': the session was torn down server-side.
|
||||
type: 'disconnected';
|
||||
};
|
||||
|
||||
export type ChatEvent =
|
||||
|
||||
@@ -120,6 +120,8 @@ export function message(ws: ServerWebSocket<WSData>, raw: string | Buffer): void
|
||||
await handleResume(ws, clientMsg);
|
||||
} else if (clientMsg.type === 'stop') {
|
||||
await handleStop(ws);
|
||||
} else if (clientMsg.type === 'disconnect') {
|
||||
await handleDisconnect(ws);
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error('Error handling WebSocket message', { email: ws.data.email, error: String(err) });
|
||||
@@ -538,6 +540,23 @@ async function handleStop(ws: ServerWebSocket<WSData>): Promise<void> {
|
||||
sendToClient(ws, { type: 'stopped' });
|
||||
}
|
||||
|
||||
// Tear down the whole session (not just the current turn): deleteSession fires _claudeKill (kills any
|
||||
// in-flight Claude/OpenCode turn) + _sidecarUnsub, clears the idle timer, and drops the session from the
|
||||
// manager's maps. The WS stays open so the client can immediately start a fresh session.
|
||||
async function handleDisconnect(ws: ServerWebSocket<WSData>): Promise<void> {
|
||||
const sessionId = wsToSessionMap.get(ws);
|
||||
if (sessionId) {
|
||||
try {
|
||||
sessionManager.deleteSession(sessionId);
|
||||
wsToSessionMap.delete(ws as any);
|
||||
logger.info('Disconnected chat session', { sessionId });
|
||||
} catch (err) {
|
||||
logger.error('Failed to disconnect session', { sessionId, error: String(err) });
|
||||
}
|
||||
}
|
||||
sendToClient(ws, { type: 'disconnected' });
|
||||
}
|
||||
|
||||
export const chatWebsocket = {
|
||||
open,
|
||||
message,
|
||||
|
||||
Reference in New Issue
Block a user