nothumanallowed 15.0.12 → 15.0.14

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": "15.0.12",
3
+ "version": "15.0.14",
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": {
@@ -738,14 +738,20 @@ WORKFLOW:
738
738
  while ((match = toolRegex.exec(stepResponse)) !== null) {
739
739
  let toolCall;
740
740
  try {
741
- // Fix common JSON issues from LLM
742
741
  let raw = match[1].trim();
743
742
  raw = raw.replace(/,\s*}/g, '}').replace(/,\s*]/g, ']');
744
743
  toolCall = JSON.parse(raw);
745
744
  } catch {
746
- toolResults.push({ op: 'error', result: 'JSON parse failed' });
747
- emit({ type: 'tool', op: 'parse_error', path: '', result: 'json_parse_failed' });
748
- continue;
745
+ // LLM often puts raw HTML/code inside JSON values without proper escaping.
746
+ // Extract fields manually using a robust regex-based parser.
747
+ try {
748
+ toolCall = _parseToolCallRobust(match[1].trim());
749
+ } catch (parseErr2) {
750
+ console.error('[TOOL-PARSE] JSON parse failed even after robust parse:', parseErr2.message, 'raw:', match[1].slice(0, 200));
751
+ toolResults.push({ op: 'error', result: 'JSON parse failed' });
752
+ emit({ type: 'tool', op: 'parse_error', path: '', result: 'json_parse_failed' });
753
+ continue;
754
+ }
749
755
  }
750
756
 
751
757
  const { op, path: relPath, old: oldStr, new: newStr, content, newPath, query, glob: globPat, cmd } = toolCall;
@@ -817,11 +823,14 @@ WORKFLOW:
817
823
 
818
824
  // ── edit ──
