infinicode 2.8.107 → 2.8.108

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.
@@ -825,6 +825,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
825
825
  <div class="section"><h3>Live insights</h3><div class="pk-camera-insight" id="pk-camera-ops-insights"></div></div>
826
826
  <div class="section"><h3>Conversation pipeline</h3><div class="pk-camera-pipeline" id="pk-camera-ops-pipeline"></div></div>
827
827
  <div class="section"><h3>Hardware and media</h3><div class="pk-camera-insight" id="pk-camera-ops-hardware"></div></div>
828
+ <div class="section"><h3>Motor operations</h3><div class="rp-field"><label>Saved sequence</label><select id="pk-camera-ops-motor-sequence"></select></div><div class="pk-ctl"><button class="rp-btn" id="pk-camera-ops-motor-discover">Discover relays</button><button class="rp-btn primary" id="pk-camera-ops-motor-run">Run sequence</button><button class="rp-btn danger" id="pk-camera-ops-motor-test">Test registered GPIO</button></div><div class="pk-motor-status" id="pk-camera-ops-motor-status">Loading robot motor profile...</div></div>
828
829
  <div class="section"><h3>Operator note</h3><div class="pk-note">Video is relayed directly from the robot's RoboVision MJPEG service through the authenticated mesh. Voice remains on LiveKit, avoiding camera ownership conflicts.</div></div>
829
830
  </aside>
830
831
  </div>
@@ -7640,10 +7641,11 @@ button.act:disabled{opacity:0.5;cursor:default;}
7640
7641
  }
7641
7642
  function motorEditorAddMotor(){captureMotorEditor();var number=motorProfile.registry.length+1;motorProfile.registry.push({id:'motor-'+number,name:'Motor '+number,gpio:Math.min(27,Math.max(2,number+4)),active_high:true,max_duration_ms:3000});renderMotorEditor();}
7642
7643
  function motorEditorAddStep(){captureMotorEditor();if(!motorProfile.registry.length){motorStatus('Register a motor before adding a timed node','bad');return;}motorEditorSequence().steps.push({motor_id:motorProfile.registry[0].id,delay_ms:0,duration_ms:300});renderMotorEditor();}
7643
- function motorStatus(message,kind){var host=$('pk-motor-status');if(host){host.textContent=message;host.className='pk-motor-status '+(kind||'');}}
7644
+ function motorStatus(message,kind){['pk-motor-status','pk-camera-ops-motor-status'].forEach(function(id){var host=$(id);if(host){host.textContent=message;host.className='pk-motor-status '+(kind||'');}});}
7645
+ function motorErrorMessage(error){var message=error&&error.message?error.message:String(error||'unknown error');return /(?:\s|^)404(?:\s|$)|not found/i.test(message)?'Motor API is not loaded on the running livekit-1 scheduler. Update and restart the scheduler, then retry.':message;}
7644
7646
  function loadMotorProfile(n){
7645
7647
  var robotId=schedId(n);motorStatus('Loading saved motor profile...');
7646
- getJson('/robopark/api/robots/'+encodeURIComponent(robotId)+'/motor-profile').then(function(data){motorProfile={registry:data.registry||[],sequences:data.sequences||[],greeting_sequence_id:data.greeting_sequence_id||null,motor_server_url:(schedulerDevice(n)||{}).motor_server_url||data.motor_server_url||'http://127.0.0.1:8001'};if(!motorProfile.sequences.length)motorProfile.sequences=[{id:'greeting-motion',name:'Greeting motion',steps:[]}];motorSequenceId=motorProfile.greeting_sequence_id||motorProfile.sequences[0].id;renderMotorEditor();if(motorProfile.registry.length){motorStatus('Saved profile loaded | '+motorProfile.registry.length+' relay(s), '+motorProfile.sequences.length+' sequence(s)','ok');return;}motorStatus('No central registry; discovering robot-local relays...');return discoverMotorRelays(n,true);}).catch(function(error){motorStatus('Motor profile unavailable: '+error.message,'bad');});
7648
+ getJson('/robopark/api/robots/'+encodeURIComponent(robotId)+'/motor-profile').then(function(data){motorProfile={registry:data.registry||[],sequences:data.sequences||[],greeting_sequence_id:data.greeting_sequence_id||null,motor_server_url:(schedulerDevice(n)||{}).motor_server_url||data.motor_server_url||'http://127.0.0.1:8001'};if(!motorProfile.sequences.length)motorProfile.sequences=[{id:'greeting-motion',name:'Greeting motion',steps:[]}];motorSequenceId=motorProfile.greeting_sequence_id||motorProfile.sequences[0].id;renderMotorEditor();if(motorProfile.registry.length){motorStatus('Saved profile loaded | '+motorProfile.registry.length+' relay(s), '+motorProfile.sequences.length+' sequence(s)','ok');return;}motorStatus('No central registry; discovering robot-local relays...');return discoverMotorRelays(n,true);}).catch(function(error){motorStatus('Motor profile unavailable: '+motorErrorMessage(error),'bad');});
7647
7649
  }
