@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.
Files changed (344) hide show
  1. package/LICENSE +29 -0
  2. package/README.md +196 -0
  3. package/dist/_virtual/_rolldown/runtime.cjs +1 -0
  4. package/dist/bin/serve.cjs +3 -0
  5. package/dist/bin/serve.cjs.map +1 -0
  6. package/dist/bin/serve.d.cts +1 -0
  7. package/dist/bin/serve.d.mts +1 -0
  8. package/dist/bin/serve.mjs +3 -0
  9. package/dist/bin/serve.mjs.map +1 -0
  10. package/dist/bundler.cjs +1 -0
  11. package/dist/bundler.d.cts +2 -0
  12. package/dist/bundler.d.mts +2 -0
  13. package/dist/bundler.mjs +1 -0
  14. package/dist/index.cjs +1 -0
  15. package/dist/index.d.cts +7 -0
  16. package/dist/index.d.mts +7 -0
  17. package/dist/index.mjs +1 -0
  18. package/dist/src/adapters/authRoutes.cjs +2 -0
  19. package/dist/src/adapters/authRoutes.cjs.map +1 -0
  20. package/dist/src/adapters/authRoutes.mjs +2 -0
  21. package/dist/src/adapters/authRoutes.mjs.map +1 -0
  22. package/dist/src/adapters/bundledServer.cjs +2 -0
  23. package/dist/src/adapters/bundledServer.cjs.map +1 -0
  24. package/dist/src/adapters/bundledServer.mjs +2 -0
  25. package/dist/src/adapters/bundledServer.mjs.map +1 -0
  26. package/dist/src/adapters/directoryListing.cjs +2 -0
  27. package/dist/src/adapters/directoryListing.cjs.map +1 -0
  28. package/dist/src/adapters/directoryListing.mjs +2 -0
  29. package/dist/src/adapters/directoryListing.mjs.map +1 -0
  30. package/dist/src/adapters/directorySearch.cjs +3 -0
  31. package/dist/src/adapters/directorySearch.cjs.map +1 -0
  32. package/dist/src/adapters/directorySearch.mjs +3 -0
  33. package/dist/src/adapters/directorySearch.mjs.map +1 -0
  34. package/dist/src/adapters/gitStatus.cjs +2 -0
  35. package/dist/src/adapters/gitStatus.cjs.map +1 -0
  36. package/dist/src/adapters/gitStatus.mjs +2 -0
  37. package/dist/src/adapters/gitStatus.mjs.map +1 -0
  38. package/dist/src/adapters/httpServer.cjs +2 -0
  39. package/dist/src/adapters/httpServer.cjs.map +1 -0
  40. package/dist/src/adapters/httpServer.d.cts +14 -0
  41. package/dist/src/adapters/httpServer.d.cts.map +1 -0
  42. package/dist/src/adapters/httpServer.d.mts +14 -0
  43. package/dist/src/adapters/httpServer.d.mts.map +1 -0
  44. package/dist/src/adapters/httpServer.mjs +2 -0
  45. package/dist/src/adapters/httpServer.mjs.map +1 -0
  46. package/dist/src/adapters/hubProbe.cjs +2 -0
  47. package/dist/src/adapters/hubProbe.cjs.map +1 -0
  48. package/dist/src/adapters/hubProbe.mjs +2 -0
  49. package/dist/src/adapters/hubProbe.mjs.map +1 -0
  50. package/dist/src/adapters/packageApiProxy.cjs +2 -0
  51. package/dist/src/adapters/packageApiProxy.cjs.map +1 -0
  52. package/dist/src/adapters/packageApiProxy.mjs +2 -0
  53. package/dist/src/adapters/packageApiProxy.mjs.map +1 -0
  54. package/dist/src/adapters/piHubService.cjs +2 -0
  55. package/dist/src/adapters/piHubService.cjs.map +1 -0
  56. package/dist/src/adapters/piHubService.mjs +2 -0
  57. package/dist/src/adapters/piHubService.mjs.map +1 -0
  58. package/dist/src/adapters/piWebSocketListener.cjs +2 -0
  59. package/dist/src/adapters/piWebSocketListener.cjs.map +1 -0
  60. package/dist/src/adapters/piWebSocketListener.mjs +2 -0
  61. package/dist/src/adapters/piWebSocketListener.mjs.map +1 -0
  62. package/dist/src/adapters/providerAuth.cjs +2 -0
  63. package/dist/src/adapters/providerAuth.cjs.map +1 -0
  64. package/dist/src/adapters/providerAuth.mjs +2 -0
  65. package/dist/src/adapters/providerAuth.mjs.map +1 -0
  66. package/dist/src/adapters/registryWatcher.cjs +2 -0
  67. package/dist/src/adapters/registryWatcher.cjs.map +1 -0
  68. package/dist/src/adapters/registryWatcher.mjs +2 -0
  69. package/dist/src/adapters/registryWatcher.mjs.map +1 -0
  70. package/dist/src/adapters/serverSpawner.cjs +2 -0
  71. package/dist/src/adapters/serverSpawner.cjs.map +1 -0
  72. package/dist/src/adapters/serverSpawner.mjs +2 -0
  73. package/dist/src/adapters/serverSpawner.mjs.map +1 -0
  74. package/dist/src/adapters/sessionFiles.cjs +3 -0
  75. package/dist/src/adapters/sessionFiles.cjs.map +1 -0
  76. package/dist/src/adapters/sessionFiles.mjs +3 -0
  77. package/dist/src/adapters/sessionFiles.mjs.map +1 -0
  78. package/dist/src/adapters/sessionHub.cjs +2 -0
  79. package/dist/src/adapters/sessionHub.cjs.map +1 -0
  80. package/dist/src/adapters/sessionHub.mjs +2 -0
  81. package/dist/src/adapters/sessionHub.mjs.map +1 -0
  82. package/dist/src/adapters/sessionSocketClient.cjs +2 -0
  83. package/dist/src/adapters/sessionSocketClient.cjs.map +1 -0
  84. package/dist/src/adapters/sessionSocketClient.mjs +2 -0
  85. package/dist/src/adapters/sessionSocketClient.mjs.map +1 -0
  86. package/dist/src/adapters/syncGuard.cjs +2 -0
  87. package/dist/src/adapters/syncGuard.cjs.map +1 -0
  88. package/dist/src/adapters/syncGuard.mjs +2 -0
  89. package/dist/src/adapters/syncGuard.mjs.map +1 -0
  90. package/dist/src/adapters/threadJournals.cjs +2 -0
  91. package/dist/src/adapters/threadJournals.cjs.map +1 -0
  92. package/dist/src/adapters/threadJournals.mjs +2 -0
  93. package/dist/src/adapters/threadJournals.mjs.map +1 -0
  94. package/dist/src/adapters/webBundler.cjs +2 -0
  95. package/dist/src/adapters/webBundler.cjs.map +1 -0
  96. package/dist/src/adapters/webBundler.d.cts +28 -0
  97. package/dist/src/adapters/webBundler.d.cts.map +1 -0
  98. package/dist/src/adapters/webBundler.d.mts +28 -0
  99. package/dist/src/adapters/webBundler.d.mts.map +1 -0
  100. package/dist/src/adapters/webBundler.mjs +2 -0
  101. package/dist/src/adapters/webBundler.mjs.map +1 -0
  102. package/dist/src/adapters/webHubPluginLoader.cjs +2 -0
  103. package/dist/src/adapters/webHubPluginLoader.cjs.map +1 -0
  104. package/dist/src/adapters/webHubPluginLoader.mjs +2 -0
  105. package/dist/src/adapters/webHubPluginLoader.mjs.map +1 -0
  106. package/dist/src/adapters/webHubPlugins.generated.cjs +2 -0
  107. package/dist/src/adapters/webHubPlugins.generated.cjs.map +1 -0
  108. package/dist/src/adapters/webHubPlugins.generated.mjs +2 -0
  109. package/dist/src/adapters/webHubPlugins.generated.mjs.map +1 -0
  110. package/dist/src/adapters/webPluginGenerate.cjs +4 -0
  111. package/dist/src/adapters/webPluginGenerate.cjs.map +1 -0
  112. package/dist/src/adapters/webPluginGenerate.mjs +4 -0
  113. package/dist/src/adapters/webPluginGenerate.mjs.map +1 -0
  114. package/dist/src/adapters/webPluginScan.cjs +2 -0
  115. package/dist/src/adapters/webPluginScan.cjs.map +1 -0
  116. package/dist/src/adapters/webPluginScan.mjs +2 -0
  117. package/dist/src/adapters/webPluginScan.mjs.map +1 -0
  118. package/dist/src/adapters/webPluginVite.cjs +2 -0
  119. package/dist/src/adapters/webPluginVite.cjs.map +1 -0
  120. package/dist/src/adapters/webPluginVite.mjs +2 -0
  121. package/dist/src/adapters/webPluginVite.mjs.map +1 -0
  122. package/dist/src/services/directoryMatch.cjs +2 -0
  123. package/dist/src/services/directoryMatch.cjs.map +1 -0
  124. package/dist/src/services/directoryMatch.mjs +2 -0
  125. package/dist/src/services/directoryMatch.mjs.map +1 -0
  126. package/dist/src/services/fileMatch.cjs +2 -0
  127. package/dist/src/services/fileMatch.cjs.map +1 -0
  128. package/dist/src/services/fileMatch.mjs +2 -0
  129. package/dist/src/services/fileMatch.mjs.map +1 -0
  130. package/dist/src/services/fileMedia.cjs +2 -0
  131. package/dist/src/services/fileMedia.cjs.map +1 -0
  132. package/dist/src/services/fileMedia.mjs +2 -0
  133. package/dist/src/services/fileMedia.mjs.map +1 -0
  134. package/dist/src/services/frameRing.cjs +2 -0
  135. package/dist/src/services/frameRing.cjs.map +1 -0
  136. package/dist/src/services/frameRing.mjs +2 -0
  137. package/dist/src/services/frameRing.mjs.map +1 -0
  138. package/dist/src/services/journalTail.cjs +3 -0
  139. package/dist/src/services/journalTail.cjs.map +1 -0
  140. package/dist/src/services/journalTail.mjs +3 -0
  141. package/dist/src/services/journalTail.mjs.map +1 -0
  142. package/dist/src/services/loginFlow.cjs +2 -0
  143. package/dist/src/services/loginFlow.cjs.map +1 -0
  144. package/dist/src/services/loginFlow.mjs +2 -0
  145. package/dist/src/services/loginFlow.mjs.map +1 -0
  146. package/dist/src/services/registryStore.cjs +2 -0
  147. package/dist/src/services/registryStore.cjs.map +1 -0
  148. package/dist/src/services/registryStore.mjs +2 -0
  149. package/dist/src/services/registryStore.mjs.map +1 -0
  150. package/dist/src/services/retryPolicy.cjs +2 -0
  151. package/dist/src/services/retryPolicy.cjs.map +1 -0
  152. package/dist/src/services/retryPolicy.mjs +2 -0
  153. package/dist/src/services/retryPolicy.mjs.map +1 -0
  154. package/dist/src/services/serveOptions.cjs +2 -0
  155. package/dist/src/services/serveOptions.cjs.map +1 -0
  156. package/dist/src/services/serveOptions.d.cts +21 -0
  157. package/dist/src/services/serveOptions.d.cts.map +1 -0
  158. package/dist/src/services/serveOptions.d.mts +21 -0
  159. package/dist/src/services/serveOptions.d.mts.map +1 -0
  160. package/dist/src/services/serveOptions.mjs +2 -0
  161. package/dist/src/services/serveOptions.mjs.map +1 -0
  162. package/dist/src/services/sessionFraming.cjs +4 -0
  163. package/dist/src/services/sessionFraming.cjs.map +1 -0
  164. package/dist/src/services/sessionFraming.mjs +4 -0
  165. package/dist/src/services/sessionFraming.mjs.map +1 -0
  166. package/dist/src/services/sessionPresence.cjs +2 -0
  167. package/dist/src/services/sessionPresence.cjs.map +1 -0
  168. package/dist/src/services/sessionPresence.mjs +2 -0
  169. package/dist/src/services/sessionPresence.mjs.map +1 -0
  170. package/dist/src/services/staticAssets.cjs +2 -0
  171. package/dist/src/services/staticAssets.cjs.map +1 -0
  172. package/dist/src/services/staticAssets.mjs +2 -0
  173. package/dist/src/services/staticAssets.mjs.map +1 -0
  174. package/dist/src/services/webDevRoots.cjs +2 -0
  175. package/dist/src/services/webDevRoots.cjs.map +1 -0
  176. package/dist/src/services/webDevRoots.mjs +2 -0
  177. package/dist/src/services/webDevRoots.mjs.map +1 -0
  178. package/dist/src/services/webPluginManifest.cjs +2 -0
  179. package/dist/src/services/webPluginManifest.cjs.map +1 -0
  180. package/dist/src/services/webPluginManifest.mjs +2 -0
  181. package/dist/src/services/webPluginManifest.mjs.map +1 -0
  182. package/dist/src/types/auth.cjs +2 -0
  183. package/dist/src/types/auth.cjs.map +1 -0
  184. package/dist/src/types/auth.d.cts +94 -0
  185. package/dist/src/types/auth.d.cts.map +1 -0
  186. package/dist/src/types/auth.d.mts +94 -0
  187. package/dist/src/types/auth.d.mts.map +1 -0
  188. package/dist/src/types/auth.mjs +2 -0
  189. package/dist/src/types/auth.mjs.map +1 -0
  190. package/dist/src/types/bridge.d.cts +31 -0
  191. package/dist/src/types/bridge.d.cts.map +1 -0
  192. package/dist/src/types/bridge.d.mts +31 -0
  193. package/dist/src/types/bridge.d.mts.map +1 -0
  194. package/dist/src/types/hub.cjs +2 -0
  195. package/dist/src/types/hub.cjs.map +1 -0
  196. package/dist/src/types/hub.d.cts +58 -0
  197. package/dist/src/types/hub.d.cts.map +1 -0
  198. package/dist/src/types/hub.d.mts +58 -0
  199. package/dist/src/types/hub.d.mts.map +1 -0
  200. package/dist/src/types/hub.mjs +2 -0
  201. package/dist/src/types/hub.mjs.map +1 -0
  202. package/dist/src/types/media.cjs +2 -0
  203. package/dist/src/types/media.cjs.map +1 -0
  204. package/dist/src/types/media.mjs +2 -0
  205. package/dist/src/types/media.mjs.map +1 -0
  206. package/dist/src/types/registry.cjs +1 -0
  207. package/dist/src/types/registry.d.cts +2 -0
  208. package/dist/src/types/registry.d.mts +2 -0
  209. package/dist/src/types/registry.mjs +1 -0
  210. package/dist/src/types/session.cjs +2 -0
  211. package/dist/src/types/session.cjs.map +1 -0
  212. package/dist/src/types/session.d.cts +24 -0
  213. package/dist/src/types/session.d.cts.map +1 -0
  214. package/dist/src/types/session.d.mts +24 -0
  215. package/dist/src/types/session.d.mts.map +1 -0
  216. package/dist/src/types/session.mjs +2 -0
  217. package/dist/src/types/session.mjs.map +1 -0
  218. package/dist/web/apple-touch-icon.png +0 -0
  219. package/dist/web/assets/index-Cg52ngKv.css +2 -0
  220. package/dist/web/assets/index-DQO7NTIP.js +90 -0
  221. package/dist/web/assets/index-DQO7NTIP.js.map +1 -0
  222. package/dist/web/favicon-16.png +0 -0
  223. package/dist/web/favicon-32.png +0 -0
  224. package/dist/web/favicon.ico +0 -0
  225. package/dist/web/favicon.svg +16 -0
  226. package/dist/web/icon-512.png +0 -0
  227. package/dist/web/index.html +17 -0
  228. package/llms.txt +8 -0
  229. package/package.json +106 -0
  230. package/src/adapters/authRoutes.ts +95 -0
  231. package/src/adapters/bundledServer.ts +64 -0
  232. package/src/adapters/directoryListing.ts +127 -0
  233. package/src/adapters/directorySearch.ts +124 -0
  234. package/src/adapters/gitStatus.ts +27 -0
  235. package/src/adapters/httpServer.ts +556 -0
  236. package/src/adapters/hubProbe.ts +27 -0
  237. package/src/adapters/packageApiProxy.ts +60 -0
  238. package/src/adapters/piHubService.ts +129 -0
  239. package/src/adapters/piWebSocketListener.ts +98 -0
  240. package/src/adapters/providerAuth.ts +181 -0
  241. package/src/adapters/registryWatcher.ts +112 -0
  242. package/src/adapters/serverSpawner.ts +181 -0
  243. package/src/adapters/sessionFiles.ts +99 -0
  244. package/src/adapters/sessionHub.ts +567 -0
  245. package/src/adapters/sessionSocketClient.ts +105 -0
  246. package/src/adapters/syncGuard.ts +119 -0
  247. package/src/adapters/threadJournals.ts +184 -0
  248. package/src/adapters/webBundler.ts +83 -0
  249. package/src/adapters/webHubPluginLoader.ts +67 -0
  250. package/src/adapters/webHubPlugins.generated.ts +4 -0
  251. package/src/adapters/webPluginGenerate.ts +172 -0
  252. package/src/adapters/webPluginScan.ts +62 -0
  253. package/src/adapters/webPluginVite.ts +51 -0
  254. package/src/bin/serve.ts +64 -0
  255. package/src/exports/bundler.ts +1 -0
  256. package/src/exports/index.ts +9 -0
  257. package/src/services/directoryMatch.ts +71 -0
  258. package/src/services/fileMatch.ts +37 -0
  259. package/src/services/fileMedia.ts +27 -0
  260. package/src/services/frameRing.ts +33 -0
  261. package/src/services/journalTail.ts +47 -0
  262. package/src/services/loginFlow.ts +144 -0
  263. package/src/services/registryStore.ts +83 -0
  264. package/src/services/retryPolicy.ts +15 -0
  265. package/src/services/serveOptions.ts +63 -0
  266. package/src/services/sessionFraming.ts +32 -0
  267. package/src/services/sessionPresence.ts +131 -0
  268. package/src/services/staticAssets.ts +37 -0
  269. package/src/services/webDevRoots.ts +36 -0
  270. package/src/services/webPluginManifest.ts +149 -0
  271. package/src/types/auth.ts +118 -0
  272. package/src/types/bridge.ts +41 -0
  273. package/src/types/hub.ts +290 -0
  274. package/src/types/media.ts +38 -0
  275. package/src/types/registry.ts +2 -0
  276. package/src/types/session.ts +36 -0
  277. package/src/web/app/Providers.tsx +46 -0
  278. package/src/web/app/protocolRuntime.ts +106 -0
  279. package/src/web/app/sessionRuntime.ts +189 -0
  280. package/src/web/app/webPlugins.generated.ts +4 -0
  281. package/src/web/components/MascotMark.tsx +46 -0
  282. package/src/web/components/PluginSurface.tsx +10 -0
  283. package/src/web/components/ToolRendererBoundary.tsx +32 -0
  284. package/src/web/features/activity/ActivityDock.tsx +125 -0
  285. package/src/web/features/connection/RefusedCard.tsx +63 -0
  286. package/src/web/features/dialogs/DialogOverlay.tsx +176 -0
  287. package/src/web/features/leader/CommandPalette.tsx +386 -0
  288. package/src/web/features/selection/SelectionBar.tsx +529 -0
  289. package/src/web/features/session/Composer.tsx +368 -0
  290. package/src/web/features/session/MentionPreviews.tsx +58 -0
  291. package/src/web/features/session/ThreadView.tsx +35 -0
  292. package/src/web/features/session/Timeline.tsx +382 -0
  293. package/src/web/features/session/ToolCard.tsx +79 -0
  294. package/src/web/features/sessions/NewSessionDialog.tsx +222 -0
  295. package/src/web/features/sessions/SessionRail.tsx +411 -0
  296. package/src/web/features/settings/AppearanceSettings.tsx +88 -0
  297. package/src/web/features/settings/LoginFlowDialog.tsx +221 -0
  298. package/src/web/features/settings/PluginSettings.tsx +92 -0
  299. package/src/web/features/settings/ProviderSettings.tsx +238 -0
  300. package/src/web/features/settings/SettingsMenu.tsx +32 -0
  301. package/src/web/features/status/TopBar.tsx +208 -0
  302. package/src/web/index.html +16 -0
  303. package/src/web/lib/authApi.ts +90 -0
  304. package/src/web/lib/commands.ts +61 -0
  305. package/src/web/lib/composition.ts +225 -0
  306. package/src/web/lib/fileMentions.ts +32 -0
  307. package/src/web/lib/hubApi.ts +86 -0
  308. package/src/web/lib/leaderTree.ts +159 -0
  309. package/src/web/lib/piTransport.ts +61 -0
  310. package/src/web/lib/pluginRegistry.ts +488 -0
  311. package/src/web/lib/pluginSlotProps.ts +50 -0
  312. package/src/web/lib/promptFocus.ts +25 -0
  313. package/src/web/lib/protocolTimeline.ts +73 -0
  314. package/src/web/lib/sessionModel.ts +673 -0
  315. package/src/web/lib/sessionSummary.ts +58 -0
  316. package/src/web/lib/settingsSections.ts +21 -0
  317. package/src/web/lib/statusLine.ts +90 -0
  318. package/src/web/lib/threadRenderer.ts +25 -0
  319. package/src/web/lib/toolMessageProps.ts +21 -0
  320. package/src/web/lib/transport.ts +30 -0
  321. package/src/web/lib/wsClient.ts +73 -0
  322. package/src/web/main.tsx +17 -0
  323. package/src/web/public/apple-touch-icon.png +0 -0
  324. package/src/web/public/favicon-16.png +0 -0
  325. package/src/web/public/favicon-32.png +0 -0
  326. package/src/web/public/favicon.ico +0 -0
  327. package/src/web/public/favicon.svg +16 -0
  328. package/src/web/public/icon-512.png +0 -0
  329. package/src/web/routes/CockpitPage.tsx +98 -0
  330. package/src/web/routes/SettingsPage.tsx +60 -0
  331. package/src/web/routes/routeTree.tsx +49 -0
  332. package/src/web/stores/menuStore.ts +78 -0
  333. package/src/web/stores/paletteStore.ts +25 -0
  334. package/src/web/stores/sessionStore.ts +303 -0
  335. package/src/web/stores/sessionsStore.ts +155 -0
  336. package/src/web/stores/themeStore.ts +52 -0
  337. package/src/web/stores/threadStore.ts +68 -0
  338. package/src/web/stores/transientTabsStore.ts +63 -0
  339. package/src/web/stores/uiStore.ts +28 -0
  340. package/src/web/stores/useOpenTab.ts +19 -0
  341. package/src/web/stores/usePluginSlotProps.ts +22 -0
  342. package/src/web/styles/app.css +15 -0
  343. package/src/web/styles/webPluginSources.generated.css +1 -0
  344. package/src/web/vite-env.d.ts +1 -0
