nothumanallowed 14.1.25 → 14.1.26

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": "14.1.25",
3
+ "version": "14.1.26",
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 = '14.1.25';
8
+ export const VERSION = '14.1.26';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -11,23 +11,7 @@ import { NHA_DIR, AGENTS_DIR } from '../../constants.mjs';
11
11
  import { callLLM, callLLMStream, parseAgentFile } from '../../services/llm.mjs';
12
12
  import { webSearch, fetchUrl } from '../../services/web-tools.mjs';
13
13
 
14
- // ── Studio web tool definitions injected into agent system prompts ────────────
15
- const STUDIO_WEB_TOOLS = `
16
- You have access to two web tools. Use them to gather real data before writing your analysis.
17
-
18
- TOOL USAGE — output a JSON block wrapped in <tool_call> tags:
19
- <tool_call>{"tool": "web_search", "query": "your search query"}</tool_call>
20
- <tool_call>{"tool": "fetch_url", "url": "https://example.com/article"}</tool_call>
21
-
22
- Rules:
23
- - Use web_search to find relevant URLs, news, prices, data
24
- - Use fetch_url on specific URLs from search results to read full article content
25
- - You may call multiple tools (each on its own line)
26
- - After receiving <tool_response> results, write your complete analysis
27
- - NEVER fabricate data — only use what the tools return
28
- `;
29
-
30
- // Agents that should have web tool access in Studio
14
+ // Agents that get web data pre-fetched before their LLM call
31
15
  const WEB_TOOL_AGENTS = new Set([
32
16
  'WebSearchAgent', 'TravelAgent', 'mercury', 'MERCURY',
33
17
  'athena', 'ATHENA', 'oracle', 'ORACLE', 'cassandra', 'CASSANDRA',
@@ -35,49 +19,58 @@ const WEB_TOOL_AGENTS = new Set([
35
19
  ]);
36
20
 
37
21
  /**
38
- * Execute a single studio tool call. Returns a result string.
22
+ * Run a web search and return formatted results string.
39
23
  */
40
- async function executeStudioTool(toolCall) {
24
+ async function runWebSearch(query) {
41
25
  try {
42
- const { tool, query, url } = toolCall;
43
- if (tool === 'web_search') {
44
- if (!query) return 'ERROR: web_search requires a query parameter';
45
- const result = await webSearch(query);
46
- if (result.error) return `Search failed: ${result.message}`;
47
- const snippets = result.results
48
- .slice(0, 6)
49
- .map((r, i) => `[${i + 1}] ${r.title}\nURL: ${r.url}\n${r.snippet || ''}`)
50
- .join('\n\n');
51
- return `Search results for "${query}" (${result.resultCount} found):\n\n${snippets}`;
52
- }
53
- if (tool === 'fetch_url') {
54
- if (!url) return 'ERROR: fetch_url requires a url parameter';
55
- const result = await fetchUrl(url);
56
- if (result.error) return `Fetch failed: ${result.message}`;
57
- const titlePart = result.title ? `Title: ${result.title}\n\n` : '';
58
- const text = (result.body || '').slice(0, 5000);
59
- return `Content from ${url}:\n\n${titlePart}${text}`;
60
- }
61
- return `Unknown tool: ${tool}`;
26
+ const result = await webSearch(query);
27
+ if (result.error) return `Search failed: ${result.message}`;
28
+ const snippets = result.results
29
+ .slice(0, 6)
30
+ .map((r, i) => `[${i + 1}] ${r.title}\nURL: ${r.url}\n${r.snippet || ''}`)
31
+ .join('\n\n');
32
+ return `Search results for "${query}" (${result.resultCount} found):\n\n${snippets}`;
62
33
  } catch (e) {
63
- return `Tool execution error: ${e.message}`;
34
+ return `Search error: ${e.message}`;
64
35
  }
65
36
  }
66
37
 
67
38
  /**
68
- * Parse <tool_call> blocks from LLM output.
39
+ * Fetch a URL and return formatted content string.
69
40
  */
70
- function parseToolCalls(text) {
71
- const calls = [];
72
- const re = /<tool_call>([\s\S]*?)<\/tool_call>/g;
73
- let m;
74
- while ((m = re.exec(text)) !== null) {
75
- try {
76
- const parsed = JSON.parse(m[1].trim());
77
- if (parsed.tool) calls.push(parsed);
78
- } catch {}
41
+ async function runFetchUrl(url) {
42
+ try {
43
+ const result = await fetchUrl(url);
44
+ if (result.error) return `Fetch failed: ${result.message}`;
45
+ const titlePart = result.title ? `Title: ${result.title}\n\n` : '';
46
+ const text = (result.body || '').slice(0, 5000);
47
+ return `Content from ${url}:\n\n${titlePart}${text}`;
48
+ } catch (e) {
49
+ return `Fetch error: ${e.message}`;
79
50
  }
80
- return calls;
51
+ }
52
+
53
+ /**
54
+ * Extract search queries from a task string.
55
+ * Returns up to 3 queries covering different angles.
56
+ */
57
+ function extractSearchQueries(task, stepPrompt) {
58
+ const text = (stepPrompt || task).slice(0, 500);
59
+ // Primary query: the full task condensed
60
+ const primary = text.replace(/[^\w\s.,&/-]/g, ' ').replace(/\s+/g, ' ').trim().slice(0, 120);
61
+ const queries = [primary];
62
+
63
+ // Finance: add price + news queries
64
+ if (/gold|silver|oil|stock|bitcoin|crypto|eur|usd|etf|nasdaq|sp500|dow|tesla|apple|nvidia/i.test(text)) {
65
+ const ticker = text.match(/\b([A-Z]{2,5}|gold|silver|oil|bitcoin|ethereum)\b/i)?.[1] || '';
66
+ if (ticker) {
67
+ queries.push(`${ticker} price today 2025`);
68
+ queries.push(`${ticker} analyst forecast outlook 2025`);
69
+ }
70
+ }
71
+
72
+ // Dedup and cap at 3
73
+ return [...new Set(queries)].slice(0, 3);
81
74
  }
82
75
 
83
76
  export function register(router) {
@@ -233,62 +226,59 @@ export function register(router) {
233
226
  const userMessage = stepDef?.prompt || task;
234
227
 
235
228
  const useWebTools = WEB_TOOL_AGENTS.has(agent);
236
- const finalSystemPrompt = useWebTools
237
- ? systemPrompt + '\n\n' + STUDIO_WEB_TOOLS
229
+ let webDataBlock = '';
230
+
231
+ // Pre-fetch web data BEFORE the LLM call so the model writes with real facts
232
+ if (useWebTools) {
233
+ sse({ token: '[Raccolta dati web...]\n' });
234
+
235
+ const queries = extractSearchQueries(task, stepDef?.prompt);
236
+ const searchResults = await Promise.all(
237
+ queries.map(async (q) => {
238
+ sse({ token: `[Searching: "${q}"]\n` });
239
+ return { query: q, result: await runWebSearch(q) };
240
+ })
241
+ );
242
+
243
+ // From first search, extract up to 3 URLs and fetch their content
244
+ const firstResult = searchResults[0]?.result || '';
245
+ const urlMatches = [...firstResult.matchAll(/URL: (https?:\/\/[^\s\n]+)/g)]
246
+ .map((m) => m[1])
247
+ .slice(0, 3);
248
+
249
+ const fetchResults = await Promise.all(
250
+ urlMatches.map(async (url) => {
251
+ sse({ token: `[Fetching: ${url}]\n` });
252
+ return { url, content: await runFetchUrl(url) };
253
+ })
254
+ );
255
+
256
+ const searchBlock = searchResults
257
+ .map((s) => `### Search: "${s.query}"\n${s.result}`)
258
+ .join('\n\n---\n\n');
259
+
260
+ const fetchBlock = fetchResults.length > 0
261
+ ? fetchResults.map((f) => `### Full content: ${f.url}\n${f.content}`).join('\n\n---\n\n')
262
+ : '';
263
+
264
+ webDataBlock = `\n\n## REAL-TIME WEB DATA (use ONLY this data — do NOT invent prices or figures):\n\n${searchBlock}${fetchBlock ? '\n\n---\n\n' + fetchBlock : ''}`;
265
+
266
+ sse({ token: '\n' });
267
+ }
268
+
269
+ const finalSystemPrompt = webDataBlock
270
+ ? systemPrompt + webDataBlock
238
271
  : systemPrompt;
239
272
 
240
273
  let output = '';
241
274
  let tokensOut = 0;
242
275
 
243
- // Round 1: initial LLM call (may contain tool_call blocks)
244
276
  await callLLMStream(config, finalSystemPrompt, userMessage, (tok) => {
245
277
  output += tok;
246
278
  tokensOut += Math.ceil(tok.length / 4);
247
279
  sse({ token: tok });
248
280
  }, { max_tokens: 8192 });
249
281
 
250
- // Tool execution loop (max 2 rounds to prevent runaway)
251
- if (useWebTools) {
252
- for (let round = 0; round < 2; round++) {
253
- const toolCalls = parseToolCalls(output);
254
- if (toolCalls.length === 0) break;
255
-
256
- sse({ token: '\n\n' });
257
-
258
- // Execute all tool calls in parallel
259
- const toolResults = await Promise.all(
260
- toolCalls.map(async (tc) => {
261
- const label = tc.tool === 'web_search' ? `Searching: "${tc.query}"` : `Fetching: ${tc.url}`;
262
- sse({ token: `\n[${label}...] ` });
263
- const result = await executeStudioTool(tc);
264
- return { call: tc, result };
265
- })
266
- );
267
-
268
- // Build tool responses block
269
- const toolResponseBlock = toolResults
270
- .map(({ call, result }) => {
271
- const callJson = JSON.stringify(call);
272
- return `<tool_call>${callJson}</tool_call>\n<tool_response>${result}</tool_response>`;
273
- })
274
- .join('\n\n');
275
-
276
- // Round 2: synthesis with tool results
277
- const synthesisPrompt = `## TOOL RESULTS:\n${toolResponseBlock}\n\n## YOUR TASK:\n${userMessage}\n\nNow write your complete analysis using the real data from the tool results above. Do not emit more tool calls — write the final answer directly.`;
278
-
279
- sse({ token: '\n\n' });
280
- let synthOutput = '';
281
- await callLLMStream(config, finalSystemPrompt, synthesisPrompt, (tok) => {
282
- synthOutput += tok;
283
- tokensOut += Math.ceil(tok.length / 4);
284
- sse({ token: tok });
285
- }, { max_tokens: 8192 });
286
-
287
- output = synthOutput;
288
- break; // one tool round is sufficient
289
- }
290
- }
291
-
292
282
  clearInterval(keepalive);
293
283
  sse({ done: true, output, tokensOut });
294
284
  res.write('data: [DONE]\n\n');