collabmd 0.1.48 → 0.1.49

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 (268) hide show
  1. package/README.md +61 -5
  2. package/bin/collabmd.js +3 -0
  3. package/dist/client/assets/CodeMirrorEditor-UCMRAMCV-C-nmgL5g.js +1 -0
  4. package/dist/client/assets/abnfDiagram-VCTEODGH-CdVZqicp.js +1 -0
  5. package/dist/client/assets/{architectureDiagram-5GKGNRK7-I39sYjHI.js → architectureDiagram-5GKGNRK7-wUo0bXif.js} +3 -3
  6. package/dist/client/assets/{blockDiagram-NRAW4CY4-C6DtJy5Y.js → blockDiagram-I7D4REHJ-BRSK3m49.js} +4 -4
  7. package/dist/client/assets/browser-utils-DbXSMI-6.js +1 -0
  8. package/dist/client/assets/{c4Diagram-UCG6FXSJ-CtWtxS9_.js → c4Diagram-7LVT6UL2-yD2poxqw.js} +1 -1
  9. package/dist/client/assets/channel-Fa4GS_-F.js +1 -0
  10. package/dist/client/assets/chunk-2E4U76K2-BPnTT-J3.js +2 -0
  11. package/dist/client/assets/{chunk-TEH6E4GO-DVayPj-q.js → chunk-4HAMMTFA-DStbIfJ1.js} +20 -20
  12. package/dist/client/assets/{chunk-75Z2AOVW-CdFSaPjz.js → chunk-75Z2AOVW-C5pGd3W9.js} +1 -1
  13. package/dist/client/assets/{chunk-CLGD4ZFX-DQLgbQrv.js → chunk-CLGD4ZFX-BMeArk03.js} +1 -1
  14. package/dist/client/assets/chunk-DU6HZSFF-Zkp69c6c.js +127 -0
  15. package/dist/client/assets/{chunk-F27PBJKO-CNHIFhjo.js → chunk-F27PBJKO-D4xNct1r.js} +1 -1
  16. package/dist/client/assets/{chunk-GMAD6QVW-CbcBqPM5.js → chunk-GMAD6QVW-NZ6WpDb8.js} +1 -1
  17. package/dist/client/assets/{chunk-OBVCFTLP-BJR7u1jh.js → chunk-GVQU2GXP-D8SDprfX.js} +1 -1
  18. package/dist/client/assets/{chunk-G27WJ6UU-D9-eg537.js → chunk-IMKFNOWR-D8jEuU3Q.js} +2 -2
  19. package/dist/client/assets/{chunk-JQ64N6SF-DSFNH4bh.js → chunk-L3NEJ4N5-aijKP-eh.js} +1 -1
  20. package/dist/client/assets/chunk-OSK3NFVY-CCbLQAMa.js +10 -0
  21. package/dist/client/assets/{chunk-P2QGCYS3-BRpqY32H.js → chunk-P2QGCYS3-Oag_FdCq.js} +1 -1
  22. package/dist/client/assets/{chunk-POPQ4Y6H-Cb4SlVZF.js → chunk-POPQ4Y6H-CYnpIgXT.js} +1 -1
  23. package/dist/client/assets/{chunk-PWAF6VOD-Cz8IwBB5.js → chunk-PWAF6VOD-JSNrcBmP.js} +1 -1
  24. package/dist/client/assets/{chunk-RM4UPSZO-CWRwxNWJ.js → chunk-RM4UPSZO-DOWllDY8.js} +4 -4
  25. package/dist/client/assets/{chunk-RHFEMEQ7-CWY8cf4H.js → chunk-SHT3W25Y-BiQGljao.js} +5 -5
  26. package/dist/client/assets/{chunk-SVP7TREG-CyHOmcac.js → chunk-SVP7TREG-DB485R5x.js} +1 -1
  27. package/dist/client/assets/{chunk-LCL6LL3I-DmDkVukh.js → chunk-TICWLB2K-8rAVvoS3.js} +4 -4
  28. package/dist/client/assets/{chunk-CKBBP62Z-DG26UiBv.js → chunk-TLUHSLCS-DTY5v0aj.js} +2 -2
  29. package/dist/client/assets/classDiagram-ZZMXUADV-BsQWoR3Q.js +1 -0
  30. package/dist/client/assets/classDiagram-v2-VYDZK3BY-BsQWoR3Q.js +1 -0
  31. package/dist/client/assets/{comment-thread-store-CdcwrObc.js → comment-thread-store-CX98zR6q.js} +1 -1
  32. package/dist/client/assets/comment-threads-BnRwo5B9.js +4 -0
  33. package/dist/client/assets/{cynefinDiagram-5FMLGOSQ-CdTPi2yO.js → cynefinDiagram-5FMLGOSQ-rTz9RhFH.js} +1 -1
  34. package/dist/client/assets/{dagre-3AP2YEHR-5gtJtd0O.js → dagre-GXQ25YYZ-Cr67wLpQ.js} +2 -2
  35. package/dist/client/assets/{diagram-S7CK7UJ4-BudWz9WU.js → diagram-S7CK7UJ4-DywERe__.js} +2 -2
  36. package/dist/client/assets/{diagram-UQ7AKVKN-3v4rbH-s.js → diagram-UQ7AKVKN-CNYlXtu4.js} +1 -1
  37. package/dist/client/assets/diagram-VSXAHHWV-0H1a61Bn.js +3 -0
  38. package/dist/client/assets/{diagram-VX7I27RA-C_-rj0sw.js → diagram-VX7I27RA-CsJbsx5F.js} +1 -1
  39. package/dist/client/assets/{diagram-Z3DM3KII-B4yxavgj.js → diagram-Z3DM3KII-D0mLxEgI.js} +1 -1
  40. package/dist/client/assets/dist--hk9I5HQ.js +1 -0
  41. package/dist/client/assets/{dist-DutPuFmC.js → dist-B1XEa-Zs.js} +1 -1
  42. package/dist/client/assets/dist-Bd1JucQF.js +1 -0
  43. package/dist/client/assets/{dist-DZAAWTG-.js → dist-C0G3AaXG.js} +1 -1
  44. package/dist/client/assets/dist-Dm4w5UId.js +1 -0
  45. package/dist/client/assets/{dist-B_yQaEv_.js → dist-DpYDHR9p.js} +1 -1
  46. package/dist/client/assets/{dist-CietwTVS.js → dist-DzNmC81X.js} +1 -1
  47. package/dist/client/assets/{dist-DDUn5zTz.js → dist-GvvCNBe4.js} +1 -1
  48. package/dist/client/assets/dist-JaU3aRlD.js +19 -0
  49. package/dist/client/assets/{dist-Bw3VIE_v.js → dist-TW8iFRyW.js} +1 -1
  50. package/dist/client/assets/{dist-Dir5dDKZ.js → dist-jVU5Be5P.js} +1 -1
  51. package/dist/client/assets/document-formatter-rDDjRfZ2.js +2 -0
  52. package/dist/client/assets/drawioEditor-JubEEha7.js +9 -0
  53. package/dist/client/assets/ebnfDiagram-PWID7BFC-HeTxHpCU.js +1 -0
  54. package/dist/client/assets/editor-session-Bk6oIvSr.js +54 -0
  55. package/dist/client/assets/{embedded-editor-base-B3l6Xy65.css → embedded-editor-base-BlCgz6mw.css} +1 -1
  56. package/dist/client/assets/{erDiagram-SSCWMZ5O-C-iy6VkX.js → erDiagram-RLTQ6QDP-CbrDdd5P.js} +2 -2
  57. package/dist/client/assets/{vault-utils-DvoSftJT.js → es-Bfdp096f.js} +13 -13
  58. package/dist/client/assets/excalidraw-element-link-YLIaTnVF.js +7 -0
  59. package/dist/client/assets/{excalidraw-scene-BOben2gr.js → excalidraw-scene-6uROebca.js} +1 -1
  60. package/dist/client/assets/excalidrawEditor-dBMbRX_S.js +9 -0
  61. package/dist/client/assets/export-page-CRRQ2NEl.js +57 -0
  62. package/dist/client/assets/exportDocument-BlQRd9_m.js +2 -0
  63. package/dist/client/assets/exportDocument-CNSbjZwu.css +1 -0
  64. package/dist/client/assets/flowDiagram-HODETNUW-D5Gy0wmg.js +1 -0
  65. package/dist/client/assets/{ganttDiagram-EL5Y4UJY-DtKHAbez.js → ganttDiagram-EL5Y4UJY-BHgOYiZz.js} +1 -1
  66. package/dist/client/assets/{gitGraphDiagram-WWUBYQGX-oue8fQ5o.js → gitGraphDiagram-WWUBYQGX-Dl__X3Sk.js} +1 -1
  67. package/dist/client/assets/image-ZVKINSPA-DgzW5zAy.js +1 -0
  68. package/dist/client/assets/index-D3RER1SZ.js +1023 -0
  69. package/dist/client/assets/index-DREVKl_K.css +1 -0
  70. package/dist/client/assets/{infoDiagram-RXCK75RN-BMzo6KIn.js → infoDiagram-27XIBGKW-DBpoANC9.js} +1 -1
  71. package/dist/client/assets/{ishikawaDiagram-5VMMS53U-BIM3h287.js → ishikawaDiagram-5VMMS53U-BMPC6hfp.js} +1 -1
  72. package/dist/client/assets/{journeyDiagram-EYS64GPL-mF6Lh8O-.js → journeyDiagram-3NMN7TZE-B1sApUmM.js} +2 -2
  73. package/dist/client/assets/{kanban-definition-3QL26DDD-DLN2NyoR.js → kanban-definition-UXKFOSKX-C9yP4td1.js} +4 -4
  74. package/dist/client/assets/{line-BfTWJmgf.js → line-BFl3ujec.js} +1 -1
  75. package/dist/client/assets/mermaid.core-BrY09czX.js +11 -0
  76. package/dist/client/assets/mermaid.core-DfcNfOTv.js +1 -0
  77. package/dist/client/assets/{mindmap-definition-FBJOCRG2-CRyHaHTk.js → mindmap-definition-YA3MSWOX-BUQHcYbK.js} +8 -8
  78. package/dist/client/assets/pegDiagram-XKGWAZYB-Bi2Iml7Z.js +1 -0
  79. package/dist/client/assets/{pieDiagram-E7YTZNPT-B5OZPb88.js → pieDiagram-E7YTZNPT-_gYXnMoi.js} +1 -1
  80. package/dist/client/assets/preview-render-compiler-CfxJYldJ.js +41 -0
  81. package/dist/client/assets/preview-render-compiler-Cu8_i9iS.js +1 -0
  82. package/dist/client/assets/{preview-render-worker-_QTxqKvP.js → preview-render-worker-c4Yi93qW.js} +41 -35
  83. package/dist/client/assets/prod-BiJL30sl.js +1 -0
  84. package/dist/client/assets/prod-qAk7rHa4.js +177 -0
  85. package/dist/client/assets/{quadrantDiagram-AXDQQJYC-Drrw14YH.js → quadrantDiagram-AXDQQJYC-DwexhEhd.js} +1 -1
  86. package/dist/client/assets/quick-switcher-controller-BoIK_wuG.js +1 -0
  87. package/dist/client/assets/railroadDiagram-O6MQD6OU-BVA5UbMu.js +1 -0
  88. package/dist/client/assets/{requirementDiagram-EFPCY7ZU-CRZG6mRB.js → requirementDiagram-BXWQKSXE-C4cKwiIw.js} +2 -2
  89. package/dist/client/assets/runtime-config-DcmnGcXU.js +1 -0
  90. package/dist/client/assets/{sankeyDiagram-P5KCCOFB-DvarEJpo.js → sankeyDiagram-P5KCCOFB-B5HHkRT2.js} +1 -1
  91. package/dist/client/assets/{sequenceDiagram-WJ2MYXX4-B4oWlb-w.js → sequenceDiagram-WJ2MYXX4-BX6jqJFD.js} +6 -6
  92. package/dist/client/assets/{stateDiagram-HBIQ2CUA-qbHIIqRZ.js → stateDiagram-D77RDMKH-CmqxyRGx.js} +1 -1
  93. package/dist/client/assets/stateDiagram-v2-MP3YSRHH-zoLLQVyh.js +1 -0
  94. package/dist/client/assets/{swimlanes-XN3QIQJK-xB6CtUGj.js → swimlanes-42K2YHIH-Bmgb9Zld.js} +1 -1
  95. package/dist/client/assets/swimlanesDiagram-VR7AAH4N-DS-KQN37.js +8 -0
  96. package/dist/client/assets/{timeline-definition-24CTP7MA-Lj_yTNlY.js → timeline-definition-24CTP7MA-BA-lVH6P.js} +1 -1
  97. package/dist/client/assets/vault-api-client-iZLhMJKe.js +6 -0
  98. package/dist/client/assets/{vennDiagram-4TSXK5OY-BekRp5Y0.js → vennDiagram-4TSXK5OY-DNdUC_Xj.js} +1 -1
  99. package/dist/client/assets/{wardleyDiagram-VM6X3IG4-kdnV_Hdm.js → wardleyDiagram-VM6X3IG4-C2oY-YbF.js} +1 -1
  100. package/dist/client/assets/wiki-link-resolver-DFIwErjc.js +1 -0
  101. package/dist/client/assets/{xychartDiagram-S5SC5T6Z-DufGwNSB.js → xychartDiagram-S5SC5T6Z-CoIR8CBX.js} +1 -1
  102. package/dist/client/assets/{yjs-provider-reset-guard-DE0vINDP.js → yjs-provider-reset-guard-9ul1qF6k.js} +1 -1
  103. package/dist/client/drawio-editor.html +11 -3
  104. package/dist/client/excalidraw-editor.html +45 -2
  105. package/dist/client/export-document.html +3 -3
  106. package/dist/client/index.html +67 -35
  107. package/integrations/pi/index.ts +94 -0
  108. package/package.json +41 -32
  109. package/src/client/app/drawio-editor.html +2 -2
  110. package/src/client/app/excalidraw-editor.html +2 -2
  111. package/src/client/app/export-document.html +1 -1
  112. package/src/client/app/index.html +49 -33
  113. package/src/client/app/main-entry.js +15 -1
  114. package/src/client/application/app-shell/chat-feature.js +7 -22
  115. package/src/client/application/app-shell/git-feature.js +23 -5
  116. package/src/client/application/app-shell/presence-feature.js +8 -25
  117. package/src/client/application/app-shell/ui-feature-shell.js +36 -91
  118. package/src/client/application/app-shell/ui-feature-sidebar.js +43 -7
  119. package/src/client/application/app-shell/ui-feature-tab-activity.js +19 -6
  120. package/src/client/application/app-shell/ui-feature-toolbar.js +16 -129
  121. package/src/client/application/app-shell-elements.js +4 -0
  122. package/src/client/application/preview-render-compiler.js +1 -87
  123. package/src/client/application/preview-render-scheduler.js +8 -24
  124. package/src/client/application/preview-renderer.js +2 -2
  125. package/src/client/application/webmcp-diagram-renderer.js +47 -0
  126. package/src/client/application/workspace-coordinator.js +3 -1
  127. package/src/client/application/workspace-route-controller.js +7 -0
  128. package/src/client/bootstrap/collabmd-app-shell.js +78 -1
  129. package/src/client/domain/excalidraw-element-link.js +73 -0
  130. package/src/client/domain/excalidraw-scene.js +21 -0
  131. package/src/client/domain/hash-routes.js +8 -0
  132. package/src/client/domain/room.js +1 -5
  133. package/src/client/domain/runtime-paths.js +5 -0
  134. package/src/client/excalidraw-editor.js +135 -10
  135. package/src/client/export/export-host.js +3 -1
  136. package/src/client/export/export-pipeline.js +4 -17
  137. package/src/client/infrastructure/agent-connection-api-client.js +23 -0
  138. package/src/client/infrastructure/auth-client.js +3 -13
  139. package/src/client/infrastructure/editor-session.js +11 -0
  140. package/src/client/infrastructure/editor-view-adapter.js +13 -0
  141. package/src/client/infrastructure/excalidraw-room-client.js +80 -1
  142. package/src/client/infrastructure/git-api-client.js +39 -33
  143. package/src/client/infrastructure/lobby-presence.js +2 -5
  144. package/src/client/infrastructure/runtime-config.js +11 -1
  145. package/src/client/infrastructure/tab-activity-lock.js +2 -5
  146. package/src/client/infrastructure/vault-api-client.js +33 -26
  147. package/src/client/infrastructure/webmcp-tool-registry.js +283 -0
  148. package/src/client/infrastructure/workspace-sync-client.js +6 -4
  149. package/src/client/presentation/agent-connection-controller.js +255 -0
  150. package/src/client/presentation/backlinks-panel.js +3 -0
  151. package/src/client/presentation/bases-preview-controller.js +27 -18
  152. package/src/client/presentation/comment-ui/comment-ui-card.js +4 -2
  153. package/src/client/presentation/create-menu-presenter.js +19 -16
  154. package/src/client/presentation/excalidraw-embed-controller.js +164 -4
  155. package/src/client/presentation/file-explorer-view.js +70 -12
  156. package/src/client/presentation/file-history-view-controller.js +13 -11
  157. package/src/client/presentation/git-diff-view-controller.js +35 -43
  158. package/src/client/presentation/git-panel-controller.js +77 -27
  159. package/src/client/presentation/image-lightbox-controller.js +20 -16
  160. package/src/client/presentation/layout-controller.js +3 -1
  161. package/src/client/presentation/quick-switcher-controller.js +20 -78
  162. package/src/client/presentation/quick-switcher-text-search.js +10 -14
  163. package/src/client/styles/components/navigation.css +1 -2
  164. package/src/client/styles/features/agent-connection.css +202 -0
  165. package/src/client/styles/features/collaboration-chat.css +8 -1
  166. package/src/client/styles/features/collaboration-dialogs.css +16 -1
  167. package/src/client/styles/features/collaboration-presence.css +17 -4
  168. package/src/client/styles/features/diff-toolbar.css +1 -1
  169. package/src/client/styles/features/media-lightbox.css +12 -3
  170. package/src/client/styles/features/quick-switcher.css +20 -11
  171. package/src/client/styles/foundation/themes.css +2 -0
  172. package/src/client/styles/layout/editor-page.css +25 -11
  173. package/src/client/styles/layout/responsive.css +2 -20
  174. package/src/client/styles/layout/shell.css +1 -0
  175. package/src/client/styles/layout/sidebar.css +7 -4
  176. package/src/client/styles/primitives/controls.css +21 -54
  177. package/src/client/styles/style.css +1 -0
  178. package/src/domain/agent-tool-definitions.js +796 -0
  179. package/src/domain/collabmd-content-capabilities.js +149 -0
  180. package/src/domain/collaboration-protocol.js +2 -0
  181. package/src/domain/comment-threads.js +3 -5
  182. package/src/domain/diagram-source.js +50 -0
  183. package/src/domain/editable-content-revision.js +10 -0
  184. package/src/domain/editable-text.js +3 -0
  185. package/src/domain/exact-text-edits.js +69 -0
  186. package/src/domain/excalidraw-agent-scene.js +522 -0
  187. package/src/domain/excalidraw-agent-verification.js +460 -0
  188. package/src/domain/excalidraw-room-codec.js +1 -5
  189. package/src/domain/video-embed.js +76 -0
  190. package/src/server/application/agent-connection-service.js +178 -0
  191. package/src/server/application/agent-content-service.js +773 -0
  192. package/src/server/application/workspace-reconciliation.js +5 -7
  193. package/src/server/config/env.js +51 -1
  194. package/src/server/create-app-server.js +36 -0
  195. package/src/server/domain/bases/base-query-service.js +1 -5
  196. package/src/server/domain/collaboration/collaboration-room.js +121 -25
  197. package/src/server/domain/collaboration/protocol.js +4 -0
  198. package/src/server/domain/hosted-workspace.js +21 -0
  199. package/src/server/domain/markdown-reference-extractor.js +15 -1
  200. package/src/server/infrastructure/git/git-service.js +19 -1
  201. package/src/server/infrastructure/git/history-service.js +1 -1
  202. package/src/server/infrastructure/git/local-exclude.js +25 -0
  203. package/src/server/infrastructure/git/parsers.js +19 -17
  204. package/src/server/infrastructure/git/path-utils.js +0 -16
  205. package/src/server/infrastructure/git/status-service.js +1 -1
  206. package/src/server/infrastructure/git/untracked-files.js +12 -21
  207. package/src/server/infrastructure/http/create-agent-api-handler.js +171 -0
  208. package/src/server/infrastructure/http/create-git-api-command-handler.js +32 -0
  209. package/src/server/infrastructure/http/create-request-handler.js +28 -0
  210. package/src/server/infrastructure/http/create-static-handler.js +6 -0
  211. package/src/server/infrastructure/mcp/create-agent-mcp-handler.js +199 -0
  212. package/src/server/infrastructure/persistence/agent-connection-store.js +120 -0
  213. package/src/server/infrastructure/persistence/pull-backup-store.js +2 -27
  214. package/src/server/infrastructure/persistence/vault-file-store.js +9 -2
  215. package/src/server/shared/agent-tool-image.js +22 -0
  216. package/src/server/shared/agent-tool-rate-limiter.js +20 -0
  217. package/src/server/startup/git-remote-bootstrap.js +5 -33
  218. package/dist/client/assets/CodeMirrorEditor-UCMRAMCV-BPH4FJkv.js +0 -1
  219. package/dist/client/assets/abnfDiagram-VCTEODGH-BNDLzJ7L.js +0 -1
  220. package/dist/client/assets/browser-utils--H_DkyEF.js +0 -1
  221. package/dist/client/assets/channel-CaoKA_We.js +0 -1
  222. package/dist/client/assets/chunk-DSNCTWBM-8xkug7fQ.js +0 -2
  223. package/dist/client/assets/chunk-DU6HZSFF-COXDzJK1.js +0 -127
  224. package/dist/client/assets/chunk-R7TYR2AO-CTd0Yj98.js +0 -10
  225. package/dist/client/assets/classDiagram-DTDB5LWJ-7fEmn0zV.js +0 -1
  226. package/dist/client/assets/classDiagram-v2-JRS7N3AN-7fEmn0zV.js +0 -1
  227. package/dist/client/assets/comment-threads-9J6Em-_y.js +0 -4
  228. package/dist/client/assets/comment-ui-shared-DuoyznXW.js +0 -7
  229. package/dist/client/assets/diagram-VSXAHHWV-CQRVEZR7.js +0 -3
  230. package/dist/client/assets/dist-73RPdcyU.js +0 -1
  231. package/dist/client/assets/dist-CH-sQu3r.js +0 -1
  232. package/dist/client/assets/document-formatter-Dxl_a8XO.js +0 -2
  233. package/dist/client/assets/drawio-editor-BVaNNtYp.js +0 -9
  234. package/dist/client/assets/drawioEditor-BkIBlXx0.js +0 -2
  235. package/dist/client/assets/ebnfDiagram-PWID7BFC-OXa8VEp1.js +0 -1
  236. package/dist/client/assets/editor-session-DMyVdHLj.js +0 -54
  237. package/dist/client/assets/excalidraw-editor-BjYaNEMY.js +0 -9
  238. package/dist/client/assets/excalidraw-mermaid-stub-BNckbX2n.js +0 -1
  239. package/dist/client/assets/excalidraw-mermaid-stub-DE5FTiZB.js +0 -1
  240. package/dist/client/assets/excalidrawEditor-ChqF_d82.js +0 -2
  241. package/dist/client/assets/export-page-Dyu0XCbu.js +0 -57
  242. package/dist/client/assets/exportDocument-CJBx4Xjx.css +0 -1
  243. package/dist/client/assets/exportDocument-Dgvfa2Sd.js +0 -2
  244. package/dist/client/assets/flowDiagram-A5DVABFB-CiW1eN8C.js +0 -1
  245. package/dist/client/assets/image-ZVKINSPA-0zXSnE7M.js +0 -1
  246. package/dist/client/assets/index-CSNIMyOP.css +0 -1
  247. package/dist/client/assets/index-DG3N2Ps-.js +0 -2
  248. package/dist/client/assets/main-D0wCxpVn.js +0 -903
  249. package/dist/client/assets/mermaid.core-BJRLUQZE.js +0 -11
  250. package/dist/client/assets/pegDiagram-XKGWAZYB-B31fKPR2.js +0 -1
  251. package/dist/client/assets/preview-render-compiler-CrJIooVD.js +0 -35
  252. package/dist/client/assets/preview-render-compiler-O0JxBVNF.js +0 -1
  253. package/dist/client/assets/preview-render-profile-B4RWYWHt.js +0 -1
  254. package/dist/client/assets/prod-BiYPMR1i.js +0 -177
  255. package/dist/client/assets/prod-VxdGZ5Ly.js +0 -1
  256. package/dist/client/assets/quick-switcher-controller-CaEjEtq_.js +0 -1
  257. package/dist/client/assets/railroadDiagram-O6MQD6OU-BPifP0MB.js +0 -1
  258. package/dist/client/assets/runtime-config-BLrEKVO1.js +0 -1
  259. package/dist/client/assets/stateDiagram-v2-4QOOHH4V-B--gjrit.js +0 -1
  260. package/dist/client/assets/swimlanesDiagram-VK2B7HYN-DO8XNXEg.js +0 -8
  261. package/dist/client/assets/vault-api-client-BkwdFjx0.js +0 -6
  262. package/src/client/app/drawio-editor-entry.js +0 -1
  263. package/src/client/app/excalidraw-editor-entry.js +0 -1
  264. package/src/client/excalidraw-mermaid-stub.js +0 -7
  265. package/src/client/main.js +0 -16
  266. /package/dist/client/assets/{drawio-editor-UQQ8XHou.css → drawioEditor-UQQ8XHou.css} +0 -0
  267. /package/dist/client/assets/{excalidraw-editor-BbSbI80a.css → excalidrawEditor-BbSbI80a.css} +0 -0
  268. /package/dist/client/assets/{sizeCapture-X5ZJPWSS-B0uUizjq.js → sizeCapture-INFHLROL-B0uUizjq.js} +0 -0
