infinicode 2.8.49 → 2.8.52

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.
@@ -5448,14 +5448,16 @@ button.act:disabled{opacity:0.5;cursor:default;}
5448
5448
  // LiveKit is the authoritative camera feed. The standalone OpenCV MJPEG
5449
5449
  // server is opt-in because running it beside preview_agent would open the
5450
5450
  // same Windows camera twice and make both feeds unreliable.
5451
- function tryVisionOverlay(n){
5452
- var img=$('pk-vision-img'); if(!img) return;
5453
- if(String(window.localStorage.getItem('robopark.visionOverlay')||'').toLowerCase()!=='true'){
5454
- visionOn=false; img.src=''; img.style.display='none'; return;
5455
- }
5456
- var row=schedRow(n), ip=row&&row.lan_ip;
5457
- if(!ip){ visionOn=false; img.src=''; img.style.display='none'; return; }
5458
- var port=(row&&row.vision_port)||5000;
5451
+ function tryVisionOverlay(n, force){
5452
+ var img=$('pk-vision-img'); if(!img) return;
5453
+ if(!force && String(window.localStorage.getItem('robopark.visionOverlay')||'').toLowerCase()!=='true'){
5454
+ visionOn=false; img.src=''; img.style.display='none'; return;
5455
+ }
5456
+ var row=schedRow(n)||{}, device=schedulerDevice(n)||{};
5457
+ var preferTail=/^100\./.test(location.hostname);
5458
+ var ip=preferTail ? (device.tailscale_ip||row.tailscale_ip||device.lan_ip||row.lan_ip) : (device.lan_ip||row.lan_ip||device.tailscale_ip||row.tailscale_ip);
5459
+ if(!ip){ visionOn=false; img.src=''; img.style.display='none'; return; }
5460
+ var port=row.vision_port||5000;
5459
5461
  visionOn=true;
5460
5462
  img.onerror=function(){ visionOn=false; img.style.display='none'; img.src=''; };
5461
5463
  img.onload=function(){ img.style.display='block'; };
@@ -5485,7 +5487,8 @@ button.act:disabled{opacity:0.5;cursor:default;}
5485
5487
  var device=schedulerDevice(n)||{}, inv=device.device_inventory||{};
5486
5488
  if(hasRealInventory(inv)) deviceSelectorsReady=true;
5487
5489
  var search=$('pk-device-search'), query=(search&&search.value||'').trim().toLowerCase();
5488
- var video=deviceList(inv,'video',['auto','none','picamera','/dev/video0','/dev/video1','/dev/video2']);
5490
+ // Do not invent Picamera/V4L2 choices when the robot has not reported hardware.
5491
+ var video=deviceList(inv,'video',['auto','none']);
5489
5492
  var input=deviceList(inv,'audio_input',['default','none']);
5490
5493
  var output=deviceList(inv,'audio_output',['default','none']);
5491
5494
  function fill(id, list, selected){
@@ -5501,7 +5504,11 @@ button.act:disabled{opacity:0.5;cursor:default;}
5501
5504
  fill('pk-audio-output', output, device.audio_output_device||'default');
5502
5505
  var greetings=$('pk-greeting-phrases');
5503
5506
  if(greetings && document.activeElement!==greetings) greetings.value=(device.greeting_phrases||[]).join(String.fromCharCode(10));
5504
- var note=$('pk-device-note'); if(note) note.textContent=(device.id?'inventory from '+(device.name||device.id):'waiting for robot heartbeat inventory');
5507
+ var note=$('pk-device-note');
5508
+ if(note){
5509
+ var source=inv.source||'robot heartbeat', counts=(inv.video||[]).length+'/'+(inv.audio_input||[]).length+'/'+(inv.audio_output||[]).length;
5510
+ note.textContent=hasRealInventory(inv) ? ('inventory from '+source+' · camera/mic/speaker '+counts) : (device.id ? 'no real hardware inventory reported yet · restart vision-agent + preview-agent on this robot' : 'waiting for robot heartbeat inventory');
5511
+ }
5505
5512
  }
5506
5513
  function saveDeviceSelectors(n){
5507
5514
  var device=schedulerDevice(n), id=device&&device.id; if(!id){ showOut('device inventory is not available yet'); return; }
@@ -5867,7 +5874,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
5867
5874
  var effPresetOptions='', effVoiceOptions='';
5868
5875
  for(var ci=0;ci<characterPresets.length;ci++){ var cp=characterPresets[ci]; effPresetOptions += '<option value="'+esc(cp.id||'')+'"'+((cp.id===n.id)?' selected':'')+'>'+esc(cp.name||cp.id||'')+'</option>'; }
5869
5876
  for(var vi=0;vi<voiceStacks.length;vi++){ var vs=voiceStacks[vi]; effVoiceOptions += '<option value="'+esc(vs.id||'')+'">'+esc(vs.name||vs.id||'')+'</option>'; }
5870
- var devForPm=schedulerDevice(n)||{};
5877
+ var devForPm=schedulerDevice(n)||{};
5871
5878
  b.innerHTML =
5872
5879
  sect('👁 camera feed',
5873
5880
  '<div class="pk-cam" id="pk-cam"><canvas id="pk-camcv" width="384" height="216"></canvas>'
@@ -5895,7 +5902,12 @@ button.act:disabled{opacity:0.5;cursor:default;}
5895
5902
  + buildShellSection(schedId(n))
5896
5903
  + 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><button class="rp-btn" id="pk-eff-save" data-robot="'+esc(schedId(n))+'" style="margin-top:0.35rem;">Set up on this robot</button>');
5897
5904
  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><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>');
5898
- refreshDeviceSelectors(n);
5905
+ // Put live hardware selection beside the camera and audio tests,
5906
+ // ahead of persona and service administration in the Park drawer.
5907
+ var deviceInput=$('pk-device-search'), deviceSection=deviceInput&&deviceInput.closest('.pk-sect');
5908
+ var speakerTest=$('pk-spk-test'), speakerSection=speakerTest&&speakerTest.closest('.pk-sect');
5909
+ if(deviceSection && speakerSection) speakerSection.after(deviceSection);
5910
+ refreshDeviceSelectors(n);
5899
5911
  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>';
5900
5912
  var deviceSearch=$('pk-device-search'); if(deviceSearch) deviceSearch.addEventListener('input', function(){ refreshDeviceSelectors(n); });
5901
5913
  var deviceSave=$('pk-device-save'); if(deviceSave) deviceSave.addEventListener('click', function(){ saveDeviceSelectors(n); });
@@ -5903,7 +5915,13 @@ button.act:disabled{opacity:0.5;cursor:default;}
5903
5915
  _bindShellSection(n);
5904
5916
  _spkTestRenderHistory(schedId(n));
5905
5917
  var runSpkTest = $('pk-spk-runtest'); if(runSpkTest) runSpkTest.addEventListener('click', function(){ if(rpReadOnly){ showOut('read-only node'); return; } runSpeakerTest(n); });
5906
- var gl=$('pk-golive'); if(gl) gl.addEventListener('click', function(){ if(rpReadOnly){ showOut('read-only node'); return; } goLive(n); });
5918
+ var gl=$('pk-golive');
5919
+ if(gl){
5920
+ gl.addEventListener('click', function(){ if(rpReadOnly){ showOut('read-only node'); return; } goLive(n); });
5921
+ var vl=document.createElement('button'); vl.className='rp-btn'; vl.id='pk-vision-live'; vl.textContent='Open RoboVision feed';
5922
+ vl.addEventListener('click', function(){ tryVisionOverlay(n, true); if(!visionOn) showOut('RoboVision feed unavailable: robot has no reachable LAN/Tailscale address'); });
5923
+ gl.after(vl);
5924
+ }
5907
5925
  var pm=$('pk-prodmode'); if(pm) pm.addEventListener('change', function(){ if(rpReadOnly){ pm.checked=!pm.checked; showOut('read-only node'); return; } setProductionMode(n, pm.checked); });
5908
5926
  var svcCopy=$('pk-svc-clicmd-copy'); if(svcCopy) svcCopy.addEventListener('click', function(){ var i=$('pk-svc-clicmd'); i.select(); try{document.execCommand('copy');}catch(e){} showOut('command copied — run it on the robot itself'); });
5909
5927
  var svcList=$('pk-svc-list'); if(svcList) svcList.addEventListener('click', function(ev){
@@ -9,7 +9,7 @@ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
9
9
  import { homedir, hostname } from 'node:os';
10
10
  import { join } from 'node:path';
11
11
  import chalk from 'chalk';
12
- import { findPython, findSchedulerPath, ensurePythonDeps } from './python-env.js';
12
+ import { findPython, findSchedulerPath, prepareRobotPython } from './python-env.js';
13
13
  const ROBOPARK_DIR = join(homedir(), '.robopark');
14
14
  const ENROLLMENT_FILE = join(ROBOPARK_DIR, 'enrollment_token');
15
15
  function ensureDir() {
@@ -39,8 +39,8 @@ export async function roboparkEnroll(opts) {
39
39
  console.log(chalk.red(' ✗ could not find preview_agent.py'));
40
40
  process.exit(1);
41
41
  }
42
- const python = findPython();
43
- if (!ensurePythonDeps(python, script))
42
+ const python = prepareRobotPython(findPython(), script);
43
+ if (!python)
44
44
  process.exit(1);
45
45
  const args = [
46
46
  script,
@@ -8,7 +8,7 @@ import { spawn } from 'node:child_process';
8
8
  import { hostname } from 'node:os';
9
9
  import chalk from 'chalk';
10
10
  import { discoverContext } from './discovery.js';
11
- import { findPython, findSchedulerPath, ensurePythonDeps } from './python-env.js';
11
+ import { findPython, findSchedulerPath, prepareRobotPython } from './python-env.js';
12
12
  const DEFAULT_SCHEDULER_PORT = 8080;
13
13
  export async function roboparkPreviewAgent(opts) {
14
14
  const ctx = await discoverContext();
@@ -24,8 +24,8 @@ export async function roboparkPreviewAgent(opts) {
24
24
  console.log(chalk.red(' ✗ could not find preview_agent.py'));
25
25
  process.exit(1);
26
26
  }
27
- const python = findPython();
28
- if (!ensurePythonDeps(python, script))
27
+ const python = prepareRobotPython(findPython(), script);
28
+ if (!python)
29
29
  process.exit(1);
30
30
  const args = [
31
31
  script,
@@ -13,3 +13,9 @@ export declare function findPython(): string;
13
13
  * (e.g. app_pi_clean.py + requirements_pi_unified.txt).
14
14
  */
15
15
  export declare function ensurePythonDeps(python: string, scriptPath: string, modules?: string[], reqFile?: string): boolean;
16
+ /**
17
+ * Create and maintain the isolated robot runtime at ~/.robopark/venv.
18
+ * System site packages stay visible so Pi apt packages such as OpenCV,
19
+ * PyAudio and Picamera2 do not need to be rebuilt by pip on ARM.
20
+ */
21
+ export declare function prepareRobotPython(basePython: string, scriptPath: string, modules?: string[], reqFile?: string): string | null;
@@ -3,8 +3,10 @@
3
3
  * and `vision-agent`, which spawn preview_agent.py / app_pi_clean.py.
4
4
  */
5
5
  import { execSync, spawnSync } from 'node:child_process';
6
- import { existsSync } from 'node:fs';
6
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
7
+ import { homedir } from 'node:os';
7
8
  import { join } from 'node:path';
9
+ import { createHash } from 'node:crypto';
8
10
  import chalk from 'chalk';
9
11
  /** Modules preview_agent.py imports — kept in sync with requirements-robot.txt. */
10
12
  const REQUIRED_MODULES = ['httpx', 'cv2', 'pyaudio', 'livekit'];
@@ -52,6 +54,12 @@ export function findPython() {
52
54
  }
53
55
  return process.platform === 'win32' ? 'python' : 'python3';
54
56
  }
57
+ const ROBOPARK_VENV = join(homedir(), '.robopark', 'venv');
58
+ function venvPythonPath() {
59
+ return process.platform === 'win32'
60
+ ? join(ROBOPARK_VENV, 'Scripts', 'python.exe')
61
+ : join(ROBOPARK_VENV, 'bin', 'python');
62
+ }
55
63
  function missingModules(python, modules) {
56
64
  return modules.filter(mod => {
57
65
  const res = spawnSync(python, ['-c', `import ${mod}`], { stdio: 'ignore' });
@@ -89,3 +97,44 @@ export function ensurePythonDeps(python, scriptPath, modules = REQUIRED_MODULES,
89
97
  }
90
98
  return true;
91
99
  }
100
+ /**
101
+ * Create and maintain the isolated robot runtime at ~/.robopark/venv.
102
+ * System site packages stay visible so Pi apt packages such as OpenCV,
103
+ * PyAudio and Picamera2 do not need to be rebuilt by pip on ARM.
104
+ */
105
+ export function prepareRobotPython(basePython, scriptPath, modules = REQUIRED_MODULES, reqFile = 'requirements-robot.txt') {
106
+ const reqPath = join(scriptPath, '..', reqFile);
107
+ if (!existsSync(reqPath)) {
108
+ console.log(chalk.red(` ✗ ${reqFile} not found next to ${scriptPath}`));
109
+ return null;
110
+ }
111
+ const python = venvPythonPath();
112
+ if (!existsSync(python)) {
113
+ console.log(chalk.dim(` creating RoboPark Python environment: ${ROBOPARK_VENV}`));
114
+ mkdirSync(join(homedir(), '.robopark'), { recursive: true });
115
+ const created = spawnSync(basePython, ['-m', 'venv', '--system-site-packages', ROBOPARK_VENV], { stdio: 'inherit' });
116
+ if (created.status !== 0 || !existsSync(python)) {
117
+ console.log(chalk.red(' ✗ could not create ~/.robopark/venv. On Debian/Raspberry Pi install python3-venv, then retry.'));
118
+ return null;
119
+ }
120
+ }
121
+ const requirements = readFileSync(reqPath, 'utf8');
122
+ const fingerprint = createHash('sha256').update(requirements).digest('hex');
123
+ const marker = join(ROBOPARK_VENV, `.robopark-${reqFile}.sha256`);
124
+ const installedFingerprint = existsSync(marker) ? readFileSync(marker, 'utf8').trim() : '';
125
+ if (installedFingerprint !== fingerprint || missingModules(python, modules).length > 0) {
126
+ console.log(chalk.dim(` syncing RoboPark Python dependencies from ${reqFile}…`));
127
+ const install = spawnSync(python, ['-m', 'pip', 'install', '--upgrade', '-r', reqPath], { stdio: 'inherit' });
128
+ if (install.status !== 0) {
129
+ console.log(chalk.red(` ✗ dependency install failed in ${ROBOPARK_VENV}`));
130
+ return null;
131
+ }
132
+ writeFileSync(marker, `${fingerprint}\n`);
133
+ }
134
+ const stillMissing = missingModules(python, modules);
135
+ if (stillMissing.length) {
136
+ console.log(chalk.red(` ✗ venv is still missing: ${stillMissing.join(', ')}`));
137
+ return null;
138
+ }
139
+ return python;
140
+ }
@@ -7,7 +7,7 @@
7
7
  */
8
8
  import { spawn } from 'node:child_process';
9
9
  import chalk from 'chalk';
10
- import { findPython, findVisionPath, ensurePythonDeps, VISION_REQUIRED_MODULES } from './python-env.js';
10
+ import { findPython, findVisionPath, prepareRobotPython, VISION_REQUIRED_MODULES } from './python-env.js';
11
11
  const DEFAULT_VISION_PORT = 5000;
12
12
  const DEFAULT_MOTION_WEBHOOK_PORT = 5057; // must match preview-agent's --vision-webhook-port default
13
13
  function isPiArm() {
@@ -19,12 +19,13 @@ export async function roboparkVisionAgent(opts) {
19
19
  console.log(chalk.red(' ✗ could not find app_pi_clean.py'));
20
20
  process.exit(1);
21
21
  }
22
- const python = findPython();
22
+ const basePython = findPython();
23
23
  // Real Pi: opencv comes from `apt install python3-opencv`, not pip (see
24
24
  // requirements_pi_unified.txt) — pip-installing it here would fight the
25
25
  // ARM-optimized system build. Everywhere else: plain pip install works.
26
26
  const reqFile = isPiArm() ? 'requirements_pi_unified.txt' : 'requirements-demo.txt';
27
- if (!ensurePythonDeps(python, script, VISION_REQUIRED_MODULES, reqFile))
27
+ const python = prepareRobotPython(basePython, script, VISION_REQUIRED_MODULES, reqFile);
28
+ if (!python)
28
29
  process.exit(1);
29
30
  const port = opts.port ?? String(DEFAULT_VISION_PORT);
30
31
  const webhookUrl = opts.motionWebhookUrl ?? `http://127.0.0.1:${DEFAULT_MOTION_WEBHOOK_PORT}/`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "infinicode",
3
- "version": "2.8.49",
3
+ "version": "2.8.52",
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",