nothumanallowed 15.1.13 → 15.1.16

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": "15.1.13",
3
+ "version": "15.1.16",
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/cli.mjs CHANGED
@@ -55,12 +55,14 @@ export async function main(argv) {
55
55
  // npm version check (non-blocking). The one-liner uses --prefer-online to
56
56
  // bypass npm's metadata cache, which is the #1 reason `npm install -g`
57
57
  // appears to "do nothing" — it had stale "latest" in the local cache.
58
+ // Every command separated by `&&` so users can copy-paste the whole line.
58
59
  checkNpmVersion().then(result => {
59
60
  if (result?.updateAvailable) {
60
61
  console.log('');
61
62
  warn(`New NHA version available: ${result.current} → ${result.latest}`);
62
63
  info(`Run "nha update" (recommended — auto-installs npm + agents)`);
63
- info(`Or manually: npm cache clean --force && npm install -g nothumanallowed@${result.latest} --prefer-online`);
64
+ info(`Or copy-paste this ENTIRE line (note: --prefer-online, not --pref-online):`);
65
+ info(` npm cache clean --force && npm install -g nothumanallowed@${result.latest} --prefer-online && hash -r && nha version`);
64
66
  }
65
67
  }).catch(() => {});
66
68
  }
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 = '15.1.13';
8
+ export const VERSION = '15.1.16';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -759,16 +759,35 @@ ${okBlock}`;
759
759
 
760
760
  tok('[Parlamento — Round 2: Cross-Reading & Refinamento] ');
761
761
  const r2Results = [];
762
+ // Per-agent timeout — if any single agent stalls (network, LLM hung)
763
+ // the loop must not block the whole deliberation. After this budget,
764
+ // fall back to the agent's Round 1 output and continue.
765
+ const PER_AGENT_TIMEOUT_MS = 90_000;
762
766
  for (const proposal of eligible) {
763
767
  tok(`[Round 2: ${proposal.label || proposal.agent}] `);
764
768
  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.`;
765
769
  let r2Out = '';
770
+ let fellBack = false;
766
771
  try {
767
- await callLLMStream(config, r2Sys, 'Produce your refined Round 2 response. Write complete content under every heading — never leave a section title without body text.',
768
- (t) => { r2Out += t; }, { max_tokens: 16384 });
769
- } catch { r2Out = proposal.output; }
770
- r2Results.push({ agent: proposal.agent, label: proposal.label, icon: proposal.icon, output: r2Out });
771
- sse({ deliberation_r2: { agent: proposal.agent, label: proposal.label, icon: proposal.icon, output: r2Out } });
772
+ // Race the LLM stream against a hard timeout.
773
+ await Promise.race([
774
+ callLLMStream(config, r2Sys, 'Produce your refined Round 2 response. Write complete content under every heading — never leave a section title without body text.',
775
+ (t) => { r2Out += t; }, { max_tokens: 16384 }),
776
+ new Promise((_, reject) => setTimeout(() => reject(new Error('R2_AGENT_TIMEOUT')), PER_AGENT_TIMEOUT_MS)),
777
+ ]);
778
+ } catch (err) {
779
+ // Fallback: use Round 1 output so the rest of deliberation can proceed.
780
+ fellBack = true;
781
+ r2Out = proposal.output;
782
+ tok(`[Round 2 ${proposal.label || proposal.agent}: timeout — uso Round 1 come fallback] `);
783
+ }
784
+ // If the stream returned but produced no text (LLM hiccup), also fall back.
785
+ if (!r2Out || r2Out.trim().length < 50) {
786
+ fellBack = true;
787
+ r2Out = proposal.output;
788
+ }
789
+ r2Results.push({ agent: proposal.agent, label: proposal.label, icon: proposal.icon, output: r2Out, fellBack });
790
+ sse({ deliberation_r2: { agent: proposal.agent, label: proposal.label, icon: proposal.icon, output: r2Out, fellBack } });
772
791
  }
773
792
 
774
793
  const r2Conv = measureConvergence(r2Results.map(r => r.output));
@@ -792,9 +811,22 @@ ${okBlock}`;
792
811
  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.`;
793
812
 
794
813
  let mediationOutput = '';
