infinicode 2.8.81 → 2.8.83

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.
@@ -4005,7 +4005,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
4005
4005
  var drawerId=null, camRAF=null, meterRAF=null, camAbort=null, drawerTimer=null, pipelineTimer=null, deviceSelectorsReady=false;
4006
4006
  var previewOn=false, previewTimer=null; // on-demand operator camera preview
4007
4007
  var visionOn=false; // RoboVisionAI_PI overlay feed (direct MJPEG, not LiveKit)
4008
- var lkRoom=null, micAnalyser=null, audioCtx=null, lkAudioEls=[], micMonitorConnecting=false, micMonitorState='no active conversation session', micMonitorAudible=false, micMonitorVolume=0.8;
4008
+ var lkRoom=null, micAnalyser=null, micMonitorSource=null, micMonitorGain=null, audioCtx=null, lkAudioEls=[], micMonitorConnecting=false, micMonitorState='no active conversation session', micMonitorAudible=false, micMonitorVolume=0.8;
4009
4009
  var imgs={}; // preloaded robot concept avatars
4010
4010
  var TW=44, TH=22, PARK_W=15, PARK_D=8;
4011
4011
  // Plaza footprint and parking lot (upper-right corner of the plaza)
@@ -6096,6 +6096,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
6096
6096
  if(pipelineTimer){ clearInterval(pipelineTimer); pipelineTimer=null; }
6097
6097
  if(lkRoom){ try{ lkRoom.disconnect(); }catch(e){} lkRoom=null; }
6098
6098
  lkAudioEls.forEach(function(el){try{el.pause();el.remove();}catch(e){}});lkAudioEls=[];
6099
+ if(micMonitorSource)try{micMonitorSource.disconnect();}catch(e){}micMonitorSource=null;micMonitorGain=null;
6099
6100
  micMonitorAudible=false;micMonitorConnecting=false;micMonitorState='no active conversation session';
6100
6101
  micAnalyser=null;
6101
6102
  }
@@ -6265,11 +6266,12 @@ button.act:disabled{opacity:0.5;cursor:default;}
6265
6266
  function refreshDeviceSelectors(n){
6266
6267
  var device=schedulerDevice(n)||{}, inv=device.device_inventory||{};
6267
6268
  if(hasRealInventory(inv)) deviceSelectorsReady=true;
6268
- var search=$('pk-device-search'), query=(search&&search.value||'').trim().toLowerCase();
6269
- // Do not invent Picamera/V4L2 choices when the robot has not reported hardware.
6270
- var video=deviceList(inv,'video',['auto','none']);
6271
- var input=deviceList(inv,'audio_input',['default','none']);
6272
- var output=deviceList(inv,'audio_output',['default','none']);
6269
+ var query='';
6270
+ // Launch hardware profile shared by every deployed robot. Do not expose
6271
+ // unstable PortAudio indexes, HDMI, or Pi codec devices in production.
6272
+ var video=[{id:'/dev/video0',name:'H65 USB CAMERA (/dev/video0)'}];
6273
+ var input=[{id:'2',name:'USB microphone (hw:3,0)'}];
6274
+ var output=[{id:'1',name:'USB speaker (hw:2,0)'}];
6273
6275
  function fill(id, list, selected){
6274
6276
  var el=$(id); if(!el) return;
6275
6277
  // Heartbeat polling rebuilds this panel frequently. Preserve an
@@ -6282,9 +6284,9 @@ button.act:disabled{opacity:0.5;cursor:default;}
6282
6284
  });
6283
6285
  if(!el.value && effectiveSelected){ var o=document.createElement('option'); o.value=String(effectiveSelected); o.textContent=String(effectiveSelected)+(el.dataset.dirty==='true'?' (selected)':' (saved)'); o.selected=true; el.appendChild(o); }
6284
6286
  }
