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
package/scheduler/main.py CHANGED
@@ -16,13 +16,16 @@ import hmac
16
16
  import time
17
17
  import uuid
18
18
  from datetime import datetime, timedelta
19
+ from html import escape as html_escape
20
+ from pathlib import Path
19
21
  from typing import Any, Optional, List
22
+ from urllib.parse import quote
20
23
  from contextlib import asynccontextmanager
21
24
 
22
25
  from fastapi import FastAPI, WebSocket, WebSocketDisconnect, HTTPException, Query, Header
23
26
  from fastapi.middleware.cors import CORSMiddleware
24
27
  from fastapi.staticfiles import StaticFiles
25
- from fastapi.responses import HTMLResponse
28
+ from fastapi.responses import HTMLResponse, FileResponse
26
29
  from pydantic import BaseModel
27
30
  import httpx
28
31
  import aiosqlite
@@ -1740,6 +1743,60 @@ async def _select_available_server(db):
1740
1743
  raise HTTPException(503, "All servers at capacity")
1741
1744
  return server
1742
1745
 
1746
+ async def _claim_character_device(
1747
+ db,
1748
+ *,
1749
+ character_id: str,
1750
+ join_link_id: str,
1751
+ requested_robot_id: str,
1752
+ client_name: str | None,
1753
+ now: datetime,
1754
+ ):
1755
+ """Bind whichever device opened a character link to that character.
1756
+
1757
+ Production calls used to demand a pre-enrolled robot, which meant a freshly
1758
+ imaged voice/vision box could not take a call until someone ran `robopark
1759
+ enroll` on it. Instead the link itself is the identity: the claim is keyed on
1760
+ the join link when there is one and on the character otherwise, so opening the
1761
+ same link from a different browser transfers the character to that device and
1762
+ ends the previous holder's live session.
1763
+
1764
+ Returns (robot_id, robot_video_ref).
1765
+ """
1766
+ claim_key = (join_link_id or f"character:{character_id}").strip()[:120]
1767
+ device_id = "claim_" + hashlib.sha256(claim_key.encode("utf-8")).hexdigest()[:16]
1768
+ label = (requested_robot_id or client_name or character_id or "claimed device").strip()[:80]
1769
+ stamp = now.isoformat()
1770
+
1771
+ async with db.execute("SELECT id, name FROM devices WHERE id = ?", (device_id,)) as cursor:
1772
+ existing = await cursor.fetchone()
1773
+
1774
+ if existing:
1775
+ # Same link opened again: the newest device wins. Close whatever session
1776
+ # the previous holder still had open so two browsers cannot both drive
1777
+ # the character, then re-point the claim at the current caller.
1778
+ await db.execute(
1779
+ "UPDATE sessions SET ended_at = ?, end_reason = ? WHERE robot_id = ? AND ended_at IS NULL",
1780
+ (stamp, "claim_transferred", device_id),
1781
+ )
1782
+ await db.execute(
1783
+ "UPDATE devices SET character_id = ?, name = ?, status = ?, last_heartbeat = ? WHERE id = ?",
1784
+ (character_id, label, "claimed", stamp, device_id),
1785
+ )
1786
+ else:
1787
+ await db.execute(
1788
+ """INSERT INTO devices (id, name, character_id, status, device_role,
1789
+ last_heartbeat, enrolled_at, notes)
1790
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)""",
1791
+ (device_id, label, character_id, "claimed", "voice_vision", stamp, stamp,
1792
+ f"auto-claimed via join link {claim_key}"),
1793
+ )
1794
+ await db.commit()
1795
+ # No MJPEG ref: a claimed device publishes its own browser camera rather than
1796
+ # exposing a robot-side vision server, so the page falls back to local video.
1797
+ return device_id, None
1798
+
1799
+
1743
1800
  @app.post("/api/sessions/voice-call")
1744
1801
  async def create_voice_call(payload: VoiceCallRequest):
