nothumanallowed 13.2.74 → 13.2.76

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.74",
3
+ "version": "13.2.76",
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": {
@@ -2883,14 +2883,20 @@ export async function cmdUI(args) {
2883
2883
  sendToken('[Structuring document content...] ');
2884
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
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}.
2886
+ // Put the raw PDF text in the SYSTEM prompt SENTINEL only scans the user message.
2887
+ // The user message is a short, safe instruction that won't trigger false positives.
2888
+ const docSys = `You are a technical document analyst. The following is the raw text extracted from the document "${stepPdfName || 'document.pdf'}". Your job is to structure it into clear, readable markdown. Respond in ${docLang}.
2889
+
2887
2890
  Rules:
2888
2891
  - List ALL technical specifications with their exact values (codes, voltages, pressures, temperatures, dimensions, flow rates, etc.)
2889
2892
  - Use markdown headers (##), bullet points (-), and tables where appropriate
2890
2893
  - Do NOT invent, interpret, or add anything not present in the raw text
2891
2894
  - 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)}`;
2895
+ - Keep all numeric values with their units
2896
+
2897
+ RAW DOCUMENT TEXT:
2898
+ ${rawText.slice(0, 18000)}`;
2899
+ const docUser = `Structure the document content above into clean, readable markdown with all technical specifications.`;
2894
2900
  let structuredOutput = '';
2895
2901
  let inThink = false;
2896
2902
  try {
@@ -2944,101 +2950,36 @@ Rules:
2944
2950
  } else if (agent === 'WebSearchAgent' || agent === 'ResearchAgent') {
2945
2951
  sendToken('[Searching the web and reading pages...] ');
2946
2952
  try {
2947
- // Extract a concise search query from the step prompt
2948
- let searchQuery = stepPrompt;
2949
-
2950
- // If context contains extracted PDF/document data, build a smart search query.
2951
- // Detect whether the task asks for "similar" products vs. exact product lookup.
2953
+ // If there is document context from a previous step, ask the LLM to derive
2954
+ // the optimal search queries. This is generic and works for any document/task.
2955
+ let searchQueries = [stepPrompt.slice(0, 120)];
2952
2956
  if (context && context.length > 50) {
2953
- const taskLow2 = task.toLowerCase();
2954
- const wantsSimilar = /simil|equivalen|alternativ|sostitut|find similar|alternative|replacement/i.test(taskLow2 + ' ' + stepPrompt);
2955
-
2956
- // Extract manufacturer name from structured context
2957
- const mfrMatch = context.match(/(?:Marca|Marchio|Manufacturer|Brand|Produttore|Costruttore|Parker|Bosch|Rexroth|SMC|Festo|Burkert|Norgren|Sirai|Asco)[:\s]*([A-Za-z0-9 &]{2,40})/i);
2958
- const mfr = mfrMatch ? mfrMatch[1].trim().split(/\s+/).slice(0,2).join(' ') : '';
2959
-
2960
- // Extract model/part code: 3-8 char alphanumeric, NOT common words/units
2961
- const IGNORE = new Set(['NBR','FKM','RUBY','BSP','PDF','URL','HTTP','THE','AND','FOR','MIN','MAX','BAR','VAC','KV','AC','DC','CE','UK','EU','ISO','DIN']);
2962
- const codeRx = /\b([A-Z][A-Z0-9]{2,7}(?:[-\/][A-Z0-9]{1,6})*)\b/g;
2963
- const allCodes = [];
2964
- let cm;
2965
- while ((cm = codeRx.exec(context)) !== null) {
2966
- const c = cm[1];
2967
- if (!IGNORE.has(c) && c.length >= 4 && /\d/.test(c)) allCodes.push(c);
2968
- }
2969
- const productCode = [...new Set(allCodes)][0] || '';
2970
-
2971
- // Extract key technical specs for similarity search
2972
- const pressMatch = context.match(/(?:pressione|pressure|MOPD)[^\d]*(\d+)\s*bar/i);
2973
- const pressure = pressMatch ? pressMatch[1] + ' bar' : '';
2974
- const portMatch = context.match(/(?:porta|port|attacco|fitting)[^\d]*(\d+\/\d+[''"]?)/i);
2975
- const port = portMatch ? portMatch[1] : '';
2976
- const typeMatch = context.match(/(?:valvola|valve|solenoid)[^,\n]{0,30}?(2\/2|3\/2|pilot.operated|normally.closed|normally.open)/i);
2977
- const valveType = typeMatch ? typeMatch[1] : '';
2978
-
2979
- if (wantsSimilar) {
2980
- // Task wants equivalent/similar product — search by specs, not by exact code
2981
- const specParts = [
2982
- valveType || 'solenoid valve',
2983
- pressure,
2984
- port,
2985
- 'hydraulic oil',
2986
- 'acquista distributore Italy'
2987
- ].filter(Boolean);
2988
- searchQuery = specParts.join(' ');
2989
- if (mfr && mfr.toLowerCase() !== productCode.toLowerCase()) searchQuery = mfr + ' ' + searchQuery;
2990
- sendToken(`[Search query (similar product): "${searchQuery}"] `);
2991
- } else if (productCode) {
2992
- // Task wants the exact product — search by code + manufacturer
2993
- searchQuery = (mfr ? mfr + ' ' : '') + productCode + ' acquista distributore';
2994
- sendToken(`[Search query (exact product): "${searchQuery}"] `);
2995
- }
2996
- }
2997
-
2998
- if (!context && searchQuery.length > 120) {
2999
- 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);
3000
- if (keywordMatch) {
3001
- searchQuery = keywordMatch[1].trim();
3002
- } else {
3003
- searchQuery = searchQuery.split(/[,\.\n]/)[0].slice(0, 100).trim();
3004
- }
3005
- }
3006
- // If step prompt / task contains a specific domain, fetch that page directly first
3007
- const domainMatch = (stepPrompt + ' ' + task).match(/(?:https?:\/\/)?(?:www\.)?([a-z0-9-]+\.[a-z]{2,}(?:\/[^\s,]*)?)/i);
3008
- if (domainMatch) {
3009
- let targetUrl = domainMatch[0];
3010
- if (!targetUrl.startsWith('http')) targetUrl = 'https://' + targetUrl;
3011
- sendToken(`[Fetching ${targetUrl}...] `);
2957
+ sendToken('[Building search queries from document...] ');
3012
2958
  try {
3013
- const fetchResult = await withTimeout(executeTool('fetch_url', { url: targetUrl }, config), 20000);
3014
- const fetchStr = typeof fetchResult === 'string' ? fetchResult : JSON.stringify(fetchResult);
3015
- if (fetchStr && !fetchStr.startsWith('HTTP ') && !fetchStr.startsWith('Content blocked')) {
3016
- toolData = `## Content from ${targetUrl}:\n${fetchStr}`;
2959
+ // Document context goes in system prompt SENTINEL only scans user message
2960
+ const queryPlanSys = `You are a search query generator. Given a document summary and a user task, output a JSON array of 1-3 concise web search queries (strings, max 80 chars each) that will find the best results. Output ONLY the JSON array, no explanation.\n\nDocument content:\n${context.slice(0, 3000)}`;
2961
+ const queryPlanUser = `User task: "${task.slice(0, 200)}". Generate search queries. If task asks for similar/alternative products use technical specs. If it asks where to buy include vendor queries. Output: ["query1","query2",...]`;
2962
+ const planConfig2 = Object.assign({}, config, { thinking: 'off' });
2963
+ const queryRaw = await withTimeout(callLLM(planConfig2, queryPlanSys, queryPlanUser, { max_tokens: 200 }), 15000);
2964
+ const jsonMatch = queryRaw.match(/\[[\s\S]*?\]/);
2965
+ if (jsonMatch) {
2966
+ const parsed = JSON.parse(jsonMatch[0]);
2967
+ if (Array.isArray(parsed) && parsed.length > 0) {
2968
+ searchQueries = parsed.filter(q => typeof q === 'string' && q.length > 2).slice(0, 3);
2969
+ }
3017
2970
  }
