nothumanallowed 15.1.16 → 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.16",
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/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.16';
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
 
@@ -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
 
@@ -696,7 +725,7 @@ class TelegramResponder {
696
725
  return d.text || '';
697
726
  }
698
727
 
699
- 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)');
700
729
  }
701
730
 
702
731
  async _handleMessage(message) {
@@ -730,7 +759,7 @@ class TelegramResponder {
730
759
  this.log(`[Telegram] Voice transcription failed: ${err.message}`);
731
760
  await this._telegramCall('sendMessage', {
732
761
  chat_id: chatId,
733
- 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`,
734
763
  });
735
764
  return;
736
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])) {