nothumanallowed 13.5.70 → 13.5.72

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": "13.5.70",
3
+ "version": "13.5.72",
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": {
@@ -4086,7 +4086,11 @@ ${completedHeadings ? `## SECTIONS ALREADY WRITTEN (headings only):\n${completed
4086
4086
  'Connection': 'keep-alive',
4087
4087
  'Access-Control-Allow-Origin': '*',
4088
4088
  });
4089
- const sendLog = (msg) => res.write(`data: ${JSON.stringify({ type: 'log', msg })}\n\n`);
4089
+ const _sbLogLines = [];
4090
+ const sendLog = (msg) => {
4091
+ _sbLogLines.push(msg);
4092
+ res.write(`data: ${JSON.stringify({ type: 'log', msg })}\n\n`);
4093
+ };
4090
4094
  const sendReady = (port, dir) => res.write(`data: ${JSON.stringify({ type: 'ready', port, dir })}\n\n`);
4091
4095
  const sendError = (msg) => res.write(`data: ${JSON.stringify({ type: 'error', msg })}\n\n`);
4092
4096
 
@@ -4274,6 +4278,32 @@ module.exports = { get, set, del, exists };
4274
4278
  fs.mkdirSync(path.join(sandboxDir, 'server', 'services'), { recursive: true });
4275
4279
  fs.writeFileSync(path.join(sandboxDir, 'server', 'services', 'cache.js'), cacheShim, 'utf8');
4276
4280
 
4281
+ // Models shim — LLM often generates require('../models/User') etc. that don't exist
4282
+ // Create a generic User model shim backed by the in-memory DB shim
4283
+ const userModelShim = `
4284
+ // NHA WebCraft Sandbox — models/User shim (in-memory, no PostgreSQL)
4285
+ const db = require('../db');
4286
+ const bcrypt = require('bcryptjs');
4287
+ const crypto = require('crypto');
4288
+ const User = {
4289
+ findById: async function(id) { var r = await db.query('SELECT * FROM users WHERE id=$1',[id]); return (r.rows||[])[0]||null; },
4290
+ findByEmail: async function(email) { var r = await db.query('SELECT * FROM users WHERE email=$1',[email]); return (r.rows||[])[0]||null; },
4291
+ create: async function(data) {
4292
+ var hash = data.password ? await bcrypt.hash(data.password,12) : data.password_hash||'';
4293
+ var token = crypto.randomBytes(32).toString('hex');
4294
+ var r = await db.query('INSERT INTO users (name,email,password_hash,verification_token) VALUES ($1,$2,$3,$4) RETURNING *',[data.name||data.username||'',data.email,hash,token]);
4295
+ return (r.rows||[])[0]||null;
4296
+ },
4297
+ update: async function(id, data) { var r = await db.query('UPDATE users SET verified=true WHERE id=$1 RETURNING *',[id]); return (r.rows||[])[0]||null; },
4298
+ findByVerificationToken: async function(token) { var r = await db.query('SELECT * FROM users WHERE verification_token=$1',[token]); return (r.rows||[])[0]||null; },
4299
+ };
4300
+ module.exports = User;
4301
+ `;
4302
+ fs.mkdirSync(path.join(sandboxDir, 'server', 'models'), { recursive: true });
4303
+ if (!fs.existsSync(path.join(sandboxDir, 'server', 'models', 'User.js'))) {
4304
+ fs.writeFileSync(path.join(sandboxDir, 'server', 'models', 'User.js'), userModelShim, 'utf8');
4305
+ }
4306
+
4277
4307
  // Validators shim — LLM often generates require('../utils/validators') with helpers that don't exist
4278
4308
  const validatorsShim = `
4279
4309
  // NHA WebCraft Sandbox — utils/validators shim
@@ -4310,6 +4340,19 @@ module.exports = { validateEmail, sanitizeText, validatePassword, validateUserna
4310
4340
  [/require\(['"]compression['"]\)/g, "(()=>(r,s,n)=>n())"],
4311
4341
  [/require\(['"]express-validator['"]\)/g, "{body:()=>({isEmail:()=>({normalizeEmail:()=>({run:async()=>{}})}),isLength:()=>({run:async()=>{}}),trim:()=>({escape:()=>({run:async()=>{}})}),notEmpty:()=>({run:async()=>{}})}),validationResult:()=>({isEmpty:()=>true,array:()=>[]})}"],
4312
4342
  [/require\(['"]validator['"]\)/g, "{isEmail:(s)=>/^[^@\\s]+@[^@\\s]+[.][^@\\s]+$/.test(s),escape:(s)=>String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'),trim:(s)=>String(s).trim(),isEmpty:(s)=>!s||!String(s).trim(),isLength:(s,o)=>{ var l=String(s).length; return (!o.min||l>=o.min)&&(!o.max||l<=o.max); }}"],
4343
+ [/require\(['"]handlebars['"]\)/g, "{compile:(t)=>(d)=>t.replace(/\\{\\{([^}]+)\\}\\}/g,(_,k)=>d[k.trim()]||''),registerHelper:()=>{},registerPartial:()=>{}}"],
4344
+ [/require\(['"]express-handlebars['"]\)/g, "{engine:()=>(p,o,cb)=>cb(null,'<html>'+JSON.stringify(o)+'</html>')}"],
4345
+ [/require\(['"]hbs['"]\)/g, "{registerHelper:()=>{},registerPartial:()=>{}}"],
4346
+ [/require\(['"]ejs['"]\)/g, "{render:(t,d)=>t.replace(/<%=([^%]+)%>/g,(_,k)=>d[k.trim()]||''),renderFile:(f,d,o,cb)=>{ if(typeof o==='function'){o(null,'');}else if(cb){cb(null,'');} }}"],
4347
+ [/require\(['"]pug['"]\)/g, "{compile:()=>(d)=>'',renderFile:(f,d,cb)=>cb&&cb(null,'')}"],
4348
+ [/require\(['"]nunjucks['"]\)/g, "{configure:()=>({}),render:(t,d)=>JSON.stringify(d),renderString:(t,d)=>JSON.stringify(d)}"],
4349
+ [/require\(['"]mustache['"]\)/g, "{render:(t,d)=>t.replace(/\\{\\{([^}]+)\\}\\}/g,(_,k)=>d[k.trim()]||'')}"],
4350
+ [/require\(['"]marked['"]\)/g, "{marked:(s)=>s,parse:(s)=>s}"],
4351
+ [/require\(['"]highlight\.js['"]\)/g, "{highlight:(c)=>({value:c}),highlightAuto:(c)=>({value:c})}"],
4352
+ [/require\(['"]nodemailer['"]\)/g, "{createTransport:()=>({sendMail:(o,cb)=>{ if(cb)cb(null,{messageId:'sandbox-'+Date.now()}); return Promise.resolve({messageId:'sandbox'}); }})}"],
4353
+ [/require\(['"]stripe['"]\)/g, "(()=>({customers:{create:async()=>({id:'cus_sandbox'})},paymentIntents:{create:async()=>({id:'pi_sandbox',client_secret:'sandbox_secret'})}}))()"],
4354
+ [/require\(['"]@sendgrid\/mail['"]\)/g, "{setApiKey:()=>{},send:async()=>({statusCode:202})}"],
4355
+ [/require\(['"]twilio['"]\)/g, "(()=>({messages:{create:async()=>({sid:'SM_sandbox'})}}))()"],
4313
4356
  [/require\(['"]cookie-parser['"]\)/g, "(()=>(r,s,n)=>{r.cookies=r.cookies||{};n()})"],
4314
4357
  [/require\(['"]passport['"]\)/g, "{initialize:()=>(r,s,n)=>n(),session:()=>(r,s,n)=>n(),authenticate:()=>(r,s,n)=>n&&n()}"],
4315
4358
  [/require\(['"]express-session['"]\)/g, "(()=>(r,s,n)=>{r.session=r.session||{};n()})"],
@@ -4330,6 +4373,10 @@ module.exports = { validateEmail, sanitizeText, validatePassword, validateUserna
4330
4373
  [/require\(['"]\.\.\/\.\.\/config['"]\)/g, "{env:process.env}"],
4331
4374
  [/require\(['"]\.\.\/config['"]\)/g, "{env:process.env}"],
4332
4375
  [/require\(['"]\.\/config['"]\)/g, "{env:process.env}"],
4376
+ // models/* — redirect to shims in server/models/
4377
+ [/require\(['"]\.\.\/models\/User['"]\)/g, "require('../models/User')"],
4378
+ [/require\(['"]\.\/models\/User['"]\)/g, "require('./models/User')"],
4379
+ [/require\(['"]\.\.\/models\/user['"]\)/g, "require('../models/User')"],
4333
4380
  // utils/* — LLM generates helpers that don't exist; redirect to shim
4334
4381
  [/require\(['"]\.\.\/utils\/validators['"]\)/g, "require('../utils/validators')"],
4335
4382
  [/require\(['"]\.\/utils\/validators['"]\)/g, "require('./utils/validators')"],
@@ -4493,8 +4540,44 @@ module.exports = { validateEmail, sanitizeText, validatePassword, validateUserna
4493
4540
  });
4494
4541
 
4495
4542
  sendLog(`✅ Sandbox pronta!`);
4543
+
4544
+ // Write sandbox log to skills/ so the agent can read it as context
4545
+ try {
4546
+ const _nl = '\n';
4547
+ const logTs = new Date().toISOString().replace('T', ' ').slice(0, 19);
4548
+ const logName = projName + '-' + logTs.replace(/[: ]/g, '-') + '.log';
4549
+ const logsDir = path.join(sandboxDir, 'skills');
4550
+ fs.mkdirSync(logsDir, { recursive: true });
4551
+ // Keep only last 3 log files to avoid bloat
4552
+ const existing = fs.readdirSync(logsDir).filter(f => f.endsWith('.log') && f.startsWith(projName + '-'));
4553
+ existing.sort().slice(0, -2).forEach(f => { try { fs.unlinkSync(path.join(logsDir, f)); } catch(_) {} });
4554
+ const logContent = '# Sandbox Log — ' + projName + _nl + 'Avviato: ' + logTs + _nl + 'Porta: ' + freePort + _nl + _nl + _sbLogLines.join(_nl);
4555
+ fs.writeFileSync(path.join(logsDir, logName), logContent, 'utf8');
4556
+ // Also update _index.json so the UI knows the type
4557
+ const idxPath = path.join(logsDir, '_index.json');
4558
+ let idx = {};
4559
+ try { idx = JSON.parse(fs.readFileSync(idxPath, 'utf8')); } catch(_) {}
4560
+ idx[logName] = 'log';
4561
+ fs.writeFileSync(idxPath, JSON.stringify(idx), 'utf8');
4562
+ } catch(_) {}
4563
+
4496
4564
  sendReady(freePort, sandboxDir);
4497
4565
  } catch (e) {
4566
+ // Write error log too
4567
+ try {
4568
+ const _nl = '\n';
4569
+ const logTs = new Date().toISOString().replace('T', ' ').slice(0, 19);
4570
+ const logName = projName + '-' + logTs.replace(/[: ]/g, '-') + '-ERROR.log';
4571
+ const logsDir = path.join(sandboxDir, 'skills');
4572
+ fs.mkdirSync(logsDir, { recursive: true });
4573
+ const logContent = '# Sandbox Log — ' + projName + ' [ERRORE]' + _nl + 'Avviato: ' + logTs + _nl + _nl + _sbLogLines.join(_nl) + _nl + _nl + '❌ ERRORE: ' + e.message;
4574
+ fs.writeFileSync(path.join(logsDir, logName), logContent, 'utf8');
4575
+ const idxPath = path.join(logsDir, '_index.json');
4576
+ let idx = {};
4577
+ try { idx = JSON.parse(fs.readFileSync(idxPath, 'utf8')); } catch(_) {}
4578
+ idx[logName] = 'log';
4579
+ fs.writeFileSync(idxPath, JSON.stringify(idx), 'utf8');
4580
+ } catch(_) {}
4498
4581
  sendError(e.message);
4499
4582
  }
4500
4583
  res.end();
@@ -4536,14 +4619,14 @@ module.exports = { validateEmail, sanitizeText, validatePassword, validateUserna
4536
4619
  return;
4537
4620
  }
4538
4621
 
4539
- // Rate-limit Liara: 3 per 5 minutes
4622
+ // Rate-limit Liara: 6 per 5 minutes (autofix needs headroom for multi-crash startup sequences)
4540
4623
  const isLiara = !config.llm || !config.llm.apiKey || config.llm.provider === 'nha';
4541
4624
  if (isLiara) {
4542
4625
  if (!global._wcAgentCallLog) global._wcAgentCallLog = [];
4543
4626
  const fiveMinAgo = Date.now() - 5 * 60 * 1000;
4544
4627
  global._wcAgentCallLog = global._wcAgentCallLog.filter(t => t > fiveMinAgo);
4545
- if (global._wcAgentCallLog.length >= 3) {
4546
- sendJSON(res, 429, { error: 'Auto-fix rate limit: massimo 3 correzioni ogni 5 minuti con Liara. Usa una tua API key per correzioni illimitate.' });
4628
+ if (global._wcAgentCallLog.length >= 6) {
4629
+ sendJSON(res, 429, { error: 'Auto-fix rate limit: massimo 6 correzioni ogni 5 minuti con Liara. Usa una tua API key per correzioni illimitate.' });
4547
4630
  logRequest(method, pathname, 429, Date.now() - start);
4548
4631
  return;
4549
4632
  }
@@ -4573,7 +4656,7 @@ module.exports = { validateEmail, sanitizeText, validatePassword, validateUserna
4573
4656
  let typeIndex = {};
4574
4657
  if (fs.existsSync(indexPath)) { try { typeIndex = JSON.parse(fs.readFileSync(indexPath, 'utf8')); } catch(_) {} }
4575
4658
  const defaultType = (n) => n === 'memory.md' ? 'memory' : n === 'liara.md' ? 'provider' : 'skill';
4576
- const sections = { memory: [], provider: [], skill: [] };
4659
+ const sections = { memory: [], provider: [], skill: [], log: [] };
4577
4660
  for (const fname of fs.readdirSync(skillsDir)) {
4578
4661
  if (!fname.endsWith('.md')) continue;
4579
4662
  try {
@@ -4588,6 +4671,7 @@ module.exports = { validateEmail, sanitizeText, validatePassword, validateUserna
4588
4671
  if (sections.memory.length) parts.push('MEMORIA PROGETTO:\n' + sections.memory.join('\n\n'));
4589
4672
  if (sections.provider.length) parts.push('ISTRUZIONI MODELLO AI:\n' + sections.provider.join('\n\n'));
4590
4673
  if (sections.skill.length) parts.push('SKILLS & PATTERN:\n' + sections.skill.join('\n\n'));
4674
+ if (sections.log && sections.log.length) parts.push('LOG AVVIO SANDBOX (ultimo):\n' + sections.log[sections.log.length - 1]);
4591
4675
  if (parts.length) skillsContext = '\n' + parts.join('\n\n') + '\n';
4592
4676
  }
4593
4677
 
@@ -6565,10 +6565,10 @@ function wcEsc(s){return s?String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').
6565
6565
 
6566
6566
  function renderWebCraft(el) {
6567
6567
  var fileTabsHtml = wcState.generatedFiles.length > 0
6568
- ? '<div style="display:flex;gap:0;overflow-x:auto;border-bottom:1px solid var(--border);margin-bottom:0;flex-shrink:0">' +
6568
+ ? '<div id="wcFileTabsRow" style="display:flex;gap:0;overflow-x:auto;border-bottom:1px solid var(--border);margin-bottom:0;flex-shrink:0;scrollbar-width:none">' +
6569
6569
  wcState.generatedFiles.map(function(f,i){
6570
6570
  var active = i === wcState.activeFile;
6571
- return '<button onclick="wcSetFile('+i+')" style="padding:6px 14px;font-size:11px;font-family:var(--mono);font-weight:'+(active?'700':'400')+';background:'+(active?'var(--bg3)':'transparent')+';border:none;border-bottom:2px solid '+(active?'var(--green3)':'transparent')+';color:'+(active?'var(--green)':'var(--dim)')+';cursor:pointer;white-space:nowrap;flex-shrink:0">'+wcEsc(f.name)+'</button>';
6571
+ return '<button id="wcTab'+i+'" onclick="wcSetFile('+i+')" style="padding:6px 14px;font-size:11px;font-family:var(--mono);font-weight:'+(active?'700':'400')+';background:'+(active?'var(--bg3)':'transparent')+';border:none;border-bottom:2px solid '+(active?'var(--green3)':'transparent')+';color:'+(active?'var(--green)':'var(--dim)')+';cursor:pointer;white-space:nowrap;flex-shrink:0">'+wcEsc(f.name)+'</button>';
6572
6572
  }).join('') +
6573
6573
  '</div>'
6574
6574
  : '';
@@ -6727,11 +6727,11 @@ function wcOpenSandbox() { if (wcState.sandbox.port) window.open('http://127.0.0
6727
6727
  // ── WebCraft Context Files (Skills / Memory / Provider) ───────────────────────
6728
6728
 
6729
6729
  function wcFileTypeIcon(type) {
6730
- return type === 'memory' ? '&#129504;' : type === 'provider' ? '&#129302;' : '&#128203;';
6730
+ return type === 'memory' ? '&#129504;' : type === 'provider' ? '&#129302;' : type === 'log' ? '&#128196;' : '&#128203;';
6731
6731
  }
6732
6732
  function wcFileTypeBadge(type) {
6733
- var colors = { memory: '#7c5cbf', provider: '#2a7fff', skill: '#1a7a4a' };
6734
- var labels = { memory: 'memory', provider: 'provider', skill: 'skill' };
6733
+ var colors = { memory: '#7c5cbf', provider: '#2a7fff', skill: '#1a7a4a', log: '#555' };
6734
+ var labels = { memory: 'memory', provider: 'provider', skill: 'skill', log: 'log' };
6735
6735
  return '<span style="font-size:9px;padding:1px 5px;border-radius:3px;background:' + (colors[type]||'#444') + ';color:#fff;margin-left:4px;flex-shrink:0">' + (labels[type]||type) + '</span>';
6736
6736
  }
6737
6737
 
@@ -6756,14 +6756,16 @@ function wcSkillsPanelHtml() {
6756
6756
  // Can add new skill only (memory + provider are singletons already in defaults)
6757
6757
  var rows = wcSkills.map(function(s, si) {
6758
6758
  var isSingleton = s.type === 'memory' || s.type === 'provider';
6759
+ var isLog = s.type === 'log';
6759
6760
  var isEmpty = !s.content || s.content.trim() === '';
6760
6761
  return '<div style="display:flex;align-items:center;gap:4px;padding:5px 0;border-bottom:1px solid var(--border)">' +
6761
6762
  '<span style="font-size:13px;flex-shrink:0">' + wcFileTypeIcon(s.type) + '</span>' +
6762
- '<span style="font-size:11px;color:var(--text);flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">' + wcEsc(s.name) + '</span>' +
6763
+ '<span style="font-size:11px;color:'+(isLog?'var(--dim)':'var(--text)')+';flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="'+wcEsc(s.name)+'">' + wcEsc(s.name) + '</span>' +
6763
6764
  wcFileTypeBadge(s.type) +
6764
- (isEmpty ? '<span title="Vuoto" style="font-size:9px;color:#e09020;flex-shrink:0">&#9888;</span>' : '') +
6765
- '<button onclick="wcOpenSkill('+si+')" title="Modifica" style="background:none;border:none;color:var(--dim);cursor:pointer;font-size:12px;padding:2px 4px;flex-shrink:0">&#9998;</button>' +
6766
- (!isSingleton ? '<button onclick="wcClearSkill('+si+')" title="Svuota" style="background:none;border:none;color:var(--dim);cursor:pointer;font-size:11px;padding:2px 4px;flex-shrink:0">&#128465;</button>' : '') +
6765
+ (!isLog && isEmpty ? '<span title="Vuoto" style="font-size:9px;color:#e09020;flex-shrink:0">&#9888;</span>' : '') +
6766
+ '<button onclick="wcOpenSkill('+si+')" title="'+(isLog?'Visualizza log':'Modifica')+'" style="background:none;border:none;color:var(--dim);cursor:pointer;font-size:12px;padding:2px 4px;flex-shrink:0">'+(isLog?'&#128065;':'&#9998;')+'</button>' +
6767
+ (!isSingleton && !isLog ? '<button onclick="wcClearSkill('+si+')" title="Svuota" style="background:none;border:none;color:var(--dim);cursor:pointer;font-size:11px;padding:2px 4px;flex-shrink:0">&#128465;</button>' : '') +
6768
+ (isLog ? '<button onclick="wcDeleteSkill('+si+')" title="Elimina log" style="background:none;border:none;color:var(--dim);cursor:pointer;font-size:11px;padding:2px 4px;flex-shrink:0">&#128465;</button>' : '') +
6767
6769
  '</div>';
6768
6770
  }).join('');
6769
6771
  return '<div style="background:var(--bg2);border:1px solid var(--border);border-radius:10px;padding:14px">' +
@@ -7493,7 +7495,13 @@ function wcAddField() {
7493
7495
  wcState.authFields.push({name:'field'+wcState.authFields.length,label:'New field',type:'text',required:false});
7494
7496
  renderWebCraft(document.getElementById('content'));
7495
7497
  }
7496
- function wcSetFile(i) { wcState.activeFile = i; renderWebCraft(document.getElementById('content')); }
7498
+ function wcSetFile(i) {
7499
+ wcState.activeFile = i;
7500
+ renderWebCraft(document.getElementById('content'));
7501
+ // Scroll active tab into view after render
7502
+ var tab = document.getElementById('wcTab' + i);
7503
+ if (tab) tab.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'nearest' });
7504
+ }
7497
7505
 
7498
7506
  // ── WebCraft: Diff Viewer ─────────────────────────────────────────────────────
7499
7507
  function wcDiffLines(before, after) {