@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.
- package/README.md +114 -2
- package/bundle/LICENSE +201 -0
- package/bundle/boards/aggregator-demo-board.json +54 -0
- package/bundle/boards/alpaca-board.json +316 -0
- package/bundle/boards/animate-board.json +159 -0
- package/bundle/boards/ascii-art-demo-board.json +33 -0
- package/bundle/boards/ascii-cam-board.json +184 -0
- package/bundle/boards/asset-demo-board.json +54 -0
- package/bundle/boards/audio-encode-demo-board.json +153 -0
- package/bundle/boards/audiolize-board.json +152 -0
- package/bundle/boards/batcher-demo-board.json +43 -0
- package/bundle/boards/buffer-demo-board.json +53 -0
- package/bundle/boards/cache-demo-board.json +59 -0
- package/bundle/boards/camera-demo-board.json +28 -0
- package/bundle/boards/canvas-demo-board.json +55 -0
- package/bundle/boards/changes-demo-board.json +60 -0
- package/bundle/boards/court-booking-browser-demo-board.json +221 -0
- package/bundle/boards/court-booking-demo-board.json +218 -0
- package/bundle/boards/current-time-board.json +32 -0
- package/bundle/boards/debounce-demo-board.json +43 -0
- package/bundle/boards/decrypt-demo-board.json +47 -0
- package/bundle/boards/delay-demo-board.json +28 -0
- package/bundle/boards/detect-demo-board.json +64 -0
- package/bundle/boards/document-extract-demo-board.json +55 -0
- package/bundle/boards/dropitapp-board.json +138 -0
- package/bundle/boards/encode-demo-board.json +39 -0
- package/bundle/boards/encrypt-board.json +185 -0
- package/bundle/boards/encrypt-demo-board.json +39 -0
- package/bundle/boards/face-alert-board.json +181 -0
- package/bundle/boards/fetcher-demo-board.json +40 -0
- package/bundle/boards/fft-demo-board.json +28 -0
- package/bundle/boards/filter-demo-board.json +55 -0
- package/bundle/boards/flat-map-demo-board.json +42 -0
- package/bundle/boards/frame-limit-test-board.json +130 -0
- package/bundle/boards/game-board.json +413 -0
- package/bundle/boards/game-of-life-board.json +139 -0
- package/bundle/boards/game-of-life-demo-board.json +34 -0
- package/bundle/boards/gather-demo-board.json +62 -0
- package/bundle/boards/group-by-demo-board.json +62 -0
- package/bundle/boards/hacker-demo-board.json +45 -0
- package/bundle/boards/hacker-news-board.json +741 -0
- package/bundle/boards/hash-demo-board.json +40 -0
- package/bundle/boards/health-log-browser-demo-board.json +537 -0
- package/bundle/boards/health-log-demo-board.json +498 -0
- package/bundle/boards/helloworld-board.json +57 -0
- package/bundle/boards/hold-demo-board.json +67 -0
- package/bundle/boards/hold-scopes-demo-board.json +141 -0
- package/bundle/boards/http-client-browser-demo-board.json +379 -0
- package/bundle/boards/http-client-demo-board.json +380 -0
- package/bundle/boards/image-picker-demo-board.json +22 -0
- package/bundle/boards/imap-email-demo-board.json +35 -0
- package/bundle/boards/injector-demo-board.json +55 -0
- package/bundle/boards/iterator-demo-board.json +55 -0
- package/bundle/boards/limit-demo-board.json +66 -0
- package/bundle/boards/link-debugger.json +32 -0
- package/bundle/boards/live-location-demo-board.json +93 -0
- package/bundle/boards/live-radio-cloud-demo-board.json +216 -0
- package/bundle/boards/live-radio-cloud-direct-demo-board.json +311 -0
- package/bundle/boards/live-radio-demo-board.json +211 -0
- package/bundle/boards/live-radio-members-demo-board.json +314 -0
- package/bundle/boards/local-storage-demo-board.json +30 -0
- package/bundle/boards/lz-compress-demo-board.json +52 -0
- package/bundle/boards/map-demo-board.json +46 -0
- package/bundle/boards/match-filter-demo-board.json +43 -0
- package/bundle/boards/meeting-poll-browser-demo-board.json +649 -0
- package/bundle/boards/meeting-poll-demo-board.json +650 -0
- package/bundle/boards/microphone-monitor-demo-board.json +28 -0
- package/bundle/boards/microphone-speaker-demo-board.json +86 -0
- package/bundle/boards/monitor-demo-board.json +52 -0
- package/bundle/boards/mounted-endpoint-demo-board-hkp-rt.json +84 -0
- package/bundle/boards/mounted-endpoint-demo-board.json +84 -0
- package/bundle/boards/nested-rhythm-demo-board.json +1160 -0
- package/bundle/boards/next-bus-board.json +52 -0
- package/bundle/boards/next-bus-switch-board.json +79 -0
- package/bundle/boards/night-groove-demo-board.json +742 -0
- package/bundle/boards/noise-alert-board.json +146 -0
- package/bundle/boards/offer-followup-demo-board.json +291 -0
- package/bundle/boards/offer-intake-demo-board.json +289 -0
- package/bundle/boards/offer-intake-hetzner-demo-board.json +286 -0
- package/bundle/boards/offer-intake-imap-demo-board.json +289 -0
- package/bundle/boards/offer-intake-imap-hetzner-demo-board.json +354 -0
- package/bundle/boards/p2p-receiver-demo-board.json +65 -0
- package/bundle/boards/p2p-sender-demo-board.json +94 -0
- package/bundle/boards/pad-groove-demo-board.json +644 -0
- package/bundle/boards/peer-chat-board.json +109 -0
- package/bundle/boards/peer-chat-node-board.json +97 -0
- package/bundle/boards/qr-code-demo-board.json +22 -0
- package/bundle/boards/queue-demo-board.json +239 -0
- package/bundle/boards/reduce-demo-board.json +54 -0
- package/bundle/boards/remote-named-demo-board-hkp-rt.json +61 -0
- package/bundle/boards/remote-named-demo-board.json +61 -0
- package/bundle/boards/rss-demo-board.json +530 -0
- package/bundle/boards/rss-radio-board.json +515 -0
- package/bundle/boards/rss-radio-brief-one-board.json +237 -0
- package/bundle/boards/rss-radio-editor-unit-board.json +555 -0
- package/bundle/boards/rss-radio-library-unit-board.json +293 -0
- package/bundle/boards/rss-radio-unit-board.json +809 -0
- package/bundle/boards/rss-radio-voice-unit-board.json +146 -0
- package/bundle/boards/runtime-configs/hold-example.json +41 -0
- package/bundle/boards/runtime-configs/injector-example.json +23 -0
- package/bundle/boards/runtime-configs/speech-to-text-example.json +35 -0
- package/bundle/boards/runtime-configs/text-generation-example.json +31 -0
- package/bundle/boards/runtime-configs/text-to-speech-example.json +28 -0
- package/bundle/boards/runtime-configs/text-to-speech-inflect-example.json +28 -0
- package/bundle/boards/runtime-configs/ws-audio-playback-example.json +23 -0
- package/bundle/boards/select-demo-board.json +32 -0
- package/bundle/boards/send-ntfy-board.json +52 -0
- package/bundle/boards/shared-costs-demo-board.json +361 -0
- package/bundle/boards/sign-demo-board.json +41 -0
- package/bundle/boards/skill-router-demo-board.json +210 -0
- package/bundle/boards/smooth-demo-board.json +91 -0
- package/bundle/boards/smtp-email-demo-board.json +34 -0
- package/bundle/boards/sort-demo-board.json +55 -0
- package/bundle/boards/sound-demo-board.json +65 -0
- package/bundle/boards/spectral-modifier-board.json +98 -0
- package/bundle/boards/speech-synth-demo-board.json +37 -0
- package/bundle/boards/speech-to-text-demo-board.json +146 -0
- package/bundle/boards/spotify-board.json +75 -0
- package/bundle/boards/spread-demo-board.json +35 -0
- package/bundle/boards/sql-demo-board.json +56 -0
- package/bundle/boards/sql-explorer-board.json +447 -0
- package/bundle/boards/sql-explorer-node-board.json +474 -0
- package/bundle/boards/sql-import-board.json +156 -0
- package/bundle/boards/state-demo-board.json +65 -0
- package/bundle/boards/stopper-demo-board.json +40 -0
- package/bundle/boards/storage-demo-board.json +193 -0
- package/bundle/boards/store-demo-board.json +63 -0
- package/bundle/boards/swimlanes-demo-board.json +197 -0
- package/bundle/boards/switch-demo-board.json +51 -0
- package/bundle/boards/syn-board.json +19 -0
- package/bundle/boards/syn-booking-unit-board.json +998 -0
- package/bundle/boards/syn-conversations-anthropic-demo-board.json +925 -0
- package/bundle/boards/syn-conversations-demo-board.json +925 -0
- package/bundle/boards/syn-hotels-unit-board.json +287 -0
- package/bundle/boards/syn-mail-desk-board.json +25 -0
- package/bundle/boards/syn-mail-desk-unit-board.json +254 -0
- package/bundle/boards/telegram-listener-demo-board.json +31 -0
- package/bundle/boards/text-generation-anthropic-demo-board.json +66 -0
- package/bundle/boards/text-generation-demo-board.json +51 -0
- package/bundle/boards/text-generation-local-demo-board.json +80 -0
- package/bundle/boards/text-to-speech-demo-board.json +58 -0
- package/bundle/boards/timeline-demo-board.json +477 -0
- package/bundle/boards/timer-demo-board.json +35 -0
- package/bundle/boards/tracks-demo-board.json +95 -0
- package/bundle/boards/trigger-pad-demo-board.json +47 -0
- package/bundle/boards/triggerpad-board.json +31 -0
- package/bundle/boards/triggerpad-recorder-board.json +56 -0
- package/bundle/boards/uuid-generator-demo-board.json +166 -0
- package/bundle/boards/voice-assistant-demo-board.json +230 -0
- package/bundle/boards/voice-assistant-hkp-rt-demo-board.json +239 -0
- package/bundle/boards/voice-assistant-local-demo-board.json +232 -0
- package/bundle/boards/voice-assistant-skills-demo-board.json +316 -0
- package/bundle/boards/voice-assistant-skills-local-demo-board.json +316 -0
- package/bundle/boards/xy-pad-demo-board.json +36 -0
- package/bundle/docs/content/board-json.md +277 -0
- package/bundle/docs/content/boards/alpaca-board.md +56 -0
- package/bundle/docs/content/boards/animate-board.md +40 -0
- package/bundle/docs/content/boards/ascii-cam-board.md +37 -0
- package/bundle/docs/content/boards/audiolize-board.md +47 -0
- package/bundle/docs/content/boards/court-booking-browser-demo-board.md +43 -0
- package/bundle/docs/content/boards/court-booking-demo-board.md +120 -0
- package/bundle/docs/content/boards/current-time-board.md +32 -0
- package/bundle/docs/content/boards/dropitapp-board.md +57 -0
- package/bundle/docs/content/boards/encrypt-board.md +36 -0
- package/bundle/docs/content/boards/face-alert-board.md +50 -0
- package/bundle/docs/content/boards/game-board.md +69 -0
- package/bundle/docs/content/boards/game-of-life-board.md +47 -0
- package/bundle/docs/content/boards/hacker-news-board.md +76 -0
- package/bundle/docs/content/boards/health-log-browser-demo-board.md +56 -0
- package/bundle/docs/content/boards/health-log-demo-board.md +138 -0
- package/bundle/docs/content/boards/helloworld-board.md +40 -0
- package/bundle/docs/content/boards/link-debugger.md +31 -0
- package/bundle/docs/content/boards/live-radio-cloud-demo-board.md +70 -0
- package/bundle/docs/content/boards/live-radio-cloud-direct-demo-board.md +89 -0
- package/bundle/docs/content/boards/live-radio-demo-board.md +102 -0
- package/bundle/docs/content/boards/live-radio-members-demo-board.md +91 -0
- package/bundle/docs/content/boards/meeting-poll-browser-demo-board.md +45 -0
- package/bundle/docs/content/boards/meeting-poll-demo-board.md +121 -0
- package/bundle/docs/content/boards/mounted-endpoint-demo-board-hkp-rt.md +50 -0
- package/bundle/docs/content/boards/nested-rhythm-demo-board.md +165 -0
- package/bundle/docs/content/boards/next-bus-board.md +38 -0
- package/bundle/docs/content/boards/next-bus-switch-board.md +44 -0
- package/bundle/docs/content/boards/night-groove-demo-board.md +110 -0
- package/bundle/docs/content/boards/noise-alert-board.md +44 -0
- package/bundle/docs/content/boards/pad-groove-demo-board.md +93 -0
- package/bundle/docs/content/boards/peer-chat-board.md +49 -0
- package/bundle/docs/content/boards/peer-chat-node-board.md +47 -0
- package/bundle/docs/content/boards/rss-radio-board.md +221 -0
- package/bundle/docs/content/boards/send-ntfy-board.md +33 -0
- package/bundle/docs/content/boards/shared-costs-demo-board.md +65 -0
- package/bundle/docs/content/boards/spectral-modifier-board.md +55 -0
- package/bundle/docs/content/boards/spotify-board.md +46 -0
- package/bundle/docs/content/boards/sql-explorer-board.md +70 -0
- package/bundle/docs/content/boards/sql-explorer-node-board.md +76 -0
- package/bundle/docs/content/boards/sql-import-board.md +37 -0
- package/bundle/docs/content/boards/swimlanes-demo-board.md +148 -0
- package/bundle/docs/content/boards/syn-mail-desk-board.md +52 -0
- package/bundle/docs/content/boards/timeline-demo-board.md +115 -0
- package/bundle/docs/content/boards/triggerpad-board.md +32 -0
- package/bundle/docs/content/boards/triggerpad-recorder-board.md +52 -0
- package/bundle/docs/content/boards/voice-assistant-demo-board.md +51 -0
- package/bundle/docs/content/concepts/assets.md +245 -0
- package/bundle/docs/content/concepts/blocks.md +300 -0
- package/bundle/docs/content/concepts/board-assistant.md +307 -0
- package/bundle/docs/content/concepts/board.md +265 -0
- package/bundle/docs/content/concepts/cloud-boards.md +803 -0
- package/bundle/docs/content/concepts/coordinator.md +297 -0
- package/bundle/docs/content/concepts/logging.md +293 -0
- package/bundle/docs/content/concepts/mounts.md +314 -0
- package/bundle/docs/content/concepts/presets.md +360 -0
- package/bundle/docs/content/concepts/remotes.md +423 -0
- package/bundle/docs/content/concepts/runtime.md +664 -0
- package/bundle/docs/content/concepts/scopes.md +256 -0
- package/bundle/docs/content/concepts/service.md +227 -0
- package/bundle/docs/content/concepts/units.md +321 -0
- package/bundle/docs/content/introduction.md +163 -0
- package/bundle/docs/content/llm/circle-text-board.json +55 -0
- package/bundle/docs/content/llm/howto-workflow.md +369 -0
- package/bundle/docs/content/repository.md +161 -0
- package/bundle/docs/content/services/aggregator.md +76 -0
- package/bundle/docs/content/services/analyzer.md +44 -0
- package/bundle/docs/content/services/ascii-art.md +58 -0
- package/bundle/docs/content/services/asset.md +88 -0
- package/bundle/docs/content/services/audio-editor.md +57 -0
- package/bundle/docs/content/services/audio-encode.md +115 -0
- package/bundle/docs/content/services/audio-io.md +173 -0
- package/bundle/docs/content/services/batcher.md +49 -0
- package/bundle/docs/content/services/board-service.md +48 -0
- package/bundle/docs/content/services/browser-sub-service.md +75 -0
- package/bundle/docs/content/services/buffer.md +70 -0
- package/bundle/docs/content/services/cache.md +92 -0
- package/bundle/docs/content/services/camera.md +85 -0
- package/bundle/docs/content/services/canvas.md +196 -0
- package/bundle/docs/content/services/changes.md +87 -0
- package/bundle/docs/content/services/chunked-file-provider.md +63 -0
- package/bundle/docs/content/services/communication-dispatcher.md +190 -0
- package/bundle/docs/content/services/configurator.md +66 -0
- package/bundle/docs/content/services/conversations.md +244 -0
- package/bundle/docs/content/services/debounce.md +45 -0
- package/bundle/docs/content/services/decrypt.md +53 -0
- package/bundle/docs/content/services/delay.md +55 -0
- package/bundle/docs/content/services/detect.md +101 -0
- package/bundle/docs/content/services/document-extract.md +174 -0
- package/bundle/docs/content/services/download.md +67 -0
- package/bundle/docs/content/services/encode.md +63 -0
- package/bundle/docs/content/services/encrypt.md +53 -0
- package/bundle/docs/content/services/fetcher.md +122 -0
- package/bundle/docs/content/services/fft.md +96 -0
- package/bundle/docs/content/services/file-source.md +67 -0
- package/bundle/docs/content/services/filesystem.md +157 -0
- package/bundle/docs/content/services/filter.md +133 -0
- package/bundle/docs/content/services/flat-map.md +62 -0
- package/bundle/docs/content/services/game-of-life.md +93 -0
- package/bundle/docs/content/services/gather.md +135 -0
- package/bundle/docs/content/services/gif-encoder.md +56 -0
- package/bundle/docs/content/services/github.md +85 -0
- package/bundle/docs/content/services/group-by.md +54 -0
- package/bundle/docs/content/services/hacker.md +79 -0
- package/bundle/docs/content/services/hash.md +59 -0
- package/bundle/docs/content/services/hold.md +182 -0
- package/bundle/docs/content/services/http-relay-client.md +48 -0
- package/bundle/docs/content/services/http-uploader.md +62 -0
- package/bundle/docs/content/services/http.md +424 -0
- package/bundle/docs/content/services/if.md +79 -0
- package/bundle/docs/content/services/ifft.md +46 -0
- package/bundle/docs/content/services/image-picker.md +50 -0
- package/bundle/docs/content/services/imap-email.md +104 -0
- package/bundle/docs/content/services/injector.md +129 -0
- package/bundle/docs/content/services/input.md +90 -0
- package/bundle/docs/content/services/iterator.md +186 -0
- package/bundle/docs/content/services/join.md +153 -0
- package/bundle/docs/content/services/limit.md +47 -0
- package/bundle/docs/content/services/local-storage.md +47 -0
- package/bundle/docs/content/services/looper.md +62 -0
- package/bundle/docs/content/services/lz-compress.md +52 -0
- package/bundle/docs/content/services/map.md +257 -0
- package/bundle/docs/content/services/match-filter.md +38 -0
- package/bundle/docs/content/services/microphone-monitor.md +66 -0
- package/bundle/docs/content/services/monitor.md +67 -0
- package/bundle/docs/content/services/ollama.md +116 -0
- package/bundle/docs/content/services/openai-prompt.md +74 -0
- package/bundle/docs/content/services/output.md +107 -0
- package/bundle/docs/content/services/peer-socket.md +61 -0
- package/bundle/docs/content/services/qr-code.md +48 -0
- package/bundle/docs/content/services/queue.md +201 -0
- package/bundle/docs/content/services/reduce.md +51 -0
- package/bundle/docs/content/services/rss.md +232 -0
- package/bundle/docs/content/services/select.md +47 -0
- package/bundle/docs/content/services/sign.md +53 -0
- package/bundle/docs/content/services/skill-router.md +154 -0
- package/bundle/docs/content/services/smooth.md +59 -0
- package/bundle/docs/content/services/smtp-email.md +129 -0
- package/bundle/docs/content/services/sort.md +48 -0
- package/bundle/docs/content/services/sound.md +87 -0
- package/bundle/docs/content/services/speech-synth.md +46 -0
- package/bundle/docs/content/services/speech-to-text.md +172 -0
- package/bundle/docs/content/services/spotify.md +77 -0
- package/bundle/docs/content/services/spread.md +111 -0
- package/bundle/docs/content/services/sql.md +491 -0
- package/bundle/docs/content/services/state.md +57 -0
- package/bundle/docs/content/services/static.md +47 -0
- package/bundle/docs/content/services/stopper.md +55 -0
- package/bundle/docs/content/services/storage.md +155 -0
- package/bundle/docs/content/services/store.md +255 -0
- package/bundle/docs/content/services/sub-service.md +152 -0
- package/bundle/docs/content/services/switch.md +67 -0
- package/bundle/docs/content/services/telegram-listener.md +76 -0
- package/bundle/docs/content/services/text-generation.md +390 -0
- package/bundle/docs/content/services/text-to-speech.md +130 -0
- package/bundle/docs/content/services/timeline.md +333 -0
- package/bundle/docs/content/services/timer.md +113 -0
- package/bundle/docs/content/services/tracks.md +207 -0
- package/bundle/docs/content/services/trigger-pad.md +68 -0
- package/bundle/docs/content/services/uuid-generator.md +48 -0
- package/bundle/docs/content/services/wav-reader.md +66 -0
- package/bundle/docs/content/services/websocket.md +189 -0
- package/bundle/docs/content/services/workflow-board-builder.md +65 -0
- package/bundle/docs/content/services/xy-pad.md +60 -0
- package/bundle/docs/content/targets.md +434 -0
- package/bundle/docs/content/testing.md +263 -0
- package/bundle/docs/content/vocabulary.md +171 -0
- package/bundle/hkp-frontend/src/runtime/browser/BrowserRegistry.tsx +141 -0
- package/bundle/hkp-frontend/src/runtime/browser/BrowserRuntime.tsx +202 -0
- package/bundle/hkp-frontend/src/runtime/browser/BrowserRuntimeApi.ts +306 -0
- package/bundle/hkp-frontend/src/runtime/browser/BrowserRuntimeApp.ts +310 -0
- package/bundle/hkp-frontend/src/runtime/browser/BrowserRuntimeScope.ts +500 -0
- package/bundle/hkp-frontend/src/runtime/browser/UIRegistry.ts +150 -0
- package/bundle/hkp-frontend/src/runtime/browser/assetResolver.ts +123 -0
- package/bundle/hkp-frontend/src/runtime/browser/pipelineNames.ts +65 -0
- package/bundle/hkp-frontend/src/runtime/browser/registry/Default.ts +237 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Aggregator.ts +127 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AggregatorUI.tsx +83 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Analyzer.tsx +43 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AnalyzerUI/Bars.tsx +33 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AnalyzerUI/HorizontalSpectrogram.tsx +53 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AnalyzerUI/VerticalSpectogram.tsx +56 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AnalyzerUI/Waveform.tsx +109 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AnalyzerUI/index.tsx +99 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AsciiArt.ts +119 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AsciiArtUI.tsx +131 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Asset.ts +125 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AssetUI.tsx +37 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AudioDeviceSettings.tsx +56 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AudioEditor.ts +129 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AudioEditorUI.tsx +549 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AudioInput.ts +298 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AudioInputUI.tsx +257 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AudioOutput.ts +189 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AudioOutputUI.tsx +63 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Batcher.ts +11 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/BoardService.tsx +86 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/BoardServiceUI.tsx +410 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/BrowserGather.ts +311 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/BrowserIterator.ts +167 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/BrowserSubService.tsx +583 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/BrowserSubServiceUI.tsx +187 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/BrowserTracks.tsx +477 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Buffer.ts +11 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Cache.ts +134 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/CacheUI.tsx +125 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Camera.ts +90 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/CameraUI.tsx +321 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Canvas.ts +139 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/CanvasUI/canvasDraw.ts +543 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/CanvasUI/canvasUtils.ts +39 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/CanvasUI/index.tsx +287 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/CanvasUI/tests/canvasDraw.test.ts +355 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/CanvasUI/tests/canvasUtils.test.ts +89 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/CanvasUI/tests/fullscreen.test.tsx +26 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Changes.ts +201 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/ChangesUI.tsx +85 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/ChunkedFileProvider.tsx +183 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Configurator.ts +258 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/ConfiguratorUI.tsx +83 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/ConsideredHacker.tsx +28 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Debounce.ts +75 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/DebounceUI.tsx +70 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Decrypt.ts +127 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/DecryptUI.tsx +39 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Delay.tsx +110 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Detect.ts +214 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/DetectUI.tsx +86 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Download.ts +120 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/DownloadUI.tsx +61 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Encode.ts +138 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/EncodeUI.tsx +52 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Encrypt.ts +125 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/EncryptUI.tsx +39 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/FFT.tsx +238 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/FFTUI.tsx +112 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/FeedbackService.ts +247 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/FeedbackServiceUI.tsx +35 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Fetcher.tsx +232 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/FetcherUI.tsx +154 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/FileSource.tsx +171 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Filter.tsx +18 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/FilterUI.tsx +89 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/FlatMap.ts +68 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/FlatMapUI.tsx +38 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/GameOfLife.ts +210 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/GameOfLifeRenderer.ts +182 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/GameOfLifeRendererUI.tsx +48 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/GameOfLifeUI.tsx +58 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/GatherUI.tsx +84 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/GifEncoder.tsx +651 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/GithubAPI.ts +294 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/GithubComponents.tsx +389 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/GithubSink.tsx +109 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/GithubSinkUI.tsx +143 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/GithubSource.tsx +86 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/GithubSourceUI.tsx +162 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/GroupBy.ts +11 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/HTTPUploader.ts +102 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Hacker.ts +73 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/HackerUI.tsx +134 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Hash.ts +134 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/HashUI.tsx +45 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Hold.ts +288 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/HoldUI.tsx +43 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/HttpClient.ts +558 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/HttpClientUI.tsx +212 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/HttpUploaderTokenInjector.ts +149 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/IfService.ts +231 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/IfServiceUI.tsx +50 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/ImagePicker.tsx +118 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Injector.ts +61 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/InjectorUI.tsx +389 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Input.ts +214 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/InputUI.tsx +71 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/IteratorUI.tsx +59 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/LZCompress.ts +63 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Limit.ts +39 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/LimitUI.tsx +41 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/LocalStorage.ts +65 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/LocalStorageUI.tsx +72 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Map.ts +14 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/MapUI.tsx +334 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/MapUIModeAssist.tsx +61 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/MatchFilter.ts +11 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/MicrophoneMonitor.ts +190 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/MicrophoneMonitorUI.tsx +94 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Monitor.tsx +15 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/MonitorUI.tsx +281 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/OllamaPrompt.ts +237 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/OllamaPromptUI/BusyIndicator.tsx +18 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/OllamaPromptUI/PromptInput.tsx +71 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/OllamaPromptUI/StreamingPreview.tsx +24 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/OllamaPromptUI/index.tsx +156 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/OpenAIPrompt.ts +254 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/OpenAIPromptUI.tsx +189 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Output.tsx +145 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/OutputUI.tsx +118 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/PeerChatTemplate.json +43 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/PeerConnection.ts +260 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/PeerSocket.tsx +268 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/PeerSocketReceiverTemplate.json +29 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/PeerSocketUI.tsx +341 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/PlaceholderUI.tsx +72 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/QrCode.ts +63 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/QrCodeUI.tsx +197 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Select.ts +40 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SelectUI.tsx +41 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/ServiceBase.ts +50 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SetRuntimeVariable.ts +65 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SetRuntimeVariableUI.tsx +50 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Sign.ts +182 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SignUI.tsx +55 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Smooth.ts +101 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SmoothUI.tsx +50 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Sort.ts +59 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SortUI.tsx +61 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Sound.ts +541 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SoundUI.tsx +183 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SpeechSynth.ts +56 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SpeechSynthUI.tsx +108 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Spotify.tsx +111 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SpotifyAPI.ts +113 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SpotifyComponents.tsx +104 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SpotifyUI.tsx +186 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Spread.ts +117 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SpreadUI.tsx +38 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Sql.ts +448 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SqlInspector.tsx +332 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SqlUI.tsx +226 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/State.ts +149 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Stopper.ts +29 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Switch.tsx +419 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SwitchUI.tsx +171 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/TickRuleDelta.ts +101 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/TickRuleDeltaUI.tsx +56 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Timeline.ts +709 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/TimelineUI/TimelinePanel.tsx +79 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/TimelineUI/TimelineRows.tsx +574 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/TimelineUI/Transport.tsx +141 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/TimelineUI/index.tsx +119 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/TimelineUI/model.ts +425 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/TimelineUI/parts.tsx +708 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Timer.ts +15 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/TimerUI.tsx +213 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/TracksUI.tsx +81 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/TriggerPad.ts +108 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/TriggerPadUI.tsx +160 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/UuidGenerator.ts +74 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/UuidGeneratorUI.tsx +59 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/WaveformDisplay.tsx +104 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/WebsocketClient.ts +114 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/WebsocketClientUI.tsx +42 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/WorkflowBoardBuilder.ts +499 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/WorkflowBoardBuilderUI.tsx +521 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/XYPad.ts +68 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/XYPadUI.tsx +274 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/audio/audioHelper.ts +36 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/base/Batcher.ts +76 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/base/Buffer.ts +142 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/base/Filter.ts +125 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/base/GroupBy.ts +101 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/base/Map.ts +543 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/base/MatchFilter.ts +49 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/base/Monitor.ts +75 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/base/Reduce.ts +74 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/base/Timer.ts +342 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/base/bundle.js +7375 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/base/eval.ts +208 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/base/expressionEval.ts +223 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/base/helpers.ts +10 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/base/index.ts +37 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/camera/videoCapture.ts +78 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/changes-modes.ts +9 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/colorpalette.ts +26 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/detect/DetectionPreview.tsx +76 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/detect/mediapipe.ts +34 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/detect/models/README.md +14 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/detect/models/blaze_face_short_range.tflite +0 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/detect-modes.ts +7 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/encode-modes.ts +10 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/helpers.ts +300 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/http-client-methods.ts +10 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/sql-database.ts +594 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/sql-dump.ts +192 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/sql-inspect.ts +130 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/sql-inspector.css +155 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/sql-modes.ts +27 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/TESTING_APPROACH.md +169 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/__snapshots__/service-ui.future-enhancements.test.tsx.snap +5 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/audio-input-permissions.test.ts +42 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/audiolizeLanes.test.ts +70 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/browser-scopes.test.ts +305 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/cache-service.test.ts +53 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/camera-video-capture.test.ts +24 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/changes-service.test.ts +133 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/crypto-secrets.test.ts +196 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/detect-service.test.ts +119 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/download.test.ts +100 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/expressionEval.test.ts +117 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/fetcher-panel.test.tsx +35 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/fetcher-service.test.ts +80 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/filter-service.test.ts +37 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/github-secrets.test.ts +108 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/group-by-board.test.ts +42 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/hold-service.test.ts +199 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/http-client-service.test.ts +474 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/http-uploader-token-injector.test.ts +152 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/iterator-gather-service.test.ts +277 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/map-service.test.ts +1047 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/map-ui.test.tsx +70 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/nested-rhythm-board.test.ts +311 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/night-groove-board.test.ts +290 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/pad-groove-board.test.ts +230 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/peer-host-discovery.test.ts +77 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/peer-socket-discovery.test.ts +69 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/pipeline-names.test.ts +63 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/secret-canary.test.ts +121 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/service-modules.contract.test.ts +229 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/service-ui.advanced.test.tsx +670 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/service-ui.future-enhancements.test.tsx +219 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/service-ui.integration.test.tsx +432 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/service-ui.smoke.test.tsx +140 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/sound-service.test.ts +293 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/spread-service.test.ts +137 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/sql-dump.test.ts +172 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/sql-inspect.test.ts +163 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/sql-inspector.test.tsx +121 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/sql.test.ts +702 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/sub-service-in-block.test.tsx +84 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/switch-service.test.ts +254 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/timeline-board.test.ts +218 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/timeline-service.test.ts +1002 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/timeline-ui-model.test.ts +230 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/timeline-ui.test.tsx +371 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/timer-service.test.ts +902 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/tracks-service.test.ts +275 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/tracks-ui.test.tsx +126 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/triggerpad-recorder-board.test.ts +100 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/uuid-generator-service.test.ts +214 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/timeline-core.ts +410 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/workflow-prompt/DefaultWorkflowBoard.ts +90 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/workflow-prompt/RefinementSession.ts +121 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/workflow-prompt/SystemPromptCatalog.ts +245 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/workflow-prompt/catalog/browser-service-specs.json +110 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/workflow-prompt/catalog/runtime-capabilities.json +37 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/asset-service.test.ts +73 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/board-persistence.integration.test.tsx +391 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/browser-runtime.identity-binding.test.tsx +81 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/demo-boards.regression.test.tsx +386 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/failed-call-lifecycle.test.ts +56 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/http-client.integration.test.ts +146 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/nested-run.integration.test.ts +415 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/replay-next.test.ts +391 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/rerun.test.ts +71 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/run-no-input.test.ts +72 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/runtime-add.integration.test.tsx +256 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/runtime-operations.integration.test.tsx +310 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/runtime-processing.integration.test.ts +311 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/runtime-removal.integration.test.tsx +454 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/runtime-update.identity.integration.test.tsx +145 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/service-operations.integration.test.tsx +511 -0
- package/bundle/hkp-node/src/address.ts +72 -0
- package/bundle/hkp-node/src/assets.ts +582 -0
- package/bundle/hkp-node/src/auth.ts +369 -0
- package/bundle/hkp-node/src/callReport.ts +281 -0
- package/bundle/hkp-node/src/coordinator/auth.ts +85 -0
- package/bundle/hkp-node/src/coordinator/binaryFrame.ts +189 -0
- package/bundle/hkp-node/src/coordinator/boardStore.ts +0 -0
- package/bundle/hkp-node/src/coordinator/bridge.ts +242 -0
- package/bundle/hkp-node/src/coordinator/bridgeProtocol.ts +200 -0
- package/bundle/hkp-node/src/coordinator/coordinator.ts +594 -0
- package/bundle/hkp-node/src/coordinator/facadeAccess.ts +426 -0
- package/bundle/hkp-node/src/coordinator/fileBoardStore.ts +159 -0
- package/bundle/hkp-node/src/coordinator/frameLimits.ts +41 -0
- package/bundle/hkp-node/src/coordinator/index.ts +12 -0
- package/bundle/hkp-node/src/coordinator/join.ts +62 -0
- package/bundle/hkp-node/src/coordinator/logStore.ts +335 -0
- package/bundle/hkp-node/src/coordinator/members.ts +133 -0
- package/bundle/hkp-node/src/coordinator/mount.ts +124 -0
- package/bundle/hkp-node/src/coordinator/participantProtocol.ts +169 -0
- package/bundle/hkp-node/src/coordinator/participants.ts +666 -0
- package/bundle/hkp-node/src/coordinator/router.ts +416 -0
- package/bundle/hkp-node/src/coordinator/session.ts +1610 -0
- package/bundle/hkp-node/src/coordinator/types.ts +95 -0
- package/bundle/hkp-node/src/coordinatorLinks.ts +712 -0
- package/bundle/hkp-node/src/credentialedFetch.ts +122 -0
- package/bundle/hkp-node/src/egress.ts +344 -0
- package/bundle/hkp-node/src/expressionEval.ts +223 -0
- package/bundle/hkp-node/src/index.ts +470 -0
- package/bundle/hkp-node/src/instance.ts +172 -0
- package/bundle/hkp-node/src/mounts.ts +296 -0
- package/bundle/hkp-node/src/origins.ts +201 -0
- package/bundle/hkp-node/src/reset.ts +187 -0
- package/bundle/hkp-node/src/runtime.ts +1345 -0
- package/bundle/hkp-node/src/secrets.ts +292 -0
- package/bundle/hkp-node/src/server.ts +2120 -0
- package/bundle/hkp-node/src/services/asset.ts +150 -0
- package/bundle/hkp-node/src/services/claudeModels.ts +370 -0
- package/bundle/hkp-node/src/services/communication-dispatcher.ts +735 -0
- package/bundle/hkp-node/src/services/conversations.ts +914 -0
- package/bundle/hkp-node/src/services/database.ts +386 -0
- package/bundle/hkp-node/src/services/document-extract.ts +575 -0
- package/bundle/hkp-node/src/services/expression.ts +417 -0
- package/bundle/hkp-node/src/services/fileStore.ts +369 -0
- package/bundle/hkp-node/src/services/filesystem.ts +280 -0
- package/bundle/hkp-node/src/services/gather.ts +335 -0
- package/bundle/hkp-node/src/services/hold.ts +275 -0
- package/bundle/hkp-node/src/services/http-client.ts +525 -0
- package/bundle/hkp-node/src/services/http-server.ts +1222 -0
- package/bundle/hkp-node/src/services/imap-email.ts +627 -0
- package/bundle/hkp-node/src/services/injector.ts +135 -0
- package/bundle/hkp-node/src/services/iterator.ts +203 -0
- package/bundle/hkp-node/src/services/join.ts +226 -0
- package/bundle/hkp-node/src/services/map.ts +632 -0
- package/bundle/hkp-node/src/services/monitor.ts +128 -0
- package/bundle/hkp-node/src/services/nested-pipeline.ts +405 -0
- package/bundle/hkp-node/src/services/peer-server.ts +268 -0
- package/bundle/hkp-node/src/services/queue-store.ts +301 -0
- package/bundle/hkp-node/src/services/queue.ts +316 -0
- package/bundle/hkp-node/src/services/recordStore.ts +341 -0
- package/bundle/hkp-node/src/services/rss-parse.ts +281 -0
- package/bundle/hkp-node/src/services/rss.ts +392 -0
- package/bundle/hkp-node/src/services/smtp-email.ts +398 -0
- package/bundle/hkp-node/src/services/spread.ts +153 -0
- package/bundle/hkp-node/src/services/sql-dump.ts +192 -0
- package/bundle/hkp-node/src/services/sql.ts +429 -0
- package/bundle/hkp-node/src/services/stopper.ts +69 -0
- package/bundle/hkp-node/src/services/storage.ts +332 -0
- package/bundle/hkp-node/src/services/store.ts +609 -0
- package/bundle/hkp-node/src/services/stream.ts +367 -0
- package/bundle/hkp-node/src/services/sub-service.ts +600 -0
- package/bundle/hkp-node/src/services/telegram-listener.ts +263 -0
- package/bundle/hkp-node/src/services/telegram-sender.ts +136 -0
- package/bundle/hkp-node/src/services/text-generation.ts +1249 -0
- package/bundle/hkp-node/src/services/timer.ts +273 -0
- package/bundle/hkp-node/src/services/tracks.ts +381 -0
- package/bundle/hkp-node/src/services/websocket-reader.ts +364 -0
- package/bundle/hkp-node/src/types.ts +465 -0
- package/bundle/hkp-node/src/wsTickets.ts +77 -0
- package/bundle/hkp-node/src/yas.ts +95 -0
- package/bundle/hkp-python/src/hkp/__init__.py +1 -0
- package/bundle/hkp-python/src/hkp/__main__.py +241 -0
- package/bundle/hkp-python/src/hkp/address.py +56 -0
- package/bundle/hkp-python/src/hkp/assets.py +439 -0
- package/bundle/hkp-python/src/hkp/auth.py +252 -0
- package/bundle/hkp-python/src/hkp/binary_frame.py +100 -0
- package/bundle/hkp-python/src/hkp/call_report.py +201 -0
- package/bundle/hkp-python/src/hkp/coordinator_links.py +667 -0
- package/bundle/hkp-python/src/hkp/data.py +118 -0
- package/bundle/hkp-python/src/hkp/egress.py +310 -0
- package/bundle/hkp-python/src/hkp/mount.py +46 -0
- package/bundle/hkp-python/src/hkp/mounts.py +288 -0
- package/bundle/hkp-python/src/hkp/origins.py +169 -0
- package/bundle/hkp-python/src/hkp/runtime.py +1131 -0
- package/bundle/hkp-python/src/hkp/secrets.py +261 -0
- package/bundle/hkp-python/src/hkp/server.py +1714 -0
- package/bundle/hkp-python/src/hkp/services/__init__.py +0 -0
- package/bundle/hkp-python/src/hkp/services/asset.py +123 -0
- package/bundle/hkp-python/src/hkp/services/audio_encode.py +190 -0
- package/bundle/hkp-python/src/hkp/services/expression.py +942 -0
- package/bundle/hkp-python/src/hkp/services/gather.py +266 -0
- package/bundle/hkp-python/src/hkp/services/hold.py +246 -0
- package/bundle/hkp-python/src/hkp/services/http_client.py +444 -0
- package/bundle/hkp-python/src/hkp/services/http_server.py +790 -0
- package/bundle/hkp-python/src/hkp/services/iterator.py +141 -0
- package/bundle/hkp-python/src/hkp/services/join.py +173 -0
- package/bundle/hkp-python/src/hkp/services/map_service.py +452 -0
- package/bundle/hkp-python/src/hkp/services/monitor.py +99 -0
- package/bundle/hkp-python/src/hkp/services/nested_pipeline.py +278 -0
- package/bundle/hkp-python/src/hkp/services/skill_router.py +388 -0
- package/bundle/hkp-python/src/hkp/services/speech_to_text.py +186 -0
- package/bundle/hkp-python/src/hkp/services/spread.py +114 -0
- package/bundle/hkp-python/src/hkp/services/stopper.py +59 -0
- package/bundle/hkp-python/src/hkp/services/sub_service.py +417 -0
- package/bundle/hkp-python/src/hkp/services/text_generation.py +498 -0
- package/bundle/hkp-python/src/hkp/services/text_to_speech.py +237 -0
- package/bundle/hkp-python/src/hkp/services/timer.py +267 -0
- package/bundle/hkp-python/src/hkp/services/tracks.py +251 -0
- package/bundle/hkp-python/src/hkp/types.py +412 -0
- package/bundle/hkp-python/src/hkp/ws_tickets.py +54 -0
- package/bundle/hkp-python/src/hkp/yas.py +192 -0
- package/bundle/hkp-rt/lib/CMakeLists.txt +193 -0
- package/bundle/hkp-rt/lib/hkp-bundles/CMakeLists.txt +55 -0
- package/bundle/hkp-rt/lib/hkp-bundles/plugin_register.cpp +14 -0
- package/bundle/hkp-rt/lib/include/app.h +201 -0
- package/bundle/hkp-rt/lib/include/assets.h +239 -0
- package/bundle/hkp-rt/lib/include/auth.h +167 -0
- package/bundle/hkp-rt/lib/include/call_report.h +197 -0
- package/bundle/hkp-rt/lib/include/coordinator_links.h +127 -0
- package/bundle/hkp-rt/lib/include/discovery_hooks.h +29 -0
- package/bundle/hkp-rt/lib/include/egress.h +379 -0
- package/bundle/hkp-rt/lib/include/files.h +275 -0
- package/bundle/hkp-rt/lib/include/log_entry.h +131 -0
- package/bundle/hkp-rt/lib/include/mounts.h +139 -0
- package/bundle/hkp-rt/lib/include/origins.h +363 -0
- package/bundle/hkp-rt/lib/include/registry.h +119 -0
- package/bundle/hkp-rt/lib/include/secrets.h +414 -0
- package/bundle/hkp-rt/lib/include/server.h +122 -0
- package/bundle/hkp-rt/lib/include/service.h +190 -0
- package/bundle/hkp-rt/lib/include/slot_store.h +41 -0
- package/bundle/hkp-rt/lib/include/standalone_config.h +253 -0
- package/bundle/hkp-rt/lib/include/types/audio.h +3 -0
- package/bundle/hkp-rt/lib/include/types/data.h +152 -0
- package/bundle/hkp-rt/lib/include/types/message.h +56 -0
- package/bundle/hkp-rt/lib/include/types/models.h +154 -0
- package/bundle/hkp-rt/lib/include/types/owns_me.h +19 -0
- package/bundle/hkp-rt/lib/include/types/ringbuffer.h +86 -0
- package/bundle/hkp-rt/lib/include/types/types.h +33 -0
- package/bundle/hkp-rt/lib/include/types/validation.h +205 -0
- package/bundle/hkp-rt/lib/src/address.h +88 -0
- package/bundle/hkp-rt/lib/src/app.cpp +591 -0
- package/bundle/hkp-rt/lib/src/assets.cpp +686 -0
- package/bundle/hkp-rt/lib/src/binary_frame.h +154 -0
- package/bundle/hkp-rt/lib/src/common/expression.h +1481 -0
- package/bundle/hkp-rt/lib/src/common/inja.h +36 -0
- package/bundle/hkp-rt/lib/src/common/link_socket.cpp +437 -0
- package/bundle/hkp-rt/lib/src/common/link_socket.h +97 -0
- package/bundle/hkp-rt/lib/src/common/realtime_wakeup.h +120 -0
- package/bundle/hkp-rt/lib/src/common/stream_broadcast.h +250 -0
- package/bundle/hkp-rt/lib/src/common/string_util.h +31 -0
- package/bundle/hkp-rt/lib/src/common/websocket_client_session.cpp +217 -0
- package/bundle/hkp-rt/lib/src/common/websocket_client_session.h +37 -0
- package/bundle/hkp-rt/lib/src/common/websocket_config.h +119 -0
- package/bundle/hkp-rt/lib/src/common/websocket_protocol.h +32 -0
- package/bundle/hkp-rt/lib/src/common/websocket_server.cpp +82 -0
- package/bundle/hkp-rt/lib/src/common/websocket_server.h +57 -0
- package/bundle/hkp-rt/lib/src/common/websocket_server_listener.cpp +115 -0
- package/bundle/hkp-rt/lib/src/common/websocket_server_listener.h +35 -0
- package/bundle/hkp-rt/lib/src/common/websocket_server_session.cpp +172 -0
- package/bundle/hkp-rt/lib/src/common/websocket_server_session.h +53 -0
- package/bundle/hkp-rt/lib/src/common.h +13 -0
- package/bundle/hkp-rt/lib/src/coordinator_links.cpp +1142 -0
- package/bundle/hkp-rt/lib/src/discovery/discovery.cpp +707 -0
- package/bundle/hkp-rt/lib/src/discovery/discovery.h +89 -0
- package/bundle/hkp-rt/lib/src/http/auth.cpp +569 -0
- package/bundle/hkp-rt/lib/src/http/front_door.cpp +518 -0
- package/bundle/hkp-rt/lib/src/http/front_door.h +131 -0
- package/bundle/hkp-rt/lib/src/http/server.cpp +1539 -0
- package/bundle/hkp-rt/lib/src/http/ws_tickets.h +125 -0
- package/bundle/hkp-rt/lib/src/mount.h +63 -0
- package/bundle/hkp-rt/lib/src/process_context.h +335 -0
- package/bundle/hkp-rt/lib/src/process_depth.h +20 -0
- package/bundle/hkp-rt/lib/src/registry.cpp +233 -0
- package/bundle/hkp-rt/lib/src/runtime.cpp +986 -0
- package/bundle/hkp-rt/lib/src/runtime.h +225 -0
- package/bundle/hkp-rt/lib/src/runtime_host.h +180 -0
- package/bundle/hkp-rt/lib/src/service.cpp +315 -0
- package/bundle/hkp-rt/lib/src/services/asset.h +185 -0
- package/bundle/hkp-rt/lib/src/services/audio_encode.cpp +372 -0
- package/bundle/hkp-rt/lib/src/services/audio_encode.h +97 -0
- package/bundle/hkp-rt/lib/src/services/cache.h +362 -0
- package/bundle/hkp-rt/lib/src/services/cache_subservices.h +463 -0
- package/bundle/hkp-rt/lib/src/services/core_audio.cpp +384 -0
- package/bundle/hkp-rt/lib/src/services/core_audio.h +22 -0
- package/bundle/hkp-rt/lib/src/services/core_input.h +337 -0
- package/bundle/hkp-rt/lib/src/services/core_output.h +370 -0
- package/bundle/hkp-rt/lib/src/services/ffmpeg/detail.cpp +749 -0
- package/bundle/hkp-rt/lib/src/services/ffmpeg/detail.h +29 -0
- package/bundle/hkp-rt/lib/src/services/ffmpeg/ffmpeg.cpp +193 -0
- package/bundle/hkp-rt/lib/src/services/ffmpeg/ffmpeg.h +40 -0
- package/bundle/hkp-rt/lib/src/services/ffmpeg/types.h +120 -0
- package/bundle/hkp-rt/lib/src/services/fft_service.h +190 -0
- package/bundle/hkp-rt/lib/src/services/filesystem.h +309 -0
- package/bundle/hkp-rt/lib/src/services/filter.h +208 -0
- package/bundle/hkp-rt/lib/src/services/gather.h +272 -0
- package/bundle/hkp-rt/lib/src/services/hold.h +304 -0
- package/bundle/hkp-rt/lib/src/services/http_client.cpp +347 -0
- package/bundle/hkp-rt/lib/src/services/http_client.h +67 -0
- package/bundle/hkp-rt/lib/src/services/http_client_impl.h +45 -0
- package/bundle/hkp-rt/lib/src/services/http_client_template_examples.md +202 -0
- package/bundle/hkp-rt/lib/src/services/http_server/http_listener.cpp +130 -0
- package/bundle/hkp-rt/lib/src/services/http_server/http_listener.h +33 -0
- package/bundle/hkp-rt/lib/src/services/http_server/http_server.cpp +351 -0
- package/bundle/hkp-rt/lib/src/services/http_server/http_server.h +79 -0
- package/bundle/hkp-rt/lib/src/services/http_server/http_server_impl.cpp +230 -0
- package/bundle/hkp-rt/lib/src/services/http_server/http_server_impl.h +64 -0
- package/bundle/hkp-rt/lib/src/services/http_server/http_server_subservices.cpp +950 -0
- package/bundle/hkp-rt/lib/src/services/http_server/http_server_subservices.h +253 -0
- package/bundle/hkp-rt/lib/src/services/http_server/http_session.cpp +495 -0
- package/bundle/hkp-rt/lib/src/services/http_server/http_session.h +169 -0
- package/bundle/hkp-rt/lib/src/services/http_server/http_stream_listener.cpp +270 -0
- package/bundle/hkp-rt/lib/src/services/http_server/http_stream_listener.h +104 -0
- package/bundle/hkp-rt/lib/src/services/http_server/request_decode.h +164 -0
- package/bundle/hkp-rt/lib/src/services/if_service.h +210 -0
- package/bundle/hkp-rt/lib/src/services/ifft_service.h +198 -0
- package/bundle/hkp-rt/lib/src/services/injector.h +162 -0
- package/bundle/hkp-rt/lib/src/services/iterator.h +201 -0
- package/bundle/hkp-rt/lib/src/services/map.h +641 -0
- package/bundle/hkp-rt/lib/src/services/monitor.h +161 -0
- package/bundle/hkp-rt/lib/src/services/mp3_frames.h +119 -0
- package/bundle/hkp-rt/lib/src/services/mp4_to_wav.cpp +281 -0
- package/bundle/hkp-rt/lib/src/services/mp4_to_wav.h +263 -0
- package/bundle/hkp-rt/lib/src/services/peer_server/peer_registry.cpp +88 -0
- package/bundle/hkp-rt/lib/src/services/peer_server/peer_registry.h +43 -0
- package/bundle/hkp-rt/lib/src/services/peer_server/peer_server.h +163 -0
- package/bundle/hkp-rt/lib/src/services/peer_server/peer_server_listener.cpp +133 -0
- package/bundle/hkp-rt/lib/src/services/peer_server/peer_server_listener.h +46 -0
- package/bundle/hkp-rt/lib/src/services/peer_server/peer_server_session.cpp +165 -0
- package/bundle/hkp-rt/lib/src/services/peer_server/peer_server_session.h +44 -0
- package/bundle/hkp-rt/lib/src/services/peer_server/peer_ws_session.cpp +184 -0
- package/bundle/hkp-rt/lib/src/services/peer_server/peer_ws_session.h +55 -0
- package/bundle/hkp-rt/lib/src/services/receiver_session.h +172 -0
- package/bundle/hkp-rt/lib/src/services/root_certificates.h +3959 -0
- package/bundle/hkp-rt/lib/src/services/sha256_openssl.h +49 -0
- package/bundle/hkp-rt/lib/src/services/speech_http.h +306 -0
- package/bundle/hkp-rt/lib/src/services/speech_to_text.cpp +595 -0
- package/bundle/hkp-rt/lib/src/services/speech_to_text.h +69 -0
- package/bundle/hkp-rt/lib/src/services/static.cpp +45 -0
- package/bundle/hkp-rt/lib/src/services/static.h +41 -0
- package/bundle/hkp-rt/lib/src/services/stopper.h +72 -0
- package/bundle/hkp-rt/lib/src/services/sub_service.h +286 -0
- package/bundle/hkp-rt/lib/src/services/text_generation.cpp +1132 -0
- package/bundle/hkp-rt/lib/src/services/text_generation.h +71 -0
- package/bundle/hkp-rt/lib/src/services/text_to_speech.cpp +853 -0
- package/bundle/hkp-rt/lib/src/services/text_to_speech.h +97 -0
- package/bundle/hkp-rt/lib/src/services/timer.h +300 -0
- package/bundle/hkp-rt/lib/src/services/tracks.h +397 -0
- package/bundle/hkp-rt/lib/src/services/tts_inflect.cpp +149 -0
- package/bundle/hkp-rt/lib/src/services/tts_inflect.h +82 -0
- package/bundle/hkp-rt/lib/src/services/wav_reader.h +213 -0
- package/bundle/hkp-rt/lib/src/services/websocket_client_service.h +182 -0
- package/bundle/hkp-rt/lib/src/services/websocket_reader.h +148 -0
- package/bundle/hkp-rt/lib/src/services/websocket_server_service.h +196 -0
- package/bundle/hkp-rt/lib/src/services/websocket_writer.cpp +641 -0
- package/bundle/hkp-rt/lib/src/services/websocket_writer.h +117 -0
- package/bundle/hkp-rt/lib/src/sub_runtime.cpp +234 -0
- package/bundle/hkp-rt/lib/src/sub_runtime.h +160 -0
- package/bundle/hkp-rt/lib/src/types/data.cpp +237 -0
- package/bundle/hkp-rt/lib/src/types/message.cpp +148 -0
- package/bundle/hkp-rt/lib/src/types/models.cpp +83 -0
- package/bundle/hkp-rt/lib/src/types/ringbuffer.cpp +336 -0
- package/bundle/hkp-rt/lib/src/uuid.h +12 -0
- package/bundle/readymade-mcp-commits.json +5 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +79 -0
- package/dist/git.d.ts +8 -0
- package/dist/git.d.ts.map +1 -0
- package/dist/git.js +53 -0
- package/dist/http.d.ts +17 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +130 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/repository.d.ts +49 -0
- package/dist/repository.d.ts.map +1 -0
- package/dist/repository.js +452 -0
- package/dist/schemas.d.ts +156 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/schemas.js +43 -0
- package/dist/server.d.ts +4 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +83 -0
- package/dist/types.d.ts +63 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +20 -0
- package/dist/validation.d.ts +9 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +101 -0
- package/package.json +52 -4
|
@@ -0,0 +1,1131 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import threading
|
|
5
|
+
|
|
6
|
+
import time
|
|
7
|
+
import uuid as _uuid
|
|
8
|
+
from contextlib import contextmanager
|
|
9
|
+
from datetime import datetime, timezone
|
|
10
|
+
from typing import Any, Coroutine, Callable, Iterator, Literal
|
|
11
|
+
|
|
12
|
+
from .address import ADDRESS_SEPARATOR, descend, split_address
|
|
13
|
+
from .data import ControlFlowData
|
|
14
|
+
from .mounts import MountHandle, MountHandler, RuntimeMounts
|
|
15
|
+
from .secrets import SecretEntry, SecretVault
|
|
16
|
+
from .assets import AssetStore
|
|
17
|
+
from .auth import ANONYMOUS_SUB, AuthenticatedUser
|
|
18
|
+
from .types import (
|
|
19
|
+
Caller,
|
|
20
|
+
PersonRunActor,
|
|
21
|
+
RunActor,
|
|
22
|
+
SourceRunActor,
|
|
23
|
+
LogEntry,
|
|
24
|
+
LogLevel,
|
|
25
|
+
ProcessContext,
|
|
26
|
+
HostedService,
|
|
27
|
+
JsonRecord,
|
|
28
|
+
NotificationCallback,
|
|
29
|
+
RuntimeConfiguration,
|
|
30
|
+
RuntimeDescriptor,
|
|
31
|
+
RuntimeNotification,
|
|
32
|
+
ServiceConfiguration,
|
|
33
|
+
ServiceCreator,
|
|
34
|
+
ServiceDescriptor,
|
|
35
|
+
ServiceRegistryEntry,
|
|
36
|
+
SlotStore,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
#: Severity order, so a runtime can drop anything below what it records.
|
|
41
|
+
LOG_LEVELS = {"debug": 0, "info": 1, "warn": 2, "error": 3}
|
|
42
|
+
DEFAULT_PERSON_RUN_TTL_MS = 15 * 60 * 1000
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def iso_timestamp() -> str:
|
|
46
|
+
"""UTC, ISO 8601, to milliseconds — what every runtime stamps an entry with.
|
|
47
|
+
|
|
48
|
+
Not ``datetime.isoformat()``, which writes microseconds and ``+00:00``. A
|
|
49
|
+
board's log holds entries from every runtime it spans in one file, and it is
|
|
50
|
+
ordered and filtered by comparing this field as text — so a stamp that spells
|
|
51
|
+
the same instant differently to hkp-node's and hkp-rt's sorts against them
|
|
52
|
+
rather than among them.
|
|
53
|
+
"""
|
|
54
|
+
now = datetime.now(timezone.utc)
|
|
55
|
+
return f"{now.strftime('%Y-%m-%dT%H:%M:%S')}.{now.microsecond // 1000:03d}Z"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def new_run(
|
|
59
|
+
kind: Literal["board", "mount", "local"] = "board",
|
|
60
|
+
) -> ProcessContext:
|
|
61
|
+
"""A run with no parent: something outside the board asked for this."""
|
|
62
|
+
return ProcessContext(run_id=str(_uuid.uuid4()), actor=SourceRunActor(kind=kind))
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def context_from_wire(value: Any) -> ProcessContext | None:
|
|
66
|
+
"""Read a context a peer sent, filling in what it left out.
|
|
67
|
+
|
|
68
|
+
A caller that names no run is not continuing one, so a run is begun rather
|
|
69
|
+
than left unidentified — work that cannot be attributed to anything is worse
|
|
70
|
+
than work attributed to a run of its own. Returns None only when there was
|
|
71
|
+
no context at all, which lets the caller decide.
|
|
72
|
+
"""
|
|
73
|
+
if not isinstance(value, dict):
|
|
74
|
+
return None
|
|
75
|
+
|
|
76
|
+
def text(key: str) -> str | None:
|
|
77
|
+
found = value.get(key)
|
|
78
|
+
return found if isinstance(found, str) and found else None
|
|
79
|
+
|
|
80
|
+
return ProcessContext(
|
|
81
|
+
run_id=text("runId") or str(_uuid.uuid4()),
|
|
82
|
+
actor=SourceRunActor(kind="local"),
|
|
83
|
+
parent_run_id=text("parentRunId"),
|
|
84
|
+
request_id=text("requestId"),
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def caller_of(user: AuthenticatedUser | None) -> Caller | None:
|
|
89
|
+
"""Who a signed-in user is to a run they begin, or nothing where there is
|
|
90
|
+
no identity to state.
|
|
91
|
+
|
|
92
|
+
A server running without authentication resolves everybody to the one
|
|
93
|
+
anonymous tenant. That is *no caller*, not a caller called anonymous —
|
|
94
|
+
otherwise everybody on a development machine would be the same person.
|
|
95
|
+
"""
|
|
96
|
+
if user is None or user.sub == ANONYMOUS_SUB:
|
|
97
|
+
return None
|
|
98
|
+
# A client holding a token reaches only its own tenant's runtimes, so it
|
|
99
|
+
# is their owner; a member arrives through a coordinator, which says so.
|
|
100
|
+
return Caller(sub=user.sub, email=user.email or None, role="owner")
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def context_for_client(
|
|
104
|
+
wire: Any, user: AuthenticatedUser | None
|
|
105
|
+
) -> ProcessContext:
|
|
106
|
+
"""The context of a run a client holding a token begins: a REST process
|
|
107
|
+
call, or a ``processRuntime`` on a runtime's socket.
|
|
108
|
+
|
|
109
|
+
The run metadata the client sent is kept, as ``context_from_wire`` reads
|
|
110
|
+
it. Whatever it said about a caller is not read at all: who is calling is
|
|
111
|
+
what the server verified, so a client cannot act in another person's name
|
|
112
|
+
by saying so.
|
|
113
|
+
"""
|
|
114
|
+
context = context_from_wire(wire) or new_run("local")
|
|
115
|
+
caller = caller_of(user)
|
|
116
|
+
context.actor = (
|
|
117
|
+
PersonRunActor(
|
|
118
|
+
kind="person",
|
|
119
|
+
sub=caller.sub,
|
|
120
|
+
email=caller.email,
|
|
121
|
+
name=caller.name,
|
|
122
|
+
role=caller.role,
|
|
123
|
+
expires_at=int(time.time() * 1000) + DEFAULT_PERSON_RUN_TTL_MS,
|
|
124
|
+
)
|
|
125
|
+
if caller is not None
|
|
126
|
+
else SourceRunActor(kind="local")
|
|
127
|
+
)
|
|
128
|
+
return context
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def caller_from_wire(value: Any) -> Caller | None:
|
|
132
|
+
"""A caller as a participant link states one, or nothing when the shape
|
|
133
|
+
is off."""
|
|
134
|
+
if not isinstance(value, dict):
|
|
135
|
+
return None
|
|
136
|
+
sub = value.get("sub")
|
|
137
|
+
if not isinstance(sub, str) or not sub:
|
|
138
|
+
return None
|
|
139
|
+
email = value.get("email")
|
|
140
|
+
name = value.get("name")
|
|
141
|
+
role = value.get("role")
|
|
142
|
+
return Caller(
|
|
143
|
+
sub=sub,
|
|
144
|
+
email=email if isinstance(email, str) and email else None,
|
|
145
|
+
name=name if isinstance(name, str) and name else None,
|
|
146
|
+
role=role if role in ("owner", "member") else None,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def actor_from_wire(value: Any) -> RunActor | None:
|
|
151
|
+
"""An actor as a trusted participant link states it."""
|
|
152
|
+
if not isinstance(value, dict):
|
|
153
|
+
return None
|
|
154
|
+
kind = value.get("kind")
|
|
155
|
+
if kind == "person":
|
|
156
|
+
caller = caller_from_wire(value)
|
|
157
|
+
expires_at = value.get("expiresAt")
|
|
158
|
+
return PersonRunActor(
|
|
159
|
+
kind="person",
|
|
160
|
+
sub=caller.sub if caller is not None else "",
|
|
161
|
+
email=caller.email if caller is not None else None,
|
|
162
|
+
name=caller.name if caller is not None else None,
|
|
163
|
+
role=caller.role if caller is not None else None,
|
|
164
|
+
expires_at=(
|
|
165
|
+
int(expires_at)
|
|
166
|
+
if isinstance(expires_at, (int, float))
|
|
167
|
+
and not isinstance(expires_at, bool)
|
|
168
|
+
else 0
|
|
169
|
+
),
|
|
170
|
+
)
|
|
171
|
+
if kind in ("board", "mount", "local"):
|
|
172
|
+
return SourceRunActor(kind=kind)
|
|
173
|
+
return None
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def context_from_link(value: Any) -> ProcessContext | None:
|
|
177
|
+
"""The context of a run as a coordinator says it over a participant link.
|
|
178
|
+
|
|
179
|
+
The one path on which a caller is taken as stated. The link is the board's
|
|
180
|
+
own — opened by this server with the board's ticket — and the coordinator
|
|
181
|
+
on it is what verified the person. Kept apart from ``context_from_wire`` so
|
|
182
|
+
that no other entry point can come to trust a caller by sharing a parser.
|
|
183
|
+
"""
|
|
184
|
+
context = context_from_wire(value)
|
|
185
|
+
if context is None:
|
|
186
|
+
return None
|
|
187
|
+
# The reply address belongs to whoever was waiting at the other end.
|
|
188
|
+
context.request_id = None
|
|
189
|
+
context.actor = actor_from_wire(value.get("actor")) or SourceRunActor(
|
|
190
|
+
kind="board"
|
|
191
|
+
)
|
|
192
|
+
return context
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def context_to_wire(context: ProcessContext | None) -> dict[str, Any] | None:
|
|
196
|
+
"""A run as it is said to another runtime: which run, and who began it.
|
|
197
|
+
The reply address is left out — it means something only to whoever is
|
|
198
|
+
waiting."""
|
|
199
|
+
if context is None:
|
|
200
|
+
return None
|
|
201
|
+
wire: dict[str, Any] = {"runId": context.run_id}
|
|
202
|
+
if context.parent_run_id:
|
|
203
|
+
wire["parentRunId"] = context.parent_run_id
|
|
204
|
+
wire["actor"] = context.actor.to_wire()
|
|
205
|
+
return wire
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def child_run(parent: ProcessContext | None) -> ProcessContext:
|
|
209
|
+
"""A run invoked from inside another one, as a nested pipeline is.
|
|
210
|
+
|
|
211
|
+
The child gets an identity of its own rather than borrowing its parent's, so
|
|
212
|
+
that work done inside a sub-pipeline stays distinguishable from work done
|
|
213
|
+
around it — which is the whole difference between a trace that shows nesting
|
|
214
|
+
and one that shows a flat list in timestamp order. The actor is inherited
|
|
215
|
+
exactly, whether it is a person, the board, a mount, or local work.
|
|
216
|
+
"""
|
|
217
|
+
if parent is None:
|
|
218
|
+
return new_run()
|
|
219
|
+
return ProcessContext(
|
|
220
|
+
run_id=str(_uuid.uuid4()),
|
|
221
|
+
actor=parent.actor,
|
|
222
|
+
parent_run_id=parent.run_id,
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def run_expired(context: ProcessContext | None, now_ms: int | None = None) -> bool:
|
|
227
|
+
"""Person authority is invalid unless it carries a live finite deadline."""
|
|
228
|
+
if context is None or context.actor.kind != "person":
|
|
229
|
+
return False
|
|
230
|
+
now = int(time.time() * 1000) if now_ms is None else now_ms
|
|
231
|
+
return context.actor.expires_at <= now
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
#: The loop the server runs on, for work a service starts from a worker thread.
|
|
235
|
+
#:
|
|
236
|
+
#: A runtime's ``process`` is called on a thread pool, so a service scheduling
|
|
237
|
+
#: something during a pass has no running loop to schedule it on. The server
|
|
238
|
+
#: records its loop here once, and every runtime — nested ones included, which
|
|
239
|
+
#: are built during a pass and would otherwise capture nothing — schedules
|
|
240
|
+
#: through it.
|
|
241
|
+
_server_loop: asyncio.AbstractEventLoop | None = None
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def set_server_loop(loop: asyncio.AbstractEventLoop | None) -> None:
|
|
245
|
+
"""Told once by the server, at startup."""
|
|
246
|
+
global _server_loop
|
|
247
|
+
_server_loop = loop
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
class HostedRuntime:
|
|
251
|
+
def __init__(
|
|
252
|
+
self,
|
|
253
|
+
config: RuntimeConfiguration,
|
|
254
|
+
create_service: ServiceCreator,
|
|
255
|
+
mounts: RuntimeMounts | None = None,
|
|
256
|
+
) -> None:
|
|
257
|
+
self._mounts = mounts
|
|
258
|
+
self.id = config.id
|
|
259
|
+
self.name = config.name
|
|
260
|
+
self.board_name = config.board_name
|
|
261
|
+
#: See RuntimeConfiguration.garbage_collected. False means persist.
|
|
262
|
+
self.garbage_collected = config.garbage_collected
|
|
263
|
+
self._services: dict[str, HostedService] = {}
|
|
264
|
+
self._service_order: list[str] = []
|
|
265
|
+
self._notification_targets: set[NotificationCallback] = set()
|
|
266
|
+
self._result_targets: set[
|
|
267
|
+
Callable[[Any, ProcessContext | None], None]
|
|
268
|
+
] = set()
|
|
269
|
+
#: The call being processed right now, and which service it is inside;
|
|
270
|
+
#: see _context and _with_context. Kept per thread: passes run on a
|
|
271
|
+
#: worker thread while services are configured on the server's loop,
|
|
272
|
+
#: and a service may report from a thread of its own. One field shared
|
|
273
|
+
#: between them would let a call that began on one thread restore a
|
|
274
|
+
#: context saved from another, and leave a finished run in place for
|
|
275
|
+
#: everything that followed.
|
|
276
|
+
self._call = threading.local()
|
|
277
|
+
self._log_targets: set[Callable[[LogEntry], None]] = set()
|
|
278
|
+
#: Whether log entries may carry their ``data`` payload.
|
|
279
|
+
#:
|
|
280
|
+
#: Off unless a board turns it on, because ``data`` is the one free-form
|
|
281
|
+
#: field and therefore the only place a service can record something it
|
|
282
|
+
#: did not mean to. Redaction at the source is a discipline and
|
|
283
|
+
#: disciplines fail; leaving the channel closed by default means a lapse
|
|
284
|
+
#: can only escape somewhere somebody deliberately opened it.
|
|
285
|
+
# Absent means allowed; the per-service choice is the gate.
|
|
286
|
+
self._log_data = config.log_data
|
|
287
|
+
#: Capture mode: whether this runtime's call reports leave it with their
|
|
288
|
+
#: values. Switched while the board runs and never part of its
|
|
289
|
+
#: configuration, so a runtime always starts without it. Read where a
|
|
290
|
+
#: report leaves the server (``outgoing_payload``), so a pipeline nested
|
|
291
|
+
#: at any depth — whose reports reach the server only through this
|
|
292
|
+
#: runtime — follows it too.
|
|
293
|
+
self._capture = False
|
|
294
|
+
#: Whether anything is recorded at all; see RuntimeConfiguration.logging
|
|
295
|
+
self._logging = config.logging
|
|
296
|
+
self._log_level = config.log_level if config.log_level in LOG_LEVELS else "info"
|
|
297
|
+
self._create_service = create_service
|
|
298
|
+
#: Values for the references this runtime's services carry. Held apart
|
|
299
|
+
#: from every service's state, and reachable only through secrets().
|
|
300
|
+
#: Filled before any service is built, because a service that opens a
|
|
301
|
+
#: connection while being configured asks for its credential then.
|
|
302
|
+
self._vault = SecretVault()
|
|
303
|
+
self._vault.replace(config.secrets)
|
|
304
|
+
#: Where this runtime's secrets come from when they are not its own;
|
|
305
|
+
#: see delegate_secrets.
|
|
306
|
+
self._secrets_from: Callable[[], SecretVault | None] | None = None
|
|
307
|
+
#: The descriptors of the assets this runtime's services reference, and
|
|
308
|
+
#: the content they resolve to. Filled before any service is built, for
|
|
309
|
+
#: the same reason the vault is. Reachable through assets().
|
|
310
|
+
self._asset_store = AssetStore()
|
|
311
|
+
self._asset_store.replace(config.assets)
|
|
312
|
+
#: Where this runtime's assets come from when they are not its own; see
|
|
313
|
+
#: delegate_assets.
|
|
314
|
+
self._assets_from: Callable[[], AssetStore | None] | None = None
|
|
315
|
+
#: The cells services in this runtime hold values in between passes.
|
|
316
|
+
#: Owned rather than delegated by default: a runtime is the outermost
|
|
317
|
+
#: thing a slot name can mean, so two services that name the same slot
|
|
318
|
+
#: and are given nothing more specific share this one.
|
|
319
|
+
self._own_slots = SlotStore()
|
|
320
|
+
#: Where this runtime's slots come from when they are not its own;
|
|
321
|
+
#: see delegate_slots.
|
|
322
|
+
self._slots_from: Callable[[], SlotStore | None] | None = None
|
|
323
|
+
#: Where this runtime's mounts are claimed, when it has no server of
|
|
324
|
+
#: its own; see delegate_mounts.
|
|
325
|
+
self._mounts_from: (
|
|
326
|
+
Callable[[str, MountHandler, str | None], MountHandle | None] | None
|
|
327
|
+
) = None
|
|
328
|
+
|
|
329
|
+
for svc_config in config.services:
|
|
330
|
+
self.add_service(svc_config)
|
|
331
|
+
|
|
332
|
+
# ── Serialisation ──────────────────────────────────────────────────────────
|
|
333
|
+
|
|
334
|
+
def serialize(self, output_url: str | None = None) -> RuntimeDescriptor:
|
|
335
|
+
return RuntimeDescriptor(
|
|
336
|
+
id=self.id,
|
|
337
|
+
name=self.name,
|
|
338
|
+
board_name=self.board_name,
|
|
339
|
+
services=self.list_services(),
|
|
340
|
+
inputs=[],
|
|
341
|
+
output_url=output_url,
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
def list_services(self) -> list[ServiceDescriptor]:
|
|
345
|
+
result: list[ServiceDescriptor] = []
|
|
346
|
+
for uuid in self._service_order:
|
|
347
|
+
svc = self._services.get(uuid)
|
|
348
|
+
if svc:
|
|
349
|
+
result.append(
|
|
350
|
+
ServiceDescriptor(
|
|
351
|
+
service_id=svc.service_id,
|
|
352
|
+
service_name=svc.service_name,
|
|
353
|
+
version=getattr(svc, "version", None),
|
|
354
|
+
capabilities=getattr(svc, "capabilities", None),
|
|
355
|
+
uuid=svc.uuid,
|
|
356
|
+
state=svc.get_state(),
|
|
357
|
+
)
|
|
358
|
+
)
|
|
359
|
+
return result
|
|
360
|
+
|
|
361
|
+
# ── Service management ─────────────────────────────────────────────────────
|
|
362
|
+
|
|
363
|
+
def get_service(self, address: str) -> HostedService | None:
|
|
364
|
+
"""The service an address names, flat or scoped.
|
|
365
|
+
|
|
366
|
+
The flat list is asked first, so a uuid that happens to contain a dot
|
|
367
|
+
is still that service rather than a path into something else. Only when
|
|
368
|
+
no service carries the whole address is it read as one — see
|
|
369
|
+
``address.py``.
|
|
370
|
+
"""
|
|
371
|
+
direct = self._services.get(address)
|
|
372
|
+
if direct is not None:
|
|
373
|
+
return direct
|
|
374
|
+
segments = split_address(address)
|
|
375
|
+
if len(segments) < 2:
|
|
376
|
+
return None
|
|
377
|
+
head = self._services.get(segments[0])
|
|
378
|
+
return descend(head, segments[1:]) if head is not None else None
|
|
379
|
+
|
|
380
|
+
def add_service(self, config: ServiceConfiguration) -> JsonRecord:
|
|
381
|
+
if config.uuid in self._services:
|
|
382
|
+
raise ValueError(f"Service already exists: {config.uuid}")
|
|
383
|
+
svc = self._create_service(config)
|
|
384
|
+
if hasattr(svc, "set_host"):
|
|
385
|
+
svc.set_host(self)
|
|
386
|
+
self._services[svc.uuid] = svc
|
|
387
|
+
self._service_order.append(svc.uuid)
|
|
388
|
+
return svc.get_state()
|
|
389
|
+
|
|
390
|
+
def configure_service(
|
|
391
|
+
self,
|
|
392
|
+
address: str,
|
|
393
|
+
config: JsonRecord,
|
|
394
|
+
context: ProcessContext | None = None,
|
|
395
|
+
) -> JsonRecord | None:
|
|
396
|
+
svc = self.get_service(address)
|
|
397
|
+
if not svc:
|
|
398
|
+
return None
|
|
399
|
+
if context is None:
|
|
400
|
+
return svc.configure(config)
|
|
401
|
+
with self._with_context(context):
|
|
402
|
+
return svc.configure(config)
|
|
403
|
+
|
|
404
|
+
def remove_service(self, uuid: str) -> bool:
|
|
405
|
+
svc = self._services.get(uuid)
|
|
406
|
+
if svc and hasattr(svc, "destroy"):
|
|
407
|
+
svc.destroy()
|
|
408
|
+
if uuid not in self._services:
|
|
409
|
+
return False
|
|
410
|
+
del self._services[uuid]
|
|
411
|
+
self._service_order = [u for u in self._service_order if u != uuid]
|
|
412
|
+
return True
|
|
413
|
+
|
|
414
|
+
def rearrange_services(self, new_order: list[str]) -> bool:
|
|
415
|
+
if len(new_order) != len(self._service_order):
|
|
416
|
+
return False
|
|
417
|
+
known = set(self._service_order)
|
|
418
|
+
for uuid in new_order:
|
|
419
|
+
if uuid not in known:
|
|
420
|
+
return False
|
|
421
|
+
self._service_order = list(new_order)
|
|
422
|
+
return True
|
|
423
|
+
|
|
424
|
+
def destroy(self) -> None:
|
|
425
|
+
for svc in self._services.values():
|
|
426
|
+
if hasattr(svc, "destroy"):
|
|
427
|
+
svc.destroy()
|
|
428
|
+
self._services.clear()
|
|
429
|
+
self._service_order = []
|
|
430
|
+
self._notification_targets.clear()
|
|
431
|
+
self._result_targets.clear()
|
|
432
|
+
self._log_targets.clear()
|
|
433
|
+
|
|
434
|
+
# ── Notification / result targets ──────────────────────────────────────────
|
|
435
|
+
|
|
436
|
+
def register_notification_target(self, target: NotificationCallback) -> Callable[[], None]:
|
|
437
|
+
self._notification_targets.add(target)
|
|
438
|
+
return lambda: self._notification_targets.discard(target)
|
|
439
|
+
|
|
440
|
+
def register_result_target(
|
|
441
|
+
self, target: Callable[[Any, ProcessContext | None], None]
|
|
442
|
+
) -> Callable[[], None]:
|
|
443
|
+
"""Where what this runtime emits goes. A target is told the run the
|
|
444
|
+
value was emitted in, or None when it was emitted outside one, so that
|
|
445
|
+
whoever carries it on can say which run it continues."""
|
|
446
|
+
self._result_targets.add(target)
|
|
447
|
+
return lambda: self._result_targets.discard(target)
|
|
448
|
+
|
|
449
|
+
# ── Pipeline processing ────────────────────────────────────────────────────
|
|
450
|
+
|
|
451
|
+
def process(
|
|
452
|
+
self,
|
|
453
|
+
input: Any,
|
|
454
|
+
on_notification: NotificationCallback,
|
|
455
|
+
context: ProcessContext | None = None,
|
|
456
|
+
) -> Any:
|
|
457
|
+
with self._with_context(context or new_run()):
|
|
458
|
+
return self._process_from_index(0, input, on_notification)
|
|
459
|
+
|
|
460
|
+
def process_at(
|
|
461
|
+
self,
|
|
462
|
+
start_at_uuid: str,
|
|
463
|
+
data: Any,
|
|
464
|
+
on_notification: NotificationCallback,
|
|
465
|
+
context: ProcessContext | None = None,
|
|
466
|
+
) -> Any:
|
|
467
|
+
"""Run the pipeline starting **at** a service rather than after it.
|
|
468
|
+
|
|
469
|
+
`process_from` exists for a service handing work onward — it means
|
|
470
|
+
"carry on behind me", so it advances past the caller. This is the other
|
|
471
|
+
question: something outside the pipeline wants a particular service to
|
|
472
|
+
do its job with a given payload, and that service must actually run.
|
|
473
|
+
|
|
474
|
+
hkp-rt spells the same distinction as
|
|
475
|
+
``processFrom(service, data, advanceBefore)``; kept as a separate entry
|
|
476
|
+
point here so the advancing call, which every service uses, cannot
|
|
477
|
+
change shape by accident.
|
|
478
|
+
"""
|
|
479
|
+
try:
|
|
480
|
+
start_index = self._service_order.index(start_at_uuid)
|
|
481
|
+
except ValueError:
|
|
482
|
+
# Not one of this runtime's own: a scoped address enters the
|
|
483
|
+
# pipeline that holds it instead, and runs to the end of *that*
|
|
484
|
+
# list. What it produces is the nested pipeline's answer.
|
|
485
|
+
segments = split_address(start_at_uuid)
|
|
486
|
+
head = self._services.get(segments[0]) if len(segments) > 1 else None
|
|
487
|
+
enter = getattr(head, "process_nested", None) if head else None
|
|
488
|
+
if enter is None:
|
|
489
|
+
raise KeyError(start_at_uuid)
|
|
490
|
+
return enter(ADDRESS_SEPARATOR.join(segments[1:]), data, context)
|
|
491
|
+
|
|
492
|
+
# Nothing to continue: whoever asked for this is outside the board, so
|
|
493
|
+
# it begins a run rather than joining one.
|
|
494
|
+
with self._with_context(context or new_run()):
|
|
495
|
+
return self._process_from_index(start_index, data, on_notification)
|
|
496
|
+
|
|
497
|
+
# ── RuntimeHost interface ──────────────────────────────────────────────────
|
|
498
|
+
|
|
499
|
+
def current_context(self) -> ProcessContext | None:
|
|
500
|
+
return self._context
|
|
501
|
+
|
|
502
|
+
@property
|
|
503
|
+
def _context(self) -> ProcessContext | None:
|
|
504
|
+
"""The call this thread is processing, or None when it is in none."""
|
|
505
|
+
return getattr(self._call, "context", None)
|
|
506
|
+
|
|
507
|
+
@_context.setter
|
|
508
|
+
def _context(self, context: ProcessContext | None) -> None:
|
|
509
|
+
self._call.context = context
|
|
510
|
+
|
|
511
|
+
@property
|
|
512
|
+
def _current_service(self) -> str | None:
|
|
513
|
+
"""Which service this thread's pass is inside, for a log entry to name."""
|
|
514
|
+
return getattr(self._call, "service", None)
|
|
515
|
+
|
|
516
|
+
@_current_service.setter
|
|
517
|
+
def _current_service(self, uuid: str | None) -> None:
|
|
518
|
+
self._call.service = uuid
|
|
519
|
+
|
|
520
|
+
def process_from(
|
|
521
|
+
self,
|
|
522
|
+
start_after_uuid: str,
|
|
523
|
+
data: Any,
|
|
524
|
+
on_notification: NotificationCallback,
|
|
525
|
+
context: ProcessContext | None = None,
|
|
526
|
+
) -> Any:
|
|
527
|
+
# Three ways to arrive here, and each wants a different run:
|
|
528
|
+
#
|
|
529
|
+
# - Named explicitly: a service that left its call and came back — an
|
|
530
|
+
# HTTP response, an awaited write — handing back what it captured.
|
|
531
|
+
# - Called from inside a call: a service pulling the services after it
|
|
532
|
+
# rather than returning to them. Still the same run, and the current
|
|
533
|
+
# context already says which, so nothing has to be threaded by hand.
|
|
534
|
+
# - Neither: a timer tick, an arriving message. Nothing to continue, so
|
|
535
|
+
# this begins a run.
|
|
536
|
+
#
|
|
537
|
+
# A service that leaves its call and forgets to capture lands in the
|
|
538
|
+
# third case, which splits its trace in two rather than attributing its
|
|
539
|
+
# work to whichever run happened to be in flight. Fragmentation is
|
|
540
|
+
# visible in a trace; misattribution reads as fact.
|
|
541
|
+
run_context = context or self._context or new_run()
|
|
542
|
+
|
|
543
|
+
try:
|
|
544
|
+
start_index = self._service_order.index(start_after_uuid) + 1
|
|
545
|
+
except ValueError:
|
|
546
|
+
start_index = len(self._service_order)
|
|
547
|
+
|
|
548
|
+
# A service pushing from itself (a Timer tick, an inbound request) was
|
|
549
|
+
# never called by the loop below, so the loop never reported it. Report
|
|
550
|
+
# it here, or the UI shows a service producing nothing while the service
|
|
551
|
+
# after it plainly receives data.
|
|
552
|
+
self._emit_notification(
|
|
553
|
+
RuntimeNotification(
|
|
554
|
+
instance_id=start_after_uuid,
|
|
555
|
+
payload={"__internal": {"state": "call-process", "data": None}},
|
|
556
|
+
),
|
|
557
|
+
on_notification,
|
|
558
|
+
)
|
|
559
|
+
self._emit_notification(
|
|
560
|
+
RuntimeNotification(
|
|
561
|
+
instance_id=start_after_uuid,
|
|
562
|
+
payload={"__internal": {"state": "call-process-finished", "data": data}},
|
|
563
|
+
),
|
|
564
|
+
on_notification,
|
|
565
|
+
)
|
|
566
|
+
|
|
567
|
+
with self._with_context(run_context):
|
|
568
|
+
return self._process_from_index(start_index, data, on_notification)
|
|
569
|
+
|
|
570
|
+
def mount(
|
|
571
|
+
self,
|
|
572
|
+
service_uuid: str,
|
|
573
|
+
handler: MountHandler,
|
|
574
|
+
mount_name: str | None = None,
|
|
575
|
+
) -> MountHandle | None:
|
|
576
|
+
"""Claim a publicly reachable endpoint served by the shared server.
|
|
577
|
+
|
|
578
|
+
Returns None when the host cannot serve mounts — an inner sub-service
|
|
579
|
+
pipeline, or a server that is not listening yet — in which case the
|
|
580
|
+
service has no public endpoint and should say so in its state rather
|
|
581
|
+
than falling back to a port of its own.
|
|
582
|
+
|
|
583
|
+
The board is named here rather than by the caller: a service knows what
|
|
584
|
+
its endpoint is called, and the runtime knows which board it is in, and
|
|
585
|
+
the address is derived from both.
|
|
586
|
+
"""
|
|
587
|
+
# A nested runtime has no server of its own, so it asks the one around
|
|
588
|
+
# it — the same arrangement as secrets and slots. Without this an
|
|
589
|
+
# endpoint inside a scope had no address at all.
|
|
590
|
+
if self._mounts_from is not None:
|
|
591
|
+
return self._mounts_from(service_uuid, handler, mount_name)
|
|
592
|
+
if not self._mounts:
|
|
593
|
+
return None
|
|
594
|
+
return self._mounts.mount(
|
|
595
|
+
service_uuid,
|
|
596
|
+
handler,
|
|
597
|
+
board_name=self.board_name,
|
|
598
|
+
mount_name=mount_name,
|
|
599
|
+
)
|
|
600
|
+
|
|
601
|
+
def notify(
|
|
602
|
+
self,
|
|
603
|
+
payload: Any,
|
|
604
|
+
instance_id: str,
|
|
605
|
+
context: ProcessContext | None = None,
|
|
606
|
+
) -> None:
|
|
607
|
+
self._emit_notification(
|
|
608
|
+
RuntimeNotification(
|
|
609
|
+
instance_id=instance_id, payload=payload, context=context
|
|
610
|
+
),
|
|
611
|
+
lambda _: None,
|
|
612
|
+
)
|
|
613
|
+
|
|
614
|
+
def log(self, level: LogLevel, event: str, data: Any = None) -> None:
|
|
615
|
+
# Nothing to attribute an entry to means nothing worth recording: a
|
|
616
|
+
# service logging outside a call has no run, and an entry that names no
|
|
617
|
+
# run cannot be found again.
|
|
618
|
+
# Off means off: no entry is built, so nothing is spent deciding what
|
|
619
|
+
# it would have said.
|
|
620
|
+
if (
|
|
621
|
+
not self._logging
|
|
622
|
+
or LOG_LEVELS[level] < LOG_LEVELS[self._log_level]
|
|
623
|
+
or self._context is None
|
|
624
|
+
or not self._log_targets
|
|
625
|
+
):
|
|
626
|
+
return
|
|
627
|
+
|
|
628
|
+
entry = LogEntry(
|
|
629
|
+
run_id=self._context.run_id,
|
|
630
|
+
parent_run_id=self._context.parent_run_id,
|
|
631
|
+
ts=iso_timestamp(),
|
|
632
|
+
runtime_id=self.id,
|
|
633
|
+
service_uuid=self._current_service or "",
|
|
634
|
+
level=level,
|
|
635
|
+
event=event,
|
|
636
|
+
data=data if (self._log_data and data is not None) else None,
|
|
637
|
+
caller=(
|
|
638
|
+
self._context.actor.sub
|
|
639
|
+
if self._context.actor.kind == "person"
|
|
640
|
+
else None
|
|
641
|
+
),
|
|
642
|
+
)
|
|
643
|
+
for target in list(self._log_targets):
|
|
644
|
+
target(entry)
|
|
645
|
+
|
|
646
|
+
def _log_processed(self, result: Any, duration_ms: float) -> None:
|
|
647
|
+
"""service.processed, carrying how long the call took."""
|
|
648
|
+
if (
|
|
649
|
+
not self._logging
|
|
650
|
+
or LOG_LEVELS["debug"] < LOG_LEVELS[self._log_level]
|
|
651
|
+
or self._context is None
|
|
652
|
+
or not self._log_targets
|
|
653
|
+
):
|
|
654
|
+
return
|
|
655
|
+
entry = LogEntry(
|
|
656
|
+
run_id=self._context.run_id,
|
|
657
|
+
parent_run_id=self._context.parent_run_id,
|
|
658
|
+
ts=iso_timestamp(),
|
|
659
|
+
runtime_id=self.id,
|
|
660
|
+
service_uuid=self._current_service or "",
|
|
661
|
+
level="debug",
|
|
662
|
+
event="service.processed",
|
|
663
|
+
duration_ms=duration_ms,
|
|
664
|
+
caller=(
|
|
665
|
+
self._context.actor.sub
|
|
666
|
+
if self._context.actor.kind == "person"
|
|
667
|
+
else None
|
|
668
|
+
),
|
|
669
|
+
)
|
|
670
|
+
for target in list(self._log_targets):
|
|
671
|
+
target(entry)
|
|
672
|
+
|
|
673
|
+
def forward_log(self, entry: LogEntry) -> None:
|
|
674
|
+
"""Pass an entry a nested pipeline produced outward, unchanged."""
|
|
675
|
+
for target in list(self._log_targets):
|
|
676
|
+
target(entry)
|
|
677
|
+
|
|
678
|
+
def register_log_target(
|
|
679
|
+
self, target: Callable[[LogEntry], None]
|
|
680
|
+
) -> Callable[[], None]:
|
|
681
|
+
"""Where this runtime's entries go.
|
|
682
|
+
|
|
683
|
+
The server registers one to carry them to the board's coordinator; a
|
|
684
|
+
nested pipeline's host registers one to carry them out to the runtime
|
|
685
|
+
around it.
|
|
686
|
+
"""
|
|
687
|
+
self._log_targets.add(target)
|
|
688
|
+
return lambda: self._log_targets.discard(target)
|
|
689
|
+
|
|
690
|
+
def set_log_data(self, enabled: bool) -> None:
|
|
691
|
+
self._log_data = enabled
|
|
692
|
+
|
|
693
|
+
def set_capture(self, enabled: bool) -> None:
|
|
694
|
+
self._capture = enabled
|
|
695
|
+
|
|
696
|
+
def captures_calls(self) -> bool:
|
|
697
|
+
return self._capture
|
|
698
|
+
|
|
699
|
+
def set_logging(self, enabled: bool) -> None:
|
|
700
|
+
self._logging = enabled
|
|
701
|
+
|
|
702
|
+
def set_log_level(self, level: str) -> None:
|
|
703
|
+
if level in LOG_LEVELS:
|
|
704
|
+
self._log_level = level
|
|
705
|
+
|
|
706
|
+
def log_settings(self) -> dict[str, bool]:
|
|
707
|
+
return {
|
|
708
|
+
"logging": self._logging,
|
|
709
|
+
"log_data": self._log_data,
|
|
710
|
+
"log_level": self._log_level,
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
def secrets(self) -> SecretVault:
|
|
714
|
+
"""The runtime's secrets, for a service that has a credential to send.
|
|
715
|
+
|
|
716
|
+
A service holds the reference it was configured with and asks here for
|
|
717
|
+
the value, naming where it is about to send it. What comes back is used
|
|
718
|
+
and dropped: assigning it to state would put it back on the path a board
|
|
719
|
+
is saved from, which is the whole thing this arrangement exists to
|
|
720
|
+
prevent.
|
|
721
|
+
"""
|
|
722
|
+
if self._secrets_from is not None:
|
|
723
|
+
delegated = self._secrets_from()
|
|
724
|
+
if delegated is not None:
|
|
725
|
+
return delegated
|
|
726
|
+
return self._vault
|
|
727
|
+
|
|
728
|
+
def set_secrets(self, entries: dict[str, SecretEntry]) -> None:
|
|
729
|
+
"""Takes in values for references this runtime's services already hold.
|
|
730
|
+
|
|
731
|
+
Merges rather than replaces, because this is what a client editing one
|
|
732
|
+
entry sends, and what a client re-pushes after a restart. Replacing on a
|
|
733
|
+
partial push would strip credentials from services nobody touched.
|
|
734
|
+
"""
|
|
735
|
+
self._vault.merge(entries)
|
|
736
|
+
|
|
737
|
+
def delegate_secrets(
|
|
738
|
+
self, source: Callable[[], SecretVault | None]
|
|
739
|
+
) -> None:
|
|
740
|
+
"""Take secrets from somewhere else rather than from this runtime's own.
|
|
741
|
+
|
|
742
|
+
A nested pipeline is a runtime nobody provisions: no create payload
|
|
743
|
+
reaches it, so its own vault stays empty and a service inside it could
|
|
744
|
+
never resolve a reference. What it does have is the runtime around it,
|
|
745
|
+
which was provisioned — so it asks that one instead.
|
|
746
|
+
|
|
747
|
+
Asked for each time rather than copied, so that a value pushed after a
|
|
748
|
+
board is running reaches a nested service as immediately as a top-level
|
|
749
|
+
one, and so that nesting composes: each level delegates outward until it
|
|
750
|
+
reaches the runtime that was actually given something.
|
|
751
|
+
"""
|
|
752
|
+
self._secrets_from = source
|
|
753
|
+
|
|
754
|
+
def assets(self) -> AssetStore:
|
|
755
|
+
"""The runtime's assets, for a service that consumes content by reference.
|
|
756
|
+
|
|
757
|
+
A service holds ``hkp-asset://<id>`` and resolves it here at the moment
|
|
758
|
+
it uses it, so an asset edited while the board runs is what the next use
|
|
759
|
+
gets. The content is used and dropped, never put into state.
|
|
760
|
+
"""
|
|
761
|
+
if self._assets_from is not None:
|
|
762
|
+
delegated = self._assets_from()
|
|
763
|
+
if delegated is not None:
|
|
764
|
+
return delegated
|
|
765
|
+
return self._asset_store
|
|
766
|
+
|
|
767
|
+
def set_assets(self, entries: dict[str, Any]) -> None:
|
|
768
|
+
"""Takes in descriptors, or ``None`` for an asset that was deleted.
|
|
769
|
+
|
|
770
|
+
Merges, like secrets, because a client edits one asset at a time.
|
|
771
|
+
"""
|
|
772
|
+
self._asset_store.merge(entries)
|
|
773
|
+
|
|
774
|
+
def delegate_assets(self, source: Callable[[], AssetStore | None]) -> None:
|
|
775
|
+
"""Take assets from somewhere else rather than from this runtime's own.
|
|
776
|
+
|
|
777
|
+
The same arrangement as secrets: a nested pipeline is provisioned by
|
|
778
|
+
nobody, so it asks the runtime around it — on each lookup, so an asset
|
|
779
|
+
edited while the board runs is what a nested service resolves next.
|
|
780
|
+
"""
|
|
781
|
+
self._assets_from = source
|
|
782
|
+
|
|
783
|
+
def slots(self) -> SlotStore:
|
|
784
|
+
if self._slots_from is not None:
|
|
785
|
+
delegated = self._slots_from()
|
|
786
|
+
if delegated is not None:
|
|
787
|
+
return delegated
|
|
788
|
+
return self._own_slots
|
|
789
|
+
|
|
790
|
+
def delegate_mounts(
|
|
791
|
+
self,
|
|
792
|
+
source: Callable[[str, MountHandler, str | None], MountHandle | None],
|
|
793
|
+
) -> None:
|
|
794
|
+
"""Claim mounts somewhere else rather than on a server of this one's own.
|
|
795
|
+
|
|
796
|
+
The services here were built before this runtime could serve a mount,
|
|
797
|
+
so any that wanted one gave up on it; now that there is somewhere to
|
|
798
|
+
claim from, they are told to ask again. A service with nothing to claim
|
|
799
|
+
has nothing to do here and does not answer.
|
|
800
|
+
"""
|
|
801
|
+
self._mounts_from = source
|
|
802
|
+
for svc in list(self._services.values()):
|
|
803
|
+
remount = getattr(svc, "remount", None)
|
|
804
|
+
if remount:
|
|
805
|
+
remount()
|
|
806
|
+
|
|
807
|
+
def delegate_slots(self, source: Callable[[], SlotStore | None]) -> None:
|
|
808
|
+
"""Hold values somewhere other than this runtime's own cells.
|
|
809
|
+
|
|
810
|
+
What a nested pipeline shares with is decided by the service hosting
|
|
811
|
+
it: an endpoint that wants its two entry points to hold between them
|
|
812
|
+
gives both of them one store, and a service with nothing to share
|
|
813
|
+
passes its parent's along — so a slot named deep inside reaches the
|
|
814
|
+
nearest owner that declared one, and nesting never isolates what is
|
|
815
|
+
inside it by accident.
|
|
816
|
+
"""
|
|
817
|
+
self._slots_from = source
|
|
818
|
+
|
|
819
|
+
def spawn(self, coro: Coroutine[Any, Any, Any]) -> bool:
|
|
820
|
+
"""Run a coroutine on the server's loop, from wherever this is called.
|
|
821
|
+
|
|
822
|
+
A service that starts work during ``process`` — a request, a timer — is
|
|
823
|
+
running on a worker thread, where there is no loop to schedule on.
|
|
824
|
+
Answering False rather than raising lets a caller say so in its own
|
|
825
|
+
words; the coroutine is closed either way, so nothing is left
|
|
826
|
+
un-awaited.
|
|
827
|
+
"""
|
|
828
|
+
try:
|
|
829
|
+
running: asyncio.AbstractEventLoop | None = asyncio.get_running_loop()
|
|
830
|
+
except RuntimeError:
|
|
831
|
+
running = None
|
|
832
|
+
|
|
833
|
+
if running is not None:
|
|
834
|
+
running.create_task(coro)
|
|
835
|
+
return True
|
|
836
|
+
if _server_loop is not None:
|
|
837
|
+
asyncio.run_coroutine_threadsafe(coro, _server_loop)
|
|
838
|
+
return True
|
|
839
|
+
|
|
840
|
+
coro.close()
|
|
841
|
+
return False
|
|
842
|
+
|
|
843
|
+
def emit_result(self, output: Any) -> None:
|
|
844
|
+
context = self._context
|
|
845
|
+
for target in list(self._result_targets):
|
|
846
|
+
target(output, context)
|
|
847
|
+
|
|
848
|
+
# ── Internals ──────────────────────────────────────────────────────────────
|
|
849
|
+
|
|
850
|
+
@contextmanager
|
|
851
|
+
def _with_context(self, context: ProcessContext) -> Iterator[None]:
|
|
852
|
+
"""Runs the block with ``context`` current, restoring what was there.
|
|
853
|
+
|
|
854
|
+
Restoring rather than clearing is what makes this survive a service that
|
|
855
|
+
calls back into this runtime from inside its own ``process`` — the pull
|
|
856
|
+
that a cache miss or a router performs. That inner call is still part of
|
|
857
|
+
the outer run, and when it returns the outer loop has more services to
|
|
858
|
+
visit, so the context it was running under has to come back.
|
|
859
|
+
|
|
860
|
+
Safe as ambient state only because a pass is synchronous and the state
|
|
861
|
+
is its thread's own: it never awaits, so no second call on the thread
|
|
862
|
+
can interleave with this one, and a call on another thread has a
|
|
863
|
+
context of its own to save and restore. A pass that awaited would need
|
|
864
|
+
the context threaded through the call instead.
|
|
865
|
+
"""
|
|
866
|
+
previous = self._context
|
|
867
|
+
self._context = context
|
|
868
|
+
try:
|
|
869
|
+
yield
|
|
870
|
+
finally:
|
|
871
|
+
self._context = previous
|
|
872
|
+
|
|
873
|
+
def _process_from_index(
|
|
874
|
+
self,
|
|
875
|
+
start_index: int,
|
|
876
|
+
input: Any,
|
|
877
|
+
on_notification: NotificationCallback,
|
|
878
|
+
) -> Any:
|
|
879
|
+
result = input
|
|
880
|
+
|
|
881
|
+
for uuid in self._service_order[start_index:]:
|
|
882
|
+
if run_expired(self._context):
|
|
883
|
+
return None
|
|
884
|
+
svc = self._services.get(uuid)
|
|
885
|
+
if not svc:
|
|
886
|
+
continue
|
|
887
|
+
|
|
888
|
+
self._emit_notification(
|
|
889
|
+
RuntimeNotification(
|
|
890
|
+
instance_id=uuid,
|
|
891
|
+
payload={"__internal": {"state": "call-process", "data": result}},
|
|
892
|
+
),
|
|
893
|
+
on_notification,
|
|
894
|
+
)
|
|
895
|
+
|
|
896
|
+
notification_context = self._context
|
|
897
|
+
|
|
898
|
+
def _make_notify(u: str) -> NotificationCallback:
|
|
899
|
+
def _notify_cb(payload: Any, inst_id: str | None = None) -> None:
|
|
900
|
+
self._emit_notification(
|
|
901
|
+
RuntimeNotification(
|
|
902
|
+
instance_id=inst_id or u,
|
|
903
|
+
payload=payload,
|
|
904
|
+
context=notification_context,
|
|
905
|
+
),
|
|
906
|
+
on_notification,
|
|
907
|
+
)
|
|
908
|
+
return _notify_cb # type: ignore[return-value]
|
|
909
|
+
|
|
910
|
+
# Restored rather than cleared, for the same reason the context is:
|
|
911
|
+
# a service that pulls the ones after it re-enters this loop, and
|
|
912
|
+
# when it returns the entries that follow still belong to the
|
|
913
|
+
# service that pulled.
|
|
914
|
+
outer_service = self._current_service
|
|
915
|
+
self._current_service = uuid
|
|
916
|
+
started_at = time.monotonic()
|
|
917
|
+
try:
|
|
918
|
+
# The flow itself, at debug: which service the runtime called,
|
|
919
|
+
# and below, what it returned and how long it took.
|
|
920
|
+
#
|
|
921
|
+
# Deliberately without the value flowing through. The level says
|
|
922
|
+
# how much of the shape of a run to keep, and turning it up must
|
|
923
|
+
# not also start recording the data — what flows through is
|
|
924
|
+
# recorded only where a service was configured to record it.
|
|
925
|
+
self.log("debug", "service.process")
|
|
926
|
+
result = svc.process(result, _make_notify(uuid))
|
|
927
|
+
self._log_processed(result, (time.monotonic() - started_at) * 1000)
|
|
928
|
+
finally:
|
|
929
|
+
self._current_service = outer_service
|
|
930
|
+
|
|
931
|
+
# Early return: skip the remaining services, the carried result
|
|
932
|
+
# becomes the runtime's output.
|
|
933
|
+
early_return = isinstance(result, ControlFlowData)
|
|
934
|
+
if early_return:
|
|
935
|
+
result = result.result
|
|
936
|
+
|
|
937
|
+
self._emit_notification(
|
|
938
|
+
RuntimeNotification(
|
|
939
|
+
instance_id=uuid,
|
|
940
|
+
payload={"__internal": {"state": "call-process-finished", "data": result}},
|
|
941
|
+
),
|
|
942
|
+
on_notification,
|
|
943
|
+
)
|
|
944
|
+
|
|
945
|
+
if early_return or result is None:
|
|
946
|
+
if result is None:
|
|
947
|
+
# Where the run ended, named. Above debug because it is the
|
|
948
|
+
# outcome of the run rather than a step in it.
|
|
949
|
+
self._current_service = uuid
|
|
950
|
+
self.log("info", "pipeline.stopped")
|
|
951
|
+
self._current_service = None
|
|
952
|
+
break
|
|
953
|
+
|
|
954
|
+
return result
|
|
955
|
+
|
|
956
|
+
def _emit_notification(
|
|
957
|
+
self,
|
|
958
|
+
notification: RuntimeNotification,
|
|
959
|
+
on_notification: NotificationCallback,
|
|
960
|
+
) -> None:
|
|
961
|
+
if notification.context is None:
|
|
962
|
+
notification.context = self._context or new_run("board")
|
|
963
|
+
on_notification(notification)
|
|
964
|
+
for target in list(self._notification_targets):
|
|
965
|
+
target(notification)
|
|
966
|
+
|
|
967
|
+
|
|
968
|
+
# ── RuntimeApp ─────────────────────────────────────────────────────────────────
|
|
969
|
+
|
|
970
|
+
|
|
971
|
+
class HostedServiceFactory:
|
|
972
|
+
def __init__(
|
|
973
|
+
self,
|
|
974
|
+
descriptor: ServiceRegistryEntry,
|
|
975
|
+
create_fn: Callable[[ServiceConfiguration, ServiceCreator], HostedService],
|
|
976
|
+
) -> None:
|
|
977
|
+
self.descriptor = descriptor
|
|
978
|
+
self._create_fn = create_fn
|
|
979
|
+
|
|
980
|
+
def create(self, config: ServiceConfiguration, create_service: ServiceCreator) -> HostedService:
|
|
981
|
+
return self._create_fn(config, create_service)
|
|
982
|
+
|
|
983
|
+
|
|
984
|
+
class TenantRuntimes:
|
|
985
|
+
"""A single tenant's view of the runtime app.
|
|
986
|
+
|
|
987
|
+
Runtime ids are only unique within an owner — boards ship stable,
|
|
988
|
+
human-readable ids (``node``, ``chat-node``), so two users loading the same
|
|
989
|
+
board must each get their own runtime rather than sharing one. Every route
|
|
990
|
+
resolves runtimes through one of these views, so a handler cannot reach
|
|
991
|
+
another tenant's runtime even by id.
|
|
992
|
+
"""
|
|
993
|
+
|
|
994
|
+
def __init__(self, owner: str, app: "RuntimeApp") -> None:
|
|
995
|
+
self.owner = owner
|
|
996
|
+
self._app = app
|
|
997
|
+
|
|
998
|
+
def create_runtime(self, config: RuntimeConfiguration) -> HostedRuntime:
|
|
999
|
+
return self._app.create_runtime(self.owner, config)
|
|
1000
|
+
|
|
1001
|
+
def get_runtime(self, runtime_id: str) -> HostedRuntime | None:
|
|
1002
|
+
return self._app.get_runtime(self.owner, runtime_id)
|
|
1003
|
+
|
|
1004
|
+
def get_runtimes(self) -> list[HostedRuntime]:
|
|
1005
|
+
return self._app.get_runtimes(self.owner)
|
|
1006
|
+
|
|
1007
|
+
def remove_runtime(self, runtime_id: str) -> bool:
|
|
1008
|
+
return self._app.remove_runtime(self.owner, runtime_id)
|
|
1009
|
+
|
|
1010
|
+
def remove_all_runtimes(self) -> None:
|
|
1011
|
+
self._app.remove_all_runtimes(self.owner)
|
|
1012
|
+
|
|
1013
|
+
|
|
1014
|
+
def board_space(owner: str, board_name: str) -> str:
|
|
1015
|
+
"""The space the runtimes of one deployed board live in.
|
|
1016
|
+
|
|
1017
|
+
A runtime id is unique within a space, and there are two kinds. What a
|
|
1018
|
+
tenant's clients create over the api shares one space, named by the tenant
|
|
1019
|
+
alone. What a coordinator builds for a board gets a space per board, so two
|
|
1020
|
+
boards that both call a runtime ``python`` each keep their own, and neither
|
|
1021
|
+
is replaced by a client creating a runtime of that id.
|
|
1022
|
+
|
|
1023
|
+
NUL occurs in neither part, so a board's space is never a tenant's.
|
|
1024
|
+
"""
|
|
1025
|
+
return f"{owner}\x00{board_name}"
|
|
1026
|
+
|
|
1027
|
+
|
|
1028
|
+
class RuntimeApp:
|
|
1029
|
+
def __init__(
|
|
1030
|
+
self,
|
|
1031
|
+
registry: dict[str, HostedServiceFactory],
|
|
1032
|
+
# Supplied by the server, which owns the listening socket. Absent in
|
|
1033
|
+
# tests and anywhere runtimes need no public endpoints.
|
|
1034
|
+
mounts_for: Callable[[str, str, str], RuntimeMounts] | None = None,
|
|
1035
|
+
) -> None:
|
|
1036
|
+
self._registry = registry
|
|
1037
|
+
self._mounts_for = mounts_for
|
|
1038
|
+
# space -> runtime id -> runtime. A tenant's own space is its owner
|
|
1039
|
+
# key: the authenticated ``sub`` (or "anonymous" when auth is off,
|
|
1040
|
+
# collapsing to one bucket). A deployed board's is ``board_space``.
|
|
1041
|
+
self._runtimes: dict[str, dict[str, HostedRuntime]] = {}
|
|
1042
|
+
|
|
1043
|
+
def for_owner(self, owner: str) -> TenantRuntimes:
|
|
1044
|
+
"""A tenant-scoped view; the only way route handlers reach runtimes."""
|
|
1045
|
+
return TenantRuntimes(owner, self)
|
|
1046
|
+
|
|
1047
|
+
def create_runtime(
|
|
1048
|
+
self, owner: str, config: RuntimeConfiguration, space: str | None = None
|
|
1049
|
+
) -> HostedRuntime:
|
|
1050
|
+
"""Builds a runtime for ``owner``, replacing anything under its id in
|
|
1051
|
+
``space``: the tenant's own unless a board's is named."""
|
|
1052
|
+
space = owner if space is None else space
|
|
1053
|
+
owned = self._runtimes.setdefault(space, {})
|
|
1054
|
+
existing = owned.get(config.id)
|
|
1055
|
+
if existing:
|
|
1056
|
+
existing.destroy()
|
|
1057
|
+
runtime = HostedRuntime(
|
|
1058
|
+
config,
|
|
1059
|
+
self.create_service,
|
|
1060
|
+
self._mounts_for(owner, config.id, space) if self._mounts_for else None,
|
|
1061
|
+
)
|
|
1062
|
+
owned[runtime.id] = runtime
|
|
1063
|
+
return runtime
|
|
1064
|
+
|
|
1065
|
+
def get_runtime(self, space: str, runtime_id: str) -> HostedRuntime | None:
|
|
1066
|
+
return self._runtimes.get(space, {}).get(runtime_id)
|
|
1067
|
+
|
|
1068
|
+
def get_runtimes(self, space: str) -> list[HostedRuntime]:
|
|
1069
|
+
return list(self._runtimes.get(space, {}).values())
|
|
1070
|
+
|
|
1071
|
+
def get_board_runtimes(self, owner: str) -> list[HostedRuntime]:
|
|
1072
|
+
"""The runtimes a tenant's deployed boards have here, whichever board."""
|
|
1073
|
+
boards = board_space(owner, "")
|
|
1074
|
+
return [
|
|
1075
|
+
runtime
|
|
1076
|
+
for space, owned in self._runtimes.items()
|
|
1077
|
+
if space.startswith(boards)
|
|
1078
|
+
for runtime in owned.values()
|
|
1079
|
+
]
|
|
1080
|
+
|
|
1081
|
+
def count_runtimes(self, owner: str) -> int:
|
|
1082
|
+
"""How many runtimes a tenant has, in its own space and its boards'."""
|
|
1083
|
+
return len(self.get_runtimes(owner)) + len(self.get_board_runtimes(owner))
|
|
1084
|
+
|
|
1085
|
+
def remove_runtime(self, space: str, runtime_id: str) -> bool:
|
|
1086
|
+
owned = self._runtimes.get(space)
|
|
1087
|
+
if not owned:
|
|
1088
|
+
return False
|
|
1089
|
+
runtime = owned.pop(runtime_id, None)
|
|
1090
|
+
if not owned:
|
|
1091
|
+
self._runtimes.pop(space, None)
|
|
1092
|
+
if runtime:
|
|
1093
|
+
runtime.destroy()
|
|
1094
|
+
return True
|
|
1095
|
+
return False
|
|
1096
|
+
|
|
1097
|
+
def remove_all_runtimes(self, owner: str) -> None:
|
|
1098
|
+
"""Removes what is in a tenant's own space; a board's runtimes are its
|
|
1099
|
+
coordinator's to remove."""
|
|
1100
|
+
owned = self._runtimes.pop(owner, None)
|
|
1101
|
+
if not owned:
|
|
1102
|
+
return
|
|
1103
|
+
for runtime in owned.values():
|
|
1104
|
+
runtime.destroy()
|
|
1105
|
+
|
|
1106
|
+
def get_registry(self) -> list[dict[str, Any]]:
|
|
1107
|
+
result = []
|
|
1108
|
+
# A service may be registered under more than one id (an alias kept so
|
|
1109
|
+
# older boards still load). The registry advertises each one once, under
|
|
1110
|
+
# the id its descriptor calls canonical.
|
|
1111
|
+
seen: set[str] = set()
|
|
1112
|
+
for factory in self._registry.values():
|
|
1113
|
+
if factory.descriptor.service_id in seen:
|
|
1114
|
+
continue
|
|
1115
|
+
seen.add(factory.descriptor.service_id)
|
|
1116
|
+
entry: dict[str, Any] = {
|
|
1117
|
+
"serviceId": factory.descriptor.service_id,
|
|
1118
|
+
"serviceName": factory.descriptor.service_name,
|
|
1119
|
+
}
|
|
1120
|
+
if factory.descriptor.version is not None:
|
|
1121
|
+
entry["version"] = factory.descriptor.version
|
|
1122
|
+
if factory.descriptor.capabilities is not None:
|
|
1123
|
+
entry["capabilities"] = factory.descriptor.capabilities
|
|
1124
|
+
result.append(entry)
|
|
1125
|
+
return result
|
|
1126
|
+
|
|
1127
|
+
def create_service(self, config: ServiceConfiguration) -> HostedService:
|
|
1128
|
+
factory = self._registry.get(config.service_id)
|
|
1129
|
+
if not factory:
|
|
1130
|
+
raise ValueError(f"Unknown serviceId: {config.service_id}")
|
|
1131
|
+
return factory.create(config, self.create_service)
|