6285
- fill('pk-video-device', video, device.video_device||'auto');
6286
- fill('pk-audio-input', input, device.audio_device||'default');
6287
- fill('pk-audio-output', output, device.audio_output_device||'default');
6287
+ fill('pk-video-device', video, '/dev/video0');
6288
+ fill('pk-audio-input', input, '2');
6289
+ fill('pk-audio-output', output, '1');
6288
6290
  var selectedOutput=$('pk-audio-output'),selectedLabel=$('pk-spk-selected');
6289
6291
  if(selectedLabel&&selectedOutput){var selectedOption=selectedOutput.options&&selectedOutput.selectedIndex>=0?selectedOutput.options[selectedOutput.selectedIndex]:null;selectedLabel.textContent=selectedOption?String(selectedOption.textContent||selectedOption.value):String(selectedOutput.value||'default');}
6290
6292
  var greetings=$('pk-greeting-phrases');
@@ -6302,7 +6304,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
6302
6304
  }
6303
6305
  function saveDeviceSelectors(n){
6304
6306
  var device=schedulerDevice(n), id=device&&device.id; if(!id){ showOut('device inventory is not available yet'); return; }
6305
- var body={video_device:$('pk-video-device').value, audio_device:$('pk-audio-input').value, audio_output_device:$('pk-audio-output').value,
6307
+ var body={video_device:'/dev/video0', audio_device:'2', audio_output_device:'1',
6306
6308
  greeting_phrases:($('pk-greeting-phrases').value||'').split(String.fromCharCode(10)).map(function(x){return x.trim();}).filter(Boolean)};
6307
6309
  var button=$('pk-device-save'),note=$('pk-device-note');
6308
6310
  if(button){button.disabled=true;button.textContent='Saving...';}
@@ -6713,7 +6715,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
6713
6715
  + sect('🩺 robot services','<div id="pk-svc-list" class="pk-svc-list"><div class="pk-note">waiting for supervisor report…</div></div><div class="pk-note" id="pk-svc-age" style="margin-top:0.35rem;"></div><div class="rp-field" style="margin-top:0.55rem;"><label>Quick run (on the robot, from packages/robopark/scheduler)</label><input id="pk-svc-clicmd" readonly value="python robot_supervisor.py"></div><button class="rp-btn" id="pk-svc-clicmd-copy" style="margin-top:0.35rem;">📋 Copy command</button>')
6714
6716
  + buildShellSection(schedId(n))
6715
6717
  + sect('🎭 character / voice','<div class="pk-note" id="pk-eff-note" style="margin-bottom:0.45rem;">loading effective config…</div><div class="rp-field"><label>Character preset</label><select class="rp-char" id="pk-eff-char" data-robot="'+esc(schedId(n))+'">'+effPresetOptions+'</select></div><div class="rp-field"><label>Voice stack</label><select class="rp-voice" id="pk-eff-voice" data-robot="'+esc(schedId(n))+'">'+effVoiceOptions+'</select></div><div class="rp-config-actions" style="margin-top:.45rem"><button class="rp-btn primary" id="pk-eff-save" data-robot="'+esc(schedId(n))+'">Apply to robot</button><button class="rp-btn" id="pk-edit-character">Edit character</button><button class="rp-btn" id="pk-edit-stack">Edit stack</button><button class="rp-btn" id="pk-new-stack">New stack</button></div>');
6716
- b.innerHTML += sect('Camera + audio devices','<div class="rp-field"><label>Search devices</label><input id="pk-device-search" placeholder="camera, USB, microphone, HDMI..." autocomplete="off"></div><div class="rp-field"><label>Camera input</label><select id="pk-video-device"></select></div><div class="rp-field"><label>Microphone input</label><select id="pk-audio-input"></select></div><div class="rp-field"><label>Speaker / output inventory</label><select id="pk-audio-output"></select></div><button class="rp-btn primary" id="pk-device-test-speaker" style="margin-top:0.35rem">Test selected speaker now</button><div class="rp-field"><label>Preset greeting phrases <span class="pk-note">one per line, cached per voice</span></label><textarea id="pk-greeting-phrases" rows="3" placeholder="Hi, I am Jaguar! / Welcome to RoboPark."></textarea></div><button class="rp-btn" id="pk-device-save" style="margin-top:0.35rem">Save device selection + greetings</button><div class="pk-note" id="pk-device-note">waiting for robot heartbeat inventory</div>');
6718
+ b.innerHTML += sect('Production camera + audio','<div class="pk-note" id="pk-device-search" style="margin-bottom:.55rem">Hardware locked for launch. Camera, microphone, and speaker cannot drift between restarts.</div><div class="rp-field"><label>Camera input</label><select id="pk-video-device" disabled></select></div><div class="rp-field"><label>Microphone input</label><select id="pk-audio-input" disabled></select></div><div class="rp-field"><label>Speaker output</label><select id="pk-audio-output" disabled></select></div><button class="rp-btn primary" id="pk-device-test-speaker" style="margin-top:0.35rem">Test USB speaker now</button><div class="rp-field"><label>Preset greeting phrases <span class="pk-note">one per line, cached per voice</span></label><textarea id="pk-greeting-phrases" rows="3" placeholder="Hi, I am Jaguar! / Welcome to RoboPark."></textarea></div><button class="rp-btn" id="pk-device-save" style="margin-top:0.35rem">Save greetings + enforce hardware</button><div class="pk-note" id="pk-device-note">waiting for robot heartbeat inventory</div>');
6717
6719
  // Put live hardware selection beside the camera and audio tests,
6718
6720
  // ahead of persona and service administration in the Park drawer.
6719
6721
  var deviceInput=$('pk-device-search'), deviceSection=deviceInput&&deviceInput.closest('.pk-sect');
@@ -6721,7 +6723,6 @@ button.act:disabled{opacity:0.5;cursor:default;}
6721
6723
  if(deviceSection && speakerSection) speakerSection.after(deviceSection);
6722
6724
  refreshDeviceSelectors(n);
6723
6725
  b.innerHTML += '<div class="pk-ctl" style="margin-top:0.5rem"><button class="rp-btn danger" id="pk-recover" data-robot="'+esc(n.name)+'">Recover robot</button><span class="pk-note" style="margin:0">clears stale sessions and queued triggers for this robot</span></div>';
6724
- var deviceSearch=$('pk-device-search'); if(deviceSearch) deviceSearch.addEventListener('input', function(){ refreshDeviceSelectors(n); });
6725
6726
  var deviceSave=$('pk-device-save'); if(deviceSave) deviceSave.addEventListener('click', function(){ saveDeviceSelectors(n); });
6726
6727
  ['pk-video-device','pk-audio-input','pk-audio-output'].forEach(function(field){
6727
6728
  var select=$(field);if(!select)return;
@@ -6735,7 +6736,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
6735
6736
  });
6736
6737
  });
