nothumanallowed 15.1.61 → 15.1.63
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.
|
|
3
|
+
"version": "15.1.63",
|
|
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.63';
|
|
9
9
|
export const BASE_URL = 'https://nothumanallowed.com/cli';
|
|
10
10
|
export const API_BASE = 'https://nothumanallowed.com/api/v1';
|
|
11
11
|
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { callAgent, callLLM } from './llm.mjs';
|
|
12
|
-
import { buildSystemPrompt, parseActions, executeTool, TOOL_DEFINITIONS, LIARA_TOOL_DEFINITIONS } from './tool-executor.mjs';
|
|
12
|
+
import { buildSystemPrompt, parseActions, executeTool, TOOL_DEFINITIONS, LIARA_TOOL_DEFINITIONS, DESTRUCTIVE_ACTIONS } from './tool-executor.mjs';
|
|
13
13
|
import https from 'https';
|
|
14
14
|
import http from 'http';
|
|
15
15
|
import { URL } from 'url';
|
|
@@ -1045,7 +1045,19 @@ class TelegramResponder {
|
|
|
1045
1045
|
// appointments of May". By running the calendar tool server-side, the
|
|
1046
1046
|
// user always sees REAL data — never fabricated.
|
|
1047
1047
|
this._lastDirectAuditChatId = chatId;
|
|
1048
|
-
|
|
1048
|
+
// Run the per-domain direct-action dispatcher. First match wins; falls
|
|
1049
|
+
// through to LLM if no handler claims the message.
|
|
1050
|
+
// Fast-path specialised handlers (regex-driven, lower latency for the
|
|
1051
|
+
// common cases), then the universal dispatcher that covers ALL 50+
|
|
1052
|
+
// mutation tools via a single LLM-NLU+deterministic-execute pass.
|
|
1053
|
+
const directFresh =
|
|
1054
|
+
await this._tryDirectFreshCalendarAction(cleanText, this.config) ||
|
|
1055
|
+
await this._tryDirectFreshEmailAction(cleanText, this.config) ||
|
|
1056
|
+
await this._tryDirectFreshTaskAction(cleanText, this.config) ||
|
|
1057
|
+
await this._tryDirectFreshNoteAction(cleanText, this.config) ||
|
|
1058
|
+
await this._tryDirectFreshReminderAction(cleanText, this.config) ||
|
|
1059
|
+
await this._tryDirectFreshSlackAction(cleanText, this.config) ||
|
|
1060
|
+
await this._tryDirectFreshUniversalAction(cleanText, this.config);
|
|
1049
1061
|
if (directFresh) {
|
|
1050
1062
|
this.log(`[Telegram] ${fromUser}: direct-fresh ${directFresh.action} → ${directFresh.success ? 'OK' : 'FAIL'}`);
|
|
1051
1063
|
const personaName = this.config.responder?.telegram?.botName || this.config.responder?.botName || '';
|
|
@@ -1407,6 +1419,429 @@ class TelegramResponder {
|
|
|
1407
1419
|
return `${parseInt(m[3], 10)} ${months[parseInt(m[2], 10) - 1]} ${m[1]}`;
|
|
1408
1420
|
}
|
|
1409
1421
|
|
|
1422
|
+
/** Add N minutes to an ISO datetime string. Returns ISO. */
|
|
1423
|
+
_addMinutesIso(isoStart, minutes) {
|
|
1424
|
+
try {
|
|
1425
|
+
const d = new Date(isoStart);
|
|
1426
|
+
d.setMinutes(d.getMinutes() + minutes);
|
|
1427
|
+
return d.toISOString();
|
|
1428
|
+
} catch { return isoStart; }
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
/**
|
|
1432
|
+
* NLU only — extract structured calendar-create params from a natural
|
|
1433
|
+
* language message using a tiny LLM call. We pin output to strict JSON and
|
|
1434
|
+
* accept the result only if the title + start datetime are present.
|
|
1435
|
+
* The LLM NEVER executes anything — it only parses.
|
|
1436
|
+
*/
|
|
1437
|
+
async _nluExtractCalendarCreate(userMessage, config) {
|
|
1438
|
+
const todayIso = new Date().toISOString().slice(0, 10);
|
|
1439
|
+
const sysPrompt =
|
|
1440
|
+
`You extract calendar event parameters from natural language.\n` +
|
|
1441
|
+
`Today is ${todayIso}. Output ONLY a JSON object with these keys:\n` +
|
|
1442
|
+
` - title (string, the event subject, NOT including verbs like "fissa"/"crea")\n` +
|
|
1443
|
+
` - start (string, ISO datetime "YYYY-MM-DDTHH:MM:00" in local time)\n` +
|
|
1444
|
+
` - end (string, ISO datetime same format; if duration unknown, leave null and default 60 min will be applied)\n` +
|
|
1445
|
+
` - description (string, optional notes)\n` +
|
|
1446
|
+
`Rules: if a field is missing, use null. If the user says "domani" → ${this._addDaysIso(todayIso, 1).slice(0, 10)}. ` +
|
|
1447
|
+
`If "lunedì/martedì/..." resolve to the next occurrence. If no time is given default to 09:00. ` +
|
|
1448
|
+
`Output ONLY the JSON, no prose.`;
|
|
1449
|
+
try {
|
|
1450
|
+
const raw = await callLLM(config, sysPrompt, userMessage, { temperature: 0, maxTokens: 200 });
|
|
1451
|
+
const json = this._extractJsonObject(raw);
|
|
1452
|
+
if (!json) return null;
|
|
1453
|
+
if (!json.title || !json.start) return null;
|
|
1454
|
+
// Sanity: start must look like an ISO datetime.
|
|
1455
|
+
if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/.test(json.start)) return null;
|
|
1456
|
+
return json;
|
|
1457
|
+
} catch { return null; }
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
async _nluExtractCalendarMove(userMessage, config) {
|
|
1461
|
+
const todayIso = new Date().toISOString().slice(0, 10);
|
|
1462
|
+
const sysPrompt =
|
|
1463
|
+
`You extract reschedule-event parameters from natural language.\n` +
|
|
1464
|
+
`Today is ${todayIso}. Output ONLY a JSON object:\n` +
|
|
1465
|
+
` - title (string, the event to find and move)\n` +
|
|
1466
|
+
` - oldDate (string, original date if mentioned, format "YYYY-MM-DD"; else null)\n` +
|
|
1467
|
+
` - newStart (string, NEW datetime "YYYY-MM-DDTHH:MM:00")\n` +
|
|
1468
|
+
` - newEnd (string, optional, same format)\n` +
|
|
1469
|
+
`If "domani" → ${this._addDaysIso(todayIso, 1).slice(0, 10)}. Output JSON only.`;
|
|
1470
|
+
try {
|
|
1471
|
+
const raw = await callLLM(config, sysPrompt, userMessage, { temperature: 0, maxTokens: 200 });
|
|
1472
|
+
const json = this._extractJsonObject(raw);
|
|
1473
|
+
if (!json) return null;
|
|
1474
|
+
if (!json.newStart) return null;
|
|
1475
|
+
if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/.test(json.newStart)) return null;
|
|
1476
|
+
return json;
|
|
1477
|
+
} catch { return null; }
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
/** Robust JSON object extractor — strips fences, finds the first balanced {...}. */
|
|
1481
|
+
_extractJsonObject(text) {
|
|
1482
|
+
if (!text) return null;
|
|
1483
|
+
const cleaned = String(text).replace(/```(?:json)?\s*/gi, '').replace(/```/g, '').trim();
|
|
1484
|
+
const start = cleaned.indexOf('{');
|
|
1485
|
+
if (start < 0) return null;
|
|
1486
|
+
let depth = 0, end = -1, inStr = false, esc = false;
|
|
1487
|
+
for (let i = start; i < cleaned.length; i++) {
|
|
1488
|
+
const c = cleaned[i];
|
|
1489
|
+
if (esc) { esc = false; continue; }
|
|
1490
|
+
if (c === '\\' && inStr) { esc = true; continue; }
|
|
1491
|
+
if (c === '"' && !esc) inStr = !inStr;
|
|
1492
|
+
if (inStr) continue;
|
|
1493
|
+
if (c === '{') depth++;
|
|
1494
|
+
else if (c === '}') { depth--; if (depth === 0) { end = i; break; } }
|
|
1495
|
+
}
|
|
1496
|
+
if (end < 0) return null;
|
|
1497
|
+
try { return JSON.parse(cleaned.slice(start, end + 1)); } catch { return null; }
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
_addDaysIso(isoDate, days) {
|
|
1501
|
+
const d = new Date(isoDate + 'T00:00:00');
|
|
1502
|
+
d.setDate(d.getDate() + days);
|
|
1503
|
+
return d.toISOString();
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
// ── Direct fresh EMAIL action (LLM only for NLU, executor server-side) ──
|
|
1507
|
+
// Detects "manda email a X riguardo Y" style requests, extracts recipient
|
|
1508
|
+
// + subject + body via a tiny LLM call, then sends via gmail_send tool.
|
|
1509
|
+
async _tryDirectFreshEmailAction(userMessage, config) {
|
|
1510
|
+
if (!userMessage || typeof userMessage !== 'string') return null;
|
|
1511
|
+
const lower = userMessage.toLowerCase();
|
|
1512
|
+
const isEmailSend = /\b(manda|mand[oa]|invi[oa]|spedis(ci|co)|scriv[io]|send|email\s+to|mail\s+to)\s+(?:un'?\s+)?(email|mail|messaggio|messag|e-?mail)\s+/i.test(lower)
|
|
1513
|
+
|| /\b(invi[oa]r[ea]|mand[ao]|spedire)\s+un'?\s*e[- ]?mail\b/i.test(lower);
|
|
1514
|
+
if (!isEmailSend) return null;
|
|
1515
|
+
|
|
1516
|
+
const parsed = await this._nluExtractEmailSend(userMessage, config);
|
|
1517
|
+
if (!parsed || !parsed.to || !parsed.body) return null;
|
|
1518
|
+
|
|
1519
|
+
const { executeTool } = await import('./tool-executor.mjs');
|
|
1520
|
+
try {
|
|
1521
|
+
const result = await executeTool('gmail_send', {
|
|
1522
|
+
to: parsed.to,
|
|
1523
|
+
subject: parsed.subject || '(no subject)',
|
|
1524
|
+
body: parsed.body,
|
|
1525
|
+
}, config);
|
|
1526
|
+
const ok = typeof result === 'string' && /sent|inviat|✅|message-?id/i.test(result);
|
|
1527
|
+
const message = ok
|
|
1528
|
+
? `Fatto. Email inviata a ${parsed.to}${parsed.subject ? ` con oggetto "${parsed.subject}"` : ''}.`
|
|
1529
|
+
: `Non sono riuscito a inviare l'email: ${result}`;
|
|
1530
|
+
if (this._lastDirectAuditChatId) {
|
|
1531
|
+
this._recordAudit(this._lastDirectAuditChatId, {
|
|
1532
|
+
tool: 'gmail_send',
|
|
1533
|
+
success: ok,
|
|
1534
|
+
summary: `→ ${parsed.to}${parsed.subject ? ` · "${parsed.subject}"` : ''}`,
|
|
1535
|
+
});
|
|
1536
|
+
}
|
|
1537
|
+
return { action: 'gmail_send', success: ok, message };
|
|
1538
|
+
} catch (e) {
|
|
1539
|
+
return { action: 'gmail_send', success: false, message: `Errore nell'invio: ${e.message}` };
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
async _nluExtractEmailSend(userMessage, config) {
|
|
1544
|
+
const sysPrompt =
|
|
1545
|
+
`You extract email-send parameters from natural language. Output ONLY a JSON object:\n` +
|
|
1546
|
+
` - to (string, recipient email or name if email unknown)\n` +
|
|
1547
|
+
` - subject (string, the email subject; if missing, infer a 2-6 word summary)\n` +
|
|
1548
|
+
` - body (string, the email body in the same language as the user)\n` +
|
|
1549
|
+
`Rules: if any field is genuinely missing AND impossible to infer, use null. ` +
|
|
1550
|
+
`Keep body short and natural. Output JSON only, no markdown.`;
|
|
1551
|
+
try {
|
|
1552
|
+
const raw = await callLLM(config, sysPrompt, userMessage, { temperature: 0, maxTokens: 400 });
|
|
1553
|
+
const json = this._extractJsonObject(raw);
|
|
1554
|
+
if (!json || !json.to || !json.body) return null;
|
|
1555
|
+
return json;
|
|
1556
|
+
} catch { return null; }
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
// ── Direct fresh TASK action (add / complete) ──────────────────────────
|
|
1560
|
+
async _tryDirectFreshTaskAction(userMessage, config) {
|
|
1561
|
+
if (!userMessage || typeof userMessage !== 'string') return null;
|
|
1562
|
+
const lower = userMessage.toLowerCase();
|
|
1563
|
+
const isTaskAdd = /\b(aggiung|cre[oai]|fiss|segn|metti|add|create)\w*\s+.*\b(task|attivit[àa]|cosa\s+da\s+fare|todo|to[- ]?do|promemoria\s+da\s+fare)/i.test(lower);
|
|
1564
|
+
const isTaskDone = /\b(complet|fatt[oa]|done|fini[stct]|chiud|spunt|tick|mark\s+as\s+done)\w*\s+.*\b(task|attivit[àa]|todo|to[- ]?do)/i.test(lower);
|
|
1565
|
+
const { executeTool } = await import('./tool-executor.mjs');
|
|
1566
|
+
if (isTaskAdd) {
|
|
1567
|
+
const parsed = await this._nluExtractTaskAdd(userMessage, config);
|
|
1568
|
+
if (!parsed?.title) return null;
|
|
1569
|
+
try {
|
|
1570
|
+
const r = await executeTool('task_add', { title: parsed.title, due: parsed.due || null, priority: parsed.priority || 'medium' }, config);
|
|
1571
|
+
const ok = typeof r === 'string' && !/error/i.test(r);
|
|
1572
|
+
if (this._lastDirectAuditChatId) this._recordAudit(this._lastDirectAuditChatId, { tool: 'task_add', success: ok, summary: `"${parsed.title}"${parsed.due ? ` (entro ${parsed.due})` : ''}` });
|
|
1573
|
+
return { action: 'task_add', success: ok, message: ok ? `Fatto. Ho aggiunto il task "${parsed.title}"${parsed.due ? ` con scadenza ${parsed.due}` : ''}.` : `Errore: ${r}` };
|
|
1574
|
+
} catch (e) { return { action: 'task_add', success: false, message: `Errore: ${e.message}` }; }
|
|
1575
|
+
}
|
|
1576
|
+
if (isTaskDone) {
|
|
1577
|
+
const parsed = await this._nluExtractTaskRef(userMessage, config);
|
|
1578
|
+
if (!parsed?.title) return null;
|
|
1579
|
+
try {
|
|
1580
|
+
const r = await executeTool('task_complete', { title: parsed.title }, config);
|
|
1581
|
+
const ok = typeof r === 'string' && !/error|not found/i.test(r);
|
|
1582
|
+
if (this._lastDirectAuditChatId) this._recordAudit(this._lastDirectAuditChatId, { tool: 'task_complete', success: ok, summary: `"${parsed.title}"` });
|
|
1583
|
+
return { action: 'task_complete', success: ok, message: ok ? `Fatto. Task "${parsed.title}" segnato come completato.` : `Errore: ${r}` };
|
|
1584
|
+
} catch (e) { return { action: 'task_complete', success: false, message: `Errore: ${e.message}` }; }
|
|
1585
|
+
}
|
|
1586
|
+
return null;
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
// ── Direct fresh NOTE add ───────────────────────────────────────────────
|
|
1590
|
+
async _tryDirectFreshNoteAction(userMessage, config) {
|
|
1591
|
+
if (!userMessage || typeof userMessage !== 'string') return null;
|
|
1592
|
+
const lower = userMessage.toLowerCase();
|
|
1593
|
+
const isNoteAdd = /\b(crea|nuov[oa]|aggiung|salv|prend|scriv|appunt|new|add|save)\w*\s+(?:una\s+)?\b(nota|note|appunt)/i.test(lower);
|
|
1594
|
+
if (!isNoteAdd) return null;
|
|
1595
|
+
const parsed = await this._nluExtractNoteAdd(userMessage, config);
|
|
1596
|
+
if (!parsed?.title) return null;
|
|
1597
|
+
const { executeTool } = await import('./tool-executor.mjs');
|
|
1598
|
+
try {
|
|
1599
|
+
const r = await executeTool('note_add', { title: parsed.title, content: parsed.content || '' }, config);
|
|
1600
|
+
const ok = typeof r === 'string' && !/error/i.test(r);
|
|
1601
|
+
if (this._lastDirectAuditChatId) this._recordAudit(this._lastDirectAuditChatId, { tool: 'note_add', success: ok, summary: `"${parsed.title}"` });
|
|
1602
|
+
return { action: 'note_add', success: ok, message: ok ? `Fatto. Nota "${parsed.title}" salvata.` : `Errore: ${r}` };
|
|
1603
|
+
} catch (e) { return { action: 'note_add', success: false, message: `Errore: ${e.message}` }; }
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
// ── Direct fresh REMINDER create ────────────────────────────────────────
|
|
1607
|
+
async _tryDirectFreshReminderAction(userMessage, config) {
|
|
1608
|
+
if (!userMessage || typeof userMessage !== 'string') return null;
|
|
1609
|
+
const lower = userMessage.toLowerCase();
|
|
1610
|
+
const isReminder = /\b(ricordami|reminder|promemoria|ricord[aoo]|notificami|avvisami|remind)\b/i.test(lower);
|
|
1611
|
+
if (!isReminder) return null;
|
|
1612
|
+
const parsed = await this._nluExtractReminder(userMessage, config);
|
|
1613
|
+
if (!parsed?.message || !parsed?.when) return null;
|
|
1614
|
+
const { executeTool } = await import('./tool-executor.mjs');
|
|
1615
|
+
try {
|
|
1616
|
+
const r = await executeTool('reminder_create', { message: parsed.message, when: parsed.when }, config);
|
|
1617
|
+
const ok = typeof r === 'string' && !/error/i.test(r);
|
|
1618
|
+
if (this._lastDirectAuditChatId) this._recordAudit(this._lastDirectAuditChatId, { tool: 'reminder_create', success: ok, summary: `"${parsed.message}" @ ${parsed.when}` });
|
|
1619
|
+
return { action: 'reminder_create', success: ok, message: ok ? `Fatto. Promemoria impostato: "${parsed.message}" per ${parsed.when}.` : `Errore: ${r}` };
|
|
1620
|
+
} catch (e) { return { action: 'reminder_create', success: false, message: `Errore: ${e.message}` }; }
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
// ── Direct fresh SLACK send ─────────────────────────────────────────────
|
|
1624
|
+
async _tryDirectFreshSlackAction(userMessage, config) {
|
|
1625
|
+
if (!userMessage || typeof userMessage !== 'string') return null;
|
|
1626
|
+
const lower = userMessage.toLowerCase();
|
|
1627
|
+
const isSlackSend = /\b(manda|invi[ao]|posta|scriv[io]|send|post)\s+.*\b(slack|canale|channel|#)/i.test(lower)
|
|
1628
|
+
|| /\bsu\s+slack\b/i.test(lower);
|
|
1629
|
+
if (!isSlackSend) return null;
|
|
1630
|
+
const parsed = await this._nluExtractSlackSend(userMessage, config);
|
|
1631
|
+
if (!parsed?.channel || !parsed?.text) return null;
|
|
1632
|
+
const { executeTool } = await import('./tool-executor.mjs');
|
|
1633
|
+
try {
|
|
1634
|
+
const r = await executeTool('slack_send', { channel: parsed.channel, text: parsed.text }, config);
|
|
1635
|
+
const ok = typeof r === 'string' && !/error|not found/i.test(r);
|
|
1636
|
+
if (this._lastDirectAuditChatId) this._recordAudit(this._lastDirectAuditChatId, { tool: 'slack_send', success: ok, summary: `→ ${parsed.channel}: "${parsed.text.slice(0, 60)}"` });
|
|
1637
|
+
return { action: 'slack_send', success: ok, message: ok ? `Fatto. Messaggio inviato a ${parsed.channel}.` : `Errore: ${r}` };
|
|
1638
|
+
} catch (e) { return { action: 'slack_send', success: false, message: `Errore: ${e.message}` }; }
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
// ── NLU extractors (LLM-driven JSON parsing only, never tool execution) ──
|
|
1642
|
+
async _nluExtractTaskAdd(userMessage, config) {
|
|
1643
|
+
const todayIso = new Date().toISOString().slice(0, 10);
|
|
1644
|
+
const sys = `Today is ${todayIso}. Extract task params from the message. Output ONLY JSON:\n` +
|
|
1645
|
+
` - title (string, the task description)\n` +
|
|
1646
|
+
` - due (string, "YYYY-MM-DD" if mentioned, else null)\n` +
|
|
1647
|
+
` - priority ("low" | "medium" | "high" | null)`;
|
|
1648
|
+
try { return this._extractJsonObject(await callLLM(config, sys, userMessage, { temperature: 0, maxTokens: 200 })); } catch { return null; }
|
|
1649
|
+
}
|
|
1650
|
+
async _nluExtractTaskRef(userMessage, config) {
|
|
1651
|
+
const sys = `Extract which task to mark completed. Output ONLY JSON:\n - title (string)`;
|
|
1652
|
+
try { return this._extractJsonObject(await callLLM(config, sys, userMessage, { temperature: 0, maxTokens: 150 })); } catch { return null; }
|
|
1653
|
+
}
|
|
1654
|
+
async _nluExtractNoteAdd(userMessage, config) {
|
|
1655
|
+
const sys = `Extract a new-note request. Output ONLY JSON:\n - title (string)\n - content (string, can be empty)`;
|
|
1656
|
+
try { return this._extractJsonObject(await callLLM(config, sys, userMessage, { temperature: 0, maxTokens: 400 })); } catch { return null; }
|
|
1657
|
+
}
|
|
1658
|
+
async _nluExtractReminder(userMessage, config) {
|
|
1659
|
+
const todayIso = new Date().toISOString().slice(0, 10);
|
|
1660
|
+
const sys = `Today is ${todayIso}. Extract reminder params. Output ONLY JSON:\n` +
|
|
1661
|
+
` - message (string, what to remind)\n - when (string, ISO datetime "YYYY-MM-DDTHH:MM:00")`;
|
|
1662
|
+
try { return this._extractJsonObject(await callLLM(config, sys, userMessage, { temperature: 0, maxTokens: 200 })); } catch { return null; }
|
|
1663
|
+
}
|
|
1664
|
+
async _nluExtractSlackSend(userMessage, config) {
|
|
1665
|
+
const sys = `Extract Slack-send params. Output ONLY JSON:\n` +
|
|
1666
|
+
` - channel (string, with "#" prefix for channels or "@user" for DMs)\n - text (string, message body)`;
|
|
1667
|
+
try { return this._extractJsonObject(await callLLM(config, sys, userMessage, { temperature: 0, maxTokens: 300 })); } catch { return null; }
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
1671
|
+
// UNIVERSAL DIRECT-ACTION DISPATCHER
|
|
1672
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
1673
|
+
// Covers ALL 22 mutation tools in `DESTRUCTIVE_ACTIONS` (gmail, imap,
|
|
1674
|
+
// calendar, contacts, tasks, slack, github, file, drive, notify).
|
|
1675
|
+
// The LLM is used ONLY to (a) decide if the message maps to a tool and
|
|
1676
|
+
// (b) extract the params as JSON. Tool execution is then deterministic
|
|
1677
|
+
// server-side. No tool block is parsed from natural language by the
|
|
1678
|
+
// model — the model can never "say done" without us actually doing it.
|
|
1679
|
+
async _tryDirectFreshUniversalAction(userMessage, config) {
|
|
1680
|
+
if (!userMessage || typeof userMessage !== 'string' || userMessage.length < 3) return null;
|
|
1681
|
+
|
|
1682
|
+
const todayIso = new Date().toISOString().slice(0, 10);
|
|
1683
|
+
const sys =
|
|
1684
|
+
`You are a tool-routing classifier. Given a user message in any language, decide whether it ` +
|
|
1685
|
+
`requests a state-changing action that maps to ONE of these tools, and extract the params.\n\n` +
|
|
1686
|
+
`ALLOWED TOOLS (you MUST pick one of these OR return null):\n` +
|
|
1687
|
+
// Calendar
|
|
1688
|
+
`- calendar_create(summary, start, end, description?) — start/end ISO "YYYY-MM-DDTHH:MM:00"\n` +
|
|
1689
|
+
`- calendar_move(eventId? OR title, newStart, newEnd?) — if no eventId, title is used to find it\n` +
|
|
1690
|
+
`- calendar_update(eventId? OR title, summary?, start?, end?, description?)\n` +
|
|
1691
|
+
`- calendar_delete(eventId? OR title, date?)\n` +
|
|
1692
|
+
// Email Gmail
|
|
1693
|
+
`- gmail_send(to, subject, body) — primary email account\n` +
|
|
1694
|
+
`- gmail_reply(messageId? OR threadHint, body)\n` +
|
|
1695
|
+
`- gmail_delete(messageId? OR query)\n` +
|
|
1696
|
+
`- gmail_mark_read(messageId, isRead?)\n` +
|
|
1697
|
+
`- gmail_mark_starred(messageId, starred?)\n` +
|
|
1698
|
+
`- gmail_archive(messageId)\n` +
|
|
1699
|
+
// Email IMAP
|
|
1700
|
+
`- imap_send(accountId?, to, subject, body) — custom IMAP account\n` +
|
|
1701
|
+
`- imap_reply(accountId?, messageId, body)\n` +
|
|
1702
|
+
`- imap_trash(messageId)\n` +
|
|
1703
|
+
`- imap_mark_read(messageId, isRead?)\n` +
|
|
1704
|
+
`- imap_draft(accountId?, to, subject, body)\n` +
|
|
1705
|
+
// Contacts
|
|
1706
|
+
`- contact_add(name, email?, phone?, company?, address?)\n` +
|
|
1707
|
+
`- contact_update(query, email?, phone?, company?, address?)\n` +
|
|
1708
|
+
`- contact_delete(query)\n` +
|
|
1709
|
+
// Tasks
|
|
1710
|
+
`- task_add(title, priority?, due?)\n` +
|
|
1711
|
+
`- task_done(title)\n` +
|
|
1712
|
+
`- task_delete(title)\n` +
|
|
1713
|
+
// Google Tasks
|
|
1714
|
+
`- gtask_add(title, notes?, due?)\n` +
|
|
1715
|
+
`- gtask_complete(title)\n` +
|
|
1716
|
+
// Notes
|
|
1717
|
+
`- note_add(title, content?)\n` +
|
|
1718
|
+
// Reminders
|
|
1719
|
+
`- notify_remind(message, when) — when = ISO datetime\n` +
|
|
1720
|
+
`- reminder_create(message, when)\n` +
|
|
1721
|
+
// Slack
|
|
1722
|
+
`- slack_send(channel, text, threadTs?) — channel "#name"\n` +
|
|
1723
|
+
`- slack_dm(user, text) — user = name, id, or email\n` +
|
|
1724
|
+
`- slack_react(channel, ts, emoji)\n` +
|
|
1725
|
+
`- slack_mark_read(channel, ts)\n` +
|
|
1726
|
+
// Notion
|
|
1727
|
+
`- notion_page(title, content) — create a new Notion page\n` +
|
|
1728
|
+
// GitHub
|
|
1729
|
+
`- github_create_issue(repo, title, body?, labels?) — repo "owner/name"\n` +
|
|
1730
|
+
// File system (local to user)
|
|
1731
|
+
`- file_write(path, content)\n` +
|
|
1732
|
+
`- file_move(from, to)\n` +
|
|
1733
|
+
`- file_delete(path)\n` +
|
|
1734
|
+
`- file_mkdir(path)\n` +
|
|
1735
|
+
// Google Drive
|
|
1736
|
+
`- drive_upload(name, content, mimeType?) — Google Drive\n` +
|
|
1737
|
+
`- drive_update(fileId, content)\n` +
|
|
1738
|
+
`- drive_delete(fileId? OR name)\n` +
|
|
1739
|
+
`- drive_move(fileId, newParentFolderId? OR newName?)\n` +
|
|
1740
|
+
`- drive_share(fileId, email, role?) — role = "reader"|"writer"|"commenter"\n` +
|
|
1741
|
+
// Birthdays
|
|
1742
|
+
`- birthday_add(name, date) — date "YYYY-MM-DD" or "MM-DD"\n` +
|
|
1743
|
+
`- birthday_delete(name)\n` +
|
|
1744
|
+
// Alexandria E2E
|
|
1745
|
+
`- alexandria_send(channel, message)\n` +
|
|
1746
|
+
// Cron
|
|
1747
|
+
`- cron_create(name, schedule, command) — schedule = cron expression\n` +
|
|
1748
|
+
`- cron_delete(name)\n\n` +
|
|
1749
|
+
`Today is ${todayIso}. Relative dates: "domani" = ${this._addDaysIso(todayIso, 1).slice(0, 10)}, ` +
|
|
1750
|
+
`"dopodomani" = ${this._addDaysIso(todayIso, 2).slice(0, 10)}, "lunedì/martedì/..." resolve to next occurrence.\n\n` +
|
|
1751
|
+
`OUTPUT FORMAT (strict JSON, no markdown, no prose, no fences):\n` +
|
|
1752
|
+
`{"tool": "tool_name" | null, "params": { ... }}\n\n` +
|
|
1753
|
+
`If the message is a READ/LIST/QUERY operation (e.g. "mostra…", "che ho oggi", "leggi email", "trova"), ` +
|
|
1754
|
+
`OR is conversational chat (greetings, questions, opinions) OR is ambiguous → return {"tool": null}.\n` +
|
|
1755
|
+
`If a required param is genuinely missing AND not inferable → return {"tool": null}.\n` +
|
|
1756
|
+
`Never invent emails, eventIds, or recipient addresses.`;
|
|
1757
|
+
|
|
1758
|
+
let raw;
|
|
1759
|
+
try {
|
|
1760
|
+
raw = await callLLM(config, sys, userMessage, { temperature: 0, maxTokens: 400 });
|
|
1761
|
+
} catch (e) {
|
|
1762
|
+
this.log(`[direct-universal] LLM call failed: ${e.message}`);
|
|
1763
|
+
return null;
|
|
1764
|
+
}
|
|
1765
|
+
const parsed = this._extractJsonObject(raw);
|
|
1766
|
+
if (!parsed || !parsed.tool || !DESTRUCTIVE_ACTIONS.has(parsed.tool)) return null;
|
|
1767
|
+
if (!parsed.params || typeof parsed.params !== 'object') return null;
|
|
1768
|
+
|
|
1769
|
+
// Per-tool param normalization (matches the executor's accepted shapes).
|
|
1770
|
+
const params = { ...parsed.params };
|
|
1771
|
+
if (parsed.tool === 'calendar_create' && !params.summary) params.summary = params.title || params.name || params.subject;
|
|
1772
|
+
if (parsed.tool === 'calendar_create' && params.start && !params.end) params.end = this._addMinutesIso(params.start, 60);
|
|
1773
|
+
|
|
1774
|
+
try {
|
|
1775
|
+
const result = await executeTool(parsed.tool, params, config);
|
|
1776
|
+
const resultStr = typeof result === 'string' ? result : JSON.stringify(result);
|
|
1777
|
+
const ok = !/error|failed|not\s+found|invalid|does\s+not\s+exist|placeholder/i.test(resultStr);
|
|
1778
|
+
|
|
1779
|
+
if (this._lastDirectAuditChatId) {
|
|
1780
|
+
this._recordAudit(this._lastDirectAuditChatId, {
|
|
1781
|
+
tool: parsed.tool,
|
|
1782
|
+
success: ok,
|
|
1783
|
+
summary: this._summarizeParamsForAudit(parsed.tool, params),
|
|
1784
|
+
});
|
|
1785
|
+
}
|
|
1786
|
+
const message = ok
|
|
1787
|
+
? this._formatActionResultIT(parsed.tool, params, resultStr)
|
|
1788
|
+
: `Non sono riuscito a eseguire ${parsed.tool}: ${resultStr.slice(0, 240)}`;
|
|
1789
|
+
return { action: parsed.tool, success: ok, message };
|
|
1790
|
+
} catch (e) {
|
|
1791
|
+
return { action: parsed.tool, success: false, message: `Errore durante ${parsed.tool}: ${e.message}` };
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
/** Natural-language Italian summary for the audit log entry. */
|
|
1796
|
+
_summarizeParamsForAudit(tool, params) {
|
|
1797
|
+
if (tool.startsWith('calendar_')) {
|
|
1798
|
+
const t = params.summary || params.title || '';
|
|
1799
|
+
const s = params.start ? this._formatDateIT(String(params.start).slice(0, 10)) + ` ${String(params.start).slice(11, 16)}` : '';
|
|
1800
|
+
return `"${t}"${s ? ` · ${s}` : ''}`;
|
|
1801
|
+
}
|
|
1802
|
+
if (tool === 'gmail_send' || tool === 'imap_send') return `→ ${params.to || '?'} · "${(params.subject || '').slice(0, 60)}"`;
|
|
1803
|
+
if (tool === 'gmail_reply' || tool === 'imap_reply') return `reply → ${params.to || params.messageId || '?'}`;
|
|
1804
|
+
if (tool === 'slack_send') return `→ ${params.channel || '?'} · "${(params.text || '').slice(0, 60)}"`;
|
|
1805
|
+
if (tool === 'notify_remind') return `"${params.message || ''}" @ ${params.when || '?'}`;
|
|
1806
|
+
if (tool === 'github_create_issue') return `${params.repo || '?'}: "${params.title || ''}"`;
|
|
1807
|
+
if (tool === 'file_write') return `${params.path || '?'} (${(params.content || '').length} chars)`;
|
|
1808
|
+
if (tool === 'drive_upload') return `${params.name || '?'} (${(params.content || '').length} chars)`;
|
|
1809
|
+
if (tool === 'task_done' || tool === 'task_delete' || tool === 'contact_delete') return `"${params.title || params.name || '?'}"`;
|
|
1810
|
+
return JSON.stringify(params).slice(0, 80);
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1813
|
+
/** Italian-language natural response for a successful action. */
|
|
1814
|
+
_formatActionResultIT(tool, params, result) {
|
|
1815
|
+
switch (tool) {
|
|
1816
|
+
case 'calendar_create': {
|
|
1817
|
+
const t = params.summary || params.title || 'evento';
|
|
1818
|
+
const when = params.start ? `${this._formatDateIT(String(params.start).slice(0, 10))} alle ${String(params.start).slice(11, 16)}` : '';
|
|
1819
|
+
return `Fatto. Ho creato l'appuntamento "${t}"${when ? ` il ${when}` : ''}.`;
|
|
1820
|
+
}
|
|
1821
|
+
case 'calendar_move': return `Fatto. Appuntamento spostato.`;
|
|
1822
|
+
case 'calendar_update': return `Fatto. Appuntamento aggiornato.`;
|
|
1823
|
+
case 'calendar_delete': return `Fatto. Appuntamento cancellato.`;
|
|
1824
|
+
case 'gmail_send':
|
|
1825
|
+
case 'imap_send': return `Fatto. Email inviata a ${params.to}.`;
|
|
1826
|
+
case 'gmail_reply':
|
|
1827
|
+
case 'imap_reply': return `Fatto. Risposta inviata.`;
|
|
1828
|
+
case 'gmail_delete': return `Fatto. Email eliminata.`;
|
|
1829
|
+
case 'imap_trash': return `Fatto. Email spostata nel cestino.`;
|
|
1830
|
+
case 'contact_delete': return `Fatto. Contatto "${params.name || ''}" cancellato.`;
|
|
1831
|
+
case 'task_done': return `Fatto. Task "${params.title || ''}" completato.`;
|
|
1832
|
+
case 'task_delete': return `Fatto. Task "${params.title || ''}" cancellato.`;
|
|
1833
|
+
case 'task_clear': return `Fatto. Task list pulita.`;
|
|
1834
|
+
case 'notify_remind': return `Promemoria impostato: "${params.message || ''}" per ${params.when || ''}.`;
|
|
1835
|
+
case 'slack_send': return `Fatto. Messaggio inviato a ${params.channel || ''}.`;
|
|
1836
|
+
case 'github_create_issue': return `Issue creata su ${params.repo}: "${params.title}".`;
|
|
1837
|
+
case 'file_write': return `Fatto. File ${params.path} scritto (${(params.content || '').length} caratteri).`;
|
|
1838
|
+
case 'drive_upload': return `Fatto. "${params.name}" caricato su Google Drive.`;
|
|
1839
|
+
case 'drive_update': return `Fatto. File Drive aggiornato.`;
|
|
1840
|
+
case 'drive_delete': return `Fatto. File Drive eliminato.`;
|
|
1841
|
+
default: return `Fatto. ${result.slice(0, 200)}`;
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1410
1845
|
// ── Direct fresh calendar action (no LLM) ─────────────────────────────────
|
|
1411
1846
|
// Detects DELETE / LIST_MONTH / LIST_WEEK / LIST_DAY / LIST_TODAY /
|
|
1412
1847
|
// LIST_TOMORROW intents from a fresh user message and runs the proper tool
|
|
@@ -1432,6 +1867,14 @@ class TelegramResponder {
|
|
|
1432
1867
|
// a date or title reference. We answer FACTUALLY by re-querying the
|
|
1433
1868
|
// calendar — never by apologizing in the abstract.
|
|
1434
1869
|
const isVerify = /\b(non\s+(vedo|c'è|esiste)|c'è\s+(ancora|sempre)|è\s+ancora|stai\s+sbagliand|hai\s+sbagliat|in\s+realt[àa]|sei\s+sicur|davvero|inaffidabil|controlla|verifica|conferma\b|guarda)\b/.test(lower);
|
|
1870
|
+
// CREATE event intent — action verb + event noun. Robust enough to catch
|
|
1871
|
+
// "fissami un appuntamento dal dentista venerdì alle 10", "crea evento
|
|
1872
|
+
// ortocheratologia 18 maggio 10-11", "segna riunione con Marco lunedì 14:30".
|
|
1873
|
+
const isCreate = /\b(fiss|cre[oai]|aggiung|inserisc|programm|segn|prenoti?|impost|metti|registr|set\s+up|create|add|schedule|book)\w*\s+/.test(lower)
|
|
1874
|
+
&& /\b(appuntament|evento|event\b|meeting|riunion|incontro|chiamat|call\b|webinar|memo|reminder|promemoria|task|impegn)/i.test(lower);
|
|
1875
|
+
// MOVE intent — reschedule/postpone an existing event.
|
|
1876
|
+
const isMove = /\b(spost[ao]|rimand|rinvi|riprogramm|move|reschedule|postpone|cambia\s+(?:data|ora|orari))\w*/.test(lower)
|
|
1877
|
+
&& /\b(appuntament|evento|event\b|meeting|riunion|incontro|chiamat|call\b)/i.test(lower);
|
|
1435
1878
|
|
|
1436
1879
|
const { executeTool } = await import('./tool-executor.mjs');
|
|
1437
1880
|
|
|
@@ -1491,8 +1934,108 @@ class TelegramResponder {
|
|
|
1491
1934
|
// No date/title to verify — fall through to LIST/DELETE detection.
|
|
1492
1935
|
}
|
|
1493
1936
|
|
|
1937
|
+
// ─── CREATE intent ─────────────────────────────────────────────────────
|
|
1938
|
+
// We use a tiny LLM call ONLY to extract structured params from natural
|
|
1939
|
+
// language (NLU). Tool execution stays deterministic server-side. This
|
|
1940
|
+
// is the enterprise pattern: LLM for understanding + rendering, NEVER
|
|
1941
|
+
// for state-changing actions.
|
|
1942
|
+
if (isCreate && !isDelete && !isVerify) {
|
|
1943
|
+
const parsed = await this._nluExtractCalendarCreate(userMessage, config);
|
|
1944
|
+
if (parsed && parsed.title && parsed.start) {
|
|
1945
|
+
try {
|
|
1946
|
+
const result = await executeTool('calendar_create', {
|
|
1947
|
+
summary: parsed.title,
|
|
1948
|
+
start: parsed.start,
|
|
1949
|
+
end: parsed.end || this._addMinutesIso(parsed.start, 60),
|
|
1950
|
+
description: parsed.description || '',
|
|
1951
|
+
}, config);
|
|
1952
|
+
const ok = typeof result === 'string' && /created|event\s+"|eventId/i.test(result);
|
|
1953
|
+
const startLabel = `${this._formatDateIT(parsed.start.slice(0, 10))} alle ${parsed.start.slice(11, 16)}`;
|
|
1954
|
+
const message = ok
|
|
1955
|
+
? `Fatto. Ho creato l'appuntamento "${parsed.title}" il ${startLabel}.`
|
|
1956
|
+
: `Non sono riuscito a creare l'appuntamento: ${result}`;
|
|
1957
|
+
if (this._lastDirectAuditChatId) {
|
|
1958
|
+
this._recordAudit(this._lastDirectAuditChatId, {
|
|
1959
|
+
tool: 'calendar_create',
|
|
1960
|
+
success: ok,
|
|
1961
|
+
summary: `"${parsed.title}" il ${startLabel}`,
|
|
1962
|
+
});
|
|
1963
|
+
}
|
|
1964
|
+
return { action: 'calendar_create', success: ok, message };
|
|
1965
|
+
} catch (e) {
|
|
1966
|
+
return { action: 'calendar_create', success: false, message: `Errore durante la creazione: ${e.message}` };
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
// Couldn't extract title/date with confidence → let the LLM ask the user.
|
|
1970
|
+
// Fall through.
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
// ─── MOVE intent ───────────────────────────────────────────────────────
|
|
1974
|
+
if (isMove && !isDelete && !isVerify && !isCreate) {
|
|
1975
|
+
const parsed = await this._nluExtractCalendarMove(userMessage, config);
|
|
1976
|
+
if (parsed && (parsed.title || parsed.oldDate) && parsed.newStart) {
|
|
1977
|
+
// Find the source event.
|
|
1978
|
+
let candidates = [];
|
|
1979
|
+
try {
|
|
1980
|
+
if (parsed.oldDate) {
|
|
1981
|
+
const r = await executeTool('calendar_date', { date: parsed.oldDate }, config);
|
|
1982
|
+
candidates = this._parseEventsFromToolOutput(r);
|
|
1983
|
+
}
|
|
1984
|
+
if (candidates.length === 0 && parsed.title) {
|
|
1985
|
+
const r = await executeTool('calendar_find', { query: parsed.title, daysAhead: 60 }, config);
|
|
1986
|
+
candidates = this._parseEventsFromToolOutput(r);
|
|
1987
|
+
}
|
|
1988
|
+
} catch (e) {
|
|
1989
|
+
return { action: 'calendar_move', success: false, message: `Errore nella ricerca dell'evento: ${e.message}` };
|
|
1990
|
+
}
|
|
1991
|
+
// Match by title token overlap, same heuristic as DELETE.
|
|
1992
|
+
const norm = (s) => String(s || '').toLowerCase()
|
|
1993
|
+
.normalize('NFD').replace(/[̀-ͯ]/g, '')
|
|
1994
|
+
.replace(/[^a-z0-9\s]/g, ' ')
|
|
1995
|
+
.split(/\s+/).filter(t => t.length > 2);
|
|
1996
|
+
let match = null;
|
|
1997
|
+
if (parsed.title) {
|
|
1998
|
+
const titleTokens = norm(parsed.title);
|
|
1999
|
+
const scored = candidates.map(c => {
|
|
2000
|
+
const summaryTokens = new Set(norm(c.summary));
|
|
2001
|
+
const score = titleTokens.filter(t => summaryTokens.has(t)).length;
|
|
2002
|
+
return { c, score };
|
|
2003
|
+
}).sort((a, b) => b.score - a.score);
|
|
2004
|
+
const top = scored[0];
|
|
2005
|
+
if (top && top.score >= Math.max(1, Math.ceil(titleTokens.length * 0.5))) match = top.c;
|
|
2006
|
+
}
|
|
2007
|
+
if (!match && candidates.length === 1) match = candidates[0];
|
|
2008
|
+
if (!match || !match.eventId) {
|
|
2009
|
+
return { action: 'calendar_move', success: false,
|
|
2010
|
+
message: `Non ho trovato l'appuntamento "${parsed.title || ''}" da spostare. Verifica titolo o data originale.` };
|
|
2011
|
+
}
|
|
2012
|
+
try {
|
|
2013
|
+
const result = await executeTool('calendar_move', {
|
|
2014
|
+
eventId: match.eventId,
|
|
2015
|
+
newStart: parsed.newStart,
|
|
2016
|
+
newEnd: parsed.newEnd || this._addMinutesIso(parsed.newStart, 60),
|
|
2017
|
+
}, config);
|
|
2018
|
+
const ok = typeof result === 'string' && /rescheduled|moved|spostat/i.test(result);
|
|
2019
|
+
const newLabel = `${this._formatDateIT(parsed.newStart.slice(0, 10))} alle ${parsed.newStart.slice(11, 16)}`;
|
|
2020
|
+
const message = ok
|
|
2021
|
+
? `Fatto. Ho spostato "${match.summary}" al ${newLabel}.`
|
|
2022
|
+
: `Non sono riuscito a spostare l'appuntamento: ${result}`;
|
|
2023
|
+
if (this._lastDirectAuditChatId) {
|
|
2024
|
+
this._recordAudit(this._lastDirectAuditChatId, {
|
|
2025
|
+
tool: 'calendar_move',
|
|
2026
|
+
success: ok,
|
|
2027
|
+
summary: `"${match.summary}" → ${newLabel} (eventId ${match.eventId.slice(0, 16)}…)`,
|
|
2028
|
+
});
|
|
2029
|
+
}
|
|
2030
|
+
return { action: 'calendar_move', success: ok, message };
|
|
2031
|
+
} catch (e) {
|
|
2032
|
+
return { action: 'calendar_move', success: false, message: `Errore nello spostamento: ${e.message}` };
|
|
2033
|
+
}
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
|
|
1494
2037
|
// ─── LIST intents ──────────────────────────────────────────────────────
|
|
1495
|
-
if (isList && !isDelete && !isVerify) {
|
|
2038
|
+
if (isList && !isDelete && !isVerify && !isCreate && !isMove) {
|
|
1496
2039
|
// "appuntamenti di oggi"
|
|
1497
2040
|
if (/\b(oggi|today)\b/.test(lower)) {
|
|
1498
2041
|
try {
|
|
@@ -63,30 +63,39 @@ function getTsxPath() {
|
|
|
63
63
|
|
|
64
64
|
/** Actions that mutate external state and require user confirmation. */
|
|
65
65
|
export const DESTRUCTIVE_ACTIONS = new Set([
|
|
66
|
-
|
|
67
|
-
'gmail_send_attach',
|
|
68
|
-
'
|
|
69
|
-
|
|
70
|
-
'imap_send',
|
|
71
|
-
'
|
|
72
|
-
|
|
73
|
-
'
|
|
74
|
-
|
|
75
|
-
'
|
|
76
|
-
|
|
77
|
-
'
|
|
78
|
-
'
|
|
79
|
-
|
|
80
|
-
'
|
|
81
|
-
|
|
82
|
-
'
|
|
83
|
-
|
|
84
|
-
'slack_send',
|
|
85
|
-
|
|
86
|
-
'
|
|
87
|
-
|
|
88
|
-
'
|
|
89
|
-
|
|
66
|
+
// Gmail
|
|
67
|
+
'gmail_send', 'gmail_send_attach', 'gmail_reply', 'gmail_delete',
|
|
68
|
+
'gmail_mark_read', 'gmail_mark_starred', 'gmail_archive', 'gmail_trash',
|
|
69
|
+
// IMAP (custom email accounts)
|
|
70
|
+
'imap_send', 'imap_reply', 'imap_bulk_send', 'imap_send_template',
|
|
71
|
+
'imap_trash', 'imap_mark_read', 'imap_mark_starred', 'imap_draft',
|
|
72
|
+
// Calendar
|
|
73
|
+
'calendar_create', 'calendar_move', 'calendar_update', 'calendar_delete',
|
|
74
|
+
// Contacts
|
|
75
|
+
'contact_add', 'contact_update', 'contact_delete',
|
|
76
|
+
// Tasks (local) + Google Tasks
|
|
77
|
+
'task_add', 'task_done', 'task_delete', 'task_clear',
|
|
78
|
+
'gtask_add', 'gtask_complete', 'gtask_delete',
|
|
79
|
+
// Notes
|
|
80
|
+
'note_add',
|
|
81
|
+
// Reminders / notifications
|
|
82
|
+
'notify_remind', 'reminder_create',
|
|
83
|
+
// Slack
|
|
84
|
+
'slack_send', 'slack_dm', 'slack_react', 'slack_mark_read',
|
|
85
|
+
// Notion
|
|
86
|
+
'notion_page', 'notion_update',
|
|
87
|
+
// GitHub
|
|
88
|
+
'github_create_issue', 'github_comment',
|
|
89
|
+
// File system (local)
|
|
90
|
+
'file_write', 'file_move', 'file_delete', 'file_mkdir',
|
|
91
|
+
// Google Drive
|
|
92
|
+
'drive_upload', 'drive_update', 'drive_delete', 'drive_move', 'drive_share',
|
|
93
|
+
// Birthdays
|
|
94
|
+
'birthday_add', 'birthday_update', 'birthday_delete',
|
|
95
|
+
// Alexandria messaging
|
|
96
|
+
'alexandria_send',
|
|
97
|
+
// Cron / scheduling
|
|
98
|
+
'cron_create', 'cron_delete',
|
|
90
99
|
]);
|
|
91
100
|
|
|
92
101
|
// ── Tool Definitions (for system prompt) ─────────────────────────────────────
|