robopark 3.2.0 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (408) hide show
  1. package/README.md +66 -105
  2. package/bin/robopark.js +9 -2
  3. package/conversation/elevenlabs_agent.py +1617 -1617
  4. package/conversation/supervisor_store.py +189 -189
  5. package/dist/{kernel/config-schema.js → config-schema.js} +1 -0
  6. package/dist/config-schema.js.map +1 -0
  7. package/dist/robopark/add-robot.js +1 -0
  8. package/dist/robopark/add-robot.js.map +1 -0
  9. package/dist/robopark/agent-ctl.js +1 -0
  10. package/dist/robopark/agent-ctl.js.map +1 -0
  11. package/dist/robopark/auto-start.js +51 -73
  12. package/dist/robopark/auto-start.js.map +1 -0
  13. package/dist/robopark/conversation.js +1 -0
  14. package/dist/robopark/conversation.js.map +1 -0
  15. package/dist/robopark/discovery.js +1 -0
  16. package/dist/robopark/discovery.js.map +1 -0
  17. package/dist/robopark/doctor.js +1 -0
  18. package/dist/robopark/doctor.js.map +1 -0
  19. package/dist/robopark/enroll.js +1 -0
  20. package/dist/robopark/enroll.js.map +1 -0
  21. package/dist/robopark/llm-set.js +1 -0
  22. package/dist/robopark/llm-set.js.map +1 -0
  23. package/dist/robopark/motor-control.js +1 -0
  24. package/dist/robopark/motor-control.js.map +1 -0
  25. package/dist/robopark/preview-agent-launcher.js +1 -0
  26. package/dist/robopark/preview-agent-launcher.js.map +1 -0
  27. package/dist/robopark/probe.js +16 -15
  28. package/dist/robopark/probe.js.map +1 -0
  29. package/dist/robopark/profile.js +1 -0
  30. package/dist/robopark/profile.js.map +1 -0
  31. package/dist/robopark/python-env.js +1 -7
  32. package/dist/robopark/python-env.js.map +1 -0
  33. package/dist/robopark/robot-runtime.js +1 -0
  34. package/dist/robopark/robot-runtime.js.map +1 -0
  35. package/dist/robopark/scan.js +1 -0
  36. package/dist/robopark/scan.js.map +1 -0
  37. package/dist/robopark/screen-control.js +1 -0
  38. package/dist/robopark/screen-control.js.map +1 -0
  39. package/dist/robopark/secrets.js +1 -0
  40. package/dist/robopark/secrets.js.map +1 -0
  41. package/dist/robopark/serve.js +6 -8
  42. package/dist/robopark/serve.js.map +1 -0
  43. package/dist/robopark/server-add.js +1 -0
  44. package/dist/robopark/server-add.js.map +1 -0
  45. package/dist/robopark/setup-livekit.js +53 -52
  46. package/dist/robopark/setup-livekit.js.map +1 -0
  47. package/dist/robopark/setup.js +1 -0
  48. package/dist/robopark/setup.js.map +1 -0
  49. package/dist/robopark/stop-all.js +1 -0
  50. package/dist/robopark/stop-all.js.map +1 -0
  51. package/dist/robopark/verify.js +1 -0
  52. package/dist/robopark/verify.js.map +1 -0
  53. package/dist/robopark/vision-agent-launcher.js +1 -0
  54. package/dist/robopark/vision-agent-launcher.js.map +1 -0
  55. package/dist/robopark/vision-control.js +1 -0
  56. package/dist/robopark/vision-control.js.map +1 -0
  57. package/dist/robopark-cli.js +7 -125
  58. package/dist/robopark-cli.js.map +1 -0
  59. package/package.json +16 -56
  60. package/pi-client/_install_steps.sh +29 -29
  61. package/pi-client/client.py +76 -76
  62. package/pi-client/install.sh +40 -40
  63. package/pi-client/join_convo.sh +54 -54
  64. package/pi-client/livekit_bridge.py +24 -24
  65. package/pi-client/motor_bridge.py +10 -10
  66. package/scheduler/__pycache__/main.cpython-312.pyc +0 -0
  67. package/scheduler/main.py +196 -17
  68. package/scheduler/media_lock.py +57 -57
  69. package/scheduler/preview_agent.py +1122 -1122
  70. package/scheduler/production_config.json +73 -113
  71. package/scheduler/scripts/install-robot-supervisor-linux.sh +33 -33
  72. package/{ui/standalone/public → scheduler/static}/voice-join.html +101 -13
  73. package/screen/screen_runtime.py +75 -75
  74. package/vision/app_pi_clean.py +297 -297
  75. package/vision/audio_server_pi.py +24 -24
  76. package/vision/install.sh +34 -34
  77. package/vision/motor_server.py +275 -275
  78. package/vision/requirements_camera.txt +6 -6
  79. package/vision/requirements_motor.txt +4 -4
  80. package/vision/requirements_pi_unified.txt +2 -2
  81. package/vision/requirements_vision_agent.txt +19 -19
  82. package/vision/run.sh +244 -244
  83. package/vision/services/services.sh +12 -12
  84. package/dist/kernel/types.js +0 -7
  85. package/dist/robopark/access.js +0 -99
  86. package/dist/robopark/control-center-html.js +0 -9191
  87. package/dist/robopark/control-server.js +0 -148
  88. package/dist/robopark/deployment-commands.js +0 -47
  89. package/dist/robopark/frontend-service.js +0 -80
  90. package/dist/robopark/standalone.js +0 -584
  91. package/dist/robopark/voice-join-html.js +0 -57
  92. package/scripts/windows-shim-fix.cjs +0 -58
  93. package/static/robopark/barracuda.png +0 -0
  94. package/static/robopark/bmw.png +0 -0
  95. package/static/robopark/jaguar.png +0 -0
  96. package/static/robopark/magnus.jpg +0 -0
  97. package/static/robopark/panda.png +0 -0
  98. package/static/robopark/tesla.png +0 -0
  99. package/static/robopark/titan.jpg +0 -0
  100. package/static/robopark/volt.png +0 -0
  101. package/static/vendor/elevenlabs-client.LICENSE +0 -21
  102. package/static/vendor/elevenlabs-client.iife.js +0 -24200
  103. package/ui/ROBOVOICE_BUILD.json +0 -9
  104. package/ui/standalone/.next/BUILD_ID +0 -1
  105. package/ui/standalone/.next/app-build-manifest.json +0 -391
  106. package/ui/standalone/.next/app-path-routes-manifest.json +0 -55
  107. package/ui/standalone/.next/build-manifest.json +0 -33
  108. package/ui/standalone/.next/package.json +0 -1
  109. package/ui/standalone/.next/prerender-manifest.json +0 -41
  110. package/ui/standalone/.next/react-loadable-manifest.json +0 -14
  111. package/ui/standalone/.next/required-server-files.json +0 -362
  112. package/ui/standalone/.next/routes-manifest.json +0 -246
  113. package/ui/standalone/.next/server/app/(app)/opengraph-image-xg4ifa/route.js +0 -1
  114. package/ui/standalone/.next/server/app/(app)/opengraph-image-xg4ifa/route.js.nft.json +0 -1
  115. package/ui/standalone/.next/server/app/(app)/opengraph-image-xg4ifa/route_client-reference-manifest.js +0 -1
  116. package/ui/standalone/.next/server/app/(app)/page.js +0 -2
  117. package/ui/standalone/.next/server/app/(app)/page.js.nft.json +0 -1
  118. package/ui/standalone/.next/server/app/(app)/page_client-reference-manifest.js +0 -1
  119. package/ui/standalone/.next/server/app/(app)/voice/page.js +0 -12
  120. package/ui/standalone/.next/server/app/(app)/voice/page.js.nft.json +0 -1
  121. package/ui/standalone/.next/server/app/(app)/voice/page_client-reference-manifest.js +0 -1
  122. package/ui/standalone/.next/server/app/_not-found/page.js +0 -12
  123. package/ui/standalone/.next/server/app/_not-found/page.js.nft.json +0 -1
  124. package/ui/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +0 -1
  125. package/ui/standalone/.next/server/app/api/connection-details/route.js +0 -1
  126. package/ui/standalone/.next/server/app/api/connection-details/route.js.nft.json +0 -1
  127. package/ui/standalone/.next/server/app/api/connection-details/route_client-reference-manifest.js +0 -1
  128. package/ui/standalone/.next/server/app/api/device-character/route.js +0 -1
  129. package/ui/standalone/.next/server/app/api/device-character/route.js.nft.json +0 -1
  130. package/ui/standalone/.next/server/app/api/device-character/route_client-reference-manifest.js +0 -1
  131. package/ui/standalone/.next/server/app/api/download-piper-model/route.js +0 -1
  132. package/ui/standalone/.next/server/app/api/download-piper-model/route.js.nft.json +0 -1
  133. package/ui/standalone/.next/server/app/api/download-piper-model/route_client-reference-manifest.js +0 -1
  134. package/ui/standalone/.next/server/app/api/model-status/route.js +0 -1
  135. package/ui/standalone/.next/server/app/api/model-status/route.js.nft.json +0 -1
  136. package/ui/standalone/.next/server/app/api/model-status/route_client-reference-manifest.js +0 -1
  137. package/ui/standalone/.next/server/app/api/models/route.js +0 -1
  138. package/ui/standalone/.next/server/app/api/models/route.js.nft.json +0 -1
  139. package/ui/standalone/.next/server/app/api/models/route_client-reference-manifest.js +0 -1
  140. package/ui/standalone/.next/server/app/api/prewarm/route.js +0 -1
  141. package/ui/standalone/.next/server/app/api/prewarm/route.js.nft.json +0 -1
  142. package/ui/standalone/.next/server/app/api/prewarm/route_client-reference-manifest.js +0 -1
  143. package/ui/standalone/.next/server/app/api/prompt/route.js +0 -1
  144. package/ui/standalone/.next/server/app/api/prompt/route.js.nft.json +0 -1
  145. package/ui/standalone/.next/server/app/api/prompt/route_client-reference-manifest.js +0 -1
  146. package/ui/standalone/.next/server/app/api/reload-tools/route.js +0 -1
  147. package/ui/standalone/.next/server/app/api/reload-tools/route.js.nft.json +0 -1
  148. package/ui/standalone/.next/server/app/api/reload-tools/route_client-reference-manifest.js +0 -1
  149. package/ui/standalone/.next/server/app/api/robopark/camera-token/route.js +0 -1
  150. package/ui/standalone/.next/server/app/api/robopark/camera-token/route.js.nft.json +0 -1
  151. package/ui/standalone/.next/server/app/api/robopark/camera-token/route_client-reference-manifest.js +0 -1
  152. package/ui/standalone/.next/server/app/api/robopark/character-ai/route.js +0 -1
  153. package/ui/standalone/.next/server/app/api/robopark/character-ai/route.js.nft.json +0 -1
  154. package/ui/standalone/.next/server/app/api/robopark/character-ai/route_client-reference-manifest.js +0 -1
  155. package/ui/standalone/.next/server/app/api/robopark/device-access/route.js +0 -1
  156. package/ui/standalone/.next/server/app/api/robopark/device-access/route.js.nft.json +0 -1
  157. package/ui/standalone/.next/server/app/api/robopark/device-access/route_client-reference-manifest.js +0 -1
  158. package/ui/standalone/.next/server/app/api/robopark/devices/[deviceId]/control/route.js +0 -1
  159. package/ui/standalone/.next/server/app/api/robopark/devices/[deviceId]/control/route.js.nft.json +0 -1
  160. package/ui/standalone/.next/server/app/api/robopark/devices/[deviceId]/control/route_client-reference-manifest.js +0 -1
  161. package/ui/standalone/.next/server/app/api/robopark/live-rooms/route.js +0 -1
  162. package/ui/standalone/.next/server/app/api/robopark/live-rooms/route.js.nft.json +0 -1
  163. package/ui/standalone/.next/server/app/api/robopark/live-rooms/route_client-reference-manifest.js +0 -1
  164. package/ui/standalone/.next/server/app/api/robopark/monitor-token/route.js +0 -1
  165. package/ui/standalone/.next/server/app/api/robopark/monitor-token/route.js.nft.json +0 -1
  166. package/ui/standalone/.next/server/app/api/robopark/monitor-token/route_client-reference-manifest.js +0 -1
  167. package/ui/standalone/.next/server/app/api/robopark/robots/[robotId]/operations/route.js +0 -1
  168. package/ui/standalone/.next/server/app/api/robopark/robots/[robotId]/operations/route.js.nft.json +0 -1
  169. package/ui/standalone/.next/server/app/api/robopark/robots/[robotId]/operations/route_client-reference-manifest.js +0 -1
  170. package/ui/standalone/.next/server/app/api/robopark/robots/[robotId]/voice-engine/route.js +0 -1
  171. package/ui/standalone/.next/server/app/api/robopark/robots/[robotId]/voice-engine/route.js.nft.json +0 -1
  172. package/ui/standalone/.next/server/app/api/robopark/robots/[robotId]/voice-engine/route_client-reference-manifest.js +0 -1
  173. package/ui/standalone/.next/server/app/api/robopark/robots/route.js +0 -1
  174. package/ui/standalone/.next/server/app/api/robopark/robots/route.js.nft.json +0 -1
  175. package/ui/standalone/.next/server/app/api/robopark/robots/route_client-reference-manifest.js +0 -1
  176. package/ui/standalone/.next/server/app/api/robopark/servers/[serverId]/metrics/route.js +0 -1
  177. package/ui/standalone/.next/server/app/api/robopark/servers/[serverId]/metrics/route.js.nft.json +0 -1
  178. package/ui/standalone/.next/server/app/api/robopark/servers/[serverId]/metrics/route_client-reference-manifest.js +0 -1
  179. package/ui/standalone/.next/server/app/api/robopark/servers/[serverId]/route.js +0 -1
  180. package/ui/standalone/.next/server/app/api/robopark/servers/[serverId]/route.js.nft.json +0 -1
  181. package/ui/standalone/.next/server/app/api/robopark/servers/[serverId]/route_client-reference-manifest.js +0 -1
  182. package/ui/standalone/.next/server/app/api/robopark/servers/route.js +0 -1
  183. package/ui/standalone/.next/server/app/api/robopark/servers/route.js.nft.json +0 -1
  184. package/ui/standalone/.next/server/app/api/robopark/servers/route_client-reference-manifest.js +0 -1
  185. package/ui/standalone/.next/server/app/api/robopark/sessions/[sessionId]/end/route.js +0 -1
  186. package/ui/standalone/.next/server/app/api/robopark/sessions/[sessionId]/end/route.js.nft.json +0 -1
  187. package/ui/standalone/.next/server/app/api/robopark/sessions/[sessionId]/end/route_client-reference-manifest.js +0 -1
  188. package/ui/standalone/.next/server/app/api/robopark/sessions/route.js +0 -1
  189. package/ui/standalone/.next/server/app/api/robopark/sessions/route.js.nft.json +0 -1
  190. package/ui/standalone/.next/server/app/api/robopark/sessions/route_client-reference-manifest.js +0 -1
  191. package/ui/standalone/.next/server/app/api/robopark/transcripts/[sessionId]/route.js +0 -1
  192. package/ui/standalone/.next/server/app/api/robopark/transcripts/[sessionId]/route.js.nft.json +0 -1
  193. package/ui/standalone/.next/server/app/api/robopark/transcripts/[sessionId]/route_client-reference-manifest.js +0 -1
  194. package/ui/standalone/.next/server/app/api/robopark/transcripts/route.js +0 -1
  195. package/ui/standalone/.next/server/app/api/robopark/transcripts/route.js.nft.json +0 -1
  196. package/ui/standalone/.next/server/app/api/robopark/transcripts/route_client-reference-manifest.js +0 -1
  197. package/ui/standalone/.next/server/app/api/robovoice/robots/[robotId]/media-health/route.js +0 -1
  198. package/ui/standalone/.next/server/app/api/robovoice/robots/[robotId]/media-health/route.js.nft.json +0 -1
  199. package/ui/standalone/.next/server/app/api/robovoice/robots/[robotId]/media-health/route_client-reference-manifest.js +0 -1
  200. package/ui/standalone/.next/server/app/api/settings/route.js +0 -1
  201. package/ui/standalone/.next/server/app/api/settings/route.js.nft.json +0 -1
  202. package/ui/standalone/.next/server/app/api/settings/route_client-reference-manifest.js +0 -1
  203. package/ui/standalone/.next/server/app/api/setup/complete/route.js +0 -1
  204. package/ui/standalone/.next/server/app/api/setup/complete/route.js.nft.json +0 -1
  205. package/ui/standalone/.next/server/app/api/setup/complete/route_client-reference-manifest.js +0 -1
  206. package/ui/standalone/.next/server/app/api/setup/status/route.js +0 -1
  207. package/ui/standalone/.next/server/app/api/setup/status/route.js.nft.json +0 -1
  208. package/ui/standalone/.next/server/app/api/setup/status/route_client-reference-manifest.js +0 -1
  209. package/ui/standalone/.next/server/app/api/setup/test-groq/route.js +0 -1
  210. package/ui/standalone/.next/server/app/api/setup/test-groq/route.js.nft.json +0 -1
  211. package/ui/standalone/.next/server/app/api/setup/test-groq/route_client-reference-manifest.js +0 -1
  212. package/ui/standalone/.next/server/app/api/setup/test-hass/route.js +0 -1
  213. package/ui/standalone/.next/server/app/api/setup/test-hass/route.js.nft.json +0 -1
  214. package/ui/standalone/.next/server/app/api/setup/test-hass/route_client-reference-manifest.js +0 -1
  215. package/ui/standalone/.next/server/app/api/setup/test-n8n/route.js +0 -1
  216. package/ui/standalone/.next/server/app/api/setup/test-n8n/route.js.nft.json +0 -1
  217. package/ui/standalone/.next/server/app/api/setup/test-n8n/route_client-reference-manifest.js +0 -1
  218. package/ui/standalone/.next/server/app/api/setup/test-ollama/route.js +0 -1
  219. package/ui/standalone/.next/server/app/api/setup/test-ollama/route.js.nft.json +0 -1
  220. package/ui/standalone/.next/server/app/api/setup/test-ollama/route_client-reference-manifest.js +0 -1
  221. package/ui/standalone/.next/server/app/api/test-tts/route.js +0 -1
  222. package/ui/standalone/.next/server/app/api/test-tts/route.js.nft.json +0 -1
  223. package/ui/standalone/.next/server/app/api/test-tts/route_client-reference-manifest.js +0 -1
  224. package/ui/standalone/.next/server/app/api/turn-credentials/route.js +0 -1
  225. package/ui/standalone/.next/server/app/api/turn-credentials/route.js.nft.json +0 -1
  226. package/ui/standalone/.next/server/app/api/turn-credentials/route_client-reference-manifest.js +0 -1
  227. package/ui/standalone/.next/server/app/api/voices/route.js +0 -1
  228. package/ui/standalone/.next/server/app/api/voices/route.js.nft.json +0 -1
  229. package/ui/standalone/.next/server/app/api/voices/route_client-reference-manifest.js +0 -1
  230. package/ui/standalone/.next/server/app/api/wake/route.js +0 -1
  231. package/ui/standalone/.next/server/app/api/wake/route.js.nft.json +0 -1
  232. package/ui/standalone/.next/server/app/api/wake/route_client-reference-manifest.js +0 -1
  233. package/ui/standalone/.next/server/app/api/wake-word/models/route.js +0 -1
  234. package/ui/standalone/.next/server/app/api/wake-word/models/route.js.nft.json +0 -1
  235. package/ui/standalone/.next/server/app/api/wake-word/models/route_client-reference-manifest.js +0 -1
  236. package/ui/standalone/.next/server/app/api/wake-word/status/route.js +0 -1
  237. package/ui/standalone/.next/server/app/api/wake-word/status/route.js.nft.json +0 -1
  238. package/ui/standalone/.next/server/app/api/wake-word/status/route_client-reference-manifest.js +0 -1
  239. package/ui/standalone/.next/server/app/api/wake-word/upload/route.js +0 -1
  240. package/ui/standalone/.next/server/app/api/wake-word/upload/route.js.nft.json +0 -1
  241. package/ui/standalone/.next/server/app/api/wake-word/upload/route_client-reference-manifest.js +0 -1
  242. package/ui/standalone/.next/server/app/favicon.ico/route.js +0 -1
  243. package/ui/standalone/.next/server/app/favicon.ico/route.js.nft.json +0 -1
  244. package/ui/standalone/.next/server/app/favicon.ico.body +0 -0
  245. package/ui/standalone/.next/server/app/favicon.ico.meta +0 -1
  246. package/ui/standalone/.next/server/app/fed/[...path]/route.js +0 -4
  247. package/ui/standalone/.next/server/app/fed/[...path]/route.js.nft.json +0 -1
  248. package/ui/standalone/.next/server/app/fed/[...path]/route_client-reference-manifest.js +0 -1
  249. package/ui/standalone/.next/server/app/fed/media/robots/[robotId]/video_feed/route.js +0 -1
  250. package/ui/standalone/.next/server/app/fed/media/robots/[robotId]/video_feed/route.js.nft.json +0 -1
  251. package/ui/standalone/.next/server/app/fed/media/robots/[robotId]/video_feed/route_client-reference-manifest.js +0 -1
  252. package/ui/standalone/.next/server/app/robopark/[...path]/route.js +0 -1
  253. package/ui/standalone/.next/server/app/robopark/[...path]/route.js.nft.json +0 -1
  254. package/ui/standalone/.next/server/app/robopark/[...path]/route_client-reference-manifest.js +0 -1
  255. package/ui/standalone/.next/server/app/robopark/api/[...path]/route.js +0 -1
  256. package/ui/standalone/.next/server/app/robopark/api/[...path]/route.js.nft.json +0 -1
  257. package/ui/standalone/.next/server/app/robopark/api/[...path]/route_client-reference-manifest.js +0 -1
  258. package/ui/standalone/.next/server/app/robopark/api/robots/[robotId]/stream/route.js +0 -1
  259. package/ui/standalone/.next/server/app/robopark/api/robots/[robotId]/stream/route.js.nft.json +0 -1
  260. package/ui/standalone/.next/server/app/robopark/api/robots/[robotId]/stream/route_client-reference-manifest.js +0 -1
  261. package/ui/standalone/.next/server/app/robopark/page.js +0 -2
  262. package/ui/standalone/.next/server/app/robopark/page.js.nft.json +0 -1
  263. package/ui/standalone/.next/server/app/robopark/page_client-reference-manifest.js +0 -1
  264. package/ui/standalone/.next/server/app/tailnet/route.js +0 -1
  265. package/ui/standalone/.next/server/app/tailnet/route.js.nft.json +0 -1
  266. package/ui/standalone/.next/server/app/tailnet/route_client-reference-manifest.js +0 -1
  267. package/ui/standalone/.next/server/app/vendor/livekit-client.umd.js/route.js +0 -1
  268. package/ui/standalone/.next/server/app/vendor/livekit-client.umd.js/route.js.nft.json +0 -1
  269. package/ui/standalone/.next/server/app/vendor/livekit-client.umd.js/route_client-reference-manifest.js +0 -1
  270. package/ui/standalone/.next/server/app/voice-tester/page.js +0 -2
  271. package/ui/standalone/.next/server/app/voice-tester/page.js.nft.json +0 -1
  272. package/ui/standalone/.next/server/app/voice-tester/page_client-reference-manifest.js +0 -1
  273. package/ui/standalone/.next/server/app-paths-manifest.json +0 -55
  274. package/ui/standalone/.next/server/chunks/1428.js +0 -1
  275. package/ui/standalone/.next/server/chunks/1692.js +0 -1
  276. package/ui/standalone/.next/server/chunks/37.js +0 -1
  277. package/ui/standalone/.next/server/chunks/4876.js +0 -1
  278. package/ui/standalone/.next/server/chunks/5232.js +0 -9
  279. package/ui/standalone/.next/server/chunks/5455.js +0 -11
  280. package/ui/standalone/.next/server/chunks/5611.js +0 -6
  281. package/ui/standalone/.next/server/chunks/5873.js +0 -22
  282. package/ui/standalone/.next/server/chunks/6018.js +0 -1
  283. package/ui/standalone/.next/server/chunks/8755.js +0 -1
  284. package/ui/standalone/.next/server/chunks/9077.js +0 -11
  285. package/ui/standalone/.next/server/functions-config-manifest.json +0 -9
  286. package/ui/standalone/.next/server/middleware-build-manifest.js +0 -1
  287. package/ui/standalone/.next/server/middleware-manifest.json +0 -6
  288. package/ui/standalone/.next/server/middleware-react-loadable-manifest.js +0 -1
  289. package/ui/standalone/.next/server/next-font-manifest.js +0 -1
  290. package/ui/standalone/.next/server/next-font-manifest.json +0 -1
  291. package/ui/standalone/.next/server/pages/500.html +0 -1
  292. package/ui/standalone/.next/server/pages/_app.js +0 -1
  293. package/ui/standalone/.next/server/pages/_app.js.nft.json +0 -1
  294. package/ui/standalone/.next/server/pages/_document.js +0 -1
  295. package/ui/standalone/.next/server/pages/_document.js.nft.json +0 -1
  296. package/ui/standalone/.next/server/pages/_error.js +0 -19
  297. package/ui/standalone/.next/server/pages/_error.js.nft.json +0 -1
  298. package/ui/standalone/.next/server/pages-manifest.json +0 -5
  299. package/ui/standalone/.next/server/server-reference-manifest.js +0 -1
  300. package/ui/standalone/.next/server/server-reference-manifest.json +0 -1
  301. package/ui/standalone/.next/server/webpack-runtime.js +0 -1
  302. package/ui/standalone/.next/static/T3eGaQUuZNb6rhEStcouc/_buildManifest.js +0 -1
  303. package/ui/standalone/.next/static/T3eGaQUuZNb6rhEStcouc/_ssgManifest.js +0 -1
  304. package/ui/standalone/.next/static/chunks/1255-404bb8de49c90395.js +0 -1
  305. package/ui/standalone/.next/static/chunks/144d3bae-bd5d150c0cab972e.js +0 -1
  306. package/ui/standalone/.next/static/chunks/1646.9123ee47220ed70b.js +0 -1
  307. package/ui/standalone/.next/static/chunks/2333-43c39acb531f7d4e.js +0 -3
  308. package/ui/standalone/.next/static/chunks/4bd1b696-f785427dddbba9fb.js +0 -1
  309. package/ui/standalone/.next/static/chunks/5139.c5e46d26064a85db.js +0 -1
  310. package/ui/standalone/.next/static/chunks/76e8f37e-2ae0e3fe7b44d874.js +0 -1
  311. package/ui/standalone/.next/static/chunks/9880-53262785284464cc.js +0 -1
  312. package/ui/standalone/.next/static/chunks/adfb71a7-625c1b5ebd039b6a.js +0 -1
  313. package/ui/standalone/.next/static/chunks/app/(app)/layout-5f312b128a0caf75.js +0 -1
  314. package/ui/standalone/.next/static/chunks/app/(app)/opengraph-image-xg4ifa/route-5f312b128a0caf75.js +0 -1
  315. package/ui/standalone/.next/static/chunks/app/(app)/page-5f312b128a0caf75.js +0 -1
  316. package/ui/standalone/.next/static/chunks/app/(app)/voice/page-72d72d9a70d9c1d4.js +0 -1
  317. package/ui/standalone/.next/static/chunks/app/_not-found/page-cfd7daa82d71540c.js +0 -1
  318. package/ui/standalone/.next/static/chunks/app/api/connection-details/route-5f312b128a0caf75.js +0 -1
  319. package/ui/standalone/.next/static/chunks/app/api/device-character/route-5f312b128a0caf75.js +0 -1
  320. package/ui/standalone/.next/static/chunks/app/api/download-piper-model/route-5f312b128a0caf75.js +0 -1
  321. package/ui/standalone/.next/static/chunks/app/api/model-status/route-5f312b128a0caf75.js +0 -1
  322. package/ui/standalone/.next/static/chunks/app/api/models/route-5f312b128a0caf75.js +0 -1
  323. package/ui/standalone/.next/static/chunks/app/api/prewarm/route-5f312b128a0caf75.js +0 -1
  324. package/ui/standalone/.next/static/chunks/app/api/prompt/route-5f312b128a0caf75.js +0 -1
  325. package/ui/standalone/.next/static/chunks/app/api/reload-tools/route-5f312b128a0caf75.js +0 -1
  326. package/ui/standalone/.next/static/chunks/app/api/robopark/camera-token/route-5f312b128a0caf75.js +0 -1
  327. package/ui/standalone/.next/static/chunks/app/api/robopark/character-ai/route-5f312b128a0caf75.js +0 -1
  328. package/ui/standalone/.next/static/chunks/app/api/robopark/device-access/route-5f312b128a0caf75.js +0 -1
  329. package/ui/standalone/.next/static/chunks/app/api/robopark/devices/[deviceId]/control/route-5f312b128a0caf75.js +0 -1
  330. package/ui/standalone/.next/static/chunks/app/api/robopark/live-rooms/route-5f312b128a0caf75.js +0 -1
  331. package/ui/standalone/.next/static/chunks/app/api/robopark/monitor-token/route-5f312b128a0caf75.js +0 -1
  332. package/ui/standalone/.next/static/chunks/app/api/robopark/robots/[robotId]/operations/route-5f312b128a0caf75.js +0 -1
  333. package/ui/standalone/.next/static/chunks/app/api/robopark/robots/[robotId]/voice-engine/route-5f312b128a0caf75.js +0 -1
  334. package/ui/standalone/.next/static/chunks/app/api/robopark/robots/route-5f312b128a0caf75.js +0 -1
  335. package/ui/standalone/.next/static/chunks/app/api/robopark/servers/[serverId]/metrics/route-5f312b128a0caf75.js +0 -1
  336. package/ui/standalone/.next/static/chunks/app/api/robopark/servers/[serverId]/route-5f312b128a0caf75.js +0 -1
  337. package/ui/standalone/.next/static/chunks/app/api/robopark/servers/route-5f312b128a0caf75.js +0 -1
  338. package/ui/standalone/.next/static/chunks/app/api/robopark/sessions/[sessionId]/end/route-5f312b128a0caf75.js +0 -1
  339. package/ui/standalone/.next/static/chunks/app/api/robopark/sessions/route-5f312b128a0caf75.js +0 -1
  340. package/ui/standalone/.next/static/chunks/app/api/robopark/transcripts/[sessionId]/route-5f312b128a0caf75.js +0 -1
  341. package/ui/standalone/.next/static/chunks/app/api/robopark/transcripts/route-5f312b128a0caf75.js +0 -1
  342. package/ui/standalone/.next/static/chunks/app/api/robovoice/robots/[robotId]/media-health/route-5f312b128a0caf75.js +0 -1
  343. package/ui/standalone/.next/static/chunks/app/api/settings/route-5f312b128a0caf75.js +0 -1
  344. package/ui/standalone/.next/static/chunks/app/api/setup/complete/route-5f312b128a0caf75.js +0 -1
  345. package/ui/standalone/.next/static/chunks/app/api/setup/status/route-5f312b128a0caf75.js +0 -1
  346. package/ui/standalone/.next/static/chunks/app/api/setup/test-groq/route-5f312b128a0caf75.js +0 -1
  347. package/ui/standalone/.next/static/chunks/app/api/setup/test-hass/route-5f312b128a0caf75.js +0 -1
  348. package/ui/standalone/.next/static/chunks/app/api/setup/test-n8n/route-5f312b128a0caf75.js +0 -1
  349. package/ui/standalone/.next/static/chunks/app/api/setup/test-ollama/route-5f312b128a0caf75.js +0 -1
  350. package/ui/standalone/.next/static/chunks/app/api/test-tts/route-5f312b128a0caf75.js +0 -1
  351. package/ui/standalone/.next/static/chunks/app/api/turn-credentials/route-5f312b128a0caf75.js +0 -1
  352. package/ui/standalone/.next/static/chunks/app/api/voices/route-5f312b128a0caf75.js +0 -1
  353. package/ui/standalone/.next/static/chunks/app/api/wake/route-5f312b128a0caf75.js +0 -1
  354. package/ui/standalone/.next/static/chunks/app/api/wake-word/models/route-5f312b128a0caf75.js +0 -1
  355. package/ui/standalone/.next/static/chunks/app/api/wake-word/status/route-5f312b128a0caf75.js +0 -1
  356. package/ui/standalone/.next/static/chunks/app/api/wake-word/upload/route-5f312b128a0caf75.js +0 -1
  357. package/ui/standalone/.next/static/chunks/app/fed/[...path]/route-5f312b128a0caf75.js +0 -1
  358. package/ui/standalone/.next/static/chunks/app/fed/media/robots/[robotId]/video_feed/route-5f312b128a0caf75.js +0 -1
  359. package/ui/standalone/.next/static/chunks/app/layout-fcce963da1d8220f.js +0 -1
  360. package/ui/standalone/.next/static/chunks/app/robopark/[...path]/route-5f312b128a0caf75.js +0 -1
  361. package/ui/standalone/.next/static/chunks/app/robopark/api/[...path]/route-5f312b128a0caf75.js +0 -1
  362. package/ui/standalone/.next/static/chunks/app/robopark/api/robots/[robotId]/stream/route-5f312b128a0caf75.js +0 -1
  363. package/ui/standalone/.next/static/chunks/app/robopark/page-5f312b128a0caf75.js +0 -1
  364. package/ui/standalone/.next/static/chunks/app/tailnet/route-5f312b128a0caf75.js +0 -1
  365. package/ui/standalone/.next/static/chunks/app/vendor/livekit-client.umd.js/route-5f312b128a0caf75.js +0 -1
  366. package/ui/standalone/.next/static/chunks/app/voice-tester/page-ae699e048e0eb834.js +0 -1
  367. package/ui/standalone/.next/static/chunks/framework-e60c938074ff7136.js +0 -1
  368. package/ui/standalone/.next/static/chunks/main-9e24e8708ff075fc.js +0 -1
  369. package/ui/standalone/.next/static/chunks/main-app-a1f59168303c7f84.js +0 -1
  370. package/ui/standalone/.next/static/chunks/pages/_app-131c90850aef965b.js +0 -1
  371. package/ui/standalone/.next/static/chunks/pages/_error-e4ba546eb376bdf4.js +0 -1
  372. package/ui/standalone/.next/static/chunks/polyfills-42372ed130431b0a.js +0 -1
  373. package/ui/standalone/.next/static/chunks/webpack-5252e5254da079b5.js +0 -1
  374. package/ui/standalone/.next/static/css/3f478a573f9be82e.css +0 -1
  375. package/ui/standalone/.next/static/css/51560c4af1d5a21f.css +0 -1
  376. package/ui/standalone/.next/static/media/27fe93087559d480-s.p.otf +0 -0
  377. package/ui/standalone/.next/static/media/4d3869dd8a56345a-s.p.otf +0 -0
  378. package/ui/standalone/.next/static/media/63f3bb9ce29e22d5-s.p.otf +0 -0
  379. package/ui/standalone/.next/static/media/79e1c5d0f2fe9f68-s.p.otf +0 -0
  380. package/ui/standalone/.next/static/media/8bf7b2ceda89477b-s.p.woff2 +0 -0
  381. package/ui/standalone/.next/static/media/98baa4a7809fbf72-s.woff2 +0 -0
  382. package/ui/standalone/.next/static/media/f7650dfaa438c37c-s.woff2 +0 -0
  383. package/ui/standalone/package.json +0 -57
  384. package/ui/standalone/public/caal-logo.svg +0 -3
  385. package/ui/standalone/public/commit-mono-400-regular.woff +0 -0
  386. package/ui/standalone/public/cwl-logo-round.png +0 -0
  387. package/ui/standalone/public/everett-light.woff +0 -0
  388. package/ui/standalone/public/hey_cal.ppn +0 -0
  389. package/ui/standalone/public/install-robot-kiosk.sh +0 -242
  390. package/ui/standalone/public/lk-logo-dark.svg +0 -18
  391. package/ui/standalone/public/lk-logo.svg +0 -18
  392. package/ui/standalone/public/lk-wordmark.svg +0 -12
  393. package/ui/standalone/public/opengraph-image-bg.png +0 -0
  394. package/ui/standalone/public/porcupine_params.pv +0 -0
  395. package/ui/standalone/public/robopark-control.html +0 -9200
  396. package/ui/standalone/public/static/robopark/barracuda.png +0 -0
  397. package/ui/standalone/public/static/robopark/bmw.png +0 -0
  398. package/ui/standalone/public/static/robopark/jaguar.png +0 -0
  399. package/ui/standalone/public/static/robopark/magnus.jpg +0 -0
  400. package/ui/standalone/public/static/robopark/panda.png +0 -0
  401. package/ui/standalone/public/static/robopark/tesla.png +0 -0
  402. package/ui/standalone/public/static/robopark/titan.jpg +0 -0
  403. package/ui/standalone/public/static/robopark/volt.png +0 -0
  404. package/ui/standalone/public/tailnet/index.html +0 -8995
  405. package/ui/standalone/public/transcript-analytics.html +0 -72
  406. package/ui/standalone/public/vendor/livekit-client.umd.js +0 -2
  407. package/ui/standalone/server.js +0 -38
  408. /package/{ui/standalone/public → scheduler/static}/vendor/elevenlabs-client.iife.js +0 -0
