nothumanallowed 13.5.4 → 13.5.6

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.4",
3
+ "version": "13.5.6",
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.4';
8
+ export const VERSION = '13.5.6';
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();
@@ -3607,6 +3620,205 @@ function officeRoomDecor() {
3607
3620
  \x27<div class="prl-office-plant2">\x27+plant2Svg+\x27</div>\x27;
3608
3621
  }
3609
3622
 
3623
+ // ── Isometric JRPG-style scene renderer ────────────────────────────────────
3624
+ // Projects grid positions onto an isometric plane.
3625
+ // iso(col, row) → {x, y} pixel coordinates in the scene container.
3626
+ // Characters are positioned with position:absolute, scale by row for depth.
3627
+ var ISO_TILE_W = 72;
3628
+ var ISO_TILE_H = 36;
3629
+ var ISO_ORIGIN_X = 260; // center-left of scene
3630
+ var ISO_ORIGIN_Y = 40; // top of scene
3631
+ function isoProject(col, row) {
3632
+ return {
3633
+ x: ISO_ORIGIN_X + (col - row) * (ISO_TILE_W / 2),
3634
+ y: ISO_ORIGIN_Y + (col + row) * (ISO_TILE_H / 2)
3635
+ };
3636
+ }
3637
+ // Draw isometric floor tiles as SVG
3638
+ function isoFloorSvg(cols, rows) {
3639
+ var w = 600; var h = 240;
3640
+ var out = \x27<svg viewBox="0 0 \x27+w+\x27 \x27+h+\x27" width="\x27+w+\x27" height="\x27+h+\x27" xmlns="http://www.w3.org/2000/svg" style="position:absolute;top:0;left:0;pointer-events:none;z-index:0">\x27;
3641
+ // Back wall — two side walls
3642
+ out += \x27<polygon points="0,80 300,20 300,100 0,160" fill="#1a1535" opacity=".7"/>\x27;
3643
+ out += \x27<polygon points="300,20 600,80 600,160 300,100" fill="#141030" opacity=".7"/>\x27;
3644
+ // Wall top edge
3645
+ out += \x27<line x1="0" y1="80" x2="300" y2="20" stroke="#3a3060" stroke-width="1.5"/>\x27;
3646
+ out += \x27<line x1="300" y1="20" x2="600" y2="80" stroke="#3a3060" stroke-width="1.5"/>\x27;
3647
+ // Wall window left
3648
+ out += \x27<polygon points="60,82 130,62 130,102 60,122" fill="#1a2840" stroke="#2a4060" stroke-width="1"/>\x27;
3649
+ out += \x27<polygon points="65,85 125,66 125,98 65,117" fill="#7ecfff" opacity=".15" stroke="#4a90c0" stroke-width=".5"/>\x27;
3650
+ // Wall window right
3651
+ out += \x27<polygon points="470,82 540,62 540,102 470,122" fill="#1a2840" stroke="#2a4060" stroke-width="1"/>\x27;
3652
+ out += \x27<polygon points="475,85 535,66 535,98 475,117" fill="#7ecfff" opacity=".15" stroke="#4a90c0" stroke-width=".5"/>\x27;
3653
+ // Sunlight shafts
3654
+ out += \x27<polygon points="80,102 120,90 125,160 85,175" fill="rgba(200,230,255,.04)"/>\x27;
3655
+ out += \x27<polygon points="490,90 530,102 535,175 495,160" fill="rgba(200,230,255,.04)"/>\x27;
3656
+ // Ceiling lamp
3657
+ out += \x27<ellipse cx="300" cy="22" rx="20" ry="6" fill="#2a2050" stroke="#4a3080" stroke-width="1"/>\x27;
3658
+ out += \x27<ellipse cx="300" cy="24" rx="18" ry="10" fill="rgba(255,220,100,.12)"/>\x27;
3659
+ out += \x27<line x1="300" y1="0" x2="300" y2="22" stroke="#3a3060" stroke-width="1.5"/>\x27;
3660
+ // Floor tiles — iso grid
3661
+ for (var r = 0; r < rows; r++) {
3662
+ for (var c = 0; c < cols; c++) {
3663
+ var p = isoProject(c, r);
3664
+ var tx = p.x; var ty = p.y + 80;
3665
+ // Diamond tile
3666
+ var pts = (tx)+\x27,\x27+(ty) + \x27 \x27+(tx+ISO_TILE_W/2)+\x27,\x27+(ty+ISO_TILE_H/2) + \x27 \x27+tx+\x27,\x27+(ty+ISO_TILE_H) + \x27 \x27+(tx-ISO_TILE_W/2)+\x27,\x27+(ty+ISO_TILE_H/2);
3667
+ var tileEven = (r+c) % 2 === 0;
3668
+ out += \x27<polygon points="\x27+pts+\x27" fill="\x27+(tileEven?\x27#1e1840\x27:\x27#1a1535\x27)+\x27" stroke="#2a2050" stroke-width=".8"/>\x27;
3669
+ // Tile highlight (top-left edge)
3670
+ out += \x27<line x1="\x27+(tx-ISO_TILE_W/2)+\x27" y1="\x27+(ty+ISO_TILE_H/2)+\x27" x2="\x27+tx+\x27" y2="\x27+ty+\x27" stroke="rgba(255,255,255,.04)" stroke-width=".6"/>\x27;
3671
+ }
3672
+ }
3673
+ out += \x27</svg>\x27;
3674
+ return out;
3675
+ }
3676
+ // Small isometric desk object at iso position
3677
+ function isoDeskSvg(x, y, accentColor) {
3678
+ var ac = accentColor || \x27#3a3060\x27;
3679
+ return \x27<svg viewBox="0 0 64 40" width="64" height="40" xmlns="http://www.w3.org/2000/svg" style="position:absolute;left:\x27+(x-32)+\x27px;top:\x27+(y+10)+\x27px;z-index:\x27+(Math.round(y))+\x27;pointer-events:none">\x27+
3680
+ // Desk top face
3681
+ \x27<polygon points="32,4 56,16 32,28 8,16" fill="#1e1840" stroke="\x27+ac+\x27" stroke-width="1.2"/>\x27+
3682
+ // Desk right face
3683
+ \x27<polygon points="56,16 56,32 32,44 32,28" fill="#120e28" stroke="\x27+ac+\x2788" stroke-width=".8"/>\x27+
3684
+ // Desk left face
3685
+ \x27<polygon points="8,16 32,28 32,44 8,32" fill="#161230" stroke="\x27+ac+\x2766" stroke-width=".8"/>\x27+
3686
+ // Monitor on desk
3687
+ \x27<polygon points="24,8 38,14 38,22 24,16" fill="\x27+(ac===\x27#22c55e\x27?\x27#0a2010\x27:\x27#0d0d1e\x27)+\x27" stroke="\x27+ac+\x27" stroke-width="1"/>\x27+
3688
+ \x27<polygon points="24,10 36,15 36,20 24,15" fill="\x27+(ac===\x27#22c55e\x27?\x27#0a2810\x27:\x27#111128\x27)+\x27"/>\x27+
3689
+ // Screen lines
3690
+ \x27<line x1="26" y1="12" x2="34" y2="15" stroke="\x27+ac+\x27" stroke-width=".8" opacity=".8"/>\x27+
3691
+ \x27<line x1="26" y1="14" x2="33" y2="17" stroke="\x27+ac+\x27" stroke-width=".8" opacity=".5"/>\x27+
3692
+ \x27<line x1="26" y1="16" x2="32" y2="18" stroke="\x27+ac+\x27" stroke-width=".8" opacity=".3"/>\x27+
3693
+ \x27</svg>\x27;
3694
+ }
3695
+ // Isometric JRPG sprite character — top-down 3/4 perspective, Pokemon-style
3696
+ function isoCharSvg(opts) {
3697
+ var skin = opts.skin || \x27#fbbf24\x27;
3698
+ var shirt = opts.shirt || \x27#4f46e5\x27;
3699
+ var hair = opts.hair || \x27#1a0e08\x27;
3700
+ var isActive = opts.isActive;
3701
+ var isDone = opts.isDone;
3702
+ var scale = opts.scale || 1;
3703
+ var glow = isActive ? (\x27filter:drop-shadow(0 0 6px \x27+(opts.accentColor||shirt)+\x27)\x27) : (isDone ? \x27filter:drop-shadow(0 0 4px #22c55e44)\x27 : \x27\x27);
3704
+ var armCls = isActive ? \x27class="prl-arm"\x27 : \x27\x27;
3705
+ var headCls = isActive ? \x27class="prl-head"\x27 : \x27\x27;
3706
+ var w = Math.round(48 * scale); var h = Math.round(64 * scale);
3707
+ return \x27<svg viewBox="0 0 48 64" width="\x27+w+\x27" height="\x27+h+\x27" xmlns="http://www.w3.org/2000/svg" style="\x27+glow+\x27;display:block;overflow:visible">\x27+
3708
+ // Shadow beneath character
3709
+ \x27<ellipse cx="24" cy="62" rx="\x27+(11*scale)+\x27" ry="\x27+(4*scale)+\x27" fill="rgba(0,0,0,.3)"/>\x27+
3710
+ // Legs
3711
+ \x27<path d="M18 42 C17 50 16 56 15 60 C14 62 17 63 19 62 C21 61 21 56 21 50 L21 42 Z" fill="\x27+shirt+\x27cc" class="prl-master-leg-l"/>\x27+
3712
+ \x27<path d="M24 42 C25 50 26 56 27 60 C28 62 25 63 23 62 C21 61 21 56 21 50 L21 42 Z" fill="\x27+shirt+\x27cc" class="prl-master-leg-r"/>\x27+
3713
+ // Shoes
3714
+ \x27<ellipse cx="16" cy="62" rx="4" ry="2" fill="#1a0e04" transform="rotate(-10 16 62)"/>\x27+
3715
+ \x27<ellipse cx="26" cy="62" rx="4" ry="2" fill="#1a0e04" transform="rotate(10 26 62)"/>\x27+
3716
+ // Body
3717
+ \x27<rect x="13" y="26" width="20" height="18" rx="3" fill="\x27+shirt+\x27"/>\x27+
3718
+ \x27<rect x="13" y="26" width="10" height="18" rx="3" fill="rgba(0,0,0,.1)"/>\x27+
3719
+ // Collar
3720
+ \x27<path d="M21 26 L18 30 L21 28 L24 30 Z" fill="\x27+skin+\x27"/>\x27+
3721
+ // Arms
3722
+ \x27<g \x27+armCls+\x27>\x27+
3723
+ \x27<path d="M13 28 C8 30 6 35 6 39 C6 42 9 43 11 42 C13 41 13 37 14 33 Z" fill="\x27+shirt+\x27"/>\x27+
3724
+ \x27<ellipse cx="7" cy="41" rx="4" ry="3" fill="\x27+skin+\x27" transform="rotate(-15 7 41)"/>\x27+
3725
+ \x27<path d="M33 28 C38 30 40 35 40 39 C40 42 37 43 35 42 C33 41 33 37 32 33 Z" fill="\x27+shirt+\x27"/>\x27+
3726
+ \x27<ellipse cx="39" cy="41" rx="4" ry="3" fill="\x27+skin+\x27" transform="rotate(15 39 41)"/>\x27+
3727
+ \x27</g>\x27+
3728
+ // Head
3729
+ \x27<g \x27+headCls+\x27>\x27+
3730
+ \x27<ellipse cx="21" cy="13" rx="11" ry="12" fill="\x27+skin+\x27"/>\x27+
3731
+ \x27<path d="M10 13 C10 6 14 1 21 0 C28 1 32 6 32 13 C31 7 28 4 21 3 C14 4 11 7 10 13" fill="\x27+hair+\x27"/>\x27+
3732
+ \x27<circle cx="16" cy="14" r="1.5" fill="#0a0a14"/>\x27+
3733
+ \x27<circle cx="26" cy="14" r="1.5" fill="#0a0a14"/>\x27+
3734
+ \x27<circle cx="16.7" cy="13" r=".7" fill="rgba(255,255,255,.9)"/>\x27+
3735
+ (isDone ?
3736
+ \x27<path d="M15 20 Q21 24 27 20" stroke="#8b4513" stroke-width="1.5" fill="none" stroke-linecap="round"/>\x27+
3737
+ \x27<path d="M16 20.5 Q21 23 26 20.5 Q21 22 16 20.5" fill="#fff" opacity=".6"/>\x27
3738
+ : isActive ?
3739
+ \x27<path d="M15 19 Q21 21 27 19" stroke="#8b4513" stroke-width="1.4" fill="none" stroke-linecap="round"/>\x27
3740
+ :
3741
+ \x27<path d="M16 20 Q21 22 26 20" stroke="#8b4513" stroke-width="1.2" fill="none" stroke-linecap="round"/>\x27
3742
+ )+
3743
+ \x27</g>\x27+
3744
+ \x27</svg>\x27;
3745
+ }
3746
+ // Orchestrator — bigger, crowned, JRPG boss sprite
3747
+ function isoOrchSvg(hasActive, doneRatio) {
3748
+ var mc = \x27#818cf8\x27;
3749
+ var armCls = hasActive ? \x27class="prl-master-arm-l"\x27 : \x27\x27;
3750
+ var armRCls = hasActive ? \x27class="prl-master-arm-r"\x27 : \x27\x27;
3751
+ return \x27<svg viewBox="0 0 60 80" width="58" height="76" xmlns="http://www.w3.org/2000/svg" style="display:block;overflow:visible;filter:drop-shadow(0 0 10px \x27+mc+\x27aa)">\x27+
3752
+ // Shadow
3753
+ \x27<ellipse cx="30" cy="78" rx="16" ry="5" fill="rgba(0,0,0,.4)"/>\x27+
3754
+ // Legs
3755
+ \x27<path d="M24 52 C23 60 22 67 21 72 C20 75 22 76 24 76 C26 76 27 74 27 71 C28 65 28 58 28 52 Z" fill="#1e1c4a" class="prl-master-leg-l"/>\x27+
3756
+ \x27<path d="M30 52 C31 60 32 67 33 72 C34 75 32 76 30 76 C28 76 27 74 27 71 C26 65 26 58 26 52 Z" fill="#1e1c4a" class="prl-master-leg-r"/>\x27+
3757
+ // Shoes
3758
+ \x27<ellipse cx="20" cy="74" rx="5" ry="2.5" fill="#0a0a14" transform="rotate(-8 20 74)"/>\x27+
3759
+ \x27<ellipse cx="34" cy="74" rx="5" ry="2.5" fill="#0a0a14" transform="rotate(8 34 74)"/>\x27+
3760
+ // Suit body
3761
+ \x27<path d="M14 32 C13 30 16 27 27 25 C38 27 41 30 40 32 L41 52 L13 52 Z" fill="#252464"/>\x27+
3762
+ \x27<path d="M14 32 C13 30 16 27 27 25 L27 52 L13 52 Z" fill="#1e1d52"/>\x27+
3763
+ \x27<path d="M27 25 C38 27 41 30 40 32 L41 52 L27 52 Z" fill="#2a2870"/>\x27+
3764
+ // Lapels
3765
+ \x27<path d="M27 25 L21 33 L24 36 L27 29 Z" fill="#1a1940"/>\x27+
3766
+ \x27<path d="M27 25 L33 33 L30 36 L27 29 Z" fill="#1a1940"/>\x27+
3767
+ \x27<path d="M27 29 L24 36 L27 34 L30 36 Z" fill="#f0f0fa"/>\x27+
3768
+ // Tie
3769
+ \x27<path d="M27 33 L26 44 L27 48 L28 44 Z" fill="\x27+mc+\x27"/>\x27+
3770
+ // Arms
3771
+ \x27<g \x27+armCls+\x27 style="transform-origin:14px 35px">\x27+
3772
+ \x27<path d="M14 35 C8 38 5 44 5 48 C5 51 8 52 10 51 C12 50 13 46 14 42 C15 39 14 36 14 35" fill="#252450"/>\x27+
3773
+ \x27<ellipse cx="6" cy="51" rx="5" ry="3.5" fill="#d4a97a" transform="rotate(-15 6 51)"/>\x27+
3774
+ \x27</g>\x27+
3775
+ \x27<g \x27+armRCls+\x27 style="transform-origin:40px 35px">\x27+
3776
+ \x27<path d="M40 35 C46 38 49 43 49 47 C49 50 46 51 44 50 C42 49 41 45 40 41 C39 38 40 36 40 35" fill="#252450"/>\x27+
3777
+ // Clipboard
3778
+ \x27<rect x="46" y="30" width="12" height="16" rx="2" fill="#1a1a2e" stroke="\x27+mc+\x2799" stroke-width="1.2"/>\x27+
3779
+ \x27<rect x="49" y="28" width="6" height="4" rx="1" fill="\x27+mc+\x27"/>\x27+
3780
+ \x27<line x1="48" y1="34" x2="56" y2="34" stroke="\x27+mc+\x27cc" stroke-width=".9"/>\x27+
3781
+ \x27<line x1="48" y1="37" x2="56" y2="37" stroke="\x27+mc+\x27aa" stroke-width=".9"/>\x27+
3782
+ \x27<line x1="48" y1="40" x2="54" y2="40" stroke="\x27+mc+\x2788" stroke-width=".9"/>\x27+
3783
+ \x27</g>\x27+
3784
+ // Neck
3785
+ \x27<rect x="24" y="25" width="6" height="5" rx="2" fill="#d4a97a"/>\x27+
3786
+ // Head
3787
+ \x27<ellipse cx="27" cy="14" rx="13" ry="13" fill="#d4a97a"/>\x27+
3788
+ \x27<path d="M14 14 C14 20 18 24 27 25 C36 24 40 20 40 14" fill="#d4a97a"/>\x27+
3789
+ // Hair
3790
+ \x27<path d="M14 14 C13 7 17 2 27 1 C37 2 41 7 40 14 C39 7 35 4 27 3 C19 4 15 7 14 14" fill="#1a0e08"/>\x27+
3791
+ // Eyes
3792
+ \x27<ellipse cx="22" cy="15" rx="3" ry="3.5" fill="#fff"/>\x27+
3793
+ \x27<ellipse cx="32" cy="15" rx="3" ry="3.5" fill="#fff"/>\x27+
3794
+ \x27<circle cx="22" cy="15.5" r="2" fill="#1e3a6e"/>\x27+
3795
+ \x27<circle cx="32" cy="15.5" r="2" fill="#1e3a6e"/>\x27+
3796
+ \x27<circle cx="22" cy="15.5" r="1.2" fill="#0a0a18"/>\x27+
3797
+ \x27<circle cx="32" cy="15.5" r="1.2" fill="#0a0a18"/>\x27+
3798
+ \x27<circle cx="23" cy="14" r=".8" fill="rgba(255,255,255,.9)"/>\x27+
3799
+ (hasActive ?
3800
+ \x27<path d="M21 21 Q27 25 33 21" stroke="#8b4513" stroke-width="1.8" fill="none" stroke-linecap="round"/>\x27+
3801
+ \x27<ellipse cx="27" cy="22.5" rx="3.5" ry="2" fill="#5a1a0a" opacity=".6"/>\x27
3802
+ :
3803
+ \x27<path d="M21 22 Q27 25 33 22" stroke="#8b4513" stroke-width="1.6" fill="none" stroke-linecap="round"/>\x27
3804
+ )+
3805
+ // Crown
3806
+ \x27<path d="M17 4 L20 9 L27 6 L34 9 L37 4 L27 1 Z" fill="#f59e0b"/>\x27+
3807
+ \x27<circle cx="20" cy="9" r="2" fill="#fbbf24"/>\x27+
3808
+ \x27<circle cx="27" cy="6" r="2" fill="#fbbf24"/>\x27+
3809
+ \x27<circle cx="34" cy="9" r="2" fill="#fbbf24"/>\x27+
3810
+ \x27</svg>\x27;
3811
+ }
3812
+
3813
+ // Skin/shirt/hair palette per agent label
3814
+ function agentPalette(lbl) {
3815
+ var skins = [\x27#fbbf24\x27,\x27#f97316\x27,\x27#e8c99a\x27,\x27#c8a97a\x27,\x27#d4a97a\x27,\x27#f5c07a\x27];
3816
+ var shirts = [\x27#4f46e5\x27,\x27#0891b2\x27,\x27#7c3aed\x27,\x27#059669\x27,\x27#dc2626\x27,\x27#d97706\x27];
3817
+ var hairs = [\x27#1a0e08\x27,\x27#4a3728\x27,\x27#c4a35a\x27,\x27#8b0000\x27,\x27#2c4a7c\x27,\x27#3d2b1f\x27];
3818
+ var i = Math.abs((lbl.charCodeAt(0)||65) + (lbl.charCodeAt(lbl.length-1)||90)) % 6;
3819
+ return {skin: skins[i], shirt: shirts[i], hair: hairs[i]};
3820
+ }
3821
+
3610
3822
  function renderStudioNodes() {
3611
3823
  var el = document.getElementById('studioNodes');
3612
3824
  if (!el) return;
@@ -3614,138 +3826,94 @@ function renderStudioNodes() {
3614
3826
  el.innerHTML = '<div class="studio-canvas__empty"><div class="studio-canvas__empty-icon">&#9881;</div><div>Describe a task and click Run</div></div>';
3615
3827
  return;
3616
3828
  }
3617
- // ── Parliament-style office scene ──────────────────────────────────────────
3618
- // Renders all pipeline nodes as the same parliament office scene:
3619
- // agents at desks, orchestrator walking + sbraita (speech bubble yelling).
3620
- var hasActive = studioState.nodes.some(function(n){ return n.status === \x27running\x27; });
3621
- var hasDone = studioState.nodes.some(function(n){ return n.status === \x27done\x27; });
3622
- var showMaster = hasActive || hasDone;
3623
-
3624
- // Sbraita phrases — orchestrator yells at agents
3625
- var sbraitaPhrases = [
3626
- \x27PIU VELOCE!!!\x27, \x27FOCUS!!!\x27, \x27DEADLINE!!!\x27, \x27CHE FAI??!\x27,
3627
- \x27MUOVITI!!\x27, \x27NO ERRORI!\x27, \x27ADESSO!!\x27, \x27SBRIGATI!\x27
3628
- ];
3629
- var activeNode = studioState.nodes.find(function(n){ return n.status === \x27running\x27; });
3630
- var sbIdx = activeNode ? Math.abs((activeNode.label||activeNode.agent||\x27x\x27).charCodeAt(0)) % sbraitaPhrases.length : 0;
3631
- var sbraitaText = sbraitaPhrases[sbIdx];
3632
3829
 
3633
- // Build desks row — same card structure as parliament
3634
- var desksHtml2 = \x27\x27;
3635
- studioState.nodes.forEach(function(n) {
3830
+ var nodes = studioState.nodes;
3831
+ var hasActive = nodes.some(function(n){ return n.status === \x27running\x27; });
3832
+ var doneCount = nodes.filter(function(n){ return n.status === \x27done\x27; }).length;
3833
+ var totalCount = nodes.length;
3834
+ var showMaster = hasActive || doneCount > 0;
3835
+
3836
+ // ── ISO scene dimensions ───────────────────────────────────────────────────
3837
+ var ISO_COLS = Math.max(nodes.length, 1);
3838
+ var SCENE_W = 600; var SCENE_H = 260;
3839
+
3840
+ // Distribute agents along row=1 of the iso grid, spaced evenly
3841
+ // Orchestrator walks on row=0 (further back = smaller)
3842
+ var agentsHtml = \x27\x27;
3843
+ nodes.forEach(function(n, idx) {
3636
3844
  var isActive = n.status === \x27running\x27;
3637
3845
  var isDone = n.status === \x27done\x27;
3638
3846
  var isErr = n.status === \x27error\x27;
3639
- desksHtml2 += \x27<div class="prl-desk\x27+(isActive?\x27 prl-desk--active\x27:\x27\x27)+(isDone?\x27 wf-desk--done\x27:\x27\x27)+(isErr?\x27 wf-desk--err\x27:\x27\x27)+\x27" style="--dc:#6366f1;min-width:78px;cursor:pointer" data-agent-label="\x27+esc(n.label||n.agent)+\x27" onclick="studioScrollToAgent(this.getAttribute(String.fromCharCode(100,97,116,97,45,97,103,101,110,116,45,108,97,98,101,108)))" title="Vai al log di \x27+esc(n.label||n.agent)+\x27">\x27;
3640
- if (isActive) desksHtml2 += \x27<div class="prl-action-bubble prl-action-bubble--active" style="font-size:8px;padding:2px 6px">\u2026lavora</div>\x27;
3641
- else if (isDone) desksHtml2 += \x27<div class="prl-action-bubble" style="background:#0a2010;border-color:#22c55e;color:#4ade80;font-size:8px;padding:2px 6px">\u2714 fatto</div>\x27;
3642
- else if (isErr) desksHtml2 += \x27<div class="prl-action-bubble" style="background:#200a0a;border-color:#ef4444;color:#f87171;font-size:8px;padding:2px 6px">\u2715 errore</div>\x27;
3643
- else desksHtml2 += \x27<div class="prl-action-bubble" style="font-size:8px;padding:2px 6px;opacity:.5">in attesa</div>\x27;
3644
- desksHtml2 += buildWorkflowChar(n);
3645
- desksHtml2 += \x27<div class="prl-desk-name" style="color:\x27+(isDone?\x27#4ade80\x27:(isActive?\x27#a5b4fc\x27:(isErr?\x27#f87171\x27:\x27#6b7280\x27)))+\x27;max-width:78px;white-space:normal;word-break:break-word;text-align:center" title="\x27+esc(n.label)+\x27">\x27+esc(n.label)+\x27</div>\x27;
3646
- desksHtml2 += \x27</div>\x27;
3647
- n._rendered = true;
3847
+ var lbl = n.label || n.agent;
3848
+ var pal = agentPalette(lbl);
3849
+ var accentColor = isActive ? \x27#6366f1\x27 : (isDone ? \x27#22c55e\x27 : (isErr ? \x27#ef4444\x27 : \x27#3a3060\x27));
3850
+
3851
+ // Iso position: spread across columns, agents sit at row=2 (front row)
3852
+ var col = (idx + 0.5) * (ISO_COLS > 1 ? (ISO_COLS - 1) / ISO_COLS : 1) + 0.5;
3853
+ col = (idx * (ISO_COLS <= 1 ? 2 : (4 / ISO_COLS))) + 1;
3854
+ var row = 2.5;
3855
+ var pos = isoProject(col, row);
3856
+ var px = pos.x; var py = pos.y + 40;
3857
+ // scale: row 2.5 = 1.0, background rows smaller
3858
+ var charScale = 0.9 + row * 0.08;
3859
+ var zIdx = Math.round(py + 100);
3860
+
3861
+ // Speech bubble content
3862
+ var bubbleText = \x27\x27;
3863
+ var bubbleColor = \x27#6366f1\x27;
3864
+ if (isActive) { bubbleText = \x27...lavora\x27; bubbleColor = \x27#6366f1\x27; }
3865
+ else if (isDone) { bubbleText = \x27\u2714 fatto\x27; bubbleColor = \x27#22c55e\x27; }
3866
+ else if (isErr) { bubbleText = \x27\u2715 errore\x27; bubbleColor = \x27#ef4444\x27; }
3867
+ else { bubbleText = \x27in attesa\x27; bubbleColor = \x27#4a4a6a\x27; }
3868
+
3869
+ var charSvg = isoCharSvg({skin: pal.skin, shirt: pal.shirt, hair: pal.hair,
3870
+ isActive: isActive, isDone: isDone, scale: charScale, accentColor: accentColor});
3871
+
3872
+ var charW = Math.round(48 * charScale);
3873
+ var charH = Math.round(64 * charScale);
3874
+
3875
+ agentsHtml += \x27<div class="iso-agent" data-agent-label="\x27+esc(lbl)+\x27" style="position:absolute;left:\x27+(px - charW/2)+\x27px;top:\x27+(py - charH)+\x27px;z-index:\x27+zIdx+\x27;display:flex;flex-direction:column;align-items:center;gap:2px;cursor:pointer" onclick="studioScrollToAgent(this.getAttribute(String.fromCharCode(100,97,116,97,45,97,103,101,110,116,45,108,97,98,101,108)))">\x27;
3876
+ // Thought bubble above head
3877
+ agentsHtml += \x27<div class="iso-bubble\x27+(isActive?\x27 iso-bubble--active\x27:\x27\x27)+\x27" id="isobubble_\x27+idx+\x27" style="border-color:\x27+bubbleColor+\x27;color:\x27+bubbleColor+\x27">\x27+esc(bubbleText)+\x27</div>\x27;
3878
+ agentsHtml += charSvg;
3879
+ // Name tag
3880
+ agentsHtml += \x27<div class="iso-name" style="color:\x27+(isDone?\x27#4ade80\x27:(isActive?\x27#a5b4fc\x27:(isErr?\x27#f87171\x27:\x27#6b7280\x27)))+\x27">\x27+esc(lbl)+\x27</div>\x27;
3881
+ // Iso desk below character
3882
+ agentsHtml += isoDeskSvg(px, py - charH/2, accentColor);
3883
+ agentsHtml += \x27</div>\x27;
3648
3884
  });
3649
3885
 
3650
- // ── Master orchestrator SVG ─────────────────────────────────────────────────
3651
- var masterColor3 = \x27#818cf8\x27;
3652
- var masterSvg2 = \x27<svg viewBox="0 0 60 90" width="52" height="78" xmlns="http://www.w3.org/2000/svg" font-family="system-ui,sans-serif" style="filter:drop-shadow(0 0 10px \x27+masterColor3+\x27aa)">\x27+
3653
- \x27<path d="M22 55 C21 63 19 72 18 77 C17 80 18 82 21 82 C23 82 24 80 24 77 C25 71 25 62 26 55 Z" fill="#1e1c4a" class="prl-master-leg-l"/>\x27+
3654
- \x27<path d="M28 55 C29 63 31 72 32 77 C33 80 32 82 29 82 C27 82 26 80 26 77 C25 71 25 62 24 55 Z" fill="#1e1c4a" class="prl-master-leg-r"/>\x27+
3655
- \x27<path d="M16 79 C14 79 13 81 14 83 C15 85 18 85 21 84 C23 83 24 82 23 80 C22 79 19 79 16 79" fill="#0a0a14"/>\x27+
3656
- \x27<path d="M34 79 C36 79 37 81 36 83 C35 85 32 85 29 84 C27 83 26 82 27 80 C28 79 31 79 34 79" fill="#0a0a14"/>\x27+
3657
- \x27<path d="M13 32 C12 30 15 27 25 25 C35 27 38 30 37 32 L38 55 L12 55 Z" fill="#252450"/>\x27+
3658
- \x27<path d="M13 32 C12 30 15 27 25 25 L25 55 L12 55 Z" fill="#1e1d44"/>\x27+
3659
- \x27<path d="M25 25 C35 27 38 30 37 32 L38 55 L25 55 Z" fill="#272660"/>\x27+
3660
- \x27<path d="M25 25 L19 33 L22 36 L25 29 Z" fill="#1a1940"/>\x27+
3661
- \x27<path d="M25 25 L31 33 L28 36 L25 29 Z" fill="#1a1940"/>\x27+
3662
- \x27<path d="M25 29 L22 36 L25 34 L28 36 Z" fill="#f0f0fa"/>\x27+
3663
- \x27<path d="M25 33 L24 44 L25 48 L26 44 Z" fill="\x27+masterColor3+\x27"/>\x27+
3664
- \x27<path d="M23.5 32 L26.5 32 L25 34 Z" fill="\x27+masterColor3+\x27"/>\x27+
3665
- \x27<path d="M33 35 L36 33 L37 36 L34 37 Z" fill="\x27+masterColor3+\x2799"/>\x27+
3666
- \x27<circle cx="25" cy="42" r="1.2" fill="\x27+masterColor3+\x27aa"/>\x27+
3667
- \x27<circle cx="20" cy="36" r="2.5" fill="#0d0d1e" stroke="\x27+masterColor3+\x2799" stroke-width="1"/>\x27+
3668
- \x27<text x="20" y="39.5" text-anchor="middle" font-size="4" fill="\x27+masterColor3+\x27" font-family="system-ui,sans-serif">N</text>\x27+
3669
- \x27<g class="prl-master-arm-l">\x27+
3670
- \x27<path d="M13 34 C8 37 6 42 6 46 C6 49 9 50 11 49 C13 48 13 45 14 41 C15 38 14 35 13 34" fill="#252450"/>\x27+
3671
- \x27<path d="M6 46 C4 48 4 51 5 53 C6 55 9 55 10 53 C11 51 10 48 10 46 Z" fill="#d4a97a"/>\x27+
3672
- \x27<ellipse cx="7" cy="54" rx="4.5" ry="3.5" fill="#d4a97a" transform="rotate(-15 7 54)"/>\x27+
3673
- \x27</g>\x27+
3674
- \x27<g class="prl-master-arm-r">\x27+
3675
- \x27<path d="M37 34 C42 37 44 41 44 45 C44 48 41 49 39 48 C37 47 37 44 37 40 C37 37 37 35 37 34" fill="#252450"/>\x27+
3676
- \x27<path d="M44 44 C46 46 47 49 46 52 C45 54 42 54 41 52 C40 50 41 47 41 45 Z" fill="#d4a97a"/>\x27+
3677
- \x27<ellipse cx="45" cy="52" rx="4" ry="3" fill="#d4a97a" transform="rotate(15 45 52)"/>\x27+
3678
- \x27<rect x="43" y="32" width="14" height="19" rx="2.5" fill="#1a1a2e" stroke="\x27+masterColor3+\x2799" stroke-width="1.5"/>\x27+
3679
- \x27<rect x="47" y="30" width="6" height="5" rx="1.5" fill="\x27+masterColor3+\x27"/>\x27+
3680
- \x27<rect x="48" y="31" width="4" height="3" rx="1" fill="#0f0f1e"/>\x27+
3681
- \x27<line x1="46" y1="36" x2="54" y2="36" stroke="\x27+masterColor3+\x27cc" stroke-width="1"/>\x27+
3682
- \x27<line x1="46" y1="39" x2="54" y2="39" stroke="\x27+masterColor3+\x27aa" stroke-width="1"/>\x27+
3683
- \x27<line x1="46" y1="42" x2="54" y2="42" stroke="\x27+masterColor3+\x2788" stroke-width="1"/>\x27+
3684
- \x27</g>\x27+
3685
- \x27<path d="M22 25 L28 25 L28 29 Q28 31 25 31 Q22 31 22 29 Z" fill="#d4a97a"/>\x27+
3686
- \x27<ellipse cx="25" cy="15" rx="12" ry="13" fill="#d4a97a"/>\x27+
3687
- \x27<path d="M14 15 C14 22 18 26 25 27 C32 26 36 22 36 15" fill="#d4a97a"/>\x27+
3688
- \x27<path d="M13 13 C11 13 10 15 10 17 C10 19 11 20 13 20 C14 20 14.5 19 14 17 C14.5 15 14 13 13 13" fill="#d4a97a"/>\x27+
3689
- \x27<path d="M37 13 C39 13 40 15 40 17 C40 19 39 20 37 20 C36 20 35.5 19 36 17 C35.5 15 36 13 37 13" fill="#d4a97a"/>\x27+
3690
- \x27<path d="M13 14 C12 7 16 2 25 1 C34 2 38 7 37 14 C36 7 32 4 25 3 C18 4 14 7 13 14" fill="#1a0e08"/>\x27+
3691
- \x27<path d="M22 3 C21 4 21 6 22 8" stroke="rgba(255,255,255,.15)" stroke-width="1.5" fill="none"/>\x27+
3692
- \x27<path d="M16.5 11 Q19 9.5 21.5 11" stroke="#1a0e08" stroke-width="1.8" fill="none" stroke-linecap="round"/>\x27+
3693
- \x27<path d="M28.5 11 Q31 9.5 33.5 11" stroke="#1a0e08" stroke-width="1.8" fill="none" stroke-linecap="round"/>\x27+
3694
- \x27<ellipse cx="19.5" cy="14.5" rx="3.2" ry="3.5" fill="#fff"/>\x27+
3695
- \x27<ellipse cx="30.5" cy="14.5" rx="3.2" ry="3.5" fill="#fff"/>\x27+
3696
- \x27<circle cx="19.5" cy="15" r="2.3" fill="#1e3a6e"/>\x27+
3697
- \x27<circle cx="30.5" cy="15" r="2.3" fill="#1e3a6e"/>\x27+
3698
- \x27<circle cx="19.5" cy="15" r="1.3" fill="#0a0a18"/>\x27+
3699
- \x27<circle cx="30.5" cy="15" r="1.3" fill="#0a0a18"/>\x27+
3700
- \x27<circle cx="20.5" cy="13.7" r=".9" fill="rgba(255,255,255,.95)"/>\x27+
3701
- \x27<circle cx="31.5" cy="13.7" r=".9" fill="rgba(255,255,255,.95)"/>\x27+
3702
- // Angry expression when active (furrowed brows + open mouth)
3703
- (hasActive ?
3704
- \x27<path d="M19.5 23 Q25 26 30.5 23" stroke="#8b4513" stroke-width="1.8" fill="none" stroke-linecap="round"/>\x27+
3705
- \x27<path d="M20 23.5 Q25 26 30 23.5 Q25 25 20 23.5" fill="#fff" opacity=".6"/>\x27+
3706
- // Open mouth / yelling
3707
- \x27<ellipse cx="25" cy="24.5" rx="3" ry="1.5" fill="#5a1a0a" opacity=".7"/>\x27
3708
- :
3709
- \x27<path d="M19.5 24 Q25 27.5 30.5 24" stroke="#8b4513" stroke-width="1.8" fill="none" stroke-linecap="round"/>\x27+
3710
- \x27<path d="M20 24.5 Q25 27 30 24.5 Q25 26.5 20 24.5" fill="#fff" opacity=".7"/>\x27
3711
- )+
3712
- \x27<circle cx="25" cy="-4" r="9" fill="\x27+masterColor3+\x2722" stroke="\x27+masterColor3+\x2766" stroke-width="1.5"/>\x27+
3713
- \x27<path d="M25 -13 L30 -4 L25 5 L20 -4 Z" fill="\x27+masterColor3+\x27" stroke="\x27+masterColor3+\x27cc" stroke-width=".8"/>\x27+
3714
- \x27<path d="M25 -13 L30 -4 L25 -3 L20 -4 Z" fill="\x27+masterColor3+\x27aa"/>\x27+
3715
- \x27</svg>\x27;
3886
+ // ── Orchestrator ──────────────────────────────────────────────────────────
3887
+ var orchHtml = \x27\x27;
3888
+ if (showMaster) {
3889
+ var oPh = isoProject(ISO_COLS / 2, 0.5);
3890
+ var orchPhrase = [\x27Analizzo il progresso...\x27, \x27Verifico gli step...\x27, \x27Coordinamento...\x27, \x27Supervisione in corso\x27][Math.floor(Date.now()/3000)%4];
3891
+ var orchDone = !hasActive && doneCount === totalCount;
3892
+ var orchAnim = hasActive ? \x27prl-master-walk\x27 : (orchDone ? \x27prl-master-done\x27 : \x27\x27);
3893
+ var orchStatus = hasActive
3894
+ ? (\x27Eseguiti \x27+doneCount+\x27/\x27+totalCount)
3895
+ : (orchDone ? \x27Workflow completato!\x27 : \x27In pianificazione\x27);
3896
+ orchHtml = \x27<div class="prl-master \x27+orchAnim+\x27" id="wfOrch" style="position:absolute;left:\x27+(oPh.x+30)+\x27px;top:\x27+(oPh.y-10)+\x27px;z-index:50;display:flex;flex-direction:column;align-items:center;gap:2px">\x27+
3897
+ \x27<div class="iso-bubble iso-bubble--orch" id="wfOrchBubble" style="border-color:#818cf8;color:#a5b4fc;max-width:120px">\x27+esc(orchStatus)+\x27</div>\x27+
3898
+ isoOrchSvg(hasActive, doneCount / Math.max(totalCount,1))+
3899
+ \x27<div class="prl-master-label" style="color:#818cf8;font-size:8px">Orchestratore</div>\x27+
3900
+ \x27</div>\x27;
3901
+ }
3716
3902
 
3717
- // Speech bubble "sbraita" when there is an active agent
3718
- var bubbleHtml = showMaster && hasActive
3719
- ? (\x27<div class="wf-sbraita-bubble">\x27+sbraitaText+\x27</div>\x27)
3720
- : \x27\x27;
3721
-
3722
- // Phase label
3723
- var doneCount = studioState.nodes.filter(function(n){return n.status===\x27done\x27;}).length;
3724
- var totalCount = studioState.nodes.length;
3725
- var wfMasterAnim = hasActive ? \x27prl-master-walk\x27 : (doneCount===totalCount && totalCount>0 ? \x27prl-master-done\x27 : \x27prl-master-walk\x27);
3726
- var masterWfHtml = showMaster ? (
3727
- \x27<div class="prl-master \x27+wfMasterAnim+\x27 wf-master" style="bottom:20px">\x27+
3728
- bubbleHtml+
3729
- masterSvg2+
3730
- \x27<div class="prl-master-label" style="color:\x27+masterColor3+\x27;font-size:8px">Orchestratore</div>\x27+
3731
- \x27</div>\x27
3732
- ) : \x27\x27;
3733
3903
  var phaseLabel2 = hasActive
3734
- ? (\x27Workflow in esecuzione \u2014 \x27+doneCount+\x27/\x27+totalCount+\x27 completati\x27)
3904
+ ? (\x27Workflow in esecuzione \u2014 \x27+doneCount+\x27/\x27+totalCount)
3735
3905
  : (doneCount===totalCount && totalCount>0 ? \x27Workflow completato\x27 : \x27Workflow pianificato\x27);
3736
3906
  var phaseColor2 = hasActive ? \x27#6366f1\x27 : (doneCount===totalCount && totalCount>0 ? \x27#22c55e\x27 : \x27#6b7280\x27);
3737
3907
 
3738
- var html = \x27<div class="prl-wrap" style="border-color:\x27+phaseColor2+\x2744;padding-bottom:16px">\x27+
3908
+ el.innerHTML =
3909
+ \x27<div class="prl-wrap" style="border-color:\x27+phaseColor2+\x2744;padding-bottom:8px">\x27+
3739
3910
  \x27<div class="prl-header"><span class="prl-phase-chip" style="--pc:\x27+phaseColor2+\x27">\x27+phaseLabel2+\x27</span></div>\x27+
3740
- \x27<div class="prl-office wf-office" style="min-height:160px">\x27+
3741
- officeRoomDecor()+
3742
- \x27<div class="prl-office-floor"></div>\x27+
3743
- \x27<div class="prl-desks-row" style="justify-content:center;flex-wrap:wrap;gap:10px;padding-bottom:8px;position:relative;z-index:2">\x27+desksHtml2+\x27</div>\x27+
3744
- masterWfHtml+
3911
+ \x27<div class="iso-scene" style="position:relative;width:\x27+SCENE_W+\x27px;height:\x27+SCENE_H+\x27px;overflow:hidden;border-radius:10px">\x27+
3912
+ isoFloorSvg(ISO_COLS + 2, 4)+
3913
+ agentsHtml+
3914
+ orchHtml+
3745
3915
  \x27</div>\x27+
3746
3916
  \x27</div>\x27;
3747
-
3748
- el.innerHTML = html;
3749
3917
  }
3750
3918
 
3751
3919
  function studioScrollToAgent(agentLabel) {
@@ -3791,9 +3959,18 @@ function renderStudioLog() {
3791
3959
  if (!el) return;
3792
3960
  if (!studioState.log.length) { el.style.display = 'none'; return; }
3793
3961
  el.style.display = 'block';
3794
- el.innerHTML = studioState.log.map(function(e) {
3962
+ var existingEntries = el.querySelectorAll('.studio-log-entry');
3963
+ studioState.log.forEach(function(e, i) {
3964
+ var isStreaming = false;
3965
+ if (existingEntries[i]) {
3966
+ var tb = existingEntries[i].querySelector('.studio-log-entry__text');
3967
+ if (tb && tb.getAttribute(String.fromCharCode(100,97,116,97,45,114,108,101,110)) !== null) {
3968
+ isStreaming = true;
3969
+ }
3970
+ }
3971
+ if (isStreaming) return; // leave streaming entry DOM untouched
3795
3972
  var cls = 'studio-log-entry' + (e.type === 'system' ? ' studio-log-entry--system' : e.type === 'error' ? ' studio-log-entry--error' : '');
3796
- return '<div class="' + cls + '">' +
3973
+ var html = '<div class="' + cls + '">' +
3797
3974
  '<div class="studio-log-entry__header">' +
3798
3975
  '<span class="studio-log-entry__icon">' + e.icon + '</span>' +
3799
3976
  '<span class="studio-log-entry__agent">' + esc(e.agent) + '</span>' +
@@ -3801,7 +3978,20 @@ function renderStudioLog() {
3801
3978
  '</div>' +
3802
3979
  '<div class="studio-log-entry__text md-body">' + renderMd(e.text) + '</div>' +
3803
3980
  '</div>';
3804
- }).join('');
3981
+ if (existingEntries[i]) {
3982
+ existingEntries[i].outerHTML = html;
3983
+ } else {
3984
+ var div = document.createElement('div');
3985
+ div.innerHTML = html;
3986
+ el.appendChild(div.firstChild);
3987
+ }
3988
+ // refresh reference after replacement
3989
+ existingEntries = el.querySelectorAll('.studio-log-entry');
3990
+ });
3991
+ // remove extra entries (shouldn't happen but be safe)
3992
+ while (el.querySelectorAll('.studio-log-entry').length > studioState.log.length) {
3993
+ el.removeChild(el.lastChild);
3994
+ }
3805
3995
  el.scrollTop = el.scrollHeight;
3806
3996
  }
3807
3997
 
@@ -4261,7 +4451,7 @@ async function runStudio() {
4261
4451
  for (var i = 0; i < studioState.nodes.length; i++) {
4262
4452
  var node = studioState.nodes[i];
4263
4453
  studioSetNodeStatus(i, 'running');
4264
- studioLog(node.label, node.icon, 'Starting...', 'agent');
4454
+ studioLog(node.label, node.icon, t('starting_agent') || 'Elaborazione in corso...', 'agent');
4265
4455
 
4266
4456
  if (!studioState.running) break; // stopped by user
4267
4457
  var stepResult = await runStudioStep(i, node, task, context, planRes.steps[i], studioAbortController ? studioAbortController.signal : null);
@@ -4356,7 +4546,7 @@ async function runStudio() {
4356
4546
  // Subsequent calls ONLY update agent states (no innerHTML overwrite).
4357
4547
  // ─────────────────────────────────────────────────────────────────────
4358
4548
 
4359
- if (!parlBlockBuilt) {
4549
+ if (!parlBlockBuilt || !pb.innerHTML.trim()) {
4360
4550
  parlBlockBuilt = true;
4361
4551
 
4362
4552
  // Build agent seat HTML (box + avatar)
@@ -5365,28 +5555,53 @@ function runStudioStep(idx, node, task, context, stepDef, signal) {
5365
5555
  tb.textContent = st;
5366
5556
  }
5367
5557
  } else {
5368
- // Word-by-word streaming text with blinking cursor
5369
- var snippet = output.length > 400 ? output.slice(-400) : output;
5370
- var safeSnip = snippet.replace(/&/g,\x27&amp;\x27).replace(/</g,\x27&lt;\x27).replace(/>/g,\x27&gt;\x27);
5371
- tb.innerHTML = \x27<span style="font-size:11px;color:var(--text);line-height:1.5;white-space:pre-wrap;word-break:break-word;font-family:var(--font)">\x27 + safeSnip + \x27<span class="stream-cursor" style="display:inline-block;width:2px;height:13px;background:var(--green);margin-left:1px;vertical-align:text-bottom;animation:streamBlink .7s step-end infinite">&#8203;</span></span>\x27;
5372
- // Update thought bubble of the currently active workflow desk
5373
- var activeDeskEl = document.querySelector(\x27.prl-desk--active[data-agent-label]\x27);
5374
- if (activeDeskEl) {
5375
- var wfBubble = activeDeskEl.querySelector(\x27.prl-action-bubble--active\x27);
5376
- if (wfBubble) {
5377
- var wfRaw = output.length > 42 ? output.slice(-42) : output;
5378
- var wfSafe = wfRaw.replace(/&/g,\x27&amp;\x27).replace(/</g,\x27&lt;\x27).replace(/>/g,\x27&gt;\x27);
5379
- wfBubble.style.maxWidth = \x2796px\x27;
5380
- wfBubble.style.fontSize = \x278px\x27;
5381
- wfBubble.innerHTML = wfSafe + \x27<span style="display:inline-block;width:2px;height:9px;background:var(--green);margin-left:1px;vertical-align:text-bottom;animation:streamBlink .7s step-end infinite">&#8203;</span>\x27;
5382
- }
5558
+ // Word-by-word streaming: APPEND new chars, never overwrite
5559
+ var renderedLen = parseInt(tb.getAttribute(String.fromCharCode(100,97,116,97,45,114,108,101,110)) || \x270\x27, 10);
5560
+ if (renderedLen === 0) {
5561
+ // First token: set up container + cursor
5562
+ tb.innerHTML = \x27\x27;
5563
+ tb.setAttribute(String.fromCharCode(100,97,116,97,45,114,108,101,110), \x270\x27);
5564
+ var streamSpan = document.createElement(\x27span\x27);
5565
+ streamSpan.id = \x27streamText_\x27 + idx;
5566
+ streamSpan.style.cssText = \x27font-size:12px;color:var(--text);line-height:1.6;white-space:pre-wrap;word-break:break-word;font-family:var(--font)\x27;
5567
+ tb.appendChild(streamSpan);
5568
+ var cursorEl = document.createElement(\x27span\x27);
5569
+ cursorEl.id = \x27streamCursor_\x27 + idx;
5570
+ cursorEl.style.cssText = \x27display:inline-block;width:2px;height:13px;background:var(--green);margin-left:1px;vertical-align:text-bottom;animation:streamBlink .7s step-end infinite\x27;
5571
+ tb.appendChild(cursorEl);
5572
+ }
5573
+ // Append only newly arrived chars
5574
+ var newChars = output.slice(renderedLen);
5575
+ if (newChars.length > 0) {
5576
+ var stEl = document.getElementById(\x27streamText_\x27 + idx);
5577
+ if (stEl) { stEl.appendChild(document.createTextNode(newChars)); }
5578
+ tb.setAttribute(String.fromCharCode(100,97,116,97,45,114,108,101,110), String(output.length));
5579
+ // Keep studioState.log in sync so renderStudioLog() final call has current text
5580
+ var logLen = studioState.log.length;
5581
+ if (logLen > 0) { studioState.log[logLen - 1].text = output; }
5582
+ }
5583
+ // Update iso thought bubble of the active agent
5584
+ var isoB = document.getElementById(\x27isobubble_\x27+idx);
5585
+ if (isoB) {
5586
+ var wfRaw = output.length > 48 ? output.slice(-48) : output;
5587
+ var wfSafe = wfRaw.replace(/&/g,\x27&amp;\x27).replace(/</g,\x27&lt;\x27).replace(/>/g,\x27&gt;\x27);
5588
+ isoB.className = \x27iso-bubble iso-bubble--active\x27;
5589
+ 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;
5590
+ }
5591
+ // Update orchestrator bubble with progress
5592
+ var orchB = document.getElementById(\x27wfOrchBubble\x27);
5593
+ if (orchB) {
5594
+ var doneN = studioState.nodes.filter(function(x){return x.status===\x27done\x27;}).length;
5595
+ var totN = studioState.nodes.length;
5596
+ var orchPhrases2 = [\x27Elaboro step \x27+doneN+\x27/\x27+totN, \x27Verifico output...\x27, \x27Coordinamento...\x27, \x27Aspetto risposta...\x27];
5597
+ orchB.textContent = orchPhrases2[Math.floor(output.length/120) % orchPhrases2.length];
5383
5598
  }
5384
- // Update thought bubble of the active boardroom seat
5599
+ // Update boardroom seat bubble if parliament is active
5385
5600
  if (parlActiveAgent) {
5386
5601
  var brSafeLbl = parlActiveAgent.replace(new RegExp(\x27[^a-zA-Z0-9_-]\x27,\x27g\x27),\x27_\x27);
5387
5602
  var brBubbleEl = document.getElementById(\x27brbubble_\x27+brSafeLbl);
5388
5603
  if (brBubbleEl) {
5389
- var brRaw = output.length > 42 ? output.slice(-42) : output;
5604
+ var brRaw = output.length > 48 ? output.slice(-48) : output;
5390
5605
  var brSafe = brRaw.replace(/&/g,\x27&amp;\x27).replace(/</g,\x27&lt;\x27).replace(/>/g,\x27&gt;\x27);
5391
5606
  brBubbleEl.style.display = \x27\x27;
5392
5607
  brBubbleEl.innerHTML = brSafe + \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;
@@ -6196,8 +6411,8 @@ input:focus,textarea:focus{border-color:var(--green3)}
6196
6411
  .br-seat--active .br-seat-box{background:#1e1a45;border-color:var(--sc,#6366f1);box-shadow:0 0 20px rgba(99,102,241,.3),0 0 40px rgba(99,102,241,.08),inset 0 1px 0 rgba(150,130,255,.15)}
6197
6412
  .br-seat--active{transform:scale(1.04)}
6198
6413
  .br-seat--done .br-seat-box{background:#162516;border-color:#2a4a2a}
6199
- /* Action bubble above agent */
6200
- .br-bubble{font-size:9px;font-family:var(--mono);padding:2px 7px;border-radius:7px;border:1px solid #3a3060;background:#0d0a1e;min-height:14px;text-align:center;line-height:1.4;transition:all .3s;word-break:break-word;max-width:90px}
6414
+ /* Action bubble above agent — shows streaming text */
6415
+ .br-bubble{font-size:8px;font-family:var(--mono);padding:3px 8px;border-radius:8px 8px 8px 2px;border:1px solid #3a3060;background:rgba(10,8,20,.88);min-height:14px;text-align:left;line-height:1.45;transition:border-color .3s;word-break:break-word;max-width:100px;white-space:normal;backdrop-filter:blur(4px)}
6201
6416
  /* Agent name */
6202
6417
  .br-seat-name{font-size:9px;font-family:var(--mono);font-weight:700;text-align:center;white-space:normal;word-break:break-word;max-width:90px;line-height:1.3;margin-top:2px;transition:color .3s}
6203
6418
  /* SVG arm animations when agent is active (typing) */
@@ -6256,13 +6471,21 @@ input:focus,textarea:focus{border-color:var(--green3)}
6256
6471
  .prl-office-plant{position:absolute;bottom:14px;left:4px;width:22px;height:42px;z-index:2;pointer-events:none}
6257
6472
  /* Plant right */
6258
6473
  .prl-office-plant2{position:absolute;bottom:14px;right:8px;width:22px;height:42px;z-index:2;pointer-events:none}
6259
- /* Desks row */
6474
+ /* ── ISOMETRIC JRPG SCENE ── */
6475
+ .iso-scene{background:#0d0b1e;box-shadow:inset 0 0 40px rgba(0,0,0,.6);cursor:default;max-width:100%;overflow-x:auto}
6476
+ .iso-agent{transition:filter .3s}
6477
+ .iso-agent:hover{filter:brightness(1.15)}
6478
+ /* Thought bubble / speech bubble above character */
6479
+ .iso-bubble{font-size:8px;font-family:var(--mono);padding:2px 7px;border-radius:10px 10px 10px 2px;border:1px solid #3a3060;background:rgba(10,8,20,.85);color:#6b7280;white-space:nowrap;max-width:110px;overflow:hidden;text-overflow:ellipsis;line-height:1.4;transition:all .25s;pointer-events:none;backdrop-filter:blur(4px)}
6480
+ .iso-bubble--active{background:rgba(30,20,60,.9);border-color:#6366f1;color:#a5b4fc;animation:isoBubblePop .35s ease;white-space:normal;max-width:110px;word-break:break-word;line-height:1.35}
6481
+ .iso-bubble--orch{font-size:9px;padding:3px 9px;border-radius:12px;border-color:#818cf8;color:#a5b4fc;background:rgba(20,15,50,.9)}
6482
+ @keyframes isoBubblePop{0%{transform:scale(.8) translateY(4px);opacity:.4}100%{transform:scale(1) translateY(0);opacity:1}}
6483
+ .iso-name{font-size:8px;font-family:var(--mono);font-weight:700;letter-spacing:.3px;text-align:center;max-width:80px;white-space:normal;word-break:break-word;line-height:1.3;text-shadow:0 1px 4px rgba(0,0,0,.8);pointer-events:none}
6484
+ /* Desks row — kept for boardroom compat */
6260
6485
  .prl-desks-row{display:flex;gap:8px;align-items:flex-end;flex-wrap:wrap;position:relative;z-index:2;padding-bottom:8px}
6261
- /* Individual desk card — illuminated */
6262
6486
  .prl-desk{display:flex;flex-direction:column;align-items:center;gap:2px;padding:6px 6px 4px;border-radius:12px;background:#1a1535;border:1.5px solid #3a3060;transition:border-color .4s,background .4s,box-shadow .4s;position:relative;min-width:80px;box-shadow:0 2px 8px rgba(0,0,0,.3),inset 0 1px 0 rgba(255,255,255,.06)}
6263
6487
  .prl-desk--active{background:#1e1a45;border-color:var(--dc,#6366f1);box-shadow:0 0 20px rgba(99,102,241,.3),0 0 40px rgba(99,102,241,.1),inset 0 1px 0 rgba(150,130,255,.15)}
6264
6488
  .prl-desk--done{border-color:#2a4a2a;background:#162516}
6265
- /* Action bubble above character */
6266
6489
  .prl-action-bubble{font-size:9px;color:#6b7280;font-family:var(--mono);padding:2px 6px;border-radius:8px;background:#111;border:1px solid #2a2a38;min-height:16px;text-align:center;white-space:normal;word-break:break-word;max-width:88px;line-height:1.3;transition:all .3s}
6267
6490
  .prl-action-bubble--active{color:var(--dc,#6366f1);border-color:var(--dc,#6366f1);background:rgba(99,102,241,.08);animation:parlBubblePop .4s ease}
6268
6491
  @keyframes parlBubblePop{0%{transform:scale(.85);opacity:.5}100%{transform:scale(1);opacity:1}}