nothumanallowed 13.2.44 → 13.2.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nothumanallowed",
3
- "version": "13.2.44",
3
+ "version": "13.2.46",
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": {
@@ -2911,21 +2911,29 @@ ${context ? `## OUTPUT FROM PREVIOUS AGENTS:\n${context.slice(0, 6000)}\n` : ''}
2911
2911
  const bodyStart = bodyHtml.search(/<body[^>]*>/i);
2912
2912
  if (bodyStart >= 0) bodyHtml = bodyHtml.slice(bodyStart).replace(/<body[^>]*>/i, '').replace(/<\/body>[\s\S]*/i, '').trim();
2913
2913
  }
2914
+ // Derive a short report title from the task (skip stop words, take first 5-6 meaningful words)
2915
+ const stopWords = new Set(['di','la','il','lo','le','gli','un','una','dei','del','della','per','che','con','su','in','e','a','da','è','come','analizza','analisi','ricerca','crea','genera','fai','fammi','dammi','the','of','for','and','a','an','in','with','on','about','analyze','analysis','research','create','generate','make','find','search']);
2916
+ const titleWords = task.replace(/[.,;:!?]/g,'').split(/\s+/).filter(w => w.length > 2 && !stopWords.has(w.toLowerCase())).slice(0, 6);
2917
+ const reportTitle = titleWords.length > 0 ? titleWords.map(w => w.charAt(0).toUpperCase()+w.slice(1)).join(' ') : 'Studio Report';
2914
2918
  // Fallback: if LLM output is empty or has no HTML tags, build body from context using markdown→HTML conversion
2915
2919
  if (!bodyHtml || !bodyHtml.includes('<')) {
2916
- const reportTitle = task.slice(0, 80).replace(/</g,'&lt;');
2917
2920
  const sections = context.split(/\n#{1,3} |(?=\n\n)/).filter(s => s.trim()).slice(0, 12);
2918
- bodyHtml = `<div class="header"><h1>${reportTitle}</h1><p>NHA Studio Report \u00b7 ${today}</p><div class="meta"><span>${today}</span></div></div>` +
2921
+ bodyHtml = `<div class="header"><h1>${reportTitle.replace(/</g,'&lt;')}</h1><p>NHA Studio Report \u00b7 ${today}</p><div class="meta"><span>${today}</span></div></div>` +
2919
2922
  sections.map(s => {
2920
2923
  const lines = s.replace(/\*\*/g,'').replace(/\*/g,'').trim().split('\n').filter(Boolean);
2921
- const title = lines[0] || '';
2924
+ const stitle = lines[0] || '';
2922
2925
  const body = lines.slice(1).map(l => `<p>${l.replace(/</g,'&lt;')}</p>`).join('');
2923
- return `<div class="section"><div class="section-title">${title.replace(/</g,'&lt;')}</div>${body}</div>`;
2926
+ return `<div class="section"><div class="section-title">${stitle.replace(/</g,'&lt;')}</div>${body}</div>`;
2924
2927
  }).join('') +
2925
2928
  `<div class="footer">NHA Studio \u00b7 ${today}</div>`;
2929
+ } else {
2930
+ // Replace the h1 inside existing header div if the model included the full prompt as title
2931
+ bodyHtml = bodyHtml.replace(/(<div[^>]*class="header"[^>]*>[\s\S]*?<h1[^>]*>)([^<]{60,})(<\/h1>)/, (m, open, title, close) => {
2932
+ return open + reportTitle.replace(/</g,'&lt;') + close;
2933
+ });
2926
2934
  }
2927
2935
  // Always wrap in the guaranteed NHA dark CSS template
2928
- const finalHtml = wrapInNHATemplate(bodyHtml, task.slice(0, 60));
2936
+ const finalHtml = wrapInNHATemplate(bodyHtml, reportTitle);
2929
2937
  sendToken('\n\n[Report generato]');
2930
2938
  sendEvent({ canvas: finalHtml });
2931
2939
  }
@@ -2947,6 +2955,164 @@ ${context ? `## OUTPUT FROM PREVIOUS AGENTS:\n${context.slice(0, 6000)}\n` : ''}
2947
2955
  return;
2948
2956
  }
2949
2957
 
