nothumanallowed 16.0.20 → 16.0.22
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": "16.0.
|
|
3
|
+
"version": "16.0.22",
|
|
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 = '16.0.
|
|
8
|
+
export const VERSION = '16.0.22';
|
|
9
9
|
export const BASE_URL = 'https://nothumanallowed.com/cli';
|
|
10
10
|
export const API_BASE = 'https://nothumanallowed.com/api/v1';
|
|
11
11
|
|
|
@@ -321,12 +321,47 @@ function isContinuationMessage(text, lastCtx) {
|
|
|
321
321
|
function isCompletedAction(text) {
|
|
322
322
|
if (!text) return false;
|
|
323
323
|
const lower = text.toLowerCase();
|
|
324
|
+
// Specific high-confidence signals
|
|
324
325
|
const DONE_SIGNALS = ['cancellato con successo','eliminato con successo','evento eliminato',
|
|
325
326
|
'evento cancellato','deleted successfully','removed successfully','email inviata','email sent',
|
|
326
327
|
'draft created','bozza creata','aggiornato con successo','updated successfully',
|
|
327
328
|
'task completato','task done','creato con successo','created successfully',
|
|
328
329
|
'spostato con successo','moved successfully'];
|
|
329
|
-
|
|
330
|
+
if (DONE_SIGNALS.some(s => lower.includes(s))) return true;
|
|
331
|
+
// Broader patterns (v16.0.21 guardrail): "è stato X", "l'ho fatto", "ho cancellato".
|
|
332
|
+
// These catch HERALD-style narrations like "L'appuntamento è stato spostato al 19 maggio".
|
|
333
|
+
const BROAD = /\b(è\s+(stat[ao]|stat[ei])\s+(cancellat[ao]i?|eliminat[ao]i?|rimoss[ao]i?|spostat[ao]i?|modificat[ao]i?|aggiornat[ao]i?|creat[ao]i?|inviat[ao]i?|inoltrat[ao]i?|archiviat[ao]i?|completat[ao]i?|rinominat[ao]i?|condivis[ao]i?|segnat[ao]i?))/i;
|
|
334
|
+
if (BROAD.test(text)) return true;
|
|
335
|
+
const HO = /\b(ho\s+(cancellato|eliminato|rimosso|spostato|modificato|aggiornato|creato|inviato|inoltrato|archiviato|completato|rinominato|condiviso|segnato|fissato|prenotato|programmato|cambiato|risolto))/i;
|
|
336
|
+
if (HO.test(text)) return true;
|
|
337
|
+
const EN = /\b(i\s+(have|just)\s+(deleted|removed|moved|created|updated|sent|forwarded|archived|completed|renamed|shared|marked))/i;
|
|
338
|
+
if (EN.test(text)) return true;
|
|
339
|
+
return false;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Tool whitelist for "actually mutated state". If the agent claims a
|
|
343
|
+
// completed mutation but NONE of these were called, we treat it as fake.
|
|
344
|
+
const _MUTATION_TOOLS = new Set([
|
|
345
|
+
'calendar_create', 'calendar_update', 'calendar_delete', 'calendar_move',
|
|
346
|
+
'gmail_send', 'gmail_reply', 'gmail_forward', 'gmail_delete', 'gmail_archive',
|
|
347
|
+
'gmail_label', 'gmail_mark_read', 'gmail_mark_unread', 'gmail_draft',
|
|
348
|
+
'task_add', 'task_done', 'task_delete', 'task_edit',
|
|
349
|
+
'note_add', 'note_delete',
|
|
350
|
+
'reminder_create', 'reminder_cancel',
|
|
351
|
+
'contact_create', 'contact_update', 'contact_delete',
|
|
352
|
+
'drive_upload', 'drive_update', 'drive_delete', 'drive_rename', 'drive_move', 'drive_share',
|
|
353
|
+
'gtask_complete', 'gtask_update', 'gtask_delete',
|
|
354
|
+
'slack_send', 'notion_update', 'github_create_issue', 'github_close_issue',
|
|
355
|
+
'imap_send', 'imap_reply', 'imap_delete',
|
|
356
|
+
]);
|
|
357
|
+
function _toolResultLineIsMutation(line) {
|
|
358
|
+
if (typeof line !== 'string') return false;
|
|
359
|
+
const m = line.match(/^\[([\w_]+)\]\s+(.*)$/);
|
|
360
|
+
if (!m) return false;
|
|
361
|
+
const [, name, rest] = m;
|
|
362
|
+
if (!_MUTATION_TOOLS.has(name)) return false;
|
|
363
|
+
if (/Error:|^Error\b/i.test(rest)) return false; // failed → didn't actually mutate
|
|
364
|
+
return true;
|
|
330
365
|
}
|
|
331
366
|
|
|
332
367
|
async function callAgentWithTools(config, agentName, userMessage, languageOverride, preHistory, chatId) {
|
|
@@ -354,6 +389,9 @@ async function callAgentWithTools(config, agentName, userMessage, languageOverri
|
|
|
354
389
|
// preHistory: full conversation history from previous turn (for sticky confirmations)
|
|
355
390
|
const history = preHistory ? [...preHistory] : [];
|
|
356
391
|
let finalText = '';
|
|
392
|
+
// Track EVERY tool call across ALL rounds. The final post-response
|
|
393
|
+
// guardrail uses this to detect "claimed action without actual tool call".
|
|
394
|
+
const _allToolResults = [];
|
|
357
395
|
|
|
358
396
|
for (let round = 0; round < 5; round++) {
|
|
359
397
|
const parts = history.map(h => (h.role === 'user' ? '[User]' : '[Assistant]') + ' ' + h.content);
|
|
@@ -409,7 +447,9 @@ async function callAgentWithTools(config, agentName, userMessage, languageOverri
|
|
|
409
447
|
try {
|
|
410
448
|
const result = await _exec(action, params, config, chatId);
|
|
411
449
|
const resultStr = typeof result === 'string' ? result : JSON.stringify(result);
|
|
412
|
-
|
|
450
|
+
const line = `[${action}] ${resultStr}`;
|
|
451
|
+
toolResults.push(line);
|
|
452
|
+
_allToolResults.push(line);
|
|
413
453
|
} catch (err) {
|
|
414
454
|
// Detect Google/Microsoft OAuth token expiry — give user a clear fix instruction
|
|
415
455
|
const msg = err.message || '';
|
|
@@ -418,7 +458,9 @@ async function callAgentWithTools(config, agentName, userMessage, languageOverri
|
|
|
418
458
|
authError = action.startsWith('gmail') || action.startsWith('imap') || action.startsWith('calendar') || action.startsWith('contact') || action.startsWith('drive') || action.startsWith('gtask')
|
|
419
459
|
? 'google' : 'microsoft';
|
|
420
460
|
}
|
|
421
|
-
|
|
461
|
+
const errLine = `[${action}] Error: ${err.message}`;
|
|
462
|
+
toolResults.push(errLine);
|
|
463
|
+
_allToolResults.push(errLine);
|
|
422
464
|
}
|
|
423
465
|
}
|
|
424
466
|
|
|
@@ -451,6 +493,24 @@ async function callAgentWithTools(config, agentName, userMessage, languageOverri
|
|
|
451
493
|
`If an action was completed, say so clearly. REMEMBER: reply ONLY in ${language}.`;
|
|
452
494
|
}
|
|
453
495
|
|
|
496
|
+
// ── POST-RESPONSE ANTI-HALLUCINATION GUARDRAIL (v16.0.21) ─────────────
|
|
497
|
+
// If the agent claims a completed mutation ("ho cancellato", "è stato
|
|
498
|
+
// spostato", "I have deleted") BUT no mutation tool was actually called
|
|
499
|
+
// across any of the 5 rounds — REPLACE the lie with an honest error.
|
|
500
|
+
// The user sees the truth: "Non sono riuscito a eseguire l'azione".
|
|
501
|
+
if (finalText) {
|
|
502
|
+
const claimsAction = isCompletedAction(finalText);
|
|
503
|
+
if (claimsAction) {
|
|
504
|
+
const didMutate = _allToolResults.some(_toolResultLineIsMutation);
|
|
505
|
+
if (!didMutate) {
|
|
506
|
+
try { console.warn(`[GUARDRAIL] Mutation claim without tool call. Tools used: [${_allToolResults.map(l => l.match(/^\[([\w_]+)\]/)?.[1]).filter(Boolean).join(', ')}]. Replacing fake response: "${finalText.slice(0, 160)}"`); } catch {}
|
|
507
|
+
finalText = language === 'Italian'
|
|
508
|
+
? `⚠️ Attenzione: avevo dichiarato di aver eseguito un'azione, ma in realtà non ho chiamato nessun tool di modifica. NON è stato fatto nulla.\n\nPer favore ripeti la richiesta in modo specifico — es. "sposta l'appuntamento Tagliando macchina al 19 maggio alle 17:30" — così che io possa eseguire il comando esatto.`
|
|
509
|
+
: `⚠️ Warning: I claimed an action was completed but did not actually call any modification tool. NOTHING was changed.\n\nPlease restate your request precisely — e.g. "move the Car Service appointment to May 19 at 17:30" — so I can run the exact tool call.`;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
454
514
|
// Defensive language post-check: small models sometimes drop back to English
|
|
455
515
|
// even when instructed otherwise (especially after tool execution, where the
|
|
456
516
|
// English tool-result text biases the continuation). If the final reply is
|
|
@@ -692,7 +752,9 @@ class TelegramResponder {
|
|
|
692
752
|
async start() {
|
|
693
753
|
if (!this.enabled) return;
|
|
694
754
|
this.running = true;
|
|
695
|
-
|
|
755
|
+
// Explicit version log at boot so the user can verify what's running.
|
|
756
|
+
// Critical when chasing "bot still mentes" — answers "are you on v16.0.21?".
|
|
757
|
+
this.log(`[Telegram] Responder started — VERSION ${VERSION} — polling for messages`);
|
|
696
758
|
this._pollLoop();
|
|
697
759
|
// Check for npm updates after 60s, then every 24h
|
|
698
760
|
this._updateCheckTimer = setTimeout(() => this._scheduleUpdateCheck(), 60 * 1000);
|
|
@@ -714,8 +776,41 @@ class TelegramResponder {
|
|
|
714
776
|
|
|
715
777
|
async _scheduleUpdateCheck() {
|
|
716
778
|
await this._checkAndNotifyUpdate();
|
|
717
|
-
|
|
779
|
+
await this._checkLocalUpdateAndRestart();
|
|
780
|
+
// Then every 24h for the npm registry check + every 5 min for local install check
|
|
718
781
|
this._updateCheckTimer = setInterval(() => this._checkAndNotifyUpdate(), 24 * 60 * 60 * 1000);
|
|
782
|
+
setInterval(() => this._checkLocalUpdateAndRestart(), 5 * 60 * 1000);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
/**
|
|
786
|
+
* Detect that a NEW version of nha-cli has been installed on disk while
|
|
787
|
+
* this process is still running the OLD code in memory. When detected,
|
|
788
|
+
* exit cleanly so PM2 / launchd / the dispatcher respawns us on the
|
|
789
|
+
* latest code. Without this, the user runs `npm i -g nothumanallowed@latest`
|
|
790
|
+
* but the Telegram bot keeps mentes-ing with the old logic forever.
|
|
791
|
+
*/
|
|
792
|
+
async _checkLocalUpdateAndRestart() {
|
|
793
|
+
try {
|
|
794
|
+
const fileURL = await import('url');
|
|
795
|
+
const here = fileURL.fileURLToPath(import.meta.url);
|
|
796
|
+
// ../../package.json relative to this file (services/message-responder.mjs)
|
|
797
|
+
const pkgPath = path.join(path.dirname(here), '..', '..', 'package.json');
|
|
798
|
+
if (!fs.existsSync(pkgPath)) return;
|
|
799
|
+
const onDisk = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')).version;
|
|
800
|
+
if (!onDisk || onDisk === VERSION) return;
|
|
801
|
+
this.log(`[Telegram] Detected new install: running v${VERSION}, on-disk v${onDisk}. Restarting to pick up new code…`);
|
|
802
|
+
// Notify any active chat that we're restarting (best-effort, fire and forget)
|
|
803
|
+
try {
|
|
804
|
+
const chatIds = getAllTelegramChatIds();
|
|
805
|
+
for (const chatId of chatIds.slice(0, 3)) {
|
|
806
|
+
this._telegramCall('sendMessage', { chat_id: parseInt(chatId, 10), text: `🔄 Aggiornamento NHA v${VERSION} → v${onDisk} in corso. Torno tra 2 secondi.` }).catch(() => {});
|
|
807
|
+
}
|
|
808
|
+
} catch {}
|
|
809
|
+
// Give the message a moment to flush, then exit. PM2 / dispatcher restarts us.
|
|
810
|
+
setTimeout(() => process.exit(0), 800);
|
|
811
|
+
} catch (e) {
|
|
812
|
+
this.log(`[Telegram] Local update check failed: ${e.message}`);
|
|
813
|
+
}
|
|
719
814
|
}
|
|
720
815
|
|
|
721
816
|
async _checkAndNotifyUpdate() {
|
|
@@ -2018,11 +2018,45 @@ export async function executeTool(action, params, config) {
|
|
|
2018
2018
|
|
|
2019
2019
|
case 'calendar_move': {
|
|
2020
2020
|
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2021
|
+
// ── Resolve eventId from optional query/oldDate (v16.0.22) ──
|
|
2022
|
+
// Agent models often pass {query: 'BMW', newStart} without an eventId.
|
|
2023
|
+
// Instead of failing, resolve it here via listEvents so the move
|
|
2024
|
+
// actually happens. Returns honest error if multiple/no matches.
|
|
2025
|
+
let eventId = params.eventId;
|
|
2026
|
+
if (!eventId && (params.query || params.title || params.oldDate)) {
|
|
2027
|
+
const range = (() => {
|
|
2028
|
+
if (params.oldDate && /^\d{4}-\d{2}-\d{2}$/.test(params.oldDate)) {
|
|
2029
|
+
const [yy, mm, dd] = params.oldDate.split('-').map(n => parseInt(n, 10));
|
|
2030
|
+
const from = new Date(yy, mm - 1, dd);
|
|
2031
|
+
return { from, to: new Date(from.getTime() + 86400000) };
|
|
2032
|
+
}
|
|
2033
|
+
const from = new Date();
|
|
2034
|
+
return { from, to: new Date(from.getTime() + 90 * 86400000) };
|
|
2035
|
+
})();
|
|
2036
|
+
const candidates = await listEvents(config, 'primary', range.from, range.to);
|
|
2037
|
+
const q = String(params.query || params.title || '').toLowerCase();
|
|
2038
|
+
const matching = q
|
|
2039
|
+
? candidates.filter(e => String(e.summary || '').toLowerCase().includes(q))
|
|
2040
|
+
: candidates;
|
|
2041
|
+
if (matching.length === 0) {
|
|
2042
|
+
return `Error: no event found matching "${q || params.oldDate}" — calendar_move did NOT execute.`;
|
|
2043
|
+
}
|
|
2044
|
+
if (matching.length > 1) {
|
|
2045
|
+
const list = matching.slice(0, 5).map((e, i) =>
|
|
2046
|
+
`${i + 1}. ${(e.start || '').slice(0, 10)} ${e.summary}`).join('\n');
|
|
2047
|
+
return `Error: multiple events match "${q}" — be more specific. Candidates:\n${list}`;
|
|
2048
|
+
}
|
|
2049
|
+
eventId = matching[0].id;
|
|
2050
|
+
}
|
|
2051
|
+
if (!eventId) return 'Error: eventId or query/title required.';
|
|
2052
|
+
if (!params.newStart) return 'Error: newStart required.';
|
|
2053
|
+
const newStartISO = new Date(params.newStart).toISOString();
|
|
2054
|
+
const newEndISO = new Date(params.newEnd || (new Date(params.newStart).getTime() + 3600000)).toISOString();
|
|
2055
|
+
await updateEvent(config, 'primary', eventId, {
|
|
2056
|
+
start: { dateTime: newStartISO, timeZone: tz },
|
|
2057
|
+
end: { dateTime: newEndISO, timeZone: tz },
|
|
2024
2058
|
});
|
|
2025
|
-
return `Event rescheduled to ${formatTime(
|
|
2059
|
+
return `Event rescheduled to ${formatTime(newStartISO)} - ${formatTime(newEndISO)}.`;
|
|
2026
2060
|
}
|
|
2027
2061
|
|
|
2028
2062
|
case 'calendar_week': {
|