819
825
  } else if (op === 'edit') {
826
+ console.log(`[EDIT-DEBUG] path=${relPath} oldStr.length=${oldStr?.length ?? 'null'} newStr.length=${newStr?.length ?? 'null'}`);
827
+ if (oldStr) console.log(`[EDIT-DEBUG] old first 100: ${JSON.stringify(oldStr.slice(0, 100))}`);
820
828
  const src = ProjectStore.readFile(projectName, relPath);
821
829
  if (src === null) {
822
830
  toolResults.push({ op: 'edit', path: relPath, result: 'file_not_found' });
823
831
  emit({ type: 'tool', op: 'edit', path: relPath, result: 'file_not_found' });
824
832
  } else if (src.includes(oldStr)) {
833
+ console.log(`[EDIT-DEBUG] EXACT MATCH found — applying edit`);
825
834
  // Exact match — apply directly
826
835
  const newSrc = src.replace(oldStr, newStr ?? '');
827
836
  ProjectStore.writeFile(projectName, relPath, newSrc);
@@ -830,6 +839,7 @@ WORKFLOW:
830
839
  toolResults.push({ op: 'edit', path: relPath, result: 'ok' });
831
840
  emit({ type: 'tool', op: 'edit', path: relPath, result: 'ok', oldSnippet: oldStr.slice(0, 2000), newSnippet: newStr?.slice(0, 2000) ?? '' });
832
841
  } else {
842
+ console.log(`[EDIT-DEBUG] NO exact match — trying fuzzy. src.length=${src.length} oldStr.length=${oldStr?.length}`);
833
843
  // Fuzzy match: compare lines ignoring leading/trailing whitespace
834
844
  const oldLines = oldStr.split('\n').map(l => l.trim());
835
845
  const srcLines = src.split('\n');
@@ -2651,6 +2661,93 @@ function _safeName(name) {
2651
2661
  return (name ?? '').replace(/[^a-zA-Z0-9_\-. ]/g, '_').trim() || 'unnamed';
2652
2662
  }
2653
2663
 
2664
+ /**
2665
+ * Robust tool call parser for when JSON.parse fails.
2666
+ * The LLM often puts raw HTML/code with unescaped quotes, newlines, < > inside JSON values.
2667
+ * This extracts "op", "path", "old", "new", "content", "query", "cmd" etc. by finding
2668
+ * the key-value boundaries manually.
2669
+ */
2670
+ function _parseToolCallRobust(raw) {
2671
+ const result = {};
2672
+
2673
+ // Extract "op" — always a simple string
2674
+ const opMatch = raw.match(/"op"\s*[:>]\s*"([^"]+)"/);
2675
+ if (opMatch) result.op = opMatch[1];
2676
+
2677
+ // Extract "path" — simple string
2678
+ const pathMatch = raw.match(/"path"\s*[:>]\s*"([^"]+)"/);
2679
+ if (pathMatch) result.path = pathMatch[1];
2680
+
2681
+ // Extract "newPath" — simple string
2682
+ const newPathMatch = raw.match(/"newPath"\s*[:>]\s*"([^"]+)"/);
2683
+ if (newPathMatch) result.newPath = newPathMatch[1];
2684
+
2685
+ // Extract "query" — simple string (fix \| to |)
2686
+ const queryMatch = raw.match(/"query"\s*[:>]\s*"([^"]+)"/);
2687
+ if (queryMatch) result.query = queryMatch[1].replace(/\\\|/g, '|');
2688
+
2689
+ // Extract "glob" — simple string
2690
+ const globMatch = raw.match(/"glob"\s*[:>]\s*"([^"]+)"/);
2691
+ if (globMatch) result.glob = globMatch[1];
2692
+
2693
+ // Extract "cmd" — simple string
2694
+ const cmdMatch = raw.match(/"cmd"\s*[:>]\s*"([^"]+)"/);
2695
+ if (cmdMatch) result.cmd = cmdMatch[1];
2696
+
2697
+ // For edit: extract "old" and "new" — these can be HUGE multiline strings with HTML
2698
+ // Strategy: find "old" key, then grab everything until we hit ","new" or the end
2699
+ if (result.op === 'edit') {
2700
+ const oldIdx = raw.indexOf('"old"');
2701
+ const newIdx = raw.indexOf('"new"');
2702
+ if (oldIdx >= 0 && newIdx > oldIdx) {
2703
+ // old value is between "old":"/>" and ","new"
2704
+ let oldStart = raw.indexOf('"', oldIdx + 5); // find opening quote after "old":
2705
+ if (oldStart < 0) oldStart = raw.indexOf('>', oldIdx + 5); // handle "old">
2706
+ if (oldStart >= 0) {
2707
+ oldStart++; // skip the opening quote/bracket
2708
+ // Find the end — look for ","new" pattern
2709
+ const oldEnd = raw.lastIndexOf('"', newIdx - 1);
2710
+ if (oldEnd > oldStart) {
2711
+ result.old = raw.slice(oldStart, oldEnd);
2712
+ }
2713
+ }
2714
+ // new value is after "new":"
2715
+ let newStart = raw.indexOf('"', newIdx + 5);
2716
+ if (newStart < 0) newStart = raw.indexOf('>', newIdx + 5);
2717
+ if (newStart >= 0) {
2718
+ newStart++;
2719
+ // Find end — last " before the closing }
2720
+ const lastBrace = raw.lastIndexOf('}');
2721
+ const newEnd = raw.lastIndexOf('"', lastBrace);
2722
+ if (newEnd > newStart) {
2723
+ result.new = raw.slice(newStart, newEnd);
2724
+ }
2725
+ }
2726
+ }
2727
+ }
2728
+
2729
+ // For write: extract "content"
2730
+ if (result.op === 'write') {
2731
+ const contentIdx = raw.indexOf('"content"');
2732
+ if (contentIdx >= 0) {
2733
+ let contentStart = raw.indexOf('"', contentIdx + 9);
2734
+ if (contentStart < 0) contentStart = raw.indexOf('>', contentIdx + 9);
2735
+ if (contentStart >= 0) {
2736
+ contentStart++;
2737
+ const lastBrace = raw.lastIndexOf('}');
2738
+ const contentEnd = raw.lastIndexOf('"', lastBrace);
2739
+ if (contentEnd > contentStart) {
2740
+ result.content = raw.slice(contentStart, contentEnd);
2741
+ }
2742
+ }
2743
+ }
2744
+ }
2745
+
2746
+ if (!result.op) throw new Error('Could not extract op from tool call');
2747
+ console.log(`[TOOL-PARSE] Robust parse recovered: op=${result.op} path=${result.path} old.len=${result.old?.length ?? 0} new.len=${result.new?.length ?? 0}`);
2748
+ return result;
2749
+ }
2750
+
2654
2751
  function _isSafePath(relPath) {
2655
2752
  if (!relPath || typeof relPath !== 'string') return false;
2656
2753
  if (relPath.includes('..') || path.isAbsolute(relPath)) return false;