infinicode 2.8.81 → 2.8.82

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.
@@ -6265,11 +6265,12 @@ button.act:disabled{opacity:0.5;cursor:default;}
6265
6265
  function refreshDeviceSelectors(n){
6266
6266
  var device=schedulerDevice(n)||{}, inv=device.device_inventory||{};
6267
6267
  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']);
6268
+ var query='';
6269
+ // Launch hardware profile shared by every deployed robot. Do not expose
6270
+ // unstable PortAudio indexes, HDMI, or Pi codec devices in production.
6271
+ var video=[{id:'/dev/video0',name:'H65 USB CAMERA (/dev/video0)'}];
6272
+ var input=[{id:'2',name:'USB microphone (hw:3,0)'}];
6273
+ var output=[{id:'1',name:'USB speaker (hw:2,0)'}];
6273
6274
  function fill(id, list, selected){
6274
6275
  var el=$(id); if(!el) return;
6275
6276
  // Heartbeat polling rebuilds this panel frequently. Preserve an
@@ -6282,9 +6283,9 @@ button.act:disabled{opacity:0.5;cursor:default;}
6282
6283
  });
6283
6284
  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
6285
  }
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');
6286
+ fill('pk-video-device', video, '/dev/video0');
6287
+ fill('pk-audio-input', input, '2');
6288
+ fill('pk-audio-output', output, '1');
6288
6289
  var selectedOutput=$('pk-audio-output'),selectedLabel=$('pk-spk-selected');
6289
6290
  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
6291
  var greetings=$('pk-greeting-phrases');
@@ -6302,7 +6303,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
6302
6303
  }
6303
6304
  function saveDeviceSelectors(n){
6304
6305
  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,
6306
+ var body={video_device:'/dev/video0', audio_device:'2', audio_output_device:'1',
6306
6307
  greeting_phrases:($('pk-greeting-phrases').value||'').split(String.fromCharCode(10)).map(function(x){return x.trim();}).filter(Boolean)};
6307
6308
  var button=$('pk-device-save'),note=$('pk-device-note');
6308
6309
  if(button){button.disabled=true;button.textContent='Saving...';}
@@ -6713,7 +6714,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
6713
6714
  + 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
6715
  + buildShellSection(schedId(n))
6715
6716
  + 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>');
6717
+ 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
6718
  // Put live hardware selection beside the camera and audio tests,
6718
6719
  // ahead of persona and service administration in the Park drawer.
6719
6720
  var deviceInput=$('pk-device-search'), deviceSection=deviceInput&&deviceInput.closest('.pk-sect');
@@ -6721,7 +6722,6 @@ button.act:disabled{opacity:0.5;cursor:default;}
6721
6722
  if(deviceSection && speakerSection) speakerSection.after(deviceSection);
6722
6723
  refreshDeviceSelectors(n);
6723
6724
  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
6725
  var deviceSave=$('pk-device-save'); if(deviceSave) deviceSave.addEventListener('click', function(){ saveDeviceSelectors(n); });
6726
6726
  ['pk-video-device','pk-audio-input','pk-audio-output'].forEach(function(field){
6727
6727
  var select=$(field);if(!select)return;
@@ -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.82",
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: