@agimon-ai/doompi-web 0.0.1-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +29 -0
- package/README.md +196 -0
- package/dist/_virtual/_rolldown/runtime.cjs +1 -0
- package/dist/bin/serve.cjs +3 -0
- package/dist/bin/serve.cjs.map +1 -0
- package/dist/bin/serve.d.cts +1 -0
- package/dist/bin/serve.d.mts +1 -0
- package/dist/bin/serve.mjs +3 -0
- package/dist/bin/serve.mjs.map +1 -0
- package/dist/bundler.cjs +1 -0
- package/dist/bundler.d.cts +2 -0
- package/dist/bundler.d.mts +2 -0
- package/dist/bundler.mjs +1 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +7 -0
- package/dist/index.d.mts +7 -0
- package/dist/index.mjs +1 -0
- package/dist/src/adapters/authRoutes.cjs +2 -0
- package/dist/src/adapters/authRoutes.cjs.map +1 -0
- package/dist/src/adapters/authRoutes.mjs +2 -0
- package/dist/src/adapters/authRoutes.mjs.map +1 -0
- package/dist/src/adapters/bundledServer.cjs +2 -0
- package/dist/src/adapters/bundledServer.cjs.map +1 -0
- package/dist/src/adapters/bundledServer.mjs +2 -0
- package/dist/src/adapters/bundledServer.mjs.map +1 -0
- package/dist/src/adapters/directoryListing.cjs +2 -0
- package/dist/src/adapters/directoryListing.cjs.map +1 -0
- package/dist/src/adapters/directoryListing.mjs +2 -0
- package/dist/src/adapters/directoryListing.mjs.map +1 -0
- package/dist/src/adapters/directorySearch.cjs +3 -0
- package/dist/src/adapters/directorySearch.cjs.map +1 -0
- package/dist/src/adapters/directorySearch.mjs +3 -0
- package/dist/src/adapters/directorySearch.mjs.map +1 -0
- package/dist/src/adapters/gitStatus.cjs +2 -0
- package/dist/src/adapters/gitStatus.cjs.map +1 -0
- package/dist/src/adapters/gitStatus.mjs +2 -0
- package/dist/src/adapters/gitStatus.mjs.map +1 -0
- package/dist/src/adapters/httpServer.cjs +2 -0
- package/dist/src/adapters/httpServer.cjs.map +1 -0
- package/dist/src/adapters/httpServer.d.cts +14 -0
- package/dist/src/adapters/httpServer.d.cts.map +1 -0
- package/dist/src/adapters/httpServer.d.mts +14 -0
- package/dist/src/adapters/httpServer.d.mts.map +1 -0
- package/dist/src/adapters/httpServer.mjs +2 -0
- package/dist/src/adapters/httpServer.mjs.map +1 -0
- package/dist/src/adapters/hubProbe.cjs +2 -0
- package/dist/src/adapters/hubProbe.cjs.map +1 -0
- package/dist/src/adapters/hubProbe.mjs +2 -0
- package/dist/src/adapters/hubProbe.mjs.map +1 -0
- package/dist/src/adapters/packageApiProxy.cjs +2 -0
- package/dist/src/adapters/packageApiProxy.cjs.map +1 -0
- package/dist/src/adapters/packageApiProxy.mjs +2 -0
- package/dist/src/adapters/packageApiProxy.mjs.map +1 -0
- package/dist/src/adapters/piHubService.cjs +2 -0
- package/dist/src/adapters/piHubService.cjs.map +1 -0
- package/dist/src/adapters/piHubService.mjs +2 -0
- package/dist/src/adapters/piHubService.mjs.map +1 -0
- package/dist/src/adapters/piWebSocketListener.cjs +2 -0
- package/dist/src/adapters/piWebSocketListener.cjs.map +1 -0
- package/dist/src/adapters/piWebSocketListener.mjs +2 -0
- package/dist/src/adapters/piWebSocketListener.mjs.map +1 -0
- package/dist/src/adapters/providerAuth.cjs +2 -0
- package/dist/src/adapters/providerAuth.cjs.map +1 -0
- package/dist/src/adapters/providerAuth.mjs +2 -0
- package/dist/src/adapters/providerAuth.mjs.map +1 -0
- package/dist/src/adapters/registryWatcher.cjs +2 -0
- package/dist/src/adapters/registryWatcher.cjs.map +1 -0
- package/dist/src/adapters/registryWatcher.mjs +2 -0
- package/dist/src/adapters/registryWatcher.mjs.map +1 -0
- package/dist/src/adapters/serverSpawner.cjs +2 -0
- package/dist/src/adapters/serverSpawner.cjs.map +1 -0
- package/dist/src/adapters/serverSpawner.mjs +2 -0
- package/dist/src/adapters/serverSpawner.mjs.map +1 -0
- package/dist/src/adapters/sessionFiles.cjs +3 -0
- package/dist/src/adapters/sessionFiles.cjs.map +1 -0
- package/dist/src/adapters/sessionFiles.mjs +3 -0
- package/dist/src/adapters/sessionFiles.mjs.map +1 -0
- package/dist/src/adapters/sessionHub.cjs +2 -0
- package/dist/src/adapters/sessionHub.cjs.map +1 -0
- package/dist/src/adapters/sessionHub.mjs +2 -0
- package/dist/src/adapters/sessionHub.mjs.map +1 -0
- package/dist/src/adapters/sessionSocketClient.cjs +2 -0
- package/dist/src/adapters/sessionSocketClient.cjs.map +1 -0
- package/dist/src/adapters/sessionSocketClient.mjs +2 -0
- package/dist/src/adapters/sessionSocketClient.mjs.map +1 -0
- package/dist/src/adapters/syncGuard.cjs +2 -0
- package/dist/src/adapters/syncGuard.cjs.map +1 -0
- package/dist/src/adapters/syncGuard.mjs +2 -0
- package/dist/src/adapters/syncGuard.mjs.map +1 -0
- package/dist/src/adapters/threadJournals.cjs +2 -0
- package/dist/src/adapters/threadJournals.cjs.map +1 -0
- package/dist/src/adapters/threadJournals.mjs +2 -0
- package/dist/src/adapters/threadJournals.mjs.map +1 -0
- package/dist/src/adapters/webBundler.cjs +2 -0
- package/dist/src/adapters/webBundler.cjs.map +1 -0
- package/dist/src/adapters/webBundler.d.cts +28 -0
- package/dist/src/adapters/webBundler.d.cts.map +1 -0
- package/dist/src/adapters/webBundler.d.mts +28 -0
- package/dist/src/adapters/webBundler.d.mts.map +1 -0
- package/dist/src/adapters/webBundler.mjs +2 -0
- package/dist/src/adapters/webBundler.mjs.map +1 -0
- package/dist/src/adapters/webHubPluginLoader.cjs +2 -0
- package/dist/src/adapters/webHubPluginLoader.cjs.map +1 -0
- package/dist/src/adapters/webHubPluginLoader.mjs +2 -0
- package/dist/src/adapters/webHubPluginLoader.mjs.map +1 -0
- package/dist/src/adapters/webHubPlugins.generated.cjs +2 -0
- package/dist/src/adapters/webHubPlugins.generated.cjs.map +1 -0
- package/dist/src/adapters/webHubPlugins.generated.mjs +2 -0
- package/dist/src/adapters/webHubPlugins.generated.mjs.map +1 -0
- package/dist/src/adapters/webPluginGenerate.cjs +4 -0
- package/dist/src/adapters/webPluginGenerate.cjs.map +1 -0
- package/dist/src/adapters/webPluginGenerate.mjs +4 -0
- package/dist/src/adapters/webPluginGenerate.mjs.map +1 -0
- package/dist/src/adapters/webPluginScan.cjs +2 -0
- package/dist/src/adapters/webPluginScan.cjs.map +1 -0
- package/dist/src/adapters/webPluginScan.mjs +2 -0
- package/dist/src/adapters/webPluginScan.mjs.map +1 -0
- package/dist/src/adapters/webPluginVite.cjs +2 -0
- package/dist/src/adapters/webPluginVite.cjs.map +1 -0
- package/dist/src/adapters/webPluginVite.mjs +2 -0
- package/dist/src/adapters/webPluginVite.mjs.map +1 -0
- package/dist/src/services/directoryMatch.cjs +2 -0
- package/dist/src/services/directoryMatch.cjs.map +1 -0
- package/dist/src/services/directoryMatch.mjs +2 -0
- package/dist/src/services/directoryMatch.mjs.map +1 -0
- package/dist/src/services/fileMatch.cjs +2 -0
- package/dist/src/services/fileMatch.cjs.map +1 -0
- package/dist/src/services/fileMatch.mjs +2 -0
- package/dist/src/services/fileMatch.mjs.map +1 -0
- package/dist/src/services/fileMedia.cjs +2 -0
- package/dist/src/services/fileMedia.cjs.map +1 -0
- package/dist/src/services/fileMedia.mjs +2 -0
- package/dist/src/services/fileMedia.mjs.map +1 -0
- package/dist/src/services/frameRing.cjs +2 -0
- package/dist/src/services/frameRing.cjs.map +1 -0
- package/dist/src/services/frameRing.mjs +2 -0
- package/dist/src/services/frameRing.mjs.map +1 -0
- package/dist/src/services/journalTail.cjs +3 -0
- package/dist/src/services/journalTail.cjs.map +1 -0
- package/dist/src/services/journalTail.mjs +3 -0
- package/dist/src/services/journalTail.mjs.map +1 -0
- package/dist/src/services/loginFlow.cjs +2 -0
- package/dist/src/services/loginFlow.cjs.map +1 -0
- package/dist/src/services/loginFlow.mjs +2 -0
- package/dist/src/services/loginFlow.mjs.map +1 -0
- package/dist/src/services/registryStore.cjs +2 -0
- package/dist/src/services/registryStore.cjs.map +1 -0
- package/dist/src/services/registryStore.mjs +2 -0
- package/dist/src/services/registryStore.mjs.map +1 -0
- package/dist/src/services/retryPolicy.cjs +2 -0
- package/dist/src/services/retryPolicy.cjs.map +1 -0
- package/dist/src/services/retryPolicy.mjs +2 -0
- package/dist/src/services/retryPolicy.mjs.map +1 -0
- package/dist/src/services/serveOptions.cjs +2 -0
- package/dist/src/services/serveOptions.cjs.map +1 -0
- package/dist/src/services/serveOptions.d.cts +21 -0
- package/dist/src/services/serveOptions.d.cts.map +1 -0
- package/dist/src/services/serveOptions.d.mts +21 -0
- package/dist/src/services/serveOptions.d.mts.map +1 -0
- package/dist/src/services/serveOptions.mjs +2 -0
- package/dist/src/services/serveOptions.mjs.map +1 -0
- package/dist/src/services/sessionFraming.cjs +4 -0
- package/dist/src/services/sessionFraming.cjs.map +1 -0
- package/dist/src/services/sessionFraming.mjs +4 -0
- package/dist/src/services/sessionFraming.mjs.map +1 -0
- package/dist/src/services/sessionPresence.cjs +2 -0
- package/dist/src/services/sessionPresence.cjs.map +1 -0
- package/dist/src/services/sessionPresence.mjs +2 -0
- package/dist/src/services/sessionPresence.mjs.map +1 -0
- package/dist/src/services/staticAssets.cjs +2 -0
- package/dist/src/services/staticAssets.cjs.map +1 -0
- package/dist/src/services/staticAssets.mjs +2 -0
- package/dist/src/services/staticAssets.mjs.map +1 -0
- package/dist/src/services/webDevRoots.cjs +2 -0
- package/dist/src/services/webDevRoots.cjs.map +1 -0
- package/dist/src/services/webDevRoots.mjs +2 -0
- package/dist/src/services/webDevRoots.mjs.map +1 -0
- package/dist/src/services/webPluginManifest.cjs +2 -0
- package/dist/src/services/webPluginManifest.cjs.map +1 -0
- package/dist/src/services/webPluginManifest.mjs +2 -0
- package/dist/src/services/webPluginManifest.mjs.map +1 -0
- package/dist/src/types/auth.cjs +2 -0
- package/dist/src/types/auth.cjs.map +1 -0
- package/dist/src/types/auth.d.cts +94 -0
- package/dist/src/types/auth.d.cts.map +1 -0
- package/dist/src/types/auth.d.mts +94 -0
- package/dist/src/types/auth.d.mts.map +1 -0
- package/dist/src/types/auth.mjs +2 -0
- package/dist/src/types/auth.mjs.map +1 -0
- package/dist/src/types/bridge.d.cts +31 -0
- package/dist/src/types/bridge.d.cts.map +1 -0
- package/dist/src/types/bridge.d.mts +31 -0
- package/dist/src/types/bridge.d.mts.map +1 -0
- package/dist/src/types/hub.cjs +2 -0
- package/dist/src/types/hub.cjs.map +1 -0
- package/dist/src/types/hub.d.cts +58 -0
- package/dist/src/types/hub.d.cts.map +1 -0
- package/dist/src/types/hub.d.mts +58 -0
- package/dist/src/types/hub.d.mts.map +1 -0
- package/dist/src/types/hub.mjs +2 -0
- package/dist/src/types/hub.mjs.map +1 -0
- package/dist/src/types/media.cjs +2 -0
- package/dist/src/types/media.cjs.map +1 -0
- package/dist/src/types/media.mjs +2 -0
- package/dist/src/types/media.mjs.map +1 -0
- package/dist/src/types/registry.cjs +1 -0
- package/dist/src/types/registry.d.cts +2 -0
- package/dist/src/types/registry.d.mts +2 -0
- package/dist/src/types/registry.mjs +1 -0
- package/dist/src/types/session.cjs +2 -0
- package/dist/src/types/session.cjs.map +1 -0
- package/dist/src/types/session.d.cts +24 -0
- package/dist/src/types/session.d.cts.map +1 -0
- package/dist/src/types/session.d.mts +24 -0
- package/dist/src/types/session.d.mts.map +1 -0
- package/dist/src/types/session.mjs +2 -0
- package/dist/src/types/session.mjs.map +1 -0
- package/dist/web/apple-touch-icon.png +0 -0
- package/dist/web/assets/index-Cg52ngKv.css +2 -0
- package/dist/web/assets/index-DQO7NTIP.js +90 -0
- package/dist/web/assets/index-DQO7NTIP.js.map +1 -0
- package/dist/web/favicon-16.png +0 -0
- package/dist/web/favicon-32.png +0 -0
- package/dist/web/favicon.ico +0 -0
- package/dist/web/favicon.svg +16 -0
- package/dist/web/icon-512.png +0 -0
- package/dist/web/index.html +17 -0
- package/llms.txt +8 -0
- package/package.json +106 -0
- package/src/adapters/authRoutes.ts +95 -0
- package/src/adapters/bundledServer.ts +64 -0
- package/src/adapters/directoryListing.ts +127 -0
- package/src/adapters/directorySearch.ts +124 -0
- package/src/adapters/gitStatus.ts +27 -0
- package/src/adapters/httpServer.ts +556 -0
- package/src/adapters/hubProbe.ts +27 -0
- package/src/adapters/packageApiProxy.ts +60 -0
- package/src/adapters/piHubService.ts +129 -0
- package/src/adapters/piWebSocketListener.ts +98 -0
- package/src/adapters/providerAuth.ts +181 -0
- package/src/adapters/registryWatcher.ts +112 -0
- package/src/adapters/serverSpawner.ts +181 -0
- package/src/adapters/sessionFiles.ts +99 -0
- package/src/adapters/sessionHub.ts +567 -0
- package/src/adapters/sessionSocketClient.ts +105 -0
- package/src/adapters/syncGuard.ts +119 -0
- package/src/adapters/threadJournals.ts +184 -0
- package/src/adapters/webBundler.ts +83 -0
- package/src/adapters/webHubPluginLoader.ts +67 -0
- package/src/adapters/webHubPlugins.generated.ts +4 -0
- package/src/adapters/webPluginGenerate.ts +172 -0
- package/src/adapters/webPluginScan.ts +62 -0
- package/src/adapters/webPluginVite.ts +51 -0
- package/src/bin/serve.ts +64 -0
- package/src/exports/bundler.ts +1 -0
- package/src/exports/index.ts +9 -0
- package/src/services/directoryMatch.ts +71 -0
- package/src/services/fileMatch.ts +37 -0
- package/src/services/fileMedia.ts +27 -0
- package/src/services/frameRing.ts +33 -0
- package/src/services/journalTail.ts +47 -0
- package/src/services/loginFlow.ts +144 -0
- package/src/services/registryStore.ts +83 -0
- package/src/services/retryPolicy.ts +15 -0
- package/src/services/serveOptions.ts +63 -0
- package/src/services/sessionFraming.ts +32 -0
- package/src/services/sessionPresence.ts +131 -0
- package/src/services/staticAssets.ts +37 -0
- package/src/services/webDevRoots.ts +36 -0
- package/src/services/webPluginManifest.ts +149 -0
- package/src/types/auth.ts +118 -0
- package/src/types/bridge.ts +41 -0
- package/src/types/hub.ts +290 -0
- package/src/types/media.ts +38 -0
- package/src/types/registry.ts +2 -0
- package/src/types/session.ts +36 -0
- package/src/web/app/Providers.tsx +46 -0
- package/src/web/app/protocolRuntime.ts +106 -0
- package/src/web/app/sessionRuntime.ts +189 -0
- package/src/web/app/webPlugins.generated.ts +4 -0
- package/src/web/components/MascotMark.tsx +46 -0
- package/src/web/components/PluginSurface.tsx +10 -0
- package/src/web/components/ToolRendererBoundary.tsx +32 -0
- package/src/web/features/activity/ActivityDock.tsx +125 -0
- package/src/web/features/connection/RefusedCard.tsx +63 -0
- package/src/web/features/dialogs/DialogOverlay.tsx +176 -0
- package/src/web/features/leader/CommandPalette.tsx +386 -0
- package/src/web/features/selection/SelectionBar.tsx +529 -0
- package/src/web/features/session/Composer.tsx +368 -0
- package/src/web/features/session/MentionPreviews.tsx +58 -0
- package/src/web/features/session/ThreadView.tsx +35 -0
- package/src/web/features/session/Timeline.tsx +382 -0
- package/src/web/features/session/ToolCard.tsx +79 -0
- package/src/web/features/sessions/NewSessionDialog.tsx +222 -0
- package/src/web/features/sessions/SessionRail.tsx +411 -0
- package/src/web/features/settings/AppearanceSettings.tsx +88 -0
- package/src/web/features/settings/LoginFlowDialog.tsx +221 -0
- package/src/web/features/settings/PluginSettings.tsx +92 -0
- package/src/web/features/settings/ProviderSettings.tsx +238 -0
- package/src/web/features/settings/SettingsMenu.tsx +32 -0
- package/src/web/features/status/TopBar.tsx +208 -0
- package/src/web/index.html +16 -0
- package/src/web/lib/authApi.ts +90 -0
- package/src/web/lib/commands.ts +61 -0
- package/src/web/lib/composition.ts +225 -0
- package/src/web/lib/fileMentions.ts +32 -0
- package/src/web/lib/hubApi.ts +86 -0
- package/src/web/lib/leaderTree.ts +159 -0
- package/src/web/lib/piTransport.ts +61 -0
- package/src/web/lib/pluginRegistry.ts +488 -0
- package/src/web/lib/pluginSlotProps.ts +50 -0
- package/src/web/lib/promptFocus.ts +25 -0
- package/src/web/lib/protocolTimeline.ts +73 -0
- package/src/web/lib/sessionModel.ts +673 -0
- package/src/web/lib/sessionSummary.ts +58 -0
- package/src/web/lib/settingsSections.ts +21 -0
- package/src/web/lib/statusLine.ts +90 -0
- package/src/web/lib/threadRenderer.ts +25 -0
- package/src/web/lib/toolMessageProps.ts +21 -0
- package/src/web/lib/transport.ts +30 -0
- package/src/web/lib/wsClient.ts +73 -0
- package/src/web/main.tsx +17 -0
- package/src/web/public/apple-touch-icon.png +0 -0
- package/src/web/public/favicon-16.png +0 -0
- package/src/web/public/favicon-32.png +0 -0
- package/src/web/public/favicon.ico +0 -0
- package/src/web/public/favicon.svg +16 -0
- package/src/web/public/icon-512.png +0 -0
- package/src/web/routes/CockpitPage.tsx +98 -0
- package/src/web/routes/SettingsPage.tsx +60 -0
- package/src/web/routes/routeTree.tsx +49 -0
- package/src/web/stores/menuStore.ts +78 -0
- package/src/web/stores/paletteStore.ts +25 -0
- package/src/web/stores/sessionStore.ts +303 -0
- package/src/web/stores/sessionsStore.ts +155 -0
- package/src/web/stores/themeStore.ts +52 -0
- package/src/web/stores/threadStore.ts +68 -0
- package/src/web/stores/transientTabsStore.ts +63 -0
- package/src/web/stores/uiStore.ts +28 -0
- package/src/web/stores/useOpenTab.ts +19 -0
- package/src/web/stores/usePluginSlotProps.ts +22 -0
- package/src/web/styles/app.css +15 -0
- package/src/web/styles/webPluginSources.generated.css +1 -0
- package/src/web/vite-env.d.ts +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webHubPluginLoader.mjs","names":[],"sources":["../../../src/adapters/webHubPluginLoader.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport type { WebHubChannel } from '@agimon-ai/doompi-web-contracts';\nimport { SERVER_REGISTRY_FILE } from './webPluginGenerate.ts';\nimport { BUILTIN_HUB_CHANNELS } from './webHubPlugins.generated.ts';\n\ninterface RegistryEntry {\n pluginId?: unknown;\n hubEntry?: unknown;\n}\n\nconst UNKNOWN_PLUGIN_ID = 'unknown';\n\n/** The synced bundle's server registry, or an empty list when there is none to read. */\nfunction registryEntries(assetsDir: string, onNotice: (message: string) => void): RegistryEntry[] {\n const registryPath = path.join(assetsDir, SERVER_REGISTRY_FILE);\n if (!fs.existsSync(registryPath)) return [];\n try {\n const parsed: unknown = JSON.parse(fs.readFileSync(registryPath, 'utf8'));\n return Array.isArray(parsed) ? (parsed as RegistryEntry[]) : [];\n } catch (error) {\n onNotice(`web plugin registry ${registryPath} is unreadable (${String(error)}); plugin channels skipped`);\n return [];\n }\n}\n\n/**\n * Assembles the hub's data channels for the assets being served.\n *\n * A synced bundle carries webPlugins.server.json naming each plugin's built\n * hub entry by absolute path; those are imported lazily so an absent or\n * broken plugin package is a notice and an empty tab, never a crash. Without\n * a registry (the package's own prebuilt bundle, or a custom assets dir) the\n * built-in channels are the whole set.\n */\nexport async function loadHubChannels(\n assetsDir: string,\n onNotice: (message: string) => void,\n): Promise<WebHubChannel[]> {\n const channels: WebHubChannel[] = [...BUILTIN_HUB_CHANNELS];\n for (const entry of registryEntries(assetsDir, onNotice)) {\n const pluginId = typeof entry.pluginId === 'string' ? entry.pluginId : UNKNOWN_PLUGIN_ID;\n if (typeof entry.hubEntry !== 'string') continue;\n try {\n const module = (await import(pathToFileURL(entry.hubEntry).href)) as { webHubChannels?: unknown };\n if (!Array.isArray(module.webHubChannels)) {\n throw new Error(`'${entry.hubEntry}' exports no webHubChannels array`);\n }\n channels.push(...(module.webHubChannels as WebHubChannel[]));\n } catch (error) {\n const cause = error instanceof Error ? error.message : String(error);\n onNotice(`web plugin '${pluginId}' hub channels unavailable (${cause}); its panels stay empty`);\n }\n }\n const seen = new Set<string>();\n const unique: WebHubChannel[] = [];\n for (const channel of channels) {\n if (seen.has(channel.frameType)) {\n onNotice(`duplicate web channel '${channel.frameType}' dropped; frame types are global`);\n continue;\n }\n seen.add(channel.frameType);\n unique.push(channel);\n }\n return unique;\n}\n"],"mappings":"6NAeA,SAAS,EAAgB,EAAmB,EAAsD,CAChG,IAAM,EAAe,EAAK,KAAK,EAAW,CAAoB,EAC9D,GAAI,CAAC,EAAG,WAAW,CAAY,EAAG,MAAO,CAAC,EAC1C,GAAI,CACF,IAAM,EAAkB,KAAK,MAAM,EAAG,aAAa,EAAc,MAAM,CAAC,EACxE,OAAO,MAAM,QAAQ,CAAM,EAAK,EAA6B,CAAC,CAChE,OAAS,EAAO,CAEd,OADA,EAAS,uBAAuB,EAAa,kBAAkB,OAAO,CAAK,EAAE,2BAA2B,EACjG,CAAC,CACV,CACF,CAWA,eAAsB,EACpB,EACA,EAC0B,CAC1B,IAAM,EAA4B,CAAC,GAAG,CAAoB,EAC1D,IAAK,IAAM,KAAS,EAAgB,EAAW,CAAQ,EAAG,CACxD,IAAM,EAAW,OAAO,EAAM,UAAa,SAAW,EAAM,SAAW,UACnE,UAAO,EAAM,UAAa,SAC9B,GAAI,CACF,IAAM,EAAU,MAAM,OAAO,EAAc,EAAM,QAAQ,CAAC,CAAC,MAC3D,GAAI,CAAC,MAAM,QAAQ,EAAO,cAAc,EACtC,MAAU,MAAM,IAAI,EAAM,SAAS,kCAAkC,EAEvE,EAAS,KAAK,GAAI,EAAO,cAAkC,CAC7D,OAAS,EAAO,CAEd,EAAS,eAAe,EAAS,8BADnB,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,EACE,yBAAyB,CAChG,CACF,CACA,IAAM,EAAO,IAAI,IACX,EAA0B,CAAC,EACjC,IAAK,IAAM,KAAW,EAAU,CAC9B,GAAI,EAAK,IAAI,EAAQ,SAAS,EAAG,CAC/B,EAAS,0BAA0B,EAAQ,UAAU,kCAAkC,EACvF,QACF,CACA,EAAK,IAAI,EAAQ,SAAS,EAC1B,EAAO,KAAK,CAAO,CACrB,CACA,OAAO,CACT"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webHubPlugins.generated.cjs","names":[],"sources":["../../../src/adapters/webHubPlugins.generated.ts"],"sourcesContent":["// Generated by src/adapters/webPluginGenerate.ts. Do not edit by hand.\nimport type { WebHubChannel } from '@agimon-ai/doompi-web-contracts';\n\nexport const BUILTIN_HUB_CHANNELS: readonly WebHubChannel[] = [];\n"],"mappings":"AAGA,MAAa,EAAiD,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webHubPlugins.generated.mjs","names":[],"sources":["../../../src/adapters/webHubPlugins.generated.ts"],"sourcesContent":["// Generated by src/adapters/webPluginGenerate.ts. Do not edit by hand.\nimport type { WebHubChannel } from '@agimon-ai/doompi-web-contracts';\n\nexport const BUILTIN_HUB_CHANNELS: readonly WebHubChannel[] = [];\n"],"mappings":"AAGA,MAAa,EAAiD,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
const e=require("../../_virtual/_rolldown/runtime.cjs");let t=require("node:fs");t=e.__toESM(t,1);let n=require("node:path");n=e.__toESM(n,1);let r=require("node:url");const i=`// Generated by src/adapters/webPluginGenerate.ts. Do not edit by hand.`;function a(){let e=n.default.dirname((0,r.fileURLToPath)(require("url").pathToFileURL(__filename).href));for(;;){if(t.default.existsSync(n.default.join(e,`package.json`)))return e;let r=n.default.dirname(e);if(r===e)throw Error(`doompi-web could not locate its own package root.`);e=r}}const o=a();n.default.join(`src`,`web`,`app`,`webPlugins.generated.ts`),n.default.join(`src`,`adapters`,`webHubPlugins.generated.ts`),n.default.join(`src`,`web`,`styles`,`webPluginSources.generated.css`);function s(){return o}function c(e){return e.replace(/-([a-z0-9])/g,(e,t)=>t.toUpperCase())}function l(e,t){let r=n.default.relative(e,t).split(n.default.sep).join(`/`);return r.startsWith(`.`)?r:`./${r}`}function u(e,t){let a=[i,`import type { WebPluginDefinition } from '@agimon-ai/doompi-web-contracts';`],s=[],u=n.default.join(o,`src`,`web`,`app`);for(let i of e){let e=`${c(i.pluginId)}Plugin`;s.push(e);let o=n.default.join(i.packageDir,i.client.entry),d=t===`relative`?l(u,o):(0,r.pathToFileURL)(o).href;a.push(`import { webPlugin as ${e} } from '${d}';`)}return a.push(``,`export const webPlugins: readonly WebPluginDefinition[] = [${s.join(`, `)}];`,``),a.join(`
|
|
2
|
+
`)}function d(e){let t=[`/* ${i.slice(3)} */`];for(let r of e)r.isHost||t.push(`@source "${n.default.dirname(n.default.join(r.packageDir,r.client.entry))}";`);return t.push(``),t.join(`
|
|
3
|
+
`)}function f(e){let t=[];for(let r of e)r.hub!==void 0&&(r.isHost||t.push({pluginId:r.pluginId,channels:r.channels,hubEntry:n.default.join(r.packageDir,r.hub.dist??r.hub.entry)}));return`${JSON.stringify(t,null,2)}\n`}function p(e,r){t.default.mkdirSync(r,{recursive:!0});let i=n.default.join(r,`webPlugins.generated.ts`),a=n.default.join(r,`webPluginSources.generated.css`);return t.default.writeFileSync(i,u(e,`absolute`)),t.default.writeFileSync(a,d(e)),{clientModulePath:i,cssModulePath:a,serverRegistry:f(e)}}exports.SERVER_REGISTRY_FILE=`webPlugins.server.json`,exports.webHostPackageRoot=s,exports.writeSyncWebPluginModules=p;
|
|
4
|
+
//# sourceMappingURL=webPluginGenerate.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webPluginGenerate.cjs","names":["path","fileURLToPath","fs","pathToFileURL"],"sources":["../../../src/adapters/webPluginGenerate.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport { fileURLToPath, pathToFileURL } from 'node:url';\nimport type { DeclaredWebPlugin } from '../services/webPluginManifest.ts';\nimport { scanWebPlugins } from './webPluginScan.ts';\n\nconst HEADER = '// Generated by src/adapters/webPluginGenerate.ts. Do not edit by hand.';\n\n/** Walks up to the owning package.json: correct from src and from the unbundled dist tree alike. */\nfunction ownPackageRoot(): string {\n let dir = path.dirname(fileURLToPath(import.meta.url));\n for (;;) {\n if (fs.existsSync(path.join(dir, 'package.json'))) return dir;\n const parent = path.dirname(dir);\n if (parent === dir) throw new Error('doompi-web could not locate its own package root.');\n dir = parent;\n }\n}\n\nconst packageRoot = ownPackageRoot();\n\nexport const BUILTIN_CLIENT_MODULE = path.join('src', 'web', 'app', 'webPlugins.generated.ts');\nexport const BUILTIN_HUB_MODULE = path.join('src', 'adapters', 'webHubPlugins.generated.ts');\nexport const BUILTIN_CSS_MODULE = path.join('src', 'web', 'styles', 'webPluginSources.generated.css');\n/** The server-side registry a synced bundle carries next to its assets. */\nexport const SERVER_REGISTRY_FILE = 'webPlugins.server.json';\n\nexport function webHostPackageRoot(): string {\n return packageRoot;\n}\n\nfunction camel(pluginId: string): string {\n return pluginId.replace(/-([a-z0-9])/g, (_, ch: string) => ch.toUpperCase());\n}\n\nfunction relativeImport(fromDir: string, toFile: string): string {\n const spec = path.relative(fromDir, toFile).split(path.sep).join('/');\n return spec.startsWith('.') ? spec : `./${spec}`;\n}\n\nfunction renderClientModule(plugins: readonly DeclaredWebPlugin[], style: 'relative' | 'absolute'): string {\n const lines = [HEADER, \"import type { WebPluginDefinition } from '@agimon-ai/doompi-web-contracts';\"];\n const names: string[] = [];\n const fromDir = path.join(packageRoot, 'src', 'web', 'app');\n for (const plugin of plugins) {\n const name = `${camel(plugin.pluginId)}Plugin`;\n names.push(name);\n const entry = path.join(plugin.packageDir, plugin.client.entry);\n // The committed builtin module keeps machine-neutral relative imports; the\n // sync-generated module lives outside src, is aliased over the committed\n // path, and resolves imports against its real location, so every entry\n // becomes an absolute file URL into the installed packages.\n const specifier = style === 'relative' ? relativeImport(fromDir, entry) : pathToFileURL(entry).href;\n lines.push(`import { webPlugin as ${name} } from '${specifier}';`);\n }\n lines.push('', `export const webPlugins: readonly WebPluginDefinition[] = [${names.join(', ')}];`, '');\n return lines.join('\\n');\n}\n\nfunction renderBuiltinHubModule(plugins: readonly DeclaredWebPlugin[]): string {\n const lines = [HEADER, \"import type { WebHubChannel } from '@agimon-ai/doompi-web-contracts';\"];\n const names: string[] = [];\n for (const plugin of plugins) {\n if (plugin.hub === undefined || !plugin.isHost) continue;\n const name = `${camel(plugin.pluginId)}Channels`;\n names.push(name);\n const specifier = relativeImport(\n path.join(packageRoot, 'src', 'adapters'),\n path.join(plugin.packageDir, plugin.hub.entry),\n );\n lines.push(`import { webHubChannels as ${name} } from '${specifier}';`);\n }\n lines.push('');\n lines.push(\n names.length === 0\n ? 'export const BUILTIN_HUB_CHANNELS: readonly WebHubChannel[] = [];'\n : `export const BUILTIN_HUB_CHANNELS: readonly WebHubChannel[] = [${names.map((name) => `...${name}`).join(', ')}];`,\n );\n lines.push('');\n return lines.join('\\n');\n}\n\nfunction renderCssModule(plugins: readonly DeclaredWebPlugin[]): string {\n const lines = [`/* ${HEADER.slice(3)} */`];\n for (const plugin of plugins) {\n if (plugin.isHost) continue; // The host tree is already inside Tailwind's scan root.\n lines.push(`@source \"${path.dirname(path.join(plugin.packageDir, plugin.client.entry))}\";`);\n }\n lines.push('');\n return lines.join('\\n');\n}\n\nexport interface ServerRegistryEntry {\n pluginId: string;\n channels: string[];\n /** Absolute path to the built hub entry exporting webHubChannels. */\n hubEntry: string;\n}\n\nfunction renderServerRegistry(plugins: readonly DeclaredWebPlugin[]): string {\n const entries: ServerRegistryEntry[] = [];\n for (const plugin of plugins) {\n if (plugin.hub === undefined) continue;\n // Host channels are compiled into the server; only externals need loading.\n if (plugin.isHost) continue;\n entries.push({\n pluginId: plugin.pluginId,\n channels: plugin.channels,\n hubEntry: path.join(plugin.packageDir, plugin.hub.dist ?? plugin.hub.entry),\n });\n }\n return `${JSON.stringify(entries, null, 2)}\\n`;\n}\n\n/** The committed builtin registry, rendered from the host package alone. */\nexport function renderBuiltinWebPluginModules(): Map<string, string> {\n const plugins = scanWebPlugins(packageRoot);\n return new Map([\n [BUILTIN_CLIENT_MODULE, renderClientModule(plugins, 'relative')],\n [BUILTIN_HUB_MODULE, renderBuiltinHubModule(plugins)],\n [BUILTIN_CSS_MODULE, renderCssModule(plugins)],\n ]);\n}\n\n/**\n * Keeps the committed builtin registry fresh: write-if-changed locally,\n * check-only (throw) in CI. Both vite.config.ts and tsdown.config.ts call\n * this at config-load time.\n */\nexport function ensureBuiltinWebPluginModules({ check = false }: { check?: boolean } = {}): void {\n const stale: string[] = [];\n for (const [relativePath, content] of renderBuiltinWebPluginModules()) {\n const filePath = path.join(packageRoot, relativePath);\n const existing = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf8') : undefined;\n if (existing === content) continue;\n if (check) {\n stale.push(relativePath);\n continue;\n }\n fs.mkdirSync(path.dirname(filePath), { recursive: true });\n fs.writeFileSync(filePath, content);\n }\n if (stale.length > 0) {\n throw new Error(\n `Stale generated web plugin modules: ${stale.join(', ')}. Run pnpm build in doompi-web and commit them.`,\n );\n }\n}\n\nexport interface SyncGeneratedModules {\n clientModulePath: string;\n cssModulePath: string;\n serverRegistry: string;\n}\n\n/**\n * The sync-time variant: renders the full plugin set (host built-ins plus the\n * installed composition's packages) into a generation directory the bundler\n * aliases over the committed builtin modules, plus the server-side registry\n * the hub loads at startup.\n */\nexport function writeSyncWebPluginModules(\n plugins: readonly DeclaredWebPlugin[],\n generatedDir: string,\n): SyncGeneratedModules {\n fs.mkdirSync(generatedDir, { recursive: true });\n const clientModulePath = path.join(generatedDir, 'webPlugins.generated.ts');\n const cssModulePath = path.join(generatedDir, 'webPluginSources.generated.css');\n fs.writeFileSync(clientModulePath, renderClientModule(plugins, 'absolute'));\n fs.writeFileSync(cssModulePath, renderCssModule(plugins));\n return { clientModulePath, cssModulePath, serverRegistry: renderServerRegistry(plugins) };\n}\n"],"mappings":"wKAMA,MAAM,EAAS,0EAGf,SAAS,GAAyB,CAChC,IAAI,EAAMA,EAAAA,QAAK,SAAA,EAAQC,EAAAA,cAAAA,CAAAA,QAAAA,KAAAA,CAAAA,CAAAA,cAAAA,UAAAA,CAAAA,CAAAA,IAA6B,CAAC,EACrD,OAAS,CACP,GAAIC,EAAAA,QAAG,WAAWF,EAAAA,QAAK,KAAK,EAAK,cAAc,CAAC,EAAG,OAAO,EAC1D,IAAM,EAASA,EAAAA,QAAK,QAAQ,CAAG,EAC/B,GAAI,IAAW,EAAK,MAAU,MAAM,mDAAmD,EACvF,EAAM,CACR,CACF,CAEA,MAAM,EAAc,EAAe,EAEEA,EAAAA,QAAK,KAAK,MAAO,MAAO,MAAO,yBAAyB,EAC3DA,EAAAA,QAAK,KAAK,MAAO,WAAY,4BAA4B,EACzDA,EAAAA,QAAK,KAAK,MAAO,MAAO,SAAU,gCAAgC,EAIpG,SAAgB,GAA6B,CAC3C,OAAO,CACT,CAEA,SAAS,EAAM,EAA0B,CACvC,OAAO,EAAS,QAAQ,gBAAiB,EAAG,IAAe,EAAG,YAAY,CAAC,CAC7E,CAEA,SAAS,EAAe,EAAiB,EAAwB,CAC/D,IAAM,EAAOA,EAAAA,QAAK,SAAS,EAAS,CAAM,CAAC,CAAC,MAAMA,EAAAA,QAAK,GAAG,CAAC,CAAC,KAAK,GAAG,EACpE,OAAO,EAAK,WAAW,GAAG,EAAI,EAAO,KAAK,GAC5C,CAEA,SAAS,EAAmB,EAAuC,EAAwC,CACzG,IAAM,EAAQ,CAAC,EAAQ,6EAA6E,EAC9F,EAAkB,CAAC,EACnB,EAAUA,EAAAA,QAAK,KAAK,EAAa,MAAO,MAAO,KAAK,EAC1D,IAAK,IAAM,KAAU,EAAS,CAC5B,IAAM,EAAO,GAAG,EAAM,EAAO,QAAQ,EAAE,QACvC,EAAM,KAAK,CAAI,EACf,IAAM,EAAQA,EAAAA,QAAK,KAAK,EAAO,WAAY,EAAO,OAAO,KAAK,EAKxD,EAAY,IAAU,WAAa,EAAe,EAAS,CAAK,GAAA,EAAIG,EAAAA,cAAAA,CAAc,CAAK,CAAC,CAAC,KAC/F,EAAM,KAAK,yBAAyB,EAAK,WAAW,EAAU,GAAG,CACnE,CAEA,OADA,EAAM,KAAK,GAAI,8DAA8D,EAAM,KAAK,IAAI,EAAE,IAAK,EAAE,EAC9F,EAAM,KAAK;CAAI,CACxB,CAyBA,SAAS,EAAgB,EAA+C,CACtE,IAAM,EAAQ,CAAC,MAAM,EAAO,MAAM,CAAC,EAAE,IAAI,EACzC,IAAK,IAAM,KAAU,EACf,EAAO,QACX,EAAM,KAAK,YAAYH,EAAAA,QAAK,QAAQA,EAAAA,QAAK,KAAK,EAAO,WAAY,EAAO,OAAO,KAAK,CAAC,EAAE,GAAG,EAG5F,OADA,EAAM,KAAK,EAAE,EACN,EAAM,KAAK;CAAI,CACxB,CASA,SAAS,EAAqB,EAA+C,CAC3E,IAAM,EAAiC,CAAC,EACxC,IAAK,IAAM,KAAU,EACf,EAAO,MAAQ,IAAA,KAEf,EAAO,QACX,EAAQ,KAAK,CACX,SAAU,EAAO,SACjB,SAAU,EAAO,SACjB,SAAUA,EAAAA,QAAK,KAAK,EAAO,WAAY,EAAO,IAAI,MAAQ,EAAO,IAAI,KAAK,CAC5E,CAAC,GAEH,MAAO,GAAG,KAAK,UAAU,EAAS,KAAM,CAAC,EAAE,GAC7C,CAiDA,SAAgB,EACd,EACA,EACsB,CACtB,EAAA,QAAG,UAAU,EAAc,CAAE,UAAW,EAAK,CAAC,EAC9C,IAAM,EAAmBA,EAAAA,QAAK,KAAK,EAAc,yBAAyB,EACpE,EAAgBA,EAAAA,QAAK,KAAK,EAAc,gCAAgC,EAG9E,OAFA,EAAA,QAAG,cAAc,EAAkB,EAAmB,EAAS,UAAU,CAAC,EAC1E,EAAA,QAAG,cAAc,EAAe,EAAgB,CAAO,CAAC,EACjD,CAAE,mBAAkB,gBAAe,eAAgB,EAAqB,CAAO,CAAE,CAC1F"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import e from"node:fs";import t from"node:path";import{fileURLToPath as n,pathToFileURL as r}from"node:url";const i=`// Generated by src/adapters/webPluginGenerate.ts. Do not edit by hand.`;function a(){let r=t.dirname(n(import.meta.url));for(;;){if(e.existsSync(t.join(r,`package.json`)))return r;let n=t.dirname(r);if(n===r)throw Error(`doompi-web could not locate its own package root.`);r=n}}const o=a();t.join(`src`,`web`,`app`,`webPlugins.generated.ts`),t.join(`src`,`adapters`,`webHubPlugins.generated.ts`),t.join(`src`,`web`,`styles`,`webPluginSources.generated.css`);const s=`webPlugins.server.json`;function c(){return o}function l(e){return e.replace(/-([a-z0-9])/g,(e,t)=>t.toUpperCase())}function u(e,n){let r=t.relative(e,n).split(t.sep).join(`/`);return r.startsWith(`.`)?r:`./${r}`}function d(e,n){let a=[i,`import type { WebPluginDefinition } from '@agimon-ai/doompi-web-contracts';`],s=[],c=t.join(o,`src`,`web`,`app`);for(let i of e){let e=`${l(i.pluginId)}Plugin`;s.push(e);let o=t.join(i.packageDir,i.client.entry),d=n===`relative`?u(c,o):r(o).href;a.push(`import { webPlugin as ${e} } from '${d}';`)}return a.push(``,`export const webPlugins: readonly WebPluginDefinition[] = [${s.join(`, `)}];`,``),a.join(`
|
|
2
|
+
`)}function f(e){let n=[`/* ${i.slice(3)} */`];for(let r of e)r.isHost||n.push(`@source "${t.dirname(t.join(r.packageDir,r.client.entry))}";`);return n.push(``),n.join(`
|
|
3
|
+
`)}function p(e){let n=[];for(let r of e)r.hub!==void 0&&(r.isHost||n.push({pluginId:r.pluginId,channels:r.channels,hubEntry:t.join(r.packageDir,r.hub.dist??r.hub.entry)}));return`${JSON.stringify(n,null,2)}\n`}function m(n,r){e.mkdirSync(r,{recursive:!0});let i=t.join(r,`webPlugins.generated.ts`),a=t.join(r,`webPluginSources.generated.css`);return e.writeFileSync(i,d(n,`absolute`)),e.writeFileSync(a,f(n)),{clientModulePath:i,cssModulePath:a,serverRegistry:p(n)}}export{s as SERVER_REGISTRY_FILE,c as webHostPackageRoot,m as writeSyncWebPluginModules};
|
|
4
|
+
//# sourceMappingURL=webPluginGenerate.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webPluginGenerate.mjs","names":[],"sources":["../../../src/adapters/webPluginGenerate.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport { fileURLToPath, pathToFileURL } from 'node:url';\nimport type { DeclaredWebPlugin } from '../services/webPluginManifest.ts';\nimport { scanWebPlugins } from './webPluginScan.ts';\n\nconst HEADER = '// Generated by src/adapters/webPluginGenerate.ts. Do not edit by hand.';\n\n/** Walks up to the owning package.json: correct from src and from the unbundled dist tree alike. */\nfunction ownPackageRoot(): string {\n let dir = path.dirname(fileURLToPath(import.meta.url));\n for (;;) {\n if (fs.existsSync(path.join(dir, 'package.json'))) return dir;\n const parent = path.dirname(dir);\n if (parent === dir) throw new Error('doompi-web could not locate its own package root.');\n dir = parent;\n }\n}\n\nconst packageRoot = ownPackageRoot();\n\nexport const BUILTIN_CLIENT_MODULE = path.join('src', 'web', 'app', 'webPlugins.generated.ts');\nexport const BUILTIN_HUB_MODULE = path.join('src', 'adapters', 'webHubPlugins.generated.ts');\nexport const BUILTIN_CSS_MODULE = path.join('src', 'web', 'styles', 'webPluginSources.generated.css');\n/** The server-side registry a synced bundle carries next to its assets. */\nexport const SERVER_REGISTRY_FILE = 'webPlugins.server.json';\n\nexport function webHostPackageRoot(): string {\n return packageRoot;\n}\n\nfunction camel(pluginId: string): string {\n return pluginId.replace(/-([a-z0-9])/g, (_, ch: string) => ch.toUpperCase());\n}\n\nfunction relativeImport(fromDir: string, toFile: string): string {\n const spec = path.relative(fromDir, toFile).split(path.sep).join('/');\n return spec.startsWith('.') ? spec : `./${spec}`;\n}\n\nfunction renderClientModule(plugins: readonly DeclaredWebPlugin[], style: 'relative' | 'absolute'): string {\n const lines = [HEADER, \"import type { WebPluginDefinition } from '@agimon-ai/doompi-web-contracts';\"];\n const names: string[] = [];\n const fromDir = path.join(packageRoot, 'src', 'web', 'app');\n for (const plugin of plugins) {\n const name = `${camel(plugin.pluginId)}Plugin`;\n names.push(name);\n const entry = path.join(plugin.packageDir, plugin.client.entry);\n // The committed builtin module keeps machine-neutral relative imports; the\n // sync-generated module lives outside src, is aliased over the committed\n // path, and resolves imports against its real location, so every entry\n // becomes an absolute file URL into the installed packages.\n const specifier = style === 'relative' ? relativeImport(fromDir, entry) : pathToFileURL(entry).href;\n lines.push(`import { webPlugin as ${name} } from '${specifier}';`);\n }\n lines.push('', `export const webPlugins: readonly WebPluginDefinition[] = [${names.join(', ')}];`, '');\n return lines.join('\\n');\n}\n\nfunction renderBuiltinHubModule(plugins: readonly DeclaredWebPlugin[]): string {\n const lines = [HEADER, \"import type { WebHubChannel } from '@agimon-ai/doompi-web-contracts';\"];\n const names: string[] = [];\n for (const plugin of plugins) {\n if (plugin.hub === undefined || !plugin.isHost) continue;\n const name = `${camel(plugin.pluginId)}Channels`;\n names.push(name);\n const specifier = relativeImport(\n path.join(packageRoot, 'src', 'adapters'),\n path.join(plugin.packageDir, plugin.hub.entry),\n );\n lines.push(`import { webHubChannels as ${name} } from '${specifier}';`);\n }\n lines.push('');\n lines.push(\n names.length === 0\n ? 'export const BUILTIN_HUB_CHANNELS: readonly WebHubChannel[] = [];'\n : `export const BUILTIN_HUB_CHANNELS: readonly WebHubChannel[] = [${names.map((name) => `...${name}`).join(', ')}];`,\n );\n lines.push('');\n return lines.join('\\n');\n}\n\nfunction renderCssModule(plugins: readonly DeclaredWebPlugin[]): string {\n const lines = [`/* ${HEADER.slice(3)} */`];\n for (const plugin of plugins) {\n if (plugin.isHost) continue; // The host tree is already inside Tailwind's scan root.\n lines.push(`@source \"${path.dirname(path.join(plugin.packageDir, plugin.client.entry))}\";`);\n }\n lines.push('');\n return lines.join('\\n');\n}\n\nexport interface ServerRegistryEntry {\n pluginId: string;\n channels: string[];\n /** Absolute path to the built hub entry exporting webHubChannels. */\n hubEntry: string;\n}\n\nfunction renderServerRegistry(plugins: readonly DeclaredWebPlugin[]): string {\n const entries: ServerRegistryEntry[] = [];\n for (const plugin of plugins) {\n if (plugin.hub === undefined) continue;\n // Host channels are compiled into the server; only externals need loading.\n if (plugin.isHost) continue;\n entries.push({\n pluginId: plugin.pluginId,\n channels: plugin.channels,\n hubEntry: path.join(plugin.packageDir, plugin.hub.dist ?? plugin.hub.entry),\n });\n }\n return `${JSON.stringify(entries, null, 2)}\\n`;\n}\n\n/** The committed builtin registry, rendered from the host package alone. */\nexport function renderBuiltinWebPluginModules(): Map<string, string> {\n const plugins = scanWebPlugins(packageRoot);\n return new Map([\n [BUILTIN_CLIENT_MODULE, renderClientModule(plugins, 'relative')],\n [BUILTIN_HUB_MODULE, renderBuiltinHubModule(plugins)],\n [BUILTIN_CSS_MODULE, renderCssModule(plugins)],\n ]);\n}\n\n/**\n * Keeps the committed builtin registry fresh: write-if-changed locally,\n * check-only (throw) in CI. Both vite.config.ts and tsdown.config.ts call\n * this at config-load time.\n */\nexport function ensureBuiltinWebPluginModules({ check = false }: { check?: boolean } = {}): void {\n const stale: string[] = [];\n for (const [relativePath, content] of renderBuiltinWebPluginModules()) {\n const filePath = path.join(packageRoot, relativePath);\n const existing = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf8') : undefined;\n if (existing === content) continue;\n if (check) {\n stale.push(relativePath);\n continue;\n }\n fs.mkdirSync(path.dirname(filePath), { recursive: true });\n fs.writeFileSync(filePath, content);\n }\n if (stale.length > 0) {\n throw new Error(\n `Stale generated web plugin modules: ${stale.join(', ')}. Run pnpm build in doompi-web and commit them.`,\n );\n }\n}\n\nexport interface SyncGeneratedModules {\n clientModulePath: string;\n cssModulePath: string;\n serverRegistry: string;\n}\n\n/**\n * The sync-time variant: renders the full plugin set (host built-ins plus the\n * installed composition's packages) into a generation directory the bundler\n * aliases over the committed builtin modules, plus the server-side registry\n * the hub loads at startup.\n */\nexport function writeSyncWebPluginModules(\n plugins: readonly DeclaredWebPlugin[],\n generatedDir: string,\n): SyncGeneratedModules {\n fs.mkdirSync(generatedDir, { recursive: true });\n const clientModulePath = path.join(generatedDir, 'webPlugins.generated.ts');\n const cssModulePath = path.join(generatedDir, 'webPluginSources.generated.css');\n fs.writeFileSync(clientModulePath, renderClientModule(plugins, 'absolute'));\n fs.writeFileSync(cssModulePath, renderCssModule(plugins));\n return { clientModulePath, cssModulePath, serverRegistry: renderServerRegistry(plugins) };\n}\n"],"mappings":"4GAMA,MAAM,EAAS,0EAGf,SAAS,GAAyB,CAChC,IAAI,EAAM,EAAK,QAAQ,EAAc,YAAY,GAAG,CAAC,EACrD,OAAS,CACP,GAAI,EAAG,WAAW,EAAK,KAAK,EAAK,cAAc,CAAC,EAAG,OAAO,EAC1D,IAAM,EAAS,EAAK,QAAQ,CAAG,EAC/B,GAAI,IAAW,EAAK,MAAU,MAAM,mDAAmD,EACvF,EAAM,CACR,CACF,CAEA,MAAM,EAAc,EAAe,EAEE,EAAK,KAAK,MAAO,MAAO,MAAO,yBAAyB,EAC3D,EAAK,KAAK,MAAO,WAAY,4BAA4B,EACzD,EAAK,KAAK,MAAO,MAAO,SAAU,gCAAgC,EAEpG,MAAa,EAAuB,yBAEpC,SAAgB,GAA6B,CAC3C,OAAO,CACT,CAEA,SAAS,EAAM,EAA0B,CACvC,OAAO,EAAS,QAAQ,gBAAiB,EAAG,IAAe,EAAG,YAAY,CAAC,CAC7E,CAEA,SAAS,EAAe,EAAiB,EAAwB,CAC/D,IAAM,EAAO,EAAK,SAAS,EAAS,CAAM,CAAC,CAAC,MAAM,EAAK,GAAG,CAAC,CAAC,KAAK,GAAG,EACpE,OAAO,EAAK,WAAW,GAAG,EAAI,EAAO,KAAK,GAC5C,CAEA,SAAS,EAAmB,EAAuC,EAAwC,CACzG,IAAM,EAAQ,CAAC,EAAQ,6EAA6E,EAC9F,EAAkB,CAAC,EACnB,EAAU,EAAK,KAAK,EAAa,MAAO,MAAO,KAAK,EAC1D,IAAK,IAAM,KAAU,EAAS,CAC5B,IAAM,EAAO,GAAG,EAAM,EAAO,QAAQ,EAAE,QACvC,EAAM,KAAK,CAAI,EACf,IAAM,EAAQ,EAAK,KAAK,EAAO,WAAY,EAAO,OAAO,KAAK,EAKxD,EAAY,IAAU,WAAa,EAAe,EAAS,CAAK,EAAI,EAAc,CAAK,CAAC,CAAC,KAC/F,EAAM,KAAK,yBAAyB,EAAK,WAAW,EAAU,GAAG,CACnE,CAEA,OADA,EAAM,KAAK,GAAI,8DAA8D,EAAM,KAAK,IAAI,EAAE,IAAK,EAAE,EAC9F,EAAM,KAAK;CAAI,CACxB,CAyBA,SAAS,EAAgB,EAA+C,CACtE,IAAM,EAAQ,CAAC,MAAM,EAAO,MAAM,CAAC,EAAE,IAAI,EACzC,IAAK,IAAM,KAAU,EACf,EAAO,QACX,EAAM,KAAK,YAAY,EAAK,QAAQ,EAAK,KAAK,EAAO,WAAY,EAAO,OAAO,KAAK,CAAC,EAAE,GAAG,EAG5F,OADA,EAAM,KAAK,EAAE,EACN,EAAM,KAAK;CAAI,CACxB,CASA,SAAS,EAAqB,EAA+C,CAC3E,IAAM,EAAiC,CAAC,EACxC,IAAK,IAAM,KAAU,EACf,EAAO,MAAQ,IAAA,KAEf,EAAO,QACX,EAAQ,KAAK,CACX,SAAU,EAAO,SACjB,SAAU,EAAO,SACjB,SAAU,EAAK,KAAK,EAAO,WAAY,EAAO,IAAI,MAAQ,EAAO,IAAI,KAAK,CAC5E,CAAC,GAEH,MAAO,GAAG,KAAK,UAAU,EAAS,KAAM,CAAC,EAAE,GAC7C,CAiDA,SAAgB,EACd,EACA,EACsB,CACtB,EAAG,UAAU,EAAc,CAAE,UAAW,EAAK,CAAC,EAC9C,IAAM,EAAmB,EAAK,KAAK,EAAc,yBAAyB,EACpE,EAAgB,EAAK,KAAK,EAAc,gCAAgC,EAG9E,OAFA,EAAG,cAAc,EAAkB,EAAmB,EAAS,UAAU,CAAC,EAC1E,EAAG,cAAc,EAAe,EAAgB,CAAO,CAAC,EACjD,CAAE,mBAAkB,gBAAe,eAAgB,EAAqB,CAAO,CAAE,CAC1F"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("../services/webPluginManifest.cjs");let n=require("node:fs");n=e.__toESM(n,1);let r=require("node:path");r=e.__toESM(r,1);function i(e,n=[],i=()=>void 0){let o=new Set,s=[],c=r.default.resolve(e);for(let l of[e,...n]){let e=r.default.resolve(l);if(!o.has(e)){o.add(e);try{s.push(...a(e,e===c))}catch(n){if(e===c)throw n;i(`web plugin package ${e} skipped: ${n instanceof t.WebPluginManifestError?n.message:String(n)}`)}}}return t.orderDeclaredPlugins(s,i)}function a(e,i){let a;try{a=JSON.parse(n.default.readFileSync(r.default.join(e,`package.json`),`utf8`))}catch(n){throw new t.WebPluginManifestError(e,`package.json is unreadable: ${String(n)}`)}let o=t.declaredPluginsOf(e,a,i);for(let i of o){let a=r.default.join(e,i.client.entry);if(!n.default.existsSync(a))throw new t.WebPluginManifestError(e,`client.entry '${i.client.entry}' does not exist.`);if(i.hub&&!n.default.existsSync(r.default.join(e,i.hub.entry)))throw new t.WebPluginManifestError(e,`hub.entry '${i.hub.entry}' does not exist.`)}return o}exports.scanWebPlugins=i;
|
|
2
|
+
//# sourceMappingURL=webPluginScan.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webPluginScan.cjs","names":["path","WebPluginManifestError","orderDeclaredPlugins","fs","declaredPluginsOf"],"sources":["../../../src/adapters/webPluginScan.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport {\n type DeclaredWebPlugin,\n declaredPluginsOf,\n orderDeclaredPlugins,\n WebPluginManifestError,\n} from '../services/webPluginManifest.ts';\n\n/**\n * Reads doompiWeb declarations from explicit package roots.\n *\n * No workspace walking and no hardcoded plugin names: the caller says which\n * packages are in play. At doompi-web's own build time that is just the host\n * package (its built-in plugins); at `doompi sync` time it is the installed\n * composition's package roots. A plugin package whose manifest is malformed\n * or whose entry file is missing is skipped with a notice, so one broken or\n * half-installed package never costs the rest of the cockpit; only the host's\n * own manifest still throws, because that is a build error of this package.\n */\nexport function scanWebPlugins(\n hostDir: string,\n pluginRoots: readonly string[] = [],\n onNotice: (message: string) => void = () => undefined,\n): DeclaredWebPlugin[] {\n const seen = new Set<string>();\n const plugins: DeclaredWebPlugin[] = [];\n const hostResolved = path.resolve(hostDir);\n for (const root of [hostDir, ...pluginRoots]) {\n const resolved = path.resolve(root);\n if (seen.has(resolved)) continue;\n seen.add(resolved);\n try {\n plugins.push(...declaredPluginsIn(resolved, resolved === hostResolved));\n } catch (error) {\n if (resolved === hostResolved) throw error;\n const reason = error instanceof WebPluginManifestError ? error.message : String(error);\n onNotice(`web plugin package ${resolved} skipped: ${reason}`);\n }\n }\n return orderDeclaredPlugins(plugins, onNotice);\n}\n\nfunction declaredPluginsIn(resolved: string, isHost: boolean): DeclaredWebPlugin[] {\n let manifest: Record<string, unknown>;\n try {\n manifest = JSON.parse(fs.readFileSync(path.join(resolved, 'package.json'), 'utf8')) as Record<string, unknown>;\n } catch (error) {\n throw new WebPluginManifestError(resolved, `package.json is unreadable: ${String(error)}`);\n }\n const declared = declaredPluginsOf(resolved, manifest, isHost);\n for (const plugin of declared) {\n const entryPath = path.join(resolved, plugin.client.entry);\n if (!fs.existsSync(entryPath)) {\n throw new WebPluginManifestError(resolved, `client.entry '${plugin.client.entry}' does not exist.`);\n }\n if (plugin.hub && !fs.existsSync(path.join(resolved, plugin.hub.entry))) {\n throw new WebPluginManifestError(resolved, `hub.entry '${plugin.hub.entry}' does not exist.`);\n }\n }\n return declared;\n}\n"],"mappings":"6LAoBA,SAAgB,EACd,EACA,EAAiC,CAAC,EAClC,MAA4C,IAAA,GACvB,CACrB,IAAM,EAAO,IAAI,IACX,EAA+B,CAAC,EAChC,EAAeA,EAAAA,QAAK,QAAQ,CAAO,EACzC,IAAK,IAAM,IAAQ,CAAC,EAAS,GAAG,CAAW,EAAG,CAC5C,IAAM,EAAWA,EAAAA,QAAK,QAAQ,CAAI,EAC9B,MAAK,IAAI,CAAQ,EACrB,GAAK,IAAI,CAAQ,EACjB,GAAI,CACF,EAAQ,KAAK,GAAG,EAAkB,EAAU,IAAa,CAAY,CAAC,CACxE,OAAS,EAAO,CACd,GAAI,IAAa,EAAc,MAAM,EAErC,EAAS,sBAAsB,EAAS,YADzB,aAAiBC,EAAAA,uBAAyB,EAAM,QAAU,OAAO,CAAK,GACzB,CAC9D,CAPiB,CAQnB,CACA,OAAOC,EAAAA,qBAAqB,EAAS,CAAQ,CAC/C,CAEA,SAAS,EAAkB,EAAkB,EAAsC,CACjF,IAAI,EACJ,GAAI,CACF,EAAW,KAAK,MAAMC,EAAAA,QAAG,aAAaH,EAAAA,QAAK,KAAK,EAAU,cAAc,EAAG,MAAM,CAAC,CACpF,OAAS,EAAO,CACd,MAAM,IAAIC,EAAAA,uBAAuB,EAAU,+BAA+B,OAAO,CAAK,GAAG,CAC3F,CACA,IAAM,EAAWG,EAAAA,kBAAkB,EAAU,EAAU,CAAM,EAC7D,IAAK,IAAM,KAAU,EAAU,CAC7B,IAAM,EAAYJ,EAAAA,QAAK,KAAK,EAAU,EAAO,OAAO,KAAK,EACzD,GAAI,CAACG,EAAAA,QAAG,WAAW,CAAS,EAC1B,MAAM,IAAIF,EAAAA,uBAAuB,EAAU,iBAAiB,EAAO,OAAO,MAAM,kBAAkB,EAEpG,GAAI,EAAO,KAAO,CAACE,EAAAA,QAAG,WAAWH,EAAAA,QAAK,KAAK,EAAU,EAAO,IAAI,KAAK,CAAC,EACpE,MAAM,IAAIC,EAAAA,uBAAuB,EAAU,cAAc,EAAO,IAAI,MAAM,kBAAkB,CAEhG,CACA,OAAO,CACT"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{WebPluginManifestError as e,declaredPluginsOf as t,orderDeclaredPlugins as n}from"../services/webPluginManifest.mjs";import r from"node:fs";import i from"node:path";function a(t,r=[],a=()=>void 0){let s=new Set,c=[],l=i.resolve(t);for(let n of[t,...r]){let t=i.resolve(n);if(!s.has(t)){s.add(t);try{c.push(...o(t,t===l))}catch(n){if(t===l)throw n;a(`web plugin package ${t} skipped: ${n instanceof e?n.message:String(n)}`)}}}return n(c,a)}function o(n,a){let o;try{o=JSON.parse(r.readFileSync(i.join(n,`package.json`),`utf8`))}catch(t){throw new e(n,`package.json is unreadable: ${String(t)}`)}let s=t(n,o,a);for(let t of s){let a=i.join(n,t.client.entry);if(!r.existsSync(a))throw new e(n,`client.entry '${t.client.entry}' does not exist.`);if(t.hub&&!r.existsSync(i.join(n,t.hub.entry)))throw new e(n,`hub.entry '${t.hub.entry}' does not exist.`)}return s}export{a as scanWebPlugins};
|
|
2
|
+
//# sourceMappingURL=webPluginScan.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webPluginScan.mjs","names":[],"sources":["../../../src/adapters/webPluginScan.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport {\n type DeclaredWebPlugin,\n declaredPluginsOf,\n orderDeclaredPlugins,\n WebPluginManifestError,\n} from '../services/webPluginManifest.ts';\n\n/**\n * Reads doompiWeb declarations from explicit package roots.\n *\n * No workspace walking and no hardcoded plugin names: the caller says which\n * packages are in play. At doompi-web's own build time that is just the host\n * package (its built-in plugins); at `doompi sync` time it is the installed\n * composition's package roots. A plugin package whose manifest is malformed\n * or whose entry file is missing is skipped with a notice, so one broken or\n * half-installed package never costs the rest of the cockpit; only the host's\n * own manifest still throws, because that is a build error of this package.\n */\nexport function scanWebPlugins(\n hostDir: string,\n pluginRoots: readonly string[] = [],\n onNotice: (message: string) => void = () => undefined,\n): DeclaredWebPlugin[] {\n const seen = new Set<string>();\n const plugins: DeclaredWebPlugin[] = [];\n const hostResolved = path.resolve(hostDir);\n for (const root of [hostDir, ...pluginRoots]) {\n const resolved = path.resolve(root);\n if (seen.has(resolved)) continue;\n seen.add(resolved);\n try {\n plugins.push(...declaredPluginsIn(resolved, resolved === hostResolved));\n } catch (error) {\n if (resolved === hostResolved) throw error;\n const reason = error instanceof WebPluginManifestError ? error.message : String(error);\n onNotice(`web plugin package ${resolved} skipped: ${reason}`);\n }\n }\n return orderDeclaredPlugins(plugins, onNotice);\n}\n\nfunction declaredPluginsIn(resolved: string, isHost: boolean): DeclaredWebPlugin[] {\n let manifest: Record<string, unknown>;\n try {\n manifest = JSON.parse(fs.readFileSync(path.join(resolved, 'package.json'), 'utf8')) as Record<string, unknown>;\n } catch (error) {\n throw new WebPluginManifestError(resolved, `package.json is unreadable: ${String(error)}`);\n }\n const declared = declaredPluginsOf(resolved, manifest, isHost);\n for (const plugin of declared) {\n const entryPath = path.join(resolved, plugin.client.entry);\n if (!fs.existsSync(entryPath)) {\n throw new WebPluginManifestError(resolved, `client.entry '${plugin.client.entry}' does not exist.`);\n }\n if (plugin.hub && !fs.existsSync(path.join(resolved, plugin.hub.entry))) {\n throw new WebPluginManifestError(resolved, `hub.entry '${plugin.hub.entry}' does not exist.`);\n }\n }\n return declared;\n}\n"],"mappings":"4KAoBA,SAAgB,EACd,EACA,EAAiC,CAAC,EAClC,MAA4C,IAAA,GACvB,CACrB,IAAM,EAAO,IAAI,IACX,EAA+B,CAAC,EAChC,EAAe,EAAK,QAAQ,CAAO,EACzC,IAAK,IAAM,IAAQ,CAAC,EAAS,GAAG,CAAW,EAAG,CAC5C,IAAM,EAAW,EAAK,QAAQ,CAAI,EAC9B,MAAK,IAAI,CAAQ,EACrB,GAAK,IAAI,CAAQ,EACjB,GAAI,CACF,EAAQ,KAAK,GAAG,EAAkB,EAAU,IAAa,CAAY,CAAC,CACxE,OAAS,EAAO,CACd,GAAI,IAAa,EAAc,MAAM,EAErC,EAAS,sBAAsB,EAAS,YADzB,aAAiB,EAAyB,EAAM,QAAU,OAAO,CAAK,GACzB,CAC9D,CAPiB,CAQnB,CACA,OAAO,EAAqB,EAAS,CAAQ,CAC/C,CAEA,SAAS,EAAkB,EAAkB,EAAsC,CACjF,IAAI,EACJ,GAAI,CACF,EAAW,KAAK,MAAM,EAAG,aAAa,EAAK,KAAK,EAAU,cAAc,EAAG,MAAM,CAAC,CACpF,OAAS,EAAO,CACd,MAAM,IAAI,EAAuB,EAAU,+BAA+B,OAAO,CAAK,GAAG,CAC3F,CACA,IAAM,EAAW,EAAkB,EAAU,EAAU,CAAM,EAC7D,IAAK,IAAM,KAAU,EAAU,CAC7B,IAAM,EAAY,EAAK,KAAK,EAAU,EAAO,OAAO,KAAK,EACzD,GAAI,CAAC,EAAG,WAAW,CAAS,EAC1B,MAAM,IAAI,EAAuB,EAAU,iBAAiB,EAAO,OAAO,MAAM,kBAAkB,EAEpG,GAAI,EAAO,KAAO,CAAC,EAAG,WAAW,EAAK,KAAK,EAAU,EAAO,IAAI,KAAK,CAAC,EACpE,MAAM,IAAI,EAAuB,EAAU,cAAc,EAAO,IAAI,MAAM,kBAAkB,CAEhG,CACA,OAAO,CACT"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require("../../_virtual/_rolldown/runtime.cjs");let t=require("node:fs");t=e.__toESM(t,1);let n=require("node:path");n=e.__toESM(n,1);function r(e,t){let r=new Map([[n.default.join(e,`app`,`webPlugins.generated.ts`),t.clientModulePath],[n.default.join(e,`styles`,`webPluginSources.generated.css`),t.cssModulePath]]);return{name:`doompi-web-plugin-overrides`,enforce:`pre`,async resolveId(e,t,n){let i=await this.resolve(e,t,{...n,skipSelf:!0});return i?r.get(i.id)??null:null}}}function i(e){return{find:/^\.\/webPluginSources\.generated\.css$/,replacement:e.cssModulePath}}exports.webPluginCssAlias=i,exports.webPluginOverridePlugin=r;
|
|
2
|
+
//# sourceMappingURL=webPluginVite.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webPluginVite.cjs","names":["path"],"sources":["../../../src/adapters/webPluginVite.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport type { Alias, Plugin } from 'vite';\nimport { devPluginRoots, PLUGIN_ROOTS_ENV, PLUGIN_ROOTS_FILE } from '../services/webDevRoots.ts';\nimport type { SyncGeneratedModules } from './webPluginGenerate.ts';\n\n/** Where `doompi sync` publishes the bundle the server prefers; the roots file sits beside its assets. */\nconst SYNCED_BUNDLE_DIRECTORY = ['.doompi', 'web', 'current'];\n\n/**\n * Swaps the committed builtin registry modules for generated ones. A\n * resolveId hook rather than resolve.alias because aliases match raw import\n * specifiers while these two are known by their resolved paths. Both the\n * sync-time build and the dev server use it, so a plugin edit in dev is the\n * same module the bundle would carry.\n */\nexport function webPluginOverridePlugin(clientRoot: string, generated: SyncGeneratedModules): Plugin {\n const overrides = new Map([\n [path.join(clientRoot, 'app', 'webPlugins.generated.ts'), generated.clientModulePath],\n [path.join(clientRoot, 'styles', 'webPluginSources.generated.css'), generated.cssModulePath],\n ]);\n return {\n name: 'doompi-web-plugin-overrides',\n enforce: 'pre',\n async resolveId(source, importer, options) {\n const resolved = await this.resolve(source, importer, { ...options, skipSelf: true });\n if (!resolved) return null;\n return overrides.get(resolved.id) ?? null;\n },\n };\n}\n\n/**\n * The CSS sources module cannot ride the resolveId hook: Vite and Tailwind\n * resolve CSS @import through Vite's own id resolver, which consults aliases\n * but no plugin hooks. Without this the builtin (empty) @source list ships\n * and every plugin-only utility class is missing.\n */\nexport function webPluginCssAlias(generated: SyncGeneratedModules): Alias {\n return { find: /^\\.\\/webPluginSources\\.generated\\.css$/, replacement: generated.cssModulePath };\n}\n\n/** The plugin package roots the dev server should serve, from the environment or the last sync. */\nexport function readDevPluginRoots(environment: NodeJS.ProcessEnv, homeDirectory: string): string[] {\n const rootsFile = path.join(homeDirectory, ...SYNCED_BUNDLE_DIRECTORY, PLUGIN_ROOTS_FILE);\n return devPluginRoots({\n envValue: environment[PLUGIN_ROOTS_ENV],\n rootsFileText: fs.existsSync(rootsFile) ? fs.readFileSync(rootsFile, 'utf8') : undefined,\n delimiter: path.delimiter,\n });\n}\n"],"mappings":"8IAgBA,SAAgB,EAAwB,EAAoB,EAAyC,CACnG,IAAM,EAAY,IAAI,IAAI,CACxB,CAACA,EAAAA,QAAK,KAAK,EAAY,MAAO,yBAAyB,EAAG,EAAU,gBAAgB,EACpF,CAACA,EAAAA,QAAK,KAAK,EAAY,SAAU,gCAAgC,EAAG,EAAU,aAAa,CAC7F,CAAC,EACD,MAAO,CACL,KAAM,8BACN,QAAS,MACT,MAAM,UAAU,EAAQ,EAAU,EAAS,CACzC,IAAM,EAAW,MAAM,KAAK,QAAQ,EAAQ,EAAU,CAAE,GAAG,EAAS,SAAU,EAAK,CAAC,EAEpF,OADK,EACE,EAAU,IAAI,EAAS,EAAE,GAAK,KADf,IAExB,CACF,CACF,CAQA,SAAgB,EAAkB,EAAwC,CACxE,MAAO,CAAE,KAAM,yCAA0C,YAAa,EAAU,aAAc,CAChG"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import"node:fs";import e from"node:path";function t(t,n){let r=new Map([[e.join(t,`app`,`webPlugins.generated.ts`),n.clientModulePath],[e.join(t,`styles`,`webPluginSources.generated.css`),n.cssModulePath]]);return{name:`doompi-web-plugin-overrides`,enforce:`pre`,async resolveId(e,t,n){let i=await this.resolve(e,t,{...n,skipSelf:!0});return i?r.get(i.id)??null:null}}}function n(e){return{find:/^\.\/webPluginSources\.generated\.css$/,replacement:e.cssModulePath}}export{n as webPluginCssAlias,t as webPluginOverridePlugin};
|
|
2
|
+
//# sourceMappingURL=webPluginVite.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webPluginVite.mjs","names":[],"sources":["../../../src/adapters/webPluginVite.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport type { Alias, Plugin } from 'vite';\nimport { devPluginRoots, PLUGIN_ROOTS_ENV, PLUGIN_ROOTS_FILE } from '../services/webDevRoots.ts';\nimport type { SyncGeneratedModules } from './webPluginGenerate.ts';\n\n/** Where `doompi sync` publishes the bundle the server prefers; the roots file sits beside its assets. */\nconst SYNCED_BUNDLE_DIRECTORY = ['.doompi', 'web', 'current'];\n\n/**\n * Swaps the committed builtin registry modules for generated ones. A\n * resolveId hook rather than resolve.alias because aliases match raw import\n * specifiers while these two are known by their resolved paths. Both the\n * sync-time build and the dev server use it, so a plugin edit in dev is the\n * same module the bundle would carry.\n */\nexport function webPluginOverridePlugin(clientRoot: string, generated: SyncGeneratedModules): Plugin {\n const overrides = new Map([\n [path.join(clientRoot, 'app', 'webPlugins.generated.ts'), generated.clientModulePath],\n [path.join(clientRoot, 'styles', 'webPluginSources.generated.css'), generated.cssModulePath],\n ]);\n return {\n name: 'doompi-web-plugin-overrides',\n enforce: 'pre',\n async resolveId(source, importer, options) {\n const resolved = await this.resolve(source, importer, { ...options, skipSelf: true });\n if (!resolved) return null;\n return overrides.get(resolved.id) ?? null;\n },\n };\n}\n\n/**\n * The CSS sources module cannot ride the resolveId hook: Vite and Tailwind\n * resolve CSS @import through Vite's own id resolver, which consults aliases\n * but no plugin hooks. Without this the builtin (empty) @source list ships\n * and every plugin-only utility class is missing.\n */\nexport function webPluginCssAlias(generated: SyncGeneratedModules): Alias {\n return { find: /^\\.\\/webPluginSources\\.generated\\.css$/, replacement: generated.cssModulePath };\n}\n\n/** The plugin package roots the dev server should serve, from the environment or the last sync. */\nexport function readDevPluginRoots(environment: NodeJS.ProcessEnv, homeDirectory: string): string[] {\n const rootsFile = path.join(homeDirectory, ...SYNCED_BUNDLE_DIRECTORY, PLUGIN_ROOTS_FILE);\n return devPluginRoots({\n envValue: environment[PLUGIN_ROOTS_ENV],\n rootsFileText: fs.existsSync(rootsFile) ? fs.readFileSync(rootsFile, 'utf8') : undefined,\n delimiter: path.delimiter,\n });\n}\n"],"mappings":"yCAgBA,SAAgB,EAAwB,EAAoB,EAAyC,CACnG,IAAM,EAAY,IAAI,IAAI,CACxB,CAAC,EAAK,KAAK,EAAY,MAAO,yBAAyB,EAAG,EAAU,gBAAgB,EACpF,CAAC,EAAK,KAAK,EAAY,SAAU,gCAAgC,EAAG,EAAU,aAAa,CAC7F,CAAC,EACD,MAAO,CACL,KAAM,8BACN,QAAS,MACT,MAAM,UAAU,EAAQ,EAAU,EAAS,CACzC,IAAM,EAAW,MAAM,KAAK,QAAQ,EAAQ,EAAU,CAAE,GAAG,EAAS,SAAU,EAAK,CAAC,EAEpF,OADK,EACE,EAAU,IAAI,EAAS,EAAE,GAAK,KADf,IAExB,CACF,CACF,CAQA,SAAgB,EAAkB,EAAwC,CACxE,MAAO,CAAE,KAAM,yCAA0C,YAAa,EAAU,aAAc,CAChG"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require("../../_virtual/_rolldown/runtime.cjs");let t=require("node:path");t=e.__toESM(t,1);function n(e,n){let r=n.trim().toLowerCase();if(r===``)return;let i=t.default.basename(e).toLowerCase(),a=e.split(t.default.sep).filter(Boolean).length,o=i===r?1e3:i.startsWith(r)?800:i.includes(r)?600:e.toLowerCase().includes(r)?400:void 0;if(o!==void 0)return o-a*12}function r(e,t,r){let i=new Map;for(let r of e){if(i.has(r))continue;let e=n(r,t);e!==void 0&&i.set(r,e)}return[...i.entries()].sort(([e,t],[n,r])=>r-t||e.localeCompare(n)).slice(0,r).map(([e])=>e)}function i(e){let t=e.trim().replace(/\/+$/u,``);if(t===``)return``;let n=t.lastIndexOf(`/`);return n===-1?t:t.slice(n+1)}exports.rankDirectories=r,exports.scoreDirectory=n,exports.searchTermFor=i;
|
|
2
|
+
//# sourceMappingURL=directoryMatch.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"directoryMatch.cjs","names":["path"],"sources":["../../../src/services/directoryMatch.ts"],"sourcesContent":["import path from 'node:path';\n\n/** Scores, highest first: an exact folder name beats a prefix, which beats a substring. */\nconst EXACT_NAME = 1000;\nconst NAME_PREFIX = 800;\nconst NAME_CONTAINS = 600;\nconst PATH_CONTAINS = 400;\n/** Each level down costs this much, so a shallow answer wins a deep one of the same kind. */\nconst DEPTH_PENALTY = 12;\n\n/**\n * How well one directory answers a typed query, or undefined when it does not.\n *\n * The reader is naming a project, so the folder's own name is what they mean;\n * a match further up the path still counts, because \"workspace/agi\" is a\n * reasonable way to point at a folder, but it ranks below a name that matches\n * outright. Depth is the tiebreak: given `~/work/api` and\n * `~/work/api/node_modules/x/api`, the first is what anyone meant.\n */\nexport function scoreDirectory(absolutePath: string, query: string): number | undefined {\n const needle = query.trim().toLowerCase();\n if (needle === '') return undefined;\n const name = path.basename(absolutePath).toLowerCase();\n const depth = absolutePath.split(path.sep).filter(Boolean).length;\n\n const base =\n name === needle\n ? EXACT_NAME\n : name.startsWith(needle)\n ? NAME_PREFIX\n : name.includes(needle)\n ? NAME_CONTAINS\n : absolutePath.toLowerCase().includes(needle)\n ? PATH_CONTAINS\n : undefined;\n if (base === undefined) return undefined;\n return base - depth * DEPTH_PENALTY;\n}\n\n/**\n * The best directories for a query, deduplicated and cut to the limit. Ties\n * break alphabetically so the same query always lists the same way.\n */\nexport function rankDirectories(paths: Iterable<string>, query: string, limit: number): string[] {\n const scored = new Map<string, number>();\n for (const candidate of paths) {\n if (scored.has(candidate)) continue;\n const score = scoreDirectory(candidate, query);\n if (score !== undefined) scored.set(candidate, score);\n }\n return [...scored.entries()]\n .sort(\n ([leftPath, leftScore], [rightPath, rightScore]) => rightScore - leftScore || leftPath.localeCompare(rightPath),\n )\n .slice(0, limit)\n .map(([candidate]) => candidate);\n}\n\n/**\n * The part of a typed value to search for when it is not a path that exists.\n *\n * Someone typing a remembered path from another machine (\"/home/me/work/api\")\n * still means \"api\", so the trailing segment is the query and the rest is\n * discarded rather than treated as a location that must exist.\n */\nexport function searchTermFor(typed: string): string {\n const trimmed = typed.trim().replace(/\\/+$/u, '');\n if (trimmed === '') return '';\n const cut = trimmed.lastIndexOf('/');\n return cut === -1 ? trimmed : trimmed.slice(cut + 1);\n}\n"],"mappings":"oGAmBA,SAAgB,EAAe,EAAsB,EAAmC,CACtF,IAAM,EAAS,EAAM,KAAK,CAAC,CAAC,YAAY,EACxC,GAAI,IAAW,GAAI,OACnB,IAAM,EAAOA,EAAAA,QAAK,SAAS,CAAY,CAAC,CAAC,YAAY,EAC/C,EAAQ,EAAa,MAAMA,EAAAA,QAAK,GAAG,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,OAErD,EACJ,IAAS,EACL,IACA,EAAK,WAAW,CAAM,EACpB,IACA,EAAK,SAAS,CAAM,EAClB,IACA,EAAa,YAAY,CAAC,CAAC,SAAS,CAAM,EACxC,IACA,IAAA,GACR,OAAS,IAAA,GACb,OAAO,EAAO,EAAQ,EACxB,CAMA,SAAgB,EAAgB,EAAyB,EAAe,EAAyB,CAC/F,IAAM,EAAS,IAAI,IACnB,IAAK,IAAM,KAAa,EAAO,CAC7B,GAAI,EAAO,IAAI,CAAS,EAAG,SAC3B,IAAM,EAAQ,EAAe,EAAW,CAAK,EACzC,IAAU,IAAA,IAAW,EAAO,IAAI,EAAW,CAAK,CACtD,CACA,MAAO,CAAC,GAAG,EAAO,QAAQ,CAAC,CAAC,CACzB,MACE,CAAC,EAAU,GAAY,CAAC,EAAW,KAAgB,EAAa,GAAa,EAAS,cAAc,CAAS,CAChH,CAAC,CACA,MAAM,EAAG,CAAK,CAAC,CACf,KAAK,CAAC,KAAe,CAAS,CACnC,CASA,SAAgB,EAAc,EAAuB,CACnD,IAAM,EAAU,EAAM,KAAK,CAAC,CAAC,QAAQ,QAAS,EAAE,EAChD,GAAI,IAAY,GAAI,MAAO,GAC3B,IAAM,EAAM,EAAQ,YAAY,GAAG,EACnC,OAAO,IAAQ,GAAK,EAAU,EAAQ,MAAM,EAAM,CAAC,CACrD"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import e from"node:path";function t(t,n){let r=n.trim().toLowerCase();if(r===``)return;let i=e.basename(t).toLowerCase(),a=t.split(e.sep).filter(Boolean).length,o=i===r?1e3:i.startsWith(r)?800:i.includes(r)?600:t.toLowerCase().includes(r)?400:void 0;if(o!==void 0)return o-a*12}function n(e,n,r){let i=new Map;for(let r of e){if(i.has(r))continue;let e=t(r,n);e!==void 0&&i.set(r,e)}return[...i.entries()].sort(([e,t],[n,r])=>r-t||e.localeCompare(n)).slice(0,r).map(([e])=>e)}function r(e){let t=e.trim().replace(/\/+$/u,``);if(t===``)return``;let n=t.lastIndexOf(`/`);return n===-1?t:t.slice(n+1)}export{n as rankDirectories,t as scoreDirectory,r as searchTermFor};
|
|
2
|
+
//# sourceMappingURL=directoryMatch.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"directoryMatch.mjs","names":[],"sources":["../../../src/services/directoryMatch.ts"],"sourcesContent":["import path from 'node:path';\n\n/** Scores, highest first: an exact folder name beats a prefix, which beats a substring. */\nconst EXACT_NAME = 1000;\nconst NAME_PREFIX = 800;\nconst NAME_CONTAINS = 600;\nconst PATH_CONTAINS = 400;\n/** Each level down costs this much, so a shallow answer wins a deep one of the same kind. */\nconst DEPTH_PENALTY = 12;\n\n/**\n * How well one directory answers a typed query, or undefined when it does not.\n *\n * The reader is naming a project, so the folder's own name is what they mean;\n * a match further up the path still counts, because \"workspace/agi\" is a\n * reasonable way to point at a folder, but it ranks below a name that matches\n * outright. Depth is the tiebreak: given `~/work/api` and\n * `~/work/api/node_modules/x/api`, the first is what anyone meant.\n */\nexport function scoreDirectory(absolutePath: string, query: string): number | undefined {\n const needle = query.trim().toLowerCase();\n if (needle === '') return undefined;\n const name = path.basename(absolutePath).toLowerCase();\n const depth = absolutePath.split(path.sep).filter(Boolean).length;\n\n const base =\n name === needle\n ? EXACT_NAME\n : name.startsWith(needle)\n ? NAME_PREFIX\n : name.includes(needle)\n ? NAME_CONTAINS\n : absolutePath.toLowerCase().includes(needle)\n ? PATH_CONTAINS\n : undefined;\n if (base === undefined) return undefined;\n return base - depth * DEPTH_PENALTY;\n}\n\n/**\n * The best directories for a query, deduplicated and cut to the limit. Ties\n * break alphabetically so the same query always lists the same way.\n */\nexport function rankDirectories(paths: Iterable<string>, query: string, limit: number): string[] {\n const scored = new Map<string, number>();\n for (const candidate of paths) {\n if (scored.has(candidate)) continue;\n const score = scoreDirectory(candidate, query);\n if (score !== undefined) scored.set(candidate, score);\n }\n return [...scored.entries()]\n .sort(\n ([leftPath, leftScore], [rightPath, rightScore]) => rightScore - leftScore || leftPath.localeCompare(rightPath),\n )\n .slice(0, limit)\n .map(([candidate]) => candidate);\n}\n\n/**\n * The part of a typed value to search for when it is not a path that exists.\n *\n * Someone typing a remembered path from another machine (\"/home/me/work/api\")\n * still means \"api\", so the trailing segment is the query and the rest is\n * discarded rather than treated as a location that must exist.\n */\nexport function searchTermFor(typed: string): string {\n const trimmed = typed.trim().replace(/\\/+$/u, '');\n if (trimmed === '') return '';\n const cut = trimmed.lastIndexOf('/');\n return cut === -1 ? trimmed : trimmed.slice(cut + 1);\n}\n"],"mappings":"yBAmBA,SAAgB,EAAe,EAAsB,EAAmC,CACtF,IAAM,EAAS,EAAM,KAAK,CAAC,CAAC,YAAY,EACxC,GAAI,IAAW,GAAI,OACnB,IAAM,EAAO,EAAK,SAAS,CAAY,CAAC,CAAC,YAAY,EAC/C,EAAQ,EAAa,MAAM,EAAK,GAAG,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,OAErD,EACJ,IAAS,EACL,IACA,EAAK,WAAW,CAAM,EACpB,IACA,EAAK,SAAS,CAAM,EAClB,IACA,EAAa,YAAY,CAAC,CAAC,SAAS,CAAM,EACxC,IACA,IAAA,GACR,OAAS,IAAA,GACb,OAAO,EAAO,EAAQ,EACxB,CAMA,SAAgB,EAAgB,EAAyB,EAAe,EAAyB,CAC/F,IAAM,EAAS,IAAI,IACnB,IAAK,IAAM,KAAa,EAAO,CAC7B,GAAI,EAAO,IAAI,CAAS,EAAG,SAC3B,IAAM,EAAQ,EAAe,EAAW,CAAK,EACzC,IAAU,IAAA,IAAW,EAAO,IAAI,EAAW,CAAK,CACtD,CACA,MAAO,CAAC,GAAG,EAAO,QAAQ,CAAC,CAAC,CACzB,MACE,CAAC,EAAU,GAAY,CAAC,EAAW,KAAgB,EAAa,GAAa,EAAS,cAAc,CAAS,CAChH,CAAC,CACA,MAAM,EAAG,CAAK,CAAC,CACf,KAAK,CAAC,KAAe,CAAS,CACnC,CASA,SAAgB,EAAc,EAAuB,CACnD,IAAM,EAAU,EAAM,KAAK,CAAC,CAAC,QAAQ,QAAS,EAAE,EAChD,GAAI,IAAY,GAAI,MAAO,GAC3B,IAAM,EAAM,EAAQ,YAAY,GAAG,EACnC,OAAO,IAAQ,GAAK,EAAU,EAAQ,MAAM,EAAM,CAAC,CACrD"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
function e(e,t,n){let r=t.toLowerCase(),i=[];for(let t of e){let e=t.toLowerCase(),n=e.slice(e.lastIndexOf(`/`)+1),a;if(r===``)a=3;else if(n.startsWith(r))a=0;else if(n.includes(r))a=1;else if(e.includes(r))a=2;else continue;i.push({file:t,rank:a})}return i.sort((e,t)=>e.rank-t.rank||e.file.length-t.file.length||e.file.localeCompare(t.file)).slice(0,n).map(e=>e.file)}exports.rankFileMatches=e;
|
|
2
|
+
//# sourceMappingURL=fileMatch.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fileMatch.cjs","names":[],"sources":["../../../src/services/fileMatch.ts"],"sourcesContent":["/**\n * Ranking for the composer's @ file completion: pure string policy, no\n * filesystem, so the ordering rules stay testable on their own.\n */\n\ninterface RankedMatch {\n file: string;\n rank: number;\n}\n\n/**\n * Case-insensitive substring match, ranked so what a person is reaching for\n * surfaces first: basename prefix beats basename substring beats path\n * substring, earlier and shorter paths break ties.\n */\nexport function rankFileMatches(files: readonly string[], query: string, limit: number): string[] {\n const needle = query.toLowerCase();\n const ranked: RankedMatch[] = [];\n for (const file of files) {\n const lower = file.toLowerCase();\n const base = lower.slice(lower.lastIndexOf('/') + 1);\n let rank: number;\n if (needle === '') rank = 3;\n else if (base.startsWith(needle)) rank = 0;\n else if (base.includes(needle)) rank = 1;\n else if (lower.includes(needle)) rank = 2;\n else continue;\n ranked.push({ file, rank });\n }\n return ranked\n .sort(\n (left, right) =>\n left.rank - right.rank || left.file.length - right.file.length || left.file.localeCompare(right.file),\n )\n .slice(0, limit)\n .map((entry) => entry.file);\n}\n"],"mappings":"AAeA,SAAgB,EAAgB,EAA0B,EAAe,EAAyB,CAChG,IAAM,EAAS,EAAM,YAAY,EAC3B,EAAwB,CAAC,EAC/B,IAAK,IAAM,KAAQ,EAAO,CACxB,IAAM,EAAQ,EAAK,YAAY,EACzB,EAAO,EAAM,MAAM,EAAM,YAAY,GAAG,EAAI,CAAC,EAC/C,EACJ,GAAI,IAAW,GAAI,EAAO,OACrB,GAAI,EAAK,WAAW,CAAM,EAAG,EAAO,OACpC,GAAI,EAAK,SAAS,CAAM,EAAG,EAAO,OAClC,GAAI,EAAM,SAAS,CAAM,EAAG,EAAO,OACnC,SACL,EAAO,KAAK,CAAE,OAAM,MAAK,CAAC,CAC5B,CACA,OAAO,EACJ,MACE,EAAM,IACL,EAAK,KAAO,EAAM,MAAQ,EAAK,KAAK,OAAS,EAAM,KAAK,QAAU,EAAK,KAAK,cAAc,EAAM,IAAI,CACxG,CAAC,CACA,MAAM,EAAG,CAAK,CAAC,CACf,IAAK,GAAU,EAAM,IAAI,CAC9B"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
function e(e,t,n){let r=t.toLowerCase(),i=[];for(let t of e){let e=t.toLowerCase(),n=e.slice(e.lastIndexOf(`/`)+1),a;if(r===``)a=3;else if(n.startsWith(r))a=0;else if(n.includes(r))a=1;else if(e.includes(r))a=2;else continue;i.push({file:t,rank:a})}return i.sort((e,t)=>e.rank-t.rank||e.file.length-t.file.length||e.file.localeCompare(t.file)).slice(0,n).map(e=>e.file)}export{e as rankFileMatches};
|
|
2
|
+
//# sourceMappingURL=fileMatch.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fileMatch.mjs","names":[],"sources":["../../../src/services/fileMatch.ts"],"sourcesContent":["/**\n * Ranking for the composer's @ file completion: pure string policy, no\n * filesystem, so the ordering rules stay testable on their own.\n */\n\ninterface RankedMatch {\n file: string;\n rank: number;\n}\n\n/**\n * Case-insensitive substring match, ranked so what a person is reaching for\n * surfaces first: basename prefix beats basename substring beats path\n * substring, earlier and shorter paths break ties.\n */\nexport function rankFileMatches(files: readonly string[], query: string, limit: number): string[] {\n const needle = query.toLowerCase();\n const ranked: RankedMatch[] = [];\n for (const file of files) {\n const lower = file.toLowerCase();\n const base = lower.slice(lower.lastIndexOf('/') + 1);\n let rank: number;\n if (needle === '') rank = 3;\n else if (base.startsWith(needle)) rank = 0;\n else if (base.includes(needle)) rank = 1;\n else if (lower.includes(needle)) rank = 2;\n else continue;\n ranked.push({ file, rank });\n }\n return ranked\n .sort(\n (left, right) =>\n left.rank - right.rank || left.file.length - right.file.length || left.file.localeCompare(right.file),\n )\n .slice(0, limit)\n .map((entry) => entry.file);\n}\n"],"mappings":"AAeA,SAAgB,EAAgB,EAA0B,EAAe,EAAyB,CAChG,IAAM,EAAS,EAAM,YAAY,EAC3B,EAAwB,CAAC,EAC/B,IAAK,IAAM,KAAQ,EAAO,CACxB,IAAM,EAAQ,EAAK,YAAY,EACzB,EAAO,EAAM,MAAM,EAAM,YAAY,GAAG,EAAI,CAAC,EAC/C,EACJ,GAAI,IAAW,GAAI,EAAO,OACrB,GAAI,EAAK,WAAW,CAAM,EAAG,EAAO,OACpC,GAAI,EAAK,SAAS,CAAM,EAAG,EAAO,OAClC,GAAI,EAAM,SAAS,CAAM,EAAG,EAAO,OACnC,SACL,EAAO,KAAK,CAAE,OAAM,MAAK,CAAC,CAC5B,CACA,OAAO,EACJ,MACE,EAAM,IACL,EAAK,KAAO,EAAM,MAAQ,EAAK,KAAK,OAAS,EAAM,KAAK,QAAU,EAAK,KAAK,cAAc,EAAM,IAAI,CACxG,CAAC,CACA,MAAM,EAAG,CAAK,CAAC,CACf,IAAK,GAAU,EAAM,IAAI,CAC9B"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("../types/media.cjs");let n=require("node:path");n=e.__toESM(n,1);function r(e){return t.MEDIA_TYPES[n.default.extname(e).slice(1).toLowerCase()]}function i(e){let t=r(e),i=n.default.basename(e).replaceAll(`"`,``),a={"Content-Type":t?.contentType??`application/octet-stream`,"Content-Disposition":`${t?`inline`:`attachment`}; filename="${i}"`,"X-Content-Type-Options":`nosniff`};return t?.contentType===`image/svg+xml`&&(a[`Content-Security-Policy`]=`default-src 'none'; sandbox`),a}exports.mediaTypeFor=r,exports.sessionFileHeaders=i;
|
|
2
|
+
//# sourceMappingURL=fileMedia.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fileMedia.cjs","names":["MEDIA_TYPES","path"],"sources":["../../../src/services/fileMedia.ts"],"sourcesContent":["import path from 'node:path';\nimport { MEDIA_TYPES, type MediaType } from '../types/media.ts';\n\nconst OCTET_STREAM = 'application/octet-stream';\n\n/** The preview type for a file the hub is asked to serve, or undefined for a plain download. */\nexport function mediaTypeFor(filePath: string): MediaType | undefined {\n return MEDIA_TYPES[path.extname(filePath).slice(1).toLowerCase()];\n}\n\n/**\n * The response headers for a mentioned file. Previewable kinds render inline;\n * everything else downloads under its own name. SVG is the one previewable\n * type that can carry script, so it is served sandboxed: an <img> never runs\n * script anyway, and a direct navigation gets an inert document.\n */\nexport function sessionFileHeaders(filePath: string): Record<string, string> {\n const media = mediaTypeFor(filePath);\n const name = path.basename(filePath).replaceAll('\"', '');\n const headers: Record<string, string> = {\n 'Content-Type': media?.contentType ?? OCTET_STREAM,\n 'Content-Disposition': `${media ? 'inline' : 'attachment'}; filename=\"${name}\"`,\n 'X-Content-Type-Options': 'nosniff',\n };\n if (media?.contentType === 'image/svg+xml') headers['Content-Security-Policy'] = \"default-src 'none'; sandbox\";\n return headers;\n}\n"],"mappings":"oIAMA,SAAgB,EAAa,EAAyC,CACpE,OAAOA,EAAAA,YAAYC,EAAAA,QAAK,QAAQ,CAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,YAAY,EACjE,CAQA,SAAgB,EAAmB,EAA0C,CAC3E,IAAM,EAAQ,EAAa,CAAQ,EAC7B,EAAOA,EAAAA,QAAK,SAAS,CAAQ,CAAC,CAAC,WAAW,IAAK,EAAE,EACjD,EAAkC,CACtC,eAAgB,GAAO,aAAe,2BACtC,sBAAuB,GAAG,EAAQ,SAAW,aAAa,cAAc,EAAK,GAC7E,yBAA0B,SAC5B,EAEA,OADI,GAAO,cAAgB,kBAAiB,EAAQ,2BAA6B,+BAC1E,CACT"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{MEDIA_TYPES as e}from"../types/media.mjs";import t from"node:path";function n(n){return e[t.extname(n).slice(1).toLowerCase()]}function r(e){let r=n(e),i=t.basename(e).replaceAll(`"`,``),a={"Content-Type":r?.contentType??`application/octet-stream`,"Content-Disposition":`${r?`inline`:`attachment`}; filename="${i}"`,"X-Content-Type-Options":`nosniff`};return r?.contentType===`image/svg+xml`&&(a[`Content-Security-Policy`]=`default-src 'none'; sandbox`),a}export{n as mediaTypeFor,r as sessionFileHeaders};
|
|
2
|
+
//# sourceMappingURL=fileMedia.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fileMedia.mjs","names":[],"sources":["../../../src/services/fileMedia.ts"],"sourcesContent":["import path from 'node:path';\nimport { MEDIA_TYPES, type MediaType } from '../types/media.ts';\n\nconst OCTET_STREAM = 'application/octet-stream';\n\n/** The preview type for a file the hub is asked to serve, or undefined for a plain download. */\nexport function mediaTypeFor(filePath: string): MediaType | undefined {\n return MEDIA_TYPES[path.extname(filePath).slice(1).toLowerCase()];\n}\n\n/**\n * The response headers for a mentioned file. Previewable kinds render inline;\n * everything else downloads under its own name. SVG is the one previewable\n * type that can carry script, so it is served sandboxed: an <img> never runs\n * script anyway, and a direct navigation gets an inert document.\n */\nexport function sessionFileHeaders(filePath: string): Record<string, string> {\n const media = mediaTypeFor(filePath);\n const name = path.basename(filePath).replaceAll('\"', '');\n const headers: Record<string, string> = {\n 'Content-Type': media?.contentType ?? OCTET_STREAM,\n 'Content-Disposition': `${media ? 'inline' : 'attachment'}; filename=\"${name}\"`,\n 'X-Content-Type-Options': 'nosniff',\n };\n if (media?.contentType === 'image/svg+xml') headers['Content-Security-Policy'] = \"default-src 'none'; sandbox\";\n return headers;\n}\n"],"mappings":"0EAMA,SAAgB,EAAa,EAAyC,CACpE,OAAO,EAAY,EAAK,QAAQ,CAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,YAAY,EACjE,CAQA,SAAgB,EAAmB,EAA0C,CAC3E,IAAM,EAAQ,EAAa,CAAQ,EAC7B,EAAO,EAAK,SAAS,CAAQ,CAAC,CAAC,WAAW,IAAK,EAAE,EACjD,EAAkC,CACtC,eAAgB,GAAO,aAAe,2BACtC,sBAAuB,GAAG,EAAQ,SAAW,aAAa,cAAc,EAAK,GAC7E,yBAA0B,SAC5B,EAEA,OADI,GAAO,cAAgB,kBAAiB,EAAQ,2BAA6B,+BAC1E,CACT"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frameRing.cjs","names":[],"sources":["../../../src/services/frameRing.ts"],"sourcesContent":["import type { SessionFrame } from '../types/session.ts';\n\nconst DEFAULT_RING_LIMIT = 512;\n\n/**\n * Bounded history of one session's frame stream.\n *\n * The unix-socket backlog only fills while nothing is attached, and the hub is\n * always attached, so a page that connects later would otherwise start blind.\n * The ring is what a subscribe replays; it mirrors the server backlog's\n * semantics, dropping the oldest frames past the limit and counting the loss.\n */\nexport interface FrameRing {\n record(frame: SessionFrame): void;\n snapshot(): { frames: SessionFrame[]; dropped: number };\n}\n\nexport function createFrameRing(limit = DEFAULT_RING_LIMIT): FrameRing {\n const frames: SessionFrame[] = [];\n let dropped = 0;\n return {\n record(frame) {\n frames.push(frame);\n if (frames.length > limit) {\n frames.shift();\n dropped += 1;\n }\n },\n snapshot() {\n return { frames: [...frames], dropped };\n },\n };\n}\n"],"mappings":"AAiBA,SAAgB,EAAgB,EAAQ,IAA+B,CACrE,IAAM,EAAyB,CAAC,EAC5B,EAAU,EACd,MAAO,CACL,OAAO,EAAO,CACZ,EAAO,KAAK,CAAK,EACb,EAAO,OAAS,IAClB,EAAO,MAAM,EACb,GAAW,EAEf,EACA,UAAW,CACT,MAAO,CAAE,OAAQ,CAAC,GAAG,CAAM,EAAG,SAAQ,CACxC,CACF,CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frameRing.mjs","names":[],"sources":["../../../src/services/frameRing.ts"],"sourcesContent":["import type { SessionFrame } from '../types/session.ts';\n\nconst DEFAULT_RING_LIMIT = 512;\n\n/**\n * Bounded history of one session's frame stream.\n *\n * The unix-socket backlog only fills while nothing is attached, and the hub is\n * always attached, so a page that connects later would otherwise start blind.\n * The ring is what a subscribe replays; it mirrors the server backlog's\n * semantics, dropping the oldest frames past the limit and counting the loss.\n */\nexport interface FrameRing {\n record(frame: SessionFrame): void;\n snapshot(): { frames: SessionFrame[]; dropped: number };\n}\n\nexport function createFrameRing(limit = DEFAULT_RING_LIMIT): FrameRing {\n const frames: SessionFrame[] = [];\n let dropped = 0;\n return {\n record(frame) {\n frames.push(frame);\n if (frames.length > limit) {\n frames.shift();\n dropped += 1;\n }\n },\n snapshot() {\n return { frames: [...frames], dropped };\n },\n };\n}\n"],"mappings":"AAiBA,SAAgB,EAAgB,EAAQ,IAA+B,CACrE,IAAM,EAAyB,CAAC,EAC5B,EAAU,EACd,MAAO,CACL,OAAO,EAAO,CACZ,EAAO,KAAK,CAAK,EACb,EAAO,OAAS,IAClB,EAAO,MAAM,EACb,GAAW,EAEf,EACA,UAAW,CACT,MAAO,CAAE,OAAQ,CAAC,GAAG,CAAM,EAAG,SAAQ,CACxC,CACF,CACF"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
function e(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function t(t){let n=t.trim();if(n===``)return;let r;try{r=JSON.parse(n)}catch{return}if(!(!e(r)||r.type!==`message`)&&!(typeof r.id!=`string`||r.id===``||!e(r.message)))return r}function n(e){let n=[];for(let r of e.split(`
|
|
2
|
+
`)){let e=t(r);e&&n.push({type:`entry_appended`,entry:e})}return n}function r(e,t){return e.length<=t?[...e]:e.slice(e.length-t)}exports.journalFrames=n,exports.retainNewest=r;
|
|
3
|
+
//# sourceMappingURL=journalTail.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"journalTail.cjs","names":[],"sources":["../../../src/services/journalTail.ts"],"sourcesContent":["import type { SessionFrame } from '../types/session.ts';\n\n/** The entry type a journal line must carry to render; everything else is Pi's own bookkeeping. */\nconst MESSAGE_ENTRY_TYPE = 'message';\n/** The frame the hub already replays journal entries as, so the page folds a thread with its one reducer. */\nconst ENTRY_APPENDED_TYPE = 'entry_appended';\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/** One journal line as a renderable entry, or undefined for the header, bookkeeping, or a torn line. */\nfunction parseEntry(line: string): Record<string, unknown> | undefined {\n const trimmed = line.trim();\n if (trimmed === '') return undefined;\n let parsed: unknown;\n try {\n parsed = JSON.parse(trimmed);\n } catch {\n // A line the writer had not finished, or one that was never JSON: nothing to render from it.\n return undefined;\n }\n if (!isRecord(parsed) || parsed.type !== MESSAGE_ENTRY_TYPE) return undefined;\n // An entry without an id is the runtime's own bookkeeping; the page would skip it too.\n if (typeof parsed.id !== 'string' || parsed.id === '' || !isRecord(parsed.message)) return undefined;\n return parsed;\n}\n\n/**\n * Folds complete lines of a Pi session journal into the frames the cockpit\n * already renders for a live session: one entry_appended per message entry,\n * oldest first. The header, other entry types and unparsable lines are\n * skipped, so the page needs no second reducer for a thread.\n */\nexport function journalFrames(text: string): SessionFrame[] {\n const frames: SessionFrame[] = [];\n for (const line of text.split('\\n')) {\n const entry = parseEntry(line);\n if (entry) frames.push({ type: ENTRY_APPENDED_TYPE, entry });\n }\n return frames;\n}\n\n/** The newest `limit` frames; a long journal gives up its oldest, as an attach does. */\nexport function retainNewest(frames: readonly SessionFrame[], limit: number): SessionFrame[] {\n return frames.length <= limit ? [...frames] : frames.slice(frames.length - limit);\n}\n"],"mappings":"AAOA,SAAS,EAAS,EAAkD,CAClE,OAAO,OAAO,GAAU,YAAY,GAAkB,CAAC,MAAM,QAAQ,CAAK,CAC5E,CAGA,SAAS,EAAW,EAAmD,CACrE,IAAM,EAAU,EAAK,KAAK,EAC1B,GAAI,IAAY,GAAI,OACpB,IAAI,EACJ,GAAI,CACF,EAAS,KAAK,MAAM,CAAO,CAC7B,MAAQ,CAEN,MACF,CACI,MAAC,EAAS,CAAM,GAAK,EAAO,OAAS,YAErC,SAAO,EAAO,IAAO,UAAY,EAAO,KAAO,IAAM,CAAC,EAAS,EAAO,OAAO,GACjF,OAAO,CACT,CAQA,SAAgB,EAAc,EAA8B,CAC1D,IAAM,EAAyB,CAAC,EAChC,IAAK,IAAM,KAAQ,EAAK,MAAM;CAAI,EAAG,CACnC,IAAM,EAAQ,EAAW,CAAI,EACzB,GAAO,EAAO,KAAK,CAAE,KAAM,iBAAqB,OAAM,CAAC,CAC7D,CACA,OAAO,CACT,CAGA,SAAgB,EAAa,EAAiC,EAA+B,CAC3F,OAAO,EAAO,QAAU,EAAQ,CAAC,GAAG,CAAM,EAAI,EAAO,MAAM,EAAO,OAAS,CAAK,CAClF"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
function e(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function t(t){let n=t.trim();if(n===``)return;let r;try{r=JSON.parse(n)}catch{return}if(!(!e(r)||r.type!==`message`)&&!(typeof r.id!=`string`||r.id===``||!e(r.message)))return r}function n(e){let n=[];for(let r of e.split(`
|
|
2
|
+
`)){let e=t(r);e&&n.push({type:`entry_appended`,entry:e})}return n}function r(e,t){return e.length<=t?[...e]:e.slice(e.length-t)}export{n as journalFrames,r as retainNewest};
|
|
3
|
+
//# sourceMappingURL=journalTail.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"journalTail.mjs","names":[],"sources":["../../../src/services/journalTail.ts"],"sourcesContent":["import type { SessionFrame } from '../types/session.ts';\n\n/** The entry type a journal line must carry to render; everything else is Pi's own bookkeeping. */\nconst MESSAGE_ENTRY_TYPE = 'message';\n/** The frame the hub already replays journal entries as, so the page folds a thread with its one reducer. */\nconst ENTRY_APPENDED_TYPE = 'entry_appended';\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/** One journal line as a renderable entry, or undefined for the header, bookkeeping, or a torn line. */\nfunction parseEntry(line: string): Record<string, unknown> | undefined {\n const trimmed = line.trim();\n if (trimmed === '') return undefined;\n let parsed: unknown;\n try {\n parsed = JSON.parse(trimmed);\n } catch {\n // A line the writer had not finished, or one that was never JSON: nothing to render from it.\n return undefined;\n }\n if (!isRecord(parsed) || parsed.type !== MESSAGE_ENTRY_TYPE) return undefined;\n // An entry without an id is the runtime's own bookkeeping; the page would skip it too.\n if (typeof parsed.id !== 'string' || parsed.id === '' || !isRecord(parsed.message)) return undefined;\n return parsed;\n}\n\n/**\n * Folds complete lines of a Pi session journal into the frames the cockpit\n * already renders for a live session: one entry_appended per message entry,\n * oldest first. The header, other entry types and unparsable lines are\n * skipped, so the page needs no second reducer for a thread.\n */\nexport function journalFrames(text: string): SessionFrame[] {\n const frames: SessionFrame[] = [];\n for (const line of text.split('\\n')) {\n const entry = parseEntry(line);\n if (entry) frames.push({ type: ENTRY_APPENDED_TYPE, entry });\n }\n return frames;\n}\n\n/** The newest `limit` frames; a long journal gives up its oldest, as an attach does. */\nexport function retainNewest(frames: readonly SessionFrame[], limit: number): SessionFrame[] {\n return frames.length <= limit ? [...frames] : frames.slice(frames.length - limit);\n}\n"],"mappings":"AAOA,SAAS,EAAS,EAAkD,CAClE,OAAO,OAAO,GAAU,YAAY,GAAkB,CAAC,MAAM,QAAQ,CAAK,CAC5E,CAGA,SAAS,EAAW,EAAmD,CACrE,IAAM,EAAU,EAAK,KAAK,EAC1B,GAAI,IAAY,GAAI,OACpB,IAAI,EACJ,GAAI,CACF,EAAS,KAAK,MAAM,CAAO,CAC7B,MAAQ,CAEN,MACF,CACI,MAAC,EAAS,CAAM,GAAK,EAAO,OAAS,YAErC,SAAO,EAAO,IAAO,UAAY,EAAO,KAAO,IAAM,CAAC,EAAS,EAAO,OAAO,GACjF,OAAO,CACT,CAQA,SAAgB,EAAc,EAA8B,CAC1D,IAAM,EAAyB,CAAC,EAChC,IAAK,IAAM,KAAQ,EAAK,MAAM;CAAI,EAAG,CACnC,IAAM,EAAQ,EAAW,CAAI,EACzB,GAAO,EAAO,KAAK,CAAE,KAAM,iBAAqB,OAAM,CAAC,CAC7D,CACA,OAAO,CACT,CAGA,SAAgB,EAAa,EAAiC,EAA+B,CAC3F,OAAO,EAAO,QAAU,EAAQ,CAAC,GAAG,CAAM,EAAI,EAAO,MAAM,EAAO,OAAS,CAAK,CAClF"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=`Login cancelled`;function t(e,t){let n={id:e,type:t.type,message:t.message};return t.type===`select`?n.options=t.options.map(e=>({...e})):t.placeholder!==void 0&&(n.placeholder=t.placeholder),n}function n(n){let r=new AbortController,i=[],a=`running`,o,s,c=0,l=e=>{let t=s;t&&(s=void 0,t.detach(),t.reject(e))},u={signal:r.signal,prompt(n){if(r.signal.aborted||n.signal?.aborted)return Promise.reject(Error(e));l(Error(`Superseded by a newer prompt`)),c+=1;let i=t(String(c),n);return new Promise((t,r)=>{let a=()=>l(Error(e));n.signal?.addEventListener(`abort`,a,{once:!0}),s={view:i,resolve:t,reject:r,detach:()=>n.signal?.removeEventListener(`abort`,a)}})},notify(e){i.push(e)}};return{id:n.id,providerId:n.providerId,interaction:u,snapshot(){let e={id:n.id,providerId:n.providerId,providerName:n.providerName,type:n.type,status:a,events:[...i]};return s&&(e.prompt=s.view),o!==void 0&&(e.error=o),e},answer(e,t){let n=s;return!n||n.view.id!==e?!1:(s=void 0,n.detach(),n.resolve(t),!0)},cancel(){a===`running`&&(a=`cancelled`,r.abort(),l(Error(e)))},settle(t){a===`running`&&(t.ok?a=`succeeded`:(a=`failed`,o=t.error),l(Error(e)))}}}exports.createLoginFlow=n;
|
|
2
|
+
//# sourceMappingURL=loginFlow.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loginFlow.cjs","names":[],"sources":["../../../src/services/loginFlow.ts"],"sourcesContent":["import type {\n AuthMethodType,\n LoginEvent,\n LoginFlowSnapshot,\n LoginFlowStatus,\n LoginInteraction,\n LoginPrompt,\n LoginPromptView,\n} from '../types/auth.ts';\n\n/** The message Pi's own login dialog uses; a provider flow may match on it. */\nconst CANCELLED_MESSAGE = 'Login cancelled';\nconst SUPERSEDED_MESSAGE = 'Superseded by a newer prompt';\n\ninterface PendingPrompt {\n view: LoginPromptView;\n resolve(value: string): void;\n reject(error: Error): void;\n detach(): void;\n}\n\nexport interface LoginFlowInput {\n id: string;\n providerId: string;\n providerName: string;\n type: AuthMethodType;\n}\n\n/**\n * One login in progress, seen from both sides: the provider's flow talks to\n * it through `interaction`, the page through snapshots and answers.\n */\nexport interface LoginFlow {\n readonly id: string;\n readonly providerId: string;\n /** Hand this to the runtime's login; prompts and events route through the flow. */\n readonly interaction: LoginInteraction;\n snapshot(): LoginFlowSnapshot;\n /** Answers the pending prompt; false when the flow is not waiting on that prompt. */\n answer(promptId: string, value: string): boolean;\n /** Aborts the provider's flow; a settled flow is left as it ended. */\n cancel(): void;\n /** Records how the runtime's login ended; a cancelled flow stays cancelled. */\n settle(outcome: { ok: true } | { ok: false; error: string }): void;\n}\n\nfunction promptView(id: string, prompt: LoginPrompt): LoginPromptView {\n const view: LoginPromptView = { id, type: prompt.type, message: prompt.message };\n if (prompt.type === 'select') view.options = prompt.options.map((option) => ({ ...option }));\n else if (prompt.placeholder !== undefined) view.placeholder = prompt.placeholder;\n return view;\n}\n\n/**\n * The state machine between a provider login and a browser that can only\n * poll and post.\n *\n * A provider asks one question at a time and may withdraw it (a manual-code\n * prompt raced against a callback server aborts when the callback wins), so\n * the flow holds at most one pending prompt, rejects it when its signal\n * fires, and supersedes it if the provider asks again.\n */\nexport function createLoginFlow(input: LoginFlowInput): LoginFlow {\n const controller = new AbortController();\n const events: LoginEvent[] = [];\n let status: LoginFlowStatus = 'running';\n let error: string | undefined;\n let pending: PendingPrompt | undefined;\n let promptSeq = 0;\n\n const failPending = (reason: Error): void => {\n const current = pending;\n if (!current) return;\n pending = undefined;\n current.detach();\n current.reject(reason);\n };\n\n const interaction: LoginInteraction = {\n signal: controller.signal,\n prompt(prompt) {\n if (controller.signal.aborted || prompt.signal?.aborted) return Promise.reject(new Error(CANCELLED_MESSAGE));\n failPending(new Error(SUPERSEDED_MESSAGE));\n promptSeq += 1;\n const view = promptView(String(promptSeq), prompt);\n return new Promise<string>((resolve, reject) => {\n const onAbort = (): void => failPending(new Error(CANCELLED_MESSAGE));\n prompt.signal?.addEventListener('abort', onAbort, { once: true });\n pending = {\n view,\n resolve,\n reject,\n detach: () => prompt.signal?.removeEventListener('abort', onAbort),\n };\n });\n },\n notify(event) {\n events.push(event);\n },\n };\n\n return {\n id: input.id,\n providerId: input.providerId,\n interaction,\n snapshot() {\n const snapshot: LoginFlowSnapshot = {\n id: input.id,\n providerId: input.providerId,\n providerName: input.providerName,\n type: input.type,\n status,\n events: [...events],\n };\n if (pending) snapshot.prompt = pending.view;\n if (error !== undefined) snapshot.error = error;\n return snapshot;\n },\n answer(promptId, value) {\n const current = pending;\n if (!current || current.view.id !== promptId) return false;\n pending = undefined;\n current.detach();\n current.resolve(value);\n return true;\n },\n cancel() {\n if (status !== 'running') return;\n status = 'cancelled';\n controller.abort();\n failPending(new Error(CANCELLED_MESSAGE));\n },\n settle(outcome) {\n if (status !== 'running') return;\n if (outcome.ok) {\n status = 'succeeded';\n } else {\n status = 'failed';\n error = outcome.error;\n }\n failPending(new Error(CANCELLED_MESSAGE));\n },\n };\n}\n"],"mappings":"AAWA,MAAM,EAAoB,kBAmC1B,SAAS,EAAW,EAAY,EAAsC,CACpE,IAAM,EAAwB,CAAE,KAAI,KAAM,EAAO,KAAM,QAAS,EAAO,OAAQ,EAG/E,OAFI,EAAO,OAAS,SAAU,EAAK,QAAU,EAAO,QAAQ,IAAK,IAAY,CAAE,GAAG,CAAO,EAAE,EAClF,EAAO,cAAgB,IAAA,KAAW,EAAK,YAAc,EAAO,aAC9D,CACT,CAWA,SAAgB,EAAgB,EAAkC,CAChE,IAAM,EAAa,IAAI,gBACjB,EAAuB,CAAC,EAC1B,EAA0B,UAC1B,EACA,EACA,EAAY,EAEV,EAAe,GAAwB,CAC3C,IAAM,EAAU,EACX,IACL,EAAU,IAAA,GACV,EAAQ,OAAO,EACf,EAAQ,OAAO,CAAM,EACvB,EAEM,EAAgC,CACpC,OAAQ,EAAW,OACnB,OAAO,EAAQ,CACb,GAAI,EAAW,OAAO,SAAW,EAAO,QAAQ,QAAS,OAAO,QAAQ,OAAW,MAAM,CAAiB,CAAC,EAC3G,EAAgB,MAAM,8BAAkB,CAAC,EACzC,GAAa,EACb,IAAM,EAAO,EAAW,OAAO,CAAS,EAAG,CAAM,EACjD,OAAO,IAAI,SAAiB,EAAS,IAAW,CAC9C,IAAM,MAAsB,EAAgB,MAAM,CAAiB,CAAC,EACpE,EAAO,QAAQ,iBAAiB,QAAS,EAAS,CAAE,KAAM,EAAK,CAAC,EAChE,EAAU,CACR,OACA,UACA,SACA,WAAc,EAAO,QAAQ,oBAAoB,QAAS,CAAO,CACnE,CACF,CAAC,CACH,EACA,OAAO,EAAO,CACZ,EAAO,KAAK,CAAK,CACnB,CACF,EAEA,MAAO,CACL,GAAI,EAAM,GACV,WAAY,EAAM,WAClB,cACA,UAAW,CACT,IAAM,EAA8B,CAClC,GAAI,EAAM,GACV,WAAY,EAAM,WAClB,aAAc,EAAM,aACpB,KAAM,EAAM,KACZ,SACA,OAAQ,CAAC,GAAG,CAAM,CACpB,EAGA,OAFI,IAAS,EAAS,OAAS,EAAQ,MACnC,IAAU,IAAA,KAAW,EAAS,MAAQ,GACnC,CACT,EACA,OAAO,EAAU,EAAO,CACtB,IAAM,EAAU,EAKhB,MAJI,CAAC,GAAW,EAAQ,KAAK,KAAO,EAAiB,IACrD,EAAU,IAAA,GACV,EAAQ,OAAO,EACf,EAAQ,QAAQ,CAAK,EACd,GACT,EACA,QAAS,CACH,IAAW,YACf,EAAS,YACT,EAAW,MAAM,EACjB,EAAgB,MAAM,CAAiB,CAAC,EAC1C,EACA,OAAO,EAAS,CACV,IAAW,YACX,EAAQ,GACV,EAAS,aAET,EAAS,SACT,EAAQ,EAAQ,OAElB,EAAgB,MAAM,CAAiB,CAAC,EAC1C,CACF,CACF"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=`Login cancelled`;function t(e,t){let n={id:e,type:t.type,message:t.message};return t.type===`select`?n.options=t.options.map(e=>({...e})):t.placeholder!==void 0&&(n.placeholder=t.placeholder),n}function n(n){let r=new AbortController,i=[],a=`running`,o,s,c=0,l=e=>{let t=s;t&&(s=void 0,t.detach(),t.reject(e))},u={signal:r.signal,prompt(n){if(r.signal.aborted||n.signal?.aborted)return Promise.reject(Error(e));l(Error(`Superseded by a newer prompt`)),c+=1;let i=t(String(c),n);return new Promise((t,r)=>{let a=()=>l(Error(e));n.signal?.addEventListener(`abort`,a,{once:!0}),s={view:i,resolve:t,reject:r,detach:()=>n.signal?.removeEventListener(`abort`,a)}})},notify(e){i.push(e)}};return{id:n.id,providerId:n.providerId,interaction:u,snapshot(){let e={id:n.id,providerId:n.providerId,providerName:n.providerName,type:n.type,status:a,events:[...i]};return s&&(e.prompt=s.view),o!==void 0&&(e.error=o),e},answer(e,t){let n=s;return!n||n.view.id!==e?!1:(s=void 0,n.detach(),n.resolve(t),!0)},cancel(){a===`running`&&(a=`cancelled`,r.abort(),l(Error(e)))},settle(t){a===`running`&&(t.ok?a=`succeeded`:(a=`failed`,o=t.error),l(Error(e)))}}}export{n as createLoginFlow};
|
|
2
|
+
//# sourceMappingURL=loginFlow.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loginFlow.mjs","names":[],"sources":["../../../src/services/loginFlow.ts"],"sourcesContent":["import type {\n AuthMethodType,\n LoginEvent,\n LoginFlowSnapshot,\n LoginFlowStatus,\n LoginInteraction,\n LoginPrompt,\n LoginPromptView,\n} from '../types/auth.ts';\n\n/** The message Pi's own login dialog uses; a provider flow may match on it. */\nconst CANCELLED_MESSAGE = 'Login cancelled';\nconst SUPERSEDED_MESSAGE = 'Superseded by a newer prompt';\n\ninterface PendingPrompt {\n view: LoginPromptView;\n resolve(value: string): void;\n reject(error: Error): void;\n detach(): void;\n}\n\nexport interface LoginFlowInput {\n id: string;\n providerId: string;\n providerName: string;\n type: AuthMethodType;\n}\n\n/**\n * One login in progress, seen from both sides: the provider's flow talks to\n * it through `interaction`, the page through snapshots and answers.\n */\nexport interface LoginFlow {\n readonly id: string;\n readonly providerId: string;\n /** Hand this to the runtime's login; prompts and events route through the flow. */\n readonly interaction: LoginInteraction;\n snapshot(): LoginFlowSnapshot;\n /** Answers the pending prompt; false when the flow is not waiting on that prompt. */\n answer(promptId: string, value: string): boolean;\n /** Aborts the provider's flow; a settled flow is left as it ended. */\n cancel(): void;\n /** Records how the runtime's login ended; a cancelled flow stays cancelled. */\n settle(outcome: { ok: true } | { ok: false; error: string }): void;\n}\n\nfunction promptView(id: string, prompt: LoginPrompt): LoginPromptView {\n const view: LoginPromptView = { id, type: prompt.type, message: prompt.message };\n if (prompt.type === 'select') view.options = prompt.options.map((option) => ({ ...option }));\n else if (prompt.placeholder !== undefined) view.placeholder = prompt.placeholder;\n return view;\n}\n\n/**\n * The state machine between a provider login and a browser that can only\n * poll and post.\n *\n * A provider asks one question at a time and may withdraw it (a manual-code\n * prompt raced against a callback server aborts when the callback wins), so\n * the flow holds at most one pending prompt, rejects it when its signal\n * fires, and supersedes it if the provider asks again.\n */\nexport function createLoginFlow(input: LoginFlowInput): LoginFlow {\n const controller = new AbortController();\n const events: LoginEvent[] = [];\n let status: LoginFlowStatus = 'running';\n let error: string | undefined;\n let pending: PendingPrompt | undefined;\n let promptSeq = 0;\n\n const failPending = (reason: Error): void => {\n const current = pending;\n if (!current) return;\n pending = undefined;\n current.detach();\n current.reject(reason);\n };\n\n const interaction: LoginInteraction = {\n signal: controller.signal,\n prompt(prompt) {\n if (controller.signal.aborted || prompt.signal?.aborted) return Promise.reject(new Error(CANCELLED_MESSAGE));\n failPending(new Error(SUPERSEDED_MESSAGE));\n promptSeq += 1;\n const view = promptView(String(promptSeq), prompt);\n return new Promise<string>((resolve, reject) => {\n const onAbort = (): void => failPending(new Error(CANCELLED_MESSAGE));\n prompt.signal?.addEventListener('abort', onAbort, { once: true });\n pending = {\n view,\n resolve,\n reject,\n detach: () => prompt.signal?.removeEventListener('abort', onAbort),\n };\n });\n },\n notify(event) {\n events.push(event);\n },\n };\n\n return {\n id: input.id,\n providerId: input.providerId,\n interaction,\n snapshot() {\n const snapshot: LoginFlowSnapshot = {\n id: input.id,\n providerId: input.providerId,\n providerName: input.providerName,\n type: input.type,\n status,\n events: [...events],\n };\n if (pending) snapshot.prompt = pending.view;\n if (error !== undefined) snapshot.error = error;\n return snapshot;\n },\n answer(promptId, value) {\n const current = pending;\n if (!current || current.view.id !== promptId) return false;\n pending = undefined;\n current.detach();\n current.resolve(value);\n return true;\n },\n cancel() {\n if (status !== 'running') return;\n status = 'cancelled';\n controller.abort();\n failPending(new Error(CANCELLED_MESSAGE));\n },\n settle(outcome) {\n if (status !== 'running') return;\n if (outcome.ok) {\n status = 'succeeded';\n } else {\n status = 'failed';\n error = outcome.error;\n }\n failPending(new Error(CANCELLED_MESSAGE));\n },\n };\n}\n"],"mappings":"AAWA,MAAM,EAAoB,kBAmC1B,SAAS,EAAW,EAAY,EAAsC,CACpE,IAAM,EAAwB,CAAE,KAAI,KAAM,EAAO,KAAM,QAAS,EAAO,OAAQ,EAG/E,OAFI,EAAO,OAAS,SAAU,EAAK,QAAU,EAAO,QAAQ,IAAK,IAAY,CAAE,GAAG,CAAO,EAAE,EAClF,EAAO,cAAgB,IAAA,KAAW,EAAK,YAAc,EAAO,aAC9D,CACT,CAWA,SAAgB,EAAgB,EAAkC,CAChE,IAAM,EAAa,IAAI,gBACjB,EAAuB,CAAC,EAC1B,EAA0B,UAC1B,EACA,EACA,EAAY,EAEV,EAAe,GAAwB,CAC3C,IAAM,EAAU,EACX,IACL,EAAU,IAAA,GACV,EAAQ,OAAO,EACf,EAAQ,OAAO,CAAM,EACvB,EAEM,EAAgC,CACpC,OAAQ,EAAW,OACnB,OAAO,EAAQ,CACb,GAAI,EAAW,OAAO,SAAW,EAAO,QAAQ,QAAS,OAAO,QAAQ,OAAW,MAAM,CAAiB,CAAC,EAC3G,EAAgB,MAAM,8BAAkB,CAAC,EACzC,GAAa,EACb,IAAM,EAAO,EAAW,OAAO,CAAS,EAAG,CAAM,EACjD,OAAO,IAAI,SAAiB,EAAS,IAAW,CAC9C,IAAM,MAAsB,EAAgB,MAAM,CAAiB,CAAC,EACpE,EAAO,QAAQ,iBAAiB,QAAS,EAAS,CAAE,KAAM,EAAK,CAAC,EAChE,EAAU,CACR,OACA,UACA,SACA,WAAc,EAAO,QAAQ,oBAAoB,QAAS,CAAO,CACnE,CACF,CAAC,CACH,EACA,OAAO,EAAO,CACZ,EAAO,KAAK,CAAK,CACnB,CACF,EAEA,MAAO,CACL,GAAI,EAAM,GACV,WAAY,EAAM,WAClB,cACA,UAAW,CACT,IAAM,EAA8B,CAClC,GAAI,EAAM,GACV,WAAY,EAAM,WAClB,aAAc,EAAM,aACpB,KAAM,EAAM,KACZ,SACA,OAAQ,CAAC,GAAG,CAAM,CACpB,EAGA,OAFI,IAAS,EAAS,OAAS,EAAQ,MACnC,IAAU,IAAA,KAAW,EAAS,MAAQ,GACnC,CACT,EACA,OAAO,EAAU,EAAO,CACtB,IAAM,EAAU,EAKhB,MAJI,CAAC,GAAW,EAAQ,KAAK,KAAO,EAAiB,IACrD,EAAU,IAAA,GACV,EAAQ,OAAO,EACf,EAAQ,QAAQ,CAAK,EACd,GACT,EACA,QAAS,CACH,IAAW,YACf,EAAS,YACT,EAAW,MAAM,EACjB,EAAgB,MAAM,CAAiB,CAAC,EAC1C,EACA,OAAO,EAAS,CACV,IAAW,YACX,EAAQ,GACV,EAAS,aAET,EAAS,SACT,EAAQ,EAAQ,OAElB,EAAgB,MAAM,CAAiB,CAAC,EAC1C,CACF,CACF"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
require("../types/registry.cjs");let e=require("@agimon-ai/doompi-server");const t=`.json`,n=[`id`,`cwd`,`socketPath`,`tokenFile`,`createdAt`];function r(e){return e.flagValue?e.flagValue:e.envValue?e.envValue:`${e.homeDir}/.doompi/run`}function i(e){return`${e}/sessions`}function a(e,n){return`${i(e)}/${n}${t}`}function o(e){return e.endsWith(t)}function s(t){let r;try{r=JSON.parse(t)}catch{return}if(typeof r!=`object`||!r||Array.isArray(r))return;let i=r;if(i.version===e.SESSION_RECORD_VERSION){for(let e of n)if(typeof i[e]!=`string`||i[e]===``)return;if(typeof i.name==`string`&&!(typeof i.pid!=`number`||!Number.isInteger(i.pid)||i.pid<=0))return{version:e.SESSION_RECORD_VERSION,id:i.id,name:i.name,cwd:i.cwd,socketPath:i.socketPath,tokenFile:i.tokenFile,...typeof i.apiSocketPath==`string`&&i.apiSocketPath!==``?{apiSocketPath:i.apiSocketPath}:{},...typeof i.protocolSocketPath==`string`&&i.protocolSocketPath!==``?{protocolSocketPath:i.protocolSocketPath}:{},pid:i.pid,createdAt:i.createdAt}}}exports.REGISTRY_DIR_ENV=`DOOMPI_RUNTIME_DIR`,exports.isRecordFileName=o,exports.parseSessionRecord=s,exports.resolveRegistryDir=r,exports.sessionRecordPath=a,exports.sessionRecordsDir=i;
|
|
2
|
+
//# sourceMappingURL=registryStore.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registryStore.cjs","names":["SESSION_RECORD_VERSION"],"sources":["../../../src/services/registryStore.ts"],"sourcesContent":["import { SESSION_RECORD_VERSION, type SessionRecord } from '../types/registry.ts';\n\nconst RECORD_EXTENSION = '.json';\nconst REQUIRED_STRINGS = ['id', 'cwd', 'socketPath', 'tokenFile', 'createdAt'] as const;\n\n/** Environment variable overriding where session records live. */\nexport const REGISTRY_DIR_ENV = 'DOOMPI_RUNTIME_DIR';\n\nconst DEFAULT_RUN_DIR_SEGMENT = '.doompi/run';\nconst SESSIONS_SEGMENT = 'sessions';\n\nexport interface RegistryDirInput {\n /** Value of --registry-dir, when given; wins over everything else. */\n readonly flagValue?: string;\n /** Value of DOOMPI_RUNTIME_DIR, when set. */\n readonly envValue?: string;\n /** The user's home directory, supplied by the host. */\n readonly homeDir: string;\n}\n\n/** Mirrors doompi-server's resolution so both sides land on the same directory. */\nexport function resolveRegistryDir(input: RegistryDirInput): string {\n if (input.flagValue) return input.flagValue;\n if (input.envValue) return input.envValue;\n return `${input.homeDir}/${DEFAULT_RUN_DIR_SEGMENT}`;\n}\n\n/** Directory the watcher scans for record files. */\nexport function sessionRecordsDir(registryDir: string): string {\n return `${registryDir}/${SESSIONS_SEGMENT}`;\n}\n\nexport function sessionRecordPath(registryDir: string, sessionId: string): string {\n return `${sessionRecordsDir(registryDir)}/${sessionId}${RECORD_EXTENSION}`;\n}\n\nexport function isRecordFileName(name: string): boolean {\n return name.endsWith(RECORD_EXTENSION);\n}\n\n/**\n * Validates one record file's content.\n *\n * Returns undefined for anything that is not a well-formed current-version\n * record: a truncated write, a foreign file, or a future format this build\n * does not understand. The watcher simply skips such files.\n */\nexport function parseSessionRecord(raw: string): SessionRecord | undefined {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return undefined;\n }\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) return undefined;\n const record = parsed as Record<string, unknown>;\n if (record.version !== SESSION_RECORD_VERSION) return undefined;\n for (const key of REQUIRED_STRINGS) {\n if (typeof record[key] !== 'string' || record[key] === '') return undefined;\n }\n if (typeof record.name !== 'string') return undefined;\n if (typeof record.pid !== 'number' || !Number.isInteger(record.pid) || record.pid <= 0) return undefined;\n return {\n version: SESSION_RECORD_VERSION,\n id: record.id as string,\n name: record.name,\n cwd: record.cwd as string,\n socketPath: record.socketPath as string,\n tokenFile: record.tokenFile as string,\n // Optional: a server that mounted no package API omits it, and so does an\n // older one that predates the field.\n ...(typeof record.apiSocketPath === 'string' && record.apiSocketPath !== ''\n ? { apiSocketPath: record.apiSocketPath }\n : {}),\n // Also optional: a server predating the protocol socket serves only the\n // framed one, and a client that needs the protocol says so itself.\n ...(typeof record.protocolSocketPath === 'string' && record.protocolSocketPath !== ''\n ? { protocolSocketPath: record.protocolSocketPath }\n : {}),\n pid: record.pid,\n createdAt: record.createdAt as string,\n };\n}\n"],"mappings":"2EAEA,MAAM,EAAmB,QACnB,EAAmB,CAAC,KAAM,MAAO,aAAc,YAAa,WAAW,EAkB7E,SAAgB,EAAmB,EAAiC,CAGlE,OAFI,EAAM,UAAkB,EAAM,UAC9B,EAAM,SAAiB,EAAM,SAC1B,GAAG,EAAM,QAAQ,aAC1B,CAGA,SAAgB,EAAkB,EAA6B,CAC7D,MAAO,GAAG,EAAY,UACxB,CAEA,SAAgB,EAAkB,EAAqB,EAA2B,CAChF,MAAO,GAAG,EAAkB,CAAW,EAAE,GAAG,IAAY,GAC1D,CAEA,SAAgB,EAAiB,EAAuB,CACtD,OAAO,EAAK,SAAS,CAAgB,CACvC,CASA,SAAgB,EAAmB,EAAwC,CACzE,IAAI,EACJ,GAAI,CACF,EAAS,KAAK,MAAM,CAAG,CACzB,MAAQ,CACN,MACF,CACA,GAAI,OAAO,GAAW,WAAY,GAAmB,MAAM,QAAQ,CAAM,EAAG,OAC5E,IAAM,EAAS,EACX,KAAO,UAAYA,EAAAA,uBACvB,KAAK,IAAM,KAAO,EAChB,GAAI,OAAO,EAAO,IAAS,UAAY,EAAO,KAAS,GAAI,OAEzD,UAAO,EAAO,MAAS,UACvB,SAAO,EAAO,KAAQ,UAAY,CAAC,OAAO,UAAU,EAAO,GAAG,GAAK,EAAO,KAAO,GACrF,MAAO,CACL,QAASA,EAAAA,uBACT,GAAI,EAAO,GACX,KAAM,EAAO,KACb,IAAK,EAAO,IACZ,WAAY,EAAO,WACnB,UAAW,EAAO,UAGlB,GAAI,OAAO,EAAO,eAAkB,UAAY,EAAO,gBAAkB,GACrE,CAAE,cAAe,EAAO,aAAc,EACtC,CAAC,EAGL,GAAI,OAAO,EAAO,oBAAuB,UAAY,EAAO,qBAAuB,GAC/E,CAAE,mBAAoB,EAAO,kBAAmB,EAChD,CAAC,EACL,IAAK,EAAO,IACZ,UAAW,EAAO,SACpB,CAvBoE,CAwBtE"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{SESSION_RECORD_VERSION as e}from"../types/registry.mjs";const t=`.json`,n=[`id`,`cwd`,`socketPath`,`tokenFile`,`createdAt`],r=`DOOMPI_RUNTIME_DIR`;function i(e){return e.flagValue?e.flagValue:e.envValue?e.envValue:`${e.homeDir}/.doompi/run`}function a(e){return`${e}/sessions`}function o(e,n){return`${a(e)}/${n}${t}`}function s(e){return e.endsWith(t)}function c(t){let r;try{r=JSON.parse(t)}catch{return}if(typeof r!=`object`||!r||Array.isArray(r))return;let i=r;if(i.version===e){for(let e of n)if(typeof i[e]!=`string`||i[e]===``)return;if(typeof i.name==`string`&&!(typeof i.pid!=`number`||!Number.isInteger(i.pid)||i.pid<=0))return{version:e,id:i.id,name:i.name,cwd:i.cwd,socketPath:i.socketPath,tokenFile:i.tokenFile,...typeof i.apiSocketPath==`string`&&i.apiSocketPath!==``?{apiSocketPath:i.apiSocketPath}:{},...typeof i.protocolSocketPath==`string`&&i.protocolSocketPath!==``?{protocolSocketPath:i.protocolSocketPath}:{},pid:i.pid,createdAt:i.createdAt}}}export{r as REGISTRY_DIR_ENV,s as isRecordFileName,c as parseSessionRecord,i as resolveRegistryDir,o as sessionRecordPath,a as sessionRecordsDir};
|
|
2
|
+
//# sourceMappingURL=registryStore.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registryStore.mjs","names":[],"sources":["../../../src/services/registryStore.ts"],"sourcesContent":["import { SESSION_RECORD_VERSION, type SessionRecord } from '../types/registry.ts';\n\nconst RECORD_EXTENSION = '.json';\nconst REQUIRED_STRINGS = ['id', 'cwd', 'socketPath', 'tokenFile', 'createdAt'] as const;\n\n/** Environment variable overriding where session records live. */\nexport const REGISTRY_DIR_ENV = 'DOOMPI_RUNTIME_DIR';\n\nconst DEFAULT_RUN_DIR_SEGMENT = '.doompi/run';\nconst SESSIONS_SEGMENT = 'sessions';\n\nexport interface RegistryDirInput {\n /** Value of --registry-dir, when given; wins over everything else. */\n readonly flagValue?: string;\n /** Value of DOOMPI_RUNTIME_DIR, when set. */\n readonly envValue?: string;\n /** The user's home directory, supplied by the host. */\n readonly homeDir: string;\n}\n\n/** Mirrors doompi-server's resolution so both sides land on the same directory. */\nexport function resolveRegistryDir(input: RegistryDirInput): string {\n if (input.flagValue) return input.flagValue;\n if (input.envValue) return input.envValue;\n return `${input.homeDir}/${DEFAULT_RUN_DIR_SEGMENT}`;\n}\n\n/** Directory the watcher scans for record files. */\nexport function sessionRecordsDir(registryDir: string): string {\n return `${registryDir}/${SESSIONS_SEGMENT}`;\n}\n\nexport function sessionRecordPath(registryDir: string, sessionId: string): string {\n return `${sessionRecordsDir(registryDir)}/${sessionId}${RECORD_EXTENSION}`;\n}\n\nexport function isRecordFileName(name: string): boolean {\n return name.endsWith(RECORD_EXTENSION);\n}\n\n/**\n * Validates one record file's content.\n *\n * Returns undefined for anything that is not a well-formed current-version\n * record: a truncated write, a foreign file, or a future format this build\n * does not understand. The watcher simply skips such files.\n */\nexport function parseSessionRecord(raw: string): SessionRecord | undefined {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return undefined;\n }\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) return undefined;\n const record = parsed as Record<string, unknown>;\n if (record.version !== SESSION_RECORD_VERSION) return undefined;\n for (const key of REQUIRED_STRINGS) {\n if (typeof record[key] !== 'string' || record[key] === '') return undefined;\n }\n if (typeof record.name !== 'string') return undefined;\n if (typeof record.pid !== 'number' || !Number.isInteger(record.pid) || record.pid <= 0) return undefined;\n return {\n version: SESSION_RECORD_VERSION,\n id: record.id as string,\n name: record.name,\n cwd: record.cwd as string,\n socketPath: record.socketPath as string,\n tokenFile: record.tokenFile as string,\n // Optional: a server that mounted no package API omits it, and so does an\n // older one that predates the field.\n ...(typeof record.apiSocketPath === 'string' && record.apiSocketPath !== ''\n ? { apiSocketPath: record.apiSocketPath }\n : {}),\n // Also optional: a server predating the protocol socket serves only the\n // framed one, and a client that needs the protocol says so itself.\n ...(typeof record.protocolSocketPath === 'string' && record.protocolSocketPath !== ''\n ? { protocolSocketPath: record.protocolSocketPath }\n : {}),\n pid: record.pid,\n createdAt: record.createdAt as string,\n };\n}\n"],"mappings":"+DAEA,MAAM,EAAmB,QACnB,EAAmB,CAAC,KAAM,MAAO,aAAc,YAAa,WAAW,EAGhE,EAAmB,qBAehC,SAAgB,EAAmB,EAAiC,CAGlE,OAFI,EAAM,UAAkB,EAAM,UAC9B,EAAM,SAAiB,EAAM,SAC1B,GAAG,EAAM,QAAQ,aAC1B,CAGA,SAAgB,EAAkB,EAA6B,CAC7D,MAAO,GAAG,EAAY,UACxB,CAEA,SAAgB,EAAkB,EAAqB,EAA2B,CAChF,MAAO,GAAG,EAAkB,CAAW,EAAE,GAAG,IAAY,GAC1D,CAEA,SAAgB,EAAiB,EAAuB,CACtD,OAAO,EAAK,SAAS,CAAgB,CACvC,CASA,SAAgB,EAAmB,EAAwC,CACzE,IAAI,EACJ,GAAI,CACF,EAAS,KAAK,MAAM,CAAG,CACzB,MAAQ,CACN,MACF,CACA,GAAI,OAAO,GAAW,WAAY,GAAmB,MAAM,QAAQ,CAAM,EAAG,OAC5E,IAAM,EAAS,EACX,KAAO,UAAY,EACvB,KAAK,IAAM,KAAO,EAChB,GAAI,OAAO,EAAO,IAAS,UAAY,EAAO,KAAS,GAAI,OAEzD,UAAO,EAAO,MAAS,UACvB,SAAO,EAAO,KAAQ,UAAY,CAAC,OAAO,UAAU,EAAO,GAAG,GAAK,EAAO,KAAO,GACrF,MAAO,CACL,QAAS,EACT,GAAI,EAAO,GACX,KAAM,EAAO,KACb,IAAK,EAAO,IACZ,WAAY,EAAO,WACnB,UAAW,EAAO,UAGlB,GAAI,OAAO,EAAO,eAAkB,UAAY,EAAO,gBAAkB,GACrE,CAAE,cAAe,EAAO,aAAc,EACtC,CAAC,EAGL,GAAI,OAAO,EAAO,oBAAuB,UAAY,EAAO,qBAAuB,GAC/E,CAAE,mBAAoB,EAAO,kBAAmB,EAChD,CAAC,EACL,IAAK,EAAO,IACZ,UAAW,EAAO,SACpB,CAvBoE,CAwBtE"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retryPolicy.cjs","names":[],"sources":["../../../src/services/retryPolicy.ts"],"sourcesContent":["const BASE_DELAY_MS = 150;\nconst MAX_DELAY_MS = 4000;\n\n/**\n * Exponential backoff for reattaching to the session socket.\n *\n * A browser reload closes the old socket and opens a new one, and the server\n * only allows one client at a time, so the first retries have to be quick\n * enough to win that handover without hammering a socket that is genuinely\n * held by someone else.\n */\nexport function reattachDelayMs(attempt: number): number {\n if (attempt <= 0) return 0;\n return Math.min(BASE_DELAY_MS * 2 ** (attempt - 1), MAX_DELAY_MS);\n}\n"],"mappings":"AAWA,SAAgB,EAAgB,EAAyB,CAEvD,OADI,GAAW,EAAU,EAClB,KAAK,IAAI,IAAgB,IAAM,EAAU,GAAI,GAAY,CAClE"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retryPolicy.mjs","names":[],"sources":["../../../src/services/retryPolicy.ts"],"sourcesContent":["const BASE_DELAY_MS = 150;\nconst MAX_DELAY_MS = 4000;\n\n/**\n * Exponential backoff for reattaching to the session socket.\n *\n * A browser reload closes the old socket and opens a new one, and the server\n * only allows one client at a time, so the first retries have to be quick\n * enough to win that handover without hammering a socket that is genuinely\n * held by someone else.\n */\nexport function reattachDelayMs(attempt: number): number {\n if (attempt <= 0) return 0;\n return Math.min(BASE_DELAY_MS * 2 ** (attempt - 1), MAX_DELAY_MS);\n}\n"],"mappings":"AAWA,SAAgB,EAAgB,EAAyB,CAEvD,OADI,GAAW,EAAU,EAClB,KAAK,IAAI,IAAgB,IAAM,EAAU,GAAI,GAAY,CAClE"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
function e(e,t){if(t===void 0||t.startsWith(`--`))throw Error(`${e} needs a value.`);return t}function t(t){let n,r,i=7433,a=`127.0.0.1`,o;for(let s=0;s<t.length;s+=1){let c=t[s];switch(c){case`--registry-dir`:n=e(c,t[++s]);break;case`--spawn-command`:r=e(c,t[++s]);break;case`--port`:{let n=e(c,t[++s]),r=Number.parseInt(n,10);if(!Number.isInteger(r)||r<0||r>65535)throw Error(`--port expects a port number, received "${n}".`);i=r;break}case`--host`:a=e(c,t[++s]);break;case`--assets`:o=e(c,t[++s]);break;default:throw Error(`Unknown option "${c}".`)}}return{registryDir:n,spawnCommand:r,port:i,host:a,assetsDir:o}}exports.parseServeOptions=t;
|
|
2
|
+
//# sourceMappingURL=serveOptions.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serveOptions.cjs","names":[],"sources":["../../../src/services/serveOptions.ts"],"sourcesContent":["export interface ServeOptions {\n /** Registry override; the default is resolved by the caller. */\n registryDir?: string;\n /** Command launching created sessions; the caller resolves one when this is absent. */\n spawnCommand?: string;\n port: number;\n host: string;\n assetsDir?: string;\n}\n\nconst DEFAULT_PORT = 7433;\nconst DEFAULT_HOST = '127.0.0.1';\n\nfunction requireValue(flag: string, value: string | undefined): string {\n if (value === undefined || value.startsWith('--')) throw new Error(`${flag} needs a value.`);\n return value;\n}\n\n/**\n * Parses the doompi-web command line.\n *\n * Every flag is an override. Bare `doompi-web` is a hub over the default\n * registry directory on the default loopback port, which is the way it is\n * meant to be run; nothing here is required to get a working cockpit.\n */\nexport function parseServeOptions(argv: readonly string[]): ServeOptions {\n let registryDir: string | undefined;\n let spawnCommand: string | undefined;\n let port = DEFAULT_PORT;\n let host = DEFAULT_HOST;\n let assetsDir: string | undefined;\n\n for (let index = 0; index < argv.length; index += 1) {\n const flag = argv[index];\n switch (flag) {\n case '--registry-dir':\n registryDir = requireValue(flag, argv[++index]);\n break;\n case '--spawn-command':\n spawnCommand = requireValue(flag, argv[++index]);\n break;\n case '--port': {\n const raw = requireValue(flag, argv[++index]);\n const parsed = Number.parseInt(raw, 10);\n if (!Number.isInteger(parsed) || parsed < 0 || parsed > 65535) {\n throw new Error(`--port expects a port number, received \"${raw}\".`);\n }\n port = parsed;\n break;\n }\n case '--host':\n host = requireValue(flag, argv[++index]);\n break;\n case '--assets':\n assetsDir = requireValue(flag, argv[++index]);\n break;\n default:\n throw new Error(`Unknown option \"${flag}\".`);\n }\n }\n\n return { registryDir, spawnCommand, port, host, assetsDir };\n}\n"],"mappings":"AAaA,SAAS,EAAa,EAAc,EAAmC,CACrE,GAAI,IAAU,IAAA,IAAa,EAAM,WAAW,IAAI,EAAG,MAAU,MAAM,GAAG,EAAK,gBAAgB,EAC3F,OAAO,CACT,CASA,SAAgB,EAAkB,EAAuC,CACvE,IAAI,EACA,EACA,EAAO,KACP,EAAO,YACP,EAEJ,IAAK,IAAI,EAAQ,EAAG,EAAQ,EAAK,OAAQ,GAAS,EAAG,CACnD,IAAM,EAAO,EAAK,GAClB,OAAQ,EAAR,CACE,IAAK,iBACH,EAAc,EAAa,EAAM,EAAK,EAAE,EAAM,EAC9C,MACF,IAAK,kBACH,EAAe,EAAa,EAAM,EAAK,EAAE,EAAM,EAC/C,MACF,IAAK,SAAU,CACb,IAAM,EAAM,EAAa,EAAM,EAAK,EAAE,EAAM,EACtC,EAAS,OAAO,SAAS,EAAK,EAAE,EACtC,GAAI,CAAC,OAAO,UAAU,CAAM,GAAK,EAAS,GAAK,EAAS,MACtD,MAAU,MAAM,2CAA2C,EAAI,GAAG,EAEpE,EAAO,EACP,KACF,CACA,IAAK,SACH,EAAO,EAAa,EAAM,EAAK,EAAE,EAAM,EACvC,MACF,IAAK,WACH,EAAY,EAAa,EAAM,EAAK,EAAE,EAAM,EAC5C,MACF,QACE,MAAU,MAAM,mBAAmB,EAAK,GAAG,CAC/C,CACF,CAEA,MAAO,CAAE,cAAa,eAAc,OAAM,OAAM,WAAU,CAC5D"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
//#region src/services/serveOptions.d.ts
|
|
2
|
+
interface ServeOptions {
|
|
3
|
+
/** Registry override; the default is resolved by the caller. */
|
|
4
|
+
registryDir?: string;
|
|
5
|
+
/** Command launching created sessions; the caller resolves one when this is absent. */
|
|
6
|
+
spawnCommand?: string;
|
|
7
|
+
port: number;
|
|
8
|
+
host: string;
|
|
9
|
+
assetsDir?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Parses the doompi-web command line.
|
|
13
|
+
*
|
|
14
|
+
* Every flag is an override. Bare `doompi-web` is a hub over the default
|
|
15
|
+
* registry directory on the default loopback port, which is the way it is
|
|
16
|
+
* meant to be run; nothing here is required to get a working cockpit.
|
|
17
|
+
*/
|
|
18
|
+
declare function parseServeOptions(argv: readonly string[]): ServeOptions;
|
|
19
|
+
//#endregion
|
|
20
|
+
export { ServeOptions, parseServeOptions };
|
|
21
|
+
//# sourceMappingURL=serveOptions.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serveOptions.d.cts","names":[],"sources":["../../../src/services/serveOptions.ts"],"mappings":";UAAiB;;EAEf;;EAEA;EACA;EACA;EACA;;;;;;;;;iBAkBc,kBAAkB,0BAA0B"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
//#region src/services/serveOptions.d.ts
|
|
2
|
+
interface ServeOptions {
|
|
3
|
+
/** Registry override; the default is resolved by the caller. */
|
|
4
|
+
registryDir?: string;
|
|
5
|
+
/** Command launching created sessions; the caller resolves one when this is absent. */
|
|
6
|
+
spawnCommand?: string;
|
|
7
|
+
port: number;
|
|
8
|
+
host: string;
|
|
9
|
+
assetsDir?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Parses the doompi-web command line.
|
|
13
|
+
*
|
|
14
|
+
* Every flag is an override. Bare `doompi-web` is a hub over the default
|
|
15
|
+
* registry directory on the default loopback port, which is the way it is
|
|
16
|
+
* meant to be run; nothing here is required to get a working cockpit.
|
|
17
|
+
*/
|
|
18
|
+
declare function parseServeOptions(argv: readonly string[]): ServeOptions;
|
|
19
|
+
//#endregion
|
|
20
|
+
export { ServeOptions, parseServeOptions };
|
|
21
|
+
//# sourceMappingURL=serveOptions.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serveOptions.d.mts","names":[],"sources":["../../../src/services/serveOptions.ts"],"mappings":";UAAiB;;EAEf;;EAEA;EACA;EACA;EACA;;;;;;;;;iBAkBc,kBAAkB,0BAA0B"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
function e(e,t){if(t===void 0||t.startsWith(`--`))throw Error(`${e} needs a value.`);return t}function t(t){let n,r,i=7433,a=`127.0.0.1`,o;for(let s=0;s<t.length;s+=1){let c=t[s];switch(c){case`--registry-dir`:n=e(c,t[++s]);break;case`--spawn-command`:r=e(c,t[++s]);break;case`--port`:{let n=e(c,t[++s]),r=Number.parseInt(n,10);if(!Number.isInteger(r)||r<0||r>65535)throw Error(`--port expects a port number, received "${n}".`);i=r;break}case`--host`:a=e(c,t[++s]);break;case`--assets`:o=e(c,t[++s]);break;default:throw Error(`Unknown option "${c}".`)}}return{registryDir:n,spawnCommand:r,port:i,host:a,assetsDir:o}}export{t as parseServeOptions};
|
|
2
|
+
//# sourceMappingURL=serveOptions.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serveOptions.mjs","names":[],"sources":["../../../src/services/serveOptions.ts"],"sourcesContent":["export interface ServeOptions {\n /** Registry override; the default is resolved by the caller. */\n registryDir?: string;\n /** Command launching created sessions; the caller resolves one when this is absent. */\n spawnCommand?: string;\n port: number;\n host: string;\n assetsDir?: string;\n}\n\nconst DEFAULT_PORT = 7433;\nconst DEFAULT_HOST = '127.0.0.1';\n\nfunction requireValue(flag: string, value: string | undefined): string {\n if (value === undefined || value.startsWith('--')) throw new Error(`${flag} needs a value.`);\n return value;\n}\n\n/**\n * Parses the doompi-web command line.\n *\n * Every flag is an override. Bare `doompi-web` is a hub over the default\n * registry directory on the default loopback port, which is the way it is\n * meant to be run; nothing here is required to get a working cockpit.\n */\nexport function parseServeOptions(argv: readonly string[]): ServeOptions {\n let registryDir: string | undefined;\n let spawnCommand: string | undefined;\n let port = DEFAULT_PORT;\n let host = DEFAULT_HOST;\n let assetsDir: string | undefined;\n\n for (let index = 0; index < argv.length; index += 1) {\n const flag = argv[index];\n switch (flag) {\n case '--registry-dir':\n registryDir = requireValue(flag, argv[++index]);\n break;\n case '--spawn-command':\n spawnCommand = requireValue(flag, argv[++index]);\n break;\n case '--port': {\n const raw = requireValue(flag, argv[++index]);\n const parsed = Number.parseInt(raw, 10);\n if (!Number.isInteger(parsed) || parsed < 0 || parsed > 65535) {\n throw new Error(`--port expects a port number, received \"${raw}\".`);\n }\n port = parsed;\n break;\n }\n case '--host':\n host = requireValue(flag, argv[++index]);\n break;\n case '--assets':\n assetsDir = requireValue(flag, argv[++index]);\n break;\n default:\n throw new Error(`Unknown option \"${flag}\".`);\n }\n }\n\n return { registryDir, spawnCommand, port, host, assetsDir };\n}\n"],"mappings":"AAaA,SAAS,EAAa,EAAc,EAAmC,CACrE,GAAI,IAAU,IAAA,IAAa,EAAM,WAAW,IAAI,EAAG,MAAU,MAAM,GAAG,EAAK,gBAAgB,EAC3F,OAAO,CACT,CASA,SAAgB,EAAkB,EAAuC,CACvE,IAAI,EACA,EACA,EAAO,KACP,EAAO,YACP,EAEJ,IAAK,IAAI,EAAQ,EAAG,EAAQ,EAAK,OAAQ,GAAS,EAAG,CACnD,IAAM,EAAO,EAAK,GAClB,OAAQ,EAAR,CACE,IAAK,iBACH,EAAc,EAAa,EAAM,EAAK,EAAE,EAAM,EAC9C,MACF,IAAK,kBACH,EAAe,EAAa,EAAM,EAAK,EAAE,EAAM,EAC/C,MACF,IAAK,SAAU,CACb,IAAM,EAAM,EAAa,EAAM,EAAK,EAAE,EAAM,EACtC,EAAS,OAAO,SAAS,EAAK,EAAE,EACtC,GAAI,CAAC,OAAO,UAAU,CAAM,GAAK,EAAS,GAAK,EAAS,MACtD,MAAU,MAAM,2CAA2C,EAAI,GAAG,EAEpE,EAAO,EACP,KACF,CACA,IAAK,SACH,EAAO,EAAa,EAAM,EAAK,EAAE,EAAM,EACvC,MACF,IAAK,WACH,EAAY,EAAa,EAAM,EAAK,EAAE,EAAM,EAC5C,MACF,QACE,MAAU,MAAM,mBAAmB,EAAK,GAAG,CAC/C,CACF,CAEA,MAAO,CAAE,cAAa,eAAc,OAAM,OAAM,WAAU,CAC5D"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
function e(){let e=``;return t=>{e+=t;let n=e.split(`
|
|
2
|
+
`);e=n.pop()??``;let r=[];for(let e of n){let t=e.trim();if(!t)continue;let n=JSON.parse(t);typeof n==`object`&&n&&!Array.isArray(n)&&r.push(n)}return r}}function t(e){return`${JSON.stringify(e)}
|
|
3
|
+
`}exports.createFrameDecoder=e,exports.encodeFrame=t;
|
|
4
|
+
//# sourceMappingURL=sessionFraming.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessionFraming.cjs","names":[],"sources":["../../../src/services/sessionFraming.ts"],"sourcesContent":["import type { SessionFrame } from '../types/session.ts';\n\nconst NEWLINE = '\\n';\n\n/**\n * Splits a byte stream into newline-delimited JSON frames.\n *\n * Stateful across chunks because a socket read can land mid-frame, which is the\n * normal case for streamed agent output rather than an edge case.\n */\nexport function createFrameDecoder(): (chunk: string) => SessionFrame[] {\n let buffered = '';\n return (chunk: string): SessionFrame[] => {\n buffered += chunk;\n const parts = buffered.split(NEWLINE);\n buffered = parts.pop() ?? '';\n const frames: SessionFrame[] = [];\n for (const part of parts) {\n const line = part.trim();\n if (!line) continue;\n const parsed: unknown = JSON.parse(line);\n if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {\n frames.push(parsed as SessionFrame);\n }\n }\n return frames;\n };\n}\n\nexport function encodeFrame(frame: SessionFrame): string {\n return `${JSON.stringify(frame)}${NEWLINE}`;\n}\n"],"mappings":"AAUA,SAAgB,GAAwD,CACtE,IAAI,EAAW,GACf,MAAQ,IAAkC,CACxC,GAAY,EACZ,IAAM,EAAQ,EAAS,MAAM;CAAO,EACpC,EAAW,EAAM,IAAI,GAAK,GAC1B,IAAM,EAAyB,CAAC,EAChC,IAAK,IAAM,KAAQ,EAAO,CACxB,IAAM,EAAO,EAAK,KAAK,EACvB,GAAI,CAAC,EAAM,SACX,IAAM,EAAkB,KAAK,MAAM,CAAI,EACnC,OAAO,GAAW,UAAY,GAAmB,CAAC,MAAM,QAAQ,CAAM,GACxE,EAAO,KAAK,CAAsB,CAEtC,CACA,OAAO,CACT,CACF,CAEA,SAAgB,EAAY,EAA6B,CACvD,MAAO,GAAG,KAAK,UAAU,CAAK;CAChC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
function e(){let e=``;return t=>{e+=t;let n=e.split(`
|
|
2
|
+
`);e=n.pop()??``;let r=[];for(let e of n){let t=e.trim();if(!t)continue;let n=JSON.parse(t);typeof n==`object`&&n&&!Array.isArray(n)&&r.push(n)}return r}}function t(e){return`${JSON.stringify(e)}
|
|
3
|
+
`}export{e as createFrameDecoder,t as encodeFrame};
|
|
4
|
+
//# sourceMappingURL=sessionFraming.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessionFraming.mjs","names":[],"sources":["../../../src/services/sessionFraming.ts"],"sourcesContent":["import type { SessionFrame } from '../types/session.ts';\n\nconst NEWLINE = '\\n';\n\n/**\n * Splits a byte stream into newline-delimited JSON frames.\n *\n * Stateful across chunks because a socket read can land mid-frame, which is the\n * normal case for streamed agent output rather than an edge case.\n */\nexport function createFrameDecoder(): (chunk: string) => SessionFrame[] {\n let buffered = '';\n return (chunk: string): SessionFrame[] => {\n buffered += chunk;\n const parts = buffered.split(NEWLINE);\n buffered = parts.pop() ?? '';\n const frames: SessionFrame[] = [];\n for (const part of parts) {\n const line = part.trim();\n if (!line) continue;\n const parsed: unknown = JSON.parse(line);\n if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {\n frames.push(parsed as SessionFrame);\n }\n }\n return frames;\n };\n}\n\nexport function encodeFrame(frame: SessionFrame): string {\n return `${JSON.stringify(frame)}${NEWLINE}`;\n}\n"],"mappings":"AAUA,SAAgB,GAAwD,CACtE,IAAI,EAAW,GACf,MAAQ,IAAkC,CACxC,GAAY,EACZ,IAAM,EAAQ,EAAS,MAAM;CAAO,EACpC,EAAW,EAAM,IAAI,GAAK,GAC1B,IAAM,EAAyB,CAAC,EAChC,IAAK,IAAM,KAAQ,EAAO,CACxB,IAAM,EAAO,EAAK,KAAK,EACvB,GAAI,CAAC,EAAM,SACX,IAAM,EAAkB,KAAK,MAAM,CAAI,EACnC,OAAO,GAAW,UAAY,GAAmB,CAAC,MAAM,QAAQ,CAAM,GACxE,EAAO,KAAK,CAAsB,CAEtC,CACA,OAAO,CACT,CACF,CAEA,SAAgB,EAAY,EAA6B,CACvD,MAAO,GAAG,KAAK,UAAU,CAAK;CAChC"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=[`select`,`confirm`,`input`,`editor`];function t(e){return{phase:`idle`,phaseSince:e,updatedAt:e,pendingMessageCount:0,everPrompted:!1,awaitingInput:!1}}function n(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function r(e,t,n){return e.phase===t?e:{...e,phase:t,phaseSince:n,updatedAt:n}}function i(e,t,n){let i=e,a=t.isCompacting===!0?`compaction`:t.isStreaming===!0?`turn`:`idle`;i=r(i,a,n);let o=typeof t.pendingMessageCount==`number`?t.pendingMessageCount:i.pendingMessageCount;o!==i.pendingMessageCount&&(i={...i,pendingMessageCount:o,updatedAt:n});let s=typeof t.sessionName==`string`&&t.sessionName!==``?t.sessionName:void 0;return s!==void 0&&s!==i.sessionName&&(i={...i,sessionName:s,updatedAt:n}),i}function a(t,o,s){switch(o.type){case`replay`:return n(o.frame)?a(t,o.frame,s):t;case`agent_start`:{let e=r(t,`turn`,s);return e.awaitingInput?{...e,awaitingInput:!1,updatedAt:s}:e}case`agent_settled`:return{...r(t,`idle`,s),awaitingInput:!1,lastSettledAt:s,updatedAt:s};case`extension_ui_request`:return typeof o.method!=`string`||!e.includes(o.method)||t.awaitingInput?t:{...t,awaitingInput:!0,updatedAt:s};case`response`:return o.command===`get_state`&&n(o.data)?i(t,o.data,s):t;default:return t}}function o(e,t,n){if(e.everPrompted||t.type!==`message`)return e;let r=t.message;return typeof r!=`object`||!r||r.role!==`user`?e:{...e,everPrompted:!0,updatedAt:n}}function s(e,t,n){return t.type===`prompt`||t.type===`steer`||t.type===`follow_up`?e.everPrompted?e:{...e,everPrompted:!0,updatedAt:n}:t.type===`extension_ui_response`&&e.awaitingInput?{...e,awaitingInput:!1,updatedAt:n}:e}exports.initialPresence=t,exports.presenceAfterCommand=s,exports.presenceAfterRestoredEntry=o,exports.reducePresence=a;
|
|
2
|
+
//# sourceMappingURL=sessionPresence.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessionPresence.cjs","names":[],"sources":["../../../src/services/sessionPresence.ts"],"sourcesContent":["import type { SessionPhase } from '../types/hub.ts';\nimport type { SessionFrame } from '../types/session.ts';\n\nconst DIALOG_METHODS = ['select', 'confirm', 'input', 'editor'];\n\n/**\n * What one session's frame stream says it is doing.\n *\n * Reduced by the hub for every session it holds, attached or not focused, so\n * the rail can describe sessions nobody is looking at. The reducer returns the\n * SAME object when a frame changes nothing it reports; callers use that\n * reference equality to skip redundant summary pushes.\n */\nexport interface SessionPresence {\n phase: SessionPhase;\n /** ISO 8601 timestamp of the last phase change. */\n phaseSince: string;\n /** ISO 8601 timestamp of the last change to any reported fact. */\n updatedAt: string;\n pendingMessageCount: number;\n everPrompted: boolean;\n awaitingInput: boolean;\n lastSettledAt?: string;\n /** Name reported by get_state, fresher than the registry record when set. */\n sessionName?: string;\n}\n\nexport function initialPresence(now: string): SessionPresence {\n return {\n phase: 'idle',\n phaseSince: now,\n updatedAt: now,\n pendingMessageCount: 0,\n everPrompted: false,\n awaitingInput: false,\n };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction withPhase(presence: SessionPresence, phase: SessionPhase, now: string): SessionPresence {\n if (presence.phase === phase) return presence;\n return { ...presence, phase, phaseSince: now, updatedAt: now };\n}\n\nfunction applyState(presence: SessionPresence, data: Record<string, unknown>, now: string): SessionPresence {\n let next = presence;\n const phase: SessionPhase = data.isCompacting === true ? 'compaction' : data.isStreaming === true ? 'turn' : 'idle';\n next = withPhase(next, phase, now);\n const pending = typeof data.pendingMessageCount === 'number' ? data.pendingMessageCount : next.pendingMessageCount;\n if (pending !== next.pendingMessageCount) next = { ...next, pendingMessageCount: pending, updatedAt: now };\n const name = typeof data.sessionName === 'string' && data.sessionName !== '' ? data.sessionName : undefined;\n if (name !== undefined && name !== next.sessionName) next = { ...next, sessionName: name, updatedAt: now };\n return next;\n}\n\n/**\n * Folds one agent frame into the presence.\n *\n * Pure and total like reduceSession: an unrecognised frame changes nothing.\n */\nexport function reducePresence(presence: SessionPresence, frame: SessionFrame, now: string): SessionPresence {\n switch (frame.type) {\n case 'replay':\n return isRecord(frame.frame) ? reducePresence(presence, frame.frame, now) : presence;\n\n case 'agent_start': {\n const started = withPhase(presence, 'turn', now);\n if (!started.awaitingInput) return started;\n return { ...started, awaitingInput: false, updatedAt: now };\n }\n\n case 'agent_settled': {\n const settled = withPhase(presence, 'idle', now);\n return { ...settled, awaitingInput: false, lastSettledAt: now, updatedAt: now };\n }\n\n case 'extension_ui_request': {\n if (typeof frame.method !== 'string' || !DIALOG_METHODS.includes(frame.method)) return presence;\n if (presence.awaitingInput) return presence;\n return { ...presence, awaitingInput: true, updatedAt: now };\n }\n\n case 'response':\n return frame.command === 'get_state' && isRecord(frame.data) ? applyState(presence, frame.data, now) : presence;\n\n default:\n return presence;\n }\n}\n\n/**\n * Folds one entry restored from the session's journal.\n *\n * A session outlives every hub that watches it, so \"has this ever been\n * prompted\" cannot be answered from what this process happened to see: a hub\n * that restarts, or meets a session first driven from the TUI, saw nothing. A\n * journalled user message is the proof, and without it a session carrying an\n * hour of work introduces itself in the rail as fresh.\n */\nexport function presenceAfterRestoredEntry(\n presence: SessionPresence,\n entry: Record<string, unknown>,\n now: string,\n): SessionPresence {\n if (presence.everPrompted || entry.type !== 'message') return presence;\n const message = entry.message;\n if (typeof message !== 'object' || message === null) return presence;\n if ((message as { role?: unknown }).role !== 'user') return presence;\n return { ...presence, everPrompted: true, updatedAt: now };\n}\n\n/**\n * Folds one page command heading to the agent.\n *\n * The prompt direction carries facts the event stream does not repeat: that\n * the session has ever been prompted, and that a pending dialog was answered.\n */\nexport function presenceAfterCommand(presence: SessionPresence, frame: SessionFrame, now: string): SessionPresence {\n if (frame.type === 'prompt' || frame.type === 'steer' || frame.type === 'follow_up') {\n if (presence.everPrompted) return presence;\n return { ...presence, everPrompted: true, updatedAt: now };\n }\n if (frame.type === 'extension_ui_response') {\n if (!presence.awaitingInput) return presence;\n return { ...presence, awaitingInput: false, updatedAt: now };\n }\n return presence;\n}\n"],"mappings":"AAGA,MAAM,EAAiB,CAAC,SAAU,UAAW,QAAS,QAAQ,EAwB9D,SAAgB,EAAgB,EAA8B,CAC5D,MAAO,CACL,MAAO,OACP,WAAY,EACZ,UAAW,EACX,oBAAqB,EACrB,aAAc,GACd,cAAe,EACjB,CACF,CAEA,SAAS,EAAS,EAAkD,CAClE,OAAO,OAAO,GAAU,YAAY,GAAkB,CAAC,MAAM,QAAQ,CAAK,CAC5E,CAEA,SAAS,EAAU,EAA2B,EAAqB,EAA8B,CAE/F,OADI,EAAS,QAAU,EAAc,EAC9B,CAAE,GAAG,EAAU,QAAO,WAAY,EAAK,UAAW,CAAI,CAC/D,CAEA,SAAS,EAAW,EAA2B,EAA+B,EAA8B,CAC1G,IAAI,EAAO,EACL,EAAsB,EAAK,eAAiB,GAAO,aAAe,EAAK,cAAgB,GAAO,OAAS,OAC7G,EAAO,EAAU,EAAM,EAAO,CAAG,EACjC,IAAM,EAAU,OAAO,EAAK,qBAAwB,SAAW,EAAK,oBAAsB,EAAK,oBAC3F,IAAY,EAAK,sBAAqB,EAAO,CAAE,GAAG,EAAM,oBAAqB,EAAS,UAAW,CAAI,GACzG,IAAM,EAAO,OAAO,EAAK,aAAgB,UAAY,EAAK,cAAgB,GAAK,EAAK,YAAc,IAAA,GAElG,OADI,IAAS,IAAA,IAAa,IAAS,EAAK,cAAa,EAAO,CAAE,GAAG,EAAM,YAAa,EAAM,UAAW,CAAI,GAClG,CACT,CAOA,SAAgB,EAAe,EAA2B,EAAqB,EAA8B,CAC3G,OAAQ,EAAM,KAAd,CACE,IAAK,SACH,OAAO,EAAS,EAAM,KAAK,EAAI,EAAe,EAAU,EAAM,MAAO,CAAG,EAAI,EAE9E,IAAK,cAAe,CAClB,IAAM,EAAU,EAAU,EAAU,OAAQ,CAAG,EAE/C,OADK,EAAQ,cACN,CAAE,GAAG,EAAS,cAAe,GAAO,UAAW,CAAI,EADvB,CAErC,CAEA,IAAK,gBAEH,MAAO,CAAE,GADO,EAAU,EAAU,OAAQ,CAC1B,EAAG,cAAe,GAAO,cAAe,EAAK,UAAW,CAAI,EAGhF,IAAK,uBAGH,OAFI,OAAO,EAAM,QAAW,UAAY,CAAC,EAAe,SAAS,EAAM,MAAM,GACzE,EAAS,cAAsB,EAC5B,CAAE,GAAG,EAAU,cAAe,GAAM,UAAW,CAAI,EAG5D,IAAK,WACH,OAAO,EAAM,UAAY,aAAe,EAAS,EAAM,IAAI,EAAI,EAAW,EAAU,EAAM,KAAM,CAAG,EAAI,EAEzG,QACE,OAAO,CACX,CACF,CAWA,SAAgB,EACd,EACA,EACA,EACiB,CACjB,GAAI,EAAS,cAAgB,EAAM,OAAS,UAAW,OAAO,EAC9D,IAAM,EAAU,EAAM,QAGtB,OAFI,OAAO,GAAY,WAAY,GAC9B,EAA+B,OAAS,OAAe,EACrD,CAAE,GAAG,EAAU,aAAc,GAAM,UAAW,CAAI,CAC3D,CAQA,SAAgB,EAAqB,EAA2B,EAAqB,EAA8B,CASjH,OARI,EAAM,OAAS,UAAY,EAAM,OAAS,SAAW,EAAM,OAAS,YAClE,EAAS,aAAqB,EAC3B,CAAE,GAAG,EAAU,aAAc,GAAM,UAAW,CAAI,EAEvD,EAAM,OAAS,yBACZ,EAAS,cACP,CAAE,GAAG,EAAU,cAAe,GAAO,UAAW,CAAI,EADvB,CAIxC"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=[`select`,`confirm`,`input`,`editor`];function t(e){return{phase:`idle`,phaseSince:e,updatedAt:e,pendingMessageCount:0,everPrompted:!1,awaitingInput:!1}}function n(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function r(e,t,n){return e.phase===t?e:{...e,phase:t,phaseSince:n,updatedAt:n}}function i(e,t,n){let i=e,a=t.isCompacting===!0?`compaction`:t.isStreaming===!0?`turn`:`idle`;i=r(i,a,n);let o=typeof t.pendingMessageCount==`number`?t.pendingMessageCount:i.pendingMessageCount;o!==i.pendingMessageCount&&(i={...i,pendingMessageCount:o,updatedAt:n});let s=typeof t.sessionName==`string`&&t.sessionName!==``?t.sessionName:void 0;return s!==void 0&&s!==i.sessionName&&(i={...i,sessionName:s,updatedAt:n}),i}function a(t,o,s){switch(o.type){case`replay`:return n(o.frame)?a(t,o.frame,s):t;case`agent_start`:{let e=r(t,`turn`,s);return e.awaitingInput?{...e,awaitingInput:!1,updatedAt:s}:e}case`agent_settled`:return{...r(t,`idle`,s),awaitingInput:!1,lastSettledAt:s,updatedAt:s};case`extension_ui_request`:return typeof o.method!=`string`||!e.includes(o.method)||t.awaitingInput?t:{...t,awaitingInput:!0,updatedAt:s};case`response`:return o.command===`get_state`&&n(o.data)?i(t,o.data,s):t;default:return t}}function o(e,t,n){if(e.everPrompted||t.type!==`message`)return e;let r=t.message;return typeof r!=`object`||!r||r.role!==`user`?e:{...e,everPrompted:!0,updatedAt:n}}function s(e,t,n){return t.type===`prompt`||t.type===`steer`||t.type===`follow_up`?e.everPrompted?e:{...e,everPrompted:!0,updatedAt:n}:t.type===`extension_ui_response`&&e.awaitingInput?{...e,awaitingInput:!1,updatedAt:n}:e}export{t as initialPresence,s as presenceAfterCommand,o as presenceAfterRestoredEntry,a as reducePresence};
|
|
2
|
+
//# sourceMappingURL=sessionPresence.mjs.map
|