nothumanallowed 13.2.39 → 13.2.40

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.2.39",
3
+ "version": "13.2.40",
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 = '13.2.39';
8
+ export const VERSION = '13.2.40';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -624,6 +624,25 @@ function getProviderHeaders(provider, apiKey) {
624
624
  }
625
625
 
626
626
  /** Parse a complete SSE text body (already read via res.text()) and call onToken per token. */
627
+ /**
628
+ * Qwen3 sometimes emits entire paragraphs as a single token with no spaces/newlines.
629
+ * This restores markdown structure: newlines before headings, list items, numbered lists.
630
+ * Only applied to non-HTML content (inside HTML tags is left untouched).
631
+ */
632
+ function fixQwen3Markdown(text) {
633
+ // Don't touch HTML content
634
+ if (/<[a-zA-Z]/.test(text) && text.includes('</')) return text;
635
+ return text
636
+ // newline before markdown headings (##, ###, etc.) not at start
637
+ .replace(/([^\n])(#{1,6}\s)/g, '$1\n$2')
638
+ // newline before list items (- or * at word boundary) not at start
639
+ .replace(/([^\n])(\n?[-*]\s)/g, '$1\n$2')
640
+ // newline before numbered list items (1. 2. etc.) not at start
641
+ .replace(/([^\n])(\n?\d+\.\s)/g, '$1\n$2')
642
+ // newline before --- separators
643
+ .replace(/([^\n])(---)/g, '$1\n$2');
644
+ }
645
+
627
646
  function parseSSEText(text, format, onToken) {
628
647
  let fullText = '';
629
648
  let thinkBuf = '';
@@ -661,9 +680,8 @@ function parseSSEText(text, format, onToken) {
661
680
  }
662
681
  }
663
682
  if (out) {
664
- // Qwen3 emits tokens without spaces between words — insert space when needed
665
- // But NOT inside HTML tags (e.g. < div class = "foo" > would get broken)
666
683
  const insideTag = fullText.lastIndexOf('<') > fullText.lastIndexOf('>');
684
+ if (!insideTag) out = fixQwen3Markdown(out);
667
685
  if (fullText && out && !insideTag && !/[\s\n]$/.test(fullText) && !/^[\s\n.,;:!?)\]}'">]/.test(out)) {
668
686
  out = ' ' + out;
669
687
  }
@@ -730,9 +748,8 @@ async function streamSSEWithCallback(res, format, onToken) {
730
748
  }
731
749
  }
732
750
  if (out) {
733
- // Qwen3 emits tokens without spaces between words — insert space when needed
734
- // But NOT inside HTML tags
735
751
  const insideTag2 = fullText.lastIndexOf('<') > fullText.lastIndexOf('>');
752
+ if (!insideTag2) out = fixQwen3Markdown(out);
736
753
  if (fullText && out && !insideTag2 && !/[\s\n]$/.test(fullText) && !/^[\s\n.,;:!?)\]}'">]/.test(out)) {
737
754
  out = ' ' + out;
738
755
  }