nothumanallowed 15.1.31 → 15.1.33

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.31",
3
+ "version": "15.1.33",
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.31';
8
+ export const VERSION = '15.1.33';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -14,9 +14,15 @@
14
14
 
15
15
  import fs from 'fs';
16
16
  import path from 'path';
17
- import { exec, spawn } from 'child_process';
18
- import { createServer } from 'net';
17
+ import { exec, spawn, execSync } from 'child_process';
18
+ import { createServer, Socket } from 'net';
19
19
  import { promisify } from 'util';
20
+ import { createRequire } from 'module';
21
+ // `require` shim for the rare spots where CJS-style require() was historically
22
+ // used in this ESM file. Without this, every `require(...)` here throws
23
+ // "ReferenceError: require is not defined" — which is exactly the bug that
24
+ // took 31 releases to diagnose because it surfaced in the SSE error channel.
25
+ const require = createRequire(import.meta.url);
20
26
  import { sendJSON, sendError, parseBody, sendSSE } from '../index.mjs';
21
27
  import { loadConfig } from '../../config.mjs';
22
28
  import { callLLM, callLLMStream, callLLMWithTools, getApiKey, fixQwen3BPE } from '../../services/llm.mjs';
@@ -2410,9 +2416,9 @@ export function register(router) {
2410
2416
  path.resolve(__dir, '../../../node_modules/.bin/tsc'),
2411
2417
  path.resolve(__dir, '../../../../node_modules/.bin/tsc'),
2412
2418
  ];
2413
- // Also try global
2419
+ // Also try global — execSync imported at top of file (ESM).
2414
2420
  try {
2415
- const globalTsc = require('child_process').execSync('which tsc 2>/dev/null || where tsc 2>nul', { encoding: 'utf-8', timeout: 3000 }).trim();
2421
+ const globalTsc = execSync('which tsc 2>/dev/null || where tsc 2>nul', { encoding: 'utf-8', timeout: 3000 }).trim();
2416
2422
  if (globalTsc) candidates.push(globalTsc);
2417
2423
  } catch {}
