nothumanallowed 13.5.51 → 13.5.53

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.51",
3
+ "version": "13.5.53",
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": {
@@ -4249,11 +4249,15 @@ module.exports = { get, set, del, exists };
4249
4249
  proc.stdout.on('data', d => { const l = d.toString().trim(); if (l) sendLog(' [server] ' + l); });
4250
4250
  proc.stderr.on('data', d => {
4251
4251
  const raw = d.toString();
4252
- // Extract meaningful error: MODULE_NOT_FOUND
4252
+ // Extract meaningful error: MODULE_NOT_FOUND → trigger auto-fix
4253
4253
  const modMatch = raw.match(/Cannot find module '([^']+)'/);
4254
4254
  if (modMatch) {
4255
- sendLog(' ❌ Modulo mancante: ' + modMatch[1]);
4256
- sendLog(' Questo modulo non è supportato in sandbox. Contatta il supporto NHA.');
4255
+ const missingMod = modMatch[1];
4256
+ sendLog(' Modulo mancante: ' + missingMod);
4257
+ // Store error for auto-fix agent — will be picked up by frontend
4258
+ if (!global._wcAutoFixQueue) global._wcAutoFixQueue = [];
4259
+ global._wcAutoFixQueue.push({ type: 'module_not_found', module: missingMod, dir: sandboxDir, ts: Date.now() });
4260
+ sendLog(' 🤖 Avvio auto-fix...');
4257
4261
  return;
4258
4262
  }
4259
4263
  // Skip node internals noise
@@ -4307,6 +4311,185 @@ module.exports = { get, set, del, exists };
4307
4311
  return;
4308
4312
  }
4309
4313
 
