nothumanallowed 13.4.7 → 13.4.9

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.4.7",
3
+ "version": "13.4.9",
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": {
@@ -3230,6 +3230,69 @@ ${rawText.slice(0, 18000)}`;
3230
3230
  const files = await withTimeout(gd.listFiles(config, '', 10), 'DriveAgent');
3231
3231
  toolData = typeof files === 'string' ? files : JSON.stringify(files);
3232
3232
  } catch (e) { toolData = `Drive read failed: ${e.message}`; }
3233
+
3234
+ } else if (agent === 'FileReaderAgent') {
3235
+ // Reads local files/directories mentioned in the task or step prompt
3236
+ sendToken('[Reading local files...] ');
3237
+ try {
3238
+ // Extract paths from task + stepPrompt (absolute paths, ~/ paths, named dirs like Desktop/Downloads)
3239
+ const pathPatterns = [
3240
+ /([~/][^\s"'`,;]+\.[a-zA-Z0-9]{1,10})/g, // file with extension
3241
+ /([~/][^\s"'`,;]{3,})/g, // any path starting with / or ~
3242
+ ];
3243
+ const homedir = (await import('os')).homedir();
3244
+ const foundPaths = new Set();
3245
+ for (const re of pathPatterns) {
3246
+ const text = task + ' ' + stepPrompt + ' ' + (context || '');
3247
+ let m;
3248
+ while ((m = re.exec(text)) !== null) {
3249
+ const p = m[1].replace(/^~/, homedir).replace(/\/$/, '');
3250
+ if (p.length > 2) foundPaths.add(p);
3251
+ }
3252
+ }
3253
+ // Also resolve named directories: Desktop, Downloads, Documents
3254
+ const namedDirRe = /\b(Desktop|Downloads|Documents|Documenti|Scrivania|Scaricati)\b/i;
3255
+ const namedMatch = (task + ' ' + stepPrompt).match(namedDirRe);
3256
+ const dirMap = { desktop: 'Desktop', scrivania: 'Desktop', downloads: 'Downloads', scaricati: 'Downloads', documents: 'Documents', documenti: 'Documents' };
3257
+ if (namedMatch) foundPaths.add(path.join(homedir, dirMap[namedMatch[1].toLowerCase()] || namedMatch[1]));
3258
+
3259
+ if (foundPaths.size === 0) {
3260
+ // Fallback: list Desktop
3261
+ foundPaths.add(path.join(homedir, 'Desktop'));
3262
+ }
3263
+
3264
+ const parts = [];
3265
+ for (const p of foundPaths) {
3266
+ try {
3267
+ const result = await withTimeout(executeTool('file_list', { path: p }, config), 8000);
3268
+ parts.push(`## Directory: ${p}\n${typeof result === 'string' ? result : JSON.stringify(result)}`);
3269
+ } catch {
3270
+ try {
3271
+ const result = await withTimeout(executeTool('file_read', { path: p, lines: 300 }, config), 8000);
3272
+ parts.push(`## File: ${p}\n${typeof result === 'string' ? result : JSON.stringify(result)}`);
3273
+ } catch (e2) { parts.push(`## ${p}: could not read (${e2.message})`); }
3274
+ }
3275
+ }
3276
+ toolData = parts.join('\n\n') || 'No files found at the specified paths.';
3277
+ } catch (e) { toolData = `File read failed: ${e.message}`; }
3278
+
3279
+ } else if (agent === 'CodeExecutorAgent') {
3280
+ // Generates and executes code (Python/JS/TS) to answer data questions
3281
+ sendToken('[Preparing code execution...] ');
3282
+ try {
3283
+ // Ask LLM to write the code first
3284
+ const codeGenSys = `You are a code generator. Write a ${(/python/i.test(stepPrompt) || /dati|data|analisi|analysis|calcol/i.test(stepPrompt)) ? 'Python' : 'JavaScript'} script that answers the following task. Output ONLY the raw code, no markdown fences, no explanation.`;
3285
+ const codeGenUser = `Task: ${stepPrompt}\n\nAvailable context data:\n${(context || '').slice(0, 4000)}`;
3286
+ const codeGenConfig = Object.assign({}, config, { thinking: 'off' });
3287
+ sendToken('[Generating code...] ');
3288
+ const generatedCode = await withTimeout(callLLM(codeGenConfig, codeGenSys, codeGenUser, { max_tokens: 2000 }), 30000);
3289
+ // Strip markdown fences if LLM added them
3290
+ const cleanCode = generatedCode.replace(/^```[\w]*\n?/m, '').replace(/\n?```\s*$/m, '').trim();
3291
+ const lang = /^import |^from |^print\(|^def |^class |^#!.*python/m.test(cleanCode) ? 'python' : 'javascript';
3292
+ sendToken(`[Executing ${lang} code...] `);
3293
+ const execResult = await withTimeout(executeTool('execute_code', { language: lang, code: cleanCode, timeout: 30 }, config), 45000);
3294
+ toolData = `## Generated code (${lang}):\n\`\`\`${lang}\n${cleanCode.slice(0, 2000)}\n\`\`\`\n\n## Execution result:\n${typeof execResult === 'string' ? execResult : JSON.stringify(execResult)}`;
3295
+ } catch (e) { toolData = `Code execution failed: ${e.message}`; }
3233
3296
  }
3234
3297
 
3235
3298
  // ── Build system prompt with real tool data ───────────────────
@@ -3239,37 +3302,72 @@ ${rawText.slice(0, 18000)}`;
3239
3302
  const today = new Date().toISOString().split('T')[0];
3240
3303
  const isCanvasAgent = agent === 'CanvasAgent';
3241
3304
  // Tool-data agents: fetch real live data and use buildSystemPrompt (tool calls allowed)
3242
- const isLiveDataAgent = ['CalendarAgent','EmailAgent','GitHubAgent','NotionAgent','SlackAgent','DriveAgent','BrowserAgent','WebSearchAgent','ResearchAgent'].includes(agent);
3305
+ const isLiveDataAgent = ['CalendarAgent','EmailAgent','GitHubAgent','NotionAgent','SlackAgent','DriveAgent','BrowserAgent','WebSearchAgent','ResearchAgent','FileReaderAgent','CodeExecutorAgent'].includes(agent);
3243
3306
 
3244
3307
  // ── Canvas HTML template — built server-side, guaranteed CSS ─────
3245
3308
  // The LLM outputs ONLY the <body> inner HTML (no <html>, no <style>)
3246
3309
  // Server wraps it in the full template. This prevents the model from ignoring CSS.
3247
- const NHA_CSS = `*{margin:0;padding:0;box-sizing:border-box}body{font-family:'Inter',system-ui,sans-serif;background:#0d0d14;color:#f0f0f5;min-height:100vh;padding:24px;font-size:14px;line-height:1.65}a{color:#22d3ee;text-decoration:underline}a:hover{color:#818cf8}strong{color:#f0f0f5;font-weight:700}em{color:#a5b4fc;font-style:italic}u{text-decoration-color:#ef4444;text-underline-offset:2px}blockquote{border-left:3px solid #6366f1;padding:10px 16px;margin:12px 0;background:#15151f;border-radius:0 8px 8px 0;color:#8b8b9e;font-style:italic}.header{background:linear-gradient(135deg,#4f46e5 0%,#06b6d4 100%);border-radius:16px;padding:28px 36px;margin-bottom:20px}.header h1{font-size:24px;font-weight:800;color:#fff;margin-bottom:6px}.header p{font-size:13px;color:rgba(255,255,255,.85);margin:0}.meta{display:flex;gap:10px;margin-top:14px;flex-wrap:wrap}.meta span{background:rgba(255,255,255,.18);border-radius:20px;padding:3px 12px;font-size:11px;color:#fff;font-weight:500}.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:14px;margin-bottom:20px}.card{background:#15151f;border:1px solid #2a2a38;border-radius:12px;padding:18px}.card-label{font-size:10px;text-transform:uppercase;letter-spacing:1.5px;color:#6366f1;font-weight:700;margin-bottom:8px}.card h3{font-size:20px;font-weight:700;color:#f0f0f5;margin-bottom:4px}.card p{font-size:12px;color:#8b8b9e;margin:0}.section{background:#15151f;border:1px solid #2a2a38;border-radius:12px;padding:22px;margin-bottom:16px}.section-title{font-size:10px;text-transform:uppercase;letter-spacing:1.5px;color:#22d3ee;font-weight:700;margin-bottom:16px}.section h3{font-size:15px;font-weight:600;color:#f0f0f5;margin-bottom:6px;margin-top:14px}.section p{font-size:13px;color:#8b8b9e;line-height:1.7;margin-bottom:10px}ul{list-style:none;padding:0;margin:8px 0}ul li{padding:4px 0 4px 18px;position:relative;font-size:13px;color:#8b8b9e}ul li::before{content:'›';position:absolute;left:0;color:#6366f1;font-weight:700}ol{padding-left:20px;margin:8px 0}ol li{padding:4px 0;font-size:13px;color:#8b8b9e;line-height:1.6}.priority-list{display:flex;flex-direction:column;gap:8px}.priority-item{display:flex;align-items:flex-start;gap:12px;padding:12px;background:#1c1c28;border-radius:8px}.priority-num{width:26px;height:26px;border-radius:50%;background:#6366f1;color:#fff;font-size:11px;font-weight:700;display:flex;align-items:center;justify-content:center;flex-shrink:0}.priority-item h4{font-size:13px;font-weight:600;color:#f0f0f5;margin-bottom:3px}.priority-item p{font-size:12px;color:#8b8b9e;line-height:1.5;margin:0}.source-item{padding:14px;background:#1c1c28;border-radius:8px;margin-bottom:10px;border-left:3px solid #6366f1}.source-item h4{font-size:13px;font-weight:600;color:#f0f0f5;margin-bottom:4px}.source-item p{font-size:12px;color:#8b8b9e;line-height:1.6;margin:4px 0}.source-item a{font-size:11px}.bar-row{margin-bottom:10px}.bar-label{font-size:12px;color:#8b8b9e;margin-bottom:4px;display:flex;justify-content:space-between}.bar-track{background:#1c1c28;border-radius:4px;height:8px;overflow:hidden}.bar-fill{height:100%;border-radius:4px;background:linear-gradient(90deg,#6366f1,#22d3ee)}.badge-high{display:inline-block;background:#7f1d1d;color:#ef4444;border-radius:12px;padding:2px 10px;font-size:10px;font-weight:700;margin-right:4px}.badge-med{display:inline-block;background:#713f12;color:#f59e0b;border-radius:12px;padding:2px 10px;font-size:10px;font-weight:700;margin-right:4px}.badge-low{display:inline-block;background:#14532d;color:#34d399;border-radius:12px;padding:2px 10px;font-size:10px;font-weight:700;margin-right:4px}.badge-info{display:inline-block;background:#1e1b4b;color:#a5b4fc;border-radius:12px;padding:2px 10px;font-size:10px;font-weight:700;margin-right:4px}.divider{border:none;border-top:1px solid #2a2a38;margin:16px 0}.footer{text-align:center;padding:18px;font-size:11px;color:#4a4a5e;margin-top:8px}`;
3248
-
3249
- const wrapInNHATemplate = (bodyHtml, title) => `<!DOCTYPE html><html lang="${langCode}"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0"><title>${(title||'NHA Report').replace(/</g,'&lt;')}</title><style>${NHA_CSS}</style></head><body>${bodyHtml}</body></html>`;
3250
-
3251
- const canvasSystemPrompt = `You are a professional HTML content generator. Output ONLY the HTML content that goes INSIDE the <body> tag. Do NOT output <!DOCTYPE>, <html>, <head>, <style>, or any wrapper tags — the CSS and document structure are already provided by the system.
3252
-
3253
- AVAILABLE CSS CLASSES (use these, they are pre-defined):
3254
- - .header > h1, p, .meta > span (gradient header banner)
3255
- - .grid > .card > .card-label, h3, p (stat grid)
3256
- - .section > .section-title, h3, p (content sections)
3257
- - .source-item > h4, p, a (news/source items with left accent)
3258
- - .priority-list > .priority-item > .priority-num, h4, p (ranked list)
3259
- - .bar-row > .bar-label, .bar-track > .bar-fill[style="width:X%"] (bar charts)
3260
- - .badge-high .badge-med .badge-low .badge-info (colored badges)
3261
- - ul > li, ol > li (bullet/numbered lists with custom styling)
3262
- - blockquote (quoted excerpts)
3263
- - .divider (horizontal rule)
3264
- - .footer (footer)
3265
- - <strong> <em> <u> (inline formatting)
3266
- - <a href="URL" target="_blank"> (clickable links — use for ALL URLs in the data)
3310
+ const NHA_CSS = `*{margin:0;padding:0;box-sizing:border-box}body{font-family:'Inter',system-ui,sans-serif;background:#0d0d14;color:#f0f0f5;min-height:100vh;padding:24px;font-size:14px;line-height:1.65}a{color:#22d3ee;text-decoration:underline}a:hover{color:#818cf8}strong{color:#f0f0f5;font-weight:700}em{color:#a5b4fc;font-style:italic}u{text-decoration-color:#ef4444;text-underline-offset:2px}blockquote{border-left:3px solid #6366f1;padding:10px 16px;margin:12px 0;background:#15151f;border-radius:0 8px 8px 0;color:#8b8b9e;font-style:italic}
3311
+ /* ── HEADER ── */
3312
+ .header{background:linear-gradient(135deg,#4f46e5 0%,#06b6d4 100%);border-radius:16px;padding:28px 36px;margin-bottom:20px}.header h1{font-size:24px;font-weight:800;color:#fff;margin-bottom:6px}.header p{font-size:13px;color:rgba(255,255,255,.85);margin:0}.meta{display:flex;gap:10px;margin-top:14px;flex-wrap:wrap}.meta span{background:rgba(255,255,255,.18);border-radius:20px;padding:3px 12px;font-size:11px;color:#fff;font-weight:500}
3313
+ /* ── GRID / CARDS ── */
3314
+ .grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:14px;margin-bottom:20px}.card{background:#15151f;border:1px solid #2a2a38;border-radius:12px;padding:18px}.card-label{font-size:10px;text-transform:uppercase;letter-spacing:1.5px;color:#6366f1;font-weight:700;margin-bottom:8px}.card h3{font-size:20px;font-weight:700;color:#f0f0f5;margin-bottom:4px}.card p{font-size:12px;color:#8b8b9e;margin:0}
3315
+ /* ── SECTIONS ── */
3316
+ .section{background:#15151f;border:1px solid #2a2a38;border-radius:12px;padding:22px;margin-bottom:16px;break-inside:avoid;page-break-inside:avoid}.section-title{font-size:10px;text-transform:uppercase;letter-spacing:1.5px;color:#22d3ee;font-weight:700;margin-bottom:16px}.section h3{font-size:15px;font-weight:600;color:#f0f0f5;margin-bottom:6px;margin-top:14px}.section p{font-size:13px;color:#8b8b9e;line-height:1.7;margin-bottom:10px}
3317
+ /* ── LISTS ── */
3318
+ ul{list-style:none;padding:0;margin:8px 0}ul li{padding:4px 0 4px 18px;position:relative;font-size:13px;color:#8b8b9e}ul li::before{content:'›';position:absolute;left:0;color:#6366f1;font-weight:700}ol{padding-left:20px;margin:8px 0}ol li{padding:4px 0;font-size:13px;color:#8b8b9e;line-height:1.6}
3319
+ /* ── PRIORITY LIST ── */
3320
+ .priority-list{display:flex;flex-direction:column;gap:8px}.priority-item{display:flex;align-items:flex-start;gap:12px;padding:12px;background:#1c1c28;border-radius:8px;break-inside:avoid}.priority-num{width:26px;height:26px;border-radius:50%;background:#6366f1;color:#fff;font-size:11px;font-weight:700;display:flex;align-items:center;justify-content:center;flex-shrink:0}.priority-item h4{font-size:13px;font-weight:600;color:#f0f0f5;margin-bottom:3px}.priority-item p{font-size:12px;color:#8b8b9e;line-height:1.5;margin:0}
3321
+ /* ── SOURCE ITEMS ── */
3322
+ .source-item{padding:14px;background:#1c1c28;border-radius:8px;margin-bottom:10px;border-left:3px solid #6366f1;break-inside:avoid}.source-item h4{font-size:13px;font-weight:600;color:#f0f0f5;margin-bottom:4px}.source-item p{font-size:12px;color:#8b8b9e;line-height:1.6;margin:4px 0}.source-item a{font-size:11px}
3323
+ /* ── BAR CHARTS ── */
3324
+ .bar-row{margin-bottom:10px;break-inside:avoid}.bar-label{font-size:12px;color:#8b8b9e;margin-bottom:4px;display:flex;justify-content:space-between}.bar-track{background:#1c1c28;border-radius:4px;height:8px;overflow:hidden}.bar-fill{height:100%;border-radius:4px;background:linear-gradient(90deg,#6366f1,#22d3ee)}
3325
+ /* ── DATA TABLES ── */
3326
+ .data-table{width:100%;border-collapse:collapse;margin:12px 0;font-size:13px;border-radius:8px;overflow:hidden}.data-table th{background:#1e1b4b;color:#a5b4fc;font-weight:700;padding:10px 14px;text-align:left;font-size:11px;text-transform:uppercase;letter-spacing:.8px;border-bottom:2px solid #2a2a38}.data-table td{padding:9px 14px;color:#c4c4d4;border-bottom:1px solid #1e1e2c;vertical-align:top}.data-table tr:nth-child(even) td{background:#12121e}.data-table tr:hover td{background:#1c1c2c}.data-table td strong{color:#f0f0f5}
3327
+ /* ── CHART CONTAINERS ── */
3328
+ .chart-wrap{background:#15151f;border:1px solid #2a2a38;border-radius:12px;padding:18px;margin-bottom:16px;break-inside:avoid}.chart-title{font-size:10px;text-transform:uppercase;letter-spacing:1.5px;color:#22d3ee;font-weight:700;margin-bottom:12px}.chart-canvas{width:100%!important;max-height:300px}
3329
+ /* ── BADGES / TAGS ── */
3330
+ .badge-high{display:inline-block;background:#7f1d1d;color:#ef4444;border-radius:12px;padding:2px 10px;font-size:10px;font-weight:700;margin-right:4px}.badge-med{display:inline-block;background:#713f12;color:#f59e0b;border-radius:12px;padding:2px 10px;font-size:10px;font-weight:700;margin-right:4px}.badge-low{display:inline-block;background:#14532d;color:#34d399;border-radius:12px;padding:2px 10px;font-size:10px;font-weight:700;margin-right:4px}.badge-info{display:inline-block;background:#1e1b4b;color:#a5b4fc;border-radius:12px;padding:2px 10px;font-size:10px;font-weight:700;margin-right:4px}
3331
+ /* ── MISC ── */
3332
+ .divider{border:none;border-top:1px solid #2a2a38;margin:16px 0}.footer{text-align:center;padding:18px;font-size:11px;color:#4a4a5e;margin-top:8px}
3333
+ /* ── PRINT / PDF ── */
3334
+ @media print{*{-webkit-print-color-adjust:exact!important;print-color-adjust:exact!important;color-adjust:exact!important}body{background:#fff!important;color:#111!important;padding:0!important}.header{background:linear-gradient(135deg,#4f46e5,#06b6d4)!important}.header h1,.header p,.meta span{color:#fff!important}.section,.card,.chart-wrap,.source-item,.priority-item,.data-table,.bar-row{break-inside:avoid;page-break-inside:avoid}.section{background:#f6f8ff!important;border:1px solid #dde1f0!important}.section-title,.chart-title{color:#4f46e5!important}.section p,.section h3{color:#1a1a2e!important}ul li,ol li{color:#374151!important}ul li::before{color:#4f46e5!important}.card{background:#f0f3fc!important;border:1px solid #dde1f0!important}.card h3{color:#1a1a2e!important}.card p{color:#374151!important}.priority-item,.source-item{background:#eef0f8!important}.data-table th{background:#e8eaf6!important;color:#4f46e5!important}.data-table td{color:#374151!important;border-bottom:1px solid #dde1f0!important}.data-table tr:nth-child(even) td{background:#f4f6fb!important}.bar-track{background:#e0e4ef!important}.footer{color:#9ca3af!important}a{color:#4f46e5!important}}`;
3335
+
3336
+ const wrapInNHATemplate = (bodyHtml, title) => `<!DOCTYPE html><html lang="${langCode}"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0"><title>${(title||'NHA Report').replace(/</g,'&lt;')}</title><style>${NHA_CSS}</style><script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.2/dist/chart.umd.min.js"><\/script></head><body>${bodyHtml}</body></html>`;
3337
+
3338
+ const canvasSystemPrompt = `You are a professional HTML dashboard generator. Output ONLY the HTML content that goes INSIDE the <body> tag. Do NOT output <!DOCTYPE>, <html>, <head>, <style>, or any wrapper tags — the CSS and Chart.js are already provided.
3339
+
3340
+ AVAILABLE CSS CLASSES:
3341
+ - .header > h1, p, .meta > span (gradient header banner)
3342
+ - .grid > .card > .card-label, h3, p (KPI stat grid)
3343
+ - .section > .section-title, h3, p (content sections with auto page-break)
3344
+ - .source-item > h4, p, a (news/email source items)
3345
+ - .priority-list > .priority-item > .priority-num, h4, p (ranked action list)
3346
+ - .bar-row > .bar-label, .bar-track > .bar-fill[style="width:X%"] (CSS bar charts)
3347
+ - .data-table (full HTML table: thead > tr > th, tbody > tr > td)
3348
+ - .chart-wrap > .chart-title + <canvas id="cN" class="chart-canvas"> (Chart.js chart)
3349
+ - .badge-high .badge-med .badge-low .badge-info (colored badges)
3350
+ - ul > li, ol > li, blockquote, .divider, .footer
3351
+
3352
+ TABLES — use whenever data has rows and columns:
3353
+ <div class="section"><div class="section-title">TABLE TITLE</div>
3354
+ <table class="data-table"><thead><tr><th>Col1</th><th>Col2</th></tr></thead><tbody><tr><td>val</td><td>val</td></tr></tbody></table></div>
3355
+
3356
+ CHARTS — Chart.js is loaded. Use for any quantitative comparison, trend, distribution:
3357
+ <div class="chart-wrap"><div class="chart-title">CHART TITLE</div>
3358
+ <canvas id="c1" class="chart-canvas"></canvas></div>
3359
+ <script>new Chart(document.getElementById('c1'),{type:'bar',data:{labels:['A','B','C'],datasets:[{label:'Value',data:[10,20,15],backgroundColor:['#6366f1','#22d3ee','#f59e0b']}]},options:{responsive:true,plugins:{legend:{labels:{color:'#f0f0f5'}}},scales:{x:{ticks:{color:'#8b8b9e'},grid:{color:'#1e1e2c'}},y:{ticks:{color:'#8b8b9e'},grid:{color:'#1e1e2c'}}}}});<\/script>
3360
+ Chart types: 'bar' | 'line' | 'pie' | 'doughnut' | 'radar'. Use unique ids: c1, c2, c3...
3361
+ For pie/doughnut omit scales. For line use borderColor instead of backgroundColor.
3267
3362
 
3268
3363
  RULES:
3269
3364
  - Language: ${language}. ALL text must be in ${language}.
3270
- - Use REAL data from the input — do NOT invent or fabricate
3271
- - URLs from the data: ALWAYS wrap in <a href="URL" target="_blank">clickable text</a>
3272
- - Use .priority-list for action items, .source-item for each email/news source, .bar-row for any percentage data
3365
+ - Use REAL data from the input — NEVER invent numbers or fabricate data
3366
+ - URLs: ALWAYS wrap in <a href="URL" target="_blank">text</a>
3367
+ - Use .data-table for any tabular data (comparisons, metrics, lists with 2+ columns)
3368
+ - Use Chart.js for any numeric data (percentages, counts, trends, rankings)
3369
+ - Use .grid>.card for KPI numbers at the top
3370
+ - Use .priority-list for action items
3273
3371
  - Output must start with <div class="header"> and end with <div class="footer">`;
3274
3372
 
3275
3373
  // ── Handle PDF/image attachment on first step ─────────────────
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.4.7';
8
+ export const VERSION = '13.4.9';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -2043,7 +2043,10 @@ export async function executeTool(action, params, config) {
2043
2043
  let installCmd, installArgs;
2044
2044
  if (language === 'python') {
2045
2045
  installCmd = 'pip3';
2046
- installArgs = ['install', '--quiet', '--target', path.default.join(sandboxDir, 'site-packages'), ...safePkgs];
2046
+ // --only-binary=:all: forces pre-built wheels — no setup.py/build hook execution,
2047
+ // eliminating arbitrary code execution during install.
2048
+ // Network access is still live (accepted risk for local CLI; use --no-index for server).
2049
+ installArgs = ['install', '--quiet', '--only-binary=:all:', '--target', path.default.join(sandboxDir, 'site-packages'), ...safePkgs];
2047
2050
  } else {
2048
2051
  // javascript / typescript
2049
2052
  fs.default.writeFileSync(path.default.join(sandboxDir, 'package.json'), JSON.stringify({ type: 'module' }));
@@ -3527,12 +3527,14 @@ function buildWorkflowChar(n) {
3527
3527
  \x27<circle cx="67" cy="9" r="5.5" fill="#22c55e"/>\x27+
3528
3528
  \x27<path d="M63 9 L66 12 L71 5" stroke="#fff" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>\x27
3529
3529
  : \x27\x27)+
3530
- // Flying papers when running
3530
+ // Floating document icon when running (bobbing above desk)
3531
3531
  (isActive ?
3532
- \x27<g class="prl-fly-doc" style="animation-delay:.2s;animation-duration:1.6s;transform-origin:35px 50px">\x27+
3533
- \x27<path d="M0,0 L11,0 L14,3 L14,18 L0,18 Z" fill="#0d0d20" stroke="#6366f1" stroke-width="1.2" transform="translate(35,50)"/>\x27+
3534
- \x27<line x1="37" y1="53" x2="47" y2="53" stroke="#6366f1" stroke-width=".8" opacity=".7"/>\x27+
3535
- \x27<line x1="37" y1="56" x2="47" y2="56" stroke="#6366f1" stroke-width=".8" opacity=".5"/>\x27+
3532
+ \x27<g class="prl-doc-hold" style="transform-origin:58px 10px">\x27+
3533
+ \x27<path d="M52 4 L66 4 L69 7 L69 22 L52 22 Z" fill="#0d0d20" stroke="#6366f1" stroke-width="1.5"/>\x27+
3534
+ \x27<path d="M66 4 L66 7 L69 7" fill="none" stroke="#6366f1" stroke-width="1"/>\x27+
3535
+ \x27<line x1="55" y1="10" x2="66" y2="10" stroke="#6366f1ee" stroke-width=".9" stroke-linecap="round"/>\x27+
3536
+ \x27<line x1="55" y1="13" x2="64" y2="13" stroke="#6366f1aa" stroke-width=".9" stroke-linecap="round"/>\x27+
3537
+ \x27<line x1="55" y1="16" x2="65" y2="16" stroke="#6366f188" stroke-width=".9" stroke-linecap="round"/>\x27+
3536
3538
  \x27</g>\x27
3537
3539
  : \x27\x27)+
3538
3540
  \x27</svg>\x27;
@@ -3546,49 +3548,136 @@ function renderStudioNodes() {
3546
3548
  el.innerHTML = '<div class="studio-canvas__empty"><div class="studio-canvas__empty-icon">&#9881;</div><div>Describe a task and click Run</div></div>';
3547
3549
  return;
3548
3550
  }
3549
- var html = '<div class="studio-nodes">';
3550
- studioState.nodes.forEach(function(n, i) {
3551
- var isActive = n.status === 'running';
3552
- var isDone = n.status === 'done';
3553
- var isErr = n.status === 'error';
3554
- var isWait = !isActive && !isDone && !isErr;
3555
- var cls = 'studio-node';
3556
- if (isActive) cls += ' studio-node--active';
3557
- else if (isDone) cls += ' studio-node--done';
3558
- else if (isErr) cls += ' studio-node--error';
3559
- // Only animate entrance on first appearance
3560
- var style = n._rendered ? '' : 'animation-delay:' + (i * 110) + 'ms';
3561
- html += '<div class="' + cls + '" data-agent-label="' + esc(n.label || n.agent) + '" style="' + style + ';cursor:pointer" onclick="studioScrollToAgent(this.getAttribute(String.fromCharCode(100,97,116,97,45,97,103,101,110,116,45,108,97,98,101,108)))" title="' + esc(n.label || n.agent) + '">';
3562
- if (isActive || isDone) {
3563
- // Show animated office character
3564
- html += '<div class="studio-node__char">' + buildWorkflowChar(n) + '</div>';
3565
- if (isActive) {
3566
- var desc = n.label || n.agent;
3567
- html += '<div class="studio-node__bubble prl-action-bubble prl-action-bubble--active">...analizza</div>';
3568
- } else {
3569
- html += '<div class="studio-node__bubble prl-action-bubble" style="background:#0a2010;border-color:#22c55e;color:#4ade80">\u2714 completato</div>';
3570
- }
3571
- html += '<div class="studio-node__label studio-node__label--char">' + esc(n.label) + '</div>';
3572
- } else {
3573
- // Waiting / error: keep original compact pill
3574
- html += '<div class="studio-node__circle">' + n.icon + '</div>';
3575
- html += '<div class="studio-node__label">' + esc(n.label) + '</div>';
3576
- }
3577
- if (n.reason) {
3578
- html += '<div class="studio-node__reason" onclick="event.stopPropagation();this.classList.toggle(String.fromCharCode(111,112,101,110))" title="' + esc(n.reason) + '">&#x2139;<span class="studio-node__reason-tip">' + esc(n.reason) + '</span></div>';
3579
- }
3580
- html += '</div>';
3581
- if (i < studioState.nodes.length - 1) {
3582
- var next = studioState.nodes[i + 1];
3583
- var arrowCls = 'studio-arrow';
3584
- if (isDone && next.status === 'running') arrowCls += ' studio-arrow--active';
3585
- else if (isDone) arrowCls += ' studio-arrow--done';
3586
- var arrowStyle = n._rendered ? '' : 'opacity:0;animation:stNodeIn .3s ease ' + (i * 110 + 55) + 'ms forwards';
3587
- html += '<div class="' + arrowCls + '" style="' + arrowStyle + '">&#8594;</div>';
3588
- }
3551
+ // ── Parliament-style office scene ──────────────────────────────────────────
3552
+ // Renders all pipeline nodes as the same parliament office scene:
3553
+ // agents at desks, orchestrator walking + sbraita (speech bubble yelling).
3554
+ var hasActive = studioState.nodes.some(function(n){ return n.status === \x27running\x27; });
3555
+ var hasDone = studioState.nodes.some(function(n){ return n.status === \x27done\x27; });
3556
+ var showMaster = hasActive || hasDone;
3557
+
3558
+ // Sbraita phrases orchestrator yells at agents
3559
+ var sbraitaPhrases = [
3560
+ \x27PIU VELOCE!!!\x27, \x27FOCUS!!!\x27, \x27DEADLINE!!!\x27, \x27CHE FAI??!\x27,
3561
+ \x27MUOVITI!!\x27, \x27NO ERRORI!\x27, \x27ADESSO!!\x27, \x27SBRIGATI!\x27
3562
+ ];
3563
+ var activeNode = studioState.nodes.find(function(n){ return n.status === \x27running\x27; });
3564
+ var sbIdx = activeNode ? Math.abs((activeNode.label||activeNode.agent||\x27x\x27).charCodeAt(0)) % sbraitaPhrases.length : 0;
3565
+ var sbraitaText = sbraitaPhrases[sbIdx];
3566
+
3567
+ // Build desks row — same card structure as parliament
3568
+ var desksHtml2 = \x27\x27;
3569
+ studioState.nodes.forEach(function(n) {
3570
+ var isActive = n.status === \x27running\x27;
3571
+ var isDone = n.status === \x27done\x27;
3572
+ var isErr = n.status === \x27error\x27;
3573
+ 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;
3574
+ if (isActive) desksHtml2 += \x27<div class="prl-action-bubble prl-action-bubble--active" style="font-size:8px;padding:2px 6px">\u2026lavora</div>\x27;
3575
+ 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;
3576
+ 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;
3577
+ else desksHtml2 += \x27<div class="prl-action-bubble" style="font-size:8px;padding:2px 6px;opacity:.5">in attesa</div>\x27;
3578
+ desksHtml2 += buildWorkflowChar(n);
3579
+ 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;
3580
+ desksHtml2 += \x27</div>\x27;
3589
3581
  n._rendered = true;
3590
3582
  });
3591
- html += '</div>';
3583
+
3584
+ // ── Master orchestrator SVG ─────────────────────────────────────────────────
3585
+ var masterColor3 = \x27#818cf8\x27;
3586
+ 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+
3587
+ \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+
3588
+ \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+
3589
+ \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+
3590
+ \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+
3591
+ \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+
3592
+ \x27<path d="M13 32 C12 30 15 27 25 25 L25 55 L12 55 Z" fill="#1e1d44"/>\x27+
3593
+ \x27<path d="M25 25 C35 27 38 30 37 32 L38 55 L25 55 Z" fill="#272660"/>\x27+
3594
+ \x27<path d="M25 25 L19 33 L22 36 L25 29 Z" fill="#1a1940"/>\x27+
3595
+ \x27<path d="M25 25 L31 33 L28 36 L25 29 Z" fill="#1a1940"/>\x27+
3596
+ \x27<path d="M25 29 L22 36 L25 34 L28 36 Z" fill="#f0f0fa"/>\x27+
3597
+ \x27<path d="M25 33 L24 44 L25 48 L26 44 Z" fill="\x27+masterColor3+\x27"/>\x27+
3598
+ \x27<path d="M23.5 32 L26.5 32 L25 34 Z" fill="\x27+masterColor3+\x27"/>\x27+
3599
+ \x27<path d="M33 35 L36 33 L37 36 L34 37 Z" fill="\x27+masterColor3+\x2799"/>\x27+
3600
+ \x27<circle cx="25" cy="42" r="1.2" fill="\x27+masterColor3+\x27aa"/>\x27+
3601
+ \x27<circle cx="20" cy="36" r="2.5" fill="#0d0d1e" stroke="\x27+masterColor3+\x2799" stroke-width="1"/>\x27+
3602
+ \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+
3603
+ \x27<g class="prl-master-arm-l">\x27+
3604
+ \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+
3605
+ \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+
3606
+ \x27<ellipse cx="7" cy="54" rx="4.5" ry="3.5" fill="#d4a97a" transform="rotate(-15 7 54)"/>\x27+
3607
+ \x27</g>\x27+
3608
+ \x27<g class="prl-master-arm-r">\x27+
3609
+ \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+
3610
+ \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+
3611
+ \x27<ellipse cx="45" cy="52" rx="4" ry="3" fill="#d4a97a" transform="rotate(15 45 52)"/>\x27+
3612
+ \x27<rect x="43" y="32" width="14" height="19" rx="2.5" fill="#1a1a2e" stroke="\x27+masterColor3+\x2799" stroke-width="1.5"/>\x27+
3613
+ \x27<rect x="47" y="30" width="6" height="5" rx="1.5" fill="\x27+masterColor3+\x27"/>\x27+
3614
+ \x27<rect x="48" y="31" width="4" height="3" rx="1" fill="#0f0f1e"/>\x27+
3615
+ \x27<line x1="46" y1="36" x2="54" y2="36" stroke="\x27+masterColor3+\x27cc" stroke-width="1"/>\x27+
3616
+ \x27<line x1="46" y1="39" x2="54" y2="39" stroke="\x27+masterColor3+\x27aa" stroke-width="1"/>\x27+
3617
+ \x27<line x1="46" y1="42" x2="54" y2="42" stroke="\x27+masterColor3+\x2788" stroke-width="1"/>\x27+
3618
+ \x27</g>\x27+
3619
+ \x27<path d="M22 25 L28 25 L28 29 Q28 31 25 31 Q22 31 22 29 Z" fill="#d4a97a"/>\x27+
3620
+ \x27<ellipse cx="25" cy="15" rx="12" ry="13" fill="#d4a97a"/>\x27+
3621
+ \x27<path d="M14 15 C14 22 18 26 25 27 C32 26 36 22 36 15" fill="#d4a97a"/>\x27+
3622
+ \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+
3623
+ \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+
3624
+ \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+
3625
+ \x27<path d="M22 3 C21 4 21 6 22 8" stroke="rgba(255,255,255,.15)" stroke-width="1.5" fill="none"/>\x27+
3626
+ \x27<path d="M16.5 11 Q19 9.5 21.5 11" stroke="#1a0e08" stroke-width="1.8" fill="none" stroke-linecap="round"/>\x27+
3627
+ \x27<path d="M28.5 11 Q31 9.5 33.5 11" stroke="#1a0e08" stroke-width="1.8" fill="none" stroke-linecap="round"/>\x27+
3628
+ \x27<ellipse cx="19.5" cy="14.5" rx="3.2" ry="3.5" fill="#fff"/>\x27+
3629
+ \x27<ellipse cx="30.5" cy="14.5" rx="3.2" ry="3.5" fill="#fff"/>\x27+
3630
+ \x27<circle cx="19.5" cy="15" r="2.3" fill="#1e3a6e"/>\x27+
3631
+ \x27<circle cx="30.5" cy="15" r="2.3" fill="#1e3a6e"/>\x27+
3632
+ \x27<circle cx="19.5" cy="15" r="1.3" fill="#0a0a18"/>\x27+
3633
+ \x27<circle cx="30.5" cy="15" r="1.3" fill="#0a0a18"/>\x27+
3634
+ \x27<circle cx="20.5" cy="13.7" r=".9" fill="rgba(255,255,255,.95)"/>\x27+
3635
+ \x27<circle cx="31.5" cy="13.7" r=".9" fill="rgba(255,255,255,.95)"/>\x27+
3636
+ // Angry expression when active (furrowed brows + open mouth)
3637
+ (hasActive ?
3638
+ \x27<path d="M19.5 23 Q25 26 30.5 23" stroke="#8b4513" stroke-width="1.8" fill="none" stroke-linecap="round"/>\x27+
3639
+ \x27<path d="M20 23.5 Q25 26 30 23.5 Q25 25 20 23.5" fill="#fff" opacity=".6"/>\x27+
3640
+ // Open mouth / yelling
3641
+ \x27<ellipse cx="25" cy="24.5" rx="3" ry="1.5" fill="#5a1a0a" opacity=".7"/>\x27
3642
+ :
3643
+ \x27<path d="M19.5 24 Q25 27.5 30.5 24" stroke="#8b4513" stroke-width="1.8" fill="none" stroke-linecap="round"/>\x27+
3644
+ \x27<path d="M20 24.5 Q25 27 30 24.5 Q25 26.5 20 24.5" fill="#fff" opacity=".7"/>\x27
3645
+ )+
3646
+ \x27<circle cx="25" cy="-4" r="9" fill="\x27+masterColor3+\x2722" stroke="\x27+masterColor3+\x2766" stroke-width="1.5"/>\x27+
3647
+ \x27<path d="M25 -13 L30 -4 L25 5 L20 -4 Z" fill="\x27+masterColor3+\x27" stroke="\x27+masterColor3+\x27cc" stroke-width=".8"/>\x27+
3648
+ \x27<path d="M25 -13 L30 -4 L25 -3 L20 -4 Z" fill="\x27+masterColor3+\x27aa"/>\x27+
3649
+ \x27</svg>\x27;
3650
+
3651
+ // Speech bubble "sbraita" when there is an active agent
3652
+ var bubbleHtml = showMaster && hasActive
3653
+ ? (\x27<div class="wf-sbraita-bubble">\x27+sbraitaText+\x27</div>\x27)
3654
+ : \x27\x27;
3655
+
3656
+ var masterWfHtml = showMaster ? (
3657
+ \x27<div class="prl-master prl-master-walk wf-master" style="bottom:20px">\x27+
3658
+ bubbleHtml+
3659
+ masterSvg2+
3660
+ \x27<div class="prl-master-label" style="color:\x27+masterColor3+\x27;font-size:8px">Orchestratore</div>\x27+
3661
+ \x27</div>\x27
3662
+ ) : \x27\x27;
3663
+
3664
+ // Phase label
3665
+ var doneCount = studioState.nodes.filter(function(n){return n.status===\x27done\x27;}).length;
3666
+ var totalCount = studioState.nodes.length;
3667
+ var phaseLabel2 = hasActive
3668
+ ? (\x27Workflow in esecuzione \u2014 \x27+doneCount+\x27/\x27+totalCount+\x27 completati\x27)
3669
+ : (doneCount===totalCount && totalCount>0 ? \x27Workflow completato\x27 : \x27Workflow pianificato\x27);
3670
+ var phaseColor2 = hasActive ? \x27#6366f1\x27 : (doneCount===totalCount && totalCount>0 ? \x27#22c55e\x27 : \x27#6b7280\x27);
3671
+
3672
+ var html = \x27<div class="prl-wrap" style="border-color:\x27+phaseColor2+\x2744;padding-bottom:16px">\x27+
3673
+ \x27<div class="prl-header"><span class="prl-phase-chip" style="--pc:\x27+phaseColor2+\x27">\x27+phaseLabel2+\x27</span></div>\x27+
3674
+ \x27<div class="prl-office wf-office" style="min-height:160px">\x27+
3675
+ \x27<div class="prl-office-floor"></div>\x27+
3676
+ \x27<div class="prl-desks-row" style="justify-content:center;flex-wrap:wrap;gap:10px;padding-bottom:8px">\x27+desksHtml2+\x27</div>\x27+
3677
+ masterWfHtml+
3678
+ \x27</div>\x27+
3679
+ \x27</div>\x27;
3680
+
3592
3681
  el.innerHTML = html;
3593
3682
  }
3594
3683
 
@@ -3924,172 +4013,55 @@ function downloadStudioPDF() {
3924
4013
  '</div>' +
3925
4014
  '</body></html>';
3926
4015
 
3927
- // Generate PDF injects print-safe CSS then uses html2canvas at 2.5x for crisp output.
3928
- // Page breaks are avoided inside .section/.card/.priority-item by injecting break-inside:avoid.
3929
- var pdfFileName = (studioState.task || 'NHA Studio Report').slice(0, 60).replace(/[^a-z0-9\s]/gi,'').trim().replace(/\s+/g,'-') + '.pdf';
4016
+ // ── Generate PDF via browser native print dialog ──────────────────────────
4017
+ // Renders the full NHA report HTML in a hidden iframe, waits for Chart.js to
4018
+ // initialize all charts, then calls window.print() on the iframe.
4019
+ // The CSS @media print block (already in NHA_CSS) handles all styling:
4020
+ // - Light backgrounds for readability on paper
4021
+ // - break-inside:avoid on .section/.card/.priority-item/.data-table
4022
+ // - -webkit-print-color-adjust:exact for gradient headers and charts
4023
+ // This approach never cuts text in the middle of a paragraph.
3930
4024
  function doGeneratePdf() {
3931
4025
  var btn2 = document.getElementById('studioInlinePdfBtn');
3932
4026
  var dlBtn2 = document.querySelector('button[onclick="downloadStudioPDF()"]');
3933
4027
  function setBusy(b) {
3934
- if (btn2) { btn2.disabled = b; btn2.textContent = b ? 'Generando PDF...' : '\u2913 PDF'; }
3935
- if (dlBtn2) { dlBtn2.disabled = b; dlBtn2.textContent = b ? 'Generando PDF...' : '\u2913 Download PDF'; }
4028
+ if (btn2) { btn2.disabled = b; btn2.textContent = b ? 'Apertura stampa...' : '\u2913 PDF'; }
4029
+ if (dlBtn2) { dlBtn2.disabled = b; dlBtn2.textContent = b ? 'Apertura stampa...' : '\u2913 Download PDF'; }
3936
4030
  }
3937
4031
  setBusy(true);
3938
4032
 
3939
- // Inject page-break-safe CSS into the HTML before rendering
3940
- var printCss = '<style>body{padding:20px!important;max-width:800px!important;margin:0 auto!important}' +
3941
- '.section,.card,.priority-item,.source-item,.bar-row{break-inside:avoid;page-break-inside:avoid}' +
3942
- '.header{break-after:avoid;page-break-after:avoid}' +
3943
- 'h1,h2,h3,h4{break-after:avoid;page-break-after:avoid}' +
3944
- '@media print{body{background:#fff!important;color:#111!important}' +
3945
- '.header{background:linear-gradient(135deg,#4f46e5,#06b6d4)!important;-webkit-print-color-adjust:exact}' +
3946
- '.section{background:#f8f9fa!important;border:1px solid #e0e0e0!important}' +
3947
- '.section-title{color:#4f46e5!important}}</style>';
3948
- var pdfHtml = html.replace('</head>', printCss + '</head>');
3949
-
3950
- // Inject light-mode overrides for PDF — dark backgrounds become unreadable on paper
3951
- var lightOverride = '<style>' +
3952
- 'body{background:#ffffff!important;color:#1a1a2e!important;padding:28px!important;max-width:760px!important;margin:0 auto!important}' +
3953
- '.header{background:linear-gradient(135deg,#4f46e5 0%,#06b6d4 100%)!important;-webkit-print-color-adjust:exact!important;color-adjust:exact!important}' +
3954
- '.header h1,.header p,.meta span{color:#fff!important}' +
3955
- '.card,.section{background:#f4f6fb!important;border:1px solid #dde1ee!important}' +
3956
- '.card h3,.section h3,.section-title,.card-label{color:#4f46e5!important}' +
3957
- '.card p,.section p,ul li,ol li{color:#374151!important}' +
3958
- '.priority-item{background:#eef0f8!important}' +
3959
- '.priority-item h4{color:#1a1a2e!important}' +
3960
- '.priority-item p{color:#374151!important}' +
3961
- '.source-item{background:#eef0f8!important;border-left-color:#4f46e5!important}' +
3962
- '.source-item h4{color:#1a1a2e!important}' +
3963
- '.source-item p{color:#374151!important}' +
3964
- '.bar-track{background:#e0e4ef!important}' +
3965
- '.footer{color:#9ca3af!important}' +
3966
- 'a{color:#4f46e5!important}' +
3967
- '.section,.card,.priority-item,.source-item,.bar-row{break-inside:avoid;page-break-inside:avoid}' +
3968
- 'h1,h2,h3,h4{break-after:avoid;page-break-after:avoid}' +
3969
- '.header{break-after:avoid;page-break-after:avoid}' +
3970
- '.section-body h4{font-size:12px;font-weight:600;color:#6366f1!important;margin:10px 0 4px;text-transform:uppercase;letter-spacing:.4px}' +
3971
- '</style>';
3972
- var pdfHtml2 = html.replace('</head>', lightOverride + '</head>');
3973
-
3974
- // Build hidden iframe at 794px (A4 width at 96dpi = 210mm)
3975
- var iframe = document.createElement('iframe');
3976
- iframe.style.cssText = 'position:fixed;left:-9999px;top:0;width:794px;height:1px;border:none;visibility:hidden';
3977
- document.body.appendChild(iframe);
3978
- var ifrDoc = iframe.contentDocument || iframe.contentWindow.document;
3979
- ifrDoc.open(); ifrDoc.write(pdfHtml2); ifrDoc.close();
3980
- iframe.onload = function() {
3981
- var ifrBody = ifrDoc.body;
3982
- var totalH = Math.max(ifrBody.scrollHeight, ifrBody.offsetHeight, ifrDoc.documentElement.scrollHeight);
3983
- iframe.style.height = totalH + 'px';
3984
-
3985
- // Collect smart page-break candidates using offsetTop (document-relative, not viewport-relative).
3986
- // We use offsetTop + offsetHeight of each .section element so breaks land after each agent section.
3987
- var breakCandidates = [0];
3988
- var sectionEls = ifrDoc.querySelectorAll('.section,.cover,.toc,.workflow-bar');
3989
- for (var si2 = 0; si2 < sectionEls.length; si2++) {
3990
- var el2 = sectionEls[si2];
3991
- // Walk up to get absolute offsetTop within the iframe document
3992
- var absTop = 0; var cur = el2;
3993
- while (cur && cur !== ifrDoc.body) { absTop += cur.offsetTop; cur = cur.offsetParent; }
3994
- breakCandidates.push(absTop); // start of section (new page begins here)
3995
- breakCandidates.push(absTop + el2.offsetHeight); // end of section
3996
- }
3997
- breakCandidates.push(totalH);
3998
- breakCandidates.sort(function(a,b){ return a-b; });
3999
- // Deduplicate
4000
- breakCandidates = breakCandidates.filter(function(v,i,a){ return i===0||v!==a[i-1]; });
4001
-
4002
- // Scale: 3x on HiDPI screens, minimum 2.5x for sharp text at A4
4003
- var renderScale = Math.max(2.5, Math.min(3, window.devicePixelRatio * 1.5));
4004
- window.html2canvas(ifrBody, {
4005
- scale: renderScale,
4006
- useCORS: true,
4007
- allowTaint: true,
4008
- backgroundColor: '#ffffff',
4009
- width: 794,
4010
- windowWidth: 794,
4011
- scrollX: 0,
4012
- scrollY: 0,
4013
- logging: false,
4014
- imageTimeout: 15000,
4015
- ignoreElements: function(el){ return el.tagName === 'SCRIPT' || el.tagName === 'NOSCRIPT'; }
4016
- }).then(function(canvas) {
4017
- var pdf = new window.jspdf.jsPDF({orientation:'portrait', unit:'pt', format:'a4', compress:true});
4018
- var pageW = pdf.internal.pageSize.getWidth(); // 595.28pt
4019
- var pageH = pdf.internal.pageSize.getHeight(); // 841.89pt
4020
- var margin = 28; // pt — ~10mm margins
4021
- var usableW = pageW - margin * 2;
4022
- var usableH = pageH - margin * 2;
4023
-
4024
- // px per rendered page: canvas.width / usableW gives canvas-px per pt;
4025
- // usableH (pt) * that ratio = canvas px per A4 usable page height
4026
- var pxPerPt = canvas.width / usableW;
4027
- var maxSliceH = Math.floor(usableH * pxPerPt); // max canvas px per page
4028
-
4029
- // Convert DOM break candidates to canvas px coordinates
4030
- // (DOM px * renderScale because html2canvas renders at renderScale)
4031
- var canvasBreaks = breakCandidates.map(function(domPx){ return Math.round(domPx * renderScale); });
4032
-
4033
- // Smart page-break slicer: each slice ends at the nearest break candidate that fits within maxSliceH.
4034
- // Falls back to hard-cut only when a single section is taller than one full page.
4035
- var yCanvas = 0;
4036
- var pageNum = 0;
4037
- while (yCanvas < canvas.height) {
4038
- if (pageNum > 0) pdf.addPage();
4039
- var maxEnd = yCanvas + maxSliceH;
4040
- // Find the largest break candidate <= maxEnd (that is also > yCanvas)
4041
- var bestBreak = -1;
4042
- for (var bi2 = 0; bi2 < canvasBreaks.length; bi2++) {
4043
- var bp = canvasBreaks[bi2];
4044
- if (bp > yCanvas && bp <= maxEnd) { bestBreak = bp; }
4045
- }
4046
- // If no break found (section taller than a page), hard-cut at maxEnd
4047
- var sliceEnd = (bestBreak > yCanvas) ? bestBreak : Math.min(maxEnd, canvas.height);
4048
- var thisSlice = sliceEnd - yCanvas;
4049
- if (thisSlice <= 0) break; // safety guard
4050
-
4051
- var sliceCanvas = document.createElement('canvas');
4052
- sliceCanvas.width = canvas.width;
4053
- sliceCanvas.height = thisSlice;
4054
- var ctx = sliceCanvas.getContext('2d');
4055
- ctx.fillStyle = '#ffffff';
4056
- ctx.fillRect(0, 0, sliceCanvas.width, sliceCanvas.height);
4057
- ctx.drawImage(canvas, 0, yCanvas, canvas.width, thisSlice, 0, 0, canvas.width, thisSlice);
4058
- var sliceData = sliceCanvas.toDataURL('image/png');
4059
- // Proportional height in pt: thisSlice / pxPerPt
4060
- var sliceImgH = thisSlice / pxPerPt;
4061
- pdf.addImage(sliceData, 'PNG', margin, margin, usableW, sliceImgH, '', 'FAST');
4062
- yCanvas = sliceEnd;
4063
- pageNum++;
4064
- }
4065
- pdf.save(pdfFileName);
4066
- document.body.removeChild(iframe);
4067
- setBusy(false);
4068
- }).catch(function(e2) {
4069
- document.body.removeChild(iframe);
4033
+ // Open report in a new browser window and trigger print dialog.
4034
+ // The browser handles all page-break logic natively — no rasterization,
4035
+ // no text-cut artifacts. Charts render as vectors via Chart.js.
4036
+ // The @media print CSS block (in NHA_CSS) handles light-mode override.
4037
+ var printWin = window.open('', '_blank', 'width=900,height=700');
4038
+ if (!printWin) {
4039
+ // Popup blocked — fallback: open in current tab print
4040
+ setBusy(false);
4041
+ var fb = window.open(URL.createObjectURL(new Blob([html], {type:'text/html'})), '_blank');
4042
+ if (fb) { setTimeout(function(){ fb.print(); }, 1500); }
4043
+ return;
4044
+ }
4045
+ printWin.document.open();
4046
+ printWin.document.write(html);
4047
+ printWin.document.close();
4048
+ // Wait for Chart.js to render all charts before printing
4049
+ // (charts need ~500ms after DOMContentLoaded for animation frame)
4050
+ printWin.onload = function() {
4051
+ setTimeout(function() {
4052
+ printWin.focus();
4053
+ printWin.print();
4070
4054
  setBusy(false);
4071
- alert('PDF error: ' + e2.message);
4072
- });
4055
+ // Close the print window after a short delay (user may close it themselves)
4056
+ setTimeout(function(){ try { printWin.close(); } catch(e3){} }, 3000);
4057
+ }, 600);
4073
4058
  };
4059
+ // Fallback if onload already fired
4060
+ setTimeout(function() {
4061
+ setBusy(false);
4062
+ }, 4000);
4074
4063
  }
4075
- // Load jsPDF + html2canvas from CDN if not already loaded
4076
- if (window.jspdf && window.html2canvas) {
4077
- doGeneratePdf();
4078
- } else {
4079
- var loaded = 0;
4080
- function onLibLoad() { loaded++; if (loaded >= 2) doGeneratePdf(); }
4081
- if (!window.html2canvas) {
4082
- var s1 = document.createElement('script');
4083
- s1.src = 'https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js';
4084
- s1.onload = onLibLoad; document.head.appendChild(s1);
4085
- } else { loaded++; }
4086
- if (!window.jspdf) {
4087
- var s2 = document.createElement('script');
4088
- s2.src = 'https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js';
4089
- s2.onload = onLibLoad; document.head.appendChild(s2);
4090
- } else { loaded++; }
4091
- if (loaded >= 2) doGeneratePdf();
4092
- }
4064
+ doGeneratePdf();
4093
4065
  }
4094
4066
 
4095
4067
  function renderStudioResult() {
@@ -4290,10 +4262,22 @@ async function runStudio() {
4290
4262
  if (!pb) return;
4291
4263
  pb.style.display = \x27block\x27;
4292
4264
  if (convergence != null) {
4293
- pb.style.position = \x27\x27; pb.style.top = \x27\x27; pb.style.zIndex = \x27\x27; pb.style.boxShadow = \x27\x27;
4265
+ // Deliberation complete return to normal flow, anchored in place
4266
+ pb.style.position = \x27\x27; pb.style.bottom = \x27\x27; pb.style.left = \x27\x27;
4267
+ pb.style.transform = \x27\x27; pb.style.width = \x27\x27;
4268
+ pb.style.top = \x27\x27; pb.style.zIndex = \x27\x27; pb.style.boxShadow = \x27\x27;
4269
+ pb.style.maxWidth = \x27\x27;
4294
4270
  } else {
4295
- pb.style.position = \x27sticky\x27; pb.style.top = \x278px\x27; pb.style.zIndex = \x27200\x27;
4296
- pb.style.boxShadow = \x270 4px 32px rgba(99,102,241,.35)\x27;
4271
+ // Fixed overlay at bottom of viewport so it never scrolls away
4272
+ pb.style.position = \x27fixed\x27;
4273
+ pb.style.bottom = \x2716px\x27;
4274
+ pb.style.left = \x2750%\x27;
4275
+ pb.style.transform = \x27translateX(-50%)\x27;
4276
+ pb.style.width = \x27calc(100vw - 32px)\x27;
4277
+ pb.style.maxWidth = \x27860px\x27;
4278
+ pb.style.top = \x27\x27;
4279
+ pb.style.zIndex = \x27500\x27;
4280
+ pb.style.boxShadow = \x270 8px 40px rgba(99,102,241,.5),0 2px 0 rgba(99,102,241,.2)\x27;
4297
4281
  }
4298
4282
 
4299
4283
  // ── OFFICE CARTOON ANIMATION ─────────────────────────────────────────
@@ -4554,7 +4538,7 @@ async function runStudio() {
4554
4538
  var masterIcon = phase===\x27r3\x27 ? \x27\u26a1\x27 : (phase===\x27done\x27 ? \x27\u2714\x27 : \x27\u2666\x27);
4555
4539
  var masterColor2 = {r1:\x27#818cf8\x27,r2:\x27#818cf8\x27,r3:\x27#f59e0b\x27,done:\x27#22c55e\x27}[phase]||\x27#818cf8\x27;
4556
4540
  var masterAnim = (phase===\x27r1\x27) ? \x27prl-master-walk\x27 : (phase===\x27r2\x27 ? \x27prl-master-supervise\x27 : \x27\x27);
4557
- var masterSvg = \x27<svg viewBox="0 0 60 90" width="56" height="86" xmlns="http://www.w3.org/2000/svg" style="filter:drop-shadow(0 0 12px \x27+masterColor2+\x27aa)">\x27+
4541
+ var masterSvg = \x27<svg viewBox="0 0 60 90" width="56" height="86" xmlns="http://www.w3.org/2000/svg" font-family="system-ui,sans-serif" style="filter:drop-shadow(0 0 12px \x27+masterColor2+\x27aa)">\x27+
4558
4542
  // ════ LEGS (walking when R1) ════
4559
4543
  // Left leg — trouser
4560
4544
  \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+
@@ -4664,10 +4648,19 @@ async function runStudio() {
4664
4648
  // Confident smile
4665
4649
  \x27<path d="M19.5 24 Q25 27.5 30.5 24" stroke="#8b4513" stroke-width="1.8" fill="none" stroke-linecap="round"/>\x27+
4666
4650
  \x27<path d="M20 24.5 Q25 27 30 24.5 Q25 26.5 20 24.5" fill="#fff" opacity=".7"/>\x27+
4667
- // Crown / authority icon above head
4668
- \x27<text x="25" y="-1" text-anchor="middle" font-size="11" style="filter:drop-shadow(0 0 4px \x27+masterColor2+\x27)">\x27+masterIcon+\x27</text>\x27+
4669
- // Subtle glow ring around crown icon
4670
- \x27<circle cx="25" cy="-2" r="8" fill="none" stroke="\x27+masterColor2+\x2730" stroke-width="1.5"/>\x27+
4651
+ // Crown / authority badge above head — pure SVG geometry (no font-dependent text)
4652
+ \x27<circle cx="25" cy="-4" r="9" fill="\x27+masterColor2+\x2722" stroke="\x27+masterColor2+\x2766" stroke-width="1.5"/>\x27+
4653
+ (phase===\x27r3\x27 ?
4654
+ // Lightning bolt for R3 mediation
4655
+ \x27<path d="M27 -12 L22 -4 L25.5 -4 L23 4 L29 -3 L25.5 -3 Z" fill="\x27+masterColor2+\x27" stroke="\x27+masterColor2+\x2788" stroke-width=".6" stroke-linejoin="round"/>\x27
4656
+ : phase===\x27done\x27 ?
4657
+ // Checkmark for done
4658
+ \x27<path d="M19 -4 L23.5 0.5 L31 -9" stroke="\x27+masterColor2+\x27" stroke-width="2.2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>\x27
4659
+ :
4660
+ // Diamond (authority) for r1/r2
4661
+ \x27<path d="M25 -13 L30 -4 L25 5 L20 -4 Z" fill="\x27+masterColor2+\x27" stroke="\x27+masterColor2+\x27cc" stroke-width=".8"/>\x27+
4662
+ \x27<path d="M25 -13 L30 -4 L25 -3 L20 -4 Z" fill="\x27+masterColor2+\x27aa"/>\x27
4663
+ )+
4671
4664
  \x27</svg>\x27;
4672
4665
 
4673
4666
  var masterLabel2 = {r1:\x27Orchestratore\x27,r2:\x27Coordina\x27,r3:\x27HERALD\x27,done:\x27Completato\x27}[phase]||\x27MASTER\x27;
@@ -5211,6 +5204,8 @@ function renderStudio(el) {
5211
5204
  {icon:'&#128247;',name:'CanvasAgent',desc:'Generate HTML visual report'},
5212
5205
  {icon:'&#128274;',name:'SecurityAgent',desc:'Security analysis'},
5213
5206
  {icon:'&#128295;',name:'DevOpsAgent',desc:'Infrastructure analysis'},
5207
+ {icon:'&#128190;',name:'CodeExecutorAgent',desc:'Run Python/JS/TS code in sandbox'},
5208
+ {icon:'&#128193;',name:'FileReaderAgent',desc:'Read local files & directories'},
5214
5209
  ];
5215
5210
 
5216
5211
  var toolsHtml = STUDIO_AGENTS.map(function(t){
@@ -5873,16 +5868,26 @@ input:focus,textarea:focus{border-color:var(--green3)}
5873
5868
  .studio-arrow{display:flex;align-items:center;color:var(--border2);font-size:18px;padding:0 8px;flex-shrink:0;margin-bottom:30px;transition:color .4s}
5874
5869
  .studio-arrow--active{color:var(--green3);animation:stFlow .5s ease-in-out infinite alternate}
5875
5870
  .studio-arrow--done{color:#22c55e}
5876
- /* ── Workflow node animated character ── */
5877
- .studio-nodes{align-items:flex-end!important;min-height:190px!important}
5878
- .studio-node--active,.studio-node--done{min-width:118px!important;max-width:140px!important;gap:4px!important}
5879
- .studio-node__char{display:flex;align-items:center;justify-content:center;width:100%}
5880
- .studio-node__bubble{font-size:9px;padding:2px 7px;border-radius:20px;white-space:nowrap;text-align:center;margin:0 auto}
5881
- .studio-node__label--char{font-size:10px;text-align:center;max-width:130px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:700;letter-spacing:.3px;color:#6366f1}
5882
- .studio-node--done .studio-node__label--char{color:#22c55e}
5871
+ /* ── Workflow scene (office layout, replaces cramped pill nodes) ── */
5872
+ .studio-canvas{background:none!important;border:none!important;padding:0!important;margin-bottom:0!important}
5873
+ #studioNodes .prl-office{border-radius:10px;border:1px solid var(--border);background:var(--bg2);margin-bottom:16px;min-height:200px;padding:16px 12px 8px;position:relative;overflow:visible}
5874
+ .wf-office{display:block}
5875
+ .wf-desks-row{display:flex;align-items:flex-end;justify-content:center;gap:6px;flex-wrap:wrap;padding-bottom:10px}
5876
+ .wf-desk{position:relative;display:flex;flex-direction:column;align-items:center;gap:2px;cursor:pointer;transition:opacity .2s}
5877
+ .wf-desk:hover{opacity:.85}
5878
+ .wf-desk--active{filter:drop-shadow(0 0 8px #6366f188)}
5879
+ .wf-desk--done{filter:drop-shadow(0 0 6px #22c55e55)}
5880
+ .wf-desk--err{filter:drop-shadow(0 0 6px #ef444455)}
5881
+ .wf-desk-name{font-size:9px;font-weight:600;text-align:center;max-width:88px;word-break:break-word;line-height:1.3;padding:0 2px;margin-top:2px}
5882
+ .wf-master{position:absolute;bottom:20px;right:16px}
5883
+ /* Orchestrator speech bubble — sbraita */
5884
+ .wf-sbraita-bubble{position:absolute;top:-28px;left:50%;transform:translateX(-50%);background:#1a0a0a;border:1.5px solid #ef4444;color:#fca5a5;font-family:var(--mono);font-size:9px;font-weight:800;padding:3px 8px;border-radius:8px;white-space:nowrap;letter-spacing:.5px;animation:sbraitaPop .4s ease-in-out infinite alternate;pointer-events:none;z-index:4}
5885
+ .wf-sbraita-bubble::after{content:"";position:absolute;top:100%;left:50%;transform:translateX(-50%);border:5px solid transparent;border-top-color:#ef4444}
5886
+ @keyframes sbraitaPop{0%{transform:translateX(-50%) scale(1) rotate(-2deg)}100%{transform:translateX(-50%) scale(1.06) rotate(2deg)}}
5883
5887
  /* ── Parliament Office Cartoon ── */
5884
5888
  .prl-wrap{background:#07070f;border:1.5px solid #6366f1;border-radius:14px;padding:14px 16px 12px;margin-bottom:16px;animation:stNodeIn .35s ease forwards;overflow:hidden}
5885
- #studioParliamentBlock[style*="sticky"] .prl-wrap{animation:stNodeIn .35s ease forwards,parlPulse 2.2s ease-in-out infinite}
5889
+ #studioParliamentBlock[style*="fixed"] .prl-wrap{animation:stNodeIn .35s ease forwards,parlPulse 2.2s ease-in-out infinite}
5890
+ #studioParliamentBlock[style*="fixed"]{backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px)}
5886
5891
  @keyframes parlPulse{0%,100%{border-color:#6366f1;box-shadow:none}50%{border-color:#818cf8;box-shadow:0 0 20px rgba(99,102,241,.3)}}
5887
5892
  .prl-header{display:flex;align-items:center;margin-bottom:10px}
5888
5893
  .prl-phase-chip{font-size:10px;font-weight:800;font-family:var(--mono);letter-spacing:.3px;color:var(--pc,#6366f1);background:rgba(99,102,241,.12);border:1px solid rgba(99,102,241,.35);border-radius:20px;padding:3px 12px;display:inline-block}