infinicode 2.8.31 → 2.8.33

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.
@@ -198,11 +198,25 @@ button.act:disabled{opacity:0.5;cursor:default;}
198
198
  .rp-head{display:flex;justify-content:space-between;align-items:baseline;flex-wrap:wrap;gap:0.6rem;padding:1rem 1.1rem 0.2rem;}
199
199
  .rp-totals{display:flex;gap:0.5rem;flex-wrap:wrap;}
200
200
  .rp-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:0.75rem;padding:0.8rem 1.1rem;}
201
- .rp-form{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:0.55rem;align-items:end;background:var(--panel-2);padding:0.7rem;border-radius:11px;border:1px solid var(--glass-border);margin:0.8rem 1.1rem;}
202
- .rp-form h3{grid-column:1/-1;font-family:var(--mono);font-size:0.7rem;letter-spacing:0.08em;text-transform:uppercase;color:var(--gold-bright);margin-bottom:0.2rem;}
203
- .rp-field{display:flex;flex-direction:column;gap:0.25rem;}
204
- .rp-field label{font-size:0.68rem;color:var(--muted);}
205
- .rp-field input,.rp-field select{font-family:var(--mono);font-size:0.72rem;background:var(--panel-solid);border:1px solid var(--glass-border);color:var(--ink);border-radius:7px;padding:0.38rem 0.5rem;}
201
+ .rp-form{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:0.7rem;align-items:end;background:var(--panel-2);padding:1rem 1.05rem;border-radius:14px;border:1px solid var(--glass-border);margin:1rem 1.1rem;box-shadow:var(--shadow),inset 0 1px 0 var(--glass-hi);}
202
+ .rp-form h3{grid-column:1/-1;font-family:var(--mono);font-size:0.72rem;letter-spacing:0.09em;text-transform:uppercase;color:var(--gold-bright);margin-bottom:0.35rem;padding-bottom:0.5rem;border-bottom:1px solid var(--glass-border);}
203
+ .rp-field{display:flex;flex-direction:column;gap:0.3rem;}
204
+ .rp-field label{font-size:0.68rem;color:var(--muted);letter-spacing:0.01em;}
205
+ .rp-field input,.rp-field select,.rp-field textarea{font-family:var(--mono);font-size:0.74rem;background:var(--panel-solid);border:1px solid var(--glass-border);color:var(--ink);border-radius:8px;padding:0.48rem 0.6rem;transition:border-color .15s,box-shadow .15s;}
206
+ .rp-field input:focus,.rp-field select:focus,.rp-field textarea:focus{outline:none;border-color:var(--gold-bright);box-shadow:0 0 0 3px color-mix(in srgb,var(--gold-bright) 18%,transparent);}
207
+ .rp-quickrow{grid-column:1/-1;display:flex;gap:0.6rem;flex-wrap:wrap;align-items:flex-end;padding-bottom:0.2rem;}
208
+ .rp-quickrow .rp-field{flex:1;min-width:180px;}
209
+ .rp-autonote{grid-column:1/-1;font-family:var(--mono);font-size:0.66rem;color:var(--muted);display:flex;align-items:center;gap:0.4rem;min-height:1.2em;}
210
+ .rp-autonote.ok{color:var(--ok);} .rp-autonote.bad{color:var(--bad);} .rp-autonote.busy{color:var(--info);}
211
+ .rp-advanced{grid-column:1/-1;margin-top:0.3rem;border-top:1px dashed var(--glass-border);padding-top:0.7rem;}
212
+ .rp-advanced summary{cursor:pointer;font-family:var(--mono);font-size:0.66rem;letter-spacing:0.05em;text-transform:uppercase;color:var(--muted);list-style:none;display:flex;align-items:center;gap:0.4rem;user-select:none;}
213
+ .rp-advanced summary::-webkit-details-marker{display:none;}
214
+ .rp-advanced summary::before{content:"▸";display:inline-block;transition:transform .15s;color:var(--gold);}
215
+ .rp-advanced[open] summary::before{transform:rotate(90deg);}
216
+ .rp-advanced summary:hover{color:var(--gold-bright);}
217
+ .rp-advanced-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:0.6rem;margin-top:0.7rem;}
218
+ .rp-btn.primary{background:linear-gradient(160deg,color-mix(in srgb,var(--gold-bright) 30%,var(--panel-2)),var(--panel-2));border-color:var(--gold-bright);color:var(--gold-bright);font-weight:600;}
219
+ .rp-btn.primary:hover{box-shadow:0 0 0 3px color-mix(in srgb,var(--gold-bright) 16%,transparent);}
206
220
  .rp-card{border-radius:13px;padding:0.75rem 0.85rem;border-left:3px solid var(--gold-bright);
207
221
  background:var(--panel);-webkit-backdrop-filter:blur(14px) saturate(1.25);backdrop-filter:blur(14px) saturate(1.25);
208
222
  border:1px solid var(--glass-border);box-shadow:var(--shadow),inset 0 1px 0 var(--glass-hi);}
@@ -379,26 +393,47 @@ button.act:disabled{opacity:0.5;cursor:default;}
379
393
  <!-- Add LiveKit server -->
380
394
  <div class="rp-form" id="rp-addserver">
381
395
  <h3>Add LiveKit server</h3>
