nothumanallowed 16.0.27 → 16.0.28

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nothumanallowed",
3
- "version": "16.0.27",
3
+ "version": "16.0.28",
4
4
  "description": "Local AI assistant: 80 tools (Gmail, Calendar, Drive, GitHub, Slack, browser, code, files), 38 agents, visual workflows (Studio, AWF, WebCraft). Install with `npm i -g nothumanallowed`, run with `nha ui`. Free tier built-in (Liara), no API key required. Your data stays on your PC — OAuth tokens local, no cloud. Open-source MIT.",
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 = '16.0.27';
8
+ export const VERSION = '16.0.28';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -183,6 +183,23 @@ class SandboxManager {
183
183
  }
184
184
  emit({ type: 'status', msg: `Entry point: ${entryFile}` });
185
185
 
186
+ // Pre-flight: validate entry file with acorn. If the file content was
187
+ // corrupted by a partial LLM stream (HTTP error leaked as code), quarantine
188
+ // it with a minimal stub so the sandbox can still boot. The user sees a
189
+ // clear "re-generate from chat" message instead of a SyntaxError crash.
190
+ const entryAbs = path.join(projectDir, entryFile);
191
+ if (fs.existsSync(entryAbs)) {
192
+ try {
193
+ const entryContent = fs.readFileSync(entryAbs, 'utf-8');
194
+ const sanitized = _sanitizeGeneratedFile(entryFile, entryContent, path.basename(projectDir));
195
+ if (sanitized !== entryContent) {
196
+ emit({ type: 'warn', msg: `Entry file ${entryFile} was corrupted (likely partial stream / HTTP error leaked into content). Auto-quarantined — re-generate from chat.` });
197
+ fs.writeFileSync(entryAbs + '.corrupt-' + Date.now(), entryContent, 'utf-8');
198
+ fs.writeFileSync(entryAbs, sanitized, 'utf-8');
199
+ }
200
+ } catch {}
201
+ }
202
+
186
203
  // ── Phase 2: Dependencies (pre-scan + batch install) ──────────────────
187
204
  // Pre-scan the project source files for require()/import statements and
188
205
  // diff against package.json + node_modules. Install everything missing in
