import { createTerminalManager } from "./terminal-manager.js"; import { captureTerminalLines } from "./terminal-capture.js"; import type { TerminalSession } from "./terminal.js"; import type { TerminalWorkerRequest, TerminalWorkerToParentMessage, WorkerTerminalInfo, } from "./terminal-worker-protocol.js"; const manager = createTerminalManager(); const unsubscribeByTerminalId = new Map void>>(); let ipcClosing = false; function sendToParent(message: TerminalWorkerToParentMessage): void { if (ipcClosing || !process.connected || !process.send) { return; } try { process.send(message, (error) => { if (error) { ipcClosing = true; } }); } catch { ipcClosing = true; } } function toTerminalInfo(session: TerminalSession): WorkerTerminalInfo { return { id: session.id, name: session.name, cwd: session.cwd, ...(session.getTitle() ? { title: session.getTitle() } : {}), }; } function clearTerminalSubscriptions(terminalId: string): void { const subscriptions = unsubscribeByTerminalId.get(terminalId); if (subscriptions) { for (const unsubscribe of subscriptions) { try { unsubscribe(); } catch { // no-op } } } unsubscribeByTerminalId.delete(terminalId); } function watchTerminal(session: TerminalSession): void { clearTerminalSubscriptions(session.id); const unsubscribeMessage = session.subscribe((message) => { sendToParent({ type: "terminalMessage", terminalId: session.id, message, }); }); const unsubscribeExit = session.onExit((info) => { clearTerminalSubscriptions(session.id); sendToParent({ type: "terminalExit", terminalId: session.id, info, }); }); const unsubscribeTitle = session.onTitleChange((title) => { sendToParent({ type: "terminalTitleChange", terminalId: session.id, title, }); }); const unsubscribeCommandFinished = session.onCommandFinished((info) => { sendToParent({ type: "terminalCommandFinished", terminalId: session.id, info, }); }); unsubscribeByTerminalId.set(session.id, [ unsubscribeMessage, unsubscribeExit, unsubscribeTitle, unsubscribeCommandFinished, ]); } manager.subscribeTerminalsChanged((event) => { sendToParent({ type: "terminalsChanged", cwd: event.cwd, terminals: event.terminals, }); }); async function handleRequest(message: TerminalWorkerRequest): Promise { switch (message.type) { case "getTerminals": { const terminals = await manager.getTerminals(message.cwd); sendToParent({ type: "response", requestId: message.requestId, ok: true, result: terminals.map(toTerminalInfo), }); return; } case "createTerminal": { const session = await manager.createTerminal(message.options); watchTerminal(session); const initialSnapshot = session.getStateSnapshot(); sendToParent({ type: "terminalCreated", terminal: toTerminalInfo(session), state: initialSnapshot.state, }); sendToParent({ type: "response", requestId: message.requestId, ok: true, result: { terminal: toTerminalInfo(session), state: initialSnapshot.state, }, }); return; } case "registerCwdEnv": { manager.registerCwdEnv({ cwd: message.cwd, env: message.env }); sendToParent({ type: "response", requestId: message.requestId, ok: true }); return; } case "killTerminal": { const session = manager.getTerminal(message.terminalId); const cwd = session?.cwd; manager.killTerminal(message.terminalId); clearTerminalSubscriptions(message.terminalId); if (cwd) { sendToParent({ type: "terminalRemoved", terminalId: message.terminalId, cwd, }); } sendToParent({ type: "response", requestId: message.requestId, ok: true }); return; } case "killTerminalAndWait": { const session = manager.getTerminal(message.terminalId); const cwd = session?.cwd; await manager.killTerminalAndWait(message.terminalId, message.options); clearTerminalSubscriptions(message.terminalId); if (cwd) { sendToParent({ type: "terminalRemoved", terminalId: message.terminalId, cwd, }); } sendToParent({ type: "response", requestId: message.requestId, ok: true }); return; } case "getTerminalState": { const session = manager.getTerminal(message.terminalId); sendToParent({ type: "response", requestId: message.requestId, ok: true, result: session?.getStateSnapshot(message.options) ?? null, }); return; } case "listDirectories": { sendToParent({ type: "response", requestId: message.requestId, ok: true, result: manager.listDirectories(), }); return; } case "captureTerminal": { const session = manager.getTerminal(message.terminalId); const result = session ? captureTerminalLines(session, { start: message.start, end: message.end, stripAnsi: message.stripAnsi, }) : { lines: [], totalLines: 0 }; sendToParent({ type: "response", requestId: message.requestId, ok: true, result, }); return; } case "killAll": { manager.killAll(); for (const terminalId of Array.from(unsubscribeByTerminalId.keys())) { clearTerminalSubscriptions(terminalId); } sendToParent({ type: "response", requestId: message.requestId, ok: true }); return; } case "send": { const session = manager.getTerminal(message.terminalId); session?.send(message.message); sendToParent({ type: "response", requestId: message.requestId, ok: true }); return; } } } process.on("message", (message: TerminalWorkerRequest) => { void handleRequest(message).catch((error: unknown) => { sendToParent({ type: "response", requestId: message.requestId, ok: false, error: error instanceof Error ? error.message : "Terminal worker request failed", }); }); }); process.once("disconnect", () => { ipcClosing = true; manager.killAll(); });