382
- <div class="rp-field"><label>ID</label><input id="srv-id" placeholder="livekit-1-server"></div>
383
- <div class="rp-field"><label>Name</label><input id="srv-name" placeholder="LiveKit Server 1"></div>
384
- <div class="rp-field"><label>URL (ws:// or wss://)</label><input id="srv-url" placeholder="ws://livekit-1.tail2086c6.ts.net:7880"></div>
385
- <div class="rp-field"><label>API key</label><input id="srv-key" placeholder="devkey"></div>
386
- <div class="rp-field"><label>API secret</label><input type="password" id="srv-secret" placeholder="secret"></div>
387
- <div class="rp-field"><label>Max sessions</label><input id="srv-max" type="number" value="8"></div>
388
- <button class="rp-btn" id="srv-add" style="height:fit-content">Add server</button>
396
+ <div class="rp-quickrow">
397
+ <div class="rp-field"><label>Name</label><input id="srv-name" placeholder="LiveKit Server 1"></div>
398
+ <button class="rp-btn primary" id="srv-detect" style="height:fit-content" title="Probes this hub's own host + common local ports for a running LiveKit instance">🔍 Auto-detect &amp; fill</button>
399
+ <button class="rp-btn" id="srv-add" style="height:fit-content">Add server</button>
400
+ </div>
401
+ <div class="rp-autonote" id="srv-autonote"></div>
402
+ <details class="rp-advanced">
403
+ <summary>Advanced — ID, URL, credentials, capacity</summary>
404
+ <div class="rp-advanced-grid">
405
+ <div class="rp-field"><label>ID</label><input id="srv-id" placeholder="auto-filled from name"></div>
406
+ <div class="rp-field"><label>URL (ws:// or wss://)</label><input id="srv-url" placeholder="ws://host:7880 — auto-detect fills this"></div>
407
+ <div class="rp-field"><label>API key</label><input id="srv-key" placeholder="devkey" value="devkey"></div>
408
+ <div class="rp-field"><label>API secret</label><input type="password" id="srv-secret" placeholder="secretsecretsecretsecretsecretsecret"></div>
409
+ <div class="rp-field"><label>Max sessions</label><input id="srv-max" type="number" value="8"></div>
410
+ </div>
411
+ </details>
389
412
  </div>
390
413
 
391
414
  <!-- Add robot / device -->
392
415
  <div class="rp-form" id="rp-addrobot">
393
416
  <h3>Add robot (mint enrollment token)</h3>
394
- <div class="rp-field"><label>Robot name</label><input id="rb-name" placeholder="jaguar"></div>
395
- <div class="rp-field"><label>Character preset</label><select id="rb-char"><option value="">— auto —</option></select></div>
396
- <div class="rp-field"><label>Tailscale IP (optional)</label><input id="rb-tsip" placeholder="100.x.x.x"></div>
397
- <div class="rp-field"><label>LAN IP (optional)</label><input id="rb-lanip" placeholder="192.168.x.x"></div>
398
- <div class="rp-field"><label>Motor server URL (optional)</label><input id="rb-motor" placeholder="http://...:5000"></div>
399
- <div class="rp-field"><label>LiveKit URL (optional)</label><input id="rb-lk" placeholder="ws://...:7880"></div>
400
- <div class="rp-field"><label>Voice stack</label><select id="rb-voice"><option value="">— default —</option></select></div>
401
- <button class="rp-btn" id="rb-add" style="height:fit-content">Add robot</button>
417
+ <div class="rp-quickrow">
418
+ <div class="rp-field"><label>Robot name</label><input id="rb-name" placeholder="jaguar"></div>
419
+ <div class="rp-field"><label>Character preset</label><select id="rb-char"><option value="">— auto (matched from name) —</option></select></div>
420
+ <button class="rp-btn primary" id="rb-add" style="height:fit-content">Add robot</button>
421
+ </div>
422
+ <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>
423
+ <div class="rp-quickrow" id="rb-clicmd-row" style="display:none;">
424
+ <div class="rp-field" style="flex:1;"><label>run this ON THE ROBOT (no need to click Add — this mints its own token)</label><input id="rb-clicmd" readonly style="cursor:text;"></div>
425
+ <button class="rp-btn" id="rb-clicmd-copy" style="height:fit-content">📋 Copy</button>
426
+ </div>
427
+ <details class="rp-advanced">
428
+ <summary>Advanced — pre-set network/voice config before the robot connects</summary>
429
+ <div class="rp-advanced-grid">
430
+ <div class="rp-field"><label>Tailscale IP</label><input id="rb-tsip" placeholder="auto-detected once connected"></div>
431
+ <div class="rp-field"><label>LAN IP</label><input id="rb-lanip" placeholder="auto-detected once connected"></div>
432
+ <div class="rp-field"><label>Motor server URL</label><input id="rb-motor" placeholder="http://...:5000"></div>
433
+ <div class="rp-field"><label>LiveKit URL</label><input id="rb-lk" placeholder="— uses the registered server above —"></div>
434
+ <div class="rp-field"><label>Voice stack override</label><select id="rb-voice"><option value="">— from character preset —</option></select></div>
435
+ </div>
436
+ </details>
402
437
  </div>
403
438
 
404
439
  <div class="rp-grid" id="rp-grid"></div>
@@ -413,6 +448,31 @@ button.act:disabled{opacity:0.5;cursor:default;}
413
448
  <input id="rp-newvoice-name" placeholder="new stack name" style="flex:1;min-width:0;">
414
449
  <button class="rp-btn" id="rp-addvoice">Add</button>
415
450
  </div>
451
+ <div class="rp-form" id="rp-voice-edit" style="display:none;grid-template-columns:1fr 1fr;gap:0.4rem;margin-top:0.6rem;padding:0.6rem;border-color:var(--border-strong);">
452
+ <h4 style="grid-column:1/-1;margin:0 0 0.2rem;">Edit voice stack: <span id="rpve-name-label"></span></h4>
453
+ <div class="rp-field"><label>STT provider</label><select id="rpve-stt-provider"><option value="speaches">speaches</option><option value="groq">groq</option><option value="vibevoice">vibevoice</option></select></div>
454
+ <div class="rp-field"><label>STT model</label><input id="rpve-stt-model"></div>
455
+ <div class="rp-field"><label>STT language</label><input id="rpve-stt-language" placeholder="en / auto"></div>
456
+ <div class="rp-field"><label>LLM provider</label><select id="rpve-llm-provider"><option value="ollama">ollama</option><option value="groq">groq</option><option value="gemini">gemini</option><option value="moltbot">moltbot</option></select></div>
457
+ <div class="rp-field"><label>LLM model</label><input id="rpve-llm-model"></div>
458
+ <div class="rp-field"><label>LLM API key</label><input type="password" id="rpve-llm-key" placeholder="(uses agent's own if blank)"></div>
459
+ <div class="rp-field"><label>Temperature</label><input id="rpve-temperature" type="number" step="0.1" value="0.7"></div>
460
+ <div class="rp-field"><label>Context size (num_ctx)</label><input id="rpve-num-ctx" type="number" value="8192"></div>
461
+ <div class="rp-field"><label>TTS provider</label><select id="rpve-tts-provider"><option value="elevenlabs">elevenlabs</option><option value="kokoro">kokoro</option><option value="piper">piper</option></select></div>
462
+ <div class="rp-field"><label>TTS voice</label><input id="rpve-tts-voice"></div>
463
+ <div class="rp-field"><label>TTS language</label><input id="rpve-tts-language" placeholder="en"></div>
464
+ <div class="rp-field"><label>Max turns</label><input id="rpve-max-turns" type="number" value="20"></div>
465
+ <div class="rp-field"><label>Min endpointing delay (s)</label><input id="rpve-endpointing" type="number" step="0.1" value="0.7"></div>
466
+ <div class="rp-field"><label><input type="checkbox" id="rpve-interruptions" checked style="width:auto;margin-right:0.35rem;">Allow interruptions</label></div>
467
+ <div class="rp-field"><label><input type="checkbox" id="rpve-wake-enabled" style="width:auto;margin-right:0.35rem;">Wake word enabled</label></div>
468
+ <div class="rp-field"><label>Wake word model</label><input id="rpve-wake-model" placeholder="models/hey_jarvis.onnx"></div>
469
+ <div class="rp-field"><label>Wake threshold</label><input id="rpve-wake-threshold" type="number" step="0.05" value="0.5"></div>
470
+ <div class="rp-field"><label>Wake timeout (s)</label><input id="rpve-wake-timeout" type="number" step="0.5" value="3.0"></div>
471
+ <div style="grid-column:1/-1;display:flex;gap:0.4rem;">
472
+ <button class="rp-btn" id="rpve-save">Save</button>
473
+ <button class="rp-btn" id="rpve-cancel">Cancel</button>
474
+ </div>
475
+ </div>
416
476
  </div>
417
477
  <div>
418
478
  <h4 style="font-family:var(--mono);font-size:0.75rem;color:var(--gold-bright);margin:0.3rem 0 0.5rem;">Character presets</h4>
@@ -423,6 +483,23 @@ button.act:disabled{opacity:0.5;cursor:default;}
423
483
  <datalist id="rp-newchar-stack-list"></datalist>
424
484
  <button class="rp-btn" id="rp-addchar">Add</button>
425
485
  </div>
486
+ <div class="rp-form" id="rp-char-edit" style="display:none;grid-template-columns:1fr;gap:0.4rem;margin-top:0.6rem;padding:0.6rem;border-color:var(--border-strong);">
487
+ <h4 style="margin:0 0 0.2rem;">Edit character: <span id="rpce-name-label"></span></h4>
488
+ <div class="rp-field"><label>Name</label><input id="rpce-name"></div>
489
+ <div class="rp-field"><label>Description</label><input id="rpce-description"></div>
490
+ <div class="rp-field"><label>System prompt</label><textarea id="rpce-prompt" rows="4" style="font-family:var(--mono);font-size:0.72rem;width:100%;resize:vertical;"></textarea></div>
491
+ <div class="rp-field"><label>Voice stack</label><select id="rpce-stack"></select></div>
492
+ <div class="rp-field"><label>Motors (comma-separated)</label><input id="rpce-motors" placeholder="drive, wave"></div>
493
+ <div style="display:flex;gap:0.4rem;">
494
+ <div class="rp-field" style="flex:1"><label>Pad X (0-1)</label><input id="rpce-nx" type="number" step="0.01" min="0" max="1"></div>
495
+ <div class="rp-field" style="flex:1"><label>Pad Y (0-1)</label><input id="rpce-ny" type="number" step="0.01" min="0" max="1"></div>
496
+ </div>
497
+ <div class="rp-field"><label>Avatar image path</label><input id="rpce-img" placeholder="/static/robopark/jaguar.png"></div>
498
+ <div style="display:flex;gap:0.4rem;">
499
+ <button class="rp-btn" id="rpce-save">Save</button>
500
+ <button class="rp-btn" id="rpce-cancel">Cancel</button>
501
+ </div>
502
+ </div>
426
503
  </div>
427
504
  </div>
428
505
  </div>
@@ -895,17 +972,74 @@ button.act:disabled{opacity:0.5;cursor:default;}
895
972
  }
896
973
  }
