nothumanallowed 13.5.195 → 13.5.197
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 +104 -46
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nothumanallowed",
|
|
3
|
-
"version": "13.5.
|
|
3
|
+
"version": "13.5.197",
|
|
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.197';
|
|
9
9
|
export const BASE_URL = 'https://nothumanallowed.com/cli';
|
|
10
10
|
export const API_BASE = 'https://nothumanallowed.com/api/v1';
|
|
11
11
|
|
|
@@ -179,12 +179,55 @@ 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
|
+
// Detect if a message is a reaction/continuation (not a new independent request)
|
|
183
|
+
// Used to decide whether to use sticky agent context
|
|
184
|
+
function isContinuationMessage(text, lastCtx) {
|
|
185
|
+
if (!lastCtx) return false;
|
|
186
|
+
const lower = text.toLowerCase().trim();
|
|
187
|
+
|
|
188
|
+
// Explicit confirmations / reactions
|
|
189
|
+
const CONFIRMATIONS = ['sì','si','yes','ok','okay','procedi','fallo','vai','confermo','cancellalo',
|
|
190
|
+
'eliminalo','mandalo','esegui','perfetto','giusto','corretto','fatto','bene','certo','esatto',
|
|
191
|
+
'assolutamente','ovviamente','naturalmente','ciao','avanti','go','do it','proceed','confirm',
|
|
192
|
+
'sure','yep','yup','please','per favore','grazie','thanks'];
|
|
193
|
+
if (CONFIRMATIONS.some(c => lower === c || lower.startsWith(c + ' ') || lower.endsWith(' ' + c))) return true;
|
|
194
|
+
|
|
195
|
+
// Negative reactions that refer to previous turn (not new requests)
|
|
196
|
+
const REACTIONS = ['no','nope','annulla','stop','lascia perdere','non farlo','aspetta',
|
|
197
|
+
'sbagliato','non è quello','con cazzo','impossibile','stai scherzando','non ci credo',
|
|
198
|
+
'ma va','davvero','sicuro','sei sicuro','ma sei sicuro','ancora','di nuovo','riprova'];
|
|
199
|
+
if (REACTIONS.some(r => lower === r || lower.startsWith(r + ' ') || lower.endsWith(' ' + r))) return true;
|
|
200
|
+
|
|
201
|
+
// Short messages (≤ 6 words) without clear new-request keywords are likely continuations
|
|
202
|
+
const words = lower.split(/\s+/);
|
|
203
|
+
if (words.length <= 6) {
|
|
204
|
+
const NEW_REQUEST_KEYWORDS = ['calendario','appuntamento','email','posta','meteo','tempo',
|
|
205
|
+
'crea','aggiungi','cerca','trova','mostra','mandami','dimmi','quanto','quando','dove',
|
|
206
|
+
'create','add','find','search','show','send','delete','cancel','weather','mail','event'];
|
|
207
|
+
const hasNewKeyword = NEW_REQUEST_KEYWORDS.some(k => lower.includes(k));
|
|
208
|
+
if (!hasNewKeyword) return true;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Detect if the last agent response indicates a completed action (context should reset after)
|
|
215
|
+
function isCompletedAction(text) {
|
|
216
|
+
if (!text) return false;
|
|
217
|
+
const lower = text.toLowerCase();
|
|
218
|
+
const DONE_SIGNALS = ['cancellato con successo','eliminato con successo','evento eliminato',
|
|
219
|
+
'evento cancellato','deleted successfully','removed successfully','email inviata','email sent',
|
|
220
|
+
'draft created','bozza creata','aggiornato con successo','updated successfully',
|
|
221
|
+
'task completato','task done','creato con successo','created successfully',
|
|
222
|
+
'spostato con successo','moved successfully'];
|
|
223
|
+
return DONE_SIGNALS.some(s => lower.includes(s));
|
|
224
|
+
}
|
|
225
|
+
|
|
182
226
|
async function callAgentWithTools(config, agentName, userMessage, languageOverride, preHistory) {
|
|
183
227
|
const today = new Date().toISOString().split('T')[0];
|
|
184
228
|
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
185
229
|
const locale = Intl.DateTimeFormat().resolvedOptions().locale || 'en';
|
|
186
230
|
const LANG_MAP = { en: 'English', it: 'Italian', es: 'Spanish', fr: 'French', de: 'German', pt: 'Portuguese', nl: 'Dutch', pl: 'Polish', ru: 'Russian', ja: 'Japanese', ko: 'Korean', zh: 'Chinese', ar: 'Arabic', hi: 'Hindi', tr: 'Turkish' };
|
|
187
|
-
// Priority: explicit override (from message text detection) → config setting → system locale
|
|
188
231
|
const language = languageOverride || config?.profile?.language || config?.language || LANG_MAP[locale.split('-')[0]] || 'English';
|
|
189
232
|
|
|
190
233
|
// Use compact Liara prompt when provider is 'nha' (same logic as buildSystemPrompt)
|
|
@@ -194,17 +237,19 @@ async function callAgentWithTools(config, agentName, userMessage, languageOverri
|
|
|
194
237
|
.replace('{{TODAY}}', today)
|
|
195
238
|
.replace('{{TIMEZONE}}', tz)
|
|
196
239
|
.replace(/\{\{LANGUAGE\}\}/g, language) +
|
|
197
|
-
//
|
|
198
|
-
'\n\
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
240
|
+
// Telegram context: execute destructive actions immediately, user already confirmed via chat
|
|
241
|
+
'\n\nTELEGRAM BOT RULES:\n' +
|
|
242
|
+
'- Execute ALL actions (including delete, cancel, send) IMMEDIATELY when the user confirms. Never say "I need an ID" if you can search for the event yourself using calendar_find.\n' +
|
|
243
|
+
'- When you find an event with calendar_find, include its eventId in your reply so the user sees it.\n' +
|
|
244
|
+
'- After completing an action, confirm it simply and clearly. Do not loop back asking for more info.\n' +
|
|
245
|
+
'- If the user says "procedi", "sì", "fallo", "cancellalo" etc. — they are confirming. Execute the action.\n' +
|
|
246
|
+
'- Never ask the user to provide an eventId manually — always search with calendar_find first.';
|
|
247
|
+
|
|
248
|
+
// preHistory: full conversation history from previous turn (for sticky confirmations)
|
|
202
249
|
const history = preHistory ? [...preHistory] : [];
|
|
203
250
|
let finalText = '';
|
|
204
|
-
let lastToolResults = null; // saved for context return
|
|
205
251
|
|
|
206
|
-
for (let round = 0; round <
|
|
207
|
-
// Build serialized message
|
|
252
|
+
for (let round = 0; round < 5; round++) {
|
|
208
253
|
const parts = history.map(h => (h.role === 'user' ? '[User]' : '[Assistant]') + ' ' + h.content);
|
|
209
254
|
parts.push('[User] ' + userMessage);
|
|
210
255
|
const serialized = parts.join('\n\n');
|
|
@@ -213,31 +258,53 @@ async function callAgentWithTools(config, agentName, userMessage, languageOverri
|
|
|
213
258
|
const { textParts, actions } = parseActions(response);
|
|
214
259
|
|
|
215
260
|
if (actions.length === 0) {
|
|
216
|
-
// Final round — no tools, just text for the user
|
|
217
261
|
finalText = textParts.join('\n').trim();
|
|
218
262
|
break;
|
|
219
263
|
}
|
|
220
|
-
// Intermediate round with tools — don't expose tool-call JSON to the user
|
|
221
264
|
|
|
222
|
-
// Execute all tools
|
|
265
|
+
// Execute all tools
|
|
223
266
|
const toolResults = [];
|
|
267
|
+
let authError = null;
|
|
224
268
|
for (const { action, params } of actions) {
|
|
225
269
|
try {
|
|
226
270
|
const result = await executeTool(action, params, config);
|
|
227
271
|
const resultStr = typeof result === 'string' ? result : JSON.stringify(result);
|
|
228
272
|
toolResults.push(`[${action}] ${resultStr}`);
|
|
229
273
|
} catch (err) {
|
|
274
|
+
// Detect Google/Microsoft OAuth token expiry — give user a clear fix instruction
|
|
275
|
+
const msg = err.message || '';
|
|
276
|
+
const isAuthErr = /invalid.?credentials|token.*expired|unauthorized|401|invalid_grant|auth.*failed|authentication.*failed/i.test(msg);
|
|
277
|
+
if (isAuthErr) {
|
|
278
|
+
authError = action.startsWith('gmail') || action.startsWith('imap') || action.startsWith('calendar') || action.startsWith('contact') || action.startsWith('drive') || action.startsWith('gtask')
|
|
279
|
+
? 'google' : 'microsoft';
|
|
280
|
+
}
|
|
230
281
|
toolResults.push(`[${action}] Error: ${err.message}`);
|
|
231
282
|
}
|
|
232
283
|
}
|
|
233
|
-
lastToolResults = toolResults;
|
|
234
284
|
|
|
235
|
-
//
|
|
285
|
+
// If auth error detected, return a user-friendly message immediately — don't pass to LLM
|
|
286
|
+
if (authError === 'google') {
|
|
287
|
+
return {
|
|
288
|
+
text: language === 'Italian'
|
|
289
|
+
? 'Il token Google è scaduto. Esegui questo comando sul tuo computer per rinnovarlo:\n\nnha google auth\n\nDopo il login si rinnova tutto automaticamente.'
|
|
290
|
+
: 'Your Google token has expired. Run this command on your computer to renew it:\n\nnha google auth\n\nAfter logging in everything will work again.',
|
|
291
|
+
history,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
if (authError === 'microsoft') {
|
|
295
|
+
return {
|
|
296
|
+
text: language === 'Italian'
|
|
297
|
+
? 'Il token Microsoft è scaduto. Esegui:\n\nnha microsoft auth'
|
|
298
|
+
: 'Your Microsoft token has expired. Run:\n\nnha microsoft auth',
|
|
299
|
+
history,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
236
303
|
history.push({ role: 'assistant', content: response });
|
|
237
|
-
userMessage = 'Tool results:\n' + toolResults.join('\n') + '\n\nNow give the user a
|
|
304
|
+
userMessage = 'Tool results:\n' + toolResults.join('\n') + '\n\nNow give the user a short, clear confirmation in ' + language + '. Be direct — no preamble, no HERALD format. If an action was completed, say so clearly.';
|
|
238
305
|
}
|
|
239
306
|
|
|
240
|
-
return { text: finalText || '
|
|
307
|
+
return { text: finalText || 'Fatto.', history };
|
|
241
308
|
}
|
|
242
309
|
|
|
243
310
|
// ── Telegram Bot (Long Polling via native fetch) ─────────────────────────────
|
|
@@ -582,37 +649,33 @@ class TelegramResponder {
|
|
|
582
649
|
|
|
583
650
|
this.pendingRequests++;
|
|
584
651
|
try {
|
|
585
|
-
// Sticky agent: for short/ambiguous messages (< 6 words), reuse last agent for this chat
|
|
586
|
-
// This handles confirmation messages like "Sì", "Ok", "Fallo", "Confermo", etc.
|
|
587
|
-
const wordCount = cleanText.trim().split(/\s+/).length;
|
|
588
|
-
const isAmbiguous = wordCount <= 5;
|
|
589
652
|
const lastCtx = this._lastContextByChatId[chatId];
|
|
590
653
|
const stickyAge = lastCtx ? (Date.now() - lastCtx.ts) : Infinity;
|
|
591
|
-
const
|
|
654
|
+
const withinStickyWindow = stickyAge < 5 * 60 * 1000; // 5 min
|
|
655
|
+
|
|
656
|
+
// Determine if this message is a continuation of the previous turn
|
|
657
|
+
// (confirmation, reaction, short reply) vs a new independent request
|
|
658
|
+
const isContinuation = withinStickyWindow && isContinuationMessage(cleanText, lastCtx);
|
|
659
|
+
|
|
660
|
+
// If last response was a completed action, don't carry history forward —
|
|
661
|
+
// the next message is a fresh request even if it looks like a reaction
|
|
662
|
+
const lastWasCompleted = lastCtx && isCompletedAction(lastCtx.agentReply);
|
|
592
663
|
|
|
593
664
|
let agent;
|
|
594
665
|
let enrichedMessage = cleanText;
|
|
595
666
|
let preHistory = null;
|
|
596
|
-
|
|
667
|
+
|
|
668
|
+
if (isContinuation && !lastWasCompleted) {
|
|
669
|
+
// Continue with same agent and inject full history for context
|
|
597
670
|
agent = lastCtx.agent;
|
|
598
|
-
// Re-inject the full conversation history from last turn so the agent
|
|
599
|
-
// has the eventId/context already and can proceed directly (e.g. calendar_delete)
|
|
600
671
|
if (lastCtx.history && lastCtx.history.length > 0) {
|
|
601
672
|
preHistory = lastCtx.history;
|
|
602
|
-
} else {
|
|
603
|
-
// Fallback: text-only context injection
|
|
604
|
-
const nl = '\n';
|
|
605
|
-
enrichedMessage =
|
|
606
|
-
'[Conversazione precedente]' + nl +
|
|
607
|
-
'Utente: ' + lastCtx.userMsg + nl +
|
|
608
|
-
'Tu (' + agent.toUpperCase() + '): ' + lastCtx.agentReply.slice(0, 600) + nl + nl +
|
|
609
|
-
'[Nuovo messaggio utente — è una conferma/risposta al turno precedente]' + nl +
|
|
610
|
-
cleanText;
|
|
611
673
|
}
|
|
612
|
-
this.log(`[Telegram] ${fromUser}:
|
|
674
|
+
this.log(`[Telegram] ${fromUser}: continuation → ${agent.toUpperCase()} (ctx ${Math.round(stickyAge/1000)}s ago, history=${preHistory ? preHistory.length : 0})`);
|
|
613
675
|
} else {
|
|
676
|
+
// Fresh request — route normally
|
|
614
677
|
agent = routeMessage(cleanText, this.autoRoute);
|
|
615
|
-
this.log(`[Telegram] ${fromUser}
|
|
678
|
+
this.log(`[Telegram] ${fromUser}: new request → ${agent.toUpperCase()}${isVoice ? ' [voice]' : ''}${lastWasCompleted ? ' [prev completed]' : ''}`);
|
|
616
679
|
}
|
|
617
680
|
|
|
618
681
|
// Broadcast event
|
|
@@ -625,48 +688,43 @@ class TelegramResponder {
|
|
|
625
688
|
// Send typing indicator
|
|
626
689
|
await this._telegramCall('sendChatAction', { chat_id: chatId, action: 'typing' });
|
|
627
690
|
|
|
628
|
-
//
|
|
629
|
-
// For sticky context, use lang from previous turn if current message is ambiguous
|
|
691
|
+
// Language: detect from message, fallback to previous turn's language
|
|
630
692
|
const detectedLang = detectLanguage(cleanText) || (lastCtx ? detectLanguage(lastCtx.userMsg) : null);
|
|
631
693
|
|
|
632
|
-
// Tool-capable agents use the full tool execution loop
|
|
633
|
-
// Pure reasoning/analysis agents use the simple callAgent (no tools)
|
|
634
694
|
const TOOL_AGENTS = new Set(['herald', 'hermes', 'edi', 'jarvis', 'flux', 'echo', 'mercury', 'pipe', 'navi', 'link', 'prometheus', 'tempest']);
|
|
635
695
|
let responseText;
|
|
636
696
|
let responseHistory = null;
|
|
697
|
+
|
|
637
698
|
if (TOOL_AGENTS.has(agent)) {
|
|
638
699
|
const result = await callAgentWithTools(this.config, agent, enrichedMessage, detectedLang, preHistory);
|
|
639
700
|
responseText = result.text;
|
|
640
701
|
responseHistory = result.history;
|
|
641
702
|
} else {
|
|
642
|
-
// For non-tool agents: inject language instruction into the message
|
|
643
703
|
const langInstruction = detectedLang ? `[Respond in ${detectedLang}] ` : '';
|
|
644
704
|
responseText = await callAgent(this.config, agent, langInstruction + enrichedMessage);
|
|
645
705
|
}
|
|
646
706
|
|
|
647
|
-
// Truncate
|
|
707
|
+
// Truncate to Telegram limit (4096 chars)
|
|
648
708
|
const truncated = responseText.length > 4000
|
|
649
709
|
? responseText.slice(0, 3950) + '\n\n... [truncated]'
|
|
650
710
|
: responseText;
|
|
651
711
|
|
|
652
|
-
// Save context
|
|
653
|
-
// Save full history so next confirmation turn has the eventId already resolved
|
|
712
|
+
// Save context — if action was completed, mark it so next turn starts fresh
|
|
654
713
|
this._lastContextByChatId[chatId] = {
|
|
655
714
|
agent,
|
|
656
715
|
userMsg: cleanText,
|
|
657
716
|
agentReply: responseText,
|
|
658
|
-
history: responseHistory,
|
|
717
|
+
history: isCompletedAction(responseText) ? null : responseHistory, // clear history after success
|
|
659
718
|
ts: Date.now(),
|
|
660
719
|
};
|
|
661
720
|
this._lastAgentByChatId[chatId] = agent;
|
|
662
721
|
|
|
663
|
-
// Send response as plain text — no parse_mode to avoid Markdown entity parse errors
|
|
664
722
|
await this._telegramCall('sendMessage', {
|
|
665
723
|
chat_id: chatId,
|
|
666
724
|
text: `[${agent.toUpperCase()}]\n\n${truncated}`,
|
|
667
725
|
});
|
|
668
726
|
|
|
669
|
-
this.log(`[Telegram] Responded to ${fromUser} via ${agent.toUpperCase()} (${responseText.length} chars)`);
|
|
727
|
+
this.log(`[Telegram] Responded to ${fromUser} via ${agent.toUpperCase()} (${responseText.length} chars)${isCompletedAction(responseText) ? ' [action completed — context reset]' : ''}`);
|
|
670
728
|
} catch (err) {
|
|
671
729
|
this.log(`[Telegram] Agent call failed: ${err.message}`);
|
|
672
730
|
// Send error message to user
|