3018
2971
  } catch {}
2972
+ sendToken(`[Queries: ${searchQueries.map(q => '"' + q + '"').join(', ')}] `);
3019
2973
  }
3020
- // Primary search
3021
- const searchResult = await withTimeout(executeTool('web_search', { query: searchQuery, deep: true }, config), 25000);
3022
- const searchStr = typeof searchResult === 'string' ? searchResult : JSON.stringify(searchResult);
3023
- toolData += (toolData ? '\n\n' : '') + `## Web search results for "${searchQuery}":\n${searchStr}`;
3024
-
3025
- // Secondary search: if task mentions "similar" or "where to buy", run a second query for vendors
3026
- const taskLow3 = task.toLowerCase();
3027
- if (/simil|equivalen|alternativ|sostitut|rivendit|distributore|vend|acquist|compra|dove\s+trovare|where\s+to\s+buy/i.test(taskLow3) && context && context.length > 50) {
3028
- // Build vendor search query from manufacturer + product type
3029
- const mfrVendor = context.match(/(?:Parker|Bosch|Rexroth|SMC|Festo|Burkert|Norgren|Sirai|Asco)\s+[A-Za-z]*/i);
3030
- const mfrName = mfrVendor ? mfrVendor[0].trim() : '';
3031
- const typeVendor = context.match(/(?:valvola|valve|solenoid|elettrovalvola)[^,\n]{0,30}?(2\/2|pilot.operated|normally.closed)/i);
3032
- const typeStr = typeVendor ? typeVendor[1] : 'solenoid valve';
3033
- const pressVendor = context.match(/(\d+)\s*bar/i);
3034
- const pressStr = pressVendor ? pressVendor[1] + ' bar' : '';
3035
- const vendorQuery = [mfrName, typeStr, pressStr, 'rivenditore distributore acquisto'].filter(Boolean).join(' ');
3036
- sendToken(`[Search query (vendor/similar): "${vendorQuery}"] `);
2974
+
2975
+ // Run all queries sequentially, accumulate results
2976
+ for (let qi = 0; qi < searchQueries.length; qi++) {
2977
+ const q = searchQueries[qi];
3037
2978
  try {
3038
- const vendorResult = await withTimeout(executeTool('web_search', { query: vendorQuery, deep: true }, config), 20000);
3039
- const vendorStr = typeof vendorResult === 'string' ? vendorResult : JSON.stringify(vendorResult);
3040
- toolData += `\n\n## Vendor search results for "${vendorQuery}":\n${vendorStr}`;
3041
- } catch {}
2979
+ const searchResult = await withTimeout(executeTool('web_search', { query: q, deep: qi === 0 }, config), 25000);
2980
+ const searchStr = typeof searchResult === 'string' ? searchResult : JSON.stringify(searchResult);
2981
+ toolData += (toolData ? '\n\n' : '') + `## Web search: "${q}":\n${searchStr}`;
2982
+ } catch (e) { toolData += (toolData ? '\n\n' : '') + `## Search "${q}" failed: ${e.message}`; }
3042
2983
  }
3043
2984
  } catch (e) { toolData = toolData || `Web search failed: ${e.message}`; }
3044
2985
 
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.74';
8
+ export const VERSION = '13.2.76';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11