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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nothumanallowed",
3
- "version": "13.5.194",
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.194';
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
- const systemPrompt = TOOL_DEFINITIONS
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
- // Multi-turn: serialize history as [User]/[Assistant] string (same pattern as chat.mjs)
196
- // preHistory: optional [{role, content}] from previous conversation turn (for sticky confirmations)
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 < 4; 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 and collect results
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 concise confirmation in ' + language + '. Do NOT use HERALD format respond conversationally.';
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 || 'Done.', history, lastToolResults };
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 useStickyAgent = isAmbiguous && lastCtx && stickyAge < 5 * 60 * 1000; // sticky for 5 min
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
- if (useStickyAgent) {
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}: sticky agent ${agent.toUpperCase()} (ambiguous, last ctx ${Math.round(stickyAge/1000)}s ago, preHistory=${preHistory ? preHistory.length : 0} turns)`);
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} (chat ${chatId}): routed to ${agent.toUpperCase()}${isVoice ? ' [voice]' : ''}`);
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
- // Detect language from the message text overrides system locale
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 if too long for Telegram (4096 char limit)
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 for sticky agent continuity (next short message reuses this agent)
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