diff --git a/apps/x/apps/main/src/main.ts b/apps/x/apps/main/src/main.ts index aef29945..225c2a0d 100644 --- a/apps/x/apps/main/src/main.ts +++ b/apps/x/apps/main/src/main.ts @@ -25,7 +25,7 @@ import { init as initAgentRunner } from "@x/core/dist/agent-schedule/runner.js"; import { init as initAgentNotes } from "@x/core/dist/knowledge/agent_notes.js"; import { init as initTrackScheduler } from "@x/core/dist/knowledge/track/scheduler.js"; import { init as initTrackEventProcessor } from "@x/core/dist/knowledge/track/events.js"; -import { init as initLocalSites } from "@x/core/dist/local-sites/server.js"; +import { init as initLocalSites, shutdown as shutdownLocalSites } from "@x/core/dist/local-sites/server.js"; import { initConfigs } from "@x/core/dist/config/initConfigs.js"; import started from "electron-squirrel-startup"; @@ -315,4 +315,7 @@ app.on("before-quit", () => { stopWorkspaceWatcher(); stopRunsWatcher(); stopServicesWatcher(); + shutdownLocalSites().catch((error) => { + console.error('[LocalSites] Failed to shut down cleanly:', error); + }); }); diff --git a/apps/x/packages/core/src/local-sites/server.ts b/apps/x/packages/core/src/local-sites/server.ts index 7ad226d8..f1fb4c7e 100644 --- a/apps/x/packages/core/src/local-sites/server.ts +++ b/apps/x/packages/core/src/local-sites/server.ts @@ -2,6 +2,7 @@ import fs from 'node:fs'; import fsp from 'node:fs/promises'; import path from 'node:path'; import type { Server } from 'node:http'; +import chokidar, { type FSWatcher } from 'chokidar'; import express from 'express'; import { WorkDir } from '../config/config.js'; import { LOCAL_SITE_SCAFFOLD } from './templates.js'; @@ -12,6 +13,11 @@ export const LOCAL_SITES_BASE_URL = `http://localhost:${LOCAL_SITES_PORT}`; const LOCAL_SITES_DIR = path.join(WorkDir, 'sites'); const SITE_SLUG_RE = /^[a-z0-9][a-z0-9-_]*$/i; const IFRAME_HEIGHT_MESSAGE = 'rowboat:iframe-height'; +const SITE_RELOAD_MESSAGE = 'rowboat:site-changed'; +const SITE_EVENTS_PATH = '__rowboat_events'; +const SITE_RELOAD_DEBOUNCE_MS = 140; +const SITE_EVENTS_RETRY_MS = 1000; +const SITE_EVENTS_HEARTBEAT_MS = 15000; const TEXT_EXTENSIONS = new Set([ '.css', '.html', @@ -43,6 +49,59 @@ const MIME_TYPES: Record = { }; const IFRAME_AUTOSIZE_BOOTSTRAP = String.raw`