nothumanallowed 13.2.71 → 13.2.73

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.2.71",
3
+ "version": "13.2.73",
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": {
@@ -2692,9 +2692,10 @@ export async function cmdUI(args) {
2692
2692
 
2693
2693
  // ── Fast keyword-based planning (no LLM call needed for common patterns) ──────
2694
2694
  const taskLow = task.toLowerCase();
2695
+ const hasPdf = !!(body.hasPdf) || /pdf|allegat|catalogo|scheda\s*tecnic|document/i.test(taskLow);
2695
2696
  const hasEmail = /email|mail|inbox|posta/i.test(taskLow);
2696
2697
  const hasCalendar = /calendar|agenda|calendari|eventi|schedule/i.test(taskLow);
2697
- const hasSearch = /cerca|search|notizie|news|ultime|latest|web|internet|tendenz|trend/i.test(taskLow);
2698
+ const hasSearch = /cerca|search|notizie|news|ultime|latest|web|internet|tendenz|trend|acquista|compra|dove\s+trovare|where\s+to\s+buy|similar|simile/i.test(taskLow);
2698
2699
  const hasCanvas = /html|dashboard|visua|report|grafico|chart/i.test(taskLow);
2699
2700
  const hasGitHub = /github|git|issue|pr|pull request/i.test(taskLow);
2700
2701
  const hasSlack = /slack|channel|messag/i.test(taskLow);
@@ -2726,13 +2727,23 @@ export async function cmdUI(args) {
2726
2727
  // Build plan directly from keywords — reliable, fast, no SENTINEL risk
2727
2728
  const buildKeywordPlan = () => {
2728
2729
  const steps = [];
2730
+ // PDF attachment: always read document first to extract specs/data before any web search
2731
+ if (hasPdf) {
2732
+ const pdfName = body.pdfName || 'documento allegato';
2733
+ steps.push({icon:'\u{1F4C4}',agent:'DocumentReaderAgent',label:it?'Leggi documento':'Read document',prompt:`Extract all technical specifications, model numbers, part codes, product names, manufacturer, dimensions, ratings, and any other key data from the attached document "${pdfName}". List every technical detail precisely.`});
2734
+ }
2729
2735
  if (hasEmail) steps.push({icon:'\u{1F4E7}',agent:'EmailAgent', label:it?'Controlla email':'Check emails', prompt:'Read the latest unread emails and identify urgent items, deadlines, and required actions'});
2730
2736
  if (hasCalendar) steps.push({icon:'\u{1F4C5}',agent:'CalendarAgent', label:it?'Rivedi calendario':'Review calendar', prompt:'Check today\'s events and identify any scheduling conflicts or important meetings'});
2731
2737
  if (hasGitHub) steps.push({icon:'\u{1F4BB}',agent:'GitHubAgent', label:'GitHub', prompt:'Read open issues and pull requests, identify what needs attention'});
2732
2738
  if (hasSlack) steps.push({icon:'\u{1F4AC}',agent:'SlackAgent', label:'Slack', prompt:'Check recent Slack messages and identify important conversations'});
2733
2739
  if (hasNotion) steps.push({icon:'\u{1F4DD}',agent:'NotionAgent', label:'Notion', prompt:'Search Notion for relevant pages and notes'});
2734
- if (hasSearch || hasReputation || (!hasEmail && !hasCalendar && !hasGitHub && !hasSlack)) {
2735
- steps.push({icon:'\u{1F50D}',agent:'WebSearchAgent',label:it?'Ricerca web':'Web search',prompt:searchQuery});
2740
+ // When PDF is present: always search web (to find where to buy, similar products etc.)
2741
+ // The search query will be refined at runtime using the extracted PDF specs as context
2742
+ if (hasPdf || hasSearch || hasReputation || (!hasEmail && !hasCalendar && !hasGitHub && !hasSlack)) {
2743
+ const searchPrompt = hasPdf
2744
+ ? (it ? 'Usando le specifiche tecniche estratte dal documento (codice prodotto, modello, costruttore, caratteristiche), cerca online dove acquistare il prodotto o articoli equivalenti. Usa i codici esatti dal documento come query di ricerca.' : 'Using the technical specifications extracted from the document (product code, model, manufacturer, specs), search online for where to buy this product or equivalent alternatives. Use exact codes from the document as search queries.')
2745
+ : searchQuery;
2746
+ steps.push({icon:'\u{1F50D}',agent:'WebSearchAgent',label:it?'Ricerca web':'Web search',prompt:searchPrompt});
2736
2747
  }
2737
2748
  // Specialist agents — can stack multiple
2738
2749
  if (hasSecurity) steps.push({icon:'\u{1F6E1}',agent:'cassandra', label:it?'CASSANDRA \u2014 Rischi sicurezza':'CASSANDRA \u2014 Security risks', prompt:'Analyze the collected data and identify security risks, vulnerabilities and concrete recommendations'});
@@ -2758,7 +2769,7 @@ export async function cmdUI(args) {
2758
2769
 
2759
2770
  // Use keyword plan directly — only fall back to LLM for genuinely ambiguous tasks
2760
2771
  const keywordSteps = buildKeywordPlan();
2761
- const taskIsComplex = !hasEmail && !hasCalendar && !hasSearch && !hasGitHub && !hasSlack && !hasBriefing && !hasStrategy && !hasReputation && !hasCode && !hasWriting && !hasData && keywordSteps.length <= 1;
2772
+ const taskIsComplex = !hasPdf && !hasEmail && !hasCalendar && !hasSearch && !hasGitHub && !hasSlack && !hasBriefing && !hasStrategy && !hasReputation && !hasCode && !hasWriting && !hasData && keywordSteps.length <= 1;
2762
2773
 
2763
2774
  try {
2764
2775
  let steps;
@@ -2840,7 +2851,79 @@ export async function cmdUI(args) {
2840
2851
  let toolData = '';
2841
2852
 
2842
2853
  // ── Fetch REAL data for each agent type ──────────────────────
2843
- if (agent === 'EmailAgent') {
2854
+ if (agent === 'DocumentReaderAgent') {
2855
+ // Extract text from attached PDF, then ask the LLM to structure it cleanly.
2856
+ // The structured output becomes context for all subsequent steps.
2857
+ sendToken('[Reading attached document...] ');
2858
+ let rawText = '';
2859
+ if (stepPdfBase64) {
2860
+ try {
2861
+ const b64 = stepPdfBase64.includes(',') ? stepPdfBase64.split(',')[1] : stepPdfBase64;
2862
+ const pdfBuffer = Buffer.from(b64, 'base64');
2863
+ rawText = extractTextFromPdf(pdfBuffer) || '';
2864
+ if (!rawText || rawText.length < 20) {
2865
+ // Scanned PDF — use vision OCR
2866
+ sendToken('[No text layer — using vision OCR...] ');
2867
+ try {
2868
+ rawText = await callLLMVision(config, stepPdfBase64, 'application/pdf',
2869
+ 'Extract ALL text from this document exactly as printed, preserving all numbers, codes, and values.');
2870
+ } catch (ve) { rawText = ''; }
2871
+ }
2872
+ } catch (e) { rawText = ''; }
2873
+ }
2874
+ if (!rawText) {
2875
+ sendToken('Could not extract text from the attached document.');
2876
+ clearInterval(keepalive);
2877
+ sendEvent({ done: true, usage: { input: 0, output: 0 } });
2878
+ res.end();
2879
+ logRequest(method, pathname, 200, Date.now() - start);
2880
+ return;
2881
+ }
2882
+ // Ask LLM to structure the raw extracted text into readable markdown
2883
+ sendToken('[Structuring document content...] ');
2884
+ const LANG_MAP_DOC = {en:'English',it:'Italian',es:'Spanish',fr:'French',de:'German',pt:'Portuguese',zh:'Chinese',ja:'Japanese',ar:'Arabic',hi:'Hindi',ru:'Russian',nl:'Dutch',pl:'Polish',tr:'Turkish',ko:'Korean',sv:'Swedish',da:'Danish',fi:'Finnish',no:'Norwegian',cs:'Czech'};
2885
+ const docLang = LANG_MAP_DOC[(config?.language||'it').toLowerCase().slice(0,2)] || 'Italian';
2886
+ const docSys = `You are a technical document analyst. Extract and structure the content of this document into clear, readable markdown. Respond in ${docLang}.
2887
+ Rules:
2888
+ - List ALL technical specifications with their exact values (codes, voltages, pressures, temperatures, dimensions, flow rates, etc.)
2889
+ - Use markdown headers (##), bullet points (-), and tables where appropriate
2890
+ - Do NOT invent, interpret, or add anything not present in the raw text
2891
+ - Include all product/part codes exactly as written
2892
+ - Keep all numeric values with their units`;
2893
+ const docUser = `Here is the raw text extracted from "${stepPdfName || 'document.pdf'}". Structure it into clean, readable markdown:\n\n${rawText.slice(0, 18000)}`;
2894
+ let structuredOutput = '';
2895
+ let inThink = false;
2896
+ try {
2897
+ await withTimeout(
2898
+ callLLMStream(config, docSys, docUser,
2899
+ (token) => {
2900
+ // Strip <think> blocks
2901
+ let buf = token;
2902
+ if (inThink) {
2903
+ const ci = buf.indexOf('</think>');
2904
+ if (ci >= 0) { buf = buf.slice(ci + 8); inThink = false; }
2905
+ else return;
2906
+ }
2907
+ const oi = buf.indexOf('<think>');
2908
+ if (oi >= 0) { buf = buf.slice(0, oi); inThink = true; }
2909
+ if (buf) { structuredOutput += buf; sendToken(buf); }
2910
+ },
2911
+ { max_tokens: 3000 }
2912
+ ),
2913
+ 90000
2914
+ );
2915
+ } catch (e) {
2916
+ // LLM failed — fall back to raw text
2917
+ structuredOutput = `## ${stepPdfName || 'Document'}\n\n${rawText.slice(0, 8000)}`;
2918
+ sendToken(structuredOutput);
2919
+ }
2920
+ clearInterval(keepalive);
2921
+ sendEvent({ done: true, usage: { input: Math.ceil(rawText.length / 4), output: Math.ceil(structuredOutput.length / 4) } });
2922
+ res.end();
2923
+ logRequest(method, pathname, 200, Date.now() - start);
2924
+ return;
2925
+
2926
+ } else if (agent === 'EmailAgent') {
2844
2927
  sendToken('[Reading emails...] ');
2845
2928
  try {
2846
2929
  const emails = await withTimeout(getUnreadImportant(config, 10), 'EmailAgent');
@@ -2863,7 +2946,23 @@ export async function cmdUI(args) {
2863
2946
  try {
2864
2947
  // Extract a concise search query from the step prompt
2865
2948
  let searchQuery = stepPrompt;
2866
- if (searchQuery.length > 120) {
2949
+
2950
+ // If context contains extracted PDF data, extract the best search query from it:
2951
+ // product codes, model numbers, part numbers etc. are better search terms than the task text.
2952
+ if (context && context.length > 50) {
2953
+ // Look for product codes: alphanumeric codes, e.g. 321k63, VFD-001, 4WE6D6X
2954
+ const codeMatch = context.match(/\b([A-Z0-9]{2,}[-\/]?[A-Z0-9]{2,}(?:[-\/][A-Z0-9]+)*)\b/g);
2955
+ const productCodes = codeMatch ? [...new Set(codeMatch)].slice(0, 3) : [];
2956
+ // Also grab manufacturer name if present
2957
+ const mfrMatch = context.match(/(?:Marca|Marchio|Manufacturer|Brand|Produttore|Costruttore)[:\s]+([A-Za-z0-9 &]{2,40})/i);
2958
+ const mfr = mfrMatch ? mfrMatch[1].trim() : '';
2959
+ if (productCodes.length > 0) {
2960
+ searchQuery = (mfr ? mfr + ' ' : '') + productCodes.join(' ') + ' buy acquista distributore';
2961
+ sendToken(`[Search query from document: "${searchQuery}"] `);
2962
+ }
2963
+ }
2964
+
2965
+ if (searchQuery.length > 120 && !context) {
2867
2966
  const keywordMatch = searchQuery.match(/(?:cerca|search|find|ricerca|notizie su|news about|latest on|aggiornamenti su)\s+(.{5,80}?)(?:\s+(?:e|and|per|for|poi|then)|$)/i);
2868
2967
  if (keywordMatch) {
2869
2968
  searchQuery = keywordMatch[1].trim();
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.2.71';
8
+ export const VERSION = '13.2.73';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -3532,7 +3532,13 @@ async function runStudio() {
3532
3532
  var taskForPlan = studioState.attachmentContext
3533
3533
  ? task + nl + nl + '[User has attached a file: ' + studioState.attachmentName + '. Agents will receive the full content.]'
3534
3534
  : task;
3535
- var planRes = await apiPost('/api/studio/plan', {task: taskForPlan});
3535
+ // Include PDF info in plan request so server can add DocumentReaderAgent step
3536
+ var planBody = {task: taskForPlan};
3537
+ if (studioState.attachmentContext && studioState.attachmentContext.indexOf('[ATTACHED PDF:') === 0) {
3538
+ planBody.hasPdf = true;
3539
+ planBody.pdfName = studioState.attachmentName || 'document.pdf';
3540
+ }
3541
+ var planRes = await apiPost('/api/studio/plan', planBody);
3536
3542
  if (!planRes || !planRes.steps || !planRes.steps.length) {
3537
3543
  studioLog('Studio', '&#9888;', 'Could not plan workflow. Check your LLM provider config.', 'error');
3538
3544
  studioState.running = false;