4314
+ // ── WebCraft Agent — chat + tool-use + auto-fix ────────────────────────
4315
+ // POST /api/studio/webcraft/agent { projectName, message, attachments[]?, autofix? }
4316
+ // → SSE: { type:'text', token } | { type:'tool', tool, path, result } | { type:'done' } | { type:'error', msg }
4317
+ // The agent reads files, applies old_string→new_string edits, writes new files.
4318
+ // Rate-limit for Liara: 3 calls per 5 minutes (unlimited for own API key).
4319
+ if (pathname === '/api/studio/webcraft/agent' && method === 'POST') {
4320
+ const body = await parseBody(req, 32 * 1024 * 1024); // 32MB for attachments
4321
+ const { projectName, message, attachments, autofix } = body;
4322
+ if (!projectName || !message) {
4323
+ sendJSON(res, 400, { error: 'projectName and message required' });
4324
+ logRequest(method, pathname, 400, Date.now() - start);
4325
+ return;
4326
+ }
4327
+
4328
+ // Rate-limit Liara: 3 per 5 minutes
4329
+ const isLiara = !config.llm || !config.llm.apiKey || config.llm.provider === 'nha';
4330
+ if (isLiara) {
4331
+ if (!global._wcAgentCallLog) global._wcAgentCallLog = [];
4332
+ const fiveMinAgo = Date.now() - 5 * 60 * 1000;
4333
+ global._wcAgentCallLog = global._wcAgentCallLog.filter(t => t > fiveMinAgo);
4334
+ if (global._wcAgentCallLog.length >= 3) {
4335
+ sendJSON(res, 429, { error: 'Auto-fix rate limit: massimo 3 correzioni ogni 5 minuti con Liara. Usa una tua API key per correzioni illimitate.' });
4336
+ logRequest(method, pathname, 429, Date.now() - start);
4337
+ return;
4338
+ }
4339
+ global._wcAgentCallLog.push(Date.now());
4340
+ }
4341
+
4342
+ const sandboxDir = path.join(os.homedir(), '.nha', 'webcraft', projectName.replace(/[^a-zA-Z0-9_-]/g, '-'));
4343
+
4344
+ res.writeHead(200, {
4345
+ 'Content-Type': 'text/event-stream',
4346
+ 'Cache-Control': 'no-cache',
4347
+ 'Connection': 'keep-alive',
4348
+ 'Access-Control-Allow-Origin': '*',
4349
+ });
4350
+ const sendEv = (data) => { try { res.write(`data: ${JSON.stringify(data)}\n\n`); } catch {} };
4351
+
4352
+ try {
4353
+ // Read all project files to give agent full context
4354
+ const allFiles = [];
4355
+ if (fs.existsSync(sandboxDir)) {
4356
+ const walkDir = (dir, base) => {
4357
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
4358
+ if (entry.name === 'node_modules' || entry.name.startsWith('.')) continue;
4359
+ const rel = base ? base + '/' + entry.name : entry.name;
4360
+ if (entry.isDirectory()) { walkDir(path.join(dir, entry.name), rel); }
4361
+ else {
4362
+ try {
4363
+ const content = fs.readFileSync(path.join(dir, entry.name), 'utf8');
4364
+ if (content.length < 64 * 1024) allFiles.push({ name: rel, content }); // skip >64KB
4365
+ } catch(_) {}
4366
+ }
4367
+ }
4368
+ };
4369
+ walkDir(sandboxDir, '');
4370
+ }
4371
+
4372
+ const fileContext = allFiles.map(f => `### FILE: ${f.name}\n\`\`\`\n${f.content}\n\`\`\``).join('\n\n');
4373
+ const fileList = allFiles.map(f => f.name).join('\n');
4374
+
4375
+ // Build attachment context (images/PDF)
4376
+ const attachCtx = (attachments || []).map((a, i) => `[Allegato ${i+1}: ${a.name || 'file'}, type=${a.mimeType}]`).join('\n');
4377
+
4378
+ const systemPrompt = `Sei WebCraft Agent, un assistente AI esperto di sviluppo web fullstack embedded in NotHumanAllowed.
4379
+ Il tuo lavoro e di correggere, migliorare ed espandere il codice del progetto sandbox dell utente.
4380
+
4381
+ PROGETTO ATTIVO: ${projectName}
4382
+ PERCORSO: ${sandboxDir}
4383
+
4384
+ FILE DISPONIBILI:
4385
+ ${fileList}
4386
+
4387
+ STRUMENTI A TUA DISPOSIZIONE (rispondi SOLO con JSON valido per le operazioni):
4388
+ Quando devi modificare o creare file, includi nel testo della risposta blocchi JSON tra i tag <tool> e </tool>:
4389
+
4390
+ Per modificare parte di un file (chirurgico, preferito):
4391
+ <tool>{"op":"edit","path":"server/routes/contact.js","old":"const email = require('../utils/email')","new":"const nodemailer = require('nodemailer')"}</tool>
4392
+
4393
+ Per creare/sovrascrivere un file intero:
4394
+ <tool>{"op":"write","path":"server/utils/mailer.js","content":"// codice completo..."}</tool>
4395
+
4396
+ Per leggere un file (se hai bisogno di piu contesto):
4397
+ <tool>{"op":"read","path":"server/index.js"}</tool>
4398
+
4399
+ REGOLE CRITICHE:
4400
+ - Spiega SEMPRE in linguaggio naturale cosa stai facendo PRIMA dei blocchi tool
4401
+ - Usa "edit" (old/new) quando possibile, "write" solo per file nuovi o riscritture complete
4402
+ - old_string deve essere ESATTO come appare nel file (copy-paste)
4403
+ - Non inventare moduli npm: usa solo quelli in package.json o standard Node.js
4404
+ - Dopo ogni fix spiega brevemente cosa hai cambiato e perche
4405
+ - Se usi immagini allegate, descrivile e usale come contesto per il fix`;
4406
+
4407
+ const userMsg = message + (attachCtx ? '\n\nAllegati:\n' + attachCtx : '') +
4408
+ '\n\n--- CONTENUTO FILE ---\n' + fileContext;
4409
+
4410
+ // Call LLM - stream tokens
4411
+ let fullResponse = '';
4412
+ const visionAttachments = (attachments || []).filter(a => a.base64 && (a.mimeType || '').startsWith('image/'));
4413
+
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 });
4425
+ }
4426
+
4427
+ // Parse and execute tool calls from response
4428
+ const toolRegex = /<tool>([\s\S]*?)<\/tool>/g;
4429
+ let toolMatch;
4430
+ const toolResults = [];
4431
+ while ((toolMatch = toolRegex.exec(fullResponse)) !== null) {
4432
+ let toolCall;
4433
+ try { toolCall = JSON.parse(toolMatch[1]); } catch(e) { continue; }
4434
+
4435
+ if (toolCall.op === 'read') {
4436
+ const fp = path.join(sandboxDir, toolCall.path.replace(/^\/+/, ''));
4437
+ if (fs.existsSync(fp)) {
4438
+ const content = fs.readFileSync(fp, 'utf8');
4439
+ toolResults.push({ op: 'read', path: toolCall.path, ok: true });
4440
+ sendEv({ type: 'tool', op: 'read', path: toolCall.path, result: 'ok' });
4441
+ } else {
4442
+ sendEv({ type: 'tool', op: 'read', path: toolCall.path, result: 'file non trovato' });
4443
+ }
4444
+ } else if (toolCall.op === 'edit') {
4445
+ const fp = path.join(sandboxDir, toolCall.path.replace(/^\/+/, ''));
4446
+ if (!fs.existsSync(fp)) {
4447
+ sendEv({ type: 'tool', op: 'edit', path: toolCall.path, result: 'file non trovato' });
4448
+ continue;
4449
+ }
4450
+ let content = fs.readFileSync(fp, 'utf8');
4451
+ if (content.includes(toolCall.old)) {
4452
+ content = content.replace(toolCall.old, toolCall.new);
4453
+ fs.writeFileSync(fp, content, 'utf8');
4454
+ toolResults.push({ op: 'edit', path: toolCall.path, ok: true });
4455
+ sendEv({ type: 'tool', op: 'edit', path: toolCall.path, result: 'ok' });
4456
+ } else {
4457
+ sendEv({ type: 'tool', op: 'edit', path: toolCall.path, result: 'old_string non trovato — patch fallita' });
4458
+ }
4459
+ } else if (toolCall.op === 'write') {
4460
+ const fp = path.join(sandboxDir, toolCall.path.replace(/^\/+/, ''));
4461
+ fs.mkdirSync(path.dirname(fp), { recursive: true });
4462
+ fs.writeFileSync(fp, toolCall.content || '', 'utf8');
4463
+ toolResults.push({ op: 'write', path: toolCall.path, ok: true });
4464
+ sendEv({ type: 'tool', op: 'write', path: toolCall.path, result: 'ok' });
4465
+ }
4466
+ }
4467
+
4468
+ const anyChange = toolResults.some(r => r.ok);
4469
+ sendEv({ type: 'done', changed: anyChange, toolCount: toolResults.length });
4470
+
4471
+ // If auto-fix and changes made → signal client to restart sandbox
4472
+ if (autofix && anyChange) {
4473
+ sendEv({ type: 'restart_sandbox' });
4474
+ }
4475
+
4476
+ } catch(e) {
4477
+ sendEv({ type: 'error', msg: e.message });
4478
+ }
4479
+ res.end();
4480
+ logRequest(method, pathname, 200, Date.now() - start);
4481
+ return;
4482
+ }
4483
+
4484
+ // GET /api/studio/webcraft/agent/autofix-queue → { items[] } and clears queue
4485
+ if (pathname === '/api/studio/webcraft/agent/autofix-queue' && method === 'GET') {
4486
+ const items = global._wcAutoFixQueue || [];
4487
+ global._wcAutoFixQueue = [];
4488
+ sendJSON(res, 200, { items });
4489
+ logRequest(method, pathname, 200, Date.now() - start);
4490
+ return;
4491
+ }
4492
+
4310
4493
  // ── Studio: Parliament deliberation (SSE streaming) ──────────────────