6737
6738
  var micListen=$('pk-mic-listen');if(micListen)micListen.addEventListener('click',function(){setMicMonitorAudible(!micMonitorAudible);});
6738
- var micVolume=$('pk-mic-volume');if(micVolume)micVolume.addEventListener('input',function(){micMonitorVolume=Math.max(0,Math.min(1,Number(micVolume.value||80)/100));lkAudioEls.forEach(function(el){el.volume=micMonitorVolume;});});
6739
+ var micVolume=$('pk-mic-volume');if(micVolume)micVolume.addEventListener('input',function(){micMonitorVolume=Math.max(0,Math.min(1,Number(micVolume.value||80)/100));if(micMonitorGain)micMonitorGain.gain.value=micMonitorAudible?micMonitorVolume:0;});
6739
6740
  startCam(n); startMeters(n); loadEffectiveConfig(schedId(n)); refreshSupervisorPanel(n);
6740
6741
  _bindShellSection(n);
6741
6742
  _spkTestRenderHistory(schedId(n));
@@ -6946,12 +6947,15 @@ button.act:disabled{opacity:0.5;cursor:default;}
6946
6947
  function setMicMonitorAudible(enabled){
6947
6948
  micMonitorAudible=!!enabled;
6948
6949
  var btn=$('pk-mic-listen'),note=$('pk-mic-listen-note');
6950
+ if(micMonitorAudible){
6951
+ var AC=window.AudioContext||window.webkitAudioContext;
6952
+ if(AC) audioCtx=audioCtx||new AC();
6953
+ if(audioCtx&&audioCtx.state==='suspended') audioCtx.resume().catch(function(e){if(note)note.textContent='Browser audio resume failed: '+e.message;});
6954
+ }
6955
+ if(micMonitorGain)micMonitorGain.gain.value=micMonitorAudible?micMonitorVolume:0;
6949
6956
  if(btn){btn.textContent=micMonitorAudible?'Mute browser monitor':'Listen in browser';btn.classList.toggle('primary',micMonitorAudible);}
6950
6957
  if(note)note.textContent=micMonitorAudible?(lkAudioEls.length?'Robot mic is playing in this browser. Use headphones.':'Listening armed — waiting for the robot mic track.'):'Browser monitoring muted.';
6951
- lkAudioEls.forEach(function(el){
6952
- el.muted=!micMonitorAudible;el.volume=micMonitorVolume;
6953
- if(micMonitorAudible)el.play().catch(function(e){if(note)note.textContent='Browser blocked playback: '+e.message+' — press Listen again.';});
6954
- });
6958
+ lkAudioEls.forEach(function(el){el.muted=true;});
6955
6959
  }
