infinicode 2.8.65 → 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.
@@ -14,10 +14,9 @@
14
14
  * for its cam / mic / speaker / hardware drawer)
15
15
  * Sessions & Telemetry — RoboPark scheduler stats + production_mode + controls
16
16
  *
17
- * The Park cam panel targets a LiveKit video track: it fetches a per-robot stream
18
- * token from /robopark/api/robots/:id/stream and, when the livekit-client SDK and
19
- * a published track are present, subscribes; otherwise it shows a styled preview.
20
- * That token/track is produced by the robot-side RoboPark plugin.
17
+ * The Park cam panel relays RoboVisionAI_PI's existing MJPEG feed through the
18
+ * authenticated mesh. LiveKit remains the voice transport but is deliberately
19
+ * not used for dashboard video, avoiding camera ownership conflicts on robots.
21
20
  *
22
21
  * Kept free of backticks and ${...} so the whole page fits inside this TS
23
22
  * template literal without escaping; the client JS uses string concatenation.
@@ -5446,7 +5445,12 @@ button.act:disabled{opacity:0.5;cursor:default;}
5446
5445
  // ── on-demand operator preview: open the robot's cam without a scene-detection session ──
5447
5446
  function updateGoLive(){ var b=$('pk-golive'); if(b){ b.textContent = previewOn?'■ Stop live':'▶ Go live'; b.classList.toggle('danger', previewOn); } }
5448
5447
  function goLive(n){ if(previewOn) stopPreview(n); else startPreview(n); }
5449
- function startPreview(n){
5448
+ function startPreview(n){
5449
+ previewOn=true; updateGoLive();
5450
+ if(lkRoom){ try{ lkRoom.disconnect(); }catch(e){} lkRoom=null; }
5451
+ idleCam('connecting');
5452
+ tryVisionOverlay(n, true);
5453
+ return;
5450
5454
  previewOn=true; updateGoLive();
5451
5455
  var note=$('pk-golivenote'); if(note) note.textContent='requesting preview…';
5452
5456
  if(lkRoom){ try{ lkRoom.disconnect(); }catch(e){} lkRoom=null; } // drop any session cam first
@@ -5463,7 +5467,14 @@ button.act:disabled{opacity:0.5;cursor:default;}
5463
5467
  })
5464
5468
  .catch(function(e){ idleCam('unreachable'); if(note) note.textContent='preview request failed'; if(window.console) console.error('RoboPark preview request failed',e); previewOn=false; updateGoLive(); });
5465
5469
  }
