nothumanallowed 13.5.6 → 13.5.8

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.6",
3
+ "version": "13.5.8",
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": {
@@ -3790,8 +3790,11 @@ ${writtenSoFar ? `## REPORT WRITTEN SO FAR (for consistency):\n${writtenSoFar.sl
3790
3790
  }
3791
3791
 
3792
3792
  // Estimate token usage (aprox: 1 token ≈ 4 chars)
3793
- const inTokens = Math.ceil((sysPrompt.length + userMsg.length) / 4);
3794
- const outTokens = Math.ceil(fullOutput.length / 4);
3793
+ // Include context + toolData in input estimate since they're sent in the prompt
3794
+ const contextLen = (context || '').length;
3795
+ const toolDataLen = (toolData || '').length;
3796
+ const inTokens = Math.ceil((sysPrompt.length + userMsg.length + contextLen + toolDataLen) / 4);
3797
+ const outTokens = Math.ceil(fullOutputClean.length / 4);
3795
3798
  clearInterval(keepalive);
3796
3799
  sendEvent({ usage: { input: inTokens, output: outTokens } });
3797
3800
  sendEvent({ done: true });
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.6';
8
+ export const VERSION = '13.5.8';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -3626,70 +3626,127 @@ function officeRoomDecor() {
3626
3626
  // Characters are positioned with position:absolute, scale by row for depth.
3627
3627
  var ISO_TILE_W = 72;
3628
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
3629
+ var ISO_ORIGIN_X = 300;
3630
+ var ISO_ORIGIN_Y = 60;
3631
3631
  function isoProject(col, row) {
3632
3632
  return {
3633
3633
  x: ISO_ORIGIN_X + (col - row) * (ISO_TILE_W / 2),
3634
3634
  y: ISO_ORIGIN_Y + (col + row) * (ISO_TILE_H / 2)
3635
3635
  };
3636
3636
  }
3637
- // Draw isometric floor tiles as SVG
3637
+
3638
+ // Full bright office scene SVG background
3638
3639
  function isoFloorSvg(cols, rows) {
3639
- var w = 600; var h = 240;
3640
+ var w = 640; var h = 320;
3640
3641
  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;
3642
+
3643
+ // ── BACK WALL (white/cream) ──────────────────────────────────────────
3644
+ out += \x27<polygon points="0,100 320,20 320,120 0,200" fill="#f5f3ee"/>\x27;
3645
+ out += \x27<polygon points="320,20 640,100 640,200 320,120" fill="#edeae3"/>\x27;
3646
+ out += \x27<line x1="0" y1="100" x2="320" y2="20" stroke="#ccc" stroke-width="1.5"/>\x27;
3647
+ out += \x27<line x1="320" y1="20" x2="640" y2="100" stroke="#bbb" stroke-width="1.5"/>\x27;
3648
+ // Wall corner vertical line
3649
+ out += \x27<line x1="320" y1="20" x2="320" y2="120" stroke="#c8c4bc" stroke-width="1"/>\x27;
3650
+
3651
+ // ── WINDOWS — 3 centered on left wall ────────────────────────────────
3652
+ // Win L1
3653
+ out += \x27<polygon points="30,122 90,102 90,155 30,175" fill="#d0eeff" stroke="#aac8e8" stroke-width="1"/>\x27;
3654
+ out += \x27<line x1="60,112" y1="60" x2="60" y2="130" stroke="#aac8e8" stroke-width=".8"/>\x27;
3655
+ out += \x27<line x1="30,148" y1="148" x2="90" y2="128" stroke="#aac8e8" stroke-width=".8"/>\x27;
3656
+ out += \x27<polygon points="30,122 90,102 90,108 30,128" fill="#b8ddf8" opacity=".4"/>\x27;
3657
+ out += \x27<polygon points="30,122 90,102 90,155 30,175" fill="rgba(200,230,255,.12)"/>\x27;
3658
+ // Win L2 (center-left)
3659
+ out += \x27<polygon points="110,102 175,80 175,132 110,154" fill="#d0eeff" stroke="#aac8e8" stroke-width="1"/>\x27;
3660
+ out += \x27<polygon points="110,102 175,80 175,88 110,110" fill="#b8ddf8" opacity=".4"/>\x27;
3661
+ out += \x27<line x1="142" y1="91" x2="142" y2="143" stroke="#aac8e8" stroke-width=".8"/>\x27;
3662
+ out += \x27<line x1="110" y1="128" x2="175" y2="106" stroke="#aac8e8" stroke-width=".8"/>\x27;
3663
+ out += \x27<polygon points="110,102 175,80 175,132 110,154" fill="rgba(200,230,255,.10)"/>\x27;
3664
+ // Win L3 (center)
3665
+ out += \x27<polygon points="195,88 255,68 255,118 195,138" fill="#d0eeff" stroke="#aac8e8" stroke-width="1"/>\x27;
3666
+ out += \x27<polygon points="195,88 255,68 255,76 195,96" fill="#b8ddf8" opacity=".4"/>\x27;
3667
+ out += \x27<line x1="225" y1="78" x2="225" y2="128" stroke="#aac8e8" stroke-width=".8"/>\x27;
3668
+ out += \x27<line x1="195" y1="113" x2="255" y2="93" stroke="#aac8e8" stroke-width=".8"/>\x27;
3669
+ // Sunlight shafts from windows
3670
+ out += \x27<polygon points="50,155 90,140 120,260 80,275" fill="rgba(255,250,220,.06)"/>\x27;
3671
+ out += \x27<polygon points="130,132 175,116 200,260 155,275" fill="rgba(255,250,220,.05)"/>\x27;
3672
+ out += \x27<polygon points="210,116 255,100 270,260 225,275" fill="rgba(255,250,220,.04)"/>\x27;
3673
+
3674
+ // ── ART on left wall ────────────────────────────────────────────────
3675
+ // Frame 1 (small painting near top-right of left wall)
3676
+ out += \x27<polygon points="275,72 310,60 310,90 275,102" fill="#e8e4dc" stroke="#bbb" stroke-width="1"/>\x27;
3677
+ out += \x27<polygon points="278,74 307,63 307,88 278,99" fill="#7a9ed4"/>\x27;
3678
+ out += \x27<polygon points="285,77 303,70 303,84 285,91" fill="#a8c4e8"/>\x27;
3679
+ out += \x27<ellipse cx="294" cy="80" rx="5" ry="4" fill="#fff" opacity=".5"/>\x27;
3680
+
3681
+ // ── PLANT (bottom-left corner) ───────────────────────────────────────
3682
+ out += \x27<ellipse cx="30" cy="188" rx="10" ry="5" fill="#2d4a1e" opacity=".7"/>\x27;
3683
+ out += \x27<rect x="25" y="178" width="10" height="10" rx="2" fill="#5a3a1a"/>\x27;
3684
+ out += \x27<path d="M30 178 C25 165 18 155 22 148 C26 142 30 148 30 178" fill="#3a7a1e"/>\x27;
3685
+ out += \x27<path d="M30 178 C35 162 42 155 38 148 C34 142 30 150 30 178" fill="#4a8a26"/>\x27;
3686
+ out += \x27<path d="M30 175 C22 168 16 160 18 153" stroke="#3a7a1e" stroke-width="2" fill="none" stroke-linecap="round"/>\x27;
3687
+ out += \x27<path d="M30 170 C38 165 44 158 42 151" stroke="#4a8a26" stroke-width="2" fill="none" stroke-linecap="round"/>\x27;
3688
+
3689
+ // ── CEILING CHANDELIER with glow ─────────────────────────────────────
3690
+ out += \x27<line x1="320" y1="0" x2="320" y2="18" stroke="#999" stroke-width="2"/>\x27;
3691
+ // Chandelier body
3692
+ out += \x27<ellipse cx="320" cy="18" rx="24" ry="7" fill="#d4c88a" stroke="#b8a860" stroke-width="1.5"/>\x27;
3693
+ out += \x27<ellipse cx="320" cy="22" rx="20" ry="5" fill="#e8d898"/>\x27;
3694
+ // Bulbs hanging down
3695
+ out += \x27<circle cx="305" cy="28" r="5" fill="#fffbe8" filter="url(#glow)"/>\x27;
3696
+ out += \x27<circle cx="320" cy="26" r="6" fill="#fffbe8" filter="url(#glow)"/>\x27;
3697
+ out += \x27<circle cx="335" cy="28" r="5" fill="#fffbe8" filter="url(#glow)"/>\x27;
3698
+ out += \x27<line x1="305" y1="22" x2="305" y2="28" stroke="#aaa" stroke-width=".8"/>\x27;
3699
+ out += \x27<line x1="320" y1="22" x2="320" y2="26" stroke="#aaa" stroke-width=".8"/>\x27;
3700
+ out += \x27<line x1="335" y1="22" x2="335" y2="28" stroke="#aaa" stroke-width=".8"/>\x27;
3701
+ // Light cone glow on floor/walls
3702
+ out += \x27<radialGradient id="chandelierGlow" cx="50%" cy="0%" r="60%"><stop offset="0%" stop-color="rgba(255,245,180,.22)"/><stop offset="100%" stop-color="rgba(255,245,180,0)"/></radialGradient>\x27;
3703
+ out += \x27<filter id="glow"><feGaussianBlur stdDeviation="3" result="blur"/><feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge></filter>\x27;
3704
+ out += \x27<ellipse cx="320" cy="180" rx="200" ry="120" fill="url(#chandelierGlow)"/>\x27;
3705
+
3706
+ // ── PARQUET FLOOR — long narrow planks ───────────────────────────────
3707
+ // Draw rectangular plank tiles in iso projection
3708
+ var PLANK_W = ISO_TILE_W; var PLANK_H = ISO_TILE_H;
3709
+ var plankColors = [\x27#b5854a\x27,\x27#a8783c\x27,\x27#bf9055\x27,\x27#c49a60\x27,\x27#a07234\x27];
3710
+ for (var r2 = 0; r2 < rows; r2++) {
3711
+ for (var c2 = 0; c2 < cols; c2++) {
3712
+ var pp = isoProject(c2, r2);
3713
+ var ptx = pp.x; var pty = pp.y + 110;
3714
+ var pIdx = (r2 * 3 + c2) % plankColors.length;
3715
+ var pFill = plankColors[pIdx];
3716
+ var ppts = ptx+\x27,\x27+pty+\x27 \x27+(ptx+PLANK_W/2)+\x27,\x27+(pty+PLANK_H/2)+\x27 \x27+ptx+\x27,\x27+(pty+PLANK_H)+\x27 \x27+(ptx-PLANK_W/2)+\x27,\x27+(pty+PLANK_H/2);
3717
+ out += \x27<polygon points="\x27+ppts+\x27" fill="\x27+pFill+\x27" stroke="#8a6028" stroke-width=".7" opacity=".95"/>\x27;
3718
+ // Plank grain lines (long horizontal across plank)
3719
+ out += \x27<line x1="\x27+(ptx-PLANK_W/2+4)+\x27" y1="\x27+(pty+PLANK_H/2)+\x27" x2="\x27+(ptx+PLANK_W/2-4)+\x27" y2="\x27+(pty+PLANK_H/2)+\x27" stroke="rgba(0,0,0,.06)" stroke-width=".5"/>\x27;
3671
3720
  }
3672
3721
  }
3722
+ // Floor highlight (lamp reflection)
3723
+ out += \x27<ellipse cx="320" cy="220" rx="90" ry="30" fill="rgba(255,250,200,.08)"/>\x27;
3724
+
3673
3725
  out += \x27</svg>\x27;
3674
3726
  return out;
3675
3727
  }
3676
- // Small isometric desk object at iso position
3728
+ // Small isometric desk object at iso position — bright wood
3677
3729
  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+
3730
+ var ac = accentColor || \x27#888\x27;
3731
+ var doneDesk = ac === \x27#22c55e\x27;
3732
+ return \x27<svg viewBox="0 0 72 44" width="72" height="44" xmlns="http://www.w3.org/2000/svg" style="position:absolute;left:\x27+(x-36)+\x27px;top:\x27+(y+8)+\x27px;z-index:\x27+(Math.round(y))+\x27;pointer-events:none">\x27+
3733
+ // Desk top face warm wood
3734
+ \x27<polygon points="36,4 62,18 36,32 10,18" fill="#d4a855" stroke="#b8893a" stroke-width="1"/>\x27+
3735
+ \x27<polygon points="36,4 62,18 36,32 10,18" fill="rgba(255,255,255,.08)"/>\x27+
3736
+ // Grain lines on top
3737
+ \x27<line x1="22" y1="18" x2="50" y2="10" stroke="rgba(0,0,0,.1)" stroke-width=".8"/>\x27+
3738
+ \x27<line x1="22" y1="22" x2="50" y2="14" stroke="rgba(0,0,0,.07)" stroke-width=".8"/>\x27+
3682
3739
  // 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+
3740
+ \x27<polygon points="62,18 62,36 36,50 36,32" fill="#a8783c" stroke="#8a5c28" stroke-width=".8"/>\x27+
3684
3741
  // 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+
3742
+ \x27<polygon points="10,18 36,32 36,50 10,36" fill="#c49050" stroke="#9e7234" stroke-width=".8"/>\x27+
3743
+ // Monitor on desk — light screen
3744
+ \x27<polygon points="28,10 44,16 44,26 28,20" fill="#1e2840" stroke="\x27+ac+\x27" stroke-width="1.2"/>\x27+
3745
+ \x27<polygon points="29,11 43,17 43,25 29,19" fill="\x27+(doneDesk?\x27#0d2010\x27:\x27#0d1a2e\x27)+\x27"/>\x27+
3746
+ // Screen glow lines
3747
+ \x27<line x1="31" y1="14" x2="41" y2="18" stroke="\x27+ac+\x27" stroke-width=".9" opacity=".8"/>\x27+
3748
+ \x27<line x1="31" y1="17" x2="40" y2="20" stroke="\x27+ac+\x27" stroke-width=".9" opacity=".5"/>\x27+
3749
+ \x27<line x1="31" y1="20" x2="39" y2="23" stroke="\x27+ac+\x27" stroke-width=".9" opacity=".3"/>\x27+
3693
3750
  \x27</svg>\x27;
3694
3751
  }
3695
3752
  // Isometric JRPG sprite character — top-down 3/4 perspective, Pokemon-style
@@ -3835,10 +3892,8 @@ function renderStudioNodes() {
3835
3892
 
3836
3893
  // ── ISO scene dimensions ───────────────────────────────────────────────────
3837
3894
  var ISO_COLS = Math.max(nodes.length, 1);
3838
- var SCENE_W = 600; var SCENE_H = 260;
3895
+ var SCENE_W = 640; var SCENE_H = 420;
3839
3896
 
3840
- // Distribute agents along row=1 of the iso grid, spaced evenly
3841
- // Orchestrator walks on row=0 (further back = smaller)
3842
3897
  var agentsHtml = \x27\x27;
3843
3898
  nodes.forEach(function(n, idx) {
3844
3899
  var isActive = n.status === \x27running\x27;
@@ -3846,39 +3901,34 @@ function renderStudioNodes() {
3846
3901
  var isErr = n.status === \x27error\x27;
3847
3902
  var lbl = n.label || n.agent;
3848
3903
  var pal = agentPalette(lbl);
3849
- var accentColor = isActive ? \x27#6366f1\x27 : (isDone ? \x27#22c55e\x27 : (isErr ? \x27#ef4444\x27 : \x27#3a3060\x27));
3904
+ var accentColor = isActive ? \x27#6366f1\x27 : (isDone ? \x27#22c55e\x27 : (isErr ? \x27#ef4444\x27 : \x27#999\x27));
3850
3905
 
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;
3906
+ // Spread agents evenly across front rows
3907
+ var colStep = ISO_COLS > 1 ? 5 / (ISO_COLS - 1) : 2;
3908
+ var col = (ISO_COLS === 1) ? 2.5 : idx * colStep;
3909
+ var row = 3.5;
3855
3910
  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;
3911
+ var px = pos.x; var py = pos.y + 80;
3912
+ var charScale = 1.1;
3859
3913
  var zIdx = Math.round(py + 100);
3860
3914
 
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
-
3915
+ // Bubble: only show when active (live text) or done (checkmark)
3916
+ var bubbleText = isActive ? \x27...lavora\x27 : (isDone ? \x27\u2714 fatto\x27 : (isErr ? \x27\u2715 errore\x27 : \x27\x27));
3917
+ var bubbleColor = isActive ? \x27#6366f1\x27 : (isDone ? \x27#22c55e\x27 : \x27#ef4444\x27);
3869
3918
  var charSvg = isoCharSvg({skin: pal.skin, shirt: pal.shirt, hair: pal.hair,
3870
3919
  isActive: isActive, isDone: isDone, scale: charScale, accentColor: accentColor});
3920
+ var charW = Math.round(48 * charScale); var charH = Math.round(64 * charScale);
3871
3921
 
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;
3922
+ 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:3px;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;
3923
+ // Bubble bigger, left-aligned (no mirror issue)
3924
+ if (bubbleText || isActive) {
3925
+ 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;min-width:80px;max-width:140px;font-size:10px;padding:4px 10px;text-align:left">\x27+esc(bubbleText)+\x27</div>\x27;
3926
+ } else {
3927
+ agentsHtml += \x27<div class="iso-bubble" id="isobubble_\x27+idx+\x27" style="visibility:hidden;min-width:80px"></div>\x27;
3928
+ }
3878
3929
  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
3930
+ agentsHtml += \x27<div class="iso-name" style="font-size:10px;font-weight:700;color:\x27+(isDone?\x27#22c55e\x27:(isActive?\x27#818cf8\x27:(isErr?\x27#ef4444\x27:\x27#555\x27)))+\x27;text-shadow:0 1px 3px rgba(255,255,255,.8)">\x27+esc(lbl)+\x27</div>\x27;
3931
+ // Desk lighter wood color for bright office
3882
3932
  agentsHtml += isoDeskSvg(px, py - charH/2, accentColor);
3883
3933
  agentsHtml += \x27</div>\x27;
3884
3934
  });
@@ -3886,17 +3936,16 @@ function renderStudioNodes() {
3886
3936
  // ── Orchestrator ──────────────────────────────────────────────────────────
3887
3937
  var orchHtml = \x27\x27;
3888
3938
  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];
3939
+ var oPh = isoProject(ISO_COLS / 2 + 0.5, 1.2);
3891
3940
  var orchDone = !hasActive && doneCount === totalCount;
3892
3941
  var orchAnim = hasActive ? \x27prl-master-walk\x27 : (orchDone ? \x27prl-master-done\x27 : \x27\x27);
3893
3942
  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+
3943
+ ? (\x27Step \x27+doneCount+\x27/\x27+totalCount)
3944
+ : (orchDone ? \x27Completato!\x27 : \x27Pianificazione\x27);
3945
+ orchHtml = \x27<div class="prl-master \x27+orchAnim+\x27" id="wfOrch" style="position:absolute;left:\x27+(oPh.x-28)+\x27px;top:\x27+(oPh.y+60)+\x27px;z-index:50;display:flex;flex-direction:column;align-items:center;gap:3px">\x27+
3946
+ \x27<div class="iso-bubble iso-bubble--orch" id="wfOrchBubble" style="border-color:#6366f1;color:#6366f1;max-width:140px;font-size:10px;padding:4px 10px">\x27+esc(orchStatus)+\x27</div>\x27+
3898
3947
  isoOrchSvg(hasActive, doneCount / Math.max(totalCount,1))+
3899
- \x27<div class="prl-master-label" style="color:#818cf8;font-size:8px">Orchestratore</div>\x27+
3948
+ \x27<div class="prl-master-label" style="color:#6366f1;font-size:9px;font-weight:700;text-shadow:0 1px 3px rgba(255,255,255,.8)">Orchestratore</div>\x27+
3900
3949
  \x27</div>\x27;
3901
3950
  }
3902
3951
 
@@ -3908,11 +3957,13 @@ function renderStudioNodes() {
3908
3957
  el.innerHTML =
3909
3958
  \x27<div class="prl-wrap" style="border-color:\x27+phaseColor2+\x2744;padding-bottom:8px">\x27+
3910
3959
  \x27<div class="prl-header"><span class="prl-phase-chip" style="--pc:\x27+phaseColor2+\x27">\x27+phaseLabel2+\x27</span></div>\x27+
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)+
3960
+ \x27<div style="display:flex;justify-content:center;width:100%;overflow-x:auto">\x27+
3961
+ \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:12px;background:#f0ede6">\x27+
3962
+ isoFloorSvg(ISO_COLS + 2, 5)+
3913
3963
  agentsHtml+
3914
3964
  orchHtml+
3915
3965
  \x27</div>\x27+
3966
+ \x27</div>\x27+
3916
3967
  \x27</div>\x27;
3917
3968
  }
3918
3969
 
@@ -4440,7 +4491,7 @@ async function runStudio() {
4440
4491
  nudge = document.createElement(\x27div\x27);
4441
4492
  nudge.id = \x27studioParliamentNudge\x27;
4442
4493
  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;
4443
- 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;
4494
+ 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;
4444
4495
  var tokenBar = document.getElementById(\x27studioTokenBar\x27);
4445
4496
  if (tokenBar && tokenBar.parentNode) tokenBar.parentNode.insertBefore(nudge, tokenBar.parentNode.firstChild);
4446
4497
  }
@@ -4511,10 +4562,10 @@ async function runStudio() {
4511
4562
  proposals.push({agent: \x27Context\x27, label: \x27Contesto workflow\x27, output: context});
4512
4563
  }
4513
4564
  if (proposals.length >= 2) {
4514
- studioLog(\x27Parlamento\x27, \x27&#x2656;\x27, \x27Avvio deliberazioneRound 2 cross-reading tra agenti...\x27, \x27system\x27);
4515
- // Add Parliament node to pipeline visual
4565
+ studioLog(\x27Consiglio\x27, \x27&#x1f4bc;\x27, \x27Avvio Consigliogli agenti si riuniscono per confrontare e raffinare i risultati...\x27, \x27system\x27);
4566
+ // Add Consiglio node to pipeline visual
4516
4567
  var parlNodeIdx = studioState.nodes.length;
4517
- studioState.nodes.push({icon:\x27&#x2656;\x27, agent:\x27Parliament\x27, label:\x27Parlamento\x27, status:\x27running\x27, output:\x27\x27, _rendered:false});
4568
+ studioState.nodes.push({icon:\x27&#x1f4bc;\x27, agent:\x27Consiglio\x27, label:\x27Consiglio\x27, status:\x27running\x27, output:\x27\x27, _rendered:false});
4518
4569
  renderStudioNodes();
4519
4570
 
4520
4571
  // ── Parliament visual block ──────────────────────────────────────
@@ -4532,10 +4583,10 @@ async function runStudio() {
4532
4583
 
4533
4584
  var phaseColor = {r1:\x27#6366f1\x27,r2:\x27#22d3ee\x27,r3:\x27#f59e0b\x27,done:\x27#22c55e\x27}[phase]||\x27#6366f1\x27;
4534
4585
  var phaseLabel = {
4535
- r1:\x27Round 1 \u2014 Ogni agente analizza\x27,
4536
- r2:\x27Round 2 \u2014 Cross-reading e raffinamento\x27,
4537
- r3:\x27Round 3 \u2014 HERALD media le divergenze\x27,
4538
- done:\x27Deliberazione completata\x27
4586
+ r1:\x27Consiglio \u2014 Analisi individuale\x27,
4587
+ r2:\x27Consiglio \u2014 Confronto e raffinamento\x27,
4588
+ r3:\x27Consiglio \u2014 Sintesi finale HERALD\x27,
4589
+ done:\x27Consiglio concluso \u2014 consenso raggiunto\x27
4539
4590
  }[phase]||\x27\x27;
4540
4591
  var n = proposals.length;
4541
4592
  var doneCount = Object.keys(parlDoneAgents).length;
@@ -4646,7 +4697,7 @@ async function runStudio() {
4646
4697
  var orchHtml = \x27<div class="br-orch" id="brOrch">\x27+
4647
4698
  \x27<div class="br-orch-speech" id="brOrchSpeech"></div>\x27+
4648
4699
  orchSvg+
4649
- \x27<div class="br-orch-label">Orchestratore</div>\x27+
4700
+ \x27<div class="br-orch-label">NHA Studio</div>\x27+
4650
4701
  \x27</div>\x27;
4651
4702
 
4652
4703
  // Phase indicator + progress
@@ -4685,17 +4736,17 @@ async function runStudio() {
4685
4736
  if (convEl && convergence != null) {
4686
4737
  convEl.style.display = \x27block\x27;
4687
4738
  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+
4688
- \x27<div class="br-conv-text"><strong>\u2714 Convergenza \x27+convergence+\x27%</strong> \u2014 HERALD ha sintetizzato il consenso finale.</div>\x27;
4739
+ \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;
4689
4740
  }
4690
4741
 
4691
4742
  // Orchestrator speech bubble
4692
4743
  var orchSpeech = document.getElementById(\x27brOrchSpeech\x27);
4693
4744
  if (orchSpeech) {
4694
4745
  var orchSpeeches = {
4695
- r1: [\x27Analizzate!\x27,\x27Al lavoro!\x27,\x27Forza!\x27,\x27Pensate!\x27],
4696
- r2: [\x27Confrontate!\x27,\x27Leggete tutto!\x27,\x27Raffinare!\x27,\x27Scambiate!\x27],
4697
- r3: [\x27Mediazione!\x27,\x27Convergete!\x27,\x27Trovate accordo!\x27],
4698
- done: [\x27Ottimo!\x27,\x27Consenso!\x27,\x27Bene!\x27]
4746
+ r1: [\x27Analisi in corso...\x27,\x27Ogni team al lavoro\x27,\x27Raccolta dati\x27,\x27Prima bozza...\x27],
4747
+ r2: [\x27Confronto in corso\x27,\x27Cross-review...\x27,\x27Raffinamento\x27,\x27Scambio idee\x27],
4748
+ r3: [\x27Sintesi finale\x27,\x27Convergenza...\x27,\x27Accordo in vista\x27],
4749
+ done: [\x27Consiglio concluso\x27,\x27Consenso raggiunto\x27,\x27Report pronto\x27]
4699
4750
  };
4700
4751
  var spArr = orchSpeeches[phase] || orchSpeeches.r1;
4701
4752
  if (phase === \x27done\x27) {
@@ -4716,6 +4767,42 @@ async function runStudio() {
4716
4767
  orchEl.style.setProperty(\x27--oc\x27, phaseColor);
4717
4768
  }
4718
4769
 
4770
+ // Draw attention line from orchestrator to active agent seat
4771
+ var orchEl2 = document.getElementById(\x27brOrch\x27);
4772
+ var brRoom2 = orchEl2 ? orchEl2.closest(\x27.br-room\x27) : null;
4773
+ if (brRoom2) {
4774
+ var existLine = brRoom2.querySelector(\x27.br-attention-line\x27);
4775
+ if (existLine) existLine.parentNode.removeChild(existLine);
4776
+ if (activeLabel && phase !== \x27done\x27) {
4777
+ var aLblSafe = activeLabel.replace(/[^a-zA-Z0-9_-]/g,\x27_\x27);
4778
+ var activeSeatEl = document.getElementById(\x27brseat_\x27+aLblSafe);
4779
+ if (activeSeatEl && orchEl2) {
4780
+ var roomRect = brRoom2.getBoundingClientRect();
4781
+ var orchRect = orchEl2.getBoundingClientRect();
4782
+ var seatRect = activeSeatEl.getBoundingClientRect();
4783
+ var x1 = orchRect.left + orchRect.width/2 - roomRect.left;
4784
+ var y1 = orchRect.top + orchRect.height/2 - roomRect.top;
4785
+ var x2 = seatRect.left + seatRect.width/2 - roomRect.left;
4786
+ var y2 = seatRect.top + seatRect.height/2 - roomRect.top;
4787
+ var lineSvg = document.createElementNS(\x27http://www.w3.org/2000/svg\x27, \x27svg\x27);
4788
+ lineSvg.setAttribute(\x27class\x27, \x27br-attention-line\x27);
4789
+ lineSvg.style.cssText = \x27position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:10;overflow:visible\x27;
4790
+ var lineEl = document.createElementNS(\x27http://www.w3.org/2000/svg\x27, \x27line\x27);
4791
+ lineEl.setAttribute(\x27x1\x27, String(Math.round(x1)));
4792
+ lineEl.setAttribute(\x27y1\x27, String(Math.round(y1)));
4793
+ lineEl.setAttribute(\x27x2\x27, String(Math.round(x2)));
4794
+ lineEl.setAttribute(\x27y2\x27, String(Math.round(y2)));
4795
+ lineEl.setAttribute(\x27stroke\x27, \x27#6366f1\x27);
4796
+ lineEl.setAttribute(\x27stroke-width\x27, \x272\x27);
4797
+ lineEl.setAttribute(\x27stroke-dasharray\x27, \x276 4\x27);
4798
+ lineEl.setAttribute(\x27opacity\x27, \x27.6\x27);
4799
+ lineEl.style.animation = \x27brDashFlow 1s linear infinite\x27;
4800
+ lineSvg.appendChild(lineEl);
4801
+ brRoom2.appendChild(lineSvg);
4802
+ }
4803
+ }
4804
+ }
4805
+
4719
4806
  // Update each agent seat state
4720
4807
  proposals.forEach(function(prop) {
4721
4808
  var lbl = prop.label || prop.agent;
@@ -5198,7 +5285,7 @@ async function runStudio() {
5198
5285
  if (r2StartM) {
5199
5286
  var r2Label = r2StartM[1];
5200
5287
  parlActiveAgent = r2Label;
5201
- studioLog(r2Label, \x27&#x2656;\x27, \x27\x27, \x27agent\x27, false);
5288
+ studioLog(r2Label, \x27&#x1f4bc;\x27, \x27\x27, \x27agent\x27, false);
5202
5289
  var delEnts2 = document.querySelectorAll(\x27.studio-log-entry\x27);
5203
5290
  var delL2 = delEnts2[delEnts2.length - 1];
5204
5291
  if (delL2) {
@@ -5232,10 +5319,25 @@ async function runStudio() {
5232
5319
  var delEntries = document.querySelectorAll(\x27.studio-log-entry\x27);
5233
5320
  var delLast = delEntries[delEntries.length - 1];
5234
5321
  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); }
5322
+ // Mirror live token to the active agent boardroom bubble
5323
+ if (parlActiveAgent) {
5324
+ var brSafe2 = parlActiveAgent.replace(new RegExp(\x27[^a-zA-Z0-9_-]\x27,\x27g\x27),\x27_\x27);
5325
+ var brLiveBubble = document.getElementById(\x27brbubble_\x27+brSafe2);
5326
+ if (brLiveBubble) {
5327
+ var rawTok = dev.token.replace(new RegExp(\x27[\\r\\n]+\x27,\x27g\x27),\x27 \x27);
5328
+ var safeTok = rawTok.replace(/&/g,\x27&amp;\x27).replace(/</g,\x27&lt;\x27).replace(/>/g,\x27&gt;\x27);
5329
+ var truncTok = rawTok.length > 60 ? rawTok.slice(-60) : rawTok;
5330
+ var safeTrunc = truncTok.replace(/&/g,\x27&amp;\x27).replace(/</g,\x27&lt;\x27).replace(/>/g,\x27&gt;\x27);
5331
+ brLiveBubble.style.display = \x27\x27;
5332
+ 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;
5333
+ brLiveBubble.style.borderColor = \x27#6366f1\x27;
5334
+ brLiveBubble.style.color = \x27#a5b4fc\x27;
5335
+ }
5336
+ }
5235
5337
  }
5236
5338
  } else if (dev.deliberation_r2) {
5237
5339
  var r2d = dev.deliberation_r2;
5238
- studioLog(r2d.label || r2d.agent, \x27&#x2656;\x27, \x27[R2] \x27 + (r2d.output || \x27\x27), \x27agent\x27, true);
5340
+ studioLog(r2d.label || r2d.agent, \x27&#x1f4bc;\x27, \x27[Consiglio R2] \x27 + (r2d.output || \x27\x27), \x27agent\x27, true);
5239
5341
  var ni2 = studioState.nodes.findIndex(function(x){return x.agent===r2d.agent;});
5240
5342
  if (ni2 >= 0) { studioState.nodes[ni2].output = r2d.output; studioState.nodes[ni2].status = \x27done\x27; }
5241
5343
  studioAddTokens(0, Math.ceil((r2d.output||'').length / 4));
@@ -5252,12 +5354,12 @@ async function runStudio() {
5252
5354
  context = dev.deliberation_r3.output || context;
5253
5355
  } else if (dev.deliberation_done) {
5254
5356
  var r2Conv = Math.round((dev.r2_convergence || 0) * 100);
5255
- studioLog(\x27Parlamento\x27, \x27&#x2656;\x27, \x27Deliberazione completa — convergenza R2: \x27 + r2Conv + \x27%\x27, \x27system\x27);
5357
+ studioLog(\x27Consiglio\x27, \x27&#x1f4bc;\x27, \x27Consiglio concluso — convergenza R2: \x27 + r2Conv + \x27%\x27, \x27system\x27);
5256
5358
  if (dev.mediation) { context = dev.mediation; }
5257
5359
  renderParlBlock(\x27done\x27, null, r2Conv);
5258
5360
  if (studioState.nodes[parlNodeIdx]) {
5259
5361
  studioState.nodes[parlNodeIdx].status = \x27done\x27;
5260
- studioState.nodes[parlNodeIdx].label = \x27Parlamento (\x27 + r2Conv + \x27%)\x27;
5362
+ studioState.nodes[parlNodeIdx].label = \x27Consiglio (\x27 + r2Conv + \x27%)\x27;
5261
5363
  renderStudioNodes();
5262
5364
  }
5263
5365
  delDone = true;
@@ -5270,7 +5372,7 @@ async function runStudio() {
5270
5372
  }
5271
5373
  } catch(e3) {
5272
5374
  if (e3.name !== \x27AbortError\x27) {
5273
- studioLog(\x27Parlamento\x27, \x27&#x2656;\x27, \x27Deliberazione non disponibile: \x27 + (e3.message || String(e3)), \x27error\x27);
5375
+ studioLog(\x27Consiglio\x27, \x27&#x1f4bc;\x27, \x27Consiglio non disponibile: \x27 + (e3.message || String(e3)), \x27error\x27);
5274
5376
  // Do NOT hide the parliament block if it already has content — user is watching it
5275
5377
  }
5276
5378
  }
@@ -5341,7 +5443,9 @@ function saveStudioSession(task, nodes, log, result) {
5341
5443
  var sessions = JSON.parse(localStorage.getItem('nha_studio_sessions') || '[]');
5342
5444
  // Save parliament block HTML if present (static snapshot — animations not needed on restore)
5343
5445
  var parlEl = document.getElementById('studioParliamentBlock');
5344
- var parlHtml = (parlEl && parlEl.style.display !== 'none') ? parlEl.innerHTML : null;
5446
+ var parlRaw = (parlEl && parlEl.style.display !== 'none') ? parlEl.innerHTML : null;
5447
+ // Limit parlHtml to 30KB to avoid localStorage quota issues
5448
+ var parlHtml = parlRaw ? (parlRaw.length > 30000 ? parlRaw.slice(0, 30000) : parlRaw) : null;
5345
5449
  sessions.unshift({
5346
5450
  id: Date.now(),
5347
5451
  task: task,
@@ -5352,8 +5456,14 @@ function saveStudioSession(task, nodes, log, result) {
5352
5456
  log: log.map(function(e){return {agent:e.agent,icon:e.icon,text:e.text,type:e.type,time:e.time};}),
5353
5457
  ts: new Date().toLocaleString()
5354
5458
  });
5355
- sessions = sessions.slice(0, 20); // keep last 20
5356
- localStorage.setItem('nha_studio_sessions', JSON.stringify(sessions));
5459
+ sessions = sessions.slice(0, 10); // keep last 10 (save space)
5460
+ try {
5461
+ localStorage.setItem('nha_studio_sessions', JSON.stringify(sessions));
5462
+ } catch(qe) {
5463
+ // Quota exceeded: save without canvas/parlHtml
5464
+ sessions[0].canvas = null; sessions[0].parlHtml = null;
5465
+ try { localStorage.setItem('nha_studio_sessions', JSON.stringify(sessions)); } catch(e2) {}
5466
+ }
5357
5467
  } catch(e) {}
5358
5468
  }
5359
5469
 
@@ -5403,6 +5513,10 @@ function restoreStudioSession(idx) {
5403
5513
  if (s.parlHtml) {
5404
5514
  parlEl.innerHTML = s.parlHtml;
5405
5515
  parlEl.style.display = 'block';
5516
+ // Scroll into view after DOM settles
5517
+ setTimeout(function() {
5518
+ if (parlEl) parlEl.scrollIntoView({behavior: 'smooth', block: 'start'});
5519
+ }, 150);
5406
5520
  } else {
5407
5521
  parlEl.style.display = 'none';
5408
5522
  }
@@ -5546,13 +5660,16 @@ function runStudioStep(idx, node, task, context, stepDef, signal) {
5546
5660
  if (tb) {
5547
5661
  if (isStatus) {
5548
5662
  var st = ev.token.replace(new RegExp(\x27[\\\\r\\\\n]+\x27,\x27g\x27), \x27 \x27);
5549
- // Render [Searching: "query"] as a styled search chip
5550
- var srchM = st.match(/^\\[Searching:\\s*"([^"]+)"\\]\\s*$/);
5663
+ // Strip surrounding brackets for display
5664
+ var stLabel = st.replace(new RegExp(\x27^\\\\[\x27), \x27\x27).replace(new RegExp(\x27\\\\]\\\\s*$\x27), \x27\x27).trim();
5665
+ // Special chip for Searching
5666
+ var srchM = st.match(new RegExp(\x27^\\\\[Searching:\\\\s*"([^"]+)"\\\\]\\\\s*$\x27));
5551
5667
  if (srchM) {
5552
5668
  var qEsc = srchM[1].replace(/&/g,\x27&amp;\x27).replace(/</g,\x27&lt;\x27).replace(/>/g,\x27&gt;\x27);
5553
- tb.innerHTML = \x27<span style="display:inline-flex;align-items:center;gap:6px;background:#1c1c28;border:1px solid #6366f133;border-radius:20px;padding:3px 10px 3px 8px;font-size:11px;font-family:var(--mono)"><span style="color:#6366f1">&#128269;</span><span style="color:var(--dim)">Cercando</span><strong style="color:#a5b4fc">\x27 + qEsc + \x27</strong><span style="color:#6366f1;animation:pulse 1s infinite">&#183;&#183;&#183;</span></span>\x27;
5669
+ tb.innerHTML = \x27<span class="iso-status-chip"><span class="iso-status-dot"></span>&#128269; <span style="color:var(--dim)">Cercando</span> <strong style="color:#a5b4fc">\x27+qEsc+\x27</strong></span>\x27;
5554
5670
  } else {
5555
- tb.textContent = st;
5671
+ var stEsc = stLabel.replace(/&/g,\x27&amp;\x27).replace(/</g,\x27&lt;\x27).replace(/>/g,\x27&gt;\x27);
5672
+ tb.innerHTML = \x27<span class="iso-status-chip"><span class="iso-status-dot"></span>\x27+stEsc+\x27</span>\x27;
5556
5673
  }
5557
5674
  } else {
5558
5675
  // Word-by-word streaming: APPEND new chars, never overwrite
@@ -5639,9 +5756,14 @@ function runStudioStep(idx, node, task, context, stepDef, signal) {
5639
5756
  }
5640
5757
  }
5641
5758
  if (ev.usage) {
5759
+ // Backend sends accurate usage at end: add input, replace out estimate with real value
5642
5760
  var uIn = ev.usage.input||0; var uOut = ev.usage.output||0;
5643
- stepTokensIn += uIn; stepTokensOut += uOut;
5644
- studioAddTokens(uIn, uOut);
5761
+ // Correct output: remove per-token estimate already added, replace with real count
5762
+ var outDiff = uOut - stepTokensOut;
5763
+ studioTokens.out = Math.max(0, studioTokens.out + outDiff);
5764
+ stepTokensIn += uIn; stepTokensOut = uOut;
5765
+ studioTokens.in += uIn;
5766
+ studioUpdateTokenBar();
5645
5767
  } else if (ev.token && !isStatus) {
5646
5768
  var est = Math.ceil(ev.token.length/4);
5647
5769
  stepTokensOut += est; studioTokens.out += est; studioUpdateTokenBar();
@@ -5779,7 +5901,7 @@ function renderStudio(el) {
5779
5901
  '</div>' +
5780
5902
  '<label style="display:flex;align-items:center;gap:8px;margin-top:8px;cursor:pointer;user-select:none">' +
5781
5903
  '<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">' +
5782
- '<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>' +
5904
+ '<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>' +
5783
5905
  '</label>' +
5784
5906
  '</div>' +
5785
5907
 
@@ -5805,7 +5927,6 @@ function renderStudio(el) {
5805
5927
  '<div class="studio-log" id="studioLog" style="display:none"></div>' +
5806
5928
  '<div id="studioParliamentBlock" style="display:none;margin-bottom:12px"></div>' +
5807
5929
  '<div id="studioResult"></div>' +
5808
- '<div id="studioSessionsBar" style="margin-top:16px;display:none"></div>' +
5809
5930
  '</div>' +
5810
5931
 
5811
5932
  // ── AGENT SIDEBAR ──
@@ -5818,6 +5939,7 @@ function renderStudio(el) {
5818
5939
  '<div style="font-size:9px;color:var(--dim);margin-bottom:8px">Click to add to pipeline</div>' +
5819
5940
  '<div id="sideToolsList">'+toolsHtml+'</div>' +
5820
5941
  '<div id="sideAgentsList" style="display:none">'+specialistHtml+'</div>' +
5942
+ '<div id="studioSessionsBar" style="margin-top:16px;display:none"></div>' +
5821
5943
  '</div>' +
5822
5944
  '</div>';
5823
5945
 
@@ -6313,10 +6435,10 @@ input:focus,textarea:focus{border-color:var(--green3)}
6313
6435
  .studio-header{margin-bottom:20px}
6314
6436
  .studio-header h2{font-size:15px;color:var(--green);margin-bottom:4px}
6315
6437
  .studio-header p{font-size:11px;color:var(--dim);line-height:1.5}
6316
- .studio-input-row{display:flex;gap:8px;margin-bottom:20px}
6317
- .studio-input-row textarea{flex:1;resize:none;height:60px;padding:10px 14px;font-size:13px;border-radius:var(--r);border:1px solid var(--border2)}
6438
+ .studio-input-row{display:flex;gap:8px;margin-bottom:16px;align-items:flex-start}
6439
+ .studio-input-row textarea{flex:1;resize:vertical;min-height:90px;max-height:200px;padding:10px 14px;font-size:13px;border-radius:var(--r);border:1px solid var(--border2);line-height:1.5}
6318
6440
  .studio-input-row textarea:focus{border-color:var(--green3)}
6319
- .studio-run-btn{background:var(--green3);color:var(--bg);padding:0 20px;border-radius:var(--r);font-weight:700;font-size:13px;white-space:nowrap;align-self:stretch;min-width:90px}
6441
+ .studio-run-btn{background:var(--green3);color:var(--bg);padding:0 16px;border-radius:var(--r);font-weight:600;font-size:12px;white-space:nowrap;align-self:stretch;min-width:80px;letter-spacing:.2px}
6320
6442
  .studio-run-btn:disabled{opacity:.4}
6321
6443
  .studio-canvas{position:relative;width:100%;min-height:220px;background:var(--bg2);border:1px solid var(--border);border-radius:8px;margin-bottom:20px;overflow:hidden}
6322
6444
  .studio-canvas__empty{display:flex;align-items:center;justify-content:center;height:180px;color:var(--dim);font-size:11px;flex-direction:column;gap:8px}
@@ -6424,6 +6546,7 @@ input:focus,textarea:focus{border-color:var(--green3)}
6424
6546
  .br-conv-bar-outer{height:4px;background:#1a2e1a;border-radius:4px;overflow:hidden;margin-bottom:6px}
6425
6547
  .br-conv-bar-inner{height:100%;background:linear-gradient(90deg,#22c55e,#4ade80);border-radius:4px;transition:width .8s ease}
6426
6548
  .br-conv-text{font-size:9px;color:#86efac;line-height:1.55}
6549
+ @keyframes brDashFlow{0%{stroke-dashoffset:20}100%{stroke-dashoffset:0}}
6427
6550
  /* Keep old prl-* classes for workflow (not touched) */
6428
6551
  .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}
6429
6552
  @keyframes parlPulse{0%,100%{border-color:#6366f1;box-shadow:none}50%{border-color:#818cf8;box-shadow:0 0 20px rgba(99,102,241,.3)}}
@@ -6472,7 +6595,11 @@ input:focus,textarea:focus{border-color:var(--green3)}
6472
6595
  /* Plant right */
6473
6596
  .prl-office-plant2{position:absolute;bottom:14px;right:8px;width:22px;height:42px;z-index:2;pointer-events:none}
6474
6597
  /* ── 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}
6598
+ .iso-scene{background:#f0ede6;cursor:default;max-width:100%;overflow-x:auto;box-shadow:0 4px 24px rgba(0,0,0,.18)}
6599
+ /* Animated status chip for [bracket tokens] */
6600
+ .iso-status-chip{display:inline-flex;align-items:center;gap:6px;background:rgba(99,102,241,.1);border:1px solid rgba(99,102,241,.3);border-radius:20px;padding:3px 10px;font-size:11px;font-family:var(--mono);color:#818cf8;animation:statusPulse 2s ease-in-out infinite}
6601
+ @keyframes statusPulse{0%,100%{opacity:.7;border-color:rgba(99,102,241,.3)}50%{opacity:1;border-color:rgba(99,102,241,.7);box-shadow:0 0 8px rgba(99,102,241,.3)}}
6602
+ .iso-status-dot{width:6px;height:6px;border-radius:50%;background:#6366f1;animation:dotBounce 1s ease-in-out infinite}
6476
6603
  .iso-agent{transition:filter .3s}
6477
6604
  .iso-agent:hover{filter:brightness(1.15)}
6478
6605
  /* Thought bubble / speech bubble above character */
@@ -6542,7 +6669,7 @@ input:focus,textarea:focus{border-color:var(--green3)}
6542
6669
  .prl-conv-bar-inner{height:100%;background:linear-gradient(90deg,#22c55e,#4ade80);border-radius:4px;transition:width .8s ease}
6543
6670
  .prl-conv-text{font-size:9px;color:#86efac;line-height:1.55}
6544
6671
  .studio-log{background:var(--bg2);border:1px solid var(--border);border-radius:10px;padding:16px;max-height:380px;overflow-y:auto;font-size:11.5px;line-height:1.65}
6545
- .studio-log-entry{margin-bottom:12px;padding:10px 12px;border-radius:8px;background:var(--bg3);border:1px solid var(--border)}
6672
+ .studio-log-entry{margin-bottom:10px;padding:14px 16px;border-radius:10px;background:var(--bg3);border:1px solid var(--border);min-height:80px}
6546
6673
  .studio-log-entry:last-child{margin-bottom:0}
6547
6674
  .studio-log-entry__header{display:flex;align-items:center;gap:8px;margin-bottom:6px}
6548
6675
  .studio-log-entry__icon{font-size:15px}
@@ -6556,7 +6683,7 @@ input:focus,textarea:focus{border-color:var(--green3)}
6556
6683
  .studio-result__body{font-size:13px;color:var(--text);word-wrap:break-word;line-height:1.7}
6557
6684
  .studio-example-btn{display:inline-block;padding:5px 12px;border:1px solid var(--border2);border-radius:20px;font-size:10px;color:var(--dim);cursor:pointer;background:none;margin:0 4px 6px 0;transition:all .15s}
6558
6685
  .studio-example-btn:hover{border-color:var(--green3);color:var(--green);background:var(--greendim)}
6559
- .studio-tools-panel{width:220px;flex-shrink:0;border:1px solid var(--border);border-radius:10px;padding:12px;background:var(--bg2);max-height:600px;overflow-y:auto}
6686
+ .studio-tools-panel{width:220px;flex-shrink:0;border:1px solid var(--border);border-radius:10px;padding:12px;background:var(--bg2);max-height:calc(100vh - 120px);overflow-y:auto;position:sticky;top:16px}
6560
6687
  .studio-tool-item{display:flex;align-items:flex-start;gap:8px;padding:8px;border-radius:6px;cursor:pointer;transition:background .15s;margin-bottom:4px}
6561
6688
  .studio-tool-item:hover{background:var(--bg3);border-radius:6px}
6562
6689
  .studio-tool-icon{font-size:16px;flex-shrink:0;margin-top:1px}