897
974
 
975
+ function slugify(s){ return (s||'').toLowerCase().replace(/[^a-z0-9_-]+/g,'-').replace(/^-|-$/g,'').slice(0,40); }
976
+
977
+ // ── LiveKit auto-detect: probe this hub's own host + localhost on the
978
+ // standard LiveKit port. A no-cors fetch that *resolves* (even opaquely)
979
+ // means something answered HTTP there; one that *throws* means nothing did.
980
+ // Best-effort only — the Advanced section stays editable so the user can
981
+ // correct a false positive/negative before submitting. ──
982
+ function detectLocalLiveKit(){
983
+ var note=$('srv-autonote'); note.className='rp-autonote busy'; note.textContent='probing for a local LiveKit instance…';
984
+ var hosts=[]; var h=location.hostname||'localhost';
985
+ hosts.push(h); if(h!=='localhost') hosts.push('localhost');
986
+ var tried=0, found=null;
987
+ function tryNext(i){
988
+ if(i>=hosts.length){
989
+ if(found){
990
+ $('srv-url').value='ws://'+found+':7880';
991
+ if(!$('srv-key').value) $('srv-key').value='devkey';
992
+ note.className='rp-autonote ok'; note.textContent='✓ found a LiveKit instance at '+found+':7880 — filled URL + default dev key (verify the secret in Advanced before adding).';
993
+ } else {
994
+ note.className='rp-autonote bad'; note.textContent='✗ no local LiveKit found on '+hosts.join(', ')+':7880 — fill the URL manually in Advanced.';
995
+ }
996
+ return;
997
+ }
998
+ var host=hosts[i];
999
+ fetch('http://'+host+':7880/', {mode:'no-cors', signal: AbortSignal.timeout ? AbortSignal.timeout(2500) : undefined})
1000
+ .then(function(){ found=host; tryNext(hosts.length); })
1001
+ .catch(function(){ tryNext(i+1); });
1002
+ }
1003
+ tryNext(0);
1004
+ }
1005
+
898
1006
  function addServer(){
899
1007
  var body={
900
- id:$('srv-id').value.trim(), name:$('srv-name').value.trim(), url:$('srv-url').value.trim(),
901
- api_key:$('srv-key').value.trim(), api_secret:$('srv-secret').value,
1008
+ id:$('srv-id').value.trim()||slugify($('srv-name').value)+'-server', name:$('srv-name').value.trim(), url:$('srv-url').value.trim(),
1009
+ api_key:$('srv-key').value.trim()||'devkey', api_secret:$('srv-secret').value,
902
1010
  max_sessions:parseInt($('srv-max').value,10)||8
903
1011
  };
904
- if(!body.id||!body.name||!body.url){ showOut('Server ID, name, and URL are required'); return; }
1012
+ if(!body.name||!body.url){ showOut('Server name and URL are required (use Auto-detect, or fill URL under Advanced)'); return; }
905
1013
  body.webhook_url=body.url;
906
1014
  rpAction('POST','/robopark/api/servers',body).then(function(j){ showOut('server added: '+esc((j&&j.id)||body.id)); clearServerForm(); }).catch(function(e){ showOut('add server: '+e.message); });
907
1015
  }