1745
1802
  """Create a browser voice call using a frozen character and voice stack.
@@ -1788,17 +1845,30 @@ async def create_voice_call(payload: VoiceCallRequest):
1788
1845
  robot_id = "operator-test"
1789
1846
  robot_video_ref = None
1790
1847
  if call_mode == "production":
1791
- if not requested_robot_id:
1792
- raise HTTPException(422, "Production calls require a registered robot")
1793
- async with db.execute(
1794
- "SELECT id, name FROM devices WHERE id = ? OR lower(name) = lower(?) ORDER BY last_heartbeat DESC LIMIT 1",
1795
- (requested_robot_id, requested_robot_id),
1796
- ) as cursor:
1797
- robot_device = await cursor.fetchone()
1798
- if not robot_device:
1799
- raise HTTPException(404, f"Production robot '{requested_robot_id}' is not registered")
1800
- robot_id = robot_device["id"]
1801
- robot_video_ref = robot_device["name"] or robot_device["id"]
1848
+ # Claim-based binding. A production call no longer requires hardware to
1849
+ # have been enrolled ahead of time: whichever device opens the character
1850
+ # link claims that character, and holds it until another device opens the
1851
+ # same link. An explicitly requested robot that is already registered
1852
+ # still wins, so existing enrolled fleets behave exactly as before.
1853
+ robot_device = None
1854
+ if requested_robot_id:
1855
+ async with db.execute(
1856
+ "SELECT id, name FROM devices WHERE id = ? OR lower(name) = lower(?) ORDER BY last_heartbeat DESC LIMIT 1",
1857
+ (requested_robot_id, requested_robot_id),
1858
+ ) as cursor:
1859
+ robot_device = await cursor.fetchone()
1860
+ if robot_device:
1861
+ robot_id = robot_device["id"]
1862
+ robot_video_ref = robot_device["name"] or robot_device["id"]
1863
+ else:
1864
+ robot_id, robot_video_ref = await _claim_character_device(
1865
+ db,
1866
+ character_id=character["id"],
1867
+ join_link_id=join_link_id,
1868
+ requested_robot_id=requested_robot_id,
1869
+ client_name=payload.client_name,
1870
+ now=now,
1871
+ )
1802
1872
  metadata = {
1803
1873
  "control_center": True,
1804
1874
  "character_id": character["id"],
@@ -6993,6 +7063,114 @@ async def voice_client():
6993
7063
  </body></html>
6994
7064
  """
6995
7065
 
