infinicode 2.8.90 → 2.8.91

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": "infinicode",
3
- "version": "2.8.90",
3
+ "version": "2.8.91",
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",
@@ -455,6 +455,9 @@ class PreviewAgent:
455
455
  self._vision_session_id: Optional[str] = None
456
456
  self._vision_trigger_in_flight = False
457
457
  self._speaker_test_in_flight = False
458
+ # The USB adapter is an exclusive ALSA endpoint. Keep operator tests
459
+ # and motion cues from racing each other while LiveKit is paused.
460
+ self._speaker_operation_lock = asyncio.Lock()
458
461
  self._vision_hard_deadline: float = 0.0
459
462
  self._vision_last_activity: float = 0.0
460
463
  self.production_mode = False
@@ -896,18 +899,20 @@ class PreviewAgent:
896
899
  return
897
900
  self._speaker_test_in_flight = True
898
901
  from robot_supervisor import _speaker_roundtrip_test
899
- # LiveKit owns the USB microphone continuously. Release that owner
900
- # for an explicit hardware test, then restore the same session.
901
- restore_state = self._current_state if self._publisher else None
902
- if restore_state:
903
- await self._stop_publisher()
904
- await asyncio.sleep(1.0)
905
- try:
906
- result = await asyncio.to_thread(_speaker_roundtrip_test, request.get("params") or {})
907
- result["media_owner_paused"] = bool(restore_state)
908
- finally:
909
- if restore_state and restore_state.active:
910
- await self._start_publisher(restore_state)
902
+ async with self._speaker_operation_lock:
903
+ # LiveKit owns the USB microphone and speaker continuously.
904
+ # Pause it while holding the same lock as the motion cue so
905
+ # nothing can reopen ALSA before the explicit test starts.
906
+ restore_state = self._current_state if self._publisher else None
907
+ if restore_state:
908
+ await self._stop_publisher()
909
+ await asyncio.sleep(1.0)
910
+ try:
911
+ result = await asyncio.to_thread(_speaker_roundtrip_test, request.get("params") or {})
912
+ result["media_owner_paused"] = bool(restore_state)
913
+ finally:
914
+ if restore_state and restore_state.active:
915
+ await self._start_publisher(restore_state)
911
916
  result_response = await self._session.post(
912
917
  f"{self.scheduler_url.rstrip('/')}/api/devices/{self.device_id}/supervisor-output",
913
918
  json={"kind": "speaker_test", "service": None, "payload": result,
@@ -1066,7 +1071,8 @@ class PreviewAgent:
1066
1071
  # The cue and TTS share one physical ALSA output. Finish and release
1067
1072
  # the cue before LiveKit can deliver the greeting; background playback
1068
1073
  # races aplay and makes greeting delivery intermittently fail busy.
1069
- await asyncio.to_thread(_play_audio_effect, self.audio_playback_device, "motion")
1074
+ async with self._speaker_operation_lock:
1075
+ await asyncio.to_thread(_play_audio_effect, self.audio_playback_device, "motion")
1070
1076
  try:
1071
1077
  r = await self._session.post(
1072
1078
  f"{self.scheduler_url.rstrip('/')}/api/devices/{self.device_id}/request-session",
@@ -1317,6 +1323,11 @@ class LiveKitPublisher:
1317
1323
  process.wait(timeout=2)
1318
1324
  except subprocess.TimeoutExpired:
1319
1325
  process.terminate()
1326
+ try:
1327
+ process.wait(timeout=1)
1328
+ except subprocess.TimeoutExpired:
1329
+ process.kill()
1330
+ process.wait(timeout=1)
1320
1331
 
1321
1332
  def close(self) -> None:
1322
1333
  return
@@ -1493,16 +1504,24 @@ class LiveKitPublisher:
1493
1504
  else:
1494
1505
  data = b"".join(raw[i:i + 2] * OUT_CHANNELS for i in range(0, len(raw), 2))
1495
1506
  write_queue.put(data)
1496
- except Exception as e:
1497
- logger.warning(f"audio out stream error: {e}")
1498
- finally:
1499
- write_queue.put(None)
1500
- writer_thread.join(timeout=2.0)
1507
+ except Exception as e:
1508
+ logger.warning(f"audio out stream error: {e}")
1509
+ finally:
1510
+ cancelling = bool(asyncio.current_task() and asyncio.current_task().cancelling())
1511
+ if cancelling:
1512
+ # On publisher teardown, close ALSA first. Waiting for the
1513
+ # writer while aplay still owns the device leaves hw:X,Y busy
1514
+ # long enough for the queued dashboard test to fail.
1515
+ stream_failed.set()
1516
+ out.stop_stream()
1517
+ write_queue.put(None)
1518
+ writer_thread.join(timeout=2.0)
1501
1519
  logger.info(
1502
1520
  f"audio out: {sid} received {frame_count} frames, "
1503
1521
  f"writer wrote {written_frames[0]} chunks, queue backlog at close={write_queue.qsize()}"
1504
1522
  )
1505
- out.stop_stream()
1523
+ if not cancelling:
1524
+ out.stop_stream()
1506
1525
  out.close()
1507
1526
  if pa is not None:
1508
1527
  pa.terminate()