nothumanallowed 14.1.70 → 14.1.71

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": "14.1.70",
3
+ "version": "14.1.71",
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/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 = '14.1.70';
8
+ export const VERSION = '14.1.71';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -15,9 +15,67 @@ import { webSearch, fetchUrl } from '../../services/web-tools.mjs';
15
15
  const WEB_TOOL_AGENTS = new Set([
16
16
  'WebSearchAgent', 'TravelAgent', 'mercury', 'MERCURY',
17
17
  'athena', 'ATHENA', 'oracle', 'ORACLE', 'cassandra', 'CASSANDRA',
18
- 'HERALD', 'DataAnalystAgent',
18
+ 'HERALD', 'DataAnalystAgent', 'herald', 'tempest', 'TEMPEST',
19
+ 'epicure', 'EPICURE', 'cartographer', 'CARTOGRAPHER',
19
20
  ]);
20
21
 
22
+ // ── Complete Agent Registry ─────────────────────────────────────────────────
23
+ // Each entry: [emoji, name, short description for the planner]
24
+ const AGENT_REGISTRY = [
25
+ ['🔍','WebSearchAgent', 'Web search and real-time data retrieval. Use for any task needing current web information.'],
26
+ ['🍽','TravelAgent', 'Restaurant, hotel, B&B, travel recommendations. Searches web for real venues with reviews and prices.'],
27
+ ['📄','DocumentReaderAgent','Extract data from attached PDFs: specs, tables, model numbers, technical details.'],
28
+ ['📧','EmailAgent', 'Read/analyze inbox emails, identify urgent items and deadlines.'],
29
+ ['📅','CalendarAgent', 'Review calendar events, find scheduling conflicts, plan meetings.'],
30
+ ['💻','GitHubAgent', 'Analyze GitHub repos, issues, PRs, code quality metrics.'],
31
+ ['💬','SlackAgent', 'Read Slack channels/messages, identify important conversations.'],
32
+ ['📝','NotionAgent', 'Search and read Notion pages, databases, wikis.'],
33
+ ['📰','HERALD', 'OSINT intelligence analyst. Synthesizes all collected data into executive briefings with sourced claims and bias flags. Best used as final synthesizer after other agents.'],
34
+ ['🔭','ORACLE', 'Senior data scientist. Statistical inference, pattern recognition, decision science. Transforms raw data into actionable intelligence.'],
35
+ ['♟','ATHENA', 'Technology research analyst. Evaluates frameworks, approaches, techniques with scientific rigor. Strategic technology recommendations.'],
36
+ ['⚠','CASSANDRA', 'Risk analyst and predictive consequence engineer. Systematic risk assessment, threat modeling, scenario planning.'],
37
+ ['💰','MERCURY', 'CFA-level financial market analyst. Equity research, technical analysis, quantitative risk. Real-time market data interpretation.'],
38
+ ['🖊','QUILL', 'Conversion copywriter. Micro-content, headlines, CTAs, landing pages, email campaigns. Direct response copywriting.'],
39
+ ['📊','DataAnalystAgent', 'Data analysis, statistics, pattern extraction, trend identification from datasets.'],
40
+ ['🌐','polyglot', 'Computational linguist and translator. Cross-lingual NLP, localization, contrastive linguistics. Professional translation.'],
41
+ ['🎨','CanvasAgent', 'HTML dashboard/report generator. Creates visual canvas with charts and tables. USE ONLY AS LAST STEP.'],
42
+ // ── Specialist agents (not in old planner) ─────────
43
+ ['🗡','ADE', 'Offensive security specialist (red team). Penetration testing methodology, attack surface analysis, vulnerability exploitation paths.'],
44
+ ['🏗','ATLAS', 'Infrastructure-as-Code engineer. Cloud environments, Terraform, Pulumi, declarative configuration, production infrastructure.'],
45
+ ['🔗','BABEL', 'Integration architect and API design. Unifies disparate systems, REST/GraphQL/gRPC design, webhook orchestration.'],
46
+ ['🗺','CARTOGRAPHER', 'Geospatial analyst. GIS, geodesy, spatial data, location intelligence, map-based analysis.'],
47
+ ['🎼','CONDUCTOR', 'Workflow orchestration architect. Designs complex execution plans, dependency graphs, parallel/sequential coordination.'],
48
+ ['⏰','CRON', 'Automation architect and CI/CD engineer. Scheduled tasks, pipelines, fault-tolerant automation, GitHub Actions, cron jobs.'],
49
+ ['📢','ECHO', 'Content distribution and cross-channel amplification. Transforms content into multi-platform packages (social, blog, video scripts).'],
50
+ ['📐','EDI', 'Statistical modeling engineer. Model selection, validation, interpretation. Regression, time series, Bayesian inference.'],
51
+ ['🍳','EPICURE', 'Culinary scientist and gastronomic analyst. Food chemistry, global cuisine, nutrition science, recipe analysis, restaurant evaluation.'],
52
+ ['🔄','FLUX', 'Data transformation engineer. ETL algebra, format conversion, data reshaping, schema migration.'],
53
+ ['🔧','FORGE', 'Infrastructure architect. Production-grade systems, resilience engineering, system design, code architecture.'],
54
+ ['🐛','GLITCH', 'Data quality engineer. ETL/ELT pipelines, data cleaning, format transformation, messy data remediation.'],
55
+ ['👁','HEIMDALL', 'SRE and observability architect. Monitoring systems, alerting, SLO/SLA design, incident response, dashboards.'],
56
+ ['✉','HERMES', 'Event-driven architecture engineer. Message brokers, async communication, Kafka/RabbitMQ/NATS design.'],
57
+ ['📉','JARVIS', 'Data visualization architect. Dashboard design, chart selection, information display, D3/Plotly/Grafana.'],
58
+ ['🕸','LINK', 'Social graph analyst and community architect. Network analysis, community design, engagement optimization.'],
59
+ ['🧠','LOGOS', 'Formal logic analyst and argument auditor. Evaluates argument structure, consistency, logical validity, fallacy detection.'],
60
+ ['⚙','MACRO', 'Process automation engineer. Pattern recognition, template systems, bulk operations, workflow automation.'],
61
+ ['📜','MURASAKI', 'Long-form content architect. Whitepapers, research papers, essays, book chapters. Academic rigor + narrative craft.'],
62
+ ['🎭','MUSE', 'Visual content director. Art direction, visual communication, design systems, brand aesthetics.'],
63
+ ['🧭','NAVI', 'Data quality profiler. Dataset assessment, upstream validation, schema inspection, quality scoring before analysis.'],
64
+ ['🔌','PIPE', 'Data pipeline architect. Reliable data flows, stream processing, zero-loss record transport.'],
65
+ ['🔥','PROMETHEUS', 'Software evolution architect. System analysis, structural weakness identification, evolutionary improvement design, refactoring strategy.'],
66
+ ['🛡','SABER', 'Security auditor. Application security, infrastructure hardening, SSDLC, compliance, OWASP, pentest reports.'],
67
+ ['👁‍🗨','SAURON', 'Deep diagnostics and root cause analysis. Goes beyond symptoms to find true origins of system failures.'],
68
+ ['📖','SCHEHERAZADE', 'Content strategist and narrative architect. Storytelling, content that captivates, persuades, and converts.'],
69
+ ['🖥','SHELL', 'CLI tool architect. Command-line interface design, shell scripting, terminal UX, CLI frameworks.'],
70
+ ['⚔','SHOGUN', 'Kubernetes platform engineer. Container orchestration, pod strategy, resource placement, cluster management.'],
71
+ ['🌪','TEMPEST', 'Meteorological intelligence analyst. Weather forecasting, atmospheric data interpretation, climate analysis.'],
72
+ ['✅','VERITAS', 'Evidence analyst and fact-checker. Determines if claims are supported by evidence, epistemological rigor.'],
73
+ ['🎯','ZERO', 'Automated vulnerability scanner. Combines Snyk/Semgrep/Trivy/Nuclei precision with contextual understanding.'],
74
+ ];
75
+
76
+ const AGENT_NAMES = AGENT_REGISTRY.map(a => a[1]);
77
+ const AGENT_CATALOG = AGENT_REGISTRY.map(([icon, name, desc]) => `- ${icon} **${name}**: ${desc}`).join('\n');
78
+
21
79
  /**
22
80
  * Run a web search and return formatted results string.
23
81
  */