7066
+ # ==========================================================================
7067
+ # Operator-facing pages served directly by the scheduler.
7068
+ #
7069
+ # These used to live behind the Next frontend (Docker) and the federation mesh
7070
+ # hub, which meant a one-command deploy was impossible and the join page existed
7071
+ # in three diverging copies. The scheduler already owns the character bindings
7072
+ # and mints the ElevenLabs signed URLs, so it serves the pages too: `robopark
7073
+ # serve` alone is now enough, and joining devices need only a browser.
7074
+ # ==========================================================================
7075
+
7076
+ STATIC_DIR = Path(__file__).resolve().parent / "static"
7077
+
7078
+
7079
+ @app.get("/voice-join.html", include_in_schema=False)
7080
+ async def voice_join_page():
7081
+ """The character call page. Visitors open this; no install, no credentials."""
7082
+ page = STATIC_DIR / "voice-join.html"
7083
+ if not page.exists():
7084
+ raise HTTPException(404, "voice-join.html is missing from this install")
7085
+ return FileResponse(page, media_type="text/html; charset=utf-8")
7086
+
7087
+
7088
+ @app.get("/vendor/elevenlabs-client.iife.js", include_in_schema=False)
7089
+ async def elevenlabs_browser_client():
7090
+ """Vendored ElevenLabs browser SDK, so joining devices need no npm install."""
7091
+ asset = STATIC_DIR / "vendor" / "elevenlabs-client.iife.js"
7092
+ if not asset.exists():
7093
+ raise HTTPException(404, "ElevenLabs browser client is missing from this install")
7094
+ return FileResponse(asset, media_type="application/javascript; charset=utf-8")
7095
+
7096
+
7097
+ def _call_link(character_id: str, agent_id: str, branch_id: str | None, mode: str) -> str:
7098
+ query = f"character={quote(character_id)}&agent_id={quote(agent_id)}&mode={mode}"
7099
+ if branch_id:
7100
+ query += f"&branch_id={quote(branch_id)}"
7101
+ return f"/voice-join.html?{query}"
7102
+
7103
+
7104
+ @app.get("/calls", response_class=HTMLResponse, include_in_schema=False)
7105
+ async def call_links_page():
7106
+ """One page listing every preconfigured character call link, ready to copy.
7107
+
7108
+ Production links no longer depend on enrolled hardware — the first device to
7109
+ open one claims that character — so every character with a bound ElevenLabs
7110
+ agent gets a working production link here.
7111
+ """
7112
+ async with aiosqlite.connect(DB_PATH) as db:
7113
+ db.row_factory = aiosqlite.Row
7114
+ async with db.execute(
7115
+ """SELECT id, name, elevenlabs_agent_id, elevenlabs_branch_id, default_engine
7116
+ FROM character_presets ORDER BY name"""
7117
+ ) as cursor:
7118
+ characters = await cursor.fetchall()
7119
+
7120
+ rows = []
7121
+ ready = 0
7122
+ for character in characters:
7123
+ name = html_escape(str(character["name"] or character["id"]))
7124
+ agent_id = (character["elevenlabs_agent_id"] or "").strip()
7125
+ if not agent_id:
7126
+ rows.append(
7127
+ f'<tr class="blocked"><td>{name}</td><td colspan="2">'
7128
+ "no ElevenLabs agent bound — set one in the Control Center</td></tr>"
7129
+ )
7130
+ continue
7131
+ ready += 1
7132
+ link = _call_link(character["id"], agent_id, character["elevenlabs_branch_id"], "production")
7133
+ test = _call_link(character["id"], agent_id, character["elevenlabs_branch_id"], "test")
7134
+ rows.append(
7135
+ f"<tr><td>{name}<span class=agent>{html_escape(agent_id)}</span></td>"
7136
+ f'<td><a href="{link}">production</a>'
7137
+ f'<button data-link="{link}">copy</button></td>'
7138
+ f'<td><a href="{test}">test</a><button data-link="{test}">copy</button></td></tr>'
7139
+ )
7140
+
7141
+ return f"""<!doctype html><html lang=en><head><meta charset=utf-8>
7142
+ <meta name=viewport content="width=device-width,initial-scale=1">
7143
+ <title>RoboPark character call links</title><style>
7144
+ body{{margin:0;padding:clamp(16px,4vw,40px);background:#0b0a07;color:#f4efe2;
7145
+ font:15px/1.5 Verdana,Geneva,sans-serif}}
7146
+ h1{{margin:0 0 4px;font:700 1.6rem Georgia,serif;color:#ffe9a8}}
7147
+ p.sub{{margin:0 0 22px;color:#a99e84;font-size:.85rem}}
7148
+ table{{width:100%;border-collapse:collapse;max-width:1000px}}
7149
+ th{{text-align:left;padding:.55rem .6rem;color:#a99e84;font:600 .7rem monospace;
7150
+ text-transform:uppercase;letter-spacing:.1em;border-bottom:1px solid #3a3222}}
7151
+ td{{padding:.7rem .6rem;border-bottom:1px solid #211d14;vertical-align:top}}
7152
+ .agent{{display:block;color:#7e755f;font:.62rem monospace;margin-top:.2rem}}
7153
+ a{{color:#f4cf72}}button{{margin-left:.5rem;border:1px solid #4a4130;border-radius:7px;
7154
+ background:#171308;color:#f4cf72;padding:.24rem .5rem;font:.68rem monospace;cursor:pointer}}
7155
+ button:hover{{background:#241d0e}}tr.blocked td{{color:#ff9d93}}
7156
+ </style></head><body>
7157
+ <h1>Character call links</h1>
7158
+ <p class=sub>{ready} of {len(characters)} characters ready ·
7159
+ production links claim the character for whichever device opens them ·
7160
+ no enrollment and no install required</p>
7161
+ <table><tr><th>Character</th><th>Production</th><th>Browser test</th></tr>
7162
+ {''.join(rows)}</table>
7163
+ <script>
7164
+ document.addEventListener('click',function(e){{
7165
+ var b=e.target.closest('button[data-link]');if(!b)return;
7166
+ var url=location.origin+b.getAttribute('data-link');
7167
+ navigator.clipboard.writeText(url).then(function(){{
7168
+ var t=b.textContent;b.textContent='copied';setTimeout(function(){{b.textContent=t}},1200);
7169
+ }});
7170
+ }});
7171
+ </script></body></html>"""
7172
+
7173
+
6996
7174
  @app.get("/", response_class=HTMLResponse)
6997
7175
  async def dashboard():
6998
7176
  """Serve embedded dashboard"""
@@ -7009,14 +7187,15 @@ async def dashboard():
7009
7187
  </head>
7010
7188
  <body class="bg-gray-900 text-white">
7011
7189
  <div class="bg-amber-950 border-b border-amber-600 px-4 py-3 text-sm text-amber-100">
7012
- <strong>Scheduler service view.</strong> The canonical operator UI is the full standalone RoboPark Control Center on this gateway at port 47913.
7013
- <a id="fullControlCenterLink" class="underline ml-2" href="#">Open full Park Control Center</a>
7190
+ <strong>RoboPark Control Center.</strong> Character call links are ready to copy &mdash; production links claim the character for whichever device opens them, so no enrollment is needed.
7191
+ <a id="fullControlCenterLink" class="underline ml-2" href="/calls">Open character call links</a>
7014
7192
  </div>
7015
7193
  <div id="app"></div>
7016
7194
  <script>
