nothumanallowed 13.5.194 → 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 +84 -47
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
|
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { callAgent, callLLM } from './llm.mjs';
|
|
12
|
-
import { buildSystemPrompt, parseActions, executeTool, TOOL_DEFINITIONS } from './tool-executor.mjs';
|
|
12
|
+
import { buildSystemPrompt, parseActions, executeTool, TOOL_DEFINITIONS, LIARA_TOOL_DEFINITIONS } from './tool-executor.mjs';
|
|
13
13
|
import https from 'https';
|
|
14
14
|
import http from 'http';
|
|
15
15
|
import { URL } from 'url';
|
|
@@ -179,27 +179,77 @@ 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)
|
|
234
|
+
const isLiara = config?.llm?.provider === 'nha';
|
|
235
|
+
const baseDefinitions = isLiara ? LIARA_TOOL_DEFINITIONS : TOOL_DEFINITIONS;
|
|
236
|
+
const systemPrompt = baseDefinitions
|
|
191
237
|
.replace('{{TODAY}}', today)
|
|
192
238
|
.replace('{{TIMEZONE}}', tz)
|
|
193
|
-
.replace(/\{\{LANGUAGE\}\}/g, language)
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
239
|
+
.replace(/\{\{LANGUAGE\}\}/g, language) +
|
|
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)
|
|
197
249
|
const history = preHistory ? [...preHistory] : [];
|
|
198
250
|
let finalText = '';
|
|
199
|
-
let lastToolResults = null; // saved for context return
|
|
200
251
|
|
|
201
|
-
for (let round = 0; round <
|
|
202
|
-
// Build serialized message
|
|
252
|
+
for (let round = 0; round < 5; round++) {
|
|
203
253
|
const parts = history.map(h => (h.role === 'user' ? '[User]' : '[Assistant]') + ' ' + h.content);
|
|
204
254
|
parts.push('[User] ' + userMessage);
|
|
205
255
|
const serialized = parts.join('\n\n');
|
|
@@ -208,13 +258,11 @@ async function callAgentWithTools(config, agentName, userMessage, languageOverri
|
|
|
208
258
|
const { textParts, actions } = parseActions(response);
|
|
209
259
|
|
|
210
260
|
if (actions.length === 0) {
|
|
211
|
-
// Final round — no tools, just text for the user
|
|
212
261
|
finalText = textParts.join('\n').trim();
|
|
213
262
|
break;
|
|
214
263
|
}
|
|
215
|
-
// Intermediate round with tools — don't expose tool-call JSON to the user
|
|
216
264
|
|
|
217
|
-
// Execute all tools
|
|
265
|
+
// Execute all tools
|
|
218
266
|
const toolResults = [];
|
|
219
267
|
for (const { action, params } of actions) {
|
|
220
268
|
try {
|
|
@@ -225,14 +273,12 @@ async function callAgentWithTools(config, agentName, userMessage, languageOverri
|
|
|
225
273
|
toolResults.push(`[${action}] Error: ${err.message}`);
|
|
226
274
|
}
|
|
227
275
|
}
|
|
228
|
-
lastToolResults = toolResults;
|
|
229
276
|
|
|
230
|
-
// Feed results back: append assistant response + tool results as next user turn
|
|
231
277
|
history.push({ role: 'assistant', content: response });
|
|
232
|
-
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.';
|
|
233
279
|
}
|
|
234
280
|
|
|
235
|
-
return { text: finalText || '
|
|
281
|
+
return { text: finalText || 'Fatto.', history };
|
|
236
282
|
}
|
|
237
283
|
|
|
238
284
|
// ── Telegram Bot (Long Polling via native fetch) ─────────────────────────────
|
|
@@ -577,37 +623,33 @@ class TelegramResponder {
|
|
|
577
623
|
|
|
578
624
|
this.pendingRequests++;
|
|
579
625
|
try {
|
|
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
626
|
const lastCtx = this._lastContextByChatId[chatId];
|
|
585
627
|
const stickyAge = lastCtx ? (Date.now() - lastCtx.ts) : Infinity;
|
|
586
|
-
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);
|
|
587
637
|
|
|
588
638
|
let agent;
|
|
589
639
|
let enrichedMessage = cleanText;
|
|
590
640
|
let preHistory = null;
|
|
591
|
-
|
|
641
|
+
|
|
642
|
+
if (isContinuation && !lastWasCompleted) {
|
|
643
|
+
// Continue with same agent and inject full history for context
|
|
592
644
|
agent = lastCtx.agent;
|
|
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
645
|
if (lastCtx.history && lastCtx.history.length > 0) {
|
|
596
646
|
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
647
|
}
|
|
607
|
-
this.log(`[Telegram] ${fromUser}:
|
|
648
|
+
this.log(`[Telegram] ${fromUser}: continuation → ${agent.toUpperCase()} (ctx ${Math.round(stickyAge/1000)}s ago, history=${preHistory ? preHistory.length : 0})`);
|
|
608
649
|
} else {
|
|
650
|
+
// Fresh request — route normally
|
|
609
651
|
agent = routeMessage(cleanText, this.autoRoute);
|
|
610
|
-
this.log(`[Telegram] ${fromUser}
|
|
652
|
+
this.log(`[Telegram] ${fromUser}: new request → ${agent.toUpperCase()}${isVoice ? ' [voice]' : ''}${lastWasCompleted ? ' [prev completed]' : ''}`);
|
|
611
653
|
}
|
|
612
654
|
|
|
613
655
|
// Broadcast event
|
|
@@ -620,48 +662,43 @@ class TelegramResponder {
|
|
|
620
662
|
// Send typing indicator
|
|
621
663
|
await this._telegramCall('sendChatAction', { chat_id: chatId, action: 'typing' });
|
|
622
664
|
|
|
623
|
-
//
|
|
624
|
-
// For sticky context, use lang from previous turn if current message is ambiguous
|
|
665
|
+
// Language: detect from message, fallback to previous turn's language
|
|
625
666
|
const detectedLang = detectLanguage(cleanText) || (lastCtx ? detectLanguage(lastCtx.userMsg) : null);
|
|
626
667
|
|
|
627
|
-
// Tool-capable agents use the full tool execution loop
|
|
628
|
-
// Pure reasoning/analysis agents use the simple callAgent (no tools)
|
|
629
668
|
const TOOL_AGENTS = new Set(['herald', 'hermes', 'edi', 'jarvis', 'flux', 'echo', 'mercury', 'pipe', 'navi', 'link', 'prometheus', 'tempest']);
|
|
630
669
|
let responseText;
|
|
631
670
|
let responseHistory = null;
|
|
671
|
+
|
|
632
672
|
if (TOOL_AGENTS.has(agent)) {
|
|
633
673
|
const result = await callAgentWithTools(this.config, agent, enrichedMessage, detectedLang, preHistory);
|
|
634
674
|
responseText = result.text;
|
|
635
675
|
responseHistory = result.history;
|
|
636
676
|
} else {
|
|
637
|
-
// For non-tool agents: inject language instruction into the message
|
|
638
677
|
const langInstruction = detectedLang ? `[Respond in ${detectedLang}] ` : '';
|
|
639
678
|
responseText = await callAgent(this.config, agent, langInstruction + enrichedMessage);
|
|
640
679
|
}
|
|
641
680
|
|
|
642
|
-
// Truncate
|
|
681
|
+
// Truncate to Telegram limit (4096 chars)
|
|
643
682
|
const truncated = responseText.length > 4000
|
|
644
683
|
? responseText.slice(0, 3950) + '\n\n... [truncated]'
|
|
645
684
|
: responseText;
|
|
646
685
|
|
|
647
|
-
// Save context
|
|
648
|
-
// 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
|
|
649
687
|
this._lastContextByChatId[chatId] = {
|
|
650
688
|
agent,
|
|
651
689
|
userMsg: cleanText,
|
|
652
690
|
agentReply: responseText,
|
|
653
|
-
history: responseHistory,
|
|
691
|
+
history: isCompletedAction(responseText) ? null : responseHistory, // clear history after success
|
|
654
692
|
ts: Date.now(),
|
|
655
693
|
};
|
|
656
694
|
this._lastAgentByChatId[chatId] = agent;
|
|
657
695
|
|
|
658
|
-
// Send response as plain text — no parse_mode to avoid Markdown entity parse errors
|
|
659
696
|
await this._telegramCall('sendMessage', {
|
|
660
697
|
chat_id: chatId,
|
|
661
698
|
text: `[${agent.toUpperCase()}]\n\n${truncated}`,
|
|
662
699
|
});
|
|
663
700
|
|
|
664
|
-
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]' : ''}`);
|
|
665
702
|
} catch (err) {
|
|
666
703
|
this.log(`[Telegram] Agent call failed: ${err.message}`);
|
|
667
704
|
// Send error message to user
|