@@ -1,584 +0,0 @@
1
- import chalk from 'chalk';
2
- import { spawn } from 'node:child_process';
3
- import { closeSync, existsSync, mkdirSync, openSync, readFileSync, renameSync, writeFileSync, } from 'node:fs';
4
- import { homedir, hostname, networkInterfaces } from 'node:os';
5
- import { dirname, join, resolve } from 'node:path';
6
- import { fileURLToPath } from 'node:url';
7
- import { registerAutoStart } from './auto-start.js';
8
- import { findPython, findSchedulerPath, prepareRobotPython } from './python-env.js';
9
- import { roboparkServe, schedulerHealthy } from './serve.js';
10
- import { startControlServer } from './control-server.js';
11
- import { startFrontendService, stopFrontendService } from './frontend-service.js';
12
- import { activateFromPairing } from './access.js';
13
- const STATE_DIR = process.env.ROBOPARK_HOME
14
- ? resolve(process.env.ROBOPARK_HOME)
15
- : join(homedir(), '.robopark');
16
- const CONNECTION_PATH = join(STATE_DIR, 'connection.json');
17
- const DESIRED_PATH = join(STATE_DIR, 'desired-runtime.json');
18
- const STATUS_PATH = join(STATE_DIR, 'runtime-status.json');
19
- const GATEWAY_PATH = join(STATE_DIR, 'gateway.json');
20
- const LOG_DIR = join(STATE_DIR, 'logs');
21
- function ensureStateDir() {
22
- mkdirSync(STATE_DIR, { recursive: true });
23
- mkdirSync(LOG_DIR, { recursive: true });
24
- }
25
- function atomicJson(path, value, mode = 0o600) {
26
- ensureStateDir();
27
- const temp = `${path}.${process.pid}.tmp`;
28
- writeFileSync(temp, `${JSON.stringify(value, null, 2)}\n`, { encoding: 'utf8', mode });
29
- renameSync(temp, path);
30
- }
31
- function normalizedUrl(value) {
32
- const candidate = value.trim().replace(/\/+$/, '');
33
- const url = new URL(candidate);
34
- if (url.protocol !== 'http:' && url.protocol !== 'https:') {
35
- throw new Error('gateway URL must use http:// or https://');
36
- }
37
- return url.toString().replace(/\/+$/, '');
38
- }
39
- export function encodePairingToken(payload) {
40
- return `rp1_${Buffer.from(JSON.stringify(payload), 'utf8').toString('base64url')}`;
41
- }
42
- export function decodePairingToken(value) {
43
- const trimmed = value.trim();
44
- let payload;
45
- if (trimmed.startsWith('rp1_')) {
46
- try {
47
- payload = JSON.parse(Buffer.from(trimmed.slice(4), 'base64url').toString('utf8'));
48
- }
49
- catch {
50
- throw new Error('invalid RoboPark connection token');
51
- }
52
- }
53
- else if (/^https?:\/\//i.test(trimmed) && trimmed.includes('#')) {
54
- const split = trimmed.lastIndexOf('#');
55
- payload = {
56
- v: 1,
57
- schedulerUrl: trimmed.slice(0, split),
58
- enrollmentToken: trimmed.slice(split + 1),
59
- };
60
- }
61
- else {
62
- throw new Error('invalid connection token; copy the complete token shown by `robopark gateway start`');
63
- }
64
- if (payload.v !== 1 || !payload.schedulerUrl || !payload.enrollmentToken) {
65
- throw new Error('unsupported or incomplete RoboPark connection token');
66
- }
67
- payload.schedulerUrl = normalizedUrl(payload.schedulerUrl);
68
- if (payload.expiresAt && Date.parse(payload.expiresAt) <= Date.now()) {
69
- throw new Error('this connection token has expired; create a new token on the gateway');
70
- }
71
- return payload;
72
- }
73
- function readConnection() {
74
- if (existsSync(CONNECTION_PATH)) {
75
- const parsed = JSON.parse(readFileSync(CONNECTION_PATH, 'utf8'));
76
- if (!parsed.schedulerUrl || !parsed.deviceId || !parsed.deviceToken) {
77
- throw new Error(`invalid saved connection at ${CONNECTION_PATH}; reconnect this device`);
78
- }
79
- return parsed;
80
- }
81
- // Adopt the exact deployed preview-agent identity in place. Older releases
82
- // predate connection.json but already persist everything required here.
83
- const legacyPath = join(STATE_DIR, 'preview_agent.json');
84
- if (existsSync(legacyPath)) {
85
- try {
86
- const legacy = JSON.parse(readFileSync(legacyPath, 'utf8'));
87
- const tokenPath = join(STATE_DIR, 'device_token');
88
- const deviceToken = existsSync(tokenPath)
89
- ? readFileSync(tokenPath, 'utf8').trim()
90
- : String(legacy.device_token ?? '').trim();
91
- const schedulerUrl = String(legacy.scheduler_url ?? '').trim();
92
- const deviceId = String(legacy.device_id ?? '').trim();
93
- const name = String(legacy.robot_id ?? legacy.name ?? hostname().split('.')[0]).trim();
94
- if (schedulerUrl && deviceId && deviceToken) {
95
- const adopted = {
96
- version: 1,
97
- schedulerUrl: normalizedUrl(schedulerUrl),
98
- deviceId,
99
- deviceToken,
100
- name,
101
- connectedAt: new Date().toISOString(),
102
- };
103
- atomicJson(CONNECTION_PATH, adopted);
104
- return adopted;
105
- }
106
- }
107
- catch {
108
- // Fall through to the actionable connect error.
109
- }
110
- }
111
- throw new Error('this device is not connected; run `robopark connect <token>` first');
112
- }
113
- async function jsonRequest(url, init, timeoutMs = 10_000) {
114
- const response = await fetch(url, { ...init, signal: AbortSignal.timeout(timeoutMs) });
115
- const body = await response.text();
116
- let parsed = {};
117
- try {
118
- parsed = body ? JSON.parse(body) : {};
119
- }
120
- catch {
121
- parsed = { detail: body };
122
- }
123
- if (!response.ok) {
124
- const detail = typeof parsed === 'object' && parsed && 'detail' in parsed
125
- ? String(parsed.detail)
126
- : `HTTP ${response.status}`;
127
- throw new Error(detail);
128
- }
129
- return parsed;
130
- }
131
- export async function connectDevice(token, opts = {}) {
132
- const pairing = decodePairingToken(token);
133
- const name = (opts.name ?? hostname().split('.')[0]).trim();
134
- if (!name)
135
- throw new Error('device name cannot be empty');
136
- const enrolled = await jsonRequest(`${pairing.schedulerUrl}/api/devices/enroll`, {
137
- method: 'POST',
138
- headers: { 'content-type': 'application/json' },
139
- body: JSON.stringify({
140
- enrollment_token: pairing.enrollmentToken,
141
- name,
142
- device_role: 'voice_vision',
143
- }),
144
- });
145
- const connection = {
146
- version: 1,
147
- // The gateway may bind on 0.0.0.0 and report its own loopback default.
148
- // The pairing payload is the operator-selected device route.
149
- schedulerUrl: pairing.schedulerUrl,
150
- deviceId: enrolled.device_id,
151
- deviceToken: enrolled.device_token,
152
- name,
153
- connectedAt: new Date().toISOString(),
154
- controlPort: pairing.controlPort,
155
- };
156
- atomicJson(CONNECTION_PATH, connection);
157
- writeFileSync(join(STATE_DIR, 'device_token'), connection.deviceToken, { encoding: 'utf8', mode: 0o600 });
158
- activateFromPairing();
159
- console.log(chalk.green(`\n ✓ connected ${name}`));
160
- console.log(` gateway: ${chalk.cyan(connection.schedulerUrl)}`);
161
- console.log(` device: ${chalk.cyan(connection.deviceId)}`);
162
- console.log(chalk.dim(' credentials were saved locally; the pairing token was not retained.\n'));
163
- return connection;
164
- }
165
- function cliEntry() {
166
- const here = dirname(fileURLToPath(import.meta.url));
167
- const candidates = [
168
- join(here, '..', '..', 'bin', 'robopark.js'),
169
- join(here, '..', '..', '..', 'bin', 'robopark.js'),
170
- join(process.cwd(), 'bin', 'robopark.js'),
171
- ];
172
- const found = candidates.find(existsSync);
173
- if (!found)
174
- throw new Error('could not resolve the packaged RoboPark CLI entry point');
175
- return found;
176
- }
177
- async function updateDeviceConfiguration(desired) {
178
- const c = desired.connection;
179
- await jsonRequest(`${c.schedulerUrl}/api/devices/${encodeURIComponent(c.deviceId)}`, {
180
- method: 'PATCH',
181
- headers: {
182
- 'content-type': 'application/json',
183
- authorization: `Bearer ${c.deviceToken}`,
184
- },
185
- body: JSON.stringify({
186
- name: c.name,
187
- character_id: desired.character || null,
188
- video_device: desired.video ? desired.videoDevice : 'none',
189
- audio_device: desired.voice ? desired.audioDevice : 'none',
190
- device_role: 'voice_vision',
191
- production_mode: true,
192
- }),
193
- });
194
- }
195
- function positiveInteger(value, fallback, label) {
196
- const parsed = Number.parseInt(value ?? String(fallback), 10);
197
- if (!Number.isFinite(parsed) || parsed <= 0)
198
- throw new Error(`${label} must be a positive integer`);
199
- return parsed;
200
- }
201
- function openBrowser(url) {
202
- // Use detached platform-native launchers so this CLI remains usable from
203
- // terminals and does not inherit browser stdio or lifecycle.
204
- const command = process.platform === 'win32'
205
- ? 'explorer.exe'
206
- : process.platform === 'darwin'
207
- ? 'open'
208
- : 'xdg-open';
209
- // Do not route URLs through cmd.exe. Character links contain "&"; cmd
210
- // treats each query parameter after the first as a separate shell command,
211
- // causing the browser to receive an incomplete join link.
212
- const args = [url];
213
- try {
214
- spawn(command, args, { detached: true, stdio: 'ignore', windowsHide: true }).unref();
215
- }
216
- catch (err) {
217
- console.log(chalk.yellow(` ⚠ could not open browser: ${err instanceof Error ? err.message : String(err)}`));
218
- }
219
- }
220
- /**
221
- * Opens the character-specific ElevenLabs voice-join page (served by the Park
222
- * Control Center, not the scheduler) for the device just configured. Best
223
- * effort only: a browser-less robot host or an unreachable control server
224
- * must never fail `robopark start`/`robopark connect`.
225
- */
226
- async function openCharacterCallPage(connection, character) {
227
- try {
228
- const preset = await jsonRequest(`${connection.schedulerUrl}/api/character-presets/${encodeURIComponent(character)}`, {});
229
- if (!preset.elevenlabs_agent_id) {
230
- console.log(chalk.yellow(` ⚠ character "${character}" has no ElevenLabs agent configured; not opening a call page.`));
231
- return;
232
- }
233
- const controlPort = connection.controlPort ?? 47913;
234
- const controlHost = new URL(connection.schedulerUrl).hostname;
235
- const params = new URLSearchParams({
236
- character,
237
- agent_id: preset.elevenlabs_agent_id,
238
- mode: 'production',
239
- robot: connection.deviceId,
240
- });
241
- if (preset.elevenlabs_branch_id)
242
- params.set('branch_id', preset.elevenlabs_branch_id);
243
- const url = `http://${controlHost}:${controlPort}/voice-join.html?${params.toString()}`;
244
- console.log(` call page: ${chalk.cyan(url)}`);
245
- openBrowser(url);
246
- }
247
- catch (err) {
248
- console.log(chalk.yellow(` ⚠ could not open the character call page: ${err instanceof Error ? err.message : String(err)}`));
249
- }
250
- }
251
- export async function startDevice(opts) {
252
- const connection = readConnection();
253
- const desired = {
254
- version: 1,
255
- connection,
256
- voice: Boolean(opts.voice),
257
- video: Boolean(opts.video),
258
- character: opts.character?.trim() || undefined,
259
- videoDevice: opts.videoDevice ?? 'auto',
260
- audioDevice: opts.audioDevice ?? 'default',
261
- width: positiveInteger(opts.width, 640, 'width'),
262
- height: positiveInteger(opts.height, 480, 'height'),
263
- fps: positiveInteger(opts.fps, 15, 'fps'),
264
- updatedAt: new Date().toISOString(),
265
- };
266
- if (!desired.voice && !desired.video) {
267
- throw new Error('select at least one media path: `--voice`, `--video`, or both');
268
- }
269
- await updateDeviceConfiguration(desired);
270
- atomicJson(DESIRED_PATH, desired);
271
- if (desired.character) {
272
- await openCharacterCallPage(connection, desired.character);
273
- }
274
- if (opts.foreground) {
275
- await superviseDevice(DESIRED_PATH);
276
- return;
277
- }
278
- const registration = await registerAutoStart({
279
- role: 'robot-runtime',
280
- name: connection.name,
281
- command: process.execPath,
282
- args: [cliEntry(), '_supervise', '--config', DESIRED_PATH],
283
- workingDir: homedir(),
284
- exclusiveMedia: true,
285
- });
286
- if (!registration.ok) {
287
- throw new Error(`${registration.message}. Retry from an elevated terminal or use --foreground.`);
288
- }
289
- console.log(chalk.green(`\n ✓ RoboPark is supervising ${connection.name}`));
290
- console.log(` voice: ${desired.voice ? chalk.green('on') : chalk.dim('off')}`);
291
- console.log(` video: ${desired.video ? chalk.green('on') : chalk.dim('off')}`);
292
- console.log(` character: ${chalk.cyan(desired.character || 'gateway default')}`);
293
- console.log(` service: ${chalk.dim(registration.message)}`);
294
- console.log(` status: ${chalk.dim(STATUS_PATH)}\n`);
295
- }
296
- function status(state) {
297
- atomicJson(STATUS_PATH, { ...state, updatedAt: new Date().toISOString(), pid: process.pid });
298
- }
299
- async function reportSupervisor(desired, state, detail) {
300
- const c = desired.connection;
301
- try {
302
- await jsonRequest(`${c.schedulerUrl}/api/devices/${encodeURIComponent(c.deviceId)}/supervisor-status`, {
303
- method: 'POST',
304
- headers: {
305
- 'content-type': 'application/json',
306
- authorization: `Bearer ${c.deviceToken}`,
307
- },
308
- body: JSON.stringify({
309
- services: [{
310
- name: `robopark-device:${state}:${detail.slice(0, 120)}`,
311
- enabled: true,
312
- running: state === 'running' || state === 'starting',
313
- pid: process.pid,
314
- failure_count: 0,
315
- }],
316
- }),
317
- }, 4_000);
318
- }
319
- catch {
320
- // The local status file remains authoritative while the gateway is offline.
321
- }
322
- }
323
- async function heartbeatFresh(desired) {
324
- const c = desired.connection;
325
- try {
326
- const device = await jsonRequest(`${c.schedulerUrl}/api/devices/${encodeURIComponent(c.deviceId)}`, { headers: { authorization: `Bearer ${c.deviceToken}` } }, 5_000);
327
- const age = device.last_heartbeat ? Date.now() - Date.parse(device.last_heartbeat) : Number.POSITIVE_INFINITY;
328
- return age < 45_000 && device.status !== 'offline';
329
- }
330
- catch {
331
- // A gateway outage must not cause a restart storm. The worker's own retry
332
- // loop will reconnect, and the watchdog resumes once the gateway answers.
333
- return true;
334
- }
335
- }
336
- function wait(ms) {
337
- return new Promise(resolve => setTimeout(resolve, ms));
338
- }
339
- function terminate(child) {
340
- if (!child || child.killed)
341
- return;
342
- try {
343
- child.kill('SIGTERM');
344
- }
345
- catch { /* already gone */ }
346
- setTimeout(() => {
347
- if (child.exitCode === null) {
348
- try {
349
- child.kill('SIGKILL');
350
- }
351
- catch { /* already gone */ }
352
- }
353
- }, 10_000).unref();
354
- }
355
- export async function superviseDevice(configPath = DESIRED_PATH) {
356
- let desired = JSON.parse(readFileSync(configPath, 'utf8'));
357
- const script = await findSchedulerPath();
358
- if (!script)
359
- throw new Error('packaged preview_agent.py was not found');
360
- const python = prepareRobotPython(findPython(), script);
361
- if (!python)
362
- throw new Error('could not prepare the packaged RoboPark media runtime');
363
- let child;
364
- let stopping = false;
365
- let failures = 0;
366
- let startedAt = 0;
367
- const shutdown = (signal) => {
368
- stopping = true;
369
- status({ state: 'stopping', signal });
370
- terminate(child);
371
- };
372
- process.once('SIGINT', shutdown);
373
- process.once('SIGTERM', shutdown);
374
- while (!stopping) {
375
- desired = JSON.parse(readFileSync(configPath, 'utf8'));
376
- const c = desired.connection;
377
- const args = [
378
- script,
379
- '--scheduler-url', c.schedulerUrl,
380
- '--robot-id', c.name,
381
- '--device-token', c.deviceToken,
382
- '--video-device', desired.video ? desired.videoDevice : 'none',
383
- '--audio-device', desired.voice ? desired.audioDevice : 'none',
384
- '--width', String(desired.width),
385
- '--height', String(desired.height),
386
- '--fps', String(desired.fps),
387
- '--save-config',
388
- ];
389
- startedAt = Date.now();
390
- status({ state: 'starting', childFailures: failures, voice: desired.voice, video: desired.video });
391
- await reportSupervisor(desired, 'starting', 'starting packaged camera/voice worker');
392
- const outFd = openSync(join(LOG_DIR, 'device.log'), 'a');
393
- const errFd = openSync(join(LOG_DIR, 'device.err.log'), 'a');
394
- child = spawn(python, args, {
395
- env: { ...process.env, ROBOPARK_DEVICE_ID: c.deviceId },
396
- stdio: ['ignore', outFd, errFd],
397
- windowsHide: true,
398
- });
399
- closeSync(outFd);
400
- closeSync(errFd);
401
- status({ state: 'running', childPid: child.pid, childFailures: failures });
402
- await reportSupervisor(desired, 'running', `media worker pid ${child.pid ?? 'unknown'}`);
403
- let watchdogFailures = 0;
404
- const watchdog = setInterval(async () => {
405
- if (!child || child.exitCode !== null || Date.now() - startedAt < 60_000)
406
- return;
407
- watchdogFailures = await heartbeatFresh(desired) ? 0 : watchdogFailures + 1;
408
- if (watchdogFailures >= 3) {
409
- status({ state: 'restarting', reason: 'heartbeat_stalled', childPid: child.pid });
410
- await reportSupervisor(desired, 'degraded', 'heartbeat stalled; recycling media worker');
411
- terminate(child);
412
- }
413
- }, 15_000);
414
- watchdog.unref();
415
- const exit = await new Promise((resolve, reject) => {
416
- child?.once('error', reject);
417
- child?.once('exit', (code, signal) => resolve({ code, signal }));
418
- }).catch(error => ({ code: 1, signal: null, error }));
419
- clearInterval(watchdog);
420
- if (stopping)
421
- break;
422
- const ranFor = Date.now() - startedAt;
423
- failures = ranFor > 5 * 60_000 ? 0 : failures + 1;
424
- const delay = Math.min(30_000, 1_000 * 2 ** Math.min(failures, 5));
425
- const detail = `worker stopped (${String('error' in exit ? exit.error : exit.signal ?? exit.code)}); restart in ${delay}ms`;
426
- status({ state: 'backoff', detail, childFailures: failures, restartInMs: delay });
427
- await reportSupervisor(desired, 'restarting', detail);
428
- await wait(delay);
429
- }
430
- status({ state: 'stopped' });
431
- await reportSupervisor(desired, 'stopped', 'device supervisor stopped');
432
- }
433
- function localPublicUrl(host, port, explicit) {
434
- if (explicit)
435
- return normalizedUrl(explicit);
436
- let publicHost = host;
437
- if (host === '0.0.0.0' || host === '::') {
438
- const addresses = Object.entries(networkInterfaces())
439
- .flatMap(([name, entries]) => (entries ?? [])
440
- .filter(entry => entry.family === 'IPv4' && !entry.internal)
441
- .map(entry => ({ name: name.toLowerCase(), address: entry.address })));
442
- publicHost = addresses.find(entry => /tailscale|wireguard|wg/.test(entry.name))?.address
443
- ?? addresses.find(entry => entry.address.startsWith('100.'))?.address
444
- ?? addresses.find(entry => /ethernet|wi-?fi|wlan|eth/.test(entry.name))?.address
445
- ?? addresses[0]?.address
446
- ?? 'localhost';
447
- }
448
- return normalizedUrl(`http://${publicHost}:${port}`);
449
- }
450
- export async function gatewayStart(opts) {
451
- const port = positiveInteger(opts.port, 8080, 'port');
452
- const controlPort = positiveInteger(opts.controlPort, 47913, 'control port');
453
- const host = opts.host ?? '0.0.0.0';
454
- if (opts.foreground) {
455
- await runGatewayService({
456
- port: String(port),
457
- controlPort: String(controlPort),
458
- host,
459
- dataDir: opts.dataDir,
460
- open: opts.open,
461
- });
462
- }
463
- else {
464
- const args = [cliEntry(), '_gateway', '--port', String(port), '--host', host];
465
- args.push('--control-port', String(controlPort));
466
- if (opts.dataDir)
467
- args.push('--data-dir', opts.dataDir);
468
- const registration = await registerAutoStart({
469
- role: 'hub-scheduler',
470
- name: 'gateway',
471
- command: process.execPath,
472
- args,
473
- workingDir: homedir(),
474
- });
475
- if (!registration.ok) {
476
- throw new Error(`${registration.message}. Retry from an elevated terminal or add --foreground.`);
477
- }
478
- console.log(chalk.green(` ✓ ${registration.message}`));
479
- }
480
- const schedulerUrl = localPublicUrl(host, port, opts.publicUrl);
481
- const healthUrl = `http://127.0.0.1:${port}`;
482
- const controlHealthUrl = `http://127.0.0.1:${controlPort}/fed/ui-build`;
483
- const deadline = Date.now() + 30_000;
484
- let schedulerReady = false;
485
- let controlReady = false;
486
- while (!schedulerReady || !controlReady) {
487
- [schedulerReady, controlReady] = await Promise.all([
488
- schedulerHealthy(healthUrl, 1_000),
489
- fetch(controlHealthUrl, { signal: AbortSignal.timeout(1_000) })
490
- .then(async (response) => {
491
- if (!response.ok)
492
- return false;
493
- const body = await response.json();
494
- return body.owner === 'robopark' || body.source === 'robovoice-control';
495
- })
496
- .catch(() => false),
497
- ]);
498
- if (Date.now() >= deadline) {
499
- throw new Error(`gateway did not become healthy (scheduler ${port}: ${schedulerReady ? 'ready' : 'down'}, `
500
- + `Control Center ${controlPort}: ${controlReady ? 'ready' : 'down'})`);
501
- }
502
- await wait(500);
503
- }
504
- const token = await createGatewayTokenAt(healthUrl, schedulerUrl, controlPort);
505
- const controlHost = new URL(schedulerUrl).hostname;
506
- console.log(` Park Control Center: ${chalk.cyan(`http://${controlHost}:${controlPort}/robopark`)}`);
507
- console.log(` Voice operations: ${chalk.cyan(`http://${controlHost}:${controlPort}/voice`)}`);
508
- console.log(chalk.bold('\n Connect a device'));
509
- console.log(` ${chalk.cyan(`robopark connect ${token}`)}`);
510
- console.log(chalk.dim(' This pairing token expires in 24 hours. Connected devices keep their own durable credentials.\n'));
511
- }
512
- async function createGatewayTokenAt(localUrl, publicUrl, controlPort) {
513
- await jsonRequest(`${localUrl}/api/settings`, {
514
- method: 'PUT',
515
- headers: { 'content-type': 'application/json' },
516
- body: JSON.stringify({ production_mode: true }),
517
- });
518
- const result = await jsonRequest(`${localUrl}/api/settings/enrollment-token/rotate`, { method: 'POST' });
519
- const pairing = encodePairingToken({
520
- v: 1,
521
- schedulerUrl: publicUrl,
522
- enrollmentToken: result.enrollment_token,
523
- expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(),
524
- controlPort,
525
- });
526
- atomicJson(GATEWAY_PATH, { schedulerUrl: publicUrl, pairingToken: pairing, createdAt: new Date().toISOString() });
527
- return pairing;
528
- }
529
- export async function runGatewayService(opts) {
530
- const schedulerPort = positiveInteger(opts.port, 8080, 'port');
531
- const controlPort = positiveInteger(opts.controlPort, 47913, 'control port');
532
- const adapterPort = controlPort + 1;
533
- const host = opts.host ?? '0.0.0.0';
534
- const control = await startControlServer({
535
- host,
536
- port: adapterPort,
537
- schedulerUrl: `http://127.0.0.1:${schedulerPort}`,
538
- });
539
- let frontend;
540
- try {
541
- frontend = await startFrontendService({
542
- host,
543
- port: controlPort,
544
- adapterPort,
545
- schedulerPort,
546
- });
547
- console.log(` ROBOVOICE stable frontend listening on ${host}:${controlPort}`);
548
- console.log(` RoboPark control adapter listening on ${host}:${adapterPort}`);
549
- const frontendExit = new Promise(resolve => {
550
- frontend?.once('exit', () => resolve('frontend-exit'));
551
- });
552
- await roboparkServe({
553
- port: String(schedulerPort),
554
- host,
555
- dataDir: opts.dataDir,
556
- open: false,
557
- foreground: true,
558
- });
559
- // roboparkServe wires the scheduler child's lifecycle handlers and then
560
- // returns once it has launched. Keep this outer gateway process alive so
561
- // the sibling Control Center server is not closed by the finally block.
562
- const shutdown = new Promise(resolve => {
563
- const stop = () => resolve('shutdown');
564
- process.once('SIGINT', stop);
565
- process.once('SIGTERM', stop);
566
- });
567
- const ended = await Promise.race([shutdown, frontendExit]);
568
- if (ended === 'frontend-exit') {
569
- throw new Error('ROBOVOICE stable frontend stopped unexpectedly');
570
- }
571
- }
572
- finally {
573
- stopFrontendService(frontend);
574
- control.close();
575
- }
576
- }
577
- export function printRuntimeStatus() {
578
- if (!existsSync(STATUS_PATH)) {
579
- console.log(chalk.yellow(' no local runtime status; run `robopark start --voice --video`'));
580
- return;
581
- }
582
- const current = JSON.parse(readFileSync(STATUS_PATH, 'utf8'));
583
- console.log(JSON.stringify(current, null, 2));
584
- }