7648
7650
  function saveMotorProfile(n){
7649
7651
  captureMotorEditor();var robotId=schedId(n),url=motorProfile.motor_server_url;
@@ -7670,7 +7672,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
7670
7672
  }
7671
7673
  function testAllMotorRelays(n){
7672
7674
  captureMotorEditor();var ready=motorProfile.registry.length?Promise.resolve():discoverMotorRelays(n,true);
7673
- ready.then(function(){if(!window.confirm('Pulse all '+motorProfile.registry.length+' registered relays one at a time? Ensure the motor area is clear.'))return null;var robotId=schedId(n);motorStatus('Saving profile and testing '+motorProfile.registry.length+' registered GPIO relay(s), one at a time...');return saveMotorProfile(n).then(function(){return rpAction('POST','/robopark/api/robots/'+encodeURIComponent(robotId)+'/motors/test-all?duration_ms=300&pause_ms=200',{});}).then(function(q){return _shellWaitForResult(robotId,q.request_id,q.device_id||robotId,'','gpio-test-all');}).then(function(result){motorStatus(result.ok?'Full registered GPIO test completed: '+(result.completed_steps||[]).length+' relay(s).':'GPIO test failed: '+(result.error||'unknown error'),result.ok?'ok':'bad');});}).catch(function(error){motorStatus('GPIO test failed: '+error.message,'bad');});
7675
+ ready.then(function(){if(!window.confirm('Pulse all '+motorProfile.registry.length+' registered relays one at a time? Ensure the motor area is clear.'))return null;var robotId=schedId(n);motorStatus('Saving profile and testing '+motorProfile.registry.length+' registered GPIO relay(s), one at a time...');return saveMotorProfile(n).then(function(){return rpAction('POST','/robopark/api/robots/'+encodeURIComponent(robotId)+'/motors/test-all?duration_ms=300&pause_ms=200',{});}).then(function(q){return _shellWaitForResult(robotId,q.request_id,q.device_id||robotId,'','gpio-test-all');}).then(function(result){motorStatus(result.ok?'Full registered GPIO test completed: '+(result.completed_steps||[]).length+' relay(s).':'GPIO test failed: '+(result.error||'unknown error'),result.ok?'ok':'bad');});}).catch(function(error){motorStatus('GPIO test failed: '+motorErrorMessage(error),'bad');});
7674
7676
  }
7675
7677
 
7676
7678
  var pipelineHistoryPage=1;
@@ -7862,20 +7864,22 @@ button.act:disabled{opacity:0.5;cursor:default;}
7862
7864
  var stages=[[currentStageLabel,true,'ACTIVE'],['Robot online',!!n.connected,'ready'],['Camera streaming',cameraLive,'ready'],['Motion detector armed',!!device.production_mode,'armed'],['Scheduler session',active,'active']],pipe=$('pk-camera-ops-pipeline');if(pipe)pipe.innerHTML=stages.map(function(s,index){return '<div class="'+(index===0?'fleet-stage-focus '+esc(currentStageStatus):'')+'"><i class="'+(s[1]?'ok':'warn')+'"></i><span>'+esc(s[0])+'</span><b>'+(s[1]?s[2]:'waiting')+'</b></div>';}).join('');
