nothumanallowed 13.5.192 → 13.5.194
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.194",
|
|
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.194';
|
|
9
9
|
export const BASE_URL = 'https://nothumanallowed.com/cli';
|
|
10
10
|
export const API_BASE = 'https://nothumanallowed.com/api/v1';
|
|
11
11
|
|
|
@@ -179,7 +179,7 @@ function detectLanguage(text) {
|
|
|
179
179
|
* Like chat.mjs but headless — no confirmation prompts, all tools auto-executed.
|
|
180
180
|
* Returns a human-readable summary of what was done.
|
|
181
181
|
*/
|
|
182
|
-
async function callAgentWithTools(config, agentName, userMessage, languageOverride) {
|
|
182
|
+
async function callAgentWithTools(config, agentName, userMessage, languageOverride, preHistory) {
|
|
183
183
|
const today = new Date().toISOString().split('T')[0];
|
|
184
184
|
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
185
185
|
const locale = Intl.DateTimeFormat().resolvedOptions().locale || 'en';
|
|
@@ -193,23 +193,26 @@ async function callAgentWithTools(config, agentName, userMessage, languageOverri
|
|
|
193
193
|
.replace(/\{\{LANGUAGE\}\}/g, language);
|
|
194
194
|
|
|
195
195
|
// Multi-turn: serialize history as [User]/[Assistant] string (same pattern as chat.mjs)
|
|
196
|
-
|
|
196
|
+
// preHistory: optional [{role, content}] from previous conversation turn (for sticky confirmations)
|
|
197
|
+
const history = preHistory ? [...preHistory] : [];
|
|
197
198
|
let finalText = '';
|
|
199
|
+
let lastToolResults = null; // saved for context return
|
|
198
200
|
|
|
199
|
-
for (let round = 0; round <
|
|
201
|
+
for (let round = 0; round < 4; round++) {
|
|
200
202
|
// Build serialized message
|
|
201
203
|
const parts = history.map(h => (h.role === 'user' ? '[User]' : '[Assistant]') + ' ' + h.content);
|
|
202
204
|
parts.push('[User] ' + userMessage);
|
|
203
|
-
if (round > 0) {
|
|
204
|
-
// Replace last user with tool results continuation
|
|
205
|
-
}
|
|
206
205
|
const serialized = parts.join('\n\n');
|
|
207
206
|
|
|
208
207
|
const response = await callLLM(config, systemPrompt, serialized);
|
|
209
208
|
const { textParts, actions } = parseActions(response);
|
|
210
|
-
finalText = textParts.join('\n').trim();
|
|
211
209
|
|
|
212
|
-
if (actions.length === 0)
|
|
210
|
+
if (actions.length === 0) {
|
|
211
|
+
// Final round — no tools, just text for the user
|
|
212
|
+
finalText = textParts.join('\n').trim();
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
// Intermediate round with tools — don't expose tool-call JSON to the user
|
|
213
216
|
|
|
214
217
|
// Execute all tools and collect results
|
|
215
218
|
const toolResults = [];
|
|
@@ -222,13 +225,14 @@ async function callAgentWithTools(config, agentName, userMessage, languageOverri
|
|
|
222
225
|
toolResults.push(`[${action}] Error: ${err.message}`);
|
|
223
226
|
}
|
|
224
227
|
}
|
|
228
|
+
lastToolResults = toolResults;
|
|
225
229
|
|
|
226
230
|
// Feed results back: append assistant response + tool results as next user turn
|
|
227
231
|
history.push({ role: 'assistant', content: response });
|
|
228
232
|
userMessage = 'Tool results:\n' + toolResults.join('\n') + '\n\nNow give the user a concise confirmation in ' + language + '. Do NOT use HERALD format — respond conversationally.';
|
|
229
233
|
}
|
|
230
234
|
|
|
231
|
-
return finalText || 'Done.';
|
|
235
|
+
return { text: finalText || 'Done.', history, lastToolResults };
|
|
232
236
|
}
|
|
233
237
|
|
|
234
238
|
// ── Telegram Bot (Long Polling via native fetch) ─────────────────────────────
|
|
@@ -583,17 +587,24 @@ class TelegramResponder {
|
|
|
583
587
|
|
|
584
588
|
let agent;
|
|
585
589
|
let enrichedMessage = cleanText;
|
|
590
|
+
let preHistory = null;
|
|
586
591
|
if (useStickyAgent) {
|
|
587
592
|
agent = lastCtx.agent;
|
|
588
|
-
//
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
593
|
+
// Re-inject the full conversation history from last turn so the agent
|
|
594
|
+
// has the eventId/context already and can proceed directly (e.g. calendar_delete)
|
|
595
|
+
if (lastCtx.history && lastCtx.history.length > 0) {
|
|
596
|
+
preHistory = lastCtx.history;
|
|
597
|
+
} else {
|
|
598
|
+
// Fallback: text-only context injection
|
|
599
|
+
const nl = '\n';
|
|
600
|
+
enrichedMessage =
|
|
601
|
+
'[Conversazione precedente]' + nl +
|
|
602
|
+
'Utente: ' + lastCtx.userMsg + nl +
|
|
603
|
+
'Tu (' + agent.toUpperCase() + '): ' + lastCtx.agentReply.slice(0, 600) + nl + nl +
|
|
604
|
+
'[Nuovo messaggio utente — è una conferma/risposta al turno precedente]' + nl +
|
|
605
|
+
cleanText;
|
|
606
|
+
}
|
|
607
|
+
this.log(`[Telegram] ${fromUser}: sticky agent ${agent.toUpperCase()} (ambiguous, last ctx ${Math.round(stickyAge/1000)}s ago, preHistory=${preHistory ? preHistory.length : 0} turns)`);
|
|
597
608
|
} else {
|
|
598
609
|
agent = routeMessage(cleanText, this.autoRoute);
|
|
599
610
|
this.log(`[Telegram] ${fromUser} (chat ${chatId}): routed to ${agent.toUpperCase()}${isVoice ? ' [voice]' : ''}`);
|
|
@@ -616,25 +627,30 @@ class TelegramResponder {
|
|
|
616
627
|
// Tool-capable agents use the full tool execution loop
|
|
617
628
|
// Pure reasoning/analysis agents use the simple callAgent (no tools)
|
|
618
629
|
const TOOL_AGENTS = new Set(['herald', 'hermes', 'edi', 'jarvis', 'flux', 'echo', 'mercury', 'pipe', 'navi', 'link', 'prometheus', 'tempest']);
|
|
619
|
-
let
|
|
630
|
+
let responseText;
|
|
631
|
+
let responseHistory = null;
|
|
620
632
|
if (TOOL_AGENTS.has(agent)) {
|
|
621
|
-
|
|
633
|
+
const result = await callAgentWithTools(this.config, agent, enrichedMessage, detectedLang, preHistory);
|
|
634
|
+
responseText = result.text;
|
|
635
|
+
responseHistory = result.history;
|
|
622
636
|
} else {
|
|
623
637
|
// For non-tool agents: inject language instruction into the message
|
|
624
638
|
const langInstruction = detectedLang ? `[Respond in ${detectedLang}] ` : '';
|
|
625
|
-
|
|
639
|
+
responseText = await callAgent(this.config, agent, langInstruction + enrichedMessage);
|
|
626
640
|
}
|
|
627
641
|
|
|
628
642
|
// Truncate if too long for Telegram (4096 char limit)
|
|
629
|
-
const truncated =
|
|
630
|
-
?
|
|
631
|
-
:
|
|
643
|
+
const truncated = responseText.length > 4000
|
|
644
|
+
? responseText.slice(0, 3950) + '\n\n... [truncated]'
|
|
645
|
+
: responseText;
|
|
632
646
|
|
|
633
647
|
// Save context for sticky agent continuity (next short message reuses this agent)
|
|
648
|
+
// Save full history so next confirmation turn has the eventId already resolved
|
|
634
649
|
this._lastContextByChatId[chatId] = {
|
|
635
650
|
agent,
|
|
636
651
|
userMsg: cleanText,
|
|
637
|
-
agentReply:
|
|
652
|
+
agentReply: responseText,
|
|
653
|
+
history: responseHistory,
|
|
638
654
|
ts: Date.now(),
|
|
639
655
|
};
|
|
640
656
|
this._lastAgentByChatId[chatId] = agent;
|
|
@@ -645,7 +661,7 @@ class TelegramResponder {
|
|
|
645
661
|
text: `[${agent.toUpperCase()}]\n\n${truncated}`,
|
|
646
662
|
});
|
|
647
663
|
|
|
648
|
-
this.log(`[Telegram] Responded to ${fromUser} via ${agent.toUpperCase()} (${
|
|
664
|
+
this.log(`[Telegram] Responded to ${fromUser} via ${agent.toUpperCase()} (${responseText.length} chars)`);
|
|
649
665
|
} catch (err) {
|
|
650
666
|
this.log(`[Telegram] Agent call failed: ${err.message}`);
|
|
651
667
|
// Send error message to user
|
|
@@ -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, {
|