6956
6960
  function startMicMonitor(n){
6957
6961
  if(lkRoom||micMonitorConnecting)return;
@@ -7000,12 +7004,11 @@ button.act:disabled{opacity:0.5;cursor:default;}
7000
7004
  // that drowns out the actual conversation. The VU meter above
7001
7005
  // (hookMicAnalyser) taps the raw track directly and works fine
7002
7006
  // muted.
7003
- var audio=track.attach(); audio.autoplay=true; audio.playsInline=true;audio.setAttribute('playsinline','');audio.controls=false; audio.muted=!micMonitorAudible; audio.volume=micMonitorVolume;audio.style.display='none'; document.body.appendChild(audio); lkAudioEls.push(audio);
7007
+ var audio=track.attach(); audio.autoplay=false; audio.playsInline=true;audio.setAttribute('playsinline','');audio.controls=false; audio.muted=true; audio.style.display='none'; document.body.appendChild(audio); lkAudioEls.push(audio);
7004
7008
  var listenNote=$('pk-mic-listen-note');if(listenNote&&micMonitorAudible)listenNote.textContent='Robot mic is playing in this browser. Use headphones.';
7005
- audio.play().catch(function(e){var listenNote=$('pk-mic-listen-note');if(listenNote)listenNote.textContent='Browser blocked robot audio: '+e.message+'. Click Listen in browser.';});
7006
7009
  }
7007
7010
  });
7008
- lkRoom.on(LK.RoomEvent.Disconnected, function(){lkRoom=null;micAnalyser=null;micMonitorConnecting=false;micMonitorState='session ended';var micState=$('pk-micstate');if(micState)micState.textContent='selected '+selectedRobotMic()+' · session ended';if(!audioOnly&&note)note.textContent='session ended';if(!audioOnly&&res)res.textContent='—';});
7011
+ lkRoom.on(LK.RoomEvent.Disconnected, function(){lkRoom=null;micAnalyser=null;if(micMonitorSource)try{micMonitorSource.disconnect();}catch(e){}micMonitorSource=null;micMonitorGain=null;micMonitorConnecting=false;micMonitorState='session ended';var micState=$('pk-micstate');if(micState)micState.textContent='selected '+selectedRobotMic()+' · session ended';if(!audioOnly&&note)note.textContent='session ended';if(!audioOnly&&res)res.textContent='—';});
7009
7012
  var livekitUrl=info.url;