5466
- function stopPreview(n){
5470
+ function stopPreview(n){
5471
+ previewOn=false; updateGoLive();
5472
+ if(previewTimer){ clearInterval(previewTimer); previewTimer=null; }
5473
+ if(lkRoom){ try{ lkRoom.disconnect(); }catch(e){} lkRoom=null; }
5474
+ stopVision();
5475
+ var directNote=$('pk-golivenote'); if(directNote) directNote.textContent='';
5476
+ idleCam('idle');
5477
+ return;
5467
5478
  previewOn=false; updateGoLive();
5468
5479
  if(previewTimer){ clearInterval(previewTimer); previewTimer=null; }
5469
5480
  if(lkRoom){ try{ lkRoom.disconnect(); }catch(e){} lkRoom=null; }
@@ -5478,6 +5489,15 @@ button.act:disabled{opacity:0.5;cursor:default;}
5478
5489
  // server is opt-in because running it beside preview_agent would open the
5479
5490
  // same Windows camera twice and make both feeds unreliable.
5480
5491
  function tryVisionOverlay(n, force){
5492
+ var directImg=$('pk-vision-img'); if(!directImg) return;
5493
+ var directRef=n.nodeId||n.id||n.name;
5494
+ if(!directRef){ visionOn=false; directImg.src=''; directImg.style.display='none'; return; }
5495
+ var directCamNote=$('pk-camnote'), directLiveNote=$('pk-golivenote'), directRes=$('pk-camres');
5496
+ visionOn=true;
5497
+ directImg.onerror=function(){ visionOn=false; directImg.style.display='none'; directImg.src=''; if(directCamNote) directCamNote.textContent='RoboVision feed unavailable on robot'; if(directLiveNote) directLiveNote.textContent='camera unavailable'; if(directRes) directRes.textContent='OFFLINE'; };
5498
+ directImg.onload=function(){ directImg.style.display='block'; if(directCamNote) directCamNote.textContent='live camera via RoboVision mesh relay'; if(directLiveNote) directLiveNote.textContent='live'; if(directRes) directRes.textContent='LIVE'; };
5499
+ directImg.src='/fed/media/robots/'+encodeURIComponent(directRef)+'/video_feed'+QS+(QS?'&':'?')+'_='+Date.now();
5500
+ return;
5481
5501
  var img=$('pk-vision-img'); if(!img) return;
5482
5502
  if(!force && String(window.localStorage.getItem('robopark.visionOverlay')||'').toLowerCase()!=='true'){
5483
5503
  visionOn=false; img.src=''; img.style.display='none'; return;
@@ -6084,7 +6104,10 @@ button.act:disabled{opacity:0.5;cursor:default;}
6084
6104
  function setFill(id,pct){ var e=$(id); if(!e||pct==null) return; e.style.width=Math.max(2,Math.min(100,pct))+'%'; e.style.background=pct>=75?'var(--bad)':pct>=45?'var(--warn)':'var(--gold-bright)'; }
6085
6105
 
6086
6106
  // ── cam: target a LiveKit track; fall back to a styled preview ──
6087
- function startCam(n){
6107
+ function startCam(n){
6108
+ idleCam('connecting');
6109
+ tryVisionOverlay(n, true);
6110
+ return;
6088
6111
  var note=$('pk-camnote');
6089
6112
  camAbort = ('AbortController' in window)?new AbortController():null;
6090
6113
  idleCam('connecting');
@@ -86,6 +86,7 @@ export class Federation {
86
86
  logger,
87
87
  getManifest: () => buildManifest(identity, this.describeWithBackends()),
88
88
  getNodes: () => this.nodeStatuses(),
89
+ getPeers: () => this.peers(),
89
90
  getStatus: () => this.statusSnapshot(),
90
91
  runCommand: (input) => this.deps.kernel.command(input),
91
92
  commandGate: options.commandGate,
@@ -1,5 +1,5 @@
1
1
  import type { Logger } from '../types.js';
2
- import type { FederationEnvelope, NodeManifest, StreamFrame } from './types.js';
2
+ import type { FederationEnvelope, NodeManifest, PeerInfo, StreamFrame } from './types.js';
3
3
  export type RpcHandler = (env: FederationEnvelope, remote: string) => Promise<FederationEnvelope | void>;
4
4
  export interface MeshServerOptions {
5
5
  host?: string;
@@ -9,6 +9,8 @@ export interface MeshServerOptions {
9
9
  getManifest: () => NodeManifest;
10
10
  /** Optional: full mesh view (self + peers) for the GET /fed/nodes probe. */
11
11
  getNodes?: () => unknown;
12
+ /** Connected peers and their reachable mesh URLs. */
13
+ getPeers?: () => PeerInfo[];
12
14
  /** Optional: consolidated fleet snapshot (self + nodes + hardware + roles +
13
15
  * runs + recent activity) for the dashboard and `robopark fleet` (GET /fed/status). */
14
16
  getStatus?: () => unknown;
@@ -50,6 +52,9 @@ export declare class MeshServer {
50
52
  private seq;
51
53
  constructor(opts: MeshServerOptions);
52
54
  get streamCount(): number;
55
+ private streamHttp;
56
+ private streamLocalRoboVision;
57
+ private streamPeerRoboVision;
53
58
  start(): Promise<void>;
54
59
  /** Proxy a dashboard request to the RoboPark scheduler (product data). */
55
60
  private proxyToScheduler;
@@ -20,6 +20,7 @@ import { promisify } from 'node:util';
20
20
  import { timingSafeEqual } from 'node:crypto';
21
21
  import { readFileSync } from 'node:fs';
22
22
  import { createRequire } from 'node:module';
23
+ import { once } from 'node:events';
23
24
  import { frameToSSE, parseSSE } from './protocol.js';
24
25
  const execFileAsync = promisify(execFile);
25
26
  /** Read the livekit-client UMD bundle from the installed dependency, once.
@@ -82,6 +83,68 @@ export class MeshServer {
82
83
  get streamCount() {
83
84
  return this.sse.size;
84
85
  }
86
+ async streamHttp(req, res, target, headers = {}) {
87
+ const ctrl = new AbortController();
88
+ req.once('close', () => ctrl.abort());
89
+ try {
90
+ const upstream = await fetch(target, { headers, signal: ctrl.signal });
91
+ if (!upstream.ok || !upstream.body) {
92
+ const detail = await upstream.text().catch(() => '');
93
+ res.writeHead(upstream.status, { 'content-type': 'text/plain; charset=utf-8', 'cache-control': 'no-store' });
94
+ res.end(detail || `media upstream returned ${upstream.status}`);
95
+ return;
96
+ }
97
+ res.writeHead(200, {
98
+ 'content-type': upstream.headers.get('content-type') ?? 'multipart/x-mixed-replace; boundary=frame',
99
+ 'cache-control': 'no-store, no-cache, must-revalidate',
100
+ pragma: 'no-cache',
101
+ connection: 'keep-alive',
102
+ });
103
+ const reader = upstream.body.getReader();
104
+ for (;;) {
105
+ const { done, value } = await reader.read();
106
+ if (done || res.writableEnded)
107
+ break;
108
+ if (!res.write(Buffer.from(value)))
109
+ await once(res, 'drain');
110
+ }
111
+ if (!res.writableEnded)
112
+ res.end();
113
+ }
114
+ catch (err) {
115
+ if (ctrl.signal.aborted)
116
+ return;
117
+ if (!res.headersSent) {
118
+ res.writeHead(502, { 'content-type': 'text/plain; charset=utf-8', 'cache-control': 'no-store' });
119
+ res.end(`robot camera unavailable: ${err instanceof Error ? err.message : String(err)}`);
120
+ }
121
+ else if (!res.writableEnded) {
122
+ res.end();
123
+ }
124
+ }
125
+ }
126
+ streamLocalRoboVision(req, res) {
127
+ void this.streamHttp(req, res, 'http://127.0.0.1:5000/video_feed');
128
+ }
129
+ streamPeerRoboVision(req, res, nodeRef) {
130
+ const ref = decodeURIComponent(nodeRef).toLowerCase();
131
+ const self = this.opts.getManifest();
132
+ if (self.nodeId.toLowerCase() === ref || self.displayName.toLowerCase() === ref) {
133
+ this.streamLocalRoboVision(req, res);
134
+ return;
135
+ }
136
+ const peer = (this.opts.getPeers?.() ?? []).find(candidate => candidate.connected &&
137
+ (candidate.nodeId.toLowerCase() === ref || candidate.displayName.toLowerCase() === ref));
138
+ if (!peer) {
139
+ res.writeHead(404, { 'content-type': 'text/plain; charset=utf-8', 'cache-control': 'no-store' });
140
+ res.end(`connected robot mesh node not found: ${nodeRef}`);
141
+ return;
142
+ }
143
+ const headers = {};
144
+ if (this.opts.token)
145
+ headers.authorization = `Bearer ${this.opts.token}`;
146
+ void this.streamHttp(req, res, `${peer.url.replace(/\/+$/, '')}/fed/media/video_feed`, headers);
147
+ }
85
148
  async start() {
86
149
  const handler = (req, res) => this.handle(req, res);
87
150
  this.server = this.opts.tls
@@ -344,6 +407,18 @@ export class MeshServer {
344
407
  res.end(js);
345
408
  return;
346
409
  }
410
+ // RoboVision owns the robot camera. Satellites expose the existing local
411
+ // MJPEG feed, while the hub relays it over the authenticated mesh. This
412
+ // avoids opening /dev/video0 again in LiveKit and works across private LANs.
413
+ if (req.method === 'GET' && path === '/fed/media/video_feed') {
414
+ this.streamLocalRoboVision(req, res);
415
+ return;
416
+ }
417
+ const robotVideo = path.match(/^\/fed\/media\/robots\/([^/]+)\/video_feed$/);
418
+ if (req.method === 'GET' && robotVideo) {
419
+ this.streamPeerRoboVision(req, res, robotVideo[1]);
420
+ return;
421
+ }
347
422
  if (req.method === 'GET' && path === '/fed/status') {
348
423
  res.writeHead(200, { 'content-type': 'application/json' });
349
424
  res.end(JSON.stringify(this.opts.getStatus?.() ?? {}));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "infinicode",
3
- "version": "2.8.65",
3
+ "version": "2.8.66",
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",