@anchrd/intel 0.59.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.
Files changed (364) hide show
  1. package/README.md +496 -0
  2. package/bin/intel.mjs +134 -0
  3. package/dist/adapters/cloudflare/cloudflare-flow-workflow.d.ts +42 -0
  4. package/dist/adapters/cloudflare/cloudflare-flow-workflow.js +89 -0
  5. package/dist/adapters/cloudflare/cloudflare.d.ts +29 -0
  6. package/dist/adapters/cloudflare/cloudflare.js +416 -0
  7. package/dist/adapters/cloudflare/cloudflare.types.d.ts +31 -0
  8. package/dist/adapters/cloudflare/cloudflare.types.js +1 -0
  9. package/dist/adapters/content/content.d.ts +3 -0
  10. package/dist/adapters/content/content.js +128 -0
  11. package/dist/adapters/content/content.types.d.ts +38 -0
  12. package/dist/adapters/content/content.types.js +1 -0
  13. package/dist/adapters/db/db-audit.d.ts +62 -0
  14. package/dist/adapters/db/db-audit.js +150 -0
  15. package/dist/adapters/db/db-boards.d.ts +6 -0
  16. package/dist/adapters/db/db-boards.js +324 -0
  17. package/dist/adapters/db/db-feed.d.ts +30 -0
  18. package/dist/adapters/db/db-feed.js +276 -0
  19. package/dist/adapters/db/db-flows.d.ts +42 -0
  20. package/dist/adapters/db/db-flows.js +1311 -0
  21. package/dist/adapters/db/db-grants.d.ts +133 -0
  22. package/dist/adapters/db/db-grants.js +269 -0
  23. package/dist/adapters/db/db-indexing.d.ts +3 -0
  24. package/dist/adapters/db/db-indexing.js +200 -0
  25. package/dist/adapters/db/db-oauth.d.ts +3 -0
  26. package/dist/adapters/db/db-oauth.js +26 -0
  27. package/dist/adapters/db/db-prompts.d.ts +46 -0
  28. package/dist/adapters/db/db-prompts.js +144 -0
  29. package/dist/adapters/db/db.d.ts +6 -0
  30. package/dist/adapters/db/db.js +1687 -0
  31. package/dist/adapters/db/db.types.d.ts +16 -0
  32. package/dist/adapters/db/db.types.js +1 -0
  33. package/dist/adapters/document-converter/document-converter.d.ts +2 -0
  34. package/dist/adapters/document-converter/document-converter.js +26 -0
  35. package/dist/adapters/document-converter/document-converter.types.d.ts +17 -0
  36. package/dist/adapters/document-converter/document-converter.types.js +1 -0
  37. package/dist/adapters/flow-runtime/flow-runtime.d.ts +3 -0
  38. package/dist/adapters/flow-runtime/flow-runtime.js +22 -0
  39. package/dist/adapters/flow-runtime/flow-runtime.types.d.ts +19 -0
  40. package/dist/adapters/flow-runtime/flow-runtime.types.js +1 -0
  41. package/dist/adapters/index-queue/index-queue.d.ts +10 -0
  42. package/dist/adapters/index-queue/index-queue.js +12 -0
  43. package/dist/adapters/index-queue/index-queue.types.d.ts +11 -0
  44. package/dist/adapters/index-queue/index-queue.types.js +1 -0
  45. package/dist/adapters/json-schema/json-schema.d.ts +2 -0
  46. package/dist/adapters/json-schema/json-schema.js +77 -0
  47. package/dist/adapters/openid/openid.d.ts +5 -0
  48. package/dist/adapters/openid/openid.js +301 -0
  49. package/dist/adapters/portal-tokens/portal-tokens.d.ts +8 -0
  50. package/dist/adapters/portal-tokens/portal-tokens.js +79 -0
  51. package/dist/adapters/remote-tools/remote-tools.d.ts +5 -0
  52. package/dist/adapters/remote-tools/remote-tools.js +129 -0
  53. package/dist/adapters/semantic-index/semantic-index.d.ts +2 -0
  54. package/dist/adapters/semantic-index/semantic-index.js +139 -0
  55. package/dist/adapters/semantic-index/semantic-index.types.d.ts +56 -0
  56. package/dist/adapters/semantic-index/semantic-index.types.js +1 -0
  57. package/dist/adapters/session-cookie/session-cookie.d.ts +5 -0
  58. package/dist/adapters/session-cookie/session-cookie.js +87 -0
  59. package/dist/adapters/tool-source-policy/tool-source-policy.d.ts +1 -0
  60. package/dist/adapters/tool-source-policy/tool-source-policy.js +22 -0
  61. package/dist/audit/audit.d.ts +2 -0
  62. package/dist/audit/audit.js +87 -0
  63. package/dist/audit/audit.types.d.ts +23 -0
  64. package/dist/audit/audit.types.js +1 -0
  65. package/dist/auth/auth.d.ts +2 -0
  66. package/dist/auth/auth.js +284 -0
  67. package/dist/auth/auth.types.d.ts +108 -0
  68. package/dist/auth/auth.types.js +1 -0
  69. package/dist/boards/boards.d.ts +44 -0
  70. package/dist/boards/boards.js +447 -0
  71. package/dist/boards/boards.types.d.ts +117 -0
  72. package/dist/boards/boards.types.js +1 -0
  73. package/dist/build/build.d.ts +2 -0
  74. package/dist/build/build.js +192 -0
  75. package/dist/build/build.types.d.ts +17 -0
  76. package/dist/build/build.types.js +1 -0
  77. package/dist/bundle/bundle.d.ts +4 -0
  78. package/dist/bundle/bundle.js +1194 -0
  79. package/dist/bundle/bundle.types.d.ts +33 -0
  80. package/dist/bundle/bundle.types.js +1 -0
  81. package/dist/cli/cli.d.ts +4 -0
  82. package/dist/cli/cli.js +262 -0
  83. package/dist/cli/cli.types.d.ts +14 -0
  84. package/dist/cli/cli.types.js +1 -0
  85. package/dist/contract/audit.d.ts +50 -0
  86. package/dist/contract/audit.js +72 -0
  87. package/dist/contract/board.d.ts +150 -0
  88. package/dist/contract/board.js +262 -0
  89. package/dist/contract/bundle.d.ts +147 -0
  90. package/dist/contract/bundle.js +139 -0
  91. package/dist/contract/contract.d.ts +112 -0
  92. package/dist/contract/contract.js +143 -0
  93. package/dist/contract/feed.d.ts +126 -0
  94. package/dist/contract/feed.js +149 -0
  95. package/dist/contract/flow-run.d.ts +346 -0
  96. package/dist/contract/flow-run.js +194 -0
  97. package/dist/contract/flow.d.ts +1057 -0
  98. package/dist/contract/flow.js +552 -0
  99. package/dist/contract/node.d.ts +551 -0
  100. package/dist/contract/node.js +456 -0
  101. package/dist/contract/share.d.ts +216 -0
  102. package/dist/contract/share.js +134 -0
  103. package/dist/contract/table.d.ts +171 -0
  104. package/dist/contract/table.js +117 -0
  105. package/dist/contract/tool.d.ts +122 -0
  106. package/dist/contract/tool.js +172 -0
  107. package/dist/feed/feed.d.ts +2 -0
  108. package/dist/feed/feed.js +77 -0
  109. package/dist/feed/feed.types.d.ts +23 -0
  110. package/dist/feed/feed.types.js +1 -0
  111. package/dist/flows/flows.d.ts +41 -0
  112. package/dist/flows/flows.js +2216 -0
  113. package/dist/flows/flows.types.d.ts +339 -0
  114. package/dist/flows/flows.types.js +1 -0
  115. package/dist/http/http.d.ts +9 -0
  116. package/dist/http/http.js +874 -0
  117. package/dist/http/http.types.d.ts +22 -0
  118. package/dist/http/http.types.js +1 -0
  119. package/dist/indexing/indexing.d.ts +6 -0
  120. package/dist/indexing/indexing.js +239 -0
  121. package/dist/indexing/indexing.types.d.ts +11 -0
  122. package/dist/indexing/indexing.types.js +1 -0
  123. package/dist/intel/intel.d.ts +3 -0
  124. package/dist/intel/intel.js +194 -0
  125. package/dist/intel/intel.types.d.ts +24 -0
  126. package/dist/intel/intel.types.js +1 -0
  127. package/dist/mcp/mcp.d.ts +2 -0
  128. package/dist/mcp/mcp.js +1292 -0
  129. package/dist/mcp/mcp.types.d.ts +25 -0
  130. package/dist/mcp/mcp.types.js +1 -0
  131. package/dist/nodes/document-links/document-links.d.ts +20 -0
  132. package/dist/nodes/document-links/document-links.js +57 -0
  133. package/dist/nodes/nodes.d.ts +2 -0
  134. package/dist/nodes/nodes.js +1667 -0
  135. package/dist/nodes/nodes.types.d.ts +511 -0
  136. package/dist/nodes/nodes.types.js +1 -0
  137. package/dist/prepare/prepare.d.ts +4 -0
  138. package/dist/prepare/prepare.js +16 -0
  139. package/dist/prepare/prepare.types.d.ts +9 -0
  140. package/dist/prepare/prepare.types.js +1 -0
  141. package/dist/prompts/prompts.d.ts +2 -0
  142. package/dist/prompts/prompts.js +65 -0
  143. package/dist/prompts/prompts.types.d.ts +71 -0
  144. package/dist/prompts/prompts.types.js +1 -0
  145. package/dist/shared/callers/callers.d.ts +28 -0
  146. package/dist/shared/callers/callers.js +30 -0
  147. package/dist/shared/csv/csv.d.ts +13 -0
  148. package/dist/shared/csv/csv.js +85 -0
  149. package/dist/shared/document-text/document-text.d.ts +21 -0
  150. package/dist/shared/document-text/document-text.js +31 -0
  151. package/dist/shared/gate-authorization/gate-authorization.d.ts +5 -0
  152. package/dist/shared/gate-authorization/gate-authorization.js +26 -0
  153. package/dist/shared/grant-expiry/grant-expiry.d.ts +19 -0
  154. package/dist/shared/grant-expiry/grant-expiry.js +26 -0
  155. package/dist/shared/intel-error/intel-error.d.ts +22 -0
  156. package/dist/shared/intel-error/intel-error.js +27 -0
  157. package/dist/shared/plain-title/plain-title.d.ts +16 -0
  158. package/dist/shared/plain-title/plain-title.js +45 -0
  159. package/dist/shared/problem-details/problem-details.d.ts +30 -0
  160. package/dist/shared/problem-details/problem-details.js +18 -0
  161. package/dist/shared/report-unexpected-error/report-unexpected-error.d.ts +1 -0
  162. package/dist/shared/report-unexpected-error/report-unexpected-error.js +37 -0
  163. package/dist/shared/safe-return-path/safe-return-path.d.ts +2 -0
  164. package/dist/shared/safe-return-path/safe-return-path.js +11 -0
  165. package/dist/shared/sha256/sha256.d.ts +2 -0
  166. package/dist/shared/sha256/sha256.js +14 -0
  167. package/dist/tools/tool-servers/tool-servers.d.ts +46 -0
  168. package/dist/tools/tool-servers/tool-servers.js +177 -0
  169. package/dist/tools/tools.d.ts +2 -0
  170. package/dist/tools/tools.js +207 -0
  171. package/dist/tools/tools.types.d.ts +56 -0
  172. package/dist/tools/tools.types.js +1 -0
  173. package/examples/branding/favicon.svg +5 -0
  174. package/examples/branding/logo.svg +5 -0
  175. package/examples/branding/theme.css +16 -0
  176. package/examples/dev.vars.example +7 -0
  177. package/examples/intel.json +9 -0
  178. package/migrations/0000_intel_foundation.sql +203 -0
  179. package/migrations/0001_portal_native_tools.sql +21 -0
  180. package/migrations/0002_flows_in_the_knowledge_tree.sql +34 -0
  181. package/migrations/0003_folder_permissions.sql +211 -0
  182. package/migrations/0004_subflow_runs.sql +14 -0
  183. package/migrations/0005_flow_node_cleanup.sql +28 -0
  184. package/migrations/0005_tables_in_the_knowledge_tree.sql +68 -0
  185. package/migrations/0006_links_are_written_in_the_text.sql +20 -0
  186. package/migrations/0007_no_node_waits.sql +69 -0
  187. package/migrations/0008_three_layers.sql +130 -0
  188. package/migrations/0009_no_context_policy.sql +48 -0
  189. package/migrations/0010_no_run_waits_a_year.sql +27 -0
  190. package/migrations/0011_one_name_for_the_tree.sql +53 -0
  191. package/migrations/0012_table_snapshots.sql +29 -0
  192. package/migrations/0017_a_vector_per_card.sql +38 -0
  193. package/migrations/0018_no_context_policy_at_last.sql +97 -0
  194. package/migrations/0019_one_name_for_the_grants.sql +52 -0
  195. package/migrations/0020_cascade_purge_replay.sql +11 -0
  196. package/migrations/0021_a_flow_carries_its_own_grant.sql +44 -0
  197. package/migrations/0022_a_cursor_over_the_journal.sql +14 -0
  198. package/migrations/0023_a_board_and_its_tasks.sql +116 -0
  199. package/migrations/0024_the_archive_is_a_column.sql +33 -0
  200. package/migrations/0025_archiving_takes_the_subtree.sql +29 -0
  201. package/migrations/0026_one_feed_over_two_kinds.sql +23 -0
  202. package/migrations/0027_the_runs_of_every_flow.sql +27 -0
  203. package/migrations/0028_a_prompt_name_over_two_kinds.sql +70 -0
  204. package/package.json +118 -0
  205. package/src/contract/CLAUDE.md +152 -0
  206. package/src/contract/README.md +102 -0
  207. package/src/contract/audit.ts +85 -0
  208. package/src/contract/board.ts +306 -0
  209. package/src/contract/bundle.ts +157 -0
  210. package/src/contract/contract.ts +161 -0
  211. package/src/contract/feed.ts +165 -0
  212. package/src/contract/flow-run.ts +241 -0
  213. package/src/contract/flow.ts +675 -0
  214. package/src/contract/node.ts +583 -0
  215. package/src/contract/share.ts +196 -0
  216. package/src/contract/table.ts +178 -0
  217. package/src/contract/tool.ts +202 -0
  218. package/ui/README.md +110 -0
  219. package/ui/components.json +21 -0
  220. package/ui/index.html +13 -0
  221. package/ui/package.json +11 -0
  222. package/ui/scripts/lint-tokens.mjs +187 -0
  223. package/ui/src/access-summary/access-summary.tsx +385 -0
  224. package/ui/src/app/action-slot/action-slot.tsx +27 -0
  225. package/ui/src/app/app-sidebar/app-sidebar.tsx +71 -0
  226. package/ui/src/app/app-tree/app-tree.tsx +1175 -0
  227. package/ui/src/app/app.tsx +87 -0
  228. package/ui/src/app/header-search/header-search.tsx +294 -0
  229. package/ui/src/app/reindex-dialog/reindex-dialog.tsx +79 -0
  230. package/ui/src/app/settings-dialog/settings-dialog.tsx +135 -0
  231. package/ui/src/app/sidebar-preferences/sidebar-preferences.ts +68 -0
  232. package/ui/src/app/sidebar-preferences/sidebar-preferences.types.ts +15 -0
  233. package/ui/src/app/sidebar-resize-handle/sidebar-resize-handle.tsx +86 -0
  234. package/ui/src/app/tree-expansion/tree-expansion.ts +107 -0
  235. package/ui/src/app/tree-expansion/tree-expansion.types.ts +31 -0
  236. package/ui/src/app/tree-move/tree-move.tsx +427 -0
  237. package/ui/src/app/tree-reveal/tree-reveal.ts +64 -0
  238. package/ui/src/app/tree-reveal/tree-reveal.types.ts +16 -0
  239. package/ui/src/app/user-footer/user-footer.tsx +188 -0
  240. package/ui/src/app/view-menu/view-menu.tsx +115 -0
  241. package/ui/src/app-root/app-root.tsx +109 -0
  242. package/ui/src/app-root/app-root.types.ts +27 -0
  243. package/ui/src/archive/archive.tsx +417 -0
  244. package/ui/src/attachment-detail/attachment-detail.ts +52 -0
  245. package/ui/src/attachment-viewer/attachment-viewer.tsx +166 -0
  246. package/ui/src/blocknote-view/blocknote-view.tsx +30 -0
  247. package/ui/src/board/board-assignee/board-assignee-picker.tsx +132 -0
  248. package/ui/src/board/board-assignee/board-assignee.ts +48 -0
  249. package/ui/src/board/board-assignee/board-assignee.tsx +75 -0
  250. package/ui/src/board/board-chip/board-chip.tsx +46 -0
  251. package/ui/src/board/board-crumbs/board-crumbs.ts +56 -0
  252. package/ui/src/board/board-crumbs/board-crumbs.tsx +111 -0
  253. package/ui/src/board/board-data/board-data.ts +195 -0
  254. package/ui/src/board/board-data/board-data.types.ts +38 -0
  255. package/ui/src/board/board-dates/board-dates.ts +40 -0
  256. package/ui/src/board/board-dates/board-dates.tsx +105 -0
  257. package/ui/src/board/board-kanban/board-kanban.ts +162 -0
  258. package/ui/src/board/board-kanban/board-kanban.tsx +680 -0
  259. package/ui/src/board/board-open-task/board-open-task.ts +32 -0
  260. package/ui/src/board/board-panel/board-panel.tsx +259 -0
  261. package/ui/src/board/board-settings/board-settings.tsx +226 -0
  262. package/ui/src/board/board-status/board-status.tsx +60 -0
  263. package/ui/src/board/board-stripes/board-stripes.ts +128 -0
  264. package/ui/src/board/board-table/board-table.ts +53 -0
  265. package/ui/src/board/board-table/board-table.tsx +610 -0
  266. package/ui/src/board/board-task/board-task.ts +108 -0
  267. package/ui/src/board/board-task/board-task.tsx +335 -0
  268. package/ui/src/board/board-title-row/board-title-row.tsx +66 -0
  269. package/ui/src/branding/branding.tsx +17 -0
  270. package/ui/src/branding/custom-logo.ts +1 -0
  271. package/ui/src/branding/favicon.default.svg +5 -0
  272. package/ui/src/branding/favicon.svg +5 -0
  273. package/ui/src/components/ui/avatar.tsx +39 -0
  274. package/ui/src/components/ui/button.tsx +64 -0
  275. package/ui/src/components/ui/collapsible.tsx +20 -0
  276. package/ui/src/components/ui/command.tsx +160 -0
  277. package/ui/src/components/ui/dialog.tsx +143 -0
  278. package/ui/src/components/ui/dropdown-menu.tsx +187 -0
  279. package/ui/src/components/ui/input.tsx +21 -0
  280. package/ui/src/components/ui/popover.tsx +41 -0
  281. package/ui/src/components/ui/select.tsx +163 -0
  282. package/ui/src/components/ui/separator.tsx +26 -0
  283. package/ui/src/components/ui/sheet.tsx +136 -0
  284. package/ui/src/components/ui/sidebar.tsx +693 -0
  285. package/ui/src/components/ui/skeleton.tsx +13 -0
  286. package/ui/src/components/ui/switch.tsx +25 -0
  287. package/ui/src/components/ui/table.tsx +82 -0
  288. package/ui/src/components/ui/tabs.tsx +52 -0
  289. package/ui/src/components/ui/tooltip.tsx +51 -0
  290. package/ui/src/data/intel-data-provider/intel-data-provider.ts +685 -0
  291. package/ui/src/data/intel-data-provider/intel-data-provider.types.ts +266 -0
  292. package/ui/src/data/query-client.ts +18 -0
  293. package/ui/src/data/request-refusal/refusal-notice.tsx +36 -0
  294. package/ui/src/data/request-refusal/request-refusal.ts +51 -0
  295. package/ui/src/data/sign-in/sign-in.ts +97 -0
  296. package/ui/src/data/sign-in/sign-in.types.ts +15 -0
  297. package/ui/src/document-link/document-link.tsx +114 -0
  298. package/ui/src/document-markdown/document-markdown.ts +195 -0
  299. package/ui/src/editor-schema/editor-schema.ts +23 -0
  300. package/ui/src/entry-picker/entry-picker.tsx +238 -0
  301. package/ui/src/feed/feed.tsx +344 -0
  302. package/ui/src/file-preview/file-preview-view.tsx +496 -0
  303. package/ui/src/file-preview/file-preview.tsx +28 -0
  304. package/ui/src/file-preview/pdf-file-preview.tsx +5 -0
  305. package/ui/src/file-preview/presentation-file-preview.tsx +21 -0
  306. package/ui/src/file-preview/spreadsheet-file-preview.tsx +5 -0
  307. package/ui/src/file-preview/word-file-preview.tsx +5 -0
  308. package/ui/src/flow-runs/flow-runs.tsx +238 -0
  309. package/ui/src/flows/flows.tsx +1646 -0
  310. package/ui/src/flows/node-icon/node-icon.ts +34 -0
  311. package/ui/src/flows/node-palette/node-palette.tsx +214 -0
  312. package/ui/src/flows/node-palette/node-palette.types.ts +15 -0
  313. package/ui/src/folder-contents/folder-contents.tsx +321 -0
  314. package/ui/src/folder-index/folder-index.ts +55 -0
  315. package/ui/src/frontmatter/frontmatter.tsx +193 -0
  316. package/ui/src/graph-pane/graph-pane.tsx +53 -0
  317. package/ui/src/hooks/use-mobile.ts +19 -0
  318. package/ui/src/i18n/custom.json +4 -0
  319. package/ui/src/i18n/de.json +609 -0
  320. package/ui/src/i18n/en.json +609 -0
  321. package/ui/src/i18n/es.json +609 -0
  322. package/ui/src/i18n/i18n-context.tsx +65 -0
  323. package/ui/src/i18n/i18n-languages/i18n-languages.ts +83 -0
  324. package/ui/src/i18n/i18n.ts +32 -0
  325. package/ui/src/i18n/i18n.types.ts +49 -0
  326. package/ui/src/kind-icon.ts +97 -0
  327. package/ui/src/lib/utils.ts +6 -0
  328. package/ui/src/main.tsx +68 -0
  329. package/ui/src/modal/modal.tsx +53 -0
  330. package/ui/src/node-details/node-details.tsx +545 -0
  331. package/ui/src/node-editor/node-editor.tsx +304 -0
  332. package/ui/src/node-graph/graph-notice.tsx +21 -0
  333. package/ui/src/node-graph/node-graph.ts +214 -0
  334. package/ui/src/node-graph/node-graph.tsx +295 -0
  335. package/ui/src/node-import/node-import.tsx +418 -0
  336. package/ui/src/node-save-error.ts +46 -0
  337. package/ui/src/node-source/node-source.tsx +128 -0
  338. package/ui/src/node-table/node-table.tsx +107 -0
  339. package/ui/src/nodes/nodes.tsx +240 -0
  340. package/ui/src/rename-resource/rename-resource.ts +80 -0
  341. package/ui/src/resolved-names/resolved-names.tsx +67 -0
  342. package/ui/src/resource-error.ts +33 -0
  343. package/ui/src/resource-menu/resource-menu.tsx +1156 -0
  344. package/ui/src/router/router-context.ts +6 -0
  345. package/ui/src/router/router.tsx +67 -0
  346. package/ui/src/router/router.types.ts +8 -0
  347. package/ui/src/router/selection-search.ts +88 -0
  348. package/ui/src/save-button/save-button.tsx +94 -0
  349. package/ui/src/section-hint/section-hint.tsx +40 -0
  350. package/ui/src/sign-in-refused/sign-in-refused.tsx +35 -0
  351. package/ui/src/styles.css +189 -0
  352. package/ui/src/table-columns/table-columns.tsx +379 -0
  353. package/ui/src/theme/custom.css +1 -0
  354. package/ui/src/theme/theme-context.tsx +89 -0
  355. package/ui/src/theme/theme.ts +70 -0
  356. package/ui/src/time/relative-time.tsx +41 -0
  357. package/ui/src/time/time-context.tsx +78 -0
  358. package/ui/src/time/time.ts +165 -0
  359. package/ui/src/title-row/title-row.tsx +185 -0
  360. package/ui/src/tools/tools.tsx +546 -0
  361. package/ui/src/user-name/user-name.ts +61 -0
  362. package/ui/src/verbatim/verbatim.tsx +68 -0
  363. package/ui/tsconfig.json +17 -0
  364. package/ui/vite.config.ts +129 -0