7863
7865
  var inv=device.device_inventory||{},mh=inv.media_health||{},hardware=$('pk-camera-ops-hardware');if(hardware)hardware.innerHTML='<span>CPU</span><b>'+(hw.cpu&&hw.cpu.percent!=null?Math.round(hw.cpu.percent)+'%':'--')+'</b><span>Memory</span><b>'+(hw.memory&&hw.memory.percent!=null?Math.round(hw.memory.percent)+'%':'--')+'</b><span>Temperature</span><b>'+(hw.temperatureC!=null?Math.round(hw.temperatureC)+'C':'--')+'</b><span>Camera inputs</span><b>'+((inv.video||[]).length||0)+'</b><span>Microphones</span><b>'+((inv.audio_input||[]).length||0)+'</b><span>Speaker outputs</span><b>'+((inv.audio_output||[]).length||0)+'</b><span>Camera permission</span><b class="'+(mh.camera_access===false?'bad':'ok')+'">'+(mh.camera_access===false?'DENIED':mh.camera_access===true?'granted':'--')+'</b><span>Audio permission</span><b class="'+(mh.audio_access===false?'bad':'ok')+'">'+(mh.audio_access===false?'DENIED':mh.audio_access===true?'granted':'--')+'</b><span>Camera worker</span><b class="'+(mh.camera_stalled?'bad':'ok')+'">'+(mh.camera_stalled?'STALLED':mh.camera_worker?'streaming':'--')+'</b><span>Frame age</span><b>'+(mh.camera_frame_age_seconds!=null?Number(mh.camera_frame_age_seconds).toFixed(1)+'s':'--')+'</b><span>Selected camera</span><b>'+esc(device.video_device||'auto')+'</b><span>Selected microphone</span><b>'+esc(device.audio_device||'default')+'</b>';
7864
7866
  var prod=$('pk-camera-ops-production');if(prod){prod.textContent=device.production_mode?'Disable production':'Enable production';prod.classList.toggle('primary',!!device.production_mode);}
7867
+ var motorSelect=$('pk-camera-ops-motor-sequence');if(motorSelect){var selected=motorSelect.value||motorSequenceId;motorSelect.innerHTML=(motorProfile.sequences||[]).map(function(sequence){return '<option value="'+esc(sequence.id)+'">'+esc(sequence.name||sequence.id)+'</option>';}).join('')||'<option value="">No saved sequences</option>';if(selected)motorSelect.value=selected;}
7865
7868
  }
7866
7869
  function reloadCameraOperations(){
7867
7870
  if(!cameraOpsNode)return;var img=$('pk-camera-ops-img'),empty=$('pk-camera-ops-empty');if(!img)return;img.dataset.live='false';if(empty){empty.style.display='block';empty.textContent='opening RoboVision camera...';}
7868
7871
  img.onload=function(){img.dataset.live='true';if(cameraOpsRetryTimer){clearTimeout(cameraOpsRetryTimer);cameraOpsRetryTimer=null;}if(empty)empty.style.display='none';refreshCameraOperations();};img.onerror=function(){var failedNode=cameraOpsNode;img.dataset.live='false';if(empty){empty.style.display='block';empty.textContent='camera relay unavailable / retrying';}refreshCameraOperations();if(cameraOpsRetryTimer)clearTimeout(cameraOpsRetryTimer);cameraOpsRetryTimer=setTimeout(function(){if(cameraOpsNode&&cameraOpsNode===failedNode)reloadCameraOperations();},3000);};img.src=cameraOpsFeedUrl(cameraOpsNode);refreshCameraOperations();
7869
7872
  }
