nothumanallowed 13.5.195 → 13.5.196
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 +78 -46
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nothumanallowed",
|
|
3
|
-
"version": "13.5.
|
|
3
|
+
"version": "13.5.196",
|
|
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.196';
|
|
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,13 +258,11 @@ 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 = [];
|
|
224
267
|
for (const { action, params } of actions) {
|
|
225
268
|
try {
|
|
@@ -230,14 +273,12 @@ async function callAgentWithTools(config, agentName, userMessage, languageOverri
|
|
|
230
273
|
toolResults.push(`[${action}] Error: ${err.message}`);
|
|
231
274
|
}
|
|
232
275
|
}
|
|
233
|
-
lastToolResults = toolResults;
|
|
234
276
|
|
|
235
|
-
// Feed results back: append assistant response + tool results as next user turn
|
|
236
277
|
history.push({ role: 'assistant', content: response });
|
|
237
|
-
userMessage = 'Tool results:\n' + toolResults.join('\n') + '\n\nNow give the user a
|
|
278
|
+
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
279
|
}
|
|
239
280
|
|
|
240
|
-
return { text: finalText || '
|
|
281
|
+
return { text: finalText || 'Fatto.', history };
|
|
241
282
|
}
|
|
242
283
|
|
|
243
284
|
// ── Telegram Bot (Long Polling via native fetch) ─────────────────────────────
|
|
@@ -582,37 +623,33 @@ class TelegramResponder {
|
|
|
582
623
|
|
|
583
624
|
this.pendingRequests++;
|
|
584
625
|
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
626
|
const lastCtx = this._lastContextByChatId[chatId];
|
|
590
627
|
const stickyAge = lastCtx ? (Date.now() - lastCtx.ts) : Infinity;
|
|
591
|
-
const
|
|
628
|
+
const withinStickyWindow = stickyAge < 5 * 60 * 1000; // 5 min
|
|
629
|
+
|
|
630
|
+
// Determine if this message is a continuation of the previous turn
|
|
631
|
+
// (confirmation, reaction, short reply) vs a new independent request
|
|
632
|
+
const isContinuation = withinStickyWindow && isContinuationMessage(cleanText, lastCtx);
|
|
633
|
+
|
|
634
|
+
// If last response was a completed action, don't carry history forward —
|
|
635
|
+
// the next message is a fresh request even if it looks like a reaction
|
|
636
|
+
const lastWasCompleted = lastCtx && isCompletedAction(lastCtx.agentReply);
|
|
592
637
|
|
|
593
638
|
let agent;
|
|
594
639
|
let enrichedMessage = cleanText;
|
|
595
640
|
let preHistory = null;
|
|
596
|
-
|
|
641
|
+
|
|
642
|
+
if (isContinuation && !lastWasCompleted) {
|
|
643
|
+
// Continue with same agent and inject full history for context
|
|
597
644
|
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
645
|
if (lastCtx.history && lastCtx.history.length > 0) {
|
|
601
646
|
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
647
|
}
|
|
612
|
-
this.log(`[Telegram] ${fromUser}:
|
|
648
|
+
this.log(`[Telegram] ${fromUser}: continuation → ${agent.toUpperCase()} (ctx ${Math.round(stickyAge/1000)}s ago, history=${preHistory ? preHistory.length : 0})`);
|
|
613
649
|
} else {
|
|
650
|
+
// Fresh request — route normally
|
|
614
651
|
agent = routeMessage(cleanText, this.autoRoute);
|
|
615
|
-
this.log(`[Telegram] ${fromUser}
|
|
652
|
+
this.log(`[Telegram] ${fromUser}: new request → ${agent.toUpperCase()}${isVoice ? ' [voice]' : ''}${lastWasCompleted ? ' [prev completed]' : ''}`);
|
|
616
653
|
}
|
|
617
654
|
|
|
618
655
|
// Broadcast event
|
|
@@ -625,48 +662,43 @@ class TelegramResponder {
|
|
|
625
662
|
// Send typing indicator
|
|
626
663
|
await this._telegramCall('sendChatAction', { chat_id: chatId, action: 'typing' });
|
|
627
664
|
|
|
628
|
-
//
|
|
629
|
-
// For sticky context, use lang from previous turn if current message is ambiguous
|
|
665
|
+
// Language: detect from message, fallback to previous turn's language
|
|
630
666
|
const detectedLang = detectLanguage(cleanText) || (lastCtx ? detectLanguage(lastCtx.userMsg) : null);
|
|
631
667
|
|
|
632
|
-
// Tool-capable agents use the full tool execution loop
|
|
633
|
-
// Pure reasoning/analysis agents use the simple callAgent (no tools)
|
|
634
668
|
const TOOL_AGENTS = new Set(['herald', 'hermes', 'edi', 'jarvis', 'flux', 'echo', 'mercury', 'pipe', 'navi', 'link', 'prometheus', 'tempest']);
|
|
635
669
|
let responseText;
|
|
636
670
|
let responseHistory = null;
|
|
671
|
+
|
|
637
672
|
if (TOOL_AGENTS.has(agent)) {
|
|
638
673
|
const result = await callAgentWithTools(this.config, agent, enrichedMessage, detectedLang, preHistory);
|
|
639
674
|
responseText = result.text;
|
|
640
675
|
responseHistory = result.history;
|
|
641
676
|
} else {
|
|
642
|
-
// For non-tool agents: inject language instruction into the message
|
|
643
677
|
const langInstruction = detectedLang ? `[Respond in ${detectedLang}] ` : '';
|
|
644
678
|
responseText = await callAgent(this.config, agent, langInstruction + enrichedMessage);
|
|
645
679
|
}
|
|
646
680
|
|
|
647
|
-
// Truncate
|
|
681
|
+
// Truncate to Telegram limit (4096 chars)
|
|
648
682
|
const truncated = responseText.length > 4000
|
|
649
683
|
? responseText.slice(0, 3950) + '\n\n... [truncated]'
|
|
650
684
|
: responseText;
|
|
651
685
|
|
|
652
|
-
// Save context
|
|
653
|
-
// Save full history so next confirmation turn has the eventId already resolved
|
|
686
|
+
// Save context — if action was completed, mark it so next turn starts fresh
|
|
654
687
|
this._lastContextByChatId[chatId] = {
|
|
655
688
|
agent,
|
|
656
689
|
userMsg: cleanText,
|
|
657
690
|
agentReply: responseText,
|
|
658
|
-
history: responseHistory,
|
|
691
|
+
history: isCompletedAction(responseText) ? null : responseHistory, // clear history after success
|
|
659
692
|
ts: Date.now(),
|
|
660
693
|
};
|
|
661
694
|
this._lastAgentByChatId[chatId] = agent;
|
|
662
695
|
|
|
663
|
-
// Send response as plain text — no parse_mode to avoid Markdown entity parse errors
|
|
664
696
|
await this._telegramCall('sendMessage', {
|
|
665
697
|
chat_id: chatId,
|
|
666
698
|
text: `[${agent.toUpperCase()}]\n\n${truncated}`,
|
|
667
699
|
});
|
|
668
700
|
|
|
669
|
-
this.log(`[Telegram] Responded to ${fromUser} via ${agent.toUpperCase()} (${responseText.length} chars)`);
|
|
701
|
+
this.log(`[Telegram] Responded to ${fromUser} via ${agent.toUpperCase()} (${responseText.length} chars)${isCompletedAction(responseText) ? ' [action completed — context reset]' : ''}`);
|
|
670
702
|
} catch (err) {
|
|
671
703
|
this.log(`[Telegram] Agent call failed: ${err.message}`);
|
|
672
704
|
// Send error message to user
|