nothumanallowed 14.1.81 → 14.2.0

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": "14.1.81",
3
+ "version": "14.2.0",
4
4
  "description": "NotHumanAllowed — 38 AI agents, 80 tools, Studio (visual agentic workflows). Email, calendar, browser automation, screen capture, canvas, cron/heartbeat, Alexandria E2E messaging, GitHub, Notion, Slack, voice chat, free AI (Liara), 28 languages. Zero-dependency CLI.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/constants.mjs CHANGED
@@ -5,7 +5,7 @@ import { fileURLToPath } from 'url';
5
5
  const __filename = fileURLToPath(import.meta.url);
6
6
  const __dirname = path.dirname(__filename);
7
7
 
8
- export const VERSION = '14.1.81';
8
+ export const VERSION = '14.2.0';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -475,168 +475,259 @@ const ChatStore = {
475
475
  * Uses structured SSE events: { type: 'text', token } | { type: 'tool', ... } | { type: 'done', changed }
476
476
  */
477
477
  async function runWebCraftAgent(config, projectName, message, attachments, emit) {
478
+ const MAX_STEPS = 8; // max agentic loop iterations
478
479
  const dir = ProjectStore.dir(projectName);
479
480
  if (!fs.existsSync(dir)) { emit({ type: 'error', msg: 'Project not found' }); return; }
480
481
 
481
- // Ensure context files always exist
482
482
  SkillStore.ensureDefaults(projectName, config);
483
483
 
484
- const files = _listProjectFiles(dir);
485
- const skillCtx = SkillStore.context(projectName);
486
-
487
- const fileIndex = files.map((f) => `- ${f}`).join('\n');
488
484
  const today = new Date().toISOString().slice(0, 10);
489
485
  const LANG_MAP = { en:'English',it:'Italian',es:'Spanish',fr:'French',de:'German',pt:'Portuguese' };
490
486
  const language = LANG_MAP[(config?.language||'it').slice(0,2)] || 'Italian';
491
487
 
492
- // Build context: include files mentioned in the message + key project files
493
- const mentionedFiles = files.filter((f) => message.toLowerCase().includes(f.toLowerCase().split('/').pop() ?? ''));
494
- // Always include key structural files
495
- const keyFiles = files.filter((f) => /^(server|app|index)\.(js|mjs|ts)$/.test(f) || f === 'package.json' || f.includes('routes/index'));
496
- const contextFiles = [...new Set([...mentionedFiles, ...keyFiles, ...files.slice(0, 12)])].slice(0, 15);
497
- const fileContents = contextFiles.map((rel) => {
498
- try {
499
- const content = fs.readFileSync(path.join(dir, rel), 'utf-8');
500
- return `### FILE: ${rel}\n\`\`\`\n${content.slice(0, 8000)}\n\`\`\``;
501
- } catch { return ''; }
502
- }).filter(Boolean).join('\n\n');
503
-
504
488
  const toolSpec = `
505
- AVAILABLE TOOLS (use XML tags exactly as shown):
489
+ AVAILABLE TOOLS (use exactly ONE tool per <tool> tag):
506
490
 
507
- 1. Read a file:
508
- <tool>{"op":"read","path":"filename.js"}</tool>
491
+ 1. Read a file (to see its current content before editing):
492
+ <tool>{"op":"read","path":"relative/path.js"}</tool>
509
493
 
510
- 2. Edit a file (replace a snippet):
511
- <tool>{"op":"edit","path":"filename.js","old":"EXACT_EXISTING_CODE","new":"REPLACEMENT_CODE"}</tool>
494
+ 2. Edit a file (surgical replacement of exact code):
495
+ <tool>{"op":"edit","path":"relative/path.js","old":"EXACT_EXISTING_CODE","new":"REPLACEMENT_CODE"}</tool>
512
496
 
513
- 3. Write a complete file (full content):
514
- <tool>{"op":"write","path":"filename.js","content":"FULL_FILE_CONTENT"}</tool>
497
+ 3. Write/create a file (complete content):
498
+ <tool>{"op":"write","path":"relative/path.js","content":"FULL_FILE_CONTENT"}</tool>
499
+
500
+ 4. Check syntax of a JS file:
501
+ <tool>{"op":"check","path":"relative/path.js"}</tool>
502
+
503
+ WORKFLOW — follow this for every change:
504
+ 1. Read the file(s) you need to modify
505
+ 2. Make your changes with edit or write
506
+ 3. Use check to verify JS files have no syntax errors
507
+ 4. If check fails, read the file again and fix
508
+ 5. When ALL changes are complete and verified, output: <done/>
515
509
 
516
510
  RULES:
517
- - Always use edit for small targeted changes (preferredfaster and safer)
518
- - Use write only when creating a new file or doing a complete rewrite
519
- - "old" must be an EXACT verbatim match of the existing code — no paraphrasing
520
- - Never apply a tool to a file outside the project scope
521
- - After each tool use, continue explaining what you did
511
+ - "old" must be an EXACT verbatim copy from the file copy-paste, no paraphrasing
512
+ - Use edit for targeted changes, write for new files or complete rewrites
513
+ - ALWAYS read before edit if you haven't seen the file content yet
514
+ - ALWAYS check JS files after modifications
515
+ - Output <done/> when you are completely finished this is MANDATORY
522
516
  `;
523
517
 
524
- // Load memory.md and skills.md for context
525
- const ctxDir = SkillStore.dir(projectName);
526
- let skillContext = '';
527
- try {
528
- const memPath = path.join(ctxDir, 'memory.md');
529
- const skillsPath = path.join(ctxDir, 'skills.md');
530
- const providerPath = path.join(ctxDir, `${config.llm?.provider || 'nha'}.md`);
518
+ // Build system prompt with fresh file list each step
519
+ function buildSystemPrompt() {
520
+ const files = _listProjectFiles(dir);
521
+ const fileIndex = files.map((f) => `- ${f}`).join('\n');
522
+ const skillCtx = SkillStore.context(projectName);
523
+ const ctxDir = SkillStore.dir(projectName);
524
+ let skillContext = '';
525
+ try {
526
+ for (const name of ['memory.md', 'skills.md', `${config.llm?.provider || 'nha'}.md`]) {
527
+ const p = path.join(ctxDir, name);
528
+ if (fs.existsSync(p)) skillContext += `\n### ${name}:\n${fs.readFileSync(p, 'utf-8')}\n`;
529
+ }
530
+ } catch {}
531
531
 
532
- if (fs.existsSync(memPath)) {
533
- const memContent = fs.readFileSync(memPath, 'utf-8');
534
- skillContext += `\n### MEMORY:\n${memContent}\n`;
535
- }
536
- if (fs.existsSync(skillsPath)) {
537
- const skillsContent = fs.readFileSync(skillsPath, 'utf-8');
538
- skillContext += `\n### SKILLS:\n${skillsContent}\n`;
539
- }
540
- if (fs.existsSync(providerPath)) {
541
- const providerContent = fs.readFileSync(providerPath, 'utf-8');
542
- skillContext += `\n### MODEL INFO:\n${providerContent}\n`;
543
- }
544
- } catch {}
532
+ // Key files: load full content for server, package.json, index
533
+ const keyFiles = files.filter((f) =>
534
+ /^(server|app|index)\.(js|mjs|ts)$/.test(f) || f === 'package.json' || f.includes('routes/index')
535
+ );
536
+ const mentionedFiles = files.filter((f) =>
537
+ message.toLowerCase().includes((f.split('/').pop() || '').toLowerCase())
538
+ );
539
+ const contextFiles = [...new Set([...mentionedFiles, ...keyFiles])].slice(0, 10);
540
+ const fileContents = contextFiles.map((rel) => {
541
+ try {
542
+ const content = fs.readFileSync(path.join(dir, rel), 'utf-8');
543
+ return `### FILE: ${rel}\n\`\`\`\n${content}\n\`\`\``;
544
+ } catch { return ''; }
545
+ }).filter(Boolean).join('\n\n');
546
+
547
+ return [
548
+ `You are WebCraft Agent — an elite AI coding assistant. Today: ${today}. Language: ${language}.`,
549
+ `\nYou control the project IDE. You MUST use tools to implement changes — never just explain.`,
550
+ `\nYour workflow: read → plan → edit/write → check → verify → done.`,
551
+ `\nAfter EVERY tool use, you will receive the result. Based on the result, decide what to do next.`,
552
+ `\nWhen finished, output <done/> to signal completion.`,
553
+ `\n\n## PROJECT: ${projectName}`,
554
+ `\n## FILE TREE:\n${fileIndex}`,
555
+ skillContext,
556
+ skillCtx ? `\n\n## PROJECT KNOWLEDGE:\n${skillCtx}` : '',
557
+ attachments?.length ? `\n\n## ATTACHMENTS: ${attachments.map((a) => a.name).join(', ')}` : '',
558
+ fileContents ? `\n\n## LOADED FILES:\n${fileContents}` : '',
559
+ `\n\n${toolSpec}`,
560
+ ].join('');
561
+ }
545
562
 
546
- const systemPrompt = [
547
- `You are WebCraft Agent — a team of 200 senior developers working as one entity. Today is ${today}. Respond in ${language}.`,
548
- `\nYou have FULL control of the project IDE. You read files, edit them surgically, and write new ones.`,
549
- `\nYour edits MUST be enterprise-grade: security, error handling, responsive design, accessibility.`,
550
- `\nWhen the user asks for changes, you MUST use tools to implement them — never just explain. ACT, don't talk.`,
551
- `\nAfter each tool use, briefly explain what changed and why.`,
552
- `\n\n## PROJECT: ${projectName}`,
553
- `\n## FILES:\n${fileIndex}`,
554
- skillContext,
555
- skillCtx ? `\n\n## ADDITIONAL CONTEXT:\n${skillCtx}` : '',
556
- attachments?.length ? `\n\n## ATTACHMENTS: ${attachments.map((a) => a.name).join(', ')}` : '',
557
- `\n\n## CURRENT FILE CONTENTS:\n${fileContents}`,
558
- `\n\n${toolSpec}`,
559
- ].join('');
560
-
561
- // Prepare user content (text + images if any)
563
+ // Prepare user content
562
564
  const userContent = attachments?.length
563
565
  ? _buildMultimodalContent(message, attachments)
564
566
  : message;
565
567
 
566
- let fullResponse = '';
568
+ // ── Agentic loop ───────────────────────────────────────────────────────────
567
569
  let hasChanges = false;
570
+ const modifiedFiles = new Set();
571
+ let conversationHistory = [
572
+ { role: 'user', content: userContent },
573
+ ];
574
+
575
+ for (let step = 0; step < MAX_STEPS; step++) {
576
+ const systemPrompt = buildSystemPrompt();
577
+ emit({ type: 'step', step: step + 1, max: MAX_STEPS });
578
+
579
+ // Build user message from conversation history
580
+ // callLLMStream takes a single string, so we concatenate the conversation
581
+ let stepResponse = '';
582
+ const userMsg = conversationHistory.map((m) =>
583
+ m.role === 'user' ? m.content : `[ASSISTANT RESPONSE]\n${m.content.slice(0, 6000)}`
584
+ ).join('\n\n---\n\n');
585
+
586
+ await callLLMStream(config, systemPrompt, userMsg, (token) => {
587
+ stepResponse += token;
588
+ // Stream visible text (suppress tool tags)
589
+ const visible = token.replace(/<tool>[\s\S]*?<\/tool>/g, '').replace(/<done\s*\/>/g, '');
590
+ if (visible) emit({ type: 'text', token: visible });
591
+ }, { max_tokens: 16384 });
592
+
593
+ // Check if agent signaled completion
594
+ const isDone = stepResponse.includes('<done/>') || stepResponse.includes('<done />');
595
+
596
+ // Extract and execute ALL tool calls from this step
597
+ const toolRegex = /<tool>([\s\S]*?)<\/tool>/g;
598
+ let match;
599
+ const toolResults = [];
600
+
601
+ while ((match = toolRegex.exec(stepResponse)) !== null) {
602
+ let toolCall;
603
+ try {
604
+ // Fix common JSON issues from LLM
605
+ let raw = match[1].trim();
606
+ raw = raw.replace(/,\s*}/g, '}').replace(/,\s*]/g, ']');
607
+ toolCall = JSON.parse(raw);
608
+ } catch {
609
+ toolResults.push({ op: 'error', result: 'JSON parse failed' });
610
+ emit({ type: 'tool', op: 'parse_error', path: '', result: 'json_parse_failed' });
611
+ continue;
612
+ }
568
613
 
569
- await callLLMStream(config, systemPrompt, userContent, (token) => {
570
- fullResponse += token;
571
- // Suppress raw <tool> blocks from text stream — only emit visible text
572
- const visibleToken = token.replace(/<tool>[\s\S]*?<\/tool>/g, '');
573
- if (visibleToken) emit({ type: 'text', token: visibleToken });
574
- }, { max_tokens: 16384 });
575
-
576
- // ── Execute all tool calls found in the response ───────────────────────────
577
- const toolRegex = /<tool>([\s\S]*?)<\/tool>/g;
578
- let match;
579
- while ((match = toolRegex.exec(fullResponse)) !== null) {
580
- let toolCall;
581
- try { toolCall = JSON.parse(match[1].trim()); } catch {
582
- emit({ type: 'tool', op: 'parse_error', path: '', result: 'json_parse_failed' });
583
- continue;
584
- }
585
-
586
- const { op, path: relPath, old: oldStr, new: newStr, content } = toolCall;
587
- if (!relPath || !_isSafePath(relPath)) {
588
- emit({ type: 'tool', op, path: relPath ?? '', result: 'unsafe_path' });
589
- continue;
590
- }
591
-
592
- if (op === 'read') {
593
- const src = ProjectStore.readFile(projectName, relPath);
594
- emit({ type: 'tool', op: 'read', path: relPath, result: src !== null ? 'ok' : 'not_found' });
614
+ const { op, path: relPath, old: oldStr, new: newStr, content } = toolCall;
615
+ if (!relPath || !_isSafePath(relPath)) {
616
+ toolResults.push({ op, path: relPath, result: 'unsafe_path' });
617
+ emit({ type: 'tool', op, path: relPath ?? '', result: 'unsafe_path' });
618
+ continue;
619
+ }
595
620
 
596
- } else if (op === 'edit') {
597
- const src = ProjectStore.readFile(projectName, relPath);
598
- if (src === null) {
599
- emit({ type: 'tool', op: 'edit', path: relPath, result: 'file_not_found' });
600
- } else if (!src.includes(oldStr)) {
601
- // Fallback: try LLM-assisted repair
602
- const repaired = await _attemptEditRepair(config, relPath, src, oldStr, newStr);
603
- if (repaired) {
604
- ProjectStore.writeFile(projectName, relPath, repaired);
621
+ if (op === 'read') {
622
+ const src = ProjectStore.readFile(projectName, relPath);
623
+ const result = src !== null ? 'ok' : 'not_found';
624
+ toolResults.push({ op: 'read', path: relPath, result, content: src?.slice(0, 12000) });
625
+ emit({ type: 'tool', op: 'read', path: relPath, result });
626
+
627
+ } else if (op === 'check') {
628
+ const src = ProjectStore.readFile(projectName, relPath);
629
+ let checkResult = 'ok';
630
+ if (!src) { checkResult = 'file_not_found'; }
631
+ else if (relPath.endsWith('.js') || relPath.endsWith('.mjs')) {
632
+ try { new Function(src); } catch (e) { checkResult = `syntax_error: ${e.message.replace(/\n.*/s, '')}`; }
633
+ } else if (relPath.endsWith('.json')) {
634
+ try { JSON.parse(src); } catch (e) { checkResult = `json_error: ${e.message}`; }
635
+ } else if (relPath.endsWith('.html')) {
636
+ checkResult = src.includes('</html>') ? 'ok' : 'missing_closing_html_tag';
637
+ }
638
+ toolResults.push({ op: 'check', path: relPath, result: checkResult });
639
+ emit({ type: 'tool', op: 'check', path: relPath, result: checkResult });
640
+
641
+ } else if (op === 'edit') {
642
+ const src = ProjectStore.readFile(projectName, relPath);
643
+ if (src === null) {
644
+ toolResults.push({ op: 'edit', path: relPath, result: 'file_not_found' });
645
+ emit({ type: 'tool', op: 'edit', path: relPath, result: 'file_not_found' });
646
+ } else if (!src.includes(oldStr)) {
647
+ const repaired = await _attemptEditRepair(config, relPath, src, oldStr, newStr);
648
+ if (repaired) {
649
+ ProjectStore.writeFile(projectName, relPath, repaired);
650
+ hasChanges = true;
651
+ modifiedFiles.add(relPath);
652
+ toolResults.push({ op: 'edit', path: relPath, result: 'ok_repaired' });
653
+ emit({ type: 'tool', op: 'edit', path: relPath, result: 'ok_repaired', oldSnippet: oldStr.slice(0, 300), newSnippet: newStr?.slice(0, 300) });
654
+ } else {
655
+ toolResults.push({ op: 'edit', path: relPath, result: 'old_not_found', hint: 'Use read to see current content, then retry with exact text' });
656
+ emit({ type: 'tool', op: 'edit', path: relPath, result: 'old_not_found', oldSnippet: oldStr.slice(0, 200) });
657
+ }
658
+ } else {
659
+ const newSrc = src.replace(oldStr, newStr ?? '');
660
+ ProjectStore.writeFile(projectName, relPath, newSrc);
605
661
  hasChanges = true;
606
- emit({ type: 'tool', op: 'edit', path: relPath, result: 'ok_repaired', oldSnippet: oldStr.slice(0, 300), newSnippet: newStr.slice(0, 300) });
662
+ modifiedFiles.add(relPath);
663
+ toolResults.push({ op: 'edit', path: relPath, result: 'ok' });
664
+ emit({ type: 'tool', op: 'edit', path: relPath, result: 'ok', oldSnippet: oldStr.slice(0, 300), newSnippet: newStr?.slice(0, 300) ?? '' });
665
+ }
666
+
667
+ } else if (op === 'write') {
668
+ if (content === undefined) {
669
+ toolResults.push({ op: 'write', path: relPath, result: 'missing_content' });
670
+ emit({ type: 'tool', op: 'write', path: relPath, result: 'missing_content' });
607
671
  } else {
608
- emit({ type: 'tool', op: 'edit', path: relPath, result: 'old_not_found', oldSnippet: oldStr.slice(0, 200) });
672
+ ProjectStore.writeFile(projectName, relPath, content);
673
+ hasChanges = true;
674
+ modifiedFiles.add(relPath);
675
+ toolResults.push({ op: 'write', path: relPath, result: 'ok' });
676
+ emit({ type: 'tool', op: 'write', path: relPath, result: 'ok' });
609
677
  }
610
- } else {
611
- const newSrc = src.replace(oldStr, newStr ?? '');
612
- ProjectStore.writeFile(projectName, relPath, newSrc);
613
- hasChanges = true;
614
- emit({ type: 'tool', op: 'edit', path: relPath, result: 'ok', oldSnippet: oldStr.slice(0, 300), newSnippet: newStr?.slice(0, 300) ?? '' });
615
678
  }
679
+ }
680
+
681
+ // If agent said done or no tools were called, break
682
+ if (isDone || toolResults.length === 0) break;
683
+
684
+ // Build tool results feedback for next iteration
685
+ const feedbackParts = toolResults.map((r) => {
686
+ let msg = `[${r.op}] ${r.path || ''}: ${r.result}`;
687
+ if (r.op === 'read' && r.content) msg += `\n\`\`\`\n${r.content}\n\`\`\``;
688
+ if (r.hint) msg += ` — ${r.hint}`;
689
+ return msg;
690
+ });
691
+
692
+ // Add assistant response + tool results to conversation
693
+ conversationHistory.push({ role: 'assistant', content: stepResponse });
694
+ conversationHistory.push({ role: 'user', content: `TOOL RESULTS:\n${feedbackParts.join('\n\n')}\n\nContinue your work. When done, output <done/>` });
616
695
 
617
- } else if (op === 'write') {
618
- if (content === undefined) {
619
- emit({ type: 'tool', op: 'write', path: relPath, result: 'missing_content' });
620
- } else {
621
- ProjectStore.writeFile(projectName, relPath, content);
622
- hasChanges = true;
623
- emit({ type: 'tool', op: 'write', path: relPath, result: 'ok' });
696
+ // Trim conversation to avoid context overflow (keep first user msg + last 4 exchanges)
697
+ if (conversationHistory.length > 10) {
698
+ conversationHistory = [conversationHistory[0], ...conversationHistory.slice(-6)];
699
+ }
700
+ }
701
+
702
+ // ── Post-edit: syntax check all modified JS files ──────────────────────────
703
+ const syntaxErrors = [];
704
+ for (const relPath of modifiedFiles) {
705
+ if (relPath.endsWith('.js') || relPath.endsWith('.mjs')) {
706
+ const src = ProjectStore.readFile(projectName, relPath);
707
+ if (src) {
708
+ try { new Function(src); } catch (e) {
709
+ syntaxErrors.push({ file: relPath, error: e.message.replace(/\n.*/s, '') });
710
+ }
624
711
  }
625
712
  }
626
713
  }
714
+ if (syntaxErrors.length > 0) {
715
+ emit({ type: 'syntax_errors', errors: syntaxErrors });
716
+ }
627
717
 
628
- // Log chat interaction to changes.log.md
718
+ // Log chat interaction
629
719
  if (hasChanges) {
630
720
  try {
631
- const ctxDir = SkillStore.dir(projectName);
632
- const logFile = path.join(ctxDir, 'changes.log.md');
633
- const timestamp = new Date().toISOString();
634
- const logEntry = `\n## ${timestamp.slice(0, 16).replace('T', ' ')} Chat modification\n- User: ${message.slice(0, 100)}${message.length > 100 ? '...' : ''}\n- Files modified: ${fullResponse.match(/<tool>[\s\S]*?"op":"(write|edit)"[\s\S]*?"path":"([^"]+)"[\s\S]*?<\/tool>/g)?.map(m => m.match(/"path":"([^"]+)"/)?.[1]).filter(Boolean).join(', ') || 'none'}\n`;
635
- fs.writeFileSync(logFile, (fs.existsSync(logFile) ? fs.readFileSync(logFile, 'utf-8') : '') + logEntry, 'utf-8');
721
+ const logFile = path.join(SkillStore.dir(projectName), 'changes.log.md');
722
+ const ts = new Date().toISOString().slice(0, 16).replace('T', ' ');
723
+ const entry = `\n## ${ts} — Chat modification\n- User: ${message.slice(0, 100)}${message.length > 100 ? '...' : ''}\n- Files: ${[...modifiedFiles].join(', ')}\n`;
724
+ fs.writeFileSync(logFile, (fs.existsSync(logFile) ? fs.readFileSync(logFile, 'utf-8') : '') + entry, 'utf-8');
636
725
  } catch {}
637
726
  }
638
727
 
639
- emit({ type: 'done', changed: hasChanges });
728
+ // Notify about modified files so frontend can reload them
729
+ emit({ type: 'files_changed', files: [...modifiedFiles] });
730
+ emit({ type: 'done', changed: hasChanges, syntaxErrors: syntaxErrors.length });
640
731
  }
641
732
 
642
733
  // ── Generation pipeline (SSE) ─────────────────────────────────────────────────