robopark 3.3.57 → 3.3.60

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.
@@ -84,6 +84,11 @@ export function startFrontendService(opts) {
84
84
  ROBOPARK_SCHEDULER_URL: scheduler,
85
85
  SCHEDULER_URL: scheduler,
86
86
  V2_SCHEDULER_URL: scheduler,
87
+ // The /fed/* proxy falls back to host.docker.internal in production when
88
+ // this is unset -- a Docker-ism that does not resolve on a native install,
89
+ // so every /fed/media video_feed hung ~7s and answered 502, blacking out
90
+ // the call page's vision panel and the grid's MJPEG fallback at once.
91
+ ROBOPARK_CONTROL_URL: process.env.ROBOPARK_CONTROL_URL || 'http://127.0.0.1:47913',
87
92
  },
88
93
  stdio,
89
94
  detached: !opts.foreground,
@@ -1 +1 @@
1
- {"version":3,"file":"frontend-service.js","sourceRoot":"","sources":["../../src/robopark/frontend-service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"frontend-service.js","sourceRoot":"","sources":["../../src/robopark/frontend-service.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "robopark",
3
- "version": "3.3.57",
3
+ "version": "3.3.60",
4
4
  "description": "RoboPark fleet control CLI — scheduler, control UI, and character voice calls. Standalone: no infinicode runtime.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -454,7 +454,10 @@
454
454
  // Without a deadline a stalled control request hung the join forever: no
455
455
  // error surfaced, so nothing ever retried.
456
456
  if(!request.signal&&window.AbortSignal&&AbortSignal.timeout)request.signal=AbortSignal.timeout(25000);
457
- 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()})}
457
+ return fetch(API_BASE+path,request).then(function(response){if(!response.ok)return response.text().then(function(body){// Name the request in the failure. "Method Not Allowed" with no URL sent an
458
+ // operator (and the tooling behind them) hunting through every endpoint the
459
+ // page touches; the failing call should identify itself.
460
+ var failure=new Error((options.method||'GET')+' '+path+' -> '+response.status+(body?': '+body.slice(0,200):''));failure.status=response.status;throw failure});return response.json()})}
458
461
  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}
459
462
  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(){})}
460
463
  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()}
@@ -479,7 +482,11 @@
479
482
  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(_){}
480
483
  });
481
484
  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}}
482
- 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();startMotion(myGeneration);kioskNote();if(kioskOn){try{motionSent[kioskGreeting]=1;conversation.sendUserMessage(kioskGreeting)}catch(_){}}},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();kioskNote();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';if(speaking)kioskNote();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;
485
+ 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;var rs=el('robot-stream');rs.onerror=function(){// The MJPEG relay is a bonus, not the feed. A claim_ device is a browser
486
+ // camera with no vision server behind it, so this URL can 502 by design --
487
+ // and with no handler the panel simply went black over the top of a local
488
+ // camera that was working the whole time. Fall back to the local preview.
489
+ rs.onerror=null;rs.removeAttribute('src');rs.hidden=true;el('robot-video').hidden=false;};rs.src=API_BASE+'/api/robots/'+encodeURIComponent(call.robot_video_ref)+'/video_feed';rs.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();startMotion(myGeneration);kioskNote();if(kioskOn){try{motionSent[kioskGreeting]=1;conversation.sendUserMessage(kioskGreeting)}catch(_){}}},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();kioskNote();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';if(speaking)kioskNote();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;
483
490
  // A 4xx means the link itself is wrong and retrying cannot fix it. Anything
484
491
  // else (5xx, signing timeout, network drop) is transient, and dead-ending
485
492
  // here is what left a failed call with no reconnect until a manual re-join.
@@ -1 +1 @@
1
- yswW2wNJG-5o2FkKKTtei
1
+ HVQqws8pwZ7FuPixvQm1d
@@ -18,26 +18,26 @@
18
18
  "static/chunks/9880-53262785284464cc.js",
19
19
  "static/chunks/app/layout-fcce963da1d8220f.js"
20
20
  ],