2958
+ // ── Studio: Parliament deliberation (SSE streaming) ──────────────────
2959
+ // Implements the Legion DeliberationEngine protocol adapted for Studio:
2960
+ // Round 1 outputs already exist (from normal workflow steps).
2961
+ // Round 2: each agent cross-reads all others' Round 1 outputs and refines.
2962
+ // Convergence: Jaccard similarity on key terms between R1 and R2 outputs.
2963
+ // Round 3 (optional): if divergence > threshold, HERALD mediates.
2964
+ if (pathname === '/api/studio/deliberate' && method === 'POST') {
2965
+ const body = await parseBody(req);
2966
+ const { task, proposals, language: bodyLang } = body;
2967
+ if (!task || !Array.isArray(proposals) || proposals.length < 2) {
2968
+ sendJSON(res, 400, { error: 'task and at least 2 proposals required' });
2969
+ logRequest(method, pathname, 400, Date.now() - start);
2970
+ return;
2971
+ }
2972
+
2973
+ res.writeHead(200, {
2974
+ 'Content-Type': 'text/event-stream',
2975
+ 'Cache-Control': 'no-cache',
2976
+ 'Connection': 'keep-alive',
2977
+ 'Access-Control-Allow-Origin': '*',
2978
+ });
2979
+
2980
+ const sendEv2 = (data) => { try { res.write(`data: ${JSON.stringify(data)}\n\n`); } catch {} };
2981
+ const sendTok2 = (t) => sendEv2({ token: t });
2982
+ const keepaliveD = setInterval(() => { try { res.write(': keepalive\n\n'); } catch {} }, 5000);
2983
+ const language = bodyLang || 'Italian';
2984
+ const today = new Date().toISOString().slice(0, 10);
2985
+
2986
+ // Jaccard similarity between two texts (key terms 4+ chars)
2987
+ const jaccard = (a, b) => {
2988
+ const terms = (s) => new Set(s.toLowerCase().match(/\b\w{4,}\b/g) || []);
2989
+ const sa = terms(a), sb = terms(b);
2990
+ let inter = 0;
2991
+ for (const w of sa) { if (sb.has(w)) inter++; }
2992
+ const union = sa.size + sb.size - inter;
2993
+ return union > 0 ? inter / union : 1;
2994
+ };
2995
+
2996
+ const measureConvergence = (outputs) => {
2997
+ if (outputs.length < 2) return 1.0;
2998
+ let total = 0, pairs = 0;
2999
+ for (let i = 0; i < outputs.length; i++) {
3000
+ for (let j = i + 1; j < outputs.length; j++) {
3001
+ total += jaccard(outputs[i], outputs[j]);
3002
+ pairs++;
3003
+ }
3004
+ }
3005
+ return pairs > 0 ? total / pairs : 1.0;
3006
+ };
3007
+
3008
+ try {
3009
+ const eligibleProposals = proposals.filter(p => p.agent !== 'CanvasAgent' && p.agent !== 'GitHubAgent' && p.agent !== 'EmailAgent' && p.agent !== 'CalendarAgent');
3010
+ if (eligibleProposals.length < 2) {
3011
+ sendEv2({ deliberation_done: true, skipped: true, reason: 'not enough specialist agents' });
3012
+ sendEv2({ done: true });
3013
+ res.write('data: [DONE]\n\n');
3014
+ res.end();
3015
+ clearInterval(keepaliveD);
3016
+ logRequest(method, pathname, 200, Date.now() - start);
3017
+ return;
3018
+ }
3019
+
3020
+ // Round 1 convergence
3021
+ const r1Convergence = measureConvergence(eligibleProposals.map(p => p.output));
3022
+ sendTok2(`[Parlamento — Round 1 convergenza: ${(r1Convergence * 100).toFixed(0)}%] `);
3023
+
3024
+ const buildCrossReadCtx = (excludeAgent) =>
3025
+ eligibleProposals
3026
+ .filter(p => p.agent !== excludeAgent)
3027
+ .map(p => `## ${p.label || p.agent} (Round 1):\n${p.output.slice(0, 2000)}`)
3028
+ .join('\n\n---\n\n');
3029
+
3030
+ // Round 2: cross-reading + refinement (sequential to save tokens)
3031
+ sendTok2('[Parlamento — Round 2: Cross-Reading & Refinamento] ');
3032
+ const r2Results = [];
3033
+
3034
+ for (const proposal of eligibleProposals) {
3035
+ sendTok2(`[Round 2: ${proposal.label || proposal.agent}] `);
3036
+ const crossCtx = buildCrossReadCtx(proposal.agent);
3037
+ const r2Sys = `You are ${proposal.agent}, a specialist AI agent in NHA Studio Parliament. Today is ${today}. Respond entirely in ${language}.
3038
+
3039
+ ## WORKFLOW GOAL: ${task}
3040
+
3041
+ ## YOUR ROUND 1 RESPONSE:
3042
+ ${proposal.output.slice(0, 1500)}
3043
+
3044
+ ## OTHER AGENTS' ROUND 1 PROPOSALS:
3045
+ ${crossCtx}
3046
+
3047
+ DELIBERATION ROUND 2 — REFINEMENT:
3048
+ 1. Review the other agents' proposals
3049
+ 2. Incorporate valid points where you AGREE — mark with [ASSIST]
3050
+ 3. Flag genuine disagreements with [CONTRADICTION] and explain your reasoning
3051
+ 4. Produce your COMPLETE REFINED response (full answer, not a diff)
3052
+ 5. Keep your analysis focused on: ${task}`;
3053
+
3054
+ let r2Out = '';
3055
+ try {
3056
+ await callLLMStream(config, r2Sys, 'Produce your refined Round 2 response.',
3057
+ (tok) => { r2Out += tok; }, { max_tokens: 2048 });
3058
+ } catch (e) { r2Out = proposal.output; }
3059
+ r2Results.push({ agent: proposal.agent, label: proposal.label, icon: proposal.icon, output: r2Out });
3060
+ sendEv2({ deliberation_r2: { agent: proposal.agent, label: proposal.label, icon: proposal.icon, output: r2Out } });
3061
+ }
3062
+
3063
+ // Round 2 convergence
3064
+ const r2Convergence = measureConvergence(r2Results.map(r => r.output));
3065
+ sendTok2(`[Parlamento — Round 2 convergenza: ${(r2Convergence * 100).toFixed(0)}%] `);
3066
+ const converged = r2Convergence >= 0.30;
3067
+
3068
+ // Round 3: mediation only if still divergent
3069
+ let mediationOutput = '';
3070
+ if (!converged) {
3071
+ sendTok2('[Parlamento — Round 3: Mediazione...] ');
3072
+ const allR2Ctx = r2Results
3073
+ .map(r => `## ${r.label || r.agent}:\n${r.output.slice(0, 1500)}`)
3074
+ .join('\n\n---\n\n');
3075
+ const medSys = `You are HERALD, the Parliament Mediator in NHA Studio. Today is ${today}. Respond entirely in ${language}.
3076
+
3077
+ ## WORKFLOW GOAL: ${task}
3078
+
3079
+ ## ALL AGENTS' REFINED POSITIONS (Round 2):
3080
+ ${allR2Ctx}
3081
+
3082
+ MEDIATION TASK:
3083
+ 1. Identify core points of AGREEMENT across all agents
3084
+ 2. For each disagreement, evaluate which position has stronger evidence
3085
+ 3. Produce a UNIFIED synthesis preserving genuine insights from each agent
3086
+ 4. Make clear editorial choices — do NOT blend blindly
3087
+ 5. Output a complete executive summary with concrete action items for: ${task}`;
3088
+ try {
3089
+ await callLLMStream(config, medSys, 'Produce the mediated Parliament consensus.',
3090
+ (tok) => { mediationOutput += tok; }, { max_tokens: 3000 });
3091
+ } catch (e) { mediationOutput = ''; }
3092
+ sendEv2({ deliberation_r3: { output: mediationOutput } });
3093
+ }
3094
+
3095
+ clearInterval(keepaliveD);
3096
+ sendEv2({
3097
+ deliberation_done: true,
3098
+ r1_convergence: r1Convergence,
3099
+ r2_convergence: r2Convergence,
3100
+ converged,
3101
+ r2_results: r2Results,
3102
+ mediation: mediationOutput || null,
3103
+ });
3104
+ sendEv2({ done: true });
3105
+ res.write('data: [DONE]\n\n');
3106
+ res.end();
3107
+ } catch (e) {
3108
+ clearInterval(keepaliveD);
3109
+ sendEv2({ error: e.message });
3110
+ res.end();
3111
+ }
3112
+ logRequest(method, pathname, 200, Date.now() - start);
3113
+ return;
3114
+ }
3115
+
2950
3116
  // ── 404 ──────────────────────────────────────────────────────────
