nothumanallowed 13.5.53 → 13.5.55

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.53",
3
+ "version": "13.5.55",
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": {
@@ -3913,11 +3913,59 @@ ${completedHeadings ? `## SECTIONS ALREADY WRITTEN (headings only):\n${completed
3913
3913
  // Write meta
3914
3914
  const meta = { projectName: projName, description: body.description || '', files: (body.files || []).map(f => f.name), createdAt: new Date().toISOString() };
3915
3915
  fs.writeFileSync(path.join(projDir, 'webcraft-meta.json'), JSON.stringify(meta, null, 2), 'utf8');
3916
+ // Write agent memory file — always included as first context for WebCraft Agent
3917
+ const agentMd = [
3918
+ '# WebCraft Agent Memory — ' + projName,
3919
+ '> Auto-generated. Edit to give the agent persistent context about this project.',
3920
+ '',
3921
+ '## Progetto',
3922
+ '- **Nome**: ' + projName,
3923
+ '- **Descrizione**: ' + (body.description || ''),
3924
+ '- **Creato**: ' + new Date().toISOString(),
3925
+ '',
3926
+ '## Stack',
3927
+ '- Server: Express.js (Node.js)',
3928
+ '- Auth: JWT (access 15min, refresh 7d httpOnly cookie) + bcryptjs cost 12',
3929
+ '- DB: PostgreSQL (sandbox: in-memory shim in server/db.js)',
3930
+ '- CSS: BEM methodology',
3931
+ '- Security: helmet, express-rate-limit, custom sentinel WAF middleware',
3932
+ '',
3933
+ '## File principali',
3934
+ (body.files || []).map(f => '- ' + f.name).join('\n'),
3935
+ '',
3936
+ '## Note agente',
3937
+ '(aggiungi qui note per sessioni future — es. "usa Tailwind invece di BEM", "API key in .env.local")',
3938
+ ].join('\n');
3939
+ fs.writeFileSync(path.join(projDir, 'webcraft-agent.md'), agentMd, 'utf8');
3916
3940
  sendJSON(res, 200, { ok: true, dir: projDir });
3917
3941
  logRequest(method, pathname, 200, Date.now() - start);
3918
3942
  return;
3919
3943
  }
3920
3944
 
3945
+ // POST /api/studio/webcraft/projects/chat/save { projectName, chat[] }
3946
+ if (pathname === '/api/studio/webcraft/projects/chat/save' && method === 'POST') {
3947
+ const body = await parseBody(req, 2 * 1024 * 1024);
3948
+ const projName = (body.projectName || '').replace(/[^a-zA-Z0-9_-]/g, '-');
3949
+ if (!projName) { sendJSON(res, 400, { error: 'projectName required' }); return; }
3950
+ const projDir = path.join(os.homedir(), '.nha', 'webcraft', projName);
3951
+ fs.mkdirSync(projDir, { recursive: true });
3952
+ fs.writeFileSync(path.join(projDir, 'webcraft-chat.json'), JSON.stringify(body.chat || [], null, 2), 'utf8');
3953
+ sendJSON(res, 200, { ok: true });
3954
+ logRequest(method, pathname, 200, Date.now() - start);
3955
+ return;
3956
+ }
3957
+
3958
+ // GET /api/studio/webcraft/projects/chat/load/:name
3959
+ if (pathname.startsWith('/api/studio/webcraft/projects/chat/load/') && method === 'GET') {
3960
+ const projName = decodeURIComponent(pathname.replace('/api/studio/webcraft/projects/chat/load/', '')).replace(/[^a-zA-Z0-9_-]/g, '');
3961
+ const chatPath = path.join(os.homedir(), '.nha', 'webcraft', projName, 'webcraft-chat.json');
3962
+ if (!fs.existsSync(chatPath)) { sendJSON(res, 200, { chat: [] }); return; }
3963
+ const chat = JSON.parse(fs.readFileSync(chatPath, 'utf8'));
3964
+ sendJSON(res, 200, { chat });
3965
+ logRequest(method, pathname, 200, Date.now() - start);
3966
+ return;
3967
+ }
3968
+
3921
3969
  // GET /api/studio/webcraft/projects/load/:name → { projectName, description, files[] }
3922
3970
  if (pathname.startsWith('/api/studio/webcraft/projects/load/') && method === 'GET') {
3923
3971
  const projName = decodeURIComponent(pathname.replace('/api/studio/webcraft/projects/load/', '')).replace(/[^a-zA-Z0-9_-]/g, '');
@@ -4147,6 +4195,13 @@ module.exports = { get, set, del, exists };
4147
4195
  [/require\(['"]\.\/config\/database['"]\)/g, "require('./db')"],
4148
4196
  [/require\(['"]\.\.\/config\/db['"]\)/g, "require('../db')"],
4149
4197
  [/require\(['"]\.\/config\/db['"]\)/g, "require('./db')"],
4198
+ // email utils: LLM puts utils/email but file is in services/email
4199
+ [/require\(['"]\.\.\/utils\/email['"]\)/g, "require('../services/email')"],
4200
+ [/require\(['"]\.\/utils\/email['"]\)/g, "require('./services/email')"],
4201
+ // config module: LLM generates require('../../config') or require('../config')
4202
+ [/require\(['"]\.\.\/\.\.\/config['"]\)/g, "{env:process.env}"],
4203
+ [/require\(['"]\.\.\/config['"]\)/g, "{env:process.env}"],
4204
+ [/require\(['"]\.\/config['"]\)/g, "{env:process.env}"],
4150
4205
  ];
4151
4206
  function patchJsFiles(dir) {
4152
4207
  if (!fs.existsSync(dir)) return;
@@ -4350,6 +4405,10 @@ module.exports = { get, set, del, exists };
4350
4405
  const sendEv = (data) => { try { res.write(`data: ${JSON.stringify(data)}\n\n`); } catch {} };
4351
4406
 
4352
4407
  try {
4408
+ // Always read agent memory file first if present
4409
+ const agentMemoryPath = path.join(sandboxDir, 'webcraft-agent.md');
4410
+ const agentMemory = fs.existsSync(agentMemoryPath) ? fs.readFileSync(agentMemoryPath, 'utf8') : '';
4411
+
4353
4412
  // Read all project files to give agent full context
4354
4413
  const allFiles = [];
4355
4414
  if (fs.existsSync(sandboxDir)) {
@@ -4369,9 +4428,36 @@ module.exports = { get, set, del, exists };
4369
4428
  walkDir(sandboxDir, '');
4370
4429
  }
4371
4430
 
4372
- const fileContext = allFiles.map(f => `### FILE: ${f.name}\n\`\`\`\n${f.content}\n\`\`\``).join('\n\n');
4373
4431
  const fileList = allFiles.map(f => f.name).join('\n');
4374
4432
 
4433
+ // Smart file selection: include only relevant files to avoid overflowing context window.
4434
+ // Always include: package.json, server/index.js, .env.example (structure/deps context).
4435
+ // Also include: any file whose name appears in the user message, plus error-related files.
4436
+ const msgLower = (message || '').toLowerCase();
4437
+ const alwaysInclude = ['package.json', 'server/index.js', '.env.example'];
4438
+ const relevantFiles = allFiles.filter(f => {
4439
+ const nameLower = f.name.toLowerCase();
4440
+ if (alwaysInclude.some(a => nameLower.endsWith(a))) return true;
4441
+ // Include if file name or path fragment mentioned in message
4442
+ const parts = f.name.split('/');
4443
+ if (parts.some(p => msgLower.includes(p.replace(/\.[^.]+$/, '').toLowerCase()))) return true;
4444
+ // Include server JS files if message mentions "error", "errore", "fix", "crash", "module"
4445
+ if (/errore|error|fix|crash|module|require|import/i.test(message) && f.name.startsWith('server/') && f.name.endsWith('.js')) return true;
4446
+ return false;
4447
+ });
4448
+ // Cap total context at ~24KB to stay within 7B model limits
4449
+ let contextBudget = 24 * 1024;
4450
+ const selectedFiles = [];
4451
+ for (const f of relevantFiles) {
4452
+ if (contextBudget <= 0) break;
4453
+ selectedFiles.push(f);
4454
+ contextBudget -= f.content.length;
4455
+ }
4456
+ const fileContext = selectedFiles.map(f => `### FILE: ${f.name}\n\`\`\`\n${f.content}\n\`\`\``).join('\n\n');
4457
+ const includedNote = selectedFiles.length < allFiles.length
4458
+ ? `\n(Showing ${selectedFiles.length}/${allFiles.length} files most relevant to the request. Ask to read others explicitly.)`
4459
+ : '';
4460
+
4375
4461
  // Build attachment context (images/PDF)
4376
4462
  const attachCtx = (attachments || []).map((a, i) => `[Allegato ${i+1}: ${a.name || 'file'}, type=${a.mimeType}]`).join('\n');
4377
4463
 
@@ -4380,7 +4466,7 @@ Il tuo lavoro e di correggere, migliorare ed espandere il codice del progetto sa
4380
4466
 
4381
4467
  PROGETTO ATTIVO: ${projectName}
4382
4468
  PERCORSO: ${sandboxDir}
4383
-
4469
+ ${agentMemory ? '\nMEMORIA PROGETTO:\n' + agentMemory + '\n' : ''}
4384
4470
  FILE DISPONIBILI:
4385
4471
  ${fileList}
4386
4472
 
@@ -4405,23 +4491,37 @@ REGOLE CRITICHE:
4405
4491
  - Se usi immagini allegate, descrivile e usale come contesto per il fix`;
4406
4492
 
4407
4493
  const userMsg = message + (attachCtx ? '\n\nAllegati:\n' + attachCtx : '') +
4408
- '\n\n--- CONTENUTO FILE ---\n' + fileContext;
4494
+ '\n\n--- CONTENUTO FILE ---' + includedNote + '\n' + fileContext;
4409
4495
 
4410
4496
  // Call LLM - stream tokens
4411
4497
  let fullResponse = '';
4412
4498
  const visionAttachments = (attachments || []).filter(a => a.base64 && (a.mimeType || '').startsWith('image/'));
4413
4499
 
4414
- if (visionAttachments.length > 0) {
4415
- // Use vision call for first image attachment
4416
- const { callLLMVision } = await import('../services/llm.mjs');
4417
- const va = visionAttachments[0];
4418
- fullResponse = await callLLMVision(config, systemPrompt, userMsg, { base64: va.base64, mimeType: va.mimeType });
4419
- sendEv({ type: 'text', token: fullResponse });
4420
- } else {
4421
- await callLLMStream(config, systemPrompt, userMsg, (token) => {
4422
- fullResponse += token;
4423
- sendEv({ type: 'text', token });
4424
- }, { max_tokens: 4096 });
4500
+ try {
4501
+ if (visionAttachments.length > 0) {
4502
+ const { callLLMVision } = await import('../services/llm.mjs');
4503
+ const va = visionAttachments[0];
4504
+ fullResponse = await callLLMVision(config, systemPrompt, userMsg, { base64: va.base64, mimeType: va.mimeType });
4505
+ sendEv({ type: 'text', token: fullResponse });
4506
+ } else {
4507
+ await callLLMStream(config, systemPrompt, userMsg, (token) => {
4508
+ fullResponse += token;
4509
+ sendEv({ type: 'text', token });
4510
+ }, { max_tokens: 4096 });
4511
+ }
4512
+ } catch (llmErr) {
4513
+ const errMsg = llmErr.message || String(llmErr);
4514
+ // Surface a clean message instead of raw HTML
4515
+ if (errMsg.includes('502') || errMsg.includes('Bad Gateway')) {
4516
+ sendEv({ type: 'text', token: 'Liara non disponibile al momento (502). Riprova tra qualche secondo, oppure configura una tua API key con: nha config set provider anthropic && nha config set key sk-...' });
4517
+ } else if (errMsg.includes('429') || errMsg.includes('rate limit')) {
4518
+ sendEv({ type: 'text', token: 'Rate limit raggiunto su Liara (max 3 auto-fix per 5 minuti con il piano free). Attendi qualche minuto o usa una tua API key.' });
4519
+ } else {
4520
+ sendEv({ type: 'text', token: 'Errore LLM: ' + errMsg.slice(0, 200) });
4521
+ }
4522
+ sendEv({ type: 'done', changed: false, toolCount: 0 });
4523
+ res.end();
4524
+ return;
4425
4525
  }
4426
4526
 
4427
4527
  // Parse and execute tool calls from response
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 = '13.5.53';
8
+ export const VERSION = '13.5.55';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -6572,10 +6572,13 @@ async function wcChatSend() {
6572
6572
  }
6573
6573
  } else if (ev.type === 'done') {
6574
6574
  wcChatRunning = false;
6575
- if (ev.changed) {
6576
- // Reload project files from disk
6577
- wcReloadProjectFiles();
6578
- }
6575
+ if (ev.changed) { wcReloadProjectFiles(); }
6576
+ // Persist chat to disk
6577
+ fetch(API + '/api/studio/webcraft/projects/chat/save', {
6578
+ method: 'POST',
6579
+ headers: {'Content-Type':'application/json'},
6580
+ body: JSON.stringify({ projectName: wcState.projectName, chat: wcChat })
6581
+ }).catch(function(){});
6579
6582
  } else if (ev.type === 'restart_sandbox') {
6580
6583
  wcStartSandbox();
6581
6584
  } else if (ev.type === 'error') {
@@ -6731,7 +6734,13 @@ async function wcLoadProject(pi) {
6731
6734
  wcState.activeFile = 0;
6732
6735
  wcMainTab = 'new';
6733
6736
  wcRightTab = 'files';
6737
+ // Load persisted chat history
6738
+ try {
6739
+ var cr = await fetch(API + '/api/studio/webcraft/projects/chat/load/' + encodeURIComponent(wcState.projectName));
6740
+ if (cr.ok) { var cd = await cr.json(); wcChat = cd.chat || []; }
6741
+ } catch(_) { wcChat = []; }
6734
6742
  renderWebCraft(document.getElementById('content'));
6743
+ wcScrollChatToBottom();
6735
6744
  }
6736
6745
 
6737
6746
  async function wcDeleteProject(pi) {