2418
2424
  for (const c of candidates) {
@@ -3525,8 +3531,11 @@ function _waitForPort(port, timeoutMs) {
3525
3531
  return new Promise((resolve) => {
3526
3532
  const deadline = Date.now() + timeoutMs;
3527
3533
  const check = () => {
3528
- // Try to connect to the port if connection succeeds, the server is listening
3529
- const { Socket } = require('net');
3534
+ // Use the ESM-imported SocketNEVER `require('net')` here, this file
3535
+ // is .mjs and `require` is not defined in ESM. The require call here
3536
+ // was the single root cause of "[error] require is not defined" with
3537
+ // no autofix flow visible — the crash happened INSIDE the nha ui
3538
+ // server itself (in this exact function), not in the child sandbox.
3530
3539
  const sock = new Socket();
3531
3540
  sock.setTimeout(500);
3532
3541
  sock.once('connect', () => { sock.destroy(); resolve(true); });
@@ -822,17 +822,50 @@ class TelegramResponder {
822
822
  const shortConfirm = /^(s[ìi]\.?|ok\.?|okay\.?|yes\.?|yep\.?|sure\.?|certo\.?|procedi\.?|fallo\.?|vai\.?|go\.?|do it\.?|conferma\.?|certo che s[ìi]\.?|fallo pure\.?)$/i.test(cleanText.trim());
823
823
 
824
824
  if (proposedAction && shortConfirm) {
825
+ // Extract the SPECIFIC action the assistant proposed (create/delete/
826
+ // update/move/send/...), so we can inject an explicit tool name in
827
+ // the instruction. Without this, Liara may pick a random tool —
828
+ // e.g. user confirms a CREATE proposal and the model fires DELETE
829
+ // with a hallucinated eventId.
830
+ const r = lowerReply;
831
+ let actionHint = '';
832
+ let toolHint = '';
833
+ if (/fissat[oa]|inserir[oe]|cre(a|er)[oa]|aggiunger[oeò]|prenota|registr[oa]|mett[oa] (?:in calendario|nel calendario)/.test(r)) {
834
+ actionHint = 'CREARE un nuovo appuntamento';
835
+ toolHint = 'calendar_create con i parametri ESATTI (summary, start, end) che hai descritto nel turno precedente';
836
+ } else if (/cancell|eliminar[eo]|rimuover/.test(r)) {
837
+ actionHint = 'CANCELLARE un appuntamento esistente';
838
+ toolHint = 'PRIMA calendar_find/calendar_date per ottenere l\'eventId REALE, POI calendar_delete con quell\'eventId. Mai inventare ID';
839
+ } else if (/spostar|riprogrammar|cambiar[eo] (?:l\')?orario|cambiar[eo] (?:la )?data/.test(r)) {
840
+ actionHint = 'SPOSTARE un appuntamento';
841
+ toolHint = 'PRIMA calendar_find per ottenere eventId, POI calendar_move(eventId, newStart, newEnd)';
842
+ } else if (/modificar|correggere|rinominar|cambiar[eo] (?:il )?titolo/.test(r)) {
843
+ actionHint = 'MODIFICARE un appuntamento';
844
+ toolHint = 'PRIMA calendar_find per eventId, POI calendar_update(eventId, ...) con SOLO i campi da cambiare';
845
+ } else if (/inviar|spedir|mandar (?:l\'|la )?email|send (?:the )?email/.test(r)) {
846
+ actionHint = 'INVIARE un\'email';
847
+ toolHint = 'gmail_send (o gmail_reply se è una risposta) con destinatario, oggetto e corpo già concordati';
848
+ } else if (/aggiunger[eo] (?:un )?task|creare (?:un )?task|inserir[eo] (?:il )?task/.test(r)) {
849
+ actionHint = 'CREARE un task';
850
+ toolHint = 'task_add con la descrizione concordata';
851
+ } else {
852
+ actionHint = 'l\'azione che hai proposto';
853
+ toolHint = 'il tool corrispondente all\'azione (calendar_create se hai detto "fisso"; calendar_delete se "cancello"; gmail_send se "invio"; ecc.). NON inventare eventId — usa SOLO ID reali ricevuti da tool precedenti';
854
+ }
855
+
825
856
  enrichedMessage =
826
- `[CONFERMA UTENTE — Esegui SUBITO l'azione che hai proposto nel turno precedente]\n\n` +
827
- `Tuo turno precedente:\n"${(lastCtx.agentReply || '').slice(0, 800)}"\n\n` +
857
+ `[CONFERMA UTENTE — Esegui SUBITO l'azione che hai proposto]\n\n` +
858
+ `Tuo turno precedente (proposta):\n"${(lastCtx.agentReply || '').slice(0, 800)}"\n\n` +
828
859
  `Risposta utente: "${cleanText.trim()}"\n\n` +
860
+ `AZIONE DA ESEGUIRE: ${actionHint}\n` +
861
+ `TOOL DA USARE: ${toolHint}\n\n` +
829
862
  `ISTRUZIONI OBBLIGATORIE:\n` +
830
- `1. L'utente sta confermando il piano che TU hai proposto. NON chiedere altri chiarimenti.\n` +
831
- `2. NON dire "procedo" senza eseguire emetti SUBITO i tool block JSON.\n` +
832
- `3. Se hai promesso N step (cercare, eliminare, mostrare la lista, etc.)emetti TUTTI gli N tool block in sequenza nel TUO prossimo messaggio.\n` +
833
- `4. Esempio: se hai detto "cercherò l'evento, lo eliminerò, mostrerò la lista" emetti calendar_find quando ricevi il risultato emetti calendar_delete con l'eventId trovato infine calendar_month/calendar_week.\n` +
834
- `5. NON ripetere "Procedo. Cosa vuoi?" sai già cosa vuole, lo hai proposto tu. Esegui.`;
835
- this.log(`[Telegram] ${fromUser}: pending-action force (proposal detected in prev reply, user confirmed short)`);
863
+ `1. Emetti SUBITO il blocco JSON \`\`\`json {"action":"...","params":{...}} \`\`\` per ESATTAMENTE l'azione qui sopra.\n` +
864
+ `2. Usa i parametri (data, ora, titolo, destinatario, eccetera) che HAI GIÀ menzionato nel tuo turno precedente. NON chiedere di nuovo.\n` +
865
+ `3. Se l'azione è CREATE/SEND/ADD: emetti SOLO quel tool. NIENTE find/delete/update preliminarequei tool servono per modificare cose esistenti, non per creare di nuove.\n` +
866
+ `4. Se l'azione è DELETE/UPDATE/MOVE: emetti PRIMA calendar_find o calendar_date per trovare l'eventId REALE (lungo, alfanumerico Google). MAI usare placeholder come "A1B2C3D4E5F6G7H8I9J0", "abc123", "ABC123".\n` +
867
+ `5. NON chiedere "cosa vuoi". NON dire "Procedo." senza eseguire. SOLO il blocco JSON e una breve conferma in italiano dopo l'esecuzione.`;
868
+ this.log(`[Telegram] ${fromUser}: pending-action force ${actionHint}`);
836
869
  }
837
870
 
838
871
  this.log(`[Telegram] ${fromUser}: continuation → ${agent.toUpperCase()} (ctx ${Math.round(stickyAge/1000)}s ago, history=${preHistory ? preHistory.length : 0})`);
@@ -1091,6 +1091,41 @@ export async function buildSystemPrompt(persona, personaDescription, config, ini
1091
1091
  * @param {object} config — NHA config
1092
1092
  * @returns {Promise<string>} result description
1093
1093
  */
1094
+
1095
+ /**
1096
+ * Detect IDs that the model has clearly invented vs real Google Calendar
1097
+ * eventIds. Real ones are LONG (≥10), LOWERCASE, alphanumeric (a-v + 0-9,
1098
+ * sometimes with `_` or `-`). Placeholders are typically short, uppercase,
1099
+ * dictionary words, or alternate-letter+digit pseudo-random patterns like
1100
+ * "A1B2C3D4E5F6G7H8I9J0".
1101
+ */
1102
+ function isPlaceholderEventId(id) {
1103
+ if (!id || typeof id !== 'string') return true;
1104
+ const s = id.trim();
1105
+ // 1. Explicit literal blocklist (covers everything we've seen so far)
1106
+ const LITERAL = new Set([
1107
+ 'abc123', 'def456', 'xyz789', 'event_123', 'event_123456789',
1108
+ '123', '456', 'foo', 'bar', 'baz', 'example', 'placeholder',
1109
+ 'eventid', 'event_id', 'id', 'null', 'undefined', 'some-google-id',
1110
+ 'event-id', 'id-from-tool', 'event_abc', 'event_xyz',
1111
+ ]);
1112
+ if (LITERAL.has(s.toLowerCase())) return true;
1113
+ // 2. Too short — Google eventIds are always ≥ 16 chars in practice
1114
+ if (s.length < 10) return true;
1115
+ // 3. Contains spaces — never valid
1116
+ if (/\s/.test(s)) return true;
1117
+ // 4. Pseudo-random alternating letter+digit pattern like "A1B2C3D4..."
1118
+ // Real Google eventIds NEVER look like this — they have natural
1119
+ // base32-ish randomness, not human-readable patterns.
1120
+ if (/^[A-Z](?:\d[A-Z]){3,}/i.test(s)) return true;
1121
+ // 5. Almost-all-uppercase: Google eventIds are lowercase. UPPERCASE
1122
+ // is a model invention signature.
1123
+ const upper = (s.match(/[A-Z]/g) || []).length;
1124
+ const lower = (s.match(/[a-z]/g) || []).length;
1125
+ if (upper >= 3 && upper > lower) return true;
1126
+ return false;
1127
+ }
1128
+
1094
1129
  export async function executeTool(action, params, config) {
1095
1130
  switch (action) {
1096
1131
  // ── Gmail ──────────────────────────────────────────────────────────────
@@ -1634,14 +1669,10 @@ export async function executeTool(action, params, config) {
1634
1669
  let eventId = String(params.eventId || '').trim();
1635
1670
  if (!eventId) return 'eventId required. Call calendar_find or calendar_date first to get the REAL eventId.';
1636
1671
 
1637
- // Reject obvious placeholder IDs (model copied from prompt examples)
1638
- const PLACEHOLDERS = new Set([
1639
- 'abc123', 'def456', 'xyz789', 'event_123', 'event_123456789',
1640
- '123', '456', 'foo', 'bar', 'baz', 'example', 'placeholder',
1641
- 'eventid', 'event_id', 'id', 'null', 'undefined',
1642
- ]);
1643
- if (PLACEHOLDERS.has(eventId.toLowerCase())) {
1644
- return `"${eventId}" is a placeholder, not a real eventId. Call calendar_find or calendar_date FIRST to get real eventIds.`;
1672
+ // Reject obvious placeholder IDs (model copied from prompt examples or
1673
+ // emitted obvious patterns like "A1B2C3D4E5F6G7H8I9J0").
1674
+ if (isPlaceholderEventId(eventId)) {
1675
+ return `"${eventId}" looks invented (placeholder pattern). Call calendar_find or calendar_date FIRST to get real eventIds.`;
1645
1676
  }
1646
1677
 
1647
1678
  // Smart eventId resolution: if it looks like a name instead of a Google Calendar ID, search for it
@@ -1695,16 +1726,11 @@ export async function executeTool(action, params, config) {
1695
1726
  let delEventId = String(params.eventId).trim();
1696
1727
 
1697
1728
  // Reject placeholder IDs the model may have copied verbatim from prompt
1698
- // examples or hallucinated wholesale. These are obvious giveaways — real
1699
- // Google Calendar eventIds are long lowercase alphanumeric strings, not
1700
- // dictionary words.
1701
- const PLACEHOLDER_IDS = new Set([
1702
- 'abc123', 'def456', 'xyz789', 'event_123', 'event_123456789',
1703
- '123', '456', 'foo', 'bar', 'baz', 'example', 'placeholder',
1704
- 'eventid', 'event_id', 'id', 'null', 'undefined',
1705
- ]);
1706
- if (PLACEHOLDER_IDS.has(delEventId.toLowerCase())) {
1707
- return `"${delEventId}" is a placeholder, not a real eventId. Call calendar_date(YYYY-MM-DD) or calendar_find(query) FIRST to get the real eventIds, then call calendar_delete with one of those values.`;
1729
+ // examples or hallucinated wholesale. Real Google Calendar eventIds
1730
+ // are long LOWERCASE alphanumeric strings — never UPPERCASE patterns
1731
+ // like "A1B2C3D4E5F6G7H8I9J0" (visibly invented).
1732
+ if (isPlaceholderEventId(delEventId)) {
1733
+ return `"${delEventId}" looks invented (placeholder pattern). Real Google Calendar eventIds are long lowercase alphanumeric strings (a-v + 0-9, often with - and _). Call calendar_date(YYYY-MM-DD) or calendar_find(query) FIRST to get the real eventIds, then call calendar_delete with one of those.`;
1708
1734
  }
1709
1735
 
1710
1736
  // Branch A — looks like a free-text search query (spaces, caps, very short).