814
+ const HERALD_TIMEOUT_MS = 120_000;
795
815
  try {
796
- await callLLMStream(config, medSys, 'Produce the Parliament final synthesis. Be thorough and complete.', (t) => { mediationOutput += t; }, { max_tokens: 16384 });
797
- } catch {}
816
+ await Promise.race([
817
+ callLLMStream(config, medSys, 'Produce the Parliament final synthesis. Be thorough and complete.', (t) => { mediationOutput += t; }, { max_tokens: 16384 }),
818
+ new Promise((_, reject) => setTimeout(() => reject(new Error('HERALD_TIMEOUT')), HERALD_TIMEOUT_MS)),
819
+ ]);
820
+ } catch (err) {
821
+ // HERALD timed out — emit whatever we got so the client isn't stuck.
822
+ tok(`[HERALD mediation: timeout dopo ${HERALD_TIMEOUT_MS/1000}s — restituisco contenuto parziale] `);
823
+ }
824
+ // If absolutely nothing came back, fall back to a concatenation of the
825
+ // r2 outputs so the user has SOMETHING to read instead of a blank panel.
826
+ if (!mediationOutput || mediationOutput.trim().length < 50) {
827
+ mediationOutput = `# Sintesi automatica (HERALD non disponibile)\n\n` +
828
+ r2Results.map(r => `## ${r.label || r.agent}\n${r.output.slice(0, 2000)}`).join('\n\n---\n\n');
829
+ }
798
830
  sse({ deliberation_r3: { output: mediationOutput, converged } });
799
831
 
800
832
  clearInterval(keepalive);
@@ -320,12 +320,50 @@ async function callAgentWithTools(config, agentName, userMessage, languageOverri
320
320
  }
321
321
 
322
322
  history.push({ role: 'assistant', content: response });
323
- userMessage = 'Tool results:\n' + toolResults.join('\n') + '\n\nNow give the user a short, clear confirmation in ' + language + '. Be direct no preamble, no HERALD format. If an action was completed, say so clearly.';
323
+ // LANGUAGE enforcement up FRONTputting it last allows Liara to ignore it.
324
+ // Repeat the instruction at the top so the model commits to it before reading
325
+ // the tool results, then again at the bottom for reinforcement.
326
+ userMessage =
327
+ `RISPOSTA OBBLIGATORIA IN ${language.toUpperCase()}. Tutta la frase deve essere in ${language}, niente inglese, niente lingue miste.\n\n` +
328
+ `Tool results:\n${toolResults.join('\n')}\n\n` +
329
+ `Now give the user a short, clear confirmation. Be direct — no preamble, no HERALD format. ` +
330
+ `If an action was completed, say so clearly. REMEMBER: reply ONLY in ${language}.`;
331
+ }
332
+
333
+ // Defensive language post-check: small models sometimes drop back to English
334
+ // even when instructed otherwise (especially after tool execution, where the
335
+ // English tool-result text biases the continuation). If the final reply is
336
+ // in a clearly different language than expected, translate it.
337
+ if (finalText && language && !looksLikeLanguage(finalText, language)) {
338
+ try {
339
+ const translatePrompt =
340
+ `Traduci il seguente messaggio in ${language}. Mantieni lo stesso significato, lo stesso tono, la stessa lunghezza. Restituisci SOLO la traduzione, senza preamboli o note.\n\nMessaggio:\n${finalText}`;
341
+ const translated = await callLLM(config,
342
+ `You are a precise translator. Translate the given text into ${language}. Output ONLY the translation, no commentary.`,
343
+ translatePrompt,
344
+ { max_tokens: 800 },
345
+ );
346
+ const cleaned = (translated || '').trim();
347
+ if (cleaned && cleaned.length > 0) finalText = cleaned;
348
+ } catch { /* keep the original if translation fails */ }
324
349
  }
325
350
 
326
351
  return { text: finalText || 'Fatto.', history };
327
352
  }
328
353
 
354
+ /**
355
+ * Detect if a text is in the expected language. Used as a defensive check
356
+ * after the LLM produces the final tool-confirmation reply — small models
357
+ * sometimes drop back to English even when instructed otherwise.
358
+ * Returns true if the language seems right, false if a strong mismatch.
359
+ */
360
+ function looksLikeLanguage(text, expectedLanguage) {
361
+ if (!text || !expectedLanguage) return true;
362
+ const detected = detectLanguage(text);
363
+ if (!detected) return true; // not enough signal, give benefit of the doubt
364
+ return detected === expectedLanguage;
365
+ }
366
+
329
367
  // ── Telegram Bot (Long Polling via native fetch) ─────────────────────────────
330
368
 
331
369
  // ── User store for Telegram chat IDs (for broadcast notifications) ──────────
