@readymadeit/mcp 0.0.0-stage → 0.1.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 (916) hide show
  1. package/README.md +114 -2
  2. package/bundle/LICENSE +201 -0
  3. package/bundle/boards/aggregator-demo-board.json +54 -0
  4. package/bundle/boards/alpaca-board.json +316 -0
  5. package/bundle/boards/animate-board.json +159 -0
  6. package/bundle/boards/ascii-art-demo-board.json +33 -0
  7. package/bundle/boards/ascii-cam-board.json +184 -0
  8. package/bundle/boards/asset-demo-board.json +54 -0
  9. package/bundle/boards/audio-encode-demo-board.json +153 -0
  10. package/bundle/boards/audiolize-board.json +152 -0
  11. package/bundle/boards/batcher-demo-board.json +43 -0
  12. package/bundle/boards/buffer-demo-board.json +53 -0
  13. package/bundle/boards/cache-demo-board.json +59 -0
  14. package/bundle/boards/camera-demo-board.json +28 -0
  15. package/bundle/boards/canvas-demo-board.json +55 -0
  16. package/bundle/boards/changes-demo-board.json +60 -0
  17. package/bundle/boards/court-booking-browser-demo-board.json +221 -0
  18. package/bundle/boards/court-booking-demo-board.json +218 -0
  19. package/bundle/boards/current-time-board.json +32 -0
  20. package/bundle/boards/debounce-demo-board.json +43 -0
  21. package/bundle/boards/decrypt-demo-board.json +47 -0
  22. package/bundle/boards/delay-demo-board.json +28 -0
  23. package/bundle/boards/detect-demo-board.json +64 -0
  24. package/bundle/boards/document-extract-demo-board.json +55 -0
  25. package/bundle/boards/dropitapp-board.json +138 -0
  26. package/bundle/boards/encode-demo-board.json +39 -0
  27. package/bundle/boards/encrypt-board.json +185 -0
  28. package/bundle/boards/encrypt-demo-board.json +39 -0
  29. package/bundle/boards/face-alert-board.json +181 -0
  30. package/bundle/boards/fetcher-demo-board.json +40 -0
  31. package/bundle/boards/fft-demo-board.json +28 -0
  32. package/bundle/boards/filter-demo-board.json +55 -0
  33. package/bundle/boards/flat-map-demo-board.json +42 -0
  34. package/bundle/boards/frame-limit-test-board.json +130 -0
  35. package/bundle/boards/game-board.json +413 -0
  36. package/bundle/boards/game-of-life-board.json +139 -0
  37. package/bundle/boards/game-of-life-demo-board.json +34 -0
  38. package/bundle/boards/gather-demo-board.json +62 -0
  39. package/bundle/boards/group-by-demo-board.json +62 -0
  40. package/bundle/boards/hacker-demo-board.json +45 -0
  41. package/bundle/boards/hacker-news-board.json +741 -0
  42. package/bundle/boards/hash-demo-board.json +40 -0
  43. package/bundle/boards/health-log-browser-demo-board.json +537 -0
  44. package/bundle/boards/health-log-demo-board.json +498 -0
  45. package/bundle/boards/helloworld-board.json +57 -0
  46. package/bundle/boards/hold-demo-board.json +67 -0
  47. package/bundle/boards/hold-scopes-demo-board.json +141 -0
  48. package/bundle/boards/http-client-browser-demo-board.json +379 -0
  49. package/bundle/boards/http-client-demo-board.json +380 -0
  50. package/bundle/boards/image-picker-demo-board.json +22 -0
  51. package/bundle/boards/imap-email-demo-board.json +35 -0
  52. package/bundle/boards/injector-demo-board.json +55 -0
  53. package/bundle/boards/iterator-demo-board.json +55 -0
  54. package/bundle/boards/limit-demo-board.json +66 -0
  55. package/bundle/boards/link-debugger.json +32 -0
  56. package/bundle/boards/live-location-demo-board.json +93 -0
  57. package/bundle/boards/live-radio-cloud-demo-board.json +216 -0
  58. package/bundle/boards/live-radio-cloud-direct-demo-board.json +311 -0
  59. package/bundle/boards/live-radio-demo-board.json +211 -0
  60. package/bundle/boards/live-radio-members-demo-board.json +314 -0
  61. package/bundle/boards/local-storage-demo-board.json +30 -0
  62. package/bundle/boards/lz-compress-demo-board.json +52 -0
  63. package/bundle/boards/map-demo-board.json +46 -0
  64. package/bundle/boards/match-filter-demo-board.json +43 -0
  65. package/bundle/boards/meeting-poll-browser-demo-board.json +649 -0
  66. package/bundle/boards/meeting-poll-demo-board.json +650 -0
  67. package/bundle/boards/microphone-monitor-demo-board.json +28 -0
  68. package/bundle/boards/microphone-speaker-demo-board.json +86 -0
  69. package/bundle/boards/monitor-demo-board.json +52 -0
  70. package/bundle/boards/mounted-endpoint-demo-board-hkp-rt.json +84 -0
  71. package/bundle/boards/mounted-endpoint-demo-board.json +84 -0
  72. package/bundle/boards/nested-rhythm-demo-board.json +1160 -0
  73. package/bundle/boards/next-bus-board.json +52 -0
  74. package/bundle/boards/next-bus-switch-board.json +79 -0
  75. package/bundle/boards/night-groove-demo-board.json +742 -0
  76. package/bundle/boards/noise-alert-board.json +146 -0
  77. package/bundle/boards/offer-followup-demo-board.json +291 -0
  78. package/bundle/boards/offer-intake-demo-board.json +289 -0
  79. package/bundle/boards/offer-intake-hetzner-demo-board.json +286 -0
  80. package/bundle/boards/offer-intake-imap-demo-board.json +289 -0
  81. package/bundle/boards/offer-intake-imap-hetzner-demo-board.json +354 -0
  82. package/bundle/boards/p2p-receiver-demo-board.json +65 -0
  83. package/bundle/boards/p2p-sender-demo-board.json +94 -0
  84. package/bundle/boards/pad-groove-demo-board.json +644 -0
  85. package/bundle/boards/peer-chat-board.json +109 -0
  86. package/bundle/boards/peer-chat-node-board.json +97 -0
  87. package/bundle/boards/qr-code-demo-board.json +22 -0
  88. package/bundle/boards/queue-demo-board.json +239 -0
  89. package/bundle/boards/reduce-demo-board.json +54 -0
  90. package/bundle/boards/remote-named-demo-board-hkp-rt.json +61 -0
  91. package/bundle/boards/remote-named-demo-board.json +61 -0
  92. package/bundle/boards/rss-demo-board.json +530 -0
  93. package/bundle/boards/rss-radio-board.json +515 -0
  94. package/bundle/boards/rss-radio-brief-one-board.json +237 -0
  95. package/bundle/boards/rss-radio-editor-unit-board.json +555 -0
  96. package/bundle/boards/rss-radio-library-unit-board.json +293 -0
  97. package/bundle/boards/rss-radio-unit-board.json +809 -0
  98. package/bundle/boards/rss-radio-voice-unit-board.json +146 -0
  99. package/bundle/boards/runtime-configs/hold-example.json +41 -0
  100. package/bundle/boards/runtime-configs/injector-example.json +23 -0
  101. package/bundle/boards/runtime-configs/speech-to-text-example.json +35 -0
  102. package/bundle/boards/runtime-configs/text-generation-example.json +31 -0
  103. package/bundle/boards/runtime-configs/text-to-speech-example.json +28 -0
  104. package/bundle/boards/runtime-configs/text-to-speech-inflect-example.json +28 -0
  105. package/bundle/boards/runtime-configs/ws-audio-playback-example.json +23 -0
  106. package/bundle/boards/select-demo-board.json +32 -0
  107. package/bundle/boards/send-ntfy-board.json +52 -0
  108. package/bundle/boards/shared-costs-demo-board.json +361 -0
  109. package/bundle/boards/sign-demo-board.json +41 -0
  110. package/bundle/boards/skill-router-demo-board.json +210 -0
  111. package/bundle/boards/smooth-demo-board.json +91 -0
  112. package/bundle/boards/smtp-email-demo-board.json +34 -0
  113. package/bundle/boards/sort-demo-board.json +55 -0
  114. package/bundle/boards/sound-demo-board.json +65 -0
  115. package/bundle/boards/spectral-modifier-board.json +98 -0
  116. package/bundle/boards/speech-synth-demo-board.json +37 -0
  117. package/bundle/boards/speech-to-text-demo-board.json +146 -0
  118. package/bundle/boards/spotify-board.json +75 -0
  119. package/bundle/boards/spread-demo-board.json +35 -0
  120. package/bundle/boards/sql-demo-board.json +56 -0
  121. package/bundle/boards/sql-explorer-board.json +447 -0
  122. package/bundle/boards/sql-explorer-node-board.json +474 -0
  123. package/bundle/boards/sql-import-board.json +156 -0
  124. package/bundle/boards/state-demo-board.json +65 -0
  125. package/bundle/boards/stopper-demo-board.json +40 -0
  126. package/bundle/boards/storage-demo-board.json +193 -0
  127. package/bundle/boards/store-demo-board.json +63 -0
  128. package/bundle/boards/swimlanes-demo-board.json +197 -0
  129. package/bundle/boards/switch-demo-board.json +51 -0
  130. package/bundle/boards/syn-board.json +19 -0
  131. package/bundle/boards/syn-booking-unit-board.json +998 -0
  132. package/bundle/boards/syn-conversations-anthropic-demo-board.json +925 -0
  133. package/bundle/boards/syn-conversations-demo-board.json +925 -0
  134. package/bundle/boards/syn-hotels-unit-board.json +287 -0
  135. package/bundle/boards/syn-mail-desk-board.json +25 -0
  136. package/bundle/boards/syn-mail-desk-unit-board.json +254 -0
  137. package/bundle/boards/telegram-listener-demo-board.json +31 -0
  138. package/bundle/boards/text-generation-anthropic-demo-board.json +66 -0
  139. package/bundle/boards/text-generation-demo-board.json +51 -0
  140. package/bundle/boards/text-generation-local-demo-board.json +80 -0
  141. package/bundle/boards/text-to-speech-demo-board.json +58 -0
  142. package/bundle/boards/timeline-demo-board.json +477 -0
  143. package/bundle/boards/timer-demo-board.json +35 -0
  144. package/bundle/boards/tracks-demo-board.json +95 -0
  145. package/bundle/boards/trigger-pad-demo-board.json +47 -0
  146. package/bundle/boards/triggerpad-board.json +31 -0
  147. package/bundle/boards/triggerpad-recorder-board.json +56 -0
  148. package/bundle/boards/uuid-generator-demo-board.json +166 -0
  149. package/bundle/boards/voice-assistant-demo-board.json +230 -0
  150. package/bundle/boards/voice-assistant-hkp-rt-demo-board.json +239 -0
  151. package/bundle/boards/voice-assistant-local-demo-board.json +232 -0
  152. package/bundle/boards/voice-assistant-skills-demo-board.json +316 -0
  153. package/bundle/boards/voice-assistant-skills-local-demo-board.json +316 -0
  154. package/bundle/boards/xy-pad-demo-board.json +36 -0
  155. package/bundle/docs/content/board-json.md +277 -0
  156. package/bundle/docs/content/boards/alpaca-board.md +56 -0
  157. package/bundle/docs/content/boards/animate-board.md +40 -0
  158. package/bundle/docs/content/boards/ascii-cam-board.md +37 -0
  159. package/bundle/docs/content/boards/audiolize-board.md +47 -0
  160. package/bundle/docs/content/boards/court-booking-browser-demo-board.md +43 -0
  161. package/bundle/docs/content/boards/court-booking-demo-board.md +120 -0
  162. package/bundle/docs/content/boards/current-time-board.md +32 -0
  163. package/bundle/docs/content/boards/dropitapp-board.md +57 -0
  164. package/bundle/docs/content/boards/encrypt-board.md +36 -0
  165. package/bundle/docs/content/boards/face-alert-board.md +50 -0
  166. package/bundle/docs/content/boards/game-board.md +69 -0
  167. package/bundle/docs/content/boards/game-of-life-board.md +47 -0
  168. package/bundle/docs/content/boards/hacker-news-board.md +76 -0
  169. package/bundle/docs/content/boards/health-log-browser-demo-board.md +56 -0
  170. package/bundle/docs/content/boards/health-log-demo-board.md +138 -0
  171. package/bundle/docs/content/boards/helloworld-board.md +40 -0
  172. package/bundle/docs/content/boards/link-debugger.md +31 -0
  173. package/bundle/docs/content/boards/live-radio-cloud-demo-board.md +70 -0
  174. package/bundle/docs/content/boards/live-radio-cloud-direct-demo-board.md +89 -0
  175. package/bundle/docs/content/boards/live-radio-demo-board.md +102 -0
  176. package/bundle/docs/content/boards/live-radio-members-demo-board.md +91 -0
  177. package/bundle/docs/content/boards/meeting-poll-browser-demo-board.md +45 -0
  178. package/bundle/docs/content/boards/meeting-poll-demo-board.md +121 -0
  179. package/bundle/docs/content/boards/mounted-endpoint-demo-board-hkp-rt.md +50 -0
  180. package/bundle/docs/content/boards/nested-rhythm-demo-board.md +165 -0
  181. package/bundle/docs/content/boards/next-bus-board.md +38 -0
  182. package/bundle/docs/content/boards/next-bus-switch-board.md +44 -0
  183. package/bundle/docs/content/boards/night-groove-demo-board.md +110 -0
  184. package/bundle/docs/content/boards/noise-alert-board.md +44 -0
  185. package/bundle/docs/content/boards/pad-groove-demo-board.md +93 -0
  186. package/bundle/docs/content/boards/peer-chat-board.md +49 -0
  187. package/bundle/docs/content/boards/peer-chat-node-board.md +47 -0
  188. package/bundle/docs/content/boards/rss-radio-board.md +221 -0
  189. package/bundle/docs/content/boards/send-ntfy-board.md +33 -0
  190. package/bundle/docs/content/boards/shared-costs-demo-board.md +65 -0
  191. package/bundle/docs/content/boards/spectral-modifier-board.md +55 -0
  192. package/bundle/docs/content/boards/spotify-board.md +46 -0
  193. package/bundle/docs/content/boards/sql-explorer-board.md +70 -0
  194. package/bundle/docs/content/boards/sql-explorer-node-board.md +76 -0
  195. package/bundle/docs/content/boards/sql-import-board.md +37 -0
  196. package/bundle/docs/content/boards/swimlanes-demo-board.md +148 -0
  197. package/bundle/docs/content/boards/syn-mail-desk-board.md +52 -0
  198. package/bundle/docs/content/boards/timeline-demo-board.md +115 -0
  199. package/bundle/docs/content/boards/triggerpad-board.md +32 -0
  200. package/bundle/docs/content/boards/triggerpad-recorder-board.md +52 -0
  201. package/bundle/docs/content/boards/voice-assistant-demo-board.md +51 -0
  202. package/bundle/docs/content/concepts/assets.md +245 -0
  203. package/bundle/docs/content/concepts/blocks.md +300 -0
  204. package/bundle/docs/content/concepts/board-assistant.md +307 -0
  205. package/bundle/docs/content/concepts/board.md +265 -0
  206. package/bundle/docs/content/concepts/cloud-boards.md +803 -0
  207. package/bundle/docs/content/concepts/coordinator.md +297 -0
  208. package/bundle/docs/content/concepts/logging.md +293 -0
  209. package/bundle/docs/content/concepts/mounts.md +314 -0
  210. package/bundle/docs/content/concepts/presets.md +360 -0
  211. package/bundle/docs/content/concepts/remotes.md +423 -0
  212. package/bundle/docs/content/concepts/runtime.md +664 -0
  213. package/bundle/docs/content/concepts/scopes.md +256 -0
  214. package/bundle/docs/content/concepts/service.md +227 -0
  215. package/bundle/docs/content/concepts/units.md +321 -0
  216. package/bundle/docs/content/introduction.md +163 -0
  217. package/bundle/docs/content/llm/circle-text-board.json +55 -0
  218. package/bundle/docs/content/llm/howto-workflow.md +369 -0
  219. package/bundle/docs/content/repository.md +161 -0
  220. package/bundle/docs/content/services/aggregator.md +76 -0
  221. package/bundle/docs/content/services/analyzer.md +44 -0
  222. package/bundle/docs/content/services/ascii-art.md +58 -0
  223. package/bundle/docs/content/services/asset.md +88 -0
  224. package/bundle/docs/content/services/audio-editor.md +57 -0
  225. package/bundle/docs/content/services/audio-encode.md +115 -0
  226. package/bundle/docs/content/services/audio-io.md +173 -0
  227. package/bundle/docs/content/services/batcher.md +49 -0
  228. package/bundle/docs/content/services/board-service.md +48 -0
  229. package/bundle/docs/content/services/browser-sub-service.md +75 -0
  230. package/bundle/docs/content/services/buffer.md +70 -0
  231. package/bundle/docs/content/services/cache.md +92 -0
  232. package/bundle/docs/content/services/camera.md +85 -0
  233. package/bundle/docs/content/services/canvas.md +196 -0
  234. package/bundle/docs/content/services/changes.md +87 -0
  235. package/bundle/docs/content/services/chunked-file-provider.md +63 -0
  236. package/bundle/docs/content/services/communication-dispatcher.md +190 -0
  237. package/bundle/docs/content/services/configurator.md +66 -0
  238. package/bundle/docs/content/services/conversations.md +244 -0
  239. package/bundle/docs/content/services/debounce.md +45 -0
  240. package/bundle/docs/content/services/decrypt.md +53 -0
  241. package/bundle/docs/content/services/delay.md +55 -0
  242. package/bundle/docs/content/services/detect.md +101 -0
  243. package/bundle/docs/content/services/document-extract.md +174 -0
  244. package/bundle/docs/content/services/download.md +67 -0
  245. package/bundle/docs/content/services/encode.md +63 -0
  246. package/bundle/docs/content/services/encrypt.md +53 -0
  247. package/bundle/docs/content/services/fetcher.md +122 -0
  248. package/bundle/docs/content/services/fft.md +96 -0
  249. package/bundle/docs/content/services/file-source.md +67 -0
  250. package/bundle/docs/content/services/filesystem.md +157 -0
  251. package/bundle/docs/content/services/filter.md +133 -0
  252. package/bundle/docs/content/services/flat-map.md +62 -0
  253. package/bundle/docs/content/services/game-of-life.md +93 -0
  254. package/bundle/docs/content/services/gather.md +135 -0
  255. package/bundle/docs/content/services/gif-encoder.md +56 -0
  256. package/bundle/docs/content/services/github.md +85 -0
  257. package/bundle/docs/content/services/group-by.md +54 -0
  258. package/bundle/docs/content/services/hacker.md +79 -0
  259. package/bundle/docs/content/services/hash.md +59 -0
  260. package/bundle/docs/content/services/hold.md +182 -0
  261. package/bundle/docs/content/services/http-relay-client.md +48 -0
  262. package/bundle/docs/content/services/http-uploader.md +62 -0
  263. package/bundle/docs/content/services/http.md +424 -0
  264. package/bundle/docs/content/services/if.md +79 -0
  265. package/bundle/docs/content/services/ifft.md +46 -0
  266. package/bundle/docs/content/services/image-picker.md +50 -0
  267. package/bundle/docs/content/services/imap-email.md +104 -0
  268. package/bundle/docs/content/services/injector.md +129 -0
  269. package/bundle/docs/content/services/input.md +90 -0
  270. package/bundle/docs/content/services/iterator.md +186 -0
  271. package/bundle/docs/content/services/join.md +153 -0
  272. package/bundle/docs/content/services/limit.md +47 -0
  273. package/bundle/docs/content/services/local-storage.md +47 -0
  274. package/bundle/docs/content/services/looper.md +62 -0
  275. package/bundle/docs/content/services/lz-compress.md +52 -0
  276. package/bundle/docs/content/services/map.md +257 -0
  277. package/bundle/docs/content/services/match-filter.md +38 -0
  278. package/bundle/docs/content/services/microphone-monitor.md +66 -0
  279. package/bundle/docs/content/services/monitor.md +67 -0
  280. package/bundle/docs/content/services/ollama.md +116 -0
  281. package/bundle/docs/content/services/openai-prompt.md +74 -0
  282. package/bundle/docs/content/services/output.md +107 -0
  283. package/bundle/docs/content/services/peer-socket.md +61 -0
  284. package/bundle/docs/content/services/qr-code.md +48 -0
  285. package/bundle/docs/content/services/queue.md +201 -0
  286. package/bundle/docs/content/services/reduce.md +51 -0
  287. package/bundle/docs/content/services/rss.md +232 -0
  288. package/bundle/docs/content/services/select.md +47 -0
  289. package/bundle/docs/content/services/sign.md +53 -0
  290. package/bundle/docs/content/services/skill-router.md +154 -0
  291. package/bundle/docs/content/services/smooth.md +59 -0
  292. package/bundle/docs/content/services/smtp-email.md +129 -0
  293. package/bundle/docs/content/services/sort.md +48 -0
  294. package/bundle/docs/content/services/sound.md +87 -0
  295. package/bundle/docs/content/services/speech-synth.md +46 -0
  296. package/bundle/docs/content/services/speech-to-text.md +172 -0
  297. package/bundle/docs/content/services/spotify.md +77 -0
  298. package/bundle/docs/content/services/spread.md +111 -0
  299. package/bundle/docs/content/services/sql.md +491 -0
  300. package/bundle/docs/content/services/state.md +57 -0
  301. package/bundle/docs/content/services/static.md +47 -0
  302. package/bundle/docs/content/services/stopper.md +55 -0
  303. package/bundle/docs/content/services/storage.md +155 -0
  304. package/bundle/docs/content/services/store.md +255 -0
  305. package/bundle/docs/content/services/sub-service.md +152 -0
  306. package/bundle/docs/content/services/switch.md +67 -0
  307. package/bundle/docs/content/services/telegram-listener.md +76 -0
  308. package/bundle/docs/content/services/text-generation.md +390 -0
  309. package/bundle/docs/content/services/text-to-speech.md +130 -0
  310. package/bundle/docs/content/services/timeline.md +333 -0
  311. package/bundle/docs/content/services/timer.md +113 -0
  312. package/bundle/docs/content/services/tracks.md +207 -0
  313. package/bundle/docs/content/services/trigger-pad.md +68 -0
  314. package/bundle/docs/content/services/uuid-generator.md +48 -0
  315. package/bundle/docs/content/services/wav-reader.md +66 -0
  316. package/bundle/docs/content/services/websocket.md +189 -0
  317. package/bundle/docs/content/services/workflow-board-builder.md +65 -0
  318. package/bundle/docs/content/services/xy-pad.md +60 -0
  319. package/bundle/docs/content/targets.md +434 -0
  320. package/bundle/docs/content/testing.md +263 -0
  321. package/bundle/docs/content/vocabulary.md +171 -0
  322. package/bundle/hkp-frontend/src/runtime/browser/BrowserRegistry.tsx +141 -0
  323. package/bundle/hkp-frontend/src/runtime/browser/BrowserRuntime.tsx +202 -0
  324. package/bundle/hkp-frontend/src/runtime/browser/BrowserRuntimeApi.ts +306 -0
  325. package/bundle/hkp-frontend/src/runtime/browser/BrowserRuntimeApp.ts +310 -0
  326. package/bundle/hkp-frontend/src/runtime/browser/BrowserRuntimeScope.ts +500 -0
  327. package/bundle/hkp-frontend/src/runtime/browser/UIRegistry.ts +150 -0
  328. package/bundle/hkp-frontend/src/runtime/browser/assetResolver.ts +123 -0
  329. package/bundle/hkp-frontend/src/runtime/browser/pipelineNames.ts +65 -0
  330. package/bundle/hkp-frontend/src/runtime/browser/registry/Default.ts +237 -0
  331. package/bundle/hkp-frontend/src/runtime/browser/services/Aggregator.ts +127 -0
  332. package/bundle/hkp-frontend/src/runtime/browser/services/AggregatorUI.tsx +83 -0
  333. package/bundle/hkp-frontend/src/runtime/browser/services/Analyzer.tsx +43 -0
  334. package/bundle/hkp-frontend/src/runtime/browser/services/AnalyzerUI/Bars.tsx +33 -0
  335. package/bundle/hkp-frontend/src/runtime/browser/services/AnalyzerUI/HorizontalSpectrogram.tsx +53 -0
  336. package/bundle/hkp-frontend/src/runtime/browser/services/AnalyzerUI/VerticalSpectogram.tsx +56 -0
  337. package/bundle/hkp-frontend/src/runtime/browser/services/AnalyzerUI/Waveform.tsx +109 -0
  338. package/bundle/hkp-frontend/src/runtime/browser/services/AnalyzerUI/index.tsx +99 -0
  339. package/bundle/hkp-frontend/src/runtime/browser/services/AsciiArt.ts +119 -0
  340. package/bundle/hkp-frontend/src/runtime/browser/services/AsciiArtUI.tsx +131 -0
  341. package/bundle/hkp-frontend/src/runtime/browser/services/Asset.ts +125 -0
  342. package/bundle/hkp-frontend/src/runtime/browser/services/AssetUI.tsx +37 -0
  343. package/bundle/hkp-frontend/src/runtime/browser/services/AudioDeviceSettings.tsx +56 -0
  344. package/bundle/hkp-frontend/src/runtime/browser/services/AudioEditor.ts +129 -0
  345. package/bundle/hkp-frontend/src/runtime/browser/services/AudioEditorUI.tsx +549 -0
  346. package/bundle/hkp-frontend/src/runtime/browser/services/AudioInput.ts +298 -0
  347. package/bundle/hkp-frontend/src/runtime/browser/services/AudioInputUI.tsx +257 -0
  348. package/bundle/hkp-frontend/src/runtime/browser/services/AudioOutput.ts +189 -0
  349. package/bundle/hkp-frontend/src/runtime/browser/services/AudioOutputUI.tsx +63 -0
  350. package/bundle/hkp-frontend/src/runtime/browser/services/Batcher.ts +11 -0
  351. package/bundle/hkp-frontend/src/runtime/browser/services/BoardService.tsx +86 -0
  352. package/bundle/hkp-frontend/src/runtime/browser/services/BoardServiceUI.tsx +410 -0
  353. package/bundle/hkp-frontend/src/runtime/browser/services/BrowserGather.ts +311 -0
  354. package/bundle/hkp-frontend/src/runtime/browser/services/BrowserIterator.ts +167 -0
  355. package/bundle/hkp-frontend/src/runtime/browser/services/BrowserSubService.tsx +583 -0
  356. package/bundle/hkp-frontend/src/runtime/browser/services/BrowserSubServiceUI.tsx +187 -0
  357. package/bundle/hkp-frontend/src/runtime/browser/services/BrowserTracks.tsx +477 -0
  358. package/bundle/hkp-frontend/src/runtime/browser/services/Buffer.ts +11 -0
  359. package/bundle/hkp-frontend/src/runtime/browser/services/Cache.ts +134 -0
  360. package/bundle/hkp-frontend/src/runtime/browser/services/CacheUI.tsx +125 -0
  361. package/bundle/hkp-frontend/src/runtime/browser/services/Camera.ts +90 -0
  362. package/bundle/hkp-frontend/src/runtime/browser/services/CameraUI.tsx +321 -0
  363. package/bundle/hkp-frontend/src/runtime/browser/services/Canvas.ts +139 -0
  364. package/bundle/hkp-frontend/src/runtime/browser/services/CanvasUI/canvasDraw.ts +543 -0
  365. package/bundle/hkp-frontend/src/runtime/browser/services/CanvasUI/canvasUtils.ts +39 -0
  366. package/bundle/hkp-frontend/src/runtime/browser/services/CanvasUI/index.tsx +287 -0
  367. package/bundle/hkp-frontend/src/runtime/browser/services/CanvasUI/tests/canvasDraw.test.ts +355 -0
  368. package/bundle/hkp-frontend/src/runtime/browser/services/CanvasUI/tests/canvasUtils.test.ts +89 -0
  369. package/bundle/hkp-frontend/src/runtime/browser/services/CanvasUI/tests/fullscreen.test.tsx +26 -0
  370. package/bundle/hkp-frontend/src/runtime/browser/services/Changes.ts +201 -0
  371. package/bundle/hkp-frontend/src/runtime/browser/services/ChangesUI.tsx +85 -0
  372. package/bundle/hkp-frontend/src/runtime/browser/services/ChunkedFileProvider.tsx +183 -0
  373. package/bundle/hkp-frontend/src/runtime/browser/services/Configurator.ts +258 -0
  374. package/bundle/hkp-frontend/src/runtime/browser/services/ConfiguratorUI.tsx +83 -0
  375. package/bundle/hkp-frontend/src/runtime/browser/services/ConsideredHacker.tsx +28 -0
  376. package/bundle/hkp-frontend/src/runtime/browser/services/Debounce.ts +75 -0
  377. package/bundle/hkp-frontend/src/runtime/browser/services/DebounceUI.tsx +70 -0
  378. package/bundle/hkp-frontend/src/runtime/browser/services/Decrypt.ts +127 -0
  379. package/bundle/hkp-frontend/src/runtime/browser/services/DecryptUI.tsx +39 -0
  380. package/bundle/hkp-frontend/src/runtime/browser/services/Delay.tsx +110 -0
  381. package/bundle/hkp-frontend/src/runtime/browser/services/Detect.ts +214 -0
  382. package/bundle/hkp-frontend/src/runtime/browser/services/DetectUI.tsx +86 -0
  383. package/bundle/hkp-frontend/src/runtime/browser/services/Download.ts +120 -0
  384. package/bundle/hkp-frontend/src/runtime/browser/services/DownloadUI.tsx +61 -0
  385. package/bundle/hkp-frontend/src/runtime/browser/services/Encode.ts +138 -0
  386. package/bundle/hkp-frontend/src/runtime/browser/services/EncodeUI.tsx +52 -0
  387. package/bundle/hkp-frontend/src/runtime/browser/services/Encrypt.ts +125 -0
  388. package/bundle/hkp-frontend/src/runtime/browser/services/EncryptUI.tsx +39 -0
  389. package/bundle/hkp-frontend/src/runtime/browser/services/FFT.tsx +238 -0
  390. package/bundle/hkp-frontend/src/runtime/browser/services/FFTUI.tsx +112 -0
  391. package/bundle/hkp-frontend/src/runtime/browser/services/FeedbackService.ts +247 -0
  392. package/bundle/hkp-frontend/src/runtime/browser/services/FeedbackServiceUI.tsx +35 -0
  393. package/bundle/hkp-frontend/src/runtime/browser/services/Fetcher.tsx +232 -0
  394. package/bundle/hkp-frontend/src/runtime/browser/services/FetcherUI.tsx +154 -0
  395. package/bundle/hkp-frontend/src/runtime/browser/services/FileSource.tsx +171 -0
  396. package/bundle/hkp-frontend/src/runtime/browser/services/Filter.tsx +18 -0
  397. package/bundle/hkp-frontend/src/runtime/browser/services/FilterUI.tsx +89 -0
  398. package/bundle/hkp-frontend/src/runtime/browser/services/FlatMap.ts +68 -0
  399. package/bundle/hkp-frontend/src/runtime/browser/services/FlatMapUI.tsx +38 -0
  400. package/bundle/hkp-frontend/src/runtime/browser/services/GameOfLife.ts +210 -0
  401. package/bundle/hkp-frontend/src/runtime/browser/services/GameOfLifeRenderer.ts +182 -0
  402. package/bundle/hkp-frontend/src/runtime/browser/services/GameOfLifeRendererUI.tsx +48 -0
  403. package/bundle/hkp-frontend/src/runtime/browser/services/GameOfLifeUI.tsx +58 -0
  404. package/bundle/hkp-frontend/src/runtime/browser/services/GatherUI.tsx +84 -0
  405. package/bundle/hkp-frontend/src/runtime/browser/services/GifEncoder.tsx +651 -0
  406. package/bundle/hkp-frontend/src/runtime/browser/services/GithubAPI.ts +294 -0
  407. package/bundle/hkp-frontend/src/runtime/browser/services/GithubComponents.tsx +389 -0
  408. package/bundle/hkp-frontend/src/runtime/browser/services/GithubSink.tsx +109 -0
  409. package/bundle/hkp-frontend/src/runtime/browser/services/GithubSinkUI.tsx +143 -0
  410. package/bundle/hkp-frontend/src/runtime/browser/services/GithubSource.tsx +86 -0
  411. package/bundle/hkp-frontend/src/runtime/browser/services/GithubSourceUI.tsx +162 -0
  412. package/bundle/hkp-frontend/src/runtime/browser/services/GroupBy.ts +11 -0
  413. package/bundle/hkp-frontend/src/runtime/browser/services/HTTPUploader.ts +102 -0
  414. package/bundle/hkp-frontend/src/runtime/browser/services/Hacker.ts +73 -0
  415. package/bundle/hkp-frontend/src/runtime/browser/services/HackerUI.tsx +134 -0
  416. package/bundle/hkp-frontend/src/runtime/browser/services/Hash.ts +134 -0
  417. package/bundle/hkp-frontend/src/runtime/browser/services/HashUI.tsx +45 -0
  418. package/bundle/hkp-frontend/src/runtime/browser/services/Hold.ts +288 -0
  419. package/bundle/hkp-frontend/src/runtime/browser/services/HoldUI.tsx +43 -0
  420. package/bundle/hkp-frontend/src/runtime/browser/services/HttpClient.ts +558 -0
  421. package/bundle/hkp-frontend/src/runtime/browser/services/HttpClientUI.tsx +212 -0
  422. package/bundle/hkp-frontend/src/runtime/browser/services/HttpUploaderTokenInjector.ts +149 -0
  423. package/bundle/hkp-frontend/src/runtime/browser/services/IfService.ts +231 -0
  424. package/bundle/hkp-frontend/src/runtime/browser/services/IfServiceUI.tsx +50 -0
  425. package/bundle/hkp-frontend/src/runtime/browser/services/ImagePicker.tsx +118 -0
  426. package/bundle/hkp-frontend/src/runtime/browser/services/Injector.ts +61 -0
  427. package/bundle/hkp-frontend/src/runtime/browser/services/InjectorUI.tsx +389 -0
  428. package/bundle/hkp-frontend/src/runtime/browser/services/Input.ts +214 -0
  429. package/bundle/hkp-frontend/src/runtime/browser/services/InputUI.tsx +71 -0
  430. package/bundle/hkp-frontend/src/runtime/browser/services/IteratorUI.tsx +59 -0
  431. package/bundle/hkp-frontend/src/runtime/browser/services/LZCompress.ts +63 -0
  432. package/bundle/hkp-frontend/src/runtime/browser/services/Limit.ts +39 -0
  433. package/bundle/hkp-frontend/src/runtime/browser/services/LimitUI.tsx +41 -0
  434. package/bundle/hkp-frontend/src/runtime/browser/services/LocalStorage.ts +65 -0
  435. package/bundle/hkp-frontend/src/runtime/browser/services/LocalStorageUI.tsx +72 -0
  436. package/bundle/hkp-frontend/src/runtime/browser/services/Map.ts +14 -0
  437. package/bundle/hkp-frontend/src/runtime/browser/services/MapUI.tsx +334 -0
  438. package/bundle/hkp-frontend/src/runtime/browser/services/MapUIModeAssist.tsx +61 -0
  439. package/bundle/hkp-frontend/src/runtime/browser/services/MatchFilter.ts +11 -0
  440. package/bundle/hkp-frontend/src/runtime/browser/services/MicrophoneMonitor.ts +190 -0
  441. package/bundle/hkp-frontend/src/runtime/browser/services/MicrophoneMonitorUI.tsx +94 -0
  442. package/bundle/hkp-frontend/src/runtime/browser/services/Monitor.tsx +15 -0
  443. package/bundle/hkp-frontend/src/runtime/browser/services/MonitorUI.tsx +281 -0
  444. package/bundle/hkp-frontend/src/runtime/browser/services/OllamaPrompt.ts +237 -0
  445. package/bundle/hkp-frontend/src/runtime/browser/services/OllamaPromptUI/BusyIndicator.tsx +18 -0
  446. package/bundle/hkp-frontend/src/runtime/browser/services/OllamaPromptUI/PromptInput.tsx +71 -0
  447. package/bundle/hkp-frontend/src/runtime/browser/services/OllamaPromptUI/StreamingPreview.tsx +24 -0
  448. package/bundle/hkp-frontend/src/runtime/browser/services/OllamaPromptUI/index.tsx +156 -0
  449. package/bundle/hkp-frontend/src/runtime/browser/services/OpenAIPrompt.ts +254 -0
  450. package/bundle/hkp-frontend/src/runtime/browser/services/OpenAIPromptUI.tsx +189 -0
  451. package/bundle/hkp-frontend/src/runtime/browser/services/Output.tsx +145 -0
  452. package/bundle/hkp-frontend/src/runtime/browser/services/OutputUI.tsx +118 -0
  453. package/bundle/hkp-frontend/src/runtime/browser/services/PeerChatTemplate.json +43 -0
  454. package/bundle/hkp-frontend/src/runtime/browser/services/PeerConnection.ts +260 -0
  455. package/bundle/hkp-frontend/src/runtime/browser/services/PeerSocket.tsx +268 -0
  456. package/bundle/hkp-frontend/src/runtime/browser/services/PeerSocketReceiverTemplate.json +29 -0
  457. package/bundle/hkp-frontend/src/runtime/browser/services/PeerSocketUI.tsx +341 -0
  458. package/bundle/hkp-frontend/src/runtime/browser/services/PlaceholderUI.tsx +72 -0
  459. package/bundle/hkp-frontend/src/runtime/browser/services/QrCode.ts +63 -0
  460. package/bundle/hkp-frontend/src/runtime/browser/services/QrCodeUI.tsx +197 -0
  461. package/bundle/hkp-frontend/src/runtime/browser/services/Select.ts +40 -0
  462. package/bundle/hkp-frontend/src/runtime/browser/services/SelectUI.tsx +41 -0
  463. package/bundle/hkp-frontend/src/runtime/browser/services/ServiceBase.ts +50 -0
  464. package/bundle/hkp-frontend/src/runtime/browser/services/SetRuntimeVariable.ts +65 -0
  465. package/bundle/hkp-frontend/src/runtime/browser/services/SetRuntimeVariableUI.tsx +50 -0
  466. package/bundle/hkp-frontend/src/runtime/browser/services/Sign.ts +182 -0
  467. package/bundle/hkp-frontend/src/runtime/browser/services/SignUI.tsx +55 -0
  468. package/bundle/hkp-frontend/src/runtime/browser/services/Smooth.ts +101 -0
  469. package/bundle/hkp-frontend/src/runtime/browser/services/SmoothUI.tsx +50 -0
  470. package/bundle/hkp-frontend/src/runtime/browser/services/Sort.ts +59 -0
  471. package/bundle/hkp-frontend/src/runtime/browser/services/SortUI.tsx +61 -0
  472. package/bundle/hkp-frontend/src/runtime/browser/services/Sound.ts +541 -0
  473. package/bundle/hkp-frontend/src/runtime/browser/services/SoundUI.tsx +183 -0
  474. package/bundle/hkp-frontend/src/runtime/browser/services/SpeechSynth.ts +56 -0
  475. package/bundle/hkp-frontend/src/runtime/browser/services/SpeechSynthUI.tsx +108 -0
  476. package/bundle/hkp-frontend/src/runtime/browser/services/Spotify.tsx +111 -0
  477. package/bundle/hkp-frontend/src/runtime/browser/services/SpotifyAPI.ts +113 -0
  478. package/bundle/hkp-frontend/src/runtime/browser/services/SpotifyComponents.tsx +104 -0
  479. package/bundle/hkp-frontend/src/runtime/browser/services/SpotifyUI.tsx +186 -0
  480. package/bundle/hkp-frontend/src/runtime/browser/services/Spread.ts +117 -0
  481. package/bundle/hkp-frontend/src/runtime/browser/services/SpreadUI.tsx +38 -0
  482. package/bundle/hkp-frontend/src/runtime/browser/services/Sql.ts +448 -0
  483. package/bundle/hkp-frontend/src/runtime/browser/services/SqlInspector.tsx +332 -0
  484. package/bundle/hkp-frontend/src/runtime/browser/services/SqlUI.tsx +226 -0
  485. package/bundle/hkp-frontend/src/runtime/browser/services/State.ts +149 -0
  486. package/bundle/hkp-frontend/src/runtime/browser/services/Stopper.ts +29 -0
  487. package/bundle/hkp-frontend/src/runtime/browser/services/Switch.tsx +419 -0
  488. package/bundle/hkp-frontend/src/runtime/browser/services/SwitchUI.tsx +171 -0
  489. package/bundle/hkp-frontend/src/runtime/browser/services/TickRuleDelta.ts +101 -0
  490. package/bundle/hkp-frontend/src/runtime/browser/services/TickRuleDeltaUI.tsx +56 -0
  491. package/bundle/hkp-frontend/src/runtime/browser/services/Timeline.ts +709 -0
  492. package/bundle/hkp-frontend/src/runtime/browser/services/TimelineUI/TimelinePanel.tsx +79 -0
  493. package/bundle/hkp-frontend/src/runtime/browser/services/TimelineUI/TimelineRows.tsx +574 -0
  494. package/bundle/hkp-frontend/src/runtime/browser/services/TimelineUI/Transport.tsx +141 -0
  495. package/bundle/hkp-frontend/src/runtime/browser/services/TimelineUI/index.tsx +119 -0
  496. package/bundle/hkp-frontend/src/runtime/browser/services/TimelineUI/model.ts +425 -0
  497. package/bundle/hkp-frontend/src/runtime/browser/services/TimelineUI/parts.tsx +708 -0
  498. package/bundle/hkp-frontend/src/runtime/browser/services/Timer.ts +15 -0
  499. package/bundle/hkp-frontend/src/runtime/browser/services/TimerUI.tsx +213 -0
  500. package/bundle/hkp-frontend/src/runtime/browser/services/TracksUI.tsx +81 -0
  501. package/bundle/hkp-frontend/src/runtime/browser/services/TriggerPad.ts +108 -0
  502. package/bundle/hkp-frontend/src/runtime/browser/services/TriggerPadUI.tsx +160 -0
  503. package/bundle/hkp-frontend/src/runtime/browser/services/UuidGenerator.ts +74 -0
  504. package/bundle/hkp-frontend/src/runtime/browser/services/UuidGeneratorUI.tsx +59 -0
  505. package/bundle/hkp-frontend/src/runtime/browser/services/WaveformDisplay.tsx +104 -0
  506. package/bundle/hkp-frontend/src/runtime/browser/services/WebsocketClient.ts +114 -0
  507. package/bundle/hkp-frontend/src/runtime/browser/services/WebsocketClientUI.tsx +42 -0
  508. package/bundle/hkp-frontend/src/runtime/browser/services/WorkflowBoardBuilder.ts +499 -0
  509. package/bundle/hkp-frontend/src/runtime/browser/services/WorkflowBoardBuilderUI.tsx +521 -0
  510. package/bundle/hkp-frontend/src/runtime/browser/services/XYPad.ts +68 -0
  511. package/bundle/hkp-frontend/src/runtime/browser/services/XYPadUI.tsx +274 -0
  512. package/bundle/hkp-frontend/src/runtime/browser/services/audio/audioHelper.ts +36 -0
  513. package/bundle/hkp-frontend/src/runtime/browser/services/base/Batcher.ts +76 -0
  514. package/bundle/hkp-frontend/src/runtime/browser/services/base/Buffer.ts +142 -0
  515. package/bundle/hkp-frontend/src/runtime/browser/services/base/Filter.ts +125 -0
  516. package/bundle/hkp-frontend/src/runtime/browser/services/base/GroupBy.ts +101 -0
  517. package/bundle/hkp-frontend/src/runtime/browser/services/base/Map.ts +543 -0
  518. package/bundle/hkp-frontend/src/runtime/browser/services/base/MatchFilter.ts +49 -0
  519. package/bundle/hkp-frontend/src/runtime/browser/services/base/Monitor.ts +75 -0
  520. package/bundle/hkp-frontend/src/runtime/browser/services/base/Reduce.ts +74 -0
  521. package/bundle/hkp-frontend/src/runtime/browser/services/base/Timer.ts +342 -0
  522. package/bundle/hkp-frontend/src/runtime/browser/services/base/bundle.js +7375 -0
  523. package/bundle/hkp-frontend/src/runtime/browser/services/base/eval.ts +208 -0
  524. package/bundle/hkp-frontend/src/runtime/browser/services/base/expressionEval.ts +223 -0
  525. package/bundle/hkp-frontend/src/runtime/browser/services/base/helpers.ts +10 -0
  526. package/bundle/hkp-frontend/src/runtime/browser/services/base/index.ts +37 -0
  527. package/bundle/hkp-frontend/src/runtime/browser/services/camera/videoCapture.ts +78 -0
  528. package/bundle/hkp-frontend/src/runtime/browser/services/changes-modes.ts +9 -0
  529. package/bundle/hkp-frontend/src/runtime/browser/services/colorpalette.ts +26 -0
  530. package/bundle/hkp-frontend/src/runtime/browser/services/detect/DetectionPreview.tsx +76 -0
  531. package/bundle/hkp-frontend/src/runtime/browser/services/detect/mediapipe.ts +34 -0
  532. package/bundle/hkp-frontend/src/runtime/browser/services/detect/models/README.md +14 -0
  533. package/bundle/hkp-frontend/src/runtime/browser/services/detect/models/blaze_face_short_range.tflite +0 -0
  534. package/bundle/hkp-frontend/src/runtime/browser/services/detect-modes.ts +7 -0
  535. package/bundle/hkp-frontend/src/runtime/browser/services/encode-modes.ts +10 -0
  536. package/bundle/hkp-frontend/src/runtime/browser/services/helpers.ts +300 -0
  537. package/bundle/hkp-frontend/src/runtime/browser/services/http-client-methods.ts +10 -0
  538. package/bundle/hkp-frontend/src/runtime/browser/services/sql-database.ts +594 -0
  539. package/bundle/hkp-frontend/src/runtime/browser/services/sql-dump.ts +192 -0
  540. package/bundle/hkp-frontend/src/runtime/browser/services/sql-inspect.ts +130 -0
  541. package/bundle/hkp-frontend/src/runtime/browser/services/sql-inspector.css +155 -0
  542. package/bundle/hkp-frontend/src/runtime/browser/services/sql-modes.ts +27 -0
  543. package/bundle/hkp-frontend/src/runtime/browser/services/tests/TESTING_APPROACH.md +169 -0
  544. package/bundle/hkp-frontend/src/runtime/browser/services/tests/__snapshots__/service-ui.future-enhancements.test.tsx.snap +5 -0
  545. package/bundle/hkp-frontend/src/runtime/browser/services/tests/audio-input-permissions.test.ts +42 -0
  546. package/bundle/hkp-frontend/src/runtime/browser/services/tests/audiolizeLanes.test.ts +70 -0
  547. package/bundle/hkp-frontend/src/runtime/browser/services/tests/browser-scopes.test.ts +305 -0
  548. package/bundle/hkp-frontend/src/runtime/browser/services/tests/cache-service.test.ts +53 -0
  549. package/bundle/hkp-frontend/src/runtime/browser/services/tests/camera-video-capture.test.ts +24 -0
  550. package/bundle/hkp-frontend/src/runtime/browser/services/tests/changes-service.test.ts +133 -0
  551. package/bundle/hkp-frontend/src/runtime/browser/services/tests/crypto-secrets.test.ts +196 -0
  552. package/bundle/hkp-frontend/src/runtime/browser/services/tests/detect-service.test.ts +119 -0
  553. package/bundle/hkp-frontend/src/runtime/browser/services/tests/download.test.ts +100 -0
  554. package/bundle/hkp-frontend/src/runtime/browser/services/tests/expressionEval.test.ts +117 -0
  555. package/bundle/hkp-frontend/src/runtime/browser/services/tests/fetcher-panel.test.tsx +35 -0
  556. package/bundle/hkp-frontend/src/runtime/browser/services/tests/fetcher-service.test.ts +80 -0
  557. package/bundle/hkp-frontend/src/runtime/browser/services/tests/filter-service.test.ts +37 -0
  558. package/bundle/hkp-frontend/src/runtime/browser/services/tests/github-secrets.test.ts +108 -0
  559. package/bundle/hkp-frontend/src/runtime/browser/services/tests/group-by-board.test.ts +42 -0
  560. package/bundle/hkp-frontend/src/runtime/browser/services/tests/hold-service.test.ts +199 -0
  561. package/bundle/hkp-frontend/src/runtime/browser/services/tests/http-client-service.test.ts +474 -0
  562. package/bundle/hkp-frontend/src/runtime/browser/services/tests/http-uploader-token-injector.test.ts +152 -0
  563. package/bundle/hkp-frontend/src/runtime/browser/services/tests/iterator-gather-service.test.ts +277 -0
  564. package/bundle/hkp-frontend/src/runtime/browser/services/tests/map-service.test.ts +1047 -0
  565. package/bundle/hkp-frontend/src/runtime/browser/services/tests/map-ui.test.tsx +70 -0
  566. package/bundle/hkp-frontend/src/runtime/browser/services/tests/nested-rhythm-board.test.ts +311 -0
  567. package/bundle/hkp-frontend/src/runtime/browser/services/tests/night-groove-board.test.ts +290 -0
  568. package/bundle/hkp-frontend/src/runtime/browser/services/tests/pad-groove-board.test.ts +230 -0
  569. package/bundle/hkp-frontend/src/runtime/browser/services/tests/peer-host-discovery.test.ts +77 -0
  570. package/bundle/hkp-frontend/src/runtime/browser/services/tests/peer-socket-discovery.test.ts +69 -0
  571. package/bundle/hkp-frontend/src/runtime/browser/services/tests/pipeline-names.test.ts +63 -0
  572. package/bundle/hkp-frontend/src/runtime/browser/services/tests/secret-canary.test.ts +121 -0
  573. package/bundle/hkp-frontend/src/runtime/browser/services/tests/service-modules.contract.test.ts +229 -0
  574. package/bundle/hkp-frontend/src/runtime/browser/services/tests/service-ui.advanced.test.tsx +670 -0
  575. package/bundle/hkp-frontend/src/runtime/browser/services/tests/service-ui.future-enhancements.test.tsx +219 -0
  576. package/bundle/hkp-frontend/src/runtime/browser/services/tests/service-ui.integration.test.tsx +432 -0
  577. package/bundle/hkp-frontend/src/runtime/browser/services/tests/service-ui.smoke.test.tsx +140 -0
  578. package/bundle/hkp-frontend/src/runtime/browser/services/tests/sound-service.test.ts +293 -0
  579. package/bundle/hkp-frontend/src/runtime/browser/services/tests/spread-service.test.ts +137 -0
  580. package/bundle/hkp-frontend/src/runtime/browser/services/tests/sql-dump.test.ts +172 -0
  581. package/bundle/hkp-frontend/src/runtime/browser/services/tests/sql-inspect.test.ts +163 -0
  582. package/bundle/hkp-frontend/src/runtime/browser/services/tests/sql-inspector.test.tsx +121 -0
  583. package/bundle/hkp-frontend/src/runtime/browser/services/tests/sql.test.ts +702 -0
  584. package/bundle/hkp-frontend/src/runtime/browser/services/tests/sub-service-in-block.test.tsx +84 -0
  585. package/bundle/hkp-frontend/src/runtime/browser/services/tests/switch-service.test.ts +254 -0
  586. package/bundle/hkp-frontend/src/runtime/browser/services/tests/timeline-board.test.ts +218 -0
  587. package/bundle/hkp-frontend/src/runtime/browser/services/tests/timeline-service.test.ts +1002 -0
  588. package/bundle/hkp-frontend/src/runtime/browser/services/tests/timeline-ui-model.test.ts +230 -0
  589. package/bundle/hkp-frontend/src/runtime/browser/services/tests/timeline-ui.test.tsx +371 -0
  590. package/bundle/hkp-frontend/src/runtime/browser/services/tests/timer-service.test.ts +902 -0
  591. package/bundle/hkp-frontend/src/runtime/browser/services/tests/tracks-service.test.ts +275 -0
  592. package/bundle/hkp-frontend/src/runtime/browser/services/tests/tracks-ui.test.tsx +126 -0
  593. package/bundle/hkp-frontend/src/runtime/browser/services/tests/triggerpad-recorder-board.test.ts +100 -0
  594. package/bundle/hkp-frontend/src/runtime/browser/services/tests/uuid-generator-service.test.ts +214 -0
  595. package/bundle/hkp-frontend/src/runtime/browser/services/timeline-core.ts +410 -0
  596. package/bundle/hkp-frontend/src/runtime/browser/services/workflow-prompt/DefaultWorkflowBoard.ts +90 -0
  597. package/bundle/hkp-frontend/src/runtime/browser/services/workflow-prompt/RefinementSession.ts +121 -0
  598. package/bundle/hkp-frontend/src/runtime/browser/services/workflow-prompt/SystemPromptCatalog.ts +245 -0
  599. package/bundle/hkp-frontend/src/runtime/browser/services/workflow-prompt/catalog/browser-service-specs.json +110 -0
  600. package/bundle/hkp-frontend/src/runtime/browser/services/workflow-prompt/catalog/runtime-capabilities.json +37 -0
  601. package/bundle/hkp-frontend/src/runtime/browser/tests/asset-service.test.ts +73 -0
  602. package/bundle/hkp-frontend/src/runtime/browser/tests/board-persistence.integration.test.tsx +391 -0
  603. package/bundle/hkp-frontend/src/runtime/browser/tests/browser-runtime.identity-binding.test.tsx +81 -0
  604. package/bundle/hkp-frontend/src/runtime/browser/tests/demo-boards.regression.test.tsx +386 -0
  605. package/bundle/hkp-frontend/src/runtime/browser/tests/failed-call-lifecycle.test.ts +56 -0
  606. package/bundle/hkp-frontend/src/runtime/browser/tests/http-client.integration.test.ts +146 -0
  607. package/bundle/hkp-frontend/src/runtime/browser/tests/nested-run.integration.test.ts +415 -0
  608. package/bundle/hkp-frontend/src/runtime/browser/tests/replay-next.test.ts +391 -0
  609. package/bundle/hkp-frontend/src/runtime/browser/tests/rerun.test.ts +71 -0
  610. package/bundle/hkp-frontend/src/runtime/browser/tests/run-no-input.test.ts +72 -0
  611. package/bundle/hkp-frontend/src/runtime/browser/tests/runtime-add.integration.test.tsx +256 -0
  612. package/bundle/hkp-frontend/src/runtime/browser/tests/runtime-operations.integration.test.tsx +310 -0
  613. package/bundle/hkp-frontend/src/runtime/browser/tests/runtime-processing.integration.test.ts +311 -0
  614. package/bundle/hkp-frontend/src/runtime/browser/tests/runtime-removal.integration.test.tsx +454 -0
  615. package/bundle/hkp-frontend/src/runtime/browser/tests/runtime-update.identity.integration.test.tsx +145 -0
  616. package/bundle/hkp-frontend/src/runtime/browser/tests/service-operations.integration.test.tsx +511 -0
  617. package/bundle/hkp-node/src/address.ts +72 -0
  618. package/bundle/hkp-node/src/assets.ts +582 -0
  619. package/bundle/hkp-node/src/auth.ts +369 -0
  620. package/bundle/hkp-node/src/callReport.ts +281 -0
  621. package/bundle/hkp-node/src/coordinator/auth.ts +85 -0
  622. package/bundle/hkp-node/src/coordinator/binaryFrame.ts +189 -0
  623. package/bundle/hkp-node/src/coordinator/boardStore.ts +0 -0
  624. package/bundle/hkp-node/src/coordinator/bridge.ts +242 -0
  625. package/bundle/hkp-node/src/coordinator/bridgeProtocol.ts +200 -0
  626. package/bundle/hkp-node/src/coordinator/coordinator.ts +594 -0
  627. package/bundle/hkp-node/src/coordinator/facadeAccess.ts +426 -0
  628. package/bundle/hkp-node/src/coordinator/fileBoardStore.ts +159 -0
  629. package/bundle/hkp-node/src/coordinator/frameLimits.ts +41 -0
  630. package/bundle/hkp-node/src/coordinator/index.ts +12 -0
  631. package/bundle/hkp-node/src/coordinator/join.ts +62 -0
  632. package/bundle/hkp-node/src/coordinator/logStore.ts +335 -0
  633. package/bundle/hkp-node/src/coordinator/members.ts +133 -0
  634. package/bundle/hkp-node/src/coordinator/mount.ts +124 -0
  635. package/bundle/hkp-node/src/coordinator/participantProtocol.ts +169 -0
  636. package/bundle/hkp-node/src/coordinator/participants.ts +666 -0
  637. package/bundle/hkp-node/src/coordinator/router.ts +416 -0
  638. package/bundle/hkp-node/src/coordinator/session.ts +1610 -0
  639. package/bundle/hkp-node/src/coordinator/types.ts +95 -0
  640. package/bundle/hkp-node/src/coordinatorLinks.ts +712 -0
  641. package/bundle/hkp-node/src/credentialedFetch.ts +122 -0
  642. package/bundle/hkp-node/src/egress.ts +344 -0
  643. package/bundle/hkp-node/src/expressionEval.ts +223 -0
  644. package/bundle/hkp-node/src/index.ts +470 -0
  645. package/bundle/hkp-node/src/instance.ts +172 -0
  646. package/bundle/hkp-node/src/mounts.ts +296 -0
  647. package/bundle/hkp-node/src/origins.ts +201 -0
  648. package/bundle/hkp-node/src/reset.ts +187 -0
  649. package/bundle/hkp-node/src/runtime.ts +1345 -0
  650. package/bundle/hkp-node/src/secrets.ts +292 -0
  651. package/bundle/hkp-node/src/server.ts +2120 -0
  652. package/bundle/hkp-node/src/services/asset.ts +150 -0
  653. package/bundle/hkp-node/src/services/claudeModels.ts +370 -0
  654. package/bundle/hkp-node/src/services/communication-dispatcher.ts +735 -0
  655. package/bundle/hkp-node/src/services/conversations.ts +914 -0
  656. package/bundle/hkp-node/src/services/database.ts +386 -0
  657. package/bundle/hkp-node/src/services/document-extract.ts +575 -0
  658. package/bundle/hkp-node/src/services/expression.ts +417 -0
  659. package/bundle/hkp-node/src/services/fileStore.ts +369 -0
  660. package/bundle/hkp-node/src/services/filesystem.ts +280 -0
  661. package/bundle/hkp-node/src/services/gather.ts +335 -0
  662. package/bundle/hkp-node/src/services/hold.ts +275 -0
  663. package/bundle/hkp-node/src/services/http-client.ts +525 -0
  664. package/bundle/hkp-node/src/services/http-server.ts +1222 -0
  665. package/bundle/hkp-node/src/services/imap-email.ts +627 -0
  666. package/bundle/hkp-node/src/services/injector.ts +135 -0
  667. package/bundle/hkp-node/src/services/iterator.ts +203 -0
  668. package/bundle/hkp-node/src/services/join.ts +226 -0
  669. package/bundle/hkp-node/src/services/map.ts +632 -0
  670. package/bundle/hkp-node/src/services/monitor.ts +128 -0
  671. package/bundle/hkp-node/src/services/nested-pipeline.ts +405 -0
  672. package/bundle/hkp-node/src/services/peer-server.ts +268 -0
  673. package/bundle/hkp-node/src/services/queue-store.ts +301 -0
  674. package/bundle/hkp-node/src/services/queue.ts +316 -0
  675. package/bundle/hkp-node/src/services/recordStore.ts +341 -0
  676. package/bundle/hkp-node/src/services/rss-parse.ts +281 -0
  677. package/bundle/hkp-node/src/services/rss.ts +392 -0
  678. package/bundle/hkp-node/src/services/smtp-email.ts +398 -0
  679. package/bundle/hkp-node/src/services/spread.ts +153 -0
  680. package/bundle/hkp-node/src/services/sql-dump.ts +192 -0
  681. package/bundle/hkp-node/src/services/sql.ts +429 -0
  682. package/bundle/hkp-node/src/services/stopper.ts +69 -0
  683. package/bundle/hkp-node/src/services/storage.ts +332 -0
  684. package/bundle/hkp-node/src/services/store.ts +609 -0
  685. package/bundle/hkp-node/src/services/stream.ts +367 -0
  686. package/bundle/hkp-node/src/services/sub-service.ts +600 -0
  687. package/bundle/hkp-node/src/services/telegram-listener.ts +263 -0
  688. package/bundle/hkp-node/src/services/telegram-sender.ts +136 -0
  689. package/bundle/hkp-node/src/services/text-generation.ts +1249 -0
  690. package/bundle/hkp-node/src/services/timer.ts +273 -0
  691. package/bundle/hkp-node/src/services/tracks.ts +381 -0
  692. package/bundle/hkp-node/src/services/websocket-reader.ts +364 -0
  693. package/bundle/hkp-node/src/types.ts +465 -0
  694. package/bundle/hkp-node/src/wsTickets.ts +77 -0
  695. package/bundle/hkp-node/src/yas.ts +95 -0
  696. package/bundle/hkp-python/src/hkp/__init__.py +1 -0
  697. package/bundle/hkp-python/src/hkp/__main__.py +241 -0
  698. package/bundle/hkp-python/src/hkp/address.py +56 -0
  699. package/bundle/hkp-python/src/hkp/assets.py +439 -0
  700. package/bundle/hkp-python/src/hkp/auth.py +252 -0
  701. package/bundle/hkp-python/src/hkp/binary_frame.py +100 -0
  702. package/bundle/hkp-python/src/hkp/call_report.py +201 -0
  703. package/bundle/hkp-python/src/hkp/coordinator_links.py +667 -0
  704. package/bundle/hkp-python/src/hkp/data.py +118 -0
  705. package/bundle/hkp-python/src/hkp/egress.py +310 -0
  706. package/bundle/hkp-python/src/hkp/mount.py +46 -0
  707. package/bundle/hkp-python/src/hkp/mounts.py +288 -0
  708. package/bundle/hkp-python/src/hkp/origins.py +169 -0
  709. package/bundle/hkp-python/src/hkp/runtime.py +1131 -0
  710. package/bundle/hkp-python/src/hkp/secrets.py +261 -0
  711. package/bundle/hkp-python/src/hkp/server.py +1714 -0
  712. package/bundle/hkp-python/src/hkp/services/__init__.py +0 -0
  713. package/bundle/hkp-python/src/hkp/services/asset.py +123 -0
  714. package/bundle/hkp-python/src/hkp/services/audio_encode.py +190 -0
  715. package/bundle/hkp-python/src/hkp/services/expression.py +942 -0
  716. package/bundle/hkp-python/src/hkp/services/gather.py +266 -0
  717. package/bundle/hkp-python/src/hkp/services/hold.py +246 -0
  718. package/bundle/hkp-python/src/hkp/services/http_client.py +444 -0
  719. package/bundle/hkp-python/src/hkp/services/http_server.py +790 -0
  720. package/bundle/hkp-python/src/hkp/services/iterator.py +141 -0
  721. package/bundle/hkp-python/src/hkp/services/join.py +173 -0
  722. package/bundle/hkp-python/src/hkp/services/map_service.py +452 -0
  723. package/bundle/hkp-python/src/hkp/services/monitor.py +99 -0
  724. package/bundle/hkp-python/src/hkp/services/nested_pipeline.py +278 -0
  725. package/bundle/hkp-python/src/hkp/services/skill_router.py +388 -0
  726. package/bundle/hkp-python/src/hkp/services/speech_to_text.py +186 -0
  727. package/bundle/hkp-python/src/hkp/services/spread.py +114 -0
  728. package/bundle/hkp-python/src/hkp/services/stopper.py +59 -0
  729. package/bundle/hkp-python/src/hkp/services/sub_service.py +417 -0
  730. package/bundle/hkp-python/src/hkp/services/text_generation.py +498 -0
  731. package/bundle/hkp-python/src/hkp/services/text_to_speech.py +237 -0
  732. package/bundle/hkp-python/src/hkp/services/timer.py +267 -0
  733. package/bundle/hkp-python/src/hkp/services/tracks.py +251 -0
  734. package/bundle/hkp-python/src/hkp/types.py +412 -0
  735. package/bundle/hkp-python/src/hkp/ws_tickets.py +54 -0
  736. package/bundle/hkp-python/src/hkp/yas.py +192 -0
  737. package/bundle/hkp-rt/lib/CMakeLists.txt +193 -0
  738. package/bundle/hkp-rt/lib/hkp-bundles/CMakeLists.txt +55 -0
  739. package/bundle/hkp-rt/lib/hkp-bundles/plugin_register.cpp +14 -0
  740. package/bundle/hkp-rt/lib/include/app.h +201 -0
  741. package/bundle/hkp-rt/lib/include/assets.h +239 -0
  742. package/bundle/hkp-rt/lib/include/auth.h +167 -0
  743. package/bundle/hkp-rt/lib/include/call_report.h +197 -0
  744. package/bundle/hkp-rt/lib/include/coordinator_links.h +127 -0
  745. package/bundle/hkp-rt/lib/include/discovery_hooks.h +29 -0
  746. package/bundle/hkp-rt/lib/include/egress.h +379 -0
  747. package/bundle/hkp-rt/lib/include/files.h +275 -0
  748. package/bundle/hkp-rt/lib/include/log_entry.h +131 -0
  749. package/bundle/hkp-rt/lib/include/mounts.h +139 -0
  750. package/bundle/hkp-rt/lib/include/origins.h +363 -0
  751. package/bundle/hkp-rt/lib/include/registry.h +119 -0
  752. package/bundle/hkp-rt/lib/include/secrets.h +414 -0
  753. package/bundle/hkp-rt/lib/include/server.h +122 -0
  754. package/bundle/hkp-rt/lib/include/service.h +190 -0
  755. package/bundle/hkp-rt/lib/include/slot_store.h +41 -0
  756. package/bundle/hkp-rt/lib/include/standalone_config.h +253 -0
  757. package/bundle/hkp-rt/lib/include/types/audio.h +3 -0
  758. package/bundle/hkp-rt/lib/include/types/data.h +152 -0
  759. package/bundle/hkp-rt/lib/include/types/message.h +56 -0
  760. package/bundle/hkp-rt/lib/include/types/models.h +154 -0
  761. package/bundle/hkp-rt/lib/include/types/owns_me.h +19 -0
  762. package/bundle/hkp-rt/lib/include/types/ringbuffer.h +86 -0
  763. package/bundle/hkp-rt/lib/include/types/types.h +33 -0
  764. package/bundle/hkp-rt/lib/include/types/validation.h +205 -0
  765. package/bundle/hkp-rt/lib/src/address.h +88 -0
  766. package/bundle/hkp-rt/lib/src/app.cpp +591 -0
  767. package/bundle/hkp-rt/lib/src/assets.cpp +686 -0
  768. package/bundle/hkp-rt/lib/src/binary_frame.h +154 -0
  769. package/bundle/hkp-rt/lib/src/common/expression.h +1481 -0
  770. package/bundle/hkp-rt/lib/src/common/inja.h +36 -0
  771. package/bundle/hkp-rt/lib/src/common/link_socket.cpp +437 -0
  772. package/bundle/hkp-rt/lib/src/common/link_socket.h +97 -0
  773. package/bundle/hkp-rt/lib/src/common/realtime_wakeup.h +120 -0
  774. package/bundle/hkp-rt/lib/src/common/stream_broadcast.h +250 -0
  775. package/bundle/hkp-rt/lib/src/common/string_util.h +31 -0
  776. package/bundle/hkp-rt/lib/src/common/websocket_client_session.cpp +217 -0
  777. package/bundle/hkp-rt/lib/src/common/websocket_client_session.h +37 -0
  778. package/bundle/hkp-rt/lib/src/common/websocket_config.h +119 -0
  779. package/bundle/hkp-rt/lib/src/common/websocket_protocol.h +32 -0
  780. package/bundle/hkp-rt/lib/src/common/websocket_server.cpp +82 -0
  781. package/bundle/hkp-rt/lib/src/common/websocket_server.h +57 -0
  782. package/bundle/hkp-rt/lib/src/common/websocket_server_listener.cpp +115 -0
  783. package/bundle/hkp-rt/lib/src/common/websocket_server_listener.h +35 -0
  784. package/bundle/hkp-rt/lib/src/common/websocket_server_session.cpp +172 -0
  785. package/bundle/hkp-rt/lib/src/common/websocket_server_session.h +53 -0
  786. package/bundle/hkp-rt/lib/src/common.h +13 -0
  787. package/bundle/hkp-rt/lib/src/coordinator_links.cpp +1142 -0
  788. package/bundle/hkp-rt/lib/src/discovery/discovery.cpp +707 -0
  789. package/bundle/hkp-rt/lib/src/discovery/discovery.h +89 -0
  790. package/bundle/hkp-rt/lib/src/http/auth.cpp +569 -0
  791. package/bundle/hkp-rt/lib/src/http/front_door.cpp +518 -0
  792. package/bundle/hkp-rt/lib/src/http/front_door.h +131 -0
  793. package/bundle/hkp-rt/lib/src/http/server.cpp +1539 -0
  794. package/bundle/hkp-rt/lib/src/http/ws_tickets.h +125 -0
  795. package/bundle/hkp-rt/lib/src/mount.h +63 -0
  796. package/bundle/hkp-rt/lib/src/process_context.h +335 -0
  797. package/bundle/hkp-rt/lib/src/process_depth.h +20 -0
  798. package/bundle/hkp-rt/lib/src/registry.cpp +233 -0
  799. package/bundle/hkp-rt/lib/src/runtime.cpp +986 -0
  800. package/bundle/hkp-rt/lib/src/runtime.h +225 -0
  801. package/bundle/hkp-rt/lib/src/runtime_host.h +180 -0
  802. package/bundle/hkp-rt/lib/src/service.cpp +315 -0
  803. package/bundle/hkp-rt/lib/src/services/asset.h +185 -0
  804. package/bundle/hkp-rt/lib/src/services/audio_encode.cpp +372 -0
  805. package/bundle/hkp-rt/lib/src/services/audio_encode.h +97 -0
  806. package/bundle/hkp-rt/lib/src/services/cache.h +362 -0
  807. package/bundle/hkp-rt/lib/src/services/cache_subservices.h +463 -0
  808. package/bundle/hkp-rt/lib/src/services/core_audio.cpp +384 -0
  809. package/bundle/hkp-rt/lib/src/services/core_audio.h +22 -0
  810. package/bundle/hkp-rt/lib/src/services/core_input.h +337 -0
  811. package/bundle/hkp-rt/lib/src/services/core_output.h +370 -0
  812. package/bundle/hkp-rt/lib/src/services/ffmpeg/detail.cpp +749 -0
  813. package/bundle/hkp-rt/lib/src/services/ffmpeg/detail.h +29 -0
  814. package/bundle/hkp-rt/lib/src/services/ffmpeg/ffmpeg.cpp +193 -0
  815. package/bundle/hkp-rt/lib/src/services/ffmpeg/ffmpeg.h +40 -0
  816. package/bundle/hkp-rt/lib/src/services/ffmpeg/types.h +120 -0
  817. package/bundle/hkp-rt/lib/src/services/fft_service.h +190 -0
  818. package/bundle/hkp-rt/lib/src/services/filesystem.h +309 -0
  819. package/bundle/hkp-rt/lib/src/services/filter.h +208 -0
  820. package/bundle/hkp-rt/lib/src/services/gather.h +272 -0
  821. package/bundle/hkp-rt/lib/src/services/hold.h +304 -0
  822. package/bundle/hkp-rt/lib/src/services/http_client.cpp +347 -0
  823. package/bundle/hkp-rt/lib/src/services/http_client.h +67 -0
  824. package/bundle/hkp-rt/lib/src/services/http_client_impl.h +45 -0
  825. package/bundle/hkp-rt/lib/src/services/http_client_template_examples.md +202 -0
  826. package/bundle/hkp-rt/lib/src/services/http_server/http_listener.cpp +130 -0
  827. package/bundle/hkp-rt/lib/src/services/http_server/http_listener.h +33 -0
  828. package/bundle/hkp-rt/lib/src/services/http_server/http_server.cpp +351 -0
  829. package/bundle/hkp-rt/lib/src/services/http_server/http_server.h +79 -0
  830. package/bundle/hkp-rt/lib/src/services/http_server/http_server_impl.cpp +230 -0
  831. package/bundle/hkp-rt/lib/src/services/http_server/http_server_impl.h +64 -0
  832. package/bundle/hkp-rt/lib/src/services/http_server/http_server_subservices.cpp +950 -0
  833. package/bundle/hkp-rt/lib/src/services/http_server/http_server_subservices.h +253 -0
  834. package/bundle/hkp-rt/lib/src/services/http_server/http_session.cpp +495 -0
  835. package/bundle/hkp-rt/lib/src/services/http_server/http_session.h +169 -0
  836. package/bundle/hkp-rt/lib/src/services/http_server/http_stream_listener.cpp +270 -0
  837. package/bundle/hkp-rt/lib/src/services/http_server/http_stream_listener.h +104 -0
  838. package/bundle/hkp-rt/lib/src/services/http_server/request_decode.h +164 -0
  839. package/bundle/hkp-rt/lib/src/services/if_service.h +210 -0
  840. package/bundle/hkp-rt/lib/src/services/ifft_service.h +198 -0
  841. package/bundle/hkp-rt/lib/src/services/injector.h +162 -0
  842. package/bundle/hkp-rt/lib/src/services/iterator.h +201 -0
  843. package/bundle/hkp-rt/lib/src/services/map.h +641 -0
  844. package/bundle/hkp-rt/lib/src/services/monitor.h +161 -0
  845. package/bundle/hkp-rt/lib/src/services/mp3_frames.h +119 -0
  846. package/bundle/hkp-rt/lib/src/services/mp4_to_wav.cpp +281 -0
  847. package/bundle/hkp-rt/lib/src/services/mp4_to_wav.h +263 -0
  848. package/bundle/hkp-rt/lib/src/services/peer_server/peer_registry.cpp +88 -0
  849. package/bundle/hkp-rt/lib/src/services/peer_server/peer_registry.h +43 -0
  850. package/bundle/hkp-rt/lib/src/services/peer_server/peer_server.h +163 -0
  851. package/bundle/hkp-rt/lib/src/services/peer_server/peer_server_listener.cpp +133 -0
  852. package/bundle/hkp-rt/lib/src/services/peer_server/peer_server_listener.h +46 -0
  853. package/bundle/hkp-rt/lib/src/services/peer_server/peer_server_session.cpp +165 -0
  854. package/bundle/hkp-rt/lib/src/services/peer_server/peer_server_session.h +44 -0
  855. package/bundle/hkp-rt/lib/src/services/peer_server/peer_ws_session.cpp +184 -0
  856. package/bundle/hkp-rt/lib/src/services/peer_server/peer_ws_session.h +55 -0
  857. package/bundle/hkp-rt/lib/src/services/receiver_session.h +172 -0
  858. package/bundle/hkp-rt/lib/src/services/root_certificates.h +3959 -0
  859. package/bundle/hkp-rt/lib/src/services/sha256_openssl.h +49 -0
  860. package/bundle/hkp-rt/lib/src/services/speech_http.h +306 -0
  861. package/bundle/hkp-rt/lib/src/services/speech_to_text.cpp +595 -0
  862. package/bundle/hkp-rt/lib/src/services/speech_to_text.h +69 -0
  863. package/bundle/hkp-rt/lib/src/services/static.cpp +45 -0
  864. package/bundle/hkp-rt/lib/src/services/static.h +41 -0
  865. package/bundle/hkp-rt/lib/src/services/stopper.h +72 -0
  866. package/bundle/hkp-rt/lib/src/services/sub_service.h +286 -0
  867. package/bundle/hkp-rt/lib/src/services/text_generation.cpp +1132 -0
  868. package/bundle/hkp-rt/lib/src/services/text_generation.h +71 -0
  869. package/bundle/hkp-rt/lib/src/services/text_to_speech.cpp +853 -0
  870. package/bundle/hkp-rt/lib/src/services/text_to_speech.h +97 -0
  871. package/bundle/hkp-rt/lib/src/services/timer.h +300 -0
  872. package/bundle/hkp-rt/lib/src/services/tracks.h +397 -0
  873. package/bundle/hkp-rt/lib/src/services/tts_inflect.cpp +149 -0
  874. package/bundle/hkp-rt/lib/src/services/tts_inflect.h +82 -0
  875. package/bundle/hkp-rt/lib/src/services/wav_reader.h +213 -0
  876. package/bundle/hkp-rt/lib/src/services/websocket_client_service.h +182 -0
  877. package/bundle/hkp-rt/lib/src/services/websocket_reader.h +148 -0
  878. package/bundle/hkp-rt/lib/src/services/websocket_server_service.h +196 -0
  879. package/bundle/hkp-rt/lib/src/services/websocket_writer.cpp +641 -0
  880. package/bundle/hkp-rt/lib/src/services/websocket_writer.h +117 -0
  881. package/bundle/hkp-rt/lib/src/sub_runtime.cpp +234 -0
  882. package/bundle/hkp-rt/lib/src/sub_runtime.h +160 -0
  883. package/bundle/hkp-rt/lib/src/types/data.cpp +237 -0
  884. package/bundle/hkp-rt/lib/src/types/message.cpp +148 -0
  885. package/bundle/hkp-rt/lib/src/types/models.cpp +83 -0
  886. package/bundle/hkp-rt/lib/src/types/ringbuffer.cpp +336 -0
  887. package/bundle/hkp-rt/lib/src/uuid.h +12 -0
  888. package/bundle/readymade-mcp-commits.json +5 -0
  889. package/dist/cli.d.ts +3 -0
  890. package/dist/cli.d.ts.map +1 -0
  891. package/dist/cli.js +79 -0
  892. package/dist/git.d.ts +8 -0
  893. package/dist/git.d.ts.map +1 -0
  894. package/dist/git.js +53 -0
  895. package/dist/http.d.ts +17 -0
  896. package/dist/http.d.ts.map +1 -0
  897. package/dist/http.js +130 -0
  898. package/dist/index.d.ts +7 -0
  899. package/dist/index.d.ts.map +1 -0
  900. package/dist/index.js +6 -0
  901. package/dist/repository.d.ts +49 -0
  902. package/dist/repository.d.ts.map +1 -0
  903. package/dist/repository.js +452 -0
  904. package/dist/schemas.d.ts +156 -0
  905. package/dist/schemas.d.ts.map +1 -0
  906. package/dist/schemas.js +43 -0
  907. package/dist/server.d.ts +4 -0
  908. package/dist/server.d.ts.map +1 -0
  909. package/dist/server.js +83 -0
  910. package/dist/types.d.ts +63 -0
  911. package/dist/types.d.ts.map +1 -0
  912. package/dist/types.js +20 -0
  913. package/dist/validation.d.ts +9 -0
  914. package/dist/validation.d.ts.map +1 -0
  915. package/dist/validation.js +101 -0
  916. package/package.json +52 -4
