/** * `useLogTail` — tail a managed process's stdout/stderr over SSE. * * Watches a process-id ref; (re)opens `/__nwire/run/logs/:id/stream` whenever it * changes, parses each `LogLine`, and keeps a bounded buffer. The scoped * `EventSource` (patched in main.ts) carries `?project=`; SSE reconnects on * drop natively. Shared by the Run + Commands panels so the streaming logic * lives in one place. */ import { ref, watch, onUnmounted, toValue, type MaybeRefOrGetter } from "vue"; import type { LogLine } from "@/lib/operate"; const MAX_LINES = 5000; export function useLogTail(id: MaybeRefOrGetter) { const logs = ref([]); let es: EventSource | null = null; function close(): void { es?.close(); es = null; } function clear(): void { logs.value = []; } function connect(target: string | null | undefined): void { close(); clear(); if (!target) return; es = new EventSource(`/__nwire/run/logs/${encodeURIComponent(target)}/stream`); es.onmessage = (msg) => { try { const line = JSON.parse(msg.data) as LogLine; const next = logs.value.length >= MAX_LINES ? logs.value.slice(-(MAX_LINES - 1)) : logs.value.slice(); next.push(line); logs.value = next; } catch { /* keepalive comment / non-JSON frame — ignore */ } }; // EventSource auto-reconnects on error; nothing to do. } watch(() => toValue(id), connect, { immediate: true }); onUnmounted(close); return { logs, clear }; }