@@ -509,8 +547,9 @@ class TelegramResponder {
509
547
  const msg =
510
548
  `🆕 NHA v${latest} disponibile!\n\n` +
511
549
  `Una nuova versione di NotHumanAllowed è stata pubblicata.\n\n` +
512
- `Aggiorna con:\nnpm install -g nothumanallowed@latest\n\n` +
513
- `Poi riavvia il bot con: nha ops stop && nha ops start`;
550
+ `Aggiorna con UN SOLO comando (copia e incolla tutto):\n` +
551
+ `npm cache clean --force && npm install -g nothumanallowed@latest --prefer-online && nha ops stop && nha ops start\n\n` +
552
+ `Importante: usa esattamente "--prefer-online" (NON "--pref-online") e tieni tutti i "&&" tra i comandi.`;
514
553
 
515
554
  this.log(`[Telegram] Broadcasting update notification v${latest} to ${chatIds.length} users`);
516
555
 
@@ -165,12 +165,11 @@ TOOLS:
165
165
  Use this when the user says: "inserisci", "aggiungi", "crea", "metti", "fissa", "prenota", "add", "create", "schedule", "book".
166
166
 
167
167
  PARAMETER MEANING (CRITICAL — most common mistake):
168
- - summary = the TITLE of the event (what you'd write on a calendar grid). Short, like "Tagliando BMW", "Riunione Cliente X", "Cena con Marta".
168
+ - summary = the TITLE of the event (what you'd write on a calendar grid). Short, derived from what the user said.
169
169
  - description = optional NOTES/details. Leave empty unless the user gave extra context that doesn't fit in the title.
170
170
  - NEVER put the title in description. NEVER leave summary empty.
171
171
 
172
- Example user says "fissa tagliando BMW per il 15 maggio alle 17":
173
- { "action": "calendar_create", "params": { "summary": "Tagliando BMW", "start": "2026-05-15T17:00:00", "end": "2026-05-15T18:00:00" } }
172
+ Always derive summary, date and time from the actual user message never use literal values from this documentation.
174
173
 
175
174
  IMPORTANT: When user says "inserisci appuntamento" or "crea evento" → use calendar_create, NOT calendar_find.
176
175
  Extract the summary, date, and time from the user message. If end time is not specified, default to 1 hour after start.
@@ -667,7 +666,15 @@ RULES:
667
666
  // Used ONLY when provider === 'nha'. Liara already knows tool signatures from
668
667
  // LoRA training — no verbose descriptions needed. Dynamic values (today, tz,
669
668
  // language, profile, imap accounts) are still injected at runtime below.
670
- export const LIARA_TOOL_DEFINITIONS = `You are Liara, the NHA personal AI assistant.
669
+ export const LIARA_TOOL_DEFINITIONS = `## LANGUAGE HIGHEST PRIORITY
670
+
671
+ You MUST write your ENTIRE response in {{LANGUAGE}}. Every sentence, every confirmation, every error message. This overrides everything else. Even when this prompt itself is written in English, your reply to the user must be in {{LANGUAGE}} only. Do NOT mix languages. Do NOT default to English.
672
+
673
+ If {{LANGUAGE}} is Italian, write only in Italian: "L'appuntamento è stato cancellato." NOT "The event has been deleted." or "Both events have been deleted."
674
+
675
+ ---
676
+
677
+ You are Liara, the NHA personal AI assistant.
671
678
  Today: {{TODAY}} | Timezone: {{TIMEZONE}} | Language: {{LANGUAGE}}
672
679
 
673
680
  When the user's request requires an action, output one or more fenced JSON blocks:
@@ -688,12 +695,11 @@ Never output a JSON block as a suggestion — every block executes immediately.
688
695
 
689
696
  ### Calendar
690
697
  calendar_create(summary, start, end, description?, attendees?, location?)
691
- - summary = SHORT title (e.g. "Tagliando BMW"). REQUIRED. Do NOT leave empty.
698
+ - summary = SHORT title derived from what the user actually said. REQUIRED. Do NOT leave empty.
692
699
  - description = optional notes only. Do NOT put the title here.
693
700
  - start/end = ISO 8601 datetimes. Default end = start + 1 hour.
694
- - The tool response includes the REAL eventId in parentheses, e.g. "(eventId: <some-google-id>)". Use that exact value verbatim for subsequent operations. **The eventId is a long lowercase alphanumeric string returned by Google — NEVER make one up.**
695
- Example user says "fissa tagliando BMW 15 maggio ore 17":
696
- {"action":"calendar_create","params":{"summary":"Tagliando BMW","start":"2026-05-15T17:00:00","end":"2026-05-15T18:00:00"}}
701
+ - The tool response includes the REAL eventId. Use that exact value verbatim for any subsequent operation on the same event. The eventId is a long alphanumeric string returned by Google — NEVER fabricate one.
702
+ - Always derive every parameter value from the user's message, never from this documentation.
697
703
 
698
704
  calendar_update(eventId, summary?, location?, description?, start?, end?)
699
705
  - Use this for "correggi", "modifica", "rinomina", "cambia titolo", "sposta", "aggiorna" referring to an event you JUST created or found.