4311
4494
  // Implements the Legion DeliberationEngine protocol adapted for Studio:
4312
4495
  // Round 1 outputs already exist (from normal workflow steps).
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.51';
8
+ export const VERSION = '13.5.53';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -6246,6 +6246,12 @@ var wcRightTab = 'files';
6246
6246
  var wcMainTab = 'new'; // 'new' | 'projects'
6247
6247
  var wcProjectsList = []; // cached list from server
6248
6248
  var wcSandboxExpanded = {}; // { phaseKey: true/false }
6249
+ // Agent chat state
6250
+ var wcChat = []; // [{role:'user'|'agent', text, tools:[]}]
6251
+ var wcChatRunning = false;
6252
+ var wcChatAttachments = []; // [{name, mimeType, base64, size}]
6253
+ var _wcAutoFixAttempts = 0;
6254
+ var _wcAutoFixTimer = null;
6249
6255
 
6250
6256
  function wcEsc(s){return s?String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'):''}
6251
6257
 
@@ -6307,14 +6313,10 @@ function renderWebCraft(el) {
6307
6313
  '</div>';
6308
6314
 
6309
6315
  var editorHtml =
6316
+ '<div style="display:flex;flex-direction:column;height:100%">' +
6310
6317
  wcExHtml +
6311
6318
  '<div style="display:flex;gap:14px;align-items:flex-start;flex:1;min-height:0">' +
6312
- '<div style="width:280px;flex-shrink:0;display:flex;flex-direction:column;gap:10px;overflow-y:auto;max-height:calc(100vh - 120px)">' +
6313
- '<div style="background:var(--bg2);border:1px solid var(--border);border-radius:10px;padding:14px">' +
6314
- '<div style="font-size:10px;color:var(--dim);text-transform:uppercase;letter-spacing:.8px;margin-bottom:8px">'+t('wc_project')+'</div>' +
6315
- '<input id="wcProjectName" placeholder="'+t('wc_project_name')+'" value="'+wcEsc(wcState.projectName)+'" oninput="wcState.projectName=this.value" style="width:100%;padding:8px 10px;font-size:12px;border-radius:6px;border:1px solid var(--border2);background:var(--bg3);color:var(--text);margin-bottom:8px;box-sizing:border-box">' +
6316
- '<textarea id="wcDesc" placeholder="'+t('wc_desc')+'" rows="4" oninput="wcState.description=this.value" style="width:100%;padding:8px 10px;font-size:12px;border-radius:6px;border:1px solid var(--border2);background:var(--bg3);color:var(--text);resize:vertical;box-sizing:border-box;line-height:1.5">'+wcEsc(wcState.description)+'</textarea>' +
6317
- '</div>' +
6319
+ '<div style="width:240px;flex-shrink:0;display:flex;flex-direction:column;gap:10px;overflow-y:auto;height:100%">' +
6318
6320
  '<div style="background:var(--bg2);border:1px solid var(--border);border-radius:10px;padding:14px">' +
6319
6321
  '<div style="font-size:10px;color:var(--dim);text-transform:uppercase;letter-spacing:.8px;margin-bottom:10px">'+t('wc_blocks')+'</div>' +
6320
6322
  ['auth','cookieBanner','securityMiddleware','emailVerification'].map(function(b){
@@ -6334,27 +6336,31 @@ function renderWebCraft(el) {
6334
6336
  '<div id="wcFieldsList">'+authFieldsHtml+'</div>' +
6335
6337
  '<div style="font-size:9px;color:var(--dim);margin-top:4px">'+t('wc_required_hint')+'</div>' +
6336
6338
  '</div>' +
6337
- '<button id="wcRunBtn" onclick="wcGenerate()" style="width:100%;padding:12px;background:var(--green3);border:none;border-radius:8px;color:var(--bg);font-size:13px;font-weight:700;cursor:pointer;letter-spacing:.2px"'+(wcState.running?' disabled':'')+'>'+
6338
- (wcState.running ? '&#9203; '+t('wc_generating')+'...' : '&#9654; '+t('wc_generate')) +
6339
- '</button>' +
6339
+ (wcState.running ?
6340
+ '<div style="width:100%;padding:11px;background:var(--bg3);border:1px solid var(--border);border-radius:8px;color:var(--dim);font-size:12px;text-align:center">&#9203; '+t('wc_generating')+'...</div>'
6341
+ : '') +
6340
6342
  (wcState.generatedFiles.length > 0 && !wcState.running ?
6341
6343
  '<button onclick="wcDownloadZip()" style="width:100%;padding:10px;background:var(--bg3);border:1px solid var(--border2);border-radius:8px;color:var(--text);font-size:12px;font-weight:600;cursor:pointer">&#8681; '+t('wc_download')+'</button>' +
6342
6344
  '<button onclick="wcStartSandbox()" id="wcSandboxBtn" style="width:100%;padding:10px;background:var(--bg3);border:1px solid var(--green3);border-radius:8px;color:var(--green);font-size:12px;font-weight:600;cursor:pointer">&#9654; '+t('wc_sandbox_start')+'</button>'
6343
6345
  : '') +
6344
6346
  '</div>' +
6345
- '<div style="flex:1;min-width:0;background:var(--bg2);border:1px solid var(--border);border-radius:10px;display:flex;flex-direction:column;height:calc(100vh - 120px);overflow:hidden">' +
6347
+ '<div style="flex:1;min-width:0;background:var(--bg2);border:1px solid var(--border);border-radius:10px;display:flex;flex-direction:column;height:100%;overflow:hidden">' +
6346
6348
  '<div style="display:flex;border-bottom:1px solid var(--border);flex-shrink:0">' +
6347
6349
  '<button onclick="wcTabFiles()" style="padding:8px 16px;background:'+(wcRightTab==='preview'?'transparent':'var(--bg3)')+';border:none;border-right:1px solid var(--border);color:'+(wcRightTab==='preview'?'var(--dim)':'var(--text)')+';font-size:11px;font-weight:600;cursor:pointer">&#128196; File</button>' +
6348
6350
  '<button onclick="wcTabPreview()" style="padding:8px 16px;background:'+(wcRightTab==='preview'?'var(--bg3)':'transparent')+';border:none;color:'+(wcRightTab==='preview'?'var(--text)':'var(--dim)')+';font-size:11px;font-weight:600;cursor:pointer">&#127760; Sandbox</button>' +
6349
6351
  '</div>' +
6350
6352
  (wcRightTab === 'preview' ? wcSandboxPanelHtml() : (fileTabsHtml + codeHtml)) +
6351
6353
  '</div>' +
6354
+ '</div>' +
6352
6355
  '</div>';
6353
6356
 
6354
6357
  el.innerHTML =
6355
6358
  '<div style="display:flex;flex-direction:column;height:100%;min-height:0;padding:0 4px">' +
6356
6359
  headerHtml +
6357
- (wcMainTab === 'projects' ? wcProjectsPanelHtml() : editorHtml) +
6360
+ '<div style="flex:1;min-height:0;overflow:hidden">' +
6361
+ (wcMainTab === 'projects' ? wcProjectsPanelHtml() : editorHtml) +
6362
+ '</div>' +
6363
+ wcChatPanelHtml() +
6358
6364
  '</div>';
6359
6365
  }
6360
6366
 
@@ -6376,6 +6382,301 @@ function wcPickExample(i) {
6376
6382
  function wcTabFiles() { wcRightTab = 'files'; renderWebCraft(document.getElementById('content')); }
6377
6383
  function wcTabPreview() { wcRightTab = 'preview'; renderWebCraft(document.getElementById('content')); }
6378
6384
  function wcOpenSandbox() { if (wcState.sandbox.port) window.open('http://127.0.0.1:' + wcState.sandbox.port, '_blank'); }
6385
+
6386
+ // ── WebCraft Agent Chat Panel ─────────────────────────────────────────────
6387
+ function wcChatPanelHtml() {
6388
+ var hasProject = wcState.projectName && wcState.generatedFiles.length > 0;
6389
+ var placeholder = hasProject
6390
+ ? 'Parla con il tuo agente: chiedi correzioni, migliorie, nuove funzionalit\u00e0...'
6391
+ : 'Descrivi il progetto da creare, poi premi Genera...';
6392
+
6393
+ // Chat messages
6394
+ var messagesHtml = '';
6395
+ if (wcChat.length === 0 && hasProject) {
6396
+ messagesHtml = '<div style="font-size:11px;color:var(--dim);padding:8px 12px;font-style:italic">&#129302; Pronto! Dimmi cosa vuoi modificare o migliorare nel progetto.</div>';
6397
+ }
6398
+ for (var mi = 0; mi < wcChat.length; mi++) {
6399
+ var msg = wcChat[mi];
6400
+ if (msg.role === 'user') {
6401
+ messagesHtml += '<div style="display:flex;justify-content:flex-end;margin:4px 12px">' +
6402
+ '<div style="background:var(--green3);color:var(--bg);padding:6px 12px;border-radius:10px 10px 2px 10px;font-size:11px;max-width:70%;line-height:1.5">'+wcEsc(msg.text)+'</div>' +
6403
+ '</div>';
6404
+ if (msg.attachments && msg.attachments.length) {
6405
+ messagesHtml += '<div style="display:flex;justify-content:flex-end;margin:2px 12px;gap:4px">' +
6406
+ msg.attachments.map(function(a){ return '<span style="background:var(--bg3);border:1px solid var(--border2);border-radius:5px;padding:2px 7px;font-size:10px;color:var(--dim)">&#128206; '+wcEsc(a.name)+'</span>'; }).join('') +
6407
+ '</div>';
6408
+ }
6409
+ } else {
6410
+ var toolBadges = (msg.tools || []).map(function(tool){
6411
+ var icon = tool.op === 'edit' ? '&#9998;' : (tool.op === 'write' ? '&#10133;' : '&#128065;');
6412
+ var color = tool.result === 'ok' ? 'var(--green)' : 'var(--red)';
6413
+ return '<span style="display:inline-flex;align-items:center;gap:3px;background:var(--bg3);border:1px solid var(--border);border-radius:4px;padding:2px 6px;font-size:9px;font-family:var(--mono);color:'+color+'">'+icon+' '+wcEsc(tool.path)+'</span>';
6414
+ }).join(' ');
6415
+ messagesHtml += '<div style="margin:4px 12px">' +
6416
+ '<div style="display:flex;align-items:center;gap:6px;margin-bottom:3px">' +
6417
+ '<span style="font-size:10px;font-weight:700;color:var(--green)">&#129302; WebCraft Agent</span>' +
6418
+ '</div>' +
6419
+ '<div style="font-size:11px;color:var(--text);line-height:1.6;white-space:pre-wrap;max-width:85%">'+wcEsc(msg.text.replace(new RegExp('<tool>[\\s\\S]*?<\\/tool>', 'g'), '').trim())+'</div>' +
6420
+ (toolBadges ? '<div style="display:flex;flex-wrap:wrap;gap:4px;margin-top:5px">'+toolBadges+'</div>' : '') +
6421
+ '</div>';
6422
+ }
6423
+ }
6424
+ if (wcChatRunning) {
6425
+ messagesHtml += '<div id="wcChatTyping" style="margin:4px 12px;font-size:11px;color:var(--dim)">&#129302; <span style="opacity:.6">&#8230;</span></div>';
6426
+ }
6427
+
6428
+ // Attachments preview
6429
+ var attachPreview = '';
6430
+ if (wcChatAttachments.length > 0) {
6431
+ attachPreview = '<div style="display:flex;gap:6px;flex-wrap:wrap;padding:4px 12px 0">' +
6432
+ wcChatAttachments.map(function(a, ai){
6433
+ return '<span style="display:inline-flex;align-items:center;gap:4px;background:var(--bg3);border:1px solid var(--border2);border-radius:5px;padding:3px 8px;font-size:10px;color:var(--text)">' +
6434
+ '&#128206; '+wcEsc(a.name)+' <button onclick="wcRemoveAttachment('+ai+')" style="background:none;border:none;color:var(--dim);cursor:pointer;font-size:11px;line-height:1;padding:0">&times;</button>' +
6435
+ '</span>';
6436
+ }).join('') +
6437
+ '</div>';
6438
+ }
6439
+
6440
+ var sendBtnLabel = wcState.running ? '&#9203;' : (hasProject ? '&#9654;' : '&#9654; Genera');
6441
+ var inputDisabled = wcChatRunning || wcState.running;
6442
+
6443
+ // Project name row — shown only when no project yet
6444
+ var projNameRow = !hasProject
6445
+ ? '<div style="display:flex;align-items:center;gap:8px;padding:6px 12px 0">' +
6446
+ '<span style="font-size:10px;color:var(--dim);white-space:nowrap">Nome progetto:</span>' +
6447
+ '<input id="wcProjectName" placeholder="MioProgetto" value="'+wcEsc(wcState.projectName)+'" oninput="wcState.projectName=this.value" style="flex:1;padding:4px 8px;font-size:11px;border-radius:5px;border:1px solid var(--border2);background:var(--bg3);color:var(--text)">' +
6448
+ '</div>'
6449
+ : '<div style="padding:4px 12px 0;font-size:10px;color:var(--dim)">&#128196; <strong style="color:var(--green)">'+wcEsc(wcState.projectName)+'</strong> &mdash; scrivi per modificare o migliorare il progetto</div>';
6450
+
6451
+ return '<div style="border-top:1px solid var(--border);background:var(--bg2);flex-shrink:0;display:flex;flex-direction:column">' +
6452
+ // Messages
6453
+ '<div id="wcChatMessages" style="max-height:160px;overflow-y:auto;padding:6px 0">' +
6454
+ messagesHtml +
6455
+ '</div>' +
6456
+ // Attachments
6457
+ attachPreview +
6458
+ // Project name (only pre-generation)
6459
+ projNameRow +
6460
+ // Input row
6461
+ '<div style="display:flex;align-items:flex-end;gap:8px;padding:8px 12px">' +
6462
+ '<label style="cursor:pointer;color:var(--dim);font-size:16px;flex-shrink:0;padding-bottom:2px" title="Allega immagine o PDF">' +
6463
+ '&#128206;' +
6464
+ '<input type="file" id="wcFileInput" multiple accept="image/*,.pdf" style="display:none" onchange="wcHandleFileAttach(this)">' +
6465
+ '</label>' +
6466
+ '<textarea id="wcChatInput" rows="2" placeholder="'+placeholder+'" '+(inputDisabled?'disabled':'')+' style="flex:1;padding:8px 10px;font-size:12px;border-radius:8px;border:1px solid var(--border2);background:var(--bg3);color:var(--text);resize:none;line-height:1.5;font-family:inherit" onkeydown="wcChatKeydown(event)"></textarea>' +
6467
+ '<button onclick="wcChatSend()" '+(inputDisabled?'disabled':'')+' style="padding:8px 14px;background:var(--green3);border:none;border-radius:8px;color:var(--bg);font-size:13px;font-weight:700;cursor:pointer;flex-shrink:0;height:38px">'+sendBtnLabel+'</button>' +
6468
+ '</div>' +
6469
+ '</div>';
6470
+ }
6471
+
6472
+ function wcChatKeydown(e) {
6473
+ if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); wcChatSend(); }
6474
+ }
6475
+
6476
+ function wcRemoveAttachment(ai) {
6477
+ wcChatAttachments.splice(ai, 1);
6478
+ renderWebCraft(document.getElementById('content'));
6479
+ }
6480
+
6481
+ function wcHandleFileAttach(input) {
6482
+ var files = Array.from(input.files || []);
6483
+ files.forEach(function(file) {
6484
+ var reader = new FileReader();
6485
+ reader.onload = function(e) {
6486
+ var dataUrl = e.target.result;
6487
+ var base64 = dataUrl.split(',')[1];
6488
+ wcChatAttachments.push({ name: file.name, mimeType: file.type, base64: base64, size: file.size });
6489
+ renderWebCraft(document.getElementById('content'));
6490
+ };
6491
+ reader.readAsDataURL(file);
6492
+ });
6493
+ input.value = '';
6494
+ }
6495
+
6496
+ async function wcChatSend() {
6497
+ var inputEl = document.getElementById('wcChatInput');
6498
+ var msg = (inputEl ? inputEl.value : '').trim();
6499
+ var hasProject = wcState.projectName && wcState.generatedFiles.length > 0;
6500
+
6501
+ // If no project yet, use as description and generate
6502
+ if (!hasProject) {
6503
+ if (!msg || msg.length < 5) { alert(t('wc_describe_first')); return; }
6504
+ var projNameEl = document.getElementById('wcProjectName');
6505
+ if (projNameEl && projNameEl.value.trim()) wcState.projectName = projNameEl.value.trim();
6506
+ if (!wcState.projectName) wcState.projectName = 'MyProject';
6507
+ wcState.description = msg;
6508
+ if (inputEl) inputEl.value = '';
6509
+ wcGenerate();
6510
+ return;
6511
+ }
6512
+
6513
+ if (!msg && wcChatAttachments.length === 0) return;
6514
+ if (wcChatRunning || wcState.running) return;
6515
+
6516
+ var attachCopy = wcChatAttachments.slice();
6517
+ wcChatAttachments = [];
6518
+ wcChatRunning = true;
6519
+ wcChat.push({ role: 'user', text: msg, attachments: attachCopy });
6520
+ if (inputEl) inputEl.value = '';
6521
+ renderWebCraft(document.getElementById('content'));
6522
+ wcScrollChatToBottom();
6523
+
6524
+ try {
6525
+ var r = await fetch(API + '/api/studio/webcraft/agent', {
6526
+ method: 'POST',
6527
+ headers: { 'Content-Type': 'application/json' },
6528
+ body: JSON.stringify({
6529
+ projectName: wcState.projectName,
6530
+ message: msg,
6531
+ attachments: attachCopy.map(function(a){ return { name: a.name, mimeType: a.mimeType, base64: a.base64 }; })
6532
+ })
6533
+ });
6534
+
6535
+ if (!r.ok) {
6536
+ var errData = await r.json().catch(function(){ return {}; });
6537
+ wcChat.push({ role: 'agent', text: 'Errore: ' + (errData.error || r.status), tools: [] });
6538
+ wcChatRunning = false;
6539
+ renderWebCraft(document.getElementById('content'));
6540
+ return;
6541
+ }
6542
+
6543
+ var agentMsg = { role: 'agent', text: '', tools: [] };
6544
+ wcChat.push(agentMsg);
6545
+
6546
+ var reader2 = r.body.getReader();
6547
+ var dec = new TextDecoder();
6548
+ var buf = '';
6549
+ while (true) {
6550
+ var res = await reader2.read();
6551
+ if (res.done) break;
6552
+ buf += dec.decode(res.value, { stream: true });
6553
+ var parts = buf.split(String.fromCharCode(10)+String.fromCharCode(10));
6554
+ buf = parts.pop();
6555
+ for (var pi2 = 0; pi2 < parts.length; pi2++) {
6556
+ var line = parts[pi2].replace(/^data: /, '').trim();
6557
+ if (!line) continue;
6558
+ try {
6559
+ var ev = JSON.parse(line);
6560
+ if (ev.type === 'text') {
6561
+ agentMsg.text += ev.token;
6562
+ var typingEl = document.getElementById('wcChatTyping');
6563
+ if (typingEl) typingEl.textContent = '...';
6564
+ wcScrollChatToBottom();
6565
+ } else if (ev.type === 'tool') {
6566
+ agentMsg.tools.push({ op: ev.op, path: ev.path, result: ev.result });
6567
+ // Update file in generatedFiles if edited/written
6568
+ if ((ev.op === 'edit' || ev.op === 'write') && ev.result === 'ok') {
6569
+ // Reload file from server via projects/load (simplified: mark for reload)
6570
+ wcChat[wcChat.length-1] = agentMsg;
6571
+ renderWebCraft(document.getElementById('content'));
6572
+ }
6573
+ } else if (ev.type === 'done') {
6574
+ wcChatRunning = false;
6575
+ if (ev.changed) {
6576
+ // Reload project files from disk
6577
+ wcReloadProjectFiles();
6578
+ }
6579
+ } else if (ev.type === 'restart_sandbox') {
6580
+ wcStartSandbox();
6581
+ } else if (ev.type === 'error') {
6582
+ agentMsg.text += String.fromCharCode(10) + 'Errore: ' + ev.msg;
6583
+ wcChatRunning = false;
6584
+ }
6585
+ } catch(_) {}
6586
+ }
6587
+ }
6588
+ } catch(e) {
6589
+ wcChat.push({ role: 'agent', text: 'Errore di rete: ' + e.message, tools: [] });
6590
+ }
6591
+
6592
+ wcChatRunning = false;
6593
+ renderWebCraft(document.getElementById('content'));
6594
+ wcScrollChatToBottom();
6595
+ }
6596
+
6597
+ function wcScrollChatToBottom() {
6598
+ var el2 = document.getElementById('wcChatMessages');
6599
+ if (el2) el2.scrollTop = el2.scrollHeight;
6600
+ }
6601
+
6602
+ async function wcReloadProjectFiles() {
6603
+ if (!wcState.projectName) return;
6604
+ try {
6605
+ var r = await fetch(API + '/api/studio/webcraft/projects/load/' + encodeURIComponent(wcState.projectName));
6606
+ if (!r.ok) return;
6607
+ var d = await r.json();
6608
+ wcState.generatedFiles = d.files || [];
6609
+ renderWebCraft(document.getElementById('content'));
6610
+ } catch(_) {}
6611
+ }
6612
+
6613
+ // Auto-fix: poll autofix-queue every 3s while sandbox running
6614
+ function wcStartAutoFixPoller() {
6615
+ if (_wcAutoFixTimer) return;
6616
+ _wcAutoFixTimer = setInterval(function() {
6617
+ if (!wcState.sandbox.running && !wcState.sandbox.port) { wcStopAutoFixPoller(); return; }
6618
+ fetch(API + '/api/studio/webcraft/agent/autofix-queue').then(function(r){ return r.json(); }).then(function(d){
6619
+ var items = d.items || [];
6620
+ items.forEach(function(item) {
6621
+ if (item.type === 'module_not_found' && _wcAutoFixAttempts < 3) {
6622
+ _wcAutoFixAttempts++;
6623
+ wcTriggerAutoFix(item.module);
6624
+ }
6625
+ });
6626
+ }).catch(function(){});
6627
+ }, 3000);
6628
+ }
6629
+
6630
+ function wcStopAutoFixPoller() {
6631
+ if (_wcAutoFixTimer) { clearInterval(_wcAutoFixTimer); _wcAutoFixTimer = null; }
6632
+ }
6633
+
6634
+ async function wcTriggerAutoFix(missingModule) {
6635
+ if (wcChatRunning) return;
6636
+ var fixMsg = 'AUTO-FIX: Cannot find module ' + missingModule + String.fromCharCode(10) + 'Analizza tutti i file del progetto e correggi il require/import per questo modulo. Se il modulo non esiste, rimuovi il require e implementa la funzionalita con moduli disponibili o Node.js built-in.';
6637
+ wcChat.push({ role: 'user', text: '&#129302; Auto-fix modulo mancante: ' + missingModule });
6638
+ wcChatRunning = true;
6639
+ renderWebCraft(document.getElementById('content'));
6640
+ wcScrollChatToBottom();
6641
+
6642
+ try {
6643
+ var r = await fetch(API + '/api/studio/webcraft/agent', {
6644
+ method: 'POST',
6645
+ headers: { 'Content-Type': 'application/json' },
6646
+ body: JSON.stringify({ projectName: wcState.projectName, message: fixMsg, autofix: true })
6647
+ });
6648
+ if (!r.ok) { wcChatRunning = false; renderWebCraft(document.getElementById('content')); return; }
6649
+
6650
+ var agentMsg = { role: 'agent', text: '', tools: [] };
6651
+ wcChat.push(agentMsg);
6652
+ var reader3 = r.body.getReader();
6653
+ var dec2 = new TextDecoder();
6654
+ var buf2 = '';
6655
+ while (true) {
6656
+ var res2 = await reader3.read();
6657
+ if (res2.done) break;
6658
+ buf2 += dec2.decode(res2.value, { stream: true });
6659
+ var parts2 = buf2.split(String.fromCharCode(10)+String.fromCharCode(10));
6660
+ buf2 = parts2.pop();
6661
+ for (var pi3 = 0; pi3 < parts2.length; pi3++) {
6662
+ var line2 = parts2[pi3].replace(/^data: /, '').trim();
6663
+ if (!line2) continue;
6664
+ try {
6665
+ var ev2 = JSON.parse(line2);
6666
+ if (ev2.type === 'text') { agentMsg.text += ev2.token; }
6667
+ else if (ev2.type === 'tool') { agentMsg.tools.push({ op: ev2.op, path: ev2.path, result: ev2.result }); }
6668
+ else if (ev2.type === 'done') { wcChatRunning = false; if (ev2.changed) { wcReloadProjectFiles(); } }
6669
+ else if (ev2.type === 'restart_sandbox') { wcStartSandbox(); }
6670
+ else if (ev2.type === 'error') { agentMsg.text += String.fromCharCode(10)+'Errore: '+ev2.msg; wcChatRunning = false; }
6671
+ } catch(_) {}
6672
+ }
6673
+ }
6674
+ } catch(_) {}
6675
+
6676
+ wcChatRunning = false;
6677
+ renderWebCraft(document.getElementById('content'));
6678
+ wcScrollChatToBottom();
6679
+ }
6379
6680
  var _wcPhaseKeys = ['files','shims','pkg','env','deps','install','start'];
6380
6681
  function wcTogglePhase(idx) { var k = _wcPhaseKeys[idx]; if (k) { wcSandboxExpanded[k] = !wcSandboxExpanded[k]; renderWebCraft(document.getElementById('content')); } }
6381
6682
 
@@ -6453,9 +6754,9 @@ function wcSetFile(i) { wcState.activeFile = i; renderWebCraft(document.getEleme
6453
6754
 
6454
6755
  async function wcGenerate() {
6455
6756
  if (wcState.running) return;
6456
- var desc = (document.getElementById('wcDesc')||{}).value || wcState.description;
6457
- var projName = (document.getElementById('wcProjectName')||{}).value || wcState.projectName || 'myproject';
6458
- if (!desc || desc.length < 10) { alert(t('wc_describe_first')); return; }
6757
+ var desc = wcState.description;
6758
+ var projName = wcState.projectName || 'myproject';
6759
+ if (!desc || desc.length < 5) { alert(t('wc_describe_first')); return; }
6459
6760
  wcState.description = desc;
6460
6761
  wcState.projectName = projName;
6461
6762
  wcState.running = true;
@@ -6743,6 +7044,8 @@ async function wcStartSandbox() {
6743
7044
  wcState.sandbox.running = false;
6744
7045
  wcState.sandbox.port = evt.port;
6745
7046
  wcState.sandbox.dir = evt.dir;
7047
+ _wcAutoFixAttempts = 0;
7048
+ wcStartAutoFixPoller();
6746
7049
  renderWebCraft(document.getElementById('content'));
6747
7050
  } else if (evt.type === 'error') {
6748
7051
  wcState.sandbox.running = false;
@@ -6762,6 +7065,7 @@ async function wcStartSandbox() {
6762
7065
  async function wcStopSandbox() {
6763
7066
  await fetch(API + '/api/studio/webcraft/sandbox', {method:'DELETE'});
6764
7067
  wcState.sandbox = { running: false, port: null, dir: null, logs: [], error: null };
7068
+ wcStopAutoFixPoller();
6765
7069
  renderWebCraft(document.getElementById('content'));
6766
7070
  }
6767
7071