@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,1539 @@
1
+ #include "server.h"
2
+
3
+ #include <algorithm>
4
+ #include <chrono>
5
+ #include <map>
6
+ #include <mutex>
7
+ #include <set>
8
+
9
+ #include <crow.h>
10
+ #include "origins.h"
11
+
12
+ #include <app.h>
13
+ #include <auth.h>
14
+ #include <types/validation.h>
15
+
16
+ #include "common/websocket_protocol.h"
17
+ #include "discovery/discovery.h"
18
+ #include "uuid.h"
19
+ #include "process_context.h"
20
+ #include "http/front_door.h"
21
+ #include "http/ws_tickets.h"
22
+
23
+ #include <atomic>
24
+ #include <thread>
25
+
26
+ namespace hkp
27
+ {
28
+
29
+ // Which runtime server this is, reported beside the runtimes so a client can
30
+ // tell remote runtimes apart without reading their address.
31
+ constexpr const char* kRuntimeServerKind = "c++";
32
+
33
+ // Per-connection bookkeeping for a notification WebSocket, stored as the Crow
34
+ // connection's userdata. `runtimeId` is empty until the client sends its
35
+ // protocol handshake ({type, id}); after that the connection is registered
36
+ // against that runtime and forwards/receives its messages.
37
+ struct WsConnState
38
+ {
39
+ std::string runtimeId;
40
+ std::string type; // "writer" | "reader" | "readwrite"
41
+ // Whoever opened the socket, when a token said so; nobody for a socket let
42
+ // in without one (no-auth mode, the local machine).
43
+ Caller caller;
44
+ };
45
+
46
+ // Who a verified token speaks for, to a run they begin. The token's `sub` is
47
+ // the identity; hkp-rt only ever authorizes a verified, allow-listed email.
48
+ // A client holding a token is somebody this runtime is run for, so its
49
+ // owner; a member arrives through a coordinator, which says so.
50
+ inline Caller callerOf(const std::optional<Principal>& principal)
51
+ {
52
+ Caller caller;
53
+ if (principal && !principal->sub.empty())
54
+ {
55
+ caller.sub = principal->sub;
56
+ caller.email = principal->email;
57
+ caller.role = "owner";
58
+ }
59
+ return caller;
60
+ }
61
+
62
+ // Crow middleware that gates every route on the runtime's Authenticator.
63
+ // In no-auth mode (loopback bind) it is a pass-through. CORS preflight
64
+ // (OPTIONS) requests carry no Authorization header and must never be gated.
65
+ // Extracts the token from an "Authorization: Bearer <token>" header value.
66
+ // Returns empty when the header is missing or not a bearer credential.
67
+ inline std::string bearerToken(const std::string& header)
68
+ {
69
+ constexpr const char* prefix = "Bearer ";
70
+ constexpr size_t prefixLen = 7;
71
+ if (header.size() <= prefixLen || header.compare(0, prefixLen, prefix) != 0)
72
+ {
73
+ return "";
74
+ }
75
+ return header.substr(prefixLen);
76
+ }
77
+
78
+ inline bool forwarded(const crow::request& req)
79
+ {
80
+ return isForwarded([&req](const char* name) { return req.get_header_value(name); });
81
+ }
82
+
83
+ // Where a request came from. Behind the front door every request arrives from
84
+ // this process, which says who the caller was in a header — believed only
85
+ // beside the secret the front door alone knows. See http/front_door.h. A
86
+ // request another proxy says it passed on is from nowhere in particular, and
87
+ // so never from this machine: behind nginx or Caddy on the same host every
88
+ // caller would otherwise be a loopback one (origins.h).
89
+ inline std::string callerAddress(const crow::request& req, const std::string& frontSecret)
90
+ {
91
+ if (forwarded(req))
92
+ {
93
+ return "";
94
+ }
95
+ if (!frontSecret.empty() &&
96
+ req.get_header_value(FrontDoor::FRONT_HEADER) == frontSecret)
97
+ {
98
+ return req.get_header_value(FrontDoor::CLIENT_HEADER);
99
+ }
100
+ return req.remote_ip_address;
101
+ }
102
+
103
+ // What the server knows about who may call it from a browser: the origins it
104
+ // was told, and the names it answers to. Shared by the middleware and the
105
+ // WebSocket accept, and changed while requests are being served.
106
+ struct OriginGate
107
+ {
108
+ AllowedOrigins allowed() const
109
+ {
110
+ std::lock_guard<std::mutex> lock(m_mutex);
111
+ return m_allowed;
112
+ }
113
+
114
+ void setAllowed(AllowedOrigins allowed)
115
+ {
116
+ std::lock_guard<std::mutex> lock(m_mutex);
117
+ m_allowed = std::move(allowed);
118
+ }
119
+
120
+ void add(const std::vector<std::string>& origins)
121
+ {
122
+ std::lock_guard<std::mutex> lock(m_mutex);
123
+ m_allowed.add(origins);
124
+ }
125
+
126
+ void addOwnName(const std::string& name)
127
+ {
128
+ std::lock_guard<std::mutex> lock(m_mutex);
129
+ m_ownNames.push_back(name);
130
+ }
131
+
132
+ // Whether a request carrying no credential may be let in; see origins.h.
133
+ bool admits(const crow::request& req) const
134
+ {
135
+ std::lock_guard<std::mutex> lock(m_mutex);
136
+ return admitsWithoutCredential(
137
+ RequestSource{req.get_header_value("Origin"),
138
+ req.get_header_value("Sec-Fetch-Site"),
139
+ req.get_header_value("Host"),
140
+ forwarded(req)},
141
+ m_allowed, m_ownNames);
142
+ }
143
+
144
+ private:
145
+ mutable std::mutex m_mutex;
146
+ AllowedOrigins m_allowed;
147
+ std::vector<std::string> m_ownNames;
148
+ };
149
+
150
+ struct AuthMiddleware
151
+ {
152
+ struct context
153
+ {
154
+ // Set when a bearer token was verified for this request. Left empty for
155
+ // every request let in some other way — no-auth mode, the local machine, a
156
+ // scoped capability grant — which is a request with nobody to name.
157
+ std::optional<Principal> principal;
158
+ };
159
+
160
+ // Set once mounts are enabled; see callerAddress.
161
+ std::string frontSecret;
162
+
163
+ Authenticator* authenticator = nullptr;
164
+ CapabilityStore* capabilities = nullptr;
165
+ OriginGate* origins = nullptr;
166
+
167
+ void before_handle(crow::request& req, crow::response& res, context& ctx)
168
+ {
169
+ if (!authenticator)
170
+ {
171
+ return;
172
+ }
173
+ if (req.method == crow::HTTPMethod::Options)
174
+ {
175
+ return;
176
+ }
177
+ // WebSocket upgrades are authenticated in the WS onaccept handler instead:
178
+ // a browser cannot set an Authorization header on a handshake, so it
179
+ // presents a ?ticket= bought with its token (ws_tickets.h). Gating here
180
+ // would 401 every notification socket.
181
+ if (req.upgrade)
182
+ {
183
+ return;
184
+ }
185
+ const bool noAuth = authenticator->isNoAuth();
186
+ // The local machine is trusted, even when the runtime is bound to 0.0.0.0
187
+ // for LAN access: the loopback interface cannot be reached from off-host.
188
+ // This lets the owner drive (and start discovery on) their own runtime
189
+ // without having to add themselves to the allow-list; only genuine LAN
190
+ // peers are challenged for a token.
191
+ //
192
+ // A page in the owner's browser is on this machine too, though, and so is
193
+ // every site it has open. Being local therefore lets a request in only
194
+ // when it does not come from a foreign page; see origins.h.
195
+ if (noAuth || isLoopbackHost(callerAddress(req, frontSecret)))
196
+ {
197
+ if (!origins || origins->admits(req))
198
+ {
199
+ return;
200
+ }
201
+ if (noAuth)
202
+ {
203
+ // Nothing else could let it in. Answered without CORS headers (see
204
+ // CorsMiddleware), so the page that sent it can read neither this nor
205
+ // that anything answered: what a server that is not there looks like.
206
+ res.code = 403;
207
+ res.end();
208
+ return;
209
+ }
210
+ // With auth configured, a request refused as a local one may still
211
+ // carry a credential, and is asked for it like any other.
212
+ }
213
+ // Scoped capability tokens are checked before JWT, exactly like an opaque
214
+ // token — but bound to a single method+path. For example, a phone that
215
+ // scanned a freshly minted QR can POST to its one upload runtime and nothing
216
+ // else; a token presented for any other route (or to mint more tokens) fails
217
+ // the scope match here and falls through to the JWT path, which denies it.
218
+ if (capabilities &&
219
+ capabilities->authorize(bearerToken(req.get_header_value("Authorization")),
220
+ crow::method_name(req.method), req.url))
221
+ {
222
+ return;
223
+ }
224
+ const auto result = authenticator->authorize(req.get_header_value("Authorization"));
225
+ if (result.status == AuthStatus::Ok)
226
+ {
227
+ ctx.principal = result.principal;
228
+ return;
229
+ }
230
+ res.code = (result.status == AuthStatus::Forbidden) ? 403 : 401;
231
+ res.end();
232
+ }
233
+
234
+ void after_handle(crow::request&, crow::response&, context&) {}
235
+ };
236
+
237
+ // Says which page may read an answer and send a request that has to be asked
238
+ // for first: the page that is asking, when it is one this server allows.
239
+ //
240
+ // One place states it for every response — a handler's, an error's — so that
241
+ // none can answer a page the server does not allow.
242
+ //
243
+ // A preflight is the exception. Crow answers one as soon as it has read the
244
+ // request line, before the headers that say which page is asking, so it is
245
+ // answered the same for every page: yes, the request may be sent. That gives a
246
+ // foreign page nothing — the request it then sends states its origin and is
247
+ // refused on it, exactly as one that needs no preflight is.
248
+ struct CorsMiddleware
249
+ {
250
+ struct context {};
251
+
252
+ OriginGate* origins = nullptr;
253
+ Authenticator* authenticator = nullptr;
254
+
255
+ void before_handle(crow::request&, crow::response&, context&) {}
256
+
257
+ void after_handle(crow::request& req, crow::response& res, context&)
258
+ {
259
+ if (req.method == crow::HTTPMethod::Options)
260
+ {
261
+ res.set_header("Access-Control-Allow-Origin", "*");
262
+ allowRequest(res);
263
+ return;
264
+ }
265
+ const auto origin = req.get_header_value("Origin");
266
+ if (origin.empty() || !origins)
267
+ {
268
+ return;
269
+ }
270
+ // A server without auth takes no credentials, so `*` — any page, with
271
+ // one — allows nobody there.
272
+ const auto allowed = origins->allowed();
273
+ const bool noAuth = !authenticator || authenticator->isNoAuth();
274
+ if (noAuth ? !allowed.allowsWithoutCredential(origin) : !allowed.allows(origin))
275
+ {
276
+ return;
277
+ }
278
+ res.set_header("Access-Control-Allow-Origin", origin);
279
+ res.add_header("Vary", "Origin");
280
+ allowRequest(res);
281
+ }
282
+
283
+ static void allowRequest(crow::response& res)
284
+ {
285
+ res.set_header("Access-Control-Allow-Methods", "*");
286
+ res.set_header("Access-Control-Allow-Headers",
287
+ "Content-Type, Authorization, Content-Disposition, "
288
+ "X-Upload-Id, X-Chunk-Index, X-Total-Chunks");
289
+ }
290
+ };
291
+
292
+ using CrowApp = crow::Crow<CorsMiddleware, AuthMiddleware>;
293
+
294
+ struct JsonResponse : crow::response
295
+ {
296
+ explicit JsonResponse(const json &_body)
297
+ : crow::response{_body.dump()}
298
+ {
299
+ add_header("Access-Control-Allow-Headers",
300
+ "Content-Type, Authorization, Content-Disposition, "
301
+ "X-Upload-Id, X-Chunk-Index, X-Total-Chunks");
302
+ add_header("Content-Type", "application/json");
303
+ }
304
+ };
305
+ struct Server::impl
306
+ {
307
+ impl(std::shared_ptr<App> a, const std::string& n, const std::string& ao = "*",
308
+ const std::string& dn = "", AuthConfig ac = {})
309
+ : app(a)
310
+ , name(n)
311
+ , allowedOrigins(ao)
312
+ , displayName(dn)
313
+ , authenticator(std::make_unique<Authenticator>(std::move(ac)))
314
+ {
315
+ originGate.setAllowed(AllowedOrigins::parse(allowedOrigins));
316
+ setupRoutes();
317
+ }
318
+
319
+ // Friendly name shown to peers during discovery. Platforms that have a better
320
+ // name than the hostname (e.g. iOS, UIDevice.name) pass it in; otherwise fall
321
+ // back to the machine hostname.
322
+ std::string discoveryName() const
323
+ {
324
+ return displayName.empty() ? discoveryDeviceName() : displayName;
325
+ }
326
+
327
+ void setupRoutes()
328
+ {
329
+ auto& cors = crow.get_middleware<CorsMiddleware>();
330
+ cors.origins = &originGate;
331
+ cors.authenticator = authenticator.get();
332
+
333
+ auto& authMiddleware = crow.get_middleware<AuthMiddleware>();
334
+ authMiddleware.authenticator = authenticator.get();
335
+ authMiddleware.capabilities = &capabilities;
336
+ authMiddleware.origins = &originGate;
337
+
338
+ // A ticket for the notification socket, minted for whoever this request
339
+ // was let in as, so the socket is opened without a token in its address.
340
+ CROW_ROUTE(crow, "/ws-tickets")
341
+ .methods("POST"_method)([this](const crow::request& req) {
342
+ const auto ticket = wsTickets.mint(callerOfRequest(req));
343
+ if (ticket.empty())
344
+ {
345
+ return crow::response(503);
346
+ }
347
+ crow::response res(json{{"ticket", ticket},
348
+ {"expiresInMs", std::chrono::milliseconds(WsTickets<Caller>::TTL).count()}}
349
+ .dump());
350
+ res.set_header("Content-Type", "application/json");
351
+ return res;
352
+ });
353
+
354
+ CROW_ROUTE(crow, "/runtimes")
355
+ .methods("GET"_method)([this]() { return getRuntimes(); });
356
+
357
+ CROW_ROUTE(crow, "/runtimes")
358
+ .methods("DELETE"_method)([this](const crow::request &req) { return deleteAllRuntimes(); });
359
+
360
+ CROW_ROUTE(crow, "/runtimes")
361
+ .methods("POST"_method)([this](const crow::request &req) -> crow::response { return createRuntimes(req); });
362
+
363
+ CROW_ROUTE(crow, "/runtimes/<string>")
364
+ .methods("GET"_method)([this](const crow::request &req, std::string id) { return getRuntimeById(id); });
365
+
366
+ CROW_ROUTE(crow, "/runtimes/<string>")
367
+ .methods("DELETE"_method)([this](const crow::request &req, std::string id) { return deleteRuntime(id); });
368
+
369
+ // Values for the references this runtime's services hold.
370
+ //
371
+ // Provisioning carries them already; this is for the moments it cannot
372
+ // cover — a board being built a service at a time, an entry edited while a
373
+ // board is running, and a re-push after a restart where the services
374
+ // survived but the vault did not. It merges, so a client sending one entry
375
+ // does not strip the rest.
376
+ //
377
+ // POST rather than PUT: it merges rather than replaces, and every other
378
+ // mutation this server takes is a POST — a lone PUT is a method each
379
+ // runtime implementation would have to remember to allow through CORS.
380
+ //
381
+ // There is deliberately no GET. The values go one way: in, and then only to
382
+ // a service resolving a reference for a call it is making. What is held can
383
+ // be *named* — the response says which aliases the runtime now has —
384
+ // because a client needs to show whether a credential is configured.
385
+ CROW_ROUTE(crow, "/runtimes/<string>/secrets")
386
+ .methods("POST"_method)([this](const crow::request &req, std::string runtimeId) -> crow::response { return setSecrets(req, runtimeId); });
387
+
388
+ // Descriptors for the assets this runtime's services reference. Provisioning
389
+ // carries them already; this is for a configuration naming one the runtime
390
+ // was not given, for an asset edited while the board runs — which is how an
391
+ // edit reaches a service without reconfiguring it — and for a re-push after
392
+ // a restart. It merges, and null removes an asset. Answers with ids, never
393
+ // content.
394
+ CROW_ROUTE(crow, "/runtimes/<string>/assets")
395
+ .methods("POST"_method)([this](const crow::request &req, std::string runtimeId) -> crow::response { return setAssets(req, runtimeId); });
396
+
397
+ // Whether an asset resolves here, and to what: a check, not a download.
398
+ CROW_ROUTE(crow, "/runtimes/<string>/assets/<string>")
399
+ .methods("GET"_method)([this](const crow::request &, std::string runtimeId, std::string assetId) -> crow::response {
400
+ auto checked = app->checkRuntimeAsset(runtimeId, assetId);
401
+ if (checked.is_null())
402
+ {
403
+ return crow::response{crow::status::NOT_FOUND};
404
+ }
405
+ return makeJsonResponse(checked);
406
+ });
407
+
408
+ // Change what a running runtime records, without rebuilding it. Separate
409
+ // from POST /runtimes/<id>, which processes data rather than configuring
410
+ // anything.
411
+ CROW_ROUTE(crow, "/runtimes/<string>/state")
412
+ .methods("PATCH"_method)([this](const crow::request &req, std::string runtimeId) -> crow::response { return setRuntimeState(req, runtimeId); });
413
+
414
+ CROW_ROUTE(crow, "/runtimes/<string>/rearrange")
415
+ .methods("POST"_method)([this](const crow::request &req, std::string runtimeId) -> crow::response { return rearrangeServices(req, runtimeId); });
416
+
417
+ CROW_ROUTE(crow, "/runtimes/<string>")
418
+ .methods("POST"_method)([this](const crow::request &req, std::string runtimeId) -> crow::response { return processRuntime(req, runtimeId); });
419
+
420
+ // A person's own client introducing this server to a coordinator, for one
421
+ // runtime of one board: it has asked the coordinator for a ticket and
422
+ // passes it on. This server then connects to the coordinator — the
423
+ // coordinator connects to nothing — and keeps the ticket to reconnect with.
424
+ CROW_ROUTE(crow, "/coordinator-links")
425
+ .methods("POST"_method)([this](const crow::request &req) -> crow::response { return introduceCoordinator(req); });
426
+
427
+ // The links held: which runtimes belong to which board, never a ticket.
428
+ CROW_ROUTE(crow, "/coordinator-links")
429
+ .methods("GET"_method)([this]() -> crow::response {
430
+ return makeJsonResponse(json{{"links", coordinatorLinks ? coordinatorLinks->list() : json::array()}});
431
+ });
432
+
433
+ // Leaves a board: drops the link and the runtime it was for.
434
+ CROW_ROUTE(crow, "/coordinator-links/<string>/<string>")
435
+ .methods("DELETE"_method)([this](const crow::request &req, std::string boardName, std::string runtimeId) -> crow::response {
436
+ const bool removed = coordinatorLinks && coordinatorLinks->remove(boardName, runtimeId);
437
+ return crow::response{removed ? crow::status::OK : crow::status::NOT_FOUND};
438
+ });
439
+
440
+ CROW_ROUTE(crow, "/runtimes/<string>/inputs")
441
+ .methods("GET"_method)([this](const crow::request &req, std::string runtimeId) -> crow::response { return getRuntimeInputs(req, runtimeId); });
442
+
443
+ CROW_ROUTE(crow, "/runtimes/<string>/inputs/<string>")
444
+ .methods("GET"_method)([this](const crow::request &req, std::string runtimeId, std::string inputId) -> crow::response { return getRuntimeInput(req, runtimeId, inputId); });
445
+
446
+ CROW_ROUTE(crow, "/runtimes/<string>/services/<string>")
447
+ .methods("POST"_method)([this](const crow::request &req, std::string runtimeId, std::string instanceId) -> crow::response { return configureService(req, runtimeId, instanceId); });
448
+
449
+ CROW_ROUTE(crow, "/runtimes/<string>/services/<string>/process")
450
+ .methods("POST"_method)([this](const crow::request &req, std::string runtimeId, std::string instanceId) -> crow::response { return processService(req, runtimeId, instanceId); });
451
+
452
+ CROW_ROUTE(crow, "/runtimes/<string>/services/<string>")
453
+ .methods("GET"_method)([this](const crow::request &req, std::string runtimeId, std::string instanceId) -> crow::response { return getServiceState(runtimeId, instanceId); });
454
+
455
+ CROW_ROUTE(crow, "/runtimes/<string>/services/<string>/property/<string>")
456
+ .methods("GET"_method)([this](const crow::request &req, std::string runtimeId, std::string instanceId, std::string propertyId) -> crow::response { return getServiceStateProperty(runtimeId, instanceId, propertyId); });
457
+
458
+ CROW_ROUTE(crow, "/runtimes/<string>/services")
459
+ .methods("GET"_method)([this](const crow::request &req, std::string runtimeId) -> crow::response { return getServices(req, runtimeId); });
460
+
461
+ // TODO this should be a PUT and the POST should replace all services
462
+ CROW_ROUTE(crow, "/runtimes/<string>/services")
463
+ .methods("POST"_method)([this](const crow::request &req, std::string runtimeId) -> crow::response { return createService(req, runtimeId); });
464
+
465
+ CROW_ROUTE(crow, "/runtimes/<string>/services/<string>")
466
+ .methods("DELETE"_method)([this](const crow::request &req, std::string runtimeId, std::string instanceId) -> crow::response { return deleteService(runtimeId, instanceId); });
467
+
468
+ // ── LAN discovery ──
469
+ CROW_ROUTE(crow, "/discover")
470
+ .methods("POST"_method)([this](const crow::request &req) { return startDiscover(req); });
471
+
472
+ CROW_ROUTE(crow, "/discover")
473
+ .methods("GET"_method)([this]() { return getDiscover(); });
474
+
475
+ CROW_ROUTE(crow, "/discover")
476
+ .methods("DELETE"_method)([this]() { return stopDiscover(); });
477
+
478
+ CROW_ROUTE(crow, "/identity")
479
+ .methods("GET"_method)([this]() { return getIdentity(); });
480
+
481
+ // ── Notification WebSocket ──
482
+ // One socket for every runtime in this process; connections bind to a
483
+ // runtime via their protocol handshake. Authenticated in onaccept (the HTTP
484
+ // AuthMiddleware deliberately skips upgrades).
485
+ CROW_WEBSOCKET_ROUTE(crow, "/notifications")
486
+ .onaccept([this](const crow::request& req, void** userdata) { return wsOnAccept(req, userdata); })
487
+ .onmessage([this](crow::websocket::connection& conn, const std::string& message, bool isBinary) { wsOnMessage(conn, message, isBinary); })
488
+ .onclose([this](crow::websocket::connection& conn, const std::string& /*reason*/, uint16_t /*code*/) { wsOnClose(conn); });
489
+ }
490
+
491
+ crow::response getRuntimes();
492
+ crow::response deleteRuntime(const std::string& runtimeId);
493
+ crow::response deleteAllRuntimes();
494
+ crow::response createRuntimes(const crow::request &req);
495
+ crow::response configureService(const crow::request &req, const std::string& runtimeId, const std::string& instanceId);
496
+ crow::response getServiceState(const std::string& runtimeId, const std::string& instanceId);
497
+ crow::response getServiceStateProperty(const std::string& runtimeId, const std::string& instanceId, const std::string& propertyId);
498
+ crow::response getServices(const crow::request &req, const std::string& runtimeId);
499
+ crow::response createService(const crow::request &req, const std::string& runtimeId);
500
+ crow::response deleteService(const std::string& runtimeId, const std::string& instanceId);
501
+ crow::response getRuntimeById(const std::string& id);
502
+ crow::response rearrangeServices(const crow::request &req, const std::string& runtimeId);
503
+ crow::response setSecrets(const crow::request &req, const std::string& runtimeId);
504
+ crow::response setAssets(const crow::request &req, const std::string& runtimeId);
505
+ crow::response setRuntimeState(const crow::request &req, const std::string& runtimeId);
506
+ crow::response introduceCoordinator(const crow::request &req);
507
+ crow::response processRuntime(const crow::request &req, const std::string& runtimeId);
508
+ // Who a request is from, when a token said so; see AuthMiddleware::context.
509
+ Caller callerOfRequest(const crow::request& req);
510
+ crow::response processService(const crow::request &req, const std::string& runtimeId, const std::string& instanceId);
511
+ crow::response getRuntimeInputs(const crow::request &req, const std::string& runtimeId);
512
+ crow::response getRuntimeInput(const crow::request &req, const std::string& runtimeId, const std::string& inputId);
513
+
514
+ // ── Notification WebSocket ──────────────────────────────────────────────────
515
+ bool wsOnAccept(const crow::request& req, void** userdata);
516
+ void wsOnMessage(crow::websocket::connection& conn, const std::string& message, bool isBinary);
517
+ void wsOnClose(crow::websocket::connection& conn);
518
+ void reapIfAbandoned(const std::string& runtimeId);
519
+ void sendNotification(const std::string& runtimeId, const std::string& frame);
520
+ void sendText(const std::string& runtimeId, const std::string& text);
521
+
522
+ JsonResponse makeJsonResponse(const json &_body)
523
+ {
524
+ return JsonResponse(_body);
525
+ }
526
+
527
+ // ── LAN discovery ──────────────────────────────────────────────────────────
528
+ // Advertising is only meaningful when the runtime is LAN-reachable (bound to
529
+ // 0.0.0.0); a localhost-only instance can still browse for peers.
530
+ json discoverState()
531
+ {
532
+ auto peers = json::array();
533
+ for (const auto& peer : discovery.peers())
534
+ {
535
+ peers.push_back(peer);
536
+ }
537
+ return json{
538
+ {"active", discovery.isActive()},
539
+ {"endsAt", discovery.endsAtEpochMs()},
540
+ {"peers", peers},
541
+ };
542
+ }
543
+
544
+ crow::response startDiscover(const crow::request& req)
545
+ {
546
+ int seconds = 30;
547
+ if (!req.body.empty())
548
+ {
549
+ try
550
+ {
551
+ auto body = json::parse(req.body);
552
+ if (body.contains("durationSeconds") && body["durationSeconds"].is_number_integer())
553
+ {
554
+ seconds = body["durationSeconds"].get<int>();
555
+ }
556
+ }
557
+ catch (...) { /* fall back to default */ }
558
+ }
559
+ seconds = std::clamp(seconds, 5, 120);
560
+
561
+ DiscoveryManager::Identity self;
562
+ self.id = instanceId;
563
+ self.name = discoveryName();
564
+ self.port = crow.port();
565
+ discovery.start(self, /*advertise=*/bindAddress == "0.0.0.0", seconds);
566
+ return makeJsonResponse(discoverState());
567
+ }
568
+
569
+ crow::response getDiscover()
570
+ {
571
+ return makeJsonResponse(discoverState());
572
+ }
573
+
574
+ crow::response stopDiscover()
575
+ {
576
+ discovery.stop();
577
+ return makeJsonResponse(discoverState());
578
+ }
579
+
580
+ crow::response getIdentity()
581
+ {
582
+ return makeJsonResponse(json{
583
+ {"id", instanceId},
584
+ {"name", discoveryName()},
585
+ {"platform", discoveryPlatformName()},
586
+ {"port", crow.port()},
587
+ {"exposed", bindAddress == "0.0.0.0"},
588
+ });
589
+ }
590
+
591
+ CrowApp crow;
592
+ std::string externalIP;
593
+ std::shared_ptr<App> app;
594
+ std::string name;
595
+ std::string allowedOrigins;
596
+ OriginGate originGate;
597
+ // What a browser opens the notification socket with instead of its token;
598
+ // see http/ws_tickets.h.
599
+ WsTickets<Caller> wsTickets;
600
+ std::string displayName;
601
+ std::string bindAddress;
602
+ std::string instanceId = generateUUID();
603
+ std::unique_ptr<Authenticator> authenticator;
604
+ // Set by a host that serves mounts; see Server::enableMounts.
605
+ std::unique_ptr<FrontDoor> frontDoor;
606
+ Server::MountOptions mountOptions;
607
+ std::atomic<unsigned int> publicPort{0};
608
+ // Set by a host that lets this server be introduced to coordinators.
609
+ std::unique_ptr<CoordinatorLinks> coordinatorLinks;
610
+ CapabilityStore capabilities;
611
+ DiscoveryManager discovery;
612
+
613
+ // runtimeId → live notification connections. Guarded by wsMutex because it is
614
+ // touched from Crow's IO thread (accept/message/close) and the App event-loop
615
+ // thread (sendNotification).
616
+ std::mutex wsMutex;
617
+ std::map<std::string, std::set<crow::websocket::connection*>> wsByRuntime;
618
+ };
619
+
620
+ Server::Server(
621
+ std::shared_ptr<App> app,
622
+ const std::string& name,
623
+ const std::string& allowedOrigins,
624
+ const std::string& displayName,
625
+ AuthConfig authConfig
626
+ ) : m_impl(std::make_unique<impl>(app, name, allowedOrigins, displayName, std::move(authConfig)))
627
+ {
628
+ app->setServer(this);
629
+ }
630
+
631
+ Server::~Server()
632
+ {
633
+ m_impl->app->setServer(nullptr);
634
+ }
635
+
636
+ void Server::handleRequest(crow::request& req, crow::response& res)
637
+ {
638
+ m_impl->crow.handle_full(req, res);
639
+ }
640
+
641
+ void Server::start(const std::string& externalIP, unsigned int port, const std::string& bindAddress)
642
+ {
643
+ m_impl->externalIP = externalIP;
644
+ // A name this server is reached by, when it was given one rather than an
645
+ // address; see isKnownHost.
646
+ m_impl->originGate.addOwnName(externalIP);
647
+ m_impl->bindAddress = bindAddress;
648
+ if (!m_impl->frontDoor)
649
+ {
650
+ m_impl->crow.bindaddr(bindAddress).port(port).run();
651
+ return;
652
+ }
653
+
654
+ // The front door takes the port. The api listens on loopback, on a port the
655
+ // OS picks and nobody is told, and is reached through the front door only.
656
+ m_impl->publicPort = port;
657
+ std::atomic<bool> failed{false};
658
+ auto* impl = m_impl.get();
659
+ std::thread opener([impl, &failed, bindAddress, port]() {
660
+ impl->crow.wait_for_server_start(std::chrono::milliseconds(30000));
661
+ const auto bound = impl->frontDoor->start(
662
+ bindAddress, static_cast<unsigned short>(port), impl->crow.port());
663
+ if (bound == 0)
664
+ {
665
+ failed = true;
666
+ impl->crow.stop();
667
+ return;
668
+ }
669
+ impl->publicPort = bound;
670
+ });
671
+ m_impl->crow.bindaddr("127.0.0.1").port(0).run();
672
+ opener.join();
673
+ m_impl->frontDoor->stop();
674
+ if (failed)
675
+ {
676
+ throw std::runtime_error("could not listen on " + bindAddress + ":" + std::to_string(port));
677
+ }
678
+ }
679
+
680
+ void Server::enableMounts(MountOptions options)
681
+ {
682
+ if (options.secret.empty())
683
+ {
684
+ options.secret = generateUUID() + generateUUID();
685
+ }
686
+ m_impl->mountOptions = std::move(options);
687
+ m_impl->originGate.addOwnName(m_impl->mountOptions.externalHost);
688
+ m_impl->publicPort = m_impl->mountOptions.port;
689
+ if (m_impl->externalIP.empty())
690
+ {
691
+ m_impl->externalIP = m_impl->mountOptions.externalHost;
692
+ }
693
+ // Known to this process only: what makes the caller's address, as the front
694
+ // door passes it on, something the api can believe.
695
+ m_impl->frontDoor = std::make_unique<FrontDoor>(generateUUID() + generateUUID());
696
+ m_impl->crow.get_middleware<AuthMiddleware>().frontSecret = m_impl->frontDoor->frontSecret();
697
+ }
698
+
699
+ MountHandle Server::mount(const std::string& boardName, const std::string& runtimeId,
700
+ const std::string& name, MountAdopter adopter,
701
+ bool deployed)
702
+ {
703
+ if (!m_impl->frontDoor)
704
+ {
705
+ return {};
706
+ }
707
+ const auto id = deriveMountId(m_impl->mountOptions.secret, "", boardName, runtimeId, name);
708
+ auto release = m_impl->frontDoor->mount(id, std::move(adopter), deployed);
709
+ const std::string base = !m_impl->mountOptions.externalUrl.empty()
710
+ ? m_impl->mountOptions.externalUrl
711
+ : "http://" + m_impl->externalIP + ":" + std::to_string(port());
712
+ return MountHandle(name, base + MOUNT_PREFIX + "/" + id, std::move(release));
713
+ }
714
+
715
+ void Server::enableCoordinatorLinks(std::shared_ptr<LinkStore> store,
716
+ CoordinatorLinksOptions options)
717
+ {
718
+ m_impl->coordinatorLinks = std::make_unique<CoordinatorLinks>(
719
+ m_impl->app, std::move(store), std::move(options));
720
+ m_impl->coordinatorLinks->restore();
721
+ }
722
+
723
+ void Server::stop()
724
+ {
725
+ // Before the routes go: a link's connection is closed, its ticket kept.
726
+ if (m_impl->coordinatorLinks)
727
+ {
728
+ m_impl->coordinatorLinks->stop();
729
+ }
730
+ m_impl->crow.stop();
731
+ }
732
+
733
+ unsigned int Server::port() const
734
+ {
735
+ // The port callers use, which with mounts is the front door's.
736
+ return m_impl->frontDoor ? m_impl->publicPort.load() : m_impl->crow.port();
737
+ }
738
+
739
+ const std::string& Server::externalIP() const
740
+ {
741
+ return m_impl->externalIP;
742
+ }
743
+
744
+ const std::string& Server::name() const
745
+ {
746
+ return m_impl->name;
747
+ }
748
+
749
+ const std::string& Server::allowedOrigins() const
750
+ {
751
+ return m_impl->allowedOrigins;
752
+ }
753
+
754
+ void Server::allowOrigins(const std::vector<std::string>& origins)
755
+ {
756
+ m_impl->originGate.add(origins);
757
+ }
758
+
759
+ crow::response Server::impl::getRuntimes()
760
+ {
761
+ auto arr = json::array();
762
+ for (auto& rt : app->getRuntimes())
763
+ {
764
+ arr.push_back(jsonSerialise(rt));
765
+ }
766
+ return makeJsonResponse(json{{"runtimes", arr}, {"registry", app->getRegistry()}, {"server", kRuntimeServerKind}, {"coordinatorLinks", coordinatorLinks != nullptr}, {"boardRuntimes", true}});
767
+ }
768
+
769
+
770
+ crow::response Server::impl::deleteRuntime(const std::string& runtimeId)
771
+ {
772
+ // Idempotent: a runtime already gone is the desired end state, not an error.
773
+ // A client removing a runtime closes its notification socket first, and that
774
+ // close reaps a garbage-collected runtime on its own — so by the time this
775
+ // explicit DELETE arrives the runtime is frequently already removed. Returning
776
+ // NOT_FOUND there surfaces a spurious "Failed to remove runtime" to the user.
777
+ app->removeRuntime(runtimeId);
778
+ return makeJsonResponse(json{{"id", runtimeId}});
779
+ }
780
+
781
+ crow::response Server::impl::deleteAllRuntimes()
782
+ {
783
+ app->removeAllRuntimes();
784
+ return crow::response{crow::status::OK};
785
+ }
786
+
787
+ crow::response Server::impl::createRuntimes(const crow::request &req)
788
+ {
789
+ if (req.body.empty())
790
+ {
791
+ return crow::response(crow::status::BAD_REQUEST); // same as crow::response(400)
792
+ }
793
+
794
+ auto arr = json::array();
795
+ auto body = json::parse(req.body);
796
+ if (!body.is_array() && !body.is_object())
797
+ {
798
+ return crow::response(crow::status::BAD_REQUEST);
799
+ }
800
+ auto runtimeBody = body.is_array() ? body : json::array({body});
801
+ for (auto it : runtimeBody)
802
+ {
803
+ auto rtConfig = validateRuntime(it);
804
+ if (!rtConfig)
805
+ {
806
+ return crow::response(crow::status::BAD_REQUEST);
807
+ }
808
+ auto createdConfig = app->createRuntime(*rtConfig);
809
+ arr.push_back(jsonSerialise(createdConfig));
810
+ }
811
+ return makeJsonResponse({json{{"runtimes", arr}, {"registry", app->getRegistry()}, {"server", kRuntimeServerKind}, {"coordinatorLinks", coordinatorLinks != nullptr}, {"boardRuntimes", true}}});
812
+ }
813
+
814
+ crow::response Server::impl::configureService(const crow::request &req, const std::string& runtimeId, const std::string& instanceId)
815
+ {
816
+ if (req.body.empty())
817
+ {
818
+ return crow::response(crow::status::BAD_REQUEST); // same as crow::response(400)
819
+ }
820
+
821
+ auto body = json::parse(req.body);
822
+ if (!body.is_object())
823
+ {
824
+ return crow::response(crow::status::BAD_REQUEST);
825
+ }
826
+ const auto context = ProcessContext::forClient(json(), callerOfRequest(req));
827
+ auto config = app->configureService(runtimeId, instanceId, body, "", &context);
828
+ if (config.is_null())
829
+ {
830
+ return crow::response(crow::status::NOT_FOUND);
831
+ }
832
+
833
+ return makeJsonResponse({config});
834
+ }
835
+
836
+ crow::response Server::impl::getServiceState(const std::string& runtimeId, const std::string& instanceId)
837
+ {
838
+ auto rt = app->getRuntime(runtimeId);
839
+ if (!rt)
840
+ {
841
+ return crow::response{crow::status::NOT_FOUND};
842
+ }
843
+ auto state = app->getServiceState(runtimeId, instanceId);
844
+ if (state.is_null())
845
+ {
846
+ return crow::response{crow::status::NOT_FOUND};
847
+ }
848
+ return makeJsonResponse({state});
849
+ }
850
+
851
+ crow::response Server::impl::getServiceStateProperty(const std::string& runtimeId, const std::string& instanceId, const std::string& propertyId)
852
+ {
853
+ auto rt = app->getRuntime(runtimeId);
854
+ if (!rt)
855
+ {
856
+ return crow::response{crow::status::NOT_FOUND};
857
+ }
858
+ auto state = app->getServiceState(runtimeId, instanceId);
859
+ if (state.is_null())
860
+ {
861
+ return crow::response{crow::status::NOT_FOUND};
862
+ }
863
+ auto property = state[propertyId];
864
+ if (property.is_null())
865
+ {
866
+ std::cout << "Propety not found in: " << state << std::endl;
867
+ return crow::response{crow::status::NOT_FOUND};
868
+ }
869
+ return makeJsonResponse(property);
870
+ }
871
+
872
+ crow::response Server::impl::getServices(const crow::request &req, const std::string& runtimeId)
873
+ {
874
+ auto rt = app->getRuntime(runtimeId);
875
+ if (!rt)
876
+ {
877
+ return crow::response{crow::status::NOT_FOUND};
878
+ }
879
+ auto services = app->getServices(runtimeId);
880
+ return makeJsonResponse(services);
881
+ }
882
+
883
+ crow::response Server::impl::createService(const crow::request &req, const std::string& runtimeId)
884
+ {
885
+ auto rt = app->getRuntime(runtimeId);
886
+ if (!rt)
887
+ {
888
+ return crow::response{crow::status::NOT_FOUND};
889
+ }
890
+
891
+ if (req.body.empty())
892
+ {
893
+ return crow::response(crow::status::BAD_REQUEST);
894
+ }
895
+
896
+ auto arr = json::array();
897
+ auto body = json::parse(req.body);
898
+ if (body.is_null())
899
+ {
900
+ return crow::response(crow::status::BAD_REQUEST);
901
+ }
902
+
903
+ auto serviceConfig = validateService(body);
904
+ if (!serviceConfig)
905
+ {
906
+ return crow::response(crow::status::BAD_REQUEST);
907
+ }
908
+
909
+ auto res = app->appendService(runtimeId, *serviceConfig);
910
+ if (res.is_null())
911
+ {
912
+ return crow::response(crow::status::BAD_REQUEST);
913
+ }
914
+
915
+ return makeJsonResponse(res);
916
+ }
917
+
918
+ crow::response Server::impl::deleteService(const std::string& runtimeId, const std::string& instanceId)
919
+ {
920
+ auto rt = app->getRuntime(runtimeId);
921
+ if (!rt)
922
+ {
923
+ return crow::response{crow::status::NOT_FOUND};
924
+ }
925
+
926
+ auto res = app->removeService(runtimeId, instanceId);
927
+ return makeJsonResponse(res);
928
+ }
929
+
930
+ crow::response Server::impl::getRuntimeById(const std::string& id)
931
+ {
932
+ auto rt = app->getRuntime(id);
933
+ if (!rt)
934
+ {
935
+ return crow::response{crow::status::NOT_FOUND};
936
+ }
937
+ return makeJsonResponse(jsonSerialise(*rt));
938
+ }
939
+
940
+ crow::response Server::impl::introduceCoordinator(const crow::request &req)
941
+ {
942
+ if (!coordinatorLinks)
943
+ {
944
+ return crow::response{crow::status::NOT_FOUND};
945
+ }
946
+ const json body = json::parse(req.body, nullptr, /*allow_exceptions=*/false);
947
+ const auto text = [&body](const char* key) {
948
+ return body.is_object() && body.contains(key) && body[key].is_string()
949
+ ? body[key].get<std::string>() : std::string();
950
+ };
951
+ const LinkRecord record{text("boardName"), text("runtimeId"),
952
+ text("coordinatorUrl"), text("ticket")};
953
+ if (record.boardName.empty() || record.runtimeId.empty() ||
954
+ record.coordinatorUrl.empty() || record.ticket.empty())
955
+ {
956
+ return crow::response(crow::status::BAD_REQUEST);
957
+ }
958
+ // The values for the references that runtime's services carry. Handed to the
959
+ // runtime when the coordinator builds it, and not sent to the coordinator.
960
+ const auto reason = coordinatorLinks->introduce(
961
+ record, readSecretsPayload(body.contains("secrets") ? body["secrets"] : json()));
962
+ if (!reason.empty())
963
+ {
964
+ auto refused = makeJsonResponse(json{{"error", reason}});
965
+ refused.code = 502;
966
+ return refused;
967
+ }
968
+ auto connected = makeJsonResponse(json{{"connected", true}});
969
+ connected.code = 201;
970
+ return connected;
971
+ }
972
+
973
+ crow::response Server::impl::setRuntimeState(const crow::request &req, const std::string& runtimeId)
974
+ {
975
+ json body;
976
+ try
977
+ {
978
+ body = json::parse(req.body);
979
+ }
980
+ catch (const std::exception&)
981
+ {
982
+ return crow::response(crow::status::BAD_REQUEST);
983
+ }
984
+ if (!body.is_object())
985
+ {
986
+ return crow::response(crow::status::BAD_REQUEST);
987
+ }
988
+ auto settings = app->setRuntimeState(runtimeId, body);
989
+ if (settings.is_null())
990
+ {
991
+ return crow::response{crow::status::NOT_FOUND};
992
+ }
993
+ return makeJsonResponse(settings);
994
+ }
995
+
996
+ crow::response Server::impl::setSecrets(const crow::request &req, const std::string& runtimeId)
997
+ {
998
+ json body;
999
+ try
1000
+ {
1001
+ body = json::parse(req.body);
1002
+ }
1003
+ catch (const std::exception&)
1004
+ {
1005
+ return crow::response(crow::status::BAD_REQUEST);
1006
+ }
1007
+ if (!body.is_object())
1008
+ {
1009
+ return crow::response(crow::status::BAD_REQUEST);
1010
+ }
1011
+
1012
+ auto held = app->setRuntimeSecrets(runtimeId, readSecretsPayload(body));
1013
+ if (held.is_null())
1014
+ {
1015
+ return crow::response{crow::status::NOT_FOUND};
1016
+ }
1017
+ return makeJsonResponse(held);
1018
+ }
1019
+
1020
+ crow::response Server::impl::setAssets(const crow::request &req, const std::string& runtimeId)
1021
+ {
1022
+ json body;
1023
+ try
1024
+ {
1025
+ body = json::parse(req.body);
1026
+ }
1027
+ catch (const std::exception&)
1028
+ {
1029
+ return crow::response(crow::status::BAD_REQUEST);
1030
+ }
1031
+ if (!body.is_object() && !body.is_array())
1032
+ {
1033
+ return crow::response(crow::status::BAD_REQUEST);
1034
+ }
1035
+
1036
+ auto held = app->setRuntimeAssets(runtimeId, readAssetsPayload(body));
1037
+ if (held.is_null())
1038
+ {
1039
+ return crow::response{crow::status::NOT_FOUND};
1040
+ }
1041
+ return makeJsonResponse(held);
1042
+ }
1043
+
1044
+ crow::response Server::impl::rearrangeServices(const crow::request &req, const std::string& runtimeId)
1045
+ {
1046
+ auto rt = app->getRuntime(runtimeId);
1047
+ if (!rt)
1048
+ {
1049
+ return crow::response{crow::status::NOT_FOUND};
1050
+ }
1051
+
1052
+ auto body = json::parse(req.body);
1053
+ if (!body.is_array())
1054
+ {
1055
+ return crow::response(crow::status::BAD_REQUEST);
1056
+ }
1057
+
1058
+ auto serviceOrdering = validateServiceOrdering(body);
1059
+ if (!serviceOrdering)
1060
+ {
1061
+ return crow::response(crow::status::BAD_REQUEST);
1062
+ }
1063
+
1064
+ auto res = app->rearrangeServices(runtimeId, *serviceOrdering);
1065
+ if (res.is_null())
1066
+ {
1067
+ return crow::response(crow::status::BAD_REQUEST);
1068
+ }
1069
+
1070
+ return makeJsonResponse(res);
1071
+ }
1072
+
1073
+ // Builds the Data variant a request body means, by its content type. Shared
1074
+ // by the runtime-wide process route and the per-service one, so an upload
1075
+ // reaches a service the same way whichever entry point it arrives through.
1076
+ static std::optional<Data> buildInputData(const crow::request& req,
1077
+ const std::string& contentType)
1078
+ {
1079
+
1080
+ if (contentType == "application/json")
1081
+ {
1082
+ try
1083
+ {
1084
+ auto body = json::parse(req.body);
1085
+ // `null` is a payload: it says run with nothing on the input, which is
1086
+ // how a caller writes "no input" in a format that has no undefined.
1087
+ // Undefined is what the first service is handed, the same as over the
1088
+ // socket. A body that is absent entirely is still refused, above.
1089
+ if (body.is_null())
1090
+ return Data();
1091
+ if (!body.is_object())
1092
+ return std::nullopt;
1093
+ return Data(std::move(body));
1094
+ }
1095
+ catch (...) { return std::nullopt; }
1096
+ }
1097
+
1098
+ if (contentType.rfind("text/plain", 0) == 0)
1099
+ return Data(req.body);
1100
+
1101
+ if (contentType.rfind("multipart/form-data", 0) == 0)
1102
+ {
1103
+ auto msg = crow::multipart::message(req);
1104
+ if (msg.parts.empty())
1105
+ return std::nullopt;
1106
+
1107
+ // Use the first part as the binary payload; collect its headers as meta.
1108
+ auto& firstPart = msg.parts[0];
1109
+ BinaryData binary(firstPart.body.begin(), firstPart.body.end());
1110
+
1111
+ json meta = json::object();
1112
+ for (auto& [headerName, header] : firstPart.headers)
1113
+ {
1114
+ json headerMeta;
1115
+ headerMeta["value"] = header.value;
1116
+ for (auto& [paramName, paramValue] : header.params)
1117
+ headerMeta["params"][paramName] = paramValue;
1118
+ meta[headerName] = std::move(headerMeta);
1119
+ }
1120
+
1121
+ return Data(MixedData{std::move(meta), std::move(binary)});
1122
+ }
1123
+
1124
+ // Raw binary fallback: image/*, application/octet-stream, etc.
1125
+ // Promote to MixedData when a filename is present in Content-Disposition so
1126
+ // that downstream services (e.g. filesystem) can use the original filename.
1127
+ {
1128
+ BinaryData binary(req.body.begin(), req.body.end());
1129
+ auto contentDisposition = req.get_header_value("Content-Disposition");
1130
+ std::string filename;
1131
+ if (!contentDisposition.empty())
1132
+ {
1133
+ auto pos = contentDisposition.find("filename=");
1134
+ if (pos != std::string::npos)
1135
+ {
1136
+ filename = contentDisposition.substr(pos + 9);
1137
+ // Strip surrounding quotes if present
1138
+ if (filename.size() >= 2 && filename.front() == '"')
1139
+ filename = filename.substr(1, filename.size() - 2);
1140
+ }
1141
+ }
1142
+ if (!filename.empty())
1143
+ {
1144
+ json meta = json::object();
1145
+ meta["path"] = filename;
1146
+ auto uploadId = req.get_header_value("X-Upload-Id");
1147
+ auto chunkIndexStr = req.get_header_value("X-Chunk-Index");
1148
+ auto totalChunksStr= req.get_header_value("X-Total-Chunks");
1149
+ if (!uploadId.empty()) meta["uploadId"] = uploadId;
1150
+ if (!chunkIndexStr.empty())
1151
+ {
1152
+ try { meta["chunkIndex"] = std::stoi(chunkIndexStr); } catch (...) {}
1153
+ }
1154
+ if (!totalChunksStr.empty())
1155
+ {
1156
+ try { meta["totalChunks"] = std::stoi(totalChunksStr); } catch (...) {}
1157
+ }
1158
+ return Data(MixedData{std::move(meta), std::move(binary)});
1159
+ }
1160
+ return Data(std::move(binary));
1161
+ }
1162
+ }
1163
+
1164
+ Caller Server::impl::callerOfRequest(const crow::request& req)
1165
+ {
1166
+ // A request a host hands over in-process (Server::handleRequest) never
1167
+ // passed the middleware, so there is no context to read — and nobody to
1168
+ // name: it comes from the host's own page, not from a token.
1169
+ if (!req.middleware_context)
1170
+ {
1171
+ return {};
1172
+ }
1173
+ return callerOf(crow.get_context<AuthMiddleware>(req).principal);
1174
+ }
1175
+
1176
+ crow::response Server::impl::processRuntime(const crow::request &req, const std::string& runtimeId)
1177
+ {
1178
+ auto rt = app->getRuntime(runtimeId);
1179
+ if (!rt)
1180
+ {
1181
+ return crow::response{crow::status::NOT_FOUND};
1182
+ }
1183
+
1184
+ if (req.body.empty())
1185
+ {
1186
+ return crow::response(crow::status::BAD_REQUEST);
1187
+ }
1188
+
1189
+ auto contentType = req.get_header_value("Content-Type");
1190
+
1191
+ auto inputData = buildInputData(req, contentType);
1192
+ if (!inputData)
1193
+ {
1194
+ std::cerr << "processRuntime: cannot parse body with Content-Type: " << contentType << '\n';
1195
+ return crow::response(crow::status::BAD_REQUEST);
1196
+ }
1197
+
1198
+ try
1199
+ {
1200
+ // An external HTTP caller is not continuing a run, it is starting one —
1201
+ // as whoever its token says it is.
1202
+ auto result = app->processRuntimeAs(
1203
+ runtimeId, std::move(*inputData), ProcessContext::forClient(json(), callerOfRequest(req)));
1204
+
1205
+ if (auto j = getJSONFromData(result))
1206
+ return makeJsonResponse(*j);
1207
+
1208
+ if (auto b = getBinaryFromData(result))
1209
+ {
1210
+ crow::response res(200);
1211
+ res.body = std::string(b->begin(), b->end());
1212
+ res.set_header("Content-Type", "application/octet-stream");
1213
+ return res;
1214
+ }
1215
+
1216
+ if (auto s = getStringFromData(result))
1217
+ {
1218
+ crow::response res(200);
1219
+ res.body = *s;
1220
+ res.set_header("Content-Type", "text/plain");
1221
+ return res;
1222
+ }
1223
+
1224
+ return crow::response(crow::status::OK);
1225
+ }
1226
+ catch(const std::exception& e)
1227
+ {
1228
+ std::cerr << "processRuntime error: " << e.what() << '\n';
1229
+ return crow::response(crow::status::INTERNAL_SERVER_ERROR);
1230
+ }
1231
+ }
1232
+
1233
+ // Runs the pipeline starting at one service, with a given payload.
1234
+ //
1235
+ // Distinct from configuring it: configure says what a service *is*, this says
1236
+ // do your job with this. A facade button had only the former, so anything it
1237
+ // needed to cause had to be smuggled in as a config field a service read as a
1238
+ // command.
1239
+ crow::response Server::impl::processService(const crow::request &req, const std::string& runtimeId, const std::string& instanceId)
1240
+ {
1241
+ auto rt = app->getRuntime(runtimeId);
1242
+ if (!rt)
1243
+ {
1244
+ return crow::response{crow::status::NOT_FOUND};
1245
+ }
1246
+
1247
+ auto contentType = req.get_header_value("Content-Type");
1248
+ // An empty body is a service being told to act on nothing, which is a
1249
+ // reasonable thing to ask of one that takes no input.
1250
+ auto inputData = req.body.empty() ? std::optional<Data>(json::object())
1251
+ : buildInputData(req, contentType);
1252
+ if (!inputData)
1253
+ {
1254
+ return crow::response(crow::status::BAD_REQUEST);
1255
+ }
1256
+
1257
+ try
1258
+ {
1259
+ // The run is the one the body names, or a new one. The caller is never
1260
+ // the body's to name: it is whoever the token was verified as.
1261
+ json wire;
1262
+ if (const auto asJson = getJSONFromData(*inputData); asJson && asJson->is_object())
1263
+ {
1264
+ if (const auto it = asJson->find("__context"); it != asJson->end())
1265
+ {
1266
+ wire = *it;
1267
+ }
1268
+ }
1269
+ auto result = app->processServiceAtAs(
1270
+ runtimeId, instanceId, std::move(*inputData),
1271
+ ProcessContext::forClient(wire, callerOfRequest(req)));
1272
+
1273
+ if (auto j = getJSONFromData(result))
1274
+ return makeJsonResponse(*j);
1275
+
1276
+ if (auto b = getBinaryFromData(result))
1277
+ {
1278
+ crow::response res(200);
1279
+ res.body = std::string(b->begin(), b->end());
1280
+ res.set_header("Content-Type", "application/octet-stream");
1281
+ return res;
1282
+ }
1283
+
1284
+ if (auto s = getStringFromData(result))
1285
+ {
1286
+ crow::response res(200);
1287
+ res.body = *s;
1288
+ res.set_header("Content-Type", "text/plain");
1289
+ return res;
1290
+ }
1291
+
1292
+ return crow::response(crow::status::OK);
1293
+ }
1294
+ catch (const std::exception& e)
1295
+ {
1296
+ // The runtime holds no service by that id; anything else it throws is a
1297
+ // service failing, which is not the caller's mistake.
1298
+ std::cerr << "processService error: " << e.what() << '\n';
1299
+ return crow::response(crow::status::NOT_FOUND);
1300
+ }
1301
+ }
1302
+
1303
+ crow::response Server::impl::getRuntimeInputs(const crow::request &req, const std::string& runtimeId)
1304
+ {
1305
+ auto rt = app->getRuntime(runtimeId);
1306
+ if (!rt)
1307
+ {
1308
+ return crow::response{crow::status::NOT_FOUND};
1309
+ }
1310
+
1311
+ auto arr = json::array();
1312
+ for (auto& input : rt->inputs)
1313
+ {
1314
+ arr.push_back(jsonSerialise(input));
1315
+ }
1316
+ return makeJsonResponse(arr);
1317
+ }
1318
+
1319
+ crow::response Server::impl::getRuntimeInput(const crow::request &req, const std::string& runtimeId, const std::string& inputId)
1320
+ {
1321
+ auto rt = app->getRuntime(runtimeId);
1322
+ if (!rt)
1323
+ {
1324
+ return crow::response{crow::status::NOT_FOUND};
1325
+ }
1326
+ for (auto& input : rt->inputs)
1327
+ {
1328
+ if (input.id == inputId)
1329
+ {
1330
+ return makeJsonResponse(jsonSerialise(input));
1331
+ }
1332
+ }
1333
+ return crow::response{crow::status::NOT_FOUND};
1334
+ }
1335
+
1336
+ bool Server::impl::wsOnAccept(const crow::request& req, void** userdata)
1337
+ {
1338
+ // Same policy as the REST AuthMiddleware: no-auth mode and loopback clients
1339
+ // are allowed unless a foreign page is asking (a browser always states the
1340
+ // page's origin on a handshake); otherwise the socket must present a ticket,
1341
+ // or a token that verifies and is allow-listed (see below).
1342
+ const bool local = authenticator->isNoAuth() ||
1343
+ isLoopbackHost(callerAddress(req, frontDoor ? frontDoor->frontSecret() : std::string()));
1344
+ bool allowed = local && originGate.admits(req);
1345
+ // Whoever a token names; nobody for a socket let in without one.
1346
+ Caller caller;
1347
+ // A browser presents a ticket it was given for its token (ws_tickets.h);
1348
+ // `?access_token=` is what browsers sent before tickets, and is still taken,
1349
+ // though it puts the token where logs keep it.
1350
+ if (!allowed)
1351
+ {
1352
+ if (const char* ticket = req.url_params.get("ticket"))
1353
+ {
1354
+ if (auto held = wsTickets.redeem(ticket))
1355
+ {
1356
+ allowed = true;
1357
+ caller = std::move(*held);
1358
+ }
1359
+ }
1360
+ }
1361
+ if (!allowed)
1362
+ {
1363
+ if (const char* token = req.url_params.get("access_token"))
1364
+ {
1365
+ const auto result = authenticator->authorize(std::string("Bearer ") + token);
1366
+ allowed = result.status == AuthStatus::Ok;
1367
+ if (allowed)
1368
+ {
1369
+ caller = callerOf(result.principal);
1370
+ }
1371
+ }
1372
+ }
1373
+ if (!allowed)
1374
+ {
1375
+ return false; // reject the upgrade
1376
+ }
1377
+ auto* state = new WsConnState();
1378
+ state->caller = caller;
1379
+ *userdata = state;
1380
+ return true;
1381
+ }
1382
+
1383
+ void Server::impl::wsOnMessage(crow::websocket::connection& conn, const std::string& message, bool isBinary)
1384
+ {
1385
+ auto* state = static_cast<WsConnState*>(conn.userdata());
1386
+ if (!state)
1387
+ {
1388
+ return;
1389
+ }
1390
+
1391
+ // The first frame is the protocol handshake ({type, id}); it binds this
1392
+ // connection to a runtime. Everything after it is runtime traffic.
1393
+ if (state->runtimeId.empty())
1394
+ {
1395
+ try
1396
+ {
1397
+ auto protocol = WebsocketProtocol::parse(message);
1398
+ state->runtimeId = protocol.id;
1399
+ state->type = protocol.type;
1400
+ std::lock_guard<std::mutex> lock(wsMutex);
1401
+ wsByRuntime[state->runtimeId].insert(&conn);
1402
+ }
1403
+ catch (const std::exception& e)
1404
+ {
1405
+ std::cerr << "Server WS: invalid protocol handshake: " << e.what() << std::endl;
1406
+ }
1407
+ return;
1408
+ }
1409
+
1410
+ app->dispatchRuntimeWsMessage(state->runtimeId, message, isBinary, &state->caller);
1411
+ }
1412
+
1413
+ void Server::impl::wsOnClose(crow::websocket::connection& conn)
1414
+ {
1415
+ auto* state = static_cast<WsConnState*>(conn.userdata());
1416
+ if (!state)
1417
+ {
1418
+ return;
1419
+ }
1420
+ bool abandoned = false;
1421
+ {
1422
+ std::lock_guard<std::mutex> lock(wsMutex);
1423
+ auto it = wsByRuntime.find(state->runtimeId);
1424
+ if (it != wsByRuntime.end())
1425
+ {
1426
+ it->second.erase(&conn);
1427
+ if (it->second.empty())
1428
+ {
1429
+ wsByRuntime.erase(it);
1430
+ abandoned = true;
1431
+ }
1432
+ }
1433
+ }
1434
+
1435
+ if (abandoned)
1436
+ {
1437
+ reapIfAbandoned(state->runtimeId);
1438
+ }
1439
+
1440
+ delete state;
1441
+ conn.userdata(nullptr);
1442
+ }
1443
+
1444
+ /**
1445
+ * Tears down a runtime whose creator asked for cleanup, now that its last
1446
+ * client has disconnected.
1447
+ *
1448
+ * Whoever created it said whether it should outlive its clients. A browser
1449
+ * running a board is that board's controller and asks for cleanup: closing the
1450
+ * tab should not leave runtimes behind. A coordinator, a config file or a
1451
+ * script says nothing, and their runtimes stay until deleted — a headless
1452
+ * runtime is not an abandoned one, and a runtime is never reaped because of who
1453
+ * happened to connect to it.
1454
+ */
1455
+ void Server::impl::reapIfAbandoned(const std::string& runtimeId)
1456
+ {
1457
+ // getConfiguration carries the declaration back, so the public API is enough
1458
+ // to answer this — no need to reach for the Runtime itself.
1459
+ auto config = app->getRuntime(runtimeId);
1460
+ if (!config || !config->garbageCollected)
1461
+ {
1462
+ return;
1463
+ }
1464
+ std::cout << "Server: releasing runtime \"" << runtimeId
1465
+ << "\" — its last client disconnected and it asked to be cleaned up"
1466
+ << std::endl;
1467
+ app->removeRuntime(runtimeId);
1468
+ }
1469
+
1470
+ void Server::impl::sendText(const std::string& runtimeId, const std::string& text)
1471
+ {
1472
+ // A text frame rather than the binary one notifications take: what travels
1473
+ // here is a JSON message whose `type` the receiver dispatches on, not a YAS
1474
+ // frame carrying a Data.
1475
+ std::lock_guard<std::mutex> lock(wsMutex);
1476
+ auto it = wsByRuntime.find(runtimeId);
1477
+ if (it == wsByRuntime.end())
1478
+ {
1479
+ return;
1480
+ }
1481
+ for (auto* conn : it->second)
1482
+ {
1483
+ auto* state = static_cast<WsConnState*>(conn->userdata());
1484
+ if (state && state->type == "writer")
1485
+ {
1486
+ continue; // write-only clients don't receive anything back
1487
+ }
1488
+ conn->send_text(text);
1489
+ }
1490
+ }
1491
+
1492
+ void Server::impl::sendNotification(const std::string& runtimeId, const std::string& frame)
1493
+ {
1494
+ std::lock_guard<std::mutex> lock(wsMutex);
1495
+ auto it = wsByRuntime.find(runtimeId);
1496
+ if (it == wsByRuntime.end())
1497
+ {
1498
+ return;
1499
+ }
1500
+ for (auto* conn : it->second)
1501
+ {
1502
+ auto* state = static_cast<WsConnState*>(conn->userdata());
1503
+ if (state && state->type == "writer")
1504
+ {
1505
+ continue; // write-only clients don't receive notifications
1506
+ }
1507
+ conn->send_binary(frame);
1508
+ }
1509
+ }
1510
+
1511
+ void Server::sendNotification(const std::string& runtimeId, const std::string& frame)
1512
+ {
1513
+ m_impl->sendNotification(runtimeId, frame);
1514
+ }
1515
+
1516
+ void Server::sendText(const std::string& runtimeId, const std::string& text)
1517
+ {
1518
+ m_impl->sendText(runtimeId, text);
1519
+ }
1520
+
1521
+ void Server::setAllowedUsers(const std::vector<std::string>& emails)
1522
+ {
1523
+ m_impl->authenticator->setAllowedEmails(emails);
1524
+ }
1525
+
1526
+ std::string Server::mintProcessRuntimeGrant(const std::string& runtimeId, std::chrono::seconds ttl)
1527
+ {
1528
+ // Only mint for a runtime that actually exists in this process, so a token can
1529
+ // never be scoped to a phantom endpoint.
1530
+ if (runtimeId.empty() || !m_impl->app->getRuntime(runtimeId))
1531
+ {
1532
+ std::cerr << "[auth] refusing to mint process-runtime grant for unknown or empty runtime '"
1533
+ << runtimeId << "'" << std::endl;
1534
+ return "";
1535
+ }
1536
+ return m_impl->capabilities.mintProcessRuntimeGrant(runtimeId, ttl);
1537
+ }
1538
+
1539
+ }