@@ -0,0 +1,86 @@
1
+ import { DIRECTORIES_API_ROUTE, SESSIONS_API_ROUTE } from '../../types/hub.ts';
2
+
3
+ export type CreateSessionResult = { sessionId: string } | { error: string };
4
+
5
+ function isRecord(value: unknown): value is Record<string, unknown> {
6
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
7
+ }
8
+
9
+ /**
10
+ * Asks the hub to start a new doompi-server in the given directory.
11
+ *
12
+ * The one place the page talks REST to the hub; if creation ever moves onto
13
+ * the WebSocket, only this file changes.
14
+ */
15
+ export async function createSession(input: { cwd: string; name?: string }): Promise<CreateSessionResult> {
16
+ let response: Response;
17
+ try {
18
+ response = await fetch(SESSIONS_API_ROUTE, {
19
+ method: 'POST',
20
+ headers: { 'Content-Type': 'application/json' },
21
+ body: JSON.stringify(input),
22
+ });
23
+ } catch {
24
+ return { error: 'The cockpit hub is unreachable.' };
25
+ }
26
+ let body: unknown;
27
+ try {
28
+ body = await response.json();
29
+ } catch {
30
+ body = undefined;
31
+ }
32
+ if (response.ok && isRecord(body) && typeof body.sessionId === 'string') return { sessionId: body.sessionId };
33
+ const error = isRecord(body) && typeof body.error === 'string' ? body.error : `The hub answered ${response.status}.`;
34
+ return { error };
35
+ }
36
+
37
+ export type StopSessionResult = { ok: true } | { error: string };
38
+
39
+ /** Asks the hub to stop a session's server; the rail card leaves once the server withdraws its record. */
40
+ export async function stopSession(sessionId: string): Promise<StopSessionResult> {
41
+ let response: Response;
42
+ try {
43
+ response = await fetch(`${SESSIONS_API_ROUTE}/${encodeURIComponent(sessionId)}`, { method: 'DELETE' });
44
+ } catch {
45
+ return { error: 'The cockpit hub is unreachable.' };
46
+ }
47
+ if (response.ok) return { ok: true };
48
+ let body: unknown;
49
+ try {
50
+ body = await response.json();
51
+ } catch {
52
+ body = undefined;
53
+ }
54
+ const error = isRecord(body) && typeof body.error === 'string' ? body.error : `The hub answered ${response.status}.`;
55
+ return { error };
56
+ }
57
+
58
+ /** File paths under a session's cwd matching the query, for @ completion. */
59
+ export async function searchSessionFiles(sessionId: string, query: string): Promise<string[]> {
60
+ try {
61
+ const response = await fetch(`/api/sessions/${encodeURIComponent(sessionId)}/files?q=${encodeURIComponent(query)}`);
62
+ if (!response.ok) return [];
63
+ const body = (await response.json()) as { files?: unknown };
64
+ return Array.isArray(body.files) ? body.files.filter((file): file is string => typeof file === 'string') : [];
65
+ } catch {
66
+ return []; // Completion is a convenience; a failed lookup just shows nothing.
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Directories the typed path could complete to, for the new-session picker.
72
+ * The hub lists the parent directory's children and filters them by the
73
+ * trailing segment as a regular expression.
74
+ */
75
+ export async function searchDirectories(query: string): Promise<string[]> {
76
+ try {
77
+ const response = await fetch(`${DIRECTORIES_API_ROUTE}?q=${encodeURIComponent(query)}`);
78
+ if (!response.ok) return [];
79
+ const body = (await response.json()) as { directories?: unknown };
80
+ return Array.isArray(body.directories)
81
+ ? body.directories.filter((directory): directory is string => typeof directory === 'string')
82
+ : [];
83
+ } catch {
84
+ return []; // Suggestions are a convenience; a failed lookup just shows nothing.
85
+ }
86
+ }
@@ -0,0 +1,159 @@
1
+ import type { LeaderBindingContribution } from '@agimon-ai/doompi-web-contracts';
2
+
3
+ export interface LeaderOption {
4
+ key: string;
5
+ label: string;
6
+ detail: string;
7
+ /** A group opens the next level; a leaf carries the binding that fires. */
8
+ binding?: LeaderBindingContribution;
9
+ /** The keys one level down, for a group's preview; empty for a leaf. */
10
+ children: LeaderOption[];
11
+ }
12
+
13
+ export interface LeaderGroup {
14
+ /** The label of the segment that opened this level; 'leader' at the root. */
15
+ label: string;
16
+ options: LeaderOption[];
17
+ }
18
+
19
+ interface LeaderNode {
20
+ key: string;
21
+ label: string;
22
+ detail: string;
23
+ binding?: LeaderBindingContribution;
24
+ children: Map<string, LeaderNode>;
25
+ }
26
+
27
+ const ROOT_LABEL = 'leader';
28
+
29
+ /**
30
+ * Inserts one binding the way the TUI registry does: a group segment keeps
31
+ * the label of whoever registered it first (adopting a detail if it had
32
+ * none), and a leaf that is already bound is taken over by the newcomer,
33
+ * along with any group that stood where the leaf now goes.
34
+ */
35
+ function insert(root: LeaderNode, binding: LeaderBindingContribution): void {
36
+ let current = root;
37
+ for (const [index, segment] of binding.path.entries()) {
38
+ const isLeaf = index === binding.path.length - 1;
39
+ let node = current.children.get(segment.key);
40
+ if (!node) {
41
+ node = { key: segment.key, label: segment.label, detail: segment.detail ?? '', children: new Map() };
42
+ current.children.set(segment.key, node);
43
+ } else if (!node.detail && segment.detail) {
44
+ node.detail = segment.detail;
45
+ }
46
+ if (isLeaf) {
47
+ node.children.clear();
48
+ node.binding = binding;
49
+ node.label = segment.label;
50
+ node.detail = segment.detail ?? '';
51
+ } else if (node.binding) {
52
+ delete node.binding;
53
+ }
54
+ current = node;
55
+ }
56
+ }
57
+
58
+ export interface LeaderConflict {
59
+ /** A later binding took a bound leaf (or a whole group) over, or a group segment was worded differently. */
60
+ kind: 'leaf-override' | 'group-label';
61
+ /** The keys of the contested segment, space separated. */
62
+ path: string;
63
+ /** The binding the tree keeps: the later one for a leaf, the first namer for a group label. */
64
+ winner: LeaderBindingContribution;
65
+ loser: LeaderBindingContribution;
66
+ }
67
+
68
+ function leavesUnder(node: LeaderNode): LeaderBindingContribution[] {
69
+ const leaves: LeaderBindingContribution[] = [];
70
+ if (node.binding) leaves.push(node.binding);
71
+ for (const child of node.children.values()) leaves.push(...leavesUnder(child));
72
+ return leaves;
73
+ }
74
+
75
+ /**
76
+ * Every place `insert` would silently prefer one binding over another, in
77
+ * the same order insert walks them: a leaf landing on a bound leaf or over a
78
+ * group displaces those bindings, a group segment crossing a bound leaf
79
+ * displaces it, and a group segment worded differently from the first namer
80
+ * keeps the first wording.
81
+ */
82
+ export function leaderConflicts(bindings: readonly LeaderBindingContribution[]): LeaderConflict[] {
83
+ const conflicts: LeaderConflict[] = [];
84
+ const root: LeaderNode = { key: '', label: ROOT_LABEL, detail: '', children: new Map() };
85
+ const namers = new Map<LeaderNode, LeaderBindingContribution>();
86
+ for (const binding of bindings) {
87
+ let current = root;
88
+ const keys: string[] = [];
89
+ for (const [index, segment] of binding.path.entries()) {
90
+ keys.push(segment.key);
91
+ const path = keys.join(' ');
92
+ const isLeaf = index === binding.path.length - 1;
93
+ let node = current.children.get(segment.key);
94
+ if (node) {
95
+ if (isLeaf) {
96
+ for (const loser of leavesUnder(node)) {
97
+ if (loser !== binding) conflicts.push({ kind: 'leaf-override', path, winner: binding, loser });
98
+ }
99
+ } else {
100
+ if (node.binding && node.binding !== binding) {
101
+ conflicts.push({ kind: 'leaf-override', path, winner: binding, loser: node.binding });
102
+ }
103
+ const namer = namers.get(node);
104
+ if (namer !== undefined && namer !== binding && node.label !== segment.label) {
105
+ conflicts.push({ kind: 'group-label', path, winner: namer, loser: binding });
106
+ }
107
+ }
108
+ } else {
109
+ node = { key: segment.key, label: segment.label, detail: '', children: new Map() };
110
+ current.children.set(segment.key, node);
111
+ namers.set(node, binding);
112
+ }
113
+ if (isLeaf) {
114
+ node.children.clear();
115
+ node.binding = binding;
116
+ node.label = segment.label;
117
+ namers.set(node, binding);
118
+ } else if (node.binding) {
119
+ delete node.binding;
120
+ }
121
+ current = node;
122
+ }
123
+ }
124
+ return conflicts;
125
+ }
126
+
127
+ function toOption(node: LeaderNode): LeaderOption {
128
+ const children = [...node.children.values()]
129
+ .sort((left, right) => left.key.localeCompare(right.key))
130
+ .map((child) => ({ key: child.key, label: child.label, detail: child.detail, children: [] }));
131
+ return {
132
+ key: node.key,
133
+ label: node.label,
134
+ detail: node.detail,
135
+ ...(node.binding ? { binding: node.binding } : {}),
136
+ children,
137
+ };
138
+ }
139
+
140
+ /**
141
+ * The options one level of Leader Space offers, keyed by what the reader
142
+ * would press, sorted by key. Undefined when the path runs off the tree or
143
+ * lands on a leaf, which is how the palette knows a path is no longer a group.
144
+ */
145
+ export function leaderGroup(
146
+ bindings: readonly LeaderBindingContribution[],
147
+ path: readonly string[],
148
+ ): LeaderGroup | undefined {
149
+ const root: LeaderNode = { key: '', label: ROOT_LABEL, detail: '', children: new Map() };
150
+ for (const binding of bindings) insert(root, binding);
151
+ let current = root;
152
+ for (const key of path) {
153
+ const next = current.children.get(key);
154
+ if (!next || next.binding) return undefined;
155
+ current = next;
156
+ }
157
+ const options = [...current.children.values()].sort((left, right) => left.key.localeCompare(right.key)).map(toOption);
158
+ return { label: current.label, options };
159
+ }
@@ -0,0 +1,61 @@
1
+ import type { ByteTransport, ByteTransportFactory, ByteTransportHandlers } from '@earendil-works/pi-client';
2
+
3
+ const PROTOCOL_PATH = '/api/pi';
4
+ const SECURE_PAGE = 'https:';
5
+ const SECURE_SOCKET = 'wss:';
6
+ const PLAIN_SOCKET = 'ws:';
7
+
8
+ /** The cockpit's protocol endpoint for this page. */
9
+ export function protocolSocketUrl(location: Location): string {
10
+ const scheme = location.protocol === SECURE_PAGE ? SECURE_SOCKET : PLAIN_SOCKET;
11
+ return `${scheme}//${location.host}${PROTOCOL_PATH}`;
12
+ }
13
+
14
+ /**
15
+ * Carries Pi's protocol to the browser.
16
+ *
17
+ * Pi ships a Unix transport, which a page cannot open, but the client only
18
+ * asks for ordered bytes and a closure signal. A WebSocket in binary mode is
19
+ * exactly that, so the page runs the same client the terminal would.
20
+ */
21
+ export function createProtocolTransport(url: string): ByteTransportFactory {
22
+ return async (handlers: ByteTransportHandlers): Promise<ByteTransport> => {
23
+ const socket = new WebSocket(url);
24
+ socket.binaryType = 'arraybuffer';
25
+
26
+ await new Promise<void>((resolve, reject) => {
27
+ const onOpen = (): void => {
28
+ socket.removeEventListener('error', onError);
29
+ resolve();
30
+ };
31
+ const onError = (): void => {
32
+ socket.removeEventListener('open', onOpen);
33
+ reject(new Error('The cockpit protocol socket failed to open'));
34
+ };
35
+ socket.addEventListener('open', onOpen, { once: true });
36
+ socket.addEventListener('error', onError, { once: true });
37
+ });
38
+
39
+ socket.addEventListener('message', (event: MessageEvent<ArrayBuffer | Blob | string>) => {
40
+ const data = event.data;
41
+ // A text frame is not protocol traffic; the codec would reject it, so it
42
+ // is dropped rather than handed on as bytes it never produced.
43
+ if (typeof data === 'string') return;
44
+ if (data instanceof ArrayBuffer) handlers.onData(new Uint8Array(data));
45
+ else void data.arrayBuffer().then((buffer) => handlers.onData(new Uint8Array(buffer)));
46
+ });
47
+ socket.addEventListener('close', () => handlers.onClose());
48
+ socket.addEventListener('error', () => handlers.onError(new Error('The cockpit protocol socket failed')));
49
+
50
+ return {
51
+ async send(chunk) {
52
+ // Copied into a plain ArrayBuffer: the protocol codec may hand back a
53
+ // view onto a shared buffer, which send() will not take.
54
+ socket.send(chunk.slice().buffer as ArrayBuffer);
55
+ },
56
+ close() {
57
+ socket.close();
58
+ },
59
+ };
60
+ };
61
+ }