908
- function clearServerForm(){ $('srv-id').value=''; $('srv-name').value=''; $('srv-url').value=''; $('srv-key').value=''; $('srv-secret').value=''; $('srv-max').value='8'; }
1016
+ function clearServerForm(){ $('srv-id').value=''; $('srv-name').value=''; $('srv-url').value=''; $('srv-key').value=''; $('srv-secret').value=''; $('srv-max').value='8'; $('srv-autonote').textContent=''; $('srv-autonote').className='rp-autonote'; }
1017
+
1018
+ // ── robust CLI one-liner generation, live as you type — no API call
1019
+ // needed to see it; 'robopark add-robot' mints its own token when run ──
1020
+ function buildAddRobotCommand(name, characterId){
1021
+ if(!name) return '';
1022
+ var hubUrl=location.origin;
1023
+ var schedulerUrl=(lastStatus && lastStatus.control && lastStatus.control.schedulerUrl) || '';
1024
+ var argv=['robopark','add-robot',name];
1025
+ if(characterId) argv.push('--character', characterId);
1026
+ if(hubUrl) argv.push('--hub-url', hubUrl);
1027
+ if(TOKEN) argv.push('--token', TOKEN);
1028
+ if(schedulerUrl) argv.push('--scheduler-url', schedulerUrl);
1029
+ argv.push('--start');
1030
+ return argv.map(function(a){ return /[^a-zA-Z0-9_./:=,-]/.test(a) ? '"'+a.replace(/"/g,'\\"')+'"' : a; }).join(' ');
1031
+ }
1032
+ function refreshRobotCliCommand(){
1033
+ var name=$('rb-name').value.trim(); var cid=$('rb-char').value||'';
1034
+ var row=$('rb-clicmd-row'), inp=$('rb-clicmd');
1035
+ if(!name){ row.style.display='none'; return; }
1036
+ inp.value=buildAddRobotCommand(name, cid);
1037
+ row.style.display='flex';
1038
+ }
1039
+ var rbNameEl=$('rb-name'); if(rbNameEl) rbNameEl.addEventListener('input', refreshRobotCliCommand);
1040
+ var rbCharEl=$('rb-char'); if(rbCharEl) rbCharEl.addEventListener('change', refreshRobotCliCommand);
1041
+ 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'); });
1042
+
909
1043
  function addRobot(){
910
1044
  var body={
911
1045
  name:$('rb-name').value.trim(), character_id:$('rb-char').value||null,
@@ -916,7 +1050,8 @@ button.act:disabled{opacity:0.5;cursor:default;}
916
1050
  if(!body.name){ showOut('Robot name is required'); return; }
917
1051
  rpAction('POST','/robopark/api/devices',body).then(function(j){ showOut('robot added: '+esc((j&&j.name)||body.name)+'\\ntoken: '+esc((j&&j.enrollment_token)||'(none)')); clearRobotForm(); pollRobopark(); }).catch(function(e){ showOut('add robot: '+e.message); });
918
1052
  }
919
- function clearRobotForm(){ $('rb-name').value=''; $('rb-char').value=''; $('rb-tsip').value=''; $('rb-lanip').value=''; $('rb-motor').value=''; $('rb-lk').value=''; $('rb-voice').value=''; }
1053
+ function clearRobotForm(){ $('rb-name').value=''; $('rb-char').value=''; $('rb-tsip').value=''; $('rb-lanip').value=''; $('rb-motor').value=''; $('rb-lk').value=''; $('rb-voice').value=''; refreshRobotCliCommand(); }
1054
+ var srvDetectBtn=$('srv-detect'); if(srvDetectBtn) srvDetectBtn.addEventListener('click', detectLocalLiveKit);
920
1055
  function triggerSession(id){
921
1056
  rpAction('POST','/robopark/api/robots/'+encodeURIComponent(id)+'/trigger-session',{confidence:0.9}).then(function(){ showOut('triggered motion for '+id); pollRobopark(); Park.refreshDrawer(); }).catch(function(e){ showOut('trigger-session ('+id+'): '+e.message); });
922
1057
  }
@@ -942,7 +1077,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
942
1077
  function fillTemplateVoiceList(){
943
1078
  var el=$('rp-voice-list'); if(!el) return;
944
1079
  var html='';
945
- for(var i=0;i<voiceStacks.length;i++){ var s=voiceStacks[i]; html += '<div class="rp-card" style="padding:0.5rem 0.6rem;border-left-color:var(--info)"><div style="font-family:var(--mono);font-size:0.74rem;font-weight:600;">'+esc(s.name||s.id)+'</div><div style="font-size:0.6rem;color:var(--muted);margin-top:0.15rem;">'+esc(s.stt_provider)+' → '+esc(s.llm_provider)+' → '+esc(s.tts_provider)+'</div></div>'; }
1080
+ for(var i=0;i<voiceStacks.length;i++){ var s=voiceStacks[i]; html += '<div class="rp-card" data-vsid="'+esc(s.id)+'" style="padding:0.5rem 0.6rem;border-left-color:var(--info);cursor:pointer;"><div style="font-family:var(--mono);font-size:0.74rem;font-weight:600;">'+esc(s.name||s.id)+'</div><div style="font-size:0.6rem;color:var(--muted);margin-top:0.15rem;">'+esc(s.stt_provider)+' → '+esc(s.llm_provider)+'/'+esc(s.llm_model||'')+' → '+esc(s.tts_provider)+'</div></div>'; }
946
1081
  if(!voiceStacks.length) html='<div class="pk-note">no voice stacks loaded</div>';
947
1082
  el.innerHTML=html;
948
1083
  var dl=$('rp-newchar-stack-list'); if(dl){ dl.innerHTML=''; for(var j=0;j<voiceStacks.length;j++){ var o=document.createElement('option'); o.value=voiceStacks[j].id; dl.appendChild(o); } }
@@ -950,11 +1085,93 @@ button.act:disabled{opacity:0.5;cursor:default;}
950
1085
  function fillTemplateCharacterList(){
951
1086
  var el=$('rp-char-list'); if(!el) return;
952
1087
  var html='';
953
- for(var i=0;i<characterPresets.length;i++){ var p=characterPresets[i]; html += '<div class="rp-card" style="padding:0.5rem 0.6rem;"><div style="font-family:var(--mono);font-size:0.74rem;font-weight:600;">'+esc(p.name||p.id)+'</div><div style="font-size:0.6rem;color:var(--muted);margin-top:0.15rem;">stack: '+esc(p.voice_stack_id||'—')+' · motors: '+esc((p.motors||[]).join(', '))+'</div></div>'; }
1088
+ for(var i=0;i<characterPresets.length;i++){ var p=characterPresets[i]; html += '<div class="rp-card" data-cpid="'+esc(p.id)+'" style="padding:0.5rem 0.6rem;cursor:pointer;"><div style="font-family:var(--mono);font-size:0.74rem;font-weight:600;">'+esc(p.name||p.id)+'</div><div style="font-size:0.6rem;color:var(--muted);margin-top:0.15rem;">stack: '+esc(p.voice_stack_id||'—')+' · motors: '+esc((p.motors||[]).join(', '))+(p.system_prompt?' · has prompt':' · no prompt set')+'</div></div>'; }
954
1089
  if(!characterPresets.length) html='<div class="pk-note">no character presets loaded</div>';
955
1090
  el.innerHTML=html;
956
1091
  }
957
1092
 
1093
+ // ── voice-stack edit panel ──
1094
+ var editingVoiceStackId=null;
1095
+ function openVoiceStackEdit(id){
1096
+ var s=null; for(var i=0;i<voiceStacks.length;i++){ if(voiceStacks[i].id===id){ s=voiceStacks[i]; break; } }
1097
+ if(!s) return;
1098
+ editingVoiceStackId=id;
1099
+ $('rpve-name-label').textContent=s.name||s.id;
1100
+ $('rpve-stt-provider').value=s.stt_provider||'speaches';
1101
+ $('rpve-stt-model').value=s.stt_model||'';
1102
+ $('rpve-stt-language').value=s.stt_language||'en';
1103
+ $('rpve-llm-provider').value=s.llm_provider||'ollama';
1104
+ $('rpve-llm-model').value=s.llm_model||'';
1105
+ $('rpve-llm-key').value=s.llm_api_key||'';
1106
+ $('rpve-temperature').value=(s.temperature!=null?s.temperature:0.7);
1107
+ $('rpve-num-ctx').value=(s.num_ctx!=null?s.num_ctx:8192);
1108
+ $('rpve-tts-provider').value=s.tts_provider||'elevenlabs';
1109
+ $('rpve-tts-voice').value=s.tts_voice||'';
1110
+ $('rpve-tts-language').value=s.tts_language||'en';
1111
+ $('rpve-max-turns').value=(s.max_turns!=null?s.max_turns:20);
1112
+ $('rpve-endpointing').value=(s.min_endpointing_delay!=null?s.min_endpointing_delay:0.7);
1113
+ $('rpve-interruptions').checked=!!s.allow_interruptions;
1114
+ $('rpve-wake-enabled').checked=!!s.wake_word_enabled;
1115
+ $('rpve-wake-model').value=s.wake_word_model||'';
1116
+ $('rpve-wake-threshold').value=(s.wake_word_threshold!=null?s.wake_word_threshold:0.5);
1117
+ $('rpve-wake-timeout').value=(s.wake_word_timeout!=null?s.wake_word_timeout:3.0);
1118
+ $('rp-voice-edit').style.display='grid';
1119
+ }
1120
+ function saveVoiceStackEdit(){
1121
+ if(!editingVoiceStackId) return;
1122
+ var body={
1123
+ name:$('rpve-name-label').textContent,
1124
+ stt_provider:$('rpve-stt-provider').value, stt_model:$('rpve-stt-model').value.trim(), stt_language:$('rpve-stt-language').value.trim()||'en',
1125
+ llm_provider:$('rpve-llm-provider').value, llm_model:$('rpve-llm-model').value.trim(), llm_api_key:$('rpve-llm-key').value.trim()||null,
1126
+ temperature:parseFloat($('rpve-temperature').value)||0.7, num_ctx:parseInt($('rpve-num-ctx').value,10)||8192,
1127
+ tts_provider:$('rpve-tts-provider').value, tts_voice:$('rpve-tts-voice').value.trim(), tts_language:$('rpve-tts-language').value.trim()||'en',
1128
+ max_turns:parseInt($('rpve-max-turns').value,10)||20, min_endpointing_delay:parseFloat($('rpve-endpointing').value)||0.7,
1129
+ allow_interruptions:$('rpve-interruptions').checked, wake_word_enabled:$('rpve-wake-enabled').checked,
1130
+ wake_word_model:$('rpve-wake-model').value.trim()||null, wake_word_threshold:parseFloat($('rpve-wake-threshold').value)||0.5,
1131
+ wake_word_timeout:parseFloat($('rpve-wake-timeout').value)||3.0
1132
+ };
1133
+ rpAction('PUT','/robopark/api/voice-stacks/'+encodeURIComponent(editingVoiceStackId),body).then(function(){ showOut('voice stack saved'); $('rp-voice-edit').style.display='none'; editingVoiceStackId=null; loadVoiceStacks(); }).catch(function(e){ showOut('save voice stack: '+e.message); });
1134
+ }
1135
+ var vl=$('rp-voice-list'); if(vl) vl.addEventListener('click', function(e){ var c=e.target.closest?e.target.closest('.rp-card'):null; if(!c) return; var id=c.getAttribute('data-vsid'); if(id) openVoiceStackEdit(id); });
1136
+ var vsSave=$('rpve-save'); if(vsSave) vsSave.addEventListener('click', saveVoiceStackEdit);
1137
+ var vsCancel=$('rpve-cancel'); if(vsCancel) vsCancel.addEventListener('click', function(){ $('rp-voice-edit').style.display='none'; editingVoiceStackId=null; });
1138
+
1139
+ // ── character-preset edit panel ──
1140
+ var editingCharPresetId=null;
1141
+ function fillCharEditStackOptions(cur){
1142
+ var sel=$('rpce-stack'); if(!sel) return;
1143
+ sel.innerHTML='<option value="">— default —</option>';
1144
+ for(var i=0;i<voiceStacks.length;i++){ var s=voiceStacks[i]; var o=document.createElement('option'); o.value=s.id; o.textContent=s.name||s.id; if(s.id===cur) o.selected=true; sel.appendChild(o); }
1145
+ }
1146
+ function openCharPresetEdit(id){
1147
+ var p=null; for(var i=0;i<characterPresets.length;i++){ if(characterPresets[i].id===id){ p=characterPresets[i]; break; } }
1148
+ if(!p) return;
1149
+ editingCharPresetId=id;
1150
+ $('rpce-name-label').textContent=p.name||p.id;
1151
+ $('rpce-name').value=p.name||'';
1152
+ $('rpce-description').value=p.description||'';
1153
+ $('rpce-prompt').value=p.system_prompt||'';
1154
+ fillCharEditStackOptions(p.voice_stack_id);
1155
+ $('rpce-motors').value=(p.motors||[]).join(', ');
1156
+ $('rpce-nx').value=(p.nx!=null?p.nx:0.5);
1157
+ $('rpce-ny').value=(p.ny!=null?p.ny:0.5);
1158
+ $('rpce-img').value=p.img||'';
1159
+ $('rp-char-edit').style.display='grid';
1160
+ }
1161
+ function saveCharPresetEdit(){
1162
+ if(!editingCharPresetId) return;
1163
+ var motors=$('rpce-motors').value.split(',').map(function(x){return x.trim();}).filter(function(x){return !!x;});
1164
+ var body={
1165
+ name:$('rpce-name').value.trim()||editingCharPresetId, description:$('rpce-description').value.trim()||null,
1166
+ system_prompt:$('rpce-prompt').value.trim()||null, voice_stack_id:$('rpce-stack').value||null,
1167
+ motors:motors, nx:parseFloat($('rpce-nx').value), ny:parseFloat($('rpce-ny').value), img:$('rpce-img').value.trim()||null
1168
+ };
1169
+ rpAction('PUT','/robopark/api/character-presets/'+encodeURIComponent(editingCharPresetId),body).then(function(){ showOut('character preset saved'); $('rp-char-edit').style.display='none'; editingCharPresetId=null; loadCharacterPresets(); loadParkSlots(); }).catch(function(e){ showOut('save character preset: '+e.message); });
1170
+ }
1171
+ var cl=$('rp-char-list'); if(cl) cl.addEventListener('click', function(e){ var c=e.target.closest?e.target.closest('.rp-card'):null; if(!c) return; var id=c.getAttribute('data-cpid'); if(id) openCharPresetEdit(id); });
1172
+ var cpSave=$('rpce-save'); if(cpSave) cpSave.addEventListener('click', saveCharPresetEdit);
1173
+ var cpCancel=$('rpce-cancel'); if(cpCancel) cpCancel.addEventListener('click', function(){ $('rp-char-edit').style.display='none'; editingCharPresetId=null; });
1174
+
958
1175
  function renderRobotCardSelect(name, current){
959
1176
  var sel=$('rp-char-'+name); if(!sel) return;
960
1177
  sel.dataset.current=current||'';
@@ -1532,19 +1749,32 @@ button.act:disabled{opacity:0.5;cursor:default;}
1532
1749
  + '<div class="pk-kv"><div class="v">'+esc(n.label)+'</div><div class="l">pad</div></div>'
1533
1750
  + '<div class="pk-kv"><div class="v" style="color:var(--muted)">idle</div><div class="l">status</div></div>'
1534
1751
  + '</div>'
1535
- + '<div class="rp-form" style="padding:0.55rem;grid-template-columns:1fr;gap:0.5rem;margin:0;border-color:var(--border-strong);">'
1752
+ + '<div class="rp-form" style="padding:0.7rem;grid-template-columns:1fr;gap:0.55rem;margin:0;border-color:var(--border-strong);">'
1536
1753
  + '<div class="rp-field"><label>Robot name</label><input id="pk-setup-name" value="'+esc(defaultName)+'"></div>'
1537
1754
  + '<div class="rp-field"><label>Character preset</label><select id="pk-setup-char"><option value="">— auto —</option>'+presetOptions+'</select></div>'
1538
- + '<div class="rp-field"><label>Tailscale IP</label><input id="pk-setup-tsip" placeholder="100.x.x.x"></div>'
1539
- + '<div class="rp-field"><label>LAN IP</label><input id="pk-setup-lanip" placeholder="192.168.x.x"></div>'
1755
+ + '<div class="rp-quickrow" style="padding:0;"><button class="rp-btn primary" id="pk-setup-btn" data-preset="'+esc(n.id)+'" style="width:fit-content;">Mint enrollment token &amp; add robot</button></div>'
1756
+ + '<div class="rp-field"><label>or run this ON THE ROBOT (mints its own token)</label><input id="pk-setup-clicmd" readonly></div>'
1757
+ + '<div class="rp-quickrow" style="padding:0;"><button class="rp-btn" id="pk-setup-clicmd-copy" style="width:fit-content;">📋 Copy command</button></div>'
1758
+ + '<details class="rp-advanced"><summary>Advanced — pre-set network/voice config</summary>'
1759
+ + '<div class="rp-advanced-grid">'
1760
+ + '<div class="rp-field"><label>Tailscale IP</label><input id="pk-setup-tsip" placeholder="auto-detected once connected"></div>'
1761
+ + '<div class="rp-field"><label>LAN IP</label><input id="pk-setup-lanip" placeholder="auto-detected once connected"></div>'
1540
1762
  + '<div class="rp-field"><label>Motor server URL</label><input id="pk-setup-motor" placeholder="http://...:5000"></div>'
1541
- + '<div class="rp-field"><label>LiveKit URL</label><input id="pk-setup-lk" placeholder="ws://...:7880"></div>'
1542
- + '<div class="rp-field"><label>Voice stack</label><select id="pk-setup-voice"><option value="">— default —</option>'+voiceOptions+'</select></div>'
1543
- + '<button class="rp-btn" id="pk-setup-btn" data-preset="'+esc(n.id)+'" style="width:fit-content;">Mint enrollment token & add robot</button>'
1763
+ + '<div class="rp-field"><label>LiveKit URL</label><input id="pk-setup-lk" placeholder="— uses the registered server —"></div>'
1764
+ + '<div class="rp-field"><label>Voice stack override</label><select id="pk-setup-voice"><option value="">— from character preset —</option>'+voiceOptions+'</select></div>'
1765
+ + '</div></details>'
1544
1766
  + '</div>'
1545
1767
  + '<div class="pk-sect" id="pk-setup-result" style="display:none;"><h4>Enrollment token (copy once)</h4><input id="pk-setup-token" class="rp-char" readonly style="margin-bottom:0.4rem;"><button class="rp-btn" id="pk-setup-copy">Copy</button></div>');
1546
1768
  var setupBtn=$('pk-setup-btn'); if(setupBtn) setupBtn.addEventListener('click', function(){ submitPadSetup(n); });
1547
1769
  var copyBtn=$('pk-setup-copy'); if(copyBtn) copyBtn.addEventListener('click', function(){ var t=$('pk-setup-token'); if(t){ t.select(); try{document.execCommand('copy');}catch(e){} showOut('token copied'); } });
1770
+ function refreshPadCliCommand(){
1771
+ var nm=$('pk-setup-name').value.trim()||defaultName; var cid=$('pk-setup-char').value||n.id;
1772
+ var el=$('pk-setup-clicmd'); if(el) el.value=buildAddRobotCommand(nm, cid);
1773
+ }
1774
+ var padNameEl=$('pk-setup-name'); if(padNameEl) padNameEl.addEventListener('input', refreshPadCliCommand);
1775
+ var padCharEl=$('pk-setup-char'); if(padCharEl) padCharEl.addEventListener('change', refreshPadCliCommand);
1776
+ var padCliCopy=$('pk-setup-clicmd-copy'); if(padCliCopy) padCliCopy.addEventListener('click', function(){ var i=$('pk-setup-clicmd'); i.select(); try{document.execCommand('copy');}catch(e){} showOut('command copied — run it on the robot itself'); });
1777
+ refreshPadCliCommand();
1548
1778
  return;
1549
1779
  }
1550
1780
  if(n.kind==='robot'){
@@ -103,6 +103,7 @@ export interface FleetStatus {
103
103
  control: {
104
104
  readonly: boolean;
105
105
  schedulerLinked: boolean;
106
+ schedulerUrl?: string;
106
107
  };
107
108
  ts: number;
108
109
  }
@@ -717,6 +717,7 @@ export class Federation {
717
717
  control: {
718
718
  readonly: Boolean(this.deps.options.schedulerReadOnly),
719
719
  schedulerLinked: Boolean(this.deps.options.schedulerUrl),
720
+ schedulerUrl: this.deps.options.schedulerUrl,
720
721
  },
721
722
  ts: Date.now(),
722
723
  };
@@ -4,5 +4,8 @@ import { type ExplicitContextOpts } from './profile.js';
4
4
  export interface AddRobotOptions extends ExplicitContextOpts {
5
5
  name: string;
6
6
  start?: boolean;
7
+ /** Force a specific park-map pad/character preset id instead of the
8
+ * fuzzy name-based match below — e.g. `--character jaguar`. */
9
+ character?: string;
7
10
  }
8
11
  export declare function roboparkAddRobot(config: Conf<InfinicodeConfig>, opts: AddRobotOptions): Promise<void>;
@@ -81,9 +81,12 @@ export async function roboparkAddRobot(config, opts) {
81
81
  console.log(` scheduler: ${chalk.cyan(ctx.schedulerUrl)}`);
82
82
  console.log(` name: ${chalk.cyan(opts.name)}${ctx.site ? chalk.dim(' @ ' + ctx.site) : ''}`);
83
83
  console.log();
84
- const characterId = await pickCharacterPreset(ctx.schedulerUrl, opts.name);
84
+ const characterId = opts.character?.trim() || await pickCharacterPreset(ctx.schedulerUrl, opts.name);
85
85
  if (characterId) {
86
- console.log(` character: ${chalk.cyan(characterId)}`);
86
+ console.log(` character: ${chalk.cyan(characterId)}${opts.character ? chalk.dim(' (forced via --character)') : chalk.dim(' (auto-matched)')}`);
87
+ }
88
+ else {
89
+ console.log(chalk.yellow(` ⚠ no character preset matched '${opts.name}' — pass --character <id> to bind this robot to a specific park-map pad`));
87
90
  }
88
91
  let minted;
89
92
  try {
@@ -129,7 +132,10 @@ export async function roboparkAddRobot(config, opts) {
129
132
  argv.push('--scheduler-port', schedulerPort);
130
133
  }
131
134
  argv.push('--enrollment-token', minted.enrollment_token, '--start', '--auto-start');
132
- console.log(chalk.dim(' run this ON THE ROBOT:'));
135
+ console.log(chalk.dim(` run this ON THE ROBOT${characterId ? ` (binds to pad: ${characterId})` : ''}:`));
133
136
  console.log(' ' + chalk.cyan(commandString(argv)));
137
+ if (!ctx.hubUrl) {
138
+ console.log(chalk.yellow(' ⚠ no hub URL resolved — the command above omits --hub-url; run `robopark hub-use` first or pass --hub-url explicitly if the robot needs it.'));
139
+ }
134
140
  console.log();
135
141
  }
@@ -226,6 +226,7 @@ program
226
226
  .option('--hub-url <url>', 'hub federation URL (defaults to saved hub profile / auto-discovery)')
227
227
  .option('--token <token>', 'shared mesh token (defaults to saved hub profile / auto-discovery)')
228
228
  .option('--scheduler-url <url>', 'scheduler base URL (defaults to saved hub profile / auto-discovery)')
229
+ .option('--character <id>', 'force a specific park-map pad/character preset id (default: fuzzy-matched from the robot name)')
229
230
  .option('--start', 'start the robot node on this machine now, instead of printing the one-liner')
230
231
  .action(async (name, opts) => {
231
232
  await roboparkAddRobot(config, { name, ...opts });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "infinicode",
3
- "version": "2.8.31",
3
+ "version": "2.8.33",
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",
@@ -424,6 +424,22 @@ async def init_db():
424
424
  pass
425
425
  await db.commit()
426
426
 
427
+ # ---- voice_stacks: options ROBOVOICE actually reads at runtime but
428
+ # this table didn't expose yet (llm auth + sampling + wake-word tuning) ----
429
+ for _table, _column, _coldef in (
430
+ ("voice_stacks", "llm_api_key", "TEXT"),
431
+ ("voice_stacks", "temperature", "REAL DEFAULT 0.7"),
432
+ ("voice_stacks", "num_ctx", "INTEGER DEFAULT 8192"),
433
+ ("voice_stacks", "wake_word_model", "TEXT"),
434
+ ("voice_stacks", "wake_word_threshold", "REAL DEFAULT 0.5"),
435
+ ("voice_stacks", "wake_word_timeout", "REAL DEFAULT 3.0"),
436
+ ):
437
+ try:
438
+ await db.execute(f"ALTER TABLE {_table} ADD COLUMN {_column} {_coldef}")
439
+ except Exception:
440
+ pass
441
+ await db.commit()
442
+
427
443
  # No demo robots are seeded. Actual robots enroll via the device
428
444
  # enrollment API or are added through the scheduler UI. This keeps the
429
445
  # fleet view honest — only real connected hardware appears.
@@ -2272,6 +2288,9 @@ class VoiceStack(BaseModel):
2272
2288
  stt_language: str = "en"
2273
2289
  llm_provider: str = "ollama"
2274
2290
  llm_model: str = "gemma4:e4b"
2291
+ llm_api_key: Optional[str] = None
2292
+ temperature: float = 0.7
2293
+ num_ctx: int = 8192
2275
2294
  tts_provider: str = "elevenlabs"
2276
2295
  tts_voice: str = "21m00Tcm4TlvDq8ikWAM"
2277
2296
  tts_language: str = "en"
@@ -2279,6 +2298,9 @@ class VoiceStack(BaseModel):
2279
2298
  min_endpointing_delay: float = 0.7
2280
2299
  max_turns: int = 20
2281
2300
  wake_word_enabled: bool = False
2301
+ wake_word_model: Optional[str] = None
2302
+ wake_word_threshold: float = 0.5
2303
+ wake_word_timeout: float = 3.0
2282
2304
  created_at: Optional[datetime] = None
2283
2305
  updated_at: Optional[datetime] = None
2284
2306
 
@@ -2290,6 +2312,9 @@ class VoiceStackCreate(BaseModel):
2290
2312
  stt_language: str = "en"
2291
2313
  llm_provider: str = "ollama"
2292
2314
  llm_model: str = "gemma4:e4b"
2315
+ llm_api_key: Optional[str] = None
2316
+ temperature: float = 0.7
2317
+ num_ctx: int = 8192
2293
2318
  tts_provider: str = "elevenlabs"
2294
2319
  tts_voice: str = "21m00Tcm4TlvDq8ikWAM"
2295
2320
  tts_language: str = "en"
@@ -2297,6 +2322,9 @@ class VoiceStackCreate(BaseModel):
2297
2322
  min_endpointing_delay: float = 0.7
2298
2323
  max_turns: int = 20
2299
2324
  wake_word_enabled: bool = False
2325
+ wake_word_model: Optional[str] = None
2326
+ wake_word_threshold: float = 0.5
2327
+ wake_word_timeout: float = 3.0
2300
2328
 
2301
2329
  # Effective configuration returned to the ROBOVOICE agent for a given room/robot.
2302
2330
  class RobotVoiceConfig(BaseModel):
@@ -2493,14 +2521,20 @@ async def create_voice_stack(payload: VoiceStackCreate):
2493
2521
  await db.execute(
2494
2522
  """INSERT INTO voice_stacks
2495
2523
  (id, name, stt_provider, stt_model, stt_language, llm_provider, llm_model,
2524
+ llm_api_key, temperature, num_ctx,
2496
2525
  tts_provider, tts_voice, tts_language, allow_interruptions,
2497
- min_endpointing_delay, max_turns, wake_word_enabled, created_at, updated_at)
2498
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
2526
+ min_endpointing_delay, max_turns, wake_word_enabled,
2527
+ wake_word_model, wake_word_threshold, wake_word_timeout,
2528
+ created_at, updated_at)
2529
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
2499
2530
  (
2500
2531
  stack_id, payload.name, payload.stt_provider, payload.stt_model, payload.stt_language,
2501
- payload.llm_provider, payload.llm_model, payload.tts_provider, payload.tts_voice,
2532
+ payload.llm_provider, payload.llm_model, payload.llm_api_key, payload.temperature, payload.num_ctx,
2533
+ payload.tts_provider, payload.tts_voice,
2502
2534
  payload.tts_language, int(payload.allow_interruptions), payload.min_endpointing_delay,
2503
- payload.max_turns, int(payload.wake_word_enabled), now, now,
2535
+ payload.max_turns, int(payload.wake_word_enabled),
2536
+ payload.wake_word_model, payload.wake_word_threshold, payload.wake_word_timeout,
2537
+ now, now,
2504
2538
  ),
2505
2539
  )
2506
2540
  await db.commit()
@@ -2532,15 +2566,20 @@ async def update_voice_stack(stack_id: str, payload: VoiceStackCreate):
2532
2566
  await db.execute(
2533
2567
  """UPDATE voice_stacks SET
2534
2568
  name = ?, stt_provider = ?, stt_model = ?, stt_language = ?, llm_provider = ?,
2535
- llm_model = ?, tts_provider = ?, tts_voice = ?, tts_language = ?,
2569
+ llm_model = ?, llm_api_key = ?, temperature = ?, num_ctx = ?,
2570
+ tts_provider = ?, tts_voice = ?, tts_language = ?,
2536
2571
  allow_interruptions = ?, min_endpointing_delay = ?, max_turns = ?,
2537
- wake_word_enabled = ?, updated_at = ?
2572
+ wake_word_enabled = ?, wake_word_model = ?, wake_word_threshold = ?, wake_word_timeout = ?,
2573
+ updated_at = ?
2538
2574
  WHERE id = ?""",
2539
2575
  (
2540
2576
  payload.name, payload.stt_provider, payload.stt_model, payload.stt_language,
2541
- payload.llm_provider, payload.llm_model, payload.tts_provider, payload.tts_voice,
2577
+ payload.llm_provider, payload.llm_model, payload.llm_api_key, payload.temperature, payload.num_ctx,
2578
+ payload.tts_provider, payload.tts_voice,
2542
2579
  payload.tts_language, int(payload.allow_interruptions), payload.min_endpointing_delay,
2543
- payload.max_turns, int(payload.wake_word_enabled), now, stack_id,
2580
+ payload.max_turns, int(payload.wake_word_enabled),
2581
+ payload.wake_word_model, payload.wake_word_threshold, payload.wake_word_timeout,
2582
+ now, stack_id,
2544
2583
  ),
2545
2584
  )
2546
2585
  await db.commit()