7870
7873
  function openCameraOperations(n){
7871
- cameraOpsNode=n;var host=$('pk-camera-ops');if(!host)return;host.classList.add('open');document.body.classList.add('camera-ops-open');var name=n.displayName||n.name||'robot',avatar=rpAvatarFor(name),av=$('pk-camera-ops-avatar');$('pk-camera-ops-name').textContent=name+' / live operations';$('pk-camera-ops-sub').textContent='RoboVision mesh relay / '+(n.connected?'connected':'offline');if(av){if(avatar){av.src=avatar+QS;av.style.display='block';}else{av.removeAttribute('src');av.style.display='none';}}reloadCameraOperations();if(cameraOpsTimer)clearInterval(cameraOpsTimer);cameraOpsTimer=setInterval(refreshCameraOperations,1000);
7874
+ cameraOpsNode=n;var host=$('pk-camera-ops');if(!host)return;host.classList.add('open');document.body.classList.add('camera-ops-open');var name=n.displayName||n.name||'robot',avatar=rpAvatarFor(name),av=$('pk-camera-ops-avatar');$('pk-camera-ops-name').textContent=name+' / live operations';$('pk-camera-ops-sub').textContent='RoboVision mesh relay / '+(n.connected?'connected':'offline');if(av){if(avatar){av.src=avatar+QS;av.style.display='block';}else{av.removeAttribute('src');av.style.display='none';}}loadMotorProfile(n);reloadCameraOperations();if(cameraOpsTimer)clearInterval(cameraOpsTimer);cameraOpsTimer=setInterval(refreshCameraOperations,1000);
7872
7875
  }
7873
7876
  function closeCameraOperations(){
7874
7877
  var host=$('pk-camera-ops'),img=$('pk-camera-ops-img');if(host)host.classList.remove('open');document.body.classList.remove('camera-ops-open');if(cameraOpsRetryTimer){clearTimeout(cameraOpsRetryTimer);cameraOpsRetryTimer=null;}if(img){img.src='';img.dataset.live='false';}if(cameraOpsTimer){clearInterval(cameraOpsTimer);cameraOpsTimer=null;}cameraOpsNode=null;if(document.fullscreenElement&&document.exitFullscreen)document.exitFullscreen().catch(function(){});
7875
7878
  }
7876
7879
  (function bindCameraOperations(){
7877
- var close=$('pk-camera-ops-close'),refresh=$('pk-camera-ops-refresh'),nativeBtn=$('pk-camera-ops-native'),trigger=$('pk-camera-ops-trigger'),prod=$('pk-camera-ops-production'),end=$('pk-camera-ops-end');
7880
+ var close=$('pk-camera-ops-close'),refresh=$('pk-camera-ops-refresh'),nativeBtn=$('pk-camera-ops-native'),trigger=$('pk-camera-ops-trigger'),prod=$('pk-camera-ops-production'),end=$('pk-camera-ops-end'),motorDiscover=$('pk-camera-ops-motor-discover'),motorRun=$('pk-camera-ops-motor-run'),motorTest=$('pk-camera-ops-motor-test'),motorSelect=$('pk-camera-ops-motor-sequence');
7878
7881
  if(close)close.addEventListener('click',closeCameraOperations);if(refresh)refresh.addEventListener('click',reloadCameraOperations);if(nativeBtn)nativeBtn.addEventListener('click',function(){var host=$('pk-camera-ops');if(host&&host.requestFullscreen)host.requestFullscreen().catch(function(e){showOut('fullscreen: '+e.message);});});if(trigger)trigger.addEventListener('click',function(){if(cameraOpsNode)triggerSession(schedId(cameraOpsNode));});if(prod)prod.addEventListener('click',function(){if(cameraOpsNode){var d=schedulerDevice(cameraOpsNode)||{};setProductionMode(cameraOpsNode,!d.production_mode);setTimeout(refreshCameraOperations,400);}});if(end)end.addEventListener('click',function(){if(cameraOpsNode)endSession(schedId(cameraOpsNode));});document.addEventListener('keydown',function(e){if(e.key==='Escape'&&cameraOpsNode)closeCameraOperations();});
7882
+ if(motorDiscover)motorDiscover.addEventListener('click',function(){if(cameraOpsNode)discoverMotorRelays(cameraOpsNode,false).catch(function(error){motorStatus('Discovery failed: '+error.message,'bad');});});if(motorRun)motorRun.addEventListener('click',function(){if(cameraOpsNode)runMotorSequence(cameraOpsNode);});if(motorTest)motorTest.addEventListener('click',function(){if(cameraOpsNode)testAllMotorRelays(cameraOpsNode);});if(motorSelect)motorSelect.addEventListener('change',function(){motorSequenceId=motorSelect.value;renderMotorEditor();refreshCameraOperations();});
7879
7883
  })();