@@ -0,0 +1,1610 @@
1
+ import { WebSocket } from "ws";
2
+ import { randomUUID } from "crypto";
3
+ import {
4
+ CloudBoardConfig,
5
+ CloudRuntimeDescriptor,
6
+ BoardSessionStatus,
7
+ isRemoteRuntime,
8
+ isBrowserRuntime,
9
+ } from "./types";
10
+ import { LogStore } from "./logStore";
11
+ import {
12
+ BinaryPayload,
13
+ decodeBinaryFrame,
14
+ encodeBinaryFrame,
15
+ frameBytes,
16
+ } from "./binaryFrame";
17
+ import { Caller, LogEntry, LogLevel, ProcessContext } from "../types";
18
+ import { MOUNT_FIELD, collectMountRefs, formatMountRef } from "./mount";
19
+ import {
20
+ BridgeMessage,
21
+ RuntimeSnapshot,
22
+ ServiceStates,
23
+ isBridgeMessage,
24
+ } from "./bridgeProtocol";
25
+ import {
26
+ BoardParticipants,
27
+ NO_PARTICIPANTS,
28
+ Participant,
29
+ ParticipantEvent,
30
+ ProvisionResult,
31
+ ReportedService,
32
+ } from "./participantProtocol";
33
+ import { AssetDescriptor } from "../assets";
34
+ import {
35
+ FacadeAccess,
36
+ projectConfig,
37
+ projectNotification,
38
+ projectState,
39
+ readFacadeAccess,
40
+ runtimeHolding,
41
+ } from "./facadeAccess";
42
+ import { DEFAULT_MEMBER_LIMITS, MemberLimits } from "./members";
43
+ import { frameLimitFrom } from "./frameLimits";
44
+
45
+ /**
46
+ * What a browser attached to a board is to it.
47
+ *
48
+ * The **owner** deployed the board: their bridge is sent the board whole, may
49
+ * configure its services, and hosts its browser runtimes. A **member** is
50
+ * somebody the board is shared with: their bridge is sent the facade and what
51
+ * the facade reads, and may ask for exactly what the facade asks for.
52
+ */
53
+ export type BridgeRole = "owner" | "member";
54
+
55
+ /** Who attached, as whoever admitted them established it. */
56
+ export type BridgeAttach = {
57
+ role: BridgeRole;
58
+ /** Absent where there is no identity to state — a coordinator without auth. */
59
+ caller?: Caller;
60
+ };
61
+
62
+ type BrowserBridge = BridgeAttach & {
63
+ ws: WebSocket;
64
+ runtimeIds: Set<string>;
65
+ // Each browser is told the board in its own sequence: what a member is sent
66
+ // is a subset of what the owner is, and a shared count would read to them as
67
+ // increments going missing.
68
+ seq: number;
69
+ };
70
+
71
+ /** Closing a member's bridge because the board is no longer shared with them.
72
+ * Final: a client that reconnected would only be refused. */
73
+ export const CLOSE_NOT_A_MEMBER = 4403;
74
+
75
+ /** Machinery a runtime reports about its own flow, carrying the data passing
76
+ * through. A facade ignores it, so a member is not sent it. */
77
+ function isFlowNotification(payload: unknown): boolean {
78
+ return (
79
+ !!payload &&
80
+ typeof payload === "object" &&
81
+ "__internal" in (payload as Record<string, unknown>)
82
+ );
83
+ }
84
+
85
+ /**
86
+ * One board, owned by this coordinator.
87
+ *
88
+ * The session dials nothing. Every runtime the board has is run by a
89
+ * *participant* that connected in — a runtime server holding a ticket for that
90
+ * runtime, or a browser attached over the bridge — and the session builds,
91
+ * configures and drives the board over those connections.
92
+ *
93
+ * Participants are of two kinds, told apart by the runtime's type:
94
+ *
95
+ * - **Required** — a remote runtime. The board cannot run without it, so one
96
+ * that is not connected puts the board in `error`, naming it. That is not
97
+ * terminal: when its runtime server connects, the runtime is built from the
98
+ * board's config and the board goes back to `running`.
99
+ * - **Transient** — a browser runtime, run by whichever browser has the board
100
+ * open. Browsers come and go as a matter of course, so data arriving for one
101
+ * that is away stops there, the way a `null` stops a pipeline, and the board
102
+ * stays `running`.
103
+ */
104
+ export class BoardSession {
105
+ readonly createdAt: string;
106
+ // Set while the board is not being run: stopped by its owner, or restored
107
+ // from a store that says it was.
108
+ private stopped = false;
109
+ // Why each runtime is not as the board wants it, by runtime id.
110
+ private readonly runtimeErrors = new Map<string, string>();
111
+ // What stopping could not release. Kept apart from the above because it
112
+ // describes the run that ended, not the one being attempted.
113
+ private residue: string[] = [];
114
+ // The runtimes that produced `residue`, by id. A stopped board only removes
115
+ // one that reconnects when it knows that runtime is a leftover from its own
116
+ // run. An editor may legitimately recreate the same runtime id after Stop;
117
+ // treating every runtime seen while stopped as residue would delete the
118
+ // editor's live work during the next deploy introduction.
119
+ private readonly pendingRelease = new Set<string>();
120
+ // Runtimes this session has built or picked back up, whose participant is
121
+ // connected right now.
122
+ private readonly live = new Set<string>();
123
+ // Runtimes this session built at some point. One whose participant dropped
124
+ // and came back with the runtime still running is picked up, not rebuilt.
125
+ private readonly built = new Set<string>();
126
+ // Capture mode: whether this board's runtimes send their call reports with
127
+ // the values in them. The owner's, switched while attached, and held here
128
+ // only — never in the config, never persisted — so a restart or a redeploy
129
+ // turns it off. What passes inside a run is private unless someone building
130
+ // the board asked to see it (plans/TODO-CAPTURE-MODE.md).
131
+ private capture = false;
132
+ // Every browser currently viewing this board has its own bridge. Multiple
133
+ // clients (e.g. Readymade + a browser tab) can watch the same board at once;
134
+ // runtime output is fanned out to all of them.
135
+ private readonly bridges = new Set<BrowserBridge>();
136
+ private readonly pendingBrowserResults = new Map<
137
+ string,
138
+ { resolve: (data: unknown) => void; timer: ReturnType<typeof setTimeout> }
139
+ >();
140
+ // Addresses services published for the mounts they own, keyed
141
+ // "runtimeId/serviceUuid". This session coordinates the board, so it is what
142
+ // turns a reference into the address it names — no runtime can see far enough
143
+ // to do it for itself.
144
+ private readonly mountAddresses = new Map<string, string>();
145
+ // The address last handed to each consumer, keyed the same way. Guards
146
+ // against re-configuring a service with what it already has, while still
147
+ // letting a *changed* address through.
148
+ private readonly pushedStates = new Map<string, string>();
149
+ // What each remote runtime's services last reported, and what that runtime
150
+ // says it can run. This is the board as the coordinator knows it, and what an
151
+ // attached browser renders from — it owns none of it itself.
152
+ private readonly serviceStates = new Map<string, ServiceStates>();
153
+ private readonly registries = new Map<string, unknown[]>();
154
+ // What the board's facade lets a member do and see. The config is fixed for
155
+ // the life of a session — deploying again makes a new one — so it is read
156
+ // once.
157
+ private readonly access: FacadeAccess;
158
+ // When each member last asked for something to be processed, by email, for
159
+ // the rate that bounds them.
160
+ private readonly memberProcessTimes = new Map<string, number[]>();
161
+ // Building, releasing and picking up runtimes happen one at a time: a
162
+ // participant arriving while the board is starting must not be built twice.
163
+ private queue: Promise<unknown> = Promise.resolve();
164
+ private unsubscribe: (() => void) | null = null;
165
+ private destroyed = false;
166
+
167
+ constructor(
168
+ readonly boardName: string,
169
+ readonly userId: string,
170
+ readonly config: CloudBoardConfig,
171
+ // The runtime servers connected for this board, and word of their coming
172
+ // and going. Supplied by the coordinator, which holds the tickets.
173
+ private readonly participants: BoardParticipants = NO_PARTICIPANTS,
174
+ // Set when the board comes back from a store rather than from a deploy. It
175
+ // keeps the date the board was first registered, and whether it was left
176
+ // stopped. See docs/content/concepts/cloud-boards.md.
177
+ restored?: { createdAt: string; stopped: boolean },
178
+ // Where this board's entries are kept. Absent means nothing is collected —
179
+ // a session in a test, or a coordinator configured without a log root.
180
+ private readonly logStore?: LogStore,
181
+ // The largest frame a browser may send that is passed on; unset means no
182
+ // limit. The same setting bounds what a runtime server sends, where the
183
+ // participants are accepted.
184
+ private readonly limits: { maxFrameBytes?: number } & MemberLimits = {},
185
+ // Checked at every coordinator boundary. The coordinator owns membership,
186
+ // so runtimes cannot decide whether a captured person is still entitled.
187
+ private readonly mayActAs: (caller: Caller) => boolean = () => true,
188
+ ) {
189
+ this.createdAt = restored?.createdAt ?? new Date().toISOString();
190
+ this.stopped = restored?.stopped ?? false;
191
+ this.access = readFacadeAccess(config.facade);
192
+ }
193
+
194
+ /** The runtimes this board cannot run without, in chain order. */
195
+ private required(): CloudRuntimeDescriptor[] {
196
+ return this.config.runtimes.filter(isRemoteRuntime);
197
+ }
198
+
199
+ private serial<T>(work: () => Promise<T>): Promise<T> {
200
+ const run = this.queue.then(work, work);
201
+ this.queue = run.catch(() => undefined);
202
+ return run;
203
+ }
204
+
205
+ /**
206
+ * Runs the board: builds every required runtime whose participant is
207
+ * connected, and from then on builds the others as they arrive.
208
+ *
209
+ * A participant that is not connected is not waited for. The board says
210
+ * which one is missing and comes up when it does.
211
+ */
212
+ async start(): Promise<void> {
213
+ await this.serial(async () => {
214
+ this.stopped = false;
215
+ this.residue = [];
216
+ this.pendingRelease.clear();
217
+ this.unsubscribe ??= this.participants.subscribe({
218
+ onJoin: (participant) => this.onJoin(participant),
219
+ onLeave: (runtimeId) => this.onLeave(runtimeId),
220
+ });
221
+
222
+ for (const runtime of this.required()) {
223
+ const participant = this.participants.get(runtime.id);
224
+ if (participant) {
225
+ await this.bringUp(runtime, participant);
226
+ }
227
+ }
228
+
229
+ // Runtimes are built one by one, so a service pointed at a mount on a
230
+ // runtime built later cannot have been resolved as it was created. Hand
231
+ // out the addresses once the whole board exists.
232
+ await this.publishMountAddresses();
233
+ });
234
+ this.broadcastSnapshot();
235
+ }
236
+
237
+ /** Whether the board was left stopped, for a store to remember. */
238
+ isStopped(): boolean {
239
+ return this.stopped;
240
+ }
241
+
242
+ getStatus(): BoardSessionStatus {
243
+ if (this.stopped) {
244
+ return "stopped";
245
+ }
246
+ return this.getErrors().length > 0 ? "error" : "running";
247
+ }
248
+
249
+ getErrors(): string[] {
250
+ if (this.stopped) {
251
+ return [...this.residue];
252
+ }
253
+ const errors: string[] = [];
254
+ for (const runtime of this.required()) {
255
+ const reason = this.runtimeErrors.get(runtime.id);
256
+ if (reason) {
257
+ errors.push(`Runtime "${runtime.id}": ${reason}`);
258
+ } else if (!this.live.has(runtime.id)) {
259
+ errors.push(
260
+ `Runtime "${runtime.id}" is not connected — its runtime server has to connect to this coordinator`,
261
+ );
262
+ }
263
+ }
264
+ return errors;
265
+ }
266
+
267
+ /**
268
+ * Builds a runtime on the participant that connected for it, or picks it
269
+ * back up when that participant only dropped its connection and still has
270
+ * the runtime this session built.
271
+ */
272
+ private async bringUp(
273
+ runtime: CloudRuntimeDescriptor,
274
+ participant: Participant,
275
+ ): Promise<void> {
276
+ const { id } = runtime;
277
+ try {
278
+ let services: ReportedService[] | undefined;
279
+ if (this.built.has(id) && participant.hello.runtimeExists) {
280
+ // Its state is live and the board's is not: rebuilding would throw
281
+ // away what the runtime has been doing.
282
+ services = (
283
+ await participant.request<{ services?: ReportedService[] }>({
284
+ op: "describe",
285
+ })
286
+ )?.services;
287
+ this.registries.set(id, participant.hello.registry);
288
+ // Whatever stopped it from being picked up last time no longer does.
289
+ // What it was built without is still missing, and still said.
290
+ if (!this.runtimeErrors.get(id)?.startsWith("needs configuration")) {
291
+ this.runtimeErrors.delete(id);
292
+ }
293
+ } else {
294
+ const result = await participant.request<ProvisionResult>({
295
+ op: "provision",
296
+ name: runtime.name,
297
+ boardName: this.boardName,
298
+ // The board's own settings for this runtime — `logData`, and
299
+ // whatever a runtime reads from its state later.
300
+ state: runtime.state ?? {},
301
+ services: (this.config.services[id] ?? []).map((svc) => ({
302
+ uuid: svc.uuid,
303
+ serviceId: svc.serviceId,
304
+ serviceName: svc.serviceName ?? svc.name ?? svc.serviceId,
305
+ state: svc.state ?? {},
306
+ })),
307
+ // Every asset this runtime is given, named by a service or not.
308
+ // Host-local content cannot travel this way — a `file://` source is
309
+ // read by the runtime, inside its own volumes.
310
+ assets: this.assetsFor(runtime),
311
+ });
312
+ this.registries.set(
313
+ id,
314
+ Array.isArray(result?.registry)
315
+ ? result.registry
316
+ : participant.hello.registry,
317
+ );
318
+ services =
319
+ result?.services ??
320
+ (
321
+ await participant.request<{ services?: ReportedService[] }>({
322
+ op: "describe",
323
+ })
324
+ )?.services;
325
+ // A newly built runtime has been told nothing yet, so whatever was
326
+ // handed to the one before it has to be handed over again.
327
+ for (const svc of this.config.services[id] ?? []) {
328
+ this.pushedStates.delete(
329
+ formatMountRef({ runtimeId: id, serviceUuid: svc.uuid }),
330
+ );
331
+ }
332
+ this.built.add(id);
333
+ // Rebuilt while the board is captured: it starts without Capture mode,
334
+ // as every runtime does, so it is told again. Not part of what it is
335
+ // built from, which a board document could otherwise switch on.
336
+ if (this.capture) {
337
+ await participant
338
+ .request({ op: "setState", state: { capture: true } })
339
+ .catch(() => {
340
+ // It runs without; its frames show activity but no values.
341
+ });
342
+ }
343
+ if (result?.missingSecrets?.length) {
344
+ // Built, and unable to do its job: say what is missing and where,
345
+ // rather than leave it to surface as a failed login hours later.
346
+ this.runtimeErrors.set(
347
+ id,
348
+ `needs configuration — its runtime server holds no value for ${result.missingSecrets.join(", ")}`,
349
+ );
350
+ } else {
351
+ this.runtimeErrors.delete(id);
352
+ }
353
+ }
354
+
355
+ this.recordServices(id, services ?? []);
356
+ participant.listen((event) => this.onParticipantEvent(id, event));
357
+ this.live.add(id);
358
+ } catch (err) {
359
+ const reason = err instanceof Error ? err.message : String(err);
360
+ this.live.delete(id);
361
+ this.runtimeErrors.set(id, reason);
362
+ console.error(
363
+ `[coordinator] Failed to provision runtime "${id}" for board "${this.boardName}":`,
364
+ reason,
365
+ );
366
+ }
367
+ }
368
+
369
+ /** A required participant connected, now or again. */
370
+ private onJoin(participant: Participant): void {
371
+ const runtime = this.required().find((rt) => rt.id === participant.runtimeId);
372
+ void this.serial(async () => {
373
+ if (this.destroyed) {
374
+ return;
375
+ }
376
+ if (!runtime) {
377
+ // The board no longer contains this runtime. Its ticket is revoked by
378
+ // the coordinator immediately after the old session is destroyed, but
379
+ // a connection already arriving is still made harmless here.
380
+ if (participant.hello.runtimeExists) {
381
+ await participant.request({ op: "remove" }).catch(() => undefined);
382
+ }
383
+ return;
384
+ }
385
+ if (this.stopped) {
386
+ // Only a runtime this session failed to release is ours to remove. A
387
+ // stopped board is also the normal state while a browser edits it, and
388
+ // that browser may have recreated the same runtime id on this server.
389
+ if (
390
+ participant.hello.runtimeExists &&
391
+ this.pendingRelease.has(participant.runtimeId)
392
+ ) {
393
+ try {
394
+ await participant.request({ op: "remove" });
395
+ this.pendingRelease.delete(participant.runtimeId);
396
+ this.residue = this.residue.filter(
397
+ (line) => !line.startsWith(`Runtime "${participant.runtimeId}"`),
398
+ );
399
+ } catch {
400
+ // Still residue; the status continues to say so.
401
+ }
402
+ }
403
+ return;
404
+ }
405
+ await this.bringUp(runtime, participant);
406
+ await this.publishMountAddresses();
407
+ })
408
+ .catch((err) => {
409
+ console.error(
410
+ `[coordinator] Failed to take runtime "${participant.runtimeId}" back into board "${this.boardName}":`,
411
+ err instanceof Error ? err.message : err,
412
+ );
413
+ })
414
+ .finally(() => this.broadcastSnapshot());
415
+ }
416
+
417
+ /** A required participant's connection went away. */
418
+ private onLeave(runtimeId: string): void {
419
+ if (!this.live.delete(runtimeId)) {
420
+ return;
421
+ }
422
+ console.log(
423
+ `[coordinator] Runtime "${runtimeId}" of board "${this.boardName}" disconnected`,
424
+ );
425
+ this.broadcastSnapshot();
426
+ }
427
+
428
+ private onParticipantEvent(runtimeId: string, event: ParticipantEvent): void {
429
+ // An entry from one of this board's runtimes. It is written before it is
430
+ // forwarded: a browser may or may not be attached, and the log is for the
431
+ // case where none is.
432
+ if (event.type === "log") {
433
+ if (event.entry) {
434
+ this.logStore?.append(this.userId, this.boardName, event.entry);
435
+ // The board's log is its owner's: an entry names services, runs and
436
+ // callers a member has no business seeing.
437
+ const caller = event.entry.caller
438
+ ? this.knownCallers.get(event.entry.caller)
439
+ : undefined;
440
+ this.toOwners({
441
+ type: "log",
442
+ entry: event.entry,
443
+ ...(caller ? { caller } : {}),
444
+ });
445
+ }
446
+ return;
447
+ }
448
+
449
+ // A service publishes the address of a mount it owns through a
450
+ // notification — when it is unbypassed at runtime, say, long after the
451
+ // board loaded. Whoever is waiting on that address learns of it here.
452
+ if (event.type === "notification") {
453
+ if (event.serviceUuid) {
454
+ this.onRuntimeNotification(
455
+ runtimeId,
456
+ event.serviceUuid,
457
+ event.payload,
458
+ event.context,
459
+ );
460
+ }
461
+ return;
462
+ }
463
+
464
+ if (event.data === null || event.data === undefined) {
465
+ return;
466
+ }
467
+ this.routeResult(runtimeId, event.data, event.context).catch((err) => {
468
+ console.error(
469
+ `[coordinator] Failed to route result from runtime "${runtimeId}":`,
470
+ err instanceof Error ? err.message : err,
471
+ );
472
+ });
473
+ }
474
+
475
+ /** Takes in what a runtime's services report: their state, and any mounts. */
476
+ private recordServices(runtimeId: string, services: ReportedService[]): void {
477
+ const states: ServiceStates = {};
478
+ for (const svc of services) {
479
+ if (!svc?.uuid) {
480
+ continue;
481
+ }
482
+ states[svc.uuid] = svc.state;
483
+ const published = svc.state?.[MOUNT_FIELD];
484
+ if (typeof published === "string" && published) {
485
+ this.recordMountAddress(runtimeId, svc.uuid, published);
486
+ }
487
+ }
488
+ this.serviceStates.set(runtimeId, states);
489
+ }
490
+
491
+ /**
492
+ * Hands the board's runtimes back without giving up the board.
493
+ *
494
+ * This is what editing does: the browser takes the runtimes over and
495
+ * provisions them itself, so the coordinator must not still hold them — but
496
+ * the board keeps its place in the coordinator's list and keeps its config,
497
+ * because a board being edited must not be a board that can be lost.
498
+ * Attached browsers stay attached and are told the board is now empty.
499
+ */
500
+ async stop(): Promise<void> {
501
+ await this.serial(async () => {
502
+ const unreleased = await this.teardownRuntimes();
503
+ this.live.clear();
504
+ this.built.clear();
505
+ this.runtimeErrors.clear();
506
+ this.mountAddresses.clear();
507
+ this.pushedStates.clear();
508
+ this.serviceStates.clear();
509
+ this.registries.clear();
510
+ // Whatever went wrong starting the board is history now; what is worth
511
+ // carrying is what would not let go.
512
+ this.residue = unreleased;
513
+ this.stopped = true;
514
+ });
515
+ this.broadcastSnapshot();
516
+ }
517
+
518
+ /**
519
+ * Turn this board's logging on or off, on the runtimes already running.
520
+ *
521
+ * Applied live rather than by re-provisioning: the setting is one a board
522
+ * revisits — on to look into something, off again afterwards — and rebuilding
523
+ * every runtime to carry a boolean would restart the board to change its mind.
524
+ *
525
+ * The config is updated too, so the setting survives a restart and so a
526
+ * runtime provisioned later comes up with it already on. Returns the runtimes
527
+ * it could not reach; they keep whatever they had, which is why the caller is
528
+ * told rather than left to assume it took.
529
+ */
530
+ async setLogging(enabled: boolean, level: LogLevel = "info"): Promise<string[]> {
531
+ for (const runtime of this.config.runtimes) {
532
+ // Only what this switch is for. `logData` is not touched: what a board
533
+ // records about its own flow and what it is willing to write of the data
534
+ // passing through are different decisions, and a control that quietly
535
+ // did both would turn a verbosity setting into an exposure one.
536
+ runtime.state = {
537
+ ...(runtime.state ?? {}),
538
+ logging: enabled,
539
+ logLevel: level,
540
+ };
541
+ }
542
+
543
+ const unreachable: string[] = [];
544
+ await Promise.all(
545
+ this.required().map(async ({ id }) => {
546
+ if (!this.built.has(id)) {
547
+ return;
548
+ }
549
+ const participant = this.live.has(id)
550
+ ? this.participants.get(id)
551
+ : undefined;
552
+ if (!participant) {
553
+ unreachable.push(id);
554
+ return;
555
+ }
556
+ try {
557
+ await participant.request({
558
+ op: "setState",
559
+ state: { logging: enabled, logLevel: level },
560
+ });
561
+ } catch {
562
+ unreachable.push(id);
563
+ }
564
+ }),
565
+ );
566
+ return unreachable;
567
+ }
568
+
569
+ /**
570
+ * Turn Capture mode on or off for this board's runtimes, while they run.
571
+ *
572
+ * Applied the way logging is (`setLogging`), and unlike logging never written
573
+ * into the config: Capture mode is a builder's switch, not a property of the
574
+ * board, so nothing that saves or restores the board can carry it. Returns
575
+ * the runtimes it could not reach.
576
+ */
577
+ async setCapture(enabled: boolean): Promise<string[]> {
578
+ this.capture = enabled;
579
+ const unreachable: string[] = [];
580
+ await Promise.all(
581
+ this.required().map(async ({ id }) => {
582
+ if (!this.built.has(id)) {
583
+ return;
584
+ }
585
+ const participant = this.live.has(id)
586
+ ? this.participants.get(id)
587
+ : undefined;
588
+ if (!participant) {
589
+ unreachable.push(id);
590
+ return;
591
+ }
592
+ try {
593
+ await participant.request({ op: "setState", state: { capture: enabled } });
594
+ } catch {
595
+ unreachable.push(id);
596
+ }
597
+ }),
598
+ );
599
+ return unreachable;
600
+ }
601
+
602
+ capturesCalls(): boolean {
603
+ return this.capture;
604
+ }
605
+
606
+ /**
607
+ * Releases the runtimes this session built, and reports the ones it could
608
+ * not.
609
+ *
610
+ * Releasing is best-effort by design: a runtime server that is away must not
611
+ * make a board impossible to stop. But a runtime that was not released is
612
+ * very likely still running — built to persist, holding its mount. Saying so
613
+ * is the difference between an orphan someone can go and deal with and one
614
+ * nobody ever hears about. It is released when its server next connects.
615
+ */
616
+ private async teardownRuntimes(): Promise<string[]> {
617
+ const unreleased: string[] = [];
618
+ await Promise.all(
619
+ [...this.built].map(async (id) => {
620
+ const participant = this.participants.get(id);
621
+ if (!participant) {
622
+ this.pendingRelease.add(id);
623
+ unreleased.push(
624
+ `Runtime "${id}" could not be released because its runtime server is not connected; it may still be running, and is released when that server reconnects.`,
625
+ );
626
+ return;
627
+ }
628
+ try {
629
+ await participant.request({ op: "remove" });
630
+ participant.listen(null);
631
+ this.pendingRelease.delete(id);
632
+ } catch (err) {
633
+ this.pendingRelease.add(id);
634
+ const reason = err instanceof Error ? err.message : String(err);
635
+ console.error(
636
+ `[coordinator] Failed to release runtime "${id}":`,
637
+ reason,
638
+ );
639
+ unreleased.push(
640
+ `Runtime "${id}" could not be released (${reason}); it may still be running.`,
641
+ );
642
+ }
643
+ }),
644
+ );
645
+ return unreleased;
646
+ }
647
+
648
+ /**
649
+ * Ends this session: releases its runtimes and lets go of its browsers.
650
+ *
651
+ * The participants' connections are not this session's to close. They belong
652
+ * to the tickets, so a session replacing this one finds them still there.
653
+ */
654
+ async destroy(): Promise<void> {
655
+ await this.serial(async () => {
656
+ this.destroyed = true;
657
+ this.unsubscribe?.();
658
+ this.unsubscribe = null;
659
+ // What it reported while it ran is left as it was: a session that has
660
+ // been replaced still answers for the run it was.
661
+ await this.teardownRuntimes();
662
+ });
663
+
664
+ // Bridges are either transferred to the new session or closed here.
665
+ for (const bridge of this.bridges) {
666
+ console.log(
667
+ `[bridge-close] Server initiating close: destroy() closing bridge for board "${this.boardName}"`,
668
+ );
669
+ bridge.ws.close();
670
+ }
671
+ this.bridges.clear();
672
+ for (const [requestId, pending] of this.pendingBrowserResults) {
673
+ this.pendingBrowserResults.delete(requestId);
674
+ clearTimeout(pending.timer);
675
+ pending.resolve(null);
676
+ }
677
+ }
678
+
679
+ /**
680
+ * Detach all browser bridges from this session and return them so the caller
681
+ * can hand them to the replacement session without the browsers seeing a
682
+ * disconnect. Each comes with who it is, which the replacement checks again.
683
+ */
684
+ takeBridges(): Array<{ ws: WebSocket; runtimeIds: string[] } & BridgeAttach> {
685
+ const taken = [...this.bridges].map((bridge) => {
686
+ // Detach handlers so the old session no longer processes bridge messages.
687
+ bridge.ws.removeAllListeners("message");
688
+ bridge.ws.removeAllListeners("close");
689
+ return {
690
+ ws: bridge.ws,
691
+ runtimeIds: [...bridge.runtimeIds],
692
+ role: bridge.role,
693
+ ...(bridge.caller ? { caller: bridge.caller } : {}),
694
+ };
695
+ });
696
+ this.bridges.clear();
697
+ return taken;
698
+ }
699
+
700
+ /** How many bridges somebody holds on this board, by the email they attached as. */
701
+ countMemberBridges(email: string): number {
702
+ let count = 0;
703
+ for (const bridge of this.bridges) {
704
+ if (bridge.role === "member" && bridge.caller?.email === email) {
705
+ count += 1;
706
+ }
707
+ }
708
+ return count;
709
+ }
710
+
711
+ /**
712
+ * Closes the bridges of somebody the board is no longer shared with. At
713
+ * once: being taken off the list is not something that waits for a
714
+ * reconnect.
715
+ */
716
+ evictMember(email: string): void {
717
+ for (const bridge of [...this.bridges]) {
718
+ if (bridge.role === "member" && bridge.caller?.email === email) {
719
+ bridge.ws.close(CLOSE_NOT_A_MEMBER, "no longer shared");
720
+ }
721
+ }
722
+ this.memberProcessTimes.delete(email);
723
+ }
724
+
725
+ /**
726
+ * What the board's list now calls an email, for the bridges attached as it.
727
+ * The name rides with every run they begin, so a rename has to reach
728
+ * somebody who is already attached.
729
+ */
730
+ renameMember(email: string, name: string | undefined): void {
731
+ for (const bridge of this.bridges) {
732
+ if (bridge.caller?.email !== email) {
733
+ continue;
734
+ }
735
+ const { name: _previous, ...rest } = bridge.caller;
736
+ bridge.caller = name ? { ...rest, name } : rest;
737
+ this.rememberCaller(bridge.caller.sub, name, bridge.role);
738
+ this.send(bridge.ws, this.snapshotFor(bridge));
739
+ }
740
+ }
741
+
742
+ /**
743
+ * Takes a browser that whoever is calling has already admitted, as what it
744
+ * was admitted as. Deciding that is not this session's job: it holds no
745
+ * list, and is told.
746
+ */
747
+ registerBrowserSocket(
748
+ ws: WebSocket,
749
+ runtimeIds: string[],
750
+ attach: BridgeAttach = { role: "owner" },
751
+ ): void {
752
+ // A member's bridge never hosts a runtime, whatever it says it has: the
753
+ // chain would wait on an answer that is not theirs to give.
754
+ const hosted = attach.role === "owner" ? runtimeIds : [];
755
+
756
+ // The same socket re-registering (e.g. its browser runtimes changed) — just
757
+ // refresh its runtimeIds rather than adding a duplicate or re-attaching
758
+ // listeners.
759
+ for (const existing of this.bridges) {
760
+ if (existing.ws === ws) {
761
+ existing.runtimeIds = new Set(hosted);
762
+ return;
763
+ }
764
+ }
765
+
766
+ const bridge: BrowserBridge = {
767
+ ws,
768
+ runtimeIds: new Set(hosted),
769
+ role: attach.role,
770
+ ...(attach.caller ? { caller: attach.caller } : {}),
771
+ seq: 0,
772
+ };
773
+ this.bridges.add(bridge);
774
+ if (attach.caller) {
775
+ this.rememberCaller(attach.caller.sub, attach.caller.name, attach.role);
776
+ }
777
+
778
+ ws.on("message", (raw, isBinary) => {
779
+ const bytes = frameBytes(raw);
780
+ const limit = frameLimitFrom(this.limits.maxFrameBytes);
781
+ if (bytes.length > limit) {
782
+ this.logStore?.append(this.userId, this.boardName, {
783
+ runId: "",
784
+ ts: new Date().toISOString(),
785
+ runtimeId: [...bridge.runtimeIds][0] ?? "",
786
+ serviceUuid: "",
787
+ level: "warn",
788
+ event: "frame-dropped",
789
+ data: { bytes: bytes.length, limit },
790
+ });
791
+ return;
792
+ }
793
+
794
+ let message: BridgeMessage;
795
+ try {
796
+ // A browser runtime's output that holds bytes. The header is the
797
+ // message it would have sent as text; the payload goes on as it came.
798
+ const frame = isBinary ? decodeBinaryFrame(bytes) : null;
799
+ if (isBinary && !frame) {
800
+ return;
801
+ }
802
+ const parsed: unknown = frame
803
+ ? { ...frame.header, data: frame.payload }
804
+ : JSON.parse(bytes.toString("utf8"));
805
+ if (!isBridgeMessage(parsed)) {
806
+ return;
807
+ }
808
+ message = parsed;
809
+ } catch (err) {
810
+ console.error(
811
+ `[coordinator] Failed to parse bridge message for board "${this.boardName}":`,
812
+ err instanceof Error ? err.message : err,
813
+ );
814
+ return;
815
+ }
816
+
817
+ // A browser that reconnected, or noticed a gap in the sequence, asking to
818
+ // be told the board again rather than carrying on from a stale view.
819
+ if (message.type === "resync") {
820
+ this.send(ws, this.snapshotFor(bridge));
821
+ return;
822
+ }
823
+
824
+ if (message.type === "processService") {
825
+ void this.serveProcessRequest(bridge, message);
826
+ return;
827
+ }
828
+
829
+ // Everything below is the owner's. A member's bridge may ask to be told
830
+ // the board again and ask the facade's services to do their job, and
831
+ // that is the whole of what it may send: it configures nothing, records
832
+ // nothing, and answers for no runtime.
833
+ if (bridge.role !== "owner") {
834
+ if (message.type === "configureService") {
835
+ this.send(ws, {
836
+ type: "response",
837
+ requestId: message.requestId,
838
+ error: "Only the board's owner may configure it",
839
+ });
840
+ }
841
+ return;
842
+ }
843
+
844
+ if (message.type === "configureService") {
845
+ void this.serveBrowserRequest(bridge, message);
846
+ return;
847
+ }
848
+
849
+ // An entry from a runtime this browser hosts. The board's log spans every
850
+ // runtime it uses, and a browser runtime has no other route into it —
851
+ // it does not hold a socket of its own to this coordinator.
852
+ //
853
+ // Not forwarded on to the other bridges, unlike an entry arriving from a
854
+ // remote runtime: the browser that sent it already has it, and a second
855
+ // browser watching the same board is not hosting the runtime that
856
+ // produced it.
857
+ if (message.type === "log" && message.entry) {
858
+ this.logStore?.append(
859
+ this.userId,
860
+ this.boardName,
861
+ message.entry as LogEntry,
862
+ );
863
+ return;
864
+ }
865
+
866
+ if (message.type === "result" && message.requestId) {
867
+ const pending = this.pendingBrowserResults.get(message.requestId);
868
+ if (pending) {
869
+ this.pendingBrowserResults.delete(message.requestId);
870
+ clearTimeout(pending.timer);
871
+ pending.resolve(message.data);
872
+ }
873
+ return;
874
+ }
875
+
876
+ if (message.type === "result-from-browser" && message.runtimeId) {
877
+ // A person using the browser begins a run as the verified bridge
878
+ // caller. A Timer or standing subscription with no active call is the
879
+ // board's own and begins one with no caller. Only an owner bridge can
880
+ // reach this branch, so letting it say "nobody" grants it nothing.
881
+ this.routeResult(
882
+ message.runtimeId,
883
+ message.data,
884
+ message.boardOrigin === true || message.callerless === true
885
+ ? { runId: randomUUID(), actor: { kind: "board" } }
886
+ : this.beginRun(bridge),
887
+ ).catch((err) => {
888
+ console.error(
889
+ `[coordinator] Failed to route result from browser runtime "${message.runtimeId}":`,
890
+ err instanceof Error ? err.message : err,
891
+ );
892
+ });
893
+ }
894
+ });
895
+
896
+ ws.on("close", () => {
897
+ this.bridges.delete(bridge);
898
+ // Capture mode lasts while its owner is looking: the last of their
899
+ // bridges closing ends it, so a tab closed with it on does not leave the
900
+ // board sending what passes inside its runs to a coordinator nobody
901
+ // reads from.
902
+ if (
903
+ this.capture &&
904
+ ![...this.bridges].some((open) => open.role === "owner")
905
+ ) {
906
+ void this.setCapture(false);
907
+ }
908
+ // Only abandon in-flight browser results once nobody remains who could
909
+ // answer them; another of the owner's bridges may still reply.
910
+ if (this.hostingBridges().length === 0) {
911
+ for (const [requestId, pending] of this.pendingBrowserResults) {
912
+ this.pendingBrowserResults.delete(requestId);
913
+ clearTimeout(pending.timer);
914
+ pending.resolve(null);
915
+ }
916
+ }
917
+ });
918
+
919
+ // Attaching is a read: the browser renders what the board currently is,
920
+ // rather than provisioning anything itself.
921
+ this.send(ws, this.snapshotFor(bridge));
922
+
923
+ console.log(
924
+ `[coordinator] Browser bridge registered for board "${this.boardName}" (${bridge.role}, runtimeIds: ${hosted.join(", ")}, bridges: ${this.bridges.size})`,
925
+ );
926
+ }
927
+
928
+ // ── Private ───────────────────────────────────────────────────────────────
929
+
930
+ /**
931
+ * The assets a runtime is given: every one the board declares, unless an
932
+ * asset names the runtimes it is for. Not only the ones its services name —
933
+ * which asset a service uses can be decided as it runs, by its input or by a
934
+ * request, and a reference that reaches a runtime has to resolve there.
935
+ *
936
+ * None for a runtime a unit contributed: it resolves against that unit's
937
+ * assets, which are not the board's.
938
+ */
939
+ private assetsFor(runtime: CloudRuntimeDescriptor): Record<string, AssetDescriptor> {
940
+ const assets: Record<string, AssetDescriptor> = {};
941
+ if (runtime.unit) {
942
+ return assets;
943
+ }
944
+ for (const descriptor of this.config.assets ?? []) {
945
+ if (!descriptor.runtimes || descriptor.runtimes.includes(runtime.id)) {
946
+ assets[descriptor.id] = descriptor;
947
+ }
948
+ }
949
+ return assets;
950
+ }
951
+
952
+ private onRuntimeNotification(
953
+ runtimeId: string,
954
+ serviceUuid: string,
955
+ payload: unknown,
956
+ context?: ProcessContext,
957
+ ): void {
958
+ // Pass it on as what it is. A service's notifications are its output, not
959
+ // its state — a Monitor's message never appears in its getState — so a
960
+ // browser reaching this runtime through us must receive the same
961
+ // notifications it would have received from the runtime directly. A payload
962
+ // is whatever the service said, string and number included; only the mount
963
+ // address below is looked for, and only an object can carry one.
964
+ this.deliverNotification(runtimeId, serviceUuid, payload, context);
965
+
966
+ if (!payload || typeof payload !== "object") {
967
+ return;
968
+ }
969
+
970
+ const published = (payload as Record<string, unknown>)[MOUNT_FIELD];
971
+ if (typeof published !== "string" || !published) {
972
+ return;
973
+ }
974
+
975
+ // A published address *is* state, and the one piece of it a browser cannot
976
+ // work out for itself, so keep the board's view of it current.
977
+ const states = this.serviceStates.get(runtimeId) ?? {};
978
+ const previous = states[serviceUuid];
979
+ states[serviceUuid] =
980
+ previous && typeof previous === "object"
981
+ ? { ...(previous as Record<string, unknown>), [MOUNT_FIELD]: published }
982
+ : { [MOUNT_FIELD]: published };
983
+ this.serviceStates.set(runtimeId, states);
984
+ this.broadcastServiceState(runtimeId, serviceUuid, states[serviceUuid]);
985
+
986
+ if (this.recordMountAddress(runtimeId, serviceUuid, published)) {
987
+ void this.publishMountAddresses().catch((err) => {
988
+ console.error(
989
+ `[coordinator] Failed to publish mount addresses for board "${this.boardName}":`,
990
+ err instanceof Error ? err.message : err,
991
+ );
992
+ });
993
+ }
994
+ }
995
+
996
+ /**
997
+ * Tells the browsers that are to hear it what a service said.
998
+ *
999
+ * A notification raised with a person actor goes to that person's bridges
1000
+ * and nobody else's — what one member's action produced is not another
1001
+ * member's to read, and not the owner's to watch. Board, mount and local
1002
+ * actors produce shared board news, so those notifications go to everyone.
1003
+ *
1004
+ * A member is sent it only from a service the facade reads, cut down to
1005
+ * what the facade reads of it, and never the runtime's own account of its
1006
+ * flow, which carries the data passing through every service. The owner is
1007
+ * sent what was said.
1008
+ */
1009
+ private deliverNotification(
1010
+ runtimeId: string,
1011
+ serviceUuid: string,
1012
+ payload: unknown,
1013
+ context?: ProcessContext,
1014
+ ): void {
1015
+ if (!this.runMayContinue(context)) {
1016
+ return;
1017
+ }
1018
+ const caller =
1019
+ context?.actor.kind === "person" ? context.actor : undefined;
1020
+ const frameOf = (said: unknown) =>
1021
+ JSON.stringify({ type: "notification", runtimeId, serviceUuid, payload: said });
1022
+ let whole: string | null = null;
1023
+ let projected: string | null = null;
1024
+ let projectionDone = false;
1025
+ for (const bridge of this.bridges) {
1026
+ if (caller && bridge.caller?.sub !== caller.sub) {
1027
+ continue;
1028
+ }
1029
+ if (
1030
+ bridge.role === "member" &&
1031
+ (!this.access.sources.has(serviceUuid) || isFlowNotification(payload))
1032
+ ) {
1033
+ continue;
1034
+ }
1035
+ if (bridge.ws.readyState !== WebSocket.OPEN) {
1036
+ continue;
1037
+ }
1038
+ if (bridge.role === "member") {
1039
+ if (!projectionDone) {
1040
+ const said = projectNotification(this.access, serviceUuid, payload);
1041
+ projected = said === undefined ? null : frameOf(said);
1042
+ projectionDone = true;
1043
+ }
1044
+ if (projected !== null) {
1045
+ bridge.ws.send(projected);
1046
+ }
1047
+ } else {
1048
+ whole ??= frameOf(payload);
1049
+ bridge.ws.send(whole);
1050
+ }
1051
+ }
1052
+ }
1053
+
1054
+ /**
1055
+ * Who a `sub` in this board's log is, as the board's list calls them, for
1056
+ * the owner's live view of it. Learnt from the bridges people attach with,
1057
+ * and kept after they leave, so a run is still named once its caller has
1058
+ * closed the board. Held only while the session lives, and never written
1059
+ * to the log.
1060
+ */
1061
+ private readonly knownCallers = new Map<
1062
+ string,
1063
+ { name?: string; role: "owner" | "member" }
1064
+ >();
1065
+
1066
+ private rememberCaller(
1067
+ sub: string,
1068
+ name: string | undefined,
1069
+ role: "owner" | "member",
1070
+ ): void {
1071
+ this.knownCallers.set(sub, { ...(name ? { name } : {}), role });
1072
+ }
1073
+
1074
+ /** Sends a message to every browser the board's owner has attached. */
1075
+ private toOwners(message: BridgeMessage): void {
1076
+ const payload = JSON.stringify(message);
1077
+ for (const bridge of this.bridges) {
1078
+ if (bridge.role === "owner" && bridge.ws.readyState === WebSocket.OPEN) {
1079
+ bridge.ws.send(payload);
1080
+ }
1081
+ }
1082
+ }
1083
+
1084
+ /** The owner's open bridges: the only ones a browser runtime can be on. */
1085
+ private hostingBridges(): BrowserBridge[] {
1086
+ return [...this.bridges].filter(
1087
+ (bridge) =>
1088
+ bridge.role === "owner" && bridge.ws.readyState === WebSocket.OPEN,
1089
+ );
1090
+ }
1091
+
1092
+ /** Tells every browser the board again, each as it is entitled to see it. */
1093
+ private broadcastSnapshot(): void {
1094
+ for (const bridge of this.bridges) {
1095
+ this.send(bridge.ws, this.snapshotFor(bridge));
1096
+ }
1097
+ }
1098
+
1099
+ /**
1100
+ * Tells every browser one service's new state. A member is told only of a
1101
+ * service the facade names, and only what the facade reads of it.
1102
+ */
1103
+ private broadcastServiceState(
1104
+ runtimeId: string,
1105
+ serviceUuid: string,
1106
+ state: unknown,
1107
+ ): void {
1108
+ for (const bridge of this.bridges) {
1109
+ if (bridge.role === "member") {
1110
+ if (!this.access.named.has(serviceUuid)) {
1111
+ continue;
1112
+ }
1113
+ this.send(bridge.ws, {
1114
+ type: "serviceState",
1115
+ seq: ++bridge.seq,
1116
+ runtimeId,
1117
+ serviceUuid,
1118
+ state: projectState(this.access, serviceUuid, state),
1119
+ });
1120
+ continue;
1121
+ }
1122
+ this.send(bridge.ws, {
1123
+ type: "serviceState",
1124
+ seq: ++bridge.seq,
1125
+ runtimeId,
1126
+ serviceUuid,
1127
+ state,
1128
+ });
1129
+ }
1130
+ }
1131
+
1132
+ private send(ws: WebSocket, message: BridgeMessage): void {
1133
+ if (ws.readyState === WebSocket.OPEN) {
1134
+ ws.send(JSON.stringify(message));
1135
+ }
1136
+ }
1137
+
1138
+ /** A run a browser begins, as whoever attached with it. */
1139
+ private beginRun(bridge: BrowserBridge): ProcessContext {
1140
+ const now = Date.now();
1141
+ return {
1142
+ runId: randomUUID(),
1143
+ actor: bridge.caller
1144
+ ? {
1145
+ kind: "person",
1146
+ ...bridge.caller,
1147
+ expiresAt:
1148
+ now +
1149
+ (this.limits.maxPersonRunAgeMs ??
1150
+ DEFAULT_MEMBER_LIMITS.maxPersonRunAgeMs),
1151
+ }
1152
+ : { kind: "local" },
1153
+ };
1154
+ }
1155
+
1156
+ /** Whether the authority captured by a run is still valid now. */
1157
+ private runMayContinue(context?: ProcessContext): boolean {
1158
+ if (!context || context.actor.kind !== "person") {
1159
+ return true;
1160
+ }
1161
+ return (
1162
+ Number.isFinite(context.actor.expiresAt) &&
1163
+ context.actor.expiresAt > Date.now() &&
1164
+ this.mayActAs(context.actor)
1165
+ );
1166
+ }
1167
+
1168
+ /**
1169
+ * The board as this coordinator knows it, for one browser.
1170
+ *
1171
+ * Sent when a browser attaches, and again on request. For the owner it is
1172
+ * every remote runtime's registry and the state its services last reported:
1173
+ * the live state rather than the saved board, because that is the part a
1174
+ * browser cannot work out for itself — a mount's address is assigned when
1175
+ * the runtime is provisioned and appears in no saved board.
1176
+ *
1177
+ * For a member it is a projection: the facade, and of the board only the
1178
+ * services the facade names with what it reads of each. A board's config
1179
+ * carries service state, and state can carry credentials, so not showing a
1180
+ * member the board would mean little if this sent it to them.
1181
+ */
1182
+ private snapshotFor(bridge: BrowserBridge): BridgeMessage {
1183
+ const you = bridge.caller
1184
+ ? {
1185
+ ...(bridge.caller.email ? { email: bridge.caller.email } : {}),
1186
+ ...(bridge.caller.name ? { name: bridge.caller.name } : {}),
1187
+ }
1188
+ : undefined;
1189
+ const common = {
1190
+ type: "snapshot" as const,
1191
+ seq: ++bridge.seq,
1192
+ boardName: this.boardName,
1193
+ status: this.getStatus(),
1194
+ role: bridge.role,
1195
+ ...(you ? { you } : {}),
1196
+ };
1197
+
1198
+ if (bridge.role === "member") {
1199
+ const config = projectConfig(this.config, this.access, (runtimeId, uuid) =>
1200
+ this.liveState(runtimeId, uuid),
1201
+ );
1202
+ return {
1203
+ ...common,
1204
+ // Whether the board is running is a member's to know; which runtime
1205
+ // server is away, and why, is its owner's.
1206
+ errors: [],
1207
+ config,
1208
+ runtimes: config.runtimes.map(({ id }) => ({
1209
+ runtimeId: id,
1210
+ registry: [],
1211
+ services: Object.fromEntries(
1212
+ (config.services[id] ?? []).map((svc) => [svc.uuid, svc.state ?? {}]),
1213
+ ),
1214
+ })),
1215
+ };
1216
+ }
1217
+
1218
+ // In the board's own order, and including a runtime whose participant has
1219
+ // dropped: what it last reported is still the best account of it there is,
1220
+ // and the status beside it says that it is away.
1221
+ const runtimes: RuntimeSnapshot[] = this.required()
1222
+ .filter(({ id }) => this.built.has(id))
1223
+ .map(({ id }) => ({
1224
+ runtimeId: id,
1225
+ registry: this.registries.get(id) ?? [],
1226
+ services: this.serviceStates.get(id) ?? {},
1227
+ }));
1228
+ return {
1229
+ ...common,
1230
+ errors: this.getErrors(),
1231
+ config: this.config,
1232
+ runtimes,
1233
+ };
1234
+ }
1235
+
1236
+ /** What a service last reported, or what the board configured it with. */
1237
+ private liveState(runtimeId: string, serviceUuid: string): unknown {
1238
+ const reported = this.serviceStates.get(runtimeId)?.[serviceUuid];
1239
+ if (reported !== undefined) {
1240
+ return reported;
1241
+ }
1242
+ return (this.config.services[runtimeId] ?? []).find(
1243
+ (svc) => svc.uuid === serviceUuid,
1244
+ )?.state;
1245
+ }
1246
+
1247
+ /**
1248
+ * Whether a member has asked for more than they are allowed in the last
1249
+ * minute. Counted per person rather than per bridge, so opening another tab
1250
+ * is not a way round it.
1251
+ */
1252
+ private overMemberRate(email: string): boolean {
1253
+ const limit =
1254
+ this.limits.maxMemberProcessPerMinute ??
1255
+ DEFAULT_MEMBER_LIMITS.maxMemberProcessPerMinute;
1256
+ const now = Date.now();
1257
+ const recent = (this.memberProcessTimes.get(email) ?? []).filter(
1258
+ (at) => now - at < 60_000,
1259
+ );
1260
+ if (recent.length >= limit) {
1261
+ this.memberProcessTimes.set(email, recent);
1262
+ return true;
1263
+ }
1264
+ recent.push(now);
1265
+ this.memberProcessTimes.set(email, recent);
1266
+ return false;
1267
+ }
1268
+
1269
+ /**
1270
+ * Asks a service on a remote runtime to do its job, for a browser.
1271
+ *
1272
+ * The run begins here. Its person actor, when there is one, was established
1273
+ * when the bridge was admitted, so runtimes can rely on it and nothing in
1274
+ * the message can change it.
1275
+ *
1276
+ * The answer says only whether the work was taken. What it produces arrives
1277
+ * the way a pipeline's output always does — as notifications, and as a
1278
+ * result this session carries to the board's next runtime.
1279
+ */
1280
+ private async serveProcessRequest(
1281
+ bridge: BrowserBridge,
1282
+ message: Extract<BridgeMessage, { type: "processService" }>,
1283
+ ): Promise<void> {
1284
+ const refuse = (error: string) =>
1285
+ this.send(bridge.ws, {
1286
+ type: "response",
1287
+ requestId: message.requestId,
1288
+ error,
1289
+ });
1290
+ if (
1291
+ typeof message.requestId !== "string" ||
1292
+ typeof message.serviceUuid !== "string"
1293
+ ) {
1294
+ return;
1295
+ }
1296
+
1297
+ let runtimeId = message.runtimeId;
1298
+ if (bridge.role === "member") {
1299
+ // The entry point is the capability: a member may begin at a service
1300
+ // the facade asks to process, and at no other.
1301
+ if (!this.access.processTargets.has(message.serviceUuid)) {
1302
+ refuse("This board does not offer that");
1303
+ return;
1304
+ }
1305
+ // Where that service is, is the board's to say and not the message's.
1306
+ runtimeId = runtimeHolding(this.config, message.serviceUuid) ?? "";
1307
+ const email = bridge.caller?.email ?? "";
1308
+ if (this.overMemberRate(email)) {
1309
+ refuse("Too many requests — try again in a moment");
1310
+ return;
1311
+ }
1312
+ }
1313
+
1314
+ if (this.stopped) {
1315
+ refuse("This board is stopped");
1316
+ return;
1317
+ }
1318
+ const participant = this.live.has(runtimeId)
1319
+ ? this.participants.get(runtimeId)
1320
+ : undefined;
1321
+ if (!participant) {
1322
+ refuse(
1323
+ bridge.role === "member"
1324
+ ? "This board is not running right now"
1325
+ : this.required().some((rt) => rt.id === runtimeId)
1326
+ ? `Runtime "${runtimeId}" is not connected`
1327
+ : `Unknown runtime "${runtimeId}"`,
1328
+ );
1329
+ return;
1330
+ }
1331
+
1332
+ try {
1333
+ await participant.request({
1334
+ op: "processService",
1335
+ serviceUuid: message.serviceUuid,
1336
+ params: message.payload ?? null,
1337
+ context: this.beginRun(bridge),
1338
+ });
1339
+ this.send(bridge.ws, {
1340
+ type: "response",
1341
+ requestId: message.requestId,
1342
+ data: { accepted: true },
1343
+ });
1344
+ } catch (err) {
1345
+ const reason = err instanceof Error ? err.message : String(err);
1346
+ refuse(
1347
+ bridge.role === "member"
1348
+ ? "This board could not take that right now"
1349
+ : `Runtime "${runtimeId}": ${reason}`,
1350
+ );
1351
+ }
1352
+ }
1353
+
1354
+ /**
1355
+ * Acts on a remote service for a browser that cannot reach it.
1356
+ *
1357
+ * The browser is a viewer: it renders this board and asks for changes, but the
1358
+ * runtimes are the coordinator's to talk to. The reply carries whatever the
1359
+ * runtime returned, so a panel can reconcile its optimistic state with what
1360
+ * actually took effect.
1361
+ */
1362
+ private async serveBrowserRequest(
1363
+ bridge: BrowserBridge,
1364
+ message: Extract<BridgeMessage, { type: "configureService" }>,
1365
+ ): Promise<void> {
1366
+ const ws = bridge.ws;
1367
+ const participant = this.live.has(message.runtimeId)
1368
+ ? this.participants.get(message.runtimeId)
1369
+ : undefined;
1370
+ if (!participant) {
1371
+ this.send(ws, {
1372
+ type: "response",
1373
+ requestId: message.requestId,
1374
+ error: this.required().some((rt) => rt.id === message.runtimeId)
1375
+ ? `Runtime "${message.runtimeId}" is not connected`
1376
+ : `Unknown runtime "${message.runtimeId}"`,
1377
+ });
1378
+ return;
1379
+ }
1380
+
1381
+ try {
1382
+ // Configuring returns the service's whole state; record it so a browser
1383
+ // attaching later sees the same thing this one just did.
1384
+ const data = await participant.request({
1385
+ op: "configureService",
1386
+ serviceUuid: message.serviceUuid,
1387
+ config: message.config ?? {},
1388
+ context: this.beginRun(bridge),
1389
+ });
1390
+ const states = this.serviceStates.get(message.runtimeId) ?? {};
1391
+ states[message.serviceUuid] = data;
1392
+ this.serviceStates.set(message.runtimeId, states);
1393
+ this.broadcastServiceState(message.runtimeId, message.serviceUuid, data);
1394
+ this.send(ws, { type: "response", requestId: message.requestId, data });
1395
+ } catch (err) {
1396
+ this.send(ws, {
1397
+ type: "response",
1398
+ requestId: message.requestId,
1399
+ error: `Runtime "${message.runtimeId}": ${err instanceof Error ? err.message : String(err)}`,
1400
+ });
1401
+ }
1402
+ }
1403
+
1404
+ /** Records a published address. Returns whether it changed anything. */
1405
+ private recordMountAddress(
1406
+ runtimeId: string,
1407
+ serviceUuid: string,
1408
+ url: string,
1409
+ ): boolean {
1410
+ const key = formatMountRef({ runtimeId, serviceUuid });
1411
+ if (this.mountAddresses.get(key) === url) {
1412
+ return false;
1413
+ }
1414
+ this.mountAddresses.set(key, url);
1415
+ return true;
1416
+ }
1417
+
1418
+ /**
1419
+ * Hands every service holding a mount reference the address it names.
1420
+ *
1421
+ * A runtime cannot do this for itself: it sees its own services and nothing
1422
+ * else, while a reference names a service on another runtime, possibly on
1423
+ * another machine. So the coordinator resolves against the board it owns and
1424
+ * configures the consumer with a plain address, which is the same value the
1425
+ * consumer would have been given had the board been exported.
1426
+ *
1427
+ * Services on browser runtimes are not reachable from here — the bridge only
1428
+ * carries processing — so those still resolve in the browser, which
1429
+ * coordinates its own board state.
1430
+ */
1431
+ private async publishMountAddresses(): Promise<void> {
1432
+ if (this.mountAddresses.size === 0) {
1433
+ return;
1434
+ }
1435
+
1436
+ await Promise.all(
1437
+ this.required().map(async (descriptor) => {
1438
+ if (!this.live.has(descriptor.id)) {
1439
+ return;
1440
+ }
1441
+ const services = this.config.services[descriptor.id] ?? [];
1442
+ for (const svc of services) {
1443
+ const state = svc.state;
1444
+ if (!state) {
1445
+ continue;
1446
+ }
1447
+ // A reference is written wherever the service names its target, so it
1448
+ // is looked for by scheme rather than read out of one field.
1449
+ const refs = [...collectMountRefs(state)];
1450
+ if (refs.length === 0) {
1451
+ continue;
1452
+ }
1453
+ if (refs.length > 1) {
1454
+ // One address field, so one mount per consumer. A service needing
1455
+ // two should host a pipeline instead.
1456
+ console.warn(
1457
+ `[coordinator] Service "${descriptor.id}/${svc.uuid}" names ${refs.length} mounts; only the first is resolved`,
1458
+ );
1459
+ }
1460
+ const address = this.mountAddresses.get(refs[0]);
1461
+ if (!address) {
1462
+ // The owner has not published yet. Later passes run whenever an
1463
+ // address appears.
1464
+ continue;
1465
+ }
1466
+ const key = formatMountRef({
1467
+ runtimeId: descriptor.id,
1468
+ serviceUuid: svc.uuid,
1469
+ });
1470
+ if (this.pushedStates.get(key) === address) {
1471
+ continue;
1472
+ }
1473
+ this.pushedStates.set(key, address);
1474
+ // Only the address, and only in the field addresses live in. The
1475
+ // board keeps its reference: that is what survives being saved and
1476
+ // reopened somewhere else, while an address is only true of this run.
1477
+ await this.configureService(descriptor.id, svc.uuid, {
1478
+ [MOUNT_FIELD]: address,
1479
+ });
1480
+ }
1481
+ }),
1482
+ );
1483
+ }
1484
+
1485
+ private async configureService(
1486
+ runtimeId: string,
1487
+ serviceUuid: string,
1488
+ state: Record<string, unknown>,
1489
+ ): Promise<void> {
1490
+ try {
1491
+ await this.participants
1492
+ .get(runtimeId)
1493
+ ?.request({
1494
+ op: "configureService",
1495
+ serviceUuid,
1496
+ config: state,
1497
+ context: { runId: randomUUID(), actor: { kind: "board" } },
1498
+ });
1499
+ } catch (err) {
1500
+ console.error(
1501
+ `[coordinator] Failed to configure "${serviceUuid}" on runtime "${runtimeId}":`,
1502
+ err instanceof Error ? err.message : err,
1503
+ );
1504
+ }
1505
+ }
1506
+
1507
+ /**
1508
+ * Hands what one runtime produced to the next one in the board.
1509
+ *
1510
+ * `context` is the run it was produced in. It goes on with the data, which
1511
+ * is what makes a board's runtimes one run rather than one each — and what
1512
+ * lets the second runtime know who began it.
1513
+ */
1514
+ private async routeResult(
1515
+ fromRuntimeId: string,
1516
+ data: unknown,
1517
+ context?: ProcessContext,
1518
+ ): Promise<void> {
1519
+ if (!this.runMayContinue(context)) {
1520
+ return;
1521
+ }
1522
+ const next = this.nextRuntime(fromRuntimeId, this.config.runtimes);
1523
+ if (!next) {
1524
+ return;
1525
+ }
1526
+
1527
+ if (isRemoteRuntime(next)) {
1528
+ const participant = this.live.has(next.id)
1529
+ ? this.participants.get(next.id)
1530
+ : undefined;
1531
+ if (!participant) {
1532
+ // A required participant that is away: the board already says so.
1533
+ console.warn(
1534
+ `[coordinator] Next runtime "${next.id}" is not connected — dropping result`,
1535
+ );
1536
+ return;
1537
+ }
1538
+ participant.process(data, context);
1539
+ return;
1540
+ }
1541
+
1542
+ if (isBrowserRuntime(next)) {
1543
+ // The owner's browsers, and only those: a browser runtime is theirs to
1544
+ // run, and a member's bridge asked would leave the chain waiting on an
1545
+ // answer that is dropped when it comes.
1546
+ const targets = this.hostingBridges();
1547
+ if (targets.length === 0) {
1548
+ // A transient participant that is away. Nothing is wrong: the data
1549
+ // stops here, as it would at a service that returned null, and the
1550
+ // board goes on running.
1551
+ return;
1552
+ }
1553
+
1554
+ const requestId = randomUUID();
1555
+ // Fan the work out to each of them so each client's UI (e.g. a
1556
+ // Monitor) updates. The first reply resolves the chain; later replies for
1557
+ // the same requestId are no-ops since its pending entry is already gone.
1558
+ const result = await new Promise<unknown>((resolve) => {
1559
+ const remaining =
1560
+ context?.actor.kind === "person"
1561
+ ? Math.max(0, context.actor.expiresAt - Date.now())
1562
+ : this.limits.maxPersonRunAgeMs ??
1563
+ DEFAULT_MEMBER_LIMITS.maxPersonRunAgeMs;
1564
+ const timer = setTimeout(() => {
1565
+ this.pendingBrowserResults.delete(requestId);
1566
+ resolve(null);
1567
+ }, remaining);
1568
+ timer.unref?.();
1569
+ this.pendingBrowserResults.set(requestId, { resolve, timer });
1570
+ // The browser is told the run so that what it records belongs to it.
1571
+ // What continues the chain below is the context held here, not
1572
+ // whatever comes back.
1573
+ const header = {
1574
+ type: "processRuntime",
1575
+ runtimeId: next.id,
1576
+ requestId,
1577
+ ...(context ? { context } : {}),
1578
+ };
1579
+ const payload =
1580
+ data instanceof BinaryPayload
1581
+ ? encodeBinaryFrame(header, data)
1582
+ : JSON.stringify({ ...header, params: data });
1583
+ for (const target of targets) {
1584
+ target.ws.send(payload);
1585
+ }
1586
+ });
1587
+
1588
+ if (result !== null) {
1589
+ await this.routeResult(next.id, result, context);
1590
+ }
1591
+ }
1592
+ }
1593
+
1594
+ private nextRuntime(
1595
+ currentId: string,
1596
+ allRuntimes: CloudRuntimeDescriptor[],
1597
+ ): CloudRuntimeDescriptor | null {
1598
+ const idx = allRuntimes.findIndex((rt) => rt.id === currentId);
1599
+ for (let i = idx + 1; i < allRuntimes.length; i++) {
1600
+ const next = allRuntimes[i];
1601
+ if (isBrowserRuntime(next)) {
1602
+ return next;
1603
+ }
1604
+ if (isRemoteRuntime(next)) {
1605
+ return next;
1606
+ }
1607
+ }
1608
+ return null;
1609
+ }
1610
+ }