nothumanallowed 13.5.24 → 13.5.26

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.24",
3
+ "version": "13.5.26",
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": {
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.24';
8
+ export const VERSION = '13.5.26';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -3325,7 +3325,7 @@ var studioAbortController = null;
3325
3325
  var parlActiveAgent = null; // active agent label during parliament streaming
3326
3326
  var parlDoneAgents = {}; // set of completed agent labels during parliament
3327
3327
  var _parlPersistHtml = null; // persists parliament block HTML across tab navigations
3328
- var _PARL_STAMP = '<!--nha-parl-v13.5.24-->';
3328
+ var _PARL_STAMP = '<!--nha-parl-v13.5.26-->';
3329
3329
 
3330
3330
  function stopStudio() {
3331
3331
  if (!studioState.running) return;
@@ -3932,7 +3932,11 @@ function renderStudioNodes() {
3932
3932
  var bigPlant = String.fromCodePoint(0x1FAB4);
3933
3933
  var plantEmoji = String.fromCodePoint(0x1F331);
3934
3934
 
3935
- function buildStation2(label, toolEmoji, isOrch, isActive, isDone, isErr, emojiIdx) {
3935
+ // Find the index of the currently active node (for orchestrator positioning)
3936
+ var activeNodeIdx = -1;
3937
+ nodes.forEach(function(n, i) { if (n.status === \x27running\x27) activeNodeIdx = i; });
3938
+
3939
+ function buildStation2(label, toolEmoji, isOrch, isActive, isDone, isErr, emojiIdx, nodeIdx) {
3936
3940
  var accentColor = isOrch ? \x27#818cf8\x27 : (isActive ? \x27#6366f1\x27 : (isDone ? \x27#374151\x27 : (isErr ? \x27#ef4444\x27 : \x27#9ca3af\x27)));
3937
3941
  var nameBg = isDone ? \x27rgba(0,0,0,.1)\x27 : (isActive ? \x27#ede9fe\x27 : (isOrch ? \x27#e0e7ff\x27 : \x27rgba(255,255,255,.85)\x27));
3938
3942
  var nameColor = isDone ? \x27#111827\x27 : (isActive ? \x27#4f46e5\x27 : (isOrch ? \x27#4338ca\x27 : (isErr ? \x27#dc2626\x27 : \x27#374151\x27)));
@@ -3941,20 +3945,24 @@ function renderStudioNodes() {
3941
3945
  : (isDone ? \x27<span style="color:#111827;font-size:13px">&#10003;</span>\x27
3942
3946
  : (isActive ? \x27<span class="iso-monitor-blink"></span>\x27
3943
3947
  : \x27<span style="font-size:8px;opacity:.35;color:#818cf8">&#9632;</span>\x27));
3948
+ // Bubble: for agents, leave text empty so JS streaming fills it live; show "✓ fatto" when done
3944
3949
  var bubbleText = isOrch
3945
- ? (hasActive ? (\x27Step \x27+doneCount+\x27/\x27+totalCount) : (doneCount===totalCount&&totalCount>0 ? \x27\u2714 Fatto!\x27 : \x27In attesa\x27))
3946
- : (isActive ? \x27\u2022\u2022\u2022 lavora\x27 : (isDone ? \x27\u2714 fatto\x27 : (isErr ? \x27\u2715 errore\x27 : \x27\x27)));
3950
+ ? (hasActive ? (\x27Assegno step \x27+(doneCount+1)+\x27/\x27+totalCount) : (doneCount===totalCount&&totalCount>0 ? \x27\u2714 Fatto!\x27 : \x27In attesa\x27))
3951
+ : (isDone ? \x27\u2714 fatto\x27 : (isErr ? \x27\u2715 errore\x27 : (isActive ? \x27\x27 : \x27\x27)));
3947
3952
  var bubbleBg = isOrch ? \x27rgba(99,102,241,.15)\x27 : (isActive ? \x27rgba(99,102,241,.12)\x27 : (isDone ? \x27rgba(0,0,0,.08)\x27 : \x27rgba(239,68,68,.12)\x27));
3948
3953
  var glowBox = isActive ? (\x270 0 0 3px \x27+accentColor+\x2744,0 8px 24px \x27+accentColor+\x2733\x27) : (isDone ? (\x270 0 0 2px rgba(0,0,0,.25)\x27) : \x27none\x27);
3949
- // orchWalkClass goes on the CHAR only, not the whole station card
3950
- var orchWalkClass = (isOrch && hasActive) ? \x27 iso-orch-walking\x27 : (isOrch && doneCount===totalCount&&totalCount>0 ? \x27 iso-orch-done\x27 : \x27\x27);
3951
- var charHtml = \x27<div class="iso-char-mover\x27+orchWalkClass+\x27">\x27+isoCharSvg({emojiIdx: isOrch ? 99 : emojiIdx, isActive: isActive, isDone: isDone, scale: 1.1, accentColor: accentColor})+\x27</div>\x27;
3954
+ // Orchestrator char: no CSS walk animation JS moves it via inline transform toward the active agent column
3955
+ var charIdAttr = isOrch ? \x27 id="wfOrchChar"\x27 : \x27\x27;
3956
+ var charHtml = \x27<div class="iso-char-mover"\x27+charIdAttr+\x27>\x27+isoCharSvg({emojiIdx: isOrch ? 99 : emojiIdx, isActive: isActive, isDone: isDone, scale: 1.1, accentColor: accentColor})+\x27</div>\x27;
3952
3957
  var clickAttr = isOrch ? \x27\x27 : (\x27data-agent-label="\x27+esc(label)+\x27" onclick="studioScrollToAgent(this.getAttribute(String.fromCharCode(100,97,116,97,45,97,103,101,110,116,45,108,97,98,101,108)))"\x27);
3953
3958
  // Flying doc emoji when active
3954
3959
  var flyDoc = isActive ? \x27<div class="iso-fly-doc" style="color:\x27+accentColor+\x27">\x27+String.fromCodePoint(0x1F4C4)+\x27</div>\x27 : \x27\x27;
3955
- return \x27<div class="iso-station" \x27+clickAttr+\x27 style="box-shadow:\x27+glowBox+\x27;border-color:\x27+accentColor+\x27;transition:box-shadow .4s">\x27+
3960
+ // Bubble id: orchestrator gets wfOrchBubble, agents get isobubble_IDX
3961
+ var bubbleId = isOrch ? \x27wfOrchBubble\x27 : (\x27isobubble_\x27+nodeIdx);
3962
+ var bubbleVisible = (bubbleText || isOrch || isActive) ? \x27visible\x27 : \x27hidden\x27;
3963
+ return \x27<div class="iso-station" \x27+clickAttr+\x27 data-station-idx="\x27+(isOrch?-1:nodeIdx)+\x27" style="box-shadow:\x27+glowBox+\x27;border-color:\x27+accentColor+\x27;transition:box-shadow .4s">\x27+
3956
3964
  flyDoc+
3957
- \x27<div class="iso-bubble\x27+(isActive?\x27 iso-bubble--active\x27:\x27\x27)+\x27" style="border-color:\x27+accentColor+\x27;color:\x27+accentColor+\x27;background:\x27+bubbleBg+\x27;visibility:\x27+(bubbleText||isOrch?\x27visible\x27:\x27hidden\x27)+\x27">\x27+esc(bubbleText)+\x27</div>\x27+
3965
+ \x27<div class="iso-bubble\x27+(isActive?\x27 iso-bubble--active\x27:\x27\x27)+\x27" id="\x27+bubbleId+\x27" style="border-color:\x27+accentColor+\x27;color:\x27+accentColor+\x27;background:\x27+bubbleBg+\x27;visibility:\x27+bubbleVisible+\x27">\x27+esc(bubbleText)+\x27</div>\x27+
3958
3966
  \x27<div class="iso-tool-badge">\x27+toolEmoji+\x27</div>\x27+
3959
3967
  charHtml+
3960
3968
  \x27<div class="iso-desk" style="width:85%;border-top-color:\x27+accentColor+\x2733"></div>\x27+
@@ -3965,7 +3973,7 @@ function renderStudioNodes() {
3965
3973
 
3966
3974
  var stationsHtml = \x27\x27;
3967
3975
  var orchDone2 = !hasActive && doneCount===totalCount && totalCount>0;
3968
- stationsHtml += buildStation2(\x27Orchestratore\x27, String.fromCodePoint(0x1F4CB), true, hasActive, orchDone2, false, 99);
3976
+ stationsHtml += buildStation2(\x27Orchestratore\x27, String.fromCodePoint(0x1F4CB), true, hasActive, orchDone2, false, 99, -1);
3969
3977
  nodes.forEach(function(n, idx) {
3970
3978
  stationsHtml += buildStation2(
3971
3979
  n.label || n.agent,
@@ -3974,6 +3982,7 @@ function renderStudioNodes() {
3974
3982
  n.status===\x27running\x27,
3975
3983
  n.status===\x27done\x27,
3976
3984
  n.status===\x27error\x27,
3985
+ idx,
3977
3986
  idx
3978
3987
  );
3979
3988
  });
@@ -4037,6 +4046,42 @@ function renderStudioNodes() {
4037
4046
  \x27</div>\x27+
4038
4047
  \x27</div>\x27+
4039
4048
  \x27</div>\x27;
4049
+
4050
+ // Move orchestrator character toward the active agent column.
4051
+ // Grid: col 0 = orchestrator, col 1..N = agents. Each column is 1fr.
4052
+ // We measure the pixel offset between the orchestrator station and the active agent station.
4053
+ if (activeNodeIdx >= 0) {
4054
+ requestAnimationFrame(function() {
4055
+ var orchChar = document.getElementById(\x27wfOrchChar\x27);
4056
+ var orchStation = el.querySelector(\x27[data-station-idx="-1"]\x27);
4057
+ var activeStation = el.querySelector(\x27[data-station-idx="\x27+activeNodeIdx+\x27"]\x27);
4058
+ if (orchChar && orchStation && activeStation) {
4059
+ var orchRect = orchStation.getBoundingClientRect();
4060
+ var activeRect = activeStation.getBoundingClientRect();
4061
+ // Shift the char 60% of the way toward the active column center
4062
+ var delta = (activeRect.left + activeRect.width/2) - (orchRect.left + orchRect.width/2);
4063
+ var shift = Math.round(delta * 0.6);
4064
+ orchChar.style.transition = \x27transform 0.8s cubic-bezier(.4,0,.2,1)\x27;
4065
+ orchChar.style.transform = \x27translateX(\x27+shift+\x27px)\x27;
4066
+ // Also update orch bubble to show what it is assigning
4067
+ var orchBubble = document.getElementById(\x27wfOrchBubble\x27);
4068
+ if (orchBubble) {
4069
+ var activeNode = studioState.nodes[activeNodeIdx];
4070
+ orchBubble.style.visibility = \x27visible\x27;
4071
+ orchBubble.textContent = \x27Assegno a \x27+(activeNode ? (activeNode.label || activeNode.agent) : \x27agente\x27);
4072
+ }
4073
+ }
4074
+ });
4075
+ } else {
4076
+ // No active agent: reset orchestrator to home position
4077
+ requestAnimationFrame(function() {
4078
+ var orchChar = document.getElementById(\x27wfOrchChar\x27);
4079
+ if (orchChar) {
4080
+ orchChar.style.transition = \x27transform 0.8s cubic-bezier(.4,0,.2,1)\x27;
4081
+ orchChar.style.transform = \x27translateX(0)\x27;
4082
+ }
4083
+ });
4084
+ }
4040
4085
  }
4041
4086
 
4042
4087
  function studioScrollToAgent(agentLabel) {
@@ -5026,28 +5071,28 @@ async function runStudio() {
5026
5071
 
5027
5072
  var orchEl2 = document.getElementById(\x27brOrch\x27);
5028
5073
  if (phase === \x27done\x27) {
5029
- // All done: show web of connections between all agents
5074
+ // All done: show web of solid connections between all agents
5030
5075
  proposals.forEach(function(pa, ia) {
5031
5076
  proposals.forEach(function(pb2, ib2) {
5032
5077
  if (ib2 <= ia) return;
5033
5078
  var ea = document.getElementById(\x27brseat_\x27+pa.label.replace(/[^a-zA-Z0-9_-]/g,\x27_\x27));
5034
5079
  var eb = document.getElementById(\x27brseat_\x27+pb2.label.replace(/[^a-zA-Z0-9_-]/g,\x27_\x27));
5035
- addCommLine(ea, eb, \x27#22c55e\x27, \x273 3\x27, 1.5, 0.3, \x27\x27);
5080
+ addCommLine(ea, eb, \x27#16a34a\x27, \x273 3\x27, 2, 0.85, \x27\x27);
5036
5081
  });
5037
5082
  });
5038
5083
  } else if (activeLabel) {
5039
5084
  var aLblSafe2 = activeLabel.replace(/[^a-zA-Z0-9_-]/g,\x27_\x27);
5040
5085
  var activeSeatEl2 = document.getElementById(\x27brseat_\x27+aLblSafe2);
5041
- // Orch → active agent (always)
5042
- addCommLine(orchEl2, activeSeatEl2, phaseColor, \x275 4\x27, 2.5, 0.75, \x27brDashFlow 1.2s linear infinite\x27);
5043
- // R2/R3: active reads all done agents → show reading lines
5086
+ // Orch → active agent (always) — thick, bright, animated
5087
+ addCommLine(orchEl2, activeSeatEl2, \x27#818cf8\x27, \x275 4\x27, 3, 0.95, \x27brDashFlow 1.2s linear infinite\x27);
5088
+ // R2/R3: done agents → active agent (cross-reading) — amber, solid
5044
5089
  if (phase === \x27r2\x27 || phase === \x27r3\x27) {
5045
5090
  proposals.forEach(function(pp2) {
5046
5091
  var doneL = pp2.label || pp2.agent;
5047
5092
  if (doneL === activeLabel) return;
5048
5093
  if (!parlDoneAgents[doneL]) return;
5049
5094
  var doneSeat = document.getElementById(\x27brseat_\x27+doneL.replace(/[^a-zA-Z0-9_-]/g,\x27_\x27));
5050
- addCommLine(doneSeat, activeSeatEl2, \x27#f59e0b\x27, \x274 3\x27, 1.5, 0.5, \x27brDashFlow 1.8s linear infinite\x27);
5095
+ addCommLine(doneSeat, activeSeatEl2, \x27#d97706\x27, \x274 3\x27, 2, 0.9, \x27brDashFlow 1.8s linear infinite\x27);
5051
5096
  });
5052
5097
  }
5053
5098
  }
@@ -5251,18 +5296,31 @@ async function runStudio() {
5251
5296
  function saveStudioSession(task, nodes, log, result) {
5252
5297
  try {
5253
5298
  var sessions = JSON.parse(localStorage.getItem('nha_studio_sessions') || '[]');
5254
- // Save parliament block HTML if present (static snapshot animations not needed on restore)
5299
+ // Save parliament as compact node list NOT raw HTML (too large, gets truncated)
5255
5300
  var parlEl = document.getElementById('studioParliamentBlock');
5256
- var parlRaw = (parlEl && parlEl.style.display !== 'none') ? parlEl.innerHTML : null;
5257
- // Limit parlHtml to 30KB to avoid localStorage quota issues
5258
- var parlHtml = parlRaw ? (parlRaw.length > 30000 ? parlRaw.slice(0, 30000) : parlRaw) : null;
5301
+ var hasParl = parlEl && parlEl.style.display !== 'none' && parlEl.innerHTML.length > 200;
5302
+ // Derive parliament nodes from workflow nodes (exclude Canvas/tool-only agents)
5303
+ var parlNodes = hasParl ? nodes
5304
+ .filter(function(n){ return n.agent !== 'CanvasAgent' && n.agent !== 'GitHubAgent' && n.agent !== 'EmailAgent' && n.agent !== 'CalendarAgent'; })
5305
+ .map(function(n){ return {label:n.label,agent:n.agent,icon:n.icon}; })
5306
+ : null;
5307
+ // Extract r2Conv from the Consiglio node label e.g. "Consiglio (72%)"
5308
+ var r2Conv = 0;
5309
+ if (hasParl) {
5310
+ var parlNode = nodes.find(function(n){ return n.agent === 'Consiglio'; });
5311
+ if (parlNode && parlNode.label) {
5312
+ var cm = parlNode.label.match(/\((\d+)%\)/);
5313
+ if (cm) r2Conv = parseInt(cm[1], 10);
5314
+ }
5315
+ }
5259
5316
  sessions.unshift({
5260
5317
  id: Date.now(),
5261
5318
  task: task,
5262
5319
  nodes: nodes.map(function(n){return {label:n.label,icon:n.icon,agent:n.agent,output:n.output||''};}),
5263
5320
  result: result,
5264
5321
  canvas: studioState.canvas || null,
5265
- parlHtml: parlHtml,
5322
+ parlNodes: parlNodes,
5323
+ parlR2Conv: r2Conv,
5266
5324
  log: log.map(function(e){return {agent:e.agent,icon:e.icon,text:e.text,type:e.type,time:e.time};}),
5267
5325
  ts: new Date().toLocaleString()
5268
5326
  });
@@ -5305,6 +5363,79 @@ function renderStudioSessionsBar() {
5305
5363
  }).join('') + '</div>';
5306
5364
  }
5307
5365
 
5366
+ // Renders the parliament boardroom in "done" state from a compact node list.
5367
+ // Used by restoreStudioSession — independent of the runStudio closure.
5368
+ function renderParlBlockStatic(parlNodes, r2Conv) {
5369
+ var pb = document.getElementById('studioParliamentBlock');
5370
+ if (!pb || !parlNodes || parlNodes.length < 1) return;
5371
+ pb.style.display = 'block';
5372
+
5373
+ var proposals2 = parlNodes;
5374
+ var crownEm = String.fromCodePoint(0x1F451);
5375
+ var orchEmoji2 = String.fromCodePoint(0x1F9D1,0x200D,0x1F4BC);
5376
+
5377
+ function buildSeat2(prop) {
5378
+ var lbl = prop.label || prop.agent;
5379
+ var safeLbl = lbl.replace(/[^a-zA-Z0-9_-]/g,'_');
5380
+ var emojiIdx = Math.abs(lbl.charCodeAt(0)+(lbl.charCodeAt(lbl.length-1)||0)) % AGENT_EMOJIS.length;
5381
+ var agentEmoji = AGENT_EMOJIS[emojiIdx];
5382
+ return '<div class="br-seat" id="brseat_'+safeLbl+'" data-lbl="'+esc(lbl)+'">' +
5383
+ '<div class="br-char" style="font-size:40px;filter:none">'+agentEmoji+'</div>' +
5384
+ '<div class="br-seat-name">'+esc(lbl)+'</div>' +
5385
+ '</div>';
5386
+ }
5387
+
5388
+ var topSeats2 = []; var botSeats2 = [];
5389
+ proposals2.forEach(function(p,i){ if(i%2===0) topSeats2.push(p); else botSeats2.push(p); });
5390
+
5391
+ var tblSvg2 = '<svg viewBox="0 0 1000 200" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg" style="position:absolute;top:50%;left:0;width:100%;height:140px;transform:translateY(-50%);z-index:1;pointer-events:none">' +
5392
+ '<defs><linearGradient id="tblGrad2" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#6b4423"/><stop offset="0.4" stop-color="#4a2e12"/><stop offset="1" stop-color="#2e1a08"/></linearGradient></defs>' +
5393
+ '<rect x="6" y="10" width="988" height="178" rx="22" fill="rgba(0,0,0,.28)"/>' +
5394
+ '<rect x="0" y="2" width="1000" height="178" rx="20" fill="url(#tblGrad2)"/>' +
5395
+ '<text x="500" y="118" text-anchor="middle" font-family="system-ui" font-size="58" font-weight="900" fill="rgba(160,140,255,.09)" letter-spacing="10">NHA</text>' +
5396
+ '</svg>';
5397
+
5398
+ var bgSvg2 = '<svg viewBox="0 0 1000 600" preserveAspectRatio="xMidYMid slice" xmlns="http://www.w3.org/2000/svg" style="position:absolute;top:0;left:0;width:100%;height:100%;z-index:0;pointer-events:none">' +
5399
+ '<defs>' +
5400
+ '<linearGradient id="brWall2" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#f5f0e8"/><stop offset="1" stop-color="#e8e0d0"/></linearGradient>' +
5401
+ '<linearGradient id="winGrad2" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#b8dff8"/><stop offset="1" stop-color="#d8f0ff"/></linearGradient>' +
5402
+ '<linearGradient id="doorGrad2" x1="0" y1="0" x2="1" y2="0"><stop offset="0" stop-color="#a0724a"/><stop offset="0.5" stop-color="#c8905c"/><stop offset="1" stop-color="#a0724a"/></linearGradient>' +
5403
+ '</defs>' +
5404
+ '<rect x="0" y="0" width="1000" height="215" fill="url(#brWall2)"/>' +
5405
+ '<rect x="0" y="0" width="1000" height="8" fill="#d8cfc0"/>' +
5406
+ '<rect x="0" y="206" width="1000" height="9" fill="#c8b898" rx="1"/>' +
5407
+ (function(){ var s=''; var pC=['#c8a06a','#bf9860','#d4aa72','#ba9458','#caa86e']; for(var fy=215;fy<600+32;fy+=32){var ro=(Math.floor((fy-215)/32)%2)*60; for(var fx=-120+ro;fx<1000+120;fx+=120){var pc=pC[Math.abs(Math.round(fx/120+fy/32*1.3))%pC.length]; s+='<rect x="'+Math.round(fx)+'" y="'+fy+'" width="118" height="30" fill="'+pc+'" rx="2"/>';}} return s; }()) +
5408
+ '<rect x="28" y="18" width="126" height="96" rx="3" fill="#4a6080" stroke="#2a3a50" stroke-width="4"/>' +
5409
+ '<rect x="34" y="24" width="114" height="84" rx="2" fill="url(#winGrad2)"/>' +
5410
+ '<rect x="846" y="18" width="126" height="96" rx="3" fill="#4a6080" stroke="#2a3a50" stroke-width="4"/>' +
5411
+ '<rect x="852" y="24" width="114" height="84" rx="2" fill="url(#winGrad2)"/>' +
5412
+ '<rect x="455" y="0" width="90" height="215" fill="url(#doorGrad2)" stroke="#7a5030" stroke-width="3"/>' +
5413
+ '<line x1="500" y1="0" x2="500" y2="28" stroke="#aaa" stroke-width="4"/>' +
5414
+ '<ellipse cx="500" cy="36" rx="52" ry="14" fill="#f0d830" stroke="#c8a820" stroke-width="3"/>' +
5415
+ '<circle cx="470" cy="47" r="9" fill="#fffce0"/><circle cx="500" cy="50" r="9" fill="#fffce0"/><circle cx="530" cy="47" r="9" fill="#fffce0"/>' +
5416
+ '</svg>';
5417
+
5418
+ var convPct = r2Conv || 0;
5419
+ pb.innerHTML =
5420
+ '<div class="br-wrap">' +
5421
+ '<div class="br-header"><span class="br-phase-chip" style="--pc:#22c55e">Consiglio concluso — convergenza ' + convPct + '%</span></div>' +
5422
+ '<div class="br-room">' +
5423
+ bgSvg2 +
5424
+ '<div style="position:absolute;bottom:8px;left:10px;font-size:44px;z-index:5">' + String.fromCodePoint(0x1FAB4) + '</div>' +
5425
+ '<div style="position:absolute;bottom:8px;right:10px;font-size:44px;z-index:5">' + String.fromCodePoint(0x1FAB4) + '</div>' +
5426
+ '<div style="position:relative;z-index:10;display:flex;flex-direction:column;justify-content:center;min-height:480px;padding:20px 16px;gap:0;box-sizing:border-box">' +
5427
+ '<div class="br-seats-row">' + topSeats2.map(buildSeat2).join('') + '</div>' +
5428
+ '<div style="position:relative;display:flex;align-items:center;width:100%;min-height:160px">' +
5429
+ '<div class="br-orch" id="brOrch"><div class="br-orch-inner"><span class="br-orch-crown">' + crownEm + '</span><span class="br-orch-emoji">' + orchEmoji2 + '</span></div><div class="br-orch-label">Orchestratore</div></div>' +
5430
+ '<div style="position:relative;flex:1;min-height:140px">' + tblSvg2 + '</div>' +
5431
+ '</div>' +
5432
+ '<div class="br-seats-row">' + botSeats2.map(buildSeat2).join('') + '</div>' +
5433
+ '</div>' +
5434
+ '</div>' +
5435
+ '<div class="br-convergence" style="display:block">Consenso raggiunto — convergenza R2: <strong>' + convPct + '%</strong></div>' +
5436
+ '</div>';
5437
+ }
5438
+
5308
5439
  function restoreStudioSession(idx) {
5309
5440
  var sessions = loadStudioSessions();
5310
5441
  var s = sessions[idx]; if (!s) return;
@@ -5317,13 +5448,13 @@ function restoreStudioSession(idx) {
5317
5448
  var ta = document.getElementById('studioTaskInput');
5318
5449
  if (ta) ta.value = s.task;
5319
5450
  renderStudioNodes(); renderStudioLog(); renderStudioResult();
5320
- // Restore parliament block if present
5451
+ // Restore parliament block rebuild from compact parlNodes (not raw HTML)
5321
5452
  var parlEl = document.getElementById('studioParliamentBlock');
5322
5453
  if (parlEl) {
5323
- if (s.parlHtml) {
5324
- parlEl.innerHTML = s.parlHtml;
5325
- parlEl.style.display = 'block';
5326
- // Scroll into view after DOM settles
5454
+ var parlNodes = s.parlNodes || null;
5455
+ // Legacy: old sessions might have parlHtml instead — ignore it (it was truncated)
5456
+ if (parlNodes && parlNodes.length >= 2) {
5457
+ renderParlBlockStatic(parlNodes, s.parlR2Conv || 0);
5327
5458
  setTimeout(function() {
5328
5459
  if (parlEl) parlEl.scrollIntoView({behavior: 'smooth', block: 'start'});
5329
5460
  }, 150);
@@ -5510,18 +5641,37 @@ function runStudioStep(idx, node, task, context, stepDef, signal) {
5510
5641
  // Update iso thought bubble of the active agent
5511
5642
  var isoB = document.getElementById(\x27isobubble_\x27+idx);
5512
5643
  if (isoB) {
5513
- var wfRaw = output.length > 48 ? output.slice(-48) : output;
5514
- var wfSafe = wfRaw.replace(/&/g,\x27&amp;\x27).replace(/</g,\x27&lt;\x27).replace(/>/g,\x27&gt;\x27);
5644
+ // Show last ~6 complete words from the streaming output
5645
+ var wfClean = output.replace(new RegExp(\x27[\\r\\n]+\x27, \x27g\x27), \x27 \x27).trim();
5646
+ var wfWords = wfClean.split(\x27 \x27).filter(function(w){return w.length>0;});
5647
+ var wfSnippet = wfWords.slice(-6).join(\x27 \x27);
5648
+ if (wfSnippet.length > 52) wfSnippet = wfSnippet.slice(-52);
5649
+ var wfSafe = wfSnippet.replace(/&/g,\x27&amp;\x27).replace(/</g,\x27&lt;\x27).replace(/>/g,\x27&gt;\x27);
5515
5650
  isoB.className = \x27iso-bubble iso-bubble--active\x27;
5651
+ isoB.style.visibility = \x27visible\x27;
5516
5652
  isoB.innerHTML = wfSafe + \x27<span style="display:inline-block;width:2px;height:8px;background:#6366f1;margin-left:1px;vertical-align:text-bottom;animation:streamBlink .7s step-end infinite">&#8203;</span>\x27;
5517
5653
  }
5518
- // Update orchestrator bubble with progress
5654
+ // Update orchestrator bubble: show which agent it assigned and move the char
5519
5655
  var orchB = document.getElementById(\x27wfOrchBubble\x27);
5520
5656
  if (orchB) {
5521
- var doneN = studioState.nodes.filter(function(x){return x.status===\x27done\x27;}).length;
5522
- var totN = studioState.nodes.length;
5523
- var orchPhrases2 = [\x27Elaboro step \x27+doneN+\x27/\x27+totN, \x27Verifico output...\x27, \x27Coordinamento...\x27, \x27Aspetto risposta...\x27];
5524
- orchB.textContent = orchPhrases2[Math.floor(output.length/120) % orchPhrases2.length];
5657
+ var activeNode2 = studioState.nodes[idx];
5658
+ orchB.style.visibility = \x27visible\x27;
5659
+ orchB.textContent = \x27Assegno a \x27+(activeNode2 ? (activeNode2.label || activeNode2.agent) : \x27agente\x27);
5660
+ }
5661
+ // Move orchestrator char toward active agent column (live, every token)
5662
+ var orchCharEl = document.getElementById(\x27wfOrchChar\x27);
5663
+ var orchStEl = el.querySelector(\x27[data-station-idx="-1"]\x27);
5664
+ var actStEl = el.querySelector(\x27[data-station-idx="\x27+idx+\x27"]\x27);
5665
+ if (orchCharEl && orchStEl && actStEl) {
5666
+ var orchR = orchStEl.getBoundingClientRect();
5667
+ var actR = actStEl.getBoundingClientRect();
5668
+ var dlt = (actR.left + actR.width/2) - (orchR.left + orchR.width/2);
5669
+ var shft = Math.round(dlt * 0.6);
5670
+ if (orchCharEl.getAttribute(\x27data-last-shift\x27) !== String(shft)) {
5671
+ orchCharEl.style.transition = \x27transform 0.9s cubic-bezier(.4,0,.2,1)\x27;
5672
+ orchCharEl.style.transform = \x27translateX(\x27+shft+\x27px)\x27;
5673
+ orchCharEl.setAttribute(\x27data-last-shift\x27, String(shft));
5674
+ }
5525
5675
  }
5526
5676
  // Update boardroom seat bubble if parliament is active
5527
5677
  if (parlActiveAgent) {
@@ -6316,7 +6466,7 @@ input:focus,textarea:focus{border-color:var(--green3)}
6316
6466
  .wf-sbraita-bubble::after{content:"";position:absolute;top:100%;left:50%;transform:translateX(-50%);border:5px solid transparent;border-top-color:#ef4444}
6317
6467
  @keyframes sbraitaPop{0%{transform:translateX(-50%) scale(1) rotate(-2deg)}100%{transform:translateX(-50%) scale(1.06) rotate(2deg)}}
6318
6468
  /* ── Parliament Boardroom — bright office, same palette as workflow scene ── */
6319
- .br-wrap{background:var(--bg2);border:1.5px solid var(--border);border-radius:14px;padding:12px 14px;margin-bottom:16px;animation:stNodeIn .35s ease forwards;overflow:hidden}
6469
+ .br-wrap{background:var(--bg2);border:1.5px solid var(--border);border-radius:14px;padding:12px 14px;margin-bottom:16px;animation:stNodeIn .35s ease forwards;overflow:hidden;width:100%;box-sizing:border-box}
6320
6470
  .br-header{display:flex;align-items:center;gap:10px;margin-bottom:10px;flex-wrap:wrap}
6321
6471
  .br-phase-chip{font-size:10px;font-weight:800;font-family:var(--mono);letter-spacing:.3px;color:var(--pc,#6366f1);background:rgba(99,102,241,.1);border:1px solid var(--pc,rgba(99,102,241,.35));border-radius:20px;padding:3px 12px;display:inline-block;transition:color .4s,border-color .4s}
6322
6472
  .br-progress-wrap{flex:1;height:3px;background:var(--border);border-radius:4px;overflow:hidden;min-width:60px}