@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
package/src/types/hub.ts
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import type { BridgeState, SessionFrame } from './session.ts';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Wire vocabulary between the hub and its pages.
|
|
5
|
+
*
|
|
6
|
+
* One WebSocket carries every session: hub-level frames describe the set of
|
|
7
|
+
* sessions, and session-scoped traffic travels enveloped with the session id.
|
|
8
|
+
* Field naming follows @earendil-works/pi-protocol so a future swap to the
|
|
9
|
+
* upstream session server stays a transport change, not a rename.
|
|
10
|
+
*/
|
|
11
|
+
export const HUB_PROTOCOL_VERSION = 1;
|
|
12
|
+
|
|
13
|
+
/** Role marker in the health payload; doompi-server probes for it before binding the port. */
|
|
14
|
+
export const HUB_ROLE = 'hub';
|
|
15
|
+
|
|
16
|
+
/** REST endpoint for creating sessions; the page posts {cwd, name?} and receives {sessionId}. */
|
|
17
|
+
export const SESSIONS_API_ROUTE = '/api/sessions';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* REST endpoint behind the new-session directory picker; the page sends the
|
|
21
|
+
* typed path as ?q= and receives {directories}: the children of the parent
|
|
22
|
+
* directory whose names match the trailing segment as a regular expression.
|
|
23
|
+
*/
|
|
24
|
+
export const DIRECTORIES_API_ROUTE = '/api/directories';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Query parameter that routes a package API request to one session's server.
|
|
28
|
+
* Without it the request is answered by a hub-scoped API in this process.
|
|
29
|
+
*/
|
|
30
|
+
export const API_SESSION_QUERY_PARAM = 'session';
|
|
31
|
+
|
|
32
|
+
/** What the agent is doing right now, derived from its frame stream. */
|
|
33
|
+
export type SessionPhase = 'idle' | 'turn' | 'compaction' | 'retry';
|
|
34
|
+
|
|
35
|
+
export interface SessionGitStatus {
|
|
36
|
+
branch: string;
|
|
37
|
+
/** True when the working tree has uncommitted changes; rendered as a star. */
|
|
38
|
+
dirty: boolean;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Everything the rail needs to render one session without subscribing to it.
|
|
43
|
+
*/
|
|
44
|
+
export interface SessionSummary {
|
|
45
|
+
id: string;
|
|
46
|
+
name: string;
|
|
47
|
+
cwd: string;
|
|
48
|
+
/** ISO 8601, from the session's registry record. */
|
|
49
|
+
createdAt: string;
|
|
50
|
+
/** ISO 8601, bumped whenever the session's frame stream moves. */
|
|
51
|
+
updatedAt: string;
|
|
52
|
+
phase: SessionPhase;
|
|
53
|
+
/** ISO 8601 timestamp of the last phase change; drives "running · 12m". */
|
|
54
|
+
phaseSince: string;
|
|
55
|
+
/** The hub's own attachment to the session socket. */
|
|
56
|
+
attach: BridgeState;
|
|
57
|
+
/** Human-readable cause, present when attach is refused or closed. */
|
|
58
|
+
attachReason?: string;
|
|
59
|
+
pendingMessageCount: number;
|
|
60
|
+
/** False until the first prompt is forwarded; "fresh session · nothing sent yet". */
|
|
61
|
+
everPrompted: boolean;
|
|
62
|
+
/** True while the agent waits on a dialog answer; "waiting for your input". */
|
|
63
|
+
awaitingInput: boolean;
|
|
64
|
+
/** ISO 8601 of the last settled run, once one finished. */
|
|
65
|
+
lastSettledAt?: string;
|
|
66
|
+
/** Shown in the refused overlay so the user can find the competing client. */
|
|
67
|
+
socketPath: string;
|
|
68
|
+
/** Where this session serves its package APIs, when it serves any; the hub proxies there. */
|
|
69
|
+
apiSocketPath?: string;
|
|
70
|
+
/** Omitted when the cwd is not a git repository or git is unavailable. */
|
|
71
|
+
git?: SessionGitStatus;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Per-plugin session data travels as ChannelFrame: the channel name is the
|
|
76
|
+
* frame type and the payload shape belongs to the plugin. Re-exported so in-package code keeps importing wire shapes
|
|
77
|
+
* from this one root.
|
|
78
|
+
*/
|
|
79
|
+
export type { ChannelFrame } from '@agimon-ai/doompi-web-contracts';
|
|
80
|
+
|
|
81
|
+
export const HUB_HELLO_TYPE = 'hub_hello';
|
|
82
|
+
export const SESSIONS_SNAPSHOT_TYPE = 'sessions_snapshot';
|
|
83
|
+
export const SESSION_UPSERT_TYPE = 'session_upsert';
|
|
84
|
+
export const SESSION_REMOVED_TYPE = 'session_removed';
|
|
85
|
+
export const SESSION_BACKLOG_TYPE = 'session_backlog';
|
|
86
|
+
export const SESSION_FRAME_TYPE = 'session_frame';
|
|
87
|
+
/** Hub-synthesized close for an answered dialog; Pi itself never announces one. */
|
|
88
|
+
export const DIALOG_ANSWERED_TYPE = 'extension_ui_answered';
|
|
89
|
+
/**
|
|
90
|
+
* Sync rebuilt the artifacts this page is running against.
|
|
91
|
+
*
|
|
92
|
+
* The plugin bundle the browser loaded is one of them, and a bundle only
|
|
93
|
+
* changes on disk: nothing about the running page notices. The page reloads
|
|
94
|
+
* itself rather than staying on surfaces the repository no longer describes.
|
|
95
|
+
*/
|
|
96
|
+
export const HUB_RESYNCED_TYPE = 'hub_resynced';
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* The custom session entry the DoomPi runtime journals with its minor-mode
|
|
100
|
+
* catalog projection; it arrives inside Pi's entry_appended frames. The shape
|
|
101
|
+
* mirrors MinorModeProjection in doompi-extension-contracts.
|
|
102
|
+
*/
|
|
103
|
+
export const MINOR_MODE_ENTRY_TYPE = 'doom-minor-modes';
|
|
104
|
+
|
|
105
|
+
export interface MinorModeActionProjection {
|
|
106
|
+
id: string;
|
|
107
|
+
label: string;
|
|
108
|
+
description: string;
|
|
109
|
+
enabled: boolean;
|
|
110
|
+
disabledReason?: string;
|
|
111
|
+
needsInput: boolean;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export interface MinorModeRecordProjection {
|
|
115
|
+
id: string;
|
|
116
|
+
label: string;
|
|
117
|
+
description: string;
|
|
118
|
+
order: number;
|
|
119
|
+
activation: 'inactive' | 'activating' | 'active' | 'deactivating';
|
|
120
|
+
condition: string;
|
|
121
|
+
detail?: string;
|
|
122
|
+
actions: MinorModeActionProjection[];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export interface MinorModeProjection {
|
|
126
|
+
version: 1;
|
|
127
|
+
revision: number;
|
|
128
|
+
modes: MinorModeRecordProjection[];
|
|
129
|
+
}
|
|
130
|
+
export const SUBSCRIBE_TYPE = 'subscribe';
|
|
131
|
+
export const UNSUBSCRIBE_TYPE = 'unsubscribe';
|
|
132
|
+
export const SESSION_COMMAND_TYPE = 'session_command';
|
|
133
|
+
/** A page follows one thread of a session: a journal a plugin's hub source names, such as a subagent run. */
|
|
134
|
+
export const SUBSCRIBE_THREAD_TYPE = 'subscribe_thread';
|
|
135
|
+
export const UNSUBSCRIBE_THREAD_TYPE = 'unsubscribe_thread';
|
|
136
|
+
export const THREAD_BACKLOG_TYPE = 'thread_backlog';
|
|
137
|
+
export const THREAD_FRAME_TYPE = 'thread_frame';
|
|
138
|
+
|
|
139
|
+
/** A page asks for the transcript before what it already holds. */
|
|
140
|
+
export const HISTORY_REQUEST_TYPE = 'history_request';
|
|
141
|
+
/** One older window, oldest first, answering a history_request. */
|
|
142
|
+
export const HISTORY_PAGE_TYPE = 'history_page';
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* How many journalled messages one page carries. Small enough that prepending
|
|
146
|
+
* a window never blocks the reader's scroll, large enough that a flick through
|
|
147
|
+
* a long transcript does not turn into a hundred round trips.
|
|
148
|
+
*/
|
|
149
|
+
export const HISTORY_PAGE_SIZE = 100;
|
|
150
|
+
|
|
151
|
+
export interface HistoryRequestFrame {
|
|
152
|
+
type: typeof HISTORY_REQUEST_TYPE;
|
|
153
|
+
sessionId: string;
|
|
154
|
+
/** Journal id of the oldest entry the page holds; absent asks for the newest window. */
|
|
155
|
+
before?: string;
|
|
156
|
+
limit?: number;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export interface HistoryPageFrame {
|
|
160
|
+
type: typeof HISTORY_PAGE_TYPE;
|
|
161
|
+
sessionId: string;
|
|
162
|
+
/** Entry frames, oldest first, to prepend above what the page holds. */
|
|
163
|
+
frames: Record<string, unknown>[];
|
|
164
|
+
/** The cursor for the next older window; null when the transcript starts here. */
|
|
165
|
+
cursor: string | null;
|
|
166
|
+
/** Whether anything older than this window exists. */
|
|
167
|
+
hasMore: boolean;
|
|
168
|
+
/** Echoed so a page can drop an answer to a question it has since abandoned. */
|
|
169
|
+
before?: string;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** First frame on every page socket, before the snapshot. */
|
|
173
|
+
export interface HubHelloFrame {
|
|
174
|
+
type: typeof HUB_HELLO_TYPE;
|
|
175
|
+
protocol: number;
|
|
176
|
+
/** Frame types of the hub's loaded data channels; lets a panel tell "plugin server not installed" from "no data". */
|
|
177
|
+
channels: string[];
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export interface SessionsSnapshotFrame {
|
|
181
|
+
type: typeof SESSIONS_SNAPSHOT_TYPE;
|
|
182
|
+
sessions: SessionSummary[];
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export interface SessionUpsertFrame {
|
|
186
|
+
type: typeof SESSION_UPSERT_TYPE;
|
|
187
|
+
session: SessionSummary;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export interface SessionRemovedFrame {
|
|
191
|
+
type: typeof SESSION_REMOVED_TYPE;
|
|
192
|
+
sessionId: string;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** Reply to subscribe: recent history from the hub's ring, then live frames follow. */
|
|
196
|
+
export interface SessionBacklogFrame {
|
|
197
|
+
type: typeof SESSION_BACKLOG_TYPE;
|
|
198
|
+
sessionId: string;
|
|
199
|
+
frames: SessionFrame[];
|
|
200
|
+
/** Frames the bounded ring had to discard before this page subscribed. */
|
|
201
|
+
dropped: number;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** One live agent frame, addressed to subscribers of its session. */
|
|
205
|
+
export interface SessionFrameEnvelope {
|
|
206
|
+
type: typeof SESSION_FRAME_TYPE;
|
|
207
|
+
sessionId: string;
|
|
208
|
+
frame: SessionFrame;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export interface SubscribeFrame {
|
|
212
|
+
type: typeof SUBSCRIBE_TYPE;
|
|
213
|
+
sessionId: string;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export interface UnsubscribeFrame {
|
|
217
|
+
type: typeof UNSUBSCRIBE_TYPE;
|
|
218
|
+
sessionId: string;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** One command frame from the page, addressed to a session's agent. */
|
|
222
|
+
export interface SessionCommandFrame {
|
|
223
|
+
type: typeof SESSION_COMMAND_TYPE;
|
|
224
|
+
sessionId: string;
|
|
225
|
+
frame: SessionFrame;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export function hubHello(channels: string[]): HubHelloFrame {
|
|
229
|
+
return { type: HUB_HELLO_TYPE, protocol: HUB_PROTOCOL_VERSION, channels };
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export function sessionFrameEnvelope(sessionId: string, frame: SessionFrame): SessionFrameEnvelope {
|
|
233
|
+
return { type: SESSION_FRAME_TYPE, sessionId, frame };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export function sessionCommand(sessionId: string, frame: SessionFrame): SessionCommandFrame {
|
|
237
|
+
return { type: SESSION_COMMAND_TYPE, sessionId, frame };
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export function subscribeFrame(sessionId: string): SubscribeFrame {
|
|
241
|
+
return { type: SUBSCRIBE_TYPE, sessionId };
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export function unsubscribeFrame(sessionId: string): UnsubscribeFrame {
|
|
245
|
+
return { type: UNSUBSCRIBE_TYPE, sessionId };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export interface SubscribeThreadFrame {
|
|
249
|
+
type: typeof SUBSCRIBE_THREAD_TYPE;
|
|
250
|
+
sessionId: string;
|
|
251
|
+
threadId: string;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export interface UnsubscribeThreadFrame {
|
|
255
|
+
type: typeof UNSUBSCRIBE_THREAD_TYPE;
|
|
256
|
+
sessionId: string;
|
|
257
|
+
threadId: string;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/** Reply to subscribe_thread: the journal's newest entries as entry_appended frames, then live ones follow. */
|
|
261
|
+
export interface ThreadBacklogFrame {
|
|
262
|
+
type: typeof THREAD_BACKLOG_TYPE;
|
|
263
|
+
sessionId: string;
|
|
264
|
+
threadId: string;
|
|
265
|
+
frames: SessionFrame[];
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/** One journal entry a followed thread gained, addressed to the pages following it. */
|
|
269
|
+
export interface ThreadFrameEnvelope {
|
|
270
|
+
type: typeof THREAD_FRAME_TYPE;
|
|
271
|
+
sessionId: string;
|
|
272
|
+
threadId: string;
|
|
273
|
+
frame: SessionFrame;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export function subscribeThreadFrame(sessionId: string, threadId: string): SubscribeThreadFrame {
|
|
277
|
+
return { type: SUBSCRIBE_THREAD_TYPE, sessionId, threadId };
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export function unsubscribeThreadFrame(sessionId: string, threadId: string): UnsubscribeThreadFrame {
|
|
281
|
+
return { type: UNSUBSCRIBE_THREAD_TYPE, sessionId, threadId };
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export function threadBacklog(sessionId: string, threadId: string, frames: SessionFrame[]): ThreadBacklogFrame {
|
|
285
|
+
return { type: THREAD_BACKLOG_TYPE, sessionId, threadId, frames };
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export function threadFrameEnvelope(sessionId: string, threadId: string, frame: SessionFrame): ThreadFrameEnvelope {
|
|
289
|
+
return { type: THREAD_FRAME_TYPE, sessionId, threadId, frame };
|
|
290
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How the cockpit previews a file a message mentions: the hub serves it from
|
|
3
|
+
* the session's working directory and the timeline picks the element by
|
|
4
|
+
* kind. Anything not listed here is offered as a plain file link.
|
|
5
|
+
*/
|
|
6
|
+
export type MediaKind = 'image' | 'video' | 'pdf';
|
|
7
|
+
|
|
8
|
+
export interface MediaType {
|
|
9
|
+
kind: MediaKind;
|
|
10
|
+
contentType: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Keyed by lower-case extension without the dot. */
|
|
14
|
+
export const MEDIA_TYPES: Readonly<Record<string, MediaType>> = {
|
|
15
|
+
avif: { kind: 'image', contentType: 'image/avif' },
|
|
16
|
+
bmp: { kind: 'image', contentType: 'image/bmp' },
|
|
17
|
+
gif: { kind: 'image', contentType: 'image/gif' },
|
|
18
|
+
jpeg: { kind: 'image', contentType: 'image/jpeg' },
|
|
19
|
+
jpg: { kind: 'image', contentType: 'image/jpeg' },
|
|
20
|
+
png: { kind: 'image', contentType: 'image/png' },
|
|
21
|
+
svg: { kind: 'image', contentType: 'image/svg+xml' },
|
|
22
|
+
webp: { kind: 'image', contentType: 'image/webp' },
|
|
23
|
+
m4v: { kind: 'video', contentType: 'video/mp4' },
|
|
24
|
+
mov: { kind: 'video', contentType: 'video/quicktime' },
|
|
25
|
+
mp4: { kind: 'video', contentType: 'video/mp4' },
|
|
26
|
+
webm: { kind: 'video', contentType: 'video/webm' },
|
|
27
|
+
pdf: { kind: 'pdf', contentType: 'application/pdf' },
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/** The hub refuses to serve a mentioned file past this size. */
|
|
31
|
+
export const MAX_SESSION_FILE_BYTES = 25 * 1024 * 1024;
|
|
32
|
+
|
|
33
|
+
/** The route the timeline fetches a mentioned file from; path is the cwd-relative file. */
|
|
34
|
+
export const SESSION_FILE_ROUTE = '/api/sessions/:sessionId/file';
|
|
35
|
+
|
|
36
|
+
export function sessionFileUrl(sessionId: string, relativePath: string): string {
|
|
37
|
+
return `/api/sessions/${encodeURIComponent(sessionId)}/file?path=${encodeURIComponent(relativePath)}`;
|
|
38
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** One newline-delimited JSON frame, kept opaque so Pi can evolve its protocol. */
|
|
2
|
+
export type SessionFrame = Record<string, unknown>;
|
|
3
|
+
|
|
4
|
+
/** Handshake vocabulary published by @agimon-ai/doompi-server. */
|
|
5
|
+
export const ATTACH_TYPE = 'attach';
|
|
6
|
+
export const ATTACHED_TYPE = 'attached';
|
|
7
|
+
export const ATTACH_ERROR_TYPE = 'attach_error';
|
|
8
|
+
export const REPLAY_TYPE = 'replay';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* What the browser is told about the socket behind it.
|
|
12
|
+
*
|
|
13
|
+
* The token never reaches the browser, so the page cannot infer attachment from
|
|
14
|
+
* the handshake itself; the bridge reports it instead.
|
|
15
|
+
*/
|
|
16
|
+
export type BridgeState = 'connecting' | 'attached' | 'refused' | 'detached' | 'closed';
|
|
17
|
+
|
|
18
|
+
export const BRIDGE_STATUS_TYPE = 'bridge_status';
|
|
19
|
+
|
|
20
|
+
export interface BridgeStatusFrame {
|
|
21
|
+
type: typeof BRIDGE_STATUS_TYPE;
|
|
22
|
+
state: BridgeState;
|
|
23
|
+
/** Human-readable cause, present when the state is refused or closed. */
|
|
24
|
+
reason?: string;
|
|
25
|
+
/** Frames the session buffered while nothing was attached. */
|
|
26
|
+
replayed?: number;
|
|
27
|
+
/** Frames the bounded backlog had to discard. */
|
|
28
|
+
dropped?: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function bridgeStatus(
|
|
32
|
+
state: BridgeState,
|
|
33
|
+
extra: Omit<BridgeStatusFrame, 'type' | 'state'> = {},
|
|
34
|
+
): BridgeStatusFrame {
|
|
35
|
+
return { type: BRIDGE_STATUS_TYPE, state, ...extra };
|
|
36
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { TooltipProvider } from '@agimon-ai/doompi-web-components';
|
|
2
|
+
import { createRouter, RouterProvider } from '@tanstack/react-router';
|
|
3
|
+
import { useEffect } from 'react';
|
|
4
|
+
import { ThreadView } from '../features/session/ThreadView.tsx';
|
|
5
|
+
import { installWebPlugins, startWebPlugins, webPluginDiagnostics } from '../lib/pluginRegistry.ts';
|
|
6
|
+
import { bindThreadRenderer } from '../lib/threadRenderer.ts';
|
|
7
|
+
import { sendFrame, sendHubFrame } from '../lib/transport.ts';
|
|
8
|
+
import { routeTree } from '../routes/routeTree.tsx';
|
|
9
|
+
import { startSessionRuntime } from './sessionRuntime.ts';
|
|
10
|
+
import { webPlugins } from './webPlugins.generated.ts';
|
|
11
|
+
|
|
12
|
+
// Module scope: the registry is complete before the first render reads it.
|
|
13
|
+
// A collision between two installed plugins never blanks the page; it is
|
|
14
|
+
// resolved at install and reported here, once.
|
|
15
|
+
installWebPlugins(webPlugins);
|
|
16
|
+
for (const diagnostic of webPluginDiagnostics()) {
|
|
17
|
+
console.warn(`web plugin '${diagnostic.pluginId}' ${diagnostic.kind}: ${diagnostic.message}`);
|
|
18
|
+
}
|
|
19
|
+
// The thread view a plugin panel renders through its props; bound here, where
|
|
20
|
+
// the feature and the props builder can both be seen.
|
|
21
|
+
bindThreadRenderer((sessionId, threadId) => <ThreadView sessionId={sessionId} threadId={threadId} />);
|
|
22
|
+
|
|
23
|
+
const router = createRouter({ routeTree });
|
|
24
|
+
|
|
25
|
+
declare module '@tanstack/react-router' {
|
|
26
|
+
interface Register {
|
|
27
|
+
router: typeof router;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function Providers() {
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
const stopRuntime = startSessionRuntime();
|
|
34
|
+
const stopPlugins = startWebPlugins({ sendSessionFrame: sendFrame, sendHubFrame });
|
|
35
|
+
return () => {
|
|
36
|
+
stopPlugins();
|
|
37
|
+
stopRuntime();
|
|
38
|
+
};
|
|
39
|
+
}, []);
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<TooltipProvider>
|
|
43
|
+
<RouterProvider router={router} />
|
|
44
|
+
</TooltipProvider>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { PiClient } from '@earendil-works/pi-client';
|
|
2
|
+
import { RemoteSession } from '@earendil-works/pi-coding-agent/client';
|
|
3
|
+
import { createProtocolTransport, protocolSocketUrl } from '../lib/piTransport.ts';
|
|
4
|
+
import { toTimelineEntries } from '../lib/protocolTimeline.ts';
|
|
5
|
+
import { applyProtocolTranscript } from '../stores/sessionStore.ts';
|
|
6
|
+
|
|
7
|
+
/** How long to wait before dialling again after the protocol socket drops. */
|
|
8
|
+
const RECONNECT_MS = 700;
|
|
9
|
+
|
|
10
|
+
export interface ProtocolRuntime {
|
|
11
|
+
/** Points the runtime at the session the page is showing, or at none. */
|
|
12
|
+
focus(sessionId: string | null): void;
|
|
13
|
+
stop(): void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Runs Pi's own client for whichever session the page is showing.
|
|
18
|
+
*
|
|
19
|
+
* The transcript is the protocol's to state and the cockpit's only to draw, so
|
|
20
|
+
* this holds one session open and republishes what the server says. Everything
|
|
21
|
+
* DoomPi owns, dialogs and modes and plugin channels, stays on the hub socket
|
|
22
|
+
* beside it, because the protocol has no shape for any of it.
|
|
23
|
+
*/
|
|
24
|
+
export function startProtocolRuntime(location: Location = window.location): ProtocolRuntime {
|
|
25
|
+
const client = new PiClient({ transportFactory: createProtocolTransport(protocolSocketUrl(location)) });
|
|
26
|
+
let session: RemoteSession | undefined;
|
|
27
|
+
let unsubscribe: (() => void) | undefined;
|
|
28
|
+
let focused: string | null = null;
|
|
29
|
+
let retry: ReturnType<typeof setTimeout> | undefined;
|
|
30
|
+
let stopped = false;
|
|
31
|
+
/** Guards against a slow open landing after the page moved on. */
|
|
32
|
+
let generation = 0;
|
|
33
|
+
|
|
34
|
+
const release = async (): Promise<void> => {
|
|
35
|
+
unsubscribe?.();
|
|
36
|
+
unsubscribe = undefined;
|
|
37
|
+
const previous = session;
|
|
38
|
+
session = undefined;
|
|
39
|
+
await previous?.dispose().catch(() => undefined);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const publish = (sessionId: string, remote: RemoteSession): void => {
|
|
43
|
+
const state = remote.state;
|
|
44
|
+
if (!state.snapshot) return;
|
|
45
|
+
applyProtocolTranscript(sessionId, toTimelineEntries(state.transcript), state.snapshot.phase !== 'idle');
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const open = async (sessionId: string): Promise<void> => {
|
|
49
|
+
const mine = ++generation;
|
|
50
|
+
await release();
|
|
51
|
+
try {
|
|
52
|
+
const remote = await RemoteSession.open(client, sessionId);
|
|
53
|
+
if (stopped || mine !== generation) {
|
|
54
|
+
await remote.dispose().catch(() => undefined);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
session = remote;
|
|
58
|
+
unsubscribe = remote.subscribe(() => publish(sessionId, remote));
|
|
59
|
+
publish(sessionId, remote);
|
|
60
|
+
} catch {
|
|
61
|
+
// A session the hub has not caught up with yet is normal right after a
|
|
62
|
+
// page creates one; the next focus or reconnect tries again.
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const connect = async (): Promise<void> => {
|
|
67
|
+
if (stopped) return;
|
|
68
|
+
try {
|
|
69
|
+
await client.connect();
|
|
70
|
+
if (focused !== null) await open(focused);
|
|
71
|
+
} catch {
|
|
72
|
+
if (!stopped) retry = setTimeout(() => void connect(), RECONNECT_MS);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
client.onConnectionStateChange((change) => {
|
|
77
|
+
if (stopped || change.state !== 'disconnected') return;
|
|
78
|
+
retry = setTimeout(() => void reconnect(), RECONNECT_MS);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const reconnect = async (): Promise<void> => {
|
|
82
|
+
if (stopped) return;
|
|
83
|
+
try {
|
|
84
|
+
await client.reconnect();
|
|
85
|
+
if (focused !== null) await open(focused);
|
|
86
|
+
} catch {
|
|
87
|
+
if (!stopped) retry = setTimeout(() => void reconnect(), RECONNECT_MS);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
void connect();
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
focus(sessionId) {
|
|
95
|
+
if (sessionId === focused) return;
|
|
96
|
+
focused = sessionId;
|
|
97
|
+
if (sessionId === null) void release();
|
|
98
|
+
else void open(sessionId);
|
|
99
|
+
},
|
|
100
|
+
stop() {
|
|
101
|
+
stopped = true;
|
|
102
|
+
if (retry) clearTimeout(retry);
|
|
103
|
+
void release().then(() => client.dispose().catch(() => undefined));
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
}
|