nothumanallowed 16.0.26 → 16.0.27
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 +98 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nothumanallowed",
|
|
3
|
-
"version": "16.0.
|
|
3
|
+
"version": "16.0.27",
|
|
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.27';
|
|
9
9
|
export const BASE_URL = 'https://nothumanallowed.com/cli';
|
|
10
10
|
export const API_BASE = 'https://nothumanallowed.com/api/v1';
|
|
11
11
|
|
|
@@ -142,6 +142,34 @@ class SandboxManager {
|
|
|
142
142
|
return;
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
+
// ── Phase 0: Pre-flight repair ───────────────────────────────────────
|
|
146
|
+
// Before anything else, check that package.json on disk is valid JSON.
|
|
147
|
+
// If a previous LLM generation wrote a corrupt file (e.g. an HTTP error
|
|
148
|
+
// response leaked as content), Node's package_json_reader would crash
|
|
149
|
+
// with SyntaxError before we even get to load shims. Repair in place.
|
|
150
|
+
const pkgPath = path.join(projectDir, 'package.json');
|
|
151
|
+
if (fs.existsSync(pkgPath)) {
|
|
152
|
+
const pkgRaw = fs.readFileSync(pkgPath, 'utf-8');
|
|
153
|
+
let needsRepair = false;
|
|
154
|
+
let reason = '';
|
|
155
|
+
if (_looksLikeLLMError(pkgRaw)) {
|
|
156
|
+
needsRepair = true;
|
|
157
|
+
reason = 'content looks like an LLM API error response (Access denied / Rate limit / HTML block page)';
|
|
158
|
+
} else {
|
|
159
|
+
try { JSON.parse(pkgRaw); }
|
|
160
|
+
catch (e) { needsRepair = true; reason = `invalid JSON: ${e.message}`; }
|
|
161
|
+
}
|
|
162
|
+
if (needsRepair) {
|
|
163
|
+
emit({ type: 'warn', msg: `package.json corrupt (${reason}) — auto-repairing with minimal fallback so the sandbox can boot.` });
|
|
164
|
+
const projectName = path.basename(projectDir);
|
|
165
|
+
fs.writeFileSync(pkgPath, _fallbackPackageJson(projectName), 'utf-8');
|
|
166
|
+
emit({ type: 'status', msg: `package.json repaired. Original corrupt content backed up to package.json.corrupt-${Date.now()}` });
|
|
167
|
+
try {
|
|
168
|
+
fs.writeFileSync(pkgPath + '.corrupt-' + Date.now(), pkgRaw, 'utf-8');
|
|
169
|
+
} catch {}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
145
173
|
// ── Phase 1: Shims ────────────────────────────────────────────────────
|
|
146
174
|
emit({ type: 'phase', phase: 'shims', msg: 'Injecting runtime shims (pg, redis, mongoose, helmet...)' });
|
|
147
175
|
const shimDir = path.join(projectDir, '.nha-shims');
|
|
@@ -647,7 +675,8 @@ const ProjectStore = {
|
|
|
647
675
|
if (!_isSafePath(f.name)) continue;
|
|
648
676
|
const abs = path.join(dir, f.name);
|
|
649
677
|
ensureDir(path.dirname(abs));
|
|
650
|
-
|
|
678
|
+
const sanitized = _sanitizeGeneratedFile(f.name, f.content ?? '', projectName);
|
|
679
|
+
fs.writeFileSync(abs, sanitized, 'utf-8');
|
|
651
680
|
}
|
|
652
681
|
const meta = {
|
|
653
682
|
description: description ?? '',
|
|
@@ -3449,6 +3478,74 @@ function _patchEntry(projectDir, entryFile, shimDir, port) {
|
|
|
3449
3478
|
return '.nha-launcher.js';
|
|
3450
3479
|
}
|
|
3451
3480
|
|
|
3481
|
+
// Detect LLM API error responses that leaked into the file content. When the
|
|
3482
|
+
// LLM endpoint returns 4xx/5xx with a plaintext body (Cloudflare block, rate
|
|
3483
|
+
// limit, "Access temporarily denied"), the response often ends up saved as
|
|
3484
|
+
// file content. Those strings are short, don't start with valid syntax for
|
|
3485
|
+
// the file type, and contain specific marker phrases.
|
|
3486
|
+
const _LLM_ERROR_MARKERS = [
|
|
3487
|
+
/^\s*Access (temporarily )?denied/i,
|
|
3488
|
+
/^\s*Rate ?limit(ed)?/i,
|
|
3489
|
+
/^\s*Service Unavailable/i,
|
|
3490
|
+
/^\s*Internal Server Error/i,
|
|
3491
|
+
/^\s*Too Many Requests/i,
|
|
3492
|
+
/^\s*Bad Gateway/i,
|
|
3493
|
+
/^\s*Gateway Timeout/i,
|
|
3494
|
+
/^\s*<!DOCTYPE html.*Cloudflare/is,
|
|
3495
|
+
/^\s*<html.*<title>.*(Access|Forbidden|Error)/is,
|
|
3496
|
+
/^\s*\{?\s*"error"\s*:\s*"(rate.?limit|quota|unauthorized)"/i,
|
|
3497
|
+
/^\s*error code:\s*\d{3,4}/i,
|
|
3498
|
+
];
|
|
3499
|
+
|
|
3500
|
+
function _looksLikeLLMError(content) {
|
|
3501
|
+
if (typeof content !== 'string') return false;
|
|
3502
|
+
const head = content.slice(0, 500);
|
|
3503
|
+
return _LLM_ERROR_MARKERS.some(re => re.test(head));
|
|
3504
|
+
}
|
|
3505
|
+
|
|
3506
|
+
// Minimal package.json template — used when the LLM-generated one is corrupted.
|
|
3507
|
+
function _fallbackPackageJson(projectName) {
|
|
3508
|
+
return JSON.stringify({
|
|
3509
|
+
name: String(projectName || 'nha-project').toLowerCase().replace(/[^a-z0-9-]/g, '-'),
|
|
3510
|
+
version: '1.0.0',
|
|
3511
|
+
description: 'NHA-generated project (package.json auto-repaired)',
|
|
3512
|
+
main: 'index.js',
|
|
3513
|
+
type: 'commonjs',
|
|
3514
|
+
scripts: { start: 'node index.js' },
|
|
3515
|
+
dependencies: {},
|
|
3516
|
+
}, null, 2);
|
|
3517
|
+
}
|
|
3518
|
+
|
|
3519
|
+
// 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.
|
|
3523
|
+
function _sanitizeGeneratedFile(name, content, projectName) {
|
|
3524
|
+
const isJson = /\.json$/i.test(name) || name.endsWith('package.json');
|
|
3525
|
+
const isPkg = name === 'package.json' || name.endsWith('/package.json');
|
|
3526
|
+
|
|
3527
|
+
if (_looksLikeLLMError(content)) {
|
|
3528
|
+
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. */';
|
|
3530
|
+
}
|
|
3531
|
+
if (isJson) {
|
|
3532
|
+
try { JSON.parse(content); }
|
|
3533
|
+
catch {
|
|
3534
|
+
if (isPkg) return _fallbackPackageJson(projectName);
|
|
3535
|
+
try {
|
|
3536
|
+
const repaired = content
|
|
3537
|
+
.replace(/,\s*([}\]])/g, '$1')
|
|
3538
|
+
.replace(/([{,]\s*)([A-Za-z_][A-Za-z0-9_]*)\s*:/g, '$1"$2":');
|
|
3539
|
+
JSON.parse(repaired);
|
|
3540
|
+
return repaired;
|
|
3541
|
+
} catch {
|
|
3542
|
+
return '{}';
|
|
3543
|
+
}
|
|
3544
|
+
}
|
|
3545
|
+
}
|
|
3546
|
+
return content;
|
|
3547
|
+
}
|
|
3548
|
+
|
|
3452
3549
|
// Authoritative list of modules covered by our offline-safe shims.
|
|
3453
3550
|
// Used by both the pre-scan (to skip them from npm install) and the Tier 1
|
|
3454
3551
|
// retry (to detect "missing module" stderr that's already covered by a shim).
|