2951
3117
  sendJSON(res, 404, { error: 'Not found' });
2952
3118
  logRequest(method, pathname, 404, Date.now() - start);
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.44';
8
+ export const VERSION = '13.2.46';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -2971,12 +2971,13 @@ function renderSidebar() {
2971
2971
 
2972
2972
  var studioState = {
2973
2973
  task: '',
2974
- nodes: [], // [{icon,agent,label,status:'waiting'|'running'|'done'|'error'}]
2974
+ nodes: [], // [{icon,agent,label,status:'waiting'|'running'|'done'|'error',output:''}]
2975
2975
  log: [], // [{agent,icon,text,time,type:'agent'|'system'|'error'}]
2976
2976
  result: '',
2977
2977
  canvas: null, // HTML canvas content if generated
2978
2978
  running: false,
2979
- planned: false
2979
+ planned: false,
2980
+ parliamentMode: false
2980
2981
  };
2981
2982
 
2982
2983
  var studioAbortController = null;
@@ -3001,6 +3002,7 @@ function studioReset() {
3001
3002
  studioState.nodes = [];
3002
3003
  studioState.log = [];
3003
3004
  studioState.result = '';
3005
+ studioState.canvas = null;
3004
3006
  studioState.running = false;
3005
3007
  studioState.planned = false;
3006
3008
  studioTokens = {in:0, out:0};
@@ -3176,6 +3178,7 @@ async function runStudio() {
3176
3178
  }
3177
3179
  studioSetNodeStatus(i, 'done');
3178
3180
  var realOutput = (stepResult.output && stepResult.output !== '(no output)') ? stepResult.output : null;
3181
+ studioState.nodes[i].output = realOutput || '';
3179
3182
  studioLog(node.label, node.icon, realOutput || (stepResult.canvas ? '[Canvas report generated]' : '(done)'), 'agent', true);
3180
3183
  // If CanvasAgent produced HTML, open it in the canvas panel
3181
3184
  if (stepResult.canvas) {
@@ -3191,6 +3194,68 @@ async function runStudio() {
3191
3194
  context = realOutput || stepResult.canvas || context;
3192
3195
  }
3193
3196
 
3197
+ // Parliament mode: Round 2 cross-reading deliberation
3198
+ var parliamentChk = document.getElementById(\x27studioParliamentMode\x27);
3199
+ if (parliamentChk && parliamentChk.checked && studioState.nodes.length >= 2) {
3200
+ var proposals = studioState.nodes
3201
+ .filter(function(n) { return n.output && n.output !== \x27(no output)\x27 && n.agent !== \x27CanvasAgent\x27; })
3202
+ .map(function(n) { return {agent: n.agent, label: n.label, output: n.output}; });
3203
+ if (proposals.length >= 2) {
3204
+ studioLog(\x27Parlamento\x27, \x27&#x2656;\x27, \x27Avvio deliberazione — Round 2 cross-reading tra agenti...\x27, \x27system\x27);
3205
+ var deliberateBody = JSON.stringify({task: task, proposals: proposals, language: document.getElementById(\x27langSelect\x27) ? document.getElementById(\x27langSelect\x27).value : \x27it\x27});
3206
+ try {
3207
+ var delRes = await fetch(\x27/api/studio/deliberate\x27, {method:\x27POST\x27, headers:{\x27Content-Type\x27:\x27application/json\x27}, body: deliberateBody, signal: studioAbortController ? studioAbortController.signal : undefined});
3208
+ if (delRes.ok) {
3209
+ var delReader = delRes.body.getReader();
3210
+ var delDecoder = new TextDecoder();
3211
+ var delBuf = \x27\x27;
3212
+ var delDone = false;
3213
+ while (!delDone) {
3214
+ var delChunk = await delReader.read();
3215
+ if (delChunk.done) break;
3216
+ delBuf += delDecoder.decode(delChunk.value, {stream:true});
3217
+ var delLines = delBuf.split(\x27\\n\x27);
3218
+ delBuf = delLines.pop();
3219
+ delLines.forEach(function(ln) {
3220
+ if (!ln.startsWith(\x27data: \x27)) return;
3221
+ var dd = ln.slice(6).trim();
3222
+ if (dd === \x27[DONE]\x27) { delDone = true; return; }
3223
+ try {
3224
+ var dev = JSON.parse(dd);
3225
+ if (dev.token) {
3226
+ // Status tokens from server — update last log entry text inline
3227
+ var delEntries = document.querySelectorAll(\x27.studio-log-entry\x27);
3228
+ var delLast = delEntries[delEntries.length - 1];
3229
+ if (delLast) { var delTb = delLast.querySelector(\x27.studio-log-entry__text\x27); if (delTb) delTb.textContent = dev.token; }
3230
+ } else if (dev.deliberation_r2) {
3231
+ var r2d = dev.deliberation_r2;
3232
+ studioLog(r2d.label || r2d.agent, \x27&#x2656;\x27, \x27[R2] \x27 + (r2d.output || \x27\x27).slice(0, 300), \x27agent\x27, true);
3233
+ var ni2 = studioState.nodes.findIndex(function(x){return x.agent===r2d.agent;});
3234
+ if (ni2 >= 0) { studioState.nodes[ni2].output = r2d.output; }
3235
+ context = r2d.output || context;
3236
+ } else if (dev.deliberation_r3) {
3237
+ studioLog(\x27HERALD\x27, \x27&#128295;\x27, \x27[Mediazione] \x27 + (dev.deliberation_r3.output || \x27\x27).slice(0, 300), \x27system\x27, true);
3238
+ context = dev.deliberation_r3.output || context;
3239
+ } else if (dev.deliberation_done) {
3240
+ var r2Conv = Math.round((dev.r2_convergence || 0) * 100);
3241
+ studioLog(\x27Parlamento\x27, \x27&#x2656;\x27, \x27Deliberazione completa — convergenza R2: \x27 + r2Conv + \x27%\x27, \x27system\x27);
3242
+ if (dev.mediation) { context = dev.mediation; }
3243
+ delDone = true;
3244
+ } else if (dev.done) {
3245
+ delDone = true;
3246
+ }
3247
+ } catch(e2) {}
3248
+ });
3249
+ }
3250
+ }
3251
+ } catch(e3) {
3252
+ if (e3.name !== \x27AbortError\x27) {
3253
+ studioLog(\x27Parlamento\x27, \x27&#x2656;\x27, \x27Deliberazione non disponibile: \x27 + (e3.message || String(e3)), \x27error\x27);
3254
+ }
3255
+ }
3256
+ }
3257
+ }
3258
+
3194
3259
  // Final result is the last step's output
3195
3260
  studioState.result = context;
3196
3261
  renderStudioResult();
@@ -3497,6 +3562,10 @@ function renderStudio(el) {
3497
3562
  '<button onclick="studioReset()" title="' + t('reset') + '" style="padding:8px 12px;background:none;border:1px solid var(--border);border-radius:8px;color:var(--dim);cursor:pointer;font-size:16px;line-height:1" ' + (studioState.running ? 'disabled' : '') + '>&#8635;</button>' +
3498
3563
  '</div>' +
3499
3564
  '</div>' +
3565
+ '<label style="display:flex;align-items:center;gap:8px;margin-top:8px;cursor:pointer;user-select:none">' +
3566
+ '<input type="checkbox" id="studioParliamentMode" style="width:15px;height:15px;accent-color:var(--green3)" ' + (studioState.parliamentMode ? \x27checked\x27 : \x27\x27) + ' onchange="studioState.parliamentMode=this.checked">' +
3567
+ '<span style="font-size:12px;color:var(--dim)">&#x2656; <strong style="color:var(--green)">Parlamento</strong> — Round 2 cross-reading tra agenti (2x token)</span>' +
3568
+ '</label>' +
3500
3569
  '</div>' +
3501
3570
 
3502
3571
  // ── MANUAL BUILDER MODE ──