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 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.132",
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.mjs",
8
- "nothumanallowed": "./bin/nha.mjs"
7
+ "nha": "./bin/nha",
8
+ "nothumanallowed": "./bin/nha"
9
9
  },
10
10
  "engines": {
11
11
  "node": ">=20.0.0"
@@ -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
- sendEv({ type: 'text', token: fullResponse });
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
- sendEv({ type: 'text', token });
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
- toolCall = JSON.parse(sanitizeToolJson(raw));
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.132';
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
 
@@ -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) {