infinicode 2.8.102 → 2.8.104
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/federation/dashboard-html.d.ts +1 -1
- package/dist/kernel/federation/dashboard-html.js +78 -39
- package/dist/robopark/add-robot.d.ts +2 -0
- package/dist/robopark/add-robot.js +51 -13
- package/dist/robopark/robot-runtime.js +1 -1
- package/dist/robopark-cli.js +2 -0
- package/package.json +1 -1
- package/packages/robopark/scheduler/main.py +143 -10
- package/packages/robopark/scheduler/preview_agent.py +2 -2
- package/packages/robopark/scheduler/robot_supervisor.py +41 -0
|
@@ -26,7 +26,7 @@ export const DASHBOARD_HTML = `<!doctype html>
|
|
|
26
26
|
<head>
|
|
27
27
|
<meta charset="utf-8">
|
|
28
28
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
29
|
-
<meta name="robopark-ui-build" content="
|
|
29
|
+
<meta name="robopark-ui-build" content="provisioning-token-20260717-1">
|
|
30
30
|
<title>RoboPark Control</title>
|
|
31
31
|
<style>
|
|
32
32
|
/* ── premium tech glass theme ── dark is the committed default; light is a frosted toggle ── */
|
|
@@ -363,20 +363,24 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
363
363
|
.pk-ctl{display:flex;gap:0.45rem;flex-wrap:wrap;}
|
|
364
364
|
.pk-note{font-family:var(--mono);font-size:0.58rem;color:var(--gold);opacity:0.85;margin-top:0.4rem;}
|
|
365
365
|
.pk-pipeline{display:grid;gap:0.42rem;margin-top:0.7rem;position:relative;}
|
|
366
|
+
.pk-live-stage-focus{--stage-color:var(--gold-bright);position:relative;overflow:hidden;margin-top:.58rem;padding:.78rem .85rem .78rem 1rem;border:1px solid var(--stage-color);border-radius:13px;background:linear-gradient(105deg,color-mix(in srgb,var(--stage-color) 24%,transparent),rgba(8,8,8,.68));box-shadow:0 10px 30px rgba(0,0,0,.28),inset 4px 0 0 var(--stage-color);}
|
|
367
|
+
.pk-live-stage-focus:before{content:'';position:absolute;width:150px;height:150px;right:-65px;top:-95px;border-radius:50%;background:var(--stage-color);opacity:.12;filter:blur(5px);animation:pulse 1.3s infinite;}.pk-live-stage-focus .eyebrow{display:block;font-family:var(--mono);font-size:.49rem;letter-spacing:.13em;text-transform:uppercase;color:var(--stage-color);}.pk-live-stage-focus b{display:block;margin-top:.2rem;font-size:1rem;letter-spacing:.015em;color:var(--ink);}.pk-live-stage-focus span:last-child{display:block;margin-top:.18rem;font-family:var(--mono);font-size:.57rem;color:var(--muted);}.pk-live-stage-focus.blocked,.pk-live-stage-focus.failed{--stage-color:var(--bad)}.pk-live-stage-focus.waiting,.pk-live-stage-focus.paused{--stage-color:var(--muted)}.pk-live-stage-focus.active,.pk-live-stage-focus.running{--stage-color:var(--warn)}
|
|
366
368
|
.pk-pipe-step{--phase:var(--gold);display:grid;grid-template-columns:2rem minmax(7.2rem,.72fr) minmax(9rem,1.35fr);gap:0.55rem;align-items:center;padding:0.55rem 0.62rem;border-radius:11px;border:1px solid rgba(255,255,255,.06);background:linear-gradient(110deg,color-mix(in srgb,var(--phase) 7%,transparent),rgba(255,255,255,.018));font-family:var(--mono);font-size:0.58rem;color:var(--muted);transition:transform .18s,border-color .18s,background .18s;position:relative;overflow:hidden;}
|
|
367
369
|
.pk-pipe-step.phase-hardware{--phase:#63d5c6}.pk-pipe-step.phase-trigger{--phase:#efb94c}.pk-pipe-step.phase-session{--phase:#66a8ff}.pk-pipe-step.phase-media{--phase:#68d48f}.pk-pipe-step.phase-intelligence{--phase:#e38bff}.pk-pipe-step.phase-playback{--phase:#ff9b62}.pk-pipe-step.phase-close{--phase:#a8b0bd}
|
|
368
370
|
.pk-pipe-step .step-index{display:grid;place-items:center;width:1.65rem;height:1.65rem;border-radius:8px;border:1px solid color-mix(in srgb,var(--phase) 35%,transparent);color:var(--phase);background:color-mix(in srgb,var(--phase) 10%,transparent);font-weight:700;}
|
|
369
371
|
.pk-pipe-step .stage{color:var(--text);text-transform:uppercase;letter-spacing:.055em;font-weight:650;display:block;}.pk-pipe-step .phase{display:block;color:var(--phase);font-size:.49rem;letter-spacing:.08em;margin-top:.14rem;}
|
|
370
|
-
.pk-pipe-step .detail{text-align:right;overflow-wrap:anywhere;line-height:1.35;}.pk-pipe-step .time{display:block;color:color-mix(in srgb,var(--muted) 75%,transparent);font-size:.5rem;margin-top:.12rem;}
|
|
372
|
+
.pk-pipe-step .detail{text-align:right;overflow-wrap:anywhere;line-height:1.35;}.pk-pipe-step .time{display:block;color:color-mix(in srgb,var(--muted) 75%,transparent);font-size:.5rem;margin-top:.12rem;}.pk-pipe-step .step-state{display:inline-block;margin-left:.35rem;padding:.1rem .3rem;border:1px solid currentColor;border-radius:999px;font-size:.43rem;letter-spacing:.07em;vertical-align:1px;color:var(--muted)}
|
|
371
373
|
.pk-pipe-step.ok{border-color:color-mix(in srgb,var(--ok) 22%,transparent)}
|
|
374
|
+
.pk-pipe-step.ok .step-state{color:var(--ok)}.pk-pipe-step.pending{opacity:.52}.pk-pipe-step.pending .step-index{filter:saturate(.3)}
|
|
372
375
|
.pk-pipe-step.failed,.pk-pipe-step.blocked{border-color:rgba(255,90,90,.48);background:linear-gradient(110deg,rgba(255,70,70,.14),rgba(255,255,255,.02));}
|
|
373
|
-
.pk-pipe-step.current{transform:translateX(
|
|
376
|
+
.pk-pipe-step.current{transform:translateX(5px) scale(1.012);border-color:var(--phase);background:linear-gradient(105deg,color-mix(in srgb,var(--phase) 24%,transparent),rgba(255,255,255,.045));box-shadow:0 0 0 1px color-mix(in srgb,var(--phase) 28%,transparent),0 10px 32px rgba(0,0,0,.3);opacity:1;z-index:2;}
|
|
374
377
|
.pk-pipe-step.current:after{content:'ACTIVE';position:absolute;right:-1.7rem;top:.45rem;transform:rotate(38deg);padding:.12rem 2rem;background:var(--phase);color:#080808;font-size:.42rem;font-weight:800;letter-spacing:.08em;}
|
|
375
378
|
.pk-pipe-step.current .step-index{box-shadow:0 0 14px color-mix(in srgb,var(--phase) 55%,transparent);animation:pulse 1.15s infinite;}
|
|
376
379
|
.pk-pipeline-summary{padding:0.7rem 0.75rem;border:1px solid var(--glass-border);border-radius:11px;background:linear-gradient(120deg,rgba(212,170,72,.08),rgba(255,255,255,.018));font-family:var(--mono);font-size:0.62rem;color:var(--muted);line-height:1.45;}
|
|
377
380
|
.pk-pipeline-summary.ready{color:var(--ok);border-color:rgba(70,220,150,.35);}
|
|
378
381
|
.pk-pipeline-summary.running{color:var(--warn);border-color:rgba(255,196,70,.35);}
|
|
379
382
|
.pk-pipeline-summary.blocked{color:var(--bad);border-color:rgba(255,90,90,.4);}
|
|
383
|
+
.fleet-stage-focus{margin-top:.48rem;padding:.48rem .58rem;border:1px solid color-mix(in srgb,var(--warn) 48%,transparent);border-radius:9px;background:linear-gradient(100deg,rgba(255,184,62,.14),rgba(255,255,255,.02));font-family:var(--mono)}.fleet-stage-focus span{display:block;color:var(--warn);font-size:.45rem;letter-spacing:.1em;text-transform:uppercase}.fleet-stage-focus b{display:block;margin-top:.12rem;color:var(--ink);font-size:.66rem}.fleet-stage-focus.blocked,.fleet-stage-focus.failed{border-color:color-mix(in srgb,var(--bad) 55%,transparent)}.fleet-stage-focus.blocked span,.fleet-stage-focus.failed span{color:var(--bad)}.fleet-stage-focus.waiting,.fleet-stage-focus.paused{border-color:var(--glass-border)}.fleet-stage-focus.waiting span,.fleet-stage-focus.paused span{color:var(--muted)}
|
|
380
384
|
.pk-pipe-history{margin-top:.75rem;border-top:1px solid var(--glass-border);padding-top:.7rem}.pk-pipe-history-head{display:flex;align-items:center;justify-content:space-between;gap:.6rem;margin-bottom:.5rem}.pk-pipe-history-head b{font-family:var(--mono);font-size:.65rem;color:var(--ink);text-transform:uppercase;letter-spacing:.06em}.pk-pipe-history-list{display:grid;gap:.45rem}.pk-run-card{border:1px solid var(--glass-border);border-radius:10px;background:var(--panel-2);padding:.55rem .62rem;cursor:pointer}.pk-run-card:hover{border-color:var(--gold)}.pk-run-top{display:grid;grid-template-columns:1fr auto;gap:.5rem;align-items:start}.pk-run-title{font-family:var(--mono);font-size:.61rem;color:var(--ink);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pk-run-meta{font-family:var(--mono);font-size:.53rem;color:var(--muted);margin-top:.17rem}.pk-run-state{font-family:var(--mono);font-size:.5rem;text-transform:uppercase;border:1px solid var(--glass-border);border-radius:999px;padding:.18rem .4rem}.pk-run-state.ok{color:var(--ok);border-color:color-mix(in srgb,var(--ok) 45%,transparent)}.pk-run-state.bad{color:var(--bad);border-color:color-mix(in srgb,var(--bad) 45%,transparent)}.pk-run-state.running{color:var(--warn);border-color:color-mix(in srgb,var(--warn) 45%,transparent)}.pk-run-stages{display:flex;gap:3px;margin-top:.45rem}.pk-run-stages i{height:4px;flex:1;border-radius:3px;background:rgba(255,255,255,.07)}.pk-run-stages i.ok{background:var(--ok)}.pk-run-stages i.failed,.pk-run-stages i.blocked{background:var(--bad)}.pk-history-pager{display:flex;align-items:center;gap:.35rem}.pk-history-pager span{font-family:var(--mono);font-size:.54rem;color:var(--muted)}
|
|
381
385
|
.pk-mic-suite{margin-top:.6rem;display:grid;gap:.55rem}.pk-mic-health{display:grid;grid-template-columns:repeat(3,1fr);gap:.4rem}.pk-mic-check{padding:.48rem;border:1px solid var(--glass-border);border-radius:9px;background:var(--panel-2)}.pk-mic-check .k{font-family:var(--mono);font-size:.49rem;color:var(--muted);text-transform:uppercase}.pk-mic-check .v{font-family:var(--mono);font-size:.62rem;color:var(--ink);margin-top:.16rem;overflow-wrap:anywhere}.pk-mic-check.ok{border-color:color-mix(in srgb,var(--ok) 30%,transparent)}.pk-mic-check.bad{border-color:color-mix(in srgb,var(--bad) 45%,transparent)}.pk-mic-result{border:1px solid var(--glass-border);border-radius:10px;padding:.58rem;background:var(--panel-2);font-family:var(--mono);font-size:.58rem}.pk-mic-result.ok{border-color:var(--ok);background:rgba(60,180,110,.08)}.pk-mic-result.bad{border-color:var(--bad);background:rgba(220,80,80,.08)}.pk-mic-readings{display:grid;grid-template-columns:repeat(4,1fr);gap:.35rem;margin-top:.45rem}.pk-mic-reading{padding:.35rem;background:rgba(0,0,0,.16);border-radius:7px}.pk-mic-reading span{display:block;color:var(--muted);font-size:.48rem;text-transform:uppercase}.pk-mic-reading b{display:block;color:var(--ink);font-size:.67rem;margin-top:.1rem}
|
|
382
386
|
.pk-motor-registry{display:grid;gap:.4rem}.pk-motor-row{display:grid;grid-template-columns:1fr 4.5rem 5.5rem auto;gap:.35rem;align-items:end;padding:.45rem;border:1px solid var(--glass-border);border-radius:9px;background:var(--panel-2)}.pk-motor-canvas{display:flex;align-items:stretch;gap:.55rem;overflow-x:auto;padding:.65rem .2rem;min-height:130px}.pk-motor-node{min-width:165px;padding:.55rem;border:1px solid rgba(255,205,93,.35);border-radius:12px;background:linear-gradient(145deg,rgba(201,150,46,.13),rgba(255,255,255,.025));position:relative}.pk-motor-node:not(:last-child):after{content:'';position:absolute;right:-.56rem;top:50%;width:.56rem;border-top:2px solid var(--gold)}.pk-motor-node .node-num{font-family:var(--mono);font-size:.48rem;color:var(--gold);text-transform:uppercase;margin-bottom:.35rem}.pk-motor-node .rp-field{margin-bottom:.3rem}.pk-motor-empty{display:grid;place-items:center;width:100%;min-height:100px;border:1px dashed var(--glass-border);border-radius:10px;color:var(--muted);font-family:var(--mono);font-size:.58rem}.pk-motor-status{font-family:var(--mono);font-size:.58rem;padding:.5rem;border-radius:8px;background:var(--panel-2);border:1px solid var(--glass-border)}.pk-motor-status.ok{color:var(--ok);border-color:var(--ok)}.pk-motor-status.bad{color:var(--bad);border-color:var(--bad)}
|
|
@@ -1126,18 +1130,21 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
1126
1130
|
<div class="rp-form" id="rp-addrobot">
|
|
1127
1131
|
<h3>Add robot (mint enrollment token)</h3>
|
|
1128
1132
|
<div class="rp-quickrow">
|
|
1129
|
-
<div class="rp-field"><label>Robot name</label><input id="rb-name" placeholder="jaguar"></div>
|
|
1130
|
-
<div class="rp-field"><label>Character preset</label><select id="rb-char"><option value="">— auto (matched from name) —</option></select></div>
|
|
1131
|
-
<
|
|
1133
|
+
<div class="rp-field"><label>Robot name</label><input id="rb-name" placeholder="jaguar"></div>
|
|
1134
|
+
<div class="rp-field"><label>Character preset</label><select id="rb-char"><option value="">— auto (matched from name) —</option></select></div>
|
|
1135
|
+
<div class="rp-field"><label>Robot network</label><select id="rb-network"><option value="lan">LAN (onsite default)</option><option value="tailscale">Tailscale</option></select></div>
|
|
1136
|
+
<div class="rp-field"><label>Mesh token</label><input id="rb-token" spellcheck="false" autocomplete="off" placeholder="required in generated command"></div>
|
|
1137
|
+
<button class="rp-btn primary" id="rb-add" style="height:fit-content">Add robot</button>
|
|
1132
1138
|
</div>
|
|
1133
1139
|
<div class="rp-autonote" id="rb-autonote">the robot self-reports its IP and hardware on first heartbeat — you only need a name and (optionally) a character preset to get started.</div>
|
|
1134
1140
|
<div class="rp-quickrow" id="rb-clicmd-row" style="display:none;">
|
|
1135
|
-
<div class="rp-field" style="flex:1;"><label>run this ON THE ROBOT
|
|
1141
|
+
<div class="rp-field" style="flex:1;"><label>complete production one-liner — run this ON THE ROBOT</label><input id="rb-clicmd" readonly style="cursor:text;"></div>
|
|
1136
1142
|
<button class="rp-btn" id="rb-clicmd-copy" style="height:fit-content">📋 Copy</button>
|
|
1137
1143
|
</div>
|
|
1138
1144
|
<details class="rp-advanced">
|
|
1139
|
-
<summary>Advanced — pre-set network/voice config before the robot connects</summary>
|
|
1140
|
-
<div class="rp-advanced-grid">
|
|
1145
|
+
<summary>Advanced — pre-set network/voice config before the robot connects</summary>
|
|
1146
|
+
<div class="rp-advanced-grid">
|
|
1147
|
+
<div class="rp-field"><label>Site</label><input id="rb-site" placeholder="Tel Aviv"></div>
|
|
1141
1148
|
<div class="rp-field"><label>Tailscale IP</label><input id="rb-tsip" placeholder="auto-detected once connected"></div>
|
|
1142
1149
|
<div class="rp-field"><label>LAN IP</label><input id="rb-lanip" placeholder="auto-detected once connected"></div>
|
|
1143
1150
|
<div class="rp-field"><label>Motor server URL</label><input id="rb-motor" placeholder="http://...:5000"></div>
|
|
@@ -1168,7 +1175,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
1168
1175
|
<div class="rp-config-hero-actions"><button class="rp-btn" id="rp-command-reset">Reset environment</button><button class="rp-btn primary" id="rp-command-copy-all">Copy visible commands</button></div>
|
|
1169
1176
|
</div>
|
|
1170
1177
|
<div class="rp-command-env">
|
|
1171
|
-
<div class="rp-field"><label>Hub LAN IP</label><input id="rp-command-lan" value="192.168.
|
|
1178
|
+
<div class="rp-field"><label>Hub LAN IP</label><input id="rp-command-lan" value="192.168.0.9" spellcheck="false"></div>
|
|
1172
1179
|
<div class="rp-field"><label>Hub Tailscale IP / DNS</label><input id="rp-command-tail" value="100.84.147.24" spellcheck="false"></div>
|
|
1173
1180
|
<div class="rp-field"><label>Site</label><input id="rp-command-site" value="Tel Aviv"></div>
|
|
1174
1181
|
<div class="rp-field"><label>Robot name</label><input id="rp-command-robot" value="bmw" spellcheck="false"></div>
|
|
@@ -1241,6 +1248,10 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
1241
1248
|
(function(){
|
|
1242
1249
|
var TOKEN = new URLSearchParams(location.search).get('token') || '';
|
|
1243
1250
|
var QS = TOKEN ? ('?token=' + encodeURIComponent(TOKEN)) : '';
|
|
1251
|
+
function dashboardMeshToken(){
|
|
1252
|
+
if(TOKEN)return TOKEN;
|
|
1253
|
+
try{var saved=JSON.parse(localStorage.getItem('rp-command-env-v1')||'null');return saved&&String(saved.token||'').trim()||'';}catch(e){return '';}
|
|
1254
|
+
}
|
|
1244
1255
|
var UI_BUILD=(document.querySelector('meta[name="robopark-ui-build"]')||{}).content||'unknown';
|
|
1245
1256
|
fetch('/fed/ui-build'+QS,{cache:'no-store'}).then(function(r){return r.ok?r.json():null;}).then(function(info){
|
|
1246
1257
|
if(!info||!info.build||info.build===UI_BUILD)return;
|
|
@@ -1532,6 +1543,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
1532
1543
|
+ '<div class="meta">'+esc(n.platform||'')+' '+esc((hw&&hw.cpu&&hw.cpu.model)?hw.cpu.model.replace(/\\s+/g,' ').slice(0,22):'')+site+' <span class="mono">v'+esc(n.version||'?')+'</span></div>'
|
|
1533
1544
|
+ (avatar?('<div class="node-hero"><img class="node-avatar" alt="'+esc(n.displayName)+'" src="'+esc(avatar+QS)+'"><div class="node-health"><b>'+esc(String(readiness).replace(/_/g,' '))+'</b><br>'
|
|
1534
1545
|
+(tele?('sessions '+esc(tele.sessions_total||0)+' / triggers '+esc(tele.trigger_count||0)+'<br>latency '+esc(tele.avg_latency_ms!=null?Math.round(tele.avg_latency_ms)+'ms':'n/a')+' / drops '+esc(tele.drop_rate!=null?Math.round(tele.drop_rate*100)+'%':'n/a')+'<br>heartbeat '+esc(hb!=null?hb+'s ago':'unavailable')):'scheduler telemetry pending')+'</div></div>'):'');
|
|
1546
|
+
if(tele){var fleetStage=tele.current_stage||{},fleetStageStatus=fleetStage.status||tele.current_stage_status||'waiting',fleetStageLabel=fleetStage.label||tele.current_stage_label||'Waiting for pipeline telemetry';html+='<div class="fleet-stage-focus '+esc(fleetStageStatus)+'"><span>Live production stage</span><b>'+esc(fleetStageLabel)+'</b></div>';}
|
|
1535
1547
|
|
|
1536
1548
|
if((tele||String(n.role||'').toLowerCase()==='satellite'||String(n.role||'').toLowerCase()==='robot')&&cameraRef){
|
|
1537
1549
|
html += '<div class="fleet-live-camera" data-fleet-camera-host="'+esc(cameraRef)+'"><img alt="Live camera for '+esc(n.displayName)+'" data-fleet-camera="'+esc(cameraRef)+'"><span class="state">'+(up?'connecting camera':'robot offline')+'</span></div>';
|
|
@@ -2248,16 +2260,21 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2248
2260
|
|
|
2249
2261
|
// ── robust CLI one-liner generation, live as you type — no API call
|
|
2250
2262
|
// needed to see it; 'robopark add-robot' mints its own token when run ──
|
|
2251
|
-
function buildAddRobotCommand(name, characterId){
|
|
2252
|
-
if(!name) return '';
|
|
2253
|
-
var
|
|
2254
|
-
var
|
|
2255
|
-
var
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
if(
|
|
2260
|
-
argv.push('--
|
|
2263
|
+
function buildAddRobotCommand(name, characterId){
|
|
2264
|
+
if(!name) return '';
|
|
2265
|
+
var network=$('rb-network')&&$('rb-network').value||'lan';
|
|
2266
|
+
var commandEnv=rpCommandEnv();
|
|
2267
|
+
var hubHost=network==='tailscale'?commandEnv.tail:commandEnv.lan;
|
|
2268
|
+
var hubUrl=/^https?:\/\//i.test(hubHost)?hubHost.replace(/\\/+$/,''):'http://'+hubHost.replace(/\\/+$/,'')+':47913';
|
|
2269
|
+
var schedulerUrl=hubUrl+'/robopark';
|
|
2270
|
+
var argv=['robopark','add-robot',name];
|
|
2271
|
+
if(characterId) argv.push('--character', characterId);
|
|
2272
|
+
var site=$('rb-site')&&$('rb-site').value.trim();if(site)argv.push('--site',site);
|
|
2273
|
+
if(hubUrl) argv.push('--hub-url', hubUrl);
|
|
2274
|
+
var meshToken=$('rb-token')&&$('rb-token').value.trim()||dashboardMeshToken()||'<mesh-token>';
|
|
2275
|
+
argv.push('--token', meshToken);
|
|
2276
|
+
if(schedulerUrl) argv.push('--scheduler-url', schedulerUrl);
|
|
2277
|
+
argv.push(network==='tailscale'?'--tailscale':'--lan','--start','--auto-start','--yes');
|
|
2261
2278
|
return argv.map(function(a){ return /[^a-zA-Z0-9_./:=,-]/.test(a) ? '"'+a.replace(/"/g,'\\"')+'"' : a; }).join(' ');
|
|
2262
2279
|
}
|
|
2263
2280
|
function refreshRobotCliCommand(){
|
|
@@ -2267,8 +2284,11 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2267
2284
|
inp.value=buildAddRobotCommand(name, cid);
|
|
2268
2285
|
row.style.display='flex';
|
|
2269
2286
|
}
|
|
2270
|
-
var rbNameEl=$('rb-name'); if(rbNameEl) rbNameEl.addEventListener('input', refreshRobotCliCommand);
|
|
2271
|
-
var rbCharEl=$('rb-char'); if(rbCharEl) rbCharEl.addEventListener('change', refreshRobotCliCommand);
|
|
2287
|
+
var rbNameEl=$('rb-name'); if(rbNameEl) rbNameEl.addEventListener('input', refreshRobotCliCommand);
|
|
2288
|
+
var rbCharEl=$('rb-char'); if(rbCharEl) rbCharEl.addEventListener('change', refreshRobotCliCommand);
|
|
2289
|
+
var rbNetworkEl=$('rb-network'); if(rbNetworkEl) rbNetworkEl.addEventListener('change', refreshRobotCliCommand);
|
|
2290
|
+
var rbSiteEl=$('rb-site'); if(rbSiteEl) rbSiteEl.addEventListener('input', refreshRobotCliCommand);
|
|
2291
|
+
var rbTokenEl=$('rb-token'); if(rbTokenEl){ rbTokenEl.value=dashboardMeshToken(); rbTokenEl.addEventListener('input', refreshRobotCliCommand); }
|
|
2272
2292
|
var rbCliCopy=$('rb-clicmd-copy'); if(rbCliCopy) rbCliCopy.addEventListener('click', function(){ var i=$('rb-clicmd'); i.select(); try{document.execCommand('copy');}catch(e){} showOut('command copied — run it on the robot itself'); });
|
|
2273
2293
|
|
|
2274
2294
|
function addRobot(){
|
|
@@ -2653,7 +2673,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2653
2673
|
var RP_COMMAND_ENV_KEY='rp-command-env-v1';
|
|
2654
2674
|
var RP_CUSTOM_COMMANDS_KEY='rp-custom-commands-v1';
|
|
2655
2675
|
var rpVisibleCommands=[];
|
|
2656
|
-
function rpCommandDefaults(){return {lan:'192.168.
|
|
2676
|
+
function rpCommandDefaults(){return {lan:'192.168.0.9',tail:'100.84.147.24',site:'Tel Aviv',robot:'bmw',enrollment:'',token:dashboardMeshToken()};}
|
|
2657
2677
|
function rpLoadStoredJson(key,fallback){try{var value=JSON.parse(localStorage.getItem(key)||'null');return value&&typeof value==='object'?value:fallback;}catch(e){return fallback;}}
|
|
2658
2678
|
function rpCommandEnv(){
|
|
2659
2679
|
var d=rpCommandDefaults(),s=rpLoadStoredJson(RP_COMMAND_ENV_KEY,{});
|
|
@@ -2705,7 +2725,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2705
2725
|
}
|
|
2706
2726
|
function initCommandSheet(){
|
|
2707
2727
|
var fields=['lan','tail','site','robot','enrollment','token'],defaults=rpCommandDefaults(),saved=rpLoadStoredJson(RP_COMMAND_ENV_KEY,{});
|
|
2708
|
-
fields.forEach(function(name){var input=$('rp-command-'+name);if(!input)return;input.value=saved[name]!=null?saved[name]:defaults[name];input.addEventListener('input',function(){rpSaveCommandEnv();renderCommandSheet();});});
|
|
2728
|
+
fields.forEach(function(name){var input=$('rp-command-'+name);if(!input)return;input.value=name==='token'&&TOKEN?TOKEN:(saved[name]!=null?saved[name]:defaults[name]);input.addEventListener('input',function(){rpSaveCommandEnv();renderCommandSheet();});});
|
|
2709
2729
|
var search=$('rp-command-search'),category=$('rp-command-category'),grid=$('rp-command-grid'),reset=$('rp-command-reset'),copyAll=$('rp-command-copy-all'),form=$('rp-command-custom-form');
|
|
2710
2730
|
if(search)search.addEventListener('input',renderCommandSheet);if(category)category.addEventListener('change',renderCommandSheet);
|
|
2711
2731
|
if(reset)reset.addEventListener('click',function(){try{localStorage.removeItem(RP_COMMAND_ENV_KEY);}catch(e){}fields.forEach(function(name){var input=$('rp-command-'+name);if(input)input.value=defaults[name];});renderCommandSheet();showOut('command environment reset');});
|
|
@@ -6517,13 +6537,24 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
6517
6537
|
// cars that arrived at the boundary together don't land on
|
|
6518
6538
|
// top of each other.
|
|
6519
6539
|
if(car.dir > 0 && car.x > PARK_W + 0.5){
|
|
6540
|
+
// Outbound (right-bound) car reached the right edge —
|
|
6541
|
+
// re-enter on the left. We swap to the OTHER lane so two
|
|
6542
|
+
// lanes of traffic are visible at all times; the wrap dir
|
|
6543
|
+
// is explicitly set to +1 so the car continues in its
|
|
6544
|
+
// original direction on the new lane. (Previous version
|
|
6545
|
+
// computed dir from the *post-wrap* y, which always
|
|
6546
|
+
// flipped the car to the opposite direction — cars would
|
|
6547
|
+
// oscillate at the road edge instead of crossing it.)
|
|
6520
6548
|
car.x = -0.5 - Math.random() * (PARK_W * 0.5);
|
|
6521
6549
|
car.y = (car.y < -0.9) ? -0.65 : -1.18;
|
|
6522
|
-
car.dir =
|
|
6550
|
+
car.dir = +1;
|
|
6523
6551
|
} else if(car.dir < 0 && car.x < -0.5){
|
|
6552
|
+
// Inbound (left-bound) car reached the left edge —
|
|
6553
|
+
// re-enter on the right, swap to the other lane, keep
|
|
6554
|
+
// direction at -1. (See note above about the prior bug.)
|
|
6524
6555
|
car.x = PARK_W + 0.5 + Math.random() * (PARK_W * 0.5);
|
|
6525
6556
|
car.y = (car.y < -0.9) ? -0.65 : -1.18;
|
|
6526
|
-
car.dir =
|
|
6557
|
+
car.dir = -1;
|
|
6527
6558
|
}
|
|
6528
6559
|
}
|
|
6529
6560
|
}
|
|
@@ -7429,7 +7460,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
7429
7460
|
+'<div class="pk-note" id="pk-camnote">seeking live feed…</div>'
|
|
7430
7461
|
+'<div class="pk-ctl" style="margin-top:0.5rem"><button class="rp-btn" id="pk-golive" data-robot="'+esc(n.name)+'">↻ Reconnect camera</button><button class="rp-btn primary" id="pk-camera-fullscreen">Fullscreen operations view</button><span class="pk-note" id="pk-golivenote" style="margin:0">always-on RoboVision relay</span></div>'
|
|
7431
7462
|
+'<label class="pk-prodtoggle"><input type="checkbox" id="pk-prodmode"'+(devForPm.production_mode?' checked':'')+'><span>Production mode — continuous motion-triggered loop, camera always live</span></label>')
|
|
7432
|
-
+ sect('Remote production test','<div id="pk-pipeline-summary" class="pk-pipeline-summary">Checking robot, camera, scheduler and LiveKit...</div>'
|
|
7463
|
+
+ sect('Remote production test','<div id="pk-live-stage" class="pk-live-stage-focus waiting"><span class="eyebrow">Current production stage</span><b>Checking robot state...</b><span>Waiting for scheduler telemetry</span></div><div id="pk-pipeline-summary" class="pk-pipeline-summary">Checking robot, camera, scheduler and LiveKit...</div>'
|
|
7433
7464
|
+ '<div class="pk-ctl" style="margin-top:0.55rem"><button class="rp-btn primary" id="pk-unattended-test">Run unattended readiness</button><button class="rp-btn" id="pk-pipeline-start">Start full test</button><button class="rp-btn danger" id="pk-pipeline-stop">Stop conversation</button></div>'
|
|
7434
7465
|
+ '<div class="pk-note" id="pk-unattended-result">Unattended readiness runs an acoustic speaker/mic round-trip, triggers the production motion path, and requires greeting playback on the robot.</div>'
|
|
7435
7466
|
+ '<div class="pk-pipeline" id="pk-pipeline"></div>')
|
|
@@ -7581,7 +7612,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
7581
7612
|
function motorStatus(message,kind){var host=$('pk-motor-status');if(host){host.textContent=message;host.className='pk-motor-status '+(kind||'');}}
|
|
7582
7613
|
function loadMotorProfile(n){
|
|
7583
7614
|
var robotId=schedId(n);motorStatus('Loading saved motor profile...');
|
|
7584
|
-
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||'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();motorStatus('Saved profile loaded | '+motorProfile.registry.length+' relay(s), '+motorProfile.sequences.length+' sequence(s)','ok');}).catch(function(error){motorStatus('Motor profile unavailable: '+error.message,'bad');});
|
|
7615
|
+
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');});
|
|
7585
7616
|
}
|
|
7586
7617
|
function saveMotorProfile(n){
|
|
7587
7618
|
captureMotorEditor();var robotId=schedId(n),url=motorProfile.motor_server_url;
|
|
@@ -7598,11 +7629,17 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
7598
7629
|
captureMotorEditor();var motor=motorProfile.registry[index];if(!motor)return;var robotId=schedId(n);motorStatus('Saving profile before safe 300 ms '+motor.name+' test...');
|
|
7599
7630
|
saveMotorProfile(n).then(function(){return rpAction('POST','/robopark/api/robots/'+encodeURIComponent(robotId)+'/motors/'+encodeURIComponent(motor.id)+'/test?duration_ms=300',{});}).then(function(q){return _shellWaitForResult(robotId,q.request_id,q.device_id||robotId,'','motor-test');}).then(function(result){motorStatus(result.ok?motor.name+' relay test completed.':'Relay test failed: '+(result.error||'unknown error'),result.ok?'ok':'bad');}).catch(function(error){motorStatus('Relay test failed: '+error.message,'bad');});
|
|
7600
7631
|
}
|
|
7632
|
+
function discoverMotorRelays(n,automatic){
|
|
7633
|
+
var robotId=schedId(n);motorStatus((automatic?'Automatically discovering':'Discovering')+' relays from the robot motor server...');
|
|
7634
|
+
return rpAction('POST','/robopark/api/robots/'+encodeURIComponent(robotId)+'/motors/discover',{}).then(function(q){return _shellWaitForResult(robotId,q.request_id,q.device_id||robotId,'','motor-discover');}).then(function(result){
|
|
7635
|
+
if(!result.ok)throw new Error(result.error||'robot motor discovery failed');
|
|
7636
|
+
if(!result.registry||!result.registry.length)throw new Error('Robot motor server has no registered relays. Register the wired BCM pins once; unknown pins will not be pulsed.');
|
|
7637
|
+
motorProfile.registry=result.registry;renderMotorEditor();motorStatus('Discovered '+result.registry.length+' robot-local relay(s); saving centrally...','ok');return saveMotorProfile(n);
|
|
7638
|
+
});
|
|
7639
|
+
}
|
|
7601
7640
|
function testAllMotorRelays(n){
|
|
7602
|
-
captureMotorEditor();
|
|
7603
|
-
if(!window.confirm('Pulse all '+motorProfile.registry.length+' registered relays one at a time? Ensure the motor area is clear.'))return;
|
|
7604
|
-
var robotId=schedId(n);motorStatus('Saving profile and testing '+motorProfile.registry.length+' registered GPIO relay(s), one at a time...');
|
|
7605
|
-
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');});
|
|
7641
|
+
captureMotorEditor();var ready=motorProfile.registry.length?Promise.resolve():discoverMotorRelays(n,true);
|
|
7642
|
+
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');});
|
|
7606
7643
|
}
|
|
7607
7644
|
|
|
7608
7645
|
var pipelineHistoryPage=1;
|
|
@@ -7621,7 +7658,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
7621
7658
|
return stages[Math.min(stages.length-1,lastOk+1)];
|
|
7622
7659
|
}
|
|
7623
7660
|
function renderPipeline(data){
|
|
7624
|
-
var host=$('pk-pipeline'), summary=$('pk-pipeline-summary'); if(!host||!summary) return;
|
|
7661
|
+
var host=$('pk-pipeline'), summary=$('pk-pipeline-summary'),focus=$('pk-live-stage'); if(!host||!summary) return;
|
|
7625
7662
|
var latest={}, prereqs=data.prerequisites||{}, session=data.latest_session||{}, sid=session.id||null;
|
|
7626
7663
|
(data.events||[]).forEach(function(ev){
|
|
7627
7664
|
if(ev.session_id && sid && ev.session_id!==sid) return;
|
|
@@ -7630,15 +7667,16 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
7630
7667
|
['robot_online','camera_ready','microphone_ready','speaker_ready'].forEach(function(stage){
|
|
7631
7668
|
if(!latest[stage]) latest[stage]={stage:stage,status:prereqs[stage]?'ok':'blocked',message:prereqs[stage]?'Ready':'Not reported'};
|
|
7632
7669
|
});
|
|
7633
|
-
var running=data.overall==='running',stages=data.stages||Object.keys(PIPELINE_LABELS),current=pipelineCurrentStage(stages,latest,running);
|
|
7670
|
+
var running=data.overall==='running',stages=data.stages||Object.keys(PIPELINE_LABELS),authoritative=data.current_stage||{},current=authoritative.stage||pipelineCurrentStage(stages,latest,running);
|
|
7634
7671
|
host.innerHTML=stages.map(function(stage,index){
|
|
7635
|
-
var ev=latest[stage],isCurrent=stage===current,status=ev&&ev.status||(isCurrent?'running':'pending');
|
|
7636
|
-
var detail=ev&&(ev.message||ev.timestamp)||'waiting';
|
|
7672
|
+
var ev=latest[stage],isCurrent=stage===current,status=(isCurrent&&authoritative.status)||ev&&ev.status||(isCurrent?'running':'pending');
|
|
7673
|
+
var detail=isCurrent&&(authoritative.message||authoritative.label)||ev&&(ev.message||ev.timestamp)||'waiting';
|
|
7637
7674
|
var time=ev&&ev.timestamp?new Date(ev.timestamp+'Z').toLocaleString():'';
|
|
7638
7675
|
var phase=PIPELINE_PHASES[stage]||'session';
|
|
7639
|
-
return '<div class="pk-pipe-step phase-'+phase+' '+esc(status)+(isCurrent?' current':'')+'"><span class="step-index">'+String(index+1).padStart(2,'0')+'</span><span><span class="stage">'+esc(PIPELINE_LABELS[stage]||stage)+'</span><span class="phase">'+esc(phase)+'</span></span><span class="detail">'+esc(detail)+(time?'<span class="time">'+esc(time)+'</span>':'')+'</span></div>';
|
|
7676
|
+
return '<div class="pk-pipe-step phase-'+phase+' '+esc(status)+(isCurrent?' current':'')+'"'+(isCurrent?' aria-current="step"':'')+'><span class="step-index">'+String(index+1).padStart(2,'0')+'</span><span><span class="stage">'+esc(PIPELINE_LABELS[stage]||stage)+'<em class="step-state">'+esc(isCurrent?'ACTIVE':status)+'</em></span><span class="phase">'+esc(phase)+'</span></span><span class="detail">'+esc(detail)+(time?'<span class="time">'+esc(time)+'</span>':'')+'</span></div>';
|
|
7640
7677
|
}).join('');
|
|
7641
7678
|
var blockers=data.blockers||[];
|
|
7679
|
+
if(focus){var focusStatus=authoritative.status||blockers.length?'blocked':running?'active':'waiting';if(authoritative.status)focusStatus=authoritative.status;focus.className='pk-live-stage-focus '+esc(focusStatus);focus.innerHTML='<span class="eyebrow">Current production stage</span><b>'+esc(authoritative.label||PIPELINE_LABELS[current]||current||'Standing by')+'</b><span>'+esc(authoritative.message||blockers[0]||'Robot is armed and waiting for motion')+'</span>';}
|
|
7642
7680
|
summary.className='pk-pipeline-summary '+esc(data.overall||'blocked');
|
|
7643
7681
|
summary.textContent=blockers.length?('BLOCKED: '+blockers[0]):(running?('ACTIVE: '+(PIPELINE_LABELS[current]||current||'starting')+' | session '+(sid||'allocating')):'READY: camera-to-speaker remote test can start');
|
|
7644
7682
|
var micHealth=$('pk-mic-health');if(micHealth){
|
|
@@ -7784,11 +7822,12 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
7784
7822
|
function refreshCameraOperations(){
|
|
7785
7823
|
var n=cameraOpsNode;if(!n)return;var row=schedRow(n)||{},device=schedulerDevice(n)||{},hw=hwByNode[n.nodeId]||{};
|
|
7786
7824
|
var readiness=String(row.readiness_code||(!n.connected?'offline':'mesh_online')),active=!!(row.active_session||row.in_session||row.session_active||row.current_session_id||row.status==='running'||row.status==='connecting');
|
|
7825
|
+
var currentStage=row.current_stage||{},currentStageLabel=currentStage.label||row.current_stage_label||(active?'Conversation starting':device.production_mode?'Armed - waiting for motion':'Production paused'),currentStageStatus=currentStage.status||row.current_stage_status||(active?'active':'waiting');
|
|
7787
7826
|
var img=$('pk-camera-ops-img'),cameraLive=!!(img&&img.dataset.live==='true'),clockEl=$('pk-camera-ops-clock');if(clockEl)clockEl.textContent=clock(Date.now());
|
|
7788
7827
|
var rec=$('pk-camera-ops-rec');if(rec)rec.textContent=(cameraLive?'REC / LIVE':'REC / CONNECTING')+' / '+String(n.displayName||n.name||'robot').toUpperCase();
|
|
7789
|
-
var overlay=$('pk-camera-ops-overlay');if(overlay)overlay.innerHTML='<div class="metric"><b>'+esc(
|
|
7828
|
+
var overlay=$('pk-camera-ops-overlay');if(overlay)overlay.innerHTML='<div class="metric"><b>'+esc(currentStageLabel)+'</b><span>active stage</span></div><div class="metric"><b>'+esc(readiness.replace(/_/g,' '))+'</b><span>readiness</span></div><div class="metric"><b>'+(row.avg_latency_ms!=null?Math.round(row.avg_latency_ms)+'ms':'--')+'</b><span>average latency</span></div><div class="metric"><b>'+(row.drop_rate!=null?Math.round(row.drop_rate*100)+'%':'--')+'</b><span>session drops</span></div>';
|
|
7790
7829
|
var insights=$('pk-camera-ops-insights');if(insights)insights.innerHTML='<span>Camera relay</span><b class="'+(cameraLive?'ok':'bad')+'">'+(cameraLive?'LIVE':'CONNECTING')+'</b><span>Mesh node</span><b class="'+(n.connected?'ok':'bad')+'">'+(n.connected?'connected':'offline')+'</b><span>Production mode</span><b class="'+(device.production_mode?'ok':'')+'">'+(device.production_mode?'armed':'disabled')+'</b><span>Readiness</span><b>'+esc(readiness.replace(/_/g,' '))+'</b><span>Heartbeat</span><b>'+(row.heartbeat_age_seconds!=null?Math.round(row.heartbeat_age_seconds)+'s ago':'--')+'</b><span>Current room</span><b>'+esc(String(row.current_session_id||row.active_session_id||'none').slice(0,24))+'</b><span>Triggers</span><b>'+esc(row.trigger_count!=null?row.trigger_count:0)+'</b><span>Sessions</span><b>'+esc(row.sessions_total!=null?row.sessions_total:0)+'</b>';
|
|
7791
|
-
var stages=[['Robot online',!!n.connected],['Camera streaming',cameraLive],['Motion detector armed',!!device.production_mode],['Scheduler session',active
|
|
7830
|
+
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('');
|
|
7792
7831
|
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>';
|
|
7793
7832
|
var prod=$('pk-camera-ops-production');if(prod){prod.textContent=device.production_mode?'Disable production':'Enable production';prod.classList.toggle('primary',!!device.production_mode);}
|
|
7794
7833
|
}
|
|
@@ -4,6 +4,8 @@ import { type ExplicitContextOpts } from './profile.js';
|
|
|
4
4
|
export interface AddRobotOptions extends ExplicitContextOpts {
|
|
5
5
|
name: string;
|
|
6
6
|
start?: boolean;
|
|
7
|
+
autoStart?: boolean;
|
|
8
|
+
yes?: boolean;
|
|
7
9
|
/** Force a specific park-map pad/character preset id instead of the
|
|
8
10
|
* fuzzy name-based match below — e.g. `--character jaguar`. */
|
|
9
11
|
character?: string;
|
|
@@ -24,23 +24,54 @@
|
|
|
24
24
|
import chalk from 'chalk';
|
|
25
25
|
import { resolveContext } from './profile.js';
|
|
26
26
|
import { roboparkSetup } from './setup.js';
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
27
|
+
function robotReachableSchedulerUrl(schedulerUrl, hubUrl) {
|
|
28
|
+
if (!hubUrl)
|
|
29
|
+
return schedulerUrl;
|
|
30
|
+
try {
|
|
31
|
+
// Port 8080 is intentionally private to the hub. Enrollment, character
|
|
32
|
+
// lookup, heartbeats, and robot sessions must all use the authenticated
|
|
33
|
+
// mesh proxy, whether the hidden scheduler is loopback or a 192.168.x.x
|
|
34
|
+
// address that is only meaningful on the hub's own LAN.
|
|
35
|
+
new URL(schedulerUrl);
|
|
36
|
+
new URL(hubUrl);
|
|
37
|
+
return `${hubUrl.replace(/\/+$/, '')}/robopark`;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
// Keep the original value so the existing request reports a useful error.
|
|
41
|
+
}
|
|
42
|
+
return schedulerUrl;
|
|
43
|
+
}
|
|
44
|
+
function schedulerApiUrl(schedulerUrl, path, token) {
|
|
45
|
+
const url = new URL(`${schedulerUrl.replace(/\/+$/, '')}${path}`);
|
|
46
|
+
if (token && url.pathname.startsWith('/robopark/')) {
|
|
47
|
+
url.searchParams.set('token', token);
|
|
48
|
+
}
|
|
49
|
+
return url.toString();
|
|
50
|
+
}
|
|
51
|
+
async function mintEnrollmentToken(schedulerUrl, name, characterId, token) {
|
|
52
|
+
const url = schedulerApiUrl(schedulerUrl, '/api/devices', token);
|
|
53
|
+
let res;
|
|
54
|
+
try {
|
|
55
|
+
res = await fetch(url, {
|
|
56
|
+
method: 'POST',
|
|
57
|
+
headers: { 'content-type': 'application/json' },
|
|
58
|
+
body: JSON.stringify({ name, character_id: characterId }),
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
const cause = error.cause;
|
|
63
|
+
throw new Error(`cannot reach scheduler through ${schedulerUrl}: ${cause?.message ?? error.message}`);
|
|
64
|
+
}
|
|
34
65
|
if (!res.ok) {
|
|
35
66
|
const text = await res.text().catch(() => '');
|
|
36
67
|
throw new Error(`${res.status}: ${text || res.statusText}`);
|
|
37
68
|
}
|
|
38
69
|
return (await res.json());
|
|
39
70
|
}
|
|
40
|
-
async function pickCharacterPreset(schedulerUrl, name) {
|
|
71
|
+
async function pickCharacterPreset(schedulerUrl, name, token) {
|
|
41
72
|
const lower = name.toLowerCase();
|
|
42
73
|
try {
|
|
43
|
-
const res = await fetch(
|
|
74
|
+
const res = await fetch(schedulerApiUrl(schedulerUrl, '/api/character-presets', token));
|
|
44
75
|
if (!res.ok)
|
|
45
76
|
return undefined;
|
|
46
77
|
const presets = (await res.json());
|
|
@@ -83,11 +114,16 @@ export async function roboparkAddRobot(config, opts) {
|
|
|
83
114
|
console.log(chalk.red(' ✗ no scheduler URL resolved. Pass --scheduler-url/--hub-url, run `robopark hub-use` first, or run this on/near the hub.'));
|
|
84
115
|
process.exit(1);
|
|
85
116
|
}
|
|
86
|
-
|
|
117
|
+
const schedulerUrl = robotReachableSchedulerUrl(ctx.schedulerUrl, ctx.hubUrl);
|
|
118
|
+
if (schedulerUrl.endsWith('/robopark') && !ctx.token) {
|
|
119
|
+
console.log(chalk.red(' ✗ hub mesh token is required to use the scheduler proxy. Pass --token or run `robopark hub-use` first.'));
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
console.log(` scheduler: ${chalk.cyan(schedulerUrl)}`);
|
|
87
123
|
console.log(` name: ${chalk.cyan(opts.name)}${ctx.site ? chalk.dim(' @ ' + ctx.site) : ''}`);
|
|
88
124
|
console.log(` network: ${chalk.cyan(network)}`);
|
|
89
125
|
console.log();
|
|
90
|
-
const characterId = opts.character?.trim() || await pickCharacterPreset(
|
|
126
|
+
const characterId = opts.character?.trim() || await pickCharacterPreset(schedulerUrl, opts.name, ctx.token);
|
|
91
127
|
if (characterId) {
|
|
92
128
|
console.log(` character: ${chalk.cyan(characterId)}${opts.character ? chalk.dim(' (forced via --character)') : chalk.dim(' (auto-matched)')}`);
|
|
93
129
|
}
|
|
@@ -96,7 +132,7 @@ export async function roboparkAddRobot(config, opts) {
|
|
|
96
132
|
}
|
|
97
133
|
let minted;
|
|
98
134
|
try {
|
|
99
|
-
minted = await mintEnrollmentToken(
|
|
135
|
+
minted = await mintEnrollmentToken(schedulerUrl, opts.name, characterId, ctx.token);
|
|
100
136
|
}
|
|
101
137
|
catch (e) {
|
|
102
138
|
console.log(chalk.red(` ✗ failed to mint enrollment token: ${e.message}`));
|
|
@@ -120,6 +156,8 @@ export async function roboparkAddRobot(config, opts) {
|
|
|
120
156
|
schedulerPort: ctx.schedulerUrl ? String(new URL(ctx.schedulerUrl).port || '8080') : undefined,
|
|
121
157
|
enrollmentToken: minted.enrollment_token,
|
|
122
158
|
start: true,
|
|
159
|
+
autoStart: opts.autoStart !== false,
|
|
160
|
+
yes: opts.yes !== false,
|
|
123
161
|
lan: opts.lan,
|
|
124
162
|
tailscale: opts.tailscale,
|
|
125
163
|
});
|
|
@@ -140,7 +178,7 @@ export async function roboparkAddRobot(config, opts) {
|
|
|
140
178
|
argv.push('--scheduler-port', schedulerPort);
|
|
141
179
|
}
|
|
142
180
|
argv.push(network === 'tailscale' ? '--tailscale' : '--lan');
|
|
143
|
-
argv.push('--enrollment-token', minted.enrollment_token, '--start', '--auto-start');
|
|
181
|
+
argv.push('--enrollment-token', minted.enrollment_token, '--start', '--auto-start', '--yes');
|
|
144
182
|
console.log(chalk.dim(` run this ON THE ROBOT${characterId ? ` (binds to pad: ${characterId})` : ''}:`));
|
|
145
183
|
console.log(' ' + chalk.cyan(commandString(argv)));
|
|
146
184
|
if (!ctx.hubUrl) {
|
|
@@ -146,7 +146,7 @@ export async function roboparkRobotRuntime(opts) {
|
|
|
146
146
|
// only while audible robot speech is playing, then hold briefly for
|
|
147
147
|
// room echo decay so VAD cannot interrupt the robot with its own voice.
|
|
148
148
|
ROBOPARK_HALF_DUPLEX: 'true',
|
|
149
|
-
ROBOPARK_ECHO_TAIL_MS: '
|
|
149
|
+
ROBOPARK_ECHO_TAIL_MS: '350',
|
|
150
150
|
ROBOPARK_ECHO_GATE_PEAK: '96',
|
|
151
151
|
// Keep ordinary speaker echo muted but reopen the microphone when a
|
|
152
152
|
// nearby voice rises decisively above the learned acoustic echo floor.
|
package/dist/robopark-cli.js
CHANGED
|
@@ -284,6 +284,8 @@ program
|
|
|
284
284
|
.option('--lan', 'generate a robot command for the same local network (default)')
|
|
285
285
|
.option('--tailscale', 'generate a robot command for a Tailscale-reachable hub')
|
|
286
286
|
.option('--start', 'start the robot node on this machine now, instead of printing the one-liner')
|
|
287
|
+
.option('--auto-start', 'register the robot runtime to start on boot')
|
|
288
|
+
.option('--yes', 'non-interactive production setup')
|
|
287
289
|
.action(async (name, opts) => {
|
|
288
290
|
await roboparkAddRobot(config, { name, ...opts });
|
|
289
291
|
});
|
package/package.json
CHANGED