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,139 +1,99 @@
1
- {
2
- "schema_version": 1,
3
- "revision": 2,
4
- "global": {
5
- "voice_engine_default": "elevenlabs",
6
- "robovoice_enabled": true,
7
- "allow_session_override": true,
8
- "apply_changes_when_idle": true
9
- },
10
- "aliases": {},
11
- "characters": [
12
- {
13
- "id": "barracuda",
14
- "name": "BARRACUDA",
15
- "description": "The Star Guardians speed navigator and tactical pilot.",
16
- "voice_stack_id": "english-default",
17
- "default_engine": "elevenlabs",
18
- "elevenlabs_agent_id": "agent_3001ky0z8wq0fht8xyb0tnkdksdz",
19
- "elevenlabs_branch_id": null,
20
- "robovoice_profile_id": "barracuda",
21
- "system_prompt_revision": 1,
22
- "nx": 0.18,
23
- "ny": 0.28,
24
- "img": "/static/robopark/barracuda.png"
25
- },
26
- {
27
- "id": "jaguar",
28
- "name": "JAGUAR",
29
- "description": "A confident and agile Star Guardians scout.",
30
- "voice_stack_id": "english-default",
31
- "default_engine": "elevenlabs",
32
- "elevenlabs_agent_id": "agent_4201ky0zhwn4fz7bwy9767wt6g3t",
33
- "elevenlabs_branch_id": null,
34
- "robovoice_profile_id": "jaguar",
35
- "system_prompt_revision": 1,
36
- "nx": 0.34,
37
- "ny": 0.34,
38
- "img": "/static/robopark/jaguar.png"
39
- },
40
- {
41
- "id": "magnus",
42
- "name": "MAGNUS",
43
- "description": "The calm leader of the Star Guardians.",
44
- "voice_stack_id": "english-default",
45
- "default_engine": "elevenlabs",
46
- "elevenlabs_agent_id": "agent_5601ky0ymahreyft2xz9xht0dj4v",
47
- "elevenlabs_branch_id": null,
48
- "robovoice_profile_id": "magnus",
49
- "system_prompt_revision": 1,
50
- "nx": 0.5,
51
- "ny": 0.24,
52
- "img": "/static/robopark/magnus.jpg"
53
- },
1
+ {
2
+ "schema_version": 1,
3
+ "revision": 1,
4
+ "global": {
5
+ "voice_engine_default": "elevenlabs",
6
+ "robovoice_enabled": true,
7
+ "allow_session_override": true,
8
+ "apply_changes_when_idle": true
9
+ },
10
+ "aliases": {
11
+ "panda": "ronin"
12
+ },
13
+ "characters": [
54
14
  {
55
- "id": "palladin",
56
- "name": "PALLADIN",
57
- "description": "The Star Guardians steadfast protector and ceremonial guardian.",
15
+ "id": "barracuda",
16
+ "name": "BARRACUDA",
17
+ "description": "The Star Guardians speed navigator and tactical pilot.",
58
18
  "voice_stack_id": "english-default",
59
19
  "default_engine": "elevenlabs",
60
- "elevenlabs_agent_id": "agent_0001ky7mxx2ce4pseeyn6wrtnrz8",
20
+ "elevenlabs_agent_id": "agent_3001ky0z8wq0fht8xyb0tnkdksdz",
61
21
  "elevenlabs_branch_id": null,
62
- "robovoice_profile_id": "palladin",
22
+ "robovoice_profile_id": "barracuda",
63
23
  "system_prompt_revision": 1,
64
- "nx": 0.66,
24
+ "nx": 0.18,
25
+ "ny": 0.28,
26
+ "img": "/static/robopark/barracuda.png"
27
+ },
28
+ {
29
+ "id": "jaguar",
30
+ "name": "JAGUAR",
31
+ "description": "A confident and agile Star Guardians scout.",
32
+ "voice_stack_id": "english-default",
33
+ "default_engine": "elevenlabs",
34
+ "elevenlabs_agent_id": "agent_4201ky0zhwn4fz7bwy9767wt6g3t",
35
+ "elevenlabs_branch_id": null,
36
+ "robovoice_profile_id": "jaguar",
37
+ "system_prompt_revision": 1,
38
+ "nx": 0.34,
39
+ "ny": 0.34,
40
+ "img": "/static/robopark/jaguar.png"
41
+ },
42
+ {
43
+ "id": "magnus",
44
+ "name": "MAGNUS",
45
+ "description": "The calm leader of the Star Guardians.",
46
+ "voice_stack_id": "english-default",
47
+ "default_engine": "elevenlabs",
48
+ "elevenlabs_agent_id": "agent_5601ky0ymahreyft2xz9xht0dj4v",
49
+ "elevenlabs_branch_id": null,
50
+ "robovoice_profile_id": "magnus",
51
+ "system_prompt_revision": 1,
52
+ "nx": 0.5,
65
53
  "ny": 0.24,
66
- "img": null
54
+ "img": "/static/robopark/magnus.jpg"
67
55
  },
68
56
  {
69
- "id": "panda",
70
- "name": "PANDA",
57
+ "id": "ronin",
58
+ "name": "RONIN",
71
59
  "description": "The Star Guardians story keeper and patient guide.",
72
60
  "voice_stack_id": "english-default",
73
61
  "default_engine": "elevenlabs",
74
62
  "elevenlabs_agent_id": "agent_4701ky0xkz89f8t993tmg5t8t23r",
75
63
  "elevenlabs_branch_id": null,
76
- "robovoice_profile_id": "panda",
64
+ "robovoice_profile_id": "ronin",
77
65
  "system_prompt_revision": 1,
78
- "nx": 0.74,
79
- "ny": 0.44,
66
+ "nx": 0.66,
67
+ "ny": 0.34,
80
68
  "img": "/static/robopark/panda.png"
81
69
  },
82
70
  {
83
- "id": "ronin",
84
- "name": "RONIN",
85
- "description": "The Star Guardians story keeper and patient guide.",
86
- "voice_stack_id": "english-default",
87
- "default_engine": "elevenlabs",
88
- "elevenlabs_agent_id": "agent_4701ky0xkz89f8t993tmg5t8t23r",
89
- "elevenlabs_branch_id": null,
90
- "robovoice_profile_id": "ronin",
91
- "system_prompt_revision": 1,
92
- "nx": 0.78,
93
- "ny": 0.58,
94
- "img": "/static/robopark/panda.png"
95
- },
96
- {
97
- "id": "titan",
98
- "name": "TITAN",
99
- "description": "The Star Guardians iron guardian and stability anchor.",
100
- "voice_stack_id": "english-default",
101
- "default_engine": "elevenlabs",
102
- "elevenlabs_agent_id": "agent_2501ky0x8t39fh5ske8tz3148b26",
103
- "elevenlabs_branch_id": null,
104
- "robovoice_profile_id": "titan",
105
- "system_prompt_revision": 1,
106
- "nx": 0.42,
107
- "ny": 0.68,
108
- "img": "/static/robopark/titan.jpg"
109
- },
110
- {
111
- "id": "vixen",
112
- "name": "VIXEN",
113
- "description": "The Star Guardians strategist and precision planner.",
114
- "voice_stack_id": "english-default",
115
- "default_engine": "elevenlabs",
116
- "elevenlabs_agent_id": "agent_6701ky0nfavke0ysrpj5v38m1cry",
117
- "elevenlabs_branch_id": null,
118
- "robovoice_profile_id": "vixen",
119
- "system_prompt_revision": 1,
120
- "nx": 0.58,
121
- "ny": 0.66,
122
- "img": "/static/robopark/bmw.png"
71
+ "id": "titan",
72
+ "name": "TITAN",
73
+ "description": "The Star Guardians iron guardian and stability anchor.",
74
+ "voice_stack_id": "english-default",
75
+ "default_engine": "elevenlabs",
76
+ "elevenlabs_agent_id": "agent_2501ky0x8t39fh5ske8tz3148b26",
77
+ "elevenlabs_branch_id": null,
78
+ "robovoice_profile_id": "titan",
79
+ "system_prompt_revision": 1,
80
+ "nx": 0.42,
81
+ "ny": 0.68,
82
+ "img": "/static/robopark/titan.jpg"
123
83
  },
124
84
  {
125
- "id": "volt",
126
- "name": "VOLT",
127
- "description": "The Star Guardians electric systems specialist and rapid responder.",
85
+ "id": "vixen",
86
+ "name": "VIXEN",
87
+ "description": "The Star Guardians strategist and precision planner.",
128
88
  "voice_stack_id": "english-default",
129
89
  "default_engine": "elevenlabs",
130
- "elevenlabs_agent_id": "agent_4101ky97n2pze4p8ax3dgb3hbc3c",
90
+ "elevenlabs_agent_id": "agent_6701ky0nfavke0ysrpj5v38m1cry",
131
91
  "elevenlabs_branch_id": null,
132
- "robovoice_profile_id": "volt",
92
+ "robovoice_profile_id": "vixen",
133
93
  "system_prompt_revision": 1,
134
- "nx": 0.24,
135
- "ny": 0.62,
136
- "img": "/static/robopark/volt.png"
94
+ "nx": 0.58,
95
+ "ny": 0.66,
96
+ "img": "/static/robopark/bmw.png"
137
97
  }
138
98
  ]
139
99
  }
@@ -1,33 +1,33 @@
1
- #!/bin/bash
2
- # Installs the RoboPark robot supervisor as a systemd service on a real
3
- # robot (Raspberry Pi or similar). Auto-starts on boot, restarts on crash.
4
- set -euo pipefail
5
-
6
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7
- SUPERVISOR_DIR="$(dirname "$SCRIPT_DIR")"
8
- UNIT_SRC="$SCRIPT_DIR/robopark-supervisor.service"
9
- UNIT_DST="/etc/systemd/system/robopark-supervisor.service"
10
-
11
- if [ ! -f "$SUPERVISOR_DIR/robot_supervisor.py" ]; then
12
- echo "robot_supervisor.py not found in $SUPERVISOR_DIR" >&2
13
- exit 1
14
- fi
15
-
16
- if [ ! -f "$HOME/.robopark/supervisor.json" ]; then
17
- echo "No ~/.robopark/supervisor.json yet — copy supervisor.example.json there and edit it first:" >&2
18
- echo " cp $SUPERVISOR_DIR/supervisor.example.json ~/.robopark/supervisor.json" >&2
19
- exit 1
20
- fi
21
-
22
- echo "Installing systemd unit -> $UNIT_DST (requires sudo)"
23
- sudo sed \
24
- -e "s#WorkingDirectory=.*#WorkingDirectory=$SUPERVISOR_DIR#" \
25
- -e "s#User=.*#User=$(whoami)#" \
26
- "$UNIT_SRC" | sudo tee "$UNIT_DST" > /dev/null
27
-
28
- sudo systemctl daemon-reload
29
- sudo systemctl enable robopark-supervisor.service
30
- sudo systemctl restart robopark-supervisor.service
31
-
32
- echo "Installed and started. Check status: sudo systemctl status robopark-supervisor.service"
33
- echo "Logs: journalctl -u robopark-supervisor.service -f (or ~/.robopark/logs/*.log per-service)"
1
+ #!/bin/bash
2
+ # Installs the RoboPark robot supervisor as a systemd service on a real
3
+ # robot (Raspberry Pi or similar). Auto-starts on boot, restarts on crash.
4
+ set -euo pipefail
5
+
6
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7
+ SUPERVISOR_DIR="$(dirname "$SCRIPT_DIR")"
8
+ UNIT_SRC="$SCRIPT_DIR/robopark-supervisor.service"
9
+ UNIT_DST="/etc/systemd/system/robopark-supervisor.service"
10
+
11
+ if [ ! -f "$SUPERVISOR_DIR/robot_supervisor.py" ]; then
12
+ echo "robot_supervisor.py not found in $SUPERVISOR_DIR" >&2
13
+ exit 1
14
+ fi
15
+
16
+ if [ ! -f "$HOME/.robopark/supervisor.json" ]; then
17
+ echo "No ~/.robopark/supervisor.json yet — copy supervisor.example.json there and edit it first:" >&2
18
+ echo " cp $SUPERVISOR_DIR/supervisor.example.json ~/.robopark/supervisor.json" >&2
19
+ exit 1
20
+ fi
21
+
22
+ echo "Installing systemd unit -> $UNIT_DST (requires sudo)"
23
+ sudo sed \
24
+ -e "s#WorkingDirectory=.*#WorkingDirectory=$SUPERVISOR_DIR#" \
25
+ -e "s#User=.*#User=$(whoami)#" \
26
+ "$UNIT_SRC" | sudo tee "$UNIT_DST" > /dev/null
27
+
28
+ sudo systemctl daemon-reload
29
+ sudo systemctl enable robopark-supervisor.service
30
+ sudo systemctl restart robopark-supervisor.service
31
+
32
+ echo "Installed and started. Check status: sudo systemctl status robopark-supervisor.service"
33
+ echo "Logs: journalctl -u robopark-supervisor.service -f (or ~/.robopark/logs/*.log per-service)"
@@ -1,18 +1,22 @@
1
1
  <!doctype html>
2
2
  <html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
3
- <meta name="theme-color" content="#080704"><meta name="robopark-voice-join-build" content="production-native-microphone-20260725-8">
3
+ <meta name="theme-color" content="#080704"><meta name="robopark-voice-join-build" content="production-motion-greeting-20260728-1">
4
4
  <title>RoboPark Character Conversation</title>
5
5
  <style>
6
6
  .deployment{display:grid;gap:8px;margin:14px 0}.deployment-row{display:grid;grid-template-columns:minmax(150px,1fr) 2fr auto;gap:9px;align-items:center;padding:.7rem;border:1px solid var(--line);border-radius:12px;background:rgba(255,255,255,.025)}.deployment-row b{font:.62rem var(--mono);color:var(--pale)}.deployment-row span{font:.54rem var(--mono);color:var(--muted)}.deployment-row .ready{color:var(--ok)}.deployment-actions{display:flex;flex-wrap:wrap;gap:5px}.deployment-actions button{border:1px solid var(--line);border-radius:8px;background:rgba(244,207,114,.09);color:var(--gold);padding:.38rem .5rem;font:.5rem var(--mono);cursor:pointer}
7
7
  .audio-controls,.call-controls{position:relative;width:min(440px,94%);margin-top:12px;border:1px solid var(--line);border-radius:14px;background:rgba(0,0,0,.3);text-align:left}.audio-controls summary,.call-controls summary{padding:.65rem .8rem;cursor:pointer;color:var(--gold);font:600 .58rem var(--mono);letter-spacing:.07em;text-transform:uppercase}.audio-controls summary::marker,.call-controls summary::marker{color:var(--gold)}.audio-panel,.call-panel{display:grid;gap:.75rem;padding:0 .85rem .85rem}.audio-row{display:grid;grid-template-columns:105px 1fr 45px;gap:.55rem;align-items:center}.audio-row label,.audio-value,.call-row label{font:600 .54rem var(--mono);color:var(--muted)}.audio-value{text-align:right;color:var(--pale)}.audio-row input[type=range]{width:100%;accent-color:var(--gold)}.audio-note,.call-note{margin:0;color:#7e755f;font:.5rem/1.45 var(--mono)}.call-row{display:grid;gap:.3rem}.call-row select{width:100%;min-width:0;border:1px solid var(--line);border-radius:9px;background:#090805;color:var(--ink);padding:.52rem;font:600 .55rem var(--mono)}.call-actions{display:grid;grid-template-columns:1fr 1fr;gap:7px}.call-actions button{border:1px solid var(--line);border-radius:9px;background:rgba(244,207,114,.1);color:var(--gold);padding:.52rem;font:600 .52rem var(--mono);cursor:pointer}.call-actions button.primary{background:linear-gradient(135deg,#f5d77f,#b87b1d);color:#171005}.call-status.ok{color:var(--ok)}.call-status.bad{color:var(--bad)}.mode-badge{display:inline-flex;align-items:center;gap:.35rem;padding:.28rem .48rem;border:1px solid var(--line);border-radius:999px;color:var(--muted);font:600 .5rem var(--mono)}.mode-badge.production{color:var(--ok);border-color:rgba(100,223,160,.4)}
8
- :root{--bg:#080704;--panel:rgba(20,17,11,.88);--line:rgba(247,211,116,.22);--gold:#f4cf72;--pale:#fff0bd;--ink:#f7f1df;--muted:#a99e84;--ok:#64dfa0;--warn:#f0bd55;--bad:#ff776c;--mono:"JetBrains Mono","Cascadia Code",monospace;--display:Georgia,"Times New Roman",serif}*{box-sizing:border-box}html,body{margin:0;min-height:100%;background:radial-gradient(circle at 50% 8%,#332510 0,#100d08 35%,#050403 78%);color:var(--ink);font-family:Verdana,Geneva,sans-serif}body:before{content:"";position:fixed;inset:0;pointer-events:none;background:linear-gradient(115deg,transparent 0 48%,rgba(244,207,114,.025) 49% 50%,transparent 51%),repeating-linear-gradient(0deg,transparent 0 34px,rgba(255,255,255,.012) 35px)}button,input{font:inherit}.shell{width:min(1120px,100%);min-height:100vh;margin:auto;padding:clamp(14px,3vw,34px);display:grid;grid-template-rows:auto 1fr auto;gap:18px}.top{display:flex;align-items:center;justify-content:space-between;gap:16px}.brand{font:700 .65rem var(--mono);letter-spacing:.18em;text-transform:uppercase;color:var(--gold)}.ops-toggle{border:1px solid var(--line);border-radius:999px;background:rgba(0,0,0,.35);color:var(--muted);padding:.55rem .8rem;cursor:pointer}.ops-toggle.on{color:var(--pale);border-color:var(--gold)}.stage{position:relative;min-height:620px;display:grid;grid-template-columns:minmax(0,1.3fr) minmax(280px,.7fr);overflow:hidden;border:1px solid var(--line);border-radius:28px;background:linear-gradient(145deg,rgba(34,27,14,.82),rgba(8,7,5,.92));box-shadow:0 28px 90px rgba(0,0,0,.45)}.hero{position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:34px;text-align:center;overflow:hidden}.hero:before{content:"";position:absolute;width:500px;height:500px;border:1px solid rgba(244,207,114,.1);border-radius:50%;box-shadow:0 0 0 55px rgba(244,207,114,.018),0 0 0 120px rgba(244,207,114,.012)}.avatar-wrap{position:relative;width:min(330px,72vw);aspect-ratio:1;display:grid;place-items:center;border:1px solid rgba(244,207,114,.3);border-radius:50%;background:radial-gradient(circle,rgba(244,207,114,.16),rgba(8,7,5,.62) 58%,#050403 74%);box-shadow:0 0 90px rgba(214,164,55,.16)}.avatar-wrap.live{animation:breathe 2.2s ease-in-out infinite}.avatar-wrap.speaking{box-shadow:0 0 110px rgba(244,207,114,.42);border-color:var(--pale)}@keyframes breathe{50%{transform:scale(1.018)}}.avatar{position:relative;z-index:2;width:78%;height:78%;object-fit:contain;filter:drop-shadow(0 20px 28px rgba(0,0,0,.58))}.fallback{position:absolute;font:700 4rem var(--display);color:var(--gold)}h1{position:relative;margin:22px 0 5px;font:700 clamp(2rem,5vw,3.5rem) var(--display);color:var(--pale)}.subtitle{position:relative;color:var(--muted);font-size:.8rem}.state{position:relative;margin-top:14px;font:600 .68rem var(--mono);letter-spacing:.08em;text-transform:uppercase;color:var(--gold)}.join{position:relative;margin-top:22px;min-width:210px;border:1px solid var(--gold);border-radius:999px;padding:.9rem 1.25rem;background:linear-gradient(135deg,#f5d77f,#b87b1d);color:#171005;font-weight:800;cursor:pointer;box-shadow:0 12px 30px rgba(184,123,29,.24)}.join:disabled{opacity:.48;cursor:not-allowed}.end{position:relative;margin-top:10px;border:0;background:transparent;color:var(--muted);cursor:pointer}.side{position:relative;border-left:1px solid var(--line);padding:25px;display:flex;flex-direction:column;gap:14px;background:rgba(0,0,0,.2)}.vision{position:relative;aspect-ratio:16/10;border:1px solid var(--line);border-radius:17px;overflow:hidden;background:#050403}.vision img{width:100%;height:100%;object-fit:cover}.vision .label{position:absolute;left:9px;top:9px;padding:.32rem .45rem;border-radius:7px;background:rgba(0,0,0,.7);font:600 .5rem var(--mono);color:var(--gold)}.transcript{flex:1;min-height:220px;max-height:360px;overflow:auto;padding:.65rem;border:1px solid var(--line);border-radius:15px;background:rgba(0,0,0,.22)}.turn{margin:.45rem 0;padding:.55rem .65rem;border-radius:11px;font-size:.75rem;line-height:1.45}.turn.user{margin-left:13%;background:rgba(244,207,114,.12);border:1px solid rgba(244,207,114,.18)}.turn.assistant{margin-right:13%;background:rgba(255,255,255,.045)}.compose{display:flex;gap:8px}.compose input{min-width:0;flex:1;border:1px solid var(--line);border-radius:11px;background:#090805;color:var(--ink);padding:.7rem}.compose button{border:1px solid var(--line);border-radius:11px;background:rgba(244,207,114,.12);color:var(--gold);padding:.7rem;cursor:pointer}.ops{display:none;position:absolute;inset:12px;z-index:8;padding:16px;border:1px solid rgba(244,207,114,.35);border-radius:18px;background:rgba(5,4,3,.94);backdrop-filter:blur(16px);overflow:auto}.ops.open{display:block}.ops-head{display:flex;align-items:center;justify-content:space-between}.ops h2{margin:0;font:700 1.2rem var(--display);color:var(--pale)}.close{border:1px solid var(--line);border-radius:9px;background:transparent;color:var(--muted);padding:.4rem .55rem;cursor:pointer}.facts{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:8px;margin:14px 0}.fact{padding:.7rem;border:1px solid var(--line);border-radius:12px;background:rgba(255,255,255,.025)}.fact span{display:block;color:var(--muted);font:500 .49rem var(--mono);text-transform:uppercase;letter-spacing:.11em}.fact b{display:block;margin-top:.3rem;color:var(--ink);font:600 .64rem var(--mono);word-break:break-all}.pipeline{display:grid;grid-template-columns:repeat(5,1fr);gap:7px;margin:15px 0}.step{padding:.65rem .3rem;border:1px solid var(--line);border-radius:11px;text-align:center;color:var(--muted);font:600 .52rem var(--mono);text-transform:uppercase}.step.done{color:var(--ok);border-color:rgba(100,223,160,.4)}.step.live{color:#171005;border-color:var(--gold);background:var(--gold);box-shadow:0 0 24px rgba(244,207,114,.25)}.step.bad{color:var(--bad);border-color:rgba(255,119,108,.5)}.events{display:grid;gap:7px}.event{display:grid;grid-template-columns:95px 70px 1fr;gap:8px;padding:.6rem;border-bottom:1px solid var(--line);font:500 .55rem var(--mono)}.event .ok{color:var(--ok)}.event .error,.event .failed{color:var(--bad)}.foot{text-align:center;color:#716956;font:500 .52rem var(--mono);letter-spacing:.08em}.error-box{display:none;margin-top:12px;color:var(--bad);font:.65rem var(--mono)}@media(max-width:780px){.shell{padding:10px}.stage{grid-template-columns:1fr;min-height:0}.hero{min-height:600px;padding:24px 15px}.side{border-left:0;border-top:1px solid var(--line)}.pipeline{grid-template-columns:1fr 1fr}.top{padding:4px}.event{grid-template-columns:75px 55px 1fr}}
8
+ :root{--bg:#080704;--panel:rgba(20,17,11,.88);--line:rgba(247,211,116,.22);--gold:#f4cf72;--pale:#fff0bd;--ink:#f7f1df;--muted:#a99e84;--ok:#64dfa0;--warn:#f0bd55;--bad:#ff776c;--mono:"JetBrains Mono","Cascadia Code",monospace;--display:Georgia,"Times New Roman",serif}*{box-sizing:border-box}html,body{margin:0;min-height:100%;background:radial-gradient(circle at 50% 8%,#332510 0,#100d08 35%,#050403 78%);color:var(--ink);font-family:Verdana,Geneva,sans-serif}body:before{content:"";position:fixed;inset:0;pointer-events:none;background:linear-gradient(115deg,transparent 0 48%,rgba(244,207,114,.025) 49% 50%,transparent 51%),repeating-linear-gradient(0deg,transparent 0 34px,rgba(255,255,255,.012) 35px)}button,input{font:inherit}.shell{width:min(1120px,100%);min-height:100vh;margin:auto;padding:clamp(14px,3vw,34px);display:grid;grid-template-rows:auto 1fr auto;gap:18px}.top{display:flex;align-items:center;justify-content:space-between;gap:16px}.brand{font:700 .65rem var(--mono);letter-spacing:.18em;text-transform:uppercase;color:var(--gold)}.ops-toggle{border:1px solid var(--line);border-radius:999px;background:rgba(0,0,0,.35);color:var(--muted);padding:.55rem .8rem;cursor:pointer}.ops-toggle.on{color:var(--pale);border-color:var(--gold)}.stage{position:relative;min-height:620px;display:grid;grid-template-columns:minmax(0,1.3fr) minmax(280px,.7fr);overflow:hidden;border:1px solid var(--line);border-radius:28px;background:linear-gradient(145deg,rgba(34,27,14,.82),rgba(8,7,5,.92));box-shadow:0 28px 90px rgba(0,0,0,.45)}.hero{position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:34px;text-align:center;overflow:hidden}.hero:before{content:"";position:absolute;width:500px;height:500px;border:1px solid rgba(244,207,114,.1);border-radius:50%;box-shadow:0 0 0 55px rgba(244,207,114,.018),0 0 0 120px rgba(244,207,114,.012)}.avatar-wrap{position:relative;width:min(330px,72vw);aspect-ratio:1;display:grid;place-items:center;border:1px solid rgba(244,207,114,.3);border-radius:50%;background:radial-gradient(circle,rgba(244,207,114,.16),rgba(8,7,5,.62) 58%,#050403 74%);box-shadow:0 0 90px rgba(214,164,55,.16)}.avatar-wrap.live{animation:breathe 2.2s ease-in-out infinite}.avatar-wrap.speaking{box-shadow:0 0 110px rgba(244,207,114,.42);border-color:var(--pale)}@keyframes breathe{50%{transform:scale(1.018)}}.avatar{position:relative;z-index:2;width:78%;height:78%;object-fit:contain;filter:drop-shadow(0 20px 28px rgba(0,0,0,.58))}.fallback{position:absolute;font:700 4rem var(--display);color:var(--gold)}h1{position:relative;margin:22px 0 5px;font:700 clamp(2rem,5vw,3.5rem) var(--display);color:var(--pale)}.subtitle{position:relative;color:var(--muted);font-size:.8rem}.state{position:relative;margin-top:14px;font:600 .68rem var(--mono);letter-spacing:.08em;text-transform:uppercase;color:var(--gold)}.join{position:relative;margin-top:22px;min-width:210px;border:1px solid var(--gold);border-radius:999px;padding:.9rem 1.25rem;background:linear-gradient(135deg,#f5d77f,#b87b1d);color:#171005;font-weight:800;cursor:pointer;box-shadow:0 12px 30px rgba(184,123,29,.24)}.join:disabled{opacity:.48;cursor:not-allowed}.end{position:relative;margin-top:10px;border:0;background:transparent;color:var(--muted);cursor:pointer}.side{position:relative;border-left:1px solid var(--line);padding:25px;display:flex;flex-direction:column;gap:14px;background:rgba(0,0,0,.2)}.vision{position:relative;aspect-ratio:16/10;border:1px solid var(--line);border-radius:17px;overflow:hidden;background:#050403}.vision img,.vision video{width:100%;height:100%;object-fit:contain;background:#050403}.vision .label{position:absolute;left:9px;top:9px;padding:.32rem .45rem;border-radius:7px;background:rgba(0,0,0,.7);font:600 .5rem var(--mono);color:var(--gold)}.transcript{flex:1;min-height:220px;max-height:360px;overflow:auto;padding:.65rem;border:1px solid var(--line);border-radius:15px;background:rgba(0,0,0,.22)}.turn{margin:.45rem 0;padding:.55rem .65rem;border-radius:11px;font-size:.75rem;line-height:1.45}.turn.user{margin-left:13%;background:rgba(244,207,114,.12);border:1px solid rgba(244,207,114,.18)}.turn.assistant{margin-right:13%;background:rgba(255,255,255,.045)}.compose{display:flex;gap:8px}.compose input{min-width:0;flex:1;border:1px solid var(--line);border-radius:11px;background:#090805;color:var(--ink);padding:.7rem}.compose button{border:1px solid var(--line);border-radius:11px;background:rgba(244,207,114,.12);color:var(--gold);padding:.7rem;cursor:pointer}.ops{display:none;position:absolute;inset:12px;z-index:8;padding:16px;border:1px solid rgba(244,207,114,.35);border-radius:18px;background:rgba(5,4,3,.94);backdrop-filter:blur(16px);overflow:auto}.ops.open{display:block}.ops-head{display:flex;align-items:center;justify-content:space-between}.ops h2{margin:0;font:700 1.2rem var(--display);color:var(--pale)}.close{border:1px solid var(--line);border-radius:9px;background:transparent;color:var(--muted);padding:.4rem .55rem;cursor:pointer}.facts{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:8px;margin:14px 0}.fact{padding:.7rem;border:1px solid var(--line);border-radius:12px;background:rgba(255,255,255,.025)}.fact span{display:block;color:var(--muted);font:500 .49rem var(--mono);text-transform:uppercase;letter-spacing:.11em}.fact b{display:block;margin-top:.3rem;color:var(--ink);font:600 .64rem var(--mono);word-break:break-all}.pipeline{display:grid;grid-template-columns:repeat(5,1fr);gap:7px;margin:15px 0}.step{padding:.65rem .3rem;border:1px solid var(--line);border-radius:11px;text-align:center;color:var(--muted);font:600 .52rem var(--mono);text-transform:uppercase}.step.done{color:var(--ok);border-color:rgba(100,223,160,.4)}.step.live{color:#171005;border-color:var(--gold);background:var(--gold);box-shadow:0 0 24px rgba(244,207,114,.25)}.step.bad{color:var(--bad);border-color:rgba(255,119,108,.5)}.events{display:grid;gap:7px}.event{display:grid;grid-template-columns:95px 70px 1fr;gap:8px;padding:.6rem;border-bottom:1px solid var(--line);font:500 .55rem var(--mono)}.event .ok{color:var(--ok)}.event .error,.event .failed{color:var(--bad)}.foot{text-align:center;color:#716956;font:500 .52rem var(--mono);letter-spacing:.08em}.error-box{display:none;margin-top:12px;color:var(--bad);font:.65rem var(--mono)}@media(max-width:780px){.shell{padding:10px}.stage{grid-template-columns:1fr;min-height:0}.hero{min-height:600px;padding:24px 15px}.side{border-left:0;border-top:1px solid var(--line)}.pipeline{grid-template-columns:1fr 1fr}.top{padding:4px}.event{grid-template-columns:75px 55px 1fr}}
9
9
  </style></head><body><main class="shell"><header class="top"><div class="brand">RoboPark / managed character channel</div><div><button class="ops-toggle on" id="always-on" aria-pressed="true" hidden>Always on: ON</button> <button class="ops-toggle" id="ops-toggle">Operations</button></div></header><section class="stage"><div class="hero"><div class="avatar-wrap" id="avatar-wrap"><div class="fallback" id="fallback">RP</div><img class="avatar" id="avatar" alt=""></div><h1 id="character-name">Loading character...</h1><div class="subtitle" id="subtitle">Secure ElevenLabs conversation managed by RoboPark</div><div class="state" id="state">validating link</div><button class="join" id="join" disabled>Join conversation</button><button class="end" id="end" hidden>End conversation</button><div class="error-box" id="error"></div></div><aside class="side"><div class="vision" id="vision" hidden><img id="robot-video" alt="Live robot camera"><span class="label">LIVE ROBOT VISION</span></div><div class="transcript" id="transcript"><div class="turn assistant">The transcript will appear here when the conversation begins.</div></div><form class="compose" id="compose"><input id="message" placeholder="Type a message" disabled><button id="send" disabled>Send</button></form></aside><section class="ops" id="ops"><div class="ops-head"><h2>Live management overlay</h2><button class="close" id="ops-close">Close</button></div><div class="facts"><div class="fact"><span>Session</span><b id="f-session">not started</b></div><div class="fact"><span>Character</span><b id="f-character">--</b></div><div class="fact"><span>Agent</span><b id="f-agent">--</b></div><div class="fact"><span>Branch</span><b id="f-branch">default</b></div><div class="fact"><span>Robot</span><b id="f-robot">operator only</b></div><div class="fact"><span>Share link</span><b id="f-share">--</b></div></div><div class="pipeline" id="pipeline"><div class="step live" data-stage="configured">configured</div><div class="step" data-stage="permission">permission</div><div class="step" data-stage="signed_session">signed</div><div class="step" data-stage="connected">connected</div><div class="step" data-stage="conversation">conversation</div></div><div class="events" id="events"><div class="event"><span>waiting</span><span>info</span><span>Session telemetry will stream here.</span></div></div></section></section><footer class="foot">No API credentials are stored in this link. Each visit creates an isolated signed session.</footer></main><script src="/vendor/elevenlabs-client.iife.js"></script><script>
10
10
  (function(){
11
+ var API_BASE='';
11
12
  var deployment=document.createElement('div');deployment.id='deployment';deployment.className='deployment';deployment.innerHTML='<div class="deployment-row"><b>Character hardware</b><span>Loading registered voice, vision, and motor nodes...</span><span></span></div>';document.getElementById('pipeline').before(deployment);
12
13
  var audioControls=document.createElement('details');audioControls.id='audio-controls';audioControls.className='audio-controls';audioControls.innerHTML='<summary>Audio levels</summary><div class="audio-panel"><div class="audio-row"><label for="speaker-volume">Speaker</label><input id="speaker-volume" type="range" min="0" max="100" step="1"><output class="audio-value" id="speaker-value"></output></div><div class="audio-row"><label for="mic-sensitivity">Microphone</label><input id="mic-sensitivity" type="range" min="0" max="100" step="1"><output class="audio-value" id="mic-value"></output></div><p class="audio-note">Changes apply immediately and stay on this browser.</p></div>';document.getElementById('error').after(audioControls);
13
14
  var callControls=document.createElement('details');callControls.id='call-controls';callControls.className='call-controls';callControls.open=true;callControls.innerHTML='<summary>Microphone and speaker</summary><div class=call-panel id=call-panel></div>';audioControls.open=true;audioControls.after(callControls);
14
15
  var q=new URLSearchParams(location.search),character=q.get('character')||'',agent=q.get('agent_id')||'',branch=q.get('branch_id')||'',mode=q.get('mode')==='production'?'production':'test',robot=mode==='production'?(q.get('robot')||character):'',stack=q.get('stack')||'',share=q.get('share_id')||'',session=null,eventToken=null,conversation=null,conversationId=null,keepalive=null,poller=null,retryTimer=null,cameraPublishRetry=null,ending=false,desired=false,alwaysOn=false,connecting=false,generation=0,retryAttempt=0,lastProviderError=null,history=[],seen={},micPipelines=[],micAudioContext=null,localVideoStream=null,activeCameraDevice=null,cameraRoom=null,cameraSdkPromise=null,speakerVolume=storedNumber('robopark.voice.speaker',100,0,100),micSensitivity=storedNumber('robopark.voice.micSensitivity',100,0,100),inputDeviceId='',cameraDeviceId='',outputDeviceId='';
15
- (function(){var old=document.getElementById('robot-video'),video=document.createElement('video');video.id='robot-video';video.autoplay=true;video.muted=true;video.playsInline=true;if(old&&old.parentNode)old.parentNode.replaceChild(video,old)})();
16
+ // The panel carries two sources: a <video> for local/LiveKit camera tracks and the
17
+ // original <img> for the robot MJPEG feed, which a <video> element cannot decode.
18
+ // Both stay mounted so the ambient vision sampler can read whichever is showing.
19
+ (function(){var old=document.getElementById('robot-video'),video=document.createElement('video');video.id='robot-video';video.autoplay=true;video.muted=true;video.playsInline=true;if(old&&old.parentNode){old.id='robot-stream';old.hidden=true;old.parentNode.insertBefore(video,old)}})();
16
20
  function storedNumber(key,fallback,min,max){try{var raw=localStorage.getItem(key);if(raw===null)return fallback;var value=Number(raw);return Number.isFinite(value)?Math.max(min,Math.min(max,value)):fallback}catch(_){return fallback}}
17
21
  function persistNumber(key,value){try{localStorage.setItem(key,String(value))}catch(_){}}
18
22
  function applySpeakerVolume(){if(conversation&&typeof conversation.setVolume==='function')try{conversation.setVolume({volume:speakerVolume/100})}catch(_){}}
@@ -40,11 +44,76 @@
40
44
  function refreshHardware(requestPermission){if(!navigator.mediaDevices||!navigator.mediaDevices.enumerateDevices){setHardwareStatus('Media device selection is unavailable in this browser.','bad');return Promise.reject(new Error('media devices unavailable'))}var permission=Promise.resolve();if(requestPermission){permission=navigator.mediaDevices.getUserMedia({audio:true,video:true}).catch(function(){return navigator.mediaDevices.getUserMedia({audio:true})}).then(function(stream){stream.getTracks().forEach(function(track){track.stop()})})}setHardwareStatus(requestPermission?'Requesting browser media access...':'Scanning browser hardware...','');return permission.then(function(){return navigator.mediaDevices.enumerateDevices()}).then(function(devices){fillHardwareSelect('call-microphone',devices,'audioinput','microphone');fillHardwareSelect('call-camera',devices,'videoinput','camera');fillHardwareSelect('call-speaker',devices,'audiooutput','speaker');var mic=devices.filter(function(device){return device.kind==='audioinput'}).length,cam=devices.filter(function(device){return device.kind==='videoinput'}).length,out=devices.filter(function(device){return device.kind==='audiooutput'}).length;setHardwareStatus('Ready: '+mic+' microphone(s), '+cam+' camera(s), '+out+' speaker output(s).','ok');return devices}).catch(function(error){setHardwareStatus(error&&error.message||String(error),'bad');throw error})}
41
45
  function ensureCameraSdk(){if(window.LivekitClient)return Promise.resolve(window.LivekitClient);if(cameraSdkPromise)return cameraSdkPromise;cameraSdkPromise=new Promise(function(resolve,reject){var script=document.createElement('script');script.src='/vendor/livekit-client.umd.js';script.onload=function(){window.LivekitClient?resolve(window.LivekitClient):reject(new Error('LiveKit camera SDK unavailable'))};script.onerror=function(){reject(new Error('LiveKit camera SDK failed to load'))};document.head.appendChild(script)});return cameraSdkPromise}
42
46
  function scheduleCameraPublishRetry(){if(cameraPublishRetry||ending||!localVideoStream)return;cameraPublishRetry=setTimeout(function(){cameraPublishRetry=null;publishLocalVideo().catch(function(){})},3000)}
43
- function publishLocalVideo(){if(!localVideoStream||!localVideoStream.getVideoTracks().length)return Promise.resolve();if(cameraRoom)return Promise.resolve();return Promise.all([ensureCameraSdk(),fetch('/api/robopark/camera-token',{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify({character_id:character,robot_id:robot||null})}).then(function(response){if(!response.ok)throw new Error('camera token '+response.status);return response.json()})]).then(function(parts){var LK=parts[0],info=parts[1],room=new LK.Room();cameraRoom=room;room.on(LK.RoomEvent.Disconnected,function(){if(cameraRoom===room){cameraRoom=null;scheduleCameraPublishRetry()}});return room.connect(info.url,info.token).then(function(){return room.localParticipant.publishTrack(localVideoStream.getVideoTracks()[0],{name:'camera',source:LK.Track.Source.Camera})}).then(function(){if(cameraPublishRetry){clearTimeout(cameraPublishRetry);cameraPublishRetry=null}event('camera_published','ok','Camera relay live',{})})}).catch(function(error){cameraRoom=null;event('camera_published','failed',error.message,{});scheduleCameraPublishRetry();throw error})}
44
- function startLocalVideo(){if(!navigator.mediaDevices||!navigator.mediaDevices.getUserMedia)return Promise.resolve();if(localVideoStream&&activeCameraDevice===cameraDeviceId)return publishLocalVideo();if(localVideoStream)localVideoStream.getTracks().forEach(function(track){track.stop()});if(cameraRoom){cameraRoom.disconnect();cameraRoom=null}var video=cameraDeviceId?{deviceId:{exact:cameraDeviceId}}:true;return navigator.mediaDevices.getUserMedia({video:video,audio:false}).then(function(stream){localVideoStream=stream;activeCameraDevice=cameraDeviceId;el('robot-video').srcObject=stream;el('vision').hidden=false;return publishLocalVideo().catch(function(){})}).catch(function(error){localVideoStream=null;activeCameraDevice=null;el('robot-video').srcObject=null;el('vision').hidden=true;throw error})}
47
+ function publishLocalVideo(){if(!localVideoStream||!localVideoStream.getVideoTracks().length)return Promise.resolve();if(cameraRoom)return Promise.resolve();return Promise.all([ensureCameraSdk(),fetch(API_BASE+'/api/camera-token',{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify({character_id:character,robot_id:robot||null})}).then(function(response){if(!response.ok)throw new Error('camera token '+response.status);return response.json()})]).then(function(parts){var LK=parts[0],info=parts[1],room=new LK.Room();cameraRoom=room;room.on(LK.RoomEvent.Disconnected,function(){if(cameraRoom===room){cameraRoom=null;scheduleCameraPublishRetry()}});return room.connect(info.url,info.token).then(function(){return room.localParticipant.publishTrack(localVideoStream.getVideoTracks()[0],{name:'camera',source:LK.Track.Source.Camera})}).then(function(){if(cameraPublishRetry){clearTimeout(cameraPublishRetry);cameraPublishRetry=null}event('camera_published','ok','Camera relay live',{})})}).catch(function(error){cameraRoom=null;event('camera_published','failed',error.message,{});scheduleCameraPublishRetry();throw error})}
48
+ function startLocalVideo(){if(!navigator.mediaDevices||!navigator.mediaDevices.getUserMedia)return Promise.resolve();if(localVideoStream&&activeCameraDevice===cameraDeviceId)return publishLocalVideo();if(localVideoStream)localVideoStream.getTracks().forEach(function(track){track.stop()});if(cameraRoom){cameraRoom.disconnect();cameraRoom=null}var video=cameraDeviceId?{deviceId:{exact:cameraDeviceId}}:true;return navigator.mediaDevices.getUserMedia({video:video,audio:false}).then(function(stream){localVideoStream=stream;activeCameraDevice=cameraDeviceId;el('robot-video').srcObject=stream;if(!robotStreamActive())el('robot-video').hidden=false;el('vision').hidden=false;return publishLocalVideo().catch(function(){})}).catch(function(error){localVideoStream=null;activeCameraDevice=null;el('robot-video').srcObject=null;if(!robotStreamActive()){el('robot-video').hidden=true;el('vision').hidden=true}throw error})}
49
+ function robotStreamActive(){var stream=el('robot-stream');return Boolean(stream&&stream.getAttribute('src'))}
50
+ // Ambient vision. The ElevenLabs contextual_update channel carries text only, so a
51
+ // frame is captioned server-side and the sentence is injected silently — a real
52
+ // image would have to go through sendMultimodalMessage, which speaks as the user
53
+ // and would make the agent answer itself every poll. Frames are only sent when the
54
+ // scene actually changes, so a static view does not grow the agent prompt by a
55
+ // caption every 10s.
56
+ var visionTimer=null,visionCanvas=null,visionProbe=null,visionHash=null,visionLastSent=0,visionBusy=false,visionFailures=0;
57
+ function visionSource(){var stream=el('robot-stream'),video=el('robot-video');if(stream&&!stream.hidden&&stream.naturalWidth)return stream;if(video&&!video.hidden&&video.videoWidth)return video;return null}
58
+ function visionCapture(){var source=visionSource();if(!source)return false;var width=source.videoWidth||source.naturalWidth,height=source.videoHeight||source.naturalHeight;if(!width||!height)return false;if(!visionCanvas)visionCanvas=document.createElement('canvas');var scale=Math.min(1,512/Math.max(width,height));visionCanvas.width=Math.max(32,Math.round(width*scale));visionCanvas.height=Math.max(32,Math.round(height*scale));try{visionCanvas.getContext('2d').drawImage(source,0,0,visionCanvas.width,visionCanvas.height);return true}catch(_){return false}}
59
+ function visionSignature(){if(!visionProbe)visionProbe=document.createElement('canvas');visionProbe.width=8;visionProbe.height=8;var ctx=visionProbe.getContext('2d');try{ctx.drawImage(visionCanvas,0,0,8,8);var data=ctx.getImageData(0,0,8,8).data}catch(_){return null}var levels=[],total=0,i;for(i=0;i<64;i++){var level=(data[i*4]*299+data[i*4+1]*587+data[i*4+2]*114)/1000;levels.push(level);total+=level}var mean=total/64,hash=[];for(i=0;i<64;i++)hash.push(levels[i]>mean?1:0);return hash}
60
+ function visionChanged(hash){if(!visionHash)return true;for(var i=0,distance=0;i<64;i++){if(hash[i]!==visionHash[i])distance++;if(distance>=6)return true}return false}
61
+ function visionTick(myGeneration){
62
+ if(visionBusy||ending||!conversation||myGeneration!==generation)return;
63
+ if(!visionCapture())return;
64
+ var hash=visionSignature();if(!hash)return;
65
+ // A still scene is still refreshed every 45s so the agent's picture of the room
66
+ // never silently ages out of the conversation.
67
+ if(!visionChanged(hash)&&Date.now()-visionLastSent<45000)return;
68
+ visionBusy=true;
69
+ visionCanvas.toBlob(function(blob){
70
+ if(!blob){visionBusy=false;return}
71
+ fetch(API_BASE+'/api/vision/caption',{method:'POST',headers:{'content-type':'image/jpeg','x-robopark-character':character||''},body:blob}).then(function(response){if(!response.ok)return response.text().then(function(body){throw new Error(body||('caption '+response.status))});return response.json()}).then(function(data){
72
+ visionBusy=false;visionFailures=0;
73
+ var caption=String(data&&data.caption||'').trim();
74
+ if(!caption||!conversation||ending||myGeneration!==generation)return;
75
+ visionHash=hash;visionLastSent=Date.now();
76
+ try{conversation.sendContextualUpdate('[live camera] '+caption)}catch(_){return}
77
+ event('vision_context','ok',caption,{model:data.model||'',elapsed_ms:data.elapsed_ms||0})
78
+ }).catch(function(error){
79
+ visionBusy=false;visionFailures++;
80
+ if(visionFailures>=3){event('vision_context','failed','Ambient vision paused: '+(error&&error.message||error),{});stopVision()}
81
+ })
82
+ },'image/jpeg',.6)
83
+ }
84
+ function startVision(myGeneration){stopVision();visionHash=null;visionLastSent=0;visionFailures=0;visionTimer=setInterval(function(){visionTick(myGeneration)},10000);setTimeout(function(){visionTick(myGeneration)},1500)}
85
+ function stopVision(){if(visionTimer){clearInterval(visionTimer);visionTimer=null}visionBusy=false}
86
+ // Motion greeting for production always-on calls. contextual_update is silent but
87
+ // never starts a turn, so the agent is prompted with a user_message carrying a
88
+ // directive; it is suppressed from the visible transcript and the ledger so the
89
+ // visitor only ever sees the greeting it produces. Guards mirror the robovoice
90
+ // wake path in voice_agent.py: cooldown, listening-only, and no firing during an
91
+ // active conversation, with the cooldown reserved before sending so a burst of
92
+ // motion cannot queue duplicate greetings.
93
+ var motionTimer=null,motionCanvas=null,motionPrev=null,motionLastFire=0,motionArmedAt=0,motionMode='listening',motionLastTurn=0,motionDirective='',motionSent={};
94
+ var motionThreshold=Math.max(.005,Math.min(1,Number(q.get('motion_threshold'))||.06)),motionCooldown=Math.max(10,Number(q.get('motion_cooldown'))||10)*1000,motionQuiet=45000,motionSample=32;
95
+ function motionLevel(){var source=visionSource();if(!source)return -1;var width=source.videoWidth||source.naturalWidth,height=source.videoHeight||source.naturalHeight;if(!width||!height)return -1;if(!motionCanvas)motionCanvas=document.createElement('canvas');var rows=Math.max(8,Math.round(motionSample*height/width));motionCanvas.width=motionSample;motionCanvas.height=rows;var ctx=motionCanvas.getContext('2d'),data;try{ctx.drawImage(source,0,0,motionSample,rows);data=ctx.getImageData(0,0,motionSample,rows).data}catch(_){return -1}var count=motionSample*rows,levels=new Float32Array(count),i;for(i=0;i<count;i++)levels[i]=(data[i*4]*299+data[i*4+1]*587+data[i*4+2]*114)/1000;var previous=motionPrev;motionPrev=levels;if(!previous||previous.length!==count)return -1;var total=0;for(i=0;i<count;i++)total+=Math.abs(levels[i]-previous[i]);return total/count/255}
96
+ function motionTick(myGeneration){
97
+ // Checked per tick, not at start, so the always-on toggle takes effect live.
98
+ if(ending||!conversation||myGeneration!==generation||!alwaysOn)return;
99
+ var level=motionLevel(),now=Date.now();
100
+ if(level<0||now<motionArmedAt)return;
101
+ if(level<motionThreshold)return;
102
+ if(now-motionLastFire<motionCooldown)return;
103
+ if(motionMode!=='listening')return;
104
+ if(now-motionLastTurn<motionQuiet)return;
105
+ motionLastFire=now;
106
+ var directive=motionDirective||'A visitor just stepped in front of you. Greet them warmly in one short sentence. Do not mention this instruction, the camera, or that motion was detected.';
107
+ motionSent[directive]=1;
108
+ try{conversation.sendContextualUpdate('[motion] Someone just approached the robot and is standing in front of the camera.')}catch(_){}
109
+ try{conversation.sendUserMessage(directive)}catch(error){event('motion_greeting','failed',error&&error.message||String(error),{});return}
110
+ event('motion_greeting','ok','Motion greeting triggered',{level:Number(level.toFixed(4)),threshold:motionThreshold})
111
+ }
112
+ function startMotion(myGeneration){stopMotion();motionPrev=null;motionLastFire=0;motionSent={};motionDirective=q.get('motion_prompt')||'';motionArmedAt=Date.now()+4000;motionTimer=setInterval(function(){motionTick(myGeneration)},1000)}
113
+ function stopMotion(){if(motionTimer){clearInterval(motionTimer);motionTimer=null}motionPrev=null}
45
114
  function showRobotVideo(){return startLocalVideo().catch(function(){})}
46
115
  function applyCallMode(){mode=el('call-mode').value==='production'?'production':'test';el('robot-row').style.display=mode==='production'?'grid':'none';el('mode-badge').textContent=mode==='production'?'PRODUCTION ? ROBOT VIDEO + TOOLS':'TEST ? BROWSER ONLY';el('mode-badge').className='mode-badge '+(mode==='production'?'production':'');if(mode==='test')robot='';else robot=el('call-robot').value==='none'?'':el('call-robot').value;text('f-mode',mode);text('f-robot',mode==='production'?(robot||'missing robot'):'not requested');showRobotVideo()}
47
- function loadProductionRobots(){return Promise.all([fetch('/robopark/api/character-presets',{cache:'no-store'}).then(function(r){if(!r.ok)throw new Error('character inventory '+r.status);return r.json()}),fetch('/robopark/api/devices',{cache:'no-store'}).then(function(r){if(!r.ok)throw new Error('device inventory '+r.status);return r.json()})]).then(function(data){var characters=Array.isArray(data[0])?data[0]:(data[0]&&data[0].presets)||[],devices=Array.isArray(data[1])?data[1]:(data[1]&&data[1].devices)||[],select=el('call-robot'),seen={};select.innerHTML='<option value=none>Select a character...</option>';characters.forEach(function(c){var cid=String(c.id||c.preset_id||c.character_id||'');if(!cid||seen[cid.toLowerCase()])return;seen[cid.toLowerCase()]=1;var matches=devices.filter(function(d){return String(d.character_id||'').toLowerCase()===cid.toLowerCase()});matches.sort(function(a,b){var ar=String(a.device_role||'combined'),br=String(b.device_role||'combined'),ap=ar==='voice_vision'?3:ar==='combined'?2:0,bp=br==='voice_vision'?3:br==='combined'?2:0;return bp-ap||Date.parse(b.last_heartbeat||b.enrolled_at||0)-Date.parse(a.last_heartbeat||a.enrolled_at||0)});var voice=matches[0],id=voice&&(voice.id||voice.device_id)||cid,option=document.createElement('option');option.value=id;option.textContent=String(c.name||cid)+' / '+(voice?(String(voice.device_role||'combined')+' '+(voice.status||'registered')):'awaiting voice/video PC');select.appendChild(option)});if(robot&&Array.from(select.options).some(function(option){return option.value===robot}))select.value=robot;else if(character){var match=Array.from(select.options).find(function(option){return option.textContent.toLowerCase().indexOf(character.toLowerCase()+' /')===0||option.value.toLowerCase()===character.toLowerCase()});if(match){select.value=match.value;robot=match.value}}applyCallMode();return characters}).catch(function(error){setHardwareStatus('Character deployment inventory unavailable: '+error.message,'bad');return []})}
116
+ function loadProductionRobots(){return Promise.all([fetch(API_BASE+'/api/character-presets',{cache:'no-store'}).then(function(r){if(!r.ok)throw new Error('character inventory '+r.status);return r.json()}),fetch(API_BASE+'/api/devices',{cache:'no-store'}).then(function(r){if(!r.ok)throw new Error('device inventory '+r.status);return r.json()})]).then(function(data){var characters=Array.isArray(data[0])?data[0]:(data[0]&&data[0].presets)||[],devices=Array.isArray(data[1])?data[1]:(data[1]&&data[1].devices)||[],select=el('call-robot'),seen={};select.innerHTML='<option value=none>Select a character...</option>';characters.forEach(function(c){var cid=String(c.id||c.preset_id||c.character_id||'');if(!cid||seen[cid.toLowerCase()])return;seen[cid.toLowerCase()]=1;var matches=devices.filter(function(d){return String(d.character_id||'').toLowerCase()===cid.toLowerCase()});matches.sort(function(a,b){var ar=String(a.device_role||'combined'),br=String(b.device_role||'combined'),ap=ar==='voice_vision'?3:ar==='combined'?2:0,bp=br==='voice_vision'?3:br==='combined'?2:0;return bp-ap||Date.parse(b.last_heartbeat||b.enrolled_at||0)-Date.parse(a.last_heartbeat||a.enrolled_at||0)});var voice=matches[0],id=voice&&(voice.id||voice.device_id)||cid,option=document.createElement('option');option.value=id;option.textContent=String(c.name||cid)+' / '+(voice?(String(voice.device_role||'combined')+' '+(voice.status||'registered')):'awaiting voice/video PC');select.appendChild(option)});if(robot&&Array.from(select.options).some(function(option){return option.value===robot}))select.value=robot;else if(character){var match=Array.from(select.options).find(function(option){return option.textContent.toLowerCase().indexOf(character.toLowerCase()+' /')===0||option.value.toLowerCase()===character.toLowerCase()});if(match){select.value=match.value;robot=match.value}}applyCallMode();return characters}).catch(function(error){setHardwareStatus('Character deployment inventory unavailable: '+error.message,'bad');return []})}
48
117
  el('call-mode').value=mode;el('call-robot').value=robot||'none';
49
118
  el('call-mode').onchange=function(){if(desired||conversation||connecting)end('mode_changed');applyCallMode()};
50
119
  el('call-robot').onchange=function(){robot=this.value==='none'?'':this.value;showRobotVideo();text('f-robot',robot||'missing robot')};
@@ -59,7 +128,11 @@
59
128
  var hardwarePatchTimer=setInterval(function(){if(installElevenLabsHardwareRouting())clearInterval(hardwarePatchTimer)},500);
60
129
  function stage(name,status){var order=['configured','permission','signed_session','connected','conversation'],index=order.indexOf(name);document.querySelectorAll('.step').forEach(function(node,i){node.className='step '+(status==='bad'&&i===index?'bad':i<index?'done':i===index?'live':'')})}
61
130
  function headers(){return {'content-type':'application/json','X-RoboPark-Session-Token':eventToken||''}}
62
- function api(path,options){options=options||{};if(path==='/api/sessions/voice-call'&&options.body){var payload=JSON.parse(options.body);payload.call_mode=mode;payload.robot_id=mode==='production'?(robot||null):null;options.body=JSON.stringify(payload)}return fetch('/robopark'+path,Object.assign({cache:'no-store'},options)).then(function(response){if(!response.ok)return response.text().then(function(body){throw new Error(body||('request '+response.status))});return response.json()})}
131
+ function api(path,options){options=options||{};if(path==='/api/sessions/voice-call'&&options.body){var payload=JSON.parse(options.body);payload.call_mode=mode;payload.robot_id=mode==='production'?(robot||null):null;options.body=JSON.stringify(payload)}var request=Object.assign({cache:'no-store'},options);
132
+ // Without a deadline a stalled control request hung the join forever: no
133
+ // error surfaced, so nothing ever retried.
134
+ if(!request.signal&&window.AbortSignal&&AbortSignal.timeout)request.signal=AbortSignal.timeout(25000);
135
+ return fetch(API_BASE+path,request).then(function(response){if(!response.ok)return response.text().then(function(body){var failure=new Error(body||('request '+response.status));failure.status=response.status;throw failure});return response.json()})}
63
136
  function append(role,message){message=String(message||'').trim();if(!message)return;var key=role+'|'+message;if(seen[key])return;seen[key]=1;var node=document.createElement('div');node.className='turn '+role;node.textContent=message;el('transcript').appendChild(node);el('transcript').scrollTop=el('transcript').scrollHeight}
64
137
  function management(path,body){if(!session||!eventToken)return Promise.resolve();return api('/api/sessions/'+encodeURIComponent(session)+path,{method:'POST',headers:headers(),body:JSON.stringify(body||{})}).catch(function(){})}
65
138
  function event(stageName,status,message,details){var row=document.createElement('div');row.className='event';row.innerHTML='<span>'+String(stageName||'event').replace(/[<>]/g,'')+'</span><span class="'+String(status||'ok').replace(/[<>]/g,'')+'">'+String(status||'ok').replace(/[<>]/g,'')+'</span><span>'+String(message||'').replace(/[<>]/g,'')+'</span>';var host=el('events');if(host){host.prepend(row);while(host.children.length>9)host.lastElementChild.remove()}return Promise.resolve()}
@@ -68,16 +141,31 @@
68
141
  function pollDetail(){applySpeakerVolume()}
69
142
  function plainDetails(value){try{return JSON.parse(JSON.stringify(value||{}))}catch(_){return {raw:String(value||'')}}}
70
143
  function closeManaged(reason,details){var oldSession=session,oldToken=eventToken;if(!oldSession||!oldToken)return Promise.resolve();return api('/api/sessions/'+encodeURIComponent(oldSession)+'/end?reason='+encodeURIComponent(reason),{method:'POST',headers:{'content-type':'application/json','X-RoboPark-Session-Token':oldToken},body:JSON.stringify({details:plainDetails(details),source:'public_join'})}).catch(function(){})}
71
- function stopTimers(){if(retryTimer){clearTimeout(retryTimer);retryTimer=null}if(keepalive){clearInterval(keepalive);keepalive=null}if(poller){clearInterval(poller);poller=null}}
144
+ function stopTimers(){if(retryTimer){clearTimeout(retryTimer);retryTimer=null}if(keepalive){clearInterval(keepalive);keepalive=null}if(poller){clearInterval(poller);poller=null}stopVision();stopMotion()}
72
145
  function scheduleReconnect(){if(!desired||!alwaysOn||ending)return;connecting=false;conversation=null;conversationId=null;var attempt=++retryAttempt,delay=Math.min(30000,Math.round(750*Math.pow(2,Math.min(attempt-1,6))+Math.random()*500));text('state','reconnecting in '+Math.ceil(delay/1000)+'s');stage('connected');el('avatar-wrap').classList.remove('speaking');if(retryTimer)clearTimeout(retryTimer);retryTimer=setTimeout(function(){retryTimer=null;connectAttempt('reconnect')},delay)}
73
- function recover(myGeneration,details){if(myGeneration!==generation||ending||!desired)return;var captured=plainDetails(details);captured.last_provider_error=lastProviderError;event('provider_error','failed','ElevenLabs disconnected; visitor may reconnect manually',captured);closeManaged('provider_disconnected',captured);stopTimers();session=null;eventToken=null;conversation=null;conversationId=null;desired=false;connecting=false;retryAttempt=0;el('avatar-wrap').classList.remove('live','speaking');text('state','conversation ended - start again when ready');stage('conversation','bad');el('join').hidden=false;el('join').disabled=false;el('join').textContent='Start conversation';el('end').hidden=true;el('message').disabled=true;el('send').disabled=true}
74
- function end(reason){if(ending)return;ending=true;desired=false;generation++;stopTimers();var active=conversation,details={manual:true,reason:reason||'visitor'};if(active)try{active.endSession()}catch(_){};if(cameraRoom){cameraRoom.disconnect();cameraRoom=null}if(localVideoStream){localVideoStream.getTracks().forEach(function(track){track.stop()});localVideoStream=null}closeManaged(reason||'visitor',details);session=null;eventToken=null;conversation=null;conversationId=null;connecting=false;retryAttempt=0;stage('conversation','done');text('state','manually disconnected - supervisor stopped');el('avatar-wrap').classList.remove('live','speaking');el('join').hidden=false;el('join').disabled=false;el('join').textContent='Start conversation';el('end').hidden=true;el('message').disabled=true;el('send').disabled=true;ending=false}
146
+ function recover(myGeneration,details){if(myGeneration!==generation||ending||!desired)return;var captured=plainDetails(details);captured.last_provider_error=lastProviderError;event('provider_error','warning','ElevenLabs disconnected; automatic recovery is running',captured);closeManaged('provider_disconnected',captured);stopTimers();session=null;eventToken=null;conversation=null;conversationId=null;connecting=false;el('avatar-wrap').classList.remove('live','speaking');el('join').hidden=true;el('end').hidden=false;el('message').disabled=true;el('send').disabled=true;scheduleReconnect()}
147
+ function end(reason){if(ending)return;ending=true;desired=false;generation++;stopTimers();var active=conversation,details={manual:true,reason:reason||'visitor'};if(active)try{active.endSession()}catch(_){};if(cameraRoom){cameraRoom.disconnect();cameraRoom=null}if(localVideoStream){localVideoStream.getTracks().forEach(function(track){track.stop()});localVideoStream=null}var robotStream=el('robot-stream');if(robotStream){robotStream.removeAttribute('src');robotStream.hidden=true}el('robot-video').hidden=false;closeManaged(reason||'visitor',details);session=null;eventToken=null;conversation=null;conversationId=null;connecting=false;retryAttempt=0;stage('conversation','done');text('state','manually disconnected - supervisor stopped');el('avatar-wrap').classList.remove('live','speaking');el('join').hidden=false;el('join').disabled=false;el('join').textContent='Start conversation';el('end').hidden=true;el('message').disabled=true;el('send').disabled=true;ending=false}
148
+ // This call lives on an ElevenLabs WebSocket and never joins a LiveKit room,
149
+ // so the control center cannot observe it. Check in on a timer, and close the
150
+ // session on unload, or the dashboard cannot tell a live call from a closed
151
+ // tab and keeps showing the character as busy.
152
+ function startKeepalive(){if(keepalive)clearInterval(keepalive);keepalive=setInterval(function(){management('/heartbeat',{source:'public_join'})},30000)}
153
+ window.addEventListener('pagehide',function(){
154
+ if(!session||!eventToken||!navigator.sendBeacon)return;
155
+ try{navigator.sendBeacon(API_BASE+'/api/sessions/'+encodeURIComponent(session)+'/end?reason=page_closed',new Blob([JSON.stringify({details:{page_closed:true},source:'public_join'})],{type:'application/json'}))}catch(_){}
156
+ });
75
157
  function callPayload(){return {character_preset_id:character,voice_stack_id:stack||null,client_name:'Public character link',engine:'elevenlabs',agent_id:agent,branch_id:branch||null,robot_id:robot||null,join_link_id:share||null}}
76
- function connectAttempt(kind){if(!desired||ending||connecting||conversation)return;connecting=true;var myGeneration=++generation;text('state',kind==='reconnect'?'obtaining fresh voice session':'connecting');stage('signed_session');api('/api/sessions/voice-call',{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify(callPayload())}).then(function(call){if(myGeneration!==generation||!desired)throw new Error('connection superseded');session=call.session_id;eventToken=call.event_token;text('f-session',session);text('f-agent',call.agent_id);text('f-branch',call.branch_id||'default');text('f-robot',call.robot_id||'operator only');if(call.robot_video_ref&&robot){el('vision').hidden=false;el('robot-video').src='/fed/media/robots/'+encodeURIComponent(call.robot_video_ref)+'/video_feed'}stage('connected');event('signed_session','ok','Fresh signed ElevenLabs session created',{join_link_id:share,robot_id:robot,reconnect_attempt:retryAttempt});return ensureElevenLabsClient().then(function(SDK){return SDK.Conversation.startSession({signedUrl:call.signed_url,connectionType:'websocket',useWakeLock:true,onConnect:function(info){if(myGeneration!==generation)return;conversationId=info.conversationId;lastProviderError=null;retryAttempt=0;text('state','listening');el('error').style.display='none';el('avatar-wrap').classList.add('live');stage('conversation');management('/joined',{conversation_id:conversationId,connection_type:'websocket',engine:'elevenlabs',robot_id:robot,join_link_id:share,source:'public_join'});event('provider_connected','ok','ElevenLabs conversation connected',{conversation_id:conversationId,supervised:true})},onDisconnect:function(info){recover(myGeneration,info||{reason:'provider_disconnected'})},onError:function(message,context){if(myGeneration!==generation)return;lastProviderError={message:String(message||'Provider error'),context:plainDetails(context)};event('provider_error','failed',lastProviderError.message,lastProviderError);text('state','provider error')},onStatusChange:function(info){if(myGeneration===generation)event('provider_status',info&&info.status==='disconnected'?'warning':'ok',info&&info.status||'unknown',{})},onMessage:function(info){if(myGeneration!==generation)return;var message=String(info&&info.message||'').trim(),role=info&&info.role==='user'?'user':'assistant';append(role,message);if(message){history.push({role:role,text:message});history=history.slice(-20)}saveTurn(role,message)},onModeChange:function(info){if(myGeneration!==generation)return;var speaking=info.mode==='speaking';el('avatar-wrap').classList.toggle('speaking',speaking);text('state',speaking?'speaking':'listening');event('mode','ok',info.mode,{})},onInterruption:function(info){event('interruption','ok','Agent interrupted',info||{})},onMCPToolCall:function(info){event('mcp_tool','running','Tool call',info||{})}})})}).then(function(active){connecting=false;if(myGeneration!==generation||!desired){try{active.endSession()}catch(_){}return}conversation=active;el('join').hidden=true;el('end').hidden=false;el('message').disabled=false;el('send').disabled=false;pollDetail()}).catch(function(error){connecting=false;if(myGeneration!==generation||!desired)return;var details={message:error&&error.message||String(error),last_provider_error:lastProviderError};event('provider_error','failed','Voice connection attempt failed',details);closeManaged('connection_failed',details);session=null;eventToken=null;conversation=null;conversationId=null;fail(details.message)})}
77
- function start(){if(desired||conversation||connecting)return;if(mode==='production'&&!robot){fail('Select a registered production robot first.');return}if(mode==='test')ensureMicAudioContext();desired=true;retryAttempt=0;el('join').disabled=true;text('state','requesting microphone permission');stage('permission');var audio=inputDeviceId?{deviceId:{exact:inputDeviceId}}:true;navigator.mediaDevices.getUserMedia({audio:audio}).then(function(stream){stream.getTracks().forEach(function(track){track.stop()});return startLocalVideo()}).then(function(){connectAttempt('initial')}).catch(function(error){desired=false;fail(error&&error.message||String(error))})}
158
+ function connectAttempt(kind){if(!desired||ending||connecting||conversation)return;connecting=true;var myGeneration=++generation;text('state',kind==='reconnect'?'obtaining fresh voice session':'connecting');stage('signed_session');api('/api/sessions/voice-call',{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify(callPayload())}).then(function(call){if(myGeneration!==generation||!desired)throw new Error('connection superseded');session=call.session_id;eventToken=call.event_token;text('f-session',session);text('f-agent',call.agent_id);text('f-branch',call.branch_id||'default');text('f-robot',call.robot_id||'operator only');if(call.robot_video_ref&&robot){el('vision').hidden=false;el('robot-stream').src=API_BASE+'/api/robots/'+encodeURIComponent(call.robot_video_ref)+'/video_feed';el('robot-stream').hidden=false;el('robot-video').hidden=true}stage('connected');event('signed_session','ok','Fresh signed ElevenLabs session created',{join_link_id:share,robot_id:robot,reconnect_attempt:retryAttempt});return ensureElevenLabsClient().then(function(SDK){return SDK.Conversation.startSession({signedUrl:call.signed_url,connectionType:'websocket',useWakeLock:true,onConnect:function(info){if(myGeneration!==generation)return;conversationId=info.conversationId;lastProviderError=null;retryAttempt=0;text('state','listening');el('error').style.display='none';el('avatar-wrap').classList.add('live');stage('conversation');management('/joined',{conversation_id:conversationId,connection_type:'websocket',engine:'elevenlabs',robot_id:robot,join_link_id:share,source:'public_join'});event('provider_connected','ok','ElevenLabs conversation connected',{conversation_id:conversationId,supervised:true});startVision(myGeneration);motionLastTurn=Date.now();if(mode==='production')startMotion(myGeneration)},onDisconnect:function(info){recover(myGeneration,info||{reason:'provider_disconnected'})},onError:function(message,context){if(myGeneration!==generation)return;lastProviderError={message:String(message||'Provider error'),context:plainDetails(context)};event('provider_error','failed',lastProviderError.message,lastProviderError);text('state','provider error')},onStatusChange:function(info){if(myGeneration===generation)event('provider_status',info&&info.status==='disconnected'?'warning':'ok',info&&info.status||'unknown',{})},onMessage:function(info){if(myGeneration!==generation)return;var message=String(info&&info.message||'').trim(),role=info&&info.role==='user'?'user':'assistant';if(role==='user'&&motionSent[message])return;motionLastTurn=Date.now();append(role,message);if(message){history.push({role:role,text:message});history=history.slice(-20)}saveTurn(role,message)},onModeChange:function(info){if(myGeneration!==generation)return;var speaking=info.mode==='speaking';motionMode=speaking?'speaking':'listening';el('avatar-wrap').classList.toggle('speaking',speaking);text('state',speaking?'speaking':'listening');event('mode','ok',info.mode,{})},onInterruption:function(info){event('interruption','ok','Agent interrupted',info||{})},onMCPToolCall:function(info){event('mcp_tool','running','Tool call',info||{})}})})}).then(function(active){connecting=false;if(myGeneration!==generation||!desired){try{active.endSession()}catch(_){}return}conversation=active;startKeepalive();el('join').hidden=true;el('end').hidden=false;el('message').disabled=false;el('send').disabled=false;pollDetail()}).catch(function(error){connecting=false;if(myGeneration!==generation||!desired)return;var details={message:error&&error.message||String(error),last_provider_error:lastProviderError};event('provider_error','failed','Voice connection attempt failed',details);closeManaged('connection_failed',details);session=null;eventToken=null;conversation=null;conversationId=null;
159
+ // A 4xx means the link itself is wrong and retrying cannot fix it. Anything
160
+ // else (5xx, signing timeout, network drop) is transient, and dead-ending
161
+ // here is what left a failed call with no reconnect until a manual re-join.
162
+ var status=Number(error&&error.status||0),permanent=status>=400&&status<500;
163
+ if(!permanent&&alwaysOn&&desired&&!ending){el('join').hidden=true;el('end').hidden=false;scheduleReconnect();return}
164
+ fail(details.message)})}
165
+ function start(){if(desired||conversation||connecting)return;if(mode==='production'&&!robot){fail('Select a registered production robot first.');return}alwaysOn=true;el('always-on').classList.add('on');el('always-on').setAttribute('aria-pressed','true');el('always-on').textContent='Always on: ON';if(mode==='test')ensureMicAudioContext();desired=true;retryAttempt=0;el('join').disabled=true;text('state','requesting microphone permission');stage('permission');var audio=inputDeviceId?{deviceId:{exact:inputDeviceId}}:true;navigator.mediaDevices.getUserMedia({audio:audio}).then(function(stream){stream.getTracks().forEach(function(track){track.stop()});return startLocalVideo()}).then(function(){connectAttempt('initial')}).catch(function(error){if(desired){text('state','microphone unavailable; retrying');scheduleReconnect()}else{fail(error&&error.message||String(error))}})}
78
166
  el('always-on').onclick=function(){alwaysOn=!alwaysOn;this.classList.toggle('on',alwaysOn);this.setAttribute('aria-pressed',String(alwaysOn));this.textContent='Always on: '+(alwaysOn?'ON':'OFF');if(!alwaysOn&&retryTimer){clearTimeout(retryTimer);retryTimer=null;text('state','disconnected - automatic recovery paused')}else if(alwaysOn&&desired&&!conversation&&!connecting){scheduleReconnect()}};
79
167
  function pollDetail(){applySpeakerVolume()}
80
- function deploymentRequest(path,options){var token=q.get('token')||'',suffix=token?(path.indexOf('?')>=0?'&':'?')+'token='+encodeURIComponent(token):'';return fetch('/robopark'+path+suffix,Object.assign({cache:'no-store'},options||{})).then(function(response){if(!response.ok)throw new Error('request '+response.status);return response.json()})}
168
+ function deploymentRequest(path,options){var token=q.get('token')||'',suffix=token?(path.indexOf('?')>=0?'&':'?')+'token='+encodeURIComponent(token):'';return fetch(API_BASE+path+suffix,Object.assign({cache:'no-store'},options||{})).then(function(response){if(!response.ok)throw new Error('request '+response.status);return response.json()})}
81
169
  function renderDeployment(data){var token=q.get('token')||'',rows=[];[['voice_vision_boxes','Windows voice + vision',['vision','conversation']],['motor_nodes','Pi motor server',['motor']]].forEach(function(group){var devices=data[group[0]]||[];if(!devices.length){rows.push('<div class="deployment-row"><b>'+group[1]+'</b><span>not registered</span><span></span></div>');return}devices.forEach(function(device){var seen=Date.parse(device.last_heartbeat||''),live=isFinite(seen)&&Date.now()-seen<90000,buttons=token?group[2].map(function(service){return '<button data-device="'+device.id+'" data-service="'+service+'" data-action="start">Start '+service+'</button><button data-device="'+device.id+'" data-service="'+service+'" data-action="restart">Restart</button><button data-device="'+device.id+'" data-service="'+service+'" data-action="stop">Stop</button>'}).join(''):'management token required for controls';rows.push('<div class="deployment-row"><b>'+group[1]+'</b><span class="'+(live?'ready':'')+'">'+String(device.name||device.id)+' / '+(live?'online':'stale')+'</span><div class="deployment-actions">'+buttons+'</div></div>')})});deployment.innerHTML=rows.join('');deployment.querySelectorAll('button[data-action]').forEach(function(button){button.onclick=function(){var action=button.getAttribute('data-action'),service=button.getAttribute('data-service'),device=button.getAttribute('data-device');if((action==='stop'||action==='restart')&&!confirm(action+' '+service+'?'))return;button.disabled=true;deploymentRequest('/api/devices/'+encodeURIComponent(device)+'/services/'+encodeURIComponent(service)+'/'+action,{method:'POST',headers:{'content-type':'application/json'}}).then(function(){button.textContent='queued'}).catch(function(error){button.textContent='failed';fail(error.message)}).then(function(){setTimeout(function(){button.disabled=false},1800)})}})}
82
170
  function loadDeployment(){deploymentRequest('/api/characters/'+encodeURIComponent(character)+'/deployment').then(renderDeployment).catch(function(){deployment.innerHTML='<div class="deployment-row"><b>Character hardware</b><span>deployment status unavailable</span><span></span></div>'})}
83
171
  el('speaker-volume').value=String(speakerVolume);text('speaker-value',speakerVolume+'%');el('mic-sensitivity').value=String(micSensitivity);text('mic-value',micSensitivity+'%');el('speaker-volume').oninput=function(){speakerVolume=Math.max(0,Math.min(100,Number(this.value)||0));text('speaker-value',speakerVolume+'%');persistNumber('robopark.voice.speaker',speakerVolume);applySpeakerVolume()};el('mic-sensitivity').oninput=function(){setMicSensitivity(this.value);text('mic-value',micSensitivity+'%');persistNumber('robopark.voice.micSensitivity',micSensitivity)};