robopark 3.3.11 → 3.3.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "robopark",
3
- "version": "3.3.11",
3
+ "version": "3.3.12",
4
4
  "description": "RoboPark fleet control CLI \u2014 scheduler, control UI, and character voice calls. Standalone: no infinicode runtime.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -41,7 +41,18 @@
41
41
  callPanel.insertAdjacentHTML('beforeend','<p class="call-note call-status" id=hardware-status>Grant browser media access to reveal device names.</p><span class=mode-badge id=mode-badge hidden>TEST ? BROWSER ONLY</span>');
42
42
  function setHardwareStatus(message,state){text('hardware-status',message);el('hardware-status').className='call-note call-status '+(state||'')}
43
43
  function fillHardwareSelect(id,devices,kind,label){var select=el(id),current=select.value;select.innerHTML='<option value=default>System default '+label+'</option>';devices.filter(function(device){return device.kind===kind&&device.deviceId&&device.deviceId!=='default';}).forEach(function(device,index){var option=document.createElement('option');option.value=device.deviceId;option.textContent=device.label||(label+' '+(index+1));select.appendChild(option)});if(current&&Array.from(select.options).some(function(option){return option.value===current}))select.value=current}
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})}
44
+ // Browsers expose navigator.mediaDevices only in a secure context, so on a
45
+ // plain http:// LAN or tailnet address it is undefined and every camera and
46
+ // microphone call throws. Naming that explicitly matters: the failure looks
47
+ // like a broken browser or a broken build, and it is neither.
48
+ function mediaBlockedReason(){
49
+ if(navigator.mediaDevices&&navigator.mediaDevices.getUserMedia&&navigator.mediaDevices.enumerateDevices)return '';
50
+ var secure=window.isSecureContext||location.protocol==='https:'||location.hostname==='localhost'||location.hostname==='127.0.0.1';
51
+ return secure
52
+ ? 'This browser does not expose microphone and camera access.'
53
+ : 'Microphone and camera are blocked because this page is not on a secure origin ('+location.protocol+'//'+location.host+'). Open it over https:// or from localhost.';
54
+ }
55
+ function refreshHardware(requestPermission){if(!navigator.mediaDevices||!navigator.mediaDevices.enumerateDevices){setHardwareStatus(mediaBlockedReason(),'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})}
45
56
  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}
46
57
  function scheduleCameraPublishRetry(){if(cameraPublishRetry||ending||!localVideoStream)return;cameraPublishRetry=setTimeout(function(){cameraPublishRetry=null;publishLocalVideo().catch(function(){})},3000)}
47
58
  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})}
@@ -331,7 +342,7 @@
331
342
  var status=Number(error&&error.status||0),permanent=(status>=400&&status<500)||status===503;
332
343
  if(!permanent&&alwaysOn&&desired&&!ending){el('join').hidden=true;el('end').hidden=false;scheduleReconnect();return}
333
344
  fail(details.message)})}
334
- 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))}})}
345
+ function start(){if(desired||conversation||connecting)return;var mediaBlocked=mediaBlockedReason();if(mediaBlocked){fail(mediaBlocked);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))}})}
335
346
  el('kiosk').onclick=function(){
336
347
  kioskOn=!kioskOn;syncKioskButton();
337
348
  if(kioskOn){kioskStart()}
@@ -40,7 +40,18 @@
40
40
  callPanel.insertAdjacentHTML('beforeend','<p class="call-note call-status" id=hardware-status>Grant browser media access to reveal device names.</p><span class=mode-badge id=mode-badge hidden>TEST ? BROWSER ONLY</span>');
41
41
  function setHardwareStatus(message,state){text('hardware-status',message);el('hardware-status').className='call-note call-status '+(state||'')}
42
42
  function fillHardwareSelect(id,devices,kind,label){var select=el(id),current=select.value;select.innerHTML='<option value=default>System default '+label+'</option>';devices.filter(function(device){return device.kind===kind&&device.deviceId&&device.deviceId!=='default';}).forEach(function(device,index){var option=document.createElement('option');option.value=device.deviceId;option.textContent=device.label||(label+' '+(index+1));select.appendChild(option)});if(current&&Array.from(select.options).some(function(option){return option.value===current}))select.value=current}
43
- 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})}
43
+ // Browsers expose navigator.mediaDevices only in a secure context, so on a
44
+ // plain http:// LAN or tailnet address it is undefined and every camera and
45
+ // microphone call throws. Naming that explicitly matters: the failure looks
46
+ // like a broken browser or a broken build, and it is neither.
47
+ function mediaBlockedReason(){
48
+ if(navigator.mediaDevices&&navigator.mediaDevices.getUserMedia&&navigator.mediaDevices.enumerateDevices)return '';
49
+ var secure=window.isSecureContext||location.protocol==='https:'||location.hostname==='localhost'||location.hostname==='127.0.0.1';
50
+ return secure
51
+ ? 'This browser does not expose microphone and camera access.'
52
+ : 'Microphone and camera are blocked because this page is not on a secure origin ('+location.protocol+'//'+location.host+'). Open it over https:// or from localhost.';
53
+ }
54
+ function refreshHardware(requestPermission){if(!navigator.mediaDevices||!navigator.mediaDevices.enumerateDevices){setHardwareStatus(mediaBlockedReason(),'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})}
44
55
  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}
45
56
  function scheduleCameraPublishRetry(){if(cameraPublishRetry||ending||!localVideoStream)return;cameraPublishRetry=setTimeout(function(){cameraPublishRetry=null;publishLocalVideo().catch(function(){})},3000)}
46
57
  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})}
@@ -330,7 +341,7 @@
330
341
  var status=Number(error&&error.status||0),permanent=(status>=400&&status<500)||status===503;
331
342
  if(!permanent&&alwaysOn&&desired&&!ending){el('join').hidden=true;el('end').hidden=false;scheduleReconnect();return}
332
343
  fail(details.message)})}
333
- 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))}})}
344
+ function start(){if(desired||conversation||connecting)return;var mediaBlocked=mediaBlockedReason();if(mediaBlocked){fail(mediaBlocked);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))}})}
334
345
  el('kiosk').onclick=function(){
335
346
  kioskOn=!kioskOn;syncKioskButton();
336
347
  if(kioskOn){kioskStart()}