infinicode 2.8.64 → 2.8.66
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/dist/kernel/agents/backends/registry.js +4 -2
- package/dist/kernel/federation/dashboard-html.d.ts +4 -5
- package/dist/kernel/federation/dashboard-html.js +123 -19
- package/dist/kernel/federation/federation.js +1 -0
- package/dist/kernel/federation/transport-http.d.ts +6 -1
- package/dist/kernel/federation/transport-http.js +75 -0
- package/package.json +1 -1
- package/packages/robopark/scheduler/main.py +193 -17
- package/packages/robopark/scheduler/preview_agent.py +67 -25
|
@@ -433,6 +433,31 @@ class PreviewAgent:
|
|
|
433
433
|
self._motion_capture_lock = asyncio.Lock()
|
|
434
434
|
self._mesh_bootstrap_ready = False
|
|
435
435
|
self._next_mesh_bootstrap = 0.0
|
|
436
|
+
self._reported_pipeline: set[tuple[str, str]] = set()
|
|
437
|
+
|
|
438
|
+
async def _report_pipeline(self, stage: str, status: str = "ok", message: str = "",
|
|
439
|
+
details: Optional[dict] = None, once: bool = True) -> None:
|
|
440
|
+
"""Report a real robot-side transition for the Park test timeline."""
|
|
441
|
+
if not self._session or not self.device_id or not self.device_token:
|
|
442
|
+
return
|
|
443
|
+
session_id = self._vision_session_id or self._current_state.session_id
|
|
444
|
+
key = (session_id or "idle", stage)
|
|
445
|
+
if once and key in self._reported_pipeline:
|
|
446
|
+
return
|
|
447
|
+
try:
|
|
448
|
+
response = await self._session.post(
|
|
449
|
+
f"{self.scheduler_url.rstrip('/')}/api/devices/{self.device_id}/pipeline-events",
|
|
450
|
+
headers={"Authorization": f"Bearer {self.device_token}"},
|
|
451
|
+
json={"stage": stage, "status": status, "message": message,
|
|
452
|
+
"session_id": session_id, "source": "preview_agent",
|
|
453
|
+
"details": details or {}},
|
|
454
|
+
timeout=5.0,
|
|
455
|
+
)
|
|
456
|
+
response.raise_for_status()
|
|
457
|
+
if once:
|
|
458
|
+
self._reported_pipeline.add(key)
|
|
459
|
+
except Exception as exc:
|
|
460
|
+
logger.debug("pipeline event %s was not accepted: %s", stage, exc)
|
|
436
461
|
|
|
437
462
|
async def _ensure_mesh_identity(self) -> bool:
|
|
438
463
|
if not _mesh_proxy_headers():
|
|
@@ -744,7 +769,7 @@ class PreviewAgent:
|
|
|
744
769
|
dt = dt.replace(tzinfo=timezone.utc)
|
|
745
770
|
return dt.timestamp()
|
|
746
771
|
|
|
747
|
-
async def _end_vision_session(self) -> None:
|
|
772
|
+
async def _end_vision_session(self) -> None:
|
|
748
773
|
# Tell the scheduler too, not just the local publisher/LiveKit
|
|
749
774
|
# connection — otherwise the sessions row never gets ended_at set,
|
|
750
775
|
# and the server's active_sessions count climbs forever until it
|
|
@@ -763,7 +788,8 @@ class PreviewAgent:
|
|
|
763
788
|
)
|
|
764
789
|
except Exception as e:
|
|
765
790
|
logger.warning(f"failed to notify scheduler of session end: {e}")
|
|
766
|
-
self.
|
|
791
|
+
await self._report_pipeline("session_ended", "ok", "Robot conversation loop stopped")
|
|
792
|
+
self._vision_session_id = None
|
|
767
793
|
self._vision_trigger_in_flight = False
|
|
768
794
|
self._vision_hard_deadline = 0.0
|
|
769
795
|
self._vision_last_activity = 0.0
|
|
@@ -784,8 +810,16 @@ class PreviewAgent:
|
|
|
784
810
|
self._mesh_bootstrap_ready = False
|
|
785
811
|
self._next_mesh_bootstrap = 0.0
|
|
786
812
|
if production_mode is not None:
|
|
787
|
-
self.production_mode = production_mode
|
|
788
|
-
|
|
813
|
+
self.production_mode = production_mode
|
|
814
|
+
await self._report_pipeline("robot_online", message="Robot heartbeat accepted")
|
|
815
|
+
inventory = _get_device_inventory()
|
|
816
|
+
real_video = [d for d in inventory.get("video", []) if str(d.get("id", "")).lower() not in ("auto", "none")]
|
|
817
|
+
real_inputs = [d for d in inventory.get("audio_input", []) if str(d.get("id", "")).lower() not in ("default", "none")]
|
|
818
|
+
real_outputs = [d for d in inventory.get("audio_output", []) if str(d.get("id", "")).lower() not in ("default", "none")]
|
|
819
|
+
await self._report_pipeline("camera_ready", "ok" if real_video else "blocked", f"{len(real_video)} camera device(s) detected")
|
|
820
|
+
await self._report_pipeline("microphone_ready", "ok" if real_inputs else "blocked", f"{len(real_inputs)} microphone device(s) detected")
|
|
821
|
+
await self._report_pipeline("speaker_ready", "ok" if real_outputs else "blocked", f"{len(real_outputs)} speaker device(s) detected")
|
|
822
|
+
if not production_mode and self._vision_session_id:
|
|
789
823
|
await self._end_vision_session()
|
|
790
824
|
try:
|
|
791
825
|
await asyncio.wait_for(self._shutdown.wait(), timeout=self.heartbeat_interval)
|
|
@@ -923,8 +957,9 @@ class PreviewAgent:
|
|
|
923
957
|
if not self.device_id or not self.device_token or not self._session:
|
|
924
958
|
logger.warning("vision motion event received but not enrolled yet — ignoring")
|
|
925
959
|
return
|
|
926
|
-
self._vision_trigger_in_flight = True
|
|
927
|
-
|
|
960
|
+
self._vision_trigger_in_flight = True
|
|
961
|
+
await self._report_pipeline("motion_detected", "ok", f"Motion received from {payload.get('source', 'camera')}", once=False)
|
|
962
|
+
logger.info("motion detected by RoboVisionAI_PI — requesting a session")
|
|
928
963
|
# The cue is feedback only; never block session allocation on speaker
|
|
929
964
|
await self._stop_motion_capture()
|
|
930
965
|
# DirectShow can return a stale handle for a short interval after
|
|
@@ -943,9 +978,10 @@ class PreviewAgent:
|
|
|
943
978
|
)
|
|
944
979
|
r.raise_for_status()
|
|
945
980
|
data = r.json()
|
|
946
|
-
except Exception as e:
|
|
947
|
-
logger.error(f"request-session failed: {e}")
|
|
948
|
-
self.
|
|
981
|
+
except Exception as e:
|
|
982
|
+
logger.error(f"request-session failed: {e}")
|
|
983
|
+
await self._report_pipeline("scheduler_session", "failed", f"Session request failed: {type(e).__name__}", once=False)
|
|
984
|
+
self._vision_trigger_in_flight = False
|
|
949
985
|
return
|
|
950
986
|
state = PreviewState(
|
|
951
987
|
active=True,
|
|
@@ -960,8 +996,9 @@ class PreviewAgent:
|
|
|
960
996
|
self._vision_trigger_in_flight = False
|
|
961
997
|
self._vision_hard_deadline = now + self.vision_session_seconds
|
|
962
998
|
self._vision_last_activity = now
|
|
963
|
-
self._remote_session_ended = False
|
|
964
|
-
self._current_state = state
|
|
999
|
+
self._remote_session_ended = False
|
|
1000
|
+
self._current_state = state
|
|
1001
|
+
await self._report_pipeline("scheduler_session", "ok", "Scheduler allocated a conversation session")
|
|
965
1002
|
# If the scheduler returned a voice config, let the agent know by
|
|
966
1003
|
# posting it to our local webhook endpoint. The preview agent itself
|
|
967
1004
|
# does not consume it, but this makes the config observable locally
|
|
@@ -1006,9 +1043,10 @@ class LiveKitPublisher:
|
|
|
1006
1043
|
# the camera is being handed between preview and voice sessions.
|
|
1007
1044
|
self._last_motion_sample = 0.0
|
|
1008
1045
|
|
|
1009
|
-
async def start(self) -> None:
|
|
1010
|
-
self.room = self.rtc.Room()
|
|
1011
|
-
await self.room.connect(self.url, self.token)
|
|
1046
|
+
async def start(self) -> None:
|
|
1047
|
+
self.room = self.rtc.Room()
|
|
1048
|
+
await self.room.connect(self.url, self.token)
|
|
1049
|
+
await self.agent._report_pipeline("livekit_join", "ok", "Robot joined the LiveKit room")
|
|
1012
1050
|
|
|
1013
1051
|
# Register after signaling. Registering during Room.connect can invoke
|
|
1014
1052
|
# callbacks while the native LiveKit participant state is incomplete;
|
|
@@ -1019,9 +1057,10 @@ class LiveKitPublisher:
|
|
|
1019
1057
|
logger.info(f"track_subscribed: kind={track.kind} from={participant.identity} sid={publication.sid}")
|
|
1020
1058
|
if track.kind != self.rtc.TrackKind.KIND_AUDIO:
|
|
1021
1059
|
return
|
|
1022
|
-
if participant.identity == self.room.local_participant.identity:
|
|
1023
|
-
return
|
|
1024
|
-
self._tasks.append(asyncio.create_task(self.
|
|
1060
|
+
if participant.identity == self.room.local_participant.identity:
|
|
1061
|
+
return
|
|
1062
|
+
self._tasks.append(asyncio.create_task(self.agent._report_pipeline("tts_subscribed", "ok", "Subscribed to remote voice audio")))
|
|
1063
|
+
self._tasks.append(asyncio.create_task(self._play_remote_audio(track, publication.sid)))
|
|
1025
1064
|
|
|
1026
1065
|
self.room.on("track_subscribed", _on_track_subscribed)
|
|
1027
1066
|
logger.info("audio out: track_subscribed listener registered")
|
|
@@ -1073,15 +1112,17 @@ class LiveKitPublisher:
|
|
|
1073
1112
|
self.video_track = self.rtc.LocalVideoTrack.create_video_track("camera", self.video_source)
|
|
1074
1113
|
vopts = self.rtc.TrackPublishOptions()
|
|
1075
1114
|
vopts.source = self.rtc.TrackSource.SOURCE_CAMERA
|
|
1076
|
-
await self.room.local_participant.publish_track(self.video_track, vopts)
|
|
1077
|
-
self.video_source.capture_frame(first_frame)
|
|
1115
|
+
await self.room.local_participant.publish_track(self.video_track, vopts)
|
|
1116
|
+
self.video_source.capture_frame(first_frame)
|
|
1117
|
+
await self.agent._report_pipeline("camera_published", "ok", "Camera track published")
|
|
1078
1118
|
|
|
1079
1119
|
if audio_enabled:
|
|
1080
1120
|
self.audio_source = self.rtc.AudioSource(48000, 1)
|
|
1081
1121
|
self.audio_track = self.rtc.LocalAudioTrack.create_audio_track("microphone", self.audio_source)
|
|
1082
1122
|
aopts = self.rtc.TrackPublishOptions()
|
|
1083
1123
|
aopts.source = self.rtc.TrackSource.SOURCE_MICROPHONE
|
|
1084
|
-
await self.room.local_participant.publish_track(self.audio_track, aopts)
|
|
1124
|
+
await self.room.local_participant.publish_track(self.audio_track, aopts)
|
|
1125
|
+
await self.agent._report_pipeline("microphone_published", "ok", "Microphone track published")
|
|
1085
1126
|
|
|
1086
1127
|
# Register speaker playback (subscribe to the voice agent's TTS audio
|
|
1087
1128
|
# track) BEFORE opening the camera. Camera open is a slow/occasionally
|
|
@@ -1217,11 +1258,12 @@ class LiveKitPublisher:
|
|
|
1217
1258
|
async for frame in stream:
|
|
1218
1259
|
af = frame.frame if hasattr(frame, "frame") else frame
|
|
1219
1260
|
frame_count += 1
|
|
1220
|
-
if frame_count == 1:
|
|
1221
|
-
logger.info(
|
|
1222
|
-
f"audio out: first frame received for {sid} "
|
|
1223
|
-
f"(rate={af.sample_rate}, channels={getattr(af, 'num_channels', 1)})"
|
|
1224
|
-
)
|
|
1261
|
+
if frame_count == 1:
|
|
1262
|
+
logger.info(
|
|
1263
|
+
f"audio out: first frame received for {sid} "
|
|
1264
|
+
f"(rate={af.sample_rate}, channels={getattr(af, 'num_channels', 1)})"
|
|
1265
|
+
)
|
|
1266
|
+
await self.agent._report_pipeline("playback_started", "ok", "First TTS audio frame reached the robot")
|
|
1225
1267
|
in_rate = af.sample_rate
|
|
1226
1268
|
in_channels = int(getattr(af, "num_channels", 1) or 1)
|
|
1227
1269
|
raw = bytes(af.data)
|