@@ -0,0 +1,195 @@
1
+ import { BlockNoteDocument } from "@contract/node.ts";
2
+ import type { IntelEditor, IntelEditorPartialBlock } from "@/editor-schema/editor-schema.ts";
3
+ import { FrontmatterBlockType, splitFrontmatter } from "@/frontmatter/frontmatter.tsx";
4
+ import { VerbatimBlockType } from "@/verbatim/verbatim.tsx";
5
+
6
+ /**
7
+ * The bridge between a document's markdown and the editor's blocks, in both directions.
8
+ *
9
+ * ⚠️ **The markdown is the truth; the editor is a nicer view onto it** (Jack, 2026-08-30). That
10
+ * sentence was not true when it was written. The editor writes with `blocksToMarkdownLossy` — the
11
+ * name is BlockNote's, not ours — and what it does not understand it rewrites into what it does.
12
+ * Exactly one construct had a way out: the head, given one in #657 after it had already cost data.
13
+ * The comment there names the damage, and it is worth repeating because it is the reason this
14
+ * module exists: *"Opening a document and saving it left its metadata unfindable, and nothing
15
+ * anywhere went red."*
16
+ *
17
+ * ⚠️ `payload.markdown` is not decoration. `indexing.ts` pulls that field and nothing else into
18
+ * full text and vectors, so it is the whole of what a search can match, and `bundle.ts` writes it
19
+ * out as the document's `.md`. A document can be right on screen and unfindable.
20
+ *
21
+ * **The way out, generalised from the head:** a piece the editor cannot give back is parked as raw
22
+ * text and written back byte for byte (#826). Two rules follow from that, and both are load-bearing:
23
+ *
24
+ * - **What the editor OWNS, it may re-spell.** `- a` comes back as `* a`, `_kursiv_` as `*kursiv*`,
25
+ * a table padded to its widest cell. That is one markdown construct written another way, and the
26
+ * document says the same thing afterwards.
27
+ * - **What it does not own, it does not touch.** An HTML block, a comment, a math block, a
28
+ * directive: measured, those come back as their inner text, as an empty string, or mangled with
29
+ * backslashes. They are parked.
30
+ *
31
+ * ⚠️ **The separation between blocks is normalised, their CONTENT is not.** Blocks are joined with
32
+ * one blank line and the document ends with one newline, which is what the serialiser has always
33
+ * produced. A document already in that form comes back byte for byte; one with three blank lines
34
+ * between two paragraphs loses two of them on the first save, and nothing else.
35
+ */
36
+
37
+ // Everything markdown uses to SPELL something, plus whitespace. What is left over is what the
38
+ // author wrote — and a re-spelling may not change that by a single character.
39
+ //
40
+ // ⚠️ This is the whole classifier, and it is deliberately not a byte comparison: measured against
41
+ // BlockNote 0.52, a byte comparison parks every `-` bulleted list, every `_kursiv_`, every table
42
+ // and every setext heading, because the serialiser answers each of them in its own spelling. A
43
+ // document written by an agent is full of those, and parking them would make most of it read-only
44
+ // for a difference nobody can see.
45
+ const SPELLING = /[\s*_\-#>|=+~`.:()[\]!]/g;
46
+
47
+ function spoken(markdown: string): string {
48
+ return markdown.replace(SPELLING, "");
49
+ }
50
+
51
+ /**
52
+ * The document cut into top-level pieces, at blank lines.
53
+ *
54
+ * ⚠️ A fence is not cut into. Everything else may be: an HTML block with a blank line in it becomes
55
+ * two pieces, both parked, and the join puts the blank line back — the bytes come out the same
56
+ * either way.
57
+ *
58
+ * ⚠️ **A fence is closed by its own marker AND at least its own length**, never by the marker
59
+ * alone. Four backticks around a block that shows three is how one writes about markdown in
60
+ * markdown; a guard that only compares the character closes the outer fence at the inner one, cuts
61
+ * the rest at the next blank line, and the join then writes one blank line where the code had two.
62
+ * Found in review on this pull request, and its case is in the test file below.
63
+ */
64
+ function segmentsOf(body: string): string[] {
65
+ const segments: string[] = [];
66
+ let current: string[] = [];
67
+ let fence: { marker: string; length: number } | null = null;
68
+ for (const line of body.split("\n")) {
69
+ const run = /^\s{0,3}(`{3,}|~{3,})/.exec(line)?.[1];
70
+ if (fence === null) {
71
+ if (run) fence = { marker: run[0] ?? "", length: run.length };
72
+ } else if (run && run[0] === fence.marker && run.length >= fence.length) {
73
+ fence = null;
74
+ }
75
+ if (fence === null && line.trim() === "") {
76
+ if (current.length > 0) segments.push(current.join("\n"));
77
+ current = [];
78
+ continue;
79
+ }
80
+ current.push(line);
81
+ }
82
+ if (current.length > 0) segments.push(current.join("\n"));
83
+ return segments;
84
+ }
85
+
86
+ /**
87
+ * One piece, either as blocks the editor owns or as one parked block.
88
+ *
89
+ * ⚠️ A piece that throws while being parsed is parked rather than lost. A parser that gives up is
90
+ * the clearest case there is of something the editor does not understand.
91
+ */
92
+ function blocksFor(editor: IntelEditor, segment: string): IntelEditorPartialBlock[] {
93
+ const parked = [{ type: VerbatimBlockType, props: { raw: segment } } as IntelEditorPartialBlock];
94
+ try {
95
+ const parsed = editor.tryParseMarkdownToBlocks(segment) as IntelEditorPartialBlock[];
96
+ const back = editor.blocksToMarkdownLossy(parsed as never) as unknown as string;
97
+ return spoken(back) === spoken(segment) ? parsed : parked;
98
+ } catch {
99
+ return parked;
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Markdown to blocks, with the head lifted out of remark's way and everything the editor cannot
105
+ * give back parked as it stands.
106
+ *
107
+ * ⚠️ Synchronous, because both halves of the probe are: `tryParseMarkdownToBlocks` and
108
+ * `blocksToMarkdownLossy` return their answer directly in BlockNote 0.52. Making this `async` would
109
+ * put the load path behind a tick, and the effect in `node-editor.tsx` moves `savedSnapshot`
110
+ * immediately after it — a gap there is a document that reports itself unsaved the moment it opens
111
+ * (#115).
112
+ */
113
+ export function markdownToBlocks(editor: IntelEditor, markdown: string): IntelEditorPartialBlock[] {
114
+ const { raw, body } = splitFrontmatter(markdown);
115
+ const blocks = segmentsOf(body).flatMap((segment) => blocksFor(editor, segment));
116
+ if (raw === null) return blocks;
117
+ return [{ type: FrontmatterBlockType, props: { raw } } as IntelEditorPartialBlock, ...blocks];
118
+ }
119
+
120
+ function rawOf(block: { props?: { raw?: unknown } } | undefined): string {
121
+ return typeof block?.props?.raw === "string" ? block.props.raw : "";
122
+ }
123
+
124
+ /**
125
+ * Blocks to markdown: the head as a head, the parked pieces as they went in, the rest to the
126
+ * editor's own serialiser.
127
+ *
128
+ * ⚠️ The head and the parked pieces are found by TYPE, never by position. A block is a block: the
129
+ * drag handle moves them, and typing above the first one pushes it down. A positional guard would
130
+ * fall through to `blocksToMarkdownLossy` for exactly those documents, and that path serialises the
131
+ * block's rendered DOM — `payload.markdown` would carry the word "Metadata" where the metadata used
132
+ * to be (#657).
133
+ *
134
+ * ⚠️ The runs between two parked pieces are serialised TOGETHER, not one block at a time. A list is
135
+ * several blocks and one construct; serialising its items separately would write a list per item.
136
+ */
137
+ export function blocksToMarkdown(editor: IntelEditor, blocks: unknown[]): string {
138
+ const all = blocks as { type?: string; props?: { raw?: unknown } }[];
139
+ const at = all.findIndex((block) => block?.type === FrontmatterBlockType);
140
+ // A document has ONE head, and it belongs at the top wherever the block ended up. Any further
141
+ // head block stays in the body and is serialised by the spec's own `toExternalHTML` — as a code
142
+ // block, which is visible and lossless without claiming to be a second head.
143
+ const body = all.filter((_, index) => index !== at);
144
+ const chunks: string[] = [];
145
+ let run: unknown[] = [];
146
+ const flush = () => {
147
+ if (run.length === 0) return;
148
+ chunks.push(editor.blocksToMarkdownLossy(run as never) as unknown as string);
149
+ run = [];
150
+ };
151
+ for (const block of body) {
152
+ if (block?.type === VerbatimBlockType) {
153
+ flush();
154
+ chunks.push(rawOf(block));
155
+ } else {
156
+ run.push(block);
157
+ }
158
+ }
159
+ flush();
160
+ // One blank line between blocks and one newline at the end: the shape the serialiser has always
161
+ // produced, applied to the joins as well so that a parked piece and a written one are separated
162
+ // the same way.
163
+ const text = chunks
164
+ .map((chunk) => chunk.replace(/\n+$/, ""))
165
+ .filter((chunk) => chunk.length > 0)
166
+ .join("\n\n");
167
+ const document = text.length === 0 ? "" : `${text}\n`;
168
+ if (at < 0) return document;
169
+ // ⚠️ The blank line after the closing fence is part of the contract, not tidiness: without it the
170
+ // first line of the body sits against the fence, and a `# Title` there would be read back as part
171
+ // of nothing in particular the next time round.
172
+ return `---\n${rawOf(all[at])}\n---\n\n${document}`;
173
+ }
174
+
175
+ /**
176
+ * The markdown OF a stored document, whichever way it was stored.
177
+ *
178
+ * ⚠️ Two shapes reach this. What the editor writes is a BlockNote payload, and `markdown` beside its
179
+ * blocks is the whole of what anybody outside the editor reads — the same field `indexing.ts` pulls
180
+ * on the server (`document-text.ts` answers the same question there, for the same reason). What an
181
+ * agent writes over MCP is `text/markdown`, and that content IS its text.
182
+ *
183
+ * ⚠️ A payload that will not parse falls back to the raw content rather than to an empty box. An
184
+ * empty box invites somebody to type into it and save, and that save would replace a document
185
+ * nobody could read with one sentence.
186
+ */
187
+ export function documentMarkdown(content: string | null): string {
188
+ if (!content) return "";
189
+ try {
190
+ const parsed = BlockNoteDocument.safeParse(JSON.parse(content));
191
+ return parsed.success ? parsed.data.markdown : content;
192
+ } catch {
193
+ return content;
194
+ }
195
+ }
@@ -0,0 +1,23 @@
1
+ import { BlockNoteSchema } from "@blocknote/core";
2
+ import { documentLinkSpec } from "@/document-link/document-link.tsx";
3
+ import { frontmatterSpec } from "@/frontmatter/frontmatter.tsx";
4
+ import { verbatimSpec } from "@/verbatim/verbatim.tsx";
5
+
6
+ // One schema for the editor and for everything that reads its documents back: BlockNote's defaults
7
+ // plus what Intel adds — the inline element of #41, the head block of #657 and, since #826, the
8
+ // block that carries whatever the editor cannot give back unchanged.
9
+ //
10
+ // ⚠️ `extend` rather than `create({ inlineContentSpecs: { ...defaults, documentLink } })`. Both
11
+ // build the same schema at run time, but only `extend` keeps the added types in the schema's own
12
+ // generics under this repo's `exactOptionalPropertyTypes` — with the spread form the editor infers
13
+ // BlockNote's default schema, typechecks, and then refuses to insert a document link.
14
+ export const intelEditorSchema = BlockNoteSchema.create().extend({
15
+ inlineContentSpecs: { documentLink: documentLinkSpec },
16
+ blockSpecs: { frontmatter: frontmatterSpec(), verbatim: verbatimSpec() },
17
+ });
18
+
19
+ // The schema carries its own editor and block types as declaration-only properties, which is the
20
+ // only way to name them here: writing `BlockNoteEditor<typeof schema.blockSchema, ...>` by hand
21
+ // fails BlockNote's own constraint under `exactOptionalPropertyTypes`.
22
+ export type IntelEditor = (typeof intelEditorSchema)["BlockNoteEditor"];
23
+ export type IntelEditorPartialBlock = (typeof intelEditorSchema)["PartialBlock"];
@@ -0,0 +1,238 @@
1
+ import type { NodeKind } from "@contract/node.ts";
2
+ import { useQuery } from "@tanstack/react-query";
3
+ import { ChevronRight, Folder, Search } from "lucide-react";
4
+ import { useMemo, useState } from "react";
5
+ import { useI18n } from "@/i18n/i18n-context.tsx";
6
+ import { useIntelRouterContext } from "@/router/router-context.ts";
7
+
8
+ // What this picker offers, whichever side of the shared tree it came from. Deliberately the four
9
+ // fields a picker needs and not the whole record: a Node and a Flow are two different things
10
+ // (ADR-0004), and merging them into one row is only allowed where the row is the entire question.
11
+ export type PickerEntry = { id: string; title: string; parentId: string | null; kind: PickerKind };
12
+ export type PickerKind = NodeKind | "flow";
13
+
14
+ /**
15
+ * One picker for everything a node or a flow can name: search by name, or click down through the
16
+ * tree from the top-level folders.
17
+ *
18
+ * ⚠️ Both halves read ONE source — the authorized flat graph the editor already loads. Not because
19
+ * a search endpoint would be hard, but because `searchNodes` answers a different question: it
20
+ * searches PASSAGES and hands back citations. Someone looking for a document by name would get
21
+ * results ranked by what is written inside it, which is the wrong list in a picker. Filtering names
22
+ * here is also instant, and it cannot disagree with the tree about what exists.
23
+ *
24
+ * ⚠️ What the caller may not see is absent from the graph, so it is absent here — and an empty
25
+ * result never says which of the two it is (#17, #19).
26
+ */
27
+ export function EntryPicker({
28
+ kind,
29
+ flows = false,
30
+ value,
31
+ onSelect,
32
+ label,
33
+ }: {
34
+ // Which kind of node may be chosen. `null` offers every kind — what the Flow step uses.
35
+ //
36
+ // ⚠️ A list is not a convenience (#255). A caller that accepts a folder, a document OR a table
37
+ // has one question to ask, and the alternatives — one picker per kind, or a picker that offers
38
+ // everything and refuses afterwards — are either three dialogs or a rejection after the fact.
39
+ // What may be picked has to be the same question the caller can answer in one place.
40
+ kind: NodeKind | NodeKind[] | null;
41
+ // ⚠️ Flows are a second list, not a kind of node, so they are asked for rather than assumed. A
42
+ // caller that may aim at either says so; every other caller wants nodes only and pays for no
43
+ // second request.
44
+ flows?: boolean;
45
+ value: string;
46
+ onSelect(entryId: string, entryKind: PickerKind): void;
47
+ label: string;
48
+ }) {
49
+ const { data } = useIntelRouterContext();
50
+ const i18n = useI18n();
51
+ const [query, setQuery] = useState("");
52
+ const [openFolder, setOpenFolder] = useState<string | null>(null);
53
+ const graph = useQuery({
54
+ queryKey: ["node-graph"],
55
+ queryFn: () => data.getNodeGraph(),
56
+ });
57
+ const flowList = useQuery({
58
+ queryKey: ["flows"],
59
+ queryFn: () => data.listFlows(),
60
+ enabled: flows,
61
+ });
62
+
63
+ const nodes = useMemo<PickerEntry[]>(() => {
64
+ const fromNodes = (graph.data?.nodes ?? []).map((node) => ({
65
+ id: node.id,
66
+ title: node.title,
67
+ parentId: node.parentId,
68
+ kind: node.kind as PickerKind,
69
+ }));
70
+ if (!flows) return fromNodes;
71
+ return [
72
+ ...fromNodes,
73
+ ...(flowList.data?.items ?? []).map((flow) => ({
74
+ id: flow.id,
75
+ title: flow.title,
76
+ parentId: flow.parentId,
77
+ kind: "flow" as const,
78
+ })),
79
+ ];
80
+ }, [graph.data, flowList.data, flows]);
81
+ // A flow is offered exactly when it was asked for — it is only in `nodes` at all in that case,
82
+ // and saying so here keeps "may this be picked" one rule rather than two that must agree.
83
+ //
84
+ // ⚠️ The array is rebuilt from `kind` on every render when a caller passes a literal, so the set
85
+ // is keyed by its contents rather than by identity — a `useMemo` on the array itself would be a
86
+ // memo that never hits.
87
+ const wanted = Array.isArray(kind) ? kind.join(",") : kind;
88
+ const pickable = useMemo(() => {
89
+ const allowed = wanted === null ? null : new Set(wanted.split(","));
90
+ return (entry: PickerEntry) =>
91
+ entry.kind === "flow" || allowed === null || allowed.has(entry.kind);
92
+ }, [wanted]);
93
+ const eligible = useMemo(() => nodes.filter(pickable), [nodes, pickable]);
94
+
95
+ /**
96
+ * Whether an entry stands on the picker's start level.
97
+ *
98
+ * ⚠️ The root is not `parentId === null` but the upper edge of what the reader may see — the same
99
+ * rule #429 gave the server in `levelPredicate`, at the second surface (#447). A recipient whose
100
+ * only access is a NESTED share carries a `parentId` pointing at a folder that is not in the list
101
+ * at all: under the old rule their start level was empty and STAYED empty, because the folder
102
+ * that would open it can never be clicked. Only searching reached them.
103
+ *
104
+ * ⚠️ Nothing is added by this. The list is what `getNodeGraph()` — and `listFlows()` where flows
105
+ * were asked for — answered, and the answer is already the authorized one. What changes is only
106
+ * which level an entry stands on.
107
+ */
108
+ const rooted = useMemo(() => {
109
+ const present = new Set(nodes.map((node) => node.id));
110
+ return (entry: PickerEntry) => entry.parentId === null || !present.has(entry.parentId);
111
+ }, [nodes]);
112
+
113
+ // Searching looks at every eligible node wherever it sits; browsing looks at one level. The two
114
+ // are the same list seen two ways, which is why a result can be picked from either.
115
+ const searching = query.trim().length > 0;
116
+ const shown = useMemo(() => {
117
+ if (searching) {
118
+ const needle = query.trim().toLowerCase();
119
+ return eligible.filter((node) => node.title.toLowerCase().includes(needle)).slice(0, 50);
120
+ }
121
+ return nodes.filter((node) =>
122
+ openFolder === null ? rooted(node) : node.parentId === openFolder,
123
+ );
124
+ }, [searching, query, eligible, nodes, openFolder, rooted]);
125
+
126
+ // The way back up, as the chain of folders that leads to the open one.
127
+ const trail = useMemo(() => {
128
+ const chain: PickerEntry[] = [];
129
+ let current = openFolder;
130
+ while (current !== null) {
131
+ const folder = nodes.find((node) => node.id === current);
132
+ if (!folder) break;
133
+ chain.unshift(folder);
134
+ // ⚠️ The chain ends where the start level begins, and by the same rule that decides it. A
135
+ // folder at the upper edge has an ancestor the reader cannot see; walking into it would look
136
+ // for a level that does not exist here, and the "top" button above leads to this one.
137
+ current = rooted(folder) ? null : folder.parentId;
138
+ }
139
+ return chain;
140
+ }, [openFolder, nodes, rooted]);
141
+
142
+ const chosen = nodes.find((node) => node.id === value) ?? null;
143
+
144
+ return (
145
+ <div className="mt-4">
146
+ <span className="block text-sm font-medium">{label}</span>
147
+ {chosen ? (
148
+ <p className="mt-1 truncate text-sm text-muted-foreground">{chosen.title}</p>
149
+ ) : (
150
+ <p className="mt-1 text-sm text-muted-foreground">{i18n.t("flows.linkEmpty")}</p>
151
+ )}
152
+
153
+ <div className="mt-2 rounded-lg border">
154
+ <label className="flex items-center gap-2 border-b px-2 py-1.5">
155
+ <Search aria-hidden="true" className="size-4 shrink-0 text-muted-foreground" />
156
+ <span className="sr-only">{i18n.t("picker.search")}</span>
157
+ <input
158
+ type="search"
159
+ value={query}
160
+ onChange={(event) => setQuery(event.target.value)}
161
+ placeholder={i18n.t("picker.search")}
162
+ className="w-full bg-transparent text-sm outline-none"
163
+ />
164
+ </label>
165
+
166
+ {/* The trail is hidden while searching: a result can come from anywhere, so "where you are"
167
+ would be a claim about a place the list is no longer showing. */}
168
+ {!searching && trail.length > 0 ? (
169
+ <div className="flex flex-wrap items-center gap-1 border-b px-2 py-1 text-xs">
170
+ <button
171
+ type="button"
172
+ onClick={() => setOpenFolder(null)}
173
+ className="rounded px-1 outline-none hover:underline focus-visible:ring-2 focus-visible:ring-ring"
174
+ >
175
+ {i18n.t("picker.top")}
176
+ </button>
177
+ {trail.map((folder) => (
178
+ <span key={folder.id} className="flex items-center gap-1">
179
+ <ChevronRight aria-hidden="true" className="size-3 text-muted-foreground" />
180
+ <button
181
+ type="button"
182
+ onClick={() => setOpenFolder(folder.id)}
183
+ className="rounded px-1 outline-none hover:underline focus-visible:ring-2 focus-visible:ring-ring"
184
+ >
185
+ {folder.title}
186
+ </button>
187
+ </span>
188
+ ))}
189
+ </div>
190
+ ) : null}
191
+
192
+ <ul className="max-h-56 overflow-y-auto py-1">
193
+ {shown.map((node) => {
194
+ // A folder is two things while browsing: somewhere to go, and — when folders are what
195
+ // is being picked — something to choose. It gets both, side by side, rather than one
196
+ // control that has to guess which was meant.
197
+ const canOpen = node.kind === "folder" && !searching;
198
+ const canPick = pickable(node);
199
+ return (
200
+ <li key={`${node.kind}:${node.id}`} className="flex items-center gap-1 px-1">
201
+ {canPick ? (
202
+ <button
203
+ type="button"
204
+ onClick={() => onSelect(node.id, node.kind)}
205
+ aria-current={node.id === value}
206
+ className={`min-w-0 flex-1 truncate rounded px-2 py-1.5 text-left text-sm outline-none hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring ${
207
+ node.id === value ? "bg-muted font-medium" : ""
208
+ }`}
209
+ >
210
+ {node.title}
211
+ <span className="sr-only"> — {i18n.t(`node.kind.${node.kind}`)}</span>
212
+ </button>
213
+ ) : (
214
+ <span className="min-w-0 flex-1 truncate px-2 py-1.5 text-sm text-muted-foreground">
215
+ {node.title}
216
+ </span>
217
+ )}
218
+ {canOpen ? (
219
+ <button
220
+ type="button"
221
+ onClick={() => setOpenFolder(node.id)}
222
+ aria-label={i18n.t("picker.open", { title: node.title })}
223
+ className="inline-flex size-7 shrink-0 items-center justify-center rounded outline-none hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring"
224
+ >
225
+ <Folder aria-hidden="true" className="size-4" />
226
+ </button>
227
+ ) : null}
228
+ </li>
229
+ );
230
+ })}
231
+ {shown.length === 0 ? (
232
+ <li className="px-3 py-2 text-sm text-muted-foreground">{i18n.t("picker.nothing")}</li>
233
+ ) : null}
234
+ </ul>
235
+ </div>
236
+ </div>
237
+ );
238
+ }