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 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.131",
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"
@@ -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 for autofix
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(' ❌ ' + _lastCrashError);
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
- // Skip node internals noise
5475
+ // Log everything else (including at-lines during crash) for visibility
5474
5476
  const l = raw.trim();
5475
- if (!l || l.startsWith('at ') || l.startsWith('(node:') || l === '^') return;
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 10s)
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
- const s = netMod.createConnection(freePort, '127.0.0.1');
5484
- s.on('connect', () => { s.destroy(); resolve(); });
5485
- s.on('error', () => {
5486
- s.destroy();
5487
- if (++attempts > 20) reject(new Error(_lastMissingModule ? "Cannot find module '" + _lastMissingModule + "'" : 'Server non risponde dopo 10s'));
5488
- else setTimeout(tryConnect, 500);
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
- 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 });
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
- 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
+ }
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
- 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
+ }
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.131';
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) {