nothumanallowed 15.1.54 → 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 +1 -1
- package/src/constants.mjs +1 -1
- package/src/services/message-responder.mjs +190 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nothumanallowed",
|
|
3
|
-
"version": "15.1.
|
|
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.
|
|
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',
|