@@ -56,24 +114,22 @@ async function runFetchUrl(url) {
56
114
  */
57
115
  function extractSearchQueries(task, stepPrompt) {
58
116
  const text = (stepPrompt || task).slice(0, 500);
59
- // Primary query: the full task condensed
60
117
  const primary = text.replace(/[^\w\s.,&/-]/g, ' ').replace(/\s+/g, ' ').trim().slice(0, 120);
61
118
  const queries = [primary];
62
119
 
63
- // Finance: add price + news queries
64
120
  if (/gold|silver|oil|stock|bitcoin|crypto|eur|usd|etf|nasdaq|sp500|dow|tesla|apple|nvidia/i.test(text)) {
65
121
  const ticker = text.match(/\b([A-Z]{2,5}|gold|silver|oil|bitcoin|ethereum)\b/i)?.[1] || '';
66
122
  if (ticker) {
67
- queries.push(`${ticker} price today 2025`);
68
- queries.push(`${ticker} analyst forecast outlook 2025`);
123
+ queries.push(`${ticker} price today 2026`);
124
+ queries.push(`${ticker} analyst forecast outlook 2026`);
69
125
  }
70
126
  }
71
127
 
72
- // Dedup and cap at 3
73
128
  return [...new Set(queries)].slice(0, 3);
74
129
  }
75
130
 
76
131
  export function register(router) {
132
+ // ── /api/studio/plan — Intelligent workflow planner ─────────────────
77
133
  router.post('/api/studio/plan', async (req, res) => {
78
134
  const body = await parseBody(req);
79
135
  const task = (body.task || '').trim();
@@ -82,100 +138,89 @@ export function register(router) {
82
138
 
83
139
  const LANG_MAP = {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'};
84
140
  const plannerLang = LANG_MAP[(config?.language||'it').slice(0,2)] || 'Italian';
85
- const it = plannerLang === 'Italian';
86
- const taskLow = task.toLowerCase();
87
-
88
- const hasPdf = !!(body.hasPdf) || /pdf|allegat|catalogo|scheda\s*tecnic/i.test(taskLow);
89
- const hasEmail = /email|mail|inbox|posta/i.test(taskLow);
90
- const hasCalendar = /calendar|agenda|calendari|eventi|schedule/i.test(taskLow);
91
- const hasSearch = /cerca|search|notizie|news|ultime|latest|web|internet|tendenz|trend|acquista|compra|dove\s+trovare|where\s+to\s+buy|similar|simile/i.test(taskLow);
92
- const hasCanvas = /html|dashboard|visua|report|grafico|chart/i.test(taskLow);
93
- const hasGitHub = /github|git\b|issue\b|pull request|\bPR\b/i.test(taskLow);
94
- const hasSlack = /slack/i.test(taskLow);
95
- const hasNotion = /\bnotion\b/i.test(taskLow);
96
- const hasBriefing = /briefing|analisi|analizza|summary|sommario|riassunto|riepiloga|valutazione|valuta/i.test(taskLow);
97
- const hasFinance = /finance|mercato|market|stock|trading|finanz|investiment|cripto|oro|gold|petrolio|oil|commodit|prezzo|correlazion|macro|inflazion|tassi|borse|etf|bitcoin|nasdaq|sp500/i.test(taskLow);
98
- const hasSecurity = /security|sicurezza|vulnerabilit|audit|pentest|rischi|dipendenz/i.test(taskLow);
99
- const hasStrategy = /strateg|competitiv|posizionament|raccomandaz|competitive|positioning/i.test(taskLow);
100
- const hasReputation = /reputazion|reputation|online|brand|review|recension/i.test(taskLow);
101
- const hasCode = /codice|code|refactor|debug|bug|sviluppo|software|npm|package/i.test(taskLow);
102
- const hasWriting = /scrivi|write|articolo|article|blog|testo|text|documento|document/i.test(taskLow);
103
- const hasData = /dati|data|dataset|csv|json|analizza i dati|pattern|statistich|correlazion|regressione|trend|serie\s+storic/i.test(taskLow);
104
- const hasTranslate = /traduci|translate|traduzione|translation/i.test(taskLow);
105
- const hasTravel = /ristorante|restaurant|b&b|hotel|albergo|agriturismo|locanda|osteria|prenotaz|vacanz|romantico|sushi|giapponese|cinese|pizza|cena|dinner|pranzo|lunch|soggiorno|weekend|pernottament|posto\s+dove\s+mangiare|posto\s+dove\s+dormire|dove\s+mangiare|dove\s+dormire|posto\s+romantico|gita|escursione/i.test(taskLow);
106
-
107
- const extractSearchQuery = (t) => {
108
- const m = t.match(/(?:cerca|search|find|ricerca|notizie su|news about|latest on|aggiornamenti su|ultime su|tendenz|trend)\s+(.{5,80}?)(?:\s+(?:e |and |per |for |poi |then )|[,\n]|$)/i);
109
- if (m) return m[1].trim();
110
- const domainMatch = t.match(/(?:https?:\/\/)?(?:www\.)?([a-z0-9-]+\.[a-z]{2,}(?:\.[a-z]{2,})?)/i);
111
- if (domainMatch) return domainMatch[0].replace(/^https?:\/\//,'');
112
- return t.replace(/^[^:]+:\s*/,'').split(/[,\n]/)[0].slice(0,100).trim() || t.slice(0,80).trim();
113
- };
114
- const searchQuery = extractSearchQuery(task);
141
+ const sanitizedTask = task.replace(/<[^>]*>/g, ' ').replace(/[\x00-\x08\x0b\x0c\x0e-\x1f]/g, '').trim();
115
142
 
116
- const buildKeywordPlan = () => {
117
- const steps = [];
118
- if (hasPdf) {
119
- const pdfName = body.pdfName || 'documento allegato';
120
- steps.push({icon:'📄',agent:'DocumentReaderAgent',label:it?'Leggi documento':'Read document',reason:it?'Allegato PDF rilevato — estraggo dati tecnici prima di ogni altra operazione':'PDF attachment detected — extracting technical data first',prompt:`Extract all technical specifications, model numbers, part codes, product names, manufacturer, dimensions, ratings, and any other key data from the attached document "${pdfName}". List every technical detail precisely.`});
121
- }
122
- if (hasTravel) steps.push({icon:'🍽',agent:'TravelAgent', label:it?'Ricerca ristoranti & hotel':'Search restaurants & hotels', reason:it?'Task di viaggio/prenotazione':'Travel/booking task', prompt:task});
123
- if (hasEmail) steps.push({icon:'📧',agent:'EmailAgent', label:it?'Controlla email':'Check emails', reason:it?'Parola chiave email rilevata':'Email keyword detected', prompt:'Read the latest unread emails and identify urgent items, deadlines, and required actions'});
124
- if (hasCalendar) steps.push({icon:'📅',agent:'CalendarAgent',label:it?'Rivedi calendario':'Review calendar',reason:it?'Parola chiave calendario rilevata':'Calendar keyword detected', prompt:"Check today's events and identify any scheduling conflicts or important meetings"});
125
- if (hasGitHub) steps.push({icon:'💻',agent:'GitHubAgent', label:'GitHub', reason:it?'Parola chiave GitHub rilevata':'GitHub keyword detected', prompt:'Read open issues and pull requests, identify what needs attention'});
126
- if (hasSlack) steps.push({icon:'💬',agent:'SlackAgent', label:'Slack', reason:it?'Parola chiave Slack rilevata':'Slack keyword detected', prompt:'Check recent Slack messages and identify important conversations'});
127
- if (hasNotion) steps.push({icon:'📝',agent:'NotionAgent', label:'Notion', reason:it?'Parola chiave Notion rilevata':'Notion keyword detected', prompt:'Search Notion for relevant pages and notes'});
128
- if (!hasTravel && (hasPdf || hasSearch || hasReputation || (!hasEmail && !hasCalendar && !hasGitHub && !hasSlack))) {
129
- const sp = hasPdf ? (it?'Usa le specifiche tecniche estratte dal documento per cercare online dove acquistare il prodotto o articoli equivalenti.':'Use the technical specifications extracted from the document to search online for where to buy this product or equivalent alternatives.') : searchQuery;
130
- steps.push({icon:'🔍',agent:'WebSearchAgent',label:it?'Ricerca web':'Web search',reason:it?'Fonte dati web principale':'Primary web data source',prompt:sp});
131
- }
132
- if (hasSecurity) steps.push({icon:'🛡',agent:'cassandra', label:it?'CASSANDRA — Rischi':'CASSANDRA — Risks', reason:it?'Keyword sicurezza rilevata':'Security keyword detected', prompt:'Analyze the collected data and identify security risks, vulnerabilities and concrete recommendations'});
133
- if (hasFinance) steps.push({icon:'💰',agent:'mercury', label:it?'MERCURY — Mercato':'MERCURY — Market', reason:it?'Keyword finanza rilevata':'Finance keyword detected', prompt:'Analyze the financial data and market trends from the collected information'});
134
- if (hasStrategy) steps.push({icon:'♟',agent:'athena', label:it?'ATHENA — Strategia':'ATHENA — Strategy', reason:it?'Keyword strategia rilevata':'Strategy keyword detected', prompt:'Based on the collected data, produce strategic analysis with competitive positioning and concrete recommendations'});
135
- if (hasReputation) steps.push({icon:'🔭',agent:'oracle', label:it?'ORACLE — Reputazione':'ORACLE — Reputation', reason:it?'Keyword reputazione rilevata':'Reputation keyword detected', prompt:'Analyze the online reputation data, sentiment and brand positioning from the collected information'});
136
- if (hasCode) steps.push({icon:'🔧',agent:'forge', label:it?'FORGE — Codice':'FORGE — Code', reason:it?'Keyword codice rilevata':'Code keyword detected', prompt:'Analyze the code, dependencies and technical issues identified in the data'});
137
- if (hasWriting) steps.push({icon:'🖊',agent:'quill', label:it?'QUILL — Redazione':'QUILL — Writing', reason:it?'Keyword scrittura rilevata':'Writing keyword detected', prompt:'Write a polished, professional document based on all the collected information'});
138
- if (hasData) steps.push({icon:'📊',agent:'DataAnalystAgent',label:it?'Analisi dati':'Data analysis', reason:it?'Keyword dati rilevata':'Data keyword detected', prompt:'Analyze the data and extract key patterns, trends and insights'});
139
- if (hasTranslate) steps.push({icon:'🌐',agent:'polyglot', label:it?'POLYGLOT — Traduzione':'POLYGLOT — Translation',reason:it?'Keyword traduzione rilevata':'Translation keyword detected', prompt:'Translate the content as requested, maintaining meaning and style'});
140
- const hasSpecialist = hasSecurity || hasFinance || hasStrategy || hasReputation || hasCode || hasWriting || hasData || hasTranslate;
141
- if (!hasSpecialist && (hasBriefing || steps.length > 0)) {
142
- steps.push({icon:'📰',agent:'HERALD',label:it?'HERALD — Briefing':'HERALD — Briefing',reason:it?'HERALD sintetizza tutti i dati':'HERALD synthesizes all data',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.'});
143
- }
144
- const specialistCount = [hasSecurity,hasFinance,hasStrategy,hasReputation,hasCode,hasWriting,hasData].filter(Boolean).length;
145
- if (hasCanvas || specialistCount >= 2 || (hasSpecialist && hasBriefing)) {
146
- steps.push({icon:'📊',agent:'CanvasAgent',label:it?'Dashboard HTML':'HTML Dashboard',reason:it?'Analisi complessa — dashboard visuale':'Complex analysis — visual dashboard',prompt:'Create a professional HTML dashboard report summarizing all findings from the previous agents'});
147
- }
148
- return steps;
149
- };
143
+ const hasPdf = !!(body.hasPdf) || /pdf|allegat|catalogo|scheda\s*tecnic/i.test(task);
144
+ const pdfName = body.pdfName || 'documento allegato';
150
145
 
151
- const keywordSteps = buildKeywordPlan();
152
- const hasKeywordPlan = keywordSteps.length > 0;
153
- const sanitizedTask = task.replace(/<[^>]*>/g, ' ').replace(/[\x00-\x08\x0b\x0c\x0e-\x1f]/g, '').trim();
154
- const keywordPlanJson = hasKeywordPlan ? JSON.stringify(keywordSteps.map(s => ({ agent: s.agent, label: s.label, reason: s.reason || '' }))) : '[]';
155
146
  const planConfig = Object.assign({}, config, { thinking: 'off' });
156
147
 
157
148
  try {
158
- let steps = keywordSteps;
149
+ let steps = [];
150
+
151
+ const plannerSystem = `You are CONDUCTOR, the master workflow orchestrator for NHA Studio.
152
+ Your job: analyze the user's task and select the OPTIMAL sequence of specialist agents to accomplish it.
153
+
154
+ ## COMPLETE AGENT CATALOG (${AGENT_REGISTRY.length} agents available):
155
+
156
+ ${AGENT_CATALOG}
157
+
158
+ ## ORCHESTRATION RULES:
159
+
160
+ 1. **Select 2-7 agents** depending on task complexity. Simple tasks = 2-3 agents. Complex analysis = 4-7.
161
+ 2. **Order matters**: agents execute sequentially. Each agent receives the output of ALL previous agents as context.
162
+ 3. **WebSearchAgent or TravelAgent FIRST** when real-time data is needed (prices, news, reviews, weather, venues).
163
+ 4. **DocumentReaderAgent FIRST** when a PDF is attached — extract data before anything else.
164
+ 5. **HERALD as synthesizer** — place near the end to produce an executive briefing from all collected data.
165
+ 6. **CanvasAgent ALWAYS LAST** (and only if visual output is requested or >=3 specialists are used).
166
+ 7. **Match agent expertise to task domain**:
167
+ - Finance/markets/crypto → MERCURY (+ WebSearchAgent for live data)
168
+ - Security/audit/vulns → SABER, ADE, ZERO, CASSANDRA (pick the right ones)
169
+ - Code/architecture → FORGE, PROMETHEUS, SAURON
170
+ - Data/statistics → ORACLE, EDI, NAVI (NAVI for quality check first), DataAnalystAgent
171
+ - Writing/content → QUILL (short copy), MURASAKI (long-form), SCHEHERAZADE (narrative), ECHO (multi-platform)
172
+ - Translation → polyglot
173
+ - Infrastructure/cloud → ATLAS, SHOGUN, HEIMDALL, CRON
174
+ - API/integrations → BABEL, HERMES, PIPE
175
+ - Visual/design → MUSE, JARVIS
176
+ - Logic/verification → LOGOS, VERITAS
177
+ - Food/restaurants → EPICURE (+ TravelAgent for search)
178
+ - Weather → TEMPEST
179
+ - Geospatial → CARTOGRAPHER
180
+ - Social/community → LINK
181
+ - Automation → MACRO, CRON
182
+ - Strategy/competitive → ATHENA
183
+ - Risk analysis → CASSANDRA
184
+ 8. **Write detailed prompts**: each agent's \`prompt\` field should be a specific instruction telling the agent EXACTLY what to analyze, produce, or investigate. Not just the task repeated — a targeted instruction leveraging that agent's specialty.
185
+ 9. **Labels in ${plannerLang}**.
186
+ 10. **Never use agents that don't match the task**. A cooking task doesn't need SABER. A security audit doesn't need EPICURE.
187
+ ${hasPdf ? `\n11. **PDF ATTACHED**: "${pdfName}" — DocumentReaderAgent MUST be step 1.` : ''}
188
+
189
+ ## OUTPUT FORMAT (STRICT — output ONLY this JSON, nothing else):
190
+
191
+ {"steps":[{"icon":"EMOJI","agent":"AGENT_NAME","label":"LABEL_IN_${plannerLang.toUpperCase()}","reason":"WHY_THIS_AGENT","prompt":"DETAILED_INSTRUCTION_FOR_THE_AGENT"}]}`;
192
+
193
+ const plannerUser = `TASK: ${sanitizedTask}
194
+
195
+ Select the optimal agent pipeline. Output ONLY the JSON.`;
196
+
159
197
  if (config && (config.llm?.provider || config.llm?.apiKey)) {
160
198
  try {
161
- const planSys = `You are a workflow planner for NHA Studio. Output ONLY valid JSON — no explanation, no markdown.`;
162
- const planPrompt = hasKeywordPlan
163
- ? `Task: ${sanitizedTask}\n\nKeyword-detected plan (JSON):\n${keywordPlanJson}\n\nLanguage for labels: ${plannerLang}.\n\nReview the plan. You may ADD missing steps, REMOVE wrong ones, REORDER, ADJUST prompts. Keep existing reasons where step is unchanged.\n\nAvailable agents: TravelAgent, WebSearchAgent, DocumentReaderAgent, EmailAgent, CalendarAgent, GitHubAgent, SlackAgent, NotionAgent, HERALD, ORACLE, ATHENA, CASSANDRA, MERCURY, QUILL, DataAnalystAgent, polyglot, CanvasAgent (last only).\n\nOutput ONLY:\n{"steps":[{"icon":"EMOJI","agent":"AGENT_NAME","label":"LABEL","reason":"WHY","prompt":"INSTRUCTION"}]}\n\nMax 6 steps.`
164
- : `Task: ${sanitizedTask}\n\nLanguage for labels: ${plannerLang}.\n\nBuild a workflow plan. Available agents: TravelAgent, WebSearchAgent, DocumentReaderAgent, EmailAgent, CalendarAgent, GitHubAgent, SlackAgent, NotionAgent, HERALD, ORACLE, ATHENA, CASSANDRA, MERCURY, QUILL, DataAnalystAgent, polyglot, CanvasAgent.\n\nOutput ONLY:\n{"steps":[{"icon":"EMOJI","agent":"AGENT_NAME","label":"LABEL","reason":"WHY","prompt":"INSTRUCTION"}]}\n\n2-5 steps.`;
165
- const planRaw = await callLLM(planConfig, planSys, planPrompt, { max_tokens: 900 });
199
+ const planRaw = await callLLM(planConfig, plannerSystem, plannerUser, { max_tokens: 2000 });
166
200
  let clean = planRaw.replace(/<think>[\s\S]*?<\/think>/g, '').trim().replace(/^```[\w]*\r?\n?/, '').replace(/\r?\n?```$/, '').trim();
167
201
  const jm = clean.match(/\{[\s\S]*\}/);
168
202
  const parsed = JSON.parse(jm ? jm[0] : clean);
169
203
  if (Array.isArray(parsed.steps) && parsed.steps.length > 0) {
170
- const krMap = {};
171
- keywordSteps.forEach(s => { krMap[s.agent] = s.reason || ''; });
172
- steps = parsed.steps.map(s => ({ icon: s.icon || '🤖', agent: s.agent, label: s.label, reason: s.reason || krMap[s.agent] || '', prompt: s.prompt }));
204
+ steps = parsed.steps
205
+ .filter(s => AGENT_NAMES.includes(s.agent))
206
+ .map(s => {
207
+ const reg = AGENT_REGISTRY.find(r => r[1] === s.agent);
208
+ return { icon: s.icon || (reg ? reg[0] : '🤖'), agent: s.agent, label: s.label, reason: s.reason || '', prompt: s.prompt };
209
+ });
173
210
  }
174
- } catch {}
211
+ } catch { /* LLM failed, fall through to keyword fallback */ }
212
+ }
213
+
214
+ // Keyword fallback if LLM planner failed or no LLM configured
215
+ if (!steps.length) {
216
+ steps = buildKeywordFallback(task, sanitizedTask, hasPdf, pdfName, plannerLang === 'Italian');
175
217
  }
176
- if (!Array.isArray(steps) || !steps.length) {
218
+
219
+ if (!steps.length) {
220
+ const it = plannerLang === 'Italian';
177
221
  steps = [{ icon: '🔍', agent: 'WebSearchAgent', label: it ? 'Ricerca web' : 'Web search', reason: 'Fallback', prompt: sanitizedTask }];
178
222
  }
223
+
179
224
  sendJSON(res, 200, { steps });
180
225
  } catch (e) { sendError(res, 500, e.message); }
181
226
  });
@@ -212,19 +257,20 @@ export function register(router) {
212
257
  } catch {}
213
258
  }
214
259
 
215
- const contextBlock = context ? `\n\n## CONTEXT FROM PREVIOUS STEPS:\n${context.slice(0, 8000)}` : '';
216
- const proposalContextBlock = body.proposalContext ? `\n\n## OTHER AGENTS' PROPOSALS (CROSS-READING):\n${body.proposalContext.slice(0, 6000)}` : '';
260
+ const contextBlock = context ? `\n\n## CONTEXT FROM PREVIOUS STEPS:\n${context.slice(0, 12000)}` : '';
261
+ const proposalContextBlock = body.proposalContext ? `\n\n## OTHER AGENTS' PROPOSALS (CROSS-READING):\n${body.proposalContext.slice(0, 8000)}` : '';
217
262
 
218
263
  const formatInstructions = `\n\nFORMATTING RULES (CRITICAL — your output will be rendered as HTML):
219
264
  - Use MARKDOWN TABLES with | pipes | for ALL tabular data. Example:
220
265
  | Header 1 | Header 2 | Header 3 |
221
266
  |----------|----------|----------|
222
267
  | data | data | data |
223
- - NEVER use ASCII art boxes (┌─┐│└─┘╔═╗║╚═╝). They render as ugly <pre> blocks.
224
- - NEVER use ASCII art charts/graphs (├─●──┤). Describe data in tables instead.
268
+ - NEVER use ASCII art boxes or box-drawing characters. They render as ugly monospace blocks.
225
269
  - Use **bold**, *italic*, headers (## ##), bullet points, numbered lists, blockquotes (>).
226
270
  - For emphasis boxes, use blockquotes: > **Key insight:** text here
227
- - Write COMPLETE content under every heading — never leave a section empty.`;
271
+ - Write COMPLETE, EXHAUSTIVE content under every heading — never leave a section empty or superficial.
272
+ - Provide DEEP ANALYSIS with specific data points, numbers, percentages, and concrete examples.
273
+ - Minimum 800 words of substantive content. Be thorough, not brief.`;
228
274
 
229
275
  const sysParts = [
230
276
  agentSysDef || `You are ${agent}, a specialist AI agent in NHA Studio. Respond entirely in ${language}. Today is ${today}.`,
@@ -289,7 +335,7 @@ export function register(router) {
289
335
  output += tok;
290
336
  tokensOut += Math.ceil(tok.length / 4);
291
337
  sse({ token: tok });
292
- }, { max_tokens: 8192 });
338
+ }, { max_tokens: 16384 });
293
339
 
294
340
  clearInterval(keepalive);
295
341
  sse({ done: true, output, tokensOut });
@@ -351,18 +397,18 @@ export function register(router) {
351
397
 
352
398
  const crossCtx = (excludeAgent) => eligible
353
399
  .filter(p => p.agent !== excludeAgent)
354
- .map(p => `## ${p.label || p.agent} (Round 1):\n${p.output.slice(0,4000)}`)
400
+ .map(p => `## ${p.label || p.agent} (Round 1):\n${p.output.slice(0,6000)}`)
355
401
  .join('\n\n---\n\n');
356
402
 
357
403
  tok('[Parlamento — Round 2: Cross-Reading & Refinamento] ');
358
404
  const r2Results = [];
359
405
  for (const proposal of eligible) {
360
406
  tok(`[Round 2: ${proposal.label || proposal.agent}] `);
361
- const r2Sys = `You are ${proposal.agent}, a specialist AI agent in NHA Studio Parliament. Today is ${today}. Respond entirely in ${language}.\n\n## WORKFLOW GOAL: ${task}\n\n## YOUR ROUND 1 RESPONSE:\n${proposal.output.slice(0,1500)}\n\n## OTHER AGENTS' ROUND 1 PROPOSALS:\n${crossCtx(proposal.agent)}\n\nDELIBERATION ROUND 2 — REFINEMENT:\n1. Review the other agents' proposals\n2. Incorporate valid points where you AGREE — mark with [ASSIST]\n3. Flag genuine disagreements with [CONTRADICTION] and explain your reasoning\n4. Produce your COMPLETE REFINED response\n5. Keep analysis focused on: ${task}`;
407
+ const r2Sys = `You are ${proposal.agent}, a specialist AI agent in NHA Studio Parliament. Today is ${today}. Respond entirely in ${language}.\n\n## WORKFLOW GOAL: ${task}\n\n## YOUR ROUND 1 RESPONSE:\n${proposal.output.slice(0,3000)}\n\n## OTHER AGENTS' ROUND 1 PROPOSALS:\n${crossCtx(proposal.agent)}\n\nDELIBERATION ROUND 2 — REFINEMENT:\n1. Review the other agents' proposals carefully\n2. Incorporate valid points where you AGREE — mark with [AGREE]\n3. Flag genuine disagreements with [CONTRADICTION] and explain your reasoning with evidence\n4. Produce your COMPLETE REFINED response — thorough and exhaustive\n5. Keep analysis focused on: ${task}\n\nBe THOROUGH. Minimum 600 words of substantive refined analysis.`;
362
408
  let r2Out = '';
363
409
  try {
364
410
  await callLLMStream(config, r2Sys, 'Produce your refined Round 2 response. Write complete content under every heading — never leave a section title without body text.',
365
- (t) => { r2Out += t; }, { max_tokens: 8192 });
411
+ (t) => { r2Out += t; }, { max_tokens: 16384 });
366
412
  } catch { r2Out = proposal.output; }
367
413
  r2Results.push({ agent: proposal.agent, label: proposal.label, icon: proposal.icon, output: r2Out });
368
414
  sse({ deliberation_r2: { agent: proposal.agent, label: proposal.label, icon: proposal.icon, output: r2Out } });
@@ -372,7 +418,7 @@ export function register(router) {
372
418
  tok(`[Parlamento — Round 2 convergenza: ${(r2Conv*100).toFixed(0)}%] `);
373
419
  const converged = r2Conv >= 0.30;
374
420
 
375
- const allR2Ctx = r2Results.map(r => `## ${r.label || r.agent}:\n${r.output.slice(0,2000)}`).join('\n\n---\n\n');
421
+ const allR2Ctx = r2Results.map(r => `## ${r.label || r.agent}:\n${r.output.slice(0,4000)}`).join('\n\n---\n\n');
376
422
  const contradictions = [];
377
423
  for (const r of r2Results) {
378
424
  const matches = r.output.match(/\[CONTRADICTION\][^\n]*/g) || [];
@@ -383,14 +429,14 @@ export function register(router) {
383
429
  tok(converged ? '[Parlamento — Round 3: Sintesi finale HERALD...] ' : '[Parlamento — Round 3: Mediazione HERALD...] ');
384
430
 
385
431
  const medTask = converged
386
- ? `SYNTHESIS TASK (convergenza ${(r2Conv*100).toFixed(0)}%):\n1. Presenta il CONSENSO raggiunto\n2. Segnala ogni sfumatura o punto di divergenza residua\n3. Produci un executive summary unificato con azioni concrete per: ${task}\n4. Sezione "Voci dissonanti" se esistono posizioni che meritano attenzione`
387
- : `MEDIATION TASK (convergenza ${(r2Conv*100).toFixed(0)}% — divergenza significativa):\n1. Identifica i punti di ACCORDO tra tutti gli agenti\n2. Per ogni disaccordo: valuta quale posizione ha evidenze più solide, NOMINA l'agente e spiega perché è stata accolta o scartata\n3. Produci una sintesi UNIFICATA\n4. Fai scelte editoriali nette\n5. Executive summary con azioni concrete per: ${task}`;
432
+ ? `SYNTHESIS TASK (convergenza ${(r2Conv*100).toFixed(0)}%):\n1. Presenta il CONSENSO raggiunto tra tutti gli agenti\n2. Segnala ogni sfumatura o punto di divergenza residua\n3. Produci un executive summary unificato con azioni concrete per: ${task}\n4. Sezione "Voci dissonanti" se esistono posizioni che meritano attenzione\n5. SENZA ALCUN LIMITE DI LUNGHEZZA — sii esaustivo e completo`
433
+ : `MEDIATION TASK (convergenza ${(r2Conv*100).toFixed(0)}% — divergenza significativa):\n1. Identifica i punti di ACCORDO tra tutti gli agenti\n2. Per ogni disaccordo: valuta quale posizione ha evidenze piu solide, NOMINA l'agente e spiega perche e stata accolta o scartata\n3. Produci una sintesi UNIFICATA esaustiva\n4. Fai scelte editoriali nette con motivazioni\n5. Executive summary con azioni concrete per: ${task}\n6. SENZA ALCUN LIMITE DI LUNGHEZZA — sii esaustivo e completo`;
388
434
 
389
- const medSys = `You are HERALD, the Parliament Mediator in NHA Studio. Today is ${today}. Respond entirely in ${language}.\n\n## WORKFLOW GOAL: ${task}\n\n## ALL AGENTS' REFINED POSITIONS (Round 2):\n${allR2Ctx}${contBlock}\n\n${medTask}\n\nCRITICAL: NEVER write a heading without immediately writing full content below it. Every section MUST have at least 3-5 concrete bullet points or sentences.`;
435
+ const medSys = `You are HERALD, the Parliament Mediator in NHA Studio. Today is ${today}. Respond entirely in ${language}.\n\n## WORKFLOW GOAL: ${task}\n\n## ALL AGENTS' REFINED POSITIONS (Round 2):\n${allR2Ctx}${contBlock}\n\n${medTask}\n\nCRITICAL: NEVER write a heading without immediately writing full content below it. Every section MUST have at least 5-8 concrete bullet points or detailed paragraphs. Be EXHAUSTIVE.`;
390
436
 
391
437
  let mediationOutput = '';
392
438
  try {
393
- await callLLMStream(config, medSys, 'Produce the Parliament final synthesis.', (t) => { mediationOutput += t; }, { max_tokens: 8192 });
439
+ await callLLMStream(config, medSys, 'Produce the Parliament final synthesis. Be thorough and complete.', (t) => { mediationOutput += t; }, { max_tokens: 16384 });
394
440
  } catch {}
395
441
  sse({ deliberation_r3: { output: mediationOutput, converged } });
396
442
 
@@ -406,3 +452,69 @@ export function register(router) {
406
452
  }
407
453
  });
408
454
  }
455
+
456
+ // ── Keyword fallback planner (used when LLM is unavailable) ────────────
457
+
458
+ function buildKeywordFallback(task, sanitizedTask, hasPdf, pdfName, it) {
459
+ const taskLow = task.toLowerCase();
460
+ const steps = [];
461
+
462
+ const hasEmail = /email|mail|inbox|posta/i.test(taskLow);
463
+ const hasCalendar = /calendar|agenda|calendari|eventi|schedule/i.test(taskLow);
464
+ const hasSearch = /cerca|search|notizie|news|ultime|latest|web|internet|tendenz|trend|acquista|compra|dove\s+trovare|where\s+to\s+buy|similar|simile/i.test(taskLow);
465
+ const hasCanvas = /html|dashboard|visua|report|grafico|chart/i.test(taskLow);
466
+ const hasGitHub = /github|git\b|issue\b|pull request|\bPR\b/i.test(taskLow);
467
+ const hasSlack = /slack/i.test(taskLow);
468
+ const hasNotion = /\bnotion\b/i.test(taskLow);
469
+ const hasBriefing = /briefing|analisi|analizza|summary|sommario|riassunto|riepiloga|valutazione|valuta/i.test(taskLow);
470
+ const hasFinance = /finance|mercato|market|stock|trading|finanz|investiment|cripto|oro|gold|petrolio|oil|commodit|prezzo|correlazion|macro|inflazion|tassi|borse|etf|bitcoin|nasdaq|sp500/i.test(taskLow);
471
+ const hasSecurity = /security|sicurezza|vulnerabilit|audit|pentest|rischi|dipendenz/i.test(taskLow);
472
+ const hasStrategy = /strateg|competitiv|posizionament|raccomandaz|competitive|positioning/i.test(taskLow);
473
+ const hasReputation = /reputazion|reputation|online|brand|review|recension/i.test(taskLow);
474
+ const hasCode = /codice|code|refactor|debug|bug|sviluppo|software|npm|package|architettur/i.test(taskLow);
475
+ const hasWriting = /scrivi|write|articolo|article|blog|testo|text|documento|document|whitepaper|saggio|essay/i.test(taskLow);
476
+ const hasData = /dati|data|dataset|csv|json|analizza i dati|pattern|statistich|correlazion|regressione|trend|serie\s+storic/i.test(taskLow);
477
+ const hasTranslate = /traduci|translate|traduzione|translation/i.test(taskLow);
478
+ const hasTravel = /ristorante|restaurant|b&b|hotel|albergo|agriturismo|locanda|osteria|prenotaz|vacanz|romantico|sushi|giapponese|cinese|pizza|cena|dinner|pranzo|lunch|soggiorno|weekend|pernottament|posto\s+dove\s+mangiare|posto\s+dove\s+dormire|dove\s+mangiare|dove\s+dormire|posto\s+romantico|gita|escursione/i.test(taskLow);
479
+ const hasWeather = /meteo|weather|previsioni|temperatura|clima|pioggia|neve|vento/i.test(taskLow);
480
+ const hasFood = /ricetta|recipe|cucinare|cook|ingredienti|piatto|dish|gastronomia|cucina/i.test(taskLow);
481
+ const hasInfra = /kubernetes|k8s|docker|terraform|cloud|aws|azure|gcp|infrastruttura|infrastructure|deploy|pipeline|ci[\/-]cd/i.test(taskLow);
482
+ const hasApi = /\bapi\b|endpoint|webhook|integrazione|integration|rest|graphql|grpc/i.test(taskLow);
483
+ const hasLogic = /logica|logic|argoment|fallacia|fallacy|ragionamento|reasoning|valid|premessa|premise/i.test(taskLow);
484
+ const hasGeo = /mappa|map|geolocalizzazione|coordinates|latitudine|longitudine|geospatial|gis/i.test(taskLow);
485
+
486
+ if (hasPdf) steps.push({icon:'📄',agent:'DocumentReaderAgent',label:it?'Leggi documento':'Read document',reason:it?'PDF allegato':'PDF attached',prompt:`Extract all technical specifications, data, tables, and key information from the attached document "${pdfName}".`});
487
+ if (hasTravel) steps.push({icon:'🍽',agent:'TravelAgent',label:it?'Ricerca luoghi':'Search venues',reason:it?'Task di viaggio/ristorazione':'Travel/dining task',prompt:sanitizedTask});
488
+ if (hasEmail) steps.push({icon:'📧',agent:'EmailAgent',label:it?'Controlla email':'Check emails',reason:it?'Email rilevata':'Email detected',prompt:'Read the latest unread emails and identify urgent items, deadlines, and required actions'});
489
+ if (hasCalendar) steps.push({icon:'📅',agent:'CalendarAgent',label:it?'Rivedi calendario':'Review calendar',reason:it?'Calendario rilevato':'Calendar detected',prompt:"Check today's events and identify any scheduling conflicts or important meetings"});
490
+ if (hasGitHub) steps.push({icon:'💻',agent:'GitHubAgent',label:'GitHub',reason:it?'GitHub rilevato':'GitHub detected',prompt:'Read open issues and pull requests, identify what needs attention'});
491
+ if (hasSlack) steps.push({icon:'💬',agent:'SlackAgent',label:'Slack',reason:it?'Slack rilevato':'Slack detected',prompt:'Check recent Slack messages and identify important conversations'});
492
+ if (hasNotion) steps.push({icon:'📝',agent:'NotionAgent',label:'Notion',reason:it?'Notion rilevato':'Notion detected',prompt:'Search Notion for relevant pages and notes'});
493
+ if (hasWeather) steps.push({icon:'🌪',agent:'TEMPEST',label:it?'Analisi meteo':'Weather analysis',reason:it?'Meteo rilevato':'Weather detected',prompt:sanitizedTask});
494
+ if (hasFood && !hasTravel) steps.push({icon:'🍳',agent:'EPICURE',label:it?'Analisi culinaria':'Culinary analysis',reason:it?'Cucina rilevata':'Cooking detected',prompt:sanitizedTask});
495
+ if (hasGeo) steps.push({icon:'🗺',agent:'CARTOGRAPHER',label:it?'Analisi geospaziale':'Geospatial analysis',reason:it?'Geolocalizzazione rilevata':'Geo detected',prompt:sanitizedTask});
496
+ if (!hasTravel && (hasPdf || hasSearch || hasReputation || (!hasEmail && !hasCalendar && !hasGitHub && !hasSlack && !hasWeather && !hasFood))) {
497
+ steps.push({icon:'🔍',agent:'WebSearchAgent',label:it?'Ricerca web':'Web search',reason:it?'Fonte dati web':'Web data source',prompt:sanitizedTask});
498
+ }
499
+ if (hasSecurity) steps.push({icon:'🛡',agent:'SABER',label:it?'SABER — Audit sicurezza':'SABER — Security audit',reason:it?'Sicurezza rilevata':'Security detected',prompt:sanitizedTask});
500
+ if (hasFinance) steps.push({icon:'💰',agent:'MERCURY',label:it?'MERCURY — Mercato':'MERCURY — Market',reason:it?'Finanza rilevata':'Finance detected',prompt:sanitizedTask});
501
+ if (hasStrategy) steps.push({icon:'♟',agent:'ATHENA',label:it?'ATHENA — Strategia':'ATHENA — Strategy',reason:it?'Strategia rilevata':'Strategy detected',prompt:sanitizedTask});
502
+ if (hasReputation)steps.push({icon:'🔭',agent:'ORACLE',label:it?'ORACLE — Reputazione':'ORACLE — Reputation',reason:it?'Reputazione rilevata':'Reputation detected',prompt:sanitizedTask});
503
+ if (hasCode) steps.push({icon:'🔧',agent:'FORGE',label:it?'FORGE — Architettura':'FORGE — Architecture',reason:it?'Codice rilevato':'Code detected',prompt:sanitizedTask});
504
+ if (hasWriting) steps.push({icon:'📜',agent:'MURASAKI',label:it?'MURASAKI — Redazione':'MURASAKI — Writing',reason:it?'Scrittura rilevata':'Writing detected',prompt:sanitizedTask});
505
+ if (hasData) steps.push({icon:'📊',agent:'DataAnalystAgent',label:it?'Analisi dati':'Data analysis',reason:it?'Dati rilevati':'Data detected',prompt:sanitizedTask});
506
+ if (hasTranslate) steps.push({icon:'🌐',agent:'polyglot',label:it?'POLYGLOT — Traduzione':'POLYGLOT — Translation',reason:it?'Traduzione rilevata':'Translation detected',prompt:sanitizedTask});
507
+ if (hasInfra) steps.push({icon:'🏗',agent:'ATLAS',label:it?'ATLAS — Infrastruttura':'ATLAS — Infrastructure',reason:it?'Infrastruttura rilevata':'Infrastructure detected',prompt:sanitizedTask});
508
+ if (hasApi) steps.push({icon:'🔗',agent:'BABEL',label:it?'BABEL — Integrazione API':'BABEL — API Integration',reason:it?'API rilevata':'API detected',prompt:sanitizedTask});
509
+ if (hasLogic) steps.push({icon:'🧠',agent:'LOGOS',label:it?'LOGOS — Analisi logica':'LOGOS — Logic analysis',reason:it?'Logica rilevata':'Logic detected',prompt:sanitizedTask});
510
+
511
+ const hasSpecialist = hasSecurity || hasFinance || hasStrategy || hasReputation || hasCode || hasWriting || hasData || hasTranslate || hasInfra || hasApi || hasLogic;
512
+ if (!hasSpecialist && (hasBriefing || steps.length > 0)) {
513
+ steps.push({icon:'📰',agent:'HERALD',label:it?'HERALD — Briefing':'HERALD — Briefing',reason:it?'Sintesi finale':'Final synthesis',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.'});
514
+ }
515
+ const specialistCount = [hasSecurity,hasFinance,hasStrategy,hasReputation,hasCode,hasWriting,hasData,hasInfra,hasApi].filter(Boolean).length;
516
+ if (hasCanvas || specialistCount >= 2 || (hasSpecialist && hasBriefing)) {
517
+ steps.push({icon:'📊',agent:'CanvasAgent',label:it?'Dashboard HTML':'HTML Dashboard',reason:it?'Report visuale':'Visual report',prompt:'Create a professional HTML dashboard report summarizing all findings from the previous agents'});
518
+ }
519
+ return steps;
520
+ }