nothumanallowed 15.1.16 → 15.1.18
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": "15.1.
|
|
3
|
+
"version": "15.1.18",
|
|
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": {
|
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 = '15.1.
|
|
8
|
+
export const VERSION = '15.1.18';
|
|
9
9
|
export const BASE_URL = 'https://nothumanallowed.com/cli';
|
|
10
10
|
export const API_BASE = 'https://nothumanallowed.com/api/v1';
|
|
11
11
|
|
|
@@ -246,11 +246,10 @@ class SandboxManager {
|
|
|
246
246
|
if (this._stoppedByUser) return;
|
|
247
247
|
emit({ type: 'status', msg: `Process exited with code ${exitCode}` });
|
|
248
248
|
|
|
249
|
-
//
|
|
249
|
+
// ── Tier 1: missing module → npm install + retry ─────────────────────
|
|
250
250
|
const missingMatch = stderrBuf.match(/Cannot find module ['"]([^'"]+)['"]/);
|
|
251
251
|
if (missingMatch && _attempt < MAX_RETRIES) {
|
|
252
252
|
const missingMod = missingMatch[1];
|
|
253
|
-
// Skip shim-able or built-in modules
|
|
254
253
|
if (!missingMod.startsWith('.') && !missingMod.startsWith('/') && !missingMod.startsWith('node:')) {
|
|
255
254
|
const pkgName = missingMod.startsWith('@') ? missingMod.split('/').slice(0, 2).join('/') : missingMod.split('/')[0];
|
|
256
255
|
emit({ type: 'phase', phase: 'autofix', msg: `Missing module "${pkgName}" — installing...` });
|
|
@@ -268,6 +267,102 @@ class SandboxManager {
|
|
|
268
267
|
}
|
|
269
268
|
}
|
|
270
269
|
|
|
270
|
+
// ── Tier 2: runtime errors that need code fix (require/import mismatch,
|
|
271
|
+
// SyntaxError, ReferenceError, TypeError) — extract failing file from
|
|
272
|
+
// stack trace and ask LLM to repair it. This is the bug the user hit:
|
|
273
|
+
// "require is not defined" inside an ESM project should auto-rewrite
|
|
274
|
+
// require() → import statements, or flip package.json "type" field. ───
|
|
275
|
+
const runtimePatterns = [
|
|
276
|
+
{ name: 'CJS/ESM mismatch', re: /require is not defined/i,
|
|
277
|
+
hint: 'The file uses CommonJS `require()` but the project is ESM ("type":"module" in package.json or .mjs extension). Convert all `require(\'X\')` to ES module `import` statements. Convert `module.exports = ...` to `export default ...`. Keep all logic identical.' },
|
|
278
|
+
{ name: 'ESM/CJS mismatch', re: /Cannot use import statement outside a module/i,
|
|
279
|
+
hint: 'The file uses ES module `import` but the project is CommonJS. Either add `"type":"module"` to package.json (preferred for new projects) or convert imports to `require()`.' },
|
|
280
|
+
{ name: 'import.meta misuse', re: /import\.meta(?:\.url)? is only valid in/i,
|
|
281
|
+
hint: 'The file uses `import.meta` in a CommonJS context. Either switch the project to ESM (add `"type":"module"` to package.json) or replace `import.meta.url` with `__filename` / `__dirname`.' },
|
|
282
|
+
{ name: 'SyntaxError', re: /SyntaxError:.+/i,
|
|
283
|
+
hint: 'The file has a JavaScript syntax error. Fix the syntax issue — unclosed brackets, missing commas, invalid tokens. Output the complete corrected file.' },
|
|
284
|
+
{ name: 'ReferenceError', re: /ReferenceError: (\w+) is not defined/i,
|
|
285
|
+
hint: 'A variable is referenced but never declared/imported. Either import it from the correct module or declare it. Common missing globals: "require" (use import), "process" (Node only — add `import process from \'node:process\'` in ESM), "__dirname" (in ESM use `import.meta.url` + fileURLToPath).' },
|
|
286
|
+
{ name: 'TypeError null/undefined', re: /TypeError: Cannot read prop(?:erties|erty)? .+ of (?:undefined|null)/i,
|
|
287
|
+
hint: 'Null/undefined access. Add a null-check or optional chaining (?.) before the failing access.' },
|
|
288
|
+
];
|
|
289
|
+
|
|
290
|
+
const matchedPattern = runtimePatterns.find(p => p.re.test(stderrBuf));
|
|
291
|
+
if (matchedPattern && _attempt < MAX_RETRIES) {
|
|
292
|
+
// Extract the file path from the stack trace.
|
|
293
|
+
// Patterns: "at /abs/path/file.js:N:M", "at file:///abs/path/file.js:N:M",
|
|
294
|
+
// "at Object.<anonymous> (/path/file.js:N:M)", or simply at start of stderr.
|
|
295
|
+
const fileMatches = [...stderrBuf.matchAll(/(?:at\s+(?:[\w<>\.\s]+\s+)?\(?)?(?:file:\/\/)?(\/[^\s:()'"]+\.(?:m?js|cjs|jsx?|tsx?)):\d+(?::\d+)?/g)];
|
|
296
|
+
const projectFiles = fileMatches
|
|
297
|
+
.map(m => m[1])
|
|
298
|
+
.filter(p => p && p.startsWith(projectDir))
|
|
299
|
+
.map(p => path.relative(projectDir, p))
|
|
300
|
+
.filter((v, i, a) => a.indexOf(v) === i)
|
|
301
|
+
.slice(0, 3);
|
|
302
|
+
|
|
303
|
+
if (projectFiles.length > 0) {
|
|
304
|
+
emit({ type: 'phase', phase: 'autofix', msg: `Runtime error (${matchedPattern.name}) — repairing ${projectFiles.length} file(s)...` });
|
|
305
|
+
let anyFixed = false;
|
|
306
|
+
for (const rel of projectFiles) {
|
|
307
|
+
const abs = path.join(projectDir, rel);
|
|
308
|
+
let original = '';
|
|
309
|
+
try { original = fs.readFileSync(abs, 'utf-8'); } catch { continue; }
|
|
310
|
+
if (!original) continue;
|
|
311
|
+
|
|
312
|
+
// Build a focused repair prompt — include the runtime error so the
|
|
313
|
+
// model knows exactly what to fix.
|
|
314
|
+
const errSnippet = stderrBuf.split('\n').slice(0, 30).join('\n');
|
|
315
|
+
const fixPrompt =
|
|
316
|
+
`A Node.js sandbox crashed with this runtime error:
|
|
317
|
+
|
|
318
|
+
${errSnippet.slice(0, 1500)}
|
|
319
|
+
|
|
320
|
+
The failing file is: ${rel}
|
|
321
|
+
|
|
322
|
+
What to fix: ${matchedPattern.hint}
|
|
323
|
+
|
|
324
|
+
CRITICAL: Output ONLY the complete corrected file content. No commentary, no markdown fences. Keep all working logic intact — only change what's necessary to fix the error.
|
|
325
|
+
|
|
326
|
+
Current file content:
|
|
327
|
+
${original.slice(0, 12_000)}`;
|
|
328
|
+
|
|
329
|
+
try {
|
|
330
|
+
let fixed = '';
|
|
331
|
+
await callLLMStream(loadConfig(), 'You are a precise code repair assistant. Output only the corrected file, no explanation.', fixPrompt, (c) => { fixed += c; }, { max_tokens: 16384 });
|
|
332
|
+
fixed = fixed.replace(/^```[\w]*\n/, '').replace(/\n```$/, '').trim();
|
|
333
|
+
if (fixed.length > 20 && fixed !== original) {
|
|
334
|
+
fs.writeFileSync(abs, fixed, 'utf-8');
|
|
335
|
+
emit({ type: 'status', msg: `Repaired ${rel} (${fixed.length} chars)` });
|
|
336
|
+
anyFixed = true;
|
|
337
|
+
}
|
|
338
|
+
} catch (e) {
|
|
339
|
+
emit({ type: 'warn', msg: `LLM repair of ${rel} failed: ${(e.message || '').slice(0, 200)}` });
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
if (anyFixed) {
|
|
343
|
+
emit({ type: 'status', msg: `Restarting sandbox (attempt ${_attempt + 1}/${MAX_RETRIES})...` });
|
|
344
|
+
return this.start(projectName, projectDir, emit, _attempt + 1);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Special fallback for CJS/ESM mismatches: try flipping package.json "type"
|
|
349
|
+
if (matchedPattern.name.includes('CJS/ESM') || matchedPattern.name.includes('ESM/CJS')) {
|
|
350
|
+
const pkgPath = path.join(projectDir, 'package.json');
|
|
351
|
+
if (fs.existsSync(pkgPath)) {
|
|
352
|
+
try {
|
|
353
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
354
|
+
const isCjsError = /Cannot use import statement outside a module/i.test(stderrBuf);
|
|
355
|
+
if (isCjsError && pkg.type !== 'module') {
|
|
356
|
+
pkg.type = 'module';
|
|
357
|
+
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
|
|
358
|
+
emit({ type: 'status', msg: 'Added "type":"module" to package.json — retrying...' });
|
|
359
|
+
return this.start(projectName, projectDir, emit, _attempt + 1);
|
|
360
|
+
}
|
|
361
|
+
} catch { /* ignore */ }
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
271
366
|
// Show the actual error from stderr — include full trace for debugging
|
|
272
367
|
const stderrLines = stderrBuf.split('\n').filter(l => l.trim());
|
|
273
368
|
const errLine = stderrLines.find((l) => l.includes('Error') || l.includes('error'));
|
|
@@ -278,6 +278,35 @@ async function callAgentWithTools(config, agentName, userMessage, languageOverri
|
|
|
278
278
|
|
|
279
279
|
if (actions.length === 0) {
|
|
280
280
|
finalText = textParts.join('\n').trim();
|
|
281
|
+
|
|
282
|
+
// ── HALLUCINATED SUCCESS DETECTION ────────────────────────────────
|
|
283
|
+
// The model sometimes writes "X è stato cancellato/creato/inviato con
|
|
284
|
+
// successo" WITHOUT actually emitting a tool block — pure hallucination.
|
|
285
|
+
// If that happens and we're early in the round budget, force ONE retry
|
|
286
|
+
// with an explicit instruction to emit the tool JSON. If it still
|
|
287
|
+
// refuses, replace the fake success with a warning so the user knows
|
|
288
|
+
// the action did NOT actually happen.
|
|
289
|
+
const claimsSuccess = isCompletedAction(finalText);
|
|
290
|
+
const hasReferenceToTool = /\b(eventid|event id|id evento)\b/i.test(finalText);
|
|
291
|
+
const looksFakeSuccess = claimsSuccess || (hasReferenceToTool && round === 0);
|
|
292
|
+
if (looksFakeSuccess && round < 1) {
|
|
293
|
+
// Push the bad response as history so the model sees what it just did,
|
|
294
|
+
// then ask it to actually emit the tool.
|
|
295
|
+
history.push({ role: 'assistant', content: response });
|
|
296
|
+
userMessage =
|
|
297
|
+
`STOP. Hai dichiarato che un'azione è stata completata ma NON hai emesso nessun blocco tool JSON. ` +
|
|
298
|
+
`Senza il blocco tool, NESSUNA azione viene eseguita davvero. ` +
|
|
299
|
+
`Ora emetti il blocco JSON corretto (\`\`\`json ... \`\`\`) per l'azione richiesta dall'utente, usando i parametri corretti. ` +
|
|
300
|
+
`Se ti serve un eventId, chiama prima calendar_find o calendar_date. Non scrivere altro testo finché non hai eseguito davvero il tool.`;
|
|
301
|
+
continue; // restart the loop with the corrective user message
|
|
302
|
+
}
|
|
303
|
+
if (looksFakeSuccess && round >= 1) {
|
|
304
|
+
// Second attempt also lied — refuse to forward the fake success.
|
|
305
|
+
finalText =
|
|
306
|
+
`Non sono riuscito a eseguire l'azione automaticamente (il modello ha dichiarato un successo senza eseguire il tool). ` +
|
|
307
|
+
`Riprova riformulando la richiesta, oppure dimmi esattamente cosa vuoi che faccia.`;
|
|
308
|
+
}
|
|
309
|
+
|
|
281
310
|
break;
|
|
282
311
|
}
|
|
283
312
|
|
|
@@ -696,7 +725,7 @@ class TelegramResponder {
|
|
|
696
725
|
return d.text || '';
|
|
697
726
|
}
|
|
698
727
|
|
|
699
|
-
throw new Error('
|
|
728
|
+
throw new Error('servizio di trascrizione vocale momentaneamente non disponibile (NHA proxy irraggiungibile)');
|
|
700
729
|
}
|
|
701
730
|
|
|
702
731
|
async _handleMessage(message) {
|
|
@@ -730,7 +759,7 @@ class TelegramResponder {
|
|
|
730
759
|
this.log(`[Telegram] Voice transcription failed: ${err.message}`);
|
|
731
760
|
await this._telegramCall('sendMessage', {
|
|
732
761
|
chat_id: chatId,
|
|
733
|
-
text: `Non riesco a trascrivere il vocale
|
|
762
|
+
text: `Non riesco a trascrivere il vocale (${err.message}).\n\nPer abilitare la trascrizione vocale gratuita, dal computer esegui:\nnha config set groqKey TUA_CHIAVE_GROQ\n\nLa chiave si ottiene gratis su https://console.groq.com/keys`,
|
|
734
763
|
});
|
|
735
764
|
return;
|
|
736
765
|
}
|
|
@@ -687,9 +687,13 @@ Never output a JSON block as a suggestion — every block executes immediately.
|
|
|
687
687
|
## ABSOLUTE RULES (violate these and the user loses trust)
|
|
688
688
|
|
|
689
689
|
1. NEVER invent tool output. If you need data (an event ID, a price, a search result), you MUST emit a tool JSON block and wait for its real response. Do NOT write fake "(eventId: 123456789)" or fake results.
|
|
690
|
-
2. NEVER
|
|
691
|
-
3. NEVER
|
|
692
|
-
4.
|
|
690
|
+
2. NEVER claim an action was completed unless you have just received a SUCCESS tool result for that action in this same turn. NEVER write "cancellato con successo", "creato con successo", "inviato" etc. without a real tool execution behind it. If you didn't emit the tool block, the action didn't happen.
|
|
691
|
+
3. NEVER duplicate. If the user says "correggi/modifica/sposta/aggiorna" referring to an item you just created in this conversation, use the corresponding *_update / *_move tool with the eventId/taskId from your previous tool response. Do NOT create a second item.
|
|
692
|
+
4. NEVER put the TITLE in the description field. The "summary" / "title" field is the SHORT name (what shows on a calendar grid). The "description" field is ONLY for extra notes.
|
|
693
|
+
5. NEVER invent times. If the user did not specify a time, ASK them ("A che ora?"). Do not default to 10:00 or any other time silently. Same for missing date, duration, attendees.
|
|
694
|
+
6. When the user confirms ("procedi", "sì", "fallo", "ok", "vai") AND there is a pending action from the IMMEDIATELY PREVIOUS assistant turn (you proposed something like "Posso cancellare X. Procedo?"), EXECUTE that exact pending action with the same parameters — do NOT search again, do NOT propose again, do NOT ask again. Emit the tool block for the action you just proposed.
|
|
695
|
+
7. When in doubt, ASK ONE concise question — do not invent details.
|
|
696
|
+
8. Tool JSON blocks MUST be wrapped in \`\`\`json ... \`\`\` fences. If you emit raw JSON without fences, the system can sometimes still parse it but it's unreliable — always use the fences.
|
|
693
697
|
|
|
694
698
|
## TOOL SIGNATURES (parameters and how to use them)
|
|
695
699
|
|
|
@@ -814,12 +818,53 @@ export function parseActions(text) {
|
|
|
814
818
|
const trailing = normalized.slice(lastIndex).trim();
|
|
815
819
|
if (trailing) textParts.push(trailing);
|
|
816
820
|
|
|
817
|
-
// Fallback: if no fenced blocks found, scan for bare {"action": ...} objects
|
|
821
|
+
// Fallback: if no fenced blocks found, scan for bare {"action": ...} objects
|
|
822
|
+
// in the text. The previous regex was naive and stopped at the first `}`,
|
|
823
|
+
// breaking on nested params like {"action":"X","params":{"summary":"Y"}}.
|
|
824
|
+
// Now we do proper brace balancing so we always capture the FULL object.
|
|
818
825
|
if (actions.length === 0) {
|
|
826
|
+
const consumed = new Set();
|
|
827
|
+
const findBareActions = (src) => {
|
|
828
|
+
let i = 0;
|
|
829
|
+
while (i < src.length) {
|
|
830
|
+
// Quick filter: only consider candidates that start with {"action"
|
|
831
|
+
const idx = src.indexOf('{"action"', i);
|
|
832
|
+
if (idx < 0) break;
|
|
833
|
+
// Walk forward balancing braces, respecting strings
|
|
834
|
+
let depth = 0;
|
|
835
|
+
let inStr = false;
|
|
836
|
+
let escape = false;
|
|
837
|
+
let end = -1;
|
|
838
|
+
for (let j = idx; j < src.length; j++) {
|
|
839
|
+
const c = src[j];
|
|
840
|
+
if (escape) { escape = false; continue; }
|
|
841
|
+
if (c === '\\' && inStr) { escape = true; continue; }
|
|
842
|
+
if (c === '"' && !escape) { inStr = !inStr; continue; }
|
|
843
|
+
if (inStr) continue;
|
|
844
|
+
if (c === '{') depth++;
|
|
845
|
+
else if (c === '}') {
|
|
846
|
+
depth--;
|
|
847
|
+
if (depth === 0) { end = j; break; }
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
if (end < 0) break;
|
|
851
|
+
const candidate = src.slice(idx, end + 1);
|
|
852
|
+
try {
|
|
853
|
+
const parsed = JSON.parse(candidate);
|
|
854
|
+
if (parsed.action && typeof parsed.action === 'string' && !consumed.has(candidate)) {
|
|
855
|
+
actions.push({ action: parsed.action, params: parsed.params || {} });
|
|
856
|
+
consumed.add(candidate);
|
|
857
|
+
}
|
|
858
|
+
} catch { /* not valid JSON, skip */ }
|
|
859
|
+
i = end + 1;
|
|
860
|
+
}
|
|
861
|
+
};
|
|
862
|
+
findBareActions(text);
|
|
863
|
+
// Legacy fallback kept for very loose JSON shapes — only fires if the
|
|
864
|
+
// brace scanner found nothing.
|
|
819
865
|
const bareRegex = /\{[\s\S]*?"action"\s*:\s*"[^"]+[\s\S]*?\}/g;
|
|
820
866
|
let bareMatch;
|
|
821
|
-
|
|
822
|
-
while ((bareMatch = bareRegex.exec(text)) !== null) {
|
|
867
|
+
while (actions.length === 0 && (bareMatch = bareRegex.exec(text)) !== null) {
|
|
823
868
|
try {
|
|
824
869
|
const parsed = JSON.parse(bareMatch[0]);
|
|
825
870
|
if (parsed.action && typeof parsed.action === 'string' && !consumed.has(bareMatch[0])) {
|