@@ -0,0 +1,773 @@
1
+ import {
2
+ getCollabMdContentCapability,
3
+ getCollabMdSyntaxGuide,
4
+ isAgentCreatablePath,
5
+ isAgentEditablePath,
6
+ isAgentReadablePath,
7
+ listCollabMdContentCapabilities,
8
+ } from '../../domain/collabmd-content-capabilities.js';
9
+ import { listRenderableDiagrams, resolveRenderableDiagram } from '../../domain/diagram-source.js';
10
+ import { createEditableContentRevision } from '../../domain/editable-content-revision.js';
11
+ import { normalizeEditableText } from '../../domain/editable-text.js';
12
+ import { applyExactTextChanges, resolveExactTextChanges } from '../../domain/exact-text-edits.js';
13
+ import { applyAgentExcalidrawEdits, createAgentExcalidrawScene } from '../../domain/excalidraw-agent-scene.js';
14
+ import { inspectAgentExcalidrawScene, renderAgentExcalidrawSvg } from '../../domain/excalidraw-agent-verification.js';
15
+ import { getVaultFileKind } from '../../domain/file-kind.js';
16
+ import { createWikiTargetIndex } from '../../domain/wiki-link-resolver.js';
17
+ import {
18
+ classifyPublicVideoEmbed,
19
+ isPublicVideoEmbedCandidate,
20
+ } from '../../domain/video-embed.js';
21
+ import {
22
+ collectMarkdownImageSources,
23
+ collectMarkdownReferences,
24
+ } from '../domain/markdown-reference-extractor.js';
25
+ import { compareWorkspacePaths, normalizeWorkspacePath } from '../domain/workspace-state.js';
26
+
27
+ const MAX_DOCUMENT_CHARACTERS = 200_000;
28
+ const MAX_AGENT_READ_SOURCE_CHARACTERS = 1_000_000;
29
+ const MAX_READ_CHARACTERS = 100_000;
30
+ const MAX_READ_LINES = 500;
31
+ const MAX_REPLACEMENTS = 20;
32
+ const MAX_REPLACEMENT_CHARACTERS = 50_000;
33
+ const EMBEDDABLE_KINDS = new Set(['base', 'drawio', 'excalidraw', 'image', 'mermaid', 'plantuml']);
34
+
35
+ function createAgentContentError(code, message, statusCode = 400) {
36
+ const error = new Error(message);
37
+ error.code = code;
38
+ error.statusCode = statusCode;
39
+ return error;
40
+ }
41
+
42
+ function requireScope(actor, scope) {
43
+ if (!actor?.scopes?.includes(scope)) {
44
+ throw createAgentContentError('AGENT_SCOPE_REQUIRED', `Agent Connection requires ${scope}`, 403);
45
+ }
46
+ }
47
+
48
+ function createAgentSourceRef(actor) {
49
+ if (actor.sourceRef) return actor.sourceRef;
50
+ return actor.connectionId === 'anonymous'
51
+ ? 'agent:anonymous'
52
+ : `agent-connection:${actor.connectionId}`;
53
+ }
54
+
55
+ function createCollaborationOrigin(actor) {
56
+ return {
57
+ actor: actor.collaborator ?? null,
58
+ connectionId: actor.connectionId,
59
+ requestId: actor.requestId,
60
+ type: actor.origin ?? 'agent',
61
+ };
62
+ }
63
+
64
+ function getMutationOrigin(actor) {
65
+ return actor.origin ?? 'agent';
66
+ }
67
+
68
+ function clampInteger(value, fallback, min, max) {
69
+ const parsed = Number.parseInt(value, 10);
70
+ return Number.isInteger(parsed) ? Math.min(Math.max(parsed, min), max) : fallback;
71
+ }
72
+
73
+ function requireInlineExcalidrawVerificationOptions(value) {
74
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
75
+ throw createAgentContentError('AGENT_INPUT_INVALID', 'Excalidraw verification options must be an object', 400);
76
+ }
77
+ }
78
+
79
+ function createInlineExcalidrawVerification(scene, options, { deferRender = false } = {}) {
80
+ const verification = {
81
+ inspection: inspectAgentExcalidrawScene(scene, {
82
+ includeElements: false,
83
+ inspectOcclusion: options.inspectOcclusion ?? true,
84
+ }),
85
+ };
86
+ if (options.render) {
87
+ if (deferRender) {
88
+ verification.elementCount = verification.inspection.elementCount;
89
+ verification.scene = scene;
90
+ } else {
91
+ Object.assign(verification, {
92
+ ...renderAgentExcalidrawSvg(scene, {
93
+ padding: options.padding ?? 32,
94
+ scale: options.scale ?? 1,
95
+ }),
96
+ format: options.format ?? 'png',
97
+ scene,
98
+ });
99
+ }
100
+ }
101
+ return verification;
102
+ }
103
+
104
+ function searchLiveText(content, query, maxSnippets = 5) {
105
+ const normalizedQuery = query.toLocaleLowerCase();
106
+ const snippets = [];
107
+ let matchCount = 0;
108
+ String(content).split('\n').forEach((lineText, lineIndex) => {
109
+ const normalizedLine = lineText.toLocaleLowerCase();
110
+ let start = normalizedLine.indexOf(normalizedQuery);
111
+ while (start >= 0) {
112
+ matchCount += 1;
113
+ if (snippets.length < maxSnippets) {
114
+ const snippetStart = Math.max(0, start - 90);
115
+ const snippetEnd = Math.min(lineText.length, start + query.length + 90);
116
+ snippets.push({
117
+ column: start + 1,
118
+ line: lineIndex + 1,
119
+ matchEnd: start - snippetStart + query.length + (snippetStart > 0 ? 3 : 0),
120
+ matchStart: start - snippetStart + (snippetStart > 0 ? 3 : 0),
121
+ text: `${snippetStart > 0 ? '...' : ''}${lineText.slice(snippetStart, snippetEnd)}${snippetEnd < lineText.length ? '...' : ''}`,
122
+ });
123
+ }
124
+ start = normalizedLine.indexOf(normalizedQuery, start + Math.max(query.length, 1));
125
+ }
126
+ });
127
+ return { matchCount, snippets, truncated: matchCount > snippets.length };
128
+ }
129
+
130
+ export class AgentContentService {
131
+ constructor({
132
+ backlinkIndex = null,
133
+ plantUmlRenderer = null,
134
+ roomRegistry,
135
+ searchService,
136
+ vaultFileStore,
137
+ workspaceMutationCoordinator,
138
+ }) {
139
+ this.backlinkIndex = backlinkIndex;
140
+ this.plantUmlRenderer = plantUmlRenderer;
141
+ this.roomRegistry = roomRegistry;
142
+ this.searchService = searchService;
143
+ this.vaultFileStore = vaultFileStore;
144
+ this.workspaceMutationCoordinator = workspaceMutationCoordinator;
145
+ this.pathQueues = new Map();
146
+ }
147
+
148
+ runForPath(path, operation) {
149
+ const previous = this.pathQueues.get(path) ?? Promise.resolve();
150
+ const current = previous.catch(() => {}).then(operation);
151
+ this.pathQueues.set(path, current);
152
+ return current.finally(() => {
153
+ if (this.pathQueues.get(path) === current) this.pathQueues.delete(path);
154
+ });
155
+ }
156
+
157
+ async readCurrentContent(path) {
158
+ const room = this.roomRegistry?.get?.(path);
159
+ if (room) {
160
+ if (!room.isHydrated?.()) {
161
+ throw createAgentContentError('AGENT_DOCUMENT_SYNCING', 'Document is still synchronizing', 409);
162
+ }
163
+ const liveContent = room.readEditableContent?.() ?? room.getPersistedContent?.();
164
+ if (liveContent !== null && liveContent !== undefined) return String(liveContent);
165
+ }
166
+ const content = await this.vaultFileStore.readEditableVaultContent(path);
167
+ if (content === null) {
168
+ throw createAgentContentError('AGENT_DOCUMENT_NOT_FOUND', 'Document not found', 404);
169
+ }
170
+ return content;
171
+ }
172
+
173
+ async readMatchingContent(path, matches) {
174
+ try {
175
+ const content = await this.readCurrentContent(path);
176
+ return matches(content) ? content : null;
177
+ } catch {
178
+ return null;
179
+ }
180
+ }
181
+
182
+ async readExcalidrawScene(actor, path) {
183
+ requireScope(actor, 'vault:read');
184
+ const normalizedPath = normalizeWorkspacePath(path);
185
+ if (getVaultFileKind(normalizedPath) !== 'excalidraw') {
186
+ throw createAgentContentError('AGENT_UNSUPPORTED_DOCUMENT', 'Excalidraw tools require an .excalidraw path', 400);
187
+ }
188
+ const content = await this.readCurrentContent(normalizedPath);
189
+ if (content.length > MAX_DOCUMENT_CHARACTERS) {
190
+ throw createAgentContentError('AGENT_DOCUMENT_TOO_LARGE', 'Document is too large for agent inspection', 413);
191
+ }
192
+ let scene;
193
+ try {
194
+ scene = JSON.parse(content);
195
+ } catch {
196
+ throw createAgentContentError('AGENT_INVALID_EXCALIDRAW', 'Document is not valid Excalidraw JSON', 400);
197
+ }
198
+ if (scene?.type !== 'excalidraw' || !Array.isArray(scene.elements)) {
199
+ throw createAgentContentError('AGENT_INVALID_EXCALIDRAW', 'Document is not a valid Excalidraw scene', 400);
200
+ }
201
+ return {
202
+ path: normalizedPath,
203
+ revision: await createEditableContentRevision(content),
204
+ scene,
205
+ };
206
+ }
207
+
208
+ async inspectExcalidraw(actor, { path } = {}) {
209
+ const current = await this.readExcalidrawScene(actor, path);
210
+ return {
211
+ ...inspectAgentExcalidrawScene(current.scene),
212
+ path: current.path,
213
+ revision: current.revision,
214
+ };
215
+ }
216
+
217
+ async renderExcalidraw(actor, {
218
+ format = 'png',
219
+ padding = 32,
220
+ path,
221
+ scale = 1,
222
+ } = {}) {
223
+ const current = await this.readExcalidrawScene(actor, path);
224
+ const result = {
225
+ elementCount: current.scene.elements.filter((element) => !element.isDeleted).length,
226
+ format,
227
+ path: current.path,
228
+ revision: current.revision,
229
+ scene: current.scene,
230
+ };
231
+ if (actor.origin !== 'webmcp') {
232
+ Object.assign(result, renderAgentExcalidrawSvg(current.scene, { padding, scale }));
233
+ }
234
+ return result;
235
+ }
236
+
237
+ async verifyExcalidraw(actor, {
238
+ format = 'png',
239
+ inspectOcclusion = true,
240
+ padding = 32,
241
+ path,
242
+ scale = 1,
243
+ } = {}) {
244
+ const current = await this.readExcalidrawScene(actor, path);
245
+ const inspection = inspectAgentExcalidrawScene(current.scene, {
246
+ includeElements: false,
247
+ inspectOcclusion,
248
+ });
249
+ const result = {
250
+ elementCount: inspection.elementCount,
251
+ format,
252
+ inspection,
253
+ path: current.path,
254
+ revision: current.revision,
255
+ scene: current.scene,
256
+ };
257
+ if (actor.origin !== 'webmcp') {
258
+ Object.assign(result, renderAgentExcalidrawSvg(current.scene, { padding, scale }));
259
+ }
260
+ return result;
261
+ }
262
+
263
+ listDocuments(actor, { cursor = '', kinds = [], limit = 100, prefix = '' } = {}) {
264
+ requireScope(actor, 'vault:read');
265
+ const normalizedPrefix = normalizeWorkspacePath(prefix);
266
+ const kindFilter = new Set(Array.isArray(kinds) ? kinds : []);
267
+ const state = this.workspaceMutationCoordinator?.workspaceState;
268
+ const documents = Array.from(state?.entries?.values?.() ?? [])
269
+ .filter((entry) => entry.nodeType !== 'directory' && isAgentReadablePath(entry.path))
270
+ .filter((entry) => !normalizedPrefix || entry.path === normalizedPrefix || entry.path.startsWith(`${normalizedPrefix}/`))
271
+ .filter((entry) => kindFilter.size === 0 || kindFilter.has(getVaultFileKind(entry.path)))
272
+ .sort((left, right) => compareWorkspacePaths(left.path, right.path));
273
+ const start = cursor ? Math.max(0, documents.findIndex((entry) => entry.path === cursor) + 1) : 0;
274
+ const pageSize = clampInteger(limit, 100, 1, 200);
275
+ const page = documents.slice(start, start + pageSize).map((entry) => {
276
+ const metadata = state?.metadata?.get?.(entry.path);
277
+ return {
278
+ kind: getVaultFileKind(entry.path),
279
+ mtimeMs: Number(metadata?.mtimeMs ?? entry.mtimeMs ?? 0),
280
+ path: entry.path,
281
+ size: Number(metadata?.size ?? 0),
282
+ };
283
+ });
284
+ const truncated = start + page.length < documents.length;
285
+ return {
286
+ documents: page,
287
+ nextCursor: truncated ? page.at(-1)?.path ?? null : null,
288
+ truncated,
289
+ };
290
+ }
291
+ listWorkspaceEntries(actor, { cursor = '', limit = 100, prefix = '' } = {}) {
292
+ requireScope(actor, 'vault:read');
293
+ const normalizedPrefix = normalizeWorkspacePath(prefix);
294
+ const state = this.workspaceMutationCoordinator?.workspaceState;
295
+ const entries = Array.from(state?.entries?.values?.() ?? [])
296
+ .filter((entry) => !normalizedPrefix || entry.path === normalizedPrefix || entry.path.startsWith(`${normalizedPrefix}/`))
297
+ .sort((left, right) => compareWorkspacePaths(left.path, right.path));
298
+ const start = cursor ? Math.max(0, entries.findIndex((entry) => entry.path === cursor) + 1) : 0;
299
+ const pageSize = clampInteger(limit, 100, 1, 200);
300
+ const page = entries.slice(start, start + pageSize).map((entry) => {
301
+ const isDirectory = entry.nodeType === 'directory';
302
+ const kind = isDirectory ? null : getVaultFileKind(entry.path);
303
+ const capability = kind ? getCollabMdContentCapability(kind) : null;
304
+ const metadata = state?.metadata?.get?.(entry.path);
305
+ return {
306
+ agentEditable: Boolean(capability?.agentEditable),
307
+ embeddable: EMBEDDABLE_KINDS.has(kind),
308
+ kind,
309
+ mtimeMs: Number(metadata?.mtimeMs ?? entry.mtimeMs ?? 0),
310
+ nodeType: isDirectory ? 'directory' : 'file',
311
+ path: entry.path,
312
+ readable: Boolean(capability?.readable),
313
+ size: Number(metadata?.size ?? 0),
314
+ };
315
+ });
316
+ const truncated = start + page.length < entries.length;
317
+ return {
318
+ entries: page,
319
+ nextCursor: truncated ? page.at(-1)?.path ?? null : null,
320
+ truncated,
321
+ };
322
+ }
323
+
324
+
325
+ async searchVault(actor, { limit = 50, query = '', signal = null } = {}) {
326
+ requireScope(actor, 'vault:read');
327
+ const normalizedQuery = String(query ?? '').trim().slice(0, 500);
328
+ const result = await this.searchService.search({ limit, query: normalizedQuery, signal });
329
+ if (normalizedQuery.length < 2) return result;
330
+
331
+ const liveFiles = [];
332
+ for (const [path, room] of this.roomRegistry?.getRooms?.() ?? []) {
333
+ if (!isAgentReadablePath(path) || !room.isHydrated?.()) continue;
334
+ const content = room.readEditableContent?.();
335
+ if (content === null || content === undefined) continue;
336
+ if (content.length > MAX_AGENT_READ_SOURCE_CHARACTERS) continue;
337
+ const live = searchLiveText(content, normalizedQuery);
338
+ if (live.matchCount > 0) {
339
+ liveFiles.push({ file: path, kind: getVaultFileKind(path), ...live });
340
+ }
341
+ }
342
+ if (liveFiles.length === 0) return result;
343
+
344
+ const livePaths = new Set(liveFiles.map(({ file }) => file));
345
+ const files = [...result.files.filter(({ file }) => !livePaths.has(file)), ...liveFiles]
346
+ .sort((left, right) => compareWorkspacePaths(left.file, right.file))
347
+ .slice(0, clampInteger(limit, 50, 1, 50));
348
+ return {
349
+ ...result,
350
+ files,
351
+ matchCount: files.reduce((sum, file) => sum + file.matchCount, 0),
352
+ truncated: result.truncated || files.length < result.files.length + liveFiles.length,
353
+ };
354
+ }
355
+
356
+ async readDocument(actor, { lineCount = MAX_READ_LINES, path, startLine = 1 } = {}) {
357
+ requireScope(actor, 'vault:read');
358
+ const normalizedPath = normalizeWorkspacePath(path);
359
+ if (!isAgentReadablePath(normalizedPath)) {
360
+ throw createAgentContentError('AGENT_UNSUPPORTED_DOCUMENT', 'Document type is not readable by agents', 400);
361
+ }
362
+ const content = await this.readCurrentContent(normalizedPath);
363
+ if (content.length > MAX_AGENT_READ_SOURCE_CHARACTERS) {
364
+ throw createAgentContentError('AGENT_DOCUMENT_TOO_LARGE', 'Document is too large for agent reading', 413);
365
+ }
366
+ const lines = content.split('\n');
367
+ const start = clampInteger(startLine, 1, 1, Math.max(lines.length, 1));
368
+ const count = clampInteger(lineCount, MAX_READ_LINES, 1, MAX_READ_LINES);
369
+ let selected = lines.slice(start - 1, start - 1 + count).join('\n');
370
+ let characterTruncated = false;
371
+ let endLine = Math.min(start + count - 1, lines.length);
372
+ if (selected.length > MAX_READ_CHARACTERS) {
373
+ selected = selected.slice(0, MAX_READ_CHARACTERS);
374
+ characterTruncated = true;
375
+ let returnedLineCount = 1;
376
+ for (let index = 0; index < selected.length; index += 1) {
377
+ if (selected.charCodeAt(index) === 10) returnedLineCount += 1;
378
+ }
379
+ if (selected.endsWith('\n')) returnedLineCount -= 1;
380
+ endLine = Math.min(start + Math.max(returnedLineCount, 1) - 1, lines.length);
381
+ }
382
+ return {
383
+ content: selected,
384
+ endLine,
385
+ kind: getVaultFileKind(normalizedPath),
386
+ path: normalizedPath,
387
+ revision: await createEditableContentRevision(content),
388
+ startLine: start,
389
+ totalLines: lines.length,
390
+ truncated: characterTruncated || endLine < lines.length,
391
+ };
392
+ }
393
+ async inspectDocumentReferences(actor, { path } = {}) {
394
+ requireScope(actor, 'vault:read');
395
+ const normalizedPath = normalizeWorkspacePath(path);
396
+ if (getVaultFileKind(normalizedPath) !== 'markdown') {
397
+ throw createAgentContentError('AGENT_UNSUPPORTED_DOCUMENT', 'Reference inspection requires a Markdown document', 400);
398
+ }
399
+ const content = await this.readCurrentContent(normalizedPath);
400
+ if (content.length > MAX_AGENT_READ_SOURCE_CHARACTERS) {
401
+ throw createAgentContentError('AGENT_DOCUMENT_TOO_LARGE', 'Document is too large for agent inspection', 413);
402
+ }
403
+ const entries = Array.from(this.workspaceMutationCoordinator?.workspaceState?.entries?.values?.() ?? []);
404
+ const filePaths = entries
405
+ .filter((entry) => entry.nodeType !== 'directory')
406
+ .map((entry) => entry.path);
407
+ const references = collectMarkdownReferences(content, {
408
+ sourceFilePath: normalizedPath,
409
+ wikiTargetIndex: createWikiTargetIndex(filePaths),
410
+ });
411
+ const toPublicReference = (reference) => ({
412
+ exists: Boolean(reference.resolvedPath),
413
+ kind: getVaultFileKind(reference.resolvedPath || reference.targetPath),
414
+ line: reference.line,
415
+ rawTarget: reference.rawTarget,
416
+ resolvedPath: reference.resolvedPath || null,
417
+ targetPath: reference.targetPath,
418
+ });
419
+ const videos = collectMarkdownImageSources(content)
420
+ .filter(({ source }) => isPublicVideoEmbedCandidate(source))
421
+ .map(({ line, source }) => {
422
+ const video = classifyPublicVideoEmbed(source);
423
+ return {
424
+ kind: video?.type ?? null,
425
+ line,
426
+ mimeType: video?.mimeType ?? null,
427
+ source,
428
+ supported: Boolean(video),
429
+ url: video?.embedUrl ?? video?.sourceUrl ?? null,
430
+ };
431
+ });
432
+ return {
433
+ backlinks: await this.backlinkIndex?.getBacklinks?.(normalizedPath) ?? [],
434
+ embeds: references.filter(({ isEmbed }) => isEmbed).map(toPublicReference),
435
+ path: normalizedPath,
436
+ revision: await createEditableContentRevision(content),
437
+ videos,
438
+ wikiLinks: references.filter(({ isEmbed }) => !isEmbed).map(toPublicReference),
439
+ };
440
+ }
441
+
442
+ async validateDocument(actor, { path } = {}) {
443
+ const inspected = await this.inspectDocumentReferences(actor, { path });
444
+ const issues = [];
445
+ inspected.wikiLinks.filter(({ exists }) => !exists).forEach((reference) => {
446
+ issues.push({
447
+ code: 'REFERENCE_TARGET_NOT_FOUND',
448
+ line: reference.line,
449
+ message: `Wiki-link target not found: ${reference.rawTarget}`,
450
+ target: reference.rawTarget,
451
+ });
452
+ });
453
+ inspected.embeds.filter(({ exists }) => !exists).forEach((reference) => {
454
+ issues.push({
455
+ code: 'EMBED_TARGET_NOT_FOUND',
456
+ line: reference.line,
457
+ message: `Embed target not found: ${reference.rawTarget}`,
458
+ target: reference.rawTarget,
459
+ });
460
+ });
461
+ inspected.videos.filter(({ supported }) => !supported).forEach((video) => {
462
+ issues.push({
463
+ code: 'VIDEO_EMBED_UNSUPPORTED',
464
+ line: video.line,
465
+ message: `Unsupported public video embed: ${video.source}`,
466
+ target: video.source,
467
+ });
468
+ });
469
+ return {
470
+ issues,
471
+ path: inspected.path,
472
+ revision: inspected.revision,
473
+ valid: issues.length === 0,
474
+ };
475
+ }
476
+
477
+ async renderDiagram(actor, { format = 'png', path, startLine } = {}) {
478
+ requireScope(actor, 'vault:read');
479
+ const normalizedPath = normalizeWorkspacePath(path);
480
+ const kind = getVaultFileKind(normalizedPath);
481
+ if (kind !== 'markdown' && kind !== 'mermaid' && kind !== 'plantuml') {
482
+ throw createAgentContentError('AGENT_UNSUPPORTED_DOCUMENT', 'Diagram rendering requires Markdown, Mermaid, or PlantUML content', 400);
483
+ }
484
+ const content = await this.readCurrentContent(normalizedPath);
485
+ if (content.length > MAX_AGENT_READ_SOURCE_CHARACTERS) {
486
+ throw createAgentContentError('AGENT_DOCUMENT_TOO_LARGE', 'Document is too large for diagram rendering', 413);
487
+ }
488
+ const diagram = resolveRenderableDiagram(content, normalizedPath, { startLine });
489
+ if (!diagram) {
490
+ const count = listRenderableDiagrams(content, normalizedPath).length;
491
+ const message = count > 1 && !startLine
492
+ ? 'Document contains multiple diagrams; provide the opening fence startLine'
493
+ : 'Renderable diagram not found at the requested path and line';
494
+ throw createAgentContentError('AGENT_DIAGRAM_NOT_FOUND', message, 400);
495
+ }
496
+ if (diagram.kind === 'mermaid') {
497
+ throw createAgentContentError(
498
+ 'AGENT_BROWSER_RENDER_REQUIRED',
499
+ 'Mermaid rendering requires WebMCP in an active CollabMD browser tab',
500
+ 400,
501
+ );
502
+ }
503
+ if (!this.plantUmlRenderer?.renderSvg) {
504
+ throw createAgentContentError('AGENT_RENDER_UNAVAILABLE', 'PlantUML rendering is unavailable', 503);
505
+ }
506
+ let svg;
507
+ try {
508
+ svg = await this.plantUmlRenderer.renderSvg(diagram.source);
509
+ } catch {
510
+ throw createAgentContentError('AGENT_DIAGRAM_RENDER_FAILED', 'PlantUML rendering failed', 502);
511
+ }
512
+ return {
513
+ endLine: diagram.endLine,
514
+ format,
515
+ kind: diagram.kind,
516
+ path: normalizedPath,
517
+ renderer: 'plantuml-server',
518
+ revision: await createEditableContentRevision(content),
519
+ startLine: diagram.startLine,
520
+ svg,
521
+ warnings: [],
522
+ };
523
+ }
524
+
525
+
526
+ async applyTextEdits(actor, { path, replacements, revision } = {}) {
527
+ requireScope(actor, 'vault:edit');
528
+ const normalizedPath = normalizeWorkspacePath(path);
529
+ if (!isAgentEditablePath(normalizedPath)) {
530
+ throw createAgentContentError('AGENT_UNSUPPORTED_DOCUMENT', 'Document type is not editable by agents', 400);
531
+ }
532
+ return this.runForPath(normalizedPath, async () => {
533
+ const content = await this.readCurrentContent(normalizedPath);
534
+ if (content.length > MAX_DOCUMENT_CHARACTERS) {
535
+ throw createAgentContentError('AGENT_DOCUMENT_TOO_LARGE', 'Document is too large for agent editing', 413);
536
+ }
537
+ const currentRevision = await createEditableContentRevision(content);
538
+ if (revision !== currentRevision) {
539
+ throw createAgentContentError('AGENT_REVISION_CONFLICT', 'Document changed; read it again before editing', 409);
540
+ }
541
+ const normalizedReplacements = replacements?.map((replacement) => ({
542
+ ...replacement,
543
+ newText: normalizeEditableText(replacement?.newText),
544
+ }));
545
+ let changes;
546
+ try {
547
+ changes = resolveExactTextChanges(content, normalizedReplacements, {
548
+ maxCharacters: MAX_REPLACEMENT_CHARACTERS,
549
+ maxReplacements: MAX_REPLACEMENTS,
550
+ });
551
+ } catch (error) {
552
+ error.code = error.code?.startsWith('EXACT_EDIT_') ? 'AGENT_REPLACEMENT_MISMATCH' : error.code;
553
+ throw error;
554
+ }
555
+ const room = this.roomRegistry?.get?.(normalizedPath);
556
+ let nextContent;
557
+ if (room) {
558
+ room.applyExactTextChanges(changes, {
559
+ origin: createCollaborationOrigin(actor),
560
+ });
561
+ nextContent = room.readEditableContent();
562
+ } else {
563
+ nextContent = applyExactTextChanges(content, changes);
564
+ const result = await this.workspaceMutationCoordinator.writeEditableContent({
565
+ content: nextContent,
566
+ origin: getMutationOrigin(actor),
567
+ path: normalizedPath,
568
+ requestId: actor.requestId,
569
+ sourceRef: createAgentSourceRef(actor),
570
+ });
571
+ if (!result.ok) throw createAgentContentError('AGENT_WRITE_FAILED', result.error, 400);
572
+ }
573
+ return {
574
+ path: normalizedPath,
575
+ replacementCount: changes.length,
576
+ revision: await createEditableContentRevision(nextContent),
577
+ };
578
+ });
579
+ }
580
+
581
+ async createExcalidraw(actor, { elements, path, verify = null } = {}) {
582
+ requireScope(actor, 'vault:edit');
583
+ const normalizedPath = normalizeWorkspacePath(path);
584
+ if (getVaultFileKind(normalizedPath) !== 'excalidraw') {
585
+ throw createAgentContentError('AGENT_UNSUPPORTED_DOCUMENT', 'Excalidraw tools require an .excalidraw path', 400);
586
+ }
587
+ if (verify !== null) requireInlineExcalidrawVerificationOptions(verify);
588
+ let scene;
589
+ try {
590
+ scene = createAgentExcalidrawScene(elements);
591
+ } catch (error) {
592
+ if (error?.code === 'EXCALIDRAW_SCENE_INVALID') {
593
+ throw createAgentContentError('AGENT_INVALID_EXCALIDRAW', error.message, 400);
594
+ }
595
+ throw error;
596
+ }
597
+ let content = JSON.stringify(scene);
598
+ if (content.length > MAX_DOCUMENT_CHARACTERS) {
599
+ throw createAgentContentError('AGENT_DOCUMENT_TOO_LARGE', 'Document is too large for agent creation', 413);
600
+ }
601
+ return this.runForPath(normalizedPath, async () => {
602
+ const result = await this.workspaceMutationCoordinator.createFile({
603
+ content,
604
+ origin: getMutationOrigin(actor),
605
+ path: normalizedPath,
606
+ requestId: actor.requestId,
607
+ sourceRef: createAgentSourceRef(actor),
608
+ });
609
+ if (!result.ok) {
610
+ const rawById = new Map(elements.map((element) => [String(element.id), element]));
611
+ const existingContent = await this.readMatchingContent(normalizedPath, (candidate) => {
612
+ const existingScene = JSON.parse(candidate);
613
+ const existingById = new Map(existingScene.elements?.map((element) => [element.id, element]) ?? []);
614
+ const comparableScene = {
615
+ ...scene,
616
+ elements: scene.elements.map((element) => (
617
+ Object.hasOwn(rawById.get(element.id) ?? {}, 'updated')
618
+ ? element
619
+ : { ...element, updated: existingById.get(element.id)?.updated }
620
+ )),
621
+ };
622
+ return JSON.stringify(existingScene) === JSON.stringify(comparableScene);
623
+ });
624
+ if (existingContent === null) {
625
+ throw createAgentContentError('AGENT_CREATE_FAILED', result.error, 409);
626
+ }
627
+ scene = JSON.parse(existingContent);
628
+ content = existingContent;
629
+ }
630
+ const response = {
631
+ elementCount: scene.elements.length,
632
+ path: normalizedPath,
633
+ revision: await createEditableContentRevision(content),
634
+ };
635
+ if (verify !== null) {
636
+ response.verification = createInlineExcalidrawVerification(scene, verify, {
637
+ deferRender: actor.origin === 'webmcp',
638
+ });
639
+ }
640
+ return response;
641
+ });
642
+ }
643
+
644
+ async editExcalidraw(actor, {
645
+ create = [],
646
+ delete: remove = [],
647
+ path,
648
+ reorder = [],
649
+ replace = [],
650
+ revision,
651
+ translate = null,
652
+ update = [],
653
+ verify = null,
654
+ } = {}) {
655
+ requireScope(actor, 'vault:edit');
656
+ const normalizedPath = normalizeWorkspacePath(path);
657
+ if (getVaultFileKind(normalizedPath) !== 'excalidraw') {
658
+ throw createAgentContentError('AGENT_UNSUPPORTED_DOCUMENT', 'Excalidraw tools require an .excalidraw path', 400);
659
+ }
660
+ if (verify !== null) requireInlineExcalidrawVerificationOptions(verify);
661
+ return this.runForPath(normalizedPath, async () => {
662
+ const content = await this.readCurrentContent(normalizedPath);
663
+ if (content.length > MAX_DOCUMENT_CHARACTERS) {
664
+ throw createAgentContentError('AGENT_DOCUMENT_TOO_LARGE', 'Document is too large for agent editing', 413);
665
+ }
666
+ if (revision !== await createEditableContentRevision(content)) {
667
+ throw createAgentContentError('AGENT_REVISION_CONFLICT', 'Document changed; read it again before editing', 409);
668
+ }
669
+ let scene;
670
+ try {
671
+ scene = applyAgentExcalidrawEdits(JSON.parse(content), {
672
+ create,
673
+ delete: remove,
674
+ reorder,
675
+ replace,
676
+ update,
677
+ translate,
678
+ });
679
+ } catch (error) {
680
+ if (error instanceof SyntaxError) {
681
+ throw createAgentContentError('AGENT_INVALID_EXCALIDRAW', 'Document is not valid Excalidraw JSON', 400);
682
+ }
683
+ if (error?.code === 'EXCALIDRAW_SCENE_INVALID') {
684
+ throw createAgentContentError('AGENT_INVALID_EXCALIDRAW', error.message, 400);
685
+ }
686
+ throw error;
687
+ }
688
+ const room = this.roomRegistry?.get?.(normalizedPath);
689
+ let nextContent;
690
+ if (room) {
691
+ room.applyExcalidrawScene(scene, {
692
+ origin: createCollaborationOrigin(actor),
693
+ });
694
+ nextContent = room.getPersistedContent();
695
+ } else {
696
+ const storedScene = {
697
+ ...scene,
698
+ elements: scene.elements.filter((element) => !element.isDeleted),
699
+ };
700
+ nextContent = JSON.stringify(storedScene);
701
+ const result = await this.workspaceMutationCoordinator.writeEditableContent({
702
+ content: nextContent,
703
+ origin: getMutationOrigin(actor),
704
+ path: normalizedPath,
705
+ requestId: actor.requestId,
706
+ sourceRef: createAgentSourceRef(actor),
707
+ });
708
+ if (!result.ok) throw createAgentContentError('AGENT_WRITE_FAILED', result.error, 400);
709
+ }
710
+ const storedScene = JSON.parse(nextContent);
711
+ const result = {
712
+ created: create.length,
713
+ deleted: remove.length,
714
+ elementCount: storedScene.elements.filter((element) => !element.isDeleted).length,
715
+ path: normalizedPath,
716
+ reordered: reorder.length,
717
+ replaced: replace.length,
718
+ revision: await createEditableContentRevision(nextContent),
719
+ translated: translate?.ids?.length ?? 0,
720
+ updated: update.length,
721
+ };
722
+ if (verify !== null) {
723
+ result.verification = createInlineExcalidrawVerification(storedScene, verify, {
724
+ deferRender: actor.origin === 'webmcp',
725
+ });
726
+ }
727
+ return result;
728
+ });
729
+ }
730
+
731
+ async createDocument(actor, { content = '', path } = {}) {
732
+ requireScope(actor, 'vault:edit');
733
+ const normalizedPath = normalizeWorkspacePath(path);
734
+ if (!isAgentCreatablePath(normalizedPath)) {
735
+ throw createAgentContentError('AGENT_UNSUPPORTED_DOCUMENT', 'Document type cannot be created by agents', 400);
736
+ }
737
+ const normalizedContent = normalizeEditableText(content);
738
+ if (normalizedContent.length > MAX_DOCUMENT_CHARACTERS) {
739
+ throw createAgentContentError('AGENT_DOCUMENT_TOO_LARGE', 'Document is too large for agent creation', 413);
740
+ }
741
+ return this.runForPath(normalizedPath, async () => {
742
+ const result = await this.workspaceMutationCoordinator.createFile({
743
+ content: normalizedContent,
744
+ origin: getMutationOrigin(actor),
745
+ path: normalizedPath,
746
+ requestId: actor.requestId,
747
+ sourceRef: createAgentSourceRef(actor),
748
+ });
749
+ if (!result.ok) {
750
+ const existingContent = await this.readMatchingContent(
751
+ normalizedPath,
752
+ (candidate) => candidate === normalizedContent,
753
+ );
754
+ if (existingContent === null) {
755
+ throw createAgentContentError('AGENT_CREATE_FAILED', result.error, 409);
756
+ }
757
+ }
758
+ return {
759
+ kind: getVaultFileKind(normalizedPath),
760
+ path: normalizedPath,
761
+ revision: await createEditableContentRevision(normalizedContent),
762
+ };
763
+ });
764
+ }
765
+
766
+ getSyntax(actor, { kind = '' } = {}) {
767
+ requireScope(actor, 'vault:read');
768
+ if (!kind) return { capabilities: listCollabMdContentCapabilities() };
769
+ const syntax = getCollabMdSyntaxGuide(kind);
770
+ if (!syntax) throw createAgentContentError('AGENT_UNSUPPORTED_DOCUMENT', 'Unknown CollabMD content kind', 400);
771
+ return syntax;
772
+ }
773
+ }