nothumanallowed 13.5.131 → 13.5.133
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/bin/nha +36 -0
- package/package.json +3 -3
- package/src/commands/ui.mjs +93 -17
- package/src/constants.mjs +1 -1
- package/src/services/web-ui.mjs +1 -1
package/bin/nha
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* nha — NotHumanAllowed CLI
|
|
4
|
+
*
|
|
5
|
+
* Thin launcher for Legion (multi-agent deliberation) and PIF (agent social client).
|
|
6
|
+
* Zero dependencies. Downloads core files on first run to ~/.nha/.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* npx nha run "Compare React vs Vue for enterprise"
|
|
10
|
+
* npx nha agents
|
|
11
|
+
* npx nha pif register
|
|
12
|
+
* npx nha install nha-code-reviewer
|
|
13
|
+
* npx nha config set provider anthropic
|
|
14
|
+
* npx nha update
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { fileURLToPath, pathToFileURL } from 'url';
|
|
18
|
+
import path from 'path';
|
|
19
|
+
|
|
20
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
21
|
+
const __dirname = path.dirname(__filename);
|
|
22
|
+
|
|
23
|
+
// ── Node.js version gate ────────────────────────────────────────────────────
|
|
24
|
+
const major = parseInt(process.version.slice(1), 10);
|
|
25
|
+
if (major < 20) {
|
|
26
|
+
console.error(`\x1b[31mError: nha requires Node.js 20 or later (found ${process.version}).\x1b[0m`);
|
|
27
|
+
console.error('Install from https://nodejs.org');
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ── Launch CLI ──────────────────────────────────────────────────────────────
|
|
32
|
+
// On Windows, dynamic import() requires file:// URLs, not raw paths like C:\...
|
|
33
|
+
const cliPath = path.join(__dirname, '..', 'src', 'cli.mjs');
|
|
34
|
+
const cliUrl = pathToFileURL(cliPath).href;
|
|
35
|
+
const { main } = await import(cliUrl);
|
|
36
|
+
await main(process.argv.slice(2));
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nothumanallowed",
|
|
3
|
-
"version": "13.5.
|
|
3
|
+
"version": "13.5.133",
|
|
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": {
|
|
7
|
-
"nha": "./bin/nha
|
|
8
|
-
"nothumanallowed": "./bin/nha
|
|
7
|
+
"nha": "./bin/nha",
|
|
8
|
+
"nothumanallowed": "./bin/nha"
|
|
9
9
|
},
|
|
10
10
|
"engines": {
|
|
11
11
|
"node": ">=20.0.0"
|
package/src/commands/ui.mjs
CHANGED
|
@@ -5445,48 +5445,70 @@ module.exports = { validateEmail, sanitizeText, validatePassword, validateUserna
|
|
|
5445
5445
|
|
|
5446
5446
|
let _lastMissingModule = null;
|
|
5447
5447
|
let _lastCrashError = null;
|
|
5448
|
+
let _stderrBuffer = ''; // accumulate full stderr for rich error context
|
|
5448
5449
|
proc.stdout.on('data', d => { const l = d.toString().trim(); if (l) sendLog(' [server] ' + l); });
|
|
5449
5450
|
proc.stderr.on('data', d => {
|
|
5450
5451
|
const raw = d.toString();
|
|
5452
|
+
_stderrBuffer += raw;
|
|
5451
5453
|
// MODULE_NOT_FOUND
|
|
5452
5454
|
const modMatch = raw.match(/Cannot find module '([^']+)'/);
|
|
5453
5455
|
if (modMatch) {
|
|
5454
5456
|
const missingMod = modMatch[1];
|
|
5455
5457
|
_lastMissingModule = missingMod;
|
|
5456
|
-
_lastCrashError = "Cannot find module '" + missingMod + "'";
|
|
5458
|
+
_lastCrashError = "Cannot find module '" + missingMod + "'\n" + _stderrBuffer.slice(0, 1500);
|
|
5457
5459
|
sendLog(' ❌ Modulo mancante: ' + missingMod);
|
|
5458
5460
|
if (!global._wcAutoFixQueue) global._wcAutoFixQueue = [];
|
|
5459
5461
|
global._wcAutoFixQueue.push({ type: 'module_not_found', module: missingMod, dir: sandboxDir, ts: Date.now() });
|
|
5460
5462
|
sendLog(' 🤖 Avvio auto-fix...');
|
|
5461
5463
|
return;
|
|
5462
5464
|
}
|
|
5463
|
-
// TypeError / SyntaxError / ReferenceError — capture
|
|
5465
|
+
// TypeError / SyntaxError / ReferenceError / Error — capture with full stack
|
|
5464
5466
|
const crashMatch = raw.match(/^(TypeError|SyntaxError|ReferenceError|Error):\s+(.+)/m);
|
|
5465
5467
|
if (crashMatch) {
|
|
5466
|
-
_lastCrashError = crashMatch[1] + ': ' + crashMatch[2].trim();
|
|
5467
|
-
sendLog(' ❌ ' +
|
|
5468
|
+
_lastCrashError = crashMatch[1] + ': ' + crashMatch[2].trim() + '\n' + _stderrBuffer.slice(0, 1500);
|
|
5469
|
+
sendLog(' ❌ ' + crashMatch[1] + ': ' + crashMatch[2].trim());
|
|
5468
5470
|
if (!global._wcAutoFixQueue) global._wcAutoFixQueue = [];
|
|
5469
5471
|
global._wcAutoFixQueue.push({ type: 'crash_error', error: _lastCrashError, dir: sandboxDir, ts: Date.now() });
|
|
5470
5472
|
sendLog(' 🤖 Avvio auto-fix...');
|
|
5471
5473
|
return;
|
|
5472
5474
|
}
|
|
5473
|
-
//
|
|
5475
|
+
// Log everything else (including at-lines during crash) for visibility
|
|
5474
5476
|
const l = raw.trim();
|
|
5475
|
-
if (!l || l.startsWith('
|
|
5477
|
+
if (!l || l.startsWith('(node:') || l === '^') return;
|
|
5476
5478
|
sendLog(' [server] ' + l);
|
|
5477
5479
|
});
|
|
5478
5480
|
|
|
5479
|
-
// Wait for server to be ready (max
|
|
5481
|
+
// Wait for server to be ready (max 30s).
|
|
5482
|
+
// If process exits early (crash), report the error immediately so the client
|
|
5483
|
+
// can trigger auto-fix while the SSE connection is still open.
|
|
5480
5484
|
await new Promise((resolve, reject) => {
|
|
5481
5485
|
let attempts = 0;
|
|
5486
|
+
const MAX_ATTEMPTS = 60; // 30s at 500ms intervals
|
|
5487
|
+
let crashed = false;
|
|
5488
|
+
|
|
5489
|
+
proc.on('exit', (code) => {
|
|
5490
|
+
if (crashed || code === 0) return;
|
|
5491
|
+
crashed = true;
|
|
5492
|
+
// Give stderr a moment to flush
|
|
5493
|
+
setTimeout(() => {
|
|
5494
|
+
const errMsg = _lastCrashError || (_lastMissingModule ? "Cannot find module '" + _lastMissingModule + "'" : 'Server crashed (exit code ' + code + ')');
|
|
5495
|
+
reject(new Error(errMsg));
|
|
5496
|
+
}, 300);
|
|
5497
|
+
});
|
|
5498
|
+
|
|
5482
5499
|
const tryConnect = () => {
|
|
5483
|
-
|
|
5484
|
-
|
|
5485
|
-
|
|
5486
|
-
|
|
5487
|
-
|
|
5488
|
-
|
|
5489
|
-
|
|
5500
|
+
if (crashed) return;
|
|
5501
|
+
const s = netMod.createConnection(freePort, '127.0.0.1');
|
|
5502
|
+
s.on('connect', () => { s.destroy(); resolve(); });
|
|
5503
|
+
s.on('error', () => {
|
|
5504
|
+
s.destroy();
|
|
5505
|
+
if (crashed) return;
|
|
5506
|
+
if (++attempts > MAX_ATTEMPTS) {
|
|
5507
|
+
reject(new Error(_lastCrashError || (_lastMissingModule ? "Cannot find module '" + _lastMissingModule + "'" : 'Server non risponde dopo 30s')));
|
|
5508
|
+
} else {
|
|
5509
|
+
setTimeout(tryConnect, 500);
|
|
5510
|
+
}
|
|
5511
|
+
});
|
|
5490
5512
|
};
|
|
5491
5513
|
setTimeout(tryConnect, 1000);
|
|
5492
5514
|
});
|
|
@@ -5686,12 +5708,49 @@ REGOLE CRITICHE:
|
|
|
5686
5708
|
const { callLLMVision } = await import('../services/llm.mjs');
|
|
5687
5709
|
const va = visionAttachments[0];
|
|
5688
5710
|
fullResponse = await callLLMVision(config, systemPrompt, userMsg, { base64: va.base64, mimeType: va.mimeType });
|
|
5689
|
-
|
|
5711
|
+
// Strip <tool>...</tool> blocks before sending text to client
|
|
5712
|
+
const visibleText = fullResponse.replace(/<tool>[\s\S]*?<\/tool>/g, '').trim();
|
|
5713
|
+
if (visibleText) sendEv({ type: 'text', token: visibleText });
|
|
5690
5714
|
} else {
|
|
5715
|
+
// Stream tokens but suppress <tool>...</tool> blocks from the visible text
|
|
5716
|
+
let _toolBuf = ''; // accumulates content inside a <tool> block
|
|
5717
|
+
let _inTool = false; // are we inside a <tool>...</tool>?
|
|
5691
5718
|
await callLLMStream(config, systemPrompt, userMsg, (token) => {
|
|
5692
5719
|
fullResponse += token;
|
|
5693
|
-
|
|
5720
|
+
// Feed token through tool-suppression filter
|
|
5721
|
+
let remaining = (_inTool ? '' : '') + token; // process token char by char via buffer
|
|
5722
|
+
// Simpler approach: buffer until we can decide
|
|
5723
|
+
_toolBuf += token;
|
|
5724
|
+
// Flush visible text up to next <tool> opening
|
|
5725
|
+
while (true) {
|
|
5726
|
+
if (!_inTool) {
|
|
5727
|
+
const toolStart = _toolBuf.indexOf('<tool>');
|
|
5728
|
+
if (toolStart === -1) {
|
|
5729
|
+
// No tool block opening — safe to send everything except possible partial '<tool' at end
|
|
5730
|
+
const safeEnd = _toolBuf.length - 6; // keep last 6 chars in case '<tool>' spans tokens
|
|
5731
|
+
if (safeEnd > 0) {
|
|
5732
|
+
sendEv({ type: 'text', token: _toolBuf.slice(0, safeEnd) });
|
|
5733
|
+
_toolBuf = _toolBuf.slice(safeEnd);
|
|
5734
|
+
}
|
|
5735
|
+
break;
|
|
5736
|
+
} else {
|
|
5737
|
+
// Flush text before the <tool>
|
|
5738
|
+
if (toolStart > 0) {
|
|
5739
|
+
sendEv({ type: 'text', token: _toolBuf.slice(0, toolStart) });
|
|
5740
|
+
}
|
|
5741
|
+
_toolBuf = _toolBuf.slice(toolStart);
|
|
5742
|
+
_inTool = true;
|
|
5743
|
+
}
|
|
5744
|
+
} else {
|
|
5745
|
+
const toolEnd = _toolBuf.indexOf('</tool>');
|
|
5746
|
+
if (toolEnd === -1) break; // still accumulating tool block
|
|
5747
|
+
_toolBuf = _toolBuf.slice(toolEnd + 7); // discard the </tool> and move on
|
|
5748
|
+
_inTool = false;
|
|
5749
|
+
}
|
|
5750
|
+
}
|
|
5694
5751
|
}, { max_tokens: 4096 });
|
|
5752
|
+
// Flush any remaining visible text after stream ends
|
|
5753
|
+
if (!_inTool && _toolBuf.trim()) sendEv({ type: 'text', token: _toolBuf });
|
|
5695
5754
|
}
|
|
5696
5755
|
} catch (llmErr) {
|
|
5697
5756
|
const errMsg = llmErr.message || String(llmErr);
|
|
@@ -5731,6 +5790,17 @@ REGOLE CRITICHE:
|
|
|
5731
5790
|
return out;
|
|
5732
5791
|
};
|
|
5733
5792
|
|
|
5793
|
+
// Second-pass JSON repair: fix missing commas between object properties
|
|
5794
|
+
// e.g. {"a":1\n"b":2} → {"a":1,"b":2}
|
|
5795
|
+
const repairJson = (raw) => {
|
|
5796
|
+
// Add missing commas between } or ] or "..." or number/bool/null and the next " or { or [
|
|
5797
|
+
return raw
|
|
5798
|
+
.replace(/([}\]"'0-9truefalsNullnull])\s*\n\s*(")/g, '$1,\n"') // after value, before next key
|
|
5799
|
+
.replace(/([}\]"'0-9truefalsNullnull])\s*\n\s*([{[])/g, '$1,\n$2') // after value, before { or [
|
|
5800
|
+
// Remove trailing commas before } or ]
|
|
5801
|
+
.replace(/,(\s*[}\]])/g, '$1');
|
|
5802
|
+
};
|
|
5803
|
+
|
|
5734
5804
|
const toolRegex = /<tool>([\s\S]*?)<\/tool>/g;
|
|
5735
5805
|
let toolMatch;
|
|
5736
5806
|
const toolResults = [];
|
|
@@ -5738,7 +5808,13 @@ REGOLE CRITICHE:
|
|
|
5738
5808
|
let toolCall;
|
|
5739
5809
|
try {
|
|
5740
5810
|
const raw = toolMatch[1].trim();
|
|
5741
|
-
|
|
5811
|
+
const sanitized = sanitizeToolJson(raw);
|
|
5812
|
+
try {
|
|
5813
|
+
toolCall = JSON.parse(sanitized);
|
|
5814
|
+
} catch(_) {
|
|
5815
|
+
// Second attempt: repair missing commas
|
|
5816
|
+
toolCall = JSON.parse(repairJson(sanitized));
|
|
5817
|
+
}
|
|
5742
5818
|
} catch(e) {
|
|
5743
5819
|
sendEv({ type: 'tool', op: 'parse_error', path: '?', result: 'JSON malformato: ' + e.message.slice(0, 80) });
|
|
5744
5820
|
continue;
|
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.5.
|
|
8
|
+
export const VERSION = '13.5.133';
|
|
9
9
|
export const BASE_URL = 'https://nothumanallowed.com/cli';
|
|
10
10
|
export const API_BASE = 'https://nothumanallowed.com/api/v1';
|
|
11
11
|
|
package/src/services/web-ui.mjs
CHANGED
|
@@ -9387,7 +9387,7 @@ async function wcFixSandboxError() {
|
|
|
9387
9387
|
try {
|
|
9388
9388
|
var ev4 = JSON.parse(line4);
|
|
9389
9389
|
if (ev4.type === 'text') { agentMsg.text += ev4.token; renderWebCraft(document.getElementById('content')); wcScrollChatToBottom(); }
|
|
9390
|
-
else if (ev4.type === 'tool') { agentMsg.tools.push({ op: ev4.op, path: ev4.path, result: ev4.result }); renderWebCraft(document.getElementById('content')); }
|
|
9390
|
+
else if (ev4.type === 'tool') { agentMsg.tools.push({ op: ev4.op, path: ev4.path, result: ev4.result, oldSnippet: ev4.oldSnippet || '', newSnippet: ev4.newSnippet || '' }); renderWebCraft(document.getElementById('content')); }
|
|
9391
9391
|
else if (ev4.type === 'done') {
|
|
9392
9392
|
wcChatRunning = false;
|
|
9393
9393
|
if (ev4.changed) {
|