nothumanallowed 15.1.53 → 15.1.55

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.53",
3
+ "version": "15.1.55",
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.53';
8
+ export const VERSION = '15.1.55';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -1037,6 +1037,46 @@ class TelegramResponder {
1037
1037
 
1038
1038
  this.log(`[Telegram] ${fromUser}: continuation → ${agent.toUpperCase()} (ctx ${Math.round(stickyAge/1000)}s ago, history=${preHistory ? preHistory.length : 0})`);
1039
1039
  } else {
1040
+ // ── Direct fresh calendar action (no LLM, deterministic) ────────────
1041
+ // Catches DELETE / LIST requests sent as a fresh message (no prior
1042
+ // proposal). The model used to hallucinate "I cancelled it" without
1043
+ // touching the API, OR invent the event list when asked for "all
1044
+ // appointments of May". By running the calendar tool server-side, the
1045
+ // user always sees REAL data — never fabricated.
1046
+ const directFresh = await this._tryDirectFreshCalendarAction(cleanText, this.config);
1047
+ if (directFresh) {
1048
+ this.log(`[Telegram] ${fromUser}: direct-fresh ${directFresh.action} → ${directFresh.success ? 'OK' : 'FAIL'}`);
1049
+ const personaName = this.config.responder?.telegram?.botName || this.config.responder?.botName || '';
1050
+ const personaMode = this.config.responder?.telegram?.personaMode || (personaName ? 'persona' : 'agent');
1051
+ let reply;
1052
+ if (personaMode === 'persona-only' && personaName) {
1053
+ reply = directFresh.message;
1054
+ } else if (personaMode === 'persona+role' && personaName) {
1055
+ reply = `[${personaName} · herald]\n\n${directFresh.message}`;
1056
+ } else if (personaMode === 'persona' && personaName) {
1057
+ reply = `[${personaName}]\n\n${directFresh.message}`;
1058
+ } else {
1059
+ reply = `[HERALD]\n\n${directFresh.message}`;
1060
+ }
1061
+ await this._telegramCall('sendMessage', { chat_id: chatId, text: reply });
1062
+ const MAX = 20;
1063
+ const prevLog = (lastCtx && Array.isArray(lastCtx.conversationLog)) ? lastCtx.conversationLog : [];
1064
+ this._lastContextByChatId[chatId] = {
1065
+ agent: 'herald',
1066
+ userMsg: cleanText,
1067
+ agentReply: directFresh.message,
1068
+ history: null,
1069
+ conversationLog: [...prevLog,
1070
+ { role: 'user', content: cleanText, ts: Date.now() },
1071
+ { role: 'assistant', content: directFresh.message, ts: Date.now() },
1072
+ ].slice(-MAX * 2),
1073
+ ts: Date.now(),
1074
+ };
1075
+ this._lastAgentByChatId[chatId] = 'herald';
1076
+ this._persistContext();
1077
+ return;
1078
+ }
1079
+
1040
1080
  // Fresh request — route normally, but STILL pass the rolling log so
1041
1081
  // the model has the conversation context even when the new message
1042
1082
  // is on a fresh topic (e.g. user starts a new task but the previous
@@ -1321,6 +1361,156 @@ class TelegramResponder {
1321
1361
  return `${parseInt(m[3], 10)} ${months[parseInt(m[2], 10) - 1]} ${m[1]}`;
1322
1362
  }
1323
1363
 
1364
+ // ── Direct fresh calendar action (no LLM) ─────────────────────────────────
1365
+ // Detects DELETE / LIST_MONTH / LIST_WEEK / LIST_DAY / LIST_TODAY /
1366
+ // LIST_TOMORROW intents from a fresh user message and runs the proper tool
1367
+ // server-side. Returns { action, success, message } or null.
1368
+ //
1369
+ // Motivation: the model previously hallucinated "I cancelled it" with an
1370
+ // invented event ID, OR invented an entire list of fake appointments when
1371
+ // asked "give me May's appointments". Running the real tool removes the
1372
+ // hallucination surface entirely.
1373
+ async _tryDirectFreshCalendarAction(userMessage, config) {
1374
+ if (!userMessage || typeof userMessage !== 'string') return null;
1375
+ const lower = userMessage.toLowerCase().normalize('NFD').replace(/[̀-ͯ]/g, '');
1376
+
1377
+ const MONTHS_IT = { gennaio:1, febbraio:2, marzo:3, aprile:4, maggio:5, giugno:6, luglio:7, agosto:8, settembre:9, ottobre:10, novembre:11, dicembre:12 };
1378
+ const MONTHS_EN = { january:1, february:2, march:3, april:4, may:5, june:6, july:7, august:8, september:9, october:10, november:11, december:12, jan:1, feb:2, mar:3, apr:4, jun:6, jul:7, aug:8, sep:9, oct:10, nov:11, dec:12 };
1379
+ const MONTH_MAP = { ...MONTHS_IT, ...MONTHS_EN };
1380
+
1381
+ const isDelete = /\b(elimin|cancell|rimuov|cancel\b|delete\b|remove\b)\w*/.test(lower);
1382
+ const isList = /\b(lista|elenco|mostra|mostrami|fammi vedere|fai vedere|tutti|dammi|dimmi|cosa\s+ho|che\s+impegni|impegni|appuntamenti|eventi|meeting|agenda|calendario)\b/.test(lower);
1383
+
1384
+ const { executeTool } = await import('./tool-executor.mjs');
1385
+
1386
+ // ─── LIST intents ──────────────────────────────────────────────────────
1387
+ if (isList && !isDelete) {
1388
+ // "appuntamenti di oggi"
1389
+ if (/\b(oggi|today)\b/.test(lower)) {
1390
+ try {
1391
+ const out = await executeTool('calendar_today', {}, config);
1392
+ return { action: 'calendar_today', success: true, message: String(out) };
1393
+ } catch (e) { return { action: 'calendar_today', success: false, message: `Errore: ${e.message}` }; }
1394
+ }
1395
+ // "appuntamenti di domani"
1396
+ if (/\b(domani|tomorrow)\b/.test(lower)) {
1397
+ try {
1398
+ const out = await executeTool('calendar_tomorrow', {}, config);
1399
+ return { action: 'calendar_tomorrow', success: true, message: String(out) };
1400
+ } catch (e) { return { action: 'calendar_tomorrow', success: false, message: `Errore: ${e.message}` }; }
1401
+ }
1402
+ // "appuntamenti della settimana"
1403
+ if (/\b(settimana|week|questa\s+settimana|this\s+week)\b/.test(lower)) {
1404
+ try {
1405
+ const out = await executeTool('calendar_week', {}, config);
1406
+ return { action: 'calendar_week', success: true, message: String(out) };
1407
+ } catch (e) { return { action: 'calendar_week', success: false, message: `Errore: ${e.message}` }; }
1408
+ }
1409
+ // "appuntamenti di maggio" — month name + optional year
1410
+ const monthMatch = lower.match(new RegExp(`\\b(${Object.keys(MONTH_MAP).join('|')})(?:\\s+(20\\d{2}))?\\b`));
1411
+ if (monthMatch) {
1412
+ const monthNum = MONTH_MAP[monthMatch[1]];
1413
+ const yearNum = parseInt(monthMatch[2] || String(new Date().getFullYear()), 10);
1414
+ // calendar_month accepts a month string like "2026-05"
1415
+ const monthStr = `${yearNum}-${String(monthNum).padStart(2, '0')}`;
1416
+ try {
1417
+ const out = await executeTool('calendar_month', { month: monthStr }, config);
1418
+ return { action: 'calendar_month', success: true, message: String(out) };
1419
+ } catch (e) { return { action: 'calendar_month', success: false, message: `Errore: ${e.message}` }; }
1420
+ }
1421
+ // "appuntamenti del 15 maggio" — specific date
1422
+ const dateExtracted = this._extractCalendarProposal(userMessage);
1423
+ if (dateExtracted.date) {
1424
+ try {
1425
+ const out = await executeTool('calendar_date', { date: dateExtracted.date }, config);
1426
+ return { action: 'calendar_date', success: true, message: String(out) };
1427
+ } catch (e) { return { action: 'calendar_date', success: false, message: `Errore: ${e.message}` }; }
1428
+ }
1429
+ // Generic "appuntamenti prossimi" → upcoming 48h
1430
+ if (/\b(prossim|next|upcoming|in\s+arrivo)/.test(lower)) {
1431
+ try {
1432
+ const out = await executeTool('calendar_upcoming', { hours: 48 }, config);
1433
+ return { action: 'calendar_upcoming', success: true, message: String(out) };
1434
+ } catch (e) { return { action: 'calendar_upcoming', success: false, message: `Errore: ${e.message}` }; }
1435
+ }
1436
+ // Fall through — let the LLM handle ambiguous list requests.
1437
+ }
1438
+
1439
+ // ─── DELETE intents ────────────────────────────────────────────────────
1440
+ // Reuse the same proposal extractor — it works on raw user text too,
1441
+ // because it just looks for title quotes / dates / times.
1442
+ if (isDelete) {
1443
+ const extracted = this._extractCalendarProposal(userMessage);
1444
+ if (!extracted.date && !extracted.title) return null;
1445
+
1446
+ let candidates = [];
1447
+ try {
1448
+ if (extracted.date) {
1449
+ const result = await executeTool('calendar_date', { date: extracted.date }, config);
1450
+ candidates = this._parseEventsFromToolOutput(result);
1451
+ }
1452
+ if (candidates.length === 0 && extracted.title) {
1453
+ const result = await executeTool('calendar_find', { query: extracted.title, daysAhead: 60 }, config);
1454
+ candidates = this._parseEventsFromToolOutput(result);
1455
+ }
1456
+ } catch (err) {
1457
+ this.log(`[Telegram] direct-fresh delete lookup failed: ${err.message}`);
1458
+ return null;
1459
+ }
1460
+ if (candidates.length === 0) {
1461
+ // No event found → return a verifiable message instead of letting
1462
+ // the LLM make one up. The user sees the truth: nothing matched.
1463
+ const dateStr = extracted.date ? ` del ${this._formatDateIT(extracted.date)}` : '';
1464
+ const titleStr = extracted.title ? ` con titolo "${extracted.title}"` : '';
1465
+ return { action: 'calendar_delete', success: false, message: `Non ho trovato nessun appuntamento${titleStr}${dateStr}. Verifica la data o il titolo — non posso cancellare ciò che non esiste.` };
1466
+ }
1467
+
1468
+ // Token-match against extracted title; fall back to single-of-the-day.
1469
+ const norm = (s) => String(s || '').toLowerCase()
1470
+ .normalize('NFD').replace(/[̀-ͯ]/g, '')
1471
+ .replace(/[^a-z0-9\s]/g, ' ')
1472
+ .split(/\s+/).filter(t => t.length > 2);
1473
+ let match = null;
1474
+ if (extracted.title) {
1475
+ const titleTokens = norm(extracted.title);
1476
+ const scored = candidates.map(c => {
1477
+ const summaryTokens = new Set(norm(c.summary));
1478
+ const score = titleTokens.filter(t => summaryTokens.has(t)).length;
1479
+ return { c, score };
1480
+ }).sort((a, b) => b.score - a.score);
1481
+ const top = scored[0];
1482
+ if (top && top.score >= Math.max(1, Math.ceil(titleTokens.length * 0.5))) {
1483
+ if (scored.length === 1 || scored[1].score < top.score) match = top.c;
1484
+ }
1485
+ }
1486
+ if (!match && candidates.length === 1 && extracted.date) match = candidates[0];
1487
+ if (!match && extracted.time) {
1488
+ const exactTime = candidates.filter(c => (c.time || '').startsWith(extracted.time));
1489
+ if (exactTime.length === 1) match = exactTime[0];
1490
+ }
1491
+ if (!match || !match.eventId) {
1492
+ // Ambiguous → list options, don't guess.
1493
+ const opts = candidates.slice(0, 5).map((c, i) => `${i + 1}. ${c.time || '—'} ${c.summary}`).join('\n');
1494
+ return { action: 'calendar_delete', success: false, message: `Ho trovato più appuntamenti che corrispondono. Quale vuoi cancellare?\n\n${opts}\n\nRispondi con titolo + ora precisa.` };
1495
+ }
1496
+
1497
+ try {
1498
+ const delResult = await executeTool('calendar_delete', { eventId: match.eventId }, config);
1499
+ const ok = typeof delResult === 'string' && !/error|failed|could not|invalid|placeholder|does not exist/i.test(delResult);
1500
+ const summary = match.summary || extracted.title || 'l\'appuntamento';
1501
+ const dateStr = extracted.date || '';
1502
+ const message = ok
1503
+ ? `Fatto. Ho cancellato "${summary}"${dateStr ? ` del ${this._formatDateIT(dateStr)}` : ''}${match.time ? ` alle ${match.time}` : ''}.`
1504
+ : `Non sono riuscito a cancellare l'evento: ${delResult}`;
1505
+ return { action: 'calendar_delete', success: ok, message };
1506
+ } catch (e) {
1507
+ return { action: 'calendar_delete', success: false, message: `Errore nella cancellazione: ${e.message}` };
1508
+ }
1509
+ }
1510
+
1511
+ return null;
1512
+ }
1513
+
1324
1514
  async _telegramCall(method, body) {
1325
1515
  const res = await fetch(`https://api.telegram.org/bot${this.token}/${method}`, {
1326
1516
  method: 'POST',