import { getWebSocketClient } from "@/lib/ws/connection"; import { getBackendConfig } from "@/lib/config"; /** * Terminal info returned by HTTP API (for SSR). */ export type TerminalInfo = { terminal_id: string; process_id: string; running: boolean; label: string; closable: boolean; initial_command?: string; }; /** * Fetch terminals for a task environment via HTTP (for SSR). * This endpoint auto-creates the first "Terminal" if none exist. * * User shells are env-scoped — sessions in the same task share one shell list. */ export async function fetchTerminals(environmentId: string): Promise { const { apiBaseUrl } = getBackendConfig(); const url = `${apiBaseUrl}/api/v1/environments/${environmentId}/terminals`; try { const response = await fetch(url); if (!response.ok) { console.warn("Failed to fetch terminals:", response.status); return []; } const data = await response.json(); return data.terminals ?? []; } catch (error) { console.warn("Failed to fetch terminals:", error); return []; } } /** * Result of creating a new user shell. */ export type CreateUserShellResult = { terminalId: string; label: string; closable: boolean; initialCommand?: string; }; /** * Options for {@link createUserShell}. Pass `scriptId` to run a stored * RepositoryScript, or `command` (with optional `label`) to run an arbitrary * command — used for the repository's dev_script. Omit both for a plain shell. */ export type CreateUserShellOptions = { scriptId?: string; command?: string; label?: string; }; /** * Create a new user shell terminal. * Backend assigns the terminal ID, label, and closable status. * First terminal is "Terminal" and not closable, subsequent are "Terminal 2", etc. and closable. * * User shells are env-scoped — pass the task environment ID, not a session ID. */ export async function createUserShell( environmentId: string, options?: CreateUserShellOptions, ): Promise { const client = getWebSocketClient(); if (!client) { throw new Error("WebSocket client not available"); } const payload: Record = { task_environment_id: environmentId }; if (options?.scriptId) payload.script_id = options.scriptId; if (options?.command) payload.command = options.command; if (options?.label) payload.label = options.label; const response = (await client.request("user_shell.create", payload)) as { terminal_id: string; label: string; closable: boolean; initial_command?: string; }; return { terminalId: response.terminal_id, label: response.label, closable: response.closable, initialCommand: response.initial_command, }; } /** * Stop a user shell terminal process. * This is called when closing a terminal tab to clean up the backend process. */ export async function stopUserShell(environmentId: string, terminalId: string): Promise { const client = getWebSocketClient(); if (!client) { // WebSocket not available, silently fail (best-effort cleanup) return; } try { await client.request("user_shell.stop", { task_environment_id: environmentId, terminal_id: terminalId, }); } catch (error) { // Log but don't throw - this is best-effort cleanup console.warn("Failed to stop user shell:", error); } }