Repository Public
1'use client';2import type { QueryClient } from '@tanstack/react-query';3import type { CreateSession, TerminalSession } from '@cr/shared';4import { api } from './api';5import { qk, upsertSession } from './queries';6import { useTerminals } from '@/stores/terminals';7import { disposeController, getController } from '@/components/terminal/registry';89export type NewSessionInput = Partial<CreateSession> & { kind: 'claude' | 'shell' };1011export async function createSession(qc: QueryClient, input: NewSessionInput): Promise<TerminalSession> {12 const s = await api.post<TerminalSession>('/sessions', input);13 upsertSession(qc, s);14 useTerminals.getState().open(s.id, true);15 void qc.invalidateQueries({ queryKey: qk.projects });16 return s;17}18export async function patchSession(qc: QueryClient, id: string, patch: { title?: string; pinned?: boolean; archived?: boolean }): Promise<TerminalSession> {19 const s = await api.patch<TerminalSession>(`/sessions/${id}`, patch);20 upsertSession(qc, s);21 if (s.archived) closeTab(id);22 return s;23}24export async function terminateSession(qc: QueryClient, id: string): Promise<void> {25 await api.post(`/sessions/${id}/terminate`);26 // The exit event may already have arrived over the events channel: refetch instead of27 // writing the (possibly older) HTTP response into the cache.28 await qc.invalidateQueries({ queryKey: ['sessions'] });29}30/** RECOVERABLE only: starts a fresh process (claude --resume when the conversation is known). */31export async function restartSession(qc: QueryClient, id: string): Promise<void> {32 const s = await api.post<TerminalSession>(`/sessions/${id}/reconnect`, { restart: true });33 if (s && typeof s === 'object' && 'id' in s) upsertSession(qc, s);34 getController(id)?.reconnectNow();35}36/** Re-sync the server's view of the session with the runtime, then re-attach. */37export async function resyncSession(qc: QueryClient, id: string): Promise<void> {38 const s = await api.post<TerminalSession>(`/sessions/${id}/reconnect`, {});39 if (s && typeof s === 'object' && 'id' in s) upsertSession(qc, s);40 getController(id)?.reconnectNow();41}42export function closeTab(id: string): void {43 useTerminals.getState().close(id);44 disposeController(id); // closes only this browser's attach; the session keeps running on the server45}46/** A new session that replaces an ended one (same project/kind/cwd, optionally resuming the conversation). */47export async function replaceEndedSession(qc: QueryClient, old: TerminalSession, resume: boolean): Promise<TerminalSession> {48 const s = await api.post<TerminalSession>('/sessions', {49 projectId: old.projectId,50 kind: old.kind,51 cwd: old.cwd,52 title: old.title,53 ...(resume && old.kind === 'claude' ? (old.claudeConversationId ? { resumeConversationId: old.claudeConversationId } : { continueLast: true }) : {}),54 });55 upsertSession(qc, s);56 const st = useTerminals.getState();57 st.open(s.id, true);58 closeTab(old.id);59 return s;60}61