@ai-matrx/rich-content 0.0.0 → 0.2.3

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.
Files changed (248) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/LICENSE +1 -1
  3. package/README.md +58 -3
  4. package/dist/display/blocks/csv/CsvBlock.d.ts +10 -0
  5. package/dist/display/blocks/csv/CsvBlock.js +245 -0
  6. package/dist/display/blocks/table/parseMarkdownTable.d.ts +20 -0
  7. package/dist/display/blocks/table/parseMarkdownTable.js +35 -0
  8. package/dist/display/chat-markdown/citations/remarkMatrxCite.d.ts +33 -0
  9. package/dist/display/chat-markdown/citations/remarkMatrxCite.js +71 -0
  10. package/dist/display/chat-markdown/matrx-variables/remarkMatrxVariable.d.ts +29 -0
  11. package/dist/display/chat-markdown/matrx-variables/remarkMatrxVariable.js +71 -0
  12. package/dist/display/chat-markdown/page-break/remarkMatrxPageBreak.d.ts +55 -0
  13. package/dist/display/chat-markdown/page-break/remarkMatrxPageBreak.js +108 -0
  14. package/dist/display/chat-markdown/rehypeSafeRawHtml.d.ts +59 -0
  15. package/dist/display/chat-markdown/rehypeSafeRawHtml.js +140 -0
  16. package/dist/display/markdown-classification/processors/bock-processors/parse-markdown-table.d.ts +14 -0
  17. package/dist/display/markdown-classification/processors/bock-processors/parse-markdown-table.js +171 -0
  18. package/dist/display/markdown-classification/processors/types.d.ts +120 -0
  19. package/dist/display/markdown-classification/processors/types.js +10 -0
  20. package/dist/display/markdown-classification/processors/utils/client-blocks.d.ts +169 -0
  21. package/dist/display/markdown-classification/processors/utils/client-blocks.js +0 -0
  22. package/dist/display/markdown-classification/processors/utils/content-splitter-core.d.ts +223 -0
  23. package/dist/display/markdown-classification/processors/utils/content-splitter-core.js +1791 -0
  24. package/dist/display/markdown-classification/processors/utils/content-splitter-v2.d.ts +15 -0
  25. package/dist/display/markdown-classification/processors/utils/content-splitter-v2.js +19 -0
  26. package/dist/display/markdown-classification/processors/utils/expand-text-blocks.d.ts +9 -0
  27. package/dist/display/markdown-classification/processors/utils/expand-text-blocks.js +36 -0
  28. package/dist/display/markdown-classification/processors/utils/gfm-table-lines.d.ts +5 -0
  29. package/dist/display/markdown-classification/processors/utils/gfm-table-lines.js +40 -0
  30. package/dist/display/markdown-classification/processors/utils/gfm-table-vector-inputs.d.ts +16 -0
  31. package/dist/display/markdown-classification/processors/utils/gfm-table-vector-inputs.js +385 -0
  32. package/dist/display/markdown-classification/processors/utils/gfm-table-vectors.d.ts +43 -0
  33. package/dist/display/markdown-classification/processors/utils/gfm-table-vectors.js +33 -0
  34. package/dist/display/markdown-classification/processors/utils/table-data-parser.d.ts +9 -0
  35. package/dist/display/markdown-classification/processors/utils/table-data-parser.js +30 -0
  36. package/dist/display/markdown-classification/processors/utils/thinking-parser.d.ts +7 -0
  37. package/dist/display/markdown-classification/processors/utils/thinking-parser.js +70 -0
  38. package/dist/display/render-block.d.ts +23 -0
  39. package/dist/display/render-block.js +0 -0
  40. package/dist/display/themes.d.ts +1023 -0
  41. package/dist/display/themes.js +1105 -0
  42. package/dist/display/types.d.ts +32 -0
  43. package/dist/display/types.js +0 -0
  44. package/dist/host/ContentIrHostBoundary.d.ts +8 -0
  45. package/dist/host/ContentIrHostBoundary.js +15 -0
  46. package/dist/host/ErrorActions.d.ts +4 -0
  47. package/dist/host/ErrorActions.js +11 -0
  48. package/dist/host/NestedContent.d.ts +3 -0
  49. package/dist/host/NestedContent.js +12 -0
  50. package/dist/host/ResourcePeek.d.ts +6 -0
  51. package/dist/host/ResourcePeek.js +14 -0
  52. package/dist/host/component-registry.d.ts +2 -0
  53. package/dist/host/component-registry.js +25 -0
  54. package/dist/host/diagnostics.d.ts +2 -0
  55. package/dist/host/diagnostics.js +15 -0
  56. package/dist/host/index.d.ts +3 -0
  57. package/dist/host/index.js +18 -0
  58. package/dist/host/kind-incident.d.ts +3 -0
  59. package/dist/host/kind-incident.js +12 -0
  60. package/dist/host/kind-registry.d.ts +2 -0
  61. package/dist/host/kind-registry.js +35 -0
  62. package/dist/host/people.d.ts +3 -0
  63. package/dist/host/people.js +12 -0
  64. package/dist/host/registry.d.ts +27 -0
  65. package/dist/host/registry.js +55 -0
  66. package/dist/host/splitter-envelopes.d.ts +2 -0
  67. package/dist/host/splitter-envelopes.js +7 -0
  68. package/dist/host/tables.d.ts +6 -0
  69. package/dist/host/tables.js +8 -0
  70. package/dist/host/toast.d.ts +2 -0
  71. package/dist/host/toast.js +57 -0
  72. package/dist/host/types.d.ts +157 -0
  73. package/dist/host/types.js +0 -0
  74. package/dist/host/wikilinks.d.ts +15 -0
  75. package/dist/host/wikilinks.js +23 -0
  76. package/dist/kinds/host/route-env.d.ts +33 -0
  77. package/dist/kinds/host/route-env.js +16 -0
  78. package/dist/kinds/react/KindEscapedNotice.d.ts +14 -0
  79. package/dist/kinds/react/KindEscapedNotice.js +72 -0
  80. package/dist/kinds/react/ProvisionalKindBoundary.d.ts +19 -0
  81. package/dist/kinds/react/ProvisionalKindBoundary.js +17 -0
  82. package/dist/kinds/react/ensure-kind-renderable.d.ts +14 -0
  83. package/dist/kinds/react/ensure-kind-renderable.js +45 -0
  84. package/dist/kinds/react/kind-problems.d.ts +62 -0
  85. package/dist/kinds/react/kind-problems.js +134 -0
  86. package/dist/kinds/react/kind-route.d.ts +23 -0
  87. package/dist/kinds/react/kind-route.js +24 -0
  88. package/dist/kinds/react/loading/infer-loading-slug.d.ts +58 -0
  89. package/dist/kinds/react/loading/infer-loading-slug.js +100 -0
  90. package/dist/kinds/react/loading/kind-loading-components.d.ts +48 -0
  91. package/dist/kinds/react/loading/kind-loading-components.js +447 -0
  92. package/dist/kinds/react/loading/kind-loading-registry.d.ts +21 -0
  93. package/dist/kinds/react/loading/kind-loading-registry.js +62 -0
  94. package/dist/kinds/react/loading/kind-loading-slugs.d.ts +18 -0
  95. package/dist/kinds/react/loading/kind-loading-slugs.js +34 -0
  96. package/dist/kinds/react/loading/kind-loading.types.d.ts +79 -0
  97. package/dist/kinds/react/loading/kind-loading.types.js +31 -0
  98. package/dist/kinds/react/loading/resolve-loading-slug.d.ts +29 -0
  99. package/dist/kinds/react/loading/resolve-loading-slug.js +51 -0
  100. package/dist/kinds/react/partial-kind-route.d.ts +32 -0
  101. package/dist/kinds/react/partial-kind-route.js +27 -0
  102. package/dist/kinds/react/registry-versioned.d.ts +54 -0
  103. package/dist/kinds/react/registry-versioned.js +18 -0
  104. package/dist/kinds/react/route-at-version.d.ts +38 -0
  105. package/dist/kinds/react/route-at-version.js +78 -0
  106. package/dist/kinds/react/slot/KindSlot.d.ts +87 -0
  107. package/dist/kinds/react/slot/KindSlot.js +65 -0
  108. package/dist/kinds/react/use-registry-repaint.d.ts +6 -0
  109. package/dist/kinds/react/use-registry-repaint.js +36 -0
  110. package/dist/kinds/react/useIrNode.d.ts +9 -0
  111. package/dist/kinds/react/useIrNode.js +46 -0
  112. package/dist/kinds/react/useLiveJsonRegion.d.ts +15 -0
  113. package/dist/kinds/react/useLiveJsonRegion.js +84 -0
  114. package/dist/kinds/redux/render-block-envelope.d.ts +14 -0
  115. package/dist/kinds/redux/render-block-envelope.js +26 -0
  116. package/dist/kinds/registry/kind-correctors.d.ts +63 -0
  117. package/dist/kinds/registry/kind-correctors.js +94 -0
  118. package/dist/kinds/registry/legacy-quiz-envelope.d.ts +1 -0
  119. package/dist/kinds/registry/legacy-quiz-envelope.js +1 -0
  120. package/dist/kinds/registry/region-envelope-memo.d.ts +45 -0
  121. package/dist/kinds/registry/region-envelope-memo.js +94 -0
  122. package/dist/kinds/registry/registry-debug.d.ts +38 -0
  123. package/dist/kinds/registry/registry-debug.js +39 -0
  124. package/dist/markdown-core/MarkdownCore.d.ts +6 -0
  125. package/dist/markdown-core/MarkdownCore.js +32 -0
  126. package/dist/markdown-core/MarkdownCoreImpl.d.ts +3 -0
  127. package/dist/markdown-core/MarkdownCoreImpl.js +27 -0
  128. package/dist/markdown-core/MarkdownCoreServer.d.ts +4 -0
  129. package/dist/markdown-core/MarkdownCoreServer.js +26 -0
  130. package/dist/markdown-core/heading-anchors-context.d.ts +2 -0
  131. package/dist/markdown-core/heading-anchors-context.js +11 -0
  132. package/dist/markdown-core/heading-anchors-props.d.ts +2 -0
  133. package/dist/markdown-core/heading-anchors-props.js +6 -0
  134. package/dist/markdown-core/markdown-core-html.d.ts +3 -0
  135. package/dist/markdown-core/markdown-core-html.js +33 -0
  136. package/dist/markdown-core/markdown-core-presets.d.ts +22 -0
  137. package/dist/markdown-core/markdown-core-presets.js +118 -0
  138. package/dist/markdown-core/markdown-core-types.d.ts +43 -0
  139. package/dist/markdown-core/markdown-core-types.js +0 -0
  140. package/dist/markdown-core/math/vendor/micromark-math-math-flow.d.ts +15 -0
  141. package/dist/markdown-core/math/vendor/micromark-math-math-flow.js +143 -0
  142. package/dist/markdown-core/math/vendor/micromark-math-math-text.d.ts +24 -0
  143. package/dist/markdown-core/math/vendor/micromark-math-math-text.js +127 -0
  144. package/dist/markdown-core/math/vendor/micromark-math-syntax.d.ts +30 -0
  145. package/dist/markdown-core/math/vendor/micromark-math-syntax.js +15 -0
  146. package/dist/markdown-core/math/vendor/rehype-katex.d.ts +15 -0
  147. package/dist/markdown-core/math/vendor/rehype-katex.js +83 -0
  148. package/dist/markdown-core/math/vendor/remark-math.d.ts +9 -0
  149. package/dist/markdown-core/math/vendor/remark-math.js +20 -0
  150. package/dist/markdown-core/math-normalizer.d.ts +57 -0
  151. package/dist/markdown-core/math-normalizer.js +315 -0
  152. package/dist/markdown-core/plain-title.d.ts +2 -0
  153. package/dist/markdown-core/plain-title.js +1 -0
  154. package/dist/markdown-core/stream-heal.d.ts +2 -0
  155. package/dist/markdown-core/stream-heal.js +184 -0
  156. package/dist/markdown-core/streaming-context.d.ts +2 -0
  157. package/dist/markdown-core/streaming-context.js +11 -0
  158. package/dist/markdown-core/syntax/callout-types.d.ts +9 -0
  159. package/dist/markdown-core/syntax/callout-types.js +101 -0
  160. package/dist/markdown-core/syntax/containers.d.ts +17 -0
  161. package/dist/markdown-core/syntax/containers.js +353 -0
  162. package/dist/markdown-core/syntax/document-numbering.d.ts +52 -0
  163. package/dist/markdown-core/syntax/document-numbering.js +184 -0
  164. package/dist/markdown-core/syntax/elements/CalloutIcon.d.ts +3 -0
  165. package/dist/markdown-core/syntax/elements/CalloutIcon.js +41 -0
  166. package/dist/markdown-core/syntax/elements/CrossRef.d.ts +12 -0
  167. package/dist/markdown-core/syntax/elements/CrossRef.js +57 -0
  168. package/dist/markdown-core/syntax/elements/CsvFence.d.ts +4 -0
  169. package/dist/markdown-core/syntax/elements/CsvFence.js +18 -0
  170. package/dist/markdown-core/syntax/elements/DetailsElements.d.ts +12 -0
  171. package/dist/markdown-core/syntax/elements/DetailsElements.js +38 -0
  172. package/dist/markdown-core/syntax/elements/DocumentNumbering.d.ts +20 -0
  173. package/dist/markdown-core/syntax/elements/DocumentNumbering.js +37 -0
  174. package/dist/markdown-core/syntax/elements/DocumentPropertiesPanel.d.ts +4 -0
  175. package/dist/markdown-core/syntax/elements/DocumentPropertiesPanel.js +46 -0
  176. package/dist/markdown-core/syntax/elements/InDocAnchor.d.ts +11 -0
  177. package/dist/markdown-core/syntax/elements/InDocAnchor.js +87 -0
  178. package/dist/markdown-core/syntax/elements/MarkdownSourceEdit.d.ts +24 -0
  179. package/dist/markdown-core/syntax/elements/MarkdownSourceEdit.js +28 -0
  180. package/dist/markdown-core/syntax/elements/MatrxTabs.d.ts +10 -0
  181. package/dist/markdown-core/syntax/elements/MatrxTabs.js +59 -0
  182. package/dist/markdown-core/syntax/elements/MentionChip.d.ts +7 -0
  183. package/dist/markdown-core/syntax/elements/MentionChip.js +69 -0
  184. package/dist/markdown-core/syntax/elements/NestedBody.d.ts +3 -0
  185. package/dist/markdown-core/syntax/elements/NestedBody.js +13 -0
  186. package/dist/markdown-core/syntax/elements/TableOfContents.d.ts +5 -0
  187. package/dist/markdown-core/syntax/elements/TableOfContents.js +44 -0
  188. package/dist/markdown-core/syntax/elements/TaskCheckbox.d.ts +7 -0
  189. package/dist/markdown-core/syntax/elements/TaskCheckbox.js +63 -0
  190. package/dist/markdown-core/syntax/elements/WikiEmbed.d.ts +7 -0
  191. package/dist/markdown-core/syntax/elements/WikiEmbed.js +68 -0
  192. package/dist/markdown-core/syntax/elements/WikiLink.d.ts +10 -0
  193. package/dist/markdown-core/syntax/elements/WikiLink.js +114 -0
  194. package/dist/markdown-core/syntax/elements/core-syntax-elements.d.ts +22 -0
  195. package/dist/markdown-core/syntax/elements/core-syntax-elements.js +70 -0
  196. package/dist/markdown-core/syntax/elements/find-in-document.d.ts +3 -0
  197. package/dist/markdown-core/syntax/elements/find-in-document.js +20 -0
  198. package/dist/markdown-core/syntax/elements/wiki-target.d.ts +5 -0
  199. package/dist/markdown-core/syntax/elements/wiki-target.js +8 -0
  200. package/dist/markdown-core/syntax/frontmatter.d.ts +26 -0
  201. package/dist/markdown-core/syntax/frontmatter.js +72 -0
  202. package/dist/markdown-core/syntax/gfm-cell-pipes.d.ts +11 -0
  203. package/dist/markdown-core/syntax/gfm-cell-pipes.js +4 -0
  204. package/dist/markdown-core/syntax/inline-syntax.d.ts +26 -0
  205. package/dist/markdown-core/syntax/inline-syntax.js +163 -0
  206. package/dist/markdown-core/syntax/linear-scan.d.ts +46 -0
  207. package/dist/markdown-core/syntax/linear-scan.js +129 -0
  208. package/dist/markdown-core/syntax/mdast-helpers.d.ts +48 -0
  209. package/dist/markdown-core/syntax/mdast-helpers.js +48 -0
  210. package/dist/markdown-core/syntax/mentions.d.ts +2 -0
  211. package/dist/markdown-core/syntax/mentions.js +42 -0
  212. package/dist/markdown-core/syntax/names.d.ts +6 -0
  213. package/dist/markdown-core/syntax/names.js +18 -0
  214. package/dist/markdown-core/syntax/prepare-syntax-source.d.ts +1 -0
  215. package/dist/markdown-core/syntax/prepare-syntax-source.js +78 -0
  216. package/dist/markdown-core/syntax/rehype-matrx-syntax.d.ts +9 -0
  217. package/dist/markdown-core/syntax/rehype-matrx-syntax.js +180 -0
  218. package/dist/markdown-core/syntax/remark-inline-only.d.ts +23 -0
  219. package/dist/markdown-core/syntax/remark-inline-only.js +25 -0
  220. package/dist/markdown-core/syntax/remark-matrx-syntax.d.ts +7 -0
  221. package/dist/markdown-core/syntax/remark-matrx-syntax.js +351 -0
  222. package/dist/markdown-core/syntax/remark-table-code-pipes.d.ts +7 -0
  223. package/dist/markdown-core/syntax/remark-table-code-pipes.js +23 -0
  224. package/dist/markdown-core/syntax/stream-heal-syntax.d.ts +2 -0
  225. package/dist/markdown-core/syntax/stream-heal-syntax.js +108 -0
  226. package/dist/markdown-core/syntax/task-source.d.ts +10 -0
  227. package/dist/markdown-core/syntax/task-source.js +34 -0
  228. package/dist/markdown-core/syntax/useDocumentProperties.d.ts +3 -0
  229. package/dist/markdown-core/syntax/useDocumentProperties.js +8 -0
  230. package/dist/math-vendor-LICENSE-NOTICE.md +18 -0
  231. package/dist/styles.css +0 -0
  232. package/dist/utils/addressWithoutNavigating.d.ts +68 -0
  233. package/dist/utils/addressWithoutNavigating.js +60 -0
  234. package/dist/utils/client-lazy.d.ts +13 -0
  235. package/dist/utils/client-lazy.js +24 -0
  236. package/dist/utils/decision-options.d.ts +21 -0
  237. package/dist/utils/decision-options.js +32 -0
  238. package/dist/utils/gfm-lexer.d.ts +5 -0
  239. package/dist/utils/gfm-lexer.js +10 -0
  240. package/dist/utils/json.d.ts +63 -0
  241. package/dist/utils/json.js +22 -0
  242. package/dist/utils/plain-title.d.ts +25 -0
  243. package/dist/utils/plain-title.js +51 -0
  244. package/dist/utils/table-source.d.ts +62 -0
  245. package/dist/utils/table-source.js +191 -0
  246. package/dist/utils/youtube.d.ts +52 -0
  247. package/dist/utils/youtube.js +137 -0
  248. package/package.json +132 -5
