nothumanallowed 13.2.47 → 13.2.50

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.2.47",
3
+ "version": "13.2.50",
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": {
@@ -2732,7 +2732,7 @@ export async function cmdUI(args) {
2732
2732
  const fetchResult = await withTimeout(executeTool('fetch_url', { url: targetUrl }, config), 20000);
2733
2733
  const fetchStr = typeof fetchResult === 'string' ? fetchResult : JSON.stringify(fetchResult);
2734
2734
  if (fetchStr && !fetchStr.startsWith('HTTP ') && !fetchStr.startsWith('Content blocked')) {
2735
- toolData = `## Content from ${targetUrl}:\n${fetchStr.slice(0, 5000)}`;
2735
+ toolData = `## Content from ${targetUrl}:\n${fetchStr}`;
2736
2736
  }
2737
2737
  } catch {}
2738
2738
  }
@@ -2835,7 +2835,7 @@ RULES:
2835
2835
 
2836
2836
  if (isCanvasAgent) {
2837
2837
  sysPrompt = canvasSystemPrompt;
2838
- userMsg = `Create a professional dashboard report for this data. Output ONLY the inner HTML body content (starting with <div class="header">):\n\n${context.slice(0, 10000)}`;
2838
+ userMsg = `Create a professional dashboard report for this data. Output ONLY the inner HTML body content (starting with <div class="header">):\n\n${context}`;
2839
2839
  } else if (isLiveDataAgent) {
2840
2840
  // These agents fetched real data — use a focused prompt (no tool definitions to avoid JSON output)
2841
2841
  const agentInstruction = `You are ${agent}, a specialist AI agent inside NHA Studio. Today is ${today}. Respond entirely in ${language}.
@@ -2847,8 +2847,8 @@ CRITICAL: Do NOT invent, hallucinate, or add any data not present in the DATA se
2847
2847
  Do NOT output JSON, tool calls, or code blocks. Write in plain text with markdown headers.
2848
2848
  Always apply your analysis specifically to the subject mentioned in the WORKFLOW GOAL.
2849
2849
 
2850
- ${toolData ? `## DATA FROM TOOLS:\n${toolData.slice(0, 6000)}\n` : '## DATA: No data was retrieved by this agent.\n'}
2851
- ${context ? `## OUTPUT FROM PREVIOUS AGENTS:\n${context.slice(0, 4000)}\n` : ''}
2850
+ ${toolData ? `## DATA FROM TOOLS:\n${toolData}\n` : '## DATA: No data was retrieved by this agent.\n'}
2851
+ ${context ? `## OUTPUT FROM PREVIOUS AGENTS:\n${context}\n` : ''}
2852
2852
 
2853
2853
  Your task: ${stepPrompt}`;
2854
2854
  sysPrompt = agentInstruction;
@@ -2874,8 +2874,8 @@ CRITICAL RULES:
2874
2874
  - Be thorough and specific — this is for an executive briefing based on REAL data only
2875
2875
  - Always keep the OVERALL WORKFLOW GOAL in mind — apply your analysis specifically to the subject mentioned
2876
2876
 
2877
- ${toolData ? `## LIVE DATA FROM TOOLS:\n${toolData.slice(0, 6000)}\n` : '## LIVE DATA: No tool data was fetched for this step.\n'}
2878
- ${context ? `## OUTPUT FROM PREVIOUS AGENTS:\n${context.slice(0, 6000)}\n` : ''}`;
2877
+ ${toolData ? `## LIVE DATA FROM TOOLS:\n${toolData}\n` : '## LIVE DATA: No tool data was fetched for this step.\n'}
2878
+ ${context ? `## OUTPUT FROM PREVIOUS AGENTS:\n${context}\n` : ''}`;
2879
2879
  userMsg = hasRealData
2880
2880
  ? `Based ONLY on the real data above, complete this task specifically for the subject in the WORKFLOW GOAL: ${stepPrompt}`
2881
2881
  : `No real data is available for "${task}". State this clearly and explain what data would be needed to complete: ${stepPrompt}`;
@@ -2883,6 +2883,8 @@ ${context ? `## OUTPUT FROM PREVIOUS AGENTS:\n${context.slice(0, 6000)}\n` : ''}
2883
2883
 
2884
2884
  // ── Stream LLM response ───────────────────────────────────────
2885
2885
  let fullOutput = '';
2886
+ let inThinkBlock = false;
2887
+ let thinkBuf = '';
2886
2888
  sendToken(isCanvasAgent ? 'Generating visual report...' : '');
2887
2889
  const llmTimeout = isCanvasAgent ? 120000 : 90000;
2888
2890
  try {
@@ -2891,12 +2893,39 @@ ${context ? `## OUTPUT FROM PREVIOUS AGENTS:\n${context.slice(0, 6000)}\n` : ''}
2891
2893
  (token) => {
2892
2894
  fullOutput += token;
2893
2895
  if (!isCanvasAgent) {
2894
- // Strip JSON tool calls from synthesis agent output before sending to client
2895
- const stripped = token.replace(/\{[\s\S]*?"action"[\s\S]*?\}/g, '').trim();
2896
+ // Buffer and strip <think>...</think> blocks before sending to client
2897
+ thinkBuf += token;
2898
+ // Process thinkBuf: emit only content outside think tags
2899
+ let out = '';
2900
+ let buf = thinkBuf;
2901
+ while (buf.length > 0) {
2902
+ if (inThinkBlock) {
2903
+ const closeIdx = buf.indexOf('</think>');
2904
+ if (closeIdx >= 0) {
2905
+ buf = buf.slice(closeIdx + 8);
2906
+ inThinkBlock = false;
2907
+ } else {
2908
+ buf = '';
2909
+ }
2910
+ } else {
2911
+ const openIdx = buf.indexOf('<think>');
2912
+ if (openIdx >= 0) {
2913
+ out += buf.slice(0, openIdx);
2914
+ buf = buf.slice(openIdx + 7);
2915
+ inThinkBlock = true;
2916
+ } else {
2917
+ out += buf;
2918
+ buf = '';
2919
+ }
2920
+ }
2921
+ }
2922
+ thinkBuf = '';
2923
+ // Strip JSON tool calls from specialist agent output
2924
+ const stripped = out.replace(/\{[\s\S]*?"action"[\s\S]*?\}/g, '').trim();
2896
2925
  if (stripped) sendToken(stripped);
2897
2926
  }
2898
2927
  },
2899
- { max_tokens: isCanvasAgent ? 4096 : 2048 },
2928
+ { max_tokens: 4096 },
2900
2929
  ),
2901
2930
  llmTimeout
2902
2931
  );