@@ -3484,17 +3501,27 @@ function _patchEntry(projectDir, entryFile, shimDir, port) {
3484
3501
  // file content. Those strings are short, don't start with valid syntax for
3485
3502
  // the file type, and contain specific marker phrases.
3486
3503
  const _LLM_ERROR_MARKERS = [
3504
+ // "Access denied" / "Access temporarily denied"
3487
3505
  /^\s*Access (temporarily )?denied/i,
3488
- /^\s*Rate ?limit(ed)?/i,
3506
+ // "Service Temporarily Unavailable", "Server Temporarily Unavailable",
3507
+ // "Temporarily Unavailable" — common nginx/cloudflare/CDN 503 responses
3508
+ /^\s*(Service |Server )?Temporarily Unavailable/i,
3489
3509
  /^\s*Service Unavailable/i,
3510
+ /^\s*Rate ?limit(ed)?/i,
3490
3511
  /^\s*Internal Server Error/i,
3491
3512
  /^\s*Too Many Requests/i,
3492
3513
  /^\s*Bad Gateway/i,
3493
3514
  /^\s*Gateway Timeout/i,
3515
+ /^\s*Request Timeout/i,
3516
+ /^\s*Not Found/i,
3517
+ /^\s*Forbidden\b/i,
3518
+ /^\s*Unauthorized\b/i,
3494
3519
  /^\s*<!DOCTYPE html.*Cloudflare/is,
3495
- /^\s*<html.*<title>.*(Access|Forbidden|Error)/is,
3496
- /^\s*\{?\s*"error"\s*:\s*"(rate.?limit|quota|unauthorized)"/i,
3520
+ /^\s*<html.*<title>.*(Access|Forbidden|Error|Unavailable|Cloudflare)/is,
3521
+ /^\s*\{?\s*"error"\s*:\s*"(rate.?limit|quota|unauthorized|temporarily)"/i,
3497
3522
  /^\s*error code:\s*\d{3,4}/i,
3523
+ // Standalone HTTP status phrase at the start of a code file is suspicious
3524
+ /^\s*(HTTP\/[\d.]+\s+)?[45]\d{2}\s+/,
3498
3525
  ];
3499
3526
 
3500
3527
  function _looksLikeLLMError(content) {
@@ -3517,17 +3544,25 @@ function _fallbackPackageJson(projectName) {
3517
3544
  }
3518
3545
 
3519
3546
  // Sanitize content before writing to disk. Rejects LLM error responses,
3520
- // repairs corrupt JSON manifests, and falls back to a minimal template when
3521
- // content is unsalvageable. This is the LAST line of defense between the LLM
3522
- // API and the user's filesystem.
3547
+ // repairs corrupt JSON manifests, validates JS/TS with acorn, and falls back
3548
+ // to a safe placeholder when content is unsalvageable. This is the LAST line
3549
+ // of defense between the LLM API and the user's filesystem.
3523
3550
  function _sanitizeGeneratedFile(name, content, projectName) {
3524
- const isJson = /\.json$/i.test(name) || name.endsWith('package.json');
3551
+ const ext = (name.split('.').pop() || '').toLowerCase();
3552
+ const isJson = ext === 'json' || name.endsWith('package.json');
3525
3553
  const isPkg = name === 'package.json' || name.endsWith('/package.json');
3554
+ const isCode = ['js', 'mjs', 'cjs', 'jsx', 'ts', 'tsx'].includes(ext);
3526
3555
 
3556
+ // Layer A: detect HTTP error responses leaked as file content
3527
3557
  if (_looksLikeLLMError(content)) {
3528
3558
  if (isPkg) return _fallbackPackageJson(projectName);
3529
- return '/* nha-webcraft: file content from LLM looked like an HTTP error response and was discarded. Re-generate from chat. */';
3559
+ if (isCode) {
3560
+ return `// nha-webcraft: this file's content from the LLM looked like an HTTP error response\n// (status leaked into stream — likely '${_extractLLMErrorHint(content)}').\n// File quarantined to keep the sandbox bootable. Re-generate from chat.\nmodule.exports = {};\n`;
3561
+ }
3562
+ return '<!-- nha-webcraft: LLM error response detected, content discarded. Re-generate from chat. -->';
3530
3563
  }
3564
+
3565
+ // Layer B: validate JSON files
3531
3566
  if (isJson) {
3532
3567
  try { JSON.parse(content); }
3533
3568
  catch {
@@ -3543,9 +3578,40 @@ function _sanitizeGeneratedFile(name, content, projectName) {
3543
3578
  }
3544
3579
  }
3545
3580
  }
3581
+
3582
+ // Layer C: validate JS/JSX/TS files with acorn — catches partial streams
3583
+ // that ended mid-token, leftover HTML/error text mixed with code, etc.
3584
+ if (isCode && content && content.trim()) {
3585
+ try {
3586
+ const parser = ext === 'jsx' || ext === 'tsx' ? acorn.Parser.extend(acornJsx()) : acorn;
3587
+ parser.parse(content, {
3588
+ ecmaVersion: 'latest',
3589
+ sourceType: ['mjs'].includes(ext) ? 'module' : (/\bimport\s+|\bexport\s+/.test(content) ? 'module' : 'script'),
3590
+ allowReturnOutsideFunction: true,
3591
+ allowAwaitOutsideFunction: true,
3592
+ allowHashBang: true,
3593
+ });
3594
+ } catch (parseErr) {
3595
+ // Only quarantine if the error is at the VERY START of the file —
3596
+ // this signals "the whole file is junk" (HTTP error / partial stream)
3597
+ // rather than a normal bug at line 50 that user can fix.
3598
+ const lineMatch = parseErr.message.match(/\((\d+):(\d+)\)/);
3599
+ const startsAtTop = !lineMatch || parseInt(lineMatch[1]) <= 2;
3600
+ if (startsAtTop && content.length < 5000) {
3601
+ return `// nha-webcraft: this file failed to parse near the start (${parseErr.message}).\n// Likely a partial/corrupted stream from the LLM. Quarantined to keep sandbox bootable.\n// Re-generate from chat.\nmodule.exports = {};\n`;
3602
+ }
3603
+ // Otherwise let it through — it's a real code bug, the user will see it
3604
+ }
3605
+ }
3606
+
3546
3607
  return content;
3547
3608
  }
3548
3609
 
3610
+ function _extractLLMErrorHint(content) {
3611
+ const head = content.slice(0, 200).replace(/\n/g, ' ').replace(/\s+/g, ' ').trim();
3612
+ return head.slice(0, 80) + (head.length > 80 ? '...' : '');
3613
+ }
3614
+
3549
3615
  // Authoritative list of modules covered by our offline-safe shims.
3550
3616
  // Used by both the pre-scan (to skip them from npm install) and the Tier 1
3551
3617
  // retry (to detect "missing module" stderr that's already covered by a shim).