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 +1 -1
- package/src/constants.mjs +1 -1
- package/src/server/routes/webcraft.mjs +74 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nothumanallowed",
|
|
3
|
-
"version": "16.0.
|
|
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.
|
|
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
|
-
|
|
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,
|
|
3521
|
-
// content is unsalvageable. This is the LAST line
|
|
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
|
|
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
|
-
|
|
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).
|