7010
7013
  if((location.hostname==='localhost'||location.hostname==='127.0.0.1') && /^ws:\\/\\/localhost(?::|\\/)/i.test(livekitUrl)) livekitUrl=livekitUrl.replace(/^ws:\\/\\/localhost/i,'ws://127.0.0.1');
7011
7014
  lkRoom.connect(livekitUrl, info.token).then(function(){micMonitorState='connected, waiting for robot mic publication';var micState=$('pk-micstate');if(micState)micState.textContent='selected '+selectedRobotMic()+' · '+micMonitorState;if(!audioOnly&&note)note.textContent='connected · waiting for camera track…';})
@@ -7014,13 +7017,15 @@ button.act:disabled{opacity:0.5;cursor:default;}
7014
7017
  }
7015
7018
  // Real mic level: tap the robot's subscribed 'mic' audio track with a Web
7016
7019
  // Audio analyser and read RMS in startMeters (no invented levels).
7017
- function hookMicAnalyser(track){
7020
+ function hookMicAnalyser(track){
7018
7021
  try{
7019
7022
  var AC=window.AudioContext||window.webkitAudioContext; if(!AC) return;
7020
7023
  audioCtx=audioCtx||new AC(); if(audioCtx.state==='suspended') audioCtx.resume().catch(function(){});
7021
7024
  var ms=track.mediaStreamTrack; if(!ms) return;
7022
- var src=audioCtx.createMediaStreamSource(new MediaStream([ms]));
7023
- var an=audioCtx.createAnalyser(); an.fftSize=256; src.connect(an); micAnalyser=an;
7025
+ if(micMonitorSource)try{micMonitorSource.disconnect();}catch(e){}
7026
+ var src=audioCtx.createMediaStreamSource(new MediaStream([ms]));
7027
+ var an=audioCtx.createAnalyser(); an.fftSize=256; src.connect(an); micAnalyser=an;
7028
+ var gain=audioCtx.createGain();gain.gain.value=micMonitorAudible?micMonitorVolume:0;src.connect(gain);gain.connect(audioCtx.destination);micMonitorSource=src;micMonitorGain=gain;
7024
7029
  }catch(e){}
7025
7030
  }
7026
7031
  function previewCam(n){
@@ -101,7 +101,8 @@ export async function roboparkRobotRuntime(opts) {
101
101
  }
102
102
  const previewArgs = [cli, 'preview-agent', '--foreground',
103
103
  '--scheduler-url', opts.schedulerUrl, '--robot-id', opts.name,
104
- '--video-device', opts.videoDevice ?? 'auto', '--audio-device', opts.audioDevice ?? 'default',
104
+ '--video-device', opts.videoDevice ?? '/dev/video0',
105
+ '--audio-device', opts.audioDevice ?? 'Usb Audio Device: USB Audio (hw:3,0)',
105
106
  '--save-config'];
106
107
  if (opts.enrollmentToken)
107
108
  previewArgs.push('--enrollment-token', opts.enrollmentToken);
@@ -204,8 +204,8 @@ async function setupRobot(config, opts, ctx, internal) {
204
204
  '--token', internal.token,
205
205
  '--port', String(internal.port),
206
206
  networkFlag,
207
- '--video-device', opts.videoDevice ?? 'auto',
208
- '--audio-device', opts.audioDevice ?? 'default',
207
+ '--video-device', opts.videoDevice ?? '/dev/video0',
208
+ '--audio-device', opts.audioDevice ?? 'Usb Audio Device: USB Audio (hw:3,0)',
209
209
  ];
210
210
  if (opts.enrollmentToken)
211
211
  runtimeArgs.push('--enrollment-token', opts.enrollmentToken);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "infinicode",
3
- "version": "2.8.81",
3
+ "version": "2.8.83",
4
4
  "description": "OpenKernel — provider-agnostic AI execution kernel. Native coding agent + mission-driven execution runtime.",
5
5
  "type": "module",
6
6
  "main": "./dist/kernel/index.js",
@@ -1695,15 +1695,66 @@ def create_video_capture(device: str, width: int, height: int, fps: int,
1695
1695
  # Audio capture abstraction: PyAudio or sounddevice -> LiveKit AudioFrame.
1696
1696
  # -----------------------------------------------------------------------------
1697
1697
 
1698
- class AudioCapture:
1698
+ class AudioCapture:
1699
1699
  def read(self, samples_per_frame: int) -> Optional["rtc.AudioFrame"]:
1700
1700
  raise NotImplementedError
1701
1701
 
1702
- def stop(self) -> None:
1703
- raise NotImplementedError
1704
-
1705
-
1706
- class PyAudioCapture(AudioCapture):
1702
+ def stop(self) -> None:
1703
+ raise NotImplementedError
1704
+
1705
+
1706
+ class AlsaAudioCapture(AudioCapture):
1707
+ """Capture Linux PCM through the same ALSA path used by onsite tests."""
1708
+
1709
+ def __init__(self, device: str):
1710
+ import re
1711
+ import subprocess
1712
+
1713
+ match = re.search(r"\b(hw:\d+,\d+)\b", device)
1714
+ if not match:
1715
+ raise ValueError(f"no ALSA hardware address in {device!r}")
1716
+ alsa_device = f"plug{match.group(1)}"
1717
+ self.buffer = bytearray()
1718
+ self.process = subprocess.Popen(
1719
+ [
1720
+ "arecord", "-q", "-D", alsa_device, "-t", "raw",
1721
+ "-f", "S16_LE", "-r", "48000", "-c", "1",
1722
+ ],
1723
+ stdout=subprocess.PIPE,
1724
+ stderr=subprocess.DEVNULL,
1725
+ )
1726
+ if self.process.stdout is None:
1727
+ raise RuntimeError("arecord did not provide a PCM stream")
1728
+ self.alsa_device = alsa_device
1729
+
1730
+ def read(self, samples_per_frame: int):
1731
+ from livekit import rtc
1732
+
1733
+ bytes_needed = samples_per_frame * 2
1734
+ while len(self.buffer) < bytes_needed:
1735
+ chunk = self.process.stdout.read(bytes_needed - len(self.buffer))
1736
+ if not chunk:
1737
+ raise OSError(f"ALSA capture stopped on {self.alsa_device}")
1738
+ self.buffer.extend(chunk)
1739
+ data = bytes(self.buffer[:bytes_needed])
1740
+ del self.buffer[:bytes_needed]
1741
+ return rtc.AudioFrame(
1742
+ data=data,
1743
+ sample_rate=48000,
1744
+ num_channels=1,
1745
+ samples_per_channel=samples_per_frame,
1746
+ )
1747
+
1748
+ def stop(self):
1749
+ if self.process.poll() is None:
1750
+ self.process.terminate()
1751
+ try:
1752
+ self.process.wait(timeout=2)
1753
+ except Exception:
1754
+ self.process.kill()
1755
+
1756
+
1757
+ class PyAudioCapture(AudioCapture):
1707
1758
  def __init__(self, device: str | int | None):
1708
1759
  import pyaudio
1709
1760
  self.pa = pyaudio.PyAudio()
@@ -1782,10 +1833,15 @@ def has_audio() -> bool:
1782
1833
  return False
1783
1834
 
1784
1835
 
1785
- def create_audio_capture(device: str) -> Optional[AudioCapture]:
1786
- if device.lower() in ("none", "", "false", "null"):
1787
- return None
1788
- try:
1836
+ def create_audio_capture(device: str) -> Optional[AudioCapture]:
1837
+ if device.lower() in ("none", "", "false", "null"):
1838
+ return None
1839
+ if sys.platform.startswith("linux") and "hw:" in device:
1840
+ try:
1841
+ return AlsaAudioCapture(device)
1842
+ except Exception as e:
1843
+ logger.warning(f"ALSA capture unavailable for {device}: {e}")
1844
+ try:
1789
1845
  import pyaudio # noqa: F401
1790
1846
  return PyAudioCapture(device if device != "default" else None)
1791
1847
  except Exception as e: