nothumanallowed 15.1.14 → 15.1.17

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.14",
3
+ "version": "15.1.17",
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.14';
8
+ export const VERSION = '15.1.17';
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);
@@ -278,6 +278,35 @@ async function callAgentWithTools(config, agentName, userMessage, languageOverri
278
278
 
279
279
  if (actions.length === 0) {
280
280
  finalText = textParts.join('\n').trim();
281
+
282
+ // ── HALLUCINATED SUCCESS DETECTION ────────────────────────────────
283
+ // The model sometimes writes "X è stato cancellato/creato/inviato con
284
+ // successo" WITHOUT actually emitting a tool block — pure hallucination.
285
+ // If that happens and we're early in the round budget, force ONE retry
286
+ // with an explicit instruction to emit the tool JSON. If it still
287
+ // refuses, replace the fake success with a warning so the user knows
288
+ // the action did NOT actually happen.
289
+ const claimsSuccess = isCompletedAction(finalText);
290
+ const hasReferenceToTool = /\b(eventid|event id|id evento)\b/i.test(finalText);
291
+ const looksFakeSuccess = claimsSuccess || (hasReferenceToTool && round === 0);
292
+ if (looksFakeSuccess && round < 1) {
293
+ // Push the bad response as history so the model sees what it just did,
294
+ // then ask it to actually emit the tool.
295
+ history.push({ role: 'assistant', content: response });
296
+ userMessage =
297
+ `STOP. Hai dichiarato che un'azione è stata completata ma NON hai emesso nessun blocco tool JSON. ` +
298
+ `Senza il blocco tool, NESSUNA azione viene eseguita davvero. ` +
299
+ `Ora emetti il blocco JSON corretto (\`\`\`json ... \`\`\`) per l'azione richiesta dall'utente, usando i parametri corretti. ` +
300
+ `Se ti serve un eventId, chiama prima calendar_find o calendar_date. Non scrivere altro testo finché non hai eseguito davvero il tool.`;
301
+ continue; // restart the loop with the corrective user message
302
+ }
303
+ if (looksFakeSuccess && round >= 1) {
304
+ // Second attempt also lied — refuse to forward the fake success.
305
+ finalText =
306
+ `Non sono riuscito a eseguire l'azione automaticamente (il modello ha dichiarato un successo senza eseguire il tool). ` +
307
+ `Riprova riformulando la richiesta, oppure dimmi esattamente cosa vuoi che faccia.`;
308
+ }
309
+
281
310
  break;
282
311
  }
283
312
 
@@ -547,8 +576,9 @@ class TelegramResponder {
547
576
  const msg =
548
577
  `🆕 NHA v${latest} disponibile!\n\n` +
549
578
  `Una nuova versione di NotHumanAllowed è stata pubblicata.\n\n` +
550
- `Aggiorna con:\nnpm install -g nothumanallowed@latest\n\n` +
551
- `Poi riavvia il bot con: nha ops stop && nha ops start`;
579
+ `Aggiorna con UN SOLO comando (copia e incolla tutto):\n` +
580
+ `npm cache clean --force && npm install -g nothumanallowed@latest --prefer-online && nha ops stop && nha ops start\n\n` +
581
+ `Importante: usa esattamente "--prefer-online" (NON "--pref-online") e tieni tutti i "&&" tra i comandi.`;
552
582
 
553
583
  this.log(`[Telegram] Broadcasting update notification v${latest} to ${chatIds.length} users`);
554
584
 
@@ -695,7 +725,7 @@ class TelegramResponder {
695
725
  return d.text || '';
696
726
  }
697
727
 
698
- throw new Error('Voice transcription unavailable. The NHA proxy is temporarily unreachable.');
728
+ throw new Error('servizio di trascrizione vocale momentaneamente non disponibile (NHA proxy irraggiungibile)');
699
729
  }
700
730
 
701
731
  async _handleMessage(message) {
@@ -729,7 +759,7 @@ class TelegramResponder {
729
759
  this.log(`[Telegram] Voice transcription failed: ${err.message}`);
730
760
  await this._telegramCall('sendMessage', {
731
761
  chat_id: chatId,
732
- text: `Non riesco a trascrivere il vocale: ${err.message}\n\nAggiungi una chiave Groq (gratuita) con: nha config set groqKey gsk-...`,
762
+ text: `Non riesco a trascrivere il vocale (${err.message}).\n\nPer abilitare la trascrizione vocale gratuita, dal computer esegui:\nnha config set groqKey TUA_CHIAVE_GROQ\n\nLa chiave si ottiene gratis su https://console.groq.com/keys`,
733
763
  });
734
764
  return;
735
765
  }
@@ -687,9 +687,13 @@ Never output a JSON block as a suggestion — every block executes immediately.
687
687
  ## ABSOLUTE RULES (violate these and the user loses trust)
688
688
 
689
689
  1. NEVER invent tool output. If you need data (an event ID, a price, a search result), you MUST emit a tool JSON block and wait for its real response. Do NOT write fake "(eventId: 123456789)" or fake results.
690
- 2. NEVER duplicate. If the user says "correggi/modifica/sposta/aggiorna" referring to an item you just created in this conversation, use the corresponding *_update / *_move tool with the eventId/taskId from your previous tool response. Do NOT create a second item.
691
- 3. NEVER put the TITLE in the description field. The "summary" / "title" field is the SHORT name (what shows on a calendar grid). The "description" field is ONLY for extra notes.
692
- 4. When in doubt, ASK ONE concise question do not invent details.
690
+ 2. NEVER claim an action was completed unless you have just received a SUCCESS tool result for that action in this same turn. NEVER write "cancellato con successo", "creato con successo", "inviato" etc. without a real tool execution behind it. If you didn't emit the tool block, the action didn't happen.
691
+ 3. NEVER duplicate. If the user says "correggi/modifica/sposta/aggiorna" referring to an item you just created in this conversation, use the corresponding *_update / *_move tool with the eventId/taskId from your previous tool response. Do NOT create a second item.
692
+ 4. NEVER put the TITLE in the description field. The "summary" / "title" field is the SHORT name (what shows on a calendar grid). The "description" field is ONLY for extra notes.
693
+ 5. NEVER invent times. If the user did not specify a time, ASK them ("A che ora?"). Do not default to 10:00 or any other time silently. Same for missing date, duration, attendees.
694
+ 6. When the user confirms ("procedi", "sì", "fallo", "ok", "vai") AND there is a pending action from the IMMEDIATELY PREVIOUS assistant turn (you proposed something like "Posso cancellare X. Procedo?"), EXECUTE that exact pending action with the same parameters — do NOT search again, do NOT propose again, do NOT ask again. Emit the tool block for the action you just proposed.
695
+ 7. When in doubt, ASK ONE concise question — do not invent details.
696
+ 8. Tool JSON blocks MUST be wrapped in \`\`\`json ... \`\`\` fences. If you emit raw JSON without fences, the system can sometimes still parse it but it's unreliable — always use the fences.
693
697
 
694
698
  ## TOOL SIGNATURES (parameters and how to use them)
695
699
 
@@ -814,12 +818,53 @@ export function parseActions(text) {
814
818
  const trailing = normalized.slice(lastIndex).trim();
815
819
  if (trailing) textParts.push(trailing);
816
820
 
817
- // Fallback: if no fenced blocks found, scan for bare {"action": ...} objects in the text
821
+ // Fallback: if no fenced blocks found, scan for bare {"action": ...} objects
822
+ // in the text. The previous regex was naive and stopped at the first `}`,
823
+ // breaking on nested params like {"action":"X","params":{"summary":"Y"}}.
824
+ // Now we do proper brace balancing so we always capture the FULL object.
818
825
  if (actions.length === 0) {
826
+ const consumed = new Set();
827
+ const findBareActions = (src) => {
828
+ let i = 0;
829
+ while (i < src.length) {
830
+ // Quick filter: only consider candidates that start with {"action"
831
+ const idx = src.indexOf('{"action"', i);
832
+ if (idx < 0) break;
833
+ // Walk forward balancing braces, respecting strings
834
+ let depth = 0;
835
+ let inStr = false;
836
+ let escape = false;
837
+ let end = -1;
838
+ for (let j = idx; j < src.length; j++) {
839
+ const c = src[j];
840
+ if (escape) { escape = false; continue; }
841
+ if (c === '\\' && inStr) { escape = true; continue; }
842
+ if (c === '"' && !escape) { inStr = !inStr; continue; }
843
+ if (inStr) continue;
844
+ if (c === '{') depth++;
845
+ else if (c === '}') {
846
+ depth--;
847
+ if (depth === 0) { end = j; break; }
848
+ }
849
+ }
850
+ if (end < 0) break;
851
+ const candidate = src.slice(idx, end + 1);
852
+ try {
853
+ const parsed = JSON.parse(candidate);
854
+ if (parsed.action && typeof parsed.action === 'string' && !consumed.has(candidate)) {
855
+ actions.push({ action: parsed.action, params: parsed.params || {} });
856
+ consumed.add(candidate);
857
+ }
858
+ } catch { /* not valid JSON, skip */ }
859
+ i = end + 1;
860
+ }
861
+ };
862
+ findBareActions(text);
863
+ // Legacy fallback kept for very loose JSON shapes — only fires if the
864
+ // brace scanner found nothing.
819
865
  const bareRegex = /\{[\s\S]*?"action"\s*:\s*"[^"]+[\s\S]*?\}/g;
820
866
  let bareMatch;
821
- const consumed = new Set();
822
- while ((bareMatch = bareRegex.exec(text)) !== null) {
867
+ while (actions.length === 0 && (bareMatch = bareRegex.exec(text)) !== null) {
823
868
  try {
824
869
  const parsed = JSON.parse(bareMatch[0]);
825
870
  if (parsed.action && typeof parsed.action === 'string' && !consumed.has(bareMatch[0])) {