@@ -0,0 +1,1791 @@
1
+ import { RESERVED_PREFIX, tryDecodeDirective } from "@ai-matrx/content-ir";
2
+ import { isJsonObject } from "../../../../utils/json.js";
3
+ import { parseDecisionOptionsFromBody } from "../../../../utils/decision-options.js";
4
+ import {
5
+ DIRECTIVE_CONTAINER_OPEN,
6
+ DirectiveContainerTracker
7
+ } from "@ai-matrx/content-ir/source";
8
+ import { TITLED_IMAGE_LINE } from "@ai-matrx/content-ir/source";
9
+ import { continuesTable, isGfmDelimiterRow, opensTable, tableContainerIndent, tableStartsAt } from "./gfm-table-lines.js";
10
+ import {
11
+ parseYouTubeUrl,
12
+ isYouTubeThumbnailUrl,
13
+ youTubeWatchUrl
14
+ } from "../../../../utils/youtube.js";
15
+ import {
16
+ recognizeOurFileUrl,
17
+ mightBeOurFileUrl
18
+ } from "@ai-matrx/media/files/engine/media/our-file-sources";
19
+ import {
20
+ normalizeRecoveredContainerPiece,
21
+ splitAroundEmbeddedKindJson
22
+ } from "@ai-matrx/content-ir/surfaces";
23
+ import {
24
+ isJsonFenceLanguage,
25
+ jsonKindSignal,
26
+ bareRegionBreaksIntoProse
27
+ } from "@ai-matrx/content-ir/surfaces";
28
+ import { liftQuotedKindRegions } from "@ai-matrx/content-ir/surfaces";
29
+ import {
30
+ decodeDoubleEncodedKindText,
31
+ unescapeMarkdownKindJson
32
+ } from "@ai-matrx/content-ir/surfaces";
33
+ import { unwrapKindImageAlt } from "@ai-matrx/content-ir/surfaces";
34
+ import { IR_ENVELOPE_KEY } from "@ai-matrx/content-ir";
35
+ import { ALLOWED_RAW_HTML_TAGS } from "../../../chat-markdown/rehypeSafeRawHtml.js";
36
+ import { isPageBreakLine } from "@ai-matrx/print/directives";
37
+ import { readXmlTag } from "@ai-matrx/content-ir/source";
38
+ import { FENCE_META_KEY, splitFenceInfo } from "@ai-matrx/content-ir/source";
39
+ import {
40
+ closeFence,
41
+ fenceOpenerOf,
42
+ isHtmlBlockTagName,
43
+ XmlContainerTracker
44
+ } from "@ai-matrx/content-ir/source";
45
+ import { indexOutsideInlineCode } from "@ai-matrx/content-ir/source";
46
+ import {
47
+ findBalancedXmlClose,
48
+ initialXmlBalance
49
+ } from "@ai-matrx/content-ir/source";
50
+ const NO_SPLITTER_ENVELOPES = {
51
+ withIrEnvelope: (_source, metadata) => metadata,
52
+ fenceRegion: () => null,
53
+ xmlRegion: () => null
54
+ };
55
+ const DIRECTIVE_HEAD_RE = new RegExp(
56
+ String.raw`^\{\s*"__kind"\s*:\s*"${RESERVED_PREFIX}`
57
+ );
58
+ function looksLikeDirectiveHead(content) {
59
+ return DIRECTIVE_HEAD_RE.test(content.trimStart());
60
+ }
61
+ function countStructuralBraces(source, open = "{", close = "}") {
62
+ let opens = 0;
63
+ let closes = 0;
64
+ let inString = false;
65
+ let escaped = false;
66
+ for (const char of source) {
67
+ if (inString) {
68
+ if (escaped) escaped = false;
69
+ else if (char === "\\") escaped = true;
70
+ else if (char === '"') inString = false;
71
+ continue;
72
+ }
73
+ if (char === '"') inString = true;
74
+ else if (char === open) opens++;
75
+ else if (char === close) closes++;
76
+ }
77
+ return { opens, closes };
78
+ }
79
+ function blockHasResolvedRootKind(block) {
80
+ const envelope = block.metadata?.[IR_ENVELOPE_KEY];
81
+ if (typeof envelope !== "object" || envelope === null) return false;
82
+ const root = envelope.root;
83
+ if (typeof root !== "object" || root === null) return false;
84
+ const kind = root.kind;
85
+ return typeof kind === "string" && kind.length > 0;
86
+ }
87
+ function isJsonFenceBlock(block) {
88
+ return block.type === "code" && isJsonFenceLanguage(block.language);
89
+ }
90
+ function isQuotedSourceFenceBlock(block) {
91
+ return block.type === "code" && block.metadata?.genericXmlContainer !== true && !isJsonFenceLanguage(block.language);
92
+ }
93
+ function recoverEmbeddedKindJsonBlocksWith(input, envelopes) {
94
+ const recovered = [];
95
+ for (const block of input) {
96
+ if (block.type === "artifact" || block.type === "matrx" || block.type === "matrx_file" || // An XML TAG — closed or never closed — is STRUCTURE, not quoted
97
+ // source (the owner's ruling (b), round 3): a kind inside it is data
98
+ // and leaves the card below, exactly as the live stream lifts it. A
99
+ // ```xml FENCE stays quoted source (`isQuotedSourceFenceBlock`).
100
+ blockHasResolvedRootKind(block) || // A fence of another language (```ts, ```xml, ```markdown …) is the
101
+ // model QUOTING SOURCE: a kind inside it stays as written (the owner's
102
+ // ruling, 2026-09-30 — the same definition the leaf gate reads).
103
+ isQuotedSourceFenceBlock(block)) {
104
+ recovered.push(block);
105
+ continue;
106
+ }
107
+ const genericXml = block.metadata?.genericXmlContainer === true;
108
+ const pieces = splitAroundEmbeddedKindJson(block.content, {
109
+ excludeLiteralContexts: genericXml,
110
+ liftJsonFences: genericXml,
111
+ // Prose, tables and sections: an inline code span is quoted source.
112
+ // A JSON fence's body is JSON — a backtick inside a string is no span.
113
+ excludeQuotedSource: !genericXml && !isJsonFenceBlock(block)
114
+ });
115
+ if (pieces.length === 1 && pieces[0]?.type === "container") {
116
+ recovered.push(block);
117
+ continue;
118
+ }
119
+ let followsKind = false;
120
+ for (const piece of pieces) {
121
+ if (piece.type === "chrome") continue;
122
+ if (piece.type === "residual") {
123
+ recovered.push({
124
+ type: "code",
125
+ content: piece.json,
126
+ language: "json",
127
+ metadata: envelopes.withIrEnvelope(piece.json, void 0)
128
+ });
129
+ followsKind = false;
130
+ continue;
131
+ }
132
+ if (piece.type === "kind") {
133
+ followsKind = true;
134
+ if (looksLikeDirectiveHead(piece.content)) {
135
+ recovered.push({
136
+ type: "matrx",
137
+ content: piece.content,
138
+ language: "json"
139
+ });
140
+ continue;
141
+ }
142
+ recovered.push({
143
+ type: "code",
144
+ content: piece.content,
145
+ language: "json",
146
+ metadata: envelopes.withIrEnvelope(piece.content, void 0)
147
+ });
148
+ continue;
149
+ }
150
+ const content = normalizeRecoveredContainerPiece(
151
+ piece.content,
152
+ block.type,
153
+ followsKind
154
+ );
155
+ followsKind = false;
156
+ if (!content) continue;
157
+ recovered.push({
158
+ ...block,
159
+ content,
160
+ // An XML piece stays STRUCTURE (`isQuotedSourceXmlBlock`) — the card
161
+ // draws whatever kind it still holds as the kind.
162
+ metadata: genericXml ? { genericXmlContainer: true } : void 0
163
+ });
164
+ }
165
+ }
166
+ return recovered;
167
+ }
168
+ const SPECIAL_CODE_LANGUAGES = [
169
+ "transcript",
170
+ "tasks",
171
+ "structured_info",
172
+ "questionnaire",
173
+ "flashcards",
174
+ "cooking_recipe",
175
+ "mermaid",
176
+ "svg",
177
+ "chart",
178
+ "map",
179
+ "stats",
180
+ "diff",
181
+ // html + react are DELIVERABLES (a webpage / a live component), not throwaway
182
+ // code snippets — promote them so they materialize into persistent artifacts
183
+ // (render-by-id, no re-creation), unlike a bare ```code fence.
184
+ "html",
185
+ "react",
186
+ // A ```matrx fence carries one Matrx Envelope (reference / secret / directive
187
+ // / validation). Promoted to a first-class block so MatrxEnvelopeBlock renders
188
+ // it (chips for references, a muted card otherwise) instead of raw JSON. The
189
+ // in-content position only resolves reference/secret — see MATRX_REFERENCES.md.
190
+ "matrx"
191
+ ];
192
+ const CODE_LANGUAGE_ALIASES = {
193
+ mmd: "mermaid",
194
+ // jsx/tsx render + materialize as a live React component (same as ```react).
195
+ jsx: "react",
196
+ tsx: "react"
197
+ };
198
+ function normalizeCodeLanguage(language) {
199
+ if (!language) return language;
200
+ const lower = language.toLowerCase();
201
+ return CODE_LANGUAGE_ALIASES[lower] ?? lower;
202
+ }
203
+ const JSON_BLOCK_PATTERNS = {
204
+ quiz: {
205
+ rootKey: "quiz_title",
206
+ validate: (parsed) => isJsonObject(parsed) && parsed.quiz_title && Array.isArray(parsed.multiple_choice) && parsed.multiple_choice.length > 0
207
+ },
208
+ presentation: {
209
+ rootKey: "presentation",
210
+ validate: (parsed) => isJsonObject(parsed) && isJsonObject(parsed.presentation) && Array.isArray(parsed.presentation.slides)
211
+ },
212
+ decision_tree: {
213
+ rootKey: "decision_tree",
214
+ validate: (parsed) => isJsonObject(parsed) && isJsonObject(parsed.decision_tree) && parsed.decision_tree.title && parsed.decision_tree.root
215
+ },
216
+ comparison_table: {
217
+ rootKey: "comparison",
218
+ validate: (parsed) => isJsonObject(parsed) && isJsonObject(parsed.comparison) && parsed.comparison.title && Array.isArray(parsed.comparison.items) && Array.isArray(parsed.comparison.criteria)
219
+ },
220
+ diagram: {
221
+ rootKey: "diagram",
222
+ validate: (parsed) => isJsonObject(parsed) && isJsonObject(parsed.diagram) && parsed.diagram.title && Array.isArray(parsed.diagram.nodes)
223
+ },
224
+ math_problem: {
225
+ rootKey: "math_problem",
226
+ validate: (parsed) => isJsonObject(parsed) && parsed.math_problem && typeof parsed.math_problem === "object"
227
+ },
228
+ // A clickable reference to a platform entity (agent, note, task, file, …).
229
+ // Deliberately forgiving: any object under the key with a string `type`
230
+ // qualifies — the renderer handles unknown types via a neutral fallback.
231
+ item_presentation: {
232
+ rootKey: "item_presentation",
233
+ validate: (parsed) => isJsonObject(parsed) && isJsonObject(parsed.item_presentation) && typeof parsed.item_presentation.type === "string"
234
+ },
235
+ // An output-schema proposal — what the "JSON Schema Generator" agent emits:
236
+ // `{ name: string, schema: object, strict?: boolean }`. Recognized so the FE
237
+ // can offer "Apply to an agent" (writes agent.definition.output_schema). The validate
238
+ // is deliberately strict (string `name` AND object `schema`) so ordinary JSON
239
+ // that merely begins with a "name" key never misfires into this block.
240
+ schema_proposal: {
241
+ rootKey: "name",
242
+ validate: (parsed) => isJsonObject(parsed) && typeof parsed.name === "string" && parsed.schema !== null && typeof parsed.schema === "object" && !Array.isArray(parsed.schema)
243
+ }
244
+ };
245
+ function extractFirstJsonKey(content) {
246
+ const match = content.trimStart().match(/^\{\s*"([^"]+)"/);
247
+ return match ? match[1] : null;
248
+ }
249
+ function detectJsonBlockType(content) {
250
+ const trimmed = content.trim();
251
+ if (trimmed.startsWith("{")) {
252
+ try {
253
+ const parsed = JSON.parse(trimmed);
254
+ if (tryDecodeDirective(parsed)) return "matrx";
255
+ } catch {
256
+ }
257
+ }
258
+ if (looksLikeDirectiveHead(content)) return "matrx";
259
+ const firstKey = extractFirstJsonKey(content);
260
+ if (!firstKey) return null;
261
+ if (firstKey === "matrx_version") return "matrx";
262
+ for (const [type, pattern] of Object.entries(JSON_BLOCK_PATTERNS)) {
263
+ if (pattern.rootKey === firstKey) {
264
+ return type;
265
+ }
266
+ }
267
+ return null;
268
+ }
269
+ function containsPlaceholderText(content) {
270
+ return /\[\s*(array|object|string|number|boolean|description|example|etc|list|item)[^\]]*\]/i.test(
271
+ content
272
+ ) || /:\s*"?\[?(array|list) of /i.test(content) || /:\s*"?object with /i.test(content) || /:\s*"?<[a-z_]+>/i.test(content) || /:\s*"?\.\.\."?/.test(content);
273
+ }
274
+ function validateJsonBlock(content, type) {
275
+ if (type === "matrx") {
276
+ let trimmed2 = content.trim();
277
+ trimmed2 = trimmed2.replace(/```+\s*$/, "").trim();
278
+ try {
279
+ const parsed = JSON.parse(trimmed2);
280
+ if (tryDecodeDirective(parsed)) {
281
+ return { isComplete: true, shouldShow: true };
282
+ }
283
+ } catch {
284
+ }
285
+ return {
286
+ isComplete: false,
287
+ shouldShow: looksLikeDirectiveHead(trimmed2) || extractFirstJsonKey(trimmed2) === "matrx_version"
288
+ };
289
+ }
290
+ let trimmed = content.trim();
291
+ trimmed = trimmed.replace(/```+\s*$/, "").trim();
292
+ if (containsPlaceholderText(trimmed)) {
293
+ return { isComplete: false, shouldShow: false };
294
+ }
295
+ try {
296
+ const parsed = JSON.parse(trimmed);
297
+ const pattern = JSON_BLOCK_PATTERNS[type];
298
+ const isValid = pattern.validate(parsed);
299
+ if (isValid) {
300
+ return {
301
+ isComplete: true,
302
+ shouldShow: true,
303
+ metadata: { isComplete: true }
304
+ };
305
+ }
306
+ return {
307
+ isComplete: true,
308
+ shouldShow: false,
309
+ metadata: { isComplete: true }
310
+ };
311
+ } catch (error) {
312
+ }
313
+ const { opens: openBraces, closes: closeBraces } = countStructuralBraces(trimmed);
314
+ if (openBraces > closeBraces) {
315
+ return { isComplete: false, shouldShow: false };
316
+ }
317
+ if (trimmed.endsWith("}") && openBraces === closeBraces) {
318
+ return {
319
+ isComplete: true,
320
+ shouldShow: false,
321
+ metadata: { isComplete: true }
322
+ };
323
+ }
324
+ if (closeBraces > openBraces) {
325
+ return {
326
+ isComplete: true,
327
+ shouldShow: false,
328
+ metadata: { isComplete: true }
329
+ };
330
+ }
331
+ return { isComplete: false, shouldShow: false };
332
+ }
333
+ const questionnaireCache = /* @__PURE__ */ new Map();
334
+ const flashcardCache = /* @__PURE__ */ new Map();
335
+ const recipeCache = /* @__PURE__ */ new Map();
336
+ const tableCache = /* @__PURE__ */ new Map();
337
+ const XML_TAG_BLOCKS = {
338
+ thinking: ["<thinking>", "<think>"],
339
+ reasoning: ["<reasoning>"],
340
+ info: ["<info>"],
341
+ task: ["<task>"],
342
+ database: ["<database>"],
343
+ private: ["<private>"],
344
+ plan: ["<plan>"],
345
+ event: ["<event>"],
346
+ tool: ["<tool>"],
347
+ questionnaire: ["<questionnaire>"],
348
+ flashcards: ["<flashcards>"],
349
+ cooking_recipe: ["<cooking_recipe>"],
350
+ timeline: ["<timeline>"],
351
+ progress_tracker: ["<progress_tracker>"],
352
+ troubleshooting: ["<troubleshooting>"],
353
+ resources: ["<resources>"],
354
+ research: ["<research>"]
355
+ };
356
+ const ATTRIBUTE_XML_BLOCKS = [
357
+ "decision",
358
+ "artifact",
359
+ // Editor pills — round-trip representation of code-editor errors and
360
+ // selected code snippets. Attributes carry file/line/severity/language; the
361
+ // body carries <message>+<surrounding_code> for errors, raw code for snippets.
362
+ "editor_error",
363
+ "editor_code_snippet",
364
+ // Audio citation — an agent reference to a moment in a scribe session's audio.
365
+ // `start`/`end` are session-relative seconds; the body is the cited label.
366
+ // Emitted mid-sentence (handled by detectMidLineAttributeXml), so a reply can
367
+ // cite the audio inline like a footnote.
368
+ "audiocite"
369
+ ];
370
+ const KNOWN_XML_TAG_NAMES = /* @__PURE__ */ new Set([
371
+ ...Object.values(XML_TAG_BLOCKS).flatMap(
372
+ (tags) => tags.map((tag) => tag.slice(1, -1))
373
+ ),
374
+ ...ATTRIBUTE_XML_BLOCKS
375
+ ]);
376
+ function parseXmlAttributes(openingTag) {
377
+ const attrs = {};
378
+ const attrRegex = /(\w+)\s*=\s*"([^"]*)"/g;
379
+ let match;
380
+ while ((match = attrRegex.exec(openingTag)) !== null) {
381
+ attrs[match[1]] = match[2];
382
+ }
383
+ return attrs;
384
+ }
385
+ function detectAttributeXmlBlock(line) {
386
+ const trimmed = line.trim();
387
+ for (const type of ATTRIBUTE_XML_BLOCKS) {
388
+ const prefix = `<${type}`;
389
+ if (trimmed.startsWith(prefix) && (trimmed[prefix.length] === " " || trimmed[prefix.length] === ">")) {
390
+ const closeBracket = trimmed.indexOf(">");
391
+ if (closeBracket === -1) return null;
392
+ const fullOpeningTag = trimmed.slice(0, closeBracket + 1);
393
+ const attributes = parseXmlAttributes(fullOpeningTag);
394
+ return { type, fullOpeningTag, attributes };
395
+ }
396
+ }
397
+ return null;
398
+ }
399
+ function detectMidLineAttributeXml(line) {
400
+ for (const type of ATTRIBUTE_XML_BLOCKS) {
401
+ const prefix = `<${type}`;
402
+ const idx = indexOutsideInlineCode(line, prefix);
403
+ if (idx === -1) continue;
404
+ if (line.trimStart().startsWith(prefix)) continue;
405
+ const afterPrefix = line[idx + prefix.length];
406
+ if (afterPrefix !== " " && afterPrefix !== ">") continue;
407
+ const closeBracket = line.indexOf(">", idx);
408
+ if (closeBracket === -1) continue;
409
+ const fullOpeningTag = line.slice(idx, closeBracket + 1);
410
+ const attributes = parseXmlAttributes(fullOpeningTag);
411
+ return { tagStart: idx, type, fullOpeningTag, attributes };
412
+ }
413
+ return null;
414
+ }
415
+ function extractAttributeXmlBlock(detection, startIndex, lines, rawXmlOverride) {
416
+ const closingTag = `</${detection.type}>`;
417
+ const rawLines = [];
418
+ const consumedLines = [];
419
+ let i = startIndex;
420
+ let foundClosingTag = false;
421
+ const firstLine = normalizeLine(lines[i]).trim();
422
+ consumedLines.push(lines[i]);
423
+ const afterOpening = firstLine.slice(firstLine.indexOf(">") + 1);
424
+ if (afterOpening.includes(closingTag)) {
425
+ const closingIdx = afterOpening.indexOf(closingTag);
426
+ const inner = afterOpening.slice(0, closingIdx).trim();
427
+ if (inner) rawLines.push(inner);
428
+ const remainderAfterClose = afterOpening.slice(closingIdx + closingTag.length).trim();
429
+ foundClosingTag = true;
430
+ i++;
431
+ if (remainderAfterClose) {
432
+ lines.splice(i, 0, remainderAfterClose);
433
+ }
434
+ } else {
435
+ if (afterOpening.trim()) rawLines.push(afterOpening.trim());
436
+ i++;
437
+ while (i < lines.length) {
438
+ const current = normalizeLine(lines[i]).trim();
439
+ const closingIdx = current.indexOf(closingTag);
440
+ if (closingIdx !== -1) {
441
+ const before = current.slice(0, closingIdx).trim();
442
+ if (before) rawLines.push(before);
443
+ const originalLine = lines[i];
444
+ const originalClosingIdx = originalLine.indexOf(closingTag);
445
+ consumedLines.push(
446
+ originalClosingIdx !== -1 ? originalLine.slice(0, originalClosingIdx + closingTag.length) : originalLine
447
+ );
448
+ const remainderAfterClose = current.slice(closingIdx + closingTag.length).trim();
449
+ foundClosingTag = true;
450
+ i++;
451
+ if (remainderAfterClose) {
452
+ lines.splice(i, 0, remainderAfterClose);
453
+ }
454
+ break;
455
+ }
456
+ if (current === closingTag) {
457
+ consumedLines.push(lines[i]);
458
+ foundClosingTag = true;
459
+ i++;
460
+ break;
461
+ }
462
+ consumedLines.push(lines[i]);
463
+ rawLines.push(lines[i]);
464
+ i++;
465
+ }
466
+ }
467
+ const innerContent = rawLines.join("\n");
468
+ const fullXml = rawXmlOverride ?? consumedLines.join("\n");
469
+ if (detection.type === "editor_error" || detection.type === "editor_code_snippet" || detection.type === "audiocite") {
470
+ return {
471
+ content: innerContent,
472
+ nextIndex: i,
473
+ metadata: {
474
+ isComplete: foundClosingTag,
475
+ ...detection.attributes,
476
+ rawXml: fullXml
477
+ }
478
+ };
479
+ }
480
+ if (detection.type === "artifact") {
481
+ const artifactId = detection.attributes.id || `artifact-${startIndex}`;
482
+ const artifactIndex = artifactId.includes("_") ? parseInt(artifactId.split("_").pop() || "0", 10) : startIndex;
483
+ const parsedVersion = detection.attributes.version ? parseInt(detection.attributes.version, 10) : void 0;
484
+ return {
485
+ content: innerContent,
486
+ nextIndex: i,
487
+ metadata: {
488
+ isComplete: foundClosingTag,
489
+ artifactId,
490
+ artifactIndex,
491
+ artifactType: detection.attributes.type || "text",
492
+ artifactTitle: detection.attributes.title || "",
493
+ ...typeof parsedVersion === "number" && Number.isFinite(parsedVersion) ? { version: parsedVersion } : {},
494
+ rawXml: fullXml
495
+ }
496
+ };
497
+ }
498
+ const options = parseDecisionOptionsFromBody(innerContent).map(
499
+ (o) => ({ id: o.id, label: o.label, text: o.text })
500
+ );
501
+ const prompt = detection.attributes.prompt || "Make a selection";
502
+ const decision = {
503
+ id: `decision-${startIndex}`,
504
+ prompt,
505
+ options
506
+ };
507
+ return {
508
+ content: innerContent,
509
+ nextIndex: i,
510
+ metadata: {
511
+ isComplete: foundClosingTag,
512
+ decision,
513
+ rawXml: fullXml
514
+ }
515
+ };
516
+ }
517
+ function detectXmlBlockType(line) {
518
+ const trimmed = line.trim();
519
+ for (const [type, tags] of Object.entries(XML_TAG_BLOCKS)) {
520
+ const matchedTag = tags.find(
521
+ (tag) => trimmed === tag || trimmed.startsWith(tag)
522
+ );
523
+ if (matchedTag) {
524
+ return { type, matchedTag };
525
+ }
526
+ const bare = tags[0].slice(0, -1);
527
+ if (trimmed.startsWith(`${bare} `)) {
528
+ const close = trimmed.indexOf(">");
529
+ if (close !== -1) {
530
+ return {
531
+ type,
532
+ matchedTag: trimmed.slice(0, close + 1)
533
+ };
534
+ }
535
+ }
536
+ }
537
+ return null;
538
+ }
539
+ function xmlTagNameOf(matchedTag) {
540
+ return /^<([\w-]+)/.exec(matchedTag)?.[1] ?? matchedTag.slice(1, -1);
541
+ }
542
+ function isHtmlOrKnownTag(name) {
543
+ return KNOWN_XML_TAG_NAMES.has(name) || ALLOWED_RAW_HTML_TAGS.has(name.toLowerCase());
544
+ }
545
+ function leadingTagName(line) {
546
+ return /^<([A-Za-z_][\w.:-]*)/.exec(line.trimStart())?.[1] ?? null;
547
+ }
548
+ function isUnclosedGenericXmlOpening(source) {
549
+ if (isPageBreakLine(source)) return false;
550
+ const text = source.trimStart();
551
+ const prefix = /^<([A-Za-z_][\w.:-]*)(?=\s|\/|$)/.exec(text);
552
+ if (!prefix || isHtmlOrKnownTag(prefix[1])) return false;
553
+ let quote = null;
554
+ for (let i = prefix[0].length; i < text.length; i++) {
555
+ if (quote) {
556
+ if (text[i] === quote) quote = null;
557
+ } else if (text[i] === "'" || text[i] === '"') quote = text[i];
558
+ else if (text[i] === ">") return false;
559
+ }
560
+ return true;
561
+ }
562
+ function startUnrecognizedXmlContainer(line) {
563
+ if (isPageBreakLine(line)) return null;
564
+ const firstTrimmed = line.trimStart();
565
+ if (!firstTrimmed.startsWith("<")) return null;
566
+ const openingTag = readXmlTag(firstTrimmed, 0);
567
+ if (!openingTag || openingTag.isClosing) return null;
568
+ const rootTag = openingTag.tagName;
569
+ if (isHtmlOrKnownTag(rootTag)) return null;
570
+ return {
571
+ tracker: new XmlContainerTracker(rootTag),
572
+ rootStart: line.length - firstTrimmed.length
573
+ };
574
+ }
575
+ function extractUnrecognizedXmlBlock(startIndex, lines) {
576
+ const firstLine = normalizeLine(lines[startIndex]);
577
+ const start = startUnrecognizedXmlContainer(firstLine);
578
+ if (!start) return null;
579
+ const { tracker, rootStart } = start;
580
+ for (let lineIndex = startIndex; lineIndex < lines.length; lineIndex++) {
581
+ const currentLine = normalizeLine(lines[lineIndex]);
582
+ const rootEnd = tracker.consumeLine(
583
+ currentLine,
584
+ lineIndex === startIndex ? rootStart : 0
585
+ );
586
+ if (rootEnd === null) continue;
587
+ const contentLines = lineIndex === startIndex ? [currentLine.slice(rootStart, rootEnd)] : [
588
+ firstLine.slice(rootStart),
589
+ ...lines.slice(startIndex + 1, lineIndex).map(normalizeLine),
590
+ currentLine.slice(0, rootEnd)
591
+ ];
592
+ const remainder = currentLine.slice(rootEnd).trim();
593
+ if (remainder) lines.splice(lineIndex + 1, 0, remainder);
594
+ return {
595
+ content: contentLines.join("\n").trim(),
596
+ nextIndex: lineIndex + 1,
597
+ metadata: { isComplete: true, genericXmlContainer: true }
598
+ };
599
+ }
600
+ return null;
601
+ }
602
+ function isCompleteUnrecognizedXmlContainer(source) {
603
+ const lines = source.split(/\r?\n/);
604
+ const extraction = extractUnrecognizedXmlBlock(0, [...lines]);
605
+ return extraction !== null && extraction.nextIndex >= lines.length;
606
+ }
607
+ const THINKING_OPENERS = {
608
+ thinking: ["<thinking>", "<think>"],
609
+ reasoning: ["<reasoning>"]
610
+ };
611
+ function detectOrphanThinkingClose(trimmedLine) {
612
+ const CLOSERS = [
613
+ ["thinking", "</thinking>"],
614
+ ["thinking", "</think>"],
615
+ ["reasoning", "</reasoning>"]
616
+ ];
617
+ for (const [type, closer] of CLOSERS) {
618
+ const idx = indexOutsideInlineCode(trimmedLine, closer);
619
+ if (idx === -1) continue;
620
+ return {
621
+ type,
622
+ before: trimmedLine.slice(0, idx).trim(),
623
+ remainder: trimmedLine.slice(idx + closer.length).trim()
624
+ };
625
+ }
626
+ return null;
627
+ }
628
+ function extractXmlBlock(type, matchedTag, startIndex, lines, trackFences = true) {
629
+ const content = [];
630
+ let i = startIndex;
631
+ let foundClosingTag = false;
632
+ const balance = initialXmlBalance();
633
+ let remainderAfterClose = "";
634
+ const tagName = xmlTagNameOf(matchedTag);
635
+ const closingTag = `</${tagName}>`;
636
+ const firstLine = lines[i];
637
+ const processedFirst = normalizeLine(firstLine).trim();
638
+ if (processedFirst.startsWith(matchedTag)) {
639
+ const afterTag = processedFirst.slice(matchedTag.length);
640
+ const closingIdx = afterTag.indexOf(closingTag);
641
+ if (closingIdx !== -1) {
642
+ const beforeClosing = afterTag.slice(0, closingIdx).trim();
643
+ if (beforeClosing) content.push(beforeClosing);
644
+ remainderAfterClose = afterTag.slice(closingIdx + closingTag.length).trim();
645
+ foundClosingTag = true;
646
+ i++;
647
+ if (remainderAfterClose) {
648
+ lines.splice(i, 0, remainderAfterClose);
649
+ }
650
+ const fullContent2 = content.join("\n");
651
+ const result2 = validateStreamingXmlBlock(
652
+ type,
653
+ fullContent2,
654
+ foundClosingTag
655
+ );
656
+ return {
657
+ content: result2.content || fullContent2,
658
+ nextIndex: i,
659
+ metadata: result2.metadata
660
+ };
661
+ }
662
+ const afterTagTrimmed = afterTag.trim();
663
+ if (afterTagTrimmed) content.push(afterTagTrimmed);
664
+ i++;
665
+ }
666
+ while (i < lines.length) {
667
+ const currentTrimmed = normalizeLine(lines[i]).trim();
668
+ const closingIdx = findBalancedXmlClose(currentTrimmed, tagName, balance, {
669
+ trackFences
670
+ });
671
+ if (closingIdx !== -1) {
672
+ const beforeClosing = currentTrimmed.slice(0, closingIdx).trim();
673
+ if (beforeClosing) content.push(beforeClosing);
674
+ remainderAfterClose = currentTrimmed.slice(closingIdx + closingTag.length).trim();
675
+ foundClosingTag = true;
676
+ i++;
677
+ if (remainderAfterClose) {
678
+ lines.splice(i, 0, remainderAfterClose);
679
+ }
680
+ break;
681
+ }
682
+ if (type === "thinking" && currentTrimmed.startsWith("### I have everything")) {
683
+ content.push(lines[i]);
684
+ i++;
685
+ break;
686
+ }
687
+ content.push(lines[i]);
688
+ i++;
689
+ }
690
+ if (!foundClosingTag && trackFences && balance.fence !== null) {
691
+ return extractXmlBlock(type, matchedTag, startIndex, lines, false);
692
+ }
693
+ const fullContent = content.join("\n");
694
+ const result = validateStreamingXmlBlock(type, fullContent, foundClosingTag);
695
+ return {
696
+ content: result.content || fullContent,
697
+ nextIndex: i,
698
+ metadata: result.metadata
699
+ };
700
+ }
701
+ function validateStreamingXmlBlock(type, content, foundClosingTag) {
702
+ if (!["questionnaire", "flashcards", "cooking_recipe"].includes(type)) {
703
+ return {
704
+ content,
705
+ metadata: { isComplete: foundClosingTag }
706
+ };
707
+ }
708
+ if (type === "questionnaire") {
709
+ return validateQuestionnaireStreaming(content, foundClosingTag);
710
+ }
711
+ if (type === "flashcards") {
712
+ return validateFlashcardStreaming(content, foundClosingTag);
713
+ }
714
+ if (type === "cooking_recipe") {
715
+ return validateRecipeStreaming(content, foundClosingTag);
716
+ }
717
+ return {
718
+ content,
719
+ metadata: { isComplete: foundClosingTag }
720
+ };
721
+ }
722
+ function validateQuestionnaireStreaming(content, foundClosingTag) {
723
+ const lines = content.split("\n");
724
+ const completeQuestions = [];
725
+ let currentQuestion = [];
726
+ let totalQuestions = 0;
727
+ for (let i = 0; i < lines.length; i++) {
728
+ const line = lines[i].trim();
729
+ if (line.match(/^###\s+\*\*Q\d+:/) || line.match(/^###\s+Q\d+:/) || line.match(/^\*\*Q\d+:/)) {
730
+ totalQuestions++;
731
+ if (currentQuestion.length > 0) {
732
+ completeQuestions.push(currentQuestion.join("\n"));
733
+ }
734
+ currentQuestion = [lines[i]];
735
+ } else if (line === "---") {
736
+ currentQuestion.push(lines[i]);
737
+ if (currentQuestion.length > 1) {
738
+ completeQuestions.push(currentQuestion.join("\n"));
739
+ }
740
+ currentQuestion = [];
741
+ } else if (currentQuestion.length > 0 || line.length > 0) {
742
+ currentQuestion.push(lines[i]);
743
+ }
744
+ }
745
+ if (foundClosingTag && currentQuestion.length > 0) {
746
+ completeQuestions.push(currentQuestion.join("\n"));
747
+ }
748
+ const contentHash = `${completeQuestions.length}-${foundClosingTag}`;
749
+ const cacheKey = `questionnaire-${contentHash}`;
750
+ let contentToRelease = foundClosingTag ? content : completeQuestions.join("\n\n---\n\n");
751
+ const cached = questionnaireCache.get(cacheKey);
752
+ if (cached && cached.content === contentToRelease) {
753
+ return cached;
754
+ }
755
+ const result = {
756
+ content: contentToRelease,
757
+ metadata: {
758
+ isComplete: foundClosingTag,
759
+ completeQuestionCount: completeQuestions.length,
760
+ totalQuestions,
761
+ hasPartialContent: !foundClosingTag && currentQuestion.length > 0
762
+ }
763
+ };
764
+ questionnaireCache.set(cacheKey, {
765
+ content: contentToRelease,
766
+ hash: contentHash,
767
+ metadata: result.metadata
768
+ });
769
+ return result;
770
+ }
771
+ function validateFlashcardStreaming(content, foundClosingTag) {
772
+ const lines = content.split("\n");
773
+ const completeCards = [];
774
+ let currentCard = [];
775
+ let totalCards = 0;
776
+ let hasFront = false;
777
+ let hasBack = false;
778
+ for (let i = 0; i < lines.length; i++) {
779
+ const line = lines[i].trim();
780
+ if (line.match(/^(?:Front|Question):/i)) {
781
+ if (currentCard.length > 0 && hasFront && hasBack) {
782
+ completeCards.push(currentCard.join("\n"));
783
+ }
784
+ currentCard = [lines[i]];
785
+ hasFront = true;
786
+ hasBack = false;
787
+ totalCards++;
788
+ } else if (line.match(/^(?:Back|Answer):/i)) {
789
+ currentCard.push(lines[i]);
790
+ hasBack = true;
791
+ } else if (line === "---") {
792
+ if (currentCard.length > 0 && hasFront && hasBack) {
793
+ completeCards.push(currentCard.join("\n"));
794
+ }
795
+ currentCard = [];
796
+ hasFront = false;
797
+ hasBack = false;
798
+ } else if (currentCard.length > 0) {
799
+ currentCard.push(lines[i]);
800
+ }
801
+ }
802
+ if (foundClosingTag && currentCard.length > 0 && hasFront && hasBack) {
803
+ completeCards.push(currentCard.join("\n"));
804
+ }
805
+ const contentHash = `${completeCards.length}-${foundClosingTag}`;
806
+ const cacheKey = `flashcards-${contentHash}`;
807
+ let contentToRelease = foundClosingTag ? content : completeCards.join("\n\n---\n\n");
808
+ const cached = flashcardCache.get(cacheKey);
809
+ if (cached && cached.content === contentToRelease) {
810
+ return cached;
811
+ }
812
+ const result = {
813
+ content: contentToRelease,
814
+ metadata: {
815
+ isComplete: foundClosingTag,
816
+ completeCardCount: completeCards.length,
817
+ totalCards,
818
+ hasPartialContent: !foundClosingTag && currentCard.length > 0
819
+ }
820
+ };
821
+ flashcardCache.set(cacheKey, {
822
+ content: contentToRelease,
823
+ hash: contentHash,
824
+ metadata: result.metadata
825
+ });
826
+ return result;
827
+ }
828
+ function validateRecipeStreaming(content, foundClosingTag) {
829
+ const hasTitle = /^###\s+.+$/m.test(content);
830
+ const hasIngredients = /####\s*Ingredients?:/i.test(content);
831
+ const hasInstructions = /####\s*Instructions?:/i.test(content);
832
+ const contentLength = content.length;
833
+ const contentHash = `${contentLength}-${foundClosingTag}-${hasTitle}-${hasIngredients}-${hasInstructions}`;
834
+ const cacheKey = `cooking_recipe-${contentHash}`;
835
+ let contentToRelease = "";
836
+ if (foundClosingTag) {
837
+ contentToRelease = content;
838
+ } else if (hasTitle && (hasIngredients || hasInstructions)) {
839
+ contentToRelease = content;
840
+ }
841
+ const cached = recipeCache.get(cacheKey);
842
+ if (cached && cached.content === contentToRelease) {
843
+ return cached;
844
+ }
845
+ const result = {
846
+ content: contentToRelease,
847
+ metadata: {
848
+ isComplete: foundClosingTag,
849
+ hasTitle,
850
+ hasIngredients,
851
+ hasInstructions,
852
+ hasPartialContent: !foundClosingTag && contentLength > 0
853
+ }
854
+ };
855
+ recipeCache.set(cacheKey, {
856
+ content: contentToRelease,
857
+ hash: contentHash,
858
+ metadata: result.metadata
859
+ });
860
+ return result;
861
+ }
862
+ function detectCodeBlock(line) {
863
+ const opener = fenceOpenerOf(line);
864
+ if (!opener) {
865
+ return { isCodeBlock: false };
866
+ }
867
+ const trimmed = line.trim();
868
+ const ticks = opener.ticks;
869
+ const { language, meta } = splitFenceInfo(trimmed.slice(ticks));
870
+ return { isCodeBlock: true, language, meta, ticks, char: opener.char };
871
+ }
872
+ function jsonLineForFenceReader(line) {
873
+ if (line.includes("```")) return line;
874
+ let inString = false;
875
+ let escaped = false;
876
+ for (let i = 0; i < line.length; i++) {
877
+ const ch = line[i];
878
+ if (escaped) escaped = false;
879
+ else if (ch === "\\" && inString) escaped = true;
880
+ else if (ch === '"') inString = !inString;
881
+ }
882
+ if (!inString) return line;
883
+ return escaped ? `${line}x"` : `${line}"`;
884
+ }
885
+ function extractCodeBlock(language, openTicks, startIndex, lines, fenceChar = "`") {
886
+ const jsonBody = fenceChar === "`" && (language ?? "") === "json";
887
+ const end = closeFence(
888
+ { count: lines.length, line: (k) => jsonBody ? jsonLineForFenceReader(lines[k] ?? "") : lines[k] ?? "" },
889
+ startIndex - 1,
890
+ { char: fenceChar, ticks: openTicks, lang: language ?? "" }
891
+ );
892
+ if (!end.closed) {
893
+ return {
894
+ content: lines.slice(startIndex).join("\n"),
895
+ nextIndex: lines.length + 1,
896
+ metadata: { language },
897
+ closedCleanly: false
898
+ };
899
+ }
900
+ const closer = lines[end.line] ?? "";
901
+ const bare = fenceOpenerOf(closer)?.char === fenceChar;
902
+ const before = bare ? "" : closer.slice(0, closer.indexOf("`".repeat(3)));
903
+ const body = lines.slice(startIndex, end.line);
904
+ if (before.trim()) body.push(before);
905
+ return {
906
+ content: body.join("\n"),
907
+ nextIndex: end.line + 1,
908
+ metadata: { language },
909
+ closedCleanly: bare
910
+ };
911
+ }
912
+ function isTableSeparator(line) {
913
+ return isGfmDelimiterRow(normalizeLine(line).trim());
914
+ }
915
+ function detectTableStart(lines, index) {
916
+ return opensTable(lines, index);
917
+ }
918
+ function extractTable(startIndex, lines) {
919
+ const tableLines = [lines[startIndex]];
920
+ let i = startIndex + 1;
921
+ const container = tableContainerIndent(lines, startIndex);
922
+ while (i < lines.length && (continuesTable(normalizeLine(lines[i]), container) || i === startIndex + 1 && isTableSeparator(lines[i]))) {
923
+ tableLines.push(lines[i]);
924
+ i++;
925
+ }
926
+ const stillArriving = startIndex + 2 >= lines.length && tableLines.length >= 2 && isTableSeparator(tableLines[1]);
927
+ if (tableLines.length < 2 || !(tableStartsAt(lines, startIndex) || stillArriving)) {
928
+ return {
929
+ content: "",
930
+ nextIndex: startIndex + 1,
931
+ metadata: { isValid: false }
932
+ };
933
+ }
934
+ const tableHasEnded = i < lines.length;
935
+ const result = analyzeTableCompletionWithCache(tableLines, tableHasEnded);
936
+ return {
937
+ content: result.content,
938
+ nextIndex: i,
939
+ metadata: result.metadata
940
+ };
941
+ }
942
+ function analyzeTableCompletionWithCache(tableLines, tableHasEnded) {
943
+ if (tableLines.length < 2) {
944
+ return {
945
+ content: "",
946
+ metadata: { isComplete: false, completeRowCount: 0 }
947
+ };
948
+ }
949
+ const headerHash = tableLines.slice(0, 2).join("|").replace(/\s+/g, "");
950
+ const rowCount = tableLines.length;
951
+ const cacheKey = `table-${headerHash}-${rowCount}-${tableHasEnded}`;
952
+ const state = analyzeTableCompletion(tableLines, tableHasEnded);
953
+ const contentToRelease = tableLines.join("\n");
954
+ const cached = tableCache.get(cacheKey);
955
+ if (cached && cached.content === contentToRelease) {
956
+ return cached;
957
+ }
958
+ const result = {
959
+ content: contentToRelease,
960
+ metadata: {
961
+ cacheKey,
962
+ isComplete: state.isComplete,
963
+ completeRowCount: state.completeRowCount,
964
+ totalRows: state.totalRows,
965
+ hasPartialContent: state.bufferRow.length > 0
966
+ }
967
+ };
968
+ tableCache.set(cacheKey, {
969
+ content: contentToRelease,
970
+ hash: `${state.completeRowCount}-${state.isComplete}-${rowCount}`,
971
+ metadata: result.metadata
972
+ });
973
+ return result;
974
+ }
975
+ function analyzeTableCompletion(tableLines, tableHasEnded) {
976
+ if (tableLines.length < 3) {
977
+ return {
978
+ isComplete: false,
979
+ completeRowCount: 0,
980
+ totalRows: 0,
981
+ bufferRow: ""
982
+ };
983
+ }
984
+ const dataLines = tableLines.slice(2);
985
+ const completeRows = [];
986
+ let bufferRow = "";
987
+ for (let i = 0; i < dataLines.length; i++) {
988
+ const line = dataLines[i];
989
+ const trimmedLine = normalizeLine(line).trim();
990
+ if (continuesTable(trimmedLine)) {
991
+ const isCompleteRow = tableHasEnded || i < dataLines.length - 1;
992
+ if (isCompleteRow) {
993
+ completeRows.push(line);
994
+ } else {
995
+ bufferRow = line;
996
+ }
997
+ }
998
+ }
999
+ return {
1000
+ isComplete: tableHasEnded || bufferRow.length === 0,
1001
+ completeRowCount: completeRows.length,
1002
+ totalRows: dataLines.length,
1003
+ bufferRow
1004
+ };
1005
+ }
1006
+ function detectImageMarkdown(line) {
1007
+ const trimmed = line.trim();
1008
+ const standardMatch = trimmed.match(
1009
+ /^!\[(.*?)\]\((https?:\/\/[^\s)]+)(?:\s+(?:"[^"]*"|'[^']*'))?\)/
1010
+ );
1011
+ const customMatch = trimmed.match(/\[Image URL: (https?:\/\/[^\s\]]+)\]/);
1012
+ if (standardMatch) {
1013
+ return { isImage: true, alt: standardMatch[1], src: standardMatch[2] };
1014
+ }
1015
+ if (customMatch) {
1016
+ return { isImage: true, alt: "Image", src: customMatch[1] };
1017
+ }
1018
+ return { isImage: false };
1019
+ }
1020
+ function countInlineImages(line) {
1021
+ const re = /!\[(.*?)\]\((https?:\/\/[^\s)]+)(?:\s+(?:"[^"]*"|'[^']*'))?\)/g;
1022
+ let count = 0;
1023
+ while (re.exec(line) !== null) count++;
1024
+ return count;
1025
+ }
1026
+ function detectVideoMarkdown(line) {
1027
+ const trimmed = line.trim();
1028
+ const customMatch = trimmed.match(/\[Video URL: (https?:\/\/[^\s\]]+)\]/);
1029
+ if (customMatch) {
1030
+ return { isVideo: true, alt: "Video", src: customMatch[1] };
1031
+ }
1032
+ return { isVideo: false };
1033
+ }
1034
+ const AUDIO_URL_EXT = /\.(mp3|wav|m4a|aac|ogg|oga|opus|flac|weba|webm)(\?[^\s)]*)?$/i;
1035
+ function extractAudioLink(line) {
1036
+ const trimmed = line.trim();
1037
+ const customMatch = trimmed.match(/\[Audio URL:\s*(https?:\/\/[^\s\]]+)\]/);
1038
+ if (customMatch) {
1039
+ return { src: customMatch[1], alt: "Audio" };
1040
+ }
1041
+ const linkMatch = trimmed.match(/^!?\[(.*?)\]\((https?:\/\/[^\s)]+)\)$/);
1042
+ if (linkMatch && AUDIO_URL_EXT.test(linkMatch[2])) {
1043
+ return { src: linkMatch[2], alt: linkMatch[1] || "Audio" };
1044
+ }
1045
+ if (/^https?:\/\/[^\s)]+$/.test(trimmed) && AUDIO_URL_EXT.test(trimmed)) {
1046
+ return { src: trimmed, alt: "Audio" };
1047
+ }
1048
+ return null;
1049
+ }
1050
+ function detectAudioMarkdown(line) {
1051
+ const audio = extractAudioLink(line);
1052
+ if (audio) {
1053
+ return { isAudio: true, src: audio.src, alt: audio.alt };
1054
+ }
1055
+ return { isAudio: false };
1056
+ }
1057
+ function trimEmphasisEnd(text) {
1058
+ return text.replace(/(?:\*\*|\*|_){1,2}\s*$/, "");
1059
+ }
1060
+ function trimEmphasisStart(text) {
1061
+ return text.replace(/^\s*(?:\*\*|\*|_){1,2}/, "");
1062
+ }
1063
+ function detectMatrxFileMarkdown(line) {
1064
+ if (!mightBeOurFileUrl(line)) return { isMatrxFile: false };
1065
+ const linkRe = /!?\[([^\]]*)\]\((https?:\/\/[^\s)]+)\)/g;
1066
+ let m;
1067
+ while ((m = linkRe.exec(line)) !== null) {
1068
+ const url = m[2];
1069
+ if (recognizeOurFileUrl(url)) {
1070
+ const start = m.index;
1071
+ const end = m.index + m[0].length;
1072
+ return {
1073
+ isMatrxFile: true,
1074
+ url,
1075
+ label: m[1] || "",
1076
+ pre: trimEmphasisEnd(line.slice(0, start)),
1077
+ post: trimEmphasisStart(line.slice(end))
1078
+ };
1079
+ }
1080
+ }
1081
+ const bareRe = /(https?:\/\/[^\s)]+)/g;
1082
+ while ((m = bareRe.exec(line)) !== null) {
1083
+ const url = m[1].replace(/[).,;]+$/, "");
1084
+ if (recognizeOurFileUrl(url)) {
1085
+ const start = m.index;
1086
+ const end = m.index + url.length;
1087
+ return {
1088
+ isMatrxFile: true,
1089
+ url,
1090
+ label: "",
1091
+ pre: trimEmphasisEnd(line.slice(0, start)),
1092
+ post: trimEmphasisStart(line.slice(end))
1093
+ };
1094
+ }
1095
+ }
1096
+ return { isMatrxFile: false };
1097
+ }
1098
+ function detectYoutubeMarkdown(line) {
1099
+ const trimmed = line.trim();
1100
+ const linkedThumb = trimmed.match(
1101
+ /^\[!\[(.*?)\]\((https?:\/\/[^\s)]+?)\)\]\((https?:\/\/[^\s)]+?)\)$/
1102
+ );
1103
+ if (linkedThumb) {
1104
+ const parsed = parseYouTubeUrl(linkedThumb[3]);
1105
+ if (parsed) {
1106
+ return {
1107
+ isYoutube: true,
1108
+ videoId: parsed.videoId,
1109
+ start: parsed.start,
1110
+ title: linkedThumb[1] || void 0,
1111
+ poster: isYouTubeThumbnailUrl(linkedThumb[2]) ? linkedThumb[2] : void 0,
1112
+ watchUrl: youTubeWatchUrl(parsed.videoId, parsed.start)
1113
+ };
1114
+ }
1115
+ }
1116
+ const plainLink = trimmed.match(/^\[([^\]]*)\]\((https?:\/\/[^\s)]+?)\)$/);
1117
+ if (plainLink) {
1118
+ const parsed = parseYouTubeUrl(plainLink[2]);
1119
+ if (parsed) {
1120
+ return {
1121
+ isYoutube: true,
1122
+ videoId: parsed.videoId,
1123
+ start: parsed.start,
1124
+ title: plainLink[1] || void 0,
1125
+ watchUrl: youTubeWatchUrl(parsed.videoId, parsed.start)
1126
+ };
1127
+ }
1128
+ }
1129
+ if (/^https?:\/\/\S+$/.test(trimmed)) {
1130
+ const parsed = parseYouTubeUrl(trimmed);
1131
+ if (parsed) {
1132
+ return {
1133
+ isYoutube: true,
1134
+ videoId: parsed.videoId,
1135
+ start: parsed.start,
1136
+ watchUrl: youTubeWatchUrl(parsed.videoId, parsed.start)
1137
+ };
1138
+ }
1139
+ }
1140
+ return { isYoutube: false };
1141
+ }
1142
+ const TREE_CHARS = /[├└│┌┐┘┬┴┤┼─]/;
1143
+ const ASCII_TREE_PATTERNS = /^[\s│|]*(?:[├└]|[+|][─-])/;
1144
+ function isTreeLine(line) {
1145
+ if (!line) return false;
1146
+ if (TREE_CHARS.test(line)) return true;
1147
+ return ASCII_TREE_PATTERNS.test(line) && !isGfmDelimiterRow(line);
1148
+ }
1149
+ function isMarkdownHeadingLine(line) {
1150
+ return /^#{1,6}\s/.test(line.trim());
1151
+ }
1152
+ function findTreeBlockStart(lines, firstTreeIndex) {
1153
+ let start = firstTreeIndex;
1154
+ for (let j = firstTreeIndex - 1; j >= 0; j--) {
1155
+ const trimmed = normalizeLine(lines[j]).trim();
1156
+ if (!trimmed) break;
1157
+ if (isTreeLine(trimmed)) break;
1158
+ if (isMarkdownHeadingLine(trimmed)) break;
1159
+ start = j;
1160
+ }
1161
+ return start;
1162
+ }
1163
+ function stripAccumulatedLinesFromText(text, rawLines, fromIndex, toIndex) {
1164
+ let result = text;
1165
+ for (let j = fromIndex; j < toIndex; j++) {
1166
+ const processed = normalizeLine(rawLines[j]);
1167
+ if (result.endsWith(processed + "\n")) {
1168
+ result = result.slice(0, -(processed.length + 1));
1169
+ } else if (result.endsWith(processed)) {
1170
+ result = result.slice(0, -processed.length);
1171
+ }
1172
+ }
1173
+ return result;
1174
+ }
1175
+ function normalizeLine(text) {
1176
+ return text.trim() === "" ? "" : text;
1177
+ }
1178
+ function frontMatterLineCount(lines) {
1179
+ const opener = lines[0]?.replace(/^\uFEFF/, "");
1180
+ if (lines.length < 2 || opener !== "---" && opener !== "+++") return 0;
1181
+ for (let k = 1; k < lines.length; k++) {
1182
+ if (lines[k] === opener || opener === "---" && lines[k] === "...") return k + 1;
1183
+ }
1184
+ return 0;
1185
+ }
1186
+ const splitContentIntoBlocksWith = (mdContent, envelopes) => {
1187
+ const blocks = [];
1188
+ const lines = liftQuotedKindRegions(
1189
+ unwrapKindImageAlt(unescapeMarkdownKindJson(decodeDoubleEncodedKindText(mdContent)))
1190
+ ).split(/\r?\n/);
1191
+ let currentText = "";
1192
+ let i = 0;
1193
+ const pendingRawXmlOverrides = /* @__PURE__ */ new Map();
1194
+ const frontMatterLines = frontMatterLineCount(lines);
1195
+ if (frontMatterLines > 0) {
1196
+ currentText = lines.slice(0, frontMatterLines).map(normalizeLine).join("\n") + (frontMatterLines < lines.length ? "\n" : "");
1197
+ i = frontMatterLines;
1198
+ }
1199
+ while (i < lines.length) {
1200
+ const line = lines[i];
1201
+ const processedLine = normalizeLine(line);
1202
+ const trimmedLine = processedLine.trim();
1203
+ if (/^\*\s*\*\s*\*\s*$/.test(trimmedLine)) {
1204
+ if (currentText.trim()) {
1205
+ blocks.push({ type: "text", content: currentText.trimEnd() });
1206
+ currentText = "";
1207
+ }
1208
+ blocks.push({ type: "accent-divider", content: trimmedLine });
1209
+ i++;
1210
+ continue;
1211
+ }
1212
+ if (/^#\s*={3,}\s*$/.test(trimmedLine)) {
1213
+ if (currentText.trim()) {
1214
+ blocks.push({ type: "text", content: currentText.trimEnd() });
1215
+ currentText = "";
1216
+ }
1217
+ blocks.push({ type: "heavy-divider", content: trimmedLine });
1218
+ i++;
1219
+ continue;
1220
+ }
1221
+ if (DIRECTIVE_CONTAINER_OPEN.test(processedLine)) {
1222
+ const tracker = new DirectiveContainerTracker(processedLine);
1223
+ let j = i;
1224
+ while (j < lines.length) {
1225
+ const containerLine = normalizeLine(lines[j]);
1226
+ currentText += containerLine + (j < lines.length - 1 ? "\n" : "");
1227
+ j++;
1228
+ if (j - 1 > i && tracker.consume(containerLine)) break;
1229
+ }
1230
+ i = j;
1231
+ continue;
1232
+ }
1233
+ const codeCheck = detectCodeBlock(processedLine);
1234
+ if (codeCheck.isCodeBlock) {
1235
+ if (currentText.trim()) {
1236
+ blocks.push({ type: "text", content: currentText.trimEnd() });
1237
+ currentText = "";
1238
+ }
1239
+ const extraction = extractCodeBlock(
1240
+ codeCheck.language,
1241
+ codeCheck.ticks ?? 3,
1242
+ i + 1,
1243
+ lines,
1244
+ codeCheck.char
1245
+ );
1246
+ const normalizedLanguage = normalizeCodeLanguage(codeCheck.language);
1247
+ if (normalizedLanguage && SPECIAL_CODE_LANGUAGES.includes(normalizedLanguage)) {
1248
+ const closedCleanly = extraction.closedCleanly;
1249
+ let fenceMetadata;
1250
+ if (closedCleanly) {
1251
+ const fenceEnvelope = envelopes.fenceRegion(
1252
+ normalizedLanguage,
1253
+ extraction.content
1254
+ );
1255
+ if (fenceEnvelope) {
1256
+ fenceMetadata = { [IR_ENVELOPE_KEY]: fenceEnvelope };
1257
+ }
1258
+ }
1259
+ blocks.push({
1260
+ type: normalizedLanguage,
1261
+ content: extraction.content,
1262
+ language: codeCheck.language,
1263
+ ...fenceMetadata ? { metadata: fenceMetadata } : {}
1264
+ });
1265
+ } else if (codeCheck.language?.toLowerCase() === "json") {
1266
+ const jsonType = detectJsonBlockType(extraction.content);
1267
+ if (jsonType) {
1268
+ const streamingState = validateJsonBlock(
1269
+ extraction.content,
1270
+ jsonType
1271
+ );
1272
+ blocks.push({
1273
+ type: jsonType,
1274
+ content: extraction.content,
1275
+ language: "json",
1276
+ // content-ir: complete JSON regions carry the canonical envelope
1277
+ // (memoized one-shot parse — identical to the streaming path's).
1278
+ // EXCEPT a directive: its __kind is a reserved-namespace slug that
1279
+ // is BY DESIGN never in kind_definition, so an __ir envelope here
1280
+ // fires a doomed registry fetch per message and lets the kind
1281
+ // route re-type the block away from MatrxEnvelopeBlock the moment
1282
+ // a directive-class registry row exists (adversarial finding A-8;
1283
+ // recoverEmbeddedKindJsonBlocks already does exactly this).
1284
+ metadata: jsonType === "matrx" ? streamingState.metadata : envelopes.withIrEnvelope(extraction.content, streamingState.metadata)
1285
+ });
1286
+ } else {
1287
+ blocks.push({
1288
+ type: "code",
1289
+ content: extraction.content,
1290
+ language: codeCheck.language,
1291
+ metadata: envelopes.withIrEnvelope(
1292
+ extraction.content,
1293
+ codeCheck.meta ? { [FENCE_META_KEY]: codeCheck.meta } : void 0
1294
+ )
1295
+ });
1296
+ }
1297
+ } else {
1298
+ blocks.push({
1299
+ type: "code",
1300
+ content: extraction.content,
1301
+ language: codeCheck.language,
1302
+ ...codeCheck.meta ? { metadata: { [FENCE_META_KEY]: codeCheck.meta } } : {}
1303
+ });
1304
+ }
1305
+ i = extraction.nextIndex;
1306
+ continue;
1307
+ }
1308
+ const attrXmlMatch = detectAttributeXmlBlock(processedLine);
1309
+ if (attrXmlMatch) {
1310
+ if (currentText.trim()) {
1311
+ blocks.push({ type: "text", content: currentText.trimEnd() });
1312
+ currentText = "";
1313
+ }
1314
+ const rawXmlOverride = pendingRawXmlOverrides.get(trimmedLine);
1315
+ if (rawXmlOverride !== void 0) {
1316
+ pendingRawXmlOverrides.delete(trimmedLine);
1317
+ }
1318
+ const extraction = extractAttributeXmlBlock(
1319
+ attrXmlMatch,
1320
+ i,
1321
+ lines,
1322
+ rawXmlOverride
1323
+ );
1324
+ blocks.push({
1325
+ type: attrXmlMatch.type,
1326
+ content: extraction.content,
1327
+ metadata: extraction.metadata
1328
+ });
1329
+ i = extraction.nextIndex;
1330
+ continue;
1331
+ }
1332
+ const xmlMatch = detectXmlBlockType(processedLine);
1333
+ if (xmlMatch) {
1334
+ if (currentText.trim()) {
1335
+ blocks.push({ type: "text", content: currentText.trimEnd() });
1336
+ currentText = "";
1337
+ }
1338
+ const extraction = extractXmlBlock(
1339
+ xmlMatch.type,
1340
+ xmlMatch.matchedTag,
1341
+ i,
1342
+ lines
1343
+ );
1344
+ let xmlMetadata = extraction.metadata;
1345
+ if (xmlMetadata?.isComplete === true) {
1346
+ const xmlTag = xmlTagNameOf(xmlMatch.matchedTag);
1347
+ const attributed = xmlMatch.matchedTag !== `<${xmlTag}>`;
1348
+ const xmlEnvelope = envelopes.xmlRegion(
1349
+ xmlTag,
1350
+ // Attributes (e.g. a questionnaire `title`) live on the opener:
1351
+ // hand the strategy the same literal framing the accumulator does.
1352
+ attributed ? `${xmlMatch.matchedTag}
1353
+ ${extraction.content}
1354
+ </${xmlTag}>` : extraction.content
1355
+ );
1356
+ if (xmlEnvelope) {
1357
+ xmlMetadata = { ...xmlMetadata, [IR_ENVELOPE_KEY]: xmlEnvelope };
1358
+ }
1359
+ }
1360
+ blocks.push({
1361
+ type: xmlMatch.type,
1362
+ content: extraction.content,
1363
+ metadata: xmlMetadata
1364
+ });
1365
+ i = extraction.nextIndex;
1366
+ continue;
1367
+ }
1368
+ const unrecognizedXml = extractUnrecognizedXmlBlock(i, lines);
1369
+ if (unrecognizedXml) {
1370
+ if (currentText.trim()) {
1371
+ blocks.push({ type: "text", content: currentText.trimEnd() });
1372
+ currentText = "";
1373
+ }
1374
+ blocks.push({
1375
+ type: "code",
1376
+ content: unrecognizedXml.content,
1377
+ language: "xml",
1378
+ metadata: unrecognizedXml.metadata
1379
+ });
1380
+ i = unrecognizedXml.nextIndex;
1381
+ continue;
1382
+ }
1383
+ const incompleteUnrecognizedXml = !isHtmlBlockTagName(leadingTagName(processedLine) ?? "") && (startUnrecognizedXmlContainer(processedLine) || isUnclosedGenericXmlOpening(processedLine));
1384
+ if (incompleteUnrecognizedXml) {
1385
+ if (currentText.trim()) {
1386
+ blocks.push({ type: "text", content: currentText.trimEnd() });
1387
+ currentText = "";
1388
+ }
1389
+ blocks.push({
1390
+ type: "code",
1391
+ content: lines.slice(i).map(normalizeLine).join("\n").trim(),
1392
+ language: "xml",
1393
+ metadata: { isComplete: false, genericXmlContainer: true }
1394
+ });
1395
+ i = lines.length;
1396
+ continue;
1397
+ }
1398
+ const orphanClose = detectOrphanThinkingClose(trimmedLine);
1399
+ if (orphanClose) {
1400
+ const openers = orphanClose.type === "thinking" ? ["<thinking>", "<think>"] : ["<reasoning>"];
1401
+ let sameLineOpener = -1;
1402
+ let sameLineLen = 0;
1403
+ for (const opener of THINKING_OPENERS[orphanClose.type]) {
1404
+ const idx = orphanClose.before.lastIndexOf(opener);
1405
+ if (idx > sameLineOpener) {
1406
+ sameLineOpener = idx;
1407
+ sameLineLen = opener.length;
1408
+ }
1409
+ }
1410
+ if (sameLineOpener >= 0 && orphanClose.before.slice(0, sameLineOpener).trim()) {
1411
+ currentText += processedLine + (i < lines.length - 1 ? "\n" : "");
1412
+ i++;
1413
+ continue;
1414
+ }
1415
+ if (sameLineOpener >= 0) {
1416
+ const proseBefore = `${currentText}${orphanClose.before.slice(0, sameLineOpener)}`;
1417
+ if (proseBefore.trim()) {
1418
+ blocks.push({ type: "text", content: proseBefore.trimEnd() });
1419
+ }
1420
+ currentText = "";
1421
+ const span = orphanClose.before.slice(sameLineOpener + sameLineLen).trim();
1422
+ if (span) {
1423
+ blocks.push({
1424
+ type: orphanClose.type,
1425
+ content: span,
1426
+ metadata: { isComplete: true }
1427
+ });
1428
+ }
1429
+ if (orphanClose.remainder) {
1430
+ lines.splice(i + 1, 0, orphanClose.remainder);
1431
+ }
1432
+ i++;
1433
+ continue;
1434
+ }
1435
+ let openerIdx = -1;
1436
+ let openerLen = 0;
1437
+ for (const opener of openers) {
1438
+ const idx = currentText.lastIndexOf(opener);
1439
+ if (idx > openerIdx) {
1440
+ openerIdx = idx;
1441
+ openerLen = opener.length;
1442
+ }
1443
+ }
1444
+ if (openerIdx >= 0 && /<\/(?:thinking|think|reasoning)>/.test(currentText.slice(openerIdx + openerLen))) {
1445
+ openerIdx = -1;
1446
+ }
1447
+ let regionText = currentText;
1448
+ if (openerIdx >= 0) {
1449
+ const proseBefore = currentText.slice(0, openerIdx);
1450
+ regionText = currentText.slice(openerIdx + openerLen);
1451
+ if (proseBefore.trim()) {
1452
+ blocks.push({ type: "text", content: proseBefore.trimEnd() });
1453
+ }
1454
+ }
1455
+ currentText = "";
1456
+ const body = [regionText.trim(), orphanClose.before].filter(Boolean).join("\n");
1457
+ if (body) {
1458
+ blocks.push({
1459
+ type: orphanClose.type,
1460
+ content: body,
1461
+ metadata: { isComplete: true, continuation: true }
1462
+ });
1463
+ }
1464
+ if (orphanClose.remainder) {
1465
+ lines.splice(i + 1, 0, orphanClose.remainder);
1466
+ }
1467
+ i++;
1468
+ continue;
1469
+ }
1470
+ const youtubeCheck = detectYoutubeMarkdown(line);
1471
+ if (youtubeCheck.isYoutube && youtubeCheck.videoId) {
1472
+ if (currentText.trim()) {
1473
+ blocks.push({ type: "text", content: currentText.trimEnd() });
1474
+ currentText = "";
1475
+ }
1476
+ blocks.push({
1477
+ type: "youtube",
1478
+ // Keep the original line as content so the DB round-trip
1479
+ // (reconstructBlockMarkdown default → content) re-detects it on reload.
1480
+ content: trimmedLine,
1481
+ src: youtubeCheck.watchUrl,
1482
+ metadata: {
1483
+ videoId: youtubeCheck.videoId,
1484
+ start: youtubeCheck.start,
1485
+ title: youtubeCheck.title,
1486
+ poster: youtubeCheck.poster
1487
+ }
1488
+ });
1489
+ i++;
1490
+ continue;
1491
+ }
1492
+ const imageCheck = detectImageMarkdown(line);
1493
+ if (imageCheck.isImage && countInlineImages(line) < 2 && !TITLED_IMAGE_LINE.test(line)) {
1494
+ if (currentText.trim()) {
1495
+ blocks.push({ type: "text", content: currentText.trimEnd() });
1496
+ currentText = "";
1497
+ }
1498
+ blocks.push({
1499
+ type: "image",
1500
+ content: trimmedLine,
1501
+ src: imageCheck.src,
1502
+ alt: imageCheck.alt
1503
+ });
1504
+ i++;
1505
+ continue;
1506
+ }
1507
+ const videoCheck = detectVideoMarkdown(line);
1508
+ if (videoCheck.isVideo) {
1509
+ if (currentText.trim()) {
1510
+ blocks.push({ type: "text", content: currentText.trimEnd() });
1511
+ currentText = "";
1512
+ }
1513
+ blocks.push({
1514
+ type: "video",
1515
+ content: trimmedLine,
1516
+ src: videoCheck.src,
1517
+ alt: videoCheck.alt
1518
+ });
1519
+ i++;
1520
+ continue;
1521
+ }
1522
+ const audioCheck = detectAudioMarkdown(line);
1523
+ if (audioCheck.isAudio) {
1524
+ if (currentText.trim()) {
1525
+ blocks.push({ type: "text", content: currentText.trimEnd() });
1526
+ currentText = "";
1527
+ }
1528
+ blocks.push({
1529
+ type: "audio",
1530
+ content: trimmedLine,
1531
+ src: audioCheck.src,
1532
+ alt: audioCheck.alt
1533
+ });
1534
+ i++;
1535
+ continue;
1536
+ }
1537
+ const matrxFileCheck = detectMatrxFileMarkdown(line);
1538
+ if (matrxFileCheck.isMatrxFile) {
1539
+ if (currentText.trim()) {
1540
+ blocks.push({ type: "text", content: currentText.trimEnd() });
1541
+ currentText = "";
1542
+ }
1543
+ blocks.push({
1544
+ type: "matrx_file",
1545
+ // Keep the original line on content so the DB round-trip re-emits it
1546
+ // (reconstructBlockMarkdown default → content) and reload re-detects.
1547
+ content: trimmedLine,
1548
+ src: matrxFileCheck.url,
1549
+ alt: matrxFileCheck.label,
1550
+ metadata: {
1551
+ pre: matrxFileCheck.pre,
1552
+ post: matrxFileCheck.post,
1553
+ label: matrxFileCheck.label
1554
+ }
1555
+ });
1556
+ i++;
1557
+ continue;
1558
+ }
1559
+ if (detectTableStart(lines, i)) {
1560
+ if (currentText.trim()) {
1561
+ blocks.push({ type: "text", content: currentText.trimEnd() });
1562
+ currentText = "";
1563
+ }
1564
+ const extraction = extractTable(i, lines);
1565
+ if (extraction.metadata?.isValid !== false) {
1566
+ blocks.push({
1567
+ type: "table",
1568
+ content: extraction.content,
1569
+ metadata: extraction.metadata
1570
+ });
1571
+ i = extraction.nextIndex;
1572
+ } else {
1573
+ currentText += processedLine + "\n";
1574
+ i++;
1575
+ }
1576
+ continue;
1577
+ }
1578
+ if (/^\[\s*(?:\{|$)/.test(trimmedLine)) {
1579
+ const arrayLines = [processedLine];
1580
+ const first = countStructuralBraces(trimmedLine, "[", "]");
1581
+ let opens = first.opens;
1582
+ let closes = first.closes;
1583
+ let j = i + 1;
1584
+ while (j < lines.length && opens > closes) {
1585
+ const nextLine = normalizeLine(lines[j]);
1586
+ arrayLines.push(nextLine);
1587
+ const counts = countStructuralBraces(nextLine, "[", "]");
1588
+ opens += counts.opens;
1589
+ closes += counts.closes;
1590
+ j++;
1591
+ }
1592
+ const arrayContent = arrayLines.join("\n").trim();
1593
+ let isKindArray = false;
1594
+ if (opens === closes && jsonKindSignal(arrayContent) === "kind") {
1595
+ try {
1596
+ isKindArray = Array.isArray(JSON.parse(arrayContent));
1597
+ } catch {
1598
+ isKindArray = false;
1599
+ }
1600
+ }
1601
+ if (isKindArray) {
1602
+ if (currentText.trim()) {
1603
+ blocks.push({ type: "text", content: currentText.trimEnd() });
1604
+ currentText = "";
1605
+ }
1606
+ blocks.push({ type: "code", content: arrayContent, language: "json" });
1607
+ i = j;
1608
+ continue;
1609
+ }
1610
+ }
1611
+ if (trimmedLine.startsWith("{")) {
1612
+ const jsonLines = [processedLine];
1613
+ const first = countStructuralBraces(trimmedLine);
1614
+ let openCount = first.opens;
1615
+ let closeCount = first.closes;
1616
+ let j = i + 1;
1617
+ while (j < lines.length && openCount > closeCount) {
1618
+ const nextLine = normalizeLine(lines[j]);
1619
+ jsonLines.push(nextLine);
1620
+ const counts = countStructuralBraces(nextLine);
1621
+ openCount += counts.opens;
1622
+ closeCount += counts.closes;
1623
+ j++;
1624
+ }
1625
+ if (openCount === closeCount && openCount > 0) {
1626
+ const jsonContent = jsonLines.join("\n").trim();
1627
+ let parsedOk = false;
1628
+ try {
1629
+ const parsed = JSON.parse(jsonContent);
1630
+ parsedOk = parsed !== null && typeof parsed === "object";
1631
+ } catch {
1632
+ }
1633
+ if (parsedOk) {
1634
+ if (currentText.trim()) {
1635
+ blocks.push({ type: "text", content: currentText.trimEnd() });
1636
+ currentText = "";
1637
+ }
1638
+ const jsonType = detectJsonBlockType(jsonContent);
1639
+ if (jsonType) {
1640
+ const streamingState = validateJsonBlock(jsonContent, jsonType);
1641
+ blocks.push({
1642
+ type: jsonType,
1643
+ content: jsonContent,
1644
+ language: "json",
1645
+ // A directive block never carries __ir — see the fenced-JSON
1646
+ // site above (adversarial finding A-8).
1647
+ metadata: jsonType === "matrx" ? streamingState.metadata : envelopes.withIrEnvelope(jsonContent, streamingState.metadata)
1648
+ });
1649
+ } else {
1650
+ blocks.push({
1651
+ type: "code",
1652
+ content: jsonContent,
1653
+ language: "json",
1654
+ metadata: envelopes.withIrEnvelope(jsonContent, void 0)
1655
+ });
1656
+ }
1657
+ i = j;
1658
+ continue;
1659
+ }
1660
+ } else if (openCount > closeCount && // Round 11 (H1): a region whose own grammar already broke into prose
1661
+ // (`{\"__kind\":…}`, `{ some code`) was never JSON — the live
1662
+ // accumulator draws it as text (same check), so reload does too. Its
1663
+ // brace count never balancing must not carry the region to the end
1664
+ // of the message, where a LATER real kind made it "a kind region".
1665
+ !bareRegionBreaksIntoProse(jsonLines.join("\n"), { firstBudget: true })) {
1666
+ const partialJson = jsonLines.join("\n").trim();
1667
+ const jsonType = detectJsonBlockType(partialJson);
1668
+ if (jsonType) {
1669
+ if (currentText.trim()) {
1670
+ blocks.push({ type: "text", content: currentText.trimEnd() });
1671
+ currentText = "";
1672
+ }
1673
+ blocks.push({
1674
+ type: jsonType,
1675
+ content: partialJson,
1676
+ language: "json",
1677
+ metadata: { isComplete: false }
1678
+ });
1679
+ i = j;
1680
+ continue;
1681
+ }
1682
+ const signal = jsonKindSignal(partialJson);
1683
+ if (signal === "kind" || signal === "undecided" && /^\{\s*(?:"[^"]*)?$/.test(partialJson)) {
1684
+ if (currentText.trim()) {
1685
+ blocks.push({ type: "text", content: currentText.trimEnd() });
1686
+ currentText = "";
1687
+ }
1688
+ blocks.push({
1689
+ type: "code",
1690
+ content: partialJson,
1691
+ language: "json",
1692
+ metadata: { isComplete: false }
1693
+ });
1694
+ i = j;
1695
+ continue;
1696
+ }
1697
+ }
1698
+ }
1699
+ const midLineAttrXml = detectMidLineAttributeXml(processedLine);
1700
+ if (midLineAttrXml) {
1701
+ if (currentText.trim()) {
1702
+ blocks.push({ type: "text", content: currentText.trimEnd() });
1703
+ currentText = "";
1704
+ }
1705
+ const textBefore = processedLine.slice(0, midLineAttrXml.tagStart).trimEnd();
1706
+ const tagAndRest = processedLine.slice(midLineAttrXml.tagStart);
1707
+ const closingTag = `</${midLineAttrXml.type}>`;
1708
+ const rawXmlParts = [tagAndRest];
1709
+ let foundClose = tagAndRest.includes(closingTag);
1710
+ if (!foundClose) {
1711
+ for (let j = i + 1; j < lines.length; j++) {
1712
+ const jLine = lines[j];
1713
+ const jIdx = jLine.indexOf(closingTag);
1714
+ if (jIdx !== -1) {
1715
+ rawXmlParts.push(jLine.slice(0, jIdx + closingTag.length));
1716
+ foundClose = true;
1717
+ break;
1718
+ }
1719
+ rawXmlParts.push(jLine);
1720
+ }
1721
+ }
1722
+ const rawXmlOverride = rawXmlParts.join("\n");
1723
+ const replacements = [];
1724
+ if (textBefore) replacements.push(textBefore);
1725
+ replacements.push(tagAndRest);
1726
+ pendingRawXmlOverrides.set(tagAndRest.trim(), rawXmlOverride);
1727
+ lines.splice(i, 1, ...replacements);
1728
+ continue;
1729
+ }
1730
+ if (isTreeLine(trimmedLine)) {
1731
+ let treeEnd = i + 1;
1732
+ while (treeEnd < lines.length) {
1733
+ const nextTrimmed = normalizeLine(lines[treeEnd]).trim();
1734
+ if (!nextTrimmed) {
1735
+ treeEnd++;
1736
+ continue;
1737
+ }
1738
+ if (isTreeLine(nextTrimmed)) {
1739
+ treeEnd++;
1740
+ continue;
1741
+ }
1742
+ break;
1743
+ }
1744
+ const treeLinesCount = lines.slice(i, treeEnd).filter((l) => isTreeLine(normalizeLine(l).trim())).length;
1745
+ if (treeLinesCount >= 3) {
1746
+ const treeStart = findTreeBlockStart(lines, i);
1747
+ if (treeStart < i) {
1748
+ currentText = stripAccumulatedLinesFromText(
1749
+ currentText,
1750
+ lines,
1751
+ treeStart,
1752
+ i
1753
+ );
1754
+ }
1755
+ if (currentText.trim()) {
1756
+ blocks.push({ type: "text", content: currentText.trimEnd() });
1757
+ currentText = "";
1758
+ }
1759
+ const treeContent = lines.slice(treeStart, treeEnd).map((l) => normalizeLine(l)).join("\n");
1760
+ blocks.push({ type: "tree", content: treeContent.trim() });
1761
+ i = treeEnd;
1762
+ continue;
1763
+ }
1764
+ }
1765
+ currentText += processedLine + (i < lines.length - 1 ? "\n" : "");
1766
+ i++;
1767
+ }
1768
+ if (currentText.trim()) {
1769
+ blocks.push({ type: "text", content: currentText.trimEnd() });
1770
+ }
1771
+ return recoverEmbeddedKindJsonBlocksWith(blocks, envelopes);
1772
+ };
1773
+ export {
1774
+ AUDIO_URL_EXT,
1775
+ CODE_LANGUAGE_ALIASES,
1776
+ NO_SPLITTER_ENVELOPES,
1777
+ SPECIAL_CODE_LANGUAGES,
1778
+ countInlineImages,
1779
+ detectImageMarkdown,
1780
+ detectJsonBlockType,
1781
+ detectMatrxFileMarkdown,
1782
+ detectVideoMarkdown,
1783
+ extractAudioLink,
1784
+ isCompleteUnrecognizedXmlContainer,
1785
+ isUnclosedGenericXmlOpening,
1786
+ normalizeCodeLanguage,
1787
+ parseXmlAttributes,
1788
+ recoverEmbeddedKindJsonBlocksWith,
1789
+ splitContentIntoBlocksWith,
1790
+ startUnrecognizedXmlContainer
1791
+ };