21
- "/api/download-piper-model/route": [
21
+ "/api/device-character/route": [
22
22
  "static/chunks/webpack-5252e5254da079b5.js",
23
23
  "static/chunks/4bd1b696-f785427dddbba9fb.js",
24
24
  "static/chunks/1255-404bb8de49c90395.js",
25
25
  "static/chunks/main-app-a1f59168303c7f84.js",
26
- "static/chunks/app/api/download-piper-model/route-0952b44bda5bfb6d.js"
26
+ "static/chunks/app/api/device-character/route-0952b44bda5bfb6d.js"
27
27
  ],
28
- "/api/device-character/route": [
28
+ "/api/models/route": [
29
29
  "static/chunks/webpack-5252e5254da079b5.js",
30
30
  "static/chunks/4bd1b696-f785427dddbba9fb.js",
31
31
  "static/chunks/1255-404bb8de49c90395.js",
32
32
  "static/chunks/main-app-a1f59168303c7f84.js",
33
- "static/chunks/app/api/device-character/route-0952b44bda5bfb6d.js"
33
+ "static/chunks/app/api/models/route-0952b44bda5bfb6d.js"
34
34
  ],
35
- "/api/models/route": [
35
+ "/api/download-piper-model/route": [
36
36
  "static/chunks/webpack-5252e5254da079b5.js",
37
37
  "static/chunks/4bd1b696-f785427dddbba9fb.js",
38
38
  "static/chunks/1255-404bb8de49c90395.js",
39
39
  "static/chunks/main-app-a1f59168303c7f84.js",
40
- "static/chunks/app/api/models/route-0952b44bda5bfb6d.js"
40
+ "static/chunks/app/api/download-piper-model/route-0952b44bda5bfb6d.js"
41
41
  ],
42
42
  "/api/prewarm/route": [
43
43
  "static/chunks/webpack-5252e5254da079b5.js",
@@ -95,6 +95,13 @@
95
95
  "static/chunks/main-app-a1f59168303c7f84.js",
96
96
  "static/chunks/app/api/setup/complete/route-0952b44bda5bfb6d.js"
97
97
  ],
98
+ "/api/setup/test-groq/route": [
99
+ "static/chunks/webpack-5252e5254da079b5.js",
100
+ "static/chunks/4bd1b696-f785427dddbba9fb.js",
101
+ "static/chunks/1255-404bb8de49c90395.js",
102
+ "static/chunks/main-app-a1f59168303c7f84.js",
103
+ "static/chunks/app/api/setup/test-groq/route-0952b44bda5bfb6d.js"
104
+ ],
98
105
  "/api/setup/status/route": [
99
106
  "static/chunks/webpack-5252e5254da079b5.js",
100
107
  "static/chunks/4bd1b696-f785427dddbba9fb.js",
@@ -102,12 +109,12 @@
102
109
  "static/chunks/main-app-a1f59168303c7f84.js",
103
110
  "static/chunks/app/api/setup/status/route-0952b44bda5bfb6d.js"
104
111
  ],
105
- "/api/setup/test-groq/route": [
112
+ "/api/setup/test-hass/route": [
106
113
  "static/chunks/webpack-5252e5254da079b5.js",
107
114
  "static/chunks/4bd1b696-f785427dddbba9fb.js",
108
115
  "static/chunks/1255-404bb8de49c90395.js",
109
116
  "static/chunks/main-app-a1f59168303c7f84.js",
110
- "static/chunks/app/api/setup/test-groq/route-0952b44bda5bfb6d.js"
117
+ "static/chunks/app/api/setup/test-hass/route-0952b44bda5bfb6d.js"
111
118
  ],
112
119
  "/api/setup/test-n8n/route": [
113
120
  "static/chunks/webpack-5252e5254da079b5.js",
@@ -116,12 +123,12 @@
116
123
  "static/chunks/main-app-a1f59168303c7f84.js",
117
124
  "static/chunks/app/api/setup/test-n8n/route-0952b44bda5bfb6d.js"
118
125
  ],
119
- "/api/setup/test-hass/route": [
126
+ "/api/test-tts/route": [
120
127
  "static/chunks/webpack-5252e5254da079b5.js",
121
128
  "static/chunks/4bd1b696-f785427dddbba9fb.js",
122
129
  "static/chunks/1255-404bb8de49c90395.js",
123
130
  "static/chunks/main-app-a1f59168303c7f84.js",
124
- "static/chunks/app/api/setup/test-hass/route-0952b44bda5bfb6d.js"
131
+ "static/chunks/app/api/test-tts/route-0952b44bda5bfb6d.js"
125
132
  ],
126
133
  "/api/voices/route": [
127
134
  "static/chunks/webpack-5252e5254da079b5.js",
@@ -130,6 +137,13 @@
130
137
  "static/chunks/main-app-a1f59168303c7f84.js",
131
138
  "static/chunks/app/api/voices/route-0952b44bda5bfb6d.js"
132
139
  ],
140
+ "/api/setup/test-ollama/route": [
141
+ "static/chunks/webpack-5252e5254da079b5.js",
142
+ "static/chunks/4bd1b696-f785427dddbba9fb.js",
143
+ "static/chunks/1255-404bb8de49c90395.js",
144
+ "static/chunks/main-app-a1f59168303c7f84.js",
145
+ "static/chunks/app/api/setup/test-ollama/route-0952b44bda5bfb6d.js"
146
+ ],
133
147
  "/api/wake-word/models/route": [
134
148
  "static/chunks/webpack-5252e5254da079b5.js",
135
149
  "static/chunks/4bd1b696-f785427dddbba9fb.js",
@@ -151,19 +165,12 @@
151
165
  "static/chunks/main-app-a1f59168303c7f84.js",
152
166
  "static/chunks/app/api/wake-word/status/route-0952b44bda5bfb6d.js"
153
167
  ],
154
- "/api/test-tts/route": [
155
- "static/chunks/webpack-5252e5254da079b5.js",
156
- "static/chunks/4bd1b696-f785427dddbba9fb.js",
157
- "static/chunks/1255-404bb8de49c90395.js",
158
- "static/chunks/main-app-a1f59168303c7f84.js",
159
- "static/chunks/app/api/test-tts/route-0952b44bda5bfb6d.js"
160
- ],
161
- "/api/setup/test-ollama/route": [
168
+ "/api/kiosk-hardware/route": [
162
169
  "static/chunks/webpack-5252e5254da079b5.js",
163
170
  "static/chunks/4bd1b696-f785427dddbba9fb.js",
164
171
  "static/chunks/1255-404bb8de49c90395.js",
165
172
  "static/chunks/main-app-a1f59168303c7f84.js",
166
- "static/chunks/app/api/setup/test-ollama/route-0952b44bda5bfb6d.js"
173
+ "static/chunks/app/api/kiosk-hardware/route-0952b44bda5bfb6d.js"
167
174
  ],
168
175
  "/api/model-status/route": [
169
176
  "static/chunks/webpack-5252e5254da079b5.js",
@@ -172,19 +179,19 @@
172
179
  "static/chunks/main-app-a1f59168303c7f84.js",
173
180
  "static/chunks/app/api/model-status/route-0952b44bda5bfb6d.js"
174
181
  ],
175
- "/api/kiosk-hardware/route": [
182
+ "/api/connection-details/route": [
176
183
  "static/chunks/webpack-5252e5254da079b5.js",
177
184
  "static/chunks/4bd1b696-f785427dddbba9fb.js",
178
185
  "static/chunks/1255-404bb8de49c90395.js",
179
186
  "static/chunks/main-app-a1f59168303c7f84.js",
180
- "static/chunks/app/api/kiosk-hardware/route-0952b44bda5bfb6d.js"
187
+ "static/chunks/app/api/connection-details/route-0952b44bda5bfb6d.js"
181
188
  ],
182
- "/api/connection-details/route": [
189
+ "/api/robopark/device-access/route": [
183
190
  "static/chunks/webpack-5252e5254da079b5.js",
184
191
  "static/chunks/4bd1b696-f785427dddbba9fb.js",
185
192
  "static/chunks/1255-404bb8de49c90395.js",
186
193
  "static/chunks/main-app-a1f59168303c7f84.js",
187
- "static/chunks/app/api/connection-details/route-0952b44bda5bfb6d.js"
194
+ "static/chunks/app/api/robopark/device-access/route-0952b44bda5bfb6d.js"
188
195
  ],
189
196
  "/api/robopark/character-ai/route": [
190
197
  "static/chunks/webpack-5252e5254da079b5.js",
@@ -200,61 +207,61 @@
200
207
  "static/chunks/main-app-a1f59168303c7f84.js",
201
208
  "static/chunks/app/api/robopark/camera-token/route-0952b44bda5bfb6d.js"
202
209
  ],
203
- "/api/robopark/monitor-token/route": [
210
+ "/api/robopark/robots/route": [
204
211
  "static/chunks/webpack-5252e5254da079b5.js",
205
212
  "static/chunks/4bd1b696-f785427dddbba9fb.js",
206
213
  "static/chunks/1255-404bb8de49c90395.js",
207
214
  "static/chunks/main-app-a1f59168303c7f84.js",
208
- "static/chunks/app/api/robopark/monitor-token/route-0952b44bda5bfb6d.js"
215
+ "static/chunks/app/api/robopark/robots/route-0952b44bda5bfb6d.js"
209
216
  ],
210
- "/api/robopark/device-access/route": [
217
+ "/api/robopark/servers/[serverId]/metrics/route": [
211
218
  "static/chunks/webpack-5252e5254da079b5.js",
212
219
  "static/chunks/4bd1b696-f785427dddbba9fb.js",
213
220
  "static/chunks/1255-404bb8de49c90395.js",
214
221
  "static/chunks/main-app-a1f59168303c7f84.js",
215
- "static/chunks/app/api/robopark/device-access/route-0952b44bda5bfb6d.js"
222
+ "static/chunks/app/api/robopark/servers/[serverId]/metrics/route-0952b44bda5bfb6d.js"
216
223
  ],
217
- "/api/robopark/robots/route": [
224
+ "/api/robopark/monitor-token/route": [
218
225
  "static/chunks/webpack-5252e5254da079b5.js",
219
226
  "static/chunks/4bd1b696-f785427dddbba9fb.js",
220
227
  "static/chunks/1255-404bb8de49c90395.js",
221
228
  "static/chunks/main-app-a1f59168303c7f84.js",
222
- "static/chunks/app/api/robopark/robots/route-0952b44bda5bfb6d.js"
229
+ "static/chunks/app/api/robopark/monitor-token/route-0952b44bda5bfb6d.js"
223
230
  ],
224
- "/api/robopark/live-rooms/route": [
231
+ "/api/robopark/servers/[serverId]/route": [
225
232
  "static/chunks/webpack-5252e5254da079b5.js",
226
233
  "static/chunks/4bd1b696-f785427dddbba9fb.js",
227
234
  "static/chunks/1255-404bb8de49c90395.js",
228
235
  "static/chunks/main-app-a1f59168303c7f84.js",
229
- "static/chunks/app/api/robopark/live-rooms/route-0952b44bda5bfb6d.js"
236
+ "static/chunks/app/api/robopark/servers/[serverId]/route-0952b44bda5bfb6d.js"
230
237
  ],
231
- "/api/robopark/servers/route": [
238
+ "/api/robopark/live-rooms/route": [
232
239
  "static/chunks/webpack-5252e5254da079b5.js",
233
240
  "static/chunks/4bd1b696-f785427dddbba9fb.js",
234
241
  "static/chunks/1255-404bb8de49c90395.js",
235
242
  "static/chunks/main-app-a1f59168303c7f84.js",
236
- "static/chunks/app/api/robopark/servers/route-0952b44bda5bfb6d.js"
243
+ "static/chunks/app/api/robopark/live-rooms/route-0952b44bda5bfb6d.js"
237
244
  ],
238
- "/api/robopark/servers/[serverId]/metrics/route": [
245
+ "/api/robopark/servers/route": [
239
246
  "static/chunks/webpack-5252e5254da079b5.js",
240
247
  "static/chunks/4bd1b696-f785427dddbba9fb.js",
241
248
  "static/chunks/1255-404bb8de49c90395.js",
242
249
  "static/chunks/main-app-a1f59168303c7f84.js",
243
- "static/chunks/app/api/robopark/servers/[serverId]/metrics/route-0952b44bda5bfb6d.js"
250
+ "static/chunks/app/api/robopark/servers/route-0952b44bda5bfb6d.js"
244
251
  ],
245
- "/api/robopark/servers/[serverId]/route": [
252
+ "/api/robopark/sessions/route": [
246
253
  "static/chunks/webpack-5252e5254da079b5.js",
247
254
  "static/chunks/4bd1b696-f785427dddbba9fb.js",
248
255
  "static/chunks/1255-404bb8de49c90395.js",
249
256
  "static/chunks/main-app-a1f59168303c7f84.js",
250
- "static/chunks/app/api/robopark/servers/[serverId]/route-0952b44bda5bfb6d.js"
257
+ "static/chunks/app/api/robopark/sessions/route-0952b44bda5bfb6d.js"
251
258
  ],
252
- "/api/robopark/robots/[robotId]/voice-engine/route": [
259
+ "/api/robovoice/robots/[robotId]/media-health/route": [
253
260
  "static/chunks/webpack-5252e5254da079b5.js",
254
261
  "static/chunks/4bd1b696-f785427dddbba9fb.js",
255
262
  "static/chunks/1255-404bb8de49c90395.js",
256
263
  "static/chunks/main-app-a1f59168303c7f84.js",
257
- "static/chunks/app/api/robopark/robots/[robotId]/voice-engine/route-0952b44bda5bfb6d.js"
264
+ "static/chunks/app/api/robovoice/robots/[robotId]/media-health/route-0952b44bda5bfb6d.js"
258
265
  ],
259
266
  "/api/robopark/vision-caption/route": [
260
267
  "static/chunks/webpack-5252e5254da079b5.js",
@@ -270,12 +277,12 @@
270
277
  "static/chunks/main-app-a1f59168303c7f84.js",
271
278
  "static/chunks/app/api/settings/route-0952b44bda5bfb6d.js"
272
279
  ],
273
- "/api/robopark/sessions/route": [
280
+ "/api/robopark/robots/[robotId]/voice-engine/route": [
274
281
  "static/chunks/webpack-5252e5254da079b5.js",
275
282
  "static/chunks/4bd1b696-f785427dddbba9fb.js",
276
283
  "static/chunks/1255-404bb8de49c90395.js",
277
284
  "static/chunks/main-app-a1f59168303c7f84.js",
278
- "static/chunks/app/api/robopark/sessions/route-0952b44bda5bfb6d.js"
285
+ "static/chunks/app/api/robopark/robots/[robotId]/voice-engine/route-0952b44bda5bfb6d.js"
279
286
  ],
280
287
  "/api/turn-credentials/route": [
281
288
  "static/chunks/webpack-5252e5254da079b5.js",
@@ -284,40 +291,33 @@
284
291
  "static/chunks/main-app-a1f59168303c7f84.js",
285
292
  "static/chunks/app/api/turn-credentials/route-0952b44bda5bfb6d.js"
286
293
  ],
287
- "/api/robopark/transcripts/route": [
288
- "static/chunks/webpack-5252e5254da079b5.js",
289
- "static/chunks/4bd1b696-f785427dddbba9fb.js",
290
- "static/chunks/1255-404bb8de49c90395.js",
291
- "static/chunks/main-app-a1f59168303c7f84.js",
292
- "static/chunks/app/api/robopark/transcripts/route-0952b44bda5bfb6d.js"
293
- ],
294
- "/api/robovoice/robots/[robotId]/media-health/route": [
294
+ "/camera-grid/route": [
295
295
  "static/chunks/webpack-5252e5254da079b5.js",
296
296
  "static/chunks/4bd1b696-f785427dddbba9fb.js",
297
297
  "static/chunks/1255-404bb8de49c90395.js",
298
298
  "static/chunks/main-app-a1f59168303c7f84.js",
299
- "static/chunks/app/api/robovoice/robots/[robotId]/media-health/route-0952b44bda5bfb6d.js"
299
+ "static/chunks/app/camera-grid/route-0952b44bda5bfb6d.js"
300
300
  ],
301
- "/api/wake/route": [
301
+ "/fed/media/robots/[robotId]/video_feed/route": [
302
302
  "static/chunks/webpack-5252e5254da079b5.js",
303
303
  "static/chunks/4bd1b696-f785427dddbba9fb.js",
304
304
  "static/chunks/1255-404bb8de49c90395.js",
305
305
  "static/chunks/main-app-a1f59168303c7f84.js",
306
- "static/chunks/app/api/wake/route-0952b44bda5bfb6d.js"
306
+ "static/chunks/app/fed/media/robots/[robotId]/video_feed/route-0952b44bda5bfb6d.js"
307
307
  ],
308
- "/camera-grid/route": [
308
+ "/api/robopark/transcripts/route": [
309
309
  "static/chunks/webpack-5252e5254da079b5.js",
310
310
  "static/chunks/4bd1b696-f785427dddbba9fb.js",
311
311
  "static/chunks/1255-404bb8de49c90395.js",
312
312
  "static/chunks/main-app-a1f59168303c7f84.js",
313
- "static/chunks/app/camera-grid/route-0952b44bda5bfb6d.js"
313
+ "static/chunks/app/api/robopark/transcripts/route-0952b44bda5bfb6d.js"
314
314
  ],
315
- "/robopark/api/robots/[robotId]/stream/route": [
315
+ "/tailnet/route": [
316
316
  "static/chunks/webpack-5252e5254da079b5.js",
317
317
  "static/chunks/4bd1b696-f785427dddbba9fb.js",
318
318
  "static/chunks/1255-404bb8de49c90395.js",
319
319
  "static/chunks/main-app-a1f59168303c7f84.js",
320
- "static/chunks/app/robopark/api/robots/[robotId]/stream/route-0952b44bda5bfb6d.js"
320
+ "static/chunks/app/tailnet/route-0952b44bda5bfb6d.js"
321
321
  ],
322
322
  "/fed/[...path]/route": [
323
323
  "static/chunks/webpack-5252e5254da079b5.js",
@@ -326,12 +326,12 @@
326
326
  "static/chunks/main-app-a1f59168303c7f84.js",
327
327
  "static/chunks/app/fed/[...path]/route-0952b44bda5bfb6d.js"
328
328
  ],
329
- "/tailnet/route": [
329
+ "/api/wake/route": [
330
330
  "static/chunks/webpack-5252e5254da079b5.js",
331
331
  "static/chunks/4bd1b696-f785427dddbba9fb.js",
332
332
  "static/chunks/1255-404bb8de49c90395.js",
333
333
  "static/chunks/main-app-a1f59168303c7f84.js",
334
- "static/chunks/app/tailnet/route-0952b44bda5bfb6d.js"
334
+ "static/chunks/app/api/wake/route-0952b44bda5bfb6d.js"
335
335
  ],
336
336
  "/robopark/[...path]/route": [
337
337
  "static/chunks/webpack-5252e5254da079b5.js",
@@ -347,12 +347,12 @@
347
347
  "static/chunks/main-app-a1f59168303c7f84.js",
348
348
  "static/chunks/app/vendor/livekit-client.umd.js/route-0952b44bda5bfb6d.js"
349
349
  ],
350
- "/fed/media/robots/[robotId]/video_feed/route": [
350
+ "/robopark/api/robots/[robotId]/stream/route": [
351
351
  "static/chunks/webpack-5252e5254da079b5.js",
352
352
  "static/chunks/4bd1b696-f785427dddbba9fb.js",
353
353
  "static/chunks/1255-404bb8de49c90395.js",
354
354
  "static/chunks/main-app-a1f59168303c7f84.js",
355
- "static/chunks/app/fed/media/robots/[robotId]/video_feed/route-0952b44bda5bfb6d.js"
355
+ "static/chunks/app/robopark/api/robots/[robotId]/stream/route-0952b44bda5bfb6d.js"
356
356
  ],
357
357
  "/(app)/opengraph-image-xg4ifa/route": [
358
358
  "static/chunks/webpack-5252e5254da079b5.js",
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "/_not-found/page": "/_not-found",
3
- "/api/download-piper-model/route": "/api/download-piper-model",
4
3
  "/api/device-character/route": "/api/device-character",
5
4
  "/api/models/route": "/api/models",
5
+ "/api/download-piper-model/route": "/api/download-piper-model",
6
6
  "/api/prewarm/route": "/api/prewarm",
7
7
  "/api/prompt/route": "/api/prompt",
8
8
  "/api/reload-tools/route": "/api/reload-tools",
@@ -11,44 +11,44 @@
11
11
  "/api/robopark/sessions/[sessionId]/end/route": "/api/robopark/sessions/[sessionId]/end",
12
12
  "/api/robopark/transcripts/[sessionId]/route": "/api/robopark/transcripts/[sessionId]",
13
13
  "/api/setup/complete/route": "/api/setup/complete",
14
- "/api/setup/status/route": "/api/setup/status",
15
14
  "/api/setup/test-groq/route": "/api/setup/test-groq",
16
- "/api/setup/test-n8n/route": "/api/setup/test-n8n",
15
+ "/api/setup/status/route": "/api/setup/status",
17
16
  "/api/setup/test-hass/route": "/api/setup/test-hass",
17
+ "/api/setup/test-n8n/route": "/api/setup/test-n8n",
18
+ "/api/test-tts/route": "/api/test-tts",
18
19
  "/api/voices/route": "/api/voices",
20
+ "/api/setup/test-ollama/route": "/api/setup/test-ollama",
19
21
  "/api/wake-word/models/route": "/api/wake-word/models",
20
22
  "/api/wake-word/upload/route": "/api/wake-word/upload",
21
23
  "/api/wake-word/status/route": "/api/wake-word/status",
22
- "/api/test-tts/route": "/api/test-tts",
23
- "/api/setup/test-ollama/route": "/api/setup/test-ollama",
24
24
  "/favicon.ico/route": "/favicon.ico",
25
- "/api/model-status/route": "/api/model-status",
26
25
  "/api/kiosk-hardware/route": "/api/kiosk-hardware",
26
+ "/api/model-status/route": "/api/model-status",
27
27
  "/api/connection-details/route": "/api/connection-details",
28
+ "/api/robopark/device-access/route": "/api/robopark/device-access",
28
29
  "/api/robopark/character-ai/route": "/api/robopark/character-ai",
29
30
  "/api/robopark/camera-token/route": "/api/robopark/camera-token",
30
- "/api/robopark/monitor-token/route": "/api/robopark/monitor-token",
31
- "/api/robopark/device-access/route": "/api/robopark/device-access",
32
31
  "/api/robopark/robots/route": "/api/robopark/robots",
33
- "/api/robopark/live-rooms/route": "/api/robopark/live-rooms",
34
- "/api/robopark/servers/route": "/api/robopark/servers",
35
32
  "/api/robopark/servers/[serverId]/metrics/route": "/api/robopark/servers/[serverId]/metrics",
33
+ "/api/robopark/monitor-token/route": "/api/robopark/monitor-token",
36
34
  "/api/robopark/servers/[serverId]/route": "/api/robopark/servers/[serverId]",
37
- "/api/robopark/robots/[robotId]/voice-engine/route": "/api/robopark/robots/[robotId]/voice-engine",
35
+ "/api/robopark/live-rooms/route": "/api/robopark/live-rooms",
36
+ "/api/robopark/servers/route": "/api/robopark/servers",
37
+ "/api/robopark/sessions/route": "/api/robopark/sessions",
38
+ "/api/robovoice/robots/[robotId]/media-health/route": "/api/robovoice/robots/[robotId]/media-health",
38
39
  "/api/robopark/vision-caption/route": "/api/robopark/vision-caption",
39
40
  "/api/settings/route": "/api/settings",
40
- "/api/robopark/sessions/route": "/api/robopark/sessions",
41
+ "/api/robopark/robots/[robotId]/voice-engine/route": "/api/robopark/robots/[robotId]/voice-engine",
41
42
  "/api/turn-credentials/route": "/api/turn-credentials",
42
- "/api/robopark/transcripts/route": "/api/robopark/transcripts",
43
- "/api/robovoice/robots/[robotId]/media-health/route": "/api/robovoice/robots/[robotId]/media-health",
44
- "/api/wake/route": "/api/wake",
45
43
  "/camera-grid/route": "/camera-grid",
46
- "/robopark/api/robots/[robotId]/stream/route": "/robopark/api/robots/[robotId]/stream",
47
- "/fed/[...path]/route": "/fed/[...path]",
44
+ "/fed/media/robots/[robotId]/video_feed/route": "/fed/media/robots/[robotId]/video_feed",
45
+ "/api/robopark/transcripts/route": "/api/robopark/transcripts",
48
46
  "/tailnet/route": "/tailnet",
47
+ "/fed/[...path]/route": "/fed/[...path]",
48
+ "/api/wake/route": "/api/wake",
49
49
  "/robopark/[...path]/route": "/robopark/[...path]",
50
50
  "/vendor/livekit-client.umd.js/route": "/vendor/livekit-client.umd.js",
51
- "/fed/media/robots/[robotId]/video_feed/route": "/fed/media/robots/[robotId]/video_feed",
51
+ "/robopark/api/robots/[robotId]/stream/route": "/robopark/api/robots/[robotId]/stream",
52
52
  "/(app)/opengraph-image-xg4ifa/route": "/opengraph-image-xg4ifa",
53
53
  "/robopark/api/[...path]/route": "/robopark/api/[...path]",
54
54
  "/robopark/page": "/robopark",
@@ -5,8 +5,8 @@
5
5
  "devFiles": [],
6
6
  "ampDevFiles": [],
7
7
  "lowPriorityFiles": [
8
- "static/yswW2wNJG-5o2FkKKTtei/_buildManifest.js",
9
- "static/yswW2wNJG-5o2FkKKTtei/_ssgManifest.js"
8
+ "static/HVQqws8pwZ7FuPixvQm1d/_buildManifest.js",
9
+ "static/HVQqws8pwZ7FuPixvQm1d/_ssgManifest.js"
10
10
  ],
11
11
  "rootMainFiles": [
12
12
  "static/chunks/webpack-5252e5254da079b5.js",
@@ -34,8 +34,8 @@
34
34
  "dynamicRoutes": {},
35
35
  "notFoundRoutes": [],
36
36
  "preview": {
37
- "previewModeId": "33702872480a5f639e2763fad1b625f6",
38
- "previewModeSigningKey": "2d70db468970bf5e4978c149028e92477b58ee44393dfb1a5e6ade29971cbc63",
39
- "previewModeEncryptionKey": "833f8f6d1a31a2c61645dd20e505e08b88c8c3c2b07b1a121691f152eba97b46"
37
+ "previewModeId": "0d998d4caf45f7b789418b21e5c7f328",
38
+ "previewModeSigningKey": "a0b0b3c3af824d87478b8e8ff7ac2d88635c6044dc512c80831a99c528ba9b24",
39
+ "previewModeEncryptionKey": "30d361a69e866a2a82b3f2afcc58296d4ed7dc870f3fbd88a21aba2fdfaf72c8"
40
40
  }
41
41
  }
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "/_not-found/page": "app/_not-found/page.js",
3
- "/api/download-piper-model/route": "app/api/download-piper-model/route.js",
4
3
  "/api/device-character/route": "app/api/device-character/route.js",
5
4
  "/api/models/route": "app/api/models/route.js",
5
+ "/api/download-piper-model/route": "app/api/download-piper-model/route.js",
6
6
  "/api/prewarm/route": "app/api/prewarm/route.js",
7
7
  "/api/prompt/route": "app/api/prompt/route.js",
8
8
  "/api/reload-tools/route": "app/api/reload-tools/route.js",
@@ -11,44 +11,44 @@
11
11
  "/api/robopark/sessions/[sessionId]/end/route": "app/api/robopark/sessions/[sessionId]/end/route.js",
12
12
  "/api/robopark/transcripts/[sessionId]/route": "app/api/robopark/transcripts/[sessionId]/route.js",
13
13
  "/api/setup/complete/route": "app/api/setup/complete/route.js",
14
- "/api/setup/status/route": "app/api/setup/status/route.js",
15
14
  "/api/setup/test-groq/route": "app/api/setup/test-groq/route.js",
16
- "/api/setup/test-n8n/route": "app/api/setup/test-n8n/route.js",
15
+ "/api/setup/status/route": "app/api/setup/status/route.js",
17
16
  "/api/setup/test-hass/route": "app/api/setup/test-hass/route.js",
17
+ "/api/setup/test-n8n/route": "app/api/setup/test-n8n/route.js",
18
+ "/api/test-tts/route": "app/api/test-tts/route.js",
18
19
  "/api/voices/route": "app/api/voices/route.js",
20
+ "/api/setup/test-ollama/route": "app/api/setup/test-ollama/route.js",
19
21
  "/api/wake-word/models/route": "app/api/wake-word/models/route.js",
20
22
  "/api/wake-word/upload/route": "app/api/wake-word/upload/route.js",
21
23
  "/api/wake-word/status/route": "app/api/wake-word/status/route.js",
22
- "/api/test-tts/route": "app/api/test-tts/route.js",
23
- "/api/setup/test-ollama/route": "app/api/setup/test-ollama/route.js",
24
24
  "/favicon.ico/route": "app/favicon.ico/route.js",
25
- "/api/model-status/route": "app/api/model-status/route.js",
26
25
  "/api/kiosk-hardware/route": "app/api/kiosk-hardware/route.js",
26
+ "/api/model-status/route": "app/api/model-status/route.js",
27
27
  "/api/connection-details/route": "app/api/connection-details/route.js",
28
+ "/api/robopark/device-access/route": "app/api/robopark/device-access/route.js",
28
29
  "/api/robopark/character-ai/route": "app/api/robopark/character-ai/route.js",
29
30
  "/api/robopark/camera-token/route": "app/api/robopark/camera-token/route.js",
30
- "/api/robopark/monitor-token/route": "app/api/robopark/monitor-token/route.js",
31
- "/api/robopark/device-access/route": "app/api/robopark/device-access/route.js",
32
31
  "/api/robopark/robots/route": "app/api/robopark/robots/route.js",
33
- "/api/robopark/live-rooms/route": "app/api/robopark/live-rooms/route.js",
34
- "/api/robopark/servers/route": "app/api/robopark/servers/route.js",
35
32
  "/api/robopark/servers/[serverId]/metrics/route": "app/api/robopark/servers/[serverId]/metrics/route.js",
33
+ "/api/robopark/monitor-token/route": "app/api/robopark/monitor-token/route.js",
36
34
  "/api/robopark/servers/[serverId]/route": "app/api/robopark/servers/[serverId]/route.js",
37
- "/api/robopark/robots/[robotId]/voice-engine/route": "app/api/robopark/robots/[robotId]/voice-engine/route.js",
35
+ "/api/robopark/live-rooms/route": "app/api/robopark/live-rooms/route.js",
36
+ "/api/robopark/servers/route": "app/api/robopark/servers/route.js",
37
+ "/api/robopark/sessions/route": "app/api/robopark/sessions/route.js",
38
+ "/api/robovoice/robots/[robotId]/media-health/route": "app/api/robovoice/robots/[robotId]/media-health/route.js",
38
39
  "/api/robopark/vision-caption/route": "app/api/robopark/vision-caption/route.js",
39
40
  "/api/settings/route": "app/api/settings/route.js",
40
- "/api/robopark/sessions/route": "app/api/robopark/sessions/route.js",
41
+ "/api/robopark/robots/[robotId]/voice-engine/route": "app/api/robopark/robots/[robotId]/voice-engine/route.js",
41
42
  "/api/turn-credentials/route": "app/api/turn-credentials/route.js",
42
- "/api/robopark/transcripts/route": "app/api/robopark/transcripts/route.js",
43
- "/api/robovoice/robots/[robotId]/media-health/route": "app/api/robovoice/robots/[robotId]/media-health/route.js",
44
- "/api/wake/route": "app/api/wake/route.js",
45
43
  "/camera-grid/route": "app/camera-grid/route.js",
46
- "/robopark/api/robots/[robotId]/stream/route": "app/robopark/api/robots/[robotId]/stream/route.js",
47
- "/fed/[...path]/route": "app/fed/[...path]/route.js",
44
+ "/fed/media/robots/[robotId]/video_feed/route": "app/fed/media/robots/[robotId]/video_feed/route.js",
45
+ "/api/robopark/transcripts/route": "app/api/robopark/transcripts/route.js",
48
46
  "/tailnet/route": "app/tailnet/route.js",
47
+ "/fed/[...path]/route": "app/fed/[...path]/route.js",
48
+ "/api/wake/route": "app/api/wake/route.js",
49
49
  "/robopark/[...path]/route": "app/robopark/[...path]/route.js",
50
50
  "/vendor/livekit-client.umd.js/route": "app/vendor/livekit-client.umd.js/route.js",
51
- "/fed/media/robots/[robotId]/video_feed/route": "app/fed/media/robots/[robotId]/video_feed/route.js",
51
+ "/robopark/api/robots/[robotId]/stream/route": "app/robopark/api/robots/[robotId]/stream/route.js",
52
52
  "/(app)/opengraph-image-xg4ifa/route": "app/(app)/opengraph-image-xg4ifa/route.js",
53
53
  "/robopark/api/[...path]/route": "app/robopark/api/[...path]/route.js",
54
54
  "/robopark/page": "app/robopark/page.js",
@@ -3,8 +3,8 @@
3
3
  "functions": {
4
4
  "/api/kiosk-hardware": {},
5
5
  "/api/robovoice/robots/[robotId]/media-health": {},
6
+ "/robopark/[...path]": {},
6
7
  "/fed/media/robots/[robotId]/video_feed": {},
7
- "/robopark/api/robots/[robotId]/stream": {},
8
- "/robopark/[...path]": {}
8
+ "/robopark/api/robots/[robotId]/stream": {}
9
9
  }
10
10
  }
@@ -1 +1 @@
1
- <!DOCTYPE html><html><head><meta charSet="utf-8" data-next-head=""/><meta name="viewport" content="width=device-width" data-next-head=""/><title data-next-head="">500: Internal Server Error</title><noscript data-n-css=""></noscript><script defer="" noModule="" src="/_next/static/chunks/polyfills-42372ed130431b0a.js"></script><script src="/_next/static/chunks/webpack-5252e5254da079b5.js" defer=""></script><script src="/_next/static/chunks/framework-e60c938074ff7136.js" defer=""></script><script src="/_next/static/chunks/main-9e24e8708ff075fc.js" defer=""></script><script src="/_next/static/chunks/pages/_app-131c90850aef965b.js" defer=""></script><script src="/_next/static/chunks/pages/_error-e4ba546eb376bdf4.js" defer=""></script><script src="/_next/static/yswW2wNJG-5o2FkKKTtei/_buildManifest.js" defer=""></script><script src="/_next/static/yswW2wNJG-5o2FkKKTtei/_ssgManifest.js" defer=""></script></head><body><div id="__next"><div style="font-family:system-ui,&quot;Segoe UI&quot;,Roboto,Helvetica,Arial,sans-serif,&quot;Apple Color Emoji&quot;,&quot;Segoe UI Emoji&quot;;height:100vh;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center"><div style="line-height:48px"><style>body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}</style><h1 class="next-error-h1" style="display:inline-block;margin:0 20px 0 0;padding-right:23px;font-size:24px;font-weight:500;vertical-align:top">500</h1><div style="display:inline-block"><h2 style="font-size:14px;font-weight:400;line-height:28px">Internal Server Error<!-- -->.</h2></div></div></div></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{"statusCode":500}},"page":"/_error","query":{},"buildId":"yswW2wNJG-5o2FkKKTtei","nextExport":true,"isFallback":false,"gip":true,"scriptLoader":[]}</script></body></html>
1
+ <!DOCTYPE html><html><head><meta charSet="utf-8" data-next-head=""/><meta name="viewport" content="width=device-width" data-next-head=""/><title data-next-head="">500: Internal Server Error</title><noscript data-n-css=""></noscript><script defer="" noModule="" src="/_next/static/chunks/polyfills-42372ed130431b0a.js"></script><script src="/_next/static/chunks/webpack-5252e5254da079b5.js" defer=""></script><script src="/_next/static/chunks/framework-e60c938074ff7136.js" defer=""></script><script src="/_next/static/chunks/main-9e24e8708ff075fc.js" defer=""></script><script src="/_next/static/chunks/pages/_app-131c90850aef965b.js" defer=""></script><script src="/_next/static/chunks/pages/_error-e4ba546eb376bdf4.js" defer=""></script><script src="/_next/static/HVQqws8pwZ7FuPixvQm1d/_buildManifest.js" defer=""></script><script src="/_next/static/HVQqws8pwZ7FuPixvQm1d/_ssgManifest.js" defer=""></script></head><body><div id="__next"><div style="font-family:system-ui,&quot;Segoe UI&quot;,Roboto,Helvetica,Arial,sans-serif,&quot;Apple Color Emoji&quot;,&quot;Segoe UI Emoji&quot;;height:100vh;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center"><div style="line-height:48px"><style>body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}</style><h1 class="next-error-h1" style="display:inline-block;margin:0 20px 0 0;padding-right:23px;font-size:24px;font-weight:500;vertical-align:top">500</h1><div style="display:inline-block"><h2 style="font-size:14px;font-weight:400;line-height:28px">Internal Server Error<!-- -->.</h2></div></div></div></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{"statusCode":500}},"page":"/_error","query":{},"buildId":"HVQqws8pwZ7FuPixvQm1d","nextExport":true,"isFallback":false,"gip":true,"scriptLoader":[]}</script></body></html>
@@ -1 +1 @@
1
- {"node":{},"edge":{},"encryptionKey":"vp2cM5NGWFNKyrkaTwm4nAQcKmJtMWAWyhjM08yE2kI="}
1
+ {"node":{},"edge":{},"encryptionKey":"ioPxRkh8Xwgx69nEuUm4wQZjzduDtHbMBv/X8CyfNLA="}
@@ -1453,6 +1453,71 @@ footer.cmd{left:var(--global-nav-width);transition:left .24s cubic-bezier(.2,.8,
1453
1453
  entry.dataHandlers=(entry.dataHandlers||[]).filter(function(handler){return !handler.node||handler.node.isConnected;});
1454
1454
  return entry;
1455
1455
  }
1456
+ // MJPEG fallback: when a LiveKit room connects but nobody publishes video
1457
+ // (no character call open), the tile fell into 'waiting for camera track'
1458
+ // forever. After a grace period we overlay the vision-server MJPEG stream in
1459
+ // the tile while keeping the room joined; a real LiveKit track always wins
1460
+ // and tears the overlay down. Every open MJPEG <img> holds an HTTP
1461
+ // connection, so teardown must be exhaustive (close, sweep, pagehide, error).
1462
+ var FLEET_FALLBACK_DELAY=12000;
1463
+ function removeFleetFallbackImgs(entry){
1464
+ (entry.fallbackImgs||[]).forEach(function(img){
1465
+ try{img.onload=null;img.onerror=null;img.src='';}catch(e){}
1466
+ var host=img._rpHost;
1467
+ if(img.parentNode)img.parentNode.removeChild(img);
1468
+ if(host&&!entry.hasTrack)host.classList.remove('live');
1469
+ });
1470
+ entry.fallbackImgs=[];entry.fallbackActive=false;
1471
+ }
1472
+ function clearFleetFallback(entry){
1473
+ if(!entry)return;
1474
+ if(entry.fallbackTimer){clearTimeout(entry.fallbackTimer);entry.fallbackTimer=null;}
1475
+ removeFleetFallbackImgs(entry);
1476
+ }
1477
+ function tryFleetFallback(deviceId,entry){
1478
+ if(fleetLiveKitRooms[deviceId]!==entry||entry.hasTrack)return;
1479
+ pruneRoomTargets(entry);
1480
+ if(!entry.videos.length)return;
1481
+ entry.fallbackImgs=(entry.fallbackImgs||[]).filter(function(img){return img&&img.isConnected;});
1482
+ entry.videos.forEach(function(video){
1483
+ var host=(video.closest&&video.closest('[data-fleet-camera-host]'))||video.parentNode;
1484
+ if(!host||host.querySelector('img[data-fleet-fallback]'))return;
1485
+ var img=document.createElement('img');
1486
+ img.setAttribute('data-fleet-fallback',deviceId);
1487
+ img.alt='fallback camera stream';
1488
+ img.style.position='absolute';img.style.inset='0';img.style.zIndex='2';
1489
+ img._rpHost=host;
1490
+ img.onload=function(){
1491
+ if(fleetLiveKitRooms[deviceId]!==entry||entry.hasTrack)return;
1492
+ entry.fallbackActive=true;entry.fallbackFailures=0;
1493
+ host.classList.add('live');
1494
+ setRoomStatus(entry,'MJPEG camera live (fallback)');
1495
+ };
1496
+ img.onerror=function(){
1497
+ if(fleetLiveKitRooms[deviceId]!==entry)return;
1498
+ removeFleetFallbackImgs(entry);
1499
+ if(entry.hasTrack)return;
1500
+ setRoomStatus(entry,'connected · waiting for camera track');
1501
+ armFleetFallback(deviceId,entry,true);
1502
+ };
1503
+ img.src='/fed/media/robots/'+encodeURIComponent(deviceId)+'/video_feed'+QS+(QS?'&':'?')+'_='+Date.now();
1504
+ entry.fallbackImgs.push(img);
1505
+ host.appendChild(img);
1506
+ });
1507
+ }
1508
+ function armFleetFallback(deviceId,entry,backoff){
1509
+ if(!entry||entry.fallbackTimer||entry.hasTrack)return;
1510
+ var delay=FLEET_FALLBACK_DELAY;
1511
+ if(backoff){
1512
+ entry.fallbackFailures=(entry.fallbackFailures||0)+1;
1513
+ delay=Math.min(15000,Math.round(1500*Math.pow(1.6,Math.min(entry.fallbackFailures-1,6))+Math.random()*500));
1514
+ }
1515
+ entry.fallbackTimer=setTimeout(function(){
1516
+ entry.fallbackTimer=null;
1517
+ if(fleetLiveKitRooms[deviceId]!==entry)return;
1518
+ tryFleetFallback(deviceId,entry);
1519
+ },delay);
1520
+ }
1456
1521
  // Always disconnect before dropping a room reference. Abandoning the object
1457
1522
  // left the monitor joined server-side, so every refresh or re-render stacked
1458
1523
  // another "RoboPark Control monitor" in the room -- rooms were carrying 3-4
@@ -1461,6 +1526,8 @@ footer.cmd{left:var(--global-nav-width);transition:left .24s cubic-bezier(.2,.8,
1461
1526
  entry=entry||fleetLiveKitRooms[deviceId];
1462
1527
  if(!entry)return;
1463
1528
  if(entry.retry){clearTimeout(entry.retry);entry.retry=null;}
1529
+ clearFleetFallback(entry);
1530
+ entry.hasTrack=false;
1464
1531
  var room=entry.room;entry.room=null;
1465
1532
  if(room){try{room.disconnect(true);}catch(e){}}
1466
1533
  if(fleetLiveKitRooms[deviceId]===entry)delete fleetLiveKitRooms[deviceId];
@@ -1517,32 +1584,48 @@ footer.cmd{left:var(--global-nav-width);transition:left .24s cubic-bezier(.2,.8,
1517
1584
  function openFleetCameraRoom(deviceId){
1518
1585
  var entry=fleetLiveKitRooms[deviceId];if(!entry||entry.connecting||entry.room)return;
1519
1586
  var roomId=rpCameraStreamIds[deviceId]||deviceId;
1587
+ entry.roomId=String(roomId);
1520
1588
  entry.connecting=true;
1521
1589
  Promise.all([ensureFleetLiveKit(),fetch('/api/robopark/monitor-token',{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify({room_name:'robopark-'+roomId})}).then(function(r){if(!r.ok)throw new Error('monitor token '+r.status);return r.json();})]).then(function(parts){
1522
1590
  var LK=parts[0],info=parts[1];
1523
1591
  entry.connecting=false;
1524
1592
  if(fleetLiveKitRooms[deviceId]!==entry)return null;
1525
1593
  var room=new LK.Room();entry.room=room;
1526
- room.on(LK.RoomEvent.TrackSubscribed,function(track){if(track.kind==='video'){pruneRoomTargets(entry);attachRoomTrack(entry,track);setRoomStatus(entry,'LiveKit camera live');}});
1594
+ room.on(LK.RoomEvent.TrackSubscribed,function(track){if(track.kind==='video'){entry.hasTrack=true;clearFleetFallback(entry);pruneRoomTargets(entry);attachRoomTrack(entry,track);setRoomStatus(entry,'LiveKit camera live');}});
1527
1595
  room.on(LK.RoomEvent.DataReceived,function(payload,participant,kind,topic){try{var data=JSON.parse(new TextDecoder().decode(payload));if(topic==='robopark_monitor'&&(data.type==='pipeline'||data.type==='stage'||data.type==='snapshot')){var canonical=data.stage||data.canonical_stage,snapshot={stage:canonical,state:data.state||data.stage,status:data.status||'active',stages:data.stages||data.pipeline||[],timestamp:data.timestamp||Date.now()};fleetPipelineByDevice[deviceId]=snapshot;renderParkMonitorPipeline(deviceId,snapshot);Array.prototype.forEach.call(document.querySelectorAll('[data-fleet-camera="'+deviceId+'"]'),function(camera){var card=camera.closest('.node'),focus=card&&card.querySelector('.fleet-stage-focus');if(focus&&canonical){focus.className='fleet-stage-focus active';focus.innerHTML='<span>Live production stage</span><b>'+esc(String(canonical).replace(/_/g,' '))+'</b>';}});}entry.dataHandlers.slice().forEach(function(handler){handler(data,topic);});}catch(e){}});
1528
- room.on(LK.RoomEvent.Disconnected,function(){if(fleetLiveKitRooms[deviceId]!==entry)return;entry.room=null;setRoomStatus(entry,'camera disconnected · retrying');scheduleFleetCameraRetry(deviceId);});
1596
+ room.on(LK.RoomEvent.Disconnected,function(){if(fleetLiveKitRooms[deviceId]!==entry)return;entry.room=null;entry.hasTrack=false;if(!entry.fallbackActive)setRoomStatus(entry,'camera disconnected · retrying');scheduleFleetCameraRetry(deviceId);});
1529
1597
  return room.connect(info.serverUrl,info.token);
1530
- }).then(function(result){if(result===null||fleetLiveKitRooms[deviceId]!==entry)return;entry.failures=0;setRoomStatus(entry,'connected · waiting for camera track');}).catch(function(error){
1531
- entry.connecting=false;entry.room=null;
1598
+ }).then(function(result){if(result===null||fleetLiveKitRooms[deviceId]!==entry)return;entry.failures=0;if(entry.fallbackActive){setRoomStatus(entry,'MJPEG camera live (fallback)');}else{setRoomStatus(entry,'connected · waiting for camera track');armFleetFallback(deviceId,entry);}}).catch(function(error){
1599
+ entry.connecting=false;entry.room=null;entry.hasTrack=false;
1532
1600
  if(fleetLiveKitRooms[deviceId]!==entry)return;
1533
- setRoomStatus(entry,'camera unavailable · '+error.message+' · retrying');
1601
+ if(!entry.fallbackActive)setRoomStatus(entry,'camera unavailable · '+error.message+' · retrying');
1534
1602
  scheduleFleetCameraRetry(deviceId);
1603
+ // LiveKit is down entirely; don't sit dark for the full grace period.
1604
+ if(!entry.fallbackActive&&!entry.fallbackTimer)armFleetFallback(deviceId,entry,true);
1535
1605
  });
1536
1606
  }
1537
1607
  function connectFleetLiveCamera(video,deviceId,state){
1538
1608
  if(!deviceId||!video)return;
1609
+ var desiredRoomId=String(rpCameraStreamIds[deviceId]||deviceId);
1539
1610
  var entry=fleetLiveKitRooms[deviceId];
1540
- if(!entry){entry={connecting:false,room:null,videos:[],states:[],dataHandlers:[],retry:null,failures:0};fleetLiveKitRooms[deviceId]=entry;}
1611
+ // The live-room census can discover that the camera publishes into a claim
1612
+ // room (robopark-claim_<hash>) after we already joined robopark-<device_id>.
1613
+ // Re-key: drop the stale subscription and rejoin the room that actually has
1614
+ // a publisher. Only when not mid-connect and the target genuinely changed.
1615
+ if(entry&&!entry.connecting&&entry.roomId&&entry.roomId!==desiredRoomId){closeFleetCameraRoom(deviceId,entry);entry=null;}
1616
+ if(!entry){entry={connecting:false,room:null,roomId:null,videos:[],states:[],dataHandlers:[],retry:null,failures:0,hasTrack:false,fallbackTimer:null,fallbackImgs:[],fallbackActive:false,fallbackFailures:0};fleetLiveKitRooms[deviceId]=entry;}
1541
1617
  pruneRoomTargets(entry);
1542
1618
  if(entry.videos.indexOf(video)<0)entry.videos.push(video);
1543
1619
  if(state&&entry.states.indexOf(state)<0)entry.states.push(state);
1544
1620
  if(entry.room){
1545
- entry.room.remoteParticipants.forEach(function(p){p.trackPublications.forEach(function(pub){if(pub.track&&pub.track.kind==='video'){attachRoomTrack(entry,pub.track);setRoomStatus(entry,'LIVE · secure camera relay');}});});
1621
+ entry.room.remoteParticipants.forEach(function(p){p.trackPublications.forEach(function(pub){if(pub.track&&pub.track.kind==='video'){entry.hasTrack=true;clearFleetFallback(entry);attachRoomTrack(entry,pub.track);setRoomStatus(entry,'LIVE · secure camera relay');}});});
1622
+ if(!entry.hasTrack){
1623
+ // Freshly rendered tile joined an already-open but publisher-less room:
1624
+ // re-cover it with the active fallback, or make sure the timer is armed
1625
+ // so 'waiting for camera track' can never be a terminal state.
1626
+ if(entry.fallbackActive)tryFleetFallback(deviceId,entry);
1627
+ else if(!entry.fallbackTimer)armFleetFallback(deviceId,entry);
1628
+ }
1546
1629
  return;
1547
1630
  }
1548
1631
  // A connect or retry is already in flight; it will pick up this element.
@@ -3373,7 +3456,7 @@ footer.cmd{left:var(--global-nav-width);transition:left .24s cubic-bezier(.2,.8,
3373
3456
  // reads the device id back out of the room name, so the device id is the
3374
3457
  // canonical stream identity. Deriving it from the character instead made
3375
3458
  // this grid watch a different room than the one cameras publish into.
3376
- rpCameraStreamIds={};devices=devices.map(function(d){var robot=robots.find(function(r){return String(r.robot_id||r.id)===String(d.id)||String(r.character_id||r.character_preset_id||'').toLowerCase()===String(d.character_id||'').toLowerCase();})||{},streamId=String(d.id).trim().toLowerCase();rpCameraStreamIds[d.id]=streamId;if(!robot.id&&!robot.robot_id)return d;var effectiveStatus=d.active_live_room?'online':(robot.connection_status||robot.status||d.connection_status||d.status);return Object.assign({},d,{status:effectiveStatus,connection_status:effectiveStatus,canonical_robot:robot});});
3459
+ rpCameraStreamIds={};devices=devices.map(function(d){var robot=robots.find(function(r){return String(r.robot_id||r.id)===String(d.id)||String(r.character_id||r.character_preset_id||'').toLowerCase()===String(d.character_id||'').toLowerCase();})||{},liveRoomName=d.active_live_room&&d.active_live_room.room_name?String(d.active_live_room.room_name).trim():'',streamId=liveRoomName?liveRoomName.replace(/^robopark-/,''):String(d.id).trim().toLowerCase();rpCameraStreamIds[d.id]=streamId;if(!robot.id&&!robot.robot_id)return d;var effectiveStatus=d.active_live_room?'online':(robot.connection_status||robot.status||d.connection_status||d.status);return Object.assign({},d,{status:effectiveStatus,connection_status:effectiveStatus,canonical_robot:robot});});
3377
3460
  var preferredDevices={};devices.forEach(function(d){var character=String(d.character_id||d.character_preset_id||'').replace(/_heb$/i,'').trim().toLowerCase(),name=String(d.name||'').replace(/_heb$/i,'').trim().toLowerCase(),key=character||name||String(d.id);var current=preferredDevices[key];if(!current||schedulerDeviceScore(d)>schedulerDeviceScore(current))preferredDevices[key]=d;});
3378
3461
  var cameraDevices=showOffline?devices:Object.keys(preferredDevices).map(function(key){return preferredDevices[key];}).filter(function(d){return String(d.connection_status||d.status||'').toLowerCase()==='online'||schedulerHeartbeatFresh(d);});
3379
3462
  var items=cameraDevices.map(function(d){var r=d.canonical_robot||robots.find(function(x){return String(x.robot_id||x.id)===String(d.id);})||{},s=sessions.find(function(x){return String(x.robot_id||x.device_id)===String(d.id);})||null,mh=d.device_inventory&&d.device_inventory.media_health||{},effectiveStatus=r.connection_status||r.status||d.connection_status||d.status||'enrolled';return {id:d.id,name:d.name||r.name||d.id,status:effectiveStatus,robot:r,device:d,session:s,camera:mh.camera_access!==false,mic:mh.audio_access!==false};});
@@ -453,7 +453,10 @@
453
453
  // Without a deadline a stalled control request hung the join forever: no
454
454
  // error surfaced, so nothing ever retried.
455
455
  if(!request.signal&&window.AbortSignal&&AbortSignal.timeout)request.signal=AbortSignal.timeout(25000);
456
- return fetch('/robopark'+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()})}
456
+ return fetch('/robopark'+path,request).then(function(response){if(!response.ok)return response.text().then(function(body){// Name the request in the failure. "Method Not Allowed" with no URL sent an
457
+ // operator (and the tooling behind them) hunting through every endpoint the
458
+ // page touches; the failing call should identify itself.
459
+ var failure=new Error((options.method||'GET')+' '+path+' -> '+response.status+(body?': '+body.slice(0,200):''));failure.status=response.status;throw failure});return response.json()})}
457
460
  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}
458
461
  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(){})}
459
462
  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()}
@@ -478,7 +481,11 @@
478
481
  try{navigator.sendBeacon('/robopark/api/sessions/'+encodeURIComponent(session)+'/end?reason=page_closed',new Blob([JSON.stringify({details:{page_closed:true},source:'public_join'})],{type:'application/json'}))}catch(_){}
479
482
  });
480
483
  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}}
481
- 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='/fed/media/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();startMotion(myGeneration);kioskNote();if(kioskOn){try{motionSent[kioskGreeting]=1;conversation.sendUserMessage(kioskGreeting)}catch(_){}}},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();kioskNote();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';if(speaking)kioskNote();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;
484
+ 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;var rs=el('robot-stream');rs.onerror=function(){// The MJPEG relay is a bonus, not the feed. A claim_ device is a browser
485
+ // camera with no vision server behind it, so this URL can 502 by design --
486
+ // and with no handler the panel simply went black over the top of a local
487
+ // camera that was working the whole time. Fall back to the local preview.
488
+ rs.onerror=null;rs.removeAttribute('src');rs.hidden=true;el('robot-video').hidden=false;};rs.src='/fed/media/robots/'+encodeURIComponent(call.robot_video_ref)+'/video_feed';rs.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();startMotion(myGeneration);kioskNote();if(kioskOn){try{motionSent[kioskGreeting]=1;conversation.sendUserMessage(kioskGreeting)}catch(_){}}},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();kioskNote();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';if(speaking)kioskNote();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;
482
489
  // A 4xx means the link itself is wrong and retrying cannot fix it. Anything
483
490
  // else (5xx, signing timeout, network drop) is transient, and dead-ending
484
491
  // here is what left a failed call with no reconnect until a manual re-join.