nothumanallowed 13.5.191 → 13.5.193
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": "13.5.
|
|
3
|
+
"version": "13.5.193",
|
|
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 = '13.5.
|
|
8
|
+
export const VERSION = '13.5.193';
|
|
9
9
|
export const BASE_URL = 'https://nothumanallowed.com/cli';
|
|
10
10
|
export const API_BASE = 'https://nothumanallowed.com/api/v1';
|
|
11
11
|
|
|
@@ -207,9 +207,13 @@ async function callAgentWithTools(config, agentName, userMessage, languageOverri
|
|
|
207
207
|
|
|
208
208
|
const response = await callLLM(config, systemPrompt, serialized);
|
|
209
209
|
const { textParts, actions } = parseActions(response);
|
|
210
|
-
finalText = textParts.join('\n').trim();
|
|
211
210
|
|
|
212
|
-
if (actions.length === 0)
|
|
211
|
+
if (actions.length === 0) {
|
|
212
|
+
// Final round — no tools, just text for the user
|
|
213
|
+
finalText = textParts.join('\n').trim();
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
// Intermediate round with tools — don't expose tool-call JSON to the user
|
|
213
217
|
|
|
214
218
|
// Execute all tools and collect results
|
|
215
219
|
const toolResults = [];
|
|
@@ -313,6 +317,9 @@ class TelegramResponder {
|
|
|
313
317
|
this.maxConcurrent = 3;
|
|
314
318
|
this._updateCheckTimer = null;
|
|
315
319
|
this._lastNotifiedVersion = null;
|
|
320
|
+
// Per-chat sticky agent: remembers last agent used, plus last turn context
|
|
321
|
+
this._lastAgentByChatId = {}; // chatId → agentName
|
|
322
|
+
this._lastContextByChatId = {}; // chatId → { agent, userMsg, agentReply, ts }
|
|
316
323
|
}
|
|
317
324
|
|
|
318
325
|
get enabled() {
|
|
@@ -359,11 +366,10 @@ class TelegramResponder {
|
|
|
359
366
|
if (chatIds.length === 0) return;
|
|
360
367
|
|
|
361
368
|
const msg =
|
|
362
|
-
`🆕
|
|
369
|
+
`🆕 NHA v${latest} disponibile!\n\n` +
|
|
363
370
|
`Una nuova versione di NotHumanAllowed è stata pubblicata.\n\n` +
|
|
364
|
-
`Aggiorna con:\n` +
|
|
365
|
-
|
|
366
|
-
`Poi riavvia il bot con: \`nha responder restart\``;
|
|
371
|
+
`Aggiorna con:\nnpm install -g nothumanallowed@latest\n\n` +
|
|
372
|
+
`Poi riavvia il bot con: nha ops stop && nha ops start`;
|
|
367
373
|
|
|
368
374
|
this.log(`[Telegram] Broadcasting update notification v${latest} to ${chatIds.length} users`);
|
|
369
375
|
|
|
@@ -372,7 +378,6 @@ class TelegramResponder {
|
|
|
372
378
|
await this._telegramCall('sendMessage', {
|
|
373
379
|
chat_id: parseInt(chatId, 10),
|
|
374
380
|
text: msg,
|
|
375
|
-
parse_mode: 'Markdown',
|
|
376
381
|
});
|
|
377
382
|
} catch {
|
|
378
383
|
// User blocked bot or chat no longer exists — ignore
|
|
@@ -572,8 +577,31 @@ class TelegramResponder {
|
|
|
572
577
|
|
|
573
578
|
this.pendingRequests++;
|
|
574
579
|
try {
|
|
575
|
-
|
|
576
|
-
|
|
580
|
+
// Sticky agent: for short/ambiguous messages (< 6 words), reuse last agent for this chat
|
|
581
|
+
// This handles confirmation messages like "Sì", "Ok", "Fallo", "Confermo", etc.
|
|
582
|
+
const wordCount = cleanText.trim().split(/\s+/).length;
|
|
583
|
+
const isAmbiguous = wordCount <= 5;
|
|
584
|
+
const lastCtx = this._lastContextByChatId[chatId];
|
|
585
|
+
const stickyAge = lastCtx ? (Date.now() - lastCtx.ts) : Infinity;
|
|
586
|
+
const useStickyAgent = isAmbiguous && lastCtx && stickyAge < 5 * 60 * 1000; // sticky for 5 min
|
|
587
|
+
|
|
588
|
+
let agent;
|
|
589
|
+
let enrichedMessage = cleanText;
|
|
590
|
+
if (useStickyAgent) {
|
|
591
|
+
agent = lastCtx.agent;
|
|
592
|
+
// Inject previous turn context so agent understands what "Sì" refers to
|
|
593
|
+
const nl = '\n';
|
|
594
|
+
enrichedMessage =
|
|
595
|
+
'[Conversazione precedente]' + nl +
|
|
596
|
+
'Utente: ' + lastCtx.userMsg + nl +
|
|
597
|
+
'Tu (' + agent.toUpperCase() + '): ' + lastCtx.agentReply.slice(0, 400) + nl + nl +
|
|
598
|
+
'[Nuovo messaggio utente]' + nl +
|
|
599
|
+
cleanText;
|
|
600
|
+
this.log(`[Telegram] ${fromUser}: sticky agent ${agent.toUpperCase()} (ambiguous message, last ctx ${Math.round(stickyAge/1000)}s ago)`);
|
|
601
|
+
} else {
|
|
602
|
+
agent = routeMessage(cleanText, this.autoRoute);
|
|
603
|
+
this.log(`[Telegram] ${fromUser} (chat ${chatId}): routed to ${agent.toUpperCase()}${isVoice ? ' [voice]' : ''}`);
|
|
604
|
+
}
|
|
577
605
|
|
|
578
606
|
// Broadcast event
|
|
579
607
|
this.wsBroadcast({
|
|
@@ -586,18 +614,19 @@ class TelegramResponder {
|
|
|
586
614
|
await this._telegramCall('sendChatAction', { chat_id: chatId, action: 'typing' });
|
|
587
615
|
|
|
588
616
|
// Detect language from the message text — overrides system locale
|
|
589
|
-
|
|
617
|
+
// For sticky context, use lang from previous turn if current message is ambiguous
|
|
618
|
+
const detectedLang = detectLanguage(cleanText) || (lastCtx ? detectLanguage(lastCtx.userMsg) : null);
|
|
590
619
|
|
|
591
620
|
// Tool-capable agents use the full tool execution loop
|
|
592
621
|
// Pure reasoning/analysis agents use the simple callAgent (no tools)
|
|
593
622
|
const TOOL_AGENTS = new Set(['herald', 'hermes', 'edi', 'jarvis', 'flux', 'echo', 'mercury', 'pipe', 'navi', 'link', 'prometheus', 'tempest']);
|
|
594
623
|
let response;
|
|
595
624
|
if (TOOL_AGENTS.has(agent)) {
|
|
596
|
-
response = await callAgentWithTools(this.config, agent,
|
|
625
|
+
response = await callAgentWithTools(this.config, agent, enrichedMessage, detectedLang);
|
|
597
626
|
} else {
|
|
598
627
|
// For non-tool agents: inject language instruction into the message
|
|
599
628
|
const langInstruction = detectedLang ? `[Respond in ${detectedLang}] ` : '';
|
|
600
|
-
response = await callAgent(this.config, agent, langInstruction +
|
|
629
|
+
response = await callAgent(this.config, agent, langInstruction + enrichedMessage);
|
|
601
630
|
}
|
|
602
631
|
|
|
603
632
|
// Truncate if too long for Telegram (4096 char limit)
|
|
@@ -605,6 +634,15 @@ class TelegramResponder {
|
|
|
605
634
|
? response.slice(0, 3950) + '\n\n... [truncated]'
|
|
606
635
|
: response;
|
|
607
636
|
|
|
637
|
+
// Save context for sticky agent continuity (next short message reuses this agent)
|
|
638
|
+
this._lastContextByChatId[chatId] = {
|
|
639
|
+
agent,
|
|
640
|
+
userMsg: cleanText,
|
|
641
|
+
agentReply: response,
|
|
642
|
+
ts: Date.now(),
|
|
643
|
+
};
|
|
644
|
+
this._lastAgentByChatId[chatId] = agent;
|
|
645
|
+
|
|
608
646
|
// Send response as plain text — no parse_mode to avoid Markdown entity parse errors
|
|
609
647
|
await this._telegramCall('sendMessage', {
|
|
610
648
|
chat_id: chatId,
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
getEventsForDate,
|
|
22
22
|
createEvent,
|
|
23
23
|
updateEvent,
|
|
24
|
+
deleteEvent,
|
|
24
25
|
listEvents,
|
|
25
26
|
markAsRead,
|
|
26
27
|
markAsUnread,
|
|
@@ -74,6 +75,7 @@ export const DESTRUCTIVE_ACTIONS = new Set([
|
|
|
74
75
|
'calendar_create',
|
|
75
76
|
'calendar_move',
|
|
76
77
|
'calendar_update',
|
|
78
|
+
'calendar_delete',
|
|
77
79
|
'contact_delete',
|
|
78
80
|
'task_done',
|
|
79
81
|
'task_delete',
|
|
@@ -171,7 +173,11 @@ TOOLS:
|
|
|
171
173
|
Update ANY field of an existing calendar event: title, location, description, start time, end time.
|
|
172
174
|
You MUST call calendar_find first to get the eventId. Only include fields that need to change. ALWAYS confirm before updating.
|
|
173
175
|
|
|
174
|
-
19.
|
|
176
|
+
19. calendar_delete(eventId: string)
|
|
177
|
+
Delete (permanently remove) a calendar event by its eventId.
|
|
178
|
+
You MUST call calendar_find first to get the eventId. ALWAYS confirm with the user before deleting.
|
|
179
|
+
|
|
180
|
+
20. schedule_meeting(clientName: string, subject: string, location: string, durationMinutes: number, dateFrom: string, dateTo: string, workdayStart?: number, workdayEnd?: number)
|
|
175
181
|
Find optimal meeting slots considering existing calendar events, locations, and estimated travel time between appointments. Returns ranked slots with travel info. dateFrom and dateTo are YYYY-MM-DD.
|
|
176
182
|
|
|
177
183
|
20. schedule_draft_email(clientName: string, subject: string, location: string, durationMinutes: number, dateFrom: string, dateTo: string)
|
|
@@ -600,7 +606,7 @@ Never output a JSON block as a suggestion — every block executes immediately.
|
|
|
600
606
|
AVAILABLE TOOLS:
|
|
601
607
|
gmail_list · gmail_read · gmail_send · gmail_draft · gmail_reply · gmail_mark_read · gmail_mark_unread · gmail_archive · gmail_delete · gmail_send_attach
|
|
602
608
|
imap_accounts · imap_list · imap_read · imap_send · imap_sync · imap_labels · imap_mark_read · imap_reply · imap_thread · imap_search · imap_mark_starred · imap_trash · imap_draft · imap_send_template · imap_bulk_send
|
|
603
|
-
calendar_today · calendar_tomorrow · calendar_date · calendar_upcoming · calendar_week · calendar_create · calendar_move · calendar_find · calendar_update · schedule_meeting · schedule_draft_email
|
|
609
|
+
calendar_today · calendar_tomorrow · calendar_date · calendar_upcoming · calendar_week · calendar_create · calendar_move · calendar_find · calendar_update · calendar_delete · schedule_meeting · schedule_draft_email
|
|
604
610
|
task_list · task_add · task_done · task_move · task_delete · task_clear · task_edit
|
|
605
611
|
contact_search · contact_add · contact_update · contact_delete
|
|
606
612
|
gtask_list · gtask_add · gtask_complete
|
|
@@ -630,12 +636,19 @@ maps_directions · notify_remind · birthdays_upcoming · birthday_add · execut
|
|
|
630
636
|
export function parseActions(text) {
|
|
631
637
|
const actions = [];
|
|
632
638
|
const textParts = [];
|
|
639
|
+
|
|
640
|
+
// Normalize: some LLMs output "json ... " (double-quote fences) instead of ```json ... ```
|
|
641
|
+
// Replace "json\n{...}\n" patterns with proper ```json fences before parsing
|
|
642
|
+
const normalized = text
|
|
643
|
+
.replace(/"json\s*\n([\s\S]*?)\n\s*"/g, (_, body) => '```json\n' + body.trim() + '\n```')
|
|
644
|
+
.replace(/'json\s*\n([\s\S]*?)\n\s*'/g, (_, body) => '```json\n' + body.trim() + '\n```');
|
|
645
|
+
|
|
633
646
|
const fenceRegex = /```json\s*\n?([\s\S]*?)```/g;
|
|
634
647
|
let lastIndex = 0;
|
|
635
648
|
let match;
|
|
636
649
|
|
|
637
|
-
while ((match = fenceRegex.exec(
|
|
638
|
-
const before =
|
|
650
|
+
while ((match = fenceRegex.exec(normalized)) !== null) {
|
|
651
|
+
const before = normalized.slice(lastIndex, match.index).trim();
|
|
639
652
|
if (before) textParts.push(before);
|
|
640
653
|
|
|
641
654
|
try {
|
|
@@ -650,9 +663,30 @@ export function parseActions(text) {
|
|
|
650
663
|
lastIndex = match.index + match[0].length;
|
|
651
664
|
}
|
|
652
665
|
|
|
653
|
-
const trailing =
|
|
666
|
+
const trailing = normalized.slice(lastIndex).trim();
|
|
654
667
|
if (trailing) textParts.push(trailing);
|
|
655
668
|
|
|
669
|
+
// Fallback: if no fenced blocks found, scan for bare {"action": ...} objects in the text
|
|
670
|
+
if (actions.length === 0) {
|
|
671
|
+
const bareRegex = /\{[\s\S]*?"action"\s*:\s*"[^"]+[\s\S]*?\}/g;
|
|
672
|
+
let bareMatch;
|
|
673
|
+
const consumed = new Set();
|
|
674
|
+
while ((bareMatch = bareRegex.exec(text)) !== null) {
|
|
675
|
+
try {
|
|
676
|
+
const parsed = JSON.parse(bareMatch[0]);
|
|
677
|
+
if (parsed.action && typeof parsed.action === 'string' && !consumed.has(bareMatch[0])) {
|
|
678
|
+
actions.push({ action: parsed.action, params: parsed.params || {} });
|
|
679
|
+
consumed.add(bareMatch[0]);
|
|
680
|
+
}
|
|
681
|
+
} catch { /* not valid JSON, skip */ }
|
|
682
|
+
}
|
|
683
|
+
// If we found bare actions, rebuild textParts stripping out the JSON blobs
|
|
684
|
+
if (actions.length > 0) {
|
|
685
|
+
const cleaned = text.replace(/\{[\s\S]*?"action"\s*:\s*"[^"]+[\s\S]*?\}/g, '').trim();
|
|
686
|
+
return { textParts: cleaned ? [cleaned] : [], actions };
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
656
690
|
return { textParts, actions };
|
|
657
691
|
}
|
|
658
692
|
|
|
@@ -1318,6 +1352,25 @@ export async function executeTool(action, params, config) {
|
|
|
1318
1352
|
return `Event updated successfully (${changes}). ${params.location ? `New location: ${params.location}` : ''}`;
|
|
1319
1353
|
}
|
|
1320
1354
|
|
|
1355
|
+
case 'calendar_delete': {
|
|
1356
|
+
if (!params.eventId) return 'eventId required. Call calendar_find first to get the eventId.';
|
|
1357
|
+
// Smart eventId resolution: if it looks like a name instead of a Google Calendar ID, search for it
|
|
1358
|
+
let delEventId = params.eventId;
|
|
1359
|
+
if (delEventId && (delEventId.includes(' ') || delEventId.length < 10 || /[A-Z]/.test(delEventId))) {
|
|
1360
|
+
const fromD = new Date();
|
|
1361
|
+
const toD = new Date(fromD.getTime() + 60 * 86400000);
|
|
1362
|
+
const evts = await listEvents(config, 'primary', fromD, toD);
|
|
1363
|
+
const m = evts.find(e => (e.summary || '').toLowerCase().includes(delEventId.toLowerCase()));
|
|
1364
|
+
if (m) {
|
|
1365
|
+
delEventId = m.id;
|
|
1366
|
+
} else {
|
|
1367
|
+
return `Could not find event matching "${params.eventId}" in the next 60 days. Use calendar_find to search first.`;
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
await deleteEvent(config, 'primary', delEventId);
|
|
1371
|
+
return `Event deleted successfully.`;
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1321
1374
|
// ── Smart Scheduling ──────────────────────────────────────────────────
|
|
1322
1375
|
case 'schedule_meeting': {
|
|
1323
1376
|
const slots = await findAvailableSlots(config, {
|