@@ -2904,10 +2933,31 @@ ${context ? `## OUTPUT FROM PREVIOUS AGENTS:\n${context.slice(0, 6000)}\n` : ''}
2904
2933
  if (!isCanvasAgent) sendToken(`[Error: ${e.message}]`);
2905
2934
  }
2906
2935
 
2907
- // Fallback: if LLM returned empty and we have tool data, send that directly
2908
- if (!isCanvasAgent && !fullOutput.trim() && toolData) {
2936
+ // Strip think tags from fullOutput before emptiness check
2937
+ const fullOutputClean = fullOutput.replace(/<think>[\s\S]*?<\/think>/g, '').trim();
2938
+
2939
+ // Fallback: if LLM returned empty and we have tool data, send it directly
2940
+ if (!isCanvasAgent && !fullOutputClean && toolData) {
2909
2941
  fullOutput = toolData;
2910
- sendToken(toolData.slice(0, 2000));
2942
+ sendToken(toolData);
2943
+ }
2944
+ // Fallback: if LLM returned empty and we have context (specialist agents like CASSANDRA),
2945
+ // retry once without thinking and with a simplified direct prompt
2946
+ else if (!isCanvasAgent && !fullOutputClean && context && !toolData) {
2947
+ sendToken('[Retrying analysis...]');
2948
+ let retryOutput = '';
2949
+ try {
2950
+ const retryConfig = Object.assign({}, config, { thinking: 'off' });
2951
+ await withTimeout(
2952
+ callLLMStream(retryConfig, `You are ${agent}. Analyze the following data and complete the task. Be thorough and write in ${language}.\n\nDATA:\n${context}\n\nTASK: ${stepPrompt}`,
2953
+ 'Write your complete analysis now.',
2954
+ (tok) => { retryOutput += tok; sendToken(tok); },
2955
+ { max_tokens: 4096 },
2956
+ ),
2957
+ 60000
2958
+ );
2959
+ } catch {}
2960
+ if (retryOutput.trim()) fullOutput = retryOutput;
2911
2961
  }
2912
2962
 
2913
2963
  if (isCanvasAgent) {
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.2.47';
8
+ export const VERSION = '13.2.50';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -104,6 +104,11 @@ function makeDraggable(el, handleSelector) {
104
104
  if (e.button !== 0) return;
105
105
  dragging = true;
106
106
  sx = e.clientX; sy = e.clientY;
107
+ // Clear transform first so getBoundingClientRect gives the real rendered position
108
+ el.style.transform = '';
109
+ if (el.dataset) el.dataset.expanded = '';
110
+ el.style.width = el.style.width || '';
111
+ el.style.height = el.style.height || '';
107
112
  var rect = el.getBoundingClientRect();
108
113
  // Switch from CSS right/top to explicit left/top
109
114
  el.style.right = 'auto';
@@ -639,7 +644,16 @@ function onConversationSwitch(){
639
644
 
640
645
  function openCanvasPanel(){
641
646
  var cp = document.getElementById('canvasPanel');
642
- if (cp) cp.classList.add('open');
647
+ if (!cp) return;
648
+ cp.classList.add('open');
649
+ // If no canvas data, show a tip in the frame area
650
+ if (!studioState.canvas) {
651
+ var cf = document.getElementById('canvasFrame');
652
+ if (cf) {
653
+ var tip = \x27<!DOCTYPE html><html><body style="background:#0a0a0a;color:#6b7280;font-family:monospace;display:flex;align-items:center;justify-content:center;height:100vh;text-align:center;padding:20px"><div><div style="font-size:32px;margin-bottom:16px">&#9632;</div><div style="font-size:13px;line-height:1.6">Nessun Canvas in questo workflow.<br>Aggiungi <strong style=\x22color:#4ade80\x22>html</strong>, <strong style=\x22color:#4ade80\x22>dashboard</strong> o <strong style=\x22color:#4ade80\x22>visual</strong> al prompt,<br>oppure usa un task con 2+ agenti specialisti.</div></div></body></html>\x27;
654
+ cf.srcdoc = tip;
655
+ }
656
+ }
643
657
  }
644
658
 
645
659
  function reopenCanvas(){
@@ -697,8 +711,18 @@ function canvasCopyImage(){
697
711
  }
698
712
  function toggleCanvasSize(){
699
713
  var p=document.getElementById('canvasPanel');if(!p)return;
700
- if(p.style.width==='80vw'){p.style.width='';p.style.height='';p.style.top='';p.style.right='';}
701
- else{p.style.width='80vw';p.style.height='80vh';p.style.top='10vh';p.style.right='10vw';}
714
+ if(p.dataset.expanded==='1'){
715
+ p.dataset.expanded='';
716
+ // Reset to CSS default: fixed position top-right
717
+ p.style.width='';p.style.height='';
718
+ p.style.top='60px';p.style.right='12px';
719
+ p.style.left='';p.style.transform='';
720
+ } else {
721
+ p.dataset.expanded='1';
722
+ p.style.width='80vw';p.style.height='80vh';
723
+ p.style.top='10vh';p.style.left='50%';p.style.right='auto';
724
+ p.style.transform='translateX(-50%)';
725
+ }
702
726
  }
703
727
  // ---- MSG ACTIONS ----
704
728
  function copyMsg(i){
@@ -3006,6 +3030,8 @@ function studioReset() {
3006
3030
  studioState.running = false;
3007
3031
  studioState.planned = false;
3008
3032
  studioTokens = {in:0, out:0};
3033
+ var nudgeEl = document.getElementById(\x27studioParliamentNudge\x27);
3034
+ if (nudgeEl) nudgeEl.remove();
3009
3035
  var ta = document.getElementById('studioTaskInput');
3010
3036
  if (ta) ta.value = '';
3011
3037
  var tb = document.getElementById('studioTokenBar');
@@ -3091,6 +3117,68 @@ function renderStudioLog() {
3091
3117
  el.scrollTop = el.scrollHeight;
3092
3118
  }
3093
3119
 
3120
+ function downloadStudioPDF() {
3121
+ var task = studioState.task || 'NHA Studio Report';
3122
+ var today = new Date().toLocaleDateString('it-IT', {day:'2-digit',month:'2-digit',year:'numeric'});
3123
+ var nodes = studioState.nodes || [];
3124
+
3125
+ // Build sections for each agent
3126
+ var sectionsHtml = nodes.map(function(n) {
3127
+ if (!n.output || n.output === '(no output)' || n.agent === 'CanvasAgent') return '';
3128
+ var mdHtml = (n.output || '')
3129
+ .replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')
3130
+ .replace(/\*\*(.*?)\*\*/g,'<strong>$1</strong>')
3131
+ .replace(/\*(.*?)\*/g,'<em>$1</em>')
3132
+ .replace(/^#{3}\s+(.+)$/gm,'<h3>$1</h3>')
3133
+ .replace(/^#{2}\s+(.+)$/gm,'<h2>$1</h2>')
3134
+ .replace(/^#{1}\s+(.+)$/gm,'<h2>$1</h2>')
3135
+ .replace(/^-\s+(.+)$/gm,'<li>$1</li>')
3136
+ .replace(/(<li>[\s\S]*?<\/li>)/g,'<ul>$1</ul>')
3137
+ .replace(/\n{2,}/g,'</p><p>')
3138
+ .replace(/\n/g,'<br>');
3139
+ return '<div class="section"><div class="agent-label">' + (n.icon||'') + ' ' + esc(n.label||n.agent) + '</div><div class="section-body"><p>' + mdHtml + '</p></div></div>';
3140
+ }).join('');
3141
+
3142
+ // Include canvas if present (as an embedded iframe screenshot fallback note)
3143
+ var canvasNote = studioState.canvas ? '<div class="section canvas-note"><div class="agent-label">&#9632; Canvas Report</div><div class="section-body"><p><em>Il Canvas HTML è disponibile nell\x27interfaccia Studio. Apri il pannello Canvas e usa la funzione stampa del browser per includerlo.</em></p></div></div>' : '';
3144
+
3145
+ var html = '<!DOCTYPE html><html lang="it"><head><meta charset="UTF-8"><title>' + esc(task) + '</title><style>' +
3146
+ 'body{font-family:"Helvetica Neue",Arial,sans-serif;color:#111;background:#fff;margin:0;padding:0}' +
3147
+ '.cover{background:#0d0d0d;color:#fff;padding:60px 50px;page-break-after:always}' +
3148
+ '.cover h1{font-size:28px;font-weight:700;margin:0 0 12px;color:#00ff41}' +
3149
+ '.cover .meta{font-size:13px;color:#aaa;margin-top:8px}' +
3150
+ '.cover .task{font-size:15px;color:#e0e0e0;margin-top:20px;line-height:1.6;max-width:700px}' +
3151
+ '.cover .brand{font-size:11px;color:#555;margin-top:40px;letter-spacing:2px;text-transform:uppercase}' +
3152
+ '.toc{padding:40px 50px;border-bottom:1px solid #e0e0e0;page-break-after:always}' +
3153
+ '.toc h2{font-size:14px;text-transform:uppercase;letter-spacing:2px;color:#555;margin-bottom:16px}' +
3154
+ '.toc ol{margin:0;padding-left:20px;font-size:13px;line-height:2}' +
3155
+ '.section{padding:36px 50px;border-bottom:1px solid #f0f0f0;page-break-inside:avoid}' +
3156
+ '.section:last-child{border-bottom:none}' +
3157
+ '.agent-label{font-size:11px;text-transform:uppercase;letter-spacing:1.5px;color:#888;font-weight:700;margin-bottom:14px}' +
3158
+ '.section-body{font-size:13px;line-height:1.8;color:#222}' +
3159
+ '.section-body h2{font-size:16px;font-weight:700;color:#111;margin:20px 0 8px}' +
3160
+ '.section-body h3{font-size:14px;font-weight:600;color:#333;margin:16px 0 6px}' +
3161
+ '.section-body ul{margin:8px 0;padding-left:20px}' +
3162
+ '.section-body li{margin-bottom:4px}' +
3163
+ '.section-body strong{font-weight:700}' +
3164
+ '.section-body p{margin:0 0 12px}' +
3165
+ '.canvas-note{background:#f9f9f9}' +
3166
+ '.footer-bar{padding:20px 50px;background:#f9f9f9;font-size:10px;color:#aaa;text-align:center;letter-spacing:1px}' +
3167
+ '@media print{body{-webkit-print-color-adjust:exact;print-color-adjust:exact}.cover{page-break-after:always}.toc{page-break-after:always}}' +
3168
+ '</style></head><body>' +
3169
+ '<div class="cover"><div class="brand">NotHumanAllowed — NHA Studio</div><h1>' + esc(task.length > 80 ? task.slice(0,80)+'...' : task) + '</h1><div class="meta">Generato il ' + today + ' &nbsp;·&nbsp; ' + nodes.filter(function(n){return n.agent!=='CanvasAgent'}).length + ' agenti</div><div class="task">' + esc(task) + '</div></div>' +
3170
+ '<div class="toc"><h2>Indice</h2><ol>' + nodes.filter(function(n){return n.output&&n.output!=='(no output)'&&n.agent!=='CanvasAgent'}).map(function(n){return '<li>' + esc(n.label||n.agent) + '</li>';}).join('') + '</ol></div>' +
3171
+ sectionsHtml + canvasNote +
3172
+ '<div class="footer-bar">NHA Studio &nbsp;·&nbsp; nothumanallowed.com &nbsp;·&nbsp; ' + today + '</div>' +
3173
+ '</body></html>';
3174
+
3175
+ var win = window.open('', '_blank');
3176
+ if (!win) { alert('Popup bloccato — abilita i popup per scaricare il PDF'); return; }
3177
+ win.document.write(html);
3178
+ win.document.close();
3179
+ win.onload = function() { setTimeout(function(){ win.print(); }, 300); };
3180
+ }
3181
+
3094
3182
  function renderStudioResult() {
3095
3183
  var el = document.getElementById('studioResult');
3096
3184
  if (!el) return;
@@ -3100,10 +3188,23 @@ function renderStudioResult() {
3100
3188
  var body = hasCanvas
3101
3189
  ? '<div style="display:flex;align-items:center;gap:12px;flex-wrap:wrap"><span style="color:var(--dim);font-size:13px">&#10003; ' + t('canvas_generated') + '</span><button onclick="openCanvasPanel()" style="padding:6px 14px;background:var(--greendim);border:1px solid var(--green3);border-radius:8px;color:var(--green);font-size:12px;cursor:pointer;font-weight:700">&#x25A3; ' + t('canvas_open') + '</button></div>'
3102
3190
  : '<div class="md-body">' + renderMd(studioState.result) + '</div>';
3103
- el.innerHTML = '<div class="studio-result__title">&#10003; ' + t('workflow_complete') + '</div>' + body;
3104
- // Show canvas button in toolbar whenever canvas data exists
3191
+ var dlBtn = '<button onclick="downloadStudioPDF()" title="Scarica il workflow come PDF" style="margin-top:10px;padding:6px 14px;background:none;border:1px solid var(--border);border-radius:8px;color:var(--dim);font-size:11px;cursor:pointer;font-family:var(--mono);letter-spacing:.5px">&#x2913; Download PDF</button>';
3192
+ el.innerHTML = '<div class="studio-result__title">&#10003; ' + t('workflow_complete') + '</div>' + body + dlBtn;
3193
+ // Update canvas button style: bright green when canvas exists, dimmed otherwise
3105
3194
  var canvasBtn = document.getElementById('studioCanvasBtn');
3106
- if (canvasBtn) canvasBtn.style.display = hasCanvas ? '' : 'none';
3195
+ if (canvasBtn) {
3196
+ if (hasCanvas) {
3197
+ canvasBtn.style.background = 'var(--greendim)';
3198
+ canvasBtn.style.borderColor = 'var(--green3)';
3199
+ canvasBtn.style.color = 'var(--green)';
3200
+ canvasBtn.title = t(\x27canvas_open\x27);
3201
+ } else {
3202
+ canvasBtn.style.background = \x27none\x27;
3203
+ canvasBtn.style.borderColor = \x27var(--border)\x27;
3204
+ canvasBtn.style.color = \x27var(--dim)\x27;
3205
+ canvasBtn.title = \x27Canvas non disponibile per questo workflow\x27;
3206
+ }
3207
+ }
3107
3208
  }
3108
3209
 
3109
3210
  function studioSetNodeStatus(idx, status) {
@@ -3158,6 +3259,21 @@ async function runStudio() {
3158
3259
  renderStudioNodes();
3159
3260
  studioLog('Studio', '&#10003;', 'Workflow planned: ' + planRes.steps.map(function(s){return s.label}).join(' -> '), 'system');
3160
3261
 
3262
+ // Parliament suggestion: show nudge if 3+ specialist steps and Parliament not already enabled
3263
+ var specialistAgents = planRes.steps.filter(function(s){ return !['WebSearchAgent','EmailAgent','CalendarAgent','GitHubAgent','SlackAgent','NotionAgent','CanvasAgent','HERALD'].includes(s.agent); });
3264
+ var parliamentChkEarly = document.getElementById(\x27studioParliamentMode\x27);
3265
+ if (specialistAgents.length >= 2 && parliamentChkEarly && !parliamentChkEarly.checked) {
3266
+ var nudge = document.getElementById(\x27studioParliamentNudge\x27);
3267
+ if (!nudge) {
3268
+ nudge = document.createElement(\x27div\x27);
3269
+ nudge.id = \x27studioParliamentNudge\x27;
3270
+ 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;
3271
+ 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;
3272
+ var tokenBar = document.getElementById(\x27studioTokenBar\x27);
3273
+ if (tokenBar && tokenBar.parentNode) tokenBar.parentNode.insertBefore(nudge, tokenBar.parentNode.firstChild);
3274
+ }
3275
+ }
3276
+
3161
3277
  // Step 2: execute each step via SSE
3162
3278
  var context = '';
3163
3279
  for (var i = 0; i < studioState.nodes.length; i++) {
@@ -3195,11 +3311,17 @@ async function runStudio() {
3195
3311
  }
3196
3312
 
3197
3313
  // Parliament mode: Round 2 cross-reading deliberation
3314
+ // Read from both DOM and studioState (supports mid-run activation via nudge)
3198
3315
  var parliamentChk = document.getElementById(\x27studioParliamentMode\x27);
3199
- if (parliamentChk && parliamentChk.checked && studioState.nodes.length >= 2) {
3316
+ var parliamentActive = studioState.parliamentMode || (parliamentChk && parliamentChk.checked);
3317
+ if (parliamentActive && studioState.nodes.length >= 1) {
3200
3318
  var proposals = studioState.nodes
3201
3319
  .filter(function(n) { return n.output && n.output !== \x27(no output)\x27 && n.agent !== \x27CanvasAgent\x27; })
3202
3320
  .map(function(n) { return {agent: n.agent, label: n.label, output: n.output}; });
3321
+ // Need at least 2 proposals for cross-reading; if only 1, include the full context as a second proposal
3322
+ if (proposals.length === 1 && context) {
3323
+ proposals.push({agent: \x27Context\x27, label: \x27Contesto workflow\x27, output: context});
3324
+ }
3203
3325
  if (proposals.length >= 2) {
3204
3326
  studioLog(\x27Parlamento\x27, \x27&#x2656;\x27, \x27Avvio deliberazione — Round 2 cross-reading tra agenti...\x27, \x27system\x27);
3205
3327
  var deliberateBody = JSON.stringify({task: task, proposals: proposals, language: document.getElementById(\x27langSelect\x27) ? document.getElementById(\x27langSelect\x27).value : \x27it\x27});
@@ -3584,7 +3706,7 @@ function renderStudio(el) {
3584
3706
 
3585
3707
  '<div style="display:flex;align-items:center;gap:8px;margin:8px 0">' +
3586
3708
  '<div id="studioTokenBar" style="font-size:10px;color:var(--dim);font-family:var(--mono);flex:1"></div>' +
3587
- '<button id="studioCanvasBtn" onclick="openCanvasPanel()" style="display:none;font-size:12px;padding:5px 14px;background:var(--greendim);border:1px solid var(--green3);border-radius:6px;color:var(--green);cursor:pointer;font-weight:700">&#9632; ' + t('canvas_open') + '</button>' +
3709
+ '<button id="studioCanvasBtn" onclick="openCanvasPanel()" title="' + t('canvas_open') + '" style="font-size:12px;padding:5px 14px;background:none;border:1px solid var(--border);border-radius:6px;color:var(--dim);cursor:pointer;font-weight:700;transition:all .2s">&#9632; Canvas</button>' +
3588
3710
  '</div>' +
3589
3711
  '<div class="studio-canvas" id="studioNodes"></div>' +
3590
3712
  '<div class="studio-log" id="studioLog" style="display:none"></div>' +