#!/usr/bin/env node /** * Live variant mode server (self-contained, zero dependencies). * * Serves the browser script (/live.js), the detection overlay (/detect.js), * uses Server-Sent Events (SSE) for server→browser push, and HTTP POST for * browser→server events. Agent communicates via HTTP long-poll (/poll). * * Usage: * node /live-server.mjs # start * node /live-server.mjs stop # stop + remove injected live.js tag * node /live-server.mjs stop --keep-inject # stop only * node /live-server.mjs --help */ import http from 'node:http'; import { randomUUID } from 'node:crypto'; import { spawn, execFileSync } from 'node:child_process'; import fs from 'node:fs'; import path from 'node:path'; import net from 'node:net'; import { fileURLToPath } from 'node:url'; import { parseDesignMd } from './lib/design-parser.mjs'; import { loadContext } from './context.mjs'; import { assembleLiveBrowserScript, assertLiveBrowserScriptParts, readLiveBrowserScriptParts, resolveLiveBrowserScriptParts, } from './live/browser-script-parts.mjs'; import { createLiveSessionStore, GENERATION_FENCED_PHASES } from './live/session-store.mjs'; import { runGenerationPreflight } from './live/generation-preflight.mjs'; import { validateEvent } from './live/event-validation.mjs'; import { selectAvailablePendingEvent } from './live/poll-lanes.mjs'; import { createManualEditRoutes } from './live/manual-edit-routes.mjs'; import { LIVE_COMMANDS, VARIANT_PROGRESS_CHECKPOINT_REASONS as VARIANT_PROGRESS_CHECKPOINT_REASON_LIST, } from './live/vocabulary.mjs'; import { getDesignSidecarPath, getLiveDir, getLiveAnnotationsDir, IMPECCABLE_COMMAND_PREFIX, readLiveServerInfo, removeLiveServerInfo, resolveDesignSidecarPath, writeLiveServerInfo, } from './lib/impeccable-paths.mjs'; import { countByPage as countPendingByPage } from './live/manual-edits-buffer.mjs'; import { createManualApplyController, summarizeManualApplyFailures, } from './live/manual-apply.mjs'; import { applyDeferredSvelteComponentAccepts, bumpSvelteComponentPreviewRevision, compileCheckVariants, removeAllSvelteComponentSessions, sweepInactiveSvelteComponentSessions, } from './live/svelte-component.mjs'; import { enterLiveRoot } from './live/roots.mjs'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); // Anchor the whole process on the live roots manifest before anything derives // a path from cwd. A server started from the wrong directory re-roots itself // onto the appRoot the boot decided on instead of minting a second project. const LIVE_ROOTS = enterLiveRoot(process.cwd()); // PRODUCT.md / DESIGN.md context, resolved lazily and per request so a server // that outlives an `impeccable document` run (or a context file created after // boot) reports current truth instead of a boot-time snapshot. The roots // manifest wins when the ambient resolution misses (nested app inheriting // repo-level context files). function resolveProjectContext() { const ctx = loadContext(process.cwd()); const designPath = ctx.designPath ? path.resolve(process.cwd(), ctx.designPath) : (LIVE_ROOTS?.designPath && fs.existsSync(LIVE_ROOTS.designPath) ? LIVE_ROOTS.designPath : null); const hasProduct = ctx.hasProduct || !!(LIVE_ROOTS?.productPath && fs.existsSync(LIVE_ROOTS.productPath)); return { ...ctx, hasProduct, hasDesign: !!designPath, resolvedDesignPath: designPath, contextDir: ctx.contextDir || LIVE_ROOTS?.contextRoot || process.cwd(), designContextDir: ctx.designContextDir || (designPath ? path.dirname(designPath) : null), }; } const DEFAULT_POLL_TIMEOUT = 600_000; // 10 min — agent re-polls on timeout anyway const SSE_HEARTBEAT_INTERVAL = 30_000; // keepalive ping every 30s // The browser events allowed to mint a NEW session journal. `generate` starts // a variant session at Go; `steer` mints its own request id. Every other // id-carrying event must land on an existing session (see the unknown_session // gate in the /events handler). const SESSION_CREATING_EVENT_TYPES = new Set(['generate', 'steer']); // The browser checkpoints for several unrelated reasons (see checkpointPayload // in live-browser.js). Only these two report that variant availability changed, // and only they may drive variant_progress / the *_reviewable phases. const VARIANT_PROGRESS_CHECKPOINT_REASONS = new Set(VARIANT_PROGRESS_CHECKPOINT_REASON_LIST); // --------------------------------------------------------------------------- // Port detection // --------------------------------------------------------------------------- async function findOpenPort(start = 8400) { return new Promise((resolve) => { const srv = net.createServer(); srv.listen(start, '127.0.0.1', () => { const port = srv.address().port; srv.close(() => resolve(port)); }); srv.on('error', () => resolve(findOpenPort(start + 1))); }); } // --------------------------------------------------------------------------- // Session state // --------------------------------------------------------------------------- const state = { token: null, port: null, sseClients: new Set(), // SSE response objects (server→browser push) pendingEvents: [], // browser events waiting for agent ack ({ event, leaseUntil }) pendingPolls: [], // agent poll callbacks waiting for browser events nextEventSeq: 1, lastAgentPollingBroadcast: null, exitTimer: null, sessionDir: null, // per-session tmp dir for annotation screenshots sessionStore: null, leaseTimer: null, manualEditActivity: null, nextManualEditSeq: 1, // Deferreds for in-flight chat-routed Apply events. Keyed by event id; each // entry is resolved when the chat agent POSTs an ack carrying the batch // result, or rejected when the hard timeout fires. pendingApplyDeferreds: new Map(), // Updated whenever a /poll long-poll request arrives or is resolved with an // event. Used to detect "a chat agent is likely attached" without requiring // a poll to be parked at the exact moment we dispatch. lastPollAt: 0, timedOutApplyIds: new Map(), }; const CHAT_POLL_FRESHNESS_MS = 60_000; const POLL_LEASE_EXPIRY_TIMER_GRACE_MS = 2; const DEBUG_MANUAL_EDIT_EVENTS = /^(1|true|yes)$/i.test(process.env.IMPECCABLE_LIVE_DEBUG_EVENTS || ''); const manualApply = createManualApplyController({ pendingEvents: state.pendingEvents, pendingApplyDeferreds: state.pendingApplyDeferreds, timedOutApplyIds: state.timedOutApplyIds, enqueueEvent, acknowledgePendingEvent, flushPendingPolls, recordManualEditActivity, cwd: () => process.cwd(), }); const manualEditRoutes = createManualEditRoutes({ getToken: () => state.token, manualApply, recordManualEditActivity, getManualEditStatus, chatAgentLikelyActive, cwd: () => process.cwd(), env: () => process.env, }); function chatAgentLikelyActive() { if (state.pendingPolls.length > 0) return true; if (!state.lastPollAt) return false; return Date.now() - state.lastPollAt < CHAT_POLL_FRESHNESS_MS; } // Cap per-annotation upload size. A full 1920×1080 PNG is typically <1 MB; // cap at 10 MB to guard against runaway writes from a misbehaving client. const MAX_ANNOTATION_BYTES = 10 * 1024 * 1024; function enqueueEvent(event) { if (!event) return; // Dedupe by (session, type), except mount failures, which are per-variant: // variant 2 failing must not be swallowed because variant 1's failure is // still queued. const duplicate = event.id && state.pendingEvents.some((entry) => ( entry.event?.id === event.id && entry.event?.type === event.type && (event.type !== 'variant_mount_failed' || entry.event?.variant === event.variant) )); if (duplicate) return; state.pendingEvents.push({ event, leaseUntil: 0, seq: state.nextEventSeq++ }); flushPendingPolls(); } function restorePendingEventsFromStore() { if (!state.sessionStore) return; for (const snapshot of state.sessionStore.listActiveSessions()) { if (snapshot.pendingEvent) enqueueEvent(snapshot.pendingEvent); } } function findAvailablePendingEvent(now = Date.now(), types = null) { return selectAvailablePendingEvent(state.pendingEvents, { now, types }); } async function leaseEvent(entry, leaseMs) { // Claim the entry before awaiting anything. prepareGenerateEventForLease // yields to the event loop, and selectAvailablePendingEvent only skips // entries whose lease is in the future — an unclaimed entry would be handed // to a second poll in that window and generated twice. entry.leaseUntil = Date.now() + leaseMs; await prepareGenerateEventForLease(entry); if (!entry.event?.id) { const idx = state.pendingEvents.indexOf(entry); if (idx !== -1) state.pendingEvents.splice(idx, 1); return entry.event; } // Re-stamp so the lease window starts when the agent actually receives the // work, not when scaffolding began. entry.leaseUntil = Date.now() + leaseMs; recordGenerateDelivery(entry); scheduleLeaseFlush(); broadcastAgentPollingIfChanged(); return entry.event; } function recordGenerateDelivery(entry) { const event = entry?.event; if (!event || event.type !== 'generate' || event.generationReadyAt) return; const at = Date.now(); entry.event = { ...event, generationReadyAt: at }; state.sessionStore?.appendEvent(entry.event); recordAgentPhase(event.id, 'generation_ready', { at }); } async function prepareGenerateEventForLease(entry) { const event = entry?.event; if (!event || event.type !== 'generate' || event.scaffoldAttempted) return; recordAgentPhase(event.id, 'picked_up'); recordAgentPhase(event.id, 'scaffolding'); const result = await runGenerationPreflight(event, { cwd: process.cwd(), scriptsDir: __dirname, }); entry.event = { ...event, scaffoldAttempted: true, scaffoldDurationMs: result.durationMs ?? null, ...(result.ok ? { scaffold: result.scaffold } : { scaffoldError: result.error || result.reason }), }; state.sessionStore?.appendEvent(entry.event); recordAgentPhase(event.id, result.ok ? 'source_ready' : 'scaffold_fallback', { durationMs: result.durationMs ?? null, previewMode: result.scaffold?.previewMode || 'source', }); } function recordAgentPhase(id, phase, details = {}) { if (!id) return; const event = { type: 'agent_phase', id, phase, at: Date.now(), ...details, }; state.sessionStore?.appendEvent(event); broadcast(event); } /** * Detect a browser that missed the generation `done` broadcast. * * The preflight no longer writes the scaffold into source for source-preview * targets (the agent writes wrapper + variants in one atomic edit), so the old * scaffold-write full-reload that opened the "stranded at 0/N" race is gone. * This recovery stays as defense in depth: any framework reload that drops the * agent's variant write + `done` while the browser is mid-reload leaves the new * page in GENERATING at 0/N. That resumed page always checkpoints * (`browser_resumed`), so a checkpoint claiming "still generating, variants * missing" for a session whose generation already completed is direct * evidence of the miss. Rebuild the `done` payload from the snapshot so the * caller can re-broadcast it; the browser's done handler is idempotent and * falls back to injecting variants from source. * * Keys on the store's monotone `generationCompletedAt`, not `phase` — the * behind checkpoint itself regresses `phase` to `generating`, and a browser * that misses the redelivered `done` too (another reload) must still trigger * redelivery from its next checkpoint. */ function detectMissedGenerationCompletion(event) { if (!event?.id || event.type !== 'checkpoint') return null; if (event.phase !== 'generating') return null; if (!variantCountLooksBehind(event.arrivedVariants, event.expectedVariants)) return null; if (!state.sessionStore) return null; let snapshot = null; try { snapshot = state.sessionStore.getSnapshot(event.id); } catch { return null; } return missedCompletionFromSnapshot(snapshot); } function variantCountLooksBehind(arrivedValue, expectedValue) { const arrived = Number(arrivedValue) || 0; const expected = Number(expectedValue) || 0; return arrived <= 0 || (expected > 0 && arrived < expected); } function missedCompletionFromSnapshot(snapshot) { if (!snapshot?.id || !snapshot.generationCompletedAt) return null; if (snapshot.generationCanceled) return null; // Accept/discard already underway: the browser is no longer waiting on // generation, and a late `done` there would collide with teardown. if (GENERATION_FENCED_PHASES.has(snapshot.phase)) return null; const file = snapshot.sourceFile || snapshot.previewFile; if (!file) return null; return { type: 'done', id: snapshot.id, file, sourceFile: snapshot.sourceFile || undefined, previewFile: snapshot.previewFile || undefined, previewMode: snapshot.previewMode || undefined, redelivered: true, }; } function recordGenerationCheckpoint(event) { if (!event?.id || event.type !== 'checkpoint') return; if (generationIsFenced(event.id)) return; // Only checkpoints that report a change in variant availability are // generation progress. The browser also checkpoints for durability on Tune // slider drags, resumes, and anchor recovery; treating those as progress // echoed `variant_progress` straight back to the browser that sent it, which // remounts the component preview mid-drag (reverting the user's live param // edit and detaching the popover's element), and permanently latched the // *_reviewable phases from the wrong trigger, corrupting generation timings. if (!VARIANT_PROGRESS_CHECKPOINT_REASONS.has(event.reason)) return; const arrived = Number(event.arrivedVariants) || 0; const expected = Number(event.expectedVariants) || 0; if (arrived <= 0 || expected <= 0) return; const previewMode = event.previewMode || 'source'; const previewFile = event.previewFile || event.file; if (previewFile) { broadcast({ type: 'variant_progress', id: event.id, file: previewFile, sourceFile: event.sourceFile || (previewMode === 'source' ? previewFile : undefined), previewFile, previewMode, arrivedVariants: arrived, expectedVariants: expected, publicationKind: event.publicationKind || 'variants', }); } const details = { arrivedVariants: arrived, expectedVariants: expected, checkpointReason: event.reason || null, }; const at = Date.now(); if (!generationPhaseAlreadyRecorded(event.id, 'first_reviewable')) { recordAgentPhase(event.id, 'first_reviewable', { ...details, at }); } if (arrived >= 2 && expected >= 3 && !generationPhaseAlreadyRecorded(event.id, 'second_reviewable')) { recordAgentPhase(event.id, 'second_reviewable', { ...details, at }); } if (arrived >= expected && !generationPhaseAlreadyRecorded(event.id, 'all_variants_ready')) { recordAgentPhase(event.id, 'all_variants_ready', { ...details, at }); } } function generationIsFenced(id) { if (!state.sessionStore || !id) return false; try { const snapshot = state.sessionStore.getSnapshot(id, { includeCompleted: true }); return snapshot?.generationCanceled === true; } catch { return false; } } function generationPhaseAlreadyRecorded(id, phase) { if (!state.sessionStore) return false; try { const snapshot = state.sessionStore.getSnapshot(id, { includeCompleted: true }); return !!snapshot?.generationTimings?.[phase]; } catch { return false; } } function acknowledgePendingEvent(id, sourceEventType) { if (!id) return false; const idx = state.pendingEvents.findIndex((entry) => ( entry.event?.id === id && (!sourceEventType || entry.event?.type === sourceEventType) )); if (idx === -1) return false; const acknowledged = state.pendingEvents[idx].event; state.pendingEvents.splice(idx, 1); scheduleLeaseFlush(); broadcastAgentPollingIfChanged(); return acknowledged; } function releasePendingEvent(id, sourceEventType) { const entry = state.pendingEvents.find((item) => ( item.event?.id === id && (!sourceEventType || item.event?.type === sourceEventType) )); if (!entry) return null; entry.leaseUntil = 0; scheduleLeaseFlush(); return entry.event; } function retirePendingGeneration(id) { if (!id) return 0; let retired = 0; for (let index = state.pendingEvents.length - 1; index >= 0; index -= 1) { const event = state.pendingEvents[index]?.event; if (event?.id !== id || event.type !== 'generate') continue; state.pendingEvents.splice(index, 1); retired += 1; } if (retired > 0) { scheduleLeaseFlush(); broadcastAgentPollingIfChanged(); } return retired; } function findPendingEventById(id, sourceEventType) { if (!id) return null; const entry = state.pendingEvents.find((item) => ( item.event?.id === id && (!sourceEventType || item.event?.type === sourceEventType) )); return entry?.event || null; } function summarizePendingEventForStatus(entry) { const event = entry.event || {}; const summary = { id: event.id, type: event.type, leased: isLeased(entry), leaseUntil: entry.leaseUntil || null, }; if (event.type === 'manual_edit_apply') { summary.pageUrl = event.pageUrl || null; summary.chunk = event.chunk || null; summary.repair = event.repair || null; summary.evidencePath = event.evidencePath || null; summary.agentAction = event.agentAction || manualApply.buildAgentAction(event); summary.manualApplySummary = manualApply.summarizeEvent(event, manualApply.getDeferred(event.id)?.batch || event.batch); } return summary; } function summarizeActiveSessionForClient(snapshot = {}) { return { id: snapshot.id, phase: snapshot.phase, pageUrl: snapshot.pageUrl ?? null, sourceFile: snapshot.sourceFile ?? null, previewFile: snapshot.previewFile ?? null, previewMode: snapshot.previewMode ?? null, expectedVariants: snapshot.expectedVariants ?? 0, arrivedVariants: snapshot.arrivedVariants ?? 0, visibleVariant: snapshot.visibleVariant ?? null, checkpointRevision: snapshot.checkpointRevision ?? 0, browserCheckpointRevision: snapshot.browserCheckpointRevision ?? snapshot.checkpointRevision ?? 0, publicationCheckpointRevision: snapshot.publicationCheckpointRevision ?? 0, paramValues: snapshot.paramValues || {}, generationPhase: snapshot.generationPhase ?? null, generationCompletedAt: snapshot.generationCompletedAt ?? null, generationCanceled: snapshot.generationCanceled === true, cancelReason: snapshot.cancelReason ?? null, // Render truth, so a browser with no localStorage can rehydrate to the // same comparison the server already knows about. mountedVariants: Array.isArray(snapshot.mountedVariants) ? snapshot.mountedVariants : [], mountFailures: Array.isArray(snapshot.mountFailures) ? snapshot.mountFailures : [], renderState: snapshot.renderState ?? null, }; } function activeSessionSummaries() { if (!state.sessionStore) return []; return state.sessionStore.listActiveSessions().map((snapshot) => summarizeActiveSessionForClient(snapshot)); } function cancelQueuedAnonymousExitEvents() { let removed = 0; for (let i = state.pendingEvents.length - 1; i >= 0; i -= 1) { const event = state.pendingEvents[i]?.event; if (event?.type !== 'exit' || event.id) continue; state.pendingEvents.splice(i, 1); removed += 1; } if (removed > 0) { scheduleLeaseFlush(); broadcastAgentPollingIfChanged(); } return removed; } function scheduleLeaseFlush() { if (state.leaseTimer) { clearTimeout(state.leaseTimer); state.leaseTimer = null; } const now = Date.now(); const nextLeaseUntil = state.pendingEvents .map((entry) => entry.leaseUntil || 0) .filter((leaseUntil) => leaseUntil > now) .sort((a, b) => a - b)[0]; if (!nextLeaseUntil) return; state.leaseTimer = setTimeout(() => { state.leaseTimer = null; flushPendingPolls(); broadcastAgentPollingIfChanged(); }, Math.max(0, nextLeaseUntil - now + POLL_LEASE_EXPIRY_TIMER_GRACE_MS)); } function flushPendingPolls() { let changed = false; while (state.pendingPolls.length > 0) { let pollIndex = -1; let entry = null; for (let index = 0; index < state.pendingPolls.length; index += 1) { const candidate = findAvailablePendingEvent(Date.now(), state.pendingPolls[index].types); if (!candidate) continue; pollIndex = index; entry = candidate; break; } if (!entry) { scheduleLeaseFlush(); broadcastAgentPollingIfChanged(); return; } const [poll] = state.pendingPolls.splice(pollIndex, 1); // leaseEvent is async (it may scaffold source), but it claims the entry // synchronously, so the next loop iteration will not re-select it. Resolve // the poll when the lease settles rather than awaiting here, so one slow // scaffold never delays the other parked polls. On the exceptional failure // path, answer `timeout` so the agent re-polls; the claim stays until the // lease expires, which keeps a deterministic failure from hot-looping. leaseEvent(entry, poll.leaseMs).then(poll.resolve, (error) => { console.error('[live] lease failed for ' + (entry.event?.id || 'unknown') + ': ' + (error?.message || error)); poll.resolve({ type: 'timeout' }); }); changed = true; } scheduleLeaseFlush(); if (changed) broadcastAgentPollingIfChanged(); } function isLeased(entry) { return !!(entry?.leaseUntil && entry.leaseUntil > Date.now()); } function agentPollingConnected() { // A leased event only proves that a poll returned once. The foreground task // may have ended immediately afterward, so only an actively waiting poll is // evidence that steering can wake the task right now. return state.pendingPolls.length > 0; } function broadcastAgentPollingIfChanged() { const connected = agentPollingConnected(); if (state.lastAgentPollingBroadcast === connected) return; state.lastAgentPollingBroadcast = connected; broadcast({ type: 'agent_polling', connected }); } /** Push a message to all connected SSE clients. */ function broadcast(msg) { const data = 'data: ' + JSON.stringify(msg) + '\n\n'; for (const res of state.sseClients) { try { res.write(data); } catch { /* client gone */ } } } function recordManualEditActivity(type, details = {}) { const entry = { seq: state.nextManualEditSeq++, type, ts: new Date().toISOString(), ...details, }; state.manualEditActivity = entry; if (DEBUG_MANUAL_EDIT_EVENTS) { try { const filePath = path.join(getLiveDir(process.cwd()), 'manual-edit-events.jsonl'); fs.mkdirSync(path.dirname(filePath), { recursive: true }); fs.appendFileSync(filePath, JSON.stringify(entry) + '\n'); } catch { /* diagnostics are best-effort; never block live mode on observability */ } } broadcast(entry); return entry; } function getManualEditStatus() { try { const { totalCount, perPage } = countPendingByPage(process.cwd()); return { totalCount, perPage, lastActivity: state.manualEditActivity }; } catch (err) { return { totalCount: null, perPage: {}, lastActivity: state.manualEditActivity, error: err.message, }; } } // --------------------------------------------------------------------------- // Load scripts // --------------------------------------------------------------------------- function loadBrowserScripts() { // Detection script: prefer the skill-bundled detector, then fall back to // source/npm package locations for local development and older installs. // This one IS cached — detect.js rarely changes during a session. const detectPaths = [ path.join(__dirname, 'detector', 'detect-antipatterns-browser.js'), path.join(__dirname, '..', '..', 'cli', 'engine', 'detect-antipatterns-browser.js'), path.join(__dirname, '..', '..', '..', '..', 'cli', 'engine', 'detect-antipatterns-browser.js'), path.join(process.cwd(), 'node_modules', 'impeccable', 'cli', 'engine', 'detect-antipatterns-browser.js'), ]; let detectScript = ''; for (const p of detectPaths) { try { detectScript = fs.readFileSync(p, 'utf-8'); break; } catch { /* try next */ } } // Browser script parts: DO NOT cache. Return paths so the /live.js handler // can re-read every part on each request. Editing browser code during // iteration should land on the next tab reload, not require a server restart. const liveScriptParts = resolveLiveBrowserScriptParts(__dirname); try { assertLiveBrowserScriptParts(liveScriptParts); } catch (err) { process.stderr.write('Error: ' + err.message + '\n'); process.exit(1); } return { detectScript, liveScriptParts }; } function hasProjectContext() { // PRODUCT.md carries brand voice / anti-references — that's what determines // whether variants are brand-aware. DESIGN.md (visual tokens) is a separate // concern, surfaced by the design panel's own empty state. return !!resolveProjectContext().hasProduct; } function statOrNull(filePath) { try { return fs.statSync(filePath); } catch { return null; } } // Strict loopback-origin test for CORS. Parses the Origin as a URL (never a // substring match, so `http://localhost.evil.com` and `http://127.0.0.1.evil.com` // fail) and accepts only http/https on localhost, 127.0.0.1, or the IPv6 loopback. function isLoopbackOrigin(origin) { if (typeof origin !== 'string' || origin.length === 0) return false; let parsed; try { parsed = new URL(origin); } catch { return false; } if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') return false; const host = parsed.hostname.toLowerCase(); return host === 'localhost' || host === '127.0.0.1' || host === '::1' || host === '[::1]'; } // HTTP request handler // --------------------------------------------------------------------------- function createRequestHandler({ detectScript, liveScriptParts }) { return (req, res) => { const url = new URL(req.url, `http://localhost:${state.port}`); // Loopback-restricted CORS. Reflect the caller's Origin only when it is a // loopback origin, always paired with `Vary: Origin` so an intermediary // cache never serves a response authorized for one origin to another. A // remote page (e.g. https://evil.example probing the port from a tab open // on the same machine) gets no Access-Control-Allow-Origin, so its // JS-initiated fetch cannot read any response. Requests with no Origin // header (script tags, curl, the agent's own fetches) are not subject to // CORS and keep working; no ACAO header is needed for them. const origin = req.headers.origin; if (origin && isLoopbackOrigin(origin)) { res.setHeader('Access-Control-Allow-Origin', origin); res.setHeader('Vary', 'Origin'); } res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); if (req.method === 'OPTIONS') { res.writeHead(204); res.end(); return; } const p = url.pathname; // --- Scripts --- if (p === '/live.js') { // Token-gated: the script body embeds state.token, which unlocks every // token-guarded route. Serving it unauthenticated let any local page read // the token and drive the session. The injected