7017
- const fullControlCenterLink = document.getElementById('fullControlCenterLink');
7018
- const meshHost = window.location.hostname || 'localhost';
7019
- fullControlCenterLink.href = `${window.location.protocol}//${meshHost}:47913/${window.location.search}#park`;
7195
+ // The call links are served by this same process now; the old link
7196
+ // pointed at the federation mesh hub on 47913, which is no longer
7197
+ // required to run a deployment.
7198
+ document.getElementById('fullControlCenterLink').href = '/calls';
7020
7199
  const { createApp, ref, computed, onMounted, onUnmounted } = Vue;
7021
7200
 
7022
7201
  createApp({
@@ -1,57 +1,57 @@
1
- """Cross-process media ownership for Linux robot appliances."""
2
- from __future__ import annotations
3
-
4
- import os
5
- import sys
6
- import time
7
- from pathlib import Path
8
- from typing import Optional
9
-
10
-
11
- class MediaLock:
12
- def __init__(self, kind: str, timeout: float = 5.0):
13
- self.kind = kind
14
- self.timeout = timeout
15
- self.fd: Optional[int] = None
16
-
17
- def acquire(self) -> "MediaLock":
18
- if not sys.platform.startswith("linux"):
19
- return self
20
- import fcntl
21
-
22
- lock_dir = Path("/run/lock") if os.access("/run/lock", os.W_OK) else Path("/tmp")
23
- path = lock_dir / f"robopark-{self.kind}.lock"
24
- self.fd = os.open(path, os.O_CREAT | os.O_RDWR, 0o660)
25
- deadline = time.monotonic() + self.timeout
26
- while True:
27
- try:
28
- fcntl.flock(self.fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
29
- os.ftruncate(self.fd, 0)
30
- os.write(self.fd, f"{os.getpid()}\n".encode("ascii"))
31
- return self
32
- except BlockingIOError:
33
- if time.monotonic() >= deadline:
34
- self.release()
35
- raise TimeoutError(f"{self.kind} is owned by another RoboPark media process")
36
- time.sleep(0.05)
37
-
38
- def release(self) -> None:
39
- if self.fd is None:
40
- return
41
- try:
42
- if sys.platform.startswith("linux"):
43
- import fcntl
44
- fcntl.flock(self.fd, fcntl.LOCK_UN)
45
- finally:
46
- os.close(self.fd)
47
- self.fd = None
48
-
49
- def __enter__(self) -> "MediaLock":
50
- return self.acquire()
51
-
52
- def __exit__(self, exc_type, exc, traceback) -> None:
53
- self.release()
54
-
55
-
56
- def media_lock(kind: str, timeout: float = 5.0) -> MediaLock:
57
- return MediaLock(kind, timeout)
1
+ """Cross-process media ownership for Linux robot appliances."""
2
+ from __future__ import annotations
3
+
4
+ import os
5
+ import sys
6
+ import time
7
+ from pathlib import Path
8
+ from typing import Optional
9
+
10
+
11
+ class MediaLock:
12
+ def __init__(self, kind: str, timeout: float = 5.0):
13
+ self.kind = kind
14
+ self.timeout = timeout
15
+ self.fd: Optional[int] = None
16
+
17
+ def acquire(self) -> "MediaLock":
18
+ if not sys.platform.startswith("linux"):
19
+ return self
20
+ import fcntl
21
+
22
+ lock_dir = Path("/run/lock") if os.access("/run/lock", os.W_OK) else Path("/tmp")
23
+ path = lock_dir / f"robopark-{self.kind}.lock"
24
+ self.fd = os.open(path, os.O_CREAT | os.O_RDWR, 0o660)
25
+ deadline = time.monotonic() + self.timeout
26
+ while True:
27
+ try:
28
+ fcntl.flock(self.fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
29
+ os.ftruncate(self.fd, 0)
30
+ os.write(self.fd, f"{os.getpid()}\n".encode("ascii"))
31
+ return self
32
+ except BlockingIOError:
33
+ if time.monotonic() >= deadline:
34
+ self.release()
35
+ raise TimeoutError(f"{self.kind} is owned by another RoboPark media process")
36
+ time.sleep(0.05)
37
+
38
+ def release(self) -> None:
39
+ if self.fd is None:
40
+ return
41
+ try:
42
+ if sys.platform.startswith("linux"):
43
+ import fcntl
44
+ fcntl.flock(self.fd, fcntl.LOCK_UN)
45
+ finally:
46
+ os.close(self.fd)
47
+ self.fd = None
48
+
49
+ def __enter__(self) -> "MediaLock":
50
+ return self.acquire()
51
+
52
+ def __exit__(self, exc_type, exc, traceback) -> None:
53
+ self.release()
54
+
55
+
56
+ def media_lock(kind: str, timeout: float = 5.0) -> MediaLock:
57
+ return MediaLock(kind, timeout)