nothumanallowed 13.5.132 → 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 +57 -3
- 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
|
@@ -5708,12 +5708,49 @@ REGOLE CRITICHE:
|
|
|
5708
5708
|
const { callLLMVision } = await import('../services/llm.mjs');
|
|
5709
5709
|
const va = visionAttachments[0];
|
|
5710
5710
|
fullResponse = await callLLMVision(config, systemPrompt, userMsg, { base64: va.base64, mimeType: va.mimeType });
|
|
5711
|
-
|
|
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 });
|
|
5712
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>?
|
|
5713
5718
|
await callLLMStream(config, systemPrompt, userMsg, (token) => {
|
|
5714
5719
|
fullResponse += token;
|
|
5715
|
-
|
|
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
|
+
}
|
|
5716
5751
|
}, { max_tokens: 4096 });
|
|
5752
|
+
// Flush any remaining visible text after stream ends
|
|
5753
|
+
if (!_inTool && _toolBuf.trim()) sendEv({ type: 'text', token: _toolBuf });
|
|
5717
5754
|
}
|
|
5718
5755
|
} catch (llmErr) {
|
|
5719
5756
|
const errMsg = llmErr.message || String(llmErr);
|
|
@@ -5753,6 +5790,17 @@ REGOLE CRITICHE:
|
|
|
5753
5790
|
return out;
|
|
5754
5791
|
};
|
|
5755
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
|
+
|
|
5756
5804
|
const toolRegex = /<tool>([\s\S]*?)<\/tool>/g;
|
|
5757
5805
|
let toolMatch;
|
|
5758
5806
|
const toolResults = [];
|
|
@@ -5760,7 +5808,13 @@ REGOLE CRITICHE:
|
|
|
5760
5808
|
let toolCall;
|
|
5761
5809
|
try {
|
|
5762
5810
|
const raw = toolMatch[1].trim();
|
|
5763
|
-
|
|
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
|
+
}
|
|
5764
5818
|
} catch(e) {
|
|
5765
5819
|
sendEv({ type: 'tool', op: 'parse_error', path: '?', result: 'JSON malformato: ' + e.message.slice(0, 80) });
|
|
5766
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) {
|