7880
7884
 
7881
7885
  // ── cam: target a LiveKit track; fall back to a styled preview ──
@@ -9,7 +9,7 @@
9
9
  */
10
10
  import { spawn } from 'node:child_process';
11
11
  import { existsSync, mkdirSync, openSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
12
- import { homedir } from 'node:os';
12
+ import { homedir, networkInterfaces } from 'node:os';
13
13
  import { dirname, join } from 'node:path';
14
14
  import { fileURLToPath } from 'node:url';
15
15
  import chalk from 'chalk';
@@ -18,6 +18,122 @@ const VISION_URL = 'http://127.0.0.1:5000/api/media/inventory';
18
18
  const VISION_STATUS_URL = 'http://127.0.0.1:5000/api/camera/status';
19
19
  const RESTART_DELAY_MS = 5_000;
20
20
  const MEDIA_WATCHDOG_INTERVAL_MS = 10_000;
21
+ const SCHEDULER_HEARTBEAT_INTERVAL_MS = 5_000;
22
+ function currentLanIp() {
23
+ for (const addresses of Object.values(networkInterfaces())) {
24
+ for (const address of addresses ?? []) {
25
+ if (address.family === 'IPv4' && !address.internal)
26
+ return address.address;
27
+ }
28
+ }
29
+ return undefined;
30
+ }
31
+ function readSchedulerIdentity() {
32
+ const configDir = join(homedir(), '.robopark');
33
+ try {
34
+ const config = JSON.parse(readFileSync(join(configDir, 'preview_agent.json'), 'utf8'));
35
+ const deviceId = String(config.device_id ?? '').trim();
36
+ const fileToken = existsSync(join(configDir, 'device_token'))
37
+ ? readFileSync(join(configDir, 'device_token'), 'utf8').trim()
38
+ : '';
39
+ const deviceToken = fileToken || String(config.device_token ?? '').trim();
40
+ if (deviceId && deviceToken)
41
+ return { deviceId, deviceToken };
42
+ }
43
+ catch {
44
+ // First boot has no identity yet.
45
+ }
46
+ return undefined;
47
+ }
48
+ function persistSchedulerIdentity(identity, schedulerUrl) {
49
+ const configDir = join(homedir(), '.robopark');
50
+ const configPath = join(configDir, 'preview_agent.json');
51
+ mkdirSync(configDir, { recursive: true });
52
+ let config = {};
53
+ try {
54
+ config = JSON.parse(readFileSync(configPath, 'utf8'));
55
+ }
56
+ catch { /* first boot */ }
57
+ config.device_id = identity.deviceId;
58
+ config.device_token = identity.deviceToken;
59
+ config.scheduler_url = schedulerUrl;
60
+ writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf8');
61
+ writeFileSync(join(configDir, 'device_token'), identity.deviceToken, { encoding: 'utf8', mode: 0o600 });
62
+ }
63
+ function startSchedulerHeartbeat(opts, livekitUrl) {
64
+ let identity = readSchedulerIdentity();
65
+ let stopped = false;
66
+ let inFlight = false;
67
+ const bootstrap = async () => {
68
+ const response = await fetch(`${opts.schedulerUrl.replace(/\/+$/, '')}/api/devices/bootstrap`, {
69
+ method: 'POST',
70
+ headers: {
71
+ 'content-type': 'application/json',
72
+ 'x-robopark-mesh-token': opts.token,
73
+ },
74
+ body: JSON.stringify({ name: opts.name, lan_ip: currentLanIp(), livekit_url: livekitUrl }),
75
+ signal: AbortSignal.timeout(10_000),
76
+ });
77
+ if (!response.ok)
78
+ throw new Error(`bootstrap HTTP ${response.status}`);
79
+ const data = await response.json();
80
+ const next = {
81
+ deviceId: String(data.device_id ?? '').trim(),
82
+ deviceToken: String(data.device_token ?? '').trim(),
83
+ };
84
+ if (!next.deviceId || !next.deviceToken)
85
+ throw new Error('bootstrap returned no device credentials');
86
+ persistSchedulerIdentity(next, opts.schedulerUrl);
87
+ return next;
88
+ };
89
+ const beat = async () => {
90
+ if (stopped || inFlight)
91
+ return;
92
+ inFlight = true;
93
+ try {
94
+ identity = readSchedulerIdentity() ?? identity;
95
+ if (!identity)
96
+ identity = await bootstrap();
97
+ let response = await fetch(`${opts.schedulerUrl.replace(/\/+$/, '')}/api/devices/${encodeURIComponent(identity.deviceId)}/heartbeat`, {
98
+ method: 'POST',
99
+ headers: {
100
+ authorization: `Bearer ${identity.deviceToken}`,
101
+ 'content-type': 'application/json',
102
+ 'x-robopark-mesh-token': opts.token,
103
+ },
104
+ body: JSON.stringify({ status: 'online', ip: currentLanIp(), livekit_url: livekitUrl }),
105
+ signal: AbortSignal.timeout(10_000),
106
+ });
107
+ if (response.status === 401 || response.status === 404) {
108
+ identity = await bootstrap();
109
+ response = await fetch(`${opts.schedulerUrl.replace(/\/+$/, '')}/api/devices/${encodeURIComponent(identity.deviceId)}/heartbeat`, {
110
+ method: 'POST',
111
+ headers: {
112
+ authorization: `Bearer ${identity.deviceToken}`,
113
+ 'content-type': 'application/json',
114
+ 'x-robopark-mesh-token': opts.token,
115
+ },
116
+ body: JSON.stringify({ status: 'online', ip: currentLanIp(), livekit_url: livekitUrl }),
117
+ signal: AbortSignal.timeout(10_000),
118
+ });
119
+ }
120
+ if (!response.ok)
121
+ throw new Error(`heartbeat HTTP ${response.status}`);
122
+ }
123
+ catch (error) {
124
+ console.error(chalk.yellow(` scheduler heartbeat watchdog: ${error instanceof Error ? error.message : String(error)}`));
125
+ }
126
+ finally {
127
+ inFlight = false;
128
+ }
129
+ };
130
+ void beat();
131
+ const timer = setInterval(() => { void beat(); }, SCHEDULER_HEARTBEAT_INTERVAL_MS);
132
+ return () => {
133
+ stopped = true;
134
+ clearInterval(timer);
135
+ };
136
+ }
21
137
  function resetStaleEnrollment() {
22
138
  const configDir = join(homedir(), '.robopark');
23
139
  const tokenPath = join(configDir, 'device_token');
@@ -288,6 +404,7 @@ export async function roboparkRobotRuntime(opts) {
288
404
  }
289
405
  // Identity and inventory reporting start immediately. RoboVision may still
290
406
  // be installing or recovering without making the robot disappear.
407
+ const stopSchedulerHeartbeat = startSchedulerHeartbeat(opts, livekitUrl);
291
408
  start(children[0]);
292
409
  start(preview);
293
410
  if (opts.vision !== false) {
@@ -303,6 +420,7 @@ export async function roboparkRobotRuntime(opts) {
303
420
  const mediaWatchdog = setInterval(() => { void monitorVision(); }, MEDIA_WATCHDOG_INTERVAL_MS);
304
421
  const shutdown = () => {
305
422
  stopping = true;
423
+ stopSchedulerHeartbeat();
306
424
  clearInterval(mediaWatchdog);
307
425
  for (const entry of [...children, preview])
308
426
  signalChild(entry, 'SIGTERM');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "infinicode",
3
- "version": "2.8.107",
3
+ "version": "2.8.108",
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",