nothumanallowed 13.5.5 → 13.5.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nothumanallowed",
3
- "version": "13.5.5",
3
+ "version": "13.5.7",
4
4
  "description": "NotHumanAllowed — 38 AI agents, 80 tools, Studio (visual agentic workflows). Email, calendar, browser automation, screen capture, canvas, cron/heartbeat, Alexandria E2E messaging, GitHub, Notion, Slack, voice chat, free AI (Liara), 28 languages. Zero-dependency CLI.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -3529,6 +3529,7 @@ ${context ? `## OUTPUT FROM PREVIOUS AGENTS (use only what is RELEVANT to the wo
3529
3529
  } else if (useStructuredGen) {
3530
3530
  // ── Structured Generation ─────────────────────────────────
3531
3531
  // Phase 1: ask for ONLY the section outline (headings list), fast and cheap
3532
+ sendToken('[Pianificazione struttura report...] ');
3532
3533
  const structConfig = Object.assign({}, config, { thinking: 'off' });
3533
3534
  const outlineSys = `You are ${agent}, a specialist AI agent. Today is ${today}. Respond in ${language}.
3534
3535
  Your task: plan the sections of a complete structured report for this goal: ${task}
@@ -3591,6 +3592,7 @@ ${dataBlock}
3591
3592
  ${writtenSoFar ? `## REPORT WRITTEN SO FAR (for consistency):\n${writtenSoFar.slice(-3000)}` : ''}`;
3592
3593
  const secUser = `Write the complete body content for this section (do NOT repeat the heading):\n${heading}`;
3593
3594
  sendToken('\n\n' + heading + '\n');
3595
+ sendToken('[Sezione ' + (si + 1) + ' di ' + headingLines.length + '...] ');
3594
3596
  let secContent = '';
3595
3597
  try {
3596
3598
  secContent = await streamCall(secSys, secUser, { max_tokens: 2000, thinking: 'off' }, 60000, sendToken);
package/src/constants.mjs CHANGED
@@ -5,7 +5,7 @@ import { fileURLToPath } from 'url';
5
5
  const __filename = fileURLToPath(import.meta.url);
6
6
  const __dirname = path.dirname(__filename);
7
7
 
8
- export const VERSION = '13.5.5';
8
+ export const VERSION = '13.5.7';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -3405,7 +3405,20 @@ function studioLog(agent, icon, text, type, update) {
3405
3405
  var time = new Date().toLocaleTimeString('en', {hour:'2-digit', minute:'2-digit', second:'2-digit', hour12:false});
3406
3406
  if (update && studioState.log.length) {
3407
3407
  var last = studioState.log[studioState.log.length - 1];
3408
- if (last.agent === agent) { last.text = text; last.type = type || last.type; renderStudioLog(); return; }
3408
+ if (last.agent === agent) {
3409
+ last.text = text; last.type = type || last.type;
3410
+ // Streaming finished: remove data-rlen from the DOM entry so renderStudioLog re-renders it as markdown
3411
+ var logEl = document.getElementById('studioLog');
3412
+ if (logEl) {
3413
+ var entries = logEl.querySelectorAll('.studio-log-entry');
3414
+ var lastEntry = entries[entries.length - 1];
3415
+ if (lastEntry) {
3416
+ var tb2 = lastEntry.querySelector('.studio-log-entry__text');
3417
+ if (tb2) tb2.removeAttribute(String.fromCharCode(100,97,116,97,45,114,108,101,110));
3418
+ }
3419
+ }
3420
+ renderStudioLog(); return;
3421
+ }
3409
3422
  }
3410
3423
  studioState.log.push({agent: agent, icon: icon, text: text, time: time, type: type||'agent'});
3411
3424
  renderStudioLog();
@@ -3895,11 +3908,13 @@ function renderStudioNodes() {
3895
3908
  el.innerHTML =
3896
3909
  \x27<div class="prl-wrap" style="border-color:\x27+phaseColor2+\x2744;padding-bottom:8px">\x27+
3897
3910
  \x27<div class="prl-header"><span class="prl-phase-chip" style="--pc:\x27+phaseColor2+\x27">\x27+phaseLabel2+\x27</span></div>\x27+
3898
- \x27<div class="iso-scene" style="position:relative;width:\x27+SCENE_W+\x27px;height:\x27+SCENE_H+\x27px;overflow:hidden;border-radius:10px">\x27+
3911
+ \x27<div style="display:flex;justify-content:center;width:100%;overflow-x:auto">\x27+
3912
+ \x27<div class="iso-scene" style="position:relative;width:\x27+SCENE_W+\x27px;min-width:\x27+SCENE_W+\x27px;height:\x27+SCENE_H+\x27px;overflow:hidden;border-radius:10px">\x27+
3899
3913
  isoFloorSvg(ISO_COLS + 2, 4)+
3900
3914
  agentsHtml+
3901
3915
  orchHtml+
3902
3916
  \x27</div>\x27+
3917
+ \x27</div>\x27+
3903
3918
  \x27</div>\x27;
3904
3919
  }
3905
3920
 
@@ -3946,9 +3961,18 @@ function renderStudioLog() {
3946
3961
  if (!el) return;
3947
3962
  if (!studioState.log.length) { el.style.display = 'none'; return; }
3948
3963
  el.style.display = 'block';
3949
- el.innerHTML = studioState.log.map(function(e) {
3964
+ var existingEntries = el.querySelectorAll('.studio-log-entry');
3965
+ studioState.log.forEach(function(e, i) {
3966
+ var isStreaming = false;
3967
+ if (existingEntries[i]) {
3968
+ var tb = existingEntries[i].querySelector('.studio-log-entry__text');
3969
+ if (tb && tb.getAttribute(String.fromCharCode(100,97,116,97,45,114,108,101,110)) !== null) {
3970
+ isStreaming = true;
3971
+ }
3972
+ }
3973
+ if (isStreaming) return; // leave streaming entry DOM untouched
3950
3974
  var cls = 'studio-log-entry' + (e.type === 'system' ? ' studio-log-entry--system' : e.type === 'error' ? ' studio-log-entry--error' : '');
3951
- return '<div class="' + cls + '">' +
3975
+ var html = '<div class="' + cls + '">' +
3952
3976
  '<div class="studio-log-entry__header">' +
3953
3977
  '<span class="studio-log-entry__icon">' + e.icon + '</span>' +
3954
3978
  '<span class="studio-log-entry__agent">' + esc(e.agent) + '</span>' +
@@ -3956,7 +3980,20 @@ function renderStudioLog() {
3956
3980
  '</div>' +
3957
3981
  '<div class="studio-log-entry__text md-body">' + renderMd(e.text) + '</div>' +
3958
3982
  '</div>';
3959
- }).join('');
3983
+ if (existingEntries[i]) {
3984
+ existingEntries[i].outerHTML = html;
3985
+ } else {
3986
+ var div = document.createElement('div');
3987
+ div.innerHTML = html;
3988
+ el.appendChild(div.firstChild);
3989
+ }
3990
+ // refresh reference after replacement
3991
+ existingEntries = el.querySelectorAll('.studio-log-entry');
3992
+ });
3993
+ // remove extra entries (shouldn't happen but be safe)
3994
+ while (el.querySelectorAll('.studio-log-entry').length > studioState.log.length) {
3995
+ el.removeChild(el.lastChild);
3996
+ }
3960
3997
  el.scrollTop = el.scrollHeight;
3961
3998
  }
3962
3999
 
@@ -4405,7 +4442,7 @@ async function runStudio() {
4405
4442
  nudge = document.createElement(\x27div\x27);
4406
4443
  nudge.id = \x27studioParliamentNudge\x27;
4407
4444
  nudge.style.cssText = \x27margin:8px 0;padding:8px 12px;background:#1a1a2e;border:1px solid #6366f1;border-radius:8px;font-size:11px;color:#a5b4fc;display:flex;align-items:center;gap:10px\x27;
4408
- nudge.innerHTML = \x27&#x2656; <span><strong>Suggerimento:</strong> questo workflow ha \x27 + specialistAgents.length + \x27 agenti specialisti — attiva <strong>Parlamento</strong> per un confronto critico tra le loro analisi.</span><button onclick="document.getElementById(\\\x27studioParliamentMode\\\x27).checked=true;studioState.parliamentMode=true;this.parentNode.remove()" style="margin-left:auto;background:#6366f1;border:none;border-radius:6px;color:#fff;padding:4px 10px;cursor:pointer;font-size:10px;white-space:nowrap">Attiva &#x2656;</button>\x27;
4445
+ nudge.innerHTML = \x27&#x1f4bc; <span><strong>Suggerimento:</strong> questo workflow ha \x27 + specialistAgents.length + \x27 agenti specialisti — attiva il <strong>Consiglio</strong> per un confronto critico tra le loro analisi.</span><button onclick="document.getElementById(\\\x27studioParliamentMode\\\x27).checked=true;studioState.parliamentMode=true;this.parentNode.remove()" style="margin-left:auto;background:#6366f1;border:none;border-radius:6px;color:#fff;padding:4px 10px;cursor:pointer;font-size:10px;white-space:nowrap">Attiva &#x1f4bc;</button>\x27;
4409
4446
  var tokenBar = document.getElementById(\x27studioTokenBar\x27);
4410
4447
  if (tokenBar && tokenBar.parentNode) tokenBar.parentNode.insertBefore(nudge, tokenBar.parentNode.firstChild);
4411
4448
  }
@@ -4476,10 +4513,10 @@ async function runStudio() {
4476
4513
  proposals.push({agent: \x27Context\x27, label: \x27Contesto workflow\x27, output: context});
4477
4514
  }
4478
4515
  if (proposals.length >= 2) {
4479
- studioLog(\x27Parlamento\x27, \x27&#x2656;\x27, \x27Avvio deliberazioneRound 2 cross-reading tra agenti...\x27, \x27system\x27);
4480
- // Add Parliament node to pipeline visual
4516
+ studioLog(\x27Consiglio\x27, \x27&#x1f4bc;\x27, \x27Avvio Consigliogli agenti si riuniscono per confrontare e raffinare i risultati...\x27, \x27system\x27);
4517
+ // Add Consiglio node to pipeline visual
4481
4518
  var parlNodeIdx = studioState.nodes.length;
4482
- studioState.nodes.push({icon:\x27&#x2656;\x27, agent:\x27Parliament\x27, label:\x27Parlamento\x27, status:\x27running\x27, output:\x27\x27, _rendered:false});
4519
+ studioState.nodes.push({icon:\x27&#x1f4bc;\x27, agent:\x27Consiglio\x27, label:\x27Consiglio\x27, status:\x27running\x27, output:\x27\x27, _rendered:false});
4483
4520
  renderStudioNodes();
4484
4521
 
4485
4522
  // ── Parliament visual block ──────────────────────────────────────
@@ -4497,10 +4534,10 @@ async function runStudio() {
4497
4534
 
4498
4535
  var phaseColor = {r1:\x27#6366f1\x27,r2:\x27#22d3ee\x27,r3:\x27#f59e0b\x27,done:\x27#22c55e\x27}[phase]||\x27#6366f1\x27;
4499
4536
  var phaseLabel = {
4500
- r1:\x27Round 1 \u2014 Ogni agente analizza\x27,
4501
- r2:\x27Round 2 \u2014 Cross-reading e raffinamento\x27,
4502
- r3:\x27Round 3 \u2014 HERALD media le divergenze\x27,
4503
- done:\x27Deliberazione completata\x27
4537
+ r1:\x27Consiglio \u2014 Analisi individuale\x27,
4538
+ r2:\x27Consiglio \u2014 Confronto e raffinamento\x27,
4539
+ r3:\x27Consiglio \u2014 Sintesi finale HERALD\x27,
4540
+ done:\x27Consiglio concluso \u2014 consenso raggiunto\x27
4504
4541
  }[phase]||\x27\x27;
4505
4542
  var n = proposals.length;
4506
4543
  var doneCount = Object.keys(parlDoneAgents).length;
@@ -4611,7 +4648,7 @@ async function runStudio() {
4611
4648
  var orchHtml = \x27<div class="br-orch" id="brOrch">\x27+
4612
4649
  \x27<div class="br-orch-speech" id="brOrchSpeech"></div>\x27+
4613
4650
  orchSvg+
4614
- \x27<div class="br-orch-label">Orchestratore</div>\x27+
4651
+ \x27<div class="br-orch-label">NHA Studio</div>\x27+
4615
4652
  \x27</div>\x27;
4616
4653
 
4617
4654
  // Phase indicator + progress
@@ -4650,17 +4687,17 @@ async function runStudio() {
4650
4687
  if (convEl && convergence != null) {
4651
4688
  convEl.style.display = \x27block\x27;
4652
4689
  convEl.innerHTML = \x27<div class="br-conv-bar-outer"><div class="br-conv-bar-inner" style="width:\x27+Math.min(convergence,100)+\x27%"></div></div>\x27+
4653
- \x27<div class="br-conv-text"><strong>\u2714 Convergenza \x27+convergence+\x27%</strong> \u2014 HERALD ha sintetizzato il consenso finale.</div>\x27;
4690
+ \x27<div class="br-conv-text"><strong>\u2714 Convergenza: \x27+convergence+\x27%</strong> \u2014 Il Consiglio ha raggiunto il consenso. HERALD ha sintetizzato il risultato finale.</div>\x27;
4654
4691
  }
4655
4692
 
4656
4693
  // Orchestrator speech bubble
4657
4694
  var orchSpeech = document.getElementById(\x27brOrchSpeech\x27);
4658
4695
  if (orchSpeech) {
4659
4696
  var orchSpeeches = {
4660
- r1: [\x27Analizzate!\x27,\x27Al lavoro!\x27,\x27Forza!\x27,\x27Pensate!\x27],
4661
- r2: [\x27Confrontate!\x27,\x27Leggete tutto!\x27,\x27Raffinare!\x27,\x27Scambiate!\x27],
4662
- r3: [\x27Mediazione!\x27,\x27Convergete!\x27,\x27Trovate accordo!\x27],
4663
- done: [\x27Ottimo!\x27,\x27Consenso!\x27,\x27Bene!\x27]
4697
+ r1: [\x27Analisi in corso...\x27,\x27Ogni team al lavoro\x27,\x27Raccolta dati\x27,\x27Prima bozza...\x27],
4698
+ r2: [\x27Confronto in corso\x27,\x27Cross-review...\x27,\x27Raffinamento\x27,\x27Scambio idee\x27],
4699
+ r3: [\x27Sintesi finale\x27,\x27Convergenza...\x27,\x27Accordo in vista\x27],
4700
+ done: [\x27Consiglio concluso\x27,\x27Consenso raggiunto\x27,\x27Report pronto\x27]
4664
4701
  };
4665
4702
  var spArr = orchSpeeches[phase] || orchSpeeches.r1;
4666
4703
  if (phase === \x27done\x27) {
@@ -4681,6 +4718,42 @@ async function runStudio() {
4681
4718
  orchEl.style.setProperty(\x27--oc\x27, phaseColor);
4682
4719
  }
4683
4720
 
4721
+ // Draw attention line from orchestrator to active agent seat
4722
+ var orchEl2 = document.getElementById(\x27brOrch\x27);
4723
+ var brRoom2 = orchEl2 ? orchEl2.closest(\x27.br-room\x27) : null;
4724
+ if (brRoom2) {
4725
+ var existLine = brRoom2.querySelector(\x27.br-attention-line\x27);
4726
+ if (existLine) existLine.parentNode.removeChild(existLine);
4727
+ if (activeLabel && phase !== \x27done\x27) {
4728
+ var aLblSafe = activeLabel.replace(/[^a-zA-Z0-9_-]/g,\x27_\x27);
4729
+ var activeSeatEl = document.getElementById(\x27brseat_\x27+aLblSafe);
4730
+ if (activeSeatEl && orchEl2) {
4731
+ var roomRect = brRoom2.getBoundingClientRect();
4732
+ var orchRect = orchEl2.getBoundingClientRect();
4733
+ var seatRect = activeSeatEl.getBoundingClientRect();
4734
+ var x1 = orchRect.left + orchRect.width/2 - roomRect.left;
4735
+ var y1 = orchRect.top + orchRect.height/2 - roomRect.top;
4736
+ var x2 = seatRect.left + seatRect.width/2 - roomRect.left;
4737
+ var y2 = seatRect.top + seatRect.height/2 - roomRect.top;
4738
+ var lineSvg = document.createElementNS(\x27http://www.w3.org/2000/svg\x27, \x27svg\x27);
4739
+ lineSvg.setAttribute(\x27class\x27, \x27br-attention-line\x27);
4740
+ lineSvg.style.cssText = \x27position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:10;overflow:visible\x27;
4741
+ var lineEl = document.createElementNS(\x27http://www.w3.org/2000/svg\x27, \x27line\x27);
4742
+ lineEl.setAttribute(\x27x1\x27, String(Math.round(x1)));
4743
+ lineEl.setAttribute(\x27y1\x27, String(Math.round(y1)));
4744
+ lineEl.setAttribute(\x27x2\x27, String(Math.round(x2)));
4745
+ lineEl.setAttribute(\x27y2\x27, String(Math.round(y2)));
4746
+ lineEl.setAttribute(\x27stroke\x27, \x27#6366f1\x27);
4747
+ lineEl.setAttribute(\x27stroke-width\x27, \x272\x27);
4748
+ lineEl.setAttribute(\x27stroke-dasharray\x27, \x276 4\x27);
4749
+ lineEl.setAttribute(\x27opacity\x27, \x27.6\x27);
4750
+ lineEl.style.animation = \x27brDashFlow 1s linear infinite\x27;
4751
+ lineSvg.appendChild(lineEl);
4752
+ brRoom2.appendChild(lineSvg);
4753
+ }
4754
+ }
4755
+ }
4756
+
4684
4757
  // Update each agent seat state
4685
4758
  proposals.forEach(function(prop) {
4686
4759
  var lbl = prop.label || prop.agent;
@@ -5163,7 +5236,7 @@ async function runStudio() {
5163
5236
  if (r2StartM) {
5164
5237
  var r2Label = r2StartM[1];
5165
5238
  parlActiveAgent = r2Label;
5166
- studioLog(r2Label, \x27&#x2656;\x27, \x27\x27, \x27agent\x27, false);
5239
+ studioLog(r2Label, \x27&#x1f4bc;\x27, \x27\x27, \x27agent\x27, false);
5167
5240
  var delEnts2 = document.querySelectorAll(\x27.studio-log-entry\x27);
5168
5241
  var delL2 = delEnts2[delEnts2.length - 1];
5169
5242
  if (delL2) {
@@ -5197,10 +5270,25 @@ async function runStudio() {
5197
5270
  var delEntries = document.querySelectorAll(\x27.studio-log-entry\x27);
5198
5271
  var delLast = delEntries[delEntries.length - 1];
5199
5272
  if (delLast) { var delTb = delLast.querySelector(\x27.studio-log-entry__text\x27); if (delTb) delTb.textContent = dev.token.replace(new RegExp(\x27[\\r\\n]+\x27,\x27g\x27),\x27 \x27); }
5273
+ // Mirror live token to the active agent boardroom bubble
5274
+ if (parlActiveAgent) {
5275
+ var brSafe2 = parlActiveAgent.replace(new RegExp(\x27[^a-zA-Z0-9_-]\x27,\x27g\x27),\x27_\x27);
5276
+ var brLiveBubble = document.getElementById(\x27brbubble_\x27+brSafe2);
5277
+ if (brLiveBubble) {
5278
+ var rawTok = dev.token.replace(new RegExp(\x27[\\r\\n]+\x27,\x27g\x27),\x27 \x27);
5279
+ var safeTok = rawTok.replace(/&/g,\x27&amp;\x27).replace(/</g,\x27&lt;\x27).replace(/>/g,\x27&gt;\x27);
5280
+ var truncTok = rawTok.length > 60 ? rawTok.slice(-60) : rawTok;
5281
+ var safeTrunc = truncTok.replace(/&/g,\x27&amp;\x27).replace(/</g,\x27&lt;\x27).replace(/>/g,\x27&gt;\x27);
5282
+ brLiveBubble.style.display = \x27\x27;
5283
+ brLiveBubble.innerHTML = safeTrunc + \x27<span style="display:inline-block;width:2px;height:8px;background:var(--green);margin-left:1px;vertical-align:text-bottom;animation:streamBlink .7s step-end infinite">&#8203;</span>\x27;
5284
+ brLiveBubble.style.borderColor = \x27#6366f1\x27;
5285
+ brLiveBubble.style.color = \x27#a5b4fc\x27;
5286
+ }
5287
+ }
5200
5288
  }
5201
5289
  } else if (dev.deliberation_r2) {
5202
5290
  var r2d = dev.deliberation_r2;
5203
- studioLog(r2d.label || r2d.agent, \x27&#x2656;\x27, \x27[R2] \x27 + (r2d.output || \x27\x27), \x27agent\x27, true);
5291
+ studioLog(r2d.label || r2d.agent, \x27&#x1f4bc;\x27, \x27[Consiglio R2] \x27 + (r2d.output || \x27\x27), \x27agent\x27, true);
5204
5292
  var ni2 = studioState.nodes.findIndex(function(x){return x.agent===r2d.agent;});
5205
5293
  if (ni2 >= 0) { studioState.nodes[ni2].output = r2d.output; studioState.nodes[ni2].status = \x27done\x27; }
5206
5294
  studioAddTokens(0, Math.ceil((r2d.output||'').length / 4));
@@ -5217,12 +5305,12 @@ async function runStudio() {
5217
5305
  context = dev.deliberation_r3.output || context;
5218
5306
  } else if (dev.deliberation_done) {
5219
5307
  var r2Conv = Math.round((dev.r2_convergence || 0) * 100);
5220
- studioLog(\x27Parlamento\x27, \x27&#x2656;\x27, \x27Deliberazione completa — convergenza R2: \x27 + r2Conv + \x27%\x27, \x27system\x27);
5308
+ studioLog(\x27Consiglio\x27, \x27&#x1f4bc;\x27, \x27Consiglio concluso — convergenza R2: \x27 + r2Conv + \x27%\x27, \x27system\x27);
5221
5309
  if (dev.mediation) { context = dev.mediation; }
5222
5310
  renderParlBlock(\x27done\x27, null, r2Conv);
5223
5311
  if (studioState.nodes[parlNodeIdx]) {
5224
5312
  studioState.nodes[parlNodeIdx].status = \x27done\x27;
5225
- studioState.nodes[parlNodeIdx].label = \x27Parlamento (\x27 + r2Conv + \x27%)\x27;
5313
+ studioState.nodes[parlNodeIdx].label = \x27Consiglio (\x27 + r2Conv + \x27%)\x27;
5226
5314
  renderStudioNodes();
5227
5315
  }
5228
5316
  delDone = true;
@@ -5235,7 +5323,7 @@ async function runStudio() {
5235
5323
  }
5236
5324
  } catch(e3) {
5237
5325
  if (e3.name !== \x27AbortError\x27) {
5238
- studioLog(\x27Parlamento\x27, \x27&#x2656;\x27, \x27Deliberazione non disponibile: \x27 + (e3.message || String(e3)), \x27error\x27);
5326
+ studioLog(\x27Consiglio\x27, \x27&#x1f4bc;\x27, \x27Consiglio non disponibile: \x27 + (e3.message || String(e3)), \x27error\x27);
5239
5327
  // Do NOT hide the parliament block if it already has content — user is watching it
5240
5328
  }
5241
5329
  }
@@ -5306,7 +5394,9 @@ function saveStudioSession(task, nodes, log, result) {
5306
5394
  var sessions = JSON.parse(localStorage.getItem('nha_studio_sessions') || '[]');
5307
5395
  // Save parliament block HTML if present (static snapshot — animations not needed on restore)
5308
5396
  var parlEl = document.getElementById('studioParliamentBlock');
5309
- var parlHtml = (parlEl && parlEl.style.display !== 'none') ? parlEl.innerHTML : null;
5397
+ var parlRaw = (parlEl && parlEl.style.display !== 'none') ? parlEl.innerHTML : null;
5398
+ // Limit parlHtml to 30KB to avoid localStorage quota issues
5399
+ var parlHtml = parlRaw ? (parlRaw.length > 30000 ? parlRaw.slice(0, 30000) : parlRaw) : null;
5310
5400
  sessions.unshift({
5311
5401
  id: Date.now(),
5312
5402
  task: task,
@@ -5317,8 +5407,14 @@ function saveStudioSession(task, nodes, log, result) {
5317
5407
  log: log.map(function(e){return {agent:e.agent,icon:e.icon,text:e.text,type:e.type,time:e.time};}),
5318
5408
  ts: new Date().toLocaleString()
5319
5409
  });
5320
- sessions = sessions.slice(0, 20); // keep last 20
5321
- localStorage.setItem('nha_studio_sessions', JSON.stringify(sessions));
5410
+ sessions = sessions.slice(0, 10); // keep last 10 (save space)
5411
+ try {
5412
+ localStorage.setItem('nha_studio_sessions', JSON.stringify(sessions));
5413
+ } catch(qe) {
5414
+ // Quota exceeded: save without canvas/parlHtml
5415
+ sessions[0].canvas = null; sessions[0].parlHtml = null;
5416
+ try { localStorage.setItem('nha_studio_sessions', JSON.stringify(sessions)); } catch(e2) {}
5417
+ }
5322
5418
  } catch(e) {}
5323
5419
  }
5324
5420
 
@@ -5368,6 +5464,10 @@ function restoreStudioSession(idx) {
5368
5464
  if (s.parlHtml) {
5369
5465
  parlEl.innerHTML = s.parlHtml;
5370
5466
  parlEl.style.display = 'block';
5467
+ // Scroll into view after DOM settles
5468
+ setTimeout(function() {
5469
+ if (parlEl) parlEl.scrollIntoView({behavior: 'smooth', block: 'start'});
5470
+ }, 150);
5371
5471
  } else {
5372
5472
  parlEl.style.display = 'none';
5373
5473
  }
@@ -5541,6 +5641,9 @@ function runStudioStep(idx, node, task, context, stepDef, signal) {
5541
5641
  var stEl = document.getElementById(\x27streamText_\x27 + idx);
5542
5642
  if (stEl) { stEl.appendChild(document.createTextNode(newChars)); }
5543
5643
  tb.setAttribute(String.fromCharCode(100,97,116,97,45,114,108,101,110), String(output.length));
5644
+ // Keep studioState.log in sync so renderStudioLog() final call has current text
5645
+ var logLen = studioState.log.length;
5646
+ if (logLen > 0) { studioState.log[logLen - 1].text = output; }
5544
5647
  }
5545
5648
  // Update iso thought bubble of the active agent
5546
5649
  var isoB = document.getElementById(\x27isobubble_\x27+idx);
@@ -5741,7 +5844,7 @@ function renderStudio(el) {
5741
5844
  '</div>' +
5742
5845
  '<label style="display:flex;align-items:center;gap:8px;margin-top:8px;cursor:pointer;user-select:none">' +
5743
5846
  '<input type="checkbox" id="studioParliamentMode" style="width:15px;height:15px;accent-color:var(--green3)" ' + (studioState.parliamentMode ? \x27checked\x27 : \x27\x27) + ' onchange="studioState.parliamentMode=this.checked">' +
5744
- '<span style="font-size:12px;color:var(--dim)">&#x2656; <strong style="color:var(--green)">Parlamento</strong> — Round 2 cross-reading tra agenti (2x token)</span>' +
5847
+ '<span style="font-size:12px;color:var(--dim)">&#x1f4bc; <strong style="color:var(--green)">Consiglio</strong> — gli agenti si confrontano dopo aver lavorato in autonomia (2x token)</span>' +
5745
5848
  '</label>' +
5746
5849
  '</div>' +
5747
5850
 
@@ -6386,6 +6489,7 @@ input:focus,textarea:focus{border-color:var(--green3)}
6386
6489
  .br-conv-bar-outer{height:4px;background:#1a2e1a;border-radius:4px;overflow:hidden;margin-bottom:6px}
6387
6490
  .br-conv-bar-inner{height:100%;background:linear-gradient(90deg,#22c55e,#4ade80);border-radius:4px;transition:width .8s ease}
6388
6491
  .br-conv-text{font-size:9px;color:#86efac;line-height:1.55}
6492
+ @keyframes brDashFlow{0%{stroke-dashoffset:20}100%{stroke-dashoffset:0}}
6389
6493
  /* Keep old prl-* classes for workflow (not touched) */
6390
6494
  .prl-wrap{background:#0b0918;border:1.5px solid #6366f1;border-radius:14px;padding:14px 16px 12px;margin-bottom:16px;animation:stNodeIn .35s ease forwards;overflow:hidden}
6391
6495
  @keyframes parlPulse{0%,100%{border-color:#6366f1;box-shadow:none}50%{border-color:#818cf8;box-shadow:0 0 20px rgba(99,102,241,.3)}}