nothumanallowed 14.1.25 → 14.1.27
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 +1 -1
- package/src/constants.mjs +1 -1
- package/src/server/routes/studio.mjs +86 -96
- package/src/ui-dist/assets/index-BRTO-LWg.css +1 -0
- package/src/ui-dist/assets/index-aUkY_NK0.js +640 -0
- package/src/ui-dist/index.html +2 -2
- package/src/ui-dist/assets/index-C9Tmwr7c.css +0 -1
- package/src/ui-dist/assets/index-yaE4fioY.js +0 -638
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nothumanallowed",
|
|
3
|
-
"version": "14.1.
|
|
3
|
+
"version": "14.1.27",
|
|
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.
|
|
8
|
+
export const VERSION = '14.1.27';
|
|
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
|
-
//
|
|
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
|
-
*
|
|
22
|
+
* Run a web search and return formatted results string.
|
|
39
23
|
*/
|
|
40
|
-
async function
|
|
24
|
+
async function runWebSearch(query) {
|
|
41
25
|
try {
|
|
42
|
-
const
|
|
43
|
-
if (
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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 `
|
|
34
|
+
return `Search error: ${e.message}`;
|
|
64
35
|
}
|
|
65
36
|
}
|
|
66
37
|
|
|
67
38
|
/**
|
|
68
|
-
*
|
|
39
|
+
* Fetch a URL and return formatted content string.
|
|
69
40
|
*/
|
|
70
|
-
function
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
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
|
-
|
|
237
|
-
|
|
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');
|