nothumanallowed 15.1.37 → 15.1.39

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": "15.1.37",
3
+ "version": "15.1.39",
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 = '15.1.37';
8
+ export const VERSION = '15.1.39';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -530,6 +530,37 @@ class TelegramResponder {
530
530
  saveTelegramContext(this._lastContextByChatId);
531
531
  }
532
532
 
533
+ /**
534
+ * Language-agnostic intent classifier (15.1.38).
535
+ *
536
+ * Previous versions hard-coded confirmation/reaction keywords for Italian
537
+ * and English. That couldn't scale — every new language ("schedule it"
538
+ * in Spanish, German, Japanese, Polish, Turkish, ...) would need a new
539
+ * keyword list. Now we delegate to a tiny LLM call: 16 tokens output,
540
+ * works in any human language the model knows.
541
+ *
542
+ * Fast path: if the keyword matcher already returned a confident verdict,
543
+ * skip the LLM. Only ambiguous cases (short messages, no obvious keyword)
544
+ * pay the ~50ms LLM cost.
545
+ *
546
+ * @returns 'continuation' | 'new_request' | 'unknown'
547
+ */
548
+ async _classifyIntent(text, lastCtx) {
549
+ if (!lastCtx) return 'new_request';
550
+ const hasKey = !!(this.config.llm?.apiKey || this.config.llm?.openaiKey || this.config.llm?.geminiKey || (this.config.llm?.provider === 'nha'));
551
+ if (!hasKey) return 'unknown';
552
+ try {
553
+ const prev = (lastCtx.agentReply || '').slice(0, 600);
554
+ const sys = 'You are a binary intent classifier for a chat assistant. Reply with EXACTLY one word: "continuation" or "new_request". Use "continuation" when the user is confirming, denying, reacting to, or refining the assistant\'s previous message (in any language). Use "new_request" when the user is starting an unrelated topic. Do not output anything else.';
555
+ const usr = `Assistant just said:\n"${prev}"\n\nUser now writes:\n"${text}"\n\nClassify the user\'s message: continuation or new_request?`;
556
+ const ans = await callLLM(this.config, sys, usr, { max_tokens: 8 });
557
+ const v = String(ans || '').trim().toLowerCase().replace(/[^a-z_]/g, '');
558
+ if (v === 'continuation' || v.startsWith('cont')) return 'continuation';
559
+ if (v === 'new_request' || v.startsWith('new')) return 'new_request';
560
+ return 'unknown';
561
+ } catch { return 'unknown'; }
562
+ }
563
+
533
564
  /**
534
565
  * Route a fresh message to the best agent.
535
566
  *
@@ -823,8 +854,27 @@ class TelegramResponder {
823
854
  const withinStickyWindow = stickyAge < 5 * 60 * 1000; // 5 min
824
855
 
825
856
  // Determine if this message is a continuation of the previous turn
826
- // (confirmation, reaction, short reply) vs a new independent request
827
- const isContinuation = withinStickyWindow && isContinuationMessage(cleanText, lastCtx);
857
+ // (confirmation, reaction, short reply) vs a new independent request.
858
+ //
859
+ // Two-stage detection:
860
+ // 1. Fast keyword path (IT + EN) — handles the obvious cases (~80%) with zero cost.
861
+ // 2. LLM classifier fallback — for ambiguous cases in any language
862
+ // ("agéndalo", "termin verschieben", "schedule it",
863
+ // "ya", "tamam", "harika", ...). Costs ~50 ms and 8 output tokens
864
+ // but is language-agnostic by design.
865
+ let isContinuation = withinStickyWindow && isContinuationMessage(cleanText, lastCtx);
866
+ if (withinStickyWindow && !isContinuation && lastCtx) {
867
+ // Only invoke the LLM if keyword fast-path didn't flag continuation
868
+ // AND the message is short enough to plausibly be a reaction (≤ 12 words).
869
+ const wordCount = cleanText.trim().split(/\s+/).length;
870
+ if (wordCount <= 12) {
871
+ const verdict = await this._classifyIntent(cleanText, lastCtx);
872
+ if (verdict === 'continuation') {
873
+ isContinuation = true;
874
+ this.log(`[Telegram] ${fromUser}: LLM classifier overrode keyword → continuation`);
875
+ }
876
+ }
877
+ }
828
878
 
829
879
  // If last response was a completed action, don't carry history forward —
830
880
  // the next message is a fresh request even if it looks like a reaction
@@ -294,8 +294,34 @@ TOOLS:
294
294
  40. slack_messages(channel: string, maxResults?: number)
295
295
  List recent messages in a Slack channel (name or ID). Default max 15.
296
296
 
297
- 41. slack_send(channel: string, text: string)
297
+ 41. slack_send(channel: string, text: string, threadTs?: string)
298
298
  Send a message to a Slack channel. ALWAYS confirm before sending.
299
+ If `threadTs` is provided, post as a thread reply instead of a top-level message.
300
+
301
+ 41b. slack_search(query: string, count?: number)
302
+ Full-text search messages across the whole Slack workspace. Returns the most
303
+ recent matches with channel, user, text and a Slack permalink each. Use this
304
+ for "find the message where X talked about Y", "trova il messaggio di Marco
305
+ su X", "did anyone post about the release", etc.
306
+
307
+ 41c. slack_dm(user: string, text?: string)
308
+ Open or send a direct message to a user. `user` accepts Slack user ID (Uxxx),
309
+ username, real name, or email — the tool resolves them automatically.
310
+ If `text` is provided, the message is sent immediately; otherwise the DM
311
+ channel is just opened.
312
+
313
+ 41d. slack_thread(channel: string, ts: string)
314
+ List all replies in a thread. `ts` is the parent message timestamp (returned
315
+ by slack_messages or slack_search). Use this before posting a contextual
316
+ reply with slack_send + threadTs.
317
+
318
+ 41e. slack_react(channel: string, ts: string, emoji: string)
319
+ Add an emoji reaction to a message. `emoji` is the name without colons
320
+ (e.g. "thumbsup", "rocket", "white_check_mark"). Confirm before reacting on
321
+ someone else's message.
322
+
323
+ 41f. slack_mark_read(channel: string, ts?: string)
324
+ Mark a Slack channel as read up to a given timestamp (default: now).
299
325
 
300
326
  --- FILE ATTACHMENT ---
301
327