nothumanallowed 13.5.51 → 13.5.52

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.52",
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.52';
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,9 +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)">' +
6319
+ '<div style="width:280px;flex-shrink:0;display:flex;flex-direction:column;gap:10px;overflow-y:auto;height:100%">' +
6313
6320
  '<div style="background:var(--bg2);border:1px solid var(--border);border-radius:10px;padding:14px">' +
6314
6321
  '<div style="font-size:10px;color:var(--dim);text-transform:uppercase;letter-spacing:.8px;margin-bottom:8px">'+t('wc_project')+'</div>' +
6315
6322
  '<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">' +
@@ -6342,19 +6349,23 @@ function renderWebCraft(el) {
6342
6349
  '<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
6350
  : '') +
6344
6351
  '</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">' +
6352
+ '<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
6353
  '<div style="display:flex;border-bottom:1px solid var(--border);flex-shrink:0">' +
6347
6354
  '<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
6355
  '<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
6356
  '</div>' +
6350
6357
  (wcRightTab === 'preview' ? wcSandboxPanelHtml() : (fileTabsHtml + codeHtml)) +
6351
6358
  '</div>' +
6359
+ '</div>' +
6352
6360
  '</div>';
6353
6361
 
6354
6362
  el.innerHTML =
6355
6363
  '<div style="display:flex;flex-direction:column;height:100%;min-height:0;padding:0 4px">' +
6356
6364
  headerHtml +
6357
- (wcMainTab === 'projects' ? wcProjectsPanelHtml() : editorHtml) +
6365
+ '<div style="flex:1;min-height:0;overflow:hidden">' +
6366
+ (wcMainTab === 'projects' ? wcProjectsPanelHtml() : editorHtml) +
6367
+ '</div>' +
6368
+ wcChatPanelHtml() +
6358
6369
  '</div>';
6359
6370
  }
6360
6371
 
@@ -6376,6 +6387,290 @@ function wcPickExample(i) {
6376
6387
  function wcTabFiles() { wcRightTab = 'files'; renderWebCraft(document.getElementById('content')); }
6377
6388
  function wcTabPreview() { wcRightTab = 'preview'; renderWebCraft(document.getElementById('content')); }
6378
6389
  function wcOpenSandbox() { if (wcState.sandbox.port) window.open('http://127.0.0.1:' + wcState.sandbox.port, '_blank'); }
6390
+
6391
+ // ── WebCraft Agent Chat Panel ─────────────────────────────────────────────
6392
+ function wcChatPanelHtml() {
6393
+ var hasProject = wcState.projectName && wcState.generatedFiles.length > 0;
6394
+ var placeholder = hasProject
6395
+ ? 'Parla con il tuo agente: chiedi correzioni, migliorie, nuove funzionalit\u00e0...'
6396
+ : 'Descrivi il progetto da creare, poi premi Genera...';
6397
+
6398
+ // Chat messages
6399
+ var messagesHtml = '';
6400
+ if (wcChat.length === 0 && hasProject) {
6401
+ 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>';
6402
+ }
6403
+ for (var mi = 0; mi < wcChat.length; mi++) {
6404
+ var msg = wcChat[mi];
6405
+ if (msg.role === 'user') {
6406
+ messagesHtml += '<div style="display:flex;justify-content:flex-end;margin:4px 12px">' +
6407
+ '<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>' +
6408
+ '</div>';
6409
+ if (msg.attachments && msg.attachments.length) {
6410
+ messagesHtml += '<div style="display:flex;justify-content:flex-end;margin:2px 12px;gap:4px">' +
6411
+ 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('') +
6412
+ '</div>';
6413
+ }
6414
+ } else {
6415
+ var toolBadges = (msg.tools || []).map(function(tool){
6416
+ var icon = tool.op === 'edit' ? '&#9998;' : (tool.op === 'write' ? '&#10133;' : '&#128065;');
6417
+ var color = tool.result === 'ok' ? 'var(--green)' : 'var(--red)';
6418
+ 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>';
6419
+ }).join(' ');
6420
+ messagesHtml += '<div style="margin:4px 12px">' +
6421
+ '<div style="display:flex;align-items:center;gap:6px;margin-bottom:3px">' +
6422
+ '<span style="font-size:10px;font-weight:700;color:var(--green)">&#129302; WebCraft Agent</span>' +
6423
+ '</div>' +
6424
+ '<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>' +
6425
+ (toolBadges ? '<div style="display:flex;flex-wrap:wrap;gap:4px;margin-top:5px">'+toolBadges+'</div>' : '') +
6426
+ '</div>';
6427
+ }
6428
+ }
6429
+ if (wcChatRunning) {
6430
+ messagesHtml += '<div id="wcChatTyping" style="margin:4px 12px;font-size:11px;color:var(--dim)">&#129302; <span style="opacity:.6">&#8230;</span></div>';
6431
+ }
6432
+
6433
+ // Attachments preview
6434
+ var attachPreview = '';
6435
+ if (wcChatAttachments.length > 0) {
6436
+ attachPreview = '<div style="display:flex;gap:6px;flex-wrap:wrap;padding:4px 12px 0">' +
6437
+ wcChatAttachments.map(function(a, ai){
6438
+ 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)">' +
6439
+ '&#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>' +
6440
+ '</span>';
6441
+ }).join('') +
6442
+ '</div>';
6443
+ }
6444
+
6445
+ var sendBtnLabel = hasProject ? (wcState.running ? '&#9203;' : '&#9654;') : '&#9654; Genera';
6446
+ var inputDisabled = wcChatRunning || wcState.running;
6447
+
6448
+ return '<div style="border-top:1px solid var(--border);background:var(--bg2);flex-shrink:0;display:flex;flex-direction:column">' +
6449
+ // Messages
6450
+ '<div id="wcChatMessages" style="max-height:160px;overflow-y:auto;padding:6px 0">' +
6451
+ messagesHtml +
6452
+ '</div>' +
6453
+ // Attachments
6454
+ attachPreview +
6455
+ // Input row
6456
+ '<div style="display:flex;align-items:flex-end;gap:8px;padding:8px 12px;border-top:1px solid var(--border)">' +
6457
+ '<label style="cursor:pointer;color:var(--dim);font-size:16px;flex-shrink:0;padding-bottom:2px" title="Allega immagine o PDF">' +
6458
+ '&#128206;' +
6459
+ '<input type="file" id="wcFileInput" multiple accept="image/*,.pdf" style="display:none" onchange="wcHandleFileAttach(this)">' +
6460
+ '</label>' +
6461
+ '<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>' +
6462
+ '<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>' +
6463
+ '</div>' +
6464
+ '</div>';
6465
+ }
6466
+
6467
+ function wcChatKeydown(e) {
6468
+ if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); wcChatSend(); }
6469
+ }
6470
+
6471
+ function wcRemoveAttachment(ai) {
6472
+ wcChatAttachments.splice(ai, 1);
6473
+ renderWebCraft(document.getElementById('content'));
6474
+ }
6475
+
6476
+ function wcHandleFileAttach(input) {
6477
+ var files = Array.from(input.files || []);
6478
+ files.forEach(function(file) {
6479
+ var reader = new FileReader();
6480
+ reader.onload = function(e) {
6481
+ var dataUrl = e.target.result;
6482
+ var base64 = dataUrl.split(',')[1];
6483
+ wcChatAttachments.push({ name: file.name, mimeType: file.type, base64: base64, size: file.size });
6484
+ renderWebCraft(document.getElementById('content'));
6485
+ };
6486
+ reader.readAsDataURL(file);
6487
+ });
6488
+ input.value = '';
6489
+ }
6490
+
6491
+ async function wcChatSend() {
6492
+ var inputEl = document.getElementById('wcChatInput');
6493
+ var msg = (inputEl ? inputEl.value : '').trim();
6494
+ var hasProject = wcState.projectName && wcState.generatedFiles.length > 0;
6495
+
6496
+ // If no project yet, use as description and generate
6497
+ if (!hasProject) {
6498
+ if (!msg || msg.length < 5) { alert(t('wc_describe_first')); return; }
6499
+ var projNameEl = document.getElementById('wcProjectName');
6500
+ if (projNameEl && projNameEl.value) wcState.projectName = projNameEl.value;
6501
+ wcState.description = msg;
6502
+ if (inputEl) inputEl.value = '';
6503
+ wcGenerate();
6504
+ return;
6505
+ }
6506
+
6507
+ if (!msg && wcChatAttachments.length === 0) return;
6508
+ if (wcChatRunning || wcState.running) return;
6509
+
6510
+ var attachCopy = wcChatAttachments.slice();
6511
+ wcChatAttachments = [];
6512
+ wcChatRunning = true;
6513
+ wcChat.push({ role: 'user', text: msg, attachments: attachCopy });
6514
+ if (inputEl) inputEl.value = '';
6515
+ renderWebCraft(document.getElementById('content'));
6516
+ wcScrollChatToBottom();
6517
+
6518
+ try {
6519
+ var r = await fetch(API + '/api/studio/webcraft/agent', {
6520
+ method: 'POST',
6521
+ headers: { 'Content-Type': 'application/json' },
6522
+ body: JSON.stringify({
6523
+ projectName: wcState.projectName,
6524
+ message: msg,
6525
+ attachments: attachCopy.map(function(a){ return { name: a.name, mimeType: a.mimeType, base64: a.base64 }; })
6526
+ })
6527
+ });
6528
+
6529
+ if (!r.ok) {
6530
+ var errData = await r.json().catch(function(){ return {}; });
6531
+ wcChat.push({ role: 'agent', text: 'Errore: ' + (errData.error || r.status), tools: [] });
6532
+ wcChatRunning = false;
6533
+ renderWebCraft(document.getElementById('content'));
6534
+ return;
6535
+ }
6536
+
6537
+ var agentMsg = { role: 'agent', text: '', tools: [] };
6538
+ wcChat.push(agentMsg);
6539
+
6540
+ var reader2 = r.body.getReader();
6541
+ var dec = new TextDecoder();
6542
+ var buf = '';
6543
+ while (true) {
6544
+ var res = await reader2.read();
6545
+ if (res.done) break;
6546
+ buf += dec.decode(res.value, { stream: true });
6547
+ var parts = buf.split(String.fromCharCode(10)+String.fromCharCode(10));
6548
+ buf = parts.pop();
6549
+ for (var pi2 = 0; pi2 < parts.length; pi2++) {
6550
+ var line = parts[pi2].replace(/^data: /, '').trim();
6551
+ if (!line) continue;
6552
+ try {
6553
+ var ev = JSON.parse(line);
6554
+ if (ev.type === 'text') {
6555
+ agentMsg.text += ev.token;
6556
+ var typingEl = document.getElementById('wcChatTyping');
6557
+ if (typingEl) typingEl.textContent = '...';
6558
+ wcScrollChatToBottom();
6559
+ } else if (ev.type === 'tool') {
6560
+ agentMsg.tools.push({ op: ev.op, path: ev.path, result: ev.result });
6561
+ // Update file in generatedFiles if edited/written
6562
+ if ((ev.op === 'edit' || ev.op === 'write') && ev.result === 'ok') {
6563
+ // Reload file from server via projects/load (simplified: mark for reload)
6564
+ wcChat[wcChat.length-1] = agentMsg;
6565
+ renderWebCraft(document.getElementById('content'));
6566
+ }
6567
+ } else if (ev.type === 'done') {
6568
+ wcChatRunning = false;
6569
+ if (ev.changed) {
6570
+ // Reload project files from disk
6571
+ wcReloadProjectFiles();
6572
+ }
6573
+ } else if (ev.type === 'restart_sandbox') {
6574
+ wcStartSandbox();
6575
+ } else if (ev.type === 'error') {
6576
+ agentMsg.text += String.fromCharCode(10) + 'Errore: ' + ev.msg;
6577
+ wcChatRunning = false;
6578
+ }
6579
+ } catch(_) {}
6580
+ }
6581
+ }
6582
+ } catch(e) {
6583
+ wcChat.push({ role: 'agent', text: 'Errore di rete: ' + e.message, tools: [] });
6584
+ }
6585
+
6586
+ wcChatRunning = false;
6587
+ renderWebCraft(document.getElementById('content'));
6588
+ wcScrollChatToBottom();
6589
+ }
6590
+
6591
+ function wcScrollChatToBottom() {
6592
+ var el2 = document.getElementById('wcChatMessages');
6593
+ if (el2) el2.scrollTop = el2.scrollHeight;
6594
+ }
6595
+
6596
+ async function wcReloadProjectFiles() {
6597
+ if (!wcState.projectName) return;
6598
+ try {
6599
+ var r = await fetch(API + '/api/studio/webcraft/projects/load/' + encodeURIComponent(wcState.projectName));
6600
+ if (!r.ok) return;
6601
+ var d = await r.json();
6602
+ wcState.generatedFiles = d.files || [];
6603
+ renderWebCraft(document.getElementById('content'));
6604
+ } catch(_) {}
6605
+ }
6606
+
6607
+ // Auto-fix: poll autofix-queue every 3s while sandbox running
6608
+ function wcStartAutoFixPoller() {
6609
+ if (_wcAutoFixTimer) return;
6610
+ _wcAutoFixTimer = setInterval(function() {
6611
+ if (!wcState.sandbox.running && !wcState.sandbox.port) { wcStopAutoFixPoller(); return; }
6612
+ fetch(API + '/api/studio/webcraft/agent/autofix-queue').then(function(r){ return r.json(); }).then(function(d){
6613
+ var items = d.items || [];
6614
+ items.forEach(function(item) {
6615
+ if (item.type === 'module_not_found' && _wcAutoFixAttempts < 3) {
6616
+ _wcAutoFixAttempts++;
6617
+ wcTriggerAutoFix(item.module);
6618
+ }
6619
+ });
6620
+ }).catch(function(){});
6621
+ }, 3000);
6622
+ }
6623
+
6624
+ function wcStopAutoFixPoller() {
6625
+ if (_wcAutoFixTimer) { clearInterval(_wcAutoFixTimer); _wcAutoFixTimer = null; }
6626
+ }
6627
+
6628
+ async function wcTriggerAutoFix(missingModule) {
6629
+ if (wcChatRunning) return;
6630
+ 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.';
6631
+ wcChat.push({ role: 'user', text: '&#129302; Auto-fix modulo mancante: ' + missingModule });
6632
+ wcChatRunning = true;
6633
+ renderWebCraft(document.getElementById('content'));
6634
+ wcScrollChatToBottom();
6635
+
6636
+ try {
6637
+ var r = await fetch(API + '/api/studio/webcraft/agent', {
6638
+ method: 'POST',
6639
+ headers: { 'Content-Type': 'application/json' },
6640
+ body: JSON.stringify({ projectName: wcState.projectName, message: fixMsg, autofix: true })
6641
+ });
6642
+ if (!r.ok) { wcChatRunning = false; renderWebCraft(document.getElementById('content')); return; }
6643
+
6644
+ var agentMsg = { role: 'agent', text: '', tools: [] };
6645
+ wcChat.push(agentMsg);
6646
+ var reader3 = r.body.getReader();
6647
+ var dec2 = new TextDecoder();
6648
+ var buf2 = '';
6649
+ while (true) {
6650
+ var res2 = await reader3.read();
6651
+ if (res2.done) break;
6652
+ buf2 += dec2.decode(res2.value, { stream: true });
6653
+ var parts2 = buf2.split(String.fromCharCode(10)+String.fromCharCode(10));
6654
+ buf2 = parts2.pop();
6655
+ for (var pi3 = 0; pi3 < parts2.length; pi3++) {
6656
+ var line2 = parts2[pi3].replace(/^data: /, '').trim();
6657
+ if (!line2) continue;
6658
+ try {
6659
+ var ev2 = JSON.parse(line2);
6660
+ if (ev2.type === 'text') { agentMsg.text += ev2.token; }
6661
+ else if (ev2.type === 'tool') { agentMsg.tools.push({ op: ev2.op, path: ev2.path, result: ev2.result }); }
6662
+ else if (ev2.type === 'done') { wcChatRunning = false; if (ev2.changed) { wcReloadProjectFiles(); } }
6663
+ else if (ev2.type === 'restart_sandbox') { wcStartSandbox(); }
6664
+ else if (ev2.type === 'error') { agentMsg.text += String.fromCharCode(10)+'Errore: '+ev2.msg; wcChatRunning = false; }
6665
+ } catch(_) {}
6666
+ }
6667
+ }
6668
+ } catch(_) {}
6669
+
6670
+ wcChatRunning = false;
6671
+ renderWebCraft(document.getElementById('content'));
6672
+ wcScrollChatToBottom();
6673
+ }
6379
6674
  var _wcPhaseKeys = ['files','shims','pkg','env','deps','install','start'];
6380
6675
  function wcTogglePhase(idx) { var k = _wcPhaseKeys[idx]; if (k) { wcSandboxExpanded[k] = !wcSandboxExpanded[k]; renderWebCraft(document.getElementById('content')); } }
6381
6676
 
@@ -6743,6 +7038,8 @@ async function wcStartSandbox() {
6743
7038
  wcState.sandbox.running = false;
6744
7039
  wcState.sandbox.port = evt.port;
6745
7040
  wcState.sandbox.dir = evt.dir;
7041
+ _wcAutoFixAttempts = 0;
7042
+ wcStartAutoFixPoller();
6746
7043
  renderWebCraft(document.getElementById('content'));
6747
7044
  } else if (evt.type === 'error') {
6748
7045
  wcState.sandbox.running = false;
@@ -6762,6 +7059,7 @@ async function wcStartSandbox() {
6762
7059
  async function wcStopSandbox() {
6763
7060
  await fetch(API + '/api/studio/webcraft/sandbox', {method:'DELETE'});
6764
7061
  wcState.sandbox = { running: false, port: null, dir: null, logs: [], error: null };
7062
+ wcStopAutoFixPoller();
6765
7063
  renderWebCraft(document.getElementById('content'));
6766
7064
  }
6767
7065