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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nothumanallowed",
3
- "version": "13.5.195",
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.195';
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
- // For Telegram: user confirmation already happened in the chat execute destructive actions directly
198
- '\n\nIMPORTANT: This is a Telegram bot context. The user has already confirmed destructive actions (delete, send, etc.) via chat messages. Execute them immediately without asking again — do NOT describe what you are about to do, just do it and confirm when done.';
199
-
200
- // Multi-turn: serialize history as [User]/[Assistant] string (same pattern as chat.mjs)
201
- // preHistory: optional [{role, content}] from previous conversation turn (for sticky confirmations)
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 < 4; 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 and collect results
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 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.';
238
279
  }
239
280
 
240
- return { text: finalText || 'Done.', history, lastToolResults };
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 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);
592
637
 
593
638
  let agent;
594
639
  let enrichedMessage = cleanText;
595
640
  let preHistory = null;
596
- if (useStickyAgent) {
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}: 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})`);
613
649
  } else {
650
+ // Fresh request — route normally
614
651
  agent = routeMessage(cleanText, this.autoRoute);
615
- 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]' : ''}`);
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
- // Detect language from the message text overrides system locale
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 if too long for Telegram (4096 char limit